diff --git a/CLAUDE.md b/CLAUDE.md index e5041c9..5f6f9c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,4 +72,4 @@ The core product principle is: agents publish content, not files. The server own ## Claude Code / OpenCode -This project uses `CLAUDE.md` for project instructions, compatible with both Claude Code and OpenCode. The `opencode.json` configuration file loads this file. After changing `opencode.json`, `.opencode/`, or this file, restart your AI coding assistant so the updated instructions are loaded. +This project uses `CLAUDE.md` for project instructions, compatible with both Claude Code and OpenCode. OpenCode is configured to load this file via a local, untracked `opencode.json` (it contains machine-specific MCP config, so it is not committed — recreate it locally with `{"$schema": "https://opencode.ai/config.json", "instructions": ["CLAUDE.md"], "permission": "allow"}` plus whatever MCP servers you want). After changing `opencode.json`, `.opencode/`, or this file, restart your AI coding assistant so the updated instructions are loaded. diff --git a/deploy/env.example b/deploy/env.example index e83e248..19b4cae 100644 --- a/deploy/env.example +++ b/deploy/env.example @@ -1,7 +1,6 @@ DJANGO_DEBUG=0 DJANGO_SECRET_KEY=replace-with-a-long-random-secret -DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,your-proxmox-container-ip,your-domain.example -DAYBOOK_API_TOKEN=replace-with-a-long-random-token +DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,your-vm-ip,your-domain.example DAYBOOK_DATA_DIR=/data DATABASE_PATH=/data/db.sqlite3 ARTIFACTS_ROOT=/data/artifacts_store diff --git a/deploy/proxmox_llm_deploy.md b/deploy/proxmox_llm_deploy.md deleted file mode 100644 index 2d01b1d..0000000 --- a/deploy/proxmox_llm_deploy.md +++ /dev/null @@ -1,348 +0,0 @@ -# Proxmox Container Deployment Runbook For LLM Operators - -This document is written for an LLM or automation agent that is deploying Daybook into a Linux container on a Proxmox host. - -The recommended deployment target is a Proxmox LXC or VM running Debian/Ubuntu with Docker and Docker Compose. Daybook itself runs as a Docker container inside that guest. - -## Deployment Model - -```text -Proxmox host - Debian/Ubuntu LXC or VM - Docker Engine - daybook container - /app application code baked into image - /data persistent mounted volume -``` - -Persistent runtime data lives outside the image in `deploy/data` on the guest: - -```text -deploy/data/ - db.sqlite3 - artifacts_store/ -``` - -Do not store runtime data inside the image. - -## Files In This Directory - -| 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. | -| `proxmox_llm_deploy.md` | This runbook. | - -## Prerequisites On The Proxmox Guest - -The guest OS should have: - -- Docker Engine -- Docker Compose plugin -- Git -- Network access to pull Python base images and Python packages - -For Debian/Ubuntu guests, install basics: - -```bash -sudo apt-get update -sudo apt-get install -y ca-certificates curl git -``` - -Install Docker using Docker's current official instructions for the guest OS. Then verify: - -```bash -docker --version -docker compose version -``` - -## Clone Or Copy The Project - -Choose a stable application path on the guest: - -```bash -sudo mkdir -p /opt/daybook -sudo chown "$USER:$USER" /opt/daybook -``` - -If using Git: - -```bash -git clone /opt/daybook -cd /opt/daybook -``` - -If files were copied by another mechanism, ensure the project root contains: - -```text -manage.py -requirements.txt -deploy/docker-compose.yml -deploy/Dockerfile -``` - -## Create The Environment File - -From the project root: - -```bash -cd /opt/daybook -cp deploy/env.example deploy/.env -``` - -Generate secrets: - -```bash -python3 - <<'PY' -import secrets -print('DJANGO_SECRET_KEY=' + secrets.token_urlsafe(50)) -print('DAYBOOK_API_TOKEN=' + secrets.token_urlsafe(32)) -PY -``` - -Edit `deploy/.env`: - -```bash -nano deploy/.env -``` - -Set at minimum: - -```text -DJANGO_DEBUG=0 -DJANGO_SECRET_KEY= -DJANGO_ALLOWED_HOSTS=,,localhost,127.0.0.1 -DAYBOOK_API_TOKEN= -DAYBOOK_DATA_DIR=/data -DATABASE_PATH=/data/db.sqlite3 -ARTIFACTS_ROOT=/data/artifacts_store -``` - -Important: - -- Do not commit `deploy/.env`. -- Do not use the development token `dev-token` for a real deployment. -- Include the actual IP or DNS name users will use in `DJANGO_ALLOWED_HOSTS`. - -## Create Persistent Data Directory - -From the project root: - -```bash -mkdir -p deploy/data -``` - -The compose file mounts this path into the container as `/data`. - -## Build And Start - -From the project root: - -```bash -docker compose -f deploy/docker-compose.yml up -d --build -``` - -Watch logs: - -```bash -docker compose -f deploy/docker-compose.yml logs -f daybook -``` - -Expected startup behavior: - -- Django migrations run. -- Static files are collected. -- Gunicorn starts on port `8000` inside the container. -- Host port `8000` maps to container port `8000`. - -## Verify Deployment - -Check container state: - -```bash -docker compose -f deploy/docker-compose.yml ps -``` - -Check capabilities endpoint from the guest: - -```bash -curl -fsS http://127.0.0.1:8000/api/capabilities -``` - -Expected JSON includes: - -```json -{ - "artifact_kinds": ["html_fragment", "html_full", "markdown", "deck", "image", "pdf", "binary"], - "auth": "bearer_token" -} -``` - -Open the app from another machine: - -```text -http://:8000/ -``` - -If this fails: - -- Confirm the Proxmox guest firewall allows TCP `8000`. -- Confirm any Proxmox firewall rules allow TCP `8000`. -- Confirm `DJANGO_ALLOWED_HOSTS` includes the hostname/IP being used. - -## Publish A Smoke-Test Artifact - -Replace `` with `DAYBOOK_API_TOKEN` from `deploy/.env`. - -```bash -curl -fsS -X POST http://127.0.0.1:8000/api/artifacts \ - -H "Authorization: Bearer " \ - -H "Content-Type: application/json" \ - -d '{ - "persona": "deploy-smoke", - "source_type": "agent", - "source_name": "Deployment Smoke Agent", - "kind": "markdown", - "title": "Deployment Smoke Test", - "content": "# Deployment Smoke Test\n\nDaybook accepted an artifact.", - "tags": ["deploy", "smoke"], - "metadata": {"run_id": "deploy-smoke-001"} - }' -``` - -Then refresh: - -```text -http://:8000/ -``` - -## Operational Commands - -Stop: - -```bash -docker compose -f deploy/docker-compose.yml down -``` - -Start: - -```bash -docker compose -f deploy/docker-compose.yml up -d -``` - -Rebuild after code changes: - -```bash -docker compose -f deploy/docker-compose.yml up -d --build -``` - -View logs: - -```bash -docker compose -f deploy/docker-compose.yml logs -f daybook -``` - -Run Django management command: - -```bash -docker compose -f deploy/docker-compose.yml exec daybook python manage.py check -``` - -Open shell: - -```bash -docker compose -f deploy/docker-compose.yml exec daybook sh -``` - -## Backup - -Back up the guest directory: - -```text -/opt/daybook/deploy/data -``` - -It contains: - -- SQLite database -- Artifact files - -Simple backup command: - -```bash -tar -czf daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data -``` - -For consistent backups during heavy writes, stop the container first: - -```bash -docker compose -f /opt/daybook/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 /opt/daybook/deploy/docker-compose.yml up -d -``` - -## Restore - -Stop Daybook: - -```bash -docker compose -f /opt/daybook/deploy/docker-compose.yml down -``` - -Replace `/opt/daybook/deploy/data` with the restored data directory, then start: - -```bash -docker compose -f /opt/daybook/deploy/docker-compose.yml up -d -``` - -## Updating The App - -If deployed from Git: - -```bash -cd /opt/daybook -git pull -docker compose -f deploy/docker-compose.yml up -d --build -``` - -The container entrypoint runs migrations on startup. - -## Reverse Proxy Notes - -If putting Daybook behind Nginx, Caddy, Traefik, or a Proxmox-level reverse proxy: - -- Proxy to `http://:8000`. -- Add the public hostname to `DJANGO_ALLOWED_HOSTS`. -- Terminate TLS at the proxy. -- Keep `DJANGO_DEBUG=0`. - -Example `DJANGO_ALLOWED_HOSTS`: - -```text -DJANGO_ALLOWED_HOSTS=daybook.example.com,localhost,127.0.0.1 -``` - -## LLM Deployment Checklist - -Before declaring deployment complete, verify all items: - -- `deploy/.env` exists and does not use placeholder secrets. -- `DJANGO_DEBUG=0`. -- `DJANGO_ALLOWED_HOSTS` includes the access hostname/IP. -- `deploy/data/` exists. -- `docker compose -f deploy/docker-compose.yml ps` shows `daybook` running or healthy. -- `curl -fsS http://127.0.0.1:8000/api/capabilities` succeeds on the guest. -- Browser opens `http://:8000/`. -- Smoke-test artifact POST succeeds with the configured bearer token. -- `deploy/data/db.sqlite3` exists after startup. -- `deploy/data/artifacts_store/` exists after posting an artifact. - -## Known MVP Constraints - -- SQLite is used by default. This is acceptable for local/self-hosted MVP usage. -- The API uses a single shared bearer token. -- No reader authentication is implemented yet. -- Manual drag/drop upload is deferred. -- Content revisions are deferred. diff --git a/docs/agent_quickstart.md b/docs/agent_quickstart.md index 28d4fe6..d6ba619 100644 --- a/docs/agent_quickstart.md +++ b/docs/agent_quickstart.md @@ -124,6 +124,17 @@ Invoke-RestMethod ` -Body $body ``` +## 8. Delete An Artifact + +```powershell +Invoke-RestMethod ` + -Uri "http://127.0.0.1:8001/api/artifacts/$($artifact.id)" ` + -Method Delete ` + -Headers @{ Authorization = "Bearer " } +``` + +Deletes the artifact's database row and its files. You can only delete artifacts your own token owns. + ## Agent Payload Checklist Recommended fields for every agent post: diff --git a/docs/api.md b/docs/api.md index 4cfce20..97c1503 100644 --- a/docs/api.md +++ b/docs/api.md @@ -333,6 +333,26 @@ Editable fields: You can only PATCH artifacts your own token owns; others return `404`. +## Delete Artifact + +Endpoint: + +```http +DELETE /api/artifacts/{id} +Authorization: Bearer +``` + +Removes the artifact's database row and its files (primary file, markdown +sidecar, and any attached assets). Returns `204` with no body on success. + +You can only delete artifacts your own token owns; others return `404`, as +does a second delete of the same id. + +```bash +curl -X DELETE "http://127.0.0.1:8001/api/artifacts/a1b2c3d4" \ + -H "Authorization: Bearer " +``` + Not editable yet: | Field | Reason | @@ -403,7 +423,12 @@ Example response: "views": ["rendered", "raw"], "raw_supported_for": ["markdown", "html_full"], "source_types": ["agent", "person", "system"], - "auth": "bearer_token" + "auth": "bearer_token", + "methods": { + "/api/artifacts": ["GET", "POST"], + "/api/artifacts/{id}": ["GET", "PATCH", "DELETE"], + "/api/assets": ["POST"] + } } ``` diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..5db5657 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,184 @@ +# 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 /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= +DJANGO_ALLOWED_HOSTS=,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 tokens +(DRF `Token` model) issued from Settings → Account once a user exists. 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 -fsS http://127.0.0.1:8000/api/capabilities +``` + +An unauthenticated capabilities check returns 403 (login/token required) — +that's expected; it confirms the app is up and enforcing auth. Open +`http://:8000/` in a browser and log in with the superuser you just +created, then grab a personal token from Settings → Account and use it for a +smoke-test post: + +```bash +curl -fsS -X POST http://127.0.0.1:8000/api/artifacts \ + -H "Authorization: Bearer " \ + -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://:8000`. +- Add the public hostname to `DJANGO_ALLOWED_HOSTS`. +- Terminate TLS at the proxy; keep `DJANGO_DEBUG=0`. + +```text +DJANGO_ALLOWED_HOSTS=daybook.example.com,localhost,127.0.0.1 +``` + +## 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. +- `deploy/data/` exists and persists across `git pull` + rebuild. +- `docker compose -f deploy/docker-compose.yml ps` shows `daybook` healthy. +- `curl -fsS http://127.0.0.1:8000/api/capabilities` returns 403 without auth, + 200 with a valid token or session. +- At least one superuser exists (`manage.py createsuperuser`) with a token + from Settings → Account. + +## 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.