Compare commits

...

34 Commits

Author SHA1 Message Date
Jonas Kaninda
041e0a07e9 Merge pull request #89 from jkaninda/develop
Develop
2024-09-28 03:42:39 +02:00
Jonas Kaninda
9daac9c654 fix: scheduled mode script, remove port number 2024-09-28 03:38:26 +02:00
Jonas Kaninda
f6098769cd fix: backup database in scheduled mode 2024-09-28 03:06:09 +02:00
Jonas Kaninda
5cdfaa4d94 chore: update version in Dockerfile 2024-09-28 02:31:07 +02:00
Jonas Kaninda
b205cd61ea Fix: Using a password on the command line interface can be insecure warning message 2024-09-28 02:25:42 +02:00
Jonas Kaninda
e1307250e8 Merge pull request #88 from jkaninda/jkaninda-patch-1
Update FUNDING.yml
2024-09-12 07:59:57 +02:00
Jonas Kaninda
17ac951deb Update FUNDING.yml 2024-09-12 07:59:46 +02:00
Jonas Kaninda
6e2e08224d Merge pull request #87 from jkaninda/jkaninda-patch-1
Create FUNDING.yml
2024-09-12 07:55:10 +02:00
Jonas Kaninda
570b775f48 Create FUNDING.yml 2024-09-12 07:54:51 +02:00
Jonas Kaninda
e38e106983 Merge pull request #86 from jkaninda/docs
chore: change notification title
2024-09-12 07:10:36 +02:00
Jonas Kaninda
3040420a09 chore: change notification title 2024-09-12 07:10:09 +02:00
Jonas Kaninda
eac5f70408 Merge pull request #85 from jkaninda/docs
Docs
2024-09-12 06:34:32 +02:00
Jonas Kaninda
3476c6f529 docs: update readme 2024-09-12 06:33:38 +02:00
Jonas Kaninda
1a9c8483f8 chore: add code comment 2024-09-12 06:23:57 +02:00
Jonas Kaninda
f8722f7ae4 Merge pull request #84 from jkaninda/docs
Update Intro
2024-09-12 06:18:09 +02:00
Jonas Kaninda
421bf12910 Update Intro 2024-09-12 06:17:46 +02:00
Jonas Kaninda
3da4a27baa Merge pull request #83 from jkaninda/docs
fix: add exit after database connection test failed
2024-09-11 08:03:44 +02:00
Jonas Kaninda
0881f075ef fix: add exit after database connection test failed 2024-09-11 08:03:16 +02:00
Jonas Kaninda
066e73f8e4 Merge pull request #82 from jkaninda/docs
clean up project
2024-09-11 04:55:01 +02:00
Jonas Kaninda
645243ff77 clean up project 2024-09-11 04:53:24 +02:00
Jonas Kaninda
9384998127 Merge pull request #81 from jkaninda/docs
refactor: add Telegram env in Dockerfile, move telegram notification …
2024-09-11 04:37:50 +02:00
Jonas Kaninda
390e7dad0c refactor: add Telegram env in Dockerfile, move telegram notification to utils 2024-09-11 04:37:02 +02:00
Jonas Kaninda
67ea22385f Merge pull request #80 from jkaninda/develop
remove operation old cmd
2024-09-10 23:15:38 +02:00
Jonas Kaninda
cde82d8cfc remove operation old cmd 2024-09-10 23:14:09 +02:00
Jonas Kaninda
4808f093e5 Merge pull request #79 from jkaninda/develop
Update version
2024-09-10 23:11:28 +02:00
Jonas Kaninda
c7a03861fe Update version 2024-09-10 23:10:24 +02:00
Jonas Kaninda
36ec63d522 Merge pull request #78 from jkaninda/develop
feat: Add Telegram notification
2024-09-10 23:04:12 +02:00
Jonas Kaninda
0f07de1d83 feat: Add Telegram notification 2024-09-10 23:01:26 +02:00
Jonas Kaninda
ae55839996 Merge pull request #77 from jkaninda/docs
docs: update Kubernetes deployment
2024-09-09 07:17:51 +02:00
Jonas Kaninda
a7f7e57a0d docs: update Kubernetes deployment 2024-09-09 07:17:15 +02:00
Jonas Kaninda
b2ddaec93b Merge pull request #76 from jkaninda/docs
docs: add buy me a coffee link
2024-09-05 22:43:09 +02:00
Jonas Kaninda
b3570d774c docs: add buy me a coffee link 2024-09-05 22:42:37 +02:00
Jonas Kaninda
38f7e91c03 Merge pull request #75 from jkaninda/develop
chore: rename environment variable for database migration operation
2024-09-03 07:06:24 +02:00
Jonas Kaninda
07c2935925 chore: rename environment variable for database migration operation 2024-09-03 06:49:26 +02:00
33 changed files with 346 additions and 174 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
ko_fi: jkaninda

View File

@@ -1,4 +1,4 @@
name: Release name: CI
on: on:
push: push:
tags: tags:

View File

@@ -1,16 +1,17 @@
# MySQL Backup # MySQL Backup
MySQL Backup is a Docker container image that can be used to backup and restore MySQL database. It supports local storage, AWS S3 or any S3 Alternatives for Object Storage, and SSH compatible storage. MySQL Backup is a Docker container image that can be used to backup, restore and migrate MySQL database. It supports local storage, AWS S3 or any S3 Alternatives for Object Storage, and SSH compatible storage.
It also supports __encrypting__ your backups using GPG. It also supports __encrypting__ your backups using GPG.
The [jkaninda/mysql-bkup](https://hub.docker.com/r/jkaninda/mysql-bkup) Docker image can be deployed on Docker, Docker Swarm and Kubernetes. The [jkaninda/mysql-bkup](https://hub.docker.com/r/jkaninda/mysql-bkup) Docker image can be deployed on Docker, Docker Swarm and Kubernetes.
It handles __recurring__ backups of postgres database on Docker and can be deployed as __CronJob on Kubernetes__ using local, AWS S3 or SSH compatible storage. It handles __recurring__ backups of postgres database on Docker and can be deployed as __CronJob on Kubernetes__ using local, AWS S3 or SSH compatible storage.
It also supports __encrypting__ your backups using GPG. It also supports database __encryption__ using GPG.
[![Build](https://github.com/jkaninda/mysql-bkup/actions/workflows/release.yml/badge.svg)](https://github.com/jkaninda/mysql-bkup/actions/workflows/release.yml) [![Build](https://github.com/jkaninda/mysql-bkup/actions/workflows/release.yml/badge.svg)](https://github.com/jkaninda/mysql-bkup/actions/workflows/release.yml)
[![Go Report](https://goreportcard.com/badge/github.com/jkaninda/mysql-bkup)](https://goreportcard.com/report/github.com/jkaninda/mysql-bkup) [![Go Report](https://goreportcard.com/badge/github.com/jkaninda/mysql-bkup)](https://goreportcard.com/report/github.com/jkaninda/mysql-bkup)
![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/jkaninda/mysql-bkup?style=flat-square) ![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/jkaninda/mysql-bkup?style=flat-square)
![Docker Pulls](https://img.shields.io/docker/pulls/jkaninda/mysql-bkup?style=flat-square) ![Docker Pulls](https://img.shields.io/docker/pulls/jkaninda/mysql-bkup?style=flat-square)
<a href="https://ko-fi.com/jkaninda"><img src="https://uploads-ssl.webflow.com/5c14e387dab576fe667689cf/5cbed8a4ae2b88347c06c923_BuyMeACoffee_blue.png" height="20" alt="buy ma a coffee"></a>
Successfully tested on: Successfully tested on:
- Docker - Docker

View File

@@ -1,3 +1,9 @@
// Package cmd /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package cmd package cmd
import ( import (

View File

@@ -1,3 +1,9 @@
// Package cmd /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package cmd package cmd
import ( import (

View File

@@ -1,7 +1,9 @@
// Package cmd /* // Package cmd /
/* /*****
Copyright © 2024 Jonas Kaninda @author Jonas Kaninda
*/ @license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package cmd package cmd
import ( import (
@@ -31,7 +33,6 @@ func Execute() {
func init() { func init() {
rootCmd.PersistentFlags().StringP("dbname", "d", "", "Database name") rootCmd.PersistentFlags().StringP("dbname", "d", "", "Database name")
rootCmd.PersistentFlags().StringVarP(&operation, "operation", "o", "", "Set operation, for old version only")
rootCmd.AddCommand(VersionCmd) rootCmd.AddCommand(VersionCmd)
rootCmd.AddCommand(BackupCmd) rootCmd.AddCommand(BackupCmd)
rootCmd.AddCommand(RestoreCmd) rootCmd.AddCommand(RestoreCmd)

View File

@@ -1,9 +1,11 @@
// Package cmd /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package cmd package cmd
/*
Copyright © 2024 Jonas Kaninda
*/
import ( import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"

View File

@@ -10,7 +10,7 @@ RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/mysql-bkup RUN CGO_ENABLED=0 GOOS=linux go build -o /app/mysql-bkup
FROM ubuntu:24.04 FROM ubuntu:24.04
ENV DB_HOST="" ENV DB_HOST="localhost"
ENV DB_NAME="" ENV DB_NAME=""
ENV DB_USERNAME="" ENV DB_USERNAME=""
ENV DB_PASSWORD="" ENV DB_PASSWORD=""
@@ -30,14 +30,16 @@ ENV SSH_PASSWORD=""
ENV SSH_HOST_NAME="" ENV SSH_HOST_NAME=""
ENV SSH_IDENTIFY_FILE="" ENV SSH_IDENTIFY_FILE=""
ENV SSH_PORT="22" ENV SSH_PORT="22"
ENV SOURCE_DB_HOST="" ENV TARGET_DB_HOST=""
ENV SOURCE_DB_PORT=3306 ENV TARGET_DB_PORT=3306
ENV SOURCE_DB_NAME="" ENV TARGET_DB_NAME="localhost"
ENV SOURCE_DB_USERNAME="" ENV TARGET_DB_USERNAME=""
ENV SOURCE_DB_PASSWORD="" ENV TARGET_DB_PASSWORD=""
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV VERSION="v1.2.3" ENV VERSION="v1.2.7"
ENV BACKUP_CRON_EXPRESSION="" ENV BACKUP_CRON_EXPRESSION=""
ENV TG_TOKEN=""
ENV TG_CHAT_ID=""
ARG WORKDIR="/config" ARG WORKDIR="/config"
ARG BACKUPDIR="/backup" ARG BACKUPDIR="/backup"
ARG BACKUP_TMP_DIR="/tmp/backup" ARG BACKUP_TMP_DIR="/tmp/backup"

View File

@@ -1,12 +0,0 @@
FROM ruby:3.3.4
ENV LC_ALL C.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8
WORKDIR /usr/src/app
COPY . ./
RUN gem install bundler && bundle install
EXPOSE 4000

View File

@@ -1,13 +0,0 @@
services:
jekyll:
build:
context: ./
ports:
- 4000:4000
environment:
- JEKYLL_ENV=development
volumes:
- .:/usr/src/app
stdin_open: true
tty: true
command: bundle exec jekyll serve -H 0.0.0.0 -t

View File

@@ -104,7 +104,7 @@ spec:
command: command:
- /bin/sh - /bin/sh
- -c - -c
- mysql-bkup backup -s s3 --path /custom_path - backup -s s3 --path /custom_path
env: env:
- name: DB_PORT - name: DB_PORT
value: "3306" value: "3306"

View File

@@ -111,7 +111,7 @@ spec:
command: command:
- /bin/sh - /bin/sh
- -c - -c
- mysql-bkup backup -s ssh - backup -s ssh
env: env:
- name: DB_PORT - name: DB_PORT
value: "3306" value: "3306"

View File

@@ -30,10 +30,7 @@ spec:
command: command:
- /bin/sh - /bin/sh
- -c - -c
- bkup - backup --storage s3
- backup
- --storage
- s3
resources: resources:
limits: limits:
memory: "128Mi" memory: "128Mi"

View File

@@ -10,11 +10,13 @@ nav_order: 9
To migrate the database, you need to add `migrate` command. To migrate the database, you need to add `migrate` command.
{: .note } {: .note }
The Mysql backup has another great feature: migrating your database from a source database to another. The Mysql backup has another great feature: migrating your database from a source database to a target.
As you know, to restore a database from a source to a target database, you need 2 operations: which is to start by backing up the source database and then restoring the source backed database to the target database. As you know, to restore a database from a source to a target database, you need 2 operations: which is to start by backing up the source database and then restoring the source backed database to the target database.
Instead of proceeding like that, you can use the integrated feature `(migrate)`, which will help you migrate your database by doing only one operation. Instead of proceeding like that, you can use the integrated feature `(migrate)`, which will help you migrate your database by doing only one operation.
{: .warning }
The `migrate` operation is irreversible, please backup your target database before this action.
### Docker compose ### Docker compose
```yml ```yml
@@ -30,18 +32,18 @@ services:
volumes: volumes:
- ./backup:/backup - ./backup:/backup
environment: environment:
## Target database ## Source database
- DB_PORT=3306 - DB_PORT=3306
- DB_HOST=mysql - DB_HOST=mysql
- DB_NAME=database - DB_NAME=database
- DB_USERNAME=username - DB_USERNAME=username
- DB_PASSWORD=password - DB_PASSWORD=password
## Source database ## Target database
- SOURCE_DB_HOST=mysql2 - TARGET_DB_HOST=target-mysql
- SOURCE_DB_PORT=3306 - TARGET_DB_PORT=3306
- SOURCE_DB_NAME=sourcedb - TARGET_DB_NAME=dbname
- SOURCE_DB_USERNAME=jonas - TARGET_DB_USERNAME=username
- SOURCE_DB_PASSWORD=password - TARGET_DB_PASSWORD=password
# 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
@@ -49,30 +51,31 @@ networks:
web: web:
``` ```
### Migrate database using Docker CLI ### Migrate database using Docker CLI
``` ```
## Target database ## Source database
DB_PORT=3306
DB_HOST=mysql DB_HOST=mysql
DB_NAME=targetdb DB_PORT=3306
DB_USERNAME=targetuser DB_NAME=dbname
DB_USERNAME=username
DB_PASSWORD=password DB_PASSWORD=password
## Source database ## Taget database
SOURCE_DB_HOST=mysql2 TARGET_DB_HOST=target-mysql
SOURCE_DB_PORT=3306 TARGET_DB_PORT=3306
SOURCE_DB_NAME=sourcedb TARGET_DB_NAME=dbname
SOURCE_DB_USERNAME=sourceuser TARGET_DB_USERNAME=username
SOURCE_DB_PASSWORD=password TARGET_DB_PASSWORD=password
``` ```
```shell ```shell
docker run --rm --network your_network_name \ docker run --rm --network your_network_name \
--env-file your-env --env-file your-env
-v $PWD/backup:/backup/ \ -v $PWD/backup:/backup/ \
jkaninda/mysql-bkup migrate -d database_name jkaninda/mysql-bkup migrate
``` ```
## Kubernetes ## Kubernetes
@@ -96,28 +99,33 @@ spec:
command: command:
- /bin/sh - /bin/sh
- -c - -c
- migrate -d targetdb - migrate
resources: resources:
limits: limits:
memory: "128Mi" memory: "128Mi"
cpu: "500m" cpu: "500m"
env: env:
## Target DB ## Source Database
- name: DB_HOST - name: DB_HOST
value: "postgres-target" value: "mysql"
- name: DB_PORT
value: "3306"
- name: DB_NAME
value: "dbname"
- name: DB_USERNAME - name: DB_USERNAME
value: "mysql" value: "username"
- name: DB_PASSWORD - name: DB_PASSWORD
value: "password" value: "password"
## Source DB ## Target Database
- name: SOURCE_DB_HOST - name: TARGET_DB_HOST
value: "postgres-source" value: "target-mysql"
- name: SOURCE_DB_NAME - name: TARGET_DB_PORT
value: "sourcedb" value: "3306"
- name: SOURCE_DB_USERNAME - name: TARGET_DB_NAME
value: "postgres" value: "dbname"
# Please use secret! - name: TARGET_DB_USERNAME
- name: SOURCE_DB_PASSWORD value: "username"
- name: TARGET_DB_PASSWORD
value: "password" value: "password"
restartPolicy: Never restartPolicy: Never
``` ```

View File

@@ -65,7 +65,7 @@ spec:
command: command:
- /bin/sh - /bin/sh
- -c - -c
- bkup restore -s s3 --path /custom_path -f store_20231219_022941.sql.gz - restore -s s3 --path /custom_path -f store_20231219_022941.sql.gz
env: env:
- name: DB_PORT - name: DB_PORT
value: "3306" value: "3306"

View File

@@ -63,7 +63,7 @@ spec:
command: command:
- /bin/sh - /bin/sh
- -c - -c
- bkup restore -s ssh -f store_20231219_022941.sql.gz - restore -s ssh -f store_20231219_022941.sql.gz
env: env:
- name: DB_PORT - name: DB_PORT
value: "3306" value: "3306"

View File

@@ -6,7 +6,7 @@ nav_order: 1
# About mysql-bkup # About mysql-bkup
{:.no_toc} {:.no_toc}
MySQL Backup is a Docker container image that can be used to backup and restore MySQL database. It supports local storage, AWS S3 or any S3 Alternatives for Object Storage, and SSH remote storage. MySQL Backup is a Docker container image that can be used to backup, restore and migrate MySQL database. It supports local storage, AWS S3 or any S3 Alternatives for Object Storage, and SSH remote storage.
It also supports __encrypting__ your backups using GPG. It also supports __encrypting__ your backups using GPG.
We are open to receiving stars, PRs, and issues! We are open to receiving stars, PRs, and issues!
@@ -19,7 +19,8 @@ We are open to receiving stars, PRs, and issues!
The [jkaninda/mysql-bkup](https://hub.docker.com/r/jkaninda/mysql-bkup) Docker image can be deployed on Docker, Docker Swarm and Kubernetes. The [jkaninda/mysql-bkup](https://hub.docker.com/r/jkaninda/mysql-bkup) Docker image can be deployed on Docker, Docker Swarm and Kubernetes.
It handles __recurring__ backups of postgres database on Docker and can be deployed as __CronJob on Kubernetes__ using local, AWS S3 or SSH compatible storage. It handles __recurring__ backups of postgres database on Docker and can be deployed as __CronJob on Kubernetes__ using local, AWS S3 or SSH compatible storage.
It also supports __encrypting__ your backups using GPG. It also supports database __encryption__ using GPG.
{: .note } {: .note }
Code and documentation for `v1` version on [this branch][v1-branch]. Code and documentation for `v1` version on [this branch][v1-branch].

View File

@@ -57,12 +57,13 @@ Backup, restore and migrate targets, schedule and retention are configured using
| 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 |
| SSH_REMOTE_PATH | Optional, required for SSH storage | ssh remote path (/home/toto/backup) | | SSH_REMOTE_PATH | Optional, required for SSH storage | ssh remote path (/home/toto/backup) |
| SOURCE_DB_HOST | Optional, required for database migration | Source database host | | TARGET_DB_HOST | Optional, required for database migration | Target database host |
| SOURCE_DB_PORT | Optional, required for database migration | Source database port | | TARGET_DB_PORT | Optional, required for database migration | Target database port |
| SOURCE_DB_NAME | Optional, required for database migration | Source database name | | TARGET_DB_NAME | Optional, required for database migration | Target database name |
| SOURCE_DB_USERNAME | Optional, required for database migration | Source database username | | TARGET_DB_USERNAME | Optional, required for database migration | Target database username |
| SOURCE_DB_PASSWORD | Optional, required for database migration | Source database password | | TARGET_DB_PASSWORD | Optional, required for database migration | Target database password |
| TG_TOKEN | Optional, required for Telegram notification | Telegram token |
| TG_CHAT_ID | Optional, required for Telegram notification | Telegram Chat ID |
--- ---
## Run in Scheduled mode ## Run in Scheduled mode

View File

@@ -15,10 +15,7 @@ spec:
command: command:
- /bin/sh - /bin/sh
- -c - -c
- bkup - backup --storage s3
- backup
- --storage
- s3
resources: resources:
limits: limits:
memory: "128Mi" memory: "128Mi"

13
main.go
View File

@@ -1,12 +1,11 @@
// Package main /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package main package main
//main
/*****
* MySQL Backup & Restore
* @author Jonas Kaninda
* @license MIT License <https://opensource.org/licenses/MIT>
* @link https://github.com/jkaninda/mysql-bkup
**/
import "github.com/jkaninda/mysql-bkup/cmd" import "github.com/jkaninda/mysql-bkup/cmd"
func main() { func main() {

View File

@@ -1,7 +1,9 @@
// Package pkg /* // Package pkg /
/* /*****
Copyright © 2024 Jonas Kaninda @author Jonas Kaninda
*/ @license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
import ( import (
@@ -17,7 +19,7 @@ import (
) )
func StartBackup(cmd *cobra.Command) { func StartBackup(cmd *cobra.Command) {
_, _ = cmd.Flags().GetString("operation") intro()
//Set env //Set env
utils.SetEnv("STORAGE_PATH", storagePath) utils.SetEnv("STORAGE_PATH", storagePath)
utils.GetEnv(cmd, "period", "BACKUP_CRON_EXPRESSION") utils.GetEnv(cmd, "period", "BACKUP_CRON_EXPRESSION")
@@ -114,6 +116,10 @@ func scheduledMode(db *dbConfig, storage string) {
fmt.Println(line.Text) fmt.Println(line.Text)
} }
} }
func intro() {
utils.Info("Starting MySQL Backup...")
utils.Info("Copyright © 2024 Jonas Kaninda ")
}
// BackupDatabase backup database // BackupDatabase backup database
func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) { func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) {
@@ -126,6 +132,10 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
} }
utils.Info("Starting database backup...") utils.Info("Starting database backup...")
err = os.Setenv("MYSQL_PWD", db.dbPassword)
if err != nil {
return
}
testDatabaseConnection(db) testDatabaseConnection(db)
// Backup Database database // Backup Database database
@@ -137,7 +147,6 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
"-h", db.dbHost, "-h", db.dbHost,
"-P", db.dbPort, "-P", db.dbPort,
"-u", db.dbUserName, "-u", db.dbUserName,
"--password="+db.dbPassword,
db.dbName, db.dbName,
) )
output, err := cmd.Output() output, err := cmd.Output()
@@ -160,7 +169,7 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
} else { } else {
// Execute mysqldump // Execute mysqldump
cmd := exec.Command("mysqldump", "-h", db.dbHost, "-P", db.dbPort, "-u", db.dbUserName, "--password="+db.dbPassword, db.dbName) cmd := exec.Command("mysqldump", "-h", db.dbHost, "-P", db.dbPort, "-u", db.dbUserName, db.dbName)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@@ -193,6 +202,8 @@ func localBackup(db *dbConfig, backupFileName string, disableCompression bool, p
} }
utils.Info("Backup name is %s", finalFileName) utils.Info("Backup name is %s", finalFileName)
moveToBackup(finalFileName, storagePath) moveToBackup(finalFileName, storagePath)
//Send notification
utils.NotifySuccess(finalFileName)
//Delete old backup //Delete old backup
if prune { if prune {
deleteOldBackup(backupRetention) deleteOldBackup(backupRetention)
@@ -234,9 +245,13 @@ func s3Backup(db *dbConfig, backupFileName string, disableCompression bool, prun
} }
} }
utils.Done("Uploading backup archive to remote storage S3 ... done ") utils.Done("Uploading backup archive to remote storage S3 ... done ")
//Send notification
utils.NotifySuccess(finalFileName)
//Delete temp //Delete temp
deleteTemp() deleteTemp()
} }
// sshBackup backup database to SSH remote server
func sshBackup(db *dbConfig, backupFileName, remotePath string, disableCompression bool, prune bool, backupRetention int, encrypt bool) { func sshBackup(db *dbConfig, backupFileName, remotePath string, disableCompression bool, prune bool, backupRetention int, encrypt bool) {
utils.Info("Backup database to Remote server") utils.Info("Backup database to Remote server")
//Backup database //Backup database
@@ -267,9 +282,13 @@ func sshBackup(db *dbConfig, backupFileName, remotePath string, disableCompressi
} }
utils.Done("Uploading backup archive to remote storage ... done ") utils.Done("Uploading backup archive to remote storage ... done ")
//Send notification
utils.NotifySuccess(finalFileName)
//Delete temp //Delete temp
deleteTemp() deleteTemp()
} }
// encryptBackup encrypt backup
func encryptBackup(backupFileName string) { func encryptBackup(backupFileName string) {
gpgPassphrase := os.Getenv("GPG_PASSPHRASE") gpgPassphrase := os.Getenv("GPG_PASSPHRASE")
err := Encrypt(filepath.Join(tmpPath, backupFileName), gpgPassphrase) err := Encrypt(filepath.Join(tmpPath, backupFileName), gpgPassphrase)

View File

@@ -1,3 +1,9 @@
// Package pkg /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
import ( import (
@@ -16,12 +22,12 @@ type dbConfig struct {
dbUserName string dbUserName string
dbPassword string dbPassword string
} }
type dbSourceConfig struct { type targetDbConfig struct {
sourceDbHost string targetDbHost string
sourceDbPort string targetDbPort string
sourceDbUserName string targetDbUserName string
sourceDbPassword string targetDbPassword string
sourceDbName string targetDbName string
} }
func getDbConfig(cmd *cobra.Command) *dbConfig { func getDbConfig(cmd *cobra.Command) *dbConfig {
@@ -41,18 +47,18 @@ func getDbConfig(cmd *cobra.Command) *dbConfig {
} }
return &dConf return &dConf
} }
func getSourceDbConfig() *dbSourceConfig { func getTargetDbConfig() *targetDbConfig {
sdbConfig := dbSourceConfig{} tdbConfig := targetDbConfig{}
sdbConfig.sourceDbHost = os.Getenv("SOURCE_DB_HOST") tdbConfig.targetDbHost = os.Getenv("TARGET_DB_HOST")
sdbConfig.sourceDbPort = os.Getenv("SOURCE_DB_PORT") tdbConfig.targetDbPort = os.Getenv("TARGET_DB_PORT")
sdbConfig.sourceDbName = os.Getenv("SOURCE_DB_NAME") tdbConfig.targetDbName = os.Getenv("TARGET_DB_NAME")
sdbConfig.sourceDbUserName = os.Getenv("SOURCE_DB_USERNAME") tdbConfig.targetDbUserName = os.Getenv("TARGET_DB_USERNAME")
sdbConfig.sourceDbPassword = os.Getenv("SOURCE_DB_PASSWORD") tdbConfig.targetDbPassword = os.Getenv("TARGET_DB_PASSWORD")
err := utils.CheckEnvVars(sdbRVars) err := utils.CheckEnvVars(tdbRVars)
if err != nil { if err != nil {
utils.Error("Please make sure all required environment variables for source database are set") utils.Error("Please make sure all required environment variables for the target database are set")
utils.Fatal("Error checking environment variables: %s", err) utils.Fatal("Error checking target database environment variables: %s", err)
} }
return &sdbConfig return &tdbConfig
} }

View File

@@ -1,3 +1,9 @@
// Package pkg /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
import ( import (

View File

@@ -1,3 +1,9 @@
// Package pkg /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
import ( import (
@@ -101,19 +107,19 @@ func deleteTemp() {
// TestDatabaseConnection tests the database connection // TestDatabaseConnection tests the database connection
func testDatabaseConnection(db *dbConfig) { func testDatabaseConnection(db *dbConfig) {
err := os.Setenv("MYSQL_PWD", db.dbPassword)
if err != nil {
return
}
utils.Info("Connecting to %s database ...", db.dbName) utils.Info("Connecting to %s database ...", db.dbName)
cmd := exec.Command("mysql", "-h", db.dbHost, "-P", db.dbPort, "-u", db.dbUserName, db.dbName, "-e", "quit")
cmd := exec.Command("mysql", "-h", db.dbHost, "-P", db.dbPort, "-u", db.dbUserName, "--password="+db.dbPassword, db.dbName, "-e", "quit")
// Capture the output // Capture the output
var out bytes.Buffer var out bytes.Buffer
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stderr = &out cmd.Stderr = &out
err := cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
utils.Error("Error testing database connection: %v\nOutput: %s", err, out.String()) utils.Fatal("Error testing database connection: %v\nOutput: %s", err, out.String())
os.Exit(1)
} }
utils.Info("Successfully connected to %s database", db.dbName) utils.Info("Successfully connected to %s database", db.dbName)

View File

@@ -1,3 +1,9 @@
// Package pkg /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
import ( import (
@@ -8,24 +14,27 @@ import (
) )
func StartMigration(cmd *cobra.Command) { func StartMigration(cmd *cobra.Command) {
intro()
utils.Info("Starting database migration...") utils.Info("Starting database migration...")
//Get DB config //Get DB config
dbConf = getDbConfig(cmd) dbConf = getDbConfig(cmd)
sDbConf = getSourceDbConfig() targetDbConf = getTargetDbConfig()
//Defining the target database variables
newDbConfig := dbConfig{}
newDbConfig.dbHost = targetDbConf.targetDbHost
newDbConfig.dbPort = targetDbConf.targetDbPort
newDbConfig.dbName = targetDbConf.targetDbName
newDbConfig.dbUserName = targetDbConf.targetDbUserName
newDbConfig.dbPassword = targetDbConf.targetDbPassword
//Generate file name //Generate file name
backupFileName := fmt.Sprintf("%s_%s.sql", sDbConf.sourceDbName, time.Now().Format("20060102_150405")) backupFileName := fmt.Sprintf("%s_%s.sql", dbConf.dbName, time.Now().Format("20060102_150405"))
//Backup Source Database //Backup source Database
newDbConfig := dbConfig{} BackupDatabase(dbConf, backupFileName, true)
newDbConfig.dbHost = sDbConf.sourceDbHost
newDbConfig.dbPort = sDbConf.sourceDbPort
newDbConfig.dbName = sDbConf.sourceDbName
newDbConfig.dbUserName = sDbConf.sourceDbUserName
newDbConfig.dbPassword = sDbConf.sourceDbPassword
BackupDatabase(&newDbConfig, backupFileName, true)
//Restore source database into target database //Restore source database into target database
utils.Info("Restoring [%s] database into [%s] database...", sDbConf.sourceDbName, dbConf.dbName) utils.Info("Restoring [%s] database into [%s] database...", dbConf.dbName, targetDbConf.targetDbName)
RestoreDatabase(dbConf, backupFileName) RestoreDatabase(&newDbConfig, backupFileName)
utils.Info("[%s] database has been restored into [%s] database", sDbConf.sourceDbName, dbConf.dbName) utils.Info("[%s] database has been restored into [%s] database", dbConf.dbName, targetDbConf.targetDbName)
utils.Info("Database migration completed!") utils.Info("Database migration completed.")
} }

View File

@@ -1,3 +1,9 @@
// Package pkg /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
import ( import (
@@ -10,7 +16,7 @@ import (
) )
func StartRestore(cmd *cobra.Command) { func StartRestore(cmd *cobra.Command) {
intro()
//Set env //Set env
utils.SetEnv("STORAGE_PATH", storagePath) utils.SetEnv("STORAGE_PATH", storagePath)
@@ -89,13 +95,17 @@ func RestoreDatabase(db *dbConfig, file string) {
} }
if utils.FileExists(fmt.Sprintf("%s/%s", tmpPath, file)) { if utils.FileExists(fmt.Sprintf("%s/%s", tmpPath, file)) {
err = os.Setenv("MYSQL_PWD", db.dbPassword)
if err != nil {
return
}
testDatabaseConnection(db) testDatabaseConnection(db)
utils.Info("Restoring database...") utils.Info("Restoring database...")
extension := filepath.Ext(fmt.Sprintf("%s/%s", tmpPath, file)) extension := filepath.Ext(fmt.Sprintf("%s/%s", tmpPath, file))
// Restore from compressed file / .sql.gz // Restore from compressed file / .sql.gz
if extension == ".gz" { if extension == ".gz" {
str := "zcat " + fmt.Sprintf("%s/%s", tmpPath, file) + " | mysql -h " + os.Getenv("DB_HOST") + " -P " + os.Getenv("DB_PORT") + " -u " + os.Getenv("DB_USERNAME") + " --password=" + os.Getenv("DB_PASSWORD") + " " + os.Getenv("DB_NAME") str := "zcat " + filepath.Join(tmpPath, file) + " | mysql -h " + db.dbHost + " -P " + db.dbPort + " -u " + db.dbUserName + " " + db.dbName
_, err := exec.Command("bash", "-c", str).Output() _, err := exec.Command("bash", "-c", str).Output()
if err != nil { if err != nil {
utils.Fatal("Error, in restoring the database %v", err) utils.Fatal("Error, in restoring the database %v", err)
@@ -107,20 +117,20 @@ func RestoreDatabase(db *dbConfig, file string) {
} else if extension == ".sql" { } else if extension == ".sql" {
//Restore from sql file //Restore from sql file
str := "cat " + fmt.Sprintf("%s/%s", tmpPath, file) + " | mysql -h " + os.Getenv("DB_HOST") + " -P " + os.Getenv("DB_PORT") + " -u " + os.Getenv("DB_USERNAME") + " --password=" + os.Getenv("DB_PASSWORD") + " " + os.Getenv("DB_NAME") str := "cat " + filepath.Join(tmpPath, file) + " | mysql -h " + db.dbHost + " -P " + db.dbPort + " -u " + db.dbUserName + " " + db.dbName
_, err := exec.Command("bash", "-c", str).Output() _, err := exec.Command("bash", "-c", str).Output()
if err != nil { if err != nil {
utils.Fatal(fmt.Sprintf("Error in restoring the database %s", err)) utils.Fatal("Error in restoring the database %v", err)
} }
utils.Info("Restoring database... done") utils.Info("Restoring database... done")
utils.Done("Database has been restored") utils.Done("Database has been restored")
//Delete temp //Delete temp
deleteTemp() deleteTemp()
} else { } else {
utils.Fatal(fmt.Sprintf("Unknown file extension %s", extension)) utils.Fatal("Unknown file extension %s", extension)
} }
} else { } else {
utils.Fatal(fmt.Sprintf("File not found in %s", fmt.Sprintf("%s/%s", tmpPath, file))) utils.Fatal("File not found in %s", filepath.Join(tmpPath, file))
} }
} }

View File

@@ -1,3 +1,9 @@
// Package pkg /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
import ( import (

View File

@@ -1,9 +1,11 @@
// Package pkg /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
// Package pkg /*
/*
Copyright © 2024 Jonas Kaninda
*/
import ( import (
"fmt" "fmt"
"github.com/jkaninda/mysql-bkup/utils" "github.com/jkaninda/mysql-bkup/utils"
@@ -24,8 +26,8 @@ func CreateCrontabScript(disableCompression bool, storage string) {
scriptContent := fmt.Sprintf(`#!/usr/bin/env bash scriptContent := fmt.Sprintf(`#!/usr/bin/env bash
set -e set -e
/usr/local/bin/mysql-bkup backup --dbname %s --port %s --storage %s %v /usr/local/bin/mysql-bkup backup --dbname %s --storage %s %v
`, os.Getenv("DB_NAME"), os.Getenv("DB_PORT"), storage, disableC) `, os.Getenv("DB_NAME"), storage, disableC)
if err := utils.WriteToFile(backupCronFile, scriptContent); err != nil { if err := utils.WriteToFile(backupCronFile, scriptContent); err != nil {
utils.Fatal("Error writing to %s: %v\n", backupCronFile, err) utils.Fatal("Error writing to %s: %v\n", backupCronFile, err)

View File

@@ -1,3 +1,9 @@
// Package pkg /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package pkg package pkg
const cronLogFile = "/var/log/mysql-bkup.log" const cronLogFile = "/var/log/mysql-bkup.log"
@@ -23,16 +29,16 @@ var dbHVars = []string{
"DB_USERNAME", "DB_USERNAME",
"DB_NAME", "DB_NAME",
} }
var sdbRVars = []string{ var tdbRVars = []string{
"SOURCE_DB_HOST", "TARGET_DB_HOST",
"SOURCE_DB_PORT", "TARGET_DB_PORT",
"SOURCE_DB_NAME", "TARGET_DB_NAME",
"SOURCE_DB_USERNAME", "TARGET_DB_USERNAME",
"SOURCE_DB_PASSWORD", "TARGET_DB_PASSWORD",
} }
var dbConf *dbConfig var dbConf *dbConfig
var sDbConf *dbSourceConfig var targetDbConf *targetDbConfig
// sshHVars Required environment variables for SSH remote server storage // sshHVars Required environment variables for SSH remote server storage
var sshHVars = []string{ var sshHVars = []string{

View File

@@ -1,3 +1,9 @@
// Package utils /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package utils package utils
const RestoreExample = "mysql-bkup restore --dbname database --file db_20231219_022941.sql.gz\n" + const RestoreExample = "mysql-bkup restore --dbname database --file db_20231219_022941.sql.gz\n" +

View File

@@ -1,3 +1,9 @@
// Package utils /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package utils package utils
import ( import (
@@ -49,8 +55,13 @@ func Fatal(msg string, args ...any) {
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)
NotifyError(msg)
} else { } else {
fmt.Printf("%s ERROR: %s\n", currentTime, formattedMessage) fmt.Printf("%s ERROR: %s\n", currentTime, formattedMessage)
NotifyError(formattedMessage)
} }
os.Exit(1) os.Exit(1)
os.Kill.Signal()
} }

View File

@@ -1,3 +1,9 @@
// Package utils /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package utils package utils
import ( import (
@@ -122,7 +128,7 @@ func DownloadFile(destinationPath, key, bucket, prefix string) error {
fmt.Println("Failed to download file", err) fmt.Println("Failed to download file", err)
return err return err
} }
Info(fmt.Sprintf("Backup downloaded: ", file.Name(), " bytes size ", numBytes)) Info("Backup downloaded: %s bytes size %s ", file.Name(), numBytes)
return nil return nil
} }

View File

@@ -1,17 +1,22 @@
// Package utils /
/*****
@author Jonas Kaninda
@license MIT License <https://opensource.org/licenses/MIT>
@Copyright © 2024 Jonas Kaninda
**/
package utils package utils
/*****
* MySQL Backup & Restore
* @author Jonas Kaninda
* @license MIT License <https://opensource.org/licenses/MIT>
* @link https://github.com/jkaninda/mysql-bkup
**/
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"io" "io"
"io/fs" "io/fs"
"io/ioutil"
"net/http"
"os" "os"
"strconv"
) )
func FileExists(filename string) bool { func FileExists(filename string) bool {
@@ -170,3 +175,78 @@ func MakeDirAll(dirPath string) error {
} }
return nil return nil
} }
func GetIntEnv(envName string) int {
val := os.Getenv(envName)
if val == "" {
return 0
}
ret, err := strconv.Atoi(val)
if err != nil {
Error("Error: %v", err)
}
return ret
}
func sendMessage(msg string) {
Info("Sending notification... ")
chatId := os.Getenv("TG_CHAT_ID")
body, _ := json.Marshal(map[string]string{
"chat_id": chatId,
"text": msg,
})
url := fmt.Sprintf("%s/sendMessage", getTgUrl())
// Create an HTTP post request
request, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil {
panic(err)
}
request.Header.Add("Content-Type", "application/json")
client := &http.Client{}
response, err := client.Do(request)
if err != nil {
panic(err)
}
code := response.StatusCode
if code == 200 {
Info("Notification has been sent")
} else {
body, _ := ioutil.ReadAll(response.Body)
Error("Message not sent, error: %s", string(body))
}
}
func NotifySuccess(fileName string) {
var vars = []string{
"TG_TOKEN",
"TG_CHAT_ID",
}
//Telegram notification
err := CheckEnvVars(vars)
if err == nil {
message := "MySQL Backup \n" +
"Database has been backed up \n" +
"Backup name is " + fileName
sendMessage(message)
}
}
func NotifyError(error string) {
var vars = []string{
"TG_TOKEN",
"TG_CHAT_ID",
}
//Telegram notification
err := CheckEnvVars(vars)
if err == nil {
message := "MySQL Backup \n" +
"An error occurred during database backup \n" +
"Error: " + error
sendMessage(message)
}
}
func getTgUrl() string {
return fmt.Sprintf("https://api.telegram.org/bot%s", os.Getenv("TG_TOKEN"))
}