diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 000000000..028b12a45 --- /dev/null +++ b/.env.production.example @@ -0,0 +1,62 @@ +# ============================================================================= +# HealthLog -- production .env example +# ============================================================================= +# Mirrors `scripts/env-manifest.json`. The `pnpm check-env --file +# .env.production.example` CI step (see `.github/workflows/env-check.yml`) +# enforces every variable declared `required: true` in the manifest has a +# non-empty placeholder here, so a contributor who introduces a new +# required var without updating this file fails the workflow. +# +# Placeholders are syntactic only -- replace with real secrets before +# deploying. The check-env presence rule treats any non-empty, +# non-whitespace string as "set", so the `` style below +# satisfies the manifest without leaking a usable credential. +# +# Generate the two crypto secrets locally with: +# $ openssl rand -hex 32 +# ============================================================================= + + +# ----------------------------------------------------------------------------- +# Core -- required for boot +# ----------------------------------------------------------------------------- +DATABASE_URL="postgresql://healthlog:@db:5432/healthlog?schema=public" +POSTGRES_PASSWORD="" +NEXT_PUBLIC_APP_URL="https://healthlog.example.com" +APP_URL="https://healthlog.example.com" +ENCRYPTION_KEY="" +API_TOKEN_HMAC_KEY="" + + +# ----------------------------------------------------------------------------- +# Withings OAuth -- optional integration +# ----------------------------------------------------------------------------- +# WITHINGS_CLIENT_ID="" +# WITHINGS_CLIENT_SECRET="" +# WITHINGS_WEBHOOK_SECRET="" + + +# ----------------------------------------------------------------------------- +# APNs -- iOS push (optional, all-or-none) +# ----------------------------------------------------------------------------- +# APNS_KEY_ID="" +# APNS_TEAM_ID="" +# APNS_BUNDLE_ID="" +# APNS_KEY="" # OR APNS_KEY_FILE -- either one satisfies the manifest +# APNS_KEY_FILE="" + + +# ----------------------------------------------------------------------------- +# Deploy webhook -- Coolify -> HealthLog auto-deploy feedback +# ----------------------------------------------------------------------------- +# DEPLOY_WEBHOOK_SECRET="" + + +# ----------------------------------------------------------------------------- +# Off-host backups -- all-or-none +# ----------------------------------------------------------------------------- +# BACKUP_S3_ENDPOINT="" +# BACKUP_S3_BUCKET="" +# BACKUP_S3_ACCESS_KEY="" +# BACKUP_S3_SECRET_KEY="" +# BACKUP_ENCRYPTION_KEY="" diff --git a/.github/workflows/env-check.yml b/.github/workflows/env-check.yml new file mode 100644 index 000000000..5612c2fc0 --- /dev/null +++ b/.github/workflows/env-check.yml @@ -0,0 +1,73 @@ +name: Env-manifest <-> example lockstep + +# v1.4.43 B5 -- enforce that `.env.production.example` stays in lockstep +# with `scripts/env-manifest.json`. A contributor adding a new required +# variable to the manifest must also add a placeholder to the example +# file in the same PR, otherwise the workflow fails with a grep-friendly +# list of missing required vars. Optional vars are reported but do not +# fail the build -- they exist for operator discovery, not deploy +# blocking. + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + workflow_dispatch: + +permissions: + contents: read + +jobs: + env-check: + name: pnpm check-env against .env.production.example + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Assert example file exists + run: | + if [ ! -f .env.production.example ]; then + echo "::error::.env.production.example is missing -- contributors must keep this file in lockstep with scripts/env-manifest.json. See docs/ops/env-check.md." + exit 1 + fi + + # The check-env script exits non-zero when at least one required + # variable from the manifest is missing/empty in the example file. + # We additionally grep the rendered output for `[MISSING-REQUIRED]` + # rows and surface them as GitHub Actions error annotations so the + # PR reviewer can see the missing var names without expanding the + # log. + - name: pnpm check-env --file .env.production.example + id: check + run: | + set +e + OUT=$(pnpm --silent check-env --file .env.production.example) + EC=$? + echo "$OUT" + if [ $EC -ne 0 ]; then + echo "" + echo "::group::Missing required variables" + echo "$OUT" | grep -E '^\s*\[MISSING-REQUIRED\]' | while IFS= read -r line; do + name=$(echo "$line" | sed -E 's/.*\[MISSING-REQUIRED\] ([A-Z0-9_]+).*/\1/') + echo "::error file=.env.production.example::Required env var $name missing from .env.production.example -- add a placeholder so the file stays in lockstep with scripts/env-manifest.json." + done + echo "::endgroup::" + echo "" + echo "::error::pnpm check-env reported missing required variables in .env.production.example. Either add a placeholder for each missing var, or downgrade the manifest entry to required:false if the variable is genuinely optional. See docs/ops/env-check.md." + exit $EC + fi