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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:
- run: npm install
- run: npm run build
- run: npm test
- name: Umami proxy tests
working-directory: infra/umami-proxy
run: npm ci && npm test
- run: npm run build:docs
- name: CLI smoke (enforcement)
run: npm run smoke
Expand Down
40 changes: 36 additions & 4 deletions .github/workflows/umami-proxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,61 @@ on:
paths:
- "infra/umami-proxy/**"
- ".github/workflows/umami-proxy.yml"
pull_request:
paths:
- "infra/umami-proxy/**"
- ".github/workflows/umami-proxy.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: umami-proxy
group: umami-proxy-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
deploy:
check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: "22"
cache: npm
cache-dependency-path: infra/umami-proxy/package-lock.json
- name: Install Worker deps
working-directory: infra/umami-proxy
run: npm ci
- name: Test
working-directory: infra/umami-proxy
run: npm test
- name: Bundle (dry-run)
working-directory: infra/umami-proxy
run: npx wrangler deploy --dry-run

deploy:
if: github.event_name != 'pull_request'
needs: check
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
- name: Require UMAMI_ORIGIN
env:
UMAMI_ORIGIN: ${{ secrets.UMAMI_ORIGIN }}
run: test -n "$UMAMI_ORIGIN"
# wrangler-action's `secrets:` input runs secret bulk BEFORE deploy, which
# fails when the Worker does not exist yet — the route stays in git and
# nothing is provisioned. Deploy the script first, then bind the origin.
- name: Deploy Worker
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: infra/umami-proxy
secrets: |
UMAMI_ORIGIN
command: deploy
postCommands: printf '%s' "$UMAMI_ORIGIN" | npx wrangler secret put UMAMI_ORIGIN
env:
UMAMI_ORIGIN: ${{ secrets.UMAMI_ORIGIN }}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Umami proxy provision
- **Deploy the Worker before binding `UMAMI_ORIGIN`.** wrangler-action’s `secrets:` input ran `secret bulk` first, which fails when the Worker does not exist yet — `wrangler.jsonc` still listed the `/s/*` route, so git looked provisioned while nothing was uploaded. CI now deploys, then `secret put`.
- PRs that touch the proxy run unit tests + `wrangler deploy --dry-run`; only `main` / `workflow_dispatch` deploy.

### CI verify on push (stacked-run walker)
- **`know-code verify --from <oid>`** walks `from..HEAD`, splits by `Know-Code-Verified` hash, and checks each run as a historical tree-pair (parent-of-first tree → last non-merge). Trailerless merges attach to the run but are not the hash tip, so a GitHub merge commit still matches after `main` moved. Linear commits without a trailer fail closed. One-non-merge runs also accept the empty-tree (index) hash of that feature tip.
- **Workflow + `init --workflow` + composite action** trigger on `push` to the base branch and pass `github.event.before`. PR verify is unchanged (`head.sha`, no `--from`). All-zeros `before` skips the walk.
Expand Down
25 changes: 19 additions & 6 deletions infra/umami-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ so the browser never talks to a third-party analytics host (ad-blocker bypass).
Neutral path names (`/s/x.js`, `/s/e`) avoid EasyPrivacy hits on `umami`,
`analytics`, `script.js`, and `/api/send`. The dashboard is **not** proxied.

`workers_dev` stays off so Cloudflare does not publish
`kc-umami-proxy.<account>.workers.dev` (the hostname would contain `umami`).
Do not attach this Worker as a custom domain on `kc.chtnnhfoundation.org` —
that would steal the host from GitHub Pages. The zone route `/s/*` is enough.

## One-time setup

1. **DNS.** `kc.chtnnhfoundation.org` must be orange-clouded on Cloudflare
Expand All @@ -25,20 +30,21 @@ Neutral path names (`/s/x.js`, `/s/e`) avoid EasyPrivacy hits on `umami`,

| Secret | Value |
|--------|--------|
| `CLOUDFLARE_API_TOKEN` | Token with Workers Scripts Edit + Workers Routes Edit on this account |
| `CLOUDFLARE_API_TOKEN` | Account-scoped token with **Workers Scripts Edit** (uploads the Worker) and **Workers Routes Edit** (attaches `/s/*`). Routes Edit alone leaves the route defined and the script missing. |
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID |
| `UMAMI_ORIGIN` | `https://<your-umami-host>` (no trailing slash) |

First deploy: **Actions → umami-proxy → Run workflow**. Later pushes to
`infra/umami-proxy/**` on `main` deploy automatically
(`.github/workflows/umami-proxy.yml`). The action syncs `UMAMI_ORIGIN` as a
Worker secret on each run.
(`.github/workflows/umami-proxy.yml`). The workflow **deploys the Worker,
then** binds `UMAMI_ORIGIN`. Putting the secret first fails when the Worker
does not exist yet.

Manual fallback:
Manual fallback (same order):

```bash
npx wrangler secret put UMAMI_ORIGIN
npx wrangler deploy
npx wrangler secret put UMAMI_ORIGIN
```

5. **Docs build.** Repo variable `UMAMI_WEBSITE_ID` (Settings → Secrets and
Expand All @@ -50,7 +56,14 @@ Neutral path names (`/s/x.js`, `/s/e`) avoid EasyPrivacy hits on `umami`,
JavaScript containing `/s/e`, not `/api/send`. Load a docs page with
ad-blocker on; Umami realtime should show a view.

## Local docs
## Local

```bash
cd infra/umami-proxy
npm install
npm test
npx wrangler deploy --dry-run
```

`docusaurus start` does not inject the script (`data-domains` would also
exclude `localhost`). No events leak from local preview.
Loading