Files
felhom-controller/controller/internal/mailrelay/lifecycle.go
T
admin 0e20eb19c1 v0.88.0: app-email SMTP relay (in-process shim + per-app injection)
In-process go-smtp shim (Shape 1): apps → shim → hub → Resend, Resend key stays
hub-side. From-header allowlist (reject 5xx pre-hub), single-shot raw-MIME forward,
status→SMTP mapping. Global + per-app toggles gate compose-time env injection from
.felhom.yml smtp_mapping. Hungarian UI on settings + app config pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 08:45:04 +02:00

80 lines
1.9 KiB
Go

package mailrelay
import (
"log"
"sync"
)
// Lifecycle owns the shim's start/stop so the global app-email toggle can take effect
// at runtime without a controller restart. The shim listens ONLY when app-email is
// globally ON (Part 1: "start listeners only when hub.enabled and global app-email ON").
// Apply is idempotent: enabling when already up (or disabling when already down) is a
// no-op.
type Lifecycle struct {
mu sync.Mutex
opts Options
srv *Server
logger *log.Logger
stopped bool // Close was called; further Apply(true) is refused (shutdown)
}
// NewLifecycle prepares (but does not start) a shim from the given options.
func NewLifecycle(opts Options) *Lifecycle {
lg := opts.Logger
if lg == nil {
lg = log.Default()
}
return &Lifecycle{opts: opts, logger: lg}
}
// Apply reconciles the shim's running state with the desired toggle value. It returns
// an error only if a requested START failed (a stop never errors meaningfully).
func (l *Lifecycle) Apply(enabled bool) error {
l.mu.Lock()
defer l.mu.Unlock()
if l.stopped {
return nil
}
if enabled {
if l.srv != nil {
return nil // already running
}
srv, err := New(l.opts)
if err != nil {
return err
}
if err := srv.Start(); err != nil {
return err
}
l.srv = srv
l.logger.Printf("[INFO] [mailrelay] app-email shim ON (host=%s plain=%s tls=%s)",
l.opts.ServiceName, srv.PlainAddr(), srv.TLSAddr())
return nil
}
// disabled → stop if running
if l.srv != nil {
_ = l.srv.Close()
l.srv = nil
l.logger.Printf("[INFO] [mailrelay] app-email shim OFF")
}
return nil
}
// Running reports whether the shim is currently listening.
func (l *Lifecycle) Running() bool {
l.mu.Lock()
defer l.mu.Unlock()
return l.srv != nil
}
// Close stops the shim permanently (shutdown). Idempotent.
func (l *Lifecycle) Close() {
l.mu.Lock()
defer l.mu.Unlock()
l.stopped = true
if l.srv != nil {
_ = l.srv.Close()
l.srv = nil
}
}