From abfccaf3e7953ecee5d97152d151393484450408 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 12:20:15 +0000 Subject: [PATCH 1/5] Deploy from GitHub Actions: test, migrate D1, deploy on push to main - .github/workflows/forum.yml tests every push and PR touching web/; on main it applies D1 migrations and runs wrangler deploy, only once the DEPLOY_ENABLED repository variable is true. - wrangler.jsonc holds production values (forum.nodebox.net, real mail); npm run dev overrides MAILER and PUBLIC_URL. - Import: the exporter now keys attachments on comment + path, so one file can appear on several comments; import it once. Test fixture updated. - DEPLOY.md: one-time setup, first deploy, rollback. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01U7kh689sSJMCiynaNSHfVB --- .github/workflows/forum.yml | 66 ++++++++++++++++++++++++++++++++++++ web/DEPLOY.md | 49 ++++++++++++++++++++++++++ web/README.md | 15 +++----- web/package.json | 3 +- web/scripts/import-tender.ts | 6 +++- web/test/import.test.ts | 9 ++--- web/wrangler.jsonc | 12 +++++-- 7 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/forum.yml create mode 100644 web/DEPLOY.md diff --git a/.github/workflows/forum.yml b/.github/workflows/forum.yml new file mode 100644 index 0000000..1578620 --- /dev/null +++ b/.github/workflows/forum.yml @@ -0,0 +1,66 @@ +# Tests every push and pull request that touches the forum app. On main, once +# deploying is switched on (repository variable DEPLOY_ENABLED = true), applies +# new D1 migrations and deploys the Worker to Cloudflare. +name: Forum + +on: + push: + branches: [main] + paths: ["web/**", ".github/workflows/forum.yml"] + pull_request: + paths: ["web/**", ".github/workflows/forum.yml"] + workflow_dispatch: + +defaults: + run: + working-directory: web + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: web/package-lock.json + - run: npm ci + - run: npm run typecheck + - run: npm test + + deploy: + needs: test + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && vars.DEPLOY_ENABLED == 'true' + runs-on: ubuntu-latest + # Add required reviewers to the "production" environment in GitHub to approve each deploy by hand. + environment: + name: production + url: https://forum.nodebox.net + # One deploy at a time; a newer push waits rather than racing a migration. + concurrency: + group: production + cancel-in-progress: false + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: web/package-lock.json + - run: npm ci + - name: Check the D1 database id is set + run: | + if grep -q '00000000-0000-0000-0000-000000000000' wrangler.jsonc; then + echo "::error::Put the real database_id in web/wrangler.jsonc (wrangler d1 create nodebox-forum)." + exit 1 + fi + - run: npm run build:client + # Migrations run before the new code goes live, so they must keep the old code working (add, don't drop). + - name: Apply D1 migrations + run: npx wrangler d1 migrations apply nodebox-forum --remote + - name: Deploy + run: npx wrangler deploy diff --git a/web/DEPLOY.md b/web/DEPLOY.md new file mode 100644 index 0000000..e96acde --- /dev/null +++ b/web/DEPLOY.md @@ -0,0 +1,49 @@ +# Deploying the forum + +Pushes to `main` that touch `web/` run `.github/workflows/forum.yml`: + +1. **test**: `npm ci`, typecheck, tests. Runs on pull requests too. +2. **deploy** (main only, and only once the repository variable + `DEPLOY_ENABLED` is `true`): builds the client, applies new D1 migrations + (`wrangler d1 migrations apply --remote`), then `wrangler deploy`. + +Migrations run before the new code is live, so every migration must keep the +running version working: add tables and columns, don't drop or rename them in +the same release. + +## One-time setup + +| What | Where | +| --- | --- | +| D1 database | `npx wrangler d1 create nodebox-forum`, then put its id in `wrangler.jsonc` (`database_id`) | +| R2 bucket | `npx wrangler r2 bucket create nodebox-forum` | +| Hostname | `forum.nodebox.net` in `wrangler.jsonc` (`routes`). The `nodebox.net` zone must be on the same Cloudflare account | +| Email | Onboard `nodebox.net` for Email Sending in the dashboard; sender is `MAIL_FROM` (`forum@nodebox.net`) | +| Images | Variants `nodebox-forum-post` (1200 px wide) and `nodebox-forum-thumb` (400 px) | +| Worker secrets | `npx wrangler secret put CF_ACCOUNT_ID`, `CF_IMAGES_TOKEN` (Images: Edit), `CF_IMAGES_HASH` | +| GitHub secrets | `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` | +| GitHub variable | `DEPLOY_ENABLED` = `true` when the above is done | +| Approval (optional) | Settings → Environments → `production` → Required reviewers | + +The API token for GitHub: start from the "Edit Cloudflare Workers" template +and add **Account → D1 → Edit**. Limit it to the NodeBox account and the +`nodebox.net` zone. + +## First deploy + +1. Merge with `DEPLOY_ENABLED` unset: only tests run. +2. Set the secrets and the variable, then run the workflow (Actions → Forum → + Run workflow). It creates the schema in the empty D1 database and deploys. +3. Load the Tender data from the machine that has `data/` (it contains email + addresses; it never goes to GitHub): + `npx wrangler d1 execute nodebox-forum --remote --file ../data/import.sql` +4. Upload the old attachments to R2 and Images (script still to write). +5. Make the first admin: `npm run admin -- grant --remote`. +6. Check: a sign-in email arrives, an upload works, a screenshot shows its + resized variant, an old Tender URL redirects. + +## Rolling back + +- Code: `npx wrangler rollback` (or pick a version under Workers → Deployments). +- Data: D1 Time Travel, `npx wrangler d1 time-travel restore nodebox-forum --timestamp `. + Migrations only go forward; a bad one is fixed by a new migration or a restore. diff --git a/web/README.md b/web/README.md index 9f78c6d..ccdb660 100644 --- a/web/README.md +++ b/web/README.md @@ -59,14 +59,7 @@ in Node against local D1/R2 (wrangler's platform proxy): sign-in by email link, claiming an imported account, posting with an attachment, reactions, moderation, CSRF and old-URL redirects. -## Deploy (once the accounts exist) - -1. `npx wrangler d1 create nodebox-forum`, paste the id into `wrangler.jsonc`. -2. `npx wrangler r2 bucket create nodebox-forum`. -3. Onboard the sending domain for Cloudflare Email Sending; set `MAILER` to - `cloudflare` and `MAIL_FROM`, `PUBLIC_URL` in `wrangler.jsonc`. -4. In Cloudflare Images, create the variants `nodebox-forum-post` (1200 px wide, - scale down) and `nodebox-forum-thumb` (400 px). -5. Secrets: `wrangler secret put` for `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, - `CF_ACCOUNT_ID`, `CF_IMAGES_TOKEN`, `CF_IMAGES_HASH`. -6. `npx wrangler d1 migrations apply nodebox-forum --remote`, then `npm run deploy`. +## Deploy + +Pushing to `main` tests, migrates and deploys through GitHub Actions. Setup, +the first deploy and rollback are in [DEPLOY.md](DEPLOY.md). diff --git a/web/package.json b/web/package.json index ef76541..eeed320 100644 --- a/web/package.json +++ b/web/package.json @@ -6,8 +6,9 @@ "type": "module", "scripts": { "build:client": "node scripts/build-client.mjs", - "dev": "npm run build:client && wrangler dev", + "dev": "npm run build:client && wrangler dev --var MAILER:log --var PUBLIC_URL:http://localhost:8787", "db:migrate:local": "wrangler d1 migrations apply nodebox-forum --local", + "db:migrate:remote": "wrangler d1 migrations apply nodebox-forum --remote", "db:seed:local": "wrangler d1 execute nodebox-forum --local --file seed/dev.sql", "deploy": "npm run build:client && wrangler deploy", "types": "wrangler types", diff --git a/web/scripts/import-tender.ts b/web/scripts/import-tender.ts index 05de566..4a720b3 100644 --- a/web/scripts/import-tender.ts +++ b/web/scripts/import-tender.ts @@ -167,8 +167,10 @@ export function convert(src: DatabaseSync, imagesPrefix = "nodebox-forum-"): { s FROM comments WHERE discussion_id = ? AND coalesce(internal, 0) = 0 AND coalesce(system_message, 0) = 0 ORDER BY number`, ); + // One file can hang on several comments (the exporter keys on comment + path); the forum keeps it on the first. + const importedAttachments = new Set(); const attachmentsStmt = src.prepare( - "SELECT download_path, comment_number, filename, content_type, size, width, height, created_at, local_path FROM attachments WHERE discussion_id = ?", + "SELECT download_path, comment_number, filename, content_type, size, width, height, created_at, local_path FROM attachments WHERE discussion_id = ? ORDER BY comment_number", ); // Old URLs -> new topic paths, for rewriting links inside bodies. @@ -260,6 +262,8 @@ export function convert(src: DatabaseSync, imagesPrefix = "nodebox-forum-"): { s const pid = postIdByNumber.get(f.comment_number); if (pid === undefined) continue; // attachment on an internal note const id = attachmentId(f.download_path); + if (importedAttachments.has(id)) continue; + importedAttachments.add(id); const type = f.content_type || "application/octet-stream"; out.push( insert("attachments", { diff --git a/web/test/import.test.ts b/web/test/import.test.ts index 5a608e8..3a5a61e 100644 --- a/web/test/import.test.ts +++ b/web/test/import.test.ts @@ -37,10 +37,11 @@ function fixture(): DatabaseSync { (101, 1, 3, 'Bob', NULL, 'Uw pakket', 0, '2014-05-04T09:00:00Z', '{}'), (102, 1, 1, 'Ann', NULL, 'secret', 0, '${t}', '{}'), (103, 1, 1, 'Ann', NULL, 'private', 0, '${t}', '{}'); - INSERT INTO attachments (download_path, discussion_id, comment_number, filename, content_type, size, raw) VALUES - ('/assets/abc/shot.png', 100, 1, 'shot.png', 'image/png', 1234, '{}'), - ('/assets/def/project.zip', 100, 5, 'project.zip', 'application/zip', 99999, '{}'), - ('/assets/ghi/note.txt', 100, 3, 'note.txt', 'text/plain', 1, '{}'); + INSERT INTO attachments (download_path, download_url, discussion_id, comment_number, filename, content_type, size, raw) VALUES + ('/assets/abc/shot.png', 'https://s3.example/abc/shot.png?sig=1', 100, 1, 'shot.png', 'image/png', 1234, '{}'), + ('/assets/def/project.zip', 'https://s3.example/def/project.zip?sig=1', 100, 5, 'project.zip', 'application/zip', 99999, '{}'), + ('/assets/abc/shot.png', 'https://s3.example/abc/shot.png?sig=2', 100, 5, 'shot.png', 'image/png', 1234, '{}'), + ('/assets/ghi/note.txt', 'https://s3.example/ghi/note.txt?sig=1', 100, 3, 'note.txt', 'text/plain', 1, '{}'); -- Same shape as the spam filter's migrations/0002_spam.sql. CREATE TABLE spam_verdicts (discussion_id INTEGER NOT NULL, comment_number INTEGER NOT NULL, verdict TEXT NOT NULL, confidence REAL, source TEXT NOT NULL, reason TEXT, model TEXT, classified_at TEXT NOT NULL, PRIMARY KEY (discussion_id, comment_number)); diff --git a/web/wrangler.jsonc b/web/wrangler.jsonc index f99e1f9..409d34a 100644 --- a/web/wrangler.jsonc +++ b/web/wrangler.jsonc @@ -5,6 +5,10 @@ "compatibility_date": "2026-09-01", "compatibility_flags": ["nodejs_compat"], "assets": { "directory": "public", "binding": "ASSETS" }, + // Production hostname. The nodebox.net zone must be on this Cloudflare account; the deploy creates the DNS record. + "routes": [{ "pattern": "forum.nodebox.net", "custom_domain": true }], + "workers_dev": false, + "observability": { "enabled": true }, "d1_databases": [ // Create with `wrangler d1 create nodebox-forum` and paste the id here. { "binding": "DB", "database_name": "nodebox-forum", "database_id": "00000000-0000-0000-0000-000000000000", "migrations_dir": "migrations" } @@ -19,16 +23,18 @@ "vars": { "SITE_NAME": "NodeBox Forum", // Absolute origin, used in sign-in emails and the Google redirect URI. - "PUBLIC_URL": "http://localhost:8787", + // These are the production values; `npm run dev` overrides PUBLIC_URL and MAILER. + "PUBLIC_URL": "https://forum.nodebox.net", // Cloudflare Images: ids and variant names are global to the account, so everything is prefixed. "CF_IMAGES_PREFIX": "nodebox-forum-", "CF_IMAGES_VARIANT_POST": "nodebox-forum-post", "CF_IMAGES_VARIANT_THUMB": "nodebox-forum-thumb", - // "log" prints sign-in links to the console (development); "cloudflare" sends real mail. - "MAILER": "log", + // "cloudflare" sends real mail; "log" prints sign-in links to the console (development). + "MAILER": "cloudflare", "MAIL_FROM": "forum@nodebox.net" } // Secrets (wrangler secret put, or .dev.vars locally): // GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET // CF_ACCOUNT_ID, CF_IMAGES_TOKEN, CF_IMAGES_HASH (the account hash in imagedelivery.net URLs) + // Deploys run from GitHub Actions (.github/workflows/forum.yml) on every push to main. } From fc444f96b97d7c8d337d3b8e21b247096f2ac7cc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 12:21:49 +0000 Subject: [PATCH 2/5] Deploy from Cloudflare Workers Builds instead of GitHub Actions Frederik prefers deploying from the Cloudflare side. Workers Builds runs npm run ci:build (typecheck, tests, client) and npm run ci:deploy (D1 migrations, wrangler deploy). GitHub Actions keeps only the test job on PRs. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01U7kh689sSJMCiynaNSHfVB --- .github/workflows/forum.yml | 44 ++----------------------------------- web/.node-version | 1 + web/DEPLOY.md | 44 ++++++++++++++++++++++--------------- web/package.json | 2 ++ web/wrangler.jsonc | 2 +- 5 files changed, 32 insertions(+), 61 deletions(-) create mode 100644 web/.node-version diff --git a/.github/workflows/forum.yml b/.github/workflows/forum.yml index 1578620..9c88d50 100644 --- a/.github/workflows/forum.yml +++ b/.github/workflows/forum.yml @@ -1,12 +1,8 @@ -# Tests every push and pull request that touches the forum app. On main, once -# deploying is switched on (repository variable DEPLOY_ENABLED = true), applies -# new D1 migrations and deploys the Worker to Cloudflare. +# Tests every pull request that touches the forum app, so a red PR shows +# before merging. Deploys happen in Cloudflare Workers Builds (see web/DEPLOY.md). name: Forum on: - push: - branches: [main] - paths: ["web/**", ".github/workflows/forum.yml"] pull_request: paths: ["web/**", ".github/workflows/forum.yml"] workflow_dispatch: @@ -28,39 +24,3 @@ jobs: - run: npm ci - run: npm run typecheck - run: npm test - - deploy: - needs: test - if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && vars.DEPLOY_ENABLED == 'true' - runs-on: ubuntu-latest - # Add required reviewers to the "production" environment in GitHub to approve each deploy by hand. - environment: - name: production - url: https://forum.nodebox.net - # One deploy at a time; a newer push waits rather than racing a migration. - concurrency: - group: production - cancel-in-progress: false - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - cache-dependency-path: web/package-lock.json - - run: npm ci - - name: Check the D1 database id is set - run: | - if grep -q '00000000-0000-0000-0000-000000000000' wrangler.jsonc; then - echo "::error::Put the real database_id in web/wrangler.jsonc (wrangler d1 create nodebox-forum)." - exit 1 - fi - - run: npm run build:client - # Migrations run before the new code goes live, so they must keep the old code working (add, don't drop). - - name: Apply D1 migrations - run: npx wrangler d1 migrations apply nodebox-forum --remote - - name: Deploy - run: npx wrangler deploy diff --git a/web/.node-version b/web/.node-version new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/web/.node-version @@ -0,0 +1 @@ +22 diff --git a/web/DEPLOY.md b/web/DEPLOY.md index e96acde..222da46 100644 --- a/web/DEPLOY.md +++ b/web/DEPLOY.md @@ -1,15 +1,28 @@ # Deploying the forum -Pushes to `main` that touch `web/` run `.github/workflows/forum.yml`: +Cloudflare Workers Builds deploys the forum on every push to `main`. In the +Worker's settings (Workers & Pages → nodebox-forum → Settings → Build): -1. **test**: `npm ci`, typecheck, tests. Runs on pull requests too. -2. **deploy** (main only, and only once the repository variable - `DEPLOY_ENABLED` is `true`): builds the client, applies new D1 migrations - (`wrangler d1 migrations apply --remote`), then `wrangler deploy`. +| Setting | Value | +| --- | --- | +| Git repository | `nodebox/forum`, production branch `main` | +| Root directory | `web` | +| Build command | `npm run ci:build` (typecheck, tests, client bundle) | +| Deploy command | `npm run ci:deploy` (D1 migrations, then `wrangler deploy`) | +| Non-production branch builds | Off. Preview versions would use the production D1 database and R2 bucket | + +A failing test stops the build, so nothing is deployed. Migrations run before +the new code is live, so every migration must keep the running version +working: add tables and columns, don't drop or rename them in the same +release. Node comes from `web/.node-version` (22). -Migrations run before the new code is live, so every migration must keep the -running version working: add tables and columns, don't drop or rename them in -the same release. +The build's API token needs **D1 → Edit** on top of the Workers permissions, +or the migration step fails with an authorisation error. If the token +Cloudflare creates for the build lacks it, edit that token (or pick one that +has it) under the build settings. + +GitHub Actions (`.github/workflows/forum.yml`) only runs the tests on pull +requests; it holds no Cloudflare credentials. ## One-time setup @@ -21,19 +34,14 @@ the same release. | Email | Onboard `nodebox.net` for Email Sending in the dashboard; sender is `MAIL_FROM` (`forum@nodebox.net`) | | Images | Variants `nodebox-forum-post` (1200 px wide) and `nodebox-forum-thumb` (400 px) | | Worker secrets | `npx wrangler secret put CF_ACCOUNT_ID`, `CF_IMAGES_TOKEN` (Images: Edit), `CF_IMAGES_HASH` | -| GitHub secrets | `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` | -| GitHub variable | `DEPLOY_ENABLED` = `true` when the above is done | -| Approval (optional) | Settings → Environments → `production` → Required reviewers | - -The API token for GitHub: start from the "Edit Cloudflare Workers" template -and add **Account → D1 → Edit**. Limit it to the NodeBox account and the -`nodebox.net` zone. +| Git connection | The build settings above. Connect it last: the first build deploys | ## First deploy -1. Merge with `DEPLOY_ENABLED` unset: only tests run. -2. Set the secrets and the variable, then run the workflow (Actions → Forum → - Run workflow). It creates the schema in the empty D1 database and deploys. +1. Merge this setup to `main` with the database id filled in. Nothing deploys + while the repository isn't connected in Cloudflare. +2. Connect the repository in the build settings. The first build creates the + schema in the empty D1 database and deploys. 3. Load the Tender data from the machine that has `data/` (it contains email addresses; it never goes to GitHub): `npx wrangler d1 execute nodebox-forum --remote --file ../data/import.sql` diff --git a/web/package.json b/web/package.json index eeed320..4f52804 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,8 @@ "db:migrate:remote": "wrangler d1 migrations apply nodebox-forum --remote", "db:seed:local": "wrangler d1 execute nodebox-forum --local --file seed/dev.sql", "deploy": "npm run build:client && wrangler deploy", + "ci:build": "npm run typecheck && npm test && npm run build:client", + "ci:deploy": "wrangler d1 migrations apply nodebox-forum --remote && wrangler deploy", "types": "wrangler types", "typecheck": "tsc --noEmit && tsc --noEmit -p client", "test": "node --disable-warning=ExperimentalWarning --test test/*.test.ts", diff --git a/web/wrangler.jsonc b/web/wrangler.jsonc index 409d34a..c755df1 100644 --- a/web/wrangler.jsonc +++ b/web/wrangler.jsonc @@ -36,5 +36,5 @@ // Secrets (wrangler secret put, or .dev.vars locally): // GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET // CF_ACCOUNT_ID, CF_IMAGES_TOKEN, CF_IMAGES_HASH (the account hash in imagedelivery.net URLs) - // Deploys run from GitHub Actions (.github/workflows/forum.yml) on every push to main. + // Deploys run in Cloudflare Workers Builds on every push to main (web/DEPLOY.md). } From 14d35947318b720a5a7f5fd30bc598290a02bf12 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 13:15:08 +0000 Subject: [PATCH 3/5] Use the account's existing Images variants (medium, thumb) Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01U7kh689sSJMCiynaNSHfVB --- web/DEPLOY.md | 2 +- web/wrangler.jsonc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/DEPLOY.md b/web/DEPLOY.md index 222da46..0028bd3 100644 --- a/web/DEPLOY.md +++ b/web/DEPLOY.md @@ -32,7 +32,7 @@ requests; it holds no Cloudflare credentials. | R2 bucket | `npx wrangler r2 bucket create nodebox-forum` | | Hostname | `forum.nodebox.net` in `wrangler.jsonc` (`routes`). The `nodebox.net` zone must be on the same Cloudflare account | | Email | Onboard `nodebox.net` for Email Sending in the dashboard; sender is `MAIL_FROM` (`forum@nodebox.net`) | -| Images | Variants `nodebox-forum-post` (1200 px wide) and `nodebox-forum-thumb` (400 px) | +| Images | Uses the account's existing variants `medium` (1000 px wide, in posts) and `thumb` (600 × 600, galleries) | | Worker secrets | `npx wrangler secret put CF_ACCOUNT_ID`, `CF_IMAGES_TOKEN` (Images: Edit), `CF_IMAGES_HASH` | | Git connection | The build settings above. Connect it last: the first build deploys | diff --git a/web/wrangler.jsonc b/web/wrangler.jsonc index c755df1..297c6f6 100644 --- a/web/wrangler.jsonc +++ b/web/wrangler.jsonc @@ -25,10 +25,10 @@ // Absolute origin, used in sign-in emails and the Google redirect URI. // These are the production values; `npm run dev` overrides PUBLIC_URL and MAILER. "PUBLIC_URL": "https://forum.nodebox.net", - // Cloudflare Images: ids and variant names are global to the account, so everything is prefixed. + // Cloudflare Images: image ids are global to the account, so they are prefixed. The variants are the account's shared ones. "CF_IMAGES_PREFIX": "nodebox-forum-", - "CF_IMAGES_VARIANT_POST": "nodebox-forum-post", - "CF_IMAGES_VARIANT_THUMB": "nodebox-forum-thumb", + "CF_IMAGES_VARIANT_POST": "medium", + "CF_IMAGES_VARIANT_THUMB": "thumb", // "cloudflare" sends real mail; "log" prints sign-in links to the console (development). "MAILER": "cloudflare", "MAIL_FROM": "forum@nodebox.net" From a17fbd3913501415c5388b992aa9f9fdfc033b47 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 13:21:09 +0000 Subject: [PATCH 4/5] Fill in the production D1 id; npm run secrets loads Worker secrets from 1Password Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01U7kh689sSJMCiynaNSHfVB --- web/DEPLOY.md | 4 ++-- web/package.json | 1 + web/scripts/put-secrets.sh | 9 +++++++++ web/wrangler.jsonc | 3 +-- 4 files changed, 13 insertions(+), 4 deletions(-) create mode 100755 web/scripts/put-secrets.sh diff --git a/web/DEPLOY.md b/web/DEPLOY.md index 0028bd3..9f91965 100644 --- a/web/DEPLOY.md +++ b/web/DEPLOY.md @@ -28,12 +28,12 @@ requests; it holds no Cloudflare credentials. | What | Where | | --- | --- | -| D1 database | `npx wrangler d1 create nodebox-forum`, then put its id in `wrangler.jsonc` (`database_id`) | +| D1 database | Created; its id is in `wrangler.jsonc` | | R2 bucket | `npx wrangler r2 bucket create nodebox-forum` | | Hostname | `forum.nodebox.net` in `wrangler.jsonc` (`routes`). The `nodebox.net` zone must be on the same Cloudflare account | | Email | Onboard `nodebox.net` for Email Sending in the dashboard; sender is `MAIL_FROM` (`forum@nodebox.net`) | | Images | Uses the account's existing variants `medium` (1000 px wide, in posts) and `thumb` (600 × 600, galleries) | -| Worker secrets | `npx wrangler secret put CF_ACCOUNT_ID`, `CF_IMAGES_TOKEN` (Images: Edit), `CF_IMAGES_HASH` | +| Worker secrets | `npm run secrets` copies `CF_ACCOUNT_ID`, `CF_IMAGES_HASH`, `CF_IMAGES_TOKEN` from 1Password (`op://NodeBox/NodeBox Forum/Cloudflare/…`) | | Git connection | The build settings above. Connect it last: the first build deploys | ## First deploy diff --git a/web/package.json b/web/package.json index 4f52804..1beb1a1 100644 --- a/web/package.json +++ b/web/package.json @@ -11,6 +11,7 @@ "db:migrate:remote": "wrangler d1 migrations apply nodebox-forum --remote", "db:seed:local": "wrangler d1 execute nodebox-forum --local --file seed/dev.sql", "deploy": "npm run build:client && wrangler deploy", + "secrets": "sh scripts/put-secrets.sh", "ci:build": "npm run typecheck && npm test && npm run build:client", "ci:deploy": "wrangler d1 migrations apply nodebox-forum --remote && wrangler deploy", "types": "wrangler types", diff --git a/web/scripts/put-secrets.sh b/web/scripts/put-secrets.sh new file mode 100755 index 0000000..e3ce058 --- /dev/null +++ b/web/scripts/put-secrets.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# Copies the forum's Cloudflare secrets from 1Password into the Worker. +# Needs the 1Password CLI (op) signed in. Values go from op straight into +# wrangler over a pipe; nothing is printed or written to disk. +set -eu +cd "$(dirname "$0")/.." +for name in CF_ACCOUNT_ID CF_IMAGES_HASH CF_IMAGES_TOKEN; do + op read "op://NodeBox/NodeBox Forum/Cloudflare/$name" | npx wrangler secret put "$name" +done diff --git a/web/wrangler.jsonc b/web/wrangler.jsonc index 297c6f6..9bd5755 100644 --- a/web/wrangler.jsonc +++ b/web/wrangler.jsonc @@ -10,8 +10,7 @@ "workers_dev": false, "observability": { "enabled": true }, "d1_databases": [ - // Create with `wrangler d1 create nodebox-forum` and paste the id here. - { "binding": "DB", "database_name": "nodebox-forum", "database_id": "00000000-0000-0000-0000-000000000000", "migrations_dir": "migrations" } + { "binding": "DB", "database_name": "nodebox-forum", "database_id": "c66a199f-225d-46eb-baff-0f16b4696a41", "migrations_dir": "migrations" } ], // Cloudflare Email Sending, for sign-in links. The sending domain must be onboarded in the dashboard. "send_email": [{ "name": "EMAIL" }], From 8f19bfb379621ab9131940f8dee39c2875936203 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 13:21:30 +0000 Subject: [PATCH 5/5] Start on workers.dev; add the custom domain after the first deploy Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01U7kh689sSJMCiynaNSHfVB --- web/DEPLOY.md | 2 +- web/wrangler.jsonc | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/web/DEPLOY.md b/web/DEPLOY.md index 9f91965..818ffde 100644 --- a/web/DEPLOY.md +++ b/web/DEPLOY.md @@ -30,7 +30,7 @@ requests; it holds no Cloudflare credentials. | --- | --- | | D1 database | Created; its id is in `wrangler.jsonc` | | R2 bucket | `npx wrangler r2 bucket create nodebox-forum` | -| Hostname | `forum.nodebox.net` in `wrangler.jsonc` (`routes`). The `nodebox.net` zone must be on the same Cloudflare account | +| Hostname | After the first deploy: add `forum.nodebox.net` as a custom domain (`routes` in `wrangler.jsonc`). Until then the forum runs on its workers.dev URL | | Email | Onboard `nodebox.net` for Email Sending in the dashboard; sender is `MAIL_FROM` (`forum@nodebox.net`) | | Images | Uses the account's existing variants `medium` (1000 px wide, in posts) and `thumb` (600 × 600, galleries) | | Worker secrets | `npm run secrets` copies `CF_ACCOUNT_ID`, `CF_IMAGES_HASH`, `CF_IMAGES_TOKEN` from 1Password (`op://NodeBox/NodeBox Forum/Cloudflare/…`) | diff --git a/web/wrangler.jsonc b/web/wrangler.jsonc index 9bd5755..89c9d12 100644 --- a/web/wrangler.jsonc +++ b/web/wrangler.jsonc @@ -5,9 +5,8 @@ "compatibility_date": "2026-09-01", "compatibility_flags": ["nodejs_compat"], "assets": { "directory": "public", "binding": "ASSETS" }, - // Production hostname. The nodebox.net zone must be on this Cloudflare account; the deploy creates the DNS record. - "routes": [{ "pattern": "forum.nodebox.net", "custom_domain": true }], - "workers_dev": false, + // Runs on the workers.dev URL until the forum.nodebox.net custom domain is added (after the first deploy). + "workers_dev": true, "observability": { "enabled": true }, "d1_databases": [ { "binding": "DB", "database_name": "nodebox-forum", "database_id": "c66a199f-225d-46eb-baff-0f16b4696a41", "migrations_dir": "migrations" }