From d84753c83975be533307cf187247a5957261af15 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 1 Sep 2026 22:23:55 +0100 Subject: [PATCH] Add cloud deployment runbook for the dedicated deploy agent Self-contained operational SOP for an autonomous agent deploying and operating Daybook on a cloud VM: bootstrapping its own scoped SSH deploy key, cloud-specific network/firewall setup, the git-pull redeploy loop with rollback, TLS hardening for internet-facing hosts, and a completion checklist. Co-Authored-By: Claude Sonnet 5 --- deploy/cloud_agent_deploy.md | 231 +++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 deploy/cloud_agent_deploy.md diff --git a/deploy/cloud_agent_deploy.md b/deploy/cloud_agent_deploy.md new file mode 100644 index 0000000..4a12d79 --- /dev/null +++ b/deploy/cloud_agent_deploy.md @@ -0,0 +1,231 @@ +# Cloud Deployment Runbook For A Dedicated Deploy Agent + +This document is written for an autonomous AI agent responsible for deploying +and operating Daybook on a cloud VM, without a human walking through each +step. Follow it in order. Where a decision depends on information you don't +have (VM IP, domain, whether TLS is required), ask the operator once, then +proceed autonomously for routine redeploys. + +For background on the app itself and the general (human-facing) version of +this workflow, see `docs/deployment.md`. This file is the operational SOP; +that one is the reference. + +## Source Of Truth + +The canonical source is a private Forgejo repository: + +```text +https://git.cygnus.systems/peter/daybook.git +``` + +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 + +You need read access to a private repo. Prefer a **repo-scoped SSH deploy +key** over a personal access token — it can only read this one repo, unlike +a token tied to a full user account. + +1. Generate an SSH key pair on the VM (or ask the operator for one to install): + ```bash + ssh-keygen -t ed25519 -C "daybook-deploy-agent" -f ~/.ssh/daybook_deploy -N "" + ``` +2. Ask the operator to add the **public** key (`~/.ssh/daybook_deploy.pub`) as + a Deploy Key on the repo in Forgejo (repo → Settings → Deploy Keys → + read-only is sufficient), or hand it to them directly — never ask them to + give you their personal account credentials. +3. Configure SSH to use it for this host: + ```bash + cat >> ~/.ssh/config <<'EOF' + Host git.cygnus.systems + IdentityFile ~/.ssh/daybook_deploy + IdentitiesOnly yes + EOF + ``` +4. Verify: + ```bash + ssh -T git@git.cygnus.systems + ``` + +If SSH access can't be arranged, fall back to an HTTPS access token the +operator generates (Forgejo → user Settings → Applications → Generate New +Token, read-only `repo` scope) and clone with: + +```bash +git clone https://:@git.cygnus.systems/peter/daybook.git /opt/daybook +``` + +## 1. Prerequisites On The VM + +Confirm before proceeding; install if missing: + +```bash +docker --version || curl -fsSL https://get.docker.com | sh +docker compose version +git --version +``` + +Confirm outbound network access to pull the Python base image and packages, +and confirm you know the VM's public IP or DNS name — you'll need it for +`DJANGO_ALLOWED_HOSTS` and for reporting the final URL back. + +## 2. Clone + +```bash +sudo mkdir -p /opt/daybook +sudo chown "$USER:$USER" /opt/daybook +git clone git@git.cygnus.systems:peter/daybook.git /opt/daybook +cd /opt/daybook +``` + +## 3. Configure Environment + +```bash +cp deploy/env.example deploy/.env +``` + +Generate a real secret key — do not deploy with the placeholder: + +```bash +python3 -c "import secrets; print('DJANGO_SECRET_KEY=' + secrets.token_urlsafe(50))" +``` + +Edit `deploy/.env`: + +```text +DJANGO_DEBUG=0 +DJANGO_SECRET_KEY= +DJANGO_ALLOWED_HOSTS=,,localhost,127.0.0.1 +DAYBOOK_DATA_DIR=/data +DATABASE_PATH=/data/db.sqlite3 +ARTIFACTS_ROOT=/data/artifacts_store +``` + +There is no shared API token to set — Daybook issues a personal token per +user from Settings → Account once a user exists (step 5). Never commit +`deploy/.env`; it is already gitignored. + +## 4. Network Access + +Cloud VMs default to a locked-down security group/firewall, unlike a home +LAN. Before starting the stack, open the port you'll serve on: + +- If exposing Daybook directly: open TCP `8000` to the addresses that should + reach it (ideally not `0.0.0.0/0` unless this is meant to be public). +- If putting a reverse proxy in front (recommended for anything + internet-facing — see §8): open `80`/`443` instead, and keep `8000` closed + to everything except `localhost`. + +This is provider-specific (AWS security group, GCP firewall rule, DigitalOcean +cloud firewall, or the VM's own `ufw`/`iptables`). Ask the operator which +provider/console to use if you don't have API access to it yourself. + +## 5. Build, Start, Create A User + +```bash +mkdir -p deploy/data +docker compose -f deploy/docker-compose.yml up -d --build +docker compose -f deploy/docker-compose.yml exec daybook python manage.py createsuperuser +``` + +The entrypoint runs migrations and collects static files automatically on +every start. + +## 6. Verify + +```bash +docker compose -f deploy/docker-compose.yml ps +curl -fsS http://127.0.0.1:8000/api/capabilities +``` + +A `403` with `"Login or a valid API token is required"` is the **expected** +response when unauthenticated — it confirms the app is up. Log in at +`http://:8000/` (or through your reverse proxy) with the +superuser from step 5, grab a personal token from Settings → Account, then +smoke-test a post: + +```bash +curl -fsS -X POST http://127.0.0.1:8000/api/artifacts \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"source": "deploy-agent", "kind": "markdown", "title": "Deployment Smoke Test", "content": "# OK\n\nDeployed successfully."}' +``` + +Confirm the response includes `id`, `url`, and `api_url`, and that opening +`url` in a browser renders the artifact. + +## 7. Redeploy Loop (routine updates) + +This is the command you run every time you're asked to ship a new version: + +```bash +cd /opt/daybook +PREV_SHA=$(git rev-parse HEAD) +git pull +docker compose -f deploy/docker-compose.yml up -d --build +``` + +Then repeat the verification in step 6 (capabilities check at minimum). If it +fails: + +```bash +git checkout "$PREV_SHA" +docker compose -f deploy/docker-compose.yml up -d --build +``` + +to roll back, then report the failure (build log / migration error / failed +health check) back to the operator rather than retrying blindly. + +`deploy/.env` and `deploy/data/` are untracked, so `git pull` never touches +them — no need to back them up before a routine redeploy. + +## 8. Hardening For Internet-Facing Deployments + +If this VM is reachable from the public internet (not just a private +network/VPN), do this before calling the deployment done: + +- Put a reverse proxy (Caddy, Nginx, or the cloud provider's load balancer) + in front with TLS. Bearer tokens go over plaintext HTTP otherwise. +- Add the proxy's public hostname to `DJANGO_ALLOWED_HOSTS`. +- Keep `DJANGO_DEBUG=0` (already set above). +- Restrict SSH to key-based auth only; disable password login if you have + access to do so. +- Confirm the VM's firewall only allows the ports actually needed (80/443, + and SSH from wherever the operator connects from). + +Caddy is the lowest-effort option (automatic TLS via Let's Encrypt): + +```text +daybook.example.com { + reverse_proxy 127.0.0.1:8000 +} +``` + +## 9. Backup + +```bash +tar -czf /tmp/daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data +``` + +Copy the resulting archive off the VM (to object storage, the operator's +machine, etc.) — a backup that only exists on the VM it's protecting against +doesn't help if the VM is lost. + +## Deployment Checklist + +Before reporting the deployment complete, confirm all of these: + +- [ ] Cloned via git (not copied), with a working `git pull`. +- [ ] `deploy/.env` has a real, non-placeholder `DJANGO_SECRET_KEY`. +- [ ] `DJANGO_ALLOWED_HOSTS` includes the actual hostname/IP being used. +- [ ] `docker compose -f deploy/docker-compose.yml ps` shows `daybook` healthy. +- [ ] `/api/capabilities` returns `403` unauthenticated, `200` with a token. +- [ ] At least one superuser exists with a working personal API token. +- [ ] A smoke-test artifact posts and renders successfully. +- [ ] If internet-facing: TLS is in front of the app, not raw HTTP on `8000`. +- [ ] A backup of `deploy/data/` has been taken and copied off the VM. + +Report back to the operator: the app URL, confirmation of the checklist +above, and the deploy SSH key fingerprint or token identity used (not the +key/token itself) for their records.