From 0d27ab666fc61c3ca6c28ec3591d9378356a19d6 Mon Sep 17 00:00:00 2001 From: Willie Chalmers III Date: Wed, 23 Sep 2026 21:49:44 -0700 Subject: [PATCH] docs(docs): Clarify Cloudflare analytics setup Document account-owned deploy and report tokens, a safe custom-domain check, and Web Analytics token setup for each hostname family. Co-authored-by: Codex --- .../tutorials/add-analytics-to-a-new-site.md | 26 +++++++++++ .../operations/how-to/rotate-the-cwa-token.md | 30 +++++++++---- docs/operations/how-to/set-up-production.md | 43 ++++++++++++------- 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/docs/development/tutorials/add-analytics-to-a-new-site.md b/docs/development/tutorials/add-analytics-to-a-new-site.md index 60fe24b..81f2e28 100644 --- a/docs/development/tutorials/add-analytics-to-a-new-site.md +++ b/docs/development/tutorials/add-analytics-to-a-new-site.md @@ -14,6 +14,32 @@ pnpm add @lasvegasfortransit/analytics Use the production hostname as `site`. Do not invent a product ID or include `https://`. +## Get a Cloudflare Web Analytics token + +Check first: a `lasvegasfortransit.org` subdomain (the root domain, `labs.`, `fund.`, or `map.`) +shares the organization's existing Web Analytics property and its token, the GitHub organization +variable `PUBLIC_LVBT_CWA_TOKEN`. Confirm it already exists — +`gh variable list --org LasVegasForTransit` — and skip to +[Configure production](#configure-production). Never create a second Web Analytics property for a +`lasvegasfortransit.org` subdomain; one property already covers the whole domain. + +A site on any other domain (for example `lvwwd.org`) needs its own property and its own token, +because a Cloudflare Web Analytics token is scoped to one hostname family: + +1. Open the account's Web Analytics page ( + with the LVBT account) and click "Add a site". If the hostname is already listed, open it instead + and skip to step 3. +2. Choose the new site's hostname and click "Done". Cloudflare defaults every new site to automatic + setup, which injects the beacon itself; open "Manage site" and change it to "Enable with JS + Snippet installation" instead, because this repository's own client loads the beacon — automatic + injection would load it twice. If the hostname is not proxied through Cloudflare (not + orange-clouded), Cloudflare only offers the JS snippet option, so there is nothing to change. +3. On "Manage site", copy only the token inside `data-cf-beacon='{"token": "..."}'` in the shown + snippet (32 lowercase letters and digits). It is public by design, so it is a GitHub environment + variable, not a secret: in the consuming repository, Settings → Environments → `production` → + Environment variables → "Add environment variable", name `PUBLIC_LVBT_CWA_TOKEN`, value the token + you copied. + For Astro, add the integration to `astro.config.ts`: ```ts diff --git a/docs/operations/how-to/rotate-the-cwa-token.md b/docs/operations/how-to/rotate-the-cwa-token.md index dbfb719..c53f02a 100644 --- a/docs/operations/how-to/rotate-the-cwa-token.md +++ b/docs/operations/how-to/rotate-the-cwa-token.md @@ -1,14 +1,28 @@ # Rotate the Web Analytics token -Token rotation changes the public site token without enabling analytics in previews. +Token rotation changes a site's public token without enabling analytics in previews. Cloudflare's +documented site controls do not include an in-place "regenerate token" action. Keep the current +property active until a replacement property and token are confirmed; do not delete it first. -1. Create the replacement manual Web Analytics site token for `lasvegasfortransit.org` in - Cloudflare. Automatic injection stays disabled. -2. Replace the GitHub organization variable `PUBLIC_LVBT_CWA_TOKEN`. -3. Run each production deployment through its normal validated `main` workflow. -4. Verify each hostname with `lvbt-analytics verify --expect present`. -5. Confirm new pageviews in the Cloudflare host breakdown. -6. Remove the previous Web Analytics site after every production hostname reports through the new +1. Open the account's Web Analytics page ( + with the LVBT account). Check that the existing property still reports pageviews. Click "Add a + site" and try the same hostname — `lasvegasfortransit.org` for the shared organization property, + or the site's own domain such as `lvwwd.org`. If Cloudflare does not permit another property for + that hostname, stop and keep the existing property and token in place. Arrange a replacement path + with Cloudflare before continuing. +2. If the replacement property was created, open "Manage site" on it and change automatic setup to + "Enable with JS Snippet installation" (automatic injection stays disabled, same as the original + site). Copy only the token inside `data-cf-beacon='{"token": "..."}'` (32 lowercase letters and + digits): this is the replacement token. +3. Replace the value that held the old token: the shared property's token is the GitHub organization + variable `PUBLIC_LVBT_CWA_TOKEN` + (`gh variable set PUBLIC_LVBT_CWA_TOKEN --org LasVegasForTransit`); a site with its own property, + such as `lvwwd.org`, keeps its token in that repository's `production` environment variable of + the same name (`gh variable set PUBLIC_LVBT_CWA_TOKEN --env production`, run in that repository). +4. Run each production deployment through its normal validated `main` workflow. +5. Verify each hostname with `lvbt-analytics verify --expect present`. +6. Confirm new pageviews in the Cloudflare host breakdown. +7. Remove the previous Web Analytics site after every production hostname reports through the new token. Preview workflows remain unchanged and continue to prove absence. diff --git a/docs/operations/how-to/set-up-production.md b/docs/operations/how-to/set-up-production.md index 8b4752e..bab73b3 100644 --- a/docs/operations/how-to/set-up-production.md +++ b/docs/operations/how-to/set-up-production.md @@ -7,7 +7,7 @@ checking whether it is already done, so you can follow the guide again at any ti done is skipped, and nothing is replaced unless a step says so. Replacing a value on purpose is covered in [Rotate the collector secret](rotate-the-events-secret.md). -Once this repository moves to repository tooling 0.4.2 or later, a `platform.json` and +Once this repository moves to repository tooling 0.4.0 or later, a `platform.json` and `pnpm bootstrap --production` will check and set up most of this for you, and this guide will point to them. @@ -58,20 +58,29 @@ collector Worker and attach its custom domain. If `gh secret list --env producti It is an account API token, so deploys keep working after the person who made it leaves. Wrangler accepts it because the workflow also sets `CLOUDFLARE_ACCOUNT_ID`. -1. In the Cloudflare dashboard, choose "Las Vegans for Better Transit" and go to Manage Account, - then "Account API Tokens" - (). Click "Create - Token", then "Create Custom Token". +1. In the Cloudflare dashboard, choose the LVBT account and check that its name is "Las Vegans for + Better Transit". If it still says "Las Vegas for Better Transit", correct the name under the + account's settings before continuing. Go to Manage Account, then "Account API Tokens" + (). This is the + account's own token list, not your personal one under your profile, so the token keeps working + after anyone leaves. Click "Create Token", then "Create Custom Token". 2. Name it `analytics collector deploy (GitHub Actions)`. 3. Under "Permissions", add these rows: "Account", "Workers Scripts", "Edit" (uploads the Worker and attaches its custom domain); "Account", "Account Settings", "Read"; "Zone", "Zone", "Read" (finds the zone for the custom domain); and "Zone", "Workers Routes", "Edit". -4. Under "Zone Resources", choose "Include", then "Specific zone", then `lasvegasfortransit.org`. -5. Leave the expiration empty, so deploys keep working. Click "Continue to summary", then "Create +4. Under "Account Resources", choose "Include", then the LVBT account by name — never "All + accounts", which would let this token touch every Cloudflare account you can reach. +5. Under "Zone Resources", choose "Include", then "Specific zone", then `lasvegasfortransit.org` — + never "All zones", so a leaked token cannot touch any other LVBT domain. +6. Leave the expiration empty, so deploys keep working. Click "Continue to summary", then "Create Token". -6. Run `gh secret set CLOUDFLARE_API_TOKEN --env production` in a terminal and leave it waiting at +7. Run `gh secret set CLOUDFLARE_API_TOKEN --env production` in a terminal and leave it waiting at its prompt. -7. Copy the token (Cloudflare shows it only once), paste it at that prompt, and press Enter. +8. Copy the token (Cloudflare shows it only once), paste it at that prompt, and press Enter. + +If a deploy fails on the custom domain step with a permissions error, check that the token has the +Workers Scripts and Workers Routes permissions above for the correct account and zone. Do not add +broader permissions without identifying the failed API call. ## 4. Create the report token @@ -81,10 +90,13 @@ The weekly report reads the collector's Analytics Engine data through Cloudflare 1. On the same "Account API Tokens" page, click "Create Token", then "Create Custom Token". 2. Name it `analytics weekly report (GitHub Actions)`. -3. Under "Permissions", add one row: "Account", "Account Analytics", "Read". Add nothing else. -4. Leave the expiration empty, click "Continue to summary", then "Create Token". -5. Run `gh secret set CLOUDFLARE_ANALYTICS_READ_TOKEN` and leave it waiting at its prompt. -6. Copy the token, paste it at that prompt, and press Enter. +3. Under "Permissions", add one row: "Account", "Account Analytics", "Read". Add nothing else — this + token can read analytics and nothing about the account, so a leak exposes only aggregate numbers. +4. Under "Account Resources", choose "Include", then the LVBT account by name — never "All + accounts". +5. Leave the expiration empty, click "Continue to summary", then "Create Token". +6. Run `gh secret set CLOUDFLARE_ANALYTICS_READ_TOKEN` and leave it waiting at its prompt. +7. Copy the token, paste it at that prompt, and press Enter. ## 5. Deploy the collector and its custom domain @@ -93,8 +105,9 @@ domain. Deploying creates the domain's DNS record and certificate, and redeployi Worker changes nothing. 1. Check: open . If it answers, go to section 6. -2. If the Cloudflare dashboard's DNS records for `lasvegasfortransit.org` already have a CNAME named - `events`, delete it first: a custom domain cannot replace an existing CNAME. +2. If the Cloudflare dashboard's DNS records for `lasvegasfortransit.org` already have a record + named `events`, check who owns it and whether it serves traffic. Stop and resolve that conflict + before deploying; do not delete an existing record just to make the custom domain creation pass. 3. In the repository's Actions tab, open "Deploy collector" and click "Run workflow" on `main`. It runs `pnpm check`, deploys, and then checks `/health`.