Skip to content
chuchi-devPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

19 Commits

Folders and files

Repository files navigation

strichli

Cookieless analytics for our sites: visitors, pageviews, pages, referrers, devices, browsers, operating systems and custom keys. No cookies, nothing stored in the browser, no ip or user agent in the database (only the kind of device, the browser and the os taken from it), so no consent banner is needed.

  • server/ the axum server: the tracking script (/s.js), the collect endpoint (/e), the api under /api and the built webui
  • webui/ Svelte 5 + chuchi dashboard

Tracking

<script async src="https://strichli.goodserver.ch/s.js"></script>

Pageviews are sent on load and on every history.pushState (single page apps). Keys:

strichli('lookup_domain', { domain: 'example.com' });

sa_event (Simple Analytics) is defined too, so existing calls keep working. strichli also answers Simple Analytics' paths /latest.js and /noscript.gif (a pageview without javascript, from the referer, never a visitor), so a custom Simple Analytics domain can point to strichli without changing the sites. Keys are [A-Za-z0-9_.-], up to 64 characters, data is a flat object of strings, numbers and booleans (up to 1 KB).

A pageview counts as a visitor when it starts a visit: the referrer is not the site itself, and it is no reload, back/forward or single page app navigation (the method Simple Analytics uses). Bots are filtered by user agent and navigator.webdriver.

API

Every user creates api tokens in the webui (API tokens); a token acts as its user on the sites routes, send it as authorization: Bearer <token>. A token reads the stats of the sites its user sees (e.g. a client's spreadsheet). An admin can also create a token that writes: it adds and changes sites, a read token gets READ_ONLY there.

  • GET /api/sites sites with the last 30 days
  • POST /api/sites { "domain", "name" } adds a site, the response has the snippet
  • PUT /api/sites/{id} { "domain", "name" }
  • GET /api/sites/{id}/stats?range=7d or ?from=YYYY-MM-DD&to=YYYY-MM-DD: presets today, 7d, 30d, 90d, 12m count back from today in the configured timezone, at most 5 years; the answer has the range it shows, visitors by day or hour, pages, referrers, keys, and devices, browsers, oses (visitors by name, null for visits from before 2026-11-07 and imported ones)
  • GET /api/sites/{id}/events?range=…&fields=domain,plan[&key=…] the events grouped by key and the values of up to 5 data fields, with the fields there are

Deleting a site needs a webui session.

Import from Simple Analytics

POST /api/sites/{id}/import/simpleanalytics (admins) takes the ndjson of Simple Analytics' export api and replaces an earlier import of the site. Only data points from before strichli's own first event are taken, so a parallel run doesn't count twice. Robots are left out, event data is requested per field as metadata.<field>_text|_int|_bool:

curl -s --get -H "Api-Key: $SA_KEY" \
	--data-urlencode hostname=soerenmeier.ch \
	--data fields=added_iso,path,is_unique,is_robot,referrer_hostname,datapoint,metadata.name_text \
	'https://simpleanalytics.com/api/export/datapoints?version=6&format=ndjson&type=all&start=2015-01-01&end=2030-01-01' \
	| curl -s -X POST https://strichli.goodserver.ch/api/sites/$SITE/import/simpleanalytics \
		-H "authorization: Bearer $TOKEN" -H 'content-type: text/plain' --data-binary @-

Development

Requires rust, node and a postgres.

cd webui
npm ci
npm run dev

# in another shell, with a config.toml in server/
cd server
cargo run
# prints a link to set the password, `password-link --email` makes a new one
cargo run -- add-user --email you@example.com --name You --admin

In debug builds ../webui/dist is served. The vite dev server passes /api on to http://127.0.0.1:8080.

Formatting: cargo fmt, npm run format, checks: cargo clippy, cargo test, npm run check.

Config

# with protocol and trailing slash
public-addr = "https://strichli.goodserver.ch/"
# days and hours of the stats, the default
timezone = "Europe/Zurich"
# origins that may embed the webui in a frame, none by default
frame-ancestors = []

[database]
# a host or the directory of the unix socket
host = "/var/run/postgresql"
name = "strichli"
user = "strichli"
password = ""

The server listens on HOST:PORT (default 127.0.0.1:8080) and takes the client ip for the rate limits from the last x-forwarded-for entry (an IPv6 client counts as its /64), so it must only be reachable through the proxy.

Privacy and security

  • No cookies, nothing in the browser's storage. The database has no ip, user agent or visitor id; the ip only counts against in-memory limits. Of the user agent only the device (mobile, tablet, desktop), the browser family and the os family are kept, the script adds whether there is a touch screen (tells an iPad from a Mac).
  • The proxy's access log still sees ip, user agent and referer of /e, /s.js and /noscript.gif; turn it off for them if that matters.
  • Session tokens, api tokens and password links are 32 random bytes, only their sha256 is stored. Sessions end 30 days after the last use, at the latest a year after the login.
  • Logins are limited per client and, after 10 failures in 15 minutes, per email. Events are limited per client and per site.
  • Anyone can send events for a site (that is how the tracking works), the limits and the size checks keep it bounded.

License

MIT or Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages