# 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.