42bf76f540
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.6 KiB
5.6 KiB
REPORT — App-email rollout: gitea + rallly wired (calcom/nextcloud/immich = findings)
Date: 2026-06-29
Task: Roll out the shipped app-email relay (controller v0.88.0 shim + smtp_mapping; hub v0.18.0 passthrough)
to calcom, rallly, gitea, nextcloud (+ investigate immich). Catalog-only; no controller/hub code change.
Baselines (verified live)
- felhom-controller v0.88.0 + hub v0.18.0 live (relay mechanism shipped). Global app-email toggle ON on guest 9201. Guest RAM 12 GB (raised from 2 GB) — confirmed before deploying heavier apps.
Per-app outcome
| App | Wired? | Shape | Self-signed handling | From | Env consumed on redeploy | Live send-test |
|---|---|---|---|---|---|---|
| gitea 1.23.4 | ✅ | STARTTLS (smtp+starttls) |
FORCE_TRUST_SERVER_CERT=true |
single GITEA__mailer__FROM |
Yes (env→app.ini every boot) | ✅ arrived — password-reset email, From gitea@felhom.eu |
| rallly 3.11.2 | ✅ | STARTTLS (SMTP_SECURE=false) |
SMTP_REJECT_UNAUTHORIZED=false |
single NOREPLY_EMAIL |
Yes (read at send time) | ✅ arrived — registration code, From rallly@felhom.eu |
| cal.com v4.8.7 | ❌ finding | — | none (hard-coded rejectUnauthorized:true) |
single EMAIL_FROM |
n/a | not wired |
| nextcloud 31.0.14 | ❌ finding | — | none + split From | MAIL_FROM_ADDRESS+MAIL_DOMAIN |
n/a | not wired |
| immich v2.5.5 | ❌ finding | — | ignoreCert (config-file/UI only) |
single (config field) | n/a | not wired |
Findings detail: felhom.eu/documentation/audits/FINDING-app-email-rollout-2026-06-29.md.
gitea 1.23.4 — WIRED, send confirmed
- Mapping (
smtp_mapping):host_var=GITEA__mailer__SMTP_ADDR,port_var=GITEA__mailer__SMTP_PORT,security_var=GITEA__mailer__PROTOCOL(smtp+starttls),from_var=GITEA__mailer__FROM,from_local=gitea,extra={GITEA__mailer__ENABLED:true, GITEA__mailer__FORCE_TRUST_SERVER_CERT:true}. - Verified live: toggle ON → controller injected all six
GITEA__mailer__*env vars (confirmed in the running container) → gitea wrote the correct[mailer]section in app.ini (env→ini every boot). Completed the install (admin = the test inbox) and triggered a password-reset (forgot-password): the shim loggedrelayed (from="gitea@felhom.eu" rcpts=1 bytes=2195 hub=200), the hub logged/api/v1/mail: relayed. Gonet/smtpSTARTTLS to the self-signed shim, trusting it via FORCE_TRUST_SERVER_CERT — works.
rallly 3.11.2 — WIRED, send confirmed (+ 2 pre-existing template bugs fixed)
- Mapping:
host_var=SMTP_HOST,port_var=SMTP_PORT,security_var=SMTP_SECURE(false),from_var=NOREPLY_EMAIL,from_name_var=NOREPLY_EMAIL_NAME,from_local=rallly,extra={SMTP_REJECT_UNAUTHORIZED:false}. - Verified live: toggle ON → injected env confirmed (
SMTP_HOST=felhom-controller,NOREPLY_EMAIL=rallly@felhom.eu,SMTP_SECURE=false,SMTP_REJECT_UNAUTHORIZED=false). Drove the real registration flow in the browser → rallly's Nodemailer sent the verification code: shim loggedrelayed (from="rallly@felhom.eu" rcpts=1 bytes=12755 hub=200), hub logged/api/v1/mail: relayed. Nodemailer opportunistic STARTTLS to the self-signed shim, accepted via reject-unauthorized=false — works. - Side-fixes (pre-existing, undeployable template):
- Image pin
lukevella/rallly:3.12.1does not exist (3.x stops at 3.11.x) → corrected to 3.11.2. - Healthcheck used
wget, which the rallly image does not ship (exit 127) → container marked unhealthy → Traefik refused to publish a route (the "URL nem elérhető" warning). Replaced with a Node http check. After the fix rallly is healthy and Traefik routes it. (Diagnosis credit: the operator spotted the unhealthy↔no-route correlation.) - rallly requires
SUPPORT_EMAIL+ a validNOREPLY_EMAILat boot (refuses to start otherwise) — added valid defaults so it boots with app-email off; the relay overridesNOREPLY_EMAILwhen on.
- Image pin
immich investigation (Task §6)
immich v2.5.5 has no SMTP environment variables. Email is system config (admin UI → DB) or a JSON file via
IMMICH_CONFIG_FILE (which supports ignoreCert:true for the self-signed shim). It does not fit the
env-injection smtp_mapping model. Left unwired — needs a future config-file-injection mechanism or manual
admin-UI setup. See the finding doc.
Observations / mechanism gaps (for follow-on)
- Gap 1 — self-signed STARTTLS vs opportunistic-upgrade clients with no skip-verify (cal.com; nextcloud's TLS half). The shim advertises STARTTLS on :2525; Nodemailer/Symfony clients auto-upgrade and validate the cert. Apps without a skip flag fail. Fix: a plaintext-only listener that does NOT advertise STARTTLS (controller change) — the highest-coverage unblock.
- Gap 2 — split From (nextcloud
MAIL_FROM_ADDRESS+MAIL_DOMAIN): the singlefrom_varcan't express it. Fix: add an optionalfrom_domain_var. - Gap 3 — no-SMTP-env apps (immich): needs config-file injection (a separate, larger capability).
- An app that needs SMTP only at first-install was NOT found among these — gitea and nextcloud both re-apply every boot; rallly reads at send time.
- Remaining email-capable apps still unwired (future passes): ghost, outline, grafana, docmost, bookstack, n8n, paperless-ngx, … — each needs the same per-app determination, and the opportunistic-STARTTLS ones need Gap-1's plaintext listener first.
No secrets in any committed file; the Resend key stays hub-side (out-of-band).