From a80086ef723634c1e6f2c7126facfea6b5f0f165 Mon Sep 17 00:00:00 2001 From: Khalid Abdi Date: Sat, 13 Jun 2026 19:15:11 +0300 Subject: [PATCH] =?UTF-8?q?backend:=20zero-setup=20Docker=20=E2=80=94=20au?= =?UTF-8?q?to-generate=20&=20persist=20secrets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `docker compose up` now works with no .env editing. A new entrypoint (docker-entrypoint.sh) generates any missing secret (BETTER_AUTH_SECRET, AI_CREDENTIALS_KEY) on first start and persists it to the temetro_secrets volume, so values stay stable across restarts (rotating them would log users out / invalidate stored AI keys). Real values passed via .env/compose still win. This also fixes the boot failure where the compose backend ran with NODE_ENV=production but never passed AI_CREDENTIALS_KEY through, tripping the production guard. The secret is now an optional pass-through and BETTER_AUTH_SECRET no longer hard-fails when unset. Co-Authored-By: Claude Opus 4.8 --- backend/Dockerfile | 6 +++++- backend/README.md | 11 ++++++---- backend/docker-compose.yml | 16 +++++++++++--- backend/docker-entrypoint.sh | 42 ++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 backend/docker-entrypoint.sh diff --git a/backend/Dockerfile b/backend/Dockerfile index 7b3ec26..0e834a7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -18,6 +18,10 @@ COPY package*.json ./ RUN npm ci --omit=dev COPY --from=build /app/dist ./dist COPY --from=build /app/drizzle ./drizzle +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh EXPOSE 4000 -# Apply migrations, then start the API. +# The entrypoint auto-generates any missing secrets, then we apply migrations +# and start the API. +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] CMD ["sh", "-c", "node dist/migrate.js && node dist/index.js"] diff --git a/backend/README.md b/backend/README.md index 6fdb683..ffe01ab 100644 --- a/backend/README.md +++ b/backend/README.md @@ -9,12 +9,15 @@ shape exactly. ## Quick start (Docker) ```bash -cp .env.example .env -# set a strong secret: -# openssl rand -base64 32 -> paste into BETTER_AUTH_SECRET -docker compose up # db + backend + frontend +docker compose up # db + backend + frontend — no setup needed ``` +No `.env` or manual secret generation is required: on first start the backend +generates any missing secrets (`BETTER_AUTH_SECRET`, `AI_CREDENTIALS_KEY`) and +persists them to a Docker volume, so they stay stable across restarts. Create a +`.env` (from `.env.example`) only if you want to **override** something — your +own auth secret, SMTP, etc. + - Frontend → http://localhost:3000 - Backend → http://localhost:4000 (health: `GET /health`, auth health: `GET /api/auth/ok`) - Postgres → localhost:5432 diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 3e17d4a..c9b977c 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,7 +1,10 @@ # Full-stack dev/run orchestration for temetro. # -# cp .env.example .env # then set BETTER_AUTH_SECRET -# docker compose up # db + backend + frontend +# docker compose up # db + backend + frontend — that's it. +# +# No .env or secret setup is required: the backend generates any missing +# secrets on first start and persists them (see docker-entrypoint.sh). Create a +# .env only if you want to override something (SMTP, a real auth secret, etc.). # # Frontend -> http://localhost:3000 # Backend -> http://localhost:4000 @@ -41,7 +44,10 @@ services: condition: service_healthy environment: DATABASE_URL: postgres://temetro:temetro@db:5432/temetro - BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET in .env (openssl rand -base64 32)} + # Secrets are optional: if unset, the entrypoint generates and persists + # them (to the temetro_secrets volume). Pass real values here to override. + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-} + AI_CREDENTIALS_KEY: ${AI_CREDENTIALS_KEY:-} BETTER_AUTH_URL: http://localhost:4000 FRONTEND_URL: http://localhost:3000 PORT: "4000" @@ -51,6 +57,9 @@ services: SMTP_USER: ${SMTP_USER:-} SMTP_PASS: ${SMTP_PASS:-} SMTP_FROM: ${SMTP_FROM:-temetro } + volumes: + # Persists auto-generated secrets so they stay stable across restarts. + - temetro_secrets:/var/lib/temetro ports: - "4000:4000" @@ -78,3 +87,4 @@ services: volumes: temetro_pgdata: + temetro_secrets: diff --git a/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh new file mode 100644 index 0000000..f33f885 --- /dev/null +++ b/backend/docker-entrypoint.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# Auto-provision the secrets temetro needs, so `docker compose up` works with +# zero manual setup. Any secret not supplied via the environment is generated +# and persisted to a Docker volume, so it stays stable across restarts (which +# matters: rotating BETTER_AUTH_SECRET logs everyone out, and rotating +# AI_CREDENTIALS_KEY invalidates stored AI provider keys). +# +# Precedence for each secret: a real value you pass in (.env / compose) wins; +# otherwise a previously generated one is reused; otherwise a new one is made. +set -e + +SECRETS_FILE="${SECRETS_FILE:-/var/lib/temetro/secrets.env}" +mkdir -p "$(dirname "$SECRETS_FILE")" +touch "$SECRETS_FILE" + +# 32 random bytes, base64 — no openssl dependency (busybox has base64). +random_secret() { + head -c 32 /dev/urandom | base64 | tr -d '\n' +} + +ensure_secret() { + name="$1" + current="$(printenv "$name" || true)" + case "$current" in + "" | dev-insecure-* | replace-me-*) + saved="$(grep "^${name}=" "$SECRETS_FILE" 2>/dev/null | tail -n1 | cut -d= -f2-)" + if [ -n "$saved" ]; then + export "$name=$saved" + else + val="$(random_secret)" + export "$name=$val" + printf '%s=%s\n' "$name" "$val" >>"$SECRETS_FILE" + echo "🔑 Generated $name (persisted; stable across restarts)" + fi + ;; + esac +} + +ensure_secret BETTER_AUTH_SECRET +ensure_secret AI_CREDENTIALS_KEY + +exec "$@"