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
This commit is contained in:
parent
83c14d3a8e
commit
34c2fa22e5
7 changed files with 129 additions and 3 deletions
57
docs/site-analytics.md
Normal file
57
docs/site-analytics.md
Normal file
|
|
@ -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:<pw>@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 <file> /root/<name>.bak-<timestamp>`). The Cloudflare ranges live only on
|
||||
the server, in `/etc/apache2/cloudflare-trusted-proxies.conf`; refresh them now and then:
|
||||
```sh
|
||||
ssh root@<server> '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@<server>:/etc/apache2/storebound-stats.conf
|
||||
scp tools/vds/apache-stats.crt.fyi.conf root@<server>:/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@<server> '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-<date>.sql` first).
|
||||
Loading…
Add table
Add a link
Reference in a new issue