- attempt-number-semantics: name the real bucket column
(consecutiveFailuresByKind) and drop a drifted line citation
- backup-restore: show the smoke-test response inside the standard
{ data, error } envelope
- deploy: the manual migration step now uses the bundled Prisma CLI
via node (the image strips pnpm); WHOOP vars are already on the
shipped compose whitelist
- encryption-key-rotation: point at the encrypted-column registry
instead of a five-model list several releases behind
- env-check: the CI gate shipped (env-check.yml); the partial-APNs
example now shows the synthetic all-or-none row and the non-zero exit
- password-reset: the admin API reset route exists; only the
self-service route does not
- tls-cert-pin: stop asserting client-repo pin-set details that cannot
be audited from this repository
- drop v141-followup-issues.md: a v1.4.1 punch list whose items are
fixed, removed, or superseded; keeping it under docs/ops invites
re-fixing shipped work
4.0 KiB
Pre-deploy env-var sanity check
pnpm check-env reads scripts/env-manifest.json and verifies every
variable declared required: true is present + non-empty in the
runtime environment. Optional groups are surfaced too, so an operator
can spot the "3 out of 4 vars set" pattern that silently disables a
feature.
This is the answer to the v1.4.40 AP-2 silent-disable: the .p8 file
went missing during a Coolify migration, three of four APNS_* vars
remained set, the app booted cleanly, and APNs notifications stopped
working — undetected for three days because the dispatcher's fallback
chain hid the gap behind the Telegram → ntfy → Web Push path.
Usage
Against the live shell
pnpm check-env
Runs against process.env (after the local .env / .env.local load,
if any). Exit code 0 on green, 1 on at least one required variable
missing, 2 on a malformed manifest.
Against a Coolify export / .env.production
pnpm check-env --file /path/to/.env.production
The file is parsed locally (no shell execution, no variable expansion) and checked against the manifest. Useful for spot-checking a Coolify export without polluting the local shell.
Output format
Output is one row per declared variable, grouped by section. Three states:
| Marker | Meaning |
|---|---|
[OK] |
Variable is set and non-empty. |
[MISSING-REQUIRED] |
Variable is required for boot. The script exits 1. |
[missing-optional] |
Variable is optional. The script reports it but does not fail. |
The grep-friendly format lets a CI pipeline do grep -q '\[MISSING-REQUIRED\]' to gate a deploy. CI already does: the
env-check.yml workflow runs pnpm check-env --file .env.production.example on every PR and fails on missing required
vars.
All-or-none groups
Some groups (e.g. off-host backups) are marked allOrNone: true in
the manifest. When such a group is partially populated — some vars
set, some missing — the script emits a synthetic <all-or-none> row
with severity REQUIRED, even if the surrounding group is required: false. This is the v1.4.40 AP-2 detection pattern.
Example output for a partial APNs config:
# APNs (iOS push)
[OK] APNS_KEY_ID
[OK] APNS_TEAM_ID
[OK] APNS_BUNDLE_ID
[missing-optional] APNS_KEY — Satisfied by any of: APNS_KEY, APNS_KEY_FILE
[MISSING-REQUIRED] <all-or-none> — Group is all-or-none but only 3/4 variables are set. Either set all or unset all.
The APNs group is allOrNone: true, so the partial config also emits
the synthetic <all-or-none> row at REQUIRED severity and the run
exits 1. A reviewer immediately sees that APNS_KEY (or its
APNS_KEY_FILE alternative) is missing while the other three APNs
vars are set — the exact configuration that silently disables iOS
push.
Editing the manifest
The manifest lives at scripts/env-manifest.json and is git-tracked
so changes go through a PR. To add a new variable:
- Pick the right group (or add a new one if the variable doesn't belong to any existing category).
- Decide whether the variable is required for boot, optional, or part of an all-or-none feature group.
- Document the variable's
purposein one sentence — the operator reads this text when the var shows up as missing. - Run
pnpm check-envlocally against your live shell to confirm the manifest still passes (or fails for the right reason). - Add a regression test under
scripts/__tests__/check-env.test.tsif the manifest change introduces a new classification branch (anyOf, allOrNone, …).
CI gate
The GitHub Actions workflow .github/workflows/env-check.yml runs
pnpm check-env --file .env.production.example on every PR, blocking
changes that introduce new required vars without updating the example
file.
Still an idea only: a Coolify pre-deploy hook that runs
pnpm check-env inside the target container and aborts the deploy on
exit code 1.