Fix healthcheck + verify commands; reframe deploy runbook for a capable agent

Real bug: the container healthcheck (and the docs' verify step) hit
/api/capabilities, which returns 403 unauthenticated; curl --fail treats
403 as an error, so a healthy container would report "unhealthy" and the
verify step would look like a failure. Healthcheck now hits /accounts/login/
(200, no auth). Verify commands now print the status code and note not to
use curl -f on the 403.

Also reframed cloud_agent_deploy.md's intro for an Opus-class deploy agent:
it's a guide + Daybook-specific gotchas, not a rigid script — points at the
authoritative sources (repo incl. the git-SSH :2222 note, deploy/ files,
requirements.txt deps auto-installed, entrypoint runs migrate+collectstatic,
docs/deployment.md) and trusts the agent's judgment. Dropped the now-
redundant Source Of Truth section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
peter 2026-09-03 14:32:39 +01:00
parent a355285f3b
commit ca8e364010
3 changed files with 41 additions and 24 deletions

View file

@ -1,25 +1,33 @@
# Cloud Deployment Runbook For A Dedicated Deploy Agent # Cloud Deployment Runbook For A Dedicated Deploy Agent
This document is written for an autonomous AI agent responsible for deploying This is written for a capable (Opus-class) agent deploying and operating
and operating Daybook on a cloud VM, without a human walking through each Daybook on a cloud VM. Treat it as a guide and a source of Daybook-specific
step. Follow it in order. Where a decision depends on information you don't facts, **not a rigid script** — you're expected to use judgment, adapt to
have (VM IP, domain, whether TLS is required), ask the operator once, then the actual VM, and read the authoritative sources rather than follow steps
proceed autonomously for routine redeploys. blindly. The steps below are a sensible default path; deviate where the host
warrants it.
For background on the app itself and the general (human-facing) version of Ground truth, in order of authority (prefer these over this doc if they ever
this workflow, see `docs/deployment.md`. This file is the operational SOP; disagree):
that one is the reference.
## Source Of Truth - **The repo** — `https://git.cygnus.systems/peter/daybook.git` (private
Forgejo). Everything ships from here; deploy from a clone so `git pull`
works. Note: this Forgejo's git-SSH is on port **2222**, not 22.
- **`deploy/`** — `Dockerfile`, `docker-compose.yml`, `entrypoint.sh`,
`env.example`. These *are* the deployment: the image installs all Python
deps from `requirements.txt` (Django, DRF, Markdown, Pygments, nh3,
openpyxl, gunicorn, whitenoise), and `entrypoint.sh` runs `migrate` +
`collectstatic` on every start, so schema and static assets are always
current. Read them; don't assume.
- **`docs/deployment.md`** — the general (host-agnostic) deployment
reference and rationale.
The canonical source is a private Forgejo repository: Where a decision needs info you don't have (VM IP, domain, whether it's
internet-facing, whether a reverse proxy/secondary volume exists), ask the
```text operator once, then proceed autonomously for routine redeploys. The rest of
https://git.cygnus.systems/peter/daybook.git this file is the recommended path plus the non-obvious, Daybook-specific
``` gotchas (SSH port, CSRF-behind-proxy, bind address, data volume, healthcheck)
that are easy to get wrong.
Do not deploy from a copied/rsynced tree. Always deploy from a git clone of
this repository so `git pull` is available for fast redeploys.
## 0. Get Repo Access ## 0. Get Repo Access
@ -166,11 +174,14 @@ every start.
```bash ```bash
docker compose -f deploy/docker-compose.yml ps docker compose -f deploy/docker-compose.yml ps
curl -fsS http://127.0.0.1:8000/api/capabilities curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/api/capabilities # expect 403
``` ```
A `403` with `"Login or a valid API token is required"` is the **expected** A `403` with `"Login or a valid API token is required"` is the **expected**
response when unauthenticated — it confirms the app is up. This `curl` runs response when unauthenticated — it confirms the app is up. (Don't use
`curl -f` here: `--fail` treats the 403 as an error and hides it. The
container's own healthcheck hits the login page instead, which is 200
without auth.) This `curl` runs
against `127.0.0.1` from on the VM itself, so it works regardless of whether against `127.0.0.1` from on the VM itself, so it works regardless of whether
`DAYBOOK_BIND_ADDR` is set. Log in from a browser at whichever address is `DAYBOOK_BIND_ADDR` is set. Log in from a browser at whichever address is
actually externally reachable at this point in the runbook — `http://<vm-ip>:8000/` actually externally reachable at this point in the runbook — `http://<vm-ip>:8000/`

View file

@ -22,7 +22,11 @@ services:
volumes: volumes:
- ${DAYBOOK_HOST_DATA_DIR:-./data}:/data - ${DAYBOOK_HOST_DATA_DIR:-./data}:/data
healthcheck: healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/api/capabilities"] # Hit the login page, not /api/capabilities: capabilities returns 403
# when unauthenticated, and `curl --fail` treats 403 as an error, which
# would mark a perfectly healthy container unhealthy. The login page
# serves 200 without auth and still proves Django is up and responding.
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/accounts/login/"]
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 5 retries: 5

View file

@ -86,11 +86,13 @@ docker compose -f deploy/docker-compose.yml exec daybook python manage.py create
```bash ```bash
docker compose -f deploy/docker-compose.yml ps docker compose -f deploy/docker-compose.yml ps
curl -fsS http://127.0.0.1:8000/api/capabilities curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/api/capabilities # expect 403
``` ```
An unauthenticated capabilities check returns 403 (login/token required) — An unauthenticated capabilities check returns 403 (login/token required) —
that's expected; it confirms the app is up and enforcing auth. Open that's expected; it confirms the app is up and enforcing auth. (Don't add
`curl -f` — it treats the 403 as an error. The container healthcheck uses
the login page, which is 200 without auth.) Open
`http://<vm-ip>:8000/` in a browser and log in with the superuser you just `http://<vm-ip>:8000/` in a browser and log in with the superuser you just
created, then grab a personal token from Settings → Account and use it for a created, then grab a personal token from Settings → Account and use it for a
smoke-test post: smoke-test post:
@ -187,8 +189,8 @@ Caddy: header_up X-Forwarded-Proto https
proxy forwards `X-Forwarded-Proto: https` (login will 403 otherwise). proxy forwards `X-Forwarded-Proto: https` (login will 403 otherwise).
- `deploy/data/` exists and persists across `git pull` + rebuild. - `deploy/data/` exists and persists across `git pull` + rebuild.
- `docker compose -f deploy/docker-compose.yml ps` shows `daybook` healthy. - `docker compose -f deploy/docker-compose.yml ps` shows `daybook` healthy.
- `curl -fsS http://127.0.0.1:8000/api/capabilities` returns 403 without auth, - `GET /api/capabilities` returns 403 without auth, 200 with a valid token or
200 with a valid token or session. session (check the status code — don't use `curl -f`, which errors on 403).
- At least one superuser exists (`manage.py createsuperuser`) with a token - At least one superuser exists (`manage.py createsuperuser`) with a token
from Settings → Account. from Settings → Account.