feat(PROJ-28): SMTP-Out Relay — DB-Konfiguration + Admin-Tab

- smtpoutconfig.Store: AES-256-GCM verschlüsseltes Passwort in DB (id=1 Singleton)
- Mailer: Reload() für Runtime-Konfigurationswechsel (sync.RWMutex)
- API: GET/PUT/DELETE /api/admin/smtp-out + POST /api/admin/smtp-out/test
- Admin-Tab: Host, Port, User, Passwort, TLS-Switch, From, Test-Button, Status-Badge
- Startup: Lädt DB-Konfiguration und aktiviert Mailer ohne Restart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-31 22:36:57 +02:00
parent 7371a73b3e
commit c1a9004720
7 changed files with 698 additions and 8 deletions
+23
View File
@@ -31,6 +31,7 @@ import (
ldapcfg "github.com/archivmail/internal/ldapconfig"
"github.com/archivmail/internal/mailer"
pop3store "github.com/archivmail/internal/pop3"
"github.com/archivmail/internal/smtpoutconfig"
"github.com/archivmail/internal/smtpd"
"github.com/archivmail/internal/storage"
tenantstore "github.com/archivmail/internal/tenantstore"
@@ -185,6 +186,28 @@ func main() {
// PROJ-28: Self-Service Onboarding — mailer + token store + FQDN
mlr := mailer.New(cfg.SMTPOut)
// SMTP-Out config store — load from DB, overrides config.yml if present
smtpOutSt, err := smtpoutconfig.New(cfg.Database.DSN(), cfg.API.Secret)
if err != nil {
logger.Error("smtp-out config store init failed", "err", err)
os.Exit(1)
}
defer smtpOutSt.Close()
srv.SetSMTPOutStore(smtpOutSt)
// Override config.yml settings with DB config if available
if dbCfg, err := smtpOutSt.GetWithPassword(context.Background()); err == nil && dbCfg != nil && dbCfg.Enabled {
mlr.Reload(config.SMTPOutConfig{
Host: dbCfg.Host,
Port: dbCfg.Port,
User: dbCfg.User,
Password: dbCfg.Password,
TLS: dbCfg.TLS,
From: dbCfg.From,
})
logger.Info("smtp_out: loaded from database")
}
srv.SetMailer(mlr)
srv.SetFQDN(cfg.Server.FQDN)
if cfg.Server.FQDN == "" {