Skip to content

Commit c5cca82

Browse files
authored
Merge pull request #111 from jkaninda/multi-backup
Add Multi database backup
2 parents 909a50d + bbd5422 commit c5cca82

File tree

12 files changed

+200
-15
lines changed

12 files changed

+200
-15
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
uses: docker/build-push-action@v3
2626
with:
2727
push: true
28-
file: "./docker/Dockerfile"
28+
file: "./Dockerfile"
2929
platforms: linux/amd64,linux/arm64,linux/arm/v7
3030
build-args: |
3131
appVersion=develop-${{ github.sha }}

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
uses: docker/build-push-action@v3
4040
with:
4141
push: true
42-
file: "./docker/Dockerfile"
42+
file: "./Dockerfile"
4343
platforms: linux/amd64,linux/arm64,linux/arm/v7
4444
build-args: |
4545
appVersion=${{ env.TAG_NAME }}
File renamed without changes.

docs/how-tos/deprecated-configs.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Update deprecated configurations
3+
layout: default
4+
parent: How Tos
5+
nav_order: 11
6+
---

docs/how-tos/mutli-backup.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: Run multiple backup schedules in the same container
3+
layout: default
4+
parent: How Tos
5+
nav_order: 11
6+
---
7+
8+
Multiple backup schedules with different configuration can be configured by mounting a configuration file into `/config/config.yaml` `/config/config.yml` or by defining an environment variable `BACKUP_CONFIG_FILE=/backup/config.yaml`.
9+
10+
## Configuration file
11+
12+
```yaml
13+
#cronExpression: "@every 20m" //Optional for scheduled backups
14+
cronExpression: ""
15+
databases:
16+
- host: mysql1
17+
port: 3306
18+
name: database1
19+
user: database1
20+
password: password
21+
path: /s3-path/database1 #For SSH or FTP you need to define the full path (/home/toto/backup/)
22+
- host: mysql2
23+
port: 3306
24+
name: lldap
25+
user: lldap
26+
password: password
27+
path: /s3-path/lldap #For SSH or FTP you need to define the full path (/home/toto/backup/)
28+
- host: mysql3
29+
port: 3306
30+
name: keycloak
31+
user: keycloak
32+
password: password
33+
path: /s3-path/keycloak #For SSH or FTP you need to define the full path (/home/toto/backup/)
34+
- host: mysql4
35+
port: 3306
36+
name: joplin
37+
user: joplin
38+
password: password
39+
path: /s3-path/joplin #For SSH or FTP you need to define the full path (/home/toto/backup/)
40+
```

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
github.com/robfig/cron/v3 v3.0.1
1414
github.com/spf13/cobra v1.8.0
1515
golang.org/x/crypto v0.18.0
16+
gopkg.in/yaml.v3 v3.0.1
1617
)
1718

1819
require (

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,5 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
101101
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
102102
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
103103
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
104+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
104105
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/backup.go

Lines changed: 81 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,23 @@ import (
2020

2121
func StartBackup(cmd *cobra.Command) {
2222
intro()
23-
dbConf = initDbConfig(cmd)
2423
//Initialize backup configs
2524
config := initBackupConfig(cmd)
26-
27-
if config.cronExpression == "" {
28-
BackupTask(dbConf, config)
29-
} else {
30-
if utils.IsValidCronExpression(config.cronExpression) {
31-
scheduledMode(dbConf, config)
25+
//Load backup configuration file
26+
configFile, err := loadConfigFile()
27+
if err != nil {
28+
dbConf = initDbConfig(cmd)
29+
if config.cronExpression == "" {
30+
BackupTask(dbConf, config)
3231
} else {
33-
utils.Fatal("Cron expression is not valid: %s", config.cronExpression)
32+
if utils.IsValidCronExpression(config.cronExpression) {
33+
scheduledMode(dbConf, config)
34+
} else {
35+
utils.Fatal("Cron expression is not valid: %s", config.cronExpression)
36+
}
3437
}
38+
} else {
39+
startMultiBackup(config, configFile)
3540
}
3641

3742
}
@@ -85,6 +90,64 @@ func BackupTask(db *dbConfig, config *BackupConfig) {
8590
localBackup(db, config)
8691
}
8792
}
93+
func multiBackupTask(databases []Database, bkConfig *BackupConfig) {
94+
for _, db := range databases {
95+
//Check if path is defined in config file
96+
if db.Path != "" {
97+
bkConfig.remotePath = db.Path
98+
}
99+
BackupTask(getDatabase(db), bkConfig)
100+
}
101+
}
102+
func startMultiBackup(bkConfig *BackupConfig, configFile string) {
103+
utils.Info("Starting multiple backup jobs...")
104+
var conf = &Config{}
105+
conf, err := readConf(configFile)
106+
if err != nil {
107+
utils.Fatal("Error reading config file: %s", err)
108+
}
109+
//Check if cronExpression is defined in config file
110+
if conf.CronExpression != "" {
111+
bkConfig.cronExpression = conf.CronExpression
112+
}
113+
// Check if cronExpression is defined
114+
if bkConfig.cronExpression == "" {
115+
multiBackupTask(conf.Databases, bkConfig)
116+
} else {
117+
// Check if cronExpression is valid
118+
if utils.IsValidCronExpression(bkConfig.cronExpression) {
119+
utils.Info("Running MultiBackup in Scheduled mode")
120+
utils.Info("Backup cron expression: %s", bkConfig.cronExpression)
121+
utils.Info("Storage type %s ", bkConfig.storage)
122+
123+
//Test backup
124+
utils.Info("Testing backup configurations...")
125+
multiBackupTask(conf.Databases, bkConfig)
126+
utils.Info("Testing backup configurations...done")
127+
utils.Info("Creating multi backup job...")
128+
// Create a new cron instance
129+
c := cron.New()
130+
131+
_, err := c.AddFunc(bkConfig.cronExpression, func() {
132+
// Create a channel
133+
multiBackupTask(conf.Databases, bkConfig)
134+
})
135+
if err != nil {
136+
return
137+
}
138+
// Start the cron scheduler
139+
c.Start()
140+
utils.Info("Creating multi backup job...done")
141+
utils.Info("Backup job started")
142+
defer c.Stop()
143+
select {}
144+
145+
} else {
146+
utils.Fatal("Cron expression is not valid: %s", bkConfig.cronExpression)
147+
}
148+
}
149+
150+
}
88151

89152
// BackupDatabase backup database
90153
func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) {
@@ -93,7 +156,7 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
93156

94157
utils.Info("Starting database backup...")
95158

96-
err := os.Setenv("PGPASSWORD", db.dbPassword)
159+
err := os.Setenv("MYSQL_PWD", db.dbPassword)
97160
if err != nil {
98161
return
99162
}
@@ -116,7 +179,7 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
116179
}
117180

118181
// save output
119-
file, err := os.Create(fmt.Sprintf("%s/%s", tmpPath, backupFileName))
182+
file, err := os.Create(filepath.Join(tmpPath, backupFileName))
120183
if err != nil {
121184
log.Fatal(err)
122185
}
@@ -137,7 +200,7 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
137200
}
138201
gzipCmd := exec.Command("gzip")
139202
gzipCmd.Stdin = stdout
140-
gzipCmd.Stdout, err = os.Create(fmt.Sprintf("%s/%s", tmpPath, backupFileName))
203+
gzipCmd.Stdout, err = os.Create(filepath.Join(tmpPath, backupFileName))
141204
gzipCmd.Start()
142205
if err != nil {
143206
log.Fatal(err)
@@ -172,6 +235,7 @@ func localBackup(db *dbConfig, config *BackupConfig) {
172235
}
173236
//Delete temp
174237
deleteTemp()
238+
utils.Info("Backup completed successfully")
175239
}
176240

177241
func s3Backup(db *dbConfig, config *BackupConfig) {
@@ -212,6 +276,8 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
212276
utils.NotifySuccess(finalFileName)
213277
//Delete temp
214278
deleteTemp()
279+
utils.Info("Backup completed successfully")
280+
215281
}
216282
func sshBackup(db *dbConfig, config *BackupConfig) {
217283
utils.Info("Backup database to Remote server")
@@ -247,6 +313,8 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
247313
utils.NotifySuccess(finalFileName)
248314
//Delete temp
249315
deleteTemp()
316+
utils.Info("Backup completed successfully")
317+
250318
}
251319
func ftpBackup(db *dbConfig, config *BackupConfig) {
252320
utils.Info("Backup database to the remote FTP server")
@@ -282,6 +350,8 @@ func ftpBackup(db *dbConfig, config *BackupConfig) {
282350
utils.NotifySuccess(finalFileName)
283351
//Delete temp
284352
deleteTemp()
353+
utils.Info("Backup completed successfully")
354+
285355
}
286356

287357
func encryptBackup(config *BackupConfig) {

pkg/config.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ import (
1414
"strconv"
1515
)
1616

17+
type Database struct {
18+
Host string `yaml:"host"`
19+
Port string `yaml:"port"`
20+
Name string `yaml:"name"`
21+
User string `yaml:"user"`
22+
Password string `yaml:"password"`
23+
Path string `yaml:"path"`
24+
}
1725
type Config struct {
26+
Databases []Database `yaml:"databases"`
27+
CronExpression string `yaml:"cronExpression"`
1828
}
1929

2030
type dbConfig struct {
@@ -92,6 +102,16 @@ func initDbConfig(cmd *cobra.Command) *dbConfig {
92102
return &dConf
93103
}
94104

105+
func getDatabase(database Database) *dbConfig {
106+
return &dbConfig{
107+
dbHost: database.Host,
108+
dbPort: database.Port,
109+
dbName: database.Name,
110+
dbUserName: database.User,
111+
dbPassword: database.Password,
112+
}
113+
}
114+
95115
// loadSSHConfig loads the SSH configuration from environment variables
96116
func loadSSHConfig() (*SSHConfig, error) {
97117
utils.GetEnvVariable("SSH_HOST", "SSH_HOST_NAME")
@@ -245,3 +265,10 @@ func initTargetDbConfig() *targetDbConfig {
245265
}
246266
return &tdbConfig
247267
}
268+
func loadConfigFile() (string, error) {
269+
backupConfigFile, err := checkConfigFile(os.Getenv("BACKUP_CONFIG_FILE"))
270+
if err == nil {
271+
return backupConfigFile, nil
272+
}
273+
return "", fmt.Errorf("backup config file not found")
274+
}

pkg/helper.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"bytes"
1111
"fmt"
1212
"github.com/jkaninda/mysql-bkup/utils"
13+
"gopkg.in/yaml.v3"
1314
"os"
1415
"os/exec"
1516
"path/filepath"
@@ -171,3 +172,42 @@ func checkPrKeyFile(prKey string) (string, error) {
171172
// Return an error if neither file exists
172173
return "", fmt.Errorf("no public key file found")
173174
}
175+
func readConf(configFile string) (*Config, error) {
176+
//configFile := filepath.Join("./", filename)
177+
if utils.FileExists(configFile) {
178+
buf, err := os.ReadFile(configFile)
179+
if err != nil {
180+
return nil, err
181+
}
182+
183+
c := &Config{}
184+
err = yaml.Unmarshal(buf, c)
185+
if err != nil {
186+
return nil, fmt.Errorf("in file %q: %w", configFile, err)
187+
}
188+
189+
return c, err
190+
}
191+
return nil, fmt.Errorf("config file %q not found", configFile)
192+
}
193+
func checkConfigFile(filePath string) (string, error) {
194+
// Define possible config file names
195+
configFiles := []string{filepath.Join(workingDir, "config.yaml"), filepath.Join(workingDir, "config.yml"), filePath}
196+
197+
// Loop through config file names and check if they exist
198+
for _, configFile := range configFiles {
199+
if _, err := os.Stat(configFile); err == nil {
200+
// File exists
201+
return configFile, nil
202+
} else if os.IsNotExist(err) {
203+
// File does not exist, continue to the next one
204+
continue
205+
} else {
206+
// An unexpected error occurred
207+
return "", err
208+
}
209+
}
210+
211+
// Return an error if neither file exists
212+
return "", fmt.Errorf("no config file found")
213+
}

pkg/restore.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package pkg
88

99
import (
10-
"fmt"
1110
"github.com/jkaninda/mysql-bkup/utils"
1211
"github.com/spf13/cobra"
1312
"os"
@@ -94,7 +93,7 @@ func RestoreDatabase(db *dbConfig, conf *RestoreConfig) {
9493

9594
}
9695

97-
if utils.FileExists(fmt.Sprintf("%s/%s", tmpPath, conf.file)) {
96+
if utils.FileExists(filepath.Join(tmpPath, conf.file)) {
9897
err := os.Setenv("MYSQL_PWD", db.dbPassword)
9998
if err != nil {
10099
return

pkg/var.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const tmpPath = "/tmp/backup"
1111
const algorithm = "aes256"
1212
const gpgHome = "/config/gnupg"
1313
const gpgExtension = "gpg"
14+
const workingDir = "/config"
1415

1516
var (
1617
storage = "local"

0 commit comments

Comments
 (0)