SPB Git

Repository Public

OPERATIONS.md 5.8 KB

# Operations

# Where things are

What Where
Site https://calendar.spboucher.ai (Caddy on BHS64 → WireGuard → M4M64c:8660)
Node M4M64c (pinned). Source of truth: M4M64c:~/apps/sp-calendar (git, origin gitsrv:srv/git/sp-calendar.git)
Processes (PM2) sp-calendar-web (next start), sp-calendar-worker (tsx), sp-calendar-backup (cron 03:35)
Database PostgreSQL 17 on M4M64c, database sp_calendar, role spcal_app
Secrets M4M64c:~/apps/sp-calendar/.env (600), ~/.config/sp-calendar/backup-passphrase (600); laptop copy ~/Desktop/Cluster/secrets/sp-calendar/
Backups M4M64c:~/backups/sp-calendar/*.dump.enc (30 days)
Enrolment link M4M64c:~/data/sp-calendar/setup-url.txt (first passkey, valid 7 days from the first deploy)
mld manifest M1M32:~/dispatch/apps/sp-calendar.json (copy: infra/mld/sp-calendar.json)

# First sign-in (owner)

  1. ssh M4M64c cat ~/data/sp-calendar/setup-url.txt and open the link on the device whose Touch ID / Face ID you want to use (Safari or Chrome). Expired? ssh M4M64c 'cd ~/apps/sp-calendar && node_modules/.bin/tsx scripts/setup-token.ts'.
  2. Create the passkey, then store the eight recovery codes.
  3. In Settings → Security, add a second passkey from another device (the dashboard reminds you until you do). With iCloud Keychain the passkey syncs to your other Apple devices by itself.

# Recovery

  • A device is lost but you have a recovery code: sign-in page → "Utiliser un code de récupération", then add a passkey.
  • Everything is lost: ssh M4M64c 'cd ~/apps/sp-calendar && node_modules/.bin/tsx scripts/setup-token.ts --recovery' (30-minute link; enrolling with it removes every other passkey and signs out every session).

# Changing the application (remote-first)

bash
ssh M4M64c
cd ~/apps/sp-calendar            # edit here
export PATH=/opt/homebrew/opt/postgresql@17/bin:/opt/homebrew/bin:$PATH
pnpm install --frozen-lockfile   # only if dependencies changed
node_modules/.bin/tsx scripts/migrate.ts          # applies new db/migrations/*.sql once each (checksummed)
node_modules/.bin/next build && pm2 restart sp-calendar-web sp-calendar-worker
curl -s http://127.0.0.1:8660/api/health
git add -A && git commit -m "…" && git push origin main

Migrations are append-only: never edit an applied file (the runner refuses a changed checksum). mld deploy sp-calendar redeploys from the gateway's staging and refuses when the node repo is ahead (by design).

# E-mail

.env: MAIL_TRANSPORT=resend + RESEND_API_KEY=… + MAIL_FROM="SP Calendar <calendar@your-verified-domain>", or MAIL_TRANSPORT=smtp + SMTP_URL=smtps://user:pass@host:465. Then pm2 restart sp-calendar-web sp-calendar-worker --update-env, and in the admin area: Settings → Notifications → send a test e-mail, then "Remettre en file" for the notifications parked while the transport was not configured. The log shows, per message, the transport's message id or the error. MAIL_TRANSPORT=none parks everything.

# Health and monitoring

  • GET /api/health → { ok, db, worker: { ok, ageSec }, mail: { configured } } (503 when the database is down). mld heal (every 5 minutes on M1M32) restarts missing PM2 processes and repairs the tunnel route.
  • Dashboard alerts: worker stopped, e-mail not configured / parked / dead, stale external calendar, request whose time is no longer free, single passkey.
  • Logs: pm2 logs sp-calendar-web, pm2 logs sp-calendar-worker. No request body is logged.
  • Audit trail: Settings → Audit (who did what, when).

# Backups and restore

Daily 03:35: pg_dump → AES-256 → ~/backups/sp-calendar/, verified by decrypting and listing the archive. Run now: pm2 restart sp-calendar-backup (or bash scripts/backup.sh).

Restore test (do it after any change to the backup script, and from time to time):

bash
createdb sp_calendar_restore_test
bash scripts/restore.sh ~/backups/sp-calendar/<latest>.dump.enc postgres://127.0.0.1:5432/sp_calendar_restore_test
psql -d sp_calendar_restore_test -c "select count(*) from bookings"; dropdb sp_calendar_restore_test

Real restore: stop both processes, restore into an empty sp_calendar owned by spcal_app, start them. The passphrase is NOT in the backups: keep ~/Desktop/Cluster/secrets/sp-calendar/backup-passphrase safe. Off-node copy of the backups is not set up (see PLAN.md).

# Moving to another node

The database does not follow mld move. Stop the processes, scripts/backup.sh, create role and database on the new node, copy .env and the passphrase, scripts/restore.sh, change the pin in the manifest, mld deploy. Passkeys keep working (they are bound to the domain).

# Tests

bash
pnpm typecheck && pnpm lint
pnpm test            # Vitest against a real PostgreSQL (database sp_calendar_test, created empty beforehand)
scripts/e2e.sh       # Playwright, desktop + mobile, production build, throw-away database sp_calendar_e2e,
                     # real SMTP transport into a local sink, WebAuthn with Chromium's virtual authenticator
pnpm secrets:scan

# Troubleshooting

Symptom Check
Passkey prompt refuses The site must be opened at exactly APP_URL's host; passkeys are bound to calendar.spboucher.ai.
"Lien d'enregistrement invalide" One use, limited life: issue a new one with scripts/setup-token.ts.
Visitor links all invalid APP_SECRET changed: links derive from it. Restore the previous value.
No e-mail leaves Settings → Notifications: transport state and per-message error.
Public page shows no slots Availability (weekly hours, exceptions), type's notice / horizon / limits, private events, external calendars set to block.
Automatic confirmations became requests An external calendar is stale (Settings → Integrations); policy in Settings → Profile.
Files main
Menu

Theme