diff --git a/deploy/cloudflare/README.md b/deploy/cloudflare/README.md index 1ba295f..b39d979 100644 --- a/deploy/cloudflare/README.md +++ b/deploy/cloudflare/README.md @@ -41,20 +41,43 @@ Produces: ## 2. R2 — bucket, CORS, custom domain, media +Bucket name: **`human-experience-simulator-media`**. The default `wrangler login` +OAuth token already has R2 + Pages + zone scopes (no separate API token needed). + ```bash -wrangler r2 bucket create hef-media -wrangler r2 bucket cors put hef-media --rules deploy/cloudflare/r2-cors.json -wrangler r2 bucket sync ./dist-media r2://hef-media +wrangler r2 bucket create human-experience-simulator-media + +# CORS — load-bearing (the WebGL dream shader taints without it). The wrangler CLI +# wants its own schema, NOT the S3-style array; use the *.wrangler.json file: +wrangler r2 bucket cors set human-experience-simulator-media --file deploy/cloudflare/r2-cors.wrangler.json +# (deploy/cloudflare/r2-cors.json is the equivalent S3-style policy, for the +# dashboard / S3 API; r2-cors.wrangler.json is the same rule in wrangler's +# `{ "rules": [ { "allowed": {...} } ] }` form.) + +# Custom domain — wrangler CAN do this (no dashboard needed); needs the zone id. +ZONE=$(curl -s -H "Authorization: Bearer $(grep '^oauth_token' ~/.wrangler/config/default.toml | cut -d'"' -f2)" \ + "https://api.cloudflare.com/client/v4/zones?name=benstull.art" | python3 -c "import sys,json;print(json.load(sys.stdin)['result'][0]['id'])") +wrangler r2 bucket domain add human-experience-simulator-media \ + --domain static.benstull.art --zone-id "$ZONE" --min-tls 1.2 ``` -Then in the dashboard (R2 → `hef-media` → Settings): -- **Custom domain:** add `static.benstull.art` (this is the `mediaBase` baked into - `config.js`). -- **CORS is load-bearing** — the WebGL dream shader taints without it. The policy - in `r2-cors.json` allows `https://benstull.art` + `Range` (video scrubbing). -- **Caching:** the media URLs are content-hash-versioned (`?v=`), so set the - objects `Cache-Control: public, max-age=31536000, immutable` (a re-baked clip - gets a new hash → new URL → cache busts cleanly). +**Media upload — there is NO `wrangler r2 bucket sync`.** Upload per-file with +`wrangler r2 object put ... --remote`, setting content-type + immutable cache. The +URLs are content-hash-versioned (`?v=`), so `immutable` is safe — a re-baked +clip gets a new hash → new URL → cache busts. ~604 files / ~2.3 GB, sequential: + +```bash +WR=$(command -v wrangler); BUCKET=human-experience-simulator-media +find dist-media -type f | while read -r f; do + key="${f#dist-media/}" + case "${f##*.}" in mp4) ct=video/mp4;; mp3) ct=audio/mpeg;; json) ct=application/json;; *) ct=application/octet-stream;; esac + "$WR" r2 object put "$BUCKET/$key" --file "$f" --remote \ + --content-type "$ct" --cache-control "public, max-age=31536000, immutable" +done +``` + +(If you have `rclone` + an R2 S3 API token, `rclone copy dist-media :bucket` +is much faster than the loop.) If you change the media base, rebuild with `--media-base https:///` so `config.js` matches. @@ -62,16 +85,16 @@ If you change the media base, rebuild with `--media-base https:///` so ## 3. Pages — deploy + domain + redirect ```bash -wrangler pages project create human-experience-simulator -wrangler pages deploy dist --project-name human-experience-simulator +wrangler pages project create human-experience-simulator --production-branch main +wrangler pages deploy dist --project-name human-experience-simulator --branch main ``` -Then in the dashboard (Pages → project → Custom domains): -- Bind **`benstull.art`** to the project. -- The app serves at `benstull.art/human-experience-simulator/`; the generated - `dist/_redirects` sends `/` and the no-slash form to it. (Alternatively, a - zone-level **Redirect Rule** `benstull.art/` → `/human-experience-simulator` - is the authoritative equivalent if you prefer not to ship `_redirects`.) +Then **in the dashboard** (Workers & Pages → project → Custom domains) — there is +no wrangler CLI for Pages custom domains: +- Bind **`benstull.art`** to the project. (Allow a few minutes — a fresh bind + serves `522` until the cert/routing provisions.) +- The app serves at `benstull.art/human-experience-simulator/`; the shipped + `dist/_redirects` sends `/` and the no-slash form to it. ## 4. Verify before/after going live diff --git a/deploy/cloudflare/r2-cors.wrangler.json b/deploy/cloudflare/r2-cors.wrangler.json new file mode 100644 index 0000000..261340c --- /dev/null +++ b/deploy/cloudflare/r2-cors.wrangler.json @@ -0,0 +1,13 @@ +{ + "rules": [ + { + "allowed": { + "origins": ["https://benstull.art"], + "methods": ["GET", "HEAD"], + "headers": ["Range", "Content-Type"] + }, + "exposeHeaders": ["Content-Length", "Content-Range", "ETag", "Accept-Ranges"], + "maxAgeSeconds": 86400 + } + ] +}