0e20eb19c1
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>
80 lines
1.9 KiB
Go
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
|
|
}
|
|
}
|