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>
This commit is contained in:
parent
4a5ea8944b
commit
d84753c839
1 changed files with 231 additions and 0 deletions
231
deploy/cloud_agent_deploy.md
Normal file
231
deploy/cloud_agent_deploy.md
Normal file
|
|
@ -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://<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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue