Shelfie Runbook
Operational notes for Edward (and future contributors).
Environments
- Local dev.
npm run devon Windows.SHELFIE_DB_MODE=localreads fromfixtures/library.jsonand overrides live in browser localStorage. Nothing leaves the laptop. - Hosted dev. Same code,
SHELFIE_DB_MODE=supabase, talks to the Supabase projectwvuctaabeoncnptuourdover the transaction pooler. - Production. Vercel project at
shelfiebook.com, same Supabase project, environment variables sourced from Vercel project settings.
Backups
Postgres (Supabase)
- Free plan: no automated backups. Upgrade to Pro before launch -- it enables daily Postgres snapshots retained for 7 days, point-in-time recovery for 24 hours.
- Manual snapshot any time:
Store underpg_dump $env:SUPABASE_DB_URL --no-owner --no-privileges --format=custom --file backup-$(Get-Date -Format yyyyMMdd-HHmm).dumpC:\Users\edwar\Claude\shelfie-backups\(gitignored separately) and copy to a cold archive (Backblaze B2 or external drive).
R2 (photos)
- Enable bucket versioning. R2 retains every overwritten / deleted object for 14 days by default once versioning is on.
- Quarterly: export the bucket via
rclone copy r2:shelfie-photos /backups/r2/$(Get-Date -Format yyyyMMdd).
Code
- GitHub mirror at
ERoske/shelfie(private).git pushafter every working commit. - Local copy at
C:\Users\edwar\Claude\shelfieis the canonical workspace.
Restore drills (monthly)
- Spin up a throwaway Supabase project in the same region.
pg_restore --clean --no-owner --dbname=$RESTORE_URL latest.dump- Run
npm run devagainst the restored URL. Verify a known book renders. - Time how long the drill took. If over 30 minutes, revisit the snapshot strategy.
- Tear down the throwaway project.
Soft-delete
libraries.deleted_at,books.deleted_at. Default queries filterdeleted_at IS NULL.- Trash UI restores by setting
deleted_at = NULL. - Hard-delete only via a support workflow: confirm with the user, then
delete from books where id = $1 and deleted_at < now() - interval '30 days'.
Secrets rotation
- Supabase service-role key: rotate quarterly via Supabase dashboard. Replace in Vercel env + local
.env.local. - Database password: rotate annually; update
SUPABASE_DB_URL. - Anthropic key: rotate when team changes; replace in Vercel env.
On-call basics
- Vercel deploy alerts -> Edward's email.
- Supabase project alerts (CPU, disk, connections) -> Supabase dashboard, configure Slack/email when team grows.
- Logs: Vercel dashboard for HTTP, Supabase dashboard for Postgres slow queries.
- Sentry (set
SENTRY_DSN,NEXT_PUBLIC_SENTRY_DSN,SENTRY_ORG,SENTRY_PROJECT, optionalSENTRY_AUTH_TOKEN). Source maps upload only whenSENTRY_AUTH_TOKENis set. Smoke test in production:curl -H "x-shelfie-test: 1" https://shelfiebook.com/api/sentry-check?token=enabled-- only fires whenSENTRY_CHECK_TOKEN=enabled.
Publish CLI / MCP packages
One-time setup on Edward's machine:
npm login # opens browser, authenticates
npm whoami # confirms
Then from the repo root:
npm run publish:mcp # publishes shelfie-mcp@<current>
npm run publish:cli # publishes shelfie-cli@<current>
# or both:
npm run publish:packages
Bump mcp/package.json and cli/package.json versions before re-publishing -- npm rejects the same version twice.
Cron jobs
Vercel cron (configured in vercel.json):
/api/cron/drip-- daily 14:00 UTC. Sends welcome / day-2 / day-5 / day-14 onboarding emails via Resend, idempotent per (user, stage)./api/cron/embeddings-- daily 07:30 UTC. Embeds new books into pgvector via Voyagevoyage-3.
Both require CRON_SECRET to authorize requests. Set it in Vercel project env (Production + Preview).
Embeddings backfill
If new books pile up unembedded, kick the cron manually:
$env:CRON_SECRET="..."
curl -H "Authorization: Bearer $env:CRON_SECRET" https://shelfiebook.com/api/cron/embeddings
The cron will keep chewing 1024 books per run until caught up.