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
26 changes: 26 additions & 0 deletions .github/workflows/forum.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# 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:
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Replaces the Tender-hosted NodeBox forum with our own on Cloudflare. `docs/PLAN.

- Branch policy: commit straight to `main` and push. No pull requests.
- Verify: `npm test && npm run typecheck` in the root, and the same two commands in `web/`.
- Deploy: none yet. The Cloudflare resources do not exist; `web/README.md` lists what to create.
- Deploy: Cloudflare Workers Builds deploys `web/` on every push to `main` (tests, D1 migrations, then deploy). Setup and rollback: `web/DEPLOY.md`.
- Secrets come from 1Password: `npm run env` writes `.env` from `.env.template`.

## Rules of the data
Expand Down
1 change: 1 addition & 0 deletions web/.node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
57 changes: 57 additions & 0 deletions web/DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Deploying the forum

Cloudflare Workers Builds deploys the forum on every push to `main`. In the
Worker's settings (Workers & Pages → nodebox-forum → Settings → Build):

| 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).

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

| What | Where |
| --- | --- |
| D1 database | Created; its id is in `wrangler.jsonc` |
| R2 bucket | `npx wrangler r2 bucket create nodebox-forum` |
| 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` (`op://NodeBox/NodeBox Forum/Cloudflare/…`) and `ANTHROPIC_API_KEY` (spam filter) from 1Password |
| Git connection | The build settings above. Connect it last: the first build deploys |

## First deploy

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`
4. Upload the old attachments to R2 and Images (script still to write).
5. Make the first admin: `npm run admin -- grant <email> --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 <ISO time>`.
Migrations only go forward; a bad one is fixed by a new migration or a restore.
18 changes: 4 additions & 14 deletions web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,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`, and `ANTHROPIC_API_KEY`
for the spam filter:
`op read "op://NodeBox/NodeBox Forum/Anthropic/ANTHROPIC_API_KEY" | npx wrangler secret put ANTHROPIC_API_KEY`.
Without that key no post is checked.
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).
6 changes: 5 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
"scripts": {
"env": "op inject -f -i .env.template -o .env && chmod 600 .env",
"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",
"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",
"typecheck": "tsc --noEmit && tsc --noEmit -p client",
"test": "node --disable-warning=ExperimentalWarning --test test/*.test.ts",
Expand Down
11 changes: 11 additions & 0 deletions web/scripts/put-secrets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/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
# The spam filter for new posts; without it no post is checked.
op read "op://NodeBox/NodeBox Forum/Anthropic/ANTHROPIC_API_KEY" | npx wrangler secret put ANTHROPIC_API_KEY
20 changes: 12 additions & 8 deletions web/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
"compatibility_date": "2026-09-01",
"compatibility_flags": ["nodejs_compat"],
"assets": { "directory": "public", "binding": "ASSETS" },
// 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": [
// 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" }],
Expand All @@ -20,17 +22,19 @@
"vars": {
"SITE_NAME": "NodeBox Forum",
// Absolute origin, used in sign-in emails and the Google redirect URI.
"PUBLIC_URL": "http://localhost:8787",
// Cloudflare Images: ids and variant names are global to the account, so everything is prefixed.
// These are the production values; `npm run dev` overrides PUBLIC_URL and MAILER.
"PUBLIC_URL": "https://forum.nodebox.net",
// 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",
// "log" prints sign-in links to the console (development); "cloudflare" sends real mail.
"MAILER": "log",
"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"
}
// 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)
// ANTHROPIC_API_KEY (spam filter on new posts; without it nothing is checked)
// Deploys run in Cloudflare Workers Builds on every push to main (web/DEPLOY.md).
}
Loading