From 8fec8cde849283cc23e9623bca7c85f224741193 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Mon, 11 May 2026 21:33:28 +0200 Subject: [PATCH 1/2] fix(local-dev): docker-compose + .env.example cover the new required env vars (closes #334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fresh `docker-compose up -d` on this branch fails on startup because the compose file is missing several env vars the app now requires. This commit adds them with local-dev defaults and documents the same contract in `.env.example` so anyone running outside docker-compose (e.g. directly via Air or `go run ./cmd/server`) has a one-stop reference. Failure chain that this fixes (each gated the next): 1. scheduled-task auth init: SCHEDULED_TASK_AUTH_MODE unset 2. admin password resolution: ADMIN_PASSWORD_SECRET required 3. (after #333 lands) SECRET_PROVIDER=aws + empty AWS creds was working by luck; switching to `env` is now the correct local-dev resolver path 4. frontend admin-setup modal asks for an API key (sourced from API_KEY_SECRET_ARN → fails when the var is empty) `docker-compose.yml` (`app` service environment block): - SECRET_PROVIDER: aws → env (internal/secrets.EnvResolver, per internal/secrets/resolver.go:50 — pairs with EMAIL_ENABLED=false so the no-op email sender from #333 kicks in). - SCHEDULED_TASK_AUTH_MODE: disabled (internal/server/scheduledauth has no default and refuses to start when unset). - ADMIN_PASSWORD_SECRET / API_KEY_SECRET_ARN as VAR-NAME indirections pointing at ADMIN_PASSWORD_DEV / ADMIN_API_KEY_DEV (the EnvResolver pattern). Concrete dev values for both, plus ADMIN_EMAIL. - CREDENTIAL_ENCRYPTION_ALLOW_DEV_KEY=1 (gate the all-zero dev key per credentials.LoadKey — refuses to start without it). `.env.example` documents: - the new SECRET_PROVIDER=env contract (replaces the now-stale "env will fail" warning that pre-dated #333), - SCHEDULED_TASK_AUTH_MODE and EMAIL_ENABLED with one-line rationale, - the VAR-NAME-indirection pattern for *_SECRET / *_SECRET_ARN with the concrete dev values co-located so future readers can trace the chain in one file. Depends on PR #333 (no-op email sender) — without that, the email factory crashes on SECRET_PROVIDER=env. Sequencing intentional. Verification: - docker-compose up -d brings postgres + app + frontend to healthy - curl http://localhost:8080/api/health → HTTP 200 - admin-setup modal accepts the documented dev defaults --- .env.example | 50 +++++++++++++++++++++++++++++++++++++--------- docker-compose.yml | 25 ++++++++++++++++++++--- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 5b8b6d3e..0cecca2e 100644 --- a/.env.example +++ b/.env.example @@ -11,14 +11,35 @@ # --------------------------------------------------------------------- # Required: secrets resolver # --------------------------------------------------------------------- -# SECRET_PROVIDER selects which cloud secret store the resolver fetches -# from. Default `aws` matches the email factory's default backend -# (internal/email/factory.go) — using a value the email factory does -# not understand (e.g. `env`) makes app startup fail because email -# init runs unconditionally. For local dev that doesn't actually call -# email or secret-store paths, `aws` is a safe placeholder. -# aws | gcp | azure -SECRET_PROVIDER=aws +# SECRET_PROVIDER selects which secret store the resolver fetches from. +# aws | gcp | azure — production: real Secrets Manager / Key Vault +# env — local dev: resolve secret names to env vars +# In `env` mode, every secret-ref var (ADMIN_PASSWORD_SECRET, +# API_KEY_SECRET_ARN, etc.) holds the NAME of another env var whose +# value is the actual secret. See `internal/secrets/env_resolver.go`. +# Pairs with EMAIL_ENABLED=false so the email factory's no-op sender +# kicks in (see PR #333) — otherwise `env` is not a recognised email +# backend and the factory would fail dispatch. +SECRET_PROVIDER=env + +# --------------------------------------------------------------------- +# Required: scheduled-task auth mode (no default — must be explicit) +# --------------------------------------------------------------------- +# Selects how the internal /api/scheduled/* endpoints authenticate. +# oidc — production: verify Google-issued OIDC ID tokens +# bearer — shared-secret token in Authorization header +# disabled — local dev: no auth check +# Required by `internal/server/scheduledauth/config.go`; app refuses +# to start when unset. +SCHEDULED_TASK_AUTH_MODE=disabled + +# --------------------------------------------------------------------- +# Required: email gate (factory short-circuit, see PR #333) +# --------------------------------------------------------------------- +# When `false`, internal/email/factory.go returns a no-op sender that +# logs each invocation at debug level instead of dispatching to a +# cloud-specific backend. Pair with SECRET_PROVIDER=env for local dev. +EMAIL_ENABLED=false # --------------------------------------------------------------------- # Required: credential encryption key @@ -35,9 +56,20 @@ CREDENTIAL_ENCRYPTION_ALLOW_DEV_KEY=1 # CREDENTIAL_ENCRYPTION_KEY=<64-hex-chars> # --------------------------------------------------------------------- -# Required for production: admin auth + API +# Required: admin auth + API # --------------------------------------------------------------------- +# With SECRET_PROVIDER=env, the *_SECRET / *_SECRET_ARN vars hold the +# NAME of another env var whose value is the actual secret. The matched +# env var must then be defined below. Two reasons for the indirection: +# (1) production uses the same var name pointing at a real ARN/name; +# (2) the dev value is co-located with its lookup key so future readers +# can trace the chain in one file. ADMIN_EMAIL=admin@example.com +ADMIN_PASSWORD_SECRET=ADMIN_PASSWORD_DEV +ADMIN_PASSWORD_DEV=LocalDev!Pass123 +API_KEY_SECRET_ARN=ADMIN_API_KEY_DEV +ADMIN_API_KEY_DEV=cudly-local-dev-api-key-not-for-prod +# Production examples (override SECRET_PROVIDER and these): # ADMIN_PASSWORD_SECRET=arn:aws:secretsmanager:us-east-1:000000000000:secret:cudly-admin-password-PLACEHOLDER # API_KEY_SECRET_ARN=arn:aws:secretsmanager:us-east-1:000000000000:secret:cudly-api-key-PLACEHOLDER diff --git a/docker-compose.yml b/docker-compose.yml index fa8d5d04..b006d78e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,10 +42,29 @@ services: DB_AUTO_MIGRATE: "true" DB_MIGRATIONS_PATH: /app/internal/database/postgres/migrations - # Secret provider (use aws with empty credentials for local dev) - SECRET_PROVIDER: aws - # Email disabled for local development (no SNS topic) + # Secret provider — "env" reads secrets from env vars (local-dev only). + # Production uses "aws" / "azure" / "gcp" with real Secrets Manager. + SECRET_PROVIDER: env + + # Admin password secret: the EnvResolver looks up the env var whose + # NAME equals ADMIN_PASSWORD_SECRET. Here it points at ADMIN_PASSWORD_DEV. + ADMIN_PASSWORD_SECRET: ADMIN_PASSWORD_DEV + ADMIN_PASSWORD_DEV: "LocalDev!Pass123" + ADMIN_EMAIL: admin@cudly.local + + # API-key secret: same pattern — name → env var that holds the value. + # Frontend asks for this in the admin-setup modal. + API_KEY_SECRET_ARN: ADMIN_API_KEY_DEV + ADMIN_API_KEY_DEV: "cudly-local-dev-api-key-not-for-prod" + + # Credential encryption: dev key (all-zero) is gated behind this flag. + CREDENTIAL_ENCRYPTION_ALLOW_DEV_KEY: "1" + + # Email disabled for local development (no SNS topic / SES creds) EMAIL_ENABLED: "false" + # Scheduled-task auth: OIDC for prod, disabled for local dev so the + # internal /api/scheduled/* endpoints don't require Google ID tokens. + SCHEDULED_TASK_AUTH_MODE: disabled # Application settings ENVIRONMENT: development From b7a5f6da3ea65fcdbbb23ef9ffbee4954c31087d Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Mon, 11 May 2026 21:44:17 +0200 Subject: [PATCH 2/2] fix(local-dev): align .env.example ADMIN_EMAIL with docker-compose default (CR pass on PR #335) CodeRabbit nitpick on PR #335: `.env.example` still listed `ADMIN_EMAIL=admin@example.com` while `docker-compose.yml` defaults to `admin@cudly.local`. The drift made the two reference points disagree about which placeholder a fresh checkout should use. Aligning on `admin@cudly.local` keeps both files telling the same story. --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 0cecca2e..8cea9a21 100644 --- a/.env.example +++ b/.env.example @@ -64,7 +64,7 @@ CREDENTIAL_ENCRYPTION_ALLOW_DEV_KEY=1 # (1) production uses the same var name pointing at a real ARN/name; # (2) the dev value is co-located with its lookup key so future readers # can trace the chain in one file. -ADMIN_EMAIL=admin@example.com +ADMIN_EMAIL=admin@cudly.local ADMIN_PASSWORD_SECRET=ADMIN_PASSWORD_DEV ADMIN_PASSWORD_DEV=LocalDev!Pass123 API_KEY_SECRET_ARN=ADMIN_API_KEY_DEV