Files
HealthLog/docs/ops/env-check.md
T
Marc-André Bombeck 9b6e237f5f chore(release): v1.4.42 — Knip enforcing, queryKey factory closed, iOS Workouts dedup, Withings off-response classification
Six touch-disjoint waves landed on develop before this release:

- W1-KNIP — exports/types tiers flipped to enforcing-mode on main;
  35 unused exports + 52 unused types triaged to 0/0; shadcn surface
  + zod-infer contract types scoped via knip.json ignore-block.
- W2-ZOD-MULTI-ISSUE — `returnAllZodIssues` shared helper +
  `/api/dashboard/widgets` 422 now returns every Zod issue + worker
  audit-ledger breadcrumb. 41 sibling routes documented for v1.4.43.
- W3-QUERYKEY-LONGTAIL — 40 settings/medications/admin/hooks files
  migrated to the queryKeys factory; 9 new factory entries; ESLint
  rule + test-guard scope extended in lockstep.
- W4-ARCH-HYGIENE — BERLIN_DAY_FORMATTER consolidated across nine
  sites (seven status helpers + bucket-series + bp-in-target);
  Suspense double-comment consolidated; doctor-report sanitiser regex
  rewritten to escape-form so diffs are reviewable; pr-detection-
  worker soft-delete filter; offhost-backup DR-intent comment.
- W5-IOS-WORKOUTS — `dedupeWorkoutBatch` write-time cross-source
  dedup helper for the v1.5 iOS HealthKit observer ingest; dashboard
  tile-strip placeholder parity with the live TrendCard chrome.
- W6-WITHINGS-OFF + ENV-CHECK — Withings off-response classifier
  (transient/reauth/persistent) + WithingsApiError typed throw + the
  `pnpm check-env` CLI. APNs `allOrNone: true` closes the AP-2
  silent-disable gap the wave was conceived to close.

No migration, no env-var change, no API contract break for iOS v0.5.4.
The new `/api/dashboard/widgets` 422 envelope is additively extended.

pnpm test --run green at 4815 passing / 1 skipped.
pnpm typecheck, pnpm lint, pnpm knip (no --include flag) green.
2026-05-21 19:33:07 +02:00

100 lines
3.8 KiB
Markdown

# 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
```sh
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
```sh
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. The CI integration itself
is deferred to v1.4.43 — this release ships the CLI tool only.
## 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
```
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:
1. Pick the right group (or add a new one if the variable doesn't
belong to any existing category).
2. Decide whether the variable is required for boot, optional, or
part of an all-or-none feature group.
3. Document the variable's `purpose` in one sentence — the operator
reads this text when the var shows up as missing.
4. Run `pnpm check-env` locally against your live shell to confirm
the manifest still passes (or fails for the right reason).
5. Add a regression test under `scripts/__tests__/check-env.test.ts`
if the manifest change introduces a new classification branch
(anyOf, allOrNone, …).
## Future: CI gate
Deferred to v1.4.43:
- GitHub Actions workflow that runs `pnpm check-env --file
.env.production.example` on every PR, blocking changes that
introduce new required vars without updating the example file.
- Coolify pre-deploy hook that runs `pnpm check-env` inside the
target container and aborts the deploy on exit code 1.