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:
parent
a355285f3b
commit
ca8e364010
3 changed files with 41 additions and 24 deletions
|
|
@ -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/`
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue