C.A.T.S.Crunchy Automated Tactical System

← All documents

Deploying to Cloudflare Pages

Everything in the repository is ready. The remaining steps need your Cloudflare and GitHub authentication, so they have to be run by you — an agent cannot authenticate as you, and should not.

Total time: about ten minutes, most of it waiting for the first build.


What is already done

ItemState
Repository515FazzyBear/CATS, private, pushed
Build scripttool/pages_build.sh — bootstraps Deno, regenerates everything, assembles dist/
Site sourceweb/ — 34 files, builds clean
Feedback APIfunctions/api/feedback.ts
Database schemamigrations/0001_init.sql
Cloudflare configwrangler.toml — needs one value pasted in (step 3)
Line endings.gitattributes forces LF, which the Linux build depends on

Step 1 — Connect the repository to Cloudflare Pages

In the Cloudflare dashboard: Workers & Pages → Create → Pages → Connect to Git.

Authorise the Cloudflare GitHub app against 515FazzyBear/CATS. A private repository is fine — the app gets read access.

Then set:

SettingValue
Project namecats-rpg
Production branchmain
Build commandbash tool/pages_build.sh
Build output directorydist
Root directory(leave blank)

The build command lives in the dashboard, not in wrangler.toml. wrangler.toml only declares the output directory and the database binding. Changing the repo alone will not change what CI runs — this catches people out.

The first build takes 2-3 minutes, most of it downloading Deno. Cloudflare's build image does not ship Deno and offers no version variable, so pages_build.sh fetches a pinned release itself. That is expected, not a workaround for something broken.

At this point the site is live at cats-rpg.pages.dev and every push to main redeploys it. The feedback button will render but return a 503 until step 3.

Step 2 — Set up API access (wrangler does not work here)

The wrangler CLI cannot run on this machine. It depends on workerd, which publishes no Windows ARM64 build, and this box has no Node, no working WSL (Wsl/CallMsi/Install/ REGDB_E_CLASS_NOT_REG), and no Docker to fall back to. Every npx wrangler instruction you will find in Cloudflare's documentation is unusable here.

Use tool/cf.ts instead. It talks to the Cloudflare REST API over HTTPS, so architecture is irrelevant, and it runs on Deno — this project's only toolchain.

Create a token: Cloudflare dashboard → My Profile → API Tokens → Create Token. Either use the Edit Cloudflare Workers template, or build a custom token with:

ScopeResourcePermission
AccountD1Edit
AccountCloudflare PagesEdit

Then put it in your environment. It is a credential — never commit it:

$env:CLOUDFLARE_API_TOKEN  = "<the token>"
$env:CLOUDFLARE_ACCOUNT_ID = "<account id, shown on the dashboard sidebar>"

Step 3 — Create the feedback database

deno task cf d1 create cats-rpg

It prints the uuid. Uncomment the binding in wrangler.toml and paste it in:

[[d1_databases]]
binding = "CATS_DB"
database_name = "cats-rpg"
database_id = "<the uuid>"

Leave the block commented out until the database exists. Pages validates the UUID at deploy time and fails the entire deployment on a bad one, so a placeholder takes the whole site down rather than degrading one endpoint. This is not hypothetical — it cost the first deploy.

Then create the table and push:

deno task cf d1 exec cats-rpg migrations/0001_init.sql
git add wrangler.toml; git commit -m "Wire up the D1 database"; git push

The migration is optional in practice — functions/api/feedback.ts calls ensureSchema() on every request and creates the table itself. Running it explicitly just means the first request is not the one paying for it.

Step 4 — Set the admin token

Invent a long random string. This gates reading and triaging feedback.

deno task cf pages secret cats-rpg CATS_ADMIN_TOKEN

It reads the value from stdin rather than from the command line, so the secret never lands in shell history or in the process list. Do not commit it. The triage console at /feedback.html asks for it and keeps it in browser local storage.

Redeploy for a new secret to take effect.

Useful checks

deno task cf pages deployments cats-rpg      # build status without opening the dashboard
deno task cf d1 query cats-rpg "SELECT count(*) AS n FROM feedback"

Step 5 — Verify

curl -X POST https://cats-rpg.pages.dev/api/feedback \
  -H "content-type: application/json" \
  -d '{"message":"deploy smoke test","category":"other"}'

A 201 with an id means the whole chain works. Then open /feedback.html, enter the admin token, and confirm the note appears.

Step 6 — Decide who can see it

The site is public the moment it deploys, regardless of the repository being private. Cloudflare Pages does not inherit GitHub permissions.

If it should not be public yet, add Cloudflare Access in front of it: Zero Trust → Access → Applications → Add a self-hosted application over cats-rpg.pages.dev, with a policy allowing specific email addresses. Collaborators then get a one-time code by email.

That is worth doing deliberately rather than by default — the whole point of the feedback widget is that people can disagree with the design in writing, which needs them to reach it.

Working from here

deno task build     # regenerate analysis and site
deno task serve     # preview at localhost:8817
git push            # deploy

dist/ is generated and gitignored. Never edit it; edits vanish on the next build.

Things that will bite

ProblemCause
Unsupported platform: win32 arm64You tried to run wrangler. It cannot work here — see step 2 and use deno task cf
Invalid database UUID and the whole site fails to deployA placeholder or wrong database_id in wrangler.toml. Comment the block out rather than leaving a placeholder
Build fails with a parse error on a scriptWindows PowerShell 5.1 writes CRLF. .gitattributes forces LF for a reason — never remove it
Build fails hunting for DenoSomeone removed the bootstrap from pages_build.sh
Feedback returns 503database_id is still the placeholder, or migrations were not applied with --remote
Feedback returns 403 on readAdmin token missing or mismatched
Charts show stale numbersSomething was hand-edited in docs/analysis/ or web/. Both are generated — change the model in tools/lib/ instead
Docs missing from the siteEvery .md under docs/ is picked up automatically; check the file has an # H1

Source: docs/11-deploy.md — this page is generated from it.