diff --git a/Dockerfile b/Dockerfile index fbb1909..e17b652 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,6 +47,7 @@ ENV TZ=UTC ARG WORKDIR="/config" ARG BACKUPDIR="/backup" ARG BACKUP_TMP_DIR="/tmp/backup" +ARG TEMPLATES_DIR="/config/templates" ARG appVersion="v1.2.12" ENV VERSION=${appVersion} LABEL author="Jonas Kaninda" @@ -55,6 +56,7 @@ LABEL version=${appVersion} RUN apk --update add --no-cache mysql-client mariadb-connector-c tzdata RUN mkdir $WORKDIR RUN mkdir $BACKUPDIR +RUN mkdir $TEMPLATES_DIR RUN mkdir -p $BACKUP_TMP_DIR RUN chmod 777 $WORKDIR RUN chmod 777 $BACKUPDIR @@ -62,6 +64,7 @@ RUN chmod 777 $BACKUP_TMP_DIR RUN chmod 777 $WORKDIR COPY --from=build /app/mysql-bkup /usr/local/bin/mysql-bkup +COPY ./templates/* $TEMPLATES_DIR/ RUN chmod +x /usr/local/bin/mysql-bkup RUN ln -s /usr/local/bin/mysql-bkup /usr/local/bin/bkup diff --git a/docs/how-tos/receive-notification.md b/docs/how-tos/receive-notification.md new file mode 100644 index 0000000..3babdc6 --- /dev/null +++ b/docs/how-tos/receive-notification.md @@ -0,0 +1,126 @@ +--- +title: Receive notifications +layout: default +parent: How Tos +nav_order: 12 +--- +Send Email or Telegram notifications on success or failed backup. + +### Email +To send out email notifications on failed backup runs, provide SMTP credentials, a sender and a recipient: + +```yaml +services: + mysql-bkup: + image: jkaninda/mysql-bkup + container_name: mysql-bkup + command: backup + volumes: + - ./backup:/backup + environment: + - DB_PORT=3306 + - DB_HOST=mysql + - DB_NAME=database + - DB_USERNAME=username + - DB_PASSWORD=password + - MAIL_HOST= + - MAIL_PORT=587 + - MAIL_USERNAME= + - MAIL_PASSWORD=! + - MAIL_FROM=sender@example.com + - MAIL_TO=me@example.com,team@example.com,manager@example.com + - MAIL_SKIP_TLS=false + networks: + - web +networks: + web: +``` + +### Telegram + +```yaml +services: + mysql-bkup: + image: jkaninda/mysql-bkup + container_name: mysql-bkup + command: backup + volumes: + - ./backup:/backup + environment: + - DB_PORT=3306 + - DB_HOST=mysql + - DB_NAME=database + - DB_USERNAME=username + - DB_PASSWORD=password + - TG_TOKEN=[BOT ID]:[BOT TOKEN] + - TG_CHAT_ID= + networks: + - web +networks: + web: +``` + +### Customize notifications + +The body of the notifications can be tailored to your needs using Go templates. +Template sources must be mounted inside the container in /config/templates: + +- email.template: Email notification template +- telegram.template: Telegram notification template +- error.template: Error notification template + +> email.template: + + +```html + + +
+ +Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
+Best regards,
+ + +``` + +> telegram.template + +```html +[✅ Database Backup Notification – {{.Database}} +Hi, +Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}. + +Backup Details: +- Database Name: {{.Database}} +- Backup Start Time: {{.StartTime}} +- Backup EndTime: {{.EndTime}} +- Backup Storage: {{.Storage}} +- Backup Location: {{.BackupLocation}} +- Backup Size: {{.BackupSize}} bytes +``` + +> error.template + + +```html +🔴 Urgent: Database Backup Failure Notification + +An error occurred during database backup. +Failure Details: + +Error Message: {{.Error}} +Date: {{.EndTime}} +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 1a57d1d..c32879b 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect github.com/cloudflare/circl v1.3.3 // indirect + github.com/go-mail/mail v2.3.1+incompatible // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -27,6 +28,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.14.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect ) diff --git a/go.sum b/go.sum index 6b448f5..1a9a97e 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM= +github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -94,6 +96,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/pkg/backup.go b/pkg/backup.go index fc975a4..d9f513a 100644 --- a/pkg/backup.go +++ b/pkg/backup.go @@ -218,17 +218,31 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool } func localBackup(db *dbConfig, config *BackupConfig) { utils.Info("Backup database to local storage") + startTime = time.Now().Format("2006-01-02 15:04:05") BackupDatabase(db, config.backupFileName, disableCompression) finalFileName := config.backupFileName if config.encryption { encryptBackup(config) finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, gpgExtension) } - + fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName)) + if err != nil { + utils.Error("Error:", err) + } + //Get backup info + backupSize = fileInfo.Size() utils.Info("Backup name is %s", finalFileName) moveToBackup(finalFileName, storagePath) //Send notification - utils.NotifySuccess(finalFileName) + utils.NotifySuccess(&utils.NotificationData{ + File: finalFileName, + BackupSize: backupSize, + Database: db.dbName, + Storage: config.storage, + BackupLocation: filepath.Join(config.remotePath, finalFileName), + StartTime: startTime, + EndTime: time.Now().Format("2006-01-02 15:04:05"), + }) //Delete old backup if config.prune { deleteOldBackup(config.backupRetention) @@ -242,6 +256,8 @@ func s3Backup(db *dbConfig, config *BackupConfig) { bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME") s3Path := utils.GetEnvVariable("AWS_S3_PATH", "S3_PATH") utils.Info("Backup database to s3 storage") + startTime = time.Now().Format("2006-01-02 15:04:05") + //Backup database BackupDatabase(db, config.backupFileName, disableCompression) finalFileName := config.backupFileName @@ -257,7 +273,12 @@ func s3Backup(db *dbConfig, config *BackupConfig) { utils.Fatal("Error uploading backup archive to S3: %s ", err) } - + //Get backup info + fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName)) + if err != nil { + utils.Error("Error:", err) + } + backupSize = fileInfo.Size() //Delete backup file from tmp folder err = utils.DeleteFile(filepath.Join(tmpPath, config.backupFileName)) if err != nil { @@ -273,7 +294,15 @@ func s3Backup(db *dbConfig, config *BackupConfig) { } utils.Done("Uploading backup archive to remote storage S3 ... done ") //Send notification - utils.NotifySuccess(finalFileName) + utils.NotifySuccess(&utils.NotificationData{ + File: finalFileName, + BackupSize: backupSize, + Database: db.dbName, + Storage: config.storage, + BackupLocation: filepath.Join(config.remotePath, finalFileName), + StartTime: startTime, + EndTime: time.Now().Format("2006-01-02 15:04:05"), + }) //Delete temp deleteTemp() utils.Info("Backup completed successfully") @@ -281,6 +310,8 @@ func s3Backup(db *dbConfig, config *BackupConfig) { } func sshBackup(db *dbConfig, config *BackupConfig) { utils.Info("Backup database to Remote server") + startTime = time.Now().Format("2006-01-02 15:04:05") + //Backup database BackupDatabase(db, config.backupFileName, disableCompression) finalFileName := config.backupFileName @@ -295,7 +326,12 @@ func sshBackup(db *dbConfig, config *BackupConfig) { utils.Fatal("Error uploading file to the remote server: %s ", err) } - + //Get backup info + fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName)) + if err != nil { + utils.Error("Error:", err) + } + backupSize = fileInfo.Size() //Delete backup file from tmp folder err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName)) if err != nil { @@ -310,7 +346,15 @@ func sshBackup(db *dbConfig, config *BackupConfig) { utils.Done("Uploading backup archive to remote storage ... done ") //Send notification - utils.NotifySuccess(finalFileName) + utils.NotifySuccess(&utils.NotificationData{ + File: finalFileName, + BackupSize: backupSize, + Database: db.dbName, + Storage: config.storage, + BackupLocation: filepath.Join(config.remotePath, finalFileName), + StartTime: startTime, + EndTime: time.Now().Format("2006-01-02 15:04:05"), + }) //Delete temp deleteTemp() utils.Info("Backup completed successfully") @@ -318,6 +362,8 @@ func sshBackup(db *dbConfig, config *BackupConfig) { } func ftpBackup(db *dbConfig, config *BackupConfig) { utils.Info("Backup database to the remote FTP server") + startTime = time.Now().Format("2006-01-02 15:04:05") + //Backup database BackupDatabase(db, config.backupFileName, disableCompression) finalFileName := config.backupFileName @@ -332,7 +378,12 @@ func ftpBackup(db *dbConfig, config *BackupConfig) { utils.Fatal("Error uploading file to the remote FTP server: %s ", err) } - + //Get backup info + fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName)) + if err != nil { + utils.Error("Error:", err) + } + backupSize = fileInfo.Size() //Delete backup file from tmp folder err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName)) if err != nil { @@ -347,7 +398,15 @@ func ftpBackup(db *dbConfig, config *BackupConfig) { utils.Done("Uploading backup archive to the remote FTP server ... done ") //Send notification - utils.NotifySuccess(finalFileName) + utils.NotifySuccess(&utils.NotificationData{ + File: finalFileName, + BackupSize: backupSize, + Database: db.dbName, + Storage: config.storage, + BackupLocation: filepath.Join(config.remotePath, finalFileName), + StartTime: startTime, + EndTime: time.Now().Format("2006-01-02 15:04:05"), + }) //Delete temp deleteTemp() utils.Info("Backup completed successfully") diff --git a/pkg/var.go b/pkg/var.go index 74aa0ff..8536d23 100644 --- a/pkg/var.go +++ b/pkg/var.go @@ -14,12 +14,14 @@ const gpgExtension = "gpg" const workingDir = "/config" var ( - storage = "local" - file = "" - storagePath = "/backup" - disableCompression = false - encryption = false - usingKey = false + storage = "local" + file = "" + storagePath = "/backup" + disableCompression = false + encryption = false + usingKey = false + backupSize int64 = 0 + startTime string ) // dbHVars Required environment variables for database diff --git a/templates/email-error.template b/templates/email-error.template new file mode 100644 index 0000000..277d890 --- /dev/null +++ b/templates/email-error.template @@ -0,0 +1,17 @@ + + + + +An error occurred during database backup.
+©2024 mysql-bkup
+ + \ No newline at end of file diff --git a/templates/email.template b/templates/email.template new file mode 100644 index 0000000..99f4d5c --- /dev/null +++ b/templates/email.template @@ -0,0 +1,23 @@ + + + + +Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
+Best regards,
+©2024 mysql-bkup
+