fix(vaultwarden): _ENABLE_SMTP boot-gate — fresh email-off deploys crash-looped (campaign F1)

Vaultwarden treats defined-but-empty SMTP_HOST/SMTP_FROM as "set"; with upstream
_enable_smtp defaulting true its validation errors out and the container
crash-loops on every fresh deploy with app-email off (the default). Gate the
SMTP group with _ENABLE_SMTP: compose default false (clean boot, mail off),
flipped "true" by the app-email injection via smtp_mapping.extra. Proven on the
pinned 1.33.2-alpine image (P1 repro exit 12 / P2 off boots / P3 on boots).
REUSE.md: strict-image TRAP row. Sweep note: the other 5 smtp-mapped templates
are campaign-boot-proven tolerant; no edits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-06 14:01:11 +02:00
parent 13eedb1a83
commit d86e25662f
5 changed files with 78 additions and 45 deletions
+43 -43
View File
@@ -1,51 +1,51 @@
# REPORT — sparkyfitness finalized: image-inspected healthchecks + probe-container naming (2026-07-03)
# REPORT — vaultwarden F1 fix: `_ENABLE_SMTP` boot-gate (2026-07-06)
**Class:** template finalization + live validation (no version scheme; push = deploy).
**Baseline:** `main` @ `15cdf7b`**`c7490f7`**. Companion: the NEW `felhom-app-catalog` skill
(felhom.eu — this task was its first worked example; see that repo's REPORT for the skill half).
**Class:** implementation (campaign-finding fix; push = deploy).
**Baseline:** `main` @ `13eedb1` → (this commit). Provenance: felhom.eu
`documentation/audits/CAMPAIGN-nomercy-2026-07-06.md` finding **F1** (pilot-blocking: every fresh
vaultwarden deploy with app-email off — the default — crash-loops).
## What changed
## Corrected root cause (the campaign report's hypothesis was wrong)
- `templates/sparkyfitness/docker-compose.yml`:
- Both `VERIFY-BEFORE-FINALIZE` markers RESOLVED by inspecting the actual images on the demo box
(the skill's core rule — never guess):
- **frontend** `codewithcj/sparkyfitness:v0.17.2` (Alpine/nginx): has `/usr/bin/wget` (and
curl; no node/python3) → the drafted BusyBox-wget `--spider :80/` probe kept, marker
replaced with the inspection provenance.
- **server** `codewithcj/sparkyfitness_server:v0.17.2`: has node v24.17.0 (and wget+curl) →
node-exec GET `:3010/api/health` kept (checks the real status, not mere reachability);
`/api/health` proven live: `{"status":"UP"}`.
- **Frontend `container_name` renamed `sparkyfitness-frontend``sparkyfitness`**: the
controller-side probe dials the container named exactly like the stack (fallback = FIRST
prefix match — could be the DB). New REUSE.md §2 row records the convention.
- Both image tags reconfirmed to resolve (`docker manifest inspect`).
- `templates/sparkyfitness/.felhom.yml`: mem-sum arithmetic comment added
(db 512 + server 1024 + frontend 256 = **1792M** — matches the existing value; nothing corrected).
- `REUSE.md` §2: NEW "Probe-container naming" row (verified against
`felhom-controller/internal/stacks/healthprobe.go` `findProbeContainer`).
Not "shim sets SMTP_FROM without SMTP_HOST" — with app-email off the shim injects **nothing**. The
crasher: the template always defines `SMTP_HOST=${SMTP_HOST:-}` and `SMTP_FROM=${SMTP_FROM:-}`;
vaultwarden's `get_env_str_value` returns `Some("")` for a defined-empty var (**empty counts as
set**), and with `_enable_smtp` defaulting `true` upstream, its validation
`smtp_host.is_some() == smtp_from.is_empty()``true == true` → config error → exit → crash-loop.
The old template comment ("empty SMTP_HOST = mail stays disabled") was wrong for this image.
## Live validation (demo guest 9201; method stated per claim)
## Empirical probe (mandatory gate, run on 180 against the pinned image BEFORE editing)
SparkyFitness was already running from the draft (deployed 4 h earlier, 3× healthy). Applying the
finalized template used the REAL dashboard UI via the browser bridge: "Sablonok frissítése" (sync
confirmed by the guest's stacks file gaining the rename, zero VERIFY markers), then the card's
"Frissítés". Result:
| Probe | Env | Result |
|---|---|---|
| P1 (pre-fix shape) | `SMTP_HOST=` `SMTP_FROM=` (defined-empty) | **exit 12***"Both `SMTP_HOST` and `SMTP_FROM` need to be set for email support without `USE_SENDMAIL`"* (exact campaign error) — the image-level red-proof |
| P2 (fix, OFF) | + `_ENABLE_SMTP=false` | **boots** — "Rocket has launched", running |
| P3 (fix, ON) | `_ENABLE_SMTP=true` + host + from | **boots** — running (no SMTP connect at boot) |
- Only the renamed frontend was recreated `sparkyfitness … Up 21 seconds (healthy)`; server + db
stayed `Up 4 hours` → **the two `data_key` secrets (API_ENCRYPTION_KEY, BETTER_AUTH_SECRET) were
NOT regenerated** (deployed app.yaml/.env untouched, exactly the data_key contract).
- Docker health: 3/3 healthy. Controller-side probe (via `GET /api/stacks/sparkyfitness`, the same
data the UI polls): `healthy: true — http :80/ → 200 (6ms)` against the renamed container.
- `sparky.demo-felhom.eu` answers **200** via Traefik in-guest (Host-header curl).
- **Kept deployed** on demo (7.3 option A — expendable box, real catalog app). The data_key
secrets exist only in the deployed app.yaml, never committed.
(P2/P3 needed `I_REALLY_WANT_VOLATILE_STORAGE=true` to bypass the unrelated no-volume guard in the
bare `docker run`; P1 control re-run with it still crashes on the SMTP error, isolating the cause.)
Logs: DooPlex `~/campaign-nomercy-2026-07-05/evidence/PROBE-F1/`.
## Notes / observations (not acted on)
## Changes
- Pre-rename, the controller probe was ALSO green — the prefix fallback happened to pick the
frontend. The rename converts luck into the deterministic convention.
- `paperless-ngx`'s container names match neither the exact nor the prefix rule of its stack name —
its controller-side probe likely never runs. Candidate follow-up, not touched.
- BusyBox `ash` `command -v` silently ignores all but its first argument — inspect one tool per
run (now in the skill).
- App-count drift recorded in the felhom.eu REPORT (53 templates vs website "45+" / category sum 49).
- `templates/vaultwarden/docker-compose.yml``_ENABLE_SMTP=${_ENABLE_SMTP:-false}` added to the
SMTP group; the wrong comment replaced with the empty-counts-as-set TRAP note (+ config.json
admin-panel precedence note). Nothing else touched.
- `templates/vaultwarden/.felhom.yml``smtp_mapping.extra: _ENABLE_SMTP: "true"` (rides the
existing `smtpEnv` extra loop; zero controller change).
- `REUSE.md` — TRAP appended to the App-email row: strict images need the enable-flag pattern;
boot-prove fresh email-off deploys for every new smtp-mapped app.
- `CHANGELOG.md` — entry incl. the sweep note.
## Sweep note (documentation only, no edits)
calcom, gitea, mealie, nextcloud, rallly (and gitea's `GITEA__mailer__SMTP_ADDR=${...:-}`) are
**boot-proven tolerant** of defined-empty mail env — all ran healthy as fresh email-off deploys in
the campaign. Vaultwarden was the only strict image.
## Validation
- Probe gate P1/P2/P3 above (image level).
- Live pipeline validation (Scenario A fresh email-off deploy healthy; Scenario B toggle ON→OFF
regression) executed as part of the controller v0.101.0 deployment — see
felhom-controller/REPORT.md for the live evidence.