Compare commits

...

118 Commits
v0.1 ... v0.7

Author SHA1 Message Date
9588c5bcee Merge pull request #49 from jkaninda/develop
Develop
2024-02-17 18:25:37 +01:00
e0457a4ed8 refactor: clean up code 2024-02-17 18:24:45 +01:00
d3efa3fc05 chore: update docker tag version 2024-02-17 18:23:27 +01:00
7bcde78136 feat: add backup prune, to delete old backup 2024-02-17 18:17:41 +01:00
b95601ab57 Merge pull request #48 from jkaninda/develop
chore: rename env variable in Dockerfile
2024-01-22 21:19:44 +01:00
facd57e2cd chore: rename env variable in Dockerfile 2024-01-22 21:18:51 +01:00
2fa7e50485 Merge pull request #47 from jkaninda/develop
docs: refactoring of code, update doc
2024-01-21 15:27:14 +01:00
f53c68cd5c New release 2024-01-21 15:26:20 +01:00
902695032c docs: add more details for env variables 2024-01-21 15:24:44 +01:00
620801cb99 refactor: refactoring of code, update docs 2024-01-21 15:18:35 +01:00
e19643ebcb Merge pull request #46 from jkaninda/refactor
docs: update docs
2024-01-20 14:34:36 +01:00
c87201d08d docs: update docs 2024-01-20 14:33:58 +01:00
bf566af4f8 Merge pull request #45 from jkaninda/refactor
Refactor
2024-01-20 14:07:32 +01:00
99ea4e18e5 chore: update app version 2024-01-20 14:06:42 +01:00
748416af9b chore: update error message 2024-01-20 14:01:23 +01:00
bce9512d6a chore: update error message 2024-01-20 13:59:47 +01:00
8eff38bdb1 docs: update readme 2024-01-20 13:09:15 +01:00
c12a6751ae docs: update readme 2024-01-20 13:07:04 +01:00
05eda4213a feat: replace --operation flag by backup and restore command 2024-01-20 13:04:39 +01:00
a919b161c8 Merge pull request #44 from jkaninda/develop
Develop
2024-01-20 04:06:19 +01:00
4b07a78f29 chore: add mysql_bkup Symlink link 2024-01-20 04:05:28 +01:00
4a0ad39d70 docs: update readme 2024-01-20 04:02:39 +01:00
0be493d51f Merge pull request #43 from jkaninda/develop
Merge pull request #42 from jkaninda/develop
2024-01-19 14:53:34 +01:00
cdb4b2017a Merge pull request #42 from jkaninda/develop
refactor: refactoring of code
2024-01-19 14:52:02 +01:00
09ba8f8981 Merge pull request #42 from jkaninda/develop
refactore: refactoring of code
2024-01-19 14:32:29 +01:00
2ae78fec57 refactore: refactoring of code 2024-01-19 14:29:37 +01:00
2cd74167de Merge pull request #41 from jkaninda/develop
chore: clean up code
2024-01-19 14:13:38 +01:00
aaad8a010c chore: clean up code 2024-01-19 14:12:05 +01:00
a086921242 Merge pull request #40 from jkaninda/develop
refactor: refactoring of code
2024-01-19 06:57:00 +01:00
3525a90b93 refactor: refactoring of code 2024-01-19 06:56:19 +01:00
40f2a2c99d Merge pull request #39 from jkaninda/develop
refactor: clean up code
2024-01-19 06:17:41 +01:00
3537532d5f refactor: clean up code 2024-01-19 06:16:51 +01:00
b98191f586 Merge pull request #38 from jkaninda/develop
docs: update readme.md
2024-01-19 05:45:42 +01:00
502767bb12 docs: update readme.md 2024-01-19 05:45:07 +01:00
b3b4248fa0 Merge pull request #37 from jkaninda/develop
Develop
2024-01-19 05:37:46 +01:00
8de463ad38 docs: update examples 2024-01-19 05:37:16 +01:00
2f0375dee3 docs: update examples 2024-01-19 05:36:40 +01:00
313e4c9525 Merge pull request #36 from jkaninda/develop
refactor: move backup, restore, s3fs tasks in pkg folder
2024-01-19 05:31:57 +01:00
164d8eda77 refactor: move backup, restore, s3fs tasks in pkg folder 2024-01-19 05:31:30 +01:00
80923885c1 Merge pull request #35 from jkaninda/develop
Develop
2024-01-18 19:15:03 +01:00
078d57fc0d Refactoring of code 2024-01-18 19:14:19 +01:00
abd04c0a37 Refactoring of code 2024-01-18 19:13:04 +01:00
8b7d1576a4 Merge pull request #34 from jkaninda/develop
Develop
2024-01-18 15:08:00 +01:00
452d77f5ee chore: update README 2024-01-18 15:07:01 +01:00
4630df0dd6 chore: update README 2024-01-18 15:05:44 +01:00
5d97ec0a4d chore: update README 2024-01-18 15:05:25 +01:00
d9a86ca053 Merge pull request #33 from jkaninda/develop
chore: clean project
2024-01-18 14:47:19 +01:00
380ea59e95 chore: clean project 2024-01-18 14:43:10 +01:00
af037f195c Merge pull request #32 from jkaninda/jkaninda-patch-1
Delete .DS_Store
2024-01-18 14:41:55 +01:00
45d397dfc8 Delete .DS_Store 2024-01-18 14:41:35 +01:00
72db75b9fb Merge pull request #31 from jkaninda/go_migration
Fix: Docker build file outpout path
2024-01-18 14:29:28 +01:00
4b1501c095 Fix: Docker build file outpout path 2024-01-18 14:28:41 +01:00
2823848fd1 Merge pull request #30 from jkaninda/go_migration
Migrate project to Go
2024-01-18 14:20:29 +01:00
d587c18e75 Migrate project to Go 2024-01-18 14:19:27 +01:00
508ca68366 Merge pull request #29 from jkaninda/develop
docs: add recommendation for backup user
2024-01-14 12:35:00 +01:00
a113b40126 docs: add recommendation for backup user 2024-01-14 12:33:44 +01:00
1c2a9fccc6 Merge pull request #28 from jkaninda/develop
Develop
2024-01-11 19:40:20 +01:00
c3f17b3d85 feat: add database backup verification 2024-01-11 19:39:50 +01:00
2a8ad3a6e2 feat: add database backup verification 2024-01-11 19:38:13 +01:00
cc15452ccf Merge pull request #27 from jkaninda/develop
Refactoring
2023-12-27 21:49:12 +01:00
bcfc69e7f9 Refactoring 2023-12-27 21:48:24 +01:00
f3f859ae05 Merge pull request #26 from jkaninda/develop
Add database connection testing before running in scheduled mode
2023-12-27 21:29:02 +01:00
947b9fa888 Add database connection testing before running in scheduled mode 2023-12-27 21:27:24 +01:00
142a2a2dc3 Merge pull request #25 from jkaninda/develop
Update docker compose example files
2023-12-27 07:12:35 +01:00
548ba17bbb Update docker compose example files 2023-12-27 07:10:57 +01:00
b245fe1a11 Merge pull request #24 from jkaninda/develop
Update docker compose example files
2023-12-27 06:59:37 +01:00
0355be59e1 Update docker compose example files 2023-12-27 06:56:06 +01:00
f5bed16951 Merge pull request #23 from jkaninda/develop
Remove volume
2023-12-26 21:54:41 +01:00
25a32823b7 Remove volume 2023-12-26 21:54:04 +01:00
498f8ee545 Merge pull request #22 from jkaninda/jkaninda-patch-1
Delete .DS_Store
2023-12-26 21:43:02 +01:00
ae01fb0edb Delete .DS_Store 2023-12-26 21:42:27 +01:00
1f797e019d Merge pull request #21 from jkaninda/develop
Add deployment example
2023-12-26 21:42:09 +01:00
3ddc00dcbd Add deployment example 2023-12-26 21:41:43 +01:00
3d4a245181 Merge pull request #20 from jkaninda/develop
Add deployment example
2023-12-26 21:39:55 +01:00
df5ec79d85 Add deployment example 2023-12-26 21:39:03 +01:00
53f7a791fa Merge pull request #19 from jkaninda/develop
Update README.md
2023-12-26 21:30:06 +01:00
31d450e218 Update README.md 2023-12-26 21:28:39 +01:00
f21f726464 Merge pull request #18 from jkaninda/develop
Develop
2023-12-25 03:42:01 +01:00
f1e116a38b Update README.md 2023-12-25 03:41:28 +01:00
5dde6feace Update action 2023-12-25 02:32:36 +01:00
4421ca1916 Merge pull request #17 from jkaninda/develop
Refactoring of code
2023-12-25 02:32:07 +01:00
aafe505dc3 Refactoring of code 2023-12-25 02:31:44 +01:00
7dc7c42c5c Merge pull request #16 from jkaninda/develop
Refactoring of code
2023-12-24 19:27:16 +01:00
509d9ad3d5 Refactoring of code 2023-12-24 19:25:54 +01:00
193d46bde6 Merge pull request #15 from jkaninda/develop
Change Default cronjob time
2023-12-24 15:01:01 +01:00
b81c6fc04c Change Default cronjob time 2023-12-24 14:59:05 +01:00
d1cbe8bbda Merge pull request #14 from jkaninda/develop
Update version
2023-12-24 14:44:45 +01:00
275fecd837 Update version 2023-12-24 14:44:15 +01:00
4e0740ac3b Merge pull request #13 from jkaninda/develop
Merge pull request #12 from jkaninda/scheduled-mode
2023-12-24 12:31:09 +01:00
4d24713d2d Merge pull request #12 from jkaninda/scheduled-mode
Scheduled mode
2023-12-24 12:30:53 +01:00
934f5db75f Merge pull request #11 from jkaninda/scheduled-mode
Update README
2023-12-24 12:30:24 +01:00
d50e7dbdad Update version 2023-12-24 12:28:54 +01:00
df899209ac Merge pull request #10 from jkaninda/scheduled-mode
Scheduled mode
2023-12-24 12:19:41 +01:00
5c559b93fd Update version 2023-12-24 12:19:12 +01:00
771d4b7d12 Update version 2023-12-24 12:07:20 +01:00
a72bf856a2 Update README.md 2023-12-24 12:03:35 +01:00
4e5f51582f Update README.md 2023-12-24 12:02:02 +01:00
65e4b9709c Update README.md 2023-12-24 12:01:31 +01:00
6d3f4a2e36 Update README.md 2023-12-24 12:00:50 +01:00
75cc9dc584 Update README.md 2023-12-24 11:59:00 +01:00
d7ba0e13a8 Update README.md 2023-12-24 11:54:23 +01:00
0e65331675 Integret scheduled mode for periodic backups 2023-12-24 11:52:11 +01:00
acbbc6a7a2 Merge pull request #8 from jkaninda/develop
Develop
2023-12-23 22:22:17 +01:00
902be39db4 Refactoring of code 2023-12-23 22:21:15 +01:00
a3d34a6600 Clean code 2023-12-23 22:06:56 +01:00
5fdc402497 Merge pull request #7 from jkaninda/develop
Develop
2023-12-22 10:46:48 +01:00
871d5b5b57 Update README 2023-12-22 10:46:13 +01:00
c4c6b58fa7 Update README 2023-12-22 10:45:45 +01:00
a0144f817f Update README 2023-12-22 10:44:38 +01:00
6372da013a Merge pull request #6 from jkaninda/develop
Update README
2023-12-22 10:29:41 +01:00
3572b24f18 Update README 2023-12-22 10:28:49 +01:00
8922ad1aaf Merge pull request #5 from jkaninda/develop
Update README
2023-12-22 10:27:04 +01:00
87c9104640 Update README 2023-12-22 10:26:40 +01:00
30755c4293 Merge pull request #4 from jkaninda/develop
Update README
2023-12-22 06:16:47 +01:00
cff384c6e9 Update README.md 2023-12-22 06:16:15 +01:00
d7aa63929b Refactoring 2023-12-22 06:07:47 +01:00
7dc86c6cde Update action 2023-12-22 06:06:10 +01:00
c8ffec2dc9 Refactoring 2023-12-22 06:04:35 +01:00
35 changed files with 1233 additions and 379 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -32,8 +32,8 @@ jobs:
uses: docker/build-push-action@v3
with:
push: true
file: "./src/docker/Dockerfile"
file: "./docker/Dockerfile"
platforms: linux/amd64,linux/arm64
tags: |
"${{env.BUILDKIT_IMAGE}}:v0.7"
"${{env.BUILDKIT_IMAGE}}:latest"
"${{env.BUILDKIT_IMAGE}}:v0.1"

8
.gitignore vendored
View File

@@ -2,4 +2,10 @@
backup
data
compose.yaml
.env
.env
test.md
.DS_Store
mysql-bkup
/.DS_Store
/.idea
bin

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Jonas Kaninda
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

32
Makefile Normal file
View File

@@ -0,0 +1,32 @@
BINARY_NAME=mysql-bkup
include .env
export
run:
go run .
build:
go build -o bin/${BINARY_NAME} .
compile:
GOOS=darwin GOARCH=arm64 go build -o bin/${BINARY_NAME}-darwin-arm64 .
GOOS=darwin GOARCH=amd64 go build -o bin/${BINARY_NAME}-darwin-amd64 .
GOOS=linux GOARCH=arm64 go build -o bin/${BINARY_NAME}-linux-arm64 .
GOOS=linux GOARCH=amd64 go build -o bin/${BINARY_NAME}-linux-amd64 .
docker-build:
docker build -f docker/Dockerfile -t jkaninda/mysql-bkup:latest .
docker-run: docker-build
docker run --rm --network internal --privileged --device /dev/fuse --name mysql-bkup -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" jkaninda/mysql-bkup bkup backup --prune --keep-last 2
docker-run-scheduled: docker-build
docker run --rm --network internal --privileged --device /dev/fuse --name mysql-bkup -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" -v "./backup:/backup" jkaninda/mysql-bkup bkup backup --prune --keep-last=2 --mode scheduled --period "* * * * *"
docker-run-scheduled-s3: docker-build
docker run --rm --network internal --privileged --device /dev/fuse --name mysql-bkup -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" -e "ACCESS_KEY=${ACCESS_KEY}" -e "SECRET_KEY=${SECRET_KEY}" -e "BUCKET_NAME=${BUCKET_NAME}" -e "S3_ENDPOINT=${S3_ENDPOINT}" jkaninda/mysql-bkup bkup backup --storage s3 --mode scheduled --path /custom-path --period "* * * * *"
docker-restore-s3: docker-build
docker run --rm --network internal --privileged --device /dev/fuse --name mysql-bkup -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" -e "ACCESS_KEY=${ACCESS_KEY}" -e "SECRET_KEY=${SECRET_KEY}" -e "BUCKET_NAME=${BUCKET_NAME}" -e "S3_ENDPOINT=${S3_ENDPOINT}" -e "FILE_NAME=${FILE_NAME}" jkaninda/mysql-bkup bkup restore --storage s3 --path /custom-path

286
README.md
View File

@@ -1,54 +1,120 @@
# MySQL Backup
MySQL Backup tool, backup database to S3 or Object Storage
- Docker
- Kubernetes
MySQL Backup and Restoration tool. Backup database to AWS S3 storage or any S3 Alternatives for Object Storage.
[![Build](https://github.com/jkaninda/mysql-bkup/actions/workflows/build.yml/badge.svg)](https://github.com/jkaninda/mysql-bkup/actions/workflows/build.yml)
[![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 Pulls](https://img.shields.io/docker/pulls/jkaninda/mysql-bkup?style=flat-square)
<p align="center">
<a href="https://github.com/jkaninda/mysql-bkup">
<img src="https://www.mysql.com/common/logos/logo-mysql-170x115.png" alt="Logo">
</a>
</p>
> Runs on:
- Docker
- Kubernetes
> Links:
- [Docker Hub](https://hub.docker.com/r/jkaninda/mysql-bkup)
- [Github](https://github.com/jkaninda/mysql-bkup)
## PostgreSQL solution :
- [PostgreSQL](https://github.com/jkaninda/pg-bkup)
## Storage:
- local
- s3
- Object storage
## Volumes:
- /s3mnt => S3 mounting path
- /backup => local storage mounting path
## Usage
| Options | Shorts | Usage |
|---------------|--------|------------------------------------|
| mysql_bkup | bkup | CLI utility |
| --operation | -o | Set operation. backup or restore (default: backup) |
| --storage | -s | Set storage. local or s3 (default: local) |
| --file | -f | Set file name for restoration |
| --path | | Set s3 path without file name. eg: /custom_path |
| --database | -db | Set database name |
| --port | -p | Set database port (default: 3306) |
| --timeout | -t | Set timeout (default: 60s) |
| --help | -h | Print this help message and exit |
| --version | -V | Print version information and exit |
| Options | Shorts | Usage |
|-----------------------|--------|-----------------------------------------------------------------------|
| mysql-bkup | bkup | CLI utility |
| backup | | Backup database operation |
| restore | | Restore database operation |
| history | | Show the history of backup |
| --storage | -s | Set storage. local or s3 (default: local) |
| --file | -f | Set file name for restoration |
| --path | | Set s3 path without file name. eg: /custom_path |
| --dbname | -d | Set database name |
| --port | -p | Set database port (default: 3306) |
| --mode | -m | Set execution mode. default or scheduled (default: default) |
| --disable-compression | | Disable database backup compression |
| --prune | | Delete old backup |
| --keep-last | | keep all backup and delete within this time interval, default 7 days |
| --period | | Set crontab period for scheduled mode only. (default: "0 1 * * *") |
| --timeout | -t | Set timeout (default: 60s) |
| --help | -h | Print this help message and exit |
| --version | -V | Print version information and exit |
## Environment variables
| Name | Requirement | Description |
|-------------|--------------------------------------------------|----------------------|
| DB_PORT | Optional, default 3306 | Database port number |
| DB_HOST | Required | Database host |
| DB_NAME | Optional if it was provided from the -d flag | Database name |
| DB_USERNAME | Required | Database user name |
| DB_PASSWORD | Required | Database password |
| ACCESS_KEY | Optional, required for S3 storage | AWS S3 Access Key |
| SECRET_KEY | Optional, required for S3 storage | AWS S3 Secret Key |
| BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
| S3_ENDPOINT | Optional, required for S3 storage | AWS S3 Endpoint |
| FILE_NAME | Optional if it was provided from the --file flag | File to restore |
## Note:
Creating a user for backup tasks who has read-only access is recommended!
> create read-only user
```sh
mysql -u root -p
```
```sql
CREATE USER read_only_user IDENTIFIED BY 'your_strong_password';
```
```sql
GRANT SELECT, SHOW VIEW ON *.* TO read_only_user;
```
```sql
FLUSH PRIVILEGES;
```
## Backup database :
Simple backup usage
```sh
bkup --operation backup
mysql-bkup backup --dbname database_name
```
```sh
bkup -o backup
mysql-bkup backup -d database_name
```
### S3
```sh
bkup --operation backup --storage s3
mysql-bkup backup --storage s3 --dbname database_name
```
## Docker run:
```sh
docker run --rm --network your_network_name --name mysql-bkup -v $PWD/backup:/backup/ -e "DB_HOST=database_host_name" -e "DB_USERNAME=username" -e "DB_PASSWORD=password" jkaninda/mysql-bkup:latest bkup -o backup -db database_name
docker run --rm --network your_network_name --name mysql-bkup -v $PWD/backup:/backup/ -e "DB_HOST=database_host_name" -e "DB_USERNAME=username" -e "DB_PASSWORD=password" jkaninda/mysql-bkup:latest mysql-bkup backup -d database_name
```
## Docker compose file:
@@ -57,25 +123,24 @@ version: '3'
services:
mariadb:
container_name: mariadb
image: mariadb:latest
image: mariadb
environment:
MYSQL_DATABASE: mariadb
MYSQL_USER: mariadb
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password
mysql-bkup:
image: jkaninda/mysql-bkup:latest
image: jkaninda/mysql-bkup
container_name: mysql-bkup
command:
- /bin/sh
- -c
- bkup --operation backup -db mariadb
- mysql-bkup backup -d database_name
volumes:
- ./backup:/backup
environment:
- DB_PORT=3306
- DB_HOST=mariadb
- DB_DATABASE=mariadb
- DB_USERNAME=mariadb
- DB_PASSWORD=password
```
@@ -84,22 +149,22 @@ services:
Simple database restore operation usage
```sh
bkup --operation restore --file database_20231217_115621.sql
mysql-bkup restore --dbname database_name --file database_20231217_115621.sql
```
```sh
bkup -o restore -f database_20231217_115621.sql
mysql-bkup restore -f database_20231217_115621.sql
```
### S3
```sh
bkup --operation restore --storage s3 --file database_20231217_115621.sql
mysql-bkup restore --storage s3 --file database_20231217_115621.sql
```
## Docker run:
```sh
docker run --rm --network your_network_name --name mysql-bkup -v $PWD/backup:/backup/ -e "DB_HOST=database_host_name" -e "DB_USERNAME=username" -e "DB_PASSWORD=password" jkaninda/mysql-bkup:latest bkup -o backup -db database_name -f napata_20231219_022941.sql.gz
docker run --rm --network your_network_name --name mysql-bkup -v $PWD/backup:/backup/ -e "DB_HOST=database_host_name" -e "DB_USERNAME=username" -e "DB_PASSWORD=password" jkaninda/mysql-bkup mysql-bkup backup -d database_name -f db_20231219_022941.sql.gz
```
## Docker compose file:
@@ -116,19 +181,19 @@ services:
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password
mysql-bkup:
image: jkaninda/mysql-bkup:latest
image: jkaninda/mysql-bkup
container_name: mysql-bkup
command:
- /bin/sh
- -c
- bkup --operation restore --file database_20231217_115621.sql
- mysql-bkup restore --file database_20231217_115621.sql --dbname database_name
volumes:
- ./backup:/backup
environment:
#- FILE_NAME=mariadb_20231217_040238.sql # Optional if file name is set from command
- DB_PORT=3306
- DB_HOST=mariadb
- DB_DATABASE=mariadb
- DB_NAME=mariadb
- DB_USERNAME=mariadb
- DB_PASSWORD=password
```
@@ -140,66 +205,126 @@ docker-compose up -d
## Backup to S3
```sh
docker run --rm --privileged --device /dev/fuse --name mysql-bkup -e "DB_HOST=db_hostname" -e "DB_USERNAME=username" -e "DB_PASSWORD=password" -e "ACCESS_KEY=your_access_key" -e "SECRET_KEY=your_secret_key" -e "BUCKETNAME=your_bucket_name" -e "S3_ENDPOINT=https://eu2.contabostorage.com" jkaninda/mysql-bkup:latest bkup -o backup -s s3 -db invoice
docker run --rm --privileged --device /dev/fuse --name mysql-bkup -e "DB_HOST=db_hostname" -e "DB_USERNAME=username" -e "DB_PASSWORD=password" -e "ACCESS_KEY=your_access_key" -e "SECRET_KEY=your_secret_key" -e "BUCKETNAME=your_bucket_name" -e "S3_ENDPOINT=https://s3.us-west-2.amazonaws.com" jkaninda/mysql-bkup mysql-bkup backup -s s3 -d database_name
```
> To change s3 backup path add this flag : --path myPath . default path is /mysql_bkup
> To change s3 backup path add this flag : --path /myPath . default path is /mysql_bkup
Simple S3 backup usage
```sh
bkup --operation backup --storage s3 --database mydatabase
bkup backup --storage s3 --dbname mydatabase
```
```yaml
version: '3'
services:
mysql-bkup:
image: jkaninda/mysql-bkup:latest
image: jkaninda/mysql-bkup
container_name: mysql-bkup
tty: true
privileged: true
devices:
- "/dev/fuse"
command:
- /bin/sh
- -c
- mysql_bkup --operation restore --source s3 -f database_20231217_115621.sql.gz
- mysql-bkup restore --storage s3 -f database_20231217_115621.sql.gz
environment:
- DB_PORT=3306
- DB_HOST=mysql
- DB_DATABASE=mariadb
- DB_NAME=mariadb
- DB_USERNAME=mariadb
- DB_PASSWORD=password
- ACCESS_KEY=${ACCESS_KEY}
- SECRET_KEY=${SECRET_KEY}
- BUCKETNAME=${BUCKETNAME}
- BUCKET_NAME=${BUCKET_NAME}
- S3_ENDPOINT=${S3_ENDPOINT}
```
## Run "docker run" from crontab
## Run in Scheduled mode
Make an automated backup (every night at 1).
This tool can be run as CronJob in Kubernetes for a regular backup which makes deployment on Kubernetes easy as Kubernetes has CronJob resources.
For Docker, you need to run it in scheduled mode by adding `--mode scheduled` flag and specify the periodical backup time by adding `--period "0 1 * * *"` flag.
> backup_script.sh
Make an automated backup on Docker
```sh
#!/bin/sh
DB_USERNAME='db_username'
DB_PASSWORD='password'
DB_HOST='db_hostname'
DB_NAME='db_name'
BACKUP_DIR='/some/path/backup/'
## Syntax of crontab (field description)
docker run --rm --name mysql-bkup -v $BACKUP_DIR:/backup/ -e "DB_HOST=$DB_HOST" -e "DB_USERNAME=$DB_USERNAME" -e "DB_PASSWORD=$DB_PASSWORD" jkaninda/mysql-bkup:latest bkup -o backup -db $DB_NAME
```
The syntax is:
```sh
chmod +x backup_script.sh
```
- 1: Minute (0-59)
- 2: Hours (0-23)
- 3: Day (0-31)
- 4: Month (0-12 [12 == December])
- 5: Day of the week(0-7 [7 or 0 == sunday])
Your crontab looks like this:
Easy to remember format:
```conf
0 1 * * * /path/to/backup_script.sh
* * * * * command to be executed
```
```conf
- - - - -
| | | | |
| | | | ----- Day of week (0 - 7) (Sunday=0 or 7)
| | | ------- Month (1 - 12)
| | --------- Day of month (1 - 31)
| ----------- Hour (0 - 23)
------------- Minute (0 - 59)
```
> At every 30th minute
```conf
*/30 * * * *
```
> “At minute 0.” every hour
```conf
0 * * * *
```
> “At 01:00.” every day
```conf
0 1 * * *
```
## Example of scheduled mode
> Docker run :
```sh
docker run --rm --name mysql-bkup -v $BACKUP_DIR:/backup/ -e "DB_HOST=$DB_HOST" -e "DB_USERNAME=$DB_USERNAME" -e "DB_PASSWORD=$DB_PASSWORD" jkaninda/mysql-bkup mysql-bkup backup --dbname $DB_NAME --mode scheduled --period "0 1 * * *"
```
> With Docker compose
```yaml
version: "3"
services:
mysql-bkup:
image: jkaninda/mysql-bkup
container_name: mysql-bkup
privileged: true
devices:
- "/dev/fuse"
command:
- /bin/sh
- -c
- mysql-bkup backup --storage s3 --path /mys3_custome_path --dbname database_name --mode scheduled --period "*/30 * * * *"
environment:
- DB_PORT=3306
- DB_HOST=mysqlhost
- DB_USERNAME=userName
- DB_PASSWORD=${DB_PASSWORD}
- ACCESS_KEY=${ACCESS_KEY}
- SECRET_KEY=${SECRET_KEY}
- BUCKET_NAME=${BUCKET_NAME}
- S3_ENDPOINT=${S3_ENDPOINT}
```
## Kubernetes CronJob
For Kubernetes you don't need to run it in scheduled mode.
Simple Kubernetes CronJob usage:
@@ -209,30 +334,57 @@ kind: CronJob
metadata:
name: mysql-bkup-job
spec:
schedule: "0 0 * * *"
schedule: "0 1 * * *"
jobTemplate:
spec:
template:
spec:
backoffLimit: 4
containers:
- name: mysql-bkup
image: jkaninda/mysql-bkup:latest
image: jkaninda/mysql-bkup
securityContext:
privileged: true
command:
- /bin/sh
- -c
- bkup --operation backup
- mysql-bkup backup -s s3 --path /custom_path
env:
- name: DB_PORT
value: "3306"
value: "3306"
- name: DB_HOST
value: "mysql-svc"
- name: DB_DATABASE
value: "mariadb"
value: ""
- name: DB_NAME
value: ""
- name: DB_USERNAME
value: "mariadb"
# Please use secret instead!
value: ""
# Please use secret!
- name: DB_PASSWORD
value: "password"
- name: ACCESS_KEY
value: ""
- name: SECRET_KEY
value: ""
- name: BUCKET_NAME
value: ""
- name: S3_ENDPOINT
value: "https://s3.us-west-2.amazonaws.com"
restartPolicy: Never
```
```
## Contributing
Contributions are welcome! If you encounter any issues or have suggestions for improvements, please create an issue or submit a pull request.
Make sure to follow the existing coding style and provide tests for your changes.
## License
This project is licensed under the MIT License. See the LICENSE file for details.
## Authors
**Jonas Kaninda**
- <https://github.com/jkaninda>
## Copyright
Copyright (c) [2023] [Jonas Kaninda]

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
if [ $# -eq 0 ]
then
tag='latest'
else
tag=$1
fi
docker build -f src/docker/Dockerfile -t jkaninda/mysql-bkup:$tag .
docker-compose up -d

30
cmd/backup.go Normal file
View File

@@ -0,0 +1,30 @@
package cmd
import (
"github.com/jkaninda/mysql-bkup/pkg"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
)
var BackupCmd = &cobra.Command{
Use: "backup ",
Short: "Backup database operation",
Example: utils.BackupExample,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
pkg.StartBackup(cmd)
} else {
utils.Fatal("Error, no argument required")
}
},
}
func init() {
//Backup
BackupCmd.PersistentFlags().StringP("mode", "m", "default", "Set execution mode. default or scheduled")
BackupCmd.PersistentFlags().StringP("period", "", "0 1 * * *", "Set schedule period time")
BackupCmd.PersistentFlags().BoolP("prune", "", false, "Prune old backup")
BackupCmd.PersistentFlags().IntP("keep-last", "", 7, "keep all backup and delete within this time interval, default 7 days")
BackupCmd.PersistentFlags().BoolP("disable-compression", "", false, "Disable backup compression")
}

14
cmd/history.go Normal file
View File

@@ -0,0 +1,14 @@
package cmd
import (
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
)
var HistoryCmd = &cobra.Command{
Use: "history",
Short: "Show the history of backup",
Run: func(cmd *cobra.Command, args []string) {
utils.ShowHistory()
},
}

28
cmd/restore.go Normal file
View File

@@ -0,0 +1,28 @@
package cmd
import (
"github.com/jkaninda/mysql-bkup/pkg"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
)
var RestoreCmd = &cobra.Command{
Use: "restore",
Short: "Restore database operation",
Example: utils.RestoreExample,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
pkg.StartRestore(cmd)
} else {
utils.Fatal("Error, no argument required")
}
},
}
func init() {
//Restore
RestoreCmd.PersistentFlags().StringP("file", "f", "", "File name of database")
}

46
cmd/root.go Normal file
View File

@@ -0,0 +1,46 @@
// Package cmd /*
/*
Copyright © 2024 Jonas Kaninda <jonaskaninda@gmail.com>
*/
package cmd
import (
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
"os"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "mysql-bkup [Command]",
Short: "MySQL Backup tool, backup database to S3 or Object Storage",
Long: `MySQL Database backup and restoration tool. Backup database to AWS S3 storage or any S3 Alternatives for Object Storage.`,
Example: utils.MainExample,
Version: appVersion,
}
var operation = ""
var s3Path = "/mysql-bkup"
// 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.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.PersistentFlags().StringP("storage", "s", "local", "Set storage. local or s3")
rootCmd.PersistentFlags().StringP("path", "P", s3Path, "Set s3 path, without file name. for S3 storage only")
rootCmd.PersistentFlags().StringP("dbname", "d", "", "Set database name")
rootCmd.PersistentFlags().IntP("timeout", "t", 30, "Set timeout")
rootCmd.PersistentFlags().IntP("port", "p", 3306, "Set database port")
rootCmd.PersistentFlags().StringVarP(&operation, "operation", "o", "", "Set operation, for old version only")
rootCmd.AddCommand(VersionCmd)
rootCmd.AddCommand(BackupCmd)
rootCmd.AddCommand(RestoreCmd)
rootCmd.AddCommand(S3MountCmd)
rootCmd.AddCommand(HistoryCmd)
}

14
cmd/s3mount.go Normal file
View File

@@ -0,0 +1,14 @@
package cmd
import (
"github.com/jkaninda/mysql-bkup/pkg"
"github.com/spf13/cobra"
)
var S3MountCmd = &cobra.Command{
Use: "s3mount",
Short: "Mount AWS S3 storage",
Run: func(cmd *cobra.Command, args []string) {
pkg.S3Mount()
},
}

26
cmd/version.go Normal file
View File

@@ -0,0 +1,26 @@
package cmd
/*
Copyright © 2024 Jonas Kaninda <jonaskaninda@gmail.com>
*/
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
var appVersion = os.Getenv("VERSION")
var VersionCmd = &cobra.Command{
Use: "version",
Short: "Show version",
Run: func(cmd *cobra.Command, args []string) {
Version()
},
}
func Version() {
fmt.Printf("Version: %s \n", appVersion)
fmt.Println()
}

View File

@@ -1,26 +0,0 @@
version: '3'
services:
mariadb:
container_name: mariadb
image: mariadb:latest
environment:
MYSQL_DATABASE: mariadb
MYSQL_USER: mariadb
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password
mysql-bkup:
image: jkaninda/mysql-bkup:latest
container_name: mysql-bkup
command:
- /bin/sh
- -c
- bkup --operation backup
volumes:
- ./backup:/backup
environment:
#- FILE_NAME=mariadb_20231217_040238.sql # Optional if file name is set from command
- DB_PORT=3306
- DB_HOST=mariadb
- DB_DATABASE=mariadb
- DB_USERNAME=mariadb
- DB_PASSWORD=password

50
docker/Dockerfile Normal file
View File

@@ -0,0 +1,50 @@
FROM golang:1.21.0 AS build
WORKDIR /app
# Copy the source code.
COPY . .
# Installs Go dependencies
RUN go mod download
# Build
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/mysql-bkup
FROM ubuntu:24.04
ENV DB_HOST=""
ENV DB_NAME=""
ENV DB_USERNAME=""
ENV DB_PASSWORD=""
ENV DB_PORT="3306"
ENV STORAGE=local
ENV BUCKET_NAME=""
ENV ACCESS_KEY=""
ENV SECRET_KEY=""
ENV S3_ENDPOINT=https://s3.amazonaws.com
ARG DEBIAN_FRONTEND=noninteractive
ENV VERSION="v0.6"
LABEL authors="Jonas Kaninda"
RUN apt-get update -qq
#RUN apt-get install build-essential libcurl4-openssl-dev libxml2-dev mime-support -y
RUN apt install s3fs mysql-client supervisor cron -y
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
RUN mkdir /s3mnt
RUN mkdir /tmp/s3cache
RUN chmod 777 /s3mnt
RUN chmod 777 /tmp/s3cache
COPY --from=build /app/mysql-bkup /usr/local/bin/mysql-bkup
RUN chmod +x /usr/local/bin/mysql-bkup
RUN ln -s /usr/local/bin/mysql-bkup /usr/local/bin/bkup
RUN ln -s /usr/local/bin/mysql-bkup /usr/local/bin/mysql_bkup
ADD docker/supervisord.conf /etc/supervisor/supervisord.conf
RUN mkdir /backup
WORKDIR /backup

13
docker/supervisord.conf Normal file
View File

@@ -0,0 +1,13 @@
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
[program:cron]
command = /bin/bash -c "declare -p | grep -Ev '^declare -[[:alpha:]]*r' > /run/supervisord.env && /usr/sbin/cron -f -L 15"
autostart=true
autorestart=true
user = root
stderr_logfile=/var/log/cron.err.log
stdout_logfile=/var/log/cron.out.log

View File

@@ -0,0 +1,21 @@
version: "3"
services:
mysql-bkup:
image: jkaninda/mysql-bkup
container_name: mysql-bkup
privileged: true
devices:
- "/dev/fuse"
command:
- /bin/sh
- -c
- mysql-bkup backup --storage s3 --path /mys3_custom_path --dbname database_name
environment:
- DB_PORT=3306
- DB_HOST=mysqlhost
- DB_USERNAME=userName
- DB_PASSWORD=${DB_PASSWORD}
- ACCESS_KEY=${ACCESS_KEY}
- SECRET_KEY=${SECRET_KEY}
- BUCKET_NAME=${BUCKET_NAME}
- S3_ENDPOINT=https://s3.us-west-2.amazonaws.com

View File

@@ -0,0 +1,16 @@
version: "3"
services:
mysql-bkup:
image: jkaninda/mysql-bkup
container_name: mysql-bkup
command:
- /bin/sh
- -c
- mysql-bkup backup --dbname database_name --mode scheduled --period "0 1 * * *"
volumes:
- ./backup:/backup
environment:
- DB_PORT=3306
- DB_HOST=mysqlhost
- DB_USERNAME=userName
- DB_PASSWORD=${DB_PASSWORD}

View File

@@ -0,0 +1,21 @@
version: "3"
services:
mysql-bkup:
image: jkaninda/mysql-bkup
container_name: mysql-bkup
privileged: true
devices:
- "/dev/fuse"
command:
- /bin/sh
- -c
- mysql-bkup backup --storage s3 --path /mys3_custom_path --dbname database_name --mode scheduled --period "0 1 * * *"
environment:
- DB_PORT=3306
- DB_HOST=mysqlhost
- DB_USERNAME=userName
- DB_PASSWORD=${DB_PASSWORD}
- ACCESS_KEY=${ACCESS_KEY}
- SECRET_KEY=${SECRET_KEY}
- BUCKET_NAME=${BUCKET_NAME}
- S3_ENDPOINT=https://s3.us-west-2.amazonaws.com

View File

@@ -0,0 +1,16 @@
version: "3"
services:
mysql-bkup:
image: jkaninda/mysql-bkup
container_name: mysql-bkup
command:
- /bin/sh
- -c
- mysql-bkup backup --dbname database_name
volumes:
- ./backup:/backup
environment:
- DB_PORT=3306
- DB_HOST=mysqlhost
- DB_USERNAME=userName
- DB_PASSWORD=${DB_PASSWORD}

40
examples/k8s-job.yaml Normal file
View File

@@ -0,0 +1,40 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: db-bkup-job
spec:
schedule: "0 1 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: mysql-bkup
image: jkaninda/mysql-bkup
securityContext:
privileged: true
command:
- /bin/sh
- -c
- mysql-bkup backup --storage s3 --path /custom_path
env:
- name: DB_PORT
value: "3306"
- name: DB_HOST
value: ""
- name: DB_NAME
value: ""
- name: DB_USERNAME
value: ""
# Please use secret!
- name: DB_PASSWORD
value: "password"
- name: ACCESS_KEY
value: ""
- name: SECRET_KEY
value: ""
- name: BUCKETNAME
value: ""
- name: S3_ENDPOINT
value: "https://s3.us-west-2.amazonaws.com"
restartPolicy: Never

10
go.mod Normal file
View File

@@ -0,0 +1,10 @@
module github.com/jkaninda/mysql-bkup
go 1.21.0
require github.com/spf13/pflag v1.0.5
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
)

10
go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,31 +0,0 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-bkup-job
spec:
schedule: "0 0 * * *"
jobTemplate:
spec:
template:
spec:
backoffLimit: 4
containers:
- name: mysql-bkup
image: jkaninda/mysql-bkup:latest
command:
- /bin/sh
- -c
- bkup --operation backup
env:
- name: DB_PORT
value: "3306"
- name: DB_HOST
value: "mysql-svc"
- name: DB_DATABASE
value: "mariadb"
- name: DB_USERNAME
value: "mariadb"
# Please use secret!
- name: DB_PASSWORD
value: "password"
restartPolicy: Never

16
main.go Normal file
View File

@@ -0,0 +1,16 @@
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"
func main() {
cmd.Execute()
}

198
pkg/backup.go Normal file
View File

@@ -0,0 +1,198 @@
// Package pkg /*
/*
Copyright © 2024 Jonas Kaninda <jonaskaninda.gmail.com>
*/
package pkg
import (
"fmt"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
"log"
"os"
"os/exec"
"path/filepath"
"time"
)
func StartBackup(cmd *cobra.Command) {
//Set env
utils.SetEnv("STORAGE_PATH", storagePath)
utils.GetEnv(cmd, "dbname", "DB_NAME")
utils.GetEnv(cmd, "port", "DB_PORT")
utils.GetEnv(cmd, "period", "SCHEDULE_PERIOD")
//Get flag value and set env
s3Path = utils.GetEnv(cmd, "path", "S3_PATH")
storage = utils.GetEnv(cmd, "storage", "STORAGE")
file = utils.GetEnv(cmd, "file", "FILE_NAME")
disableCompression, _ = cmd.Flags().GetBool("disable-compression")
keepLast, _ := cmd.Flags().GetInt("keep-last")
prune, _ := cmd.Flags().GetBool("prune")
executionMode, _ = cmd.Flags().GetString("mode")
if executionMode == "default" {
if storage == "s3" {
utils.Info("Backup database to s3 storage")
s3Backup(disableCompression, s3Path, prune, keepLast)
} else {
utils.Info("Backup database to local storage")
BackupDatabase(disableCompression, prune, keepLast)
}
} else if executionMode == "scheduled" {
scheduledMode()
} else {
utils.Fatal("Error, unknown execution mode!")
}
}
// Run in scheduled mode
func scheduledMode() {
fmt.Println()
fmt.Println("**********************************")
fmt.Println(" Starting MySQL Bkup... ")
fmt.Println("***********************************")
utils.Info("Running in Scheduled mode")
utils.Info("Log file in /var/log/mysql-bkup.log")
utils.Info("Execution period ", os.Getenv("SCHEDULE_PERIOD"))
//Test database connexion
utils.TestDatabaseConnection()
utils.Info("Creating backup job...")
CreateCrontabScript(disableCompression, storage)
//Start Supervisor
supervisordCmd := exec.Command("supervisord", "-c", "/etc/supervisor/supervisord.conf")
if err := supervisordCmd.Run(); err != nil {
utils.Fatalf("Error starting supervisord: %v\n", err)
}
}
// BackupDatabase backup database
func BackupDatabase(disableCompression bool, prune bool, keepLast int) {
dbHost = os.Getenv("DB_HOST")
dbPassword := os.Getenv("DB_PASSWORD")
dbUserName := os.Getenv("DB_USERNAME")
dbName = os.Getenv("DB_NAME")
dbPort = os.Getenv("DB_PORT")
storagePath = os.Getenv("STORAGE_PATH")
if os.Getenv("DB_HOST") == "" || os.Getenv("DB_NAME") == "" || os.Getenv("DB_USERNAME") == "" || os.Getenv("DB_PASSWORD") == "" {
utils.Fatal("Please make sure all required environment variables for database are set")
} else {
utils.TestDatabaseConnection()
// Backup Database database
utils.Info("Backing up database...")
//Generate file name
bkFileName := fmt.Sprintf("%s_%s.sql.gz", dbName, time.Now().Format("20060102_150405"))
// Verify is compression is disabled
if disableCompression {
//Generate file name
bkFileName = fmt.Sprintf("%s_%s.sql", dbName, time.Now().Format("20060102_150405"))
// Execute mysqldump
cmd := exec.Command("mysqldump",
"-h", dbHost,
"-P", dbPort,
"-u", dbUserName,
"--password="+dbPassword,
dbName,
)
output, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
// save output
file, err := os.Create(fmt.Sprintf("%s/%s", storagePath, bkFileName))
if err != nil {
log.Fatal(err)
}
defer file.Close()
_, err = file.Write(output)
if err != nil {
log.Fatal(err)
}
utils.Done("Database has been backed up")
} else {
// Execute mysqldump
cmd := exec.Command("mysqldump", "-h", dbHost, "-P", dbPort, "-u", dbUserName, "--password="+dbPassword, dbName)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
gzipCmd := exec.Command("gzip")
gzipCmd.Stdin = stdout
gzipCmd.Stdout, err = os.Create(fmt.Sprintf("%s/%s", storagePath, bkFileName))
gzipCmd.Start()
if err != nil {
log.Fatal(err)
}
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
if err := gzipCmd.Wait(); err != nil {
log.Fatal(err)
}
utils.Done("Database has been backed up")
//Delete old backup
if prune {
deleteOldBackup(keepLast)
}
}
historyFile, err := os.OpenFile(fmt.Sprintf("%s/history.txt", storagePath), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer historyFile.Close()
if _, err := historyFile.WriteString(bkFileName + "\n"); err != nil {
log.Fatal(err)
}
}
}
func s3Backup(disableCompression bool, s3Path string, prune bool, keepLast int) {
// Backup Database to S3 storage
MountS3Storage(s3Path)
BackupDatabase(disableCompression, prune, keepLast)
}
func deleteOldBackup(keepLast int) {
utils.Info("Deleting old backups...")
storagePath = os.Getenv("STORAGE_PATH")
// Define the directory path
backupDir := storagePath + "/"
// Get current time
currentTime := time.Now()
// Walk through files in the directory
err := filepath.Walk(backupDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Check if the file is older than defined day days
if info.Mode().IsRegular() && info.ModTime().Before(currentTime.AddDate(0, 0, -keepLast)) {
// Remove the file
err := os.Remove(path)
if err != nil {
utils.Fatal("Error removing file ", path, err)
} else {
utils.Done("Removed file: ", path)
}
}
return nil
})
if err != nil {
utils.Fatal("Error walking through directory: ", err)
}
}

86
pkg/restore.go Normal file
View File

@@ -0,0 +1,86 @@
package pkg
import (
"fmt"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
"os"
"os/exec"
"path/filepath"
)
func StartRestore(cmd *cobra.Command) {
//Set env
utils.SetEnv("STORAGE_PATH", storagePath)
utils.GetEnv(cmd, "dbname", "DB_NAME")
utils.GetEnv(cmd, "port", "DB_PORT")
//Get flag value and set env
s3Path = utils.GetEnv(cmd, "path", "S3_PATH")
storage = utils.GetEnv(cmd, "storage", "STORAGE")
file = utils.GetEnv(cmd, "file", "FILE_NAME")
executionMode, _ = cmd.Flags().GetString("mode")
if storage == "s3" {
utils.Info("Restore database from s3")
s3Restore(file, s3Path)
} else {
utils.Info("Restore database from local")
RestoreDatabase(file)
}
}
// RestoreDatabase restore database
func RestoreDatabase(file string) {
dbHost = os.Getenv("DB_HOST")
dbName = os.Getenv("DB_NAME")
dbPort = os.Getenv("DB_PORT")
storagePath = os.Getenv("STORAGE_PATH")
if file == "" {
utils.Fatal("Error, file required")
}
if os.Getenv("DB_HOST") == "" || os.Getenv("DB_NAME") == "" || os.Getenv("DB_USERNAME") == "" || os.Getenv("DB_PASSWORD") == "" || file == "" {
utils.Fatal("Please make sure all required environment variables are set")
} else {
if utils.FileExists(fmt.Sprintf("%s/%s", storagePath, file)) {
utils.TestDatabaseConnection()
extension := filepath.Ext(fmt.Sprintf("%s/%s", storagePath, file))
// Restore from compressed file / .sql.gz
if extension == ".gz" {
str := "zcat " + fmt.Sprintf("%s/%s", storagePath, 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")
_, err := exec.Command("bash", "-c", str).Output()
if err != nil {
utils.Fatal("Error, in restoring the database")
}
utils.Done("Database has been restored")
} else if extension == ".sql" {
//Restore from sql file
str := "cat " + fmt.Sprintf("%s/%s", storagePath, 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")
_, err := exec.Command("bash", "-c", str).Output()
if err != nil {
utils.Fatal("Error, in restoring the database", err)
}
utils.Done("Database has been restored")
} else {
utils.Fatal("Unknown file extension ", extension)
}
} else {
utils.Fatal("File not found in ", fmt.Sprintf("%s/%s", storagePath, file))
}
}
}
func s3Restore(file, s3Path string) {
// Restore database from S3
MountS3Storage(s3Path)
RestoreDatabase(file)
}

80
pkg/s3fs.go Normal file
View File

@@ -0,0 +1,80 @@
// Package pkg /*
/*
Copyright © 2024 Jonas Kaninda <jonaskaninda.gmail.com>
*/
package pkg
import (
"fmt"
"github.com/jkaninda/mysql-bkup/utils"
"os"
"os/exec"
)
var (
accessKey = ""
secretKey = ""
bucketName = ""
s3Endpoint = ""
)
func S3Mount() {
MountS3Storage(s3Path)
}
// MountS3Storage Mount s3 storage using s3fs
func MountS3Storage(s3Path string) {
accessKey = os.Getenv("ACCESS_KEY")
secretKey = os.Getenv("SECRET_KEY")
bucketName = os.Getenv("BUCKET_NAME")
if bucketName == "" {
bucketName = os.Getenv("BUCKETNAME")
}
s3Endpoint = os.Getenv("S3_ENDPOINT")
if accessKey == "" || secretKey == "" || bucketName == "" {
utils.Fatal("Please make sure all environment variables are set for S3")
} else {
storagePath := fmt.Sprintf("%s%s", s3MountPath, s3Path)
err := os.Setenv("STORAGE_PATH", storagePath)
if err != nil {
return
}
//Write file
err = utils.WriteToFile(s3fsPasswdFile, fmt.Sprintf("%s:%s", accessKey, secretKey))
if err != nil {
utils.Fatal("Error creating file")
}
//Change file permission
utils.ChangePermission(s3fsPasswdFile, 0600)
//Mount object storage
utils.Info("Mounting Object storage in ", s3MountPath)
if isEmpty, _ := utils.IsDirEmpty(s3MountPath); isEmpty {
cmd := exec.Command("s3fs", bucketName, s3MountPath,
"-o", "passwd_file="+s3fsPasswdFile,
"-o", "use_cache=/tmp/s3cache",
"-o", "allow_other",
"-o", "url="+s3Endpoint,
"-o", "use_path_request_style",
)
if err := cmd.Run(); err != nil {
utils.Fatal("Error mounting Object storage:", err)
}
if err := os.MkdirAll(storagePath, os.ModePerm); err != nil {
utils.Fatalf("Error creating directory %v %v", storagePath, err)
}
} else {
utils.Info("Object storage already mounted in " + s3MountPath)
if err := os.MkdirAll(storagePath, os.ModePerm); err != nil {
utils.Fatal("Error creating directory "+storagePath, err)
}
}
}
}

76
pkg/scripts.go Normal file
View File

@@ -0,0 +1,76 @@
package pkg
// Package pkg /*
/*
Copyright © 2024 Jonas Kaninda <jonaskaninda.gmail.com>
*/
import (
"fmt"
"github.com/jkaninda/mysql-bkup/utils"
"os"
"os/exec"
)
const cronLogFile = "/var/log/mysql-bkup.log"
const backupCronFile = "/usr/local/bin/backup_cron.sh"
func CreateCrontabScript(disableCompression bool, storage string) {
//task := "/usr/local/bin/backup_cron.sh"
touchCmd := exec.Command("touch", backupCronFile)
if err := touchCmd.Run(); err != nil {
utils.Fatalf("Error creating file %s: %v\n", backupCronFile, err)
}
var disableC = ""
if disableCompression {
disableC = "--disable-compression"
}
var scriptContent string
if storage == "s3" {
scriptContent = fmt.Sprintf(`#!/usr/bin/env bash
set -e
bkup backup --dbname %s --port %s --storage s3 --path %s %v
`, os.Getenv("DB_NAME"), os.Getenv("DB_PORT"), os.Getenv("S3_PATH"), disableC)
} else {
scriptContent = fmt.Sprintf(`#!/usr/bin/env bash
set -e
bkup backup --dbname %s --port %s %v
`, os.Getenv("DB_NAME"), os.Getenv("DB_PORT"), disableC)
}
if err := utils.WriteToFile(backupCronFile, scriptContent); err != nil {
utils.Fatalf("Error writing to %s: %v\n", backupCronFile, err)
}
chmodCmd := exec.Command("chmod", "+x", "/usr/local/bin/backup_cron.sh")
if err := chmodCmd.Run(); err != nil {
utils.Fatalf("Error changing permissions of %s: %v\n", backupCronFile, err)
}
lnCmd := exec.Command("ln", "-s", "/usr/local/bin/backup_cron.sh", "/usr/local/bin/backup_cron")
if err := lnCmd.Run(); err != nil {
utils.Fatalf("Error creating symbolic link: %v\n", err)
}
cronJob := "/etc/cron.d/backup_cron"
touchCronCmd := exec.Command("touch", cronJob)
if err := touchCronCmd.Run(); err != nil {
utils.Fatalf("Error creating file %s: %v\n", cronJob, err)
}
cronContent := fmt.Sprintf(`%s root exec /bin/bash -c ". /run/supervisord.env; /usr/local/bin/backup_cron.sh >> %s"
`, os.Getenv("SCHEDULE_PERIOD"), cronLogFile)
if err := utils.WriteToFile(cronJob, cronContent); err != nil {
utils.Fatalf("Error writing to %s: %v\n", cronJob, err)
}
utils.ChangePermission("/etc/cron.d/backup_cron", 0644)
crontabCmd := exec.Command("crontab", "/etc/cron.d/backup_cron")
if err := crontabCmd.Run(); err != nil {
utils.Fatal("Error updating crontab: ", err)
}
utils.Info("Starting backup in scheduled mode")
}

16
pkg/var.go Normal file
View File

@@ -0,0 +1,16 @@
package pkg
const s3MountPath string = "/s3mnt"
const s3fsPasswdFile string = "/etc/passwd-s3fs"
var (
storage = "local"
file = ""
s3Path = "/mysql-bkup"
dbName = ""
dbHost = ""
dbPort = "3306"
executionMode = "default"
storagePath = "/backup"
disableCompression = false
)

View File

@@ -5,4 +5,4 @@ DB_HOST='db_hostname'
DB_NAME='db_name'
BACKUP_DIR="$PWD/backup"
docker run --rm --name mysql-bkup -v $BACKUP_DIR:/backup/ -e "DB_HOST=$DB_HOST" -e "DB_USERNAME=$DB_USERNAME" -e "DB_PASSWORD=$DB_PASSWORD" jkaninda/mysql-bkup:latest bkup -o backup -db $DB_NAME
docker run --rm --name mysql-bkup -v $BACKUP_DIR:/backup/ -e "DB_HOST=$DB_HOST" -e "DB_USERNAME=$DB_USERNAME" -e "DB_PASSWORD=$DB_PASSWORD" jkaninda/mysql-bkup:latest backup -d $DB_NAME

View File

@@ -1,35 +0,0 @@
FROM ubuntu:24.04
ENV DB_HOST=""
ENV DB_DATABASE=""
ENV DB_USERNAME=""
ENV DB_PASSWORD=""
ENV DB_PORT="3306"
ENV DESTINATION=local
ENV STORAGE=local
ENV SOURCE=local
ENV BUCKETNAME=""
ENV ACCESS_KEY=""
ENV SECRET_KEY=""
ENV S3_ENDPOINT=https://s3.amazonaws.com
ARG DEBIAN_FRONTEND=noninteractive
ENV VERSION="1.0"
RUN apt-get update -qq
RUN apt-get install build-essential libcurl4-openssl-dev libxml2-dev mime-support -y
RUN apt install s3fs mysql-client -y
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
RUN mkdir /s3mnt
RUN mkdir /tmp/s3cache
RUN chmod 777 /s3mnt
RUN chmod 777 /tmp/s3cache
COPY src/mysql_bkup.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/mysql_bkup.sh
RUN ln -s /usr/local/bin/mysql_bkup.sh /usr/local/bin/mysql_bkup
RUN ln -s /usr/local/bin/mysql_bkup.sh /usr/local/bin/bkup
RUN mkdir /backup
WORKDIR /backup

View File

@@ -1,205 +0,0 @@
#!/bin/sh
set -e
TIME=$(date +%Y%m%d_%H%M%S)
MY_SQL_DUMP=/usr/bin/mysqldump
arg0=$(basename "$0" .sh)
blnk=$(echo "$arg0" | sed 's/./ /g')
export OPERATION=backup
export DESTINATION=local
export STORAGE=local
export STORAGE_PATH=/backup
export SOURCE=local
export S3_PATH=/mysql-bkup
export TIMEOUT=60
export FILE_COMPRESION=true
usage_info()
{
echo "Usage: \\"
echo " $blnk Backup: mysql_bkup -o backup -d s3 \\"
echo " $blnk Restore: mysql_bkup -o restore -s s3 -f my_db.sql \\"
echo " $blnk [-o|--operation] [{-f|--file} ] [{-s|--storage} ] [{-h|--help} ] \\"
}
version_info()
{
echo "Version: $VERSION"
exit 0
}
usage()
{
exec 1>2 # Send standard output to standard error
usage_info
exit 0
}
error()
{
echo "$arg0: $*" >&2
exit 0
}
help()
{
echo
echo " -o |--operation -- Set operation (default: backup)"
echo " -s |--storage -- Set storage (default: local)"
echo " -f |--file -- Set file name "
echo " |--path -- Set s3 path, without file name"
echo " -db|--database -- Set database name "
echo " -p |--port -- Set database port (default: 3306)"
echo " -t |--timeout -- Set timeout (default: 120s)"
echo " -h |--help -- Print this help message and exit"
echo " -V |--version -- Print version information and exit"
exit 0
}
flags()
{
while test $# -gt 0
do
case "$1" in
(-o|--operation)
shift
[ $# = 0 ] && error "No operation specified - restore or backup"
export OPERATION="$1"
shift;;
(-d|--destination)
shift
[ $# = 0 ] && error "No destination specified - local or s3 | default local"
export DESTINATION="$1"
export SOURCE="$1"
export STORAGE="$1"
shift;;
(-s|--storage)
shift
[ $# = 0 ] && error "No storage specified - local or s3 | default local"
export SOURCE="$1"
export DESTINATION="$1"
export STORAGE="$1"
shift;;
(-f|--file)
shift
[ $# = 0 ] && error "No file specified - file to restore"
export FILE_NAME="$1"
shift;;
(--path)
shift
[ $# = 0 ] && error "No s3 path specified - s3 path without file name"
export S3_PATH="$1"
shift;;
(-db|--database)
shift
[ $# = 0 ] && error "No database name specified"
export DB_DATABASE="$1"
shift;;
(-p|--port)
shift
[ $# = 0 ] && error "No database name specified"
export DB_PORT="$1"
shift;;
(-t|--timeout)
shift
[ $# = 0 ] && error "No timeout specified"
export TIMEOUT="$1"
shift;;
(-h|--help)
help;;
(-V|--version)
version_info;;
(--)
help;;
(*) usage;;
esac
done
}
backup()
{
if [ -z "${DB_HOST}"] || [ -z "${DB_DATABASE}"] || [ -z "${DB_USERNAME}"] || [ -z "${DB_PASSWORD}"]; then
echo "Please make sure all required options are set "
else
## Test database connection
mysql -h ${DB_HOST} -P ${DB_PORT} -u ${DB_USERNAME} --password=${DB_PASSWORD} ${DB_DATABASE} -e"quit"
## Backup database
mysqldump -h ${DB_HOST} -P ${DB_PORT} -u ${DB_USERNAME} --password=${DB_PASSWORD} ${DB_DATABASE} | gzip > ${STORAGE_PATH}/${DB_DATABASE}_${TIME}.sql.gz
echo "Database has been saved"
fi
exit 0
}
restore()
{
if [ -z "${DB_HOST}" ] || [ -z "${DB_DATABASE}" ] || [ -z "${DB_USERNAME}" ] || [ -z "${DB_PASSWORD}" ]; then
echo "Please make sure all required options are set "
else
## Restore database
if [ -f "${STORAGE_PATH}/$FILE_NAME" ]; then
if gzip -t ${STORAGE_PATH}/$FILE_NAME; then
zcat ${STORAGE_PATH}/${FILE_NAME} | mysql -h ${DB_HOST} -P ${DB_PORT} -u ${DB_USERNAME} --password=${DB_PASSWORD} ${DB_DATABASE}
else
cat ${STORAGE_PATH}/${FILE_NAME} | mysql -h ${DB_HOST} -P ${DB_PORT} -u ${DB_USERNAME} --password=${DB_PASSWORD} ${DB_DATABASE}
fi
echo "Database has been restored"
else
echo "Error, file not found in ${STORAGE_PATH}/${FILE_NAME}"
fi
fi
exit
}
s3_backup()
{
mount_s3
backup
}
s3_restore()
{
mount_s3
restore
}
mount_s3()
{
if [ -z "${ACCESS_KEY}"] || [ -z "${SECRET_KEY}"]; then
echo "Please make sure all environment variables are set "
echo "BUCKETNAME=$BUCKETNAME \nACCESS_KEY=$nACCESS_KEY \nSECRET_KEY=$SECRET_KEY"
else
echo "$ACCESS_KEY:$SECRET_KEY" | tee /etc/passwd-s3fs
chmod 600 /etc/passwd-s3fs
echo "Mounting Object storage in /s3mnt .... "
if [ -z "$(ls -A /s3mnt)" ]; then
s3fs $BUCKETNAME /s3mnt -o passwd_file=/etc/passwd-s3fs -o use_cache=/tmp/s3cache -o allow_other -o url=$S3_ENDPOINT -o use_path_request_style
if [ ! -d "/s3mnt$S3_PATH" ]; then
mkdir -p /s3mnt$S3_PATH
fi
else
echo "Object storage already mounted in /s3mnt"
fi
export STORAGE_PATH=/s3mnt$S3_PATH
fi
}
flags "$@"
# ?
if [ $OPERATION != 'backup' ]
then
if [ $STORAGE != 's3' ]
then
echo "Restore from local"
restore
else
echo "Restore from s3"
s3_restore
fi
else
if [ $STORAGE != 's3' ]
then
echo "Backup to local destination"
backup
else
echo "Backup to s3 storage"
s3_backup
fi
fi

10
utils/constant.go Normal file
View File

@@ -0,0 +1,10 @@
package utils
const RestoreExample = "mysql-bkup restore --dbname database --file db_20231219_022941.sql.gz\n" +
"bkup restore --dbname database --storage s3 --path /custom-path --file db_20231219_022941.sql.gz"
const BackupExample = "mysql-bkup backup --dbname database --disable-compression\n" +
"mysql-bkup backup --dbname database --storage s3 --path /custom-path --disable-compression"
const MainExample = "mysql-bkup backup --dbname database --disable-compression\n" +
"mysql-bkup backup --dbname database --storage s3 --path /custom-path\n" +
"mysql-bkup restore --dbname database --file db_20231219_022941.sql.gz"

114
utils/utils.go Normal file
View File

@@ -0,0 +1,114 @@
package utils
/*****
* MySQL Backup & Restore
* @author Jonas Kaninda
* @license MIT License <https://opensource.org/licenses/MIT>
* @link https://github.com/jkaninda/mysql-bkup
**/
import (
"fmt"
"github.com/spf13/cobra"
"io/fs"
"os"
"os/exec"
)
func Info(v ...any) {
fmt.Println("⒤ ", fmt.Sprint(v...))
}
func Done(v ...any) {
fmt.Println("✔ ", fmt.Sprint(v...))
}
func Fatal(v ...any) {
fmt.Println("✘ ", fmt.Sprint(v...))
os.Exit(1)
}
func Fatalf(msg string, v ...any) {
fmt.Printf("✘ "+msg, v...)
os.Exit(1)
}
func FileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func WriteToFile(filePath, content string) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
_, err = file.WriteString(content)
return err
}
func ChangePermission(filePath string, mod int) {
if err := os.Chmod(filePath, fs.FileMode(mod)); err != nil {
Fatalf("Error changing permissions of %s: %v\n", filePath, err)
}
}
func IsDirEmpty(name string) (bool, error) {
f, err := os.Open(name)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1)
if err == nil {
return false, nil
}
return true, nil
}
// TestDatabaseConnection tests the database connection
func TestDatabaseConnection() {
Info("Testing database connection...")
// Test database connection
cmd := exec.Command("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"), "-e", "quit")
err := cmd.Run()
if err != nil {
Fatal("Error testing database connection:", err)
}
}
func GetEnv(cmd *cobra.Command, flagName, envName string) string {
value, _ := cmd.Flags().GetString(flagName)
if value != "" {
err := os.Setenv(envName, value)
if err != nil {
return value
}
}
return os.Getenv(envName)
}
func FlagGetString(cmd *cobra.Command, flagName string) string {
value, _ := cmd.Flags().GetString(flagName)
if value != "" {
return value
}
return ""
}
func FlagGetBool(cmd *cobra.Command, flagName string) bool {
value, _ := cmd.Flags().GetBool(flagName)
return value
}
func SetEnv(key, value string) {
err := os.Setenv(key, value)
if err != nil {
return
}
}
func ShowHistory() {
}