Replaces the single per-user DRF token with named, individually-revocable
API keys, so you can run one key per agent and cut off a leaked one without
disrupting the others.
- New ApiKey model (user, name, dbk_-prefixed key, created_at, last_used_at).
Data migration copies every existing authtoken 1:1 into a "default" named
key with the same value, so no client (incl. the sibling test project's
hardcoded test-user key) needs reconfiguring.
- Auth (resolve_token_user) resolves against ApiKey and stamps last_used_at,
throttled to one write/minute.
- Settings -> API Keys: table with name/key/created/last-used + per-row
revoke and a create form; keys viewable on demand. Settings -> Account now
links here; Settings -> Users issues a named initial key.
- Gated provisioning API for test installations: GET/POST /api/keys and
DELETE /api/keys/{id}, controlled by DAYBOOK_KEY_PROVISIONING_API (unset ->
follows DEBUG, so off in deployment). Advertised via key_provisioning_api
in /api/capabilities; 403 when disabled.
- Docs (api/deployment/quickstart/posting-helper), SKILL.md, and env.example
updated from "token / Settings -> Account" to "key / Settings -> API Keys".
- Tests: new API + UI suites for keys; existing token-based tests moved to
ApiKey. Full suite 129 passing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
203 lines
6.4 KiB
Markdown
203 lines
6.4 KiB
Markdown
# Deployment
|
|
|
|
Daybook deploys the same way on any Linux host with Docker: a cloud VM, a home
|
|
server, a Proxmox guest, whatever. It runs as a single Docker container
|
|
(Gunicorn) with all persistent state — the SQLite database and artifact files —
|
|
on a bind-mounted `./data` directory outside the image.
|
|
|
|
```text
|
|
Target host (cloud VM, home server, ...)
|
|
Docker Engine
|
|
daybook container
|
|
/app application code baked into the image
|
|
/data persistent volume: db.sqlite3, artifacts_store/
|
|
```
|
|
|
|
## Files In `deploy/`
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `Dockerfile` | Builds the Daybook image. |
|
|
| `docker-compose.yml` | Runs Daybook with a persistent `./data:/data` bind mount. |
|
|
| `entrypoint.sh` | Runs migrations, collects static files, then starts Gunicorn. |
|
|
| `env.example` | Template for deployment environment variables. |
|
|
|
|
## Prerequisites On The Target Host
|
|
|
|
- Docker Engine + the Docker Compose plugin
|
|
- Git (for the clone-and-pull workflow below)
|
|
|
|
```bash
|
|
docker --version
|
|
docker compose version
|
|
```
|
|
|
|
## First-Time Setup
|
|
|
|
Clone the repo onto the host at a stable path:
|
|
|
|
```bash
|
|
git clone <REPO_URL> /opt/daybook
|
|
cd /opt/daybook
|
|
```
|
|
|
|
Create the environment file:
|
|
|
|
```bash
|
|
cp deploy/env.example deploy/.env
|
|
```
|
|
|
|
Generate a secret key:
|
|
|
|
```bash
|
|
python3 -c "import secrets; print('DJANGO_SECRET_KEY=' + secrets.token_urlsafe(50))"
|
|
```
|
|
|
|
Edit `deploy/.env` and set at minimum:
|
|
|
|
```text
|
|
DJANGO_DEBUG=0
|
|
DJANGO_SECRET_KEY=<generated-secret>
|
|
DJANGO_ALLOWED_HOSTS=<vm-ip-or-dns-name>,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 configure — Daybook uses per-user API keys
|
|
created under Settings → API Keys once a user exists (each user can hold
|
|
several, one per agent, each individually revocable). Do not commit
|
|
`deploy/.env`.
|
|
|
|
Create the persistent data directory and bring the stack up:
|
|
|
|
```bash
|
|
mkdir -p deploy/data
|
|
docker compose -f deploy/docker-compose.yml up -d --build
|
|
```
|
|
|
|
Create your first login (superuser) so you have something to log in with and
|
|
a personal API token to post through:
|
|
|
|
```bash
|
|
docker compose -f deploy/docker-compose.yml exec daybook python manage.py createsuperuser
|
|
```
|
|
|
|
## Verify Deployment
|
|
|
|
```bash
|
|
docker compose -f deploy/docker-compose.yml ps
|
|
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/api/capabilities # expect 403
|
|
```
|
|
|
|
An unauthenticated capabilities check returns 403 (login/token required) —
|
|
that's expected; it confirms the app is up and enforcing auth. (Don't add
|
|
`curl -f` — it treats the 403 as an error. The container healthcheck uses
|
|
the login page, which is 200 without auth.) Open
|
|
`http://<vm-ip>:8000/` in a browser and log in with the superuser you just
|
|
created, then create a personal key under Settings → API Keys and use it for a
|
|
smoke-test post:
|
|
|
|
```bash
|
|
curl -fsS -X POST http://127.0.0.1:8000/api/artifacts \
|
|
-H "Authorization: Bearer <your-personal-token>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"source": "deploy-smoke",
|
|
"kind": "markdown",
|
|
"title": "Deployment Smoke Test",
|
|
"content": "# Deployment Smoke Test\n\nDaybook accepted an artifact."
|
|
}'
|
|
```
|
|
|
|
## Fast Redeploys
|
|
|
|
Once the host has a git clone, shipping a new version is two commands:
|
|
|
|
```bash
|
|
cd /opt/daybook
|
|
git pull
|
|
docker compose -f deploy/docker-compose.yml up -d --build
|
|
```
|
|
|
|
The entrypoint runs Django migrations on every start, so schema changes are
|
|
picked up automatically. `deploy/.env` and `deploy/data/` are untouched by
|
|
`git pull` since they aren't tracked in the repo.
|
|
|
|
## Operational Commands
|
|
|
|
```bash
|
|
docker compose -f deploy/docker-compose.yml logs -f daybook
|
|
docker compose -f deploy/docker-compose.yml restart daybook
|
|
docker compose -f deploy/docker-compose.yml down
|
|
docker compose -f deploy/docker-compose.yml up -d
|
|
docker compose -f deploy/docker-compose.yml exec daybook python manage.py check
|
|
docker compose -f deploy/docker-compose.yml exec daybook sh
|
|
```
|
|
|
|
## Backup And Restore
|
|
|
|
Back up `deploy/data/` — it contains the SQLite database and all artifact
|
|
files:
|
|
|
|
```bash
|
|
tar -czf daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data
|
|
```
|
|
|
|
For a consistent snapshot during heavy writes, stop the container first:
|
|
|
|
```bash
|
|
docker compose -f deploy/docker-compose.yml down
|
|
tar -czf daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data
|
|
docker compose -f deploy/docker-compose.yml up -d
|
|
```
|
|
|
|
To restore: stop Daybook, replace `deploy/data/` with the backup contents,
|
|
then start it again.
|
|
|
|
## Reverse Proxy Notes
|
|
|
|
If putting Daybook behind Nginx, Caddy, Traefik, or a cloud load balancer:
|
|
|
|
- Proxy to `http://<vm-ip>:8000`.
|
|
- Add the public hostname to `DJANGO_ALLOWED_HOSTS`.
|
|
- Terminate TLS at the proxy; keep `DJANGO_DEBUG=0`.
|
|
- Set `DJANGO_CSRF_TRUSTED_ORIGINS` to the public HTTPS origin, **and**
|
|
configure the proxy to forward `X-Forwarded-Proto: https`. Without both,
|
|
login and any other POST fails with "CSRF verification failed" — Django
|
|
has no way to know the original request was HTTPS once the proxy
|
|
terminates TLS and forwards plain HTTP to the container.
|
|
|
|
```text
|
|
DJANGO_ALLOWED_HOSTS=daybook.example.com,localhost,127.0.0.1
|
|
DJANGO_CSRF_TRUSTED_ORIGINS=https://daybook.example.com
|
|
```
|
|
|
|
Forwarding the proto header, by proxy:
|
|
|
|
```text
|
|
Nginx: proxy_set_header X-Forwarded-Proto $scheme;
|
|
Apache: RequestHeader set X-Forwarded-Proto "https" (requires a2enmod headers)
|
|
Caddy: header_up X-Forwarded-Proto https
|
|
```
|
|
|
|
## Deployment Checklist
|
|
|
|
- `deploy/.env` exists with a real (non-placeholder) `DJANGO_SECRET_KEY`.
|
|
- `DJANGO_DEBUG=0`.
|
|
- `DJANGO_ALLOWED_HOSTS` includes the hostname/IP being used.
|
|
- If behind a reverse proxy: `DJANGO_CSRF_TRUSTED_ORIGINS` is set and the
|
|
proxy forwards `X-Forwarded-Proto: https` (login will 403 otherwise).
|
|
- `deploy/data/` exists and persists across `git pull` + rebuild.
|
|
- `docker compose -f deploy/docker-compose.yml ps` shows `daybook` healthy.
|
|
- `GET /api/capabilities` returns 403 without auth, 200 with a valid token or
|
|
session (check the status code — don't use `curl -f`, which errors on 403).
|
|
- At least one superuser exists (`manage.py createsuperuser`) with a key
|
|
from Settings → API Keys.
|
|
|
|
## Known MVP Constraints
|
|
|
|
- SQLite is used by default — fine for a single-VM self-hosted deployment.
|
|
- Per-user ingest tokens; no shared/global token.
|
|
- Reader/browser access requires login; feeds are private per user.
|
|
- Manual drag/drop upload and content revisions are deferred.
|