daybook/deploy/cloud_agent_deploy.md
peter d84753c839 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 <noreply@anthropic.com>
2026-09-01 22:23:55 +01:00

231 lines
7.7 KiB
Markdown

# 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://<username>:<token>@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=<generated-secret>
DJANGO_ALLOWED_HOSTS=<vm-public-ip>,<domain-if-any>,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://<vm-ip-or-domain>: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 <token-from-settings>" \
-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.