package pipes import ( "fmt" "net" gomail "gopkg.in/mail.v2" "nukumizu-backend/config" "nukumizu-backend/internal/controller" "nukumizu-backend/internal/netproxy" "nukumizu-backend/internal/node" "nukumizu-backend/internal/template" "nukumizu-backend/postLog" ) // EmailController handles email notifications via SMTP. // // cfg is written once, by the constructor, and never again: a controller that // needs different settings is replaced wholesale by the manager rather than // reconfigured in place, so the send methods can read it without locking. type EmailController struct { cfg config.EmailConfig } // NewEmailController creates a new Email controller. func NewEmailController(cfg config.EmailConfig) *EmailController { applyEmailProxy(cfg.NetworkUseProxy) return &EmailController{cfg: cfg} } // applyEmailProxy routes SMTP through the HTTP CONNECT proxy, or restores a // direct dial. gomail exposes the dial path as the package-level // NetDialTimeout, whose own default is net.DialTimeout, so turning the proxy // off has to put that back rather than leave the hook in place. There is a // single global email channel, so setting a package-level hook here is // unambiguous. func applyEmailProxy(useProxy bool) { if useProxy { gomail.NetDialTimeout = netproxy.DialWithTimeout(true) return } gomail.NetDialTimeout = net.DialTimeout } // Name returns the controller name. func (e *EmailController) Name() string { return "email" } // Start initializes the Email controller. func (e *EmailController) Start() error { if !e.cfg.Enabled { postLog.Info("Email controller is disabled") return nil } postLog.Info("Email controller started") return nil } // Stop shuts down the Email controller. func (e *EmailController) Stop() { postLog.Info("Email controller stopped") } // IsEnabled returns whether the controller is enabled. func (e *EmailController) IsEnabled() bool { return e.cfg.Enabled } // IsMarkdown returns whether the channel renders Markdown, per its markdown // setting in config.json. func (e *EmailController) IsMarkdown() bool { return e.cfg.Markdown } // SendStatusChange sends a status change notification via Email. func (e *EmailController) SendStatusChange(change node.StatusChange) error { if !e.cfg.Enabled { return nil } if len(e.cfg.To) == 0 { postLog.Debug("Email controller has no recipients configured") return nil } cfg := config.Current() params := template.BuildParamsFromStatusChange(change) body := template.Render(cfg.ControllerMessage.ServerStatusChanged, params, e.cfg.Markdown) subject := fmt.Sprintf("Server Status Change: %s - %s", change.Name, change.Event) return e.sendEmail(subject, body) } // SendServerList sends the server list via Email. func (e *EmailController) SendServerList(onlineServers, offlineServers string) error { if !e.cfg.Enabled || len(e.cfg.To) == 0 { return nil } cfg := config.Current() params := template.BuildParamsFromServerList() body := template.Render(cfg.ControllerMessage.ServerList, params, e.cfg.Markdown) return e.sendEmail("Server List", body) } // SendExecuteResult sends a command execution result via Email. func (e *EmailController) SendExecuteResult(serverName, serverUUID, command, result string) error { if !e.cfg.Enabled || len(e.cfg.To) == 0 { return nil } cfg := config.Current() params := template.BuildParamsFromExecResult(serverName, serverUUID, command, result) body := template.Render(cfg.ControllerMessage.ServerExecuteResult, params, e.cfg.Markdown) subject := fmt.Sprintf("Command Result: %s on %s", command, serverName) return e.sendEmail(subject, body) } // SendAlert sends an alert submitted through the incoming webhook API to the // configured recipients. func (e *EmailController) SendAlert(alert controller.Alert) error { if !e.cfg.Enabled { return nil } if len(e.cfg.To) == 0 { postLog.Debug("Email controller has no recipients configured") return nil } return e.sendEmail(alert.Subject, alert.Render(e.cfg.Markdown)) } func (e *EmailController) sendEmail(subject, body string) error { m := gomail.NewMessage() m.SetHeader("From", e.cfg.From) m.SetHeader("To", e.cfg.To...) m.SetHeader("Subject", subject) m.SetBody("text/plain", body) d := gomail.NewDialer(e.cfg.SMTPHost, e.cfg.SMTPPort, e.cfg.Username, e.cfg.Password) if err := d.DialAndSend(m); err != nil { postLog.Warning("Failed to send email: " + err.Error()) return err } postLog.Debug("Email sent successfully to " + fmt.Sprintf("%v", e.cfg.To)) return nil }