Files
mysql-bkup/internal/backup.go

533 lines
16 KiB
Go
Raw Normal View History

// Package internal /
2024-12-06 03:25:38 +01:00
/*
MIT License
Copyright (c) 2023 Jonas Kaninda
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
import (
"fmt"
2024-10-13 14:33:54 +02:00
"github.com/jkaninda/encryptor"
"github.com/jkaninda/go-storage/pkg/ftp"
"github.com/jkaninda/go-storage/pkg/local"
"github.com/jkaninda/go-storage/pkg/s3"
"github.com/jkaninda/go-storage/pkg/ssh"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/robfig/cron/v3"
"github.com/spf13/cobra"
"log"
"os"
"os/exec"
"path/filepath"
"time"
)
func StartBackup(cmd *cobra.Command) {
intro()
//Initialize backup configs
config := initBackupConfig(cmd)
2024-10-09 12:23:14 +02:00
//Load backup configuration file
configFile, err := loadConfigFile()
if err != nil {
dbConf = initDbConfig(cmd)
if config.cronExpression == "" {
BackupTask(dbConf, config)
} else {
2024-10-09 12:23:14 +02:00
if utils.IsValidCronExpression(config.cronExpression) {
scheduledMode(dbConf, config)
} else {
utils.Fatal("Cron expression is not valid: %s", config.cronExpression)
}
}
2024-10-09 12:23:14 +02:00
} else {
startMultiBackup(config, configFile)
}
}
// scheduledMode Runs backup in scheduled mode
func scheduledMode(db *dbConfig, config *BackupConfig) {
utils.Info("Running in Scheduled mode")
utils.Info("Backup cron expression: %s", config.cronExpression)
utils.Info("The next scheduled time is: %v", utils.CronNextTime(config.cronExpression).Format(timeFormat))
utils.Info("Storage type %s ", config.storage)
//Test backup
utils.Info("Testing backup configurations...")
BackupTask(db, config)
utils.Info("Testing backup configurations...done")
2024-09-28 10:43:08 +02:00
utils.Info("Creating backup job...")
// Create a new cron instance
c := cron.New()
_, err := c.AddFunc(config.cronExpression, func() {
BackupTask(db, config)
utils.Info("Next backup time is: %v", utils.CronNextTime(config.cronExpression).Format(timeFormat))
})
if err != nil {
return
}
// Start the cron scheduler
c.Start()
2024-09-28 10:43:08 +02:00
utils.Info("Creating backup job...done")
utils.Info("Backup job started")
defer c.Stop()
select {}
}
// multiBackupTask backup multi database
func multiBackupTask(databases []Database, bkConfig *BackupConfig) {
for _, db := range databases {
//Check if path is defined in config file
if db.Path != "" {
bkConfig.remotePath = db.Path
}
BackupTask(getDatabase(db), bkConfig)
}
}
// BackupTask backups database
func BackupTask(db *dbConfig, config *BackupConfig) {
utils.Info("Starting backup task...")
//Generate file name
2024-09-30 02:02:37 +02:00
backupFileName := fmt.Sprintf("%s_%s.sql.gz", db.dbName, time.Now().Format("20060102_150405"))
if config.disableCompression {
2024-09-30 02:02:37 +02:00
backupFileName = fmt.Sprintf("%s_%s.sql", db.dbName, time.Now().Format("20060102_150405"))
}
config.backupFileName = backupFileName
switch config.storage {
case "local":
2024-09-29 19:50:26 +02:00
localBackup(db, config)
case "s3", "S3":
2024-09-29 19:50:26 +02:00
s3Backup(db, config)
case "ssh", "SSH", "remote":
2024-09-29 19:50:26 +02:00
sshBackup(db, config)
case "ftp", "FTP":
2024-09-30 00:40:35 +02:00
ftpBackup(db, config)
default:
2024-09-29 19:50:26 +02:00
localBackup(db, config)
}
}
2024-10-09 12:23:14 +02:00
func startMultiBackup(bkConfig *BackupConfig, configFile string) {
utils.Info("Starting backup task...")
2024-10-09 12:23:14 +02:00
conf, err := readConf(configFile)
if err != nil {
utils.Fatal("Error reading config file: %s", err)
}
//Check if cronExpression is defined in config file
if conf.CronExpression != "" {
bkConfig.cronExpression = conf.CronExpression
}
// Check if cronExpression is defined
if bkConfig.cronExpression == "" {
multiBackupTask(conf.Databases, bkConfig)
} else {
// Check if cronExpression is valid
if utils.IsValidCronExpression(bkConfig.cronExpression) {
utils.Info("Running backup in Scheduled mode")
2024-10-09 12:23:14 +02:00
utils.Info("Backup cron expression: %s", bkConfig.cronExpression)
utils.Info("The next scheduled time is: %v", utils.CronNextTime(bkConfig.cronExpression).Format(timeFormat))
2024-10-09 12:23:14 +02:00
utils.Info("Storage type %s ", bkConfig.storage)
//Test backup
utils.Info("Testing backup configurations...")
multiBackupTask(conf.Databases, bkConfig)
utils.Info("Testing backup configurations...done")
utils.Info("Creating backup job...")
2024-10-09 12:23:14 +02:00
// Create a new cron instance
c := cron.New()
_, err := c.AddFunc(bkConfig.cronExpression, func() {
multiBackupTask(conf.Databases, bkConfig)
utils.Info("Next backup time is: %v", utils.CronNextTime(bkConfig.cronExpression).Format(timeFormat))
2024-10-09 12:23:14 +02:00
})
if err != nil {
return
}
// Start the cron scheduler
c.Start()
utils.Info("Creating backup job...done")
2024-10-09 12:23:14 +02:00
utils.Info("Backup job started")
defer c.Stop()
select {}
} else {
utils.Fatal("Cron expression is not valid: %s", bkConfig.cronExpression)
}
}
}
2024-01-19 06:56:19 +01:00
// BackupDatabase backup database
func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) {
storagePath = os.Getenv("STORAGE_PATH")
utils.Info("Starting database backup...")
2024-10-09 12:23:14 +02:00
err := os.Setenv("MYSQL_PWD", db.dbPassword)
if err != nil {
return
}
testDatabaseConnection(db)
// Backup Database database
utils.Info("Backing up database...")
// Verify is compression is disabled
if disableCompression {
// Execute mysqldump
cmd := exec.Command("mysqldump",
"-h", db.dbHost,
"-P", db.dbPort,
"-u", db.dbUserName,
db.dbName,
)
output, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
// save output
2024-10-09 12:23:14 +02:00
file, err := os.Create(filepath.Join(tmpPath, backupFileName))
if err != nil {
log.Fatal(err)
2024-02-20 07:57:21 +01:00
}
defer file.Close()
_, err = file.Write(output)
if err != nil {
log.Fatal(err)
}
2024-10-21 08:22:06 +02:00
utils.Info("Database has been backed up")
} else {
// Execute mysqldump
cmd := exec.Command("mysqldump", "-h", db.dbHost, "-P", db.dbPort, "-u", db.dbUserName, db.dbName)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
gzipCmd := exec.Command("gzip")
gzipCmd.Stdin = stdout
2024-10-09 12:23:14 +02:00
gzipCmd.Stdout, err = os.Create(filepath.Join(tmpPath, backupFileName))
gzipCmd.Start()
if err != nil {
log.Fatal(err)
}
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
if err := gzipCmd.Wait(); err != nil {
log.Fatal(err)
}
2024-10-21 08:22:06 +02:00
utils.Info("Database has been backed up")
}
}
2024-09-29 19:50:26 +02:00
func localBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to local storage")
startTime = time.Now().Format(utils.TimeFormat())
2024-09-29 19:50:26 +02:00
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
2024-09-29 19:50:26 +02:00
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, gpgExtension)
}
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
utils.Info("Backup name is %s", finalFileName)
localStorage := local.NewStorage(local.Config{
LocalPath: tmpPath,
RemotePath: storagePath,
})
err = localStorage.Copy(finalFileName)
if err != nil {
utils.Fatal("Error copying backup file: %s", err)
}
utils.Info("Backup saved in %s", filepath.Join(storagePath, finalFileName))
2024-09-10 23:01:26 +02:00
//Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: backupSize,
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(storagePath, finalFileName),
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
//Delete old backup
2024-09-29 19:50:26 +02:00
if config.prune {
err = localStorage.Prune(config.backupRetention)
if err != nil {
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
//Delete temp
deleteTemp()
2024-10-09 12:23:14 +02:00
utils.Info("Backup completed successfully")
}
2024-09-29 19:50:26 +02:00
func s3Backup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to s3 storage")
startTime = time.Now().Format(utils.TimeFormat())
//Backup database
2024-09-29 19:50:26 +02:00
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
2024-09-29 19:50:26 +02:00
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
utils.Info("Uploading backup archive to remote storage S3 ... ")
awsConfig := initAWSConfig()
if config.remotePath == "" {
config.remotePath = awsConfig.remotePath
}
utils.Info("Backup name is %s", finalFileName)
s3Storage, err := s3.NewStorage(s3.Config{
Endpoint: awsConfig.endpoint,
Bucket: awsConfig.bucket,
AccessKey: awsConfig.accessKey,
SecretKey: awsConfig.secretKey,
Region: awsConfig.region,
DisableSsl: awsConfig.disableSsl,
ForcePathStyle: awsConfig.forcePathStyle,
RemotePath: awsConfig.remotePath,
LocalPath: tmpPath,
})
if err != nil {
utils.Fatal("Error creating s3 storage: %s", err)
}
err = s3Storage.Copy(finalFileName)
if err != nil {
utils.Fatal("Error copying backup file: %s", err)
}
//Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
//Delete backup file from tmp folder
2024-09-29 19:50:26 +02:00
err = utils.DeleteFile(filepath.Join(tmpPath, config.backupFileName))
if err != nil {
fmt.Println("Error deleting file: ", err)
}
// Delete old backup
2024-09-29 19:50:26 +02:00
if config.prune {
err := s3Storage.Prune(config.backupRetention)
if err != nil {
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
2024-10-21 08:22:06 +02:00
utils.Info("Uploading backup archive to remote storage S3 ... done ")
2024-09-10 23:01:26 +02:00
//Send notification
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(utils.TimeFormat()),
})
//Delete temp
deleteTemp()
2024-10-09 12:23:14 +02:00
utils.Info("Backup completed successfully")
}
2024-09-29 19:50:26 +02:00
func sshBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to Remote server")
startTime = time.Now().Format(utils.TimeFormat())
//Backup database
2024-09-29 19:50:26 +02:00
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
2024-09-29 19:50:26 +02:00
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
utils.Info("Uploading backup archive to remote storage ... ")
utils.Info("Backup name is %s", finalFileName)
sshConfig, err := loadSSHConfig()
if err != nil {
utils.Fatal("Error loading ssh config: %s", err)
}
sshStorage, err := ssh.NewStorage(ssh.Config{
2024-10-23 09:36:58 +02:00
Host: sshConfig.hostName,
Port: sshConfig.port,
User: sshConfig.user,
Password: sshConfig.password,
IdentifyFile: sshConfig.identifyFile,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
if err != nil {
utils.Fatal("Error creating SSH storage: %s", err)
}
err = sshStorage.Copy(finalFileName)
if err != nil {
utils.Fatal("Error copying backup file: %s", err)
}
//Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
//Delete backup file from tmp folder
err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error deleting file: %v", err)
}
2024-09-29 19:50:26 +02:00
if config.prune {
err := sshStorage.Prune(config.backupRetention)
if err != nil {
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
2024-10-21 08:22:06 +02:00
utils.Info("Uploading backup archive to remote storage ... done ")
2024-09-10 23:01:26 +02:00
//Send notification
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(utils.TimeFormat()),
})
//Delete temp
deleteTemp()
2024-10-09 12:23:14 +02:00
utils.Info("Backup completed successfully")
}
2024-09-30 00:40:35 +02:00
func ftpBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to the remote FTP server")
startTime = time.Now().Format(utils.TimeFormat())
2024-09-30 00:40:35 +02:00
//Backup database
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
2024-09-30 00:40:35 +02:00
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
utils.Info("Uploading backup archive to the remote FTP server ... ")
utils.Info("Backup name is %s", finalFileName)
ftpConfig := loadFtpConfig()
ftpStorage, err := ftp.NewStorage(ftp.Config{
Host: ftpConfig.host,
Port: ftpConfig.port,
User: ftpConfig.user,
Password: ftpConfig.password,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
2024-09-30 00:40:35 +02:00
if err != nil {
utils.Fatal("Error creating SSH storage: %s", err)
}
err = ftpStorage.Copy(finalFileName)
if err != nil {
utils.Fatal("Error copying backup file: %s", err)
2024-09-30 00:40:35 +02:00
}
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
//Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
2024-09-30 00:40:35 +02:00
//Delete backup file from tmp folder
err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error deleting file: %v", err)
}
if config.prune {
err := ftpStorage.Prune(config.backupRetention)
if err != nil {
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
2024-09-30 00:40:35 +02:00
}
2024-10-21 08:22:06 +02:00
utils.Info("Uploading backup archive to the remote FTP server ... done ")
2024-09-30 00:40:35 +02:00
//Send notification
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(utils.TimeFormat()),
})
2024-09-30 00:40:35 +02:00
//Delete temp
deleteTemp()
2024-10-09 12:23:14 +02:00
utils.Info("Backup completed successfully")
2024-09-30 00:40:35 +02:00
}
2024-09-12 06:23:57 +02:00
func encryptBackup(config *BackupConfig) {
2024-10-13 14:33:54 +02:00
backupFile, err := os.ReadFile(filepath.Join(tmpPath, config.backupFileName))
outputFile := fmt.Sprintf("%s.%s", filepath.Join(tmpPath, config.backupFileName), gpgExtension)
if err != nil {
utils.Fatal("Error reading backup file: %s ", err)
}
if config.usingKey {
2024-10-13 14:33:54 +02:00
utils.Info("Encrypting backup using public key...")
pubKey, err := os.ReadFile(config.publicKey)
if err != nil {
2024-10-13 14:33:54 +02:00
utils.Fatal("Error reading public key: %s ", err)
}
2024-10-13 14:33:54 +02:00
err = encryptor.EncryptWithPublicKey(backupFile, fmt.Sprintf("%s.%s", filepath.Join(tmpPath, config.backupFileName), gpgExtension), pubKey)
if err != nil {
utils.Fatal("Error encrypting backup file: %v ", err)
}
utils.Info("Encrypting backup using public key...done")
} else if config.passphrase != "" {
2024-10-13 14:33:54 +02:00
utils.Info("Encrypting backup using passphrase...")
err := encryptor.Encrypt(backupFile, outputFile, config.passphrase)
if err != nil {
utils.Fatal("error during encrypting backup %v", err)
}
2024-10-13 14:33:54 +02:00
utils.Info("Encrypting backup using passphrase...done")
}
}