diff --git a/.dockerignore b/.dockerignore index e5fac2c..bfdbe9e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ node_modules npm-debug.log* Dockerfile README.md +appdata/ diff --git a/.env.example b/.env.example index 8617ff4..df911c4 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,8 @@ # Generate each with: openssl rand -base64 48 POSTGRES_PASSWORD= -# One-use secret to create snowspeeder@gmail.com; remove after bootstrap. +# One-use secret to create snowspeeder@gmail.com; leave blank/remove after bootstrap. BOOTSTRAP_SETUP_TOKEN= +# Optional host path mounted read-only at /home/node/.pi/agent (never commit it). +PI_AGENT_CONFIG_DIR=./appdata/pi-agent +# Optional explicit reverse-proxy IP/CIDR; leave blank if no trusted proxy is present. +TRUST_PROXY= diff --git a/.gitignore b/.gitignore index e05ae32..98066dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -node_modules/ +node_modules +appdata// data/ *.log .DS_Store diff --git a/Dockerfile b/Dockerfile index 658aeeb..7dad0ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,11 @@ RUN npm ci --omit=dev COPY public ./public COPY server ./server COPY shared ./shared +COPY db/migrations ./db/migrations +# The optional Pi agent configuration is mounted read-only here at runtime. +ENV HOME=/home/node +RUN mkdir -p /home/node/.pi/agent && chown -R node:node /home/node/.pi USER node EXPOSE 8090 CMD ["node", "server/index.js"] diff --git a/README.md b/README.md index 9a9d295..8e3b310 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,21 @@ The public landing page is at `/`; plans require an account at `/app`. Productio ### First administrator -Generate `BOOTSTRAP_SETUP_TOKEN` with `openssl rand -base64 48`, keep it only in the deployment environment, then call `POST /api/auth/bootstrap` with that token, `snowspeeder@gmail.com`, and a 12+ character password. The endpoint can create that account only once. Remove the setup token after success; no administrator password is stored in source control. +Generate `BOOTSTRAP_SETUP_TOKEN` with `openssl rand -base64 48`, keep it only in the deployment environment, then call `POST /api/auth/bootstrap` with that token, `snowspeeder@gmail.com`, and a 12+ character password. The endpoint can create that account only once. Remove the setup token after success; it is optional thereafter and no administrator password is stored in source control. + +### Pi agent configuration in Docker + +The `app` service mounts `PI_AGENT_CONFIG_DIR` (default `./appdata/pi-agent`) read-only at `/home/node/.pi/agent`, the non-root Node user's Pi configuration directory. This lets `/api/prefill` use the same configured model at runtime without baking credentials into the image. The directory is ignored by Git and Docker build context; do not commit its contents. + +Before bringing up the stack, sync only the local Pi agent configuration you intend to make available to the container: + +```bash +mkdir -p appdata/pi-agent +rsync -a --delete ~/.pi/agent/ appdata/pi-agent/ +docker compose --env-file .env up --build +``` + +Set `PI_AGENT_CONFIG_DIR` to another protected host directory instead if preferred. Restrict access to that directory because it can contain provider credentials. The mount is read-only, so Pi cannot alter the host configuration. If deployed behind a reverse proxy, set `TRUST_PROXY` only to that proxy's specific IP/CIDR (or keep it blank when the app is directly exposed). ## Mobile and PWA use @@ -61,8 +75,7 @@ If those tables change in the paper worksheet, port the change here too. ## Known gaps (v1) -- No automated test suite yet (the ledger math and `.alog` parser were verified manually - against the worksheet's worked examples and all 14 logs in `ref/roasts/`, respectively). +- Offline drafts are intentionally scoped to the authenticated browser account and are cleared on logout; account-backed plans remain the authoritative copy. - Roastetta (roastetta.com) integration is intentionally out of scope — it needs a headed, Cloudflare-clearing browser and the operator's own credentials. Use the `.alog` file picker, or point `ALOG_DIR` at wherever the `roastetta` skill already downloaded files. diff --git a/docker-compose.yml b/docker-compose.yml index 74c54de..d710bdb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,16 @@ services: DATABASE_URL: postgresql://roast:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@db:5432/roast APP_ORIGIN: https://roast.srmr.xyz COOKIE_SECURE: "true" - BOOTSTRAP_SETUP_TOKEN: ${BOOTSTRAP_SETUP_TOKEN:?set a one-time random token} - depends_on: [db] + # Optional after the one-time administrator bootstrap has completed. + BOOTSTRAP_SETUP_TOKEN: ${BOOTSTRAP_SETUP_TOKEN:-} + # Leave unset unless a known reverse-proxy address/CIDR is configured. + TRUST_PROXY: ${TRUST_PROXY:-} + volumes: + # Mount only non-secret Pi agent model/auth configuration; keep it read-only. + - ${PI_AGENT_CONFIG_DIR:-./appdata/pi-agent}:/home/node/.pi/agent:ro + depends_on: + db: + condition: service_healthy expose: ["8090"] restart: unless-stopped db: @@ -17,6 +25,11 @@ services: POSTGRES_USER: roast POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} volumes: [postgres-data:/var/lib/postgresql/data] + healthcheck: + test: ["CMD-SHELL", "pg_isready -U roast -d roast"] + interval: 5s + timeout: 3s + retries: 12 restart: unless-stopped volumes: postgres-data: diff --git a/public/admin.html b/public/admin.html index 7462a88..6e5b29c 100644 --- a/public/admin.html +++ b/public/admin.html @@ -1 +1,24 @@ -
New plan
-