Skip to content

Commit

Permalink
feat: add the capability to send mail notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
giuseongit committed Jul 25, 2024
1 parent ace6224 commit df85055
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 33 deletions.
125 changes: 94 additions & 31 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,51 @@ import (
"os"
)

type MailSendConfig struct {
From string `json:"from"`
To string `json:"to"`
}

type SMTPConfig struct {
Host string `json:"host"`
Port string `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
Insecure bool `json:"insecure"`
Mails []MailSendConfig `json:"mails"`
}

type Config struct {
BaseURL string `json:"baseurl"`
CertFingerprint string `json:"certfingerprint"`
AuthID string `json:"authid"`
Secret string `json:"secret"`
Datastore string `json:"datastore"`
Namespace string `json:"namespace"`
BackupID string `json:"backup-id"`
BackupSourceDir string `json:"backupdir"`
PxarOut string `json:"pxarout"`
BaseURL string `json:"baseurl"`
CertFingerprint string `json:"certfingerprint"`
AuthID string `json:"authid"`
Secret string `json:"secret"`
Datastore string `json:"datastore"`
Namespace string `json:"namespace"`
BackupID string `json:"backup-id"`
BackupSourceDir string `json:"backupdir"`
PxarOut string `json:"pxarout"`
SMTP *SMTPConfig `json:"smtp"`
}

func (c *Config) valid() bool {
return c.BaseURL != "" && c.CertFingerprint != "" && c.AuthID != "" && c.Secret != "" && c.Datastore != "" && c.BackupSourceDir != ""
baseValid := c.BaseURL != "" && c.CertFingerprint != "" && c.AuthID != "" && c.Secret != "" && c.Datastore != "" && c.BackupSourceDir != ""
if !baseValid {
return baseValid
}

if c.SMTP != nil {
mailCfgValid := c.SMTP.Host != "" && c.SMTP.Port != "" && c.SMTP.Username != "" && c.SMTP.Password != ""
if len(c.SMTP.Mails) == 0 {
return false
}
for i := range c.SMTP.Mails {
mailCfgValid = mailCfgValid && (c.SMTP.Mails[i].From != "" && c.SMTP.Mails[i].To != "")
}
return mailCfgValid
}

return true
}

func loadConfig() *Config {
Expand All @@ -34,12 +65,20 @@ func loadConfig() *Config {
backupIDFlag := flag.String("backup-id", "", "Backup ID (optional - if not specified, the hostname is used as the default)")
backupSourceDirFlag := flag.String("backupdir", "", "Backup source directory, must not be symlink")
pxarOutFlag := flag.String("pxarout", "", "Output PXAR archive for debug purposes (optional)")
configFile := flag.String("config", "", "Path to JSON config file")

mailHostFlag := flag.String("mail-host", "", "mail notification system: mail server host(optional)")
mailPortFlag := flag.String("mail-port", "", "mail notification system: mail server port(optional)")
mailUsernameFlag := flag.String("mail-username", "", "mail notification system: mail server username(optional)")
mailPasswordFlag := flag.String("mail-password", "", "mail notification system: mail server password(optional)")
mailInsecureFlag := flag.Bool("mail-insecure", false, "mail notification system: allow insecure communications(optional)")
mailFromFlag := flag.String("mail-from", "", "mail notification system: sender mail(optional)")
mailToFlag := flag.String("mail-to", "", "mail notification system: receiver mail(optional)")

configFile := flag.String("config", "", "Path to JSON config file. If this flag is provided all the others are ignored")

// Parse command line flags
flag.Parse()

// Create a config struct and try to load values from the JSON file if specified
config := &Config{}
if *configFile != "" {
file, err := os.ReadFile(*configFile)
Expand All @@ -52,35 +91,59 @@ func loadConfig() *Config {
fmt.Printf("Error parsing config file: %v\n", err)
os.Exit(1)
}

return config
}

// Override JSON config with command line flags if provided
if *baseURLFlag != "" {
config.BaseURL = *baseURLFlag
config.BaseURL = *baseURLFlag
config.CertFingerprint = *certFingerprintFlag
config.AuthID = *authIDFlag
config.Secret = *secretFlag
config.Datastore = *datastoreFlag
config.Namespace = *namespaceFlag
config.BackupID = *backupIDFlag
config.BackupSourceDir = *backupSourceDirFlag
config.PxarOut = *pxarOutFlag

initSmtpConfigIfNeeded := func() {
if config.SMTP == nil {
config.SMTP = &SMTPConfig{}
}
}
if *certFingerprintFlag != "" {
config.CertFingerprint = *certFingerprintFlag
initMailConfsIfNeeded := func() {
initSmtpConfigIfNeeded()
if len(config.SMTP.Mails) == 0 {
config.SMTP.Mails = append(config.SMTP.Mails, MailSendConfig{})
}
}
if *authIDFlag != "" {
config.AuthID = *authIDFlag

if *mailHostFlag != "" {
initSmtpConfigIfNeeded()
config.SMTP.Host = *mailHostFlag
}
if *secretFlag != "" {
config.Secret = *secretFlag
if *mailPortFlag != "" {
initSmtpConfigIfNeeded()
config.SMTP.Port = *mailPortFlag
}
if *datastoreFlag != "" {
config.Datastore = *datastoreFlag
if *mailUsernameFlag != "" {
initSmtpConfigIfNeeded()
config.SMTP.Username = *mailUsernameFlag
}
if *namespaceFlag != "" {
config.Namespace = *namespaceFlag
if *mailPasswordFlag != "" {
initSmtpConfigIfNeeded()
config.SMTP.Password = *mailPasswordFlag
}
if *backupIDFlag != "" {
config.BackupID = *backupIDFlag
if *mailInsecureFlag {
initSmtpConfigIfNeeded()
config.SMTP.Insecure = *mailInsecureFlag
}
if *backupSourceDirFlag != "" {
config.BackupSourceDir = *backupSourceDirFlag
if *mailFromFlag != "" {
initMailConfsIfNeeded()
config.SMTP.Mails[0].From = *mailFromFlag
}
if *pxarOutFlag != "" {
config.PxarOut = *pxarOutFlag
if *mailToFlag != "" {
initMailConfsIfNeeded()
config.SMTP.Mails[0].To = *mailToFlag
}

return config
Expand Down
16 changes: 15 additions & 1 deletion config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,19 @@
"backupdir": "C:",
"namespace": "",
"backup-id": "",
"pxarout": ""
"pxarout": "",
"smtp": {
"host": "smtp.example.com",
"port": "465",
"username": "[email protected]",
"password": "my-password",
"mails": [{
"from": "[email protected]",
"to": "[email protected]
}, {
"from": "[email protected]",
"to": "[email protected]
}]

}
}
115 changes: 115 additions & 0 deletions mail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package main

import (
"crypto/tls"
"errors"
"fmt"
"net/smtp"
)

type unencryptedAuth struct {
smtp.Auth
}

func (a unencryptedAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
s := *server
s.TLS = true
return a.Auth.Start(&s)
}

func setupClient(host, port, username, password string, allowInsecure bool) (*smtp.Client, error) {
var auth smtp.Auth
auth = smtp.PlainAuth("", username, password, host)
if port == "25" {
if !allowInsecure {
return nil, errors.New("sending plain password over unencrypted connection")
}
auth = unencryptedAuth{auth}
}

var tlsconfig *tls.Config
if port != "25" {
// TLS config
tlsconfig = &tls.Config{
InsecureSkipVerify: allowInsecure,
ServerName: host,
}
}

servername := host + ":" + port

var c *smtp.Client
var err error
if port == "465" {
// Here is the key, you need to call tls.Dial instead of smtp.Dial
// for smtp servers running on 465 that require an ssl connection
// from the very beginning (no starttls)
conn, err := tls.Dial("tcp", servername, tlsconfig)
if err != nil {
return nil, err
}

c, err = smtp.NewClient(conn, host)
if err != nil {
return nil, err
}
} else {
c, err = smtp.Dial(servername)
if err != nil {
return nil, err
}
if port == "587" {
c.StartTLS(tlsconfig)
}
}

// Auth
if err = c.Auth(auth); err != nil {
fmt.Println("here", err)
return nil, err
}

return c, nil
}

func sendMail(from, to, subject, body string, c *smtp.Client) error {
// Setup headers
headers := make(map[string]string)
headers["From"] = from
headers["To"] = to
headers["Subject"] = subject

// Setup message
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body

// To && From
if err := c.Mail(from); err != nil {
return err
}

if err := c.Rcpt(to); err != nil {
return err
}

// Data
w, err := c.Data()
if err != nil {
return err
}

_, err = w.Write([]byte(message))
if err != nil {
return err
}

err = w.Close()
if err != nil {
return err
}

return nil
}
23 changes: 22 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func main() {
go systray.Run(func() {
systray.SetIcon(ICON)
systray.SetTooltip("PBSGO Backup running")
beeep.Notify("Proxmox Backup Go", fmt.Sprintf("Backup started"), "")
beeep.Notify("Proxmox Backup Go", "Backup started", "")
},
func() {

Expand Down Expand Up @@ -105,6 +105,27 @@ func main() {
systray.Quit()
beeep.Notify("Proxmox Backup Go", msg, "")
}
if cfg.SMTP != nil {
var subject string
if err == nil {
subject = "Backup complete"
} else {
subject = "Backup error"
}
client, err := setupClient(cfg.SMTP.Host, cfg.SMTP.Port, cfg.SMTP.Username, cfg.SMTP.Password, cfg.SMTP.Insecure)
if err != nil {
fmt.Println("Cannot connect to mail server: " + err.Error())
os.Exit(1)
}
defer client.Quit()
for _, ccc := range cfg.SMTP.Mails {
err = sendMail(ccc.From, ccc.To, subject, msg, client)
if err != nil {
fmt.Println("Cannot send email: " + err.Error())
os.Exit(1)
}
}
}

}

Expand Down

0 comments on commit df85055

Please sign in to comment.