Phase moves to "live in production"; last-commit pointer and open-issues list reflect the deploy pipeline and proxy-headers fix. Session 3 summary added; Session 1 rotates off to the wiki (per the "most recent 3" rule). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
147 lines
6.1 KiB
Markdown
147 lines
6.1 KiB
Markdown
# 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 <reason>
|
|
```
|
|
|
|
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: `<dir-of-db-file>/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://<internal>/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).
|
|
|