Commit Graph

11 Commits

Author SHA1 Message Date
justin 00f403defa compose: drive backend/worker/migrate config from .env (env_file)
Replace the per-setting environment allow-list with `env_file: .env` on the
three app-image services, so every setting in app/core/config.py is configurable
from .env with no compose edit. This kills the recurring trap where a documented
env var (OWNER_EMAIL, the AI keys, SMTP, APP_BASE_URL) silently didn't reach the
app because it wasn't on the hand-maintained list.

`env_file` is `required: false` so local/CI without a .env still works (falls
back to ${VAR:-default} interpolation + code defaults). The small `environment:`
block that remains is only for values that must NOT come from .env:
  - RUN_MIGRATIONS=1 (backend) — a deploy flag, not an app setting.
  - DATABASE_URL — pinned to the compose-internal host, because the code default
    points at localhost (wrong inside the network). environment wins over
    env_file, so this is a safety net if .env ever omits it.

Trade-off (accepted, see comment): env_file also injects infra secrets
(POSTGRES_*, MINIO_*, CLOUDFLARE_TUNNEL_TOKEN) into the app process env; the app
ignores unknown vars (pydantic extra="ignore").

Verified on prod: DATABASE_URL resolves to postgres:5432, RUN_MIGRATIONS=1 and
OWNER_EMAIL intact, COOKIE_SECURE=true (no posture change), health 200, trees
200. The earlier explicit AI/SMTP/OWNER passthrough is now subsumed by this.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-10 08:46:00 -04:00
justin 3a1395b6af compose: forward AI provider + mailer/SMTP env to the backend
Follow-up to the OWNER_EMAIL passthrough. The backend service env block is an
explicit allow-list, so the documented model-provider keys (ANTHROPIC_*,
OPENAI_*, XAI_*, OLLAMA_*, DEFAULT_*_PROVIDER, LLM_MAX_TOKENS,
EMBEDDING_DIMENSIONS) and mailer settings (MAILER, SMTP_*, APP_BASE_URL,
REQUIRE_EMAIL_VERIFICATION) never reached the container — setting them in .env
was a no-op. The AI assistant/policy and the SMTP mailer run in the backend, so
forward them here.

Side fix: APP_BASE_URL was likewise dropped, so outbound email links used the
code default http://localhost instead of the configured domain. Now forwarded
(verified live: backend reports APP_BASE_URL=https://provenance.paul.farm).

Worker is left as-is (it consumes neither today); it'll need the model vars when
embedding/matching jobs land. Alternative to this growing allow-list is
`env_file: .env` on the service — deferred to avoid forwarding unrelated secrets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-10 08:38:49 -04:00
justin 88beb9650f compose: forward OWNER_EMAIL to the backend container
The instance-owner feature reads OWNER_EMAIL, but the backend service's
environment block is an explicit allow-list that didn't include it — so setting
it in .env never reached the app (is_instance_owner always saw "" → no owner).
Add the passthrough.

NOTE: the same allow-list omits the AI provider keys (ANTHROPIC_API_KEY,
OPENAI_*, XAI_*, OLLAMA_*) and SMTP settings, so those documented env vars also
don't currently reach the backend on this deployment. Worth a follow-up
(forward them explicitly, or switch the service to env_file) so .env actually
drives all configuration per the twelve-factor rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-09 23:22:48 -04:00
justin 7f640649b9 Auto-apply migrations on deploy (entrypoint + one-shot service)
So a deploy never needs a manual `alembic upgrade head`:

- Backend image gains an entrypoint that runs `alembic upgrade head` before
  uvicorn when RUN_MIGRATIONS=1 (set on the backend service). This self-migrates
  even on a Watchtower in-place image swap, which doesn't re-run one-shot jobs.
- A one-shot `migrate` service covers the `docker compose up` path; backend and
  worker depend on it completing, which also serializes it with the backend
  entrypoint so alembic never runs concurrently. `upgrade head` is idempotent.

Activating this needs the updated compose on the host once (Watchtower only
swaps images, not the compose file / env). After that, migrations are automatic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 10:50:28 -04:00
justin 34d30e3134 Add media (object storage) and the background worker (Phase 1)
Media model + migration; an ObjectStore interface with an S3/MinIO (boto3) implementation behind the service layer. Upload (multipart) stores bytes in object storage + a metadata row (checksum, size, content-type, optional attach to person/event/source); list returns presigned URLs; delete is soft. Editor-gated, privacy-filtered, audited. 24 tests pass (object store faked).

Introduces the worker container (same image, 'python -m app.worker'): its first job is the scheduled 30-day soft-delete purge across tables + media object cleanup. Compose gains worker + S3 env on backend/worker; dev override builds the worker too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-06 21:46:09 -04:00
justin 0b9d72c878 Drop bundled Watchtower; rely on the host's global Watchtower
ripper already runs a single global nickfedor/watchtower (label-enabled) that watches every stack; the bundled containrrr/watchtower was redundant and crash-looped (its Docker API client is too old for Docker 29). Keep the watchtower.enable labels on backend/frontend so the host instance auto-deploys them; remove the per-stack service and profile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-06 11:58:49 -04:00
justin 768d1b23d4 Add Watchtower auto-deploy for app images (2-minute poll)
Watchtower (profile-gated) watches only the label-enabled backend/frontend containers and recreates them when a new :test-main digest lands in the registry, polling every 120s. Scoped by label so it never touches Postgres/MinIO/Caddy/cloudflared. Reads registry creds from the host docker config. Lab host runs COMPOSE_PROFILES=tunnel,watchtower.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-06 11:55:38 -04:00
justin 828445a6b3 Add Cloudflare Tunnel connector (profile-gated) to the deploy stack
A cloudflared service (opt-in via the 'tunnel' compose profile, token from CLOUDFLARE_TUNNEL_TOKEN) connects the lab to Cloudflare. One public hostname -> http://caddy:80 is sufficient because Caddy does the internal path routing. Mirrors the drawbar tunnel setup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-06 11:32:15 -04:00
justin 4921ce0776 Mirror drawbar CI/CD: push to LAN registry, pull via public FQDN
Split the registry endpoints like the drawbar containers. Per-component Gitea Actions workflows (build-backend, build-frontend; runs-on docker, path-filtered) push images to the LAN endpoint 192.168.0.2:1234 over plain HTTP (buildx insecure/http) to bypass Cloudflare's request-body limit, then link each package to the repo via the Gitea API. Auth via the REGISTRY_TOKEN Actions secret (the same token drawbar uses). Tag scheme: test-main / test-sha-<long> / version / latest (v* tags).

The deploy compose now PULLS git.jpaul.io/justin/provenance-{backend,frontend}:${IMAGE_TAG:-test-main} (no host build); docker-compose.dev.yml is a local-build override for dev / pre-CI. Replaces the previous single build.yml. Docs + memory updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-06 11:19:26 -04:00
justin fccc81a6cc Wire the frontend into the deploy stack and CI
Compose gains a frontend service; Caddy now routes / to frontend:3000 (keeping /api/* and /health* on the backend). CI builds and pushes a frontend image alongside the backend. Verified end-to-end on the deploy target: / serves the app, /api and /health still resolve through Caddy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-06 11:03:07 -04:00
justin 0b5c3b260a Add self-host compose stack (Postgres, MinIO, backend, Caddy)
One env-driven compose stack stands up the whole system per ARCHITECTURE §2/§12. Postgres uses the pgvector image (pgvector + pg_trgm in contrib); MinIO is the S3-compatible store; Caddy reverse-proxies /api/* and /health* to the backend with an env-driven site address (':80' local, a domain for auto-HTTPS, or plain HTTP behind a Cloudflare Tunnel). Healthchecks and depends_on gate startup order.

.env.example documents twelve-factor config (DB, S3, SMTP, Caddy, model keys) with placeholders; no secrets in the repo. Verified end-to-end on the deploy target: all services healthy, /health/ready green against real Postgres.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-06 10:17:12 -04:00