Compare commits

...

10 Commits

Author SHA1 Message Date
140ed608ab Merge pull request #120 from jkaninda/fix-dockerfile
fix: Dockerfile backup, restore, and migrate scripts since the migration of base image from Ubuntu to alpine
2024-10-10 10:03:44 +02:00
Jonas Kaninda
98211a27b8 fix: Dockerfile backup, restore, and migrate scripts since the migration of base image from Ubuntu to alpine 2024-10-10 10:02:42 +02:00
4e4d45e555 Merge pull request #119 from jkaninda/fix-notification
fix: fix multi backup s3 path
2024-10-10 05:51:46 +02:00
Jonas Kaninda
01e41acb5c fix: fix multi backup s3 path 2024-10-10 05:51:18 +02:00
3dce2017f8 Merge pull request #118 from jkaninda/fix-notification
fix: fix multi backup s3 path
2024-10-10 05:32:08 +02:00
Jonas Kaninda
ed2f1b8d9c fix: fix multi backup s3 path 2024-10-10 05:31:18 +02:00
b64875df21 Merge pull request #117 from jkaninda/fix-notification
docs: correct grammar in  receive-notification.md
2024-10-10 04:28:52 +02:00
Jonas Kaninda
fc90507b3f docs: correct grammar in receive-notification.md 2024-10-10 04:28:02 +02:00
df0efd24d3 Merge pull request #116 from jkaninda/fix-notification
chore: fix infinity calling Fatal, add a backup reference
2024-10-10 04:15:12 +02:00
Jonas Kaninda
e5dd7e76ce chore: fix infinity calling Fatal, add a backup reference 2024-10-10 04:14:42 +02:00
10 changed files with 141 additions and 64 deletions

View File

@@ -53,7 +53,7 @@ ENV VERSION=${appVersion}
LABEL author="Jonas Kaninda" LABEL author="Jonas Kaninda"
LABEL version=${appVersion} LABEL version=${appVersion}
RUN apk --update add --no-cache mysql-client mariadb-connector-c tzdata RUN apk --update add --no-cache mysql-client mariadb-connector-c tzdata ca-certificates
RUN mkdir $WORKDIR RUN mkdir $WORKDIR
RUN mkdir $BACKUPDIR RUN mkdir $BACKUPDIR
RUN mkdir $TEMPLATES_DIR RUN mkdir $TEMPLATES_DIR
@@ -70,13 +70,13 @@ RUN chmod +x /usr/local/bin/mysql-bkup
RUN ln -s /usr/local/bin/mysql-bkup /usr/local/bin/bkup RUN ln -s /usr/local/bin/mysql-bkup /usr/local/bin/bkup
# Create backup script and make it executable # Create backup script and make it executable
RUN echo '#!/bin/sh\n/usr/local/bin/mysql-bkup backup "$@"' > /usr/local/bin/backup && \ RUN printf '#!/bin/sh\n/usr/local/bin/mysql-bkup backup "$@"' > /usr/local/bin/backup && \
chmod +x /usr/local/bin/backup chmod +x /usr/local/bin/backup
# Create restore script and make it executable # Create restore script and make it executable
RUN echo '#!/bin/sh\n/usr/local/bin/mysql-bkup restore "$@"' > /usr/local/bin/restore && \ RUN printf '#!/bin/sh\n/usr/local/bin/mysql-bkup restore "$@"' > /usr/local/bin/restore && \
chmod +x /usr/local/bin/restore chmod +x /usr/local/bin/restore
# Create migrate script and make it executable # Create migrate script and make it executable
RUN echo '#!/bin/sh\n/usr/local/bin/mysql-bkup migrate "$@"' > /usr/local/bin/migrate && \ RUN printf '#!/bin/sh\n/usr/local/bin/mysql-bkup migrate "$@"' > /usr/local/bin/migrate && \
chmod +x /usr/local/bin/migrate chmod +x /usr/local/bin/migrate
WORKDIR $WORKDIR WORKDIR $WORKDIR

View File

@@ -4,10 +4,10 @@ layout: default
parent: How Tos parent: How Tos
nav_order: 12 nav_order: 12
--- ---
Send Email or Telegram notifications on success or failed backup. Send Email or Telegram notifications on successfully or failed backup.
### Email ### Email
To send out email notifications on failed backup runs, provide SMTP credentials, a sender and a recipient: To send out email notifications on failed or successfully backup runs, provide SMTP credentials, a sender and a recipient:
```yaml ```yaml
services: services:
@@ -27,9 +27,13 @@ services:
- MAIL_PORT=587 - MAIL_PORT=587
- MAIL_USERNAME= - MAIL_USERNAME=
- MAIL_PASSWORD=! - MAIL_PASSWORD=!
- MAIL_FROM=sender@example.com - MAIL_FROM=
- MAIL_TO=me@example.com,team@example.com,manager@example.com - MAIL_TO=me@example.com,team@example.com,manager@example.com
- MAIL_SKIP_TLS=false - MAIL_SKIP_TLS=false
## Time format for notification
- TIME_FORMAT=2006-01-02 at 15:04:05
## Backup reference, in case you want to identify every backup instance
- BACKUP_REFERENCE=database/Paris cluster
networks: networks:
- web - web
networks: networks:
@@ -54,6 +58,10 @@ services:
- DB_PASSWORD=password - DB_PASSWORD=password
- TG_TOKEN=[BOT ID]:[BOT TOKEN] - TG_TOKEN=[BOT ID]:[BOT TOKEN]
- TG_CHAT_ID= - TG_CHAT_ID=
## Time format for notification
- TIME_FORMAT=2006-01-02 at 15:04:05
## Backup reference, in case you want to identify every backup instance
- BACKUP_REFERENCE=database/Paris cluster
networks: networks:
- web - web
networks: networks:
@@ -62,12 +70,13 @@ networks:
### Customize notifications ### Customize notifications
The body of the notifications can be tailored to your needs using Go templates. The title and body of the notifications can be tailored to your needs using Go templates.
Template sources must be mounted inside the container in /config/templates: Template sources must be mounted inside the container in /config/templates:
- email.template: Email notification template - email.template: Email notification template
- telegram.template: Telegram notification template - telegram.template: Telegram notification template
- error.template: Error notification template - email-error.template: Error notification template
- telegram-error.template: Error notification template
### Data ### Data
@@ -78,7 +87,7 @@ Here is a list of all data passed to the template:
- `Storage`: Backup storage - `Storage`: Backup storage
- `BackupLocation`: Backup location - `BackupLocation`: Backup location
- `BackupSize`: Backup size - `BackupSize`: Backup size
- `BackupReference`: Backup reference(eg: database/cluster name or server name)
> email.template: > email.template:
@@ -88,19 +97,20 @@ Here is a list of all data passed to the template:
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>[✅ Database Backup Notification {{.Database}}</title> <title>✅ Database Backup Notification {{.Database}}</title>
</head> </head>
<body> <body>
<h2>Hi,</h2> <h2>Hi,</h2>
<p>Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.</p> <p>Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.</p>
<h3>Backup Details:</h3> <h3>Backup Details:</h3>
<ul> <ul>
<li>Database Name: {{.Database}}</li> <li>Database Name: {{.Database}}</li>
<li>Backup Start Time: {{.StartTime}}</li> <li>Backup Start Time: {{.StartTime}}</li>
<li>Backup End Time: {{.EndTime}}</li> <li>Backup End Time: {{.EndTime}}</li>
<li>Backup Storage: {{.Storage}}</li> <li>Backup Storage: {{.Storage}}</li>
<li>Backup Location: {{.BackupLocation}}</li> <li>Backup Location: {{.BackupLocation}}</li>
<li>Backup Size: {{.BackupSize}} bytes</li> <li>Backup Size: {{.BackupSize}} bytes</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul> </ul>
<p>Best regards,</p> <p>Best regards,</p>
</body> </body>
@@ -110,7 +120,7 @@ Here is a list of all data passed to the template:
> telegram.template > telegram.template
```html ```html
[✅ Database Backup Notification {{.Database}} ✅ Database Backup Notification {{.Database}}
Hi, Hi,
Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}. Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
@@ -121,9 +131,32 @@ Backup Details:
- Backup Storage: {{.Storage}} - Backup Storage: {{.Storage}}
- Backup Location: {{.BackupLocation}} - Backup Location: {{.BackupLocation}}
- Backup Size: {{.BackupSize}} bytes - Backup Size: {{.BackupSize}} bytes
- Backup Reference: {{.BackupReference}}
``` ```
> error.template > email-error.template
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>🔴 Urgent: Database Backup Failure Notification</title>
</head>
<body>
<h2>Hi,</h2>
<p>An error occurred during database backup.</p>
<h3>Failure Details:</h3>
<ul>
<li>Error Message: {{.Error}}</li>
<li>Date: {{.EndTime}}</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul>
</body>
</html>
```
> telegram-error.template
```html ```html

View File

@@ -218,7 +218,7 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
} }
func localBackup(db *dbConfig, config *BackupConfig) { func localBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to local storage") utils.Info("Backup database to local storage")
startTime = time.Now().Format("2006-01-02 15:04:05") startTime = time.Now().Format(utils.TimeFormat())
BackupDatabase(db, config.backupFileName, disableCompression) BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName finalFileName := config.backupFileName
if config.encryption { if config.encryption {
@@ -241,7 +241,7 @@ func localBackup(db *dbConfig, config *BackupConfig) {
Storage: config.storage, Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName), BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime, StartTime: startTime,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(utils.TimeFormat()),
}) })
//Delete old backup //Delete old backup
if config.prune { if config.prune {
@@ -255,8 +255,11 @@ func localBackup(db *dbConfig, config *BackupConfig) {
func s3Backup(db *dbConfig, config *BackupConfig) { func s3Backup(db *dbConfig, config *BackupConfig) {
bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME") bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
s3Path := utils.GetEnvVariable("AWS_S3_PATH", "S3_PATH") s3Path := utils.GetEnvVariable("AWS_S3_PATH", "S3_PATH")
if config.remotePath != "" {
s3Path = config.remotePath
}
utils.Info("Backup database to s3 storage") utils.Info("Backup database to s3 storage")
startTime = time.Now().Format("2006-01-02 15:04:05") startTime = time.Now().Format(utils.TimeFormat())
//Backup database //Backup database
BackupDatabase(db, config.backupFileName, disableCompression) BackupDatabase(db, config.backupFileName, disableCompression)
@@ -299,9 +302,9 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
BackupSize: backupSize, BackupSize: backupSize,
Database: db.dbName, Database: db.dbName,
Storage: config.storage, Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName), BackupLocation: filepath.Join(s3Path, finalFileName),
StartTime: startTime, StartTime: startTime,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(utils.TimeFormat()),
}) })
//Delete temp //Delete temp
deleteTemp() deleteTemp()
@@ -310,7 +313,7 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
} }
func sshBackup(db *dbConfig, config *BackupConfig) { func sshBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to Remote server") utils.Info("Backup database to Remote server")
startTime = time.Now().Format("2006-01-02 15:04:05") startTime = time.Now().Format(utils.TimeFormat())
//Backup database //Backup database
BackupDatabase(db, config.backupFileName, disableCompression) BackupDatabase(db, config.backupFileName, disableCompression)
@@ -353,7 +356,7 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
Storage: config.storage, Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName), BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime, StartTime: startTime,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(utils.TimeFormat()),
}) })
//Delete temp //Delete temp
deleteTemp() deleteTemp()
@@ -362,7 +365,7 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
} }
func ftpBackup(db *dbConfig, config *BackupConfig) { func ftpBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to the remote FTP server") utils.Info("Backup database to the remote FTP server")
startTime = time.Now().Format("2006-01-02 15:04:05") startTime = time.Now().Format(utils.TimeFormat())
//Backup database //Backup database
BackupDatabase(db, config.backupFileName, disableCompression) BackupDatabase(db, config.backupFileName, disableCompression)
@@ -405,7 +408,7 @@ func ftpBackup(db *dbConfig, config *BackupConfig) {
Storage: config.storage, Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName), BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime, StartTime: startTime,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(utils.TimeFormat()),
}) })
//Delete temp //Delete temp
deleteTemp() deleteTemp()

View File

@@ -11,6 +11,7 @@
<ul> <ul>
<li>Error Message: {{.Error}}</li> <li>Error Message: {{.Error}}</li>
<li>Date: {{.EndTime}}</li> <li>Date: {{.EndTime}}</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul> </ul>
<p>©2024 <a href="github.com/jkaninda/mysql-bkup">mysql-bkup</a></p> <p>©2024 <a href="github.com/jkaninda/mysql-bkup">mysql-bkup</a></p>
</body> </body>

View File

@@ -15,6 +15,7 @@
<li>Backup Storage: {{.Storage}}</li> <li>Backup Storage: {{.Storage}}</li>
<li>Backup Location: {{.BackupLocation}}</li> <li>Backup Location: {{.BackupLocation}}</li>
<li>Backup Size: {{.BackupSize}} bytes</li> <li>Backup Size: {{.BackupSize}} bytes</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul> </ul>
<p>Best regards,</p> <p>Best regards,</p>
<p>©2024 <a href="github.com/jkaninda/mysql-bkup">mysql-bkup</a></p> <p>©2024 <a href="github.com/jkaninda/mysql-bkup">mysql-bkup</a></p>

View File

@@ -2,6 +2,7 @@
Hi, Hi,
An error occurred during database backup. An error occurred during database backup.
Failure Details: Failure Details:
Date: {{.EndTime}} - Date: {{.EndTime}}
Error Message: {{.Error}} - Backup Reference: {{.BackupReference}}
- Error Message: {{.Error}}

View File

@@ -1,4 +1,4 @@
✅ Database Backup Notification {{.Database}} [✅ Database Backup Notification {{.Database}}
Hi, Hi,
Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}. Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
@@ -8,4 +8,5 @@ Backup Details:
- Backup EndTime: {{.EndTime}} - Backup EndTime: {{.EndTime}}
- Backup Storage: {{.Storage}} - Backup Storage: {{.Storage}}
- Backup Location: {{.BackupLocation}} - Backup Location: {{.BackupLocation}}
- Backup Size: {{.BackupSize}} bytes - Backup Size: {{.BackupSize}} bytes
- Backup Reference: {{.BackupReference}}

View File

@@ -12,20 +12,23 @@ type MailConfig struct {
SkipTls bool SkipTls bool
} }
type NotificationData struct { type NotificationData struct {
File string File string
BackupSize int64 BackupSize int64
Database string Database string
StartTime string StartTime string
EndTime string EndTime string
Storage string Storage string
BackupLocation string BackupLocation string
BackupReference string
} }
type ErrorMessage struct { type ErrorMessage struct {
Database string Database string
EndTime string EndTime string
Error string Error string
BackupReference string
} }
// loadMailConfig gets mail environment variables and returns MailConfig
func loadMailConfig() *MailConfig { func loadMailConfig() *MailConfig {
return &MailConfig{ return &MailConfig{
MailHost: os.Getenv("MAIL_HOST"), MailHost: os.Getenv("MAIL_HOST"),
@@ -39,4 +42,18 @@ func loadMailConfig() *MailConfig {
} }
// TimeFormat returns the format of the time
func TimeFormat() string {
format := os.Getenv("TIME_FORMAT")
if format == "" {
return "2006-01-02 at 15:04:05"
}
return format
}
func backupReference() string {
return os.Getenv("BACKUP_REFERENCE")
}
const templatePath = "/config/templates" const templatePath = "/config/templates"

View File

@@ -6,9 +6,9 @@
**/ **/
package utils package utils
const RestoreExample = "mysql-bkup restore --dbname database --file db_20231219_022941.sql.gz\n" + const RestoreExample = "restore --dbname database --file db_20231219_022941.sql.gz\n" +
"restore --dbname database --storage s3 --path /custom-path --file db_20231219_022941.sql.gz" "restore --dbname database --storage s3 --path /custom-path --file db_20231219_022941.sql.gz"
const BackupExample = "mysql-bkup backup --dbname database --disable-compression\n" + const BackupExample = "backup --dbname database --disable-compression\n" +
"backup --dbname database --storage s3 --path /custom-path --disable-compression" "backup --dbname database --storage s3 --path /custom-path --disable-compression"
const MainExample = "mysql-bkup backup --dbname database --disable-compression\n" + const MainExample = "mysql-bkup backup --dbname database --disable-compression\n" +

View File

@@ -31,8 +31,8 @@ func parseTemplate[T any](data T, fileName string) (string, error) {
return buf.String(), nil return buf.String(), nil
} }
func SendEmail(subject, body string) { func SendEmail(subject, body string) error {
Info("Start sending email....") Info("Start sending email notification....")
config := loadMailConfig() config := loadMailConfig()
emails := strings.Split(config.MailTo, ",") emails := strings.Split(config.MailTo, ",")
m := mail.NewMessage() m := mail.NewMessage()
@@ -44,14 +44,16 @@ func SendEmail(subject, body string) {
d.TLSConfig = &tls.Config{InsecureSkipVerify: config.SkipTls} d.TLSConfig = &tls.Config{InsecureSkipVerify: config.SkipTls}
if err := d.DialAndSend(m); err != nil { if err := d.DialAndSend(m); err != nil {
Fatal("Error could not send email : %v", err) Error("Error could not send email : %v", err)
return err
} }
Info("Email has been sent") Info("Email notification has been sent")
return nil
} }
func sendMessage(msg string) { func sendMessage(msg string) error {
Info("Sending notification... ") Info("Sending Telegram notification... ")
chatId := os.Getenv("TG_CHAT_ID") chatId := os.Getenv("TG_CHAT_ID")
body, _ := json.Marshal(map[string]string{ body, _ := json.Marshal(map[string]string{
"chat_id": chatId, "chat_id": chatId,
@@ -67,18 +69,21 @@ func sendMessage(msg string) {
client := &http.Client{} client := &http.Client{}
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
panic(err) return err
} }
code := response.StatusCode code := response.StatusCode
if code == 200 { if code == 200 {
Info("Notification has been sent") Info("Telegram notification has been sent")
return nil
} else { } else {
body, _ := ioutil.ReadAll(response.Body) body, _ := ioutil.ReadAll(response.Body)
Fatal("Error could not send message, error: %s", string(body)) Error("Error could not send message, error: %s", string(body))
return fmt.Errorf("error could not send message %s", string(body))
} }
} }
func NotifySuccess(notificationData *NotificationData) { func NotifySuccess(notificationData *NotificationData) {
notificationData.BackupReference = backupReference()
var vars = []string{ var vars = []string{
"TG_TOKEN", "TG_TOKEN",
"TG_CHAT_ID", "TG_CHAT_ID",
@@ -99,17 +104,23 @@ func NotifySuccess(notificationData *NotificationData) {
if err != nil { if err != nil {
Error("Could not parse email template: %v", err) Error("Could not parse email template: %v", err)
} }
SendEmail(fmt.Sprintf("✅ Database Backup Notification %s", notificationData.Database), body) err = SendEmail(fmt.Sprintf("✅ Database Backup Notification %s", notificationData.Database), body)
if err != nil {
Error("Could not send email: %v", err)
}
} }
//Telegram notification //Telegram notification
err = CheckEnvVars(vars) err = CheckEnvVars(vars)
if err == nil { if err == nil {
message, err := parseTemplate(*notificationData, "telegram.template") message, err := parseTemplate(*notificationData, "telegram.template")
if err != nil { if err != nil {
Error("Could not parse email template: %v", err) Error("Could not parse telegram template: %v", err)
} }
sendMessage(message) err = sendMessage(message)
if err != nil {
Error("Could not send Telegram message: %v", err)
}
} }
} }
func NotifyError(error string) { func NotifyError(error string) {
@@ -130,26 +141,35 @@ func NotifyError(error string) {
err := CheckEnvVars(mailVars) err := CheckEnvVars(mailVars)
if err == nil { if err == nil {
body, err := parseTemplate(ErrorMessage{ body, err := parseTemplate(ErrorMessage{
Error: error, Error: error,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(TimeFormat()),
BackupReference: os.Getenv("BACKUP_REFERENCE"),
}, "email-error.template") }, "email-error.template")
if err != nil { if err != nil {
Error("Could not parse email template: %v", err) Error("Could not parse error template: %v", err)
}
err = SendEmail(fmt.Sprintf("🔴 Urgent: Database Backup Failure Notification"), body)
if err != nil {
Error("Could not send email: %v", err)
} }
SendEmail(fmt.Sprintf("🔴 Urgent: Database Backup Failure Notification"), body)
} }
//Telegram notification //Telegram notification
err = CheckEnvVars(vars) err = CheckEnvVars(vars)
if err == nil { if err == nil {
message, err := parseTemplate(ErrorMessage{ message, err := parseTemplate(ErrorMessage{
Error: error, Error: error,
EndTime: time.Now().Format("2006-01-02 15:04:05"), EndTime: time.Now().Format(TimeFormat()),
BackupReference: os.Getenv("BACKUP_REFERENCE"),
}, "telegram-error.template") }, "telegram-error.template")
if err != nil { if err != nil {
Error("Could not parse email template: %v", err) Error("Could not parse error template: %v", err)
} }
sendMessage(message) err = sendMessage(message)
if err != nil {
Error("Could not send telegram message: %v", err)
}
} }
} }