|
Some checks failed
[Workflow] On Source Change / Check Worker configuration (push) Successful in 29s
[Workflow] On Source Change / Deploy preview (push) Has been skipped
[Workflow] On Source Change / Deploy production (push) Failing after 3m43s
[Workflow] On Source Change / Deploy Production (push) Failing after 0s
Het preset gaat zelf van een gepinde verwijzing uit: default.json draagt een soak-uitzondering voor preset-updates (die alleen bestaat als er een versie te bumpen valt) en een pinDigests: false waarvan de beschrijving zegt dat de verwijzing 'is already pinned to a protected semver release tag'. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| CODEOWNERS | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| renovate.json | ||
| wrangler.toml | ||
counterscale.webgrip.dev
Counterscale is the self-hosted analytics collector
for Webgrip's static sites. This repository deploys it. There is no application
code here on purpose: @counterscale/server ships the whole Worker, and what
lives here is the configuration that is ours.
What reports into it
| Site | Reports as | How |
|---|---|---|
| twente.dev | twente-dev |
@webgrip/edge-analytics from its Worker |
| webgrip.nl | webgrip-nl |
the same package |
Reporting happens server-side, inside the request that serves the page, so there is no script in the page and nothing for a blocker to stop. The reasoning is in twente.dev's ADR 0013.
Why not npx @counterscale/cli
The upstream installer is interactive: it asks questions, writes a
wrangler.json and shells out to wrangler deploy after a wrangler login.
That puts the Cloudflare token on somebody's laptop and the deployed
configuration nowhere.
Forgejo is the sole release authority here (twente.dev ADR 0003), so the config
is checked in and the shared cloudflare-deploy.yml does the deploy with the
token from the secret store. The installer remains a perfectly good way to try
Counterscale; it is not how this one ships.
Retention
Analytics Engine keeps ninety days. Counterscale's nightly cron rolls each day
into the counterscale-daily-rollups R2 bucket before that window closes, which
is what makes a year of editions comparable rather than a rolling quarter.
That rollup is a cron that can stop without anyone noticing until the data is already gone. It deserves an alert, not trust.
Bindings and secrets
Set in wrangler.toml:
| Binding | Resource |
|---|---|
WEB_COUNTER_AE |
Analytics Engine dataset metricsDataset |
DAILY_ROLLUPS |
R2 bucket counterscale-daily-rollups |
ASSETS |
Counterscale's own dashboard, out of the installed package |
Set as Worker runtime secrets, never in this repo and never by hand:
| Secret | What it is for | Where the value comes from |
|---|---|---|
CF_BEARER_TOKEN |
Counterscale queries the Analytics Engine SQL API itself | OpenBao secret/counterscale/worker, seeded once by a human |
CF_ACCOUNT_ID |
same | OpenBao secret/cloudflare/deploy, the account id's single record |
CF_JWT_SECRET |
dashboard session signing | generated in-cluster, never typed by anyone |
CF_PASSWORD_HASH |
dashboard login (bcrypt) | OpenBao secret/counterscale/worker, seeded once by a human |
They reach the Worker through GitOps, not through a laptop. In homelab-cluster,
kubernetes/apps/forgejo/forgejo-actions-secrets/app/ holds two ExternalSecrets
and an hourly reconciler:
counterscale-workerpulls the account id and the two human-seeded values out of OpenBao.counterscale-jwtmintsCF_JWT_SECRETfrom the cluster's password generator and retains it. Regenerating it invalidates every live dashboard session, so it is generate-once by design.counterscale-worker-secretsis a CronJob that provesCF_BEARER_TOKENcan actually answer a query on the Analytics Engine SQL API, and only then PUTs all four onto this Worker over Cloudflare's API. A credential that has gone dead fails that job, on the hour, instead of surfacing as a blank dashboard weeks later. It logs secret names and outcomes, never values.
CF_BEARER_TOKEN is a second Cloudflare token, not the deploy one: it needs
Account Analytics:Read and nothing else. The deploy token
(CLOUDFLARE_API_TOKEN, Workers Scripts:Edit) and CLOUDFLARE_ACCOUNT_ID live
in the Forgejo secret store, are used by the deploy rather than by the Worker,
and are what the reconciler uses as its transport.
The one human step is seeding the two values that originate outside the cluster:
bao kv put secret/counterscale/worker \
CF_BEARER_TOKEN=<cloudflare token with Account Analytics:Read> \
CF_PASSWORD_HASH=<bcrypt hash of the dashboard password>
Everything after that converges on its own within the hour.
Before the first deploy
- The R2 bucket
counterscale-daily-rollupsis managed in webgrip/cloudflare ascloudflare_r2_bucket.counterscale_daily_rollups; it exists before this Worker's first deploy, and its absence would show in that repo's nightly drift run. - Seed
secret/counterscale/workerin OpenBao with thebao kv putabove. The other two Worker secrets need nobody:CF_ACCOUNT_IDis already in OpenBao andCF_JWT_SECRETis generated in-cluster. The reconciler publishes all four on its next tick. - Point
counterscale.webgrip.devat Cloudflare so the route can bind.
workers_dev = false with a route that cannot bind is a green deploy and a dead
hostname. The deploy workflow probes the apex afterwards for exactly that reason.
Working on it
pnpm install
pnpm run check # bundles the Worker and prints every binding, touches nothing
pnpm run deploy # only if you know why you are not letting CI do it
pnpm run tail # live logs