Skip to content

feat: cookieless web analytics (script, ingest, sessions, GeoIP) - #9

Merged
theboringhumane merged 18 commits into
mainfrom
feat/web-analytics
Nov 11, 2026
Merged

theboringhumane merged 18 commits into
mainfrom
feat/web-analytics

Conversation

@theboringhumane

Copy link
Copy Markdown
Contributor

Plausible-style web analytics on top of Lumen events.

  • /js/script.js: tiny tracker; pageviews, SPA navigation, outbound/file clicks and custom events.
  • Website keys (kind='web', allowed_origins): Origin must match, Track only, $pageview or non-$ events.
  • Cookieless visitor id: w1_ + sha256(daily salt, team, host, IP, UA); salts rotate daily (today + yesterday kept in lumen_web_salts); the IP is never stored.
  • 30-minute sessions per team (LRU), continued across midnight; URLs kept to path, utm_* and hash routes.
  • Rate limit 50/s per key+IP (IPv6 by /64); TRUSTED_PROXY_HOPS (default 1).
  • ClickHouse: web_pageviews, web_sessions, referrer_sources, fixed sessions_v; persons views skip w1_ ids.
  • GeoIP: GEOIP_SOURCE=dbip-lite downloads DB-IP City Lite into GEOIP_CACHE_DIR.
  • RotateKey now rotates only the server key, atomically, and leaves website keys alone.

Tests: go test -race ./... and the Postgres live tests pass.

Web keys accept Track/TrackStream only from allowed origins. The server
derives anon_id from a daily shared salt, assigns sessions after 30 min
idle, never stores the IP, and rate limits per key and client IP.
CreateKey validates kind/allowed_origins and stores them; CreateKey and
ListKeys return them. Client IP is now the X-Forwarded-For entry appended
by the outermost trusted proxy, not the client-controlled leftmost one.
Cache today's and yesterday's salt; when today's visitor id has no live
session, adopt one found under yesterday's id. Stored anon_id stays today's.
Mount GET /js/script.js on the ingest port and start the DB-IP City Lite
downloader when GEOIP_SOURCE=dbip-lite.
Web keys reject reserved $-events except $pageview, store minimised
URLs/referrers, keep sessions per team, and rate limit IPv6 by /64.
RotateKey now rotates server keys only. Document TRUSTED_PROXY_HOPS.
RotateKey revoked server keys one by one before saving the new key, so a
database error part-way could leave a team without an active key. Revoke
and insert now run in one statement. README shows the new GeoIP cache default.
@theboringhumane
theboringhumane merged commit e72925f into main Nov 11, 2026
4 of 6 checks passed
@theboringhumane
theboringhumane deleted the feat/web-analytics branch November 11, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant