docs/cloudflare.md 4.1 KiB 107 lines Raw

Deploy hu13 on Cloudflare Pages (Free)

The same dist/ works on Docker/hu13 serve, nginx, and Pages. On Pages the inbox is implemented by a Pages Function backed by R2; a static-only upload has no working inbox.

Pages normalizes .html request paths with a 308 (for example /issues/index.html → /issues/ and /files/a.md.html → /files/a.md). Every generated link still reaches the same page; the redirect is a Pages platform behavior, not a missing file. Docker/nginx serve the exact .html path with no redirect. If byte-identical URLs matter, put a proxy/Worker in front or accept the Pages canonical form; _redirects status 200 cannot override these clean-URL redirects.

1. Build the site

cargo build --release
./target/release/hu13 build . -o dist \
  --public-origin https://hu13.example.com

--public-origin makes the clone command, og:image, and Atom links absolute. The build refuses output over Cloudflare's limits (20,000 files, 25 MiB/file) and writes _headers and _routes.json into dist/.

2. Create the R2 bucket and Pages project

npx wrangler r2 bucket create hu13-inbox
npx wrangler pages project create hu13

Bind the bucket to the Pages project as INBOX (dashboard: Settings → Functions → R2 bindings, or the [[r2_buckets]] block in wrangler.toml). Set the required secret:

npx wrangler pages secret put INBOX_SALT --project-name hu13

INBOX_SALT is a random string (for example openssl rand -hex 32). The function hashes v1|<ip>|<yyyy-mm-dd>|<salt> with SHA-256; no plaintext IP is stored. Rotating the secret unlinks old hashes.

Optional variables: INBOX_RATE_PER_HOUR (default 2), INBOX_QUOTA (default 500), INBOX_MAX_TITLE (300), INBOX_MAX_BODY (20000).

3. Deploy

npx wrangler pages deploy dist --project-name hu13

Wrangler uploads functions/ alongside dist/. dist/_routes.json limits Functions to /inbox/*, so every other request is static. Keep wrangler.toml at the repo root for the R2 binding.

What the Function implements

functions/inbox/[kind].js serves both mailboxes:

Rate limits are layered: the Function has a best-effort isolate-local bucket, but that is not global. Add a Cloudflare WAF rate-limiting rule for /inbox/* (for example 10 requests/minute per IP) as the durable control. The global quota is checked against the R2 bucket before storing; R2 list consistency means a burst can briefly exceed it.

Reading submissions

npx wrangler r2 object list hu13-inbox
npx wrangler r2 object get hu13-inbox/<uuid>.json

hu13 inbox list reads the local filesystem inbox used by Docker/serve; it does not read R2. Copy objects down if you want the terminal reader.

Limits and caveats

view raw