ci(env-check): enforce manifest <-> example lockstep

scripts/check-env.ts shipped in v1.4.42 but only ran locally. A new
workflow runs pnpm check-env --file .env.production.example on every
push and PR against main/develop. Adds .env.production.example so a
contributor introducing a new required env var to the manifest must
update the example in the same PR, otherwise the workflow fails with
grep-friendly annotations naming every missing variable.

The example carries placeholder strings for every required group so
the presence check passes; optional groups are commented out so
operators can see the full surface without the workflow flagging
silent-disable patterns as failures.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 21:46:12 +02:00
1 parent 938d840670
commit a5c244c4d9
2 files changed
+135

No files matched your search

+62
View File
@@ -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 `<placeholder>` 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:<password>@db:5432/healthlog?schema=public"
POSTGRES_PASSWORD="<openssl rand -base64 24>"
NEXT_PUBLIC_APP_URL="https://healthlog.example.com"
APP_URL="https://healthlog.example.com"
ENCRYPTION_KEY="<openssl rand -hex 32>"
API_TOKEN_HMAC_KEY="<openssl rand -hex 32>"
# -----------------------------------------------------------------------------
# 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=""
+73
View File
@@ -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