# Quartermaster (repo instructions) ## Database safety rule The SQLite database at `./quartermaster.db` (or whatever path `QUARTERMASTER_DB_URL` points at) holds real user data. It is `.gitignored`, so losing it means the data is gone. **Before any operation that interacts with the database at the schema or destructive level**, run: ```sh ./scripts/backup-db.sh ``` That includes: * `rm`, `mv`, truncate, or otherwise replace `quartermaster.db` * Ad-hoc `sqlite3` sessions that might `DROP`, `DELETE`, or `UPDATE` * Any script that does data migration or cleanup outside the running application Alembic runs `scripts/backup-db.sh alembic` automatically on every `alembic upgrade`, `alembic downgrade`, or `alembic revision --autogenerate` via the hook in `alembic/env.py`, so you do not need to call it manually for ordinary schema work. The hook is defense in depth, not a substitute for thinking. Routine writes through the running web application are NOT covered by this rule. Those are normal application behaviour, not "interactions" in the sense meant here. ## Where backups go Default: `/backups/quartermaster-YYYYMMDD-HHMMSS-{slug}.db`. For the standard `./quartermaster.db` that resolves to `./backups/`. Override with `QUARTERMASTER_BACKUP_DIR=/some/path`. The `backups/` directory is `.gitignored`. Retention is forever: backups are small. Prune manually if you need to. ## Restoring A backup is a complete SQLite file. To restore, stop the app, replace `quartermaster.db` with the chosen backup (`cp backups/... quartermaster.db`), then restart. ## Current Project State * **Phase**: live in production on home-ctr-onyx at `https://quartermaster.unbiasedgeek.com/`. Every merge to `main` rolls out automatically via `.forgejo/workflows/deploy.yml`. * **Last worked on**: 2026-04-19 * **Last commit on main**: `ee6eaae` — fix(docker): enable uvicorn proxy-headers so url_for works behind Traefik * **Open PRs**: none * **Open issues**: #23 MCP proposal; #31 small cleanups (non-blocking polish); #26 and #27 are landed but weren't closed when their work merged — safe to close. * **Test count**: 148 / 148 passing * **Migrations**: 5 applied; latest `cc60e7f73a1c` * **Blocking issues**: none After pulling new work, always: ```sh uv run alembic upgrade head ``` The backup hook fires before any migration, so this is safe against the live DB. ## Session Log Most recent 3 sessions (full history in the [wiki](https://forgejo.labbity.unbiasedgeek.com/archeious/quartermaster/wiki)). ### Session 3 — 2026-04-19 Deploy-pipeline arc: four PRs (#32 Dockerfile, #33 compose.yml, #34 Forgejo Actions workflow, #35 post-deploy proxy-headers fix) took Quartermaster from "deploy prep merged" to live on `https://quartermaster.unbiasedgeek.com/`. All three dependency-chained issues (#28/#29/#30) closed. Key decision, locked mid-flight: the deploy workflow has **no SSH step**. Initial draft used an SSH-from-runner-to-host pattern with `DEPLOY_SSH_KEY` + `DEPLOY_KNOWN_HOSTS` secrets. Jeff pushed back — the `homelab` runner lives on home-ctr-onyx itself with the host's Docker socket mounted, so `docker compose pull && up -d` runs directly against the same daemon that hosts production. Dropped two secrets and the private-key risk surface. Remaining Actions secrets: `REGISTRY_TOKEN` (archeious PAT, `read:package` + `write:package`, minted via the Forgejo API using the admin password exposed by `homelab-IaC/bin/load-ops-secrets`) and `QUARTERMASTER_SMOKE_PASSWORD` (plaintext basic-auth for the post-deploy `/healthz` probe). Other design points: image tag parameterised via `QUARTERMASTER_TAG` (workflow writes a per-deploy `.env`), `COMPOSE_PROJECT_NAME=quartermaster` pinned so the runner's ephemeral workspace path doesn't confuse compose, smoke step does `curl -u admin:… https://quartermaster.unbiasedgeek.com/healthz` to catch TLS + routing + basic-auth regressions in one probe. One post-deploy bug: the first rolled image rendered unstyled because uvicorn was started without `--proxy-headers`, so Starlette ignored `X-Forwarded-Proto` from Traefik and `url_for()` generated `http:///static/…` hrefs, which browsers blocked as mixed content on the `https://` page. Reproduced locally by curling the pre-fix image with Traefik-style headers; added `--proxy-headers --forwarded-allow-ips='*'` to `docker/entrypoint.sh` in #35. Safe to trust all forwarded IPs because `compose.yml` publishes no host port — only Traefik on `proxy-net` can reach port 8000. Full retro: [Session3](https://forgejo.labbity.unbiasedgeek.com/archeious/quartermaster/wiki/Session3). ### Session 2 — 2026-04-19 Platform contract intake (#25) filled out and accepted; platform team provisioned DNS (`quartermaster.unbiasedgeek.com`), Traefik middlewares (basic-auth + rate-limit), the `/mnt/quartermaster/` bind mount, and basic-auth creds. Two issues landed on main (#26 `/healthz`, #27 structured JSON logs) via the superpowers brainstorm → spec → plan → subagent-driven-TDD workflow. Thirteen commits on a single branch (`feat/platform-deploy-prep`), rebased onto origin/main so history stays linear despite the unrelated MCP-doc PR that landed alongside. Key decisions: single-source-of-truth `logconfig.json` (not a Python dict + YAML shim, which would need `pyyaml` and introduce drift); five seed app events (`month_created`, `month_closed`, `template_entry_updated`, `posting_added`, `posting_deleted`) placed after commit + refresh so they only fire on durable success; `/healthz` on a dedicated router as a file boundary, with auth living in Traefik per the platform contract. Two real misses caught by code review mid-flight: the autouse-fixture workaround for Task 3's `dictConfig` state leak was wrong (fix: make the contaminating test save/restore state itself); the third LogQL example in README used `{{.path}}` on `month_closed` records that carry `year_month`, not `path`. Both landed as fix commits. Deploy-pipeline work queued as #28 (Dockerfile), #29 (compose.yml), #30 (Forgejo Actions), dependency-chained. Polish umbrella at #31. Full retro: [Session2](https://forgejo.labbity.unbiasedgeek.com/archeious/quartermaster/wiki/Session2).