diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d052f8..7642e52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +## Unreleased + +### Ops +- Add `ecosystem.config.cjs.example` template — the real `ecosystem.config.cjs` + remains gitignored, but the template documents the canonical shape and the + locally-managed-tunnel trap. + +### Notes for future ops debugging + +If `api.thehumanpatternlab.com` ever returns Cloudflare **error 1033 / HTTP 530** +("Argo Tunnel error / no available origin"), `pm2 logs cf-tunnel --err` will +usually show one of two things: + +1. **`Provided Tunnel token is not valid.`** — the cf-tunnel `args` block in + `ecosystem.config.cjs` is using the `--token ` form, but the tunnel was + created locally (`cloudflared tunnel create`). Locally-managed tunnels + authenticate via the cred file at `~/.cloudflared/.json`, not a JWT. + Fix: change the args to `["tunnel", "run", ""]`. See the + `ecosystem.config.cjs.example` header comment for the full picture. + +2. **PM2 process listed but with `▒▒` status / 0b memory / hundreds of restarts** + — half-zombie entry. Underlying cloudflared process is gone or detached but + PM2 still has the registry entry. Fix: `pm2 delete cf-tunnel && pm2 start + ecosystem.config.cjs --only cf-tunnel && pm2 save`. + ## v0.2.0 — Ledger Era + Canonical API Base ### Breaking diff --git a/ecosystem.config.cjs.example b/ecosystem.config.cjs.example new file mode 100644 index 0000000..f3726be --- /dev/null +++ b/ecosystem.config.cjs.example @@ -0,0 +1,76 @@ +/** + * PM2 Ecosystem — Human Pattern Lab (TEMPLATE) + * + * Copy this file to `ecosystem.config.cjs` on the production server and + * fill in the placeholder values. The real `ecosystem.config.cjs` is + * gitignored because it contains secrets — keep it that way. + * + * Topology: + * - lab-api runs on localhost:8001 + * - cf-tunnel exposes it via a Cloudflare Tunnel managed *locally* + * (cred file at ~/.cloudflared/.json + ~/.cloudflared/config.yml) + * + * IMPORTANT — locally-managed vs remote-managed tunnel auth: + * - Locally-managed tunnels authenticate via the cred JSON file. Run + * them with: `cloudflared tunnel run ` + * - Remote-managed tunnels (created in the Cloudflare Zero Trust + * dashboard) authenticate via a JWT-shaped `--token <...>` arg. + * - The two formats are MUTUALLY EXCLUSIVE. Feeding a remote-management + * token to a locally-managed tunnel produces an endless loop of: + * "Provided Tunnel token is not valid." + * If you see that error in `pm2 logs cf-tunnel --err`, this is the + * mismatch — confirm `~/.cloudflared/.json` exists and use the + * `args: ["tunnel", "run", ""]` form below. + */ + +module.exports = { + apps: [ + { + name: "lab-api", + cwd: "/home//lab-api", + script: "dist/index.js", + interpreter: "/home//.nvm/versions/node/v20.19.6/bin/node", + exec_mode: "fork", + time: true, + env: { + NODE_ENV: "production", + PORT: "8001", + + // Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))" + SESSION_SECRET: "REPLACE_ME_WITH_A_RANDOM_SECRET", + + UI_BASE_URL: "https://thehumanpatternlab.com", + + // TOKEN_PEPPER and other secrets are loaded from `.env` + // by dotenv at app startup (see src/env.ts). Don't put + // them here unless you also remove them from `.env` — + // having them in both can produce subtle override bugs. + }, + max_restarts: 10, + restart_delay: 2000, + }, + + { + name: "cf-tunnel", + script: "/home//bin/cloudflared", + + // Locally-managed tunnel — references the tunnel by name as + // defined in ~/.cloudflared/config.yml. cloudflared loads + // ~/.cloudflared/.json automatically for auth. + args: ["tunnel", "run", "lab-api"], + + // ⚠️ Do NOT use the "--token " form here for a tunnel + // that was created with `cloudflared tunnel create`. See the + // header comment for why. + + auto_restart: true, + restart_delay: 5000, + time: true, + max_restarts: 20, + + out_file: "/home//lab-api/logs/cloudflared.out.log", + error_file: "/home//lab-api/logs/cloudflared.err.log", + merge_logs: true, + }, + ], +};