package stacks import ( "fmt" "path/filepath" ) // smtpEnv returns the managed app-email relay env vars (as "KEY=VALUE") to inject for a // stack, or nil when app-email does not apply. It is a pure function of settings + the // app's smtp_mapping + the per-app toggle; the values are NEVER persisted to app.yaml — // they are derived on every compose so a toggle change applies on the next redeploy. // // Gates (all must hold, else nil — §8 edge table): // - global app-email toggle ON (settings.AppEmailEnabled) // - per-app toggle ON (appEmailEnabled, from app.yaml) // - the app declares a usable smtp_mapping // // Injected: host = the in-controller shim (cfg.MailRelay.ShimHost), port = 2525 // (plaintext+STARTTLS listener), security = the app's STARTTLS term, From = // @, optional From display name, plus the mapping's fixed // Extra vars (accept-invalid-cert flags, etc.). func (m *Manager) smtpEnv(meta *Metadata, appEmailEnabled bool) []string { if !appEmailEnabled { return nil } if m.settings == nil || !m.settings.AppEmailEnabled() { return nil } if !meta.HasSMTPMapping() { return nil } sm := meta.SMTPMapping host := m.cfg.MailRelay.ShimHost if host == "" { host = "felhom-controller" } fromDomain := "felhom.eu" if len(m.cfg.MailRelay.FromDomains) > 0 && m.cfg.MailRelay.FromDomains[0] != "" { fromDomain = m.cfg.MailRelay.FromDomains[0] } local := sm.FromLocal if local == "" { local = meta.Slug } security := sm.SecurityValue if security == "" { security = "starttls" } out := []string{ fmt.Sprintf("%s=%s", sm.HostVar, host), fmt.Sprintf("%s=%s", sm.PortVar, "2525"), fmt.Sprintf("%s=%s@%s", sm.FromVar, local, fromDomain), } if sm.SecurityVar != "" { out = append(out, fmt.Sprintf("%s=%s", sm.SecurityVar, security)) } if sm.FromNameVar != "" { out = append(out, fmt.Sprintf("%s=%s", sm.FromNameVar, m.fromDisplayName(meta))) } for k, v := range sm.Extra { if k != "" { out = append(out, fmt.Sprintf("%s=%s", k, v)) } } return out } // fromDisplayName picks the From display name: the household-set name (settings) wins, // else the app's display name. func (m *Manager) fromDisplayName(meta *Metadata) string { if m.settings != nil { if ae := m.settings.GetAppEmail(); ae.FromName != "" { return ae.FromName } } if meta.DisplayName != "" { return meta.DisplayName } return meta.Slug } // SetAppEmailEnabled persists the per-app email toggle in app.yaml and, if the stack is // deployed, recreates it (docker compose up -d) so the SMTP env injection takes effect // (toggle ON) or is removed (toggle OFF). Errors if the stack is unknown or not deployed. func (m *Manager) SetAppEmailEnabled(name string, enabled bool) error { stack, ok := m.GetStack(name) if !ok { return fmt.Errorf("stack %q not found", name) } stackDir := filepath.Dir(stack.ComposePath) meta := LoadMetadata(stackDir) if !meta.HasSMTPMapping() { return fmt.Errorf("a(z) %q alkalmazás nem támogatja az email-küldést", name) } appCfg := LoadAppConfig(stackDir) if appCfg == nil || !appCfg.Deployed { return fmt.Errorf("a(z) %q alkalmazás nincs telepítve", name) } if appCfg.EmailEnabled == enabled { return nil // no change } appCfg.EmailEnabled = enabled if err := SaveAppConfig(stackDir, appCfg, m.encKey, SensitiveEnvVars(&meta)); err != nil { return fmt.Errorf("saving app config: %w", err) } m.mu.Lock() if s, ok := m.stacks[name]; ok { s.AppConfig = appCfg } m.mu.Unlock() m.logger.Printf("[INFO] [stacks] App-email for %s set to %v — recreating to apply", name, enabled) // Recreate so the (now present/absent) SMTP env is applied. stackEnv injects the relay env. env := m.stackEnv(stackDir) if _, err := m.composeExecCustomEnv(stackDir, env, "up", "-d"); err != nil { return fmt.Errorf("restarting to apply app-email change: %w", err) } m.logPostStartStatus(name, stackDir, env) return m.RefreshStatus() } // AppEmailStatus reports whether an app supports email and whether its per-app toggle is on. func (m *Manager) AppEmailStatus(name string) (supported, enabled bool) { stack, ok := m.GetStack(name) if !ok { return false, false } stackDir := filepath.Dir(stack.ComposePath) meta := LoadMetadata(stackDir) if !meta.HasSMTPMapping() { return false, false } if appCfg := LoadAppConfig(stackDir); appCfg != nil { return true, appCfg.EmailEnabled } return true, false }