From 34c2fa22e5b11f7832e5b5ad870cacd704d192b8 Mon Sep 17 00:00:00 2001 From: CRT Date: Wed, 14 Oct 2026 11:11:13 +0800 Subject: [PATCH] site: first-party, cookieless visit stats (self-hosted Umami at storebound.dev/s/, Do Not Track respected) with events for copy buttons, desktop downloads per OS, the teaser (play, 50 %, complete) and outbound docs / git / npm links; a privacy note in the footer; docs/site-analytics.md and tools/vds/ configs to rebuild the server side; scrub allows a page's public stats website id --- docs/site-analytics.md | 57 ++++++++++++++++++++++++++ site/desktop/index.html | 1 + site/index.html | 5 ++- site/main.js | 24 +++++++++++ tools/scrub.ts | 3 +- tools/vds/apache-stats.crt.fyi.conf | 26 ++++++++++++ tools/vds/apache-storebound-stats.conf | 16 ++++++++ 7 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 docs/site-analytics.md create mode 100644 tools/vds/apache-stats.crt.fyi.conf create mode 100644 tools/vds/apache-storebound-stats.conf diff --git a/docs/site-analytics.md b/docs/site-analytics.md new file mode 100644 index 0000000..9c1e999 --- /dev/null +++ b/docs/site-analytics.md @@ -0,0 +1,57 @@ +# storebound.dev site analytics + +The site counts visits with a self-hosted [Umami](https://umami.is) on the VDS that serves storebound.dev. Cookieless, +no personal data, Do Not Track respected, nothing sent to a third party. This page is the rebuild recipe (no secrets). + +## What runs where + +| Piece | Where | +|---|---| +| Umami 3.4.0 + Postgres 15.14 (Docker Compose, `restart: unless-stopped`) | `/opt/umami` on the VDS, `127.0.0.1:3920` only | +| Secrets (DB password, `APP_SECRET`, `TWO_FACTOR_ENCRYPTION_KEY`) | `/opt/umami/.env`, mode 600, generated on the server | +| Script and collect endpoint, first-party | `https://storebound.dev/s/p.js`, `https://storebound.dev/s/e` (`/etc/apache2/storebound-stats.conf`, from `tools/vds/apache-storebound-stats.conf`, included in the storebound.dev :443 vhost) | +| Dashboard | `https://stats.crt.fyi` (`tools/vds/apache-stats.crt.fyi.conf`; `*.crt.fyi` wildcard DNS through Cloudflare and the `crt.fyi` wildcard cert) | +| Admin login | the owner's Mac, `~/.storebound-keys/umami.json` (0600) | + +URLs never contain "umami", "analytics", "track" or "collect" (`TRACKER_SCRIPT_NAME=p.js`, `COLLECT_API_ENDPOINT=/e`), so +common filter lists do not block them. + +**Client IP.** Cloudflare sits in front. Apache's `mod_remoteip` takes `CF-Connecting-IP` only from Cloudflare's ranges +and passes the result as `X-Sb-Client`, which Umami reads (`CLIENT_IP_HEADER=x-sb-client`). A client cannot spoof it by +sending the header to the origin directly. The collect endpoint answers `Cache-Control: no-store`. + +## Rebuild + +1. `/opt/umami/docker-compose.yml`: Umami's upstream compose with the image pinned to `ghcr.io/umami-software/umami:3.4.0`, + port `127.0.0.1:3920:3000`, `env_file: .env`, and `TRACKER_SCRIPT_NAME: p.js`, `COLLECT_API_ENDPOINT: /e`, + `CLIENT_IP_HEADER: x-sb-client`, `DISABLE_TELEMETRY: "1"`, `DISABLE_UPDATES: "1"`; Postgres `postgres:15.14-alpine` with a + named volume, both `restart: unless-stopped`. +2. `.env` (umask 077): `POSTGRES_PASSWORD`, `DATABASE_URL=postgresql://umami:@db:5432/umami`, `APP_SECRET` and + `TWO_FACTOR_ENCRYPTION_KEY` from `openssl rand -hex`. `docker compose up -d`, wait for `/api/heartbeat`. +3. Log in as `admin` / `umami`, change the password at once (`POST /api/me/password`) to a generated one and store it in + `~/.storebound-keys/umami.json` on the Mac. Create the website `storebound.dev`; its id goes into the script tag. +4. Apache (back up every file first, `cp -a /root/.bak-`). The Cloudflare ranges live only on + the server, in `/etc/apache2/cloudflare-trusted-proxies.conf`; refresh them now and then: + ```sh + ssh root@ 'for u in ips-v4 ips-v6; do curl -fs https://www.cloudflare.com/$u | sed "s/^/RemoteIPTrustedProxy /"; echo; done | grep . > /etc/apache2/cloudflare-trusted-proxies.conf' + scp tools/vds/apache-storebound-stats.conf root@:/etc/apache2/storebound-stats.conf + scp tools/vds/apache-stats.crt.fyi.conf root@:/etc/apache2/sites-available/stats.crt.fyi.conf + # in the storebound.dev :443 vhost, after the desktop include: Include /etc/apache2/storebound-stats.conf + ssh root@ 'a2ensite stats.crt.fyi && apache2ctl configtest && systemctl reload apache2' + ``` +5. Site: `site/index.html` loads `https://storebound.dev/s/p.js` with `defer`, `data-host-url="https://storebound.dev/s"`, + `data-domains="storebound.dev"` (localhost and previews send nothing) and `data-do-not-track="true"`. + +## Events (site/main.js) + +| Event | Data | +|---|---| +| `copy` | `what`: the copied command or the copied block's id (every Copy button) | +| `desktop-download` | `os` (macos, windows, linux), `file` | +| `teaser-play`, `teaser-50`, `teaser-complete` | once per page view | +| `outbound` | `kind` (docs, git, npm, other), `host`, `path` | + +## Upgrade + +Read Umami's release notes, change the pinned tag, `docker compose pull && docker compose up -d` in `/opt/umami` +(take `docker compose exec db pg_dump -U umami umami > /root/umami-.sql` first). diff --git a/site/desktop/index.html b/site/desktop/index.html index 6955c49..cf3e461 100644 --- a/site/desktop/index.html +++ b/site/desktop/index.html @@ -9,6 +9,7 @@ + diff --git a/site/index.html b/site/index.html index 1489455..e084c72 100644 --- a/site/index.html +++ b/site/index.html @@ -27,7 +27,8 @@ - + + @@ -887,7 +888,7 @@ com.example.app.gems_100 create 174 regions, 2 listings, USD 0.99 storebound logo storebound -

MIT · made by CRTkafa · not affiliated with Google or Apple.
App Store is a trademark of Apple Inc.; Google Play is a trademark of Google LLC.

+

MIT · made by CRTkafa · not affiliated with Google or Apple.
App Store is a trademark of Apple Inc.; Google Play is a trademark of Google LLC.
Privacy: this site counts visits with self-hosted Umami. No cookies, no personal data, nothing shared with third parties, and Do Not Track is respected.