v0.3.0: rename app Daybook -> AgentFeed
Removes the "Daybook" name from everything user- and agent-facing, since no agents are configured yet and this is the cheap moment to do it. - UI brand, docs, README, CLAUDE.md -> AgentFeed. - Ingest env vars DAYBOOK_URL/TOKEN/SOURCE/DATA_DIR/BIND_ADDR/HOST_DATA_DIR/ KEY_PROVISIONING_API -> AGENTFEED_* (token *values* unchanged). - Skill: skills/daybook-posting -> skills/agentfeed-posting, daybook_post.py -> agentfeed_post.py (+ scripts/ wrapper and its test). - static/daybook.css -> static/agentfeed.css; localStorage daybook.* -> agentfeed.*; templatetag daybook_extras -> agentfeed_extras; multipart boundaries; json/csv viewer element ids; daybook_exception_handler -> agentfeed_exception_handler; compose service/container -> agentfeed. - Key-provisioning API now OFF by default (independent of DEBUG); enable with AGENTFEED_KEY_PROVISIONING_API=1 on test installs only. - Posting helper now points at Settings -> API Keys. Deferred on purpose (a later, separate step): the Django package `daybook_project`, the `/opt/daybook` install path, and the Forgejo repo name. The sibling AGENT_DAYBOOK__LLM_TEST name is intentionally untouched. Full suite: 129 passing; live server verified serving under the new name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
4cacd8f64e
commit
d30e2436ab
39 changed files with 303 additions and 284 deletions
|
|
@ -1,7 +1,7 @@
|
|||
# Cloud Deployment Runbook For A Dedicated Deploy Agent
|
||||
|
||||
This is written for a capable (Opus-class) agent deploying and operating
|
||||
Daybook on a cloud VM. Treat it as a guide and a source of Daybook-specific
|
||||
AgentFeed on a cloud VM. Treat it as a guide and a source of AgentFeed-specific
|
||||
facts, **not a rigid script** — you're expected to use judgment, adapt to
|
||||
the actual VM, and read the authoritative sources rather than follow steps
|
||||
blindly. The steps below are a sensible default path; deviate where the host
|
||||
|
|
@ -25,7 +25,7 @@ disagree):
|
|||
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
|
||||
operator once, then proceed autonomously for routine redeploys. The rest of
|
||||
this file is the recommended path plus the non-obvious, Daybook-specific
|
||||
this file is the recommended path plus the non-obvious, AgentFeed-specific
|
||||
gotchas (SSH port, CSRF-behind-proxy, bind address, data volume, healthcheck)
|
||||
that are easy to get wrong.
|
||||
|
||||
|
|
@ -37,9 +37,9 @@ 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 ""
|
||||
ssh-keygen -t ed25519 -C "agentfeed-deploy-agent" -f ~/.ssh/agentfeed_deploy -N ""
|
||||
```
|
||||
2. Ask the operator to add the **public** key (`~/.ssh/daybook_deploy.pub`) as
|
||||
2. Ask the operator to add the **public** key (`~/.ssh/agentfeed_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.
|
||||
|
|
@ -52,7 +52,7 @@ a token tied to a full user account.
|
|||
cat >> ~/.ssh/config <<'EOF'
|
||||
Host git.cygnus.systems
|
||||
Port 2222
|
||||
IdentityFile ~/.ssh/daybook_deploy
|
||||
IdentityFile ~/.ssh/agentfeed_deploy
|
||||
IdentitiesOnly yes
|
||||
EOF
|
||||
```
|
||||
|
|
@ -112,12 +112,12 @@ Edit `deploy/.env`:
|
|||
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
|
||||
AGENTFEED_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
|
||||
There is no shared API token to set — AgentFeed issues a personal token per
|
||||
user from Settings → Account once a user exists (step 5). Never commit
|
||||
`deploy/.env`; it is already gitignored.
|
||||
|
||||
|
|
@ -131,17 +131,17 @@ if unsure which applies here.
|
|||
## 4. Network Access
|
||||
|
||||
**If this VM is reachable from the public internet, a reverse proxy with TLS
|
||||
in front of Daybook is not optional — it's the only path.** Bearer tokens are
|
||||
in front of AgentFeed is not optional — it's the only path.** Bearer tokens are
|
||||
this app's entire write-auth model, and they'd otherwise go over plaintext
|
||||
HTTP straight to a port on the public IP. Direct port exposure (below) is
|
||||
only for private/VPN-only networks with no public exposure.
|
||||
|
||||
- **Internet-facing (the common case):** set `DAYBOOK_BIND_ADDR=127.0.0.1` in
|
||||
- **Internet-facing (the common case):** set `AGENTFEED_BIND_ADDR=127.0.0.1` in
|
||||
`deploy/.env` so the container port is not reachable from outside the VM at
|
||||
all, then open `80`/`443` in the VM's firewall/security group for the
|
||||
reverse proxy (see §8). Do not open `8000` externally.
|
||||
- **Private/VPN-only network:** open TCP `8000` to the addresses that should
|
||||
reach it (never `0.0.0.0/0`), and leave `DAYBOOK_BIND_ADDR` unset.
|
||||
reach it (never `0.0.0.0/0`), and leave `AGENTFEED_BIND_ADDR` unset.
|
||||
|
||||
Before opening any port, check whether one is already open and in use —
|
||||
`ss -tlnp | grep -E ':80|:443|:8000'` — rather than assuming a clean slate.
|
||||
|
|
@ -153,8 +153,8 @@ don't have API access to it yourself.
|
|||
## 5. Build, Start, Create A User
|
||||
|
||||
If the target has a separate, larger data volume than its main disk (common
|
||||
on space-constrained VMs), point Daybook's persistent data at it before
|
||||
starting: set `DAYBOOK_HOST_DATA_DIR=/path/on/that/volume` in `deploy/.env`.
|
||||
on space-constrained VMs), point AgentFeed's persistent data at it before
|
||||
starting: set `AGENTFEED_HOST_DATA_DIR=/path/on/that/volume` in `deploy/.env`.
|
||||
Ask the operator whether such a volume exists and where before defaulting to
|
||||
the main disk — filling the main disk on a host running other services is a
|
||||
bigger problem than it would be on a dedicated box. Create that same path
|
||||
|
|
@ -162,9 +162,9 @@ bigger problem than it would be on a dedicated box. Create that same path
|
|||
unset) before starting the stack:
|
||||
|
||||
```bash
|
||||
mkdir -p deploy/data # or the DAYBOOK_HOST_DATA_DIR path if you set one above
|
||||
mkdir -p deploy/data # or the AGENTFEED_HOST_DATA_DIR path if you set one above
|
||||
docker compose -f deploy/docker-compose.yml up -d --build
|
||||
docker compose -f deploy/docker-compose.yml exec daybook python manage.py createsuperuser
|
||||
docker compose -f deploy/docker-compose.yml exec agentfeed python manage.py createsuperuser
|
||||
```
|
||||
|
||||
The entrypoint runs migrations and collects static files automatically on
|
||||
|
|
@ -183,7 +183,7 @@ response when unauthenticated — it confirms the app is up. (Don't use
|
|||
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
|
||||
`DAYBOOK_BIND_ADDR` is set. Log in from a browser at whichever address is
|
||||
`AGENTFEED_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/`
|
||||
if you're on the private/VPN-only path from §4, or `https://<domain>/` through
|
||||
the reverse proxy once §8 is done if you're on the internet-facing path — with
|
||||
|
|
@ -230,13 +230,13 @@ For a routine forward release that's fine; if a rollback is needed after a
|
|||
migration, restore the pre-deploy `deploy/data` backup (see §9) alongside
|
||||
the code checkout.
|
||||
|
||||
`deploy/.env` and the data directory (wherever `DAYBOOK_HOST_DATA_DIR` points,
|
||||
`deploy/.env` and the data directory (wherever `AGENTFEED_HOST_DATA_DIR` points,
|
||||
or `deploy/data` by default) are untracked, so `git pull` never touches them —
|
||||
no need to back them up before a routine redeploy.
|
||||
|
||||
**If the container crash-loops right after a build** (`docker compose ps`
|
||||
shows `restarting`, health never goes green), the most likely cause is the
|
||||
entrypoint's `collectstatic` step failing — check `docker logs daybook`
|
||||
entrypoint's `collectstatic` step failing — check `docker logs agentfeed`
|
||||
before assuming an env/network problem. A `whitenoise ... MissingFileError`
|
||||
there means a static asset references a file that isn't present (this is what
|
||||
bit v0.1.0). No data or schema impact; fix the asset in the repo, tag, and
|
||||
|
|
@ -277,14 +277,14 @@ instead. Apache example:
|
|||
|
||||
```apache
|
||||
<VirtualHost *:443>
|
||||
ServerName daybook.example.com
|
||||
ServerName agentfeed.example.com
|
||||
ProxyPreserveHost On
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
ProxyPass / http://127.0.0.1:8000/
|
||||
ProxyPassReverse / http://127.0.0.1:8000/
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/letsencrypt/live/daybook.example.com/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/daybook.example.com/privkey.pem
|
||||
SSLCertificateFile /etc/letsencrypt/live/agentfeed.example.com/fullchain.pem
|
||||
SSLCertificateKeyFile /etc/letsencrypt/live/agentfeed.example.com/privkey.pem
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
|
|
@ -296,7 +296,7 @@ Only reach for a standalone proxy like Caddy if the target genuinely has
|
|||
nothing on 80/443 yet — it still needs the same header:
|
||||
|
||||
```text
|
||||
daybook.example.com {
|
||||
agentfeed.example.com {
|
||||
reverse_proxy 127.0.0.1:8000 {
|
||||
header_up X-Forwarded-Proto https
|
||||
}
|
||||
|
|
@ -305,15 +305,15 @@ daybook.example.com {
|
|||
|
||||
## 9. Backup
|
||||
|
||||
If you set `DAYBOOK_HOST_DATA_DIR` in step 5, the real data lives there, not
|
||||
If you set `AGENTFEED_HOST_DATA_DIR` in step 5, the real data lives there, not
|
||||
at `deploy/data` — back up whichever path is actually configured, read
|
||||
straight from `deploy/.env` rather than assumed, so this keeps working
|
||||
whether or not that override is set:
|
||||
|
||||
```bash
|
||||
cd /opt/daybook
|
||||
DATA_DIR="$(grep -oP '(?<=^DAYBOOK_HOST_DATA_DIR=).*' deploy/.env || echo deploy/data)"
|
||||
tar -czf "/tmp/daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")"
|
||||
DATA_DIR="$(grep -oP '(?<=^AGENTFEED_HOST_DATA_DIR=).*' deploy/.env || echo deploy/data)"
|
||||
tar -czf "/tmp/agentfeed-data-$(date +%Y%m%d-%H%M%S).tar.gz" -C "$(dirname "$DATA_DIR")" "$(basename "$DATA_DIR")"
|
||||
```
|
||||
|
||||
Copy the resulting archive off the VM (to object storage, the operator's
|
||||
|
|
@ -331,12 +331,12 @@ Before reporting the deployment complete, confirm all of these:
|
|||
public HTTPS origin, and the proxy forwards `X-Forwarded-Proto: https`.
|
||||
Verify by actually logging in through the proxy, not just curling
|
||||
`/api/capabilities` — CSRF only bites on POST (login, artifact writes).
|
||||
- [ ] `docker compose -f deploy/docker-compose.yml ps` shows `daybook` healthy.
|
||||
- [ ] `docker compose -f deploy/docker-compose.yml ps` shows `agentfeed` 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 the data directory (`deploy/data`, or `DAYBOOK_HOST_DATA_DIR`
|
||||
- [ ] A backup of the data directory (`deploy/data`, or `AGENTFEED_HOST_DATA_DIR`
|
||||
if you set it) has been taken and copied off the VM.
|
||||
- [ ] If the target host has its own service-inventory, monitoring, or
|
||||
changelog conventions for new services, they've been followed — ask
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# ${VAR} substitution below (DAYBOOK_BIND_ADDR, DAYBOOK_HOST_DATA_DIR) reads
|
||||
# ${VAR} substitution below (AGENTFEED_BIND_ADDR, AGENTFEED_HOST_DATA_DIR) reads
|
||||
# from deploy/.env because Compose resolves .env relative to *this file's*
|
||||
# directory, not the caller's cwd. That relies on being invoked as
|
||||
# `docker compose -f deploy/docker-compose.yml` from the repo root, per
|
||||
|
|
@ -7,20 +7,20 @@
|
|||
# the defaults (0.0.0.0 bind, ./data) would be a security regression, not
|
||||
# just a broken build.
|
||||
services:
|
||||
daybook:
|
||||
agentfeed:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile
|
||||
container_name: daybook
|
||||
container_name: agentfeed
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
mem_limit: 384m
|
||||
mem_reservation: 128m
|
||||
ports:
|
||||
- "${DAYBOOK_BIND_ADDR:-0.0.0.0}:8000:8000"
|
||||
- "${AGENTFEED_BIND_ADDR:-0.0.0.0}:8000:8000"
|
||||
volumes:
|
||||
- ${DAYBOOK_HOST_DATA_DIR:-./data}:/data
|
||||
- ${AGENTFEED_HOST_DATA_DIR:-./data}:/data
|
||||
healthcheck:
|
||||
# Hit the login page, not /api/capabilities: capabilities returns 403
|
||||
# when unauthenticated, and `curl --fail` treats 403 as an error, which
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
mkdir -p "${DAYBOOK_DATA_DIR:-/data}" "${ARTIFACTS_ROOT:-/data/artifacts_store}"
|
||||
mkdir -p "${AGENTFEED_DATA_DIR:-/data}" "${ARTIFACTS_ROOT:-/data/artifacts_store}"
|
||||
|
||||
python manage.py migrate --noinput
|
||||
python manage.py collectstatic --noinput
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
DJANGO_DEBUG=0
|
||||
DJANGO_SECRET_KEY=replace-with-a-long-random-secret
|
||||
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,your-vm-ip,your-domain.example
|
||||
# Required if Daybook sits behind a TLS-terminating reverse proxy (Apache,
|
||||
# Required if AgentFeed sits behind a TLS-terminating reverse proxy (Apache,
|
||||
# Nginx, Traefik, a cloud load balancer). Without it, login/POST fails with
|
||||
# "CSRF verification failed" because Django can't tell the original request
|
||||
# was HTTPS. Set to the public HTTPS origin(s) users actually visit:
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS=https://your-domain.example
|
||||
# Leave OFF in deployment. When on, POST /api/keys can create/list/revoke API
|
||||
# keys over the network (it returns key material), which is only appropriate
|
||||
# for automated test installations. Unset, it follows DJANGO_DEBUG — so with
|
||||
# DJANGO_DEBUG=0 above it is already off. Set to 1 only on a throwaway test box.
|
||||
# DAYBOOK_KEY_PROVISIONING_API=0
|
||||
DAYBOOK_DATA_DIR=/data
|
||||
# Disabled by default. When on, POST /api/keys can create/list/revoke API keys
|
||||
# over the network (it returns key material), which is only appropriate for
|
||||
# automated test installations. Leave unset in deployment; set to 1 only on a
|
||||
# throwaway test box.
|
||||
# AGENTFEED_KEY_PROVISIONING_API=0
|
||||
AGENTFEED_DATA_DIR=/data
|
||||
DATABASE_PATH=/data/db.sqlite3
|
||||
ARTIFACTS_ROOT=/data/artifacts_store
|
||||
|
||||
# Optional, read by deploy/docker-compose.yml directly (not by the app):
|
||||
# DAYBOOK_BIND_ADDR=127.0.0.1
|
||||
# AGENTFEED_BIND_ADDR=127.0.0.1
|
||||
# Uncomment and set to 127.0.0.1 when a reverse proxy on the same host will
|
||||
# front this service — this stops the container port from being reachable
|
||||
# directly from the network. Leave unset (binds all interfaces) only for
|
||||
# private/VPN-only deployments with no proxy in front.
|
||||
# DAYBOOK_HOST_DATA_DIR=/mnt/some-larger-volume/daybook-data
|
||||
# AGENTFEED_HOST_DATA_DIR=/mnt/some-larger-volume/agentfeed-data
|
||||
# Uncomment and point at a path on a separate/larger volume if the host's
|
||||
# main disk is space-constrained. Defaults to ./data next to this file.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue