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:
peter 2026-09-04 13:10:28 +01:00
parent 4cacd8f64e
commit d30e2436ab
39 changed files with 303 additions and 284 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

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