SPB Git
5.2 KB

# Deployment — tools.spboucher.ai on MacLustr

# Topology

text
browser ──HTTPS──▶ BHS64 (OVH Beauharnois, 51.161.112.61) Caddy, Let's Encrypt
                     └─ WireGuard wg0 10.67.0.1 ──▶ M2M32c wg1 10.67.0.20 : 8540  (Next.js, PM2 `tools-web`)
                                                      ├─ PM2 `tools-worker` (jobs, sweeps, backups)
                                                      └─ PostgreSQL 17 + pgvector (localhost:5432, db tools_spboucher)
DNS: GoDaddy  A tools.spboucher.ai → 51.161.112.61  (pre-existing, verified 2026-11-02)
Orchestration: M1M32 `mld` (manifest ~/dispatch/apps/tools-spboucher-ai.json), registry, heal every 5 min, route management

# One-time node preparation (done 2026-11-02)

bash
ssh M2M32c
export PATH=/opt/homebrew/opt/postgresql@17/bin:/opt/homebrew/bin:$PATH
createdb tools_spboucher
# roles with generated passwords (printed once; copied into .env)
DATABASE_URL_ADMIN=postgres://localhost:5432/tools_spboucher pnpm db:roles
mkdir -p ~/apps/tools-spboucher-ai ~/backups/tools-spboucher-ai && chmod 700 ~/backups/tools-spboucher-ai
# .env: copy ~/Desktop/Cluster/secrets/tools-spboucher-ai/.env → ~/apps/tools-spboucher-ai/.env (mode 600)

Secrets in .env: DATABASE_URL (tools_app), DATABASE_URL_PUBLIC (tools_reader), DATABASE_URL_ADMIN (owner role for migrations/backups), SESSION_SECRET, WEBAUTHN_RP_ID=tools.spboucher.ai, WEBAUTHN_ORIGIN=https://tools.spboucher.ai, APP_URL, OWNER_EMAIL, OWNER_INITIAL_PASSWORD (changed at first login), BACKUP_PASSPHRASE, BACKUP_DIR, BACKUP_REMOTE, AI_PROVIDER=none.

# Manifest (infra/mld/tools-spboucher-ai.json → M1M32:~/dispatch/apps/)

PM2 tools-web = node node_modules/next/dist/bin/next start -p 8540 -H 0.0.0.0; tools-worker = node node_modules/tsx/dist/cli.mjs src/worker/index.ts. Hooks (post_sync): require .env → pnpm install --frozen-lockfile → pnpm db:migrate + pnpm content:import --publish + pnpm bootstrap → pnpm build (static generation reads the database, so migrations run first). Placement pinned to M2M32c. tunnel.domain = tools.spboucher.ai.

# Release procedure (remote-first after first deploy)

First deploy (creates the app):

bash
mld stage ~/Desktop/Cluster/projets/tools-spboucher-ai tools-spboucher-ai
mld deploy tools-spboucher-ai --node M2M32c      # sync, hooks, PM2, local health, Caddy route, public health
mld adopt tools-spboucher-ai                      # node dir becomes a git clone, origin = spbgit tools-spboucher-ai.git

Subsequent changes (on the node):

bash
ssh M2M32c "cd ~/apps/tools-spboucher-ai && git status --short && git pull"   # or edit on the node
ssh M2M32c "cd ~/apps/tools-spboucher-ai && pnpm install --frozen-lockfile && pnpm build && pnpm db:migrate && pm2 restart tools-web tools-worker"
curl -fsS https://tools.spboucher.ai/api/health
ssh M2M32c "cd ~/apps/tools-spboucher-ai && git add -A && git commit -m '…' && git push origin main"

Migrations are additive and tracked by checksum; pnpm build happens before pm2 restart, so the previous build keeps serving until the switch (seconds of downtime at restart).

# Rollback

bash
ssh M2M32c "cd ~/apps/tools-spboucher-ai && git log --oneline -5"
ssh M2M32c "cd ~/apps/tools-spboucher-ai && git checkout <previous-sha> && pnpm install --frozen-lockfile && pnpm build && pm2 restart tools-web tools-worker"

Schema rollback: migrations never drop data columns; if a migration must be reverted, restore the latest backup into a scratch database (pnpm restore:drill proves the path) and copy rows back, or write a forward migration. The backup taken by mld before sync (~/dispatch/stage) and the nightly encrypted dumps are the recovery points.

# Backups

  • Nightly 04:20 (worker) → ~/backups/tools-spboucher-ai/tools-<stamp>.dump.enc (AES-256-CBC, PBKDF2) → rsync to M3U96b:~/backups/tools-spboucher-ai/; retention 14.
  • Manual: pnpm backup. Drill: pnpm restore:drill (creates tools_restore_drill_*, restores, compares counts + content hash, drops it).
  • Restore for real: openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 -in <file>.enc -out dump -pass env:TOOLS_BACKUP_PASS && pg_restore --clean --if-exists -d tools_spboucher dump.

# Operations

Task Command
Where is it / health mld status --live, curl https://tools.spboucher.ai/api/health
Logs mld logs tools-spboucher-ai or ssh M2M32c pm2 logs tools-web --lines 100
Restart mld restart tools-spboucher-ai
Move to another node mld move tools-spboucher-ai --to <node> (needs Postgres 17 + pgvector there; restore DB from backup first)
Route check mld tunnel status (expect tools.spboucher.ai → 10.67.0.20:8540 (M2M32c))
Re-run link sweep Admin → Jobs → Link sweep, or pnpm links:check on the node
Content update edit content/, pnpm content:validate, pnpm content:import --publish on the node, commit

# Reverse-proxy trust

Caddy forwards X-Forwarded-For/X-Forwarded-Proto; the app reads the first XFF hop for rate limiting and audit only (never for authorization). Cookies are Secure in production; APP_URL/WEBAUTHN_ORIGIN pin the canonical origin for CSRF and passkeys. Apex spboucher.ai is unaffected (no redirect added; other subdomains untouched).

Files · laptop-initial-20261102
Menu

Theme