Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/development/tutorials/add-analytics-to-a-new-site.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (<https://dash.cloudflare.com/?to=/:account/web-analytics>
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
Expand Down
30 changes: 22 additions & 8 deletions docs/operations/how-to/rotate-the-cwa-token.md
Original file line number Diff line number Diff line change
@@ -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 <url> --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 (<https://dash.cloudflare.com/?to=/:account/web-analytics>
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 <url> --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.
43 changes: 28 additions & 15 deletions docs/operations/how-to/set-up-production.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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"
(<https://dash.cloudflare.com/2557b5c2e166292ded0f8425b73075e9/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"
(<https://dash.cloudflare.com/2557b5c2e166292ded0f8425b73075e9/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

Expand All @@ -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

Expand All @@ -93,8 +105,9 @@ domain. Deploying creates the domain's DNS record and certificate, and redeployi
Worker changes nothing.

1. Check: open <https://events.lasvegasfortransit.org/health>. 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`.

Expand Down
Loading