Compare commits

...

20 Commits

Author SHA1 Message Date
dabba2050a Merge pull request #110 from jkaninda/refactor
chore: remove os.kill.signal
2024-10-05 10:42:55 +02:00
Jonas Kaninda
47e1ac407b chore: remove os.kill.signal 2024-10-05 10:41:46 +02:00
28f6ed3a82 Merge pull request #109 from jkaninda/refactor
fix: logging time
2024-10-05 10:40:11 +02:00
Jonas Kaninda
504926c7cd fix: logging time 2024-10-05 10:39:49 +02:00
737f473f92 Merge pull request #108 from jkaninda/refactor
Refactor
2024-10-03 18:19:12 +02:00
Jonas Kaninda
300d2a8205 chore: remove testDatabaseConnection function for scheduled mode 2024-10-03 18:18:47 +02:00
Jonas Kaninda
a4ad0502cf chore: add storage type alt for smallcase and uppercase 2024-10-03 18:17:48 +02:00
f344867edf Merge pull request #107 from jkaninda/refactor
docs: update configuration reference
2024-10-02 04:26:05 +02:00
Jonas Kaninda
d774584f64 docs: update configuration reference 2024-10-02 04:25:35 +02:00
96927cd57e Merge pull request #106 from jkaninda/refactor
Refactor
2024-10-02 04:13:20 +02:00
Jonas Kaninda
ceacfa1d9d docs: update ssh and ftp deployment example 2024-10-02 04:09:42 +02:00
Jonas Kaninda
9380a18b45 refactor: remove old arguments, refactor aws and ssh configuration 2024-10-02 04:07:14 +02:00
Jonas Kaninda
d186071df9 Merge pull request #105 from jkaninda/refactor
chore: update app version
2024-09-30 17:49:21 +02:00
Jonas Kaninda
71429b0e1a chore: update app version 2024-09-30 17:48:56 +02:00
Jonas Kaninda
0bed86ded4 Merge pull request #104 from jkaninda/refactor
chore: add Time Zone
2024-09-30 17:45:38 +02:00
Jonas Kaninda
e891801125 chore: add Time Zone 2024-09-30 17:44:45 +02:00
Jonas Kaninda
01cf8a3392 Merge pull request #103 from jkaninda/refactor
fix: MySQL 8.x -Plugin caching_sha2_password could not be loaded
2024-09-30 07:58:39 +02:00
Jonas Kaninda
efea81833a fix: MySQL 8.x -Plugin caching_sha2_password could not be loaded 2024-09-30 07:57:42 +02:00
Jonas Kaninda
1cbf65d686 Merge pull request #102 from jkaninda/refactor
fix: backup date and time
2024-09-30 02:03:08 +02:00
Jonas Kaninda
73d19913f8 fix: backup date and time 2024-09-30 02:02:37 +02:00
16 changed files with 206 additions and 158 deletions

View File

@@ -27,6 +27,8 @@ jobs:
push: true push: true
file: "./docker/Dockerfile" file: "./docker/Dockerfile"
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
build-args: |
appVersion=develop-${{ github.sha }}
tags: | tags: |
"${{env.BUILDKIT_IMAGE}}:develop-${{ github.sha }}" "${{vars.BUILDKIT_IMAGE}}:develop-${{ github.sha }}"

View File

@@ -41,9 +41,11 @@ jobs:
push: true push: true
file: "./docker/Dockerfile" file: "./docker/Dockerfile"
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
build-args: |
appVersion=${{ env.TAG_NAME }}
tags: | tags: |
"${{env.BUILDKIT_IMAGE}}:${{ env.TAG_NAME }}" "${{vars.BUILDKIT_IMAGE}}:${{ env.TAG_NAME }}"
"${{env.BUILDKIT_IMAGE}}:latest" "${{vars.BUILDKIT_IMAGE}}:latest"
"ghcr.io/${{env.BUILDKIT_IMAGE}}:${{ env.TAG_NAME }}" "ghcr.io/${{vars.BUILDKIT_IMAGE}}:${{ env.TAG_NAME }}"
"ghcr.io/${{env.BUILDKIT_IMAGE}}:latest" "ghcr.io/${{vars.BUILDKIT_IMAGE}}:latest"

View File

@@ -80,6 +80,7 @@ services:
- DB_NAME=foo - DB_NAME=foo
- DB_USERNAME=bar - DB_USERNAME=bar
- DB_PASSWORD=password - DB_PASSWORD=password
- TZ=Europe/Paris
# mysql-bkup container must be connected to the same network with your database # mysql-bkup container must be connected to the same network with your database
networks: networks:
- web - web

View File

@@ -29,8 +29,6 @@ func init() {
//Backup //Backup
BackupCmd.PersistentFlags().StringP("storage", "s", "local", "Storage. local or s3") BackupCmd.PersistentFlags().StringP("storage", "s", "local", "Storage. local or s3")
BackupCmd.PersistentFlags().StringP("path", "P", "", "AWS S3 path without file name. eg: /custom_path or ssh remote path `/home/foo/backup`") BackupCmd.PersistentFlags().StringP("path", "P", "", "AWS S3 path without file name. eg: /custom_path or ssh remote path `/home/foo/backup`")
BackupCmd.PersistentFlags().StringP("mode", "m", "default", "Execution mode. | Deprecated")
BackupCmd.PersistentFlags().StringP("period", "", "", "Schedule period time | Deprecated")
BackupCmd.PersistentFlags().StringP("cron-expression", "", "", "Backup cron expression") BackupCmd.PersistentFlags().StringP("cron-expression", "", "", "Backup cron expression")
BackupCmd.PersistentFlags().BoolP("prune", "", false, "Delete old backup, default disabled") BackupCmd.PersistentFlags().BoolP("prune", "", false, "Delete old backup, default disabled")
BackupCmd.PersistentFlags().IntP("keep-last", "", 7, "Delete files created more than specified days ago, default 7 days") BackupCmd.PersistentFlags().IntP("keep-last", "", 7, "Delete files created more than specified days ago, default 7 days")

View File

@@ -23,14 +23,15 @@ ENV AWS_SECRET_KEY=""
ENV AWS_S3_PATH="" ENV AWS_S3_PATH=""
ENV AWS_REGION="us-west-2" ENV AWS_REGION="us-west-2"
ENV AWS_DISABLE_SSL="false" ENV AWS_DISABLE_SSL="false"
ENV AWS_FORCE_PATH_STYLE="true"
ENV GPG_PASSPHRASE="" ENV GPG_PASSPHRASE=""
ENV SSH_USER="" ENV SSH_USER=""
ENV SSH_PASSWORD="" ENV SSH_PASSWORD=""
ENV SSH_HOST_NAME="" ENV SSH_HOST=""
ENV SSH_IDENTIFY_FILE="" ENV SSH_IDENTIFY_FILE=""
ENV SSH_PORT=22 ENV SSH_PORT=22
ENV REMOTE_PATH="" ENV REMOTE_PATH=""
ENV FTP_HOST_NAME="" ENV FTP_HOST=""
ENV FTP_PORT=21 ENV FTP_PORT=21
ENV FTP_USER="" ENV FTP_USER=""
ENV FTP_PASSWORD="" ENV FTP_PASSWORD=""
@@ -39,16 +40,19 @@ ENV TARGET_DB_PORT=3306
ENV TARGET_DB_NAME="" ENV TARGET_DB_NAME=""
ENV TARGET_DB_USERNAME="" ENV TARGET_DB_USERNAME=""
ENV TARGET_DB_PASSWORD="" ENV TARGET_DB_PASSWORD=""
ENV VERSION="v1.2.9"
ENV BACKUP_CRON_EXPRESSION="" ENV BACKUP_CRON_EXPRESSION=""
ENV TG_TOKEN="" ENV TG_TOKEN=""
ENV TG_CHAT_ID="" ENV TG_CHAT_ID=""
ENV TZ=UTC
ARG WORKDIR="/config" ARG WORKDIR="/config"
ARG BACKUPDIR="/backup" ARG BACKUPDIR="/backup"
ARG BACKUP_TMP_DIR="/tmp/backup" ARG BACKUP_TMP_DIR="/tmp/backup"
ARG appVersion="v1.2.12"
ENV VERSION=${appVersion}
LABEL author="Jonas Kaninda" LABEL author="Jonas Kaninda"
LABEL version=${appVersion}
RUN apk --update add mysql-client gnupg RUN apk --update add --no-cache mysql-client mariadb-connector-c gnupg tzdata
RUN mkdir $WORKDIR RUN mkdir $WORKDIR
RUN mkdir $BACKUPDIR RUN mkdir $BACKUPDIR
RUN mkdir -p $BACKUP_TMP_DIR RUN mkdir -p $BACKUP_TMP_DIR

View File

@@ -11,7 +11,7 @@ As described for SSH backup section, to change the storage of your backup and us
You need to add the full remote path by adding `--path /home/jkaninda/backups` flag or using `REMOTE_PATH` environment variable. You need to add the full remote path by adding `--path /home/jkaninda/backups` flag or using `REMOTE_PATH` environment variable.
{: .note } {: .note }
These environment variables are required for SSH backup `FTP_HOST_NAME`, `FTP_USER`, `REMOTE_PATH`, `FTP_PORT` or `FTP_PASSWORD`. These environment variables are required for SSH backup `FTP_HOST`, `FTP_USER`, `REMOTE_PATH`, `FTP_PORT` or `FTP_PASSWORD`.
```yml ```yml
services: services:
@@ -30,7 +30,7 @@ services:
- DB_USERNAME=username - DB_USERNAME=username
- DB_PASSWORD=password - DB_PASSWORD=password
## FTP config ## FTP config
- FTP_HOST_NAME="hostname" - FTP_HOST="hostname"
- FTP_PORT=21 - FTP_PORT=21
- FTP_USER=user - FTP_USER=user
- FTP_PASSWORD=password - FTP_PASSWORD=password

View File

@@ -11,7 +11,7 @@ As described for s3 backup section, to change the storage of your backup and use
You need to add the full remote path by adding `--path /home/jkaninda/backups` flag or using `REMOTE_PATH` environment variable. You need to add the full remote path by adding `--path /home/jkaninda/backups` flag or using `REMOTE_PATH` environment variable.
{: .note } {: .note }
These environment variables are required for SSH backup `SSH_HOST_NAME`, `SSH_USER`, `SSH_REMOTE_PATH`, `SSH_IDENTIFY_FILE`, `SSH_PORT` or `SSH_PASSWORD` if you dont use a private key to access to your server. These environment variables are required for SSH backup `SSH_HOST`, `SSH_USER`, `SSH_REMOTE_PATH`, `SSH_IDENTIFY_FILE`, `SSH_PORT` or `SSH_PASSWORD` if you dont use a private key to access to your server.
Accessing the remote server using password is not recommended, use private key instead. Accessing the remote server using password is not recommended, use private key instead.
```yml ```yml
@@ -33,7 +33,7 @@ services:
- DB_USERNAME=username - DB_USERNAME=username
- DB_PASSWORD=password - DB_PASSWORD=password
## SSH config ## SSH config
- SSH_HOST_NAME="hostname" - SSH_HOST="hostname"
- SSH_PORT=22 - SSH_PORT=22
- SSH_USER=user - SSH_USER=user
- REMOTE_PATH=/home/jkaninda/backups - REMOTE_PATH=/home/jkaninda/backups
@@ -73,7 +73,7 @@ services:
- DB_USERNAME=username - DB_USERNAME=username
- DB_PASSWORD=password - DB_PASSWORD=password
## SSH config ## SSH config
- SSH_HOST_NAME="hostname" - SSH_HOST="hostname"
- SSH_PORT=22 - SSH_PORT=22
- SSH_USER=user - SSH_USER=user
- REMOTE_PATH=/home/jkaninda/backups - REMOTE_PATH=/home/jkaninda/backups
@@ -125,7 +125,7 @@ spec:
# Please use secret! # Please use secret!
- name: DB_PASSWORD - name: DB_PASSWORD
value: "" value: ""
- name: SSH_HOST_NAME - name: SSH_HOST
value: "" value: ""
- name: SSH_PORT - name: SSH_PORT
value: "22" value: "22"

View File

@@ -73,6 +73,7 @@ services:
- DB_NAME=foo - DB_NAME=foo
- DB_USERNAME=bar - DB_USERNAME=bar
- DB_PASSWORD=password - DB_PASSWORD=password
- TZ=Europe/Paris
# mysql-bkup container must be connected to the same network with your database # mysql-bkup container must be connected to the same network with your database
networks: networks:
- web - web

View File

@@ -47,16 +47,17 @@ Backup, restore and migrate targets, schedule and retention are configured using
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name | | AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
| AWS_REGION | Optional, required for S3 storage | AWS Region | | AWS_REGION | Optional, required for S3 storage | AWS Region |
| AWS_DISABLE_SSL | Optional, required for S3 storage | Disable SSL | | AWS_DISABLE_SSL | Optional, required for S3 storage | Disable SSL |
| AWS_FORCE_PATH_STYLE | Optional, required for S3 storage | Force path style |
| FILE_NAME | Optional if it was provided from the --file flag | Database file to restore (extensions: .sql, .sql.gz) | | FILE_NAME | Optional if it was provided from the --file flag | Database file to restore (extensions: .sql, .sql.gz) |
| GPG_PASSPHRASE | Optional, required to encrypt and restore backup | GPG passphrase | | GPG_PASSPHRASE | Optional, required to encrypt and restore backup | GPG passphrase |
| BACKUP_CRON_EXPRESSION | Optional if it was provided from the `--cron-expression` flag | Backup cron expression for docker in scheduled mode | | BACKUP_CRON_EXPRESSION | Optional if it was provided from the `--cron-expression` flag | Backup cron expression for docker in scheduled mode |
| SSH_HOST_NAME | Optional, required for SSH storage | ssh remote hostname or ip | | SSH_HOST | Optional, required for SSH storage | ssh remote hostname or ip |
| SSH_USER | Optional, required for SSH storage | ssh remote user | | SSH_USER | Optional, required for SSH storage | ssh remote user |
| SSH_PASSWORD | Optional, required for SSH storage | ssh remote user's password | | SSH_PASSWORD | Optional, required for SSH storage | ssh remote user's password |
| SSH_IDENTIFY_FILE | Optional, required for SSH storage | ssh remote user's private key | | SSH_IDENTIFY_FILE | Optional, required for SSH storage | ssh remote user's private key |
| SSH_PORT | Optional, required for SSH storage | ssh remote server port | | SSH_PORT | Optional, required for SSH storage | ssh remote server port |
| REMOTE_PATH | Optional, required for SSH or FTP storage | remote path (/home/toto/backup) | | REMOTE_PATH | Optional, required for SSH or FTP storage | remote path (/home/toto/backup) |
| FTP_HOST_NAME | Optional, required for FTP storage | FTP host name | | FTP_HOST | Optional, required for FTP storage | FTP host name |
| FTP_PORT | Optional, required for FTP storage | FTP server port number | | FTP_PORT | Optional, required for FTP storage | FTP server port number |
| FTP_USER | Optional, required for FTP storage | FTP user | | FTP_USER | Optional, required for FTP storage | FTP user |
| FTP_PASSWORD | Optional, required for FTP storage | FTP user password | | FTP_PASSWORD | Optional, required for FTP storage | FTP user password |
@@ -67,6 +68,7 @@ Backup, restore and migrate targets, schedule and retention are configured using
| TARGET_DB_PASSWORD | Optional, required for database migration | Target database password | | TARGET_DB_PASSWORD | Optional, required for database migration | Target database password |
| TG_TOKEN | Optional, required for Telegram notification | Telegram token (`BOT-ID:BOT-TOKEN`) | | TG_TOKEN | Optional, required for Telegram notification | Telegram token (`BOT-ID:BOT-TOKEN`) |
| TG_CHAT_ID | Optional, required for Telegram notification | Telegram Chat ID | | TG_CHAT_ID | Optional, required for Telegram notification | Telegram Chat ID |
| TZ | Optional | Time Zone |
--- ---
## Run in Scheduled mode ## Run in Scheduled mode

View File

@@ -42,8 +42,6 @@ func scheduledMode(db *dbConfig, config *BackupConfig) {
utils.Info("Backup cron expression: %s", config.cronExpression) utils.Info("Backup cron expression: %s", config.cronExpression)
utils.Info("Storage type %s ", config.storage) utils.Info("Storage type %s ", config.storage)
//Test database connexion
testDatabaseConnection(db)
//Test backup //Test backup
utils.Info("Testing backup configurations...") utils.Info("Testing backup configurations...")
BackupTask(db, config) BackupTask(db, config)
@@ -67,19 +65,19 @@ func scheduledMode(db *dbConfig, config *BackupConfig) {
} }
func BackupTask(db *dbConfig, config *BackupConfig) { func BackupTask(db *dbConfig, config *BackupConfig) {
//Generate backup file name //Generate backup file name
backupFileName := fmt.Sprintf("%s_%s.sql.gz", db.dbName, time.Now().Format("20240102_150405")) backupFileName := fmt.Sprintf("%s_%s.sql.gz", db.dbName, time.Now().Format("20060102_150405"))
if config.disableCompression { if config.disableCompression {
backupFileName = fmt.Sprintf("%s_%s.sql", db.dbName, time.Now().Format("20240102_150405")) backupFileName = fmt.Sprintf("%s_%s.sql", db.dbName, time.Now().Format("20060102_150405"))
} }
config.backupFileName = backupFileName config.backupFileName = backupFileName
switch config.storage { switch config.storage {
case "local": case "local":
localBackup(db, config) localBackup(db, config)
case "s3": case "s3", "S3":
s3Backup(db, config) s3Backup(db, config)
case "ssh", "remote": case "ssh", "SSH", "remote":
sshBackup(db, config) sshBackup(db, config)
case "ftp": case "ftp", "FTP":
ftpBackup(db, config) ftpBackup(db, config)
default: default:
localBackup(db, config) localBackup(db, config)

View File

@@ -7,9 +7,11 @@
package pkg package pkg
import ( import (
"fmt"
"github.com/jkaninda/mysql-bkup/utils" "github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
"strconv"
) )
type Config struct { type Config struct {
@@ -29,7 +31,10 @@ type targetDbConfig struct {
targetDbPassword string targetDbPassword string
targetDbName string targetDbName string
} }
type TgConfig struct {
Token string
ChatId string
}
type BackupConfig struct { type BackupConfig struct {
backupFileName string backupFileName string
backupRetention int backupRetention int
@@ -41,14 +46,6 @@ type BackupConfig struct {
storage string storage string
cronExpression string cronExpression string
} }
type RestoreConfig struct {
s3Path string
remotePath string
storage string
file string
bucket string
gpqPassphrase string
}
type FTPConfig struct { type FTPConfig struct {
host string host string
user string user string
@@ -57,6 +54,24 @@ type FTPConfig struct {
remotePath string remotePath string
} }
// SSHConfig holds the SSH connection details
type SSHConfig struct {
user string
password string
hostName string
port string
identifyFile string
}
type AWSConfig struct {
endpoint string
bucket string
accessKey string
secretKey string
region string
disableSsl bool
forcePathStyle bool
}
func initDbConfig(cmd *cobra.Command) *dbConfig { func initDbConfig(cmd *cobra.Command) *dbConfig {
//Set env //Set env
utils.GetEnv(cmd, "dbname", "DB_NAME") utils.GetEnv(cmd, "dbname", "DB_NAME")
@@ -74,14 +89,71 @@ func initDbConfig(cmd *cobra.Command) *dbConfig {
} }
return &dConf return &dConf
} }
// loadSSHConfig loads the SSH configuration from environment variables
func loadSSHConfig() (*SSHConfig, error) {
utils.GetEnvVariable("SSH_HOST", "SSH_HOST_NAME")
sshVars := []string{"SSH_USER", "SSH_HOST", "SSH_PORT", "REMOTE_PATH"}
err := utils.CheckEnvVars(sshVars)
if err != nil {
return nil, fmt.Errorf("error missing environment variables: %w", err)
}
return &SSHConfig{
user: os.Getenv("SSH_USER"),
password: os.Getenv("SSH_PASSWORD"),
hostName: os.Getenv("SSH_HOST"),
port: os.Getenv("SSH_PORT"),
identifyFile: os.Getenv("SSH_IDENTIFY_FILE"),
}, nil
}
func initFtpConfig() *FTPConfig {
//Initialize data configs
fConfig := FTPConfig{}
fConfig.host = utils.GetEnvVariable("FTP_HOST", "FTP_HOST_NAME")
fConfig.user = os.Getenv("FTP_USER")
fConfig.password = os.Getenv("FTP_PASSWORD")
fConfig.port = os.Getenv("FTP_PORT")
fConfig.remotePath = os.Getenv("REMOTE_PATH")
err := utils.CheckEnvVars(ftpVars)
if err != nil {
utils.Error("Please make sure all required environment variables for FTP are set")
utils.Fatal("Error missing environment variables: %s", err)
}
return &fConfig
}
func initAWSConfig() *AWSConfig {
//Initialize AWS configs
aConfig := AWSConfig{}
aConfig.endpoint = utils.GetEnvVariable("AWS_S3_ENDPOINT", "S3_ENDPOINT")
aConfig.accessKey = utils.GetEnvVariable("AWS_ACCESS_KEY", "ACCESS_KEY")
aConfig.secretKey = utils.GetEnvVariable("AWS_SECRET_KEY", "SECRET_KEY")
aConfig.bucket = utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
aConfig.region = os.Getenv("AWS_REGION")
disableSsl, err := strconv.ParseBool(os.Getenv("AWS_DISABLE_SSL"))
if err != nil {
utils.Fatal("Unable to parse AWS_DISABLE_SSL env var: %s", err)
}
forcePathStyle, err := strconv.ParseBool(os.Getenv("AWS_FORCE_PATH_STYLE"))
if err != nil {
utils.Fatal("Unable to parse AWS_FORCE_PATH_STYLE env var: %s", err)
}
aConfig.disableSsl = disableSsl
aConfig.forcePathStyle = forcePathStyle
err = utils.CheckEnvVars(awsVars)
if err != nil {
utils.Error("Please make sure all required environment variables for AWS S3 are set")
utils.Fatal("Error checking environment variables: %s", err)
}
return &aConfig
}
func initBackupConfig(cmd *cobra.Command) *BackupConfig { func initBackupConfig(cmd *cobra.Command) *BackupConfig {
utils.SetEnv("STORAGE_PATH", storagePath) utils.SetEnv("STORAGE_PATH", storagePath)
utils.GetEnv(cmd, "cron-expression", "BACKUP_CRON_EXPRESSION") utils.GetEnv(cmd, "cron-expression", "BACKUP_CRON_EXPRESSION")
utils.GetEnv(cmd, "period", "BACKUP_CRON_EXPRESSION") utils.GetEnv(cmd, "period", "BACKUP_CRON_EXPRESSION")
utils.GetEnv(cmd, "path", "REMOTE_PATH") utils.GetEnv(cmd, "path", "REMOTE_PATH")
remotePath := utils.GetEnvVariable("REMOTE_PATH", "SSH_REMOTE_PATH")
//Get flag value and set env //Get flag value and set env
remotePath := utils.GetEnvVariable("REMOTE_PATH", "SSH_REMOTE_PATH")
storage = utils.GetEnv(cmd, "storage", "STORAGE") storage = utils.GetEnv(cmd, "storage", "STORAGE")
backupRetention, _ := cmd.Flags().GetInt("keep-last") backupRetention, _ := cmd.Flags().GetInt("keep-last")
prune, _ := cmd.Flags().GetBool("prune") prune, _ := cmd.Flags().GetBool("prune")
@@ -94,6 +166,7 @@ func initBackupConfig(cmd *cobra.Command) *BackupConfig {
if passphrase != "" { if passphrase != "" {
encryption = true encryption = true
} }
//Initialize backup configs //Initialize backup configs
config := BackupConfig{} config := BackupConfig{}
config.backupRetention = backupRetention config.backupRetention = backupRetention
@@ -106,16 +179,25 @@ func initBackupConfig(cmd *cobra.Command) *BackupConfig {
config.cronExpression = cronExpression config.cronExpression = cronExpression
return &config return &config
} }
type RestoreConfig struct {
s3Path string
remotePath string
storage string
file string
bucket string
gpqPassphrase string
}
func initRestoreConfig(cmd *cobra.Command) *RestoreConfig { func initRestoreConfig(cmd *cobra.Command) *RestoreConfig {
utils.SetEnv("STORAGE_PATH", storagePath) utils.SetEnv("STORAGE_PATH", storagePath)
utils.GetEnv(cmd, "path", "REMOTE_PATH") utils.GetEnv(cmd, "path", "REMOTE_PATH")
remotePath := utils.GetEnvVariable("REMOTE_PATH", "SSH_REMOTE_PATH")
//Get flag value and set env //Get flag value and set env
s3Path := utils.GetEnv(cmd, "path", "AWS_S3_PATH") s3Path := utils.GetEnv(cmd, "path", "AWS_S3_PATH")
remotePath := utils.GetEnvVariable("REMOTE_PATH", "SSH_REMOTE_PATH")
storage = utils.GetEnv(cmd, "storage", "STORAGE") storage = utils.GetEnv(cmd, "storage", "STORAGE")
file = utils.GetEnv(cmd, "file", "FILE_NAME") file = utils.GetEnv(cmd, "file", "FILE_NAME")
_, _ = cmd.Flags().GetString("mode")
bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME") bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
gpqPassphrase := os.Getenv("GPG_PASSPHRASE") gpqPassphrase := os.Getenv("GPG_PASSPHRASE")
//Initialize restore configs //Initialize restore configs
@@ -144,18 +226,3 @@ func initTargetDbConfig() *targetDbConfig {
} }
return &tdbConfig return &tdbConfig
} }
func initFtpConfig() *FTPConfig {
//Initialize backup configs
fConfig := FTPConfig{}
fConfig.host = os.Getenv("FTP_HOST_NAME")
fConfig.user = os.Getenv("FTP_USER")
fConfig.password = os.Getenv("FTP_PASSWORD")
fConfig.port = os.Getenv("FTP_PORT")
fConfig.remotePath = os.Getenv("REMOTE_PATH")
err := utils.CheckEnvVars(ftpVars)
if err != nil {
utils.Error("Please make sure all required environment variables for FTP are set")
utils.Fatal("Error checking environment variables: %s", err)
}
return &fConfig
}

View File

@@ -21,15 +21,15 @@ func StartRestore(cmd *cobra.Command) {
restoreConf := initRestoreConfig(cmd) restoreConf := initRestoreConfig(cmd)
switch restoreConf.storage { switch restoreConf.storage {
case "s3":
restoreFromS3(dbConf, restoreConf.file, restoreConf.bucket, restoreConf.s3Path)
case "local": case "local":
utils.Info("Restore database from local") utils.Info("Restore database from local")
copyToTmp(storagePath, restoreConf.file) copyToTmp(storagePath, restoreConf.file)
RestoreDatabase(dbConf, restoreConf.file) RestoreDatabase(dbConf, restoreConf.file)
case "ssh": case "s3", "S3":
restoreFromS3(dbConf, restoreConf.file, restoreConf.bucket, restoreConf.s3Path)
case "ssh", "SSH":
restoreFromRemote(dbConf, restoreConf.file, restoreConf.remotePath) restoreFromRemote(dbConf, restoreConf.file, restoreConf.remotePath)
case "ftp": case "ftp", "FTP":
restoreFromFTP(dbConf, restoreConf.file, restoreConf.remotePath) restoreFromFTP(dbConf, restoreConf.file, restoreConf.remotePath)
default: default:
utils.Info("Restore database from local") utils.Info("Restore database from local")

View File

@@ -1,4 +1,4 @@
// Package utils / // Package pkg
/***** /*****
@author Jonas Kaninda @author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT> @license MIT License <https://opensource.org/licenses/MIT>
@@ -8,56 +8,28 @@ package pkg
import ( import (
"bytes" "bytes"
"fmt"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/jkaninda/mysql-bkup/utils" "github.com/jkaninda/mysql-bkup/utils"
"log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"time" "time"
) )
// CreateSession creates a new AWS session // CreateSession creates a new AWS session
func CreateSession() (*session.Session, error) { func CreateSession() (*session.Session, error) {
// AwsVars Required environment variables for AWS S3 storage awsConfig := initAWSConfig()
var awsVars = []string{ // Configure to use MinIO Server
"AWS_S3_ENDPOINT",
"AWS_S3_BUCKET_NAME",
"AWS_ACCESS_KEY",
"AWS_SECRET_KEY",
"AWS_REGION",
"AWS_REGION",
"AWS_REGION",
}
endPoint := utils.GetEnvVariable("AWS_S3_ENDPOINT", "S3_ENDPOINT")
accessKey := utils.GetEnvVariable("AWS_ACCESS_KEY", "ACCESS_KEY")
secretKey := utils.GetEnvVariable("AWS_SECRET_KEY", "SECRET_KEY")
_ = utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
region := os.Getenv("AWS_REGION")
awsDisableSsl, err := strconv.ParseBool(os.Getenv("AWS_DISABLE_SSL"))
if err != nil {
utils.Fatal("Unable to parse AWS_DISABLE_SSL env var: %s", err)
}
err = utils.CheckEnvVars(awsVars)
if err != nil {
utils.Fatal("Error checking environment variables\n: %s", err)
}
// S3 Config
s3Config := &aws.Config{ s3Config := &aws.Config{
Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), Credentials: credentials.NewStaticCredentials(awsConfig.accessKey, awsConfig.secretKey, ""),
Endpoint: aws.String(endPoint), Endpoint: aws.String(awsConfig.endpoint),
Region: aws.String(region), Region: aws.String(awsConfig.region),
DisableSSL: aws.Bool(awsDisableSsl), DisableSSL: aws.Bool(awsConfig.disableSsl),
S3ForcePathStyle: aws.Bool(true), S3ForcePathStyle: aws.Bool(awsConfig.forcePathStyle),
} }
return session.NewSession(s3Config) return session.NewSession(s3Config)
@@ -109,10 +81,10 @@ func DownloadFile(destinationPath, key, bucket, prefix string) error {
if err != nil { if err != nil {
return err return err
} }
utils.Info("Download backup from S3 storage...") utils.Info("Download data from S3 storage...")
file, err := os.Create(filepath.Join(destinationPath, key)) file, err := os.Create(filepath.Join(destinationPath, key))
if err != nil { if err != nil {
fmt.Println("Failed to create file", err) utils.Error("Failed to create file", err)
return err return err
} }
defer file.Close() defer file.Close()
@@ -159,18 +131,18 @@ func DeleteOldBackup(bucket, prefix string, retention int) error {
Key: object.Key, Key: object.Key,
}) })
if err != nil { if err != nil {
log.Printf("Failed to delete object %s: %v", *object.Key, err) utils.Info("Failed to delete object %s: %v", *object.Key, err)
} else { } else {
fmt.Printf("Deleted object %s\n", *object.Key) utils.Info("Deleted object %s\n", *object.Key)
} }
} }
} }
return !lastPage return !lastPage
}) })
if err != nil { if err != nil {
log.Fatalf("Failed to list objects: %v", err) utils.Error("Failed to list objects: %v", err)
} }
fmt.Println("Finished deleting old files.") utils.Info("Finished deleting old files.")
return nil return nil
} }

View File

@@ -18,83 +18,73 @@ import (
"path/filepath" "path/filepath"
) )
func CopyToRemote(fileName, remotePath string) error { // createSSHClientConfig sets up the SSH client configuration based on the provided SSHConfig
sshUser := os.Getenv("SSH_USER") func createSSHClientConfig(sshConfig *SSHConfig) (ssh.ClientConfig, error) {
sshPassword := os.Getenv("SSH_PASSWORD") if sshConfig.identifyFile != "" && utils.FileExists(sshConfig.identifyFile) {
sshHostName := os.Getenv("SSH_HOST_NAME") return auth.PrivateKey(sshConfig.user, sshConfig.identifyFile, ssh.InsecureIgnoreHostKey())
sshPort := os.Getenv("SSH_PORT")
sshIdentifyFile := os.Getenv("SSH_IDENTIFY_FILE")
err := utils.CheckEnvVars(sshHVars)
if err != nil {
utils.Error("Error checking environment variables: %s", err)
os.Exit(1)
}
clientConfig, _ := auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey())
if sshIdentifyFile != "" && utils.FileExists(sshIdentifyFile) {
clientConfig, _ = auth.PrivateKey(sshUser, sshIdentifyFile, ssh.InsecureIgnoreHostKey())
} else { } else {
if sshPassword == "" { if sshConfig.password == "" {
return errors.New("SSH_PASSWORD environment variable is required if SSH_IDENTIFY_FILE is empty") return ssh.ClientConfig{}, errors.New("SSH_PASSWORD environment variable is required if SSH_IDENTIFY_FILE is empty")
} }
utils.Warn("Accessing the remote server using password, password is not recommended") utils.Warn("Accessing the remote server using password, which is not recommended.")
clientConfig, _ = auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey()) return auth.PasswordKey(sshConfig.user, sshConfig.password, ssh.InsecureIgnoreHostKey())
} }
}
// CopyToRemote copies a file to a remote server via SCP
func CopyToRemote(fileName, remotePath string) error {
// Load environment variables
sshConfig, err := loadSSHConfig()
if err != nil {
return fmt.Errorf("failed to load SSH configuration: %w", err)
}
// Initialize SSH client config
clientConfig, err := createSSHClientConfig(sshConfig)
if err != nil {
return fmt.Errorf("failed to create SSH client config: %w", err)
}
// Create a new SCP client // Create a new SCP client
client := scp.NewClient(fmt.Sprintf("%s:%s", sshHostName, sshPort), &clientConfig) client := scp.NewClient(fmt.Sprintf("%s:%s", sshConfig.hostName, sshConfig.port), &clientConfig)
// Connect to the remote server // Connect to the remote server
err = client.Connect() err = client.Connect()
if err != nil { if err != nil {
return errors.New("Couldn't establish a connection to the remote server") return errors.New("Couldn't establish a connection to the remote server\n")
} }
// Open a file // Open the local file
file, _ := os.Open(filepath.Join(tmpPath, fileName)) filePath := filepath.Join(tmpPath, fileName)
file, err := os.Open(filePath)
// Close client connection after the file has been copied if err != nil {
return fmt.Errorf("failed to open file %s: %w", filePath, err)
}
defer client.Close() defer client.Close()
// Close the file after it has been copied // Copy file to the remote server
defer file.Close()
// the context can be adjusted to provide time-outs or inherit from other contexts if this is embedded in a larger application.
err = client.CopyFromFile(context.Background(), *file, filepath.Join(remotePath, fileName), "0655") err = client.CopyFromFile(context.Background(), *file, filepath.Join(remotePath, fileName), "0655")
if err != nil { if err != nil {
fmt.Println("Error while copying file ") return fmt.Errorf("failed to copy file to remote server: %w", err)
return err
} }
return nil return nil
} }
func CopyFromRemote(fileName, remotePath string) error { func CopyFromRemote(fileName, remotePath string) error {
sshUser := os.Getenv("SSH_USER") // Load environment variables
sshPassword := os.Getenv("SSH_PASSWORD") sshConfig, err := loadSSHConfig()
sshHostName := os.Getenv("SSH_HOST_NAME")
sshPort := os.Getenv("SSH_PORT")
sshIdentifyFile := os.Getenv("SSH_IDENTIFY_FILE")
err := utils.CheckEnvVars(sshHVars)
if err != nil { if err != nil {
utils.Error("Error checking environment variables\n: %s", err) return fmt.Errorf("failed to load SSH configuration: %w", err)
os.Exit(1)
} }
clientConfig, _ := auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey()) // Initialize SSH client config
if sshIdentifyFile != "" && utils.FileExists(sshIdentifyFile) { clientConfig, err := createSSHClientConfig(sshConfig)
clientConfig, _ = auth.PrivateKey(sshUser, sshIdentifyFile, ssh.InsecureIgnoreHostKey()) if err != nil {
return fmt.Errorf("failed to create SSH client config: %w", err)
} else {
if sshPassword == "" {
return errors.New("SSH_PASSWORD environment variable is required if SSH_IDENTIFY_FILE is empty\n")
}
utils.Warn("Accessing the remote server using password, password is not recommended")
clientConfig, _ = auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey())
} }
// Create a new SCP client // Create a new SCP client
client := scp.NewClient(fmt.Sprintf("%s:%s", sshHostName, sshPort), &clientConfig) client := scp.NewClient(fmt.Sprintf("%s:%s", sshConfig.hostName, sshConfig.port), &clientConfig)
// Connect to the remote server // Connect to the remote server
err = client.Connect() err = client.Connect()
@@ -113,7 +103,7 @@ func CopyFromRemote(fileName, remotePath string) error {
err = client.CopyFromRemote(context.Background(), file, filepath.Join(remotePath, fileName)) err = client.CopyFromRemote(context.Background(), file, filepath.Join(remotePath, fileName))
if err != nil { if err != nil {
fmt.Println("Error while copying file ", err) utils.Error("Error while copying file %s ", err)
return err return err
} }
return nil return nil

View File

@@ -51,3 +51,12 @@ var ftpVars = []string{
"FTP_PASSWORD", "FTP_PASSWORD",
"FTP_PORT", "FTP_PORT",
} }
// AwsVars Required environment variables for AWS S3 storage
var awsVars = []string{
"AWS_S3_ENDPOINT",
"AWS_S3_BUCKET_NAME",
"AWS_ACCESS_KEY",
"AWS_SECRET_KEY",
"AWS_REGION",
}

View File

@@ -12,9 +12,8 @@ import (
"time" "time"
) )
var currentTime = time.Now().Format("2006/01/02 15:04:05")
func Info(msg string, args ...any) { func Info(msg string, args ...any) {
var currentTime = time.Now().Format("2006/01/02 15:04:05")
formattedMessage := fmt.Sprintf(msg, args...) formattedMessage := fmt.Sprintf(msg, args...)
if len(args) == 0 { if len(args) == 0 {
fmt.Printf("%s INFO: %s\n", currentTime, msg) fmt.Printf("%s INFO: %s\n", currentTime, msg)
@@ -25,6 +24,7 @@ func Info(msg string, args ...any) {
// Warn warning message // Warn warning message
func Warn(msg string, args ...any) { func Warn(msg string, args ...any) {
var currentTime = time.Now().Format("2006/01/02 15:04:05")
formattedMessage := fmt.Sprintf(msg, args...) formattedMessage := fmt.Sprintf(msg, args...)
if len(args) == 0 { if len(args) == 0 {
fmt.Printf("%s WARN: %s\n", currentTime, msg) fmt.Printf("%s WARN: %s\n", currentTime, msg)
@@ -33,6 +33,7 @@ func Warn(msg string, args ...any) {
} }
} }
func Error(msg string, args ...any) { func Error(msg string, args ...any) {
var currentTime = time.Now().Format("2006/01/02 15:04:05")
formattedMessage := fmt.Sprintf(msg, args...) formattedMessage := fmt.Sprintf(msg, args...)
if len(args) == 0 { if len(args) == 0 {
fmt.Printf("%s ERROR: %s\n", currentTime, msg) fmt.Printf("%s ERROR: %s\n", currentTime, msg)
@@ -41,6 +42,7 @@ func Error(msg string, args ...any) {
} }
} }
func Done(msg string, args ...any) { func Done(msg string, args ...any) {
var currentTime = time.Now().Format("2006/01/02 15:04:05")
formattedMessage := fmt.Sprintf(msg, args...) formattedMessage := fmt.Sprintf(msg, args...)
if len(args) == 0 { if len(args) == 0 {
fmt.Printf("%s INFO: %s\n", currentTime, msg) fmt.Printf("%s INFO: %s\n", currentTime, msg)
@@ -51,6 +53,7 @@ func Done(msg string, args ...any) {
// Fatal logs an error message and exits the program // Fatal logs an error message and exits the program
func Fatal(msg string, args ...any) { func Fatal(msg string, args ...any) {
var currentTime = time.Now().Format("2006/01/02 15:04:05")
// Fatal logs an error message and exits the program. // Fatal logs an error message and exits the program.
formattedMessage := fmt.Sprintf(msg, args...) formattedMessage := fmt.Sprintf(msg, args...)
if len(args) == 0 { if len(args) == 0 {
@@ -63,5 +66,4 @@ func Fatal(msg string, args ...any) {
} }
os.Exit(1) os.Exit(1)
os.Kill.Signal()
} }