app-email: smtp_mapping for vaultwarden + mealie
Vaultwarden via STARTTLS (accepts self-signed shim cert); Mealie via plaintext
(NONE) — no accept-invalid-cert option, spike-validated mode. Compose files
reference injected ${SMTP_*}. README documents the pattern.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,110 +1,38 @@
|
||||
# REPORT — Add SparkyFitness to the app catalog (+ live deploy & health check)
|
||||
# REPORT — App-email `smtp_mapping` for Vaultwarden + Mealie
|
||||
|
||||
**Date:** 2026-06-28
|
||||
**Task class:** Implementation — pure catalog addition (no Go/controller/agent/hub code change).
|
||||
**Outcome:** ✅ Template added, synced, deployed live on guest 9201, all 3 containers healthy, end-to-end
|
||||
`/api` path verified through the public edge. One acceptance item (interactive account register/login) was
|
||||
**deliberately left for a human** — see "NOT validated" below.
|
||||
**Date:** 2026-06-29
|
||||
**Task:** SMTP app-relay (catalog leg). Add `smtp_mapping` to the two spike-proven apps so deployed apps can
|
||||
send outbound email via the managed path (app → in-controller shim → hub → Resend). Implements
|
||||
`felhom.eu/documentation/audits/SPIKE-smtp-app-relay-2026-06-28.md` §7.
|
||||
|
||||
---
|
||||
## Files modified
|
||||
- `templates/vaultwarden/.felhom.yml` — added `smtp_mapping` (STARTTLS; SMTP_SECURITY=starttls,
|
||||
SMTP_FROM=vaultwarden@felhom.eu, SMTP_FROM_NAME, + SMTP_ACCEPT_INVALID_CERTS/HOSTNAMES). SMTP_USERNAME/
|
||||
SMTP_PASSWORD intentionally unset.
|
||||
- `templates/vaultwarden/docker-compose.yml` — reference the injected `${SMTP_*}` keys (harmless defaults).
|
||||
- `templates/mealie/.felhom.yml` — added `smtp_mapping` (plaintext; SMTP_AUTH_STRATEGY=NONE on :2525,
|
||||
SMTP_FROM_EMAIL=mealie@felhom.eu, SMTP_FROM_NAME).
|
||||
- `templates/mealie/docker-compose.yml` — reference the injected `${SMTP_*}` keys (harmless defaults).
|
||||
- `README.md` — documented the `smtp_mapping` pattern (field table + STARTTLS-vs-plaintext guidance) so further
|
||||
apps are easy adds.
|
||||
- `CHANGELOG.md` — entry.
|
||||
|
||||
## 1. Confirmed baselines
|
||||
## How it works
|
||||
When app-email is on (global toggle + per-app toggle), the controller injects at deploy/redeploy:
|
||||
`SMTP_HOST` = the on-box shim, `SMTP_PORT` = 2525, From = `<app>@felhom.eu`, the app's security value, plus the
|
||||
mapping's fixed `extra` vars. The values are derived from settings each compose — never persisted to `app.yaml`.
|
||||
An empty `SMTP_HOST` (toggle off) leaves the app's mail disabled.
|
||||
|
||||
| Repo | Baseline | After |
|
||||
|------|----------|-------|
|
||||
| app-catalog-felhom.eu | `main` @ `d7a6a17` (working tree clean; `templates/sparkyfitness/` was 404) | template commit `4351d08`, docs commit (this push) |
|
||||
| felhom-controller (running on demo) | **v0.87.0** (`gitea.dooplex.hu/admin/felhom-controller:0.87.0`, healthy on guest 9201) | unchanged — template proven-compatible against 0.87.0 |
|
||||
## Validation
|
||||
- Both `.felhom.yml` and both `docker-compose.yml` YAML-validated OK.
|
||||
- Field names match the controller's `stacks.SMTPMapping` struct tags exactly (covered by the controller's
|
||||
`TestMetadata_SMTPMappingParse` and the Vaultwarden/Mealie env-injection tests).
|
||||
- Git sync picks up the change within ~15 min (or via "Sablonok frissítése").
|
||||
|
||||
The running controller renders this template's `type: subdomain` + `data_key` fields correctly (deploy
|
||||
screen showed Domain auto-filled, Subdomain `sparky`, and all 4 auto-secrets) — so **no STOP condition**;
|
||||
no controller patch was needed.
|
||||
|
||||
## 2. Files created
|
||||
|
||||
- `templates/sparkyfitness/docker-compose.yml`
|
||||
- `templates/sparkyfitness/.felhom.yml`
|
||||
|
||||
Commits on `main`:
|
||||
- `4351d08` — the two template files (after lint + image probes).
|
||||
- (this push) — CHANGELOG + README + REPORT docs.
|
||||
|
||||
`templates.json` (legacy Portainer index) intentionally left untouched. No other catalog apps modified.
|
||||
|
||||
## 3. §1.3 image healthcheck-tooling findings (verified on build server 192.168.0.180, v0.17.2)
|
||||
|
||||
| Image | Probe authored | Finding | Final probe kept |
|
||||
|-------|----------------|---------|------------------|
|
||||
| `codewithcj/sparkyfitness_server:v0.17.2` | `node -e` GET `/api/health` | `NODE_OK` (node on PATH via `--entrypoint node`) | **kept** the node-exec probe |
|
||||
| `codewithcj/sparkyfitness:v0.17.2` (frontend) | `wget --spider` | both present: `/usr/bin/wget` **and** `/usr/bin/curl` | **kept** the `wget --spider` probe |
|
||||
|
||||
No probe changes were required. Compose lints clean
|
||||
(`docker compose config -q` with the 4 `${...}` vars set to dummies → `LINT_OK`).
|
||||
|
||||
## 4. Deploy method + evidence
|
||||
|
||||
**Method:** real UI flow via **claude-in-chrome** (no hand-written `app.yaml`).
|
||||
1. `https://felhom.demo-felhom.eu` → Alkalmazások → **"Sablonok frissítése"** (sync). App count 55→56,
|
||||
Telepíthető 52→53; SparkyFitness card appeared (`/stacks/sparkyfitness/deploy`).
|
||||
2. Deploy screen: Domain auto = `demo-felhom.eu`; Subdomain default `sparky`; 4 secrets shown
|
||||
"Automatikusan generálva"; memory gate **696 MB / 1664 MB (41%)** → fits, **not refused** (Scenario C
|
||||
did not trigger). Clicked **"Telepítés indítása"** → "Konfiguráció mentve" → containers started.
|
||||
|
||||
**`docker ps` (guest 9201):**
|
||||
```
|
||||
sparkyfitness-frontend codewithcj/sparkyfitness:v0.17.2 Up (healthy)
|
||||
sparkyfitness-server codewithcj/sparkyfitness_server:v0.17.2 Up (healthy)
|
||||
sparkyfitness-db postgres:15-alpine Up (healthy)
|
||||
```
|
||||
|
||||
**Server log excerpts (clean — Scenario A):**
|
||||
```
|
||||
[INFO] Successfully applied migration: ...20260622120000_add_water_estimated_to_exercise_entries.sql
|
||||
[INFO] Ensuring permissions for role: "sparkyapp"
|
||||
[INFO] Successfully ensured permissions for role: "sparkyapp"
|
||||
[INFO] Permissions granted to application user.
|
||||
[INFO] Successfully applied all RLS policies.
|
||||
[AUTH] Better Auth handler successfully mounted.
|
||||
[INFO] SparkyFitnessServer listening on port 3010
|
||||
[INFO] Incoming request: GET /api/health
|
||||
```
|
||||
No crash loop; **no EACCES** on `/app/SparkyFitnessServer/uploads`. Both DB roles confirmed present in the
|
||||
DB (`SELECT rolname ... → sparky, sparkyapp`).
|
||||
|
||||
**Secrets (Scenario B) — verified without exposing values** (from `docker inspect` env of the server):
|
||||
- `SPARKY_FITNESS_API_ENCRYPTION_KEY` length = **64** (hex:32 ✓)
|
||||
- `BETTER_AUTH_SECRET` length = **64** (✓)
|
||||
- `SPARKY_FITNESS_FRONTEND_URL = https://sparky.demo-felhom.eu` (public https ✓ — CORS/Better-Auth origin)
|
||||
- `SPARKY_FITNESS_DB_USER = sparky`, `SPARKY_FITNESS_APP_DB_USER = sparkyapp` (two distinct roles ✓)
|
||||
- `SPARKY_FITNESS_DISABLE_SIGNUP = false` (household can register ✓)
|
||||
All four secrets are `locked_after_deploy`; the two crypto keys carry `data_key: true`.
|
||||
|
||||
**Browser checks:**
|
||||
- `https://sparky.demo-felhom.eu/login` → SparkyFitness **native email/password login + Sign Up** page
|
||||
loads over a **valid TLS cert** (no warning). No OIDC buttons (correct for the customer template).
|
||||
- `https://sparky.demo-felhom.eu/api/health` (through the public Cloudflare edge → Traefik → frontend
|
||||
nginx `/api` proxy → server) → **`{"status":"UP"}`**. This proves the frontend→server proxy + public
|
||||
routing + TLS end-to-end.
|
||||
|
||||
## 5. NOT yet validated (explicit)
|
||||
|
||||
- **Interactive account register + login.** Deliberately not performed by me: creating an account /
|
||||
entering credentials to authenticate falls under a browser-safety guardrail (prohibited even on
|
||||
request). Everything up to the authenticated API path is proven (healthy server, `/api/health` proxied
|
||||
through the public edge, CORS origin correctly set). **Recommended human step:** open
|
||||
`https://sparky.demo-felhom.eu`, click **Sign Up**, register a throwaway account, confirm login reaches
|
||||
the dashboard (≈30s). Expected to pass given the verified config.
|
||||
- **Backup / restore of this app** (incl. the `data_key` recovery path for the two crypto keys) — not
|
||||
exercised here.
|
||||
- **Upload body-size limits** for profile/exercise images (the k3s ingress set `proxy-body-size: 50m`;
|
||||
the Felhom Traefik path was not size-tested).
|
||||
- **Garmin / MCP integrations** — out of scope (commented out upstream; not enabled).
|
||||
|
||||
## 6. Observations (noticed, not acted on)
|
||||
|
||||
- Upstream's compose uses `postgres:18` + `:latest`; per the validated k3s manifest we pinned
|
||||
`postgres:15-alpine` with an explicit `PGDATA` subdir — kept that proven path.
|
||||
- The frontend image ships **both** wget and curl; the server image runs node as a valid exec entrypoint —
|
||||
both healthchecks are robust without extra tooling.
|
||||
- Demo guest 9201 has only 2 GB total RAM; SparkyFitness `mem_limit` is 1792M. The `mem_request` (400M)
|
||||
gate passed and real usage is far below the limit, but on a 2 GB box co-tenancy headroom is thin — worth
|
||||
noting for nodes running several heavier apps.
|
||||
## Notes / deviation
|
||||
- **Mealie uses plaintext (NONE), not STARTTLS.** Mealie has no accept-invalid-cert option, so STARTTLS to the
|
||||
self-signed shim would fail cert verification; the spike validated Mealie via plaintext on :2525 (the shim is
|
||||
Docker-network-only, never published). This is the spike-faithful choice (spike §7 mealie table = NONE),
|
||||
deviating from the prompt's Part-4 "TLS for mealie" wording.
|
||||
- **Scope = these two proven apps only.** Further apps (immich, nextcloud, …) are a follow-on: each just needs
|
||||
its own `smtp_mapping` block + the matching compose `${VAR}` lines.
|
||||
|
||||
Reference in New Issue
Block a user