# second-brain web app — deploy notes Target host: **herbys-dev** (10.0.21.207). Runs the FastAPI + Jinja UI under `docker compose`, on the existing `homelab` bridge network so it can reach `homelab-postgres:5432` and `ollama:11434` by service DNS. Traefik (file-provider VM at **10.0.11.20**) handles TLS + the public hostname `brain.herbylab.dev`. The compose file does NOT carry `traefik.*` labels — file-provider Traefik can't see them. --- ## ⚠ Security note (no SSO) Travis opted **not** to put this behind Authentik for the initial roll-out. The web app has **no authentication, no CSRF, no rate limiting**, and it exposes mutating endpoints (accept/reject sources, edit pipeline settings). → The Traefik route should be reachable **from LAN / trusted networks only**. If you ever publish this past Cloudflare for off-LAN access, gate it behind the Authentik forward-auth middleware first, or add an `auth_required` decorator to the FastAPI app. --- ## 1. One-time setup on herbys-dev ```bash cd /opt/projects/homelab/second-brain/deploy/web # Real env file — paste the lovebug password. cp .env.example .env chmod 0600 .env $EDITOR .env # SECOND_BRAIN_DATABASE_URL must use host=homelab-postgres (NOT 127.0.0.1). # Password lives at /opt/backups/postgres-consolidation/credentials.env. # Build + start. Compose will join the existing external `homelab` net. docker compose up -d --build # Confirm it's healthy. docker compose ps docker compose logs --tail 50 curl -fsS http://10.0.21.207:8080/dashboard | head ``` Day-to-day: ```bash docker compose logs -f # tail docker compose restart # bounce (config reload) docker compose pull && docker compose up -d # if image were registry-hosted git pull && docker compose up -d --build # build-on-host update path ``` --- ## 2. INFRA STEPS — Travis applies these manually The compose stack is *up* once `docker compose up -d --build` returns. The **public URL** at `https://brain.herbylab.dev` only resolves after the two steps below land on their respective infra. They live on machines outside this repo's authority (Knot on the DNS box; Traefik on 10.0.11.20), so this README documents them rather than executing them. ### 2a. Knot DNS — A record for `brain.herbylab.dev` Add this to the `herbylab.dev` zone file on the Knot host (file path / include layout matches the existing per-service A records — pick the spot used for the other `*.herbylab.dev` services): ```zone brain.herbylab.dev. 300 IN A 10.0.11.20 ``` 300s TTL matches the existing convention. Reload Knot: ```bash sudo knotc reload dig +short brain.herbylab.dev @ # should return 10.0.11.20 ``` ### 2b. Traefik file-provider router on 10.0.11.20 Add the snippet below to the Traefik dynamic config (same file the rest of the `*.herbylab.dev` services live in — file-provider config picks up changes without a restart): ```yaml http: routers: second-brain: rule: "Host(`brain.herbylab.dev`)" entryPoints: - websecure tls: certResolver: cloudflare service: second-brain # No middleware on day one. If/when SSO lands, prepend the # authentik forward-auth chain here (see other routers for the # shape) and remove the LAN restriction. services: second-brain: loadBalancer: servers: - url: "http://10.0.21.207:8080" passHostHeader: true ``` Verify on the Traefik host: ```bash # Watch for config-reload events: docker logs traefik --tail 20 -f # Or hit the API if it's exposed: curl -fsS http://localhost:8080/api/http/routers/second-brain@file ``` Once both 2a and 2b are in place, `https://brain.herbylab.dev/dashboard` should resolve through Cloudflare → Traefik → 10.0.21.207:8080 → the `second-brain-web` container. --- ## 3. Verification checklist After `docker compose up -d --build`: | Check | Command | Expect | |---|---|---| | Container up | `docker compose ps` | `second-brain-web` is `Up`, not restarting | | Startup log clean | `docker compose logs --tail 50` | uvicorn `Started server process` + `Application startup complete`, no tracebacks | | Local-host reachable | `curl -fsS http://10.0.21.207:8080/dashboard \| head -1` | `` or similar | | DB reachable | `curl -fsS http://10.0.21.207:8080/dashboard` | dashboard renders status counts (proves the homelab-net path to Postgres) | | Settings round-trip | open `/settings` in a browser, save | green "Saved." flash + new `updated_at` | After Travis's 2a + 2b infra steps: | Check | Command | Expect | |---|---|---| | DNS | `dig +short brain.herbylab.dev` | `10.0.11.20` | | TLS | `curl -fsS https://brain.herbylab.dev/dashboard \| head -1` | dashboard HTML | | Cert | `openssl s_client -connect brain.herbylab.dev:443 -servername brain.herbylab.dev <<<''` | Cloudflare-issued cert | --- ## 4. Follow-ups (not built this round) - **SSO via Authentik.** Add the existing forward-auth middleware to the Traefik router once we want this off-LAN. Authentik lives at 10.0.21.207:9000 (same host as this container — different port). - **CSRF.** Settings save is an HTMX POST with no token; trivial to forge from a same-origin browser. Acceptable while LAN-restricted. - **Image registry.** Currently build-on-host. Push to Gitea's container registry (`gitea.plantbasedsoutherner.com/petal-power/second-brain-web`) when there's a second box that needs to pull the image. - **Dev-side extraction systemd timer.** Travis is running extraction manually for now; the timer/cron path is deferred.