v0.89.0: app-email plaintext-only listener (:2526) + split-From mapping

Gap 1: third shim listener :2526, plaintext, does NOT advertise STARTTLS (TLSConfig
nil) — for opportunistic-STARTTLS clients with no cert-skip (cal.com, nextcloud).
Gap 2: SMTPMapping tls_mode (picks port 2525/2526/2465) + from_domain_var (split
local-part + domain for nextcloud's MAIL_FROM_ADDRESS/MAIL_DOMAIN). Default keeps
existing apps on 2525. Hub untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 13:13:40 +02:00
parent 6692a2f631
commit a405505e81
8 changed files with 252 additions and 47 deletions
+17 -7
View File
@@ -11,11 +11,11 @@ import (
// Metadata holds app information parsed from .felhom.yml.
type Metadata struct {
DisplayName string `yaml:"display_name" json:"display_name"`
Description string `yaml:"description" json:"description"`
Category string `yaml:"category" json:"category"`
Subdomain string `yaml:"subdomain" json:"subdomain"`
Slug string `yaml:"slug" json:"slug"`
DisplayName string `yaml:"display_name" json:"display_name"`
Description string `yaml:"description" json:"description"`
Category string `yaml:"category" json:"category"`
Subdomain string `yaml:"subdomain" json:"subdomain"`
Slug string `yaml:"slug" json:"slug"`
// OpenPath is appended to the app's public URL for the "Megnyitás" (open) link, for apps whose UI
// isn't at "/" (e.g. Gokapi → "/admin"). Empty = bare root. Must start with "/".
OpenPath string `yaml:"open_path,omitempty" json:"open_path,omitempty"`
@@ -50,6 +50,16 @@ type SMTPMapping struct {
FromNameVar string `yaml:"from_name_var" json:"from_name_var"` // env key for the From display name (optional)
FromLocal string `yaml:"from_local" json:"from_local"` // From local-part (defaults to the app slug)
Extra map[string]string `yaml:"extra" json:"extra"` // fixed extra env (accept-invalid-cert flags, etc.)
// TLSMode selects which shim listener the app is pointed at (which fixes the port):
// "" / "starttls" → :2525 (plaintext + STARTTLS advertised) — the default; existing apps unchanged.
// "plaintext" → :2526 (plaintext, STARTTLS NOT advertised) — for clients that opportunistically
// upgrade to STARTTLS and can't skip cert verification (cal.com, nextcloud).
// "implicit-tls" → :2465 (whole connection TLS).
TLSMode string `yaml:"tls_mode" json:"tls_mode"`
// FromDomainVar: for apps that SPLIT the From into local-part + domain env vars (nextcloud:
// MAIL_FROM_ADDRESS + MAIL_DOMAIN). When set, the controller injects FromVar=<local> and
// FromDomainVar=<allowlisted-domain> separately instead of FromVar=<local>@<domain>.
FromDomainVar string `yaml:"from_domain_var" json:"from_domain_var"`
}
// HasSMTPMapping reports whether this app declares a usable email mapping.
@@ -61,8 +71,8 @@ func (m *Metadata) HasSMTPMapping() bool {
// credential from a file inside the running container. The file path is catalog-defined (trusted).
// Verification stays catalog-driven so any future self-seeding app can reuse the mechanism.
type InitialCredentials struct {
File string `yaml:"file" json:"file"` // path INSIDE the container
Format string `yaml:"format" json:"format"` // "json" | "regex" | "plain"
File string `yaml:"file" json:"file"` // path INSIDE the container
Format string `yaml:"format" json:"format"` // "json" | "regex" | "plain"
// Container overrides which container to read from; empty → the stack's main container.
Container string `yaml:"container,omitempty" json:"container,omitempty"`
// json format: which keys hold the username/password (password_key required; username optional).