Compare commits

..

7 Commits

Author SHA1 Message Date
e5ba397bb4 Merge pull request #164 from jkaninda/nightly
Some checks failed
Deploy Documenation site to GitHub Pages / build (push) Failing after 9m27s
Deploy Documenation site to GitHub Pages / deploy (push) Has been skipped
doc: reviewed docs
2025-01-13 15:34:50 +01:00
b7b09ad6fd Merge pull request #163 from jkaninda/nightly
Nightly
2025-01-13 15:06:27 +01:00
fff0b55722 Merge pull request #162 from jkaninda/nightly
feat: add backup flags for configuration and cron expression
2025-01-13 14:57:00 +01:00
a06872834f Merge pull request #161 from jkaninda/dependabot/docker/alpine-3.21.2
chore(deps): bump alpine from 3.21.0 to 3.21.2
2025-01-13 10:53:41 +01:00
dependabot[bot]
393168c6c5 chore(deps): bump alpine from 3.21.0 to 3.21.2
Bumps alpine from 3.21.0 to 3.21.2.

---
updated-dependencies:
- dependency-name: alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 09:52:30 +00:00
5b9ec8a224 Merge pull request #160 from jkaninda/nightly
fix: the configuration file path is not being detected when it is enc…
2025-01-12 09:36:29 +01:00
0df14f37b4 Merge pull request #159 from jkaninda/refactor
chore: add convert bytes to a human-readable string with the appropri…
2024-12-12 13:29:22 +01:00
24 changed files with 130 additions and 235 deletions

View File

@@ -27,7 +27,6 @@ linters:
- gosimple
- govet
- ineffassign
# - lll
- misspell
- nakedret
- prealloc

View File

@@ -10,7 +10,7 @@ RUN go mod download
# Build
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-X 'github.com/jkaninda/mysql-bkup/utils.Version=${appVersion}'" -o /app/mysql-bkup
FROM alpine:3.21.0
FROM alpine:3.21.2
ENV TZ=UTC
ARG WORKDIR="/config"
ARG BACKUPDIR="/backup"

View File

@@ -74,7 +74,6 @@ To run a one time backup, bind your local volume to `/backup` in the container a
docker run --rm --network your_network_name \
-v $PWD/backup:/backup/ \
-e "DB_HOST=dbhost" \
-e "DB_PORT=3306" \
-e "DB_USERNAME=username" \
-e "DB_PASSWORD=password" \
jkaninda/mysql-bkup backup -d database_name
@@ -88,19 +87,7 @@ Alternatively, pass a `--env-file` in order to use a full config as described be
-v $PWD/backup:/backup/ \
jkaninda/mysql-bkup backup -d database_name
```
### Simple restore using Docker CLI
To restore a database, bind your local volume to `/backup` in the container and run the `restore` command:
```shell
docker run --rm --network your_network_name \
-v $PWD/backup:/backup/ \
-e "DB_HOST=dbhost" \
-e "DB_PORT=3306" \
-e "DB_USERNAME=username" \
-e "DB_PASSWORD=password" \
jkaninda/mysql-bkup restore -d database_name -f backup_file.sql.gz
```
### Simple backup in docker compose file
```yaml

View File

@@ -44,7 +44,7 @@ var BackupCmd = &cobra.Command{
}
func init() {
// Backup
//Backup
BackupCmd.PersistentFlags().StringP("storage", "s", "local", "Define storage: local, s3, ssh, ftp, azure")
BackupCmd.PersistentFlags().StringP("path", "P", "", "Storage path without file name. e.g: /custom_path or ssh remote path `/home/foo/backup`")
BackupCmd.PersistentFlags().StringP("cron-expression", "e", "", "Backup cron expression (e.g., `0 0 * * *` or `@daily`)")

View File

@@ -46,7 +46,7 @@ var RestoreCmd = &cobra.Command{
}
func init() {
// Restore
//Restore
RestoreCmd.PersistentFlags().StringP("file", "f", "", "File name of database")
RestoreCmd.PersistentFlags().StringP("storage", "s", "local", "Define storage: local, s3, ssh, ftp")
RestoreCmd.PersistentFlags().StringP("path", "P", "", "AWS S3 path without file name. eg: /custom_path or ssh remote path `/home/foo/backup`")

View File

@@ -38,6 +38,7 @@ var rootCmd = &cobra.Command{
Example: utils.MainExample,
Version: appVersion,
}
var operation = ""
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.

View File

@@ -8,62 +8,51 @@ nav_order: 11
# Multiple Backup Schedules
This tool supports running multiple database backup schedules within the same container.
You can configure these schedules with different settings using a **configuration file**. This flexibility allows you to manage backups for multiple databases efficiently.
You can configure multiple backup schedules with different configurations by using a configuration file.
This file can be mounted into the container at `/config/config.yaml`, `/config/config.yml`, or specified via the `BACKUP_CONFIG_FILE` environment variable.
---
## Configuration File Setup
## Configuration File
The configuration file can be mounted into the container at `/config/config.yaml`, `/config/config.yml`, or specified via the `BACKUP_CONFIG_FILE` environment variable.
The configuration file allows you to define multiple databases and their respective backup settings.
### Key Features:
- **Global Environment Variables**: Use these for databases that share the same configuration.
- **Database-Specific Overrides**: Override global settings for individual databases by specifying them in the configuration file or using the database name as a suffix in the variable name (e.g., `DB_HOST_DATABASE1`).
- **Global Cron Expression**: Define a global `cronExpression` in the configuration file to schedule backups for all databases. If omitted, backups will run immediately.
- **Configuration File Path**: Specify the configuration file path using:
- The `BACKUP_CONFIG_FILE` environment variable.
- The `--config` or `-c` flag for the backup command.
---
## Configuration File Example
Below is an example configuration file (`config.yaml`) that defines multiple databases and their respective backup settings:
Below is an example configuration file:
```yaml
# Optional: Define a global cron expression for scheduled backups.
# Example: "@every 20m" (runs every 20 minutes). If omitted, backups run immediately.
# Optional: Define a global cron expression for scheduled backups
# cronExpression: "@every 20m"
cronExpression: ""
databases:
- host: mysql1 # Optional: Overrides DB_HOST or uses DB_HOST_DATABASE1.
port: 3306 # Optional: Default is 5432. Overrides DB_PORT or uses DB_PORT_DATABASE1.
name: database1 # Required: Database name.
user: database1 # Optional: Overrides DB_USERNAME or uses DB_USERNAME_DATABASE1.
password: password # Optional: Overrides DB_PASSWORD or uses DB_PASSWORD_DATABASE1.
path: /s3-path/database1 # Required: Backup path for SSH, FTP, or S3 (e.g., /home/toto/backup/).
- host: mysql1
port: 3306
name: database1
user: database1
password: password
path: /s3-path/database1 # For SSH or FTP, define the full path (e.g., /home/toto/backup/)
- host: mysql2 # Optional: Overrides DB_HOST or uses DB_HOST_LLAP.
port: 3306 # Optional: Default is 5432. Overrides DB_PORT or uses DB_PORT_LLAP.
name: lldap # Required: Database name.
user: lldap # Optional: Overrides DB_USERNAME or uses DB_USERNAME_LLAP.
password: password # Optional: Overrides DB_PASSWORD or uses DB_PASSWORD_LLAP.
path: /s3-path/lldap # Required: Backup path for SSH, FTP, or S3 (e.g., /home/toto/backup/).
- host: mysql2
port: 3306
name: lldap
user: lldap
password: password
path: /s3-path/lldap # For SSH or FTP, define the full path (e.g., /home/toto/backup/)
- host: mysql3 # Optional: Overrides DB_HOST or uses DB_HOST_KEYCLOAK.
port: 3306 # Optional: Default is 5432. Overrides DB_PORT or uses DB_PORT_KEYCLOAK.
name: keycloak # Required: Database name.
user: keycloak # Optional: Overrides DB_USERNAME or uses DB_USERNAME_KEYCLOAK.
password: password # Optional: Overrides DB_PASSWORD or uses DB_PASSWORD_KEYCLOAK.
path: /s3-path/keycloak # Required: Backup path for SSH, FTP, or S3 (e.g., /home/toto/backup/).
- host: mysql3
port: 3306
name: keycloak
user: keycloak
password: password
path: /s3-path/keycloak # For SSH or FTP, define the full path (e.g., /home/toto/backup/)
- host: mysql4 # Optional: Overrides DB_HOST or uses DB_HOST_JOPLIN.
port: 3306 # Optional: Default is 5432. Overrides DB_PORT or uses DB_PORT_JOPLIN.
name: joplin # Required: Database name.
user: joplin # Optional: Overrides DB_USERNAME or uses DB_USERNAME_JOPLIN.
password: password # Optional: Overrides DB_PASSWORD or uses DB_PASSWORD_JOPLIN.
path: /s3-path/joplin # Required: Backup path for SSH, FTP, or S3 (e.g., /home/toto/backup/).
- host: mysql4
port: 3306
name: joplin
user: joplin
password: password
path: /s3-path/joplin # For SSH or FTP, define the full path (e.g., /home/toto/backup/)
```
---
@@ -99,5 +88,9 @@ networks:
---
## Key Notes
- **Global Cron Expression**: You can define a global `cronExpression` in the configuration file to schedule backups for all databases. If omitted, backups will run immediately.
- **Database-Specific Paths**: For SSH or FTP storage, ensure the `path` field contains the full remote path (e.g., `/home/toto/backup/`).
- **Environment Variables**: Use the `BACKUP_CONFIG_FILE` environment variable to specify the path to the configuration file.
- **Security**: Avoid hardcoding sensitive information like passwords in the configuration file. Use environment variables or secrets management tools instead.

View File

@@ -18,7 +18,6 @@ To run a one-time backup, bind your local volume to `/backup` in the container a
docker run --rm --network your_network_name \
-v $PWD/backup:/backup/ \
-e "DB_HOST=dbhost" \
-e "DB_PORT=3306" \
-e "DB_USERNAME=username" \
-e "DB_PASSWORD=password" \
jkaninda/mysql-bkup backup -d database_name
@@ -35,19 +34,6 @@ docker run --rm --network your_network_name \
jkaninda/mysql-bkup backup -d database_name
```
### Simple restore using Docker CLI
To restore a database, bind your local volume to `/backup` in the container and run the `restore` command:
```shell
docker run --rm --network your_network_name \
-v $PWD/backup:/backup/ \
-e "DB_HOST=dbhost" \
-e "DB_PORT=3306" \
-e "DB_USERNAME=username" \
-e "DB_PASSWORD=password" \
jkaninda/mysql-bkup restore -d database_name -f backup_file.sql.gz
```
---
## Simple Backup Using Docker Compose

1
go.mod
View File

@@ -26,7 +26,6 @@ require (
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
github.com/jkaninda/go-utils v0.0.0-20250122060806-26119182077a // indirect
github.com/jlaffaye/ftp v0.2.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/pkg/errors v0.9.1 // indirect

4
go.sum
View File

@@ -43,10 +43,6 @@ github.com/jkaninda/encryptor v0.0.0-20241111100652-926393c9437e h1:jtFKZHt/PLGQ
github.com/jkaninda/encryptor v0.0.0-20241111100652-926393c9437e/go.mod h1:Y1EXpPWQ9PNd7y7E6ez3xgnzZc8fuDWXwX/1/dXNCE4=
github.com/jkaninda/go-storage v0.1.3 h1:lEpHVgFLKSvjsi/6tAek96Y07za3vxmsXF2/+jiCMZU=
github.com/jkaninda/go-storage v0.1.3/go.mod h1:zVRnLprBk/9AUz2+za6Y03MgoNYrqKLy3edVtjqMaps=
github.com/jkaninda/go-utils v0.0.0-20250122054739-d330fecee150 h1:AgcKk58P/Z+u4DBE2MTyZ6kCweA89YUpX7TuttHS3oQ=
github.com/jkaninda/go-utils v0.0.0-20250122054739-d330fecee150/go.mod h1:pf0/U6k4JbxlablM2G4eSTZdQ2LFshfAsCK5Q8qNfGo=
github.com/jkaninda/go-utils v0.0.0-20250122060806-26119182077a h1:ZTpKujQGhEF266RWkD2cXnCsafk3R2+sGtfbzCQSs1s=
github.com/jkaninda/go-utils v0.0.0-20250122060806-26119182077a/go.mod h1:pf0/U6k4JbxlablM2G4eSTZdQ2LFshfAsCK5Q8qNfGo=
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=

View File

@@ -27,7 +27,6 @@ package pkg
import (
"fmt"
"github.com/jkaninda/go-storage/pkg/azure"
goutils "github.com/jkaninda/go-utils"
"github.com/jkaninda/mysql-bkup/utils"
"os"
@@ -37,13 +36,10 @@ import (
func azureBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to Azure Blob Storage")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
err := BackupDatabase(db, config.backupFileName, disableCompression)
if err != nil {
recoverMode(err, "Error backing up database")
return
}
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
@@ -91,8 +87,6 @@ func azureBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup size: %s", utils.ConvertBytes(uint64(backupSize)))
utils.Info("Uploading backup archive to Azure Blob storage ... done ")
duration := goutils.FormatDuration(time.Since(startTime), 0)
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
@@ -100,11 +94,12 @@ func azureBackup(db *dbConfig, config *BackupConfig) {
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
Duration: duration,
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete temp
deleteTemp()
utils.Info("Backup successfully completed in %s", duration)
utils.Info("Backup completed successfully")
}
func azureRestore(db *dbConfig, conf *RestoreConfig) {
utils.Info("Restore database from Azure Blob storage")

View File

@@ -29,7 +29,6 @@ import (
"fmt"
"github.com/jkaninda/encryptor"
"github.com/jkaninda/go-storage/pkg/local"
goutils "github.com/jkaninda/go-utils"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/robfig/cron/v3"
"github.com/spf13/cobra"
@@ -72,17 +71,13 @@ func scheduledMode(db *dbConfig, config *BackupConfig) {
// Test backup
utils.Info("Testing backup configurations...")
err := testDatabaseConnection(db)
if err != nil {
utils.Error("Error connecting to database: %s", db.dbName)
utils.Fatal("Error: %s", err)
}
testDatabaseConnection(db)
utils.Info("Testing backup configurations...done")
utils.Info("Creating backup job...")
// Create a new cron instance
c := cron.New()
_, err = c.AddFunc(config.cronExpression, func() {
_, err := c.AddFunc(config.cronExpression, func() {
BackupTask(db, config)
utils.Info("Next backup time is: %v", utils.CronNextTime(config.cronExpression).Format(timeFormat))
@@ -112,7 +107,6 @@ func multiBackupTask(databases []Database, bkConfig *BackupConfig) {
// BackupTask backups database
func BackupTask(db *dbConfig, config *BackupConfig) {
utils.Info("Starting backup task...")
startTime = time.Now()
// Generate file name
backupFileName := fmt.Sprintf("%s_%s.sql.gz", db.dbName, time.Now().Format("20060102_150405"))
if config.disableCompression {
@@ -124,7 +118,7 @@ func BackupTask(db *dbConfig, config *BackupConfig) {
localBackup(db, config)
case "s3", "S3":
s3Backup(db, config)
case "ssh", "SSH", "remote", "sftp":
case "ssh", "SSH", "remote":
sshBackup(db, config)
case "ftp", "FTP":
ftpBackup(db, config)
@@ -151,7 +145,6 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) {
if bkConfig.cronExpression == "" {
multiBackupTask(conf.Databases, bkConfig)
} else {
backupRescueMode = conf.BackupRescueMode
// Check if cronExpression is valid
if utils.IsValidCronExpression(bkConfig.cronExpression) {
utils.Info("Running backup in Scheduled mode")
@@ -162,11 +155,7 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) {
// Test backup
utils.Info("Testing backup configurations...")
for _, db := range conf.Databases {
err = testDatabaseConnection(getDatabase(db))
if err != nil {
recoverMode(err, fmt.Sprintf("Error connecting to database: %s", db.Name))
continue
}
testDatabaseConnection(getDatabase(db))
}
utils.Info("Testing backup configurations...done")
utils.Info("Creating backup job...")
@@ -196,19 +185,16 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) {
}
// BackupDatabase backup database
func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) error {
func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) {
storagePath = os.Getenv("STORAGE_PATH")
utils.Info("Starting database backup...")
err := os.Setenv("MYSQL_PWD", db.dbPassword)
if err != nil {
return fmt.Errorf("failed to set MYSQL_PWD environment variable: %v", err)
}
err = testDatabaseConnection(db)
if err != nil {
return fmt.Errorf("failed to connect to the database: %v", err)
return
}
testDatabaseConnection(db)
// Backup Database database
utils.Info("Backing up database...")
@@ -223,24 +209,24 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
)
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to backup database: %v", err)
utils.Fatal(err.Error())
}
// save output
file, err := os.Create(filepath.Join(tmpPath, backupFileName))
if err != nil {
return fmt.Errorf("failed to create backup file: %v", err)
utils.Fatal(err.Error())
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
return
utils.Fatal(err.Error())
}
}(file)
_, err = file.Write(output)
if err != nil {
return err
utils.Fatal(err.Error())
}
utils.Info("Database has been backed up")
@@ -249,14 +235,14 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
cmd := exec.Command("mysqldump", "-h", db.dbHost, "-P", db.dbPort, "-u", db.dbUserName, db.dbName)
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to backup database: %v", err)
log.Fatal(err)
}
gzipCmd := exec.Command("gzip")
gzipCmd.Stdin = stdout
gzipCmd.Stdout, err = os.Create(filepath.Join(tmpPath, backupFileName))
err = gzipCmd.Start()
if err != nil {
return fmt.Errorf("failed to backup database: %v", err)
return
}
if err := cmd.Run(); err != nil {
log.Fatal(err)
@@ -264,18 +250,14 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
if err := gzipCmd.Wait(); err != nil {
log.Fatal(err)
}
utils.Info("Database has been backed up")
}
utils.Info("Database has been backed up")
return nil
}
func localBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to local storage")
err := BackupDatabase(db, config.backupFileName, disableCompression)
if err != nil {
recoverMode(err, "Error backing up database")
return
}
startTime = time.Now().Format(utils.TimeFormat())
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
@@ -297,8 +279,6 @@ func localBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup name is %s", finalFileName)
utils.Info("Backup size: %s", utils.ConvertBytes(uint64(backupSize)))
utils.Info("Backup saved in %s", filepath.Join(storagePath, finalFileName))
duration := goutils.FormatDuration(time.Since(startTime), 0)
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
@@ -306,7 +286,8 @@ func localBackup(db *dbConfig, config *BackupConfig) {
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(storagePath, finalFileName),
Duration: duration,
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete old backup
if config.prune {
@@ -318,7 +299,7 @@ func localBackup(db *dbConfig, config *BackupConfig) {
}
// Delete temp
deleteTemp()
utils.Info("Backup successfully completed in %s", duration)
utils.Info("Backup completed successfully")
}
func encryptBackup(config *BackupConfig) {
@@ -350,17 +331,3 @@ func encryptBackup(config *BackupConfig) {
}
}
func recoverMode(err error, msg string) {
if err != nil {
if backupRescueMode {
utils.NotifyError(fmt.Sprintf("%s : %v", msg, err))
utils.Error("Error: %s", msg)
utils.Error("Backup rescue mode is enabled")
utils.Error("Backup will continue")
} else {
utils.Error("Error: %s", msg)
utils.Fatal("Error: %v", err)
}
}
}

View File

@@ -30,7 +30,6 @@ import (
"github.com/spf13/cobra"
"os"
"strconv"
"strings"
)
type Database struct {
@@ -42,9 +41,8 @@ type Database struct {
Path string `yaml:"path"`
}
type Config struct {
CronExpression string `yaml:"cronExpression"`
BackupRescueMode bool `yaml:"backupRescueMode"`
Databases []Database `yaml:"databases"`
Databases []Database `yaml:"databases"`
CronExpression string `yaml:"cronExpression"`
}
type dbConfig struct {
@@ -115,7 +113,7 @@ func initDbConfig(cmd *cobra.Command) *dbConfig {
utils.GetEnv(cmd, "dbname", "DB_NAME")
dConf := dbConfig{}
dConf.dbHost = os.Getenv("DB_HOST")
dConf.dbPort = utils.EnvWithDefault("DB_PORT", "3306")
dConf.dbPort = os.Getenv("DB_PORT")
dConf.dbName = os.Getenv("DB_NAME")
dConf.dbUserName = os.Getenv("DB_USERNAME")
dConf.dbPassword = os.Getenv("DB_PASSWORD")
@@ -129,11 +127,6 @@ func initDbConfig(cmd *cobra.Command) *dbConfig {
}
func getDatabase(database Database) *dbConfig {
// Set default values from environment variables if not provided
database.User = getEnvOrDefault(database.User, "DB_USERNAME", database.Name, "")
database.Password = getEnvOrDefault(database.Password, "DB_PASSWORD", database.Name, "")
database.Host = getEnvOrDefault(database.Host, "DB_HOST", database.Name, "")
database.Port = getEnvOrDefault(database.Port, "DB_PORT", database.Name, "3306")
return &dbConfig{
dbHost: database.Host,
dbPort: database.Port,
@@ -143,31 +136,6 @@ func getDatabase(database Database) *dbConfig {
}
}
// Helper function to get environment variable or use a default value
func getEnvOrDefault(currentValue, envKey, suffix, defaultValue string) string {
// Return the current value if it's already set
if currentValue != "" {
return currentValue
}
// Check for suffixed or prefixed environment variables if a suffix is provided
if suffix != "" {
suffixUpper := strings.ToUpper(suffix)
envSuffix := os.Getenv(fmt.Sprintf("%s_%s", envKey, suffixUpper))
if envSuffix != "" {
return envSuffix
}
envPrefix := os.Getenv(fmt.Sprintf("%s_%s", suffixUpper, envKey))
if envPrefix != "" {
return envPrefix
}
}
// Fall back to the default value using a helper function
return utils.EnvWithDefault(envKey, defaultValue)
}
// loadSSHConfig loads the SSH configuration from environment variables
func loadSSHConfig() (*SSHConfig, error) {
utils.GetEnvVariable("SSH_HOST", "SSH_HOST_NAME")

View File

@@ -66,10 +66,10 @@ func deleteTemp() {
}
// TestDatabaseConnection tests the database connection
func testDatabaseConnection(db *dbConfig) error {
func testDatabaseConnection(db *dbConfig) {
err := os.Setenv("MYSQL_PWD", db.dbPassword)
if err != nil {
return fmt.Errorf("failed to set MYSQL_PWD environment variable: %v", err)
return
}
utils.Info("Connecting to %s database ...", db.dbName)
// Set database name for notification error
@@ -81,11 +81,11 @@ func testDatabaseConnection(db *dbConfig) error {
cmd.Stderr = &out
err = cmd.Run()
if err != nil {
return fmt.Errorf("failed to connect to %s database: %v", db.dbName, err)
utils.Fatal("Error testing database connection: %v\nOutput: %s", err, out.String())
}
utils.Info("Successfully connected to %s database", db.dbName)
return nil
}
// checkPubKeyFile checks gpg public key

View File

@@ -51,10 +51,7 @@ func StartMigration(cmd *cobra.Command) {
conf := &RestoreConfig{}
conf.file = backupFileName
// Backup source Database
err := BackupDatabase(dbConf, backupFileName, true)
if err != nil {
utils.Fatal("Error backing up database: %s", err)
}
BackupDatabase(dbConf, backupFileName, true)
// Restore source database into target database
utils.Info("Restoring [%s] database into [%s] database...", dbConf.dbName, targetDbConf.targetDbName)
RestoreDatabase(&newDbConfig, conf)

View File

@@ -28,7 +28,6 @@ import (
"fmt"
"github.com/jkaninda/go-storage/pkg/ftp"
"github.com/jkaninda/go-storage/pkg/ssh"
goutils "github.com/jkaninda/go-utils"
"github.com/jkaninda/mysql-bkup/utils"
"os"
@@ -38,12 +37,9 @@ import (
func sshBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to Remote server")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
err := BackupDatabase(db, config.backupFileName, disableCompression)
if err != nil {
recoverMode(err, "Error backing up database")
return
}
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
@@ -95,8 +91,6 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
}
utils.Info("Uploading backup archive to remote storage ... done ")
duration := goutils.FormatDuration(time.Since(startTime), 0)
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
@@ -104,11 +98,12 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
Duration: duration,
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete temp
deleteTemp()
utils.Info("Backup successfully completed in %s", duration)
utils.Info("Backup completed successfully")
}
func remoteRestore(db *dbConfig, conf *RestoreConfig) {
@@ -158,13 +153,10 @@ func ftpRestore(db *dbConfig, conf *RestoreConfig) {
}
func ftpBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to the remote FTP server")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
err := BackupDatabase(db, config.backupFileName, disableCompression)
if err != nil {
recoverMode(err, "Error backing up database")
return
}
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
@@ -211,7 +203,6 @@ func ftpBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup name is %s", finalFileName)
utils.Info("Backup size: %s", utils.ConvertBytes(uint64(backupSize)))
utils.Info("Uploading backup archive to the remote FTP server ... done ")
duration := goutils.FormatDuration(time.Since(startTime), 0)
// Send notification
utils.NotifySuccess(&utils.NotificationData{
@@ -220,9 +211,10 @@ func ftpBackup(db *dbConfig, config *BackupConfig) {
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
Duration: duration,
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete temp
deleteTemp()
utils.Info("Backup successfully completed in %s", duration)
utils.Info("Backup completed successfully")
}

View File

@@ -118,10 +118,7 @@ func RestoreDatabase(db *dbConfig, conf *RestoreConfig) {
if err != nil {
return
}
err = testDatabaseConnection(db)
if err != nil {
utils.Fatal("Error connecting to the database %v", err)
}
testDatabaseConnection(db)
utils.Info("Restoring database...")
extension := filepath.Ext(filepath.Join(tmpPath, conf.file))

View File

@@ -27,7 +27,6 @@ package pkg
import (
"fmt"
"github.com/jkaninda/go-storage/pkg/s3"
goutils "github.com/jkaninda/go-utils"
"github.com/jkaninda/mysql-bkup/utils"
"os"
@@ -38,12 +37,9 @@ import (
func s3Backup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to s3 storage")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
err := BackupDatabase(db, config.backupFileName, disableCompression)
if err != nil {
recoverMode(err, "Error backing up database")
return
}
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
@@ -95,7 +91,6 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
}
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
utils.Info("Uploading backup archive to remote storage S3 ... done ")
duration := goutils.FormatDuration(time.Since(startTime), 0)
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
@@ -103,11 +98,12 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
Duration: duration,
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete temp
deleteTemp()
utils.Info("Backup successfully completed in %s", duration)
utils.Info("Backup completed successfully")
}
func s3Restore(db *dbConfig, conf *RestoreConfig) {

View File

@@ -24,8 +24,6 @@ SOFTWARE.
package pkg
import "time"
const tmpPath = "/tmp/backup"
const gpgHome = "/config/gnupg"
const gpgExtension = "gpg"
@@ -41,8 +39,7 @@ var (
encryption = false
usingKey = false
backupSize int64 = 0
startTime = time.Now()
backupRescueMode = false
startTime string
)
// dbHVars Required environment variables for database
@@ -62,6 +59,13 @@ var tdbRVars = []string{
var dbConf *dbConfig
var targetDbConf *targetDbConfig
// sshVars Required environment variables for SSH remote server storage
var sshVars = []string{
"SSH_USER",
"SSH_HOST_NAME",
"SSH_PORT",
"REMOTE_PATH",
}
var ftpVars = []string{
"FTP_HOST_NAME",
"FTP_USER",

View File

@@ -60,10 +60,10 @@
<p>We recommend investigating the issue as soon as possible to prevent potential data loss or service disruptions.</p>
<p>For more information, visit the <a href="https://jkaninda.github.io/mysql-bkup">mysql-bkup documentation</a>.</p>
<p>For more information, visit the <a href="https://jkaninda.github.io/pg-bkup">pg-bkup documentation</a>.</p>
<footer>
&copy; 2024 <a href="https://github.com/jkaninda/mysql-bkup">mysql-bkup</a> | Automated Backup System
&copy; 2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a> | Automated Backup System
</footer>
</body>
</html>

View File

@@ -52,7 +52,8 @@
<h3>Backup Details:</h3>
<ul>
<li><strong>Database Name:</strong> {{.Database}}</li>
<li><strong>Backup Duration:</strong> {{.Duration}}</li>
<li><strong>Backup Start Time:</strong> {{.StartTime}}</li>
<li><strong>Backup End Time:</strong> {{.EndTime}}</li>
<li><strong>Backup Storage:</strong> {{.Storage}}</li>
<li><strong>Backup Location:</strong> {{.BackupLocation}}</li>
<li><strong>Backup Size:</strong> {{.BackupSize}}</li>

View File

@@ -6,7 +6,8 @@ Please find the details below:
Backup Details:
- Database Name: {{.Database}}
- Backup Duration: {{.Duration}}
- Backup Start Time: {{.StartTime}}
- Backup EndTime: {{.EndTime}}
- Backup Storage: {{.Storage}}
- Backup Location: {{.BackupLocation}}
- Backup Size: {{.BackupSize}}

View File

@@ -39,7 +39,8 @@ type NotificationData struct {
File string
BackupSize string
Database string
Duration string
StartTime string
EndTime string
Storage string
BackupLocation string
BackupReference string
@@ -83,13 +84,3 @@ func backupReference() string {
const templatePath = "/config/templates"
var DatabaseName = ""
var vars = []string{
"TG_TOKEN",
"TG_CHAT_ID",
}
var mailVars = []string{
"MAIL_HOST",
"MAIL_PORT",
"MAIL_FROM",
"MAIL_TO",
}

View File

@@ -107,6 +107,19 @@ func sendMessage(msg string) error {
}
func NotifySuccess(notificationData *NotificationData) {
notificationData.BackupReference = backupReference()
var vars = []string{
"TG_TOKEN",
"TG_CHAT_ID",
}
var mailVars = []string{
"MAIL_HOST",
"MAIL_PORT",
"MAIL_USERNAME",
"MAIL_PASSWORD",
"MAIL_FROM",
"MAIL_TO",
}
// Email notification
err := CheckEnvVars(mailVars)
if err == nil {
@@ -134,6 +147,18 @@ func NotifySuccess(notificationData *NotificationData) {
}
}
func NotifyError(error string) {
var vars = []string{
"TG_TOKEN",
"TG_CHAT_ID",
}
var mailVars = []string{
"MAIL_HOST",
"MAIL_PORT",
"MAIL_USERNAME",
"MAIL_PASSWORD",
"MAIL_FROM",
"MAIL_TO",
}
// Email notification
err := CheckEnvVars(mailVars)