mirror of
https://github.com/jkaninda/mysql-bkup.git
synced 2025-12-06 21:49:40 +01:00
Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cbf65d686 | ||
|
|
73d19913f8 | ||
|
|
b0224e43ef | ||
|
|
fa0485bb5a | ||
|
|
65ef6d3e8f | ||
|
|
a7b6abb101 | ||
|
|
3b21c109bc | ||
|
|
a50a1ef6f9 | ||
|
|
76bbfa35c4 | ||
|
|
599d93bef4 | ||
|
|
247e90f73e | ||
|
|
7d544aca68 | ||
|
|
1722ee0eeb | ||
|
|
726fd14831 | ||
|
|
fdc88e6064 | ||
|
|
2ba1b516e9 | ||
|
|
301594676b | ||
|
|
d06f2f2d7e | ||
|
|
2f06bd1c3a | ||
|
|
f383f5559d | ||
|
|
3725809d28 | ||
|
|
b1598ef7d0 | ||
|
|
e4a83b9851 | ||
|
|
4b2527f416 | ||
|
|
e97fc7512a | ||
|
|
7912ce46ed | ||
|
|
050f5e81bc | ||
|
|
b39e97b77d | ||
|
|
cbb73ae89b | ||
|
|
29a58aa26d | ||
|
|
041e0a07e9 | ||
|
|
9daac9c654 | ||
|
|
f6098769cd | ||
|
|
5cdfaa4d94 | ||
|
|
b205cd61ea | ||
|
|
e1307250e8 | ||
|
|
17ac951deb | ||
|
|
6e2e08224d | ||
|
|
570b775f48 | ||
|
|
e38e106983 | ||
|
|
3040420a09 | ||
|
|
eac5f70408 | ||
|
|
3476c6f529 | ||
|
|
1a9c8483f8 | ||
|
|
f8722f7ae4 | ||
|
|
421bf12910 | ||
|
|
3da4a27baa | ||
|
|
0881f075ef | ||
|
|
066e73f8e4 | ||
|
|
645243ff77 | ||
|
|
9384998127 | ||
|
|
390e7dad0c | ||
|
|
67ea22385f | ||
|
|
cde82d8cfc | ||
|
|
4808f093e5 | ||
|
|
c7a03861fe | ||
|
|
36ec63d522 | ||
|
|
0f07de1d83 | ||
|
|
ae55839996 | ||
|
|
a7f7e57a0d | ||
|
|
b2ddaec93b | ||
|
|
b3570d774c | ||
|
|
38f7e91c03 | ||
|
|
07c2935925 | ||
|
|
f3c5585051 | ||
|
|
7163d030a5 | ||
|
|
a2cec86e73 | ||
|
|
662b73579d | ||
| c9f8a32de1 | |||
| 8fb008151c | |||
| 113c84c885 | |||
| 58deb92953 | |||
| c41afb8b57 | |||
| 02e51a3933 | |||
| db4061b64b | |||
| 9467b157aa | |||
| c229ebdc9d | |||
| 7b701d1740 | |||
| ad6f190bad | |||
| de4dcaaeca | |||
| 17c0a99bda | |||
| b1c9abf931 | |||
| a70a893c11 | |||
| 243e25f4fb | |||
| cb0dcf4104 | |||
| d26d8d31c9 | |||
| 71d438ba76 | |||
| a3fc58af96 | |||
| 08ca6d4a39 | |||
| 27b9ab5f36 | |||
| 6d6db7061b | |||
| d90647aae7 | |||
| 5c2c05499f | |||
| 88ada6fefd | |||
| e6c8b0923d | |||
| 59a136039c | |||
| db835e81c4 | |||
| 5b05bcbf0c | |||
| b8277c8464 | |||
| 70338b6ae6 | |||
| 33b1acf7c0 | |||
| 9a4d02f648 | |||
| 1e06600c43 | |||
| 365ab8dfff | |||
| e4ca97b99e | |||
| ae7eb7a159 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
ko_fi: jkaninda
|
||||||
32
.github/workflows/build.yml
vendored
Normal file
32
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['develop']
|
||||||
|
env:
|
||||||
|
BUILDKIT_IMAGE: jkaninda/mysql-bkup
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
-
|
||||||
|
name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
file: "./docker/Dockerfile"
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
tags: |
|
||||||
|
"${{env.BUILDKIT_IMAGE}}:develop-${{ github.sha }}"
|
||||||
|
|
||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Release
|
name: CI
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,4 +8,5 @@ test.md
|
|||||||
mysql-bkup
|
mysql-bkup
|
||||||
/.DS_Store
|
/.DS_Store
|
||||||
/.idea
|
/.idea
|
||||||
bin
|
bin
|
||||||
|
Makefile
|
||||||
46
Makefile
46
Makefile
@@ -1,46 +0,0 @@
|
|||||||
BINARY_NAME=mysql-bkup
|
|
||||||
include .env
|
|
||||||
export
|
|
||||||
run:
|
|
||||||
go run . backup
|
|
||||||
|
|
||||||
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 web --name mysql-bkup -v "./backup:/backup" -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" -e "GPG_PASSPHRASE=${GPG_PASSPHRASE}" jkaninda/mysql-bkup bkup backup --prune --keep-last 2
|
|
||||||
docker-restore: docker-build
|
|
||||||
docker run --rm --network web --name mysql-bkup -v "./backup:/backup" -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" -e "GPG_PASSPHRASE=${GPG_PASSPHRASE}" jkaninda/mysql-bkup bkup restore -f ${FILE_NAME}
|
|
||||||
|
|
||||||
|
|
||||||
docker-run-scheduled: docker-build
|
|
||||||
docker run --rm --network web --name mysql-bkup -v "./backup:/backup" -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" -e "GPG_PASSPHRASE=${GPG_PASSPHRASE}" jkaninda/mysql-bkup bkup backup --mode scheduled --period "* * * * *"
|
|
||||||
|
|
||||||
|
|
||||||
docker-run-scheduled-s3: docker-build
|
|
||||||
docker run --rm --network web --user 1000:1000 --name mysql-bkup -v "./backup:/backup" -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 "GPG_PASSPHRASE=${GPG_PASSPHRASE}" jkaninda/mysql-bkup bkup backup --storage s3 --mode scheduled --path /custom-path --period "* * * * *"
|
|
||||||
|
|
||||||
docker-run-s3: docker-build
|
|
||||||
docker run --rm --network web --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 "AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME}" -e "AWS_S3_ENDPOINT=${AWS_S3_ENDPOINT}" -e "AWS_REGION=eu2" -e "GPG_PASSPHRASE=${GPG_PASSPHRASE}" jkaninda/mysql-bkup bkup backup --storage s3 --path /custom-path
|
|
||||||
|
|
||||||
|
|
||||||
docker-restore-s3: docker-build
|
|
||||||
docker run --rm --network web --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=${AWS_S3_BUCKET_NAME}" -e "S3_ENDPOINT=${AWS_S3_ENDPOINT}" -e "AWS_REGION=eu2" -e "GPG_PASSPHRASE=${GPG_PASSPHRASE}" jkaninda/mysql-bkup bkup restore --storage s3 -f ${FILE_NAME} --path /custom-path
|
|
||||||
|
|
||||||
docker-run-ssh: docker-build
|
|
||||||
docker run --rm --network web -v "${SSH_IDENTIFY_FILE_LOCAL}:" --name mysql-bkup -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" -e "SSH_USER=${SSH_USER}" -e "SSH_HOST_NAME=${SSH_HOST_NAME}" -e "SSH_REMOTE_PATH=${SSH_REMOTE_PATH}" -e "SSH_PASSWORD=${SSH_PASSWORD}" -e "SSH_PORT=${SSH_PORT}" -e "SSH_IDENTIFY_FILE=${SSH_IDENTIFY_FILE}" -e "GPG_PASSPHRASE=${GPG_PASSPHRASE}" jkaninda/mysql-bkup bkup backup --storage ssh
|
|
||||||
|
|
||||||
docker-restore-ssh: docker-build
|
|
||||||
docker run --rm --network web --name mysql-bkup -e "DB_HOST=${DB_HOST}" -e "DB_NAME=${DB_NAME}" -e "DB_USERNAME=${DB_USERNAME}" -e "DB_PASSWORD=${DB_PASSWORD}" -e "SSH_USER=${SSH_USER}" -e "SSH_HOST_NAME=${SSH_HOST_NAME}" -e "SSH_REMOTE_PATH=${SSH_REMOTE_PATH}" -e "SSH_PASSWORD=${SSH_PASSWORD}" -e "SSH_PORT=${SSH_PORT}" -e "GPG_PASSPHRASE=${GPG_PASSPHRASE}" -e "SSH_IDENTIFY_FILE=${SSH_IDENTIFY_FILE}" jkaninda/mysql-bkup bkup restore --storage ssh -f ${FILE_NAME}
|
|
||||||
|
|
||||||
run-docs:
|
|
||||||
cd docs && bundle exec jekyll serve -H 0.0.0.0 -t
|
|
||||||
123
README.md
123
README.md
@@ -1,19 +1,23 @@
|
|||||||
# MySQL Backup
|
# MySQL Backup
|
||||||
mysql-bkup is a Docker container image that can be used to backup and restore Postgres 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, FTP 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, FTP or SSH compatible storage.
|
||||||
|
|
||||||
It also supports __encrypting__ your backups using GPG.
|
It also supports database __encryption__ using GPG.
|
||||||
|
|
||||||
[](https://github.com/jkaninda/mysql-bkup/actions/workflows/release.yml)
|
[](https://github.com/jkaninda/mysql-bkup/actions/workflows/release.yml)
|
||||||
[](https://goreportcard.com/report/github.com/jkaninda/mysql-bkup)
|
[](https://goreportcard.com/report/github.com/jkaninda/mysql-bkup)
|
||||||

|

|
||||||

|

|
||||||
|
<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:
|
||||||
- Docker
|
- Docker
|
||||||
|
- Docker in Swarm mode
|
||||||
- Kubernetes
|
- Kubernetes
|
||||||
|
- OpenShift
|
||||||
|
|
||||||
## Documentation is found at <https://jkaninda.github.io/mysql-bkup>
|
## Documentation is found at <https://jkaninda.github.io/mysql-bkup>
|
||||||
|
|
||||||
@@ -30,13 +34,13 @@ It also supports __encrypting__ your backups using GPG.
|
|||||||
## Storage:
|
## Storage:
|
||||||
- Local
|
- Local
|
||||||
- AWS S3 or any S3 Alternatives for Object Storage
|
- AWS S3 or any S3 Alternatives for Object Storage
|
||||||
- SSH
|
- SSH remote server
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
### Simple backup using Docker CLI
|
### Simple backup using Docker CLI
|
||||||
|
|
||||||
To run a one time backup, bind your local volume to `/backup` in the container and run the `mysql-bkup backup` command:
|
To run a one time backup, bind your local volume to `/backup` in the container and run the `backup` command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run --rm --network your_network_name \
|
docker run --rm --network your_network_name \
|
||||||
@@ -44,11 +48,17 @@ To run a one time backup, bind your local volume to `/backup` in the container a
|
|||||||
-e "DB_HOST=dbhost" \
|
-e "DB_HOST=dbhost" \
|
||||||
-e "DB_USERNAME=username" \
|
-e "DB_USERNAME=username" \
|
||||||
-e "DB_PASSWORD=password" \
|
-e "DB_PASSWORD=password" \
|
||||||
jkaninda/mysql-bkup mysql-bkup backup -d database_name
|
jkaninda/mysql-bkup backup -d database_name
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, pass a `--env-file` in order to use a full config as described below.
|
Alternatively, pass a `--env-file` in order to use a full config as described below.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
docker run --rm --network your_network_name \
|
||||||
|
--env-file your-env-file \
|
||||||
|
-v $PWD/backup:/backup/ \
|
||||||
|
jkaninda/mysql-bkup backup -d database_name
|
||||||
|
```
|
||||||
|
|
||||||
### Simple backup in docker compose file
|
### Simple backup in docker compose file
|
||||||
|
|
||||||
@@ -61,15 +71,12 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
- DB_PORT=5432
|
- DB_PORT=3306
|
||||||
- DB_HOST=postgres
|
- DB_HOST=mysql
|
||||||
- DB_NAME=foo
|
- DB_NAME=foo
|
||||||
- DB_USERNAME=bar
|
- DB_USERNAME=bar
|
||||||
- DB_PASSWORD=password
|
- DB_PASSWORD=password
|
||||||
@@ -79,55 +86,65 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
web:
|
web:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Docker recurring backup
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run --rm --network network_name \
|
||||||
|
-v $PWD/backup:/backup/ \
|
||||||
|
-e "DB_HOST=hostname" \
|
||||||
|
-e "DB_USERNAME=user" \
|
||||||
|
-e "DB_PASSWORD=password" \
|
||||||
|
jkaninda/mysql-bkup backup -d dbName --cron-expression "@every 1m"
|
||||||
|
```
|
||||||
|
See: https://jkaninda.github.io/mysql-bkup/reference/#predefined-schedules
|
||||||
|
|
||||||
## Deploy on Kubernetes
|
## Deploy on Kubernetes
|
||||||
|
|
||||||
For Kubernetes, you don't need to run it in scheduled mode. You can deploy it as CronJob.
|
For Kubernetes, you don't need to run it in scheduled mode. You can deploy it as Job or CronJob.
|
||||||
|
|
||||||
### Simple Kubernetes CronJob usage:
|
### Simple Kubernetes backup Job :
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: CronJob
|
kind: Job
|
||||||
metadata:
|
metadata:
|
||||||
name: bkup-job
|
name: backup-job
|
||||||
spec:
|
spec:
|
||||||
schedule: "0 1 * * *"
|
ttlSecondsAfterFinished: 100
|
||||||
jobTemplate:
|
template:
|
||||||
spec:
|
spec:
|
||||||
template:
|
containers:
|
||||||
spec:
|
- name: mysql-bkup
|
||||||
containers:
|
# In production, it is advised to lock your image tag to a proper
|
||||||
- name: mysql-bkup
|
# release version instead of using `latest`.
|
||||||
image: jkaninda/mysql-bkup
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
command:
|
# for a list of available releases.
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
- mysql-bkup backup -s s3 --path /custom_path
|
- backup -d dbname
|
||||||
env:
|
resources:
|
||||||
- name: DB_PORT
|
limits:
|
||||||
value: "5432"
|
memory: "128Mi"
|
||||||
- name: DB_HOST
|
cpu: "500m"
|
||||||
value: ""
|
env:
|
||||||
- name: DB_NAME
|
- name: DB_HOST
|
||||||
value: ""
|
value: "mysql"
|
||||||
- name: DB_USERNAME
|
- name: DB_USERNAME
|
||||||
value: ""
|
value: "user"
|
||||||
# Please use secret!
|
- name: DB_PASSWORD
|
||||||
- name: DB_PASSWORD
|
value: "password"
|
||||||
value: ""
|
volumeMounts:
|
||||||
- name: AWS_S3_ENDPOINT
|
- mountPath: /backup
|
||||||
value: "https://s3.amazonaws.com"
|
name: backup
|
||||||
- name: AWS_S3_BUCKET_NAME
|
volumes:
|
||||||
value: "xxx"
|
- name: backup
|
||||||
- name: AWS_REGION
|
hostPath:
|
||||||
value: "us-west-2"
|
path: /home/toto/backup # directory location on host
|
||||||
- name: AWS_ACCESS_KEY
|
type: Directory # this field is optional
|
||||||
value: "xxxx"
|
restartPolicy: Never
|
||||||
- name: AWS_SECRET_KEY
|
|
||||||
value: "xxxx"
|
|
||||||
- name: AWS_DISABLE_SSL
|
|
||||||
value: "false"
|
|
||||||
restartPolicy: Never
|
|
||||||
```
|
```
|
||||||
## Available image registries
|
## Available image registries
|
||||||
|
|
||||||
@@ -135,8 +152,8 @@ This Docker image is published to both Docker Hub and the GitHub container regis
|
|||||||
Depending on your preferences and needs, you can reference both `jkaninda/mysql-bkup` as well as `ghcr.io/jkaninda/mysql-bkup`:
|
Depending on your preferences and needs, you can reference both `jkaninda/mysql-bkup` as well as `ghcr.io/jkaninda/mysql-bkup`:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker pull jkaninda/mysql-bkup:v1.0
|
docker pull jkaninda/mysql-bkup
|
||||||
docker pull ghcr.io/jkaninda/mysql-bkup:v1.0
|
docker pull ghcr.io/jkaninda/mysql-bkup
|
||||||
```
|
```
|
||||||
|
|
||||||
Documentation references Docker Hub, but all examples will work using ghcr.io just as well.
|
Documentation references Docker Hub, but all examples will work using ghcr.io just as well.
|
||||||
@@ -150,7 +167,7 @@ While it may work against different implementations, there are no guarantees abo
|
|||||||
|
|
||||||
We decided to publish this image as a simpler and more lightweight alternative because of the following requirements:
|
We decided to publish this image as a simpler and more lightweight alternative because of the following requirements:
|
||||||
|
|
||||||
- The original image is based on `ubuntu` and requires additional tools, making it heavy.
|
- The original image is based on `alpine` and requires additional tools, making it heavy.
|
||||||
- This image is written in Go.
|
- This image is written in Go.
|
||||||
- `arm64` and `arm/v7` architectures are supported.
|
- `arm64` and `arm/v7` architectures are supported.
|
||||||
- Docker in Swarm mode is supported.
|
- Docker in Swarm mode is supported.
|
||||||
|
|||||||
@@ -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 (
|
||||||
@@ -21,8 +27,11 @@ var BackupCmd = &cobra.Command{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
//Backup
|
//Backup
|
||||||
BackupCmd.PersistentFlags().StringP("mode", "m", "default", "Execution mode. default or scheduled")
|
BackupCmd.PersistentFlags().StringP("storage", "s", "local", "Storage. local or s3")
|
||||||
BackupCmd.PersistentFlags().StringP("period", "", "0 1 * * *", "Schedule period time")
|
BackupCmd.PersistentFlags().StringP("path", "P", "", "AWS S3 path without file name. eg: /custom_path or ssh remote path `/home/foo/backup`")
|
||||||
|
BackupCmd.PersistentFlags().StringP("mode", "m", "default", "Execution mode. | Deprecated")
|
||||||
|
BackupCmd.PersistentFlags().StringP("period", "", "", "Schedule period time | Deprecated")
|
||||||
|
BackupCmd.PersistentFlags().StringP("cron-expression", "", "", "Backup cron expression")
|
||||||
BackupCmd.PersistentFlags().BoolP("prune", "", false, "Delete old backup, default disabled")
|
BackupCmd.PersistentFlags().BoolP("prune", "", false, "Delete old backup, default disabled")
|
||||||
BackupCmd.PersistentFlags().IntP("keep-last", "", 7, "Delete files created more than specified days ago, default 7 days")
|
BackupCmd.PersistentFlags().IntP("keep-last", "", 7, "Delete files created more than specified days ago, default 7 days")
|
||||||
BackupCmd.PersistentFlags().BoolP("disable-compression", "", false, "Disable backup compression")
|
BackupCmd.PersistentFlags().BoolP("disable-compression", "", false, "Disable backup compression")
|
||||||
|
|||||||
27
cmd/migrate.go
Normal file
27
cmd/migrate.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Package cmd /
|
||||||
|
/*****
|
||||||
|
@author Jonas Kaninda
|
||||||
|
@license MIT License <https://opensource.org/licenses/MIT>
|
||||||
|
@Copyright © 2024 Jonas Kaninda
|
||||||
|
**/
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jkaninda/mysql-bkup/pkg"
|
||||||
|
"github.com/jkaninda/mysql-bkup/utils"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MigrateCmd = &cobra.Command{
|
||||||
|
Use: "migrate",
|
||||||
|
Short: "Migrate database from a source database to a target database",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
pkg.StartMigration(cmd)
|
||||||
|
} else {
|
||||||
|
utils.Fatal("Error, no argument required")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -24,5 +24,7 @@ var RestoreCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
//Restore
|
//Restore
|
||||||
RestoreCmd.PersistentFlags().StringP("file", "f", "", "File name of database")
|
RestoreCmd.PersistentFlags().StringP("file", "f", "", "File name of database")
|
||||||
|
RestoreCmd.PersistentFlags().StringP("storage", "s", "local", "Storage. local or s3")
|
||||||
|
RestoreCmd.PersistentFlags().StringP("path", "P", "", "AWS S3 path without file name. eg: /custom_path or ssh remote path `/home/foo/backup`")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
17
cmd/root.go
17
cmd/root.go
@@ -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 (
|
||||||
@@ -30,13 +32,10 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringP("storage", "s", "local", "Storage. local or s3")
|
|
||||||
rootCmd.PersistentFlags().StringP("path", "P", "", "AWS S3 path without file name. eg: /custom_path or ssh remote path `/home/foo/backup`")
|
|
||||||
rootCmd.PersistentFlags().StringP("dbname", "d", "", "Database name")
|
rootCmd.PersistentFlags().StringP("dbname", "d", "", "Database name")
|
||||||
rootCmd.PersistentFlags().IntP("port", "p", 3306, "Database port")
|
|
||||||
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)
|
||||||
|
rootCmd.AddCommand(MigrateCmd)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -9,58 +9,68 @@ RUN go mod download
|
|||||||
# Build
|
# Build
|
||||||
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 alpine:3.20.3
|
||||||
ENV DB_HOST=""
|
ENV DB_HOST=""
|
||||||
ENV DB_NAME=""
|
ENV DB_NAME=""
|
||||||
ENV DB_USERNAME=""
|
ENV DB_USERNAME=""
|
||||||
ENV DB_PASSWORD=""
|
ENV DB_PASSWORD=""
|
||||||
ENV DB_PORT="3306"
|
ENV DB_PORT=3306
|
||||||
ENV STORAGE=local
|
ENV STORAGE=local
|
||||||
ENV AWS_S3_ENDPOINT=""
|
ENV AWS_S3_ENDPOINT=""
|
||||||
ENV AWS_S3_BUCKET_NAME=""
|
ENV AWS_S3_BUCKET_NAME=""
|
||||||
ENV AWS_ACCESS_KEY=""
|
ENV AWS_ACCESS_KEY=""
|
||||||
ENV AWS_SECRET_KEY=""
|
ENV AWS_SECRET_KEY=""
|
||||||
|
ENV AWS_S3_PATH=""
|
||||||
ENV AWS_REGION="us-west-2"
|
ENV AWS_REGION="us-west-2"
|
||||||
ENV AWS_DISABLE_SSL="false"
|
ENV AWS_DISABLE_SSL="false"
|
||||||
ENV GPG_PASSPHRASE=""
|
ENV GPG_PASSPHRASE=""
|
||||||
ENV SSH_USER=""
|
ENV SSH_USER=""
|
||||||
ENV SSH_REMOTE_PATH=""
|
|
||||||
ENV SSH_PASSWORD=""
|
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
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ENV REMOTE_PATH=""
|
||||||
ENV VERSION="v1.0"
|
ENV FTP_HOST_NAME=""
|
||||||
ARG WORKDIR="/app"
|
ENV FTP_PORT=21
|
||||||
|
ENV FTP_USER=""
|
||||||
|
ENV FTP_PASSWORD=""
|
||||||
|
ENV TARGET_DB_HOST=""
|
||||||
|
ENV TARGET_DB_PORT=3306
|
||||||
|
ENV TARGET_DB_NAME=""
|
||||||
|
ENV TARGET_DB_USERNAME=""
|
||||||
|
ENV TARGET_DB_PASSWORD=""
|
||||||
|
ENV VERSION="v1.2.9"
|
||||||
|
ENV BACKUP_CRON_EXPRESSION=""
|
||||||
|
ENV TG_TOKEN=""
|
||||||
|
ENV TG_CHAT_ID=""
|
||||||
|
ARG WORKDIR="/config"
|
||||||
ARG BACKUPDIR="/backup"
|
ARG BACKUPDIR="/backup"
|
||||||
ARG BACKUP_TMP_DIR="/tmp/backup"
|
ARG BACKUP_TMP_DIR="/tmp/backup"
|
||||||
ARG BACKUP_CRON="/etc/cron.d/backup_cron"
|
|
||||||
ARG BACKUP_CRON_SCRIPT="/usr/local/bin/backup_cron.sh"
|
|
||||||
LABEL author="Jonas Kaninda"
|
LABEL author="Jonas Kaninda"
|
||||||
|
|
||||||
RUN apt-get update -qq
|
RUN apk --update add mysql-client gnupg
|
||||||
#RUN apt-get install build-essential libcurl4-openssl-dev libxml2-dev mime-support -y
|
|
||||||
RUN apt install mysql-client supervisor cron gnupg -y
|
|
||||||
|
|
||||||
# Clear cache
|
|
||||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
RUN mkdir $WORKDIR
|
RUN mkdir $WORKDIR
|
||||||
RUN mkdir $BACKUPDIR
|
RUN mkdir $BACKUPDIR
|
||||||
RUN mkdir -p $BACKUP_TMP_DIR
|
RUN mkdir -p $BACKUP_TMP_DIR
|
||||||
RUN chmod 777 $WORKDIR
|
RUN chmod 777 $WORKDIR
|
||||||
RUN chmod 777 $BACKUPDIR
|
RUN chmod 777 $BACKUPDIR
|
||||||
RUN chmod 777 $BACKUP_TMP_DIR
|
RUN chmod 777 $BACKUP_TMP_DIR
|
||||||
RUN touch $BACKUP_CRON && \
|
RUN chmod 777 $WORKDIR
|
||||||
touch $BACKUP_CRON_SCRIPT && \
|
|
||||||
chmod 777 $BACKUP_CRON && \
|
|
||||||
chmod 777 $BACKUP_CRON_SCRIPT
|
|
||||||
|
|
||||||
COPY --from=build /app/mysql-bkup /usr/local/bin/mysql-bkup
|
COPY --from=build /app/mysql-bkup /usr/local/bin/mysql-bkup
|
||||||
RUN chmod +x /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/bkup
|
||||||
|
|
||||||
ADD docker/supervisord.conf /etc/supervisor/supervisord.conf
|
# Create backup script and make it executable
|
||||||
|
RUN echo '#!/bin/sh\n/usr/local/bin/mysql-bkup backup "$@"' > /usr/local/bin/backup && \
|
||||||
|
chmod +x /usr/local/bin/backup
|
||||||
|
# Create restore script and make it executable
|
||||||
|
RUN echo '#!/bin/sh\n/usr/local/bin/mysql-bkup restore "$@"' > /usr/local/bin/restore && \
|
||||||
|
chmod +x /usr/local/bin/restore
|
||||||
|
# Create migrate script and make it executable
|
||||||
|
RUN echo '#!/bin/sh\n/usr/local/bin/mysql-bkup migrate "$@"' > /usr/local/bin/migrate && \
|
||||||
|
chmod +x /usr/local/bin/migrate
|
||||||
|
|
||||||
WORKDIR $WORKDIR
|
WORKDIR $WORKDIR
|
||||||
|
ENTRYPOINT ["/usr/local/bin/mysql-bkup"]
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
[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
|
|
||||||
@@ -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
|
|
||||||
@@ -13,10 +13,11 @@
|
|||||||
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
|
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
|
||||||
# You can create any custom variable you would like, and they will be accessible
|
# You can create any custom variable you would like, and they will be accessible
|
||||||
# in the templates via {{ site.myvariable }}.
|
# in the templates via {{ site.myvariable }}.
|
||||||
title: MySQL database backup
|
title: MySQL Backup Docker container image
|
||||||
email: hi@jonaskaninda.com
|
email: hi@jonaskaninda.com
|
||||||
description: >- # this means to ignore newlines until "baseurl:"
|
description: >- # this means to ignore newlines until "baseurl:"
|
||||||
MySQL Backup and Restore Docker container image. Backup database to AWS S3 storage or SSH remote server.
|
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.
|
||||||
|
|
||||||
baseurl: "" # the subpath of your site, e.g. /blog
|
baseurl: "" # the subpath of your site, e.g. /blog
|
||||||
url: "jkaninda.github.io/mysql-bkup/" # the base hostname & protocol for your site, e.g. http://example.com
|
url: "jkaninda.github.io/mysql-bkup/" # the base hostname & protocol for your site, e.g. http://example.com
|
||||||
|
|||||||
@@ -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
|
|
||||||
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
44
docs/how-tos/backup-to-ftp.md
Normal file
44
docs/how-tos/backup-to-ftp.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
title: Backup to FTP remote server
|
||||||
|
layout: default
|
||||||
|
parent: How Tos
|
||||||
|
nav_order: 4
|
||||||
|
---
|
||||||
|
# Backup to FTP remote server
|
||||||
|
|
||||||
|
|
||||||
|
As described for SSH backup section, to change the storage of your backup and use FTP Remote server as storage. You need to add `--storage ftp`.
|
||||||
|
You need to add the full remote path by adding `--path /home/jkaninda/backups` flag or using `REMOTE_PATH` environment variable.
|
||||||
|
|
||||||
|
{: .note }
|
||||||
|
These environment variables are required for SSH backup `FTP_HOST_NAME`, `FTP_USER`, `REMOTE_PATH`, `FTP_PORT` or `FTP_PASSWORD`.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
services:
|
||||||
|
mysql-bkup:
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
|
# for a list of available releases.
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
container_name: mysql-bkup
|
||||||
|
command: backup --storage ftp -d database
|
||||||
|
environment:
|
||||||
|
- DB_PORT=3306
|
||||||
|
- DB_HOST=postgres
|
||||||
|
- DB_NAME=database
|
||||||
|
- DB_USERNAME=username
|
||||||
|
- DB_PASSWORD=password
|
||||||
|
## FTP config
|
||||||
|
- FTP_HOST_NAME="hostname"
|
||||||
|
- FTP_PORT=21
|
||||||
|
- FTP_USER=user
|
||||||
|
- FTP_PASSWORD=password
|
||||||
|
- REMOTE_PATH=/home/jkaninda/backups
|
||||||
|
|
||||||
|
# pg-bkup container must be connected to the same network with your database
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
```
|
||||||
@@ -22,10 +22,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup --storage s3 -d database --path /my-custom-path
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup --storage s3 -d database --path /my-custom-path
|
|
||||||
environment:
|
environment:
|
||||||
- DB_PORT=3306
|
- DB_PORT=3306
|
||||||
- DB_HOST=mysql
|
- DB_HOST=mysql
|
||||||
@@ -51,7 +48,7 @@ networks:
|
|||||||
### Recurring backups to S3
|
### Recurring backups to S3
|
||||||
|
|
||||||
As explained above, you need just to add AWS environment variables and specify the storage type `--storage s3`.
|
As explained above, you need just to add AWS environment variables and specify the storage type `--storage s3`.
|
||||||
In case you need to use recurring backups, you can use `--mode scheduled` and specify the periodical backup time by adding `--period "0 1 * * *"` flag as described below.
|
In case you need to use recurring backups, you can use `--cron-expression "0 1 * * *"` flag or `BACKUP_CRON_EXPRESSION=0 1 * * *` as described below.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
services:
|
services:
|
||||||
@@ -62,10 +59,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup --storage s3 -d my-database --cron-expression "0 1 * * *"
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup --storage s3 -d my-database --mode scheduled --period "0 1 * * *"
|
|
||||||
environment:
|
environment:
|
||||||
- DB_PORT=3306
|
- DB_PORT=3306
|
||||||
- DB_HOST=mysql
|
- DB_HOST=mysql
|
||||||
@@ -78,6 +72,7 @@ services:
|
|||||||
- AWS_REGION="us-west-2"
|
- AWS_REGION="us-west-2"
|
||||||
- AWS_ACCESS_KEY=xxxx
|
- AWS_ACCESS_KEY=xxxx
|
||||||
- AWS_SECRET_KEY=xxxxx
|
- AWS_SECRET_KEY=xxxxx
|
||||||
|
# - BACKUP_CRON_EXPRESSION=0 1 * * * # Optional
|
||||||
## In case you are using S3 alternative such as Minio and your Minio instance is not secured, you change it to true
|
## In case you are using S3 alternative such as Minio and your Minio instance is not secured, you change it to true
|
||||||
- AWS_DISABLE_SSL="false"
|
- AWS_DISABLE_SSL="false"
|
||||||
# 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
|
||||||
@@ -110,7 +105,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"
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ nav_order: 3
|
|||||||
# Backup to SSH remote server
|
# Backup to SSH remote server
|
||||||
|
|
||||||
|
|
||||||
As described for s3 backup section, to change the storage of you backup and use S3 as storage. You need to add `--storage ssh` or `--storage remote`.
|
As described for s3 backup section, to change the storage of your backup and use SSH Remote server as storage. You need to add `--storage ssh` or `--storage remote`.
|
||||||
You need to add the full remote path by adding `--path /home/jkaninda/backups` flag or using `SSH_REMOTE_PATH` environment variable.
|
You need to add the full remote path by adding `--path /home/jkaninda/backups` flag or using `REMOTE_PATH` environment variable.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
These environment variables are required for SSH backup `SSH_HOST_NAME`, `SSH_USER`, `SSH_REMOTE_PATH`, `SSH_IDENTIFY_FILE`, `SSH_PORT` or `SSH_PASSWORD` if you dont use a private key to access to your server.
|
These environment variables are required for SSH backup `SSH_HOST_NAME`, `SSH_USER`, `SSH_REMOTE_PATH`, `SSH_IDENTIFY_FILE`, `SSH_PORT` or `SSH_PASSWORD` if you dont use a private key to access to your server.
|
||||||
@@ -23,23 +23,20 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup --storage remote -d database
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup --storage remote -d database
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./id_ed25519:/tmp/id_ed25519"
|
- ./id_ed25519:/tmp/id_ed25519"
|
||||||
environment:
|
environment:
|
||||||
- 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
|
||||||
## SSH config
|
## SSH config
|
||||||
- SSH_HOST_NAME="hostname"
|
- SSH_HOST_NAME="hostname"
|
||||||
- SSH_PORT=22
|
- SSH_PORT=22
|
||||||
- SSH_USER=user
|
- SSH_USER=user
|
||||||
- SSH_REMOTE_PATH=/home/jkaninda/backups
|
- REMOTE_PATH=/home/jkaninda/backups
|
||||||
- SSH_IDENTIFY_FILE=/tmp/id_ed25519
|
- SSH_IDENTIFY_FILE=/tmp/id_ed25519
|
||||||
## We advise you to use a private jey instead of password
|
## We advise you to use a private jey instead of password
|
||||||
#- SSH_PASSWORD=password
|
#- SSH_PASSWORD=password
|
||||||
@@ -55,7 +52,7 @@ networks:
|
|||||||
### Recurring backups to SSH remote server
|
### Recurring backups to SSH remote server
|
||||||
|
|
||||||
As explained above, you need just to add required environment variables and specify the storage type `--storage ssh`.
|
As explained above, you need just to add required environment variables and specify the storage type `--storage ssh`.
|
||||||
You can use `--mode scheduled` and specify the periodical backup time by adding `--period "0 1 * * *"` flag as described below.
|
You can use `--cron-expression "* * * * *"` or `BACKUP_CRON_EXPRESSION=0 1 * * *` as described below.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
services:
|
services:
|
||||||
@@ -66,10 +63,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup -d database --storage ssh --cron-expression "0 1 * * *"
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup -d database --storage s3 --mode scheduled --period "0 1 * * *"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./id_ed25519:/tmp/id_ed25519"
|
- ./id_ed25519:/tmp/id_ed25519"
|
||||||
environment:
|
environment:
|
||||||
@@ -82,8 +76,9 @@ services:
|
|||||||
- SSH_HOST_NAME="hostname"
|
- SSH_HOST_NAME="hostname"
|
||||||
- SSH_PORT=22
|
- SSH_PORT=22
|
||||||
- SSH_USER=user
|
- SSH_USER=user
|
||||||
- SSH_REMOTE_PATH=/home/jkaninda/backups
|
- REMOTE_PATH=/home/jkaninda/backups
|
||||||
- SSH_IDENTIFY_FILE=/tmp/id_ed25519
|
- SSH_IDENTIFY_FILE=/tmp/id_ed25519
|
||||||
|
# - BACKUP_CRON_EXPRESSION=0 1 * * * # Optional
|
||||||
## We advise you to use a private jey instead of password
|
## We advise you to use a private jey instead of password
|
||||||
#- SSH_PASSWORD=password
|
#- SSH_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
|
||||||
@@ -117,7 +112,7 @@ spec:
|
|||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
- -c
|
- -c
|
||||||
- mysql-bkup backup -s s3 --path /custom_path
|
- backup -s ssh
|
||||||
env:
|
env:
|
||||||
- name: DB_PORT
|
- name: DB_PORT
|
||||||
value: "3306"
|
value: "3306"
|
||||||
@@ -136,11 +131,11 @@ spec:
|
|||||||
value: "22"
|
value: "22"
|
||||||
- name: SSH_USER
|
- name: SSH_USER
|
||||||
value: "xxx"
|
value: "xxx"
|
||||||
- name: SSH_REMOTE_PATH
|
- name: REMOTE_PATH
|
||||||
value: "/home/jkaninda/backups"
|
value: "/home/jkaninda/backups"
|
||||||
- name: AWS_ACCESS_KEY
|
- name: AWS_ACCESS_KEY
|
||||||
value: "xxxx"
|
value: "xxxx"
|
||||||
- name: SSH_IDENTIFY_FILE
|
- name: SSH_IDENTIFY_FILE
|
||||||
value: "/home/jkaninda/backups"
|
value: "/tmp/id_ed25519"
|
||||||
restartPolicy: OnFailure
|
restartPolicy: Never
|
||||||
```
|
```
|
||||||
@@ -7,7 +7,7 @@ nav_order: 1
|
|||||||
|
|
||||||
# Backup database
|
# Backup database
|
||||||
|
|
||||||
To backup the database, you need to add `backup` subcommand to `mysql-bkup` or `bkup`.
|
To backup the database, you need to add `backup` command.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
The default storage is local storage mounted to __/backup__. The backup is compressed by default using gzip. The flag __`disable-compression`__ can be used when you need to disable backup compression.
|
The default storage is local storage mounted to __/backup__. The backup is compressed by default using gzip. The flag __`disable-compression`__ can be used when you need to disable backup compression.
|
||||||
@@ -27,10 +27,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup -d database
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup -d database
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
@@ -54,10 +51,10 @@ networks:
|
|||||||
-e "DB_HOST=dbhost" \
|
-e "DB_HOST=dbhost" \
|
||||||
-e "DB_USERNAME=username" \
|
-e "DB_USERNAME=username" \
|
||||||
-e "DB_PASSWORD=password" \
|
-e "DB_PASSWORD=password" \
|
||||||
jkaninda/mysql-bkup mysql-bkup backup -d database_name
|
jkaninda/mysql-bkup backup -d database_name
|
||||||
```
|
```
|
||||||
|
|
||||||
In case you need to use recurring backups, you can use `--mode scheduled` and specify the periodical backup time by adding `--period "0 1 * * *"` flag as described below.
|
In case you need to use recurring backups, you can use `--cron-expression "0 1 * * *"` flag or `BACKUP_CRON_EXPRESSION=0 1 * * *` as described below.
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
services:
|
services:
|
||||||
@@ -68,10 +65,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup -d database --cron-expression "0 1 * * *"
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup -d database --mode scheduled --period "0 1 * * *"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
@@ -80,6 +74,7 @@ services:
|
|||||||
- DB_NAME=database
|
- DB_NAME=database
|
||||||
- DB_USERNAME=username
|
- DB_USERNAME=username
|
||||||
- DB_PASSWORD=password
|
- DB_PASSWORD=password
|
||||||
|
- BACKUP_CRON_EXPRESSION=0 1 * * *
|
||||||
# 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
|
||||||
|
|||||||
303
docs/how-tos/deploy-on-kubernetes.md
Normal file
303
docs/how-tos/deploy-on-kubernetes.md
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
---
|
||||||
|
title: Deploy on Kubernetes
|
||||||
|
layout: default
|
||||||
|
parent: How Tos
|
||||||
|
nav_order: 9
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploy on Kubernetes
|
||||||
|
|
||||||
|
To deploy MySQL Backup on Kubernetes, you can use Job to backup or Restore your database.
|
||||||
|
For recurring backup you can use CronJob, you don't need to run it in scheduled mode. as described bellow.
|
||||||
|
|
||||||
|
## Backup to S3 storage
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: backup
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysql-bkup
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
|
# for a list of available releases.
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- backup --storage s3
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "3306"
|
||||||
|
- name: DB_HOST
|
||||||
|
value: ""
|
||||||
|
- name: DB_NAME
|
||||||
|
value: "dbname"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "username"
|
||||||
|
# Please use secret!
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: ""
|
||||||
|
- name: AWS_S3_ENDPOINT
|
||||||
|
value: "https://s3.amazonaws.com"
|
||||||
|
- name: AWS_S3_BUCKET_NAME
|
||||||
|
value: "xxx"
|
||||||
|
- name: AWS_REGION
|
||||||
|
value: "us-west-2"
|
||||||
|
- name: AWS_ACCESS_KEY
|
||||||
|
value: "xxxx"
|
||||||
|
- name: AWS_SECRET_KEY
|
||||||
|
value: "xxxx"
|
||||||
|
- name: AWS_DISABLE_SSL
|
||||||
|
value: "false"
|
||||||
|
restartPolicy: Never
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup Job to SSH remote server
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: backup
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 100
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysql-bkup
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
|
# for a list of available releases.
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- bkup
|
||||||
|
- backup
|
||||||
|
- --storage
|
||||||
|
- ssh
|
||||||
|
- --disable-compression
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "3306"
|
||||||
|
- name: DB_HOST
|
||||||
|
value: ""
|
||||||
|
- name: DB_NAME
|
||||||
|
value: "dbname"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "username"
|
||||||
|
# Please use secret!
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: ""
|
||||||
|
- name: SSH_HOST_NAME
|
||||||
|
value: "xxx"
|
||||||
|
- name: SSH_PORT
|
||||||
|
value: "22"
|
||||||
|
- name: SSH_USER
|
||||||
|
value: "xxx"
|
||||||
|
- name: SSH_PASSWORD
|
||||||
|
value: "xxxx"
|
||||||
|
- name: SSH_REMOTE_PATH
|
||||||
|
value: "/home/toto/backup"
|
||||||
|
# Optional, required if you want to encrypt your backup
|
||||||
|
- name: GPG_PASSPHRASE
|
||||||
|
value: "xxxx"
|
||||||
|
restartPolicy: Never
|
||||||
|
```
|
||||||
|
|
||||||
|
## Restore Job
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: restore-job
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 100
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysql-bkup
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
|
# for a list of available releases.
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- bkup
|
||||||
|
- restore
|
||||||
|
- --storage
|
||||||
|
- ssh
|
||||||
|
- --file store_20231219_022941.sql.gz
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "3306"
|
||||||
|
- name: DB_HOST
|
||||||
|
value: ""
|
||||||
|
- name: DB_NAME
|
||||||
|
value: "dbname"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "username"
|
||||||
|
# Please use secret!
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: ""
|
||||||
|
- name: SSH_HOST_NAME
|
||||||
|
value: "xxx"
|
||||||
|
- name: SSH_PORT
|
||||||
|
value: "22"
|
||||||
|
- name: SSH_USER
|
||||||
|
value: "xxx"
|
||||||
|
- name: SSH_PASSWORD
|
||||||
|
value: "xxxx"
|
||||||
|
- name: SSH_REMOTE_PATH
|
||||||
|
value: "/home/xxxx/backup"
|
||||||
|
# Optional, required if your backup was encrypted
|
||||||
|
#- name: GPG_PASSPHRASE
|
||||||
|
# value: "xxxx"
|
||||||
|
restartPolicy: Never
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recurring backup
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: backup-job
|
||||||
|
spec:
|
||||||
|
schedule: "* * * * *"
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysql-bkup
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- bkup
|
||||||
|
- backup
|
||||||
|
- --storage
|
||||||
|
- ssh
|
||||||
|
- --disable-compression
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "3306"
|
||||||
|
- name: DB_HOST
|
||||||
|
value: ""
|
||||||
|
- name: DB_NAME
|
||||||
|
value: "username"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "username"
|
||||||
|
# Please use secret!
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: ""
|
||||||
|
- name: SSH_HOST_NAME
|
||||||
|
value: "xxx"
|
||||||
|
- name: SSH_PORT
|
||||||
|
value: "xxx"
|
||||||
|
- name: SSH_USER
|
||||||
|
value: "jkaninda"
|
||||||
|
- name: SSH_REMOTE_PATH
|
||||||
|
value: "/home/jkaninda/backup"
|
||||||
|
- name: SSH_PASSWORD
|
||||||
|
value: "password"
|
||||||
|
# Optional, required if you want to encrypt your backup
|
||||||
|
#- name: GPG_PASSPHRASE
|
||||||
|
# value: "xxx"
|
||||||
|
restartPolicy: Never
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kubernetes Rootless
|
||||||
|
|
||||||
|
This image also supports Kubernetes security context, you can run it in Rootless environment.
|
||||||
|
It has been tested on Openshift, it works well.
|
||||||
|
Deployment on OpenShift is supported, you need to remove `securityContext` section on your yaml file.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: backup-job
|
||||||
|
spec:
|
||||||
|
schedule: "* * * * *"
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 3000
|
||||||
|
fsGroup: 2000
|
||||||
|
containers:
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
|
# for a list of available releases.
|
||||||
|
- name: mysql-bkup
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- bkup
|
||||||
|
- backup
|
||||||
|
- --storage
|
||||||
|
- ssh
|
||||||
|
- --disable-compression
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "3306"
|
||||||
|
- name: DB_HOST
|
||||||
|
value: ""
|
||||||
|
- name: DB_NAME
|
||||||
|
value: "xxx"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "xxx"
|
||||||
|
# Please use secret!
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: ""
|
||||||
|
- name: SSH_HOST_NAME
|
||||||
|
value: "xxx"
|
||||||
|
- name: SSH_PORT
|
||||||
|
value: "22"
|
||||||
|
- name: SSH_USER
|
||||||
|
value: "jkaninda"
|
||||||
|
- name: SSH_REMOTE_PATH
|
||||||
|
value: "/home/jkaninda/backup"
|
||||||
|
- name: SSH_PASSWORD
|
||||||
|
value: "password"
|
||||||
|
# Optional, required if you want to encrypt your backup
|
||||||
|
#- name: GPG_PASSPHRASE
|
||||||
|
# value: "xxx"
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
```
|
||||||
@@ -2,16 +2,19 @@
|
|||||||
title: Encrypt backups using GPG
|
title: Encrypt backups using GPG
|
||||||
layout: default
|
layout: default
|
||||||
parent: How Tos
|
parent: How Tos
|
||||||
nav_order: 7
|
nav_order: 8
|
||||||
---
|
---
|
||||||
# Encrypt backup
|
# Encrypt backup
|
||||||
|
|
||||||
The image supports encrypting backups using GPG out of the box. In case a `GPG_PASSPHRASE` environment variable is set, the backup archive will be encrypted using the given key and saved as a sql.gpg file instead or sql.gz.gpg.
|
The image supports encrypting backups using GPG out of the box. In case a `GPG_PASSPHRASE` environment variable is set, the backup archive will be encrypted using the given key and saved as a sql.gpg file instead or sql.gz.gpg.
|
||||||
|
|
||||||
{: .warning }
|
{: .warning }
|
||||||
To restore an encrypted backup, you need to provide the same GPG passphrase used during backup process.
|
To restore an encrypted backup, you need to provide the same GPG passphrase or key used during backup process.
|
||||||
|
|
||||||
To decrypt manually, you need to install gnupg
|
- GPG home directory `/config/gnupg`
|
||||||
|
- Cipher algorithm `aes256`
|
||||||
|
-
|
||||||
|
To decrypt manually, you need to install `gnupg`
|
||||||
|
|
||||||
### Decrypt backup
|
### Decrypt backup
|
||||||
|
|
||||||
@@ -32,10 +35,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup -d database
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup -d database
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
131
docs/how-tos/migrate.md
Normal file
131
docs/how-tos/migrate.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
---
|
||||||
|
title: Migrate database
|
||||||
|
layout: default
|
||||||
|
parent: How Tos
|
||||||
|
nav_order: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
# Migrate database
|
||||||
|
|
||||||
|
To migrate the database, you need to add `migrate` command.
|
||||||
|
|
||||||
|
{: .note }
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
```yml
|
||||||
|
services:
|
||||||
|
mysql-bkup:
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
|
# for a list of available releases.
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
container_name: mysql-bkup
|
||||||
|
command: migrate
|
||||||
|
volumes:
|
||||||
|
- ./backup:/backup
|
||||||
|
environment:
|
||||||
|
## Source database
|
||||||
|
- DB_PORT=3306
|
||||||
|
- DB_HOST=mysql
|
||||||
|
- DB_NAME=database
|
||||||
|
- DB_USERNAME=username
|
||||||
|
- DB_PASSWORD=password
|
||||||
|
## Target database
|
||||||
|
- TARGET_DB_HOST=target-mysql
|
||||||
|
- TARGET_DB_PORT=3306
|
||||||
|
- TARGET_DB_NAME=dbname
|
||||||
|
- TARGET_DB_USERNAME=username
|
||||||
|
- TARGET_DB_PASSWORD=password
|
||||||
|
# mysql-bkup container must be connected to the same network with your database
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Migrate database using Docker CLI
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
## Source database
|
||||||
|
DB_HOST=mysql
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_NAME=dbname
|
||||||
|
DB_USERNAME=username
|
||||||
|
DB_PASSWORD=password
|
||||||
|
|
||||||
|
## Taget database
|
||||||
|
TARGET_DB_HOST=target-mysql
|
||||||
|
TARGET_DB_PORT=3306
|
||||||
|
TARGET_DB_NAME=dbname
|
||||||
|
TARGET_DB_USERNAME=username
|
||||||
|
TARGET_DB_PASSWORD=password
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run --rm --network your_network_name \
|
||||||
|
--env-file your-env
|
||||||
|
-v $PWD/backup:/backup/ \
|
||||||
|
jkaninda/mysql-bkup migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kubernetes
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: migrate-db
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 100
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysql-bkup
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
|
# for a list of available releases.
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- migrate
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
## Source Database
|
||||||
|
- name: DB_HOST
|
||||||
|
value: "mysql"
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "3306"
|
||||||
|
- name: DB_NAME
|
||||||
|
value: "dbname"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "username"
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: "password"
|
||||||
|
## Target Database
|
||||||
|
- name: TARGET_DB_HOST
|
||||||
|
value: "target-mysql"
|
||||||
|
- name: TARGET_DB_PORT
|
||||||
|
value: "3306"
|
||||||
|
- name: TARGET_DB_NAME
|
||||||
|
value: "dbname"
|
||||||
|
- name: TARGET_DB_USERNAME
|
||||||
|
value: "username"
|
||||||
|
- name: TARGET_DB_PASSWORD
|
||||||
|
value: "password"
|
||||||
|
restartPolicy: Never
|
||||||
|
```
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
title: Restore database from AWS S3
|
title: Restore database from AWS S3
|
||||||
layout: default
|
layout: default
|
||||||
parent: How Tos
|
parent: How Tos
|
||||||
nav_order: 5
|
nav_order: 6
|
||||||
---
|
---
|
||||||
|
|
||||||
# Restore database from S3 storage
|
# Restore database from S3 storage
|
||||||
|
|
||||||
To restore the database, you need to add `restore` subcommand to `mysql-bkup` or `bkup` and specify the file to restore by adding `--file store_20231219_022941.sql.gz`.
|
To restore the database, you need to add `restore` command and specify the file to restore by adding `--file store_20231219_022941.sql.gz`.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
It supports __.sql__ and __.sql.gz__ compressed file.
|
It supports __.sql__ and __.sql.gz__ compressed file.
|
||||||
@@ -23,10 +23,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: restore --storage s3 -d my-database -f store_20231219_022941.sql.gz --path /my-custom-path
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup restore --storage s3 -d my-database -f store_20231219_022941.sql.gz --path /my-custom-path
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
@@ -48,4 +45,51 @@ services:
|
|||||||
- web
|
- web
|
||||||
networks:
|
networks:
|
||||||
web:
|
web:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Restore on Kubernetes
|
||||||
|
|
||||||
|
Simple Kubernetes restore Job:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: restore-db
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysql-bkup
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- restore -s s3 --path /custom_path -f store_20231219_022941.sql.gz
|
||||||
|
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: ""
|
||||||
|
- name: AWS_S3_ENDPOINT
|
||||||
|
value: "https://s3.amazonaws.com"
|
||||||
|
- name: AWS_S3_BUCKET_NAME
|
||||||
|
value: "xxx"
|
||||||
|
- name: AWS_REGION
|
||||||
|
value: "us-west-2"
|
||||||
|
- name: AWS_ACCESS_KEY
|
||||||
|
value: "xxxx"
|
||||||
|
- name: AWS_SECRET_KEY
|
||||||
|
value: "xxxx"
|
||||||
|
- name: AWS_DISABLE_SSL
|
||||||
|
value: "false"
|
||||||
|
restartPolicy: Never
|
||||||
|
backoffLimit: 4
|
||||||
|
```
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
title: Restore database from SSH
|
title: Restore database from SSH
|
||||||
layout: default
|
layout: default
|
||||||
parent: How Tos
|
parent: How Tos
|
||||||
nav_order: 6
|
nav_order: 7
|
||||||
---
|
---
|
||||||
# Restore database from SSH remote server
|
# Restore database from SSH remote server
|
||||||
|
|
||||||
To restore the database from your remote server, you need to add `restore` subcommand to `mysql-bkup` or `bkup` and specify the file to restore by adding `--file store_20231219_022941.sql.gz`.
|
To restore the database from your remote server, you need to add `restore` command and specify the file to restore by adding `--file store_20231219_022941.sql.gz`.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
It supports __.sql__ and __.sql.gz__ compressed file.
|
It supports __.sql__ and __.sql.gz__ compressed file.
|
||||||
@@ -22,10 +22,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: restore --storage ssh -d my-database -f store_20231219_022941.sql.gz --path /home/jkaninda/backups
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup restore --storage ssh -d my-database -f store_20231219_022941.sql.gz --path /home/jkaninda/backups
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
@@ -47,4 +44,50 @@ services:
|
|||||||
- web
|
- web
|
||||||
networks:
|
networks:
|
||||||
web:
|
web:
|
||||||
|
```
|
||||||
|
## Restore on Kubernetes
|
||||||
|
|
||||||
|
Simple Kubernetes restore Job:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: restore-db
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysql-bkup
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- restore -s ssh -f store_20231219_022941.sql.gz
|
||||||
|
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: ""
|
||||||
|
- name: SSH_HOST_NAME
|
||||||
|
value: ""
|
||||||
|
- name: SSH_PORT
|
||||||
|
value: "22"
|
||||||
|
- name: SSH_USER
|
||||||
|
value: "xxx"
|
||||||
|
- name: SSH_REMOTE_PATH
|
||||||
|
value: "/home/jkaninda/backups"
|
||||||
|
- name: AWS_ACCESS_KEY
|
||||||
|
value: "xxxx"
|
||||||
|
- name: SSH_IDENTIFY_FILE
|
||||||
|
value: "/tmp/id_ed25519"
|
||||||
|
restartPolicy: Never
|
||||||
|
backoffLimit: 4
|
||||||
```
|
```
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
title: Restore database
|
title: Restore database
|
||||||
layout: default
|
layout: default
|
||||||
parent: How Tos
|
parent: How Tos
|
||||||
nav_order: 4
|
nav_order: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
# Restore database
|
# Restore database
|
||||||
|
|
||||||
To restore the database, you need to add `restore` subcommand to `mysql-bkup` or `bkup` and specify the file to restore by adding `--file store_20231219_022941.sql.gz`.
|
To restore the database, you need to add `restore` command and specify the file to restore by adding `--file store_20231219_022941.sql.gz`.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
It supports __.sql__ and __.sql.gz__ compressed file.
|
It supports __.sql__ and __.sql.gz__ compressed file.
|
||||||
@@ -23,10 +23,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: restore -d database -f store_20231219_022941.sql.gz
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup restore -d database -f store_20231219_022941.sql.gz
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ nav_order: 1
|
|||||||
|
|
||||||
# About mysql-bkup
|
# About mysql-bkup
|
||||||
{:.no_toc}
|
{:.no_toc}
|
||||||
mysql-bkup 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, FTP 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].
|
||||||
@@ -32,7 +33,7 @@ Code and documentation for `v1` version on [this branch][v1-branch].
|
|||||||
|
|
||||||
### Simple backup using Docker CLI
|
### Simple backup using Docker CLI
|
||||||
|
|
||||||
To run a one time backup, bind your local volume to `/backup` in the container and run the `mysql-bkup backup` command:
|
To run a one time backup, bind your local volume to `/backup` in the container and run the `backup` command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run --rm --network your_network_name \
|
docker run --rm --network your_network_name \
|
||||||
@@ -40,11 +41,18 @@ To run a one time backup, bind your local volume to `/backup` in the container a
|
|||||||
-e "DB_HOST=dbhost" \
|
-e "DB_HOST=dbhost" \
|
||||||
-e "DB_USERNAME=username" \
|
-e "DB_USERNAME=username" \
|
||||||
-e "DB_PASSWORD=password" \
|
-e "DB_PASSWORD=password" \
|
||||||
jkaninda/mysql-bkup mysql-bkup backup -d database_name
|
jkaninda/mysql-bkup backup -d database_name
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, pass a `--env-file` in order to use a full config as described below.
|
Alternatively, pass a `--env-file` in order to use a full config as described below.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
docker run --rm --network your_network_name \
|
||||||
|
--env-file your-env-file \
|
||||||
|
-v $PWD/backup:/backup/ \
|
||||||
|
jkaninda/mysql-bkup backup -d database_name
|
||||||
|
```
|
||||||
|
|
||||||
### Simple backup in docker compose file
|
### Simple backup in docker compose file
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -56,15 +64,12 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
- DB_PORT=3306
|
- DB_PORT=3306
|
||||||
- DB_HOST=postgres
|
- DB_HOST=mysql
|
||||||
- DB_NAME=foo
|
- DB_NAME=foo
|
||||||
- DB_USERNAME=bar
|
- DB_USERNAME=bar
|
||||||
- DB_PASSWORD=password
|
- DB_PASSWORD=password
|
||||||
@@ -74,6 +79,61 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
web:
|
web:
|
||||||
```
|
```
|
||||||
|
### Docker recurring backup
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run --rm --network network_name \
|
||||||
|
-v $PWD/backup:/backup/ \
|
||||||
|
-e "DB_HOST=hostname" \
|
||||||
|
-e "DB_USERNAME=user" \
|
||||||
|
-e "DB_PASSWORD=password" \
|
||||||
|
jkaninda/mysql-bkup backup -d dbName --cron-expression "@every 1m"
|
||||||
|
```
|
||||||
|
See: https://jkaninda.github.io/mysql-bkup/reference/#predefined-schedules
|
||||||
|
|
||||||
|
## Kubernetes
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: backup-job
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 100
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mysql-bkup
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
|
# for a list of available releases.
|
||||||
|
image: jkaninda/mysql-bkup
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- backup -d dbname
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: "mysql"
|
||||||
|
- name: DB_USERNAME
|
||||||
|
value: "user"
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: "password"
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /backup
|
||||||
|
name: backup
|
||||||
|
volumes:
|
||||||
|
- name: backup
|
||||||
|
hostPath:
|
||||||
|
path: /home/toto/backup # directory location on host
|
||||||
|
type: Directory # this field is optional
|
||||||
|
restartPolicy: Never
|
||||||
|
```
|
||||||
|
|
||||||
## Available image registries
|
## Available image registries
|
||||||
|
|
||||||
@@ -81,8 +141,8 @@ This Docker image is published to both Docker Hub and the GitHub container regis
|
|||||||
Depending on your preferences and needs, you can reference both `jkaninda/mysql-bkup` as well as `ghcr.io/jkaninda/mysql-bkup`:
|
Depending on your preferences and needs, you can reference both `jkaninda/mysql-bkup` as well as `ghcr.io/jkaninda/mysql-bkup`:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker pull jkaninda/mysql-bkup:v1.0
|
docker pull jkaninda/mysql-bkup
|
||||||
docker pull ghcr.io/jkaninda/mysql-bkup:v1.0
|
docker pull ghcr.io/jkaninda/mysql-bkup
|
||||||
```
|
```
|
||||||
|
|
||||||
Documentation references Docker Hub, but all examples will work using ghcr.io just as well.
|
Documentation references Docker Hub, but all examples will work using ghcr.io just as well.
|
||||||
@@ -96,7 +156,7 @@ While it may work against different implementations, there are no guarantees abo
|
|||||||
|
|
||||||
We decided to publish this image as a simpler and more lightweight alternative because of the following requirements:
|
We decided to publish this image as a simpler and more lightweight alternative because of the following requirements:
|
||||||
|
|
||||||
- The original image is based on `ubuntu` and requires additional tools, making it heavy.
|
- The original image is based on `alpine` and requires additional tools, making it heavy.
|
||||||
- This image is written in Go.
|
- This image is written in Go.
|
||||||
- `arm64` and `arm/v7` architectures are supported.
|
- `arm64` and `arm/v7` architectures are supported.
|
||||||
- Docker in Swarm mode is supported.
|
- Docker in Swarm mode is supported.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ In the old version, S3 storage was mounted using s3fs, so we decided to migrate
|
|||||||
|
|
||||||
| Options | Shorts | Usage |
|
| Options | Shorts | Usage |
|
||||||
|-----------------------|--------|------------------------------------------------------------------------|
|
|-----------------------|--------|------------------------------------------------------------------------|
|
||||||
| mysql-bkup | bkup | CLI utility |
|
| mysql-bkup | bkup | CLI utility |
|
||||||
| backup | | Backup database operation |
|
| backup | | Backup database operation |
|
||||||
| restore | | Restore database operation |
|
| restore | | Restore database operation |
|
||||||
| history | | Show the history of backup |
|
| history | | Show the history of backup |
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ nav_order: 2
|
|||||||
|
|
||||||
# Configuration reference
|
# Configuration reference
|
||||||
|
|
||||||
Backup and restore targets, schedule and retention are configured using environment variables or flags.
|
Backup, restore and migrate targets, schedule and retention are configured using environment variables or flags.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -19,48 +19,60 @@ Backup and restore targets, schedule and retention are configured using environm
|
|||||||
| mysql-bkup | bkup | CLI utility |
|
| mysql-bkup | bkup | CLI utility |
|
||||||
| backup | | Backup database operation |
|
| backup | | Backup database operation |
|
||||||
| restore | | Restore database operation |
|
| restore | | Restore database operation |
|
||||||
|
| migrate | | Migrate database from one instance to another one |
|
||||||
| --storage | -s | Storage. local or s3 (default: local) |
|
| --storage | -s | Storage. local or s3 (default: local) |
|
||||||
| --file | -f | File name for restoration |
|
| --file | -f | File name for restoration |
|
||||||
| --path | | AWS S3 path without file name. eg: /custom_path or ssh remote path `/home/foo/backup` |
|
| --path | | AWS S3 path without file name. eg: /custom_path or ssh remote path `/home/foo/backup` |
|
||||||
| --dbname | -d | Database name |
|
| --dbname | -d | Database name |
|
||||||
| --port | -p | Database port (default: 3306) |
|
| --port | -p | Database port (default: 3306) |
|
||||||
| --mode | -m | Execution mode. default or scheduled (default: default) |
|
|
||||||
| --disable-compression | | Disable database backup compression |
|
| --disable-compression | | Disable database backup compression |
|
||||||
| --prune | | Delete old backup, default disabled |
|
| --prune | | Delete old backup, default disabled |
|
||||||
| --keep-last | | Delete old backup created more than specified days ago, default 7 days |
|
| --keep-last | | Delete old backup created more than specified days ago, default 7 days |
|
||||||
| --period | | Crontab period for scheduled mode only. (default: "0 1 * * *") |
|
| --cron-expression | | Backup cron expression, eg: (* * * * *) or @daily |
|
||||||
| --help | -h | Print this help message and exit |
|
| --help | -h | Print this help message and exit |
|
||||||
| --version | -V | Print version information and exit |
|
| --version | -V | Print version information and exit |
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
| Name | Requirement | Description |
|
| Name | Requirement | Description |
|
||||||
|-------------------|--------------------------------------------------|------------------------------------------------------|
|
|------------------------|---------------------------------------------------------------|------------------------------------------------------|
|
||||||
| DB_PORT | Optional, default 3306 | Database port number |
|
| DB_PORT | Optional, default 3306 | Database port number |
|
||||||
| DB_HOST | Required | Database host |
|
| DB_HOST | Required | Database host |
|
||||||
| DB_NAME | Optional if it was provided from the -d flag | Database name |
|
| DB_NAME | Optional if it was provided from the -d flag | Database name |
|
||||||
| DB_USERNAME | Required | Database user name |
|
| DB_USERNAME | Required | Database user name |
|
||||||
| DB_PASSWORD | Required | Database password |
|
| DB_PASSWORD | Required | Database password |
|
||||||
| AWS_ACCESS_KEY | Optional, required for S3 storage | AWS S3 Access Key |
|
| AWS_ACCESS_KEY | Optional, required for S3 storage | AWS S3 Access Key |
|
||||||
| AWS_SECRET_KEY | Optional, required for S3 storage | AWS S3 Secret Key |
|
| AWS_SECRET_KEY | Optional, required for S3 storage | AWS S3 Secret Key |
|
||||||
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
|
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
|
||||||
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
|
| AWS_BUCKET_NAME | Optional, required for S3 storage | AWS S3 Bucket Name |
|
||||||
| AWS_REGION | Optional, required for S3 storage | AWS Region |
|
| AWS_REGION | Optional, required for S3 storage | AWS Region |
|
||||||
| AWS_DISABLE_SSL | Optional, required for S3 storage | Disable SSL |
|
| AWS_DISABLE_SSL | Optional, required for S3 storage | Disable SSL |
|
||||||
| FILE_NAME | Optional if it was provided from the --file flag | Database file to restore (extensions: .sql, .sql.gz) |
|
| FILE_NAME | Optional if it was provided from the --file flag | Database file to restore (extensions: .sql, .sql.gz) |
|
||||||
| Gmysql_PASSPHRASE | Optional, required to encrypt and restore backup | Gmysql passphrase |
|
| GPG_PASSPHRASE | Optional, required to encrypt and restore backup | GPG passphrase |
|
||||||
| SSH_HOST_NAME | Optional, required for SSH storage | ssh remote hostname or ip |
|
| BACKUP_CRON_EXPRESSION | Optional if it was provided from the `--cron-expression` flag | Backup cron expression for docker in scheduled mode |
|
||||||
| SSH_USER | Optional, required for SSH storage | ssh remote user |
|
| SSH_HOST_NAME | Optional, required for SSH storage | ssh remote hostname or ip |
|
||||||
| SSH_PASSWORD | Optional, required for SSH storage | ssh remote user's password |
|
| SSH_USER | Optional, required for SSH storage | ssh remote user |
|
||||||
| SSH_IDENTIFY_FILE | Optional, required for SSH storage | ssh remote user's private key |
|
| SSH_PASSWORD | Optional, required for SSH storage | ssh remote user's password |
|
||||||
| SSH_PORT | Optional, required for SSH storage | ssh remote server port |
|
| SSH_IDENTIFY_FILE | Optional, required for SSH storage | ssh remote user's private key |
|
||||||
| SSH_REMOTE_PATH | Optional, required for SSH storage | ssh remote path (/home/toto/backup) |
|
| SSH_PORT | Optional, required for SSH storage | ssh remote server port |
|
||||||
|
| REMOTE_PATH | Optional, required for SSH or FTP storage | remote path (/home/toto/backup) |
|
||||||
|
| FTP_HOST_NAME | Optional, required for FTP storage | FTP host name |
|
||||||
|
| FTP_PORT | Optional, required for FTP storage | FTP server port number |
|
||||||
|
| FTP_USER | Optional, required for FTP storage | FTP user |
|
||||||
|
| FTP_PASSWORD | Optional, required for FTP storage | FTP user password |
|
||||||
|
| TARGET_DB_HOST | Optional, required for database migration | Target database host |
|
||||||
|
| TARGET_DB_PORT | Optional, required for database migration | Target database port |
|
||||||
|
| TARGET_DB_NAME | Optional, required for database migration | Target database name |
|
||||||
|
| TARGET_DB_USERNAME | Optional, required for database migration | Target database username |
|
||||||
|
| TARGET_DB_PASSWORD | Optional, required for database migration | Target database password |
|
||||||
|
| TG_TOKEN | Optional, required for Telegram notification | Telegram token (`BOT-ID:BOT-TOKEN`) |
|
||||||
|
| TG_CHAT_ID | Optional, required for Telegram notification | Telegram Chat ID |
|
||||||
|
|
||||||
---
|
---
|
||||||
## Run in Scheduled mode
|
## Run in Scheduled mode
|
||||||
|
|
||||||
This image can be run as CronJob in Kubernetes for a regular backup which makes deployment on Kubernetes easy as Kubernetes has CronJob resources.
|
This image 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.
|
For Docker, you need to run it in scheduled mode by adding `--cron-expression "* * * * *"` flag or by defining `BACKUP_CRON_EXPRESSION=0 1 * * *` environment variable.
|
||||||
|
|
||||||
## Syntax of crontab (field description)
|
## Syntax of crontab (field description)
|
||||||
|
|
||||||
@@ -102,4 +114,22 @@ Easy to remember format:
|
|||||||
|
|
||||||
```conf
|
```conf
|
||||||
0 1 * * *
|
0 1 * * *
|
||||||
```
|
```
|
||||||
|
## Predefined schedules
|
||||||
|
You may use one of several pre-defined schedules in place of a cron expression.
|
||||||
|
|
||||||
|
| Entry | Description | Equivalent To |
|
||||||
|
|------------------------|--------------------------------------------|---------------|
|
||||||
|
| @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 1 1 * |
|
||||||
|
| @monthly | Run once a month, midnight, first of month | 0 0 1 * * |
|
||||||
|
| @weekly | Run once a week, midnight between Sat/Sun | 0 0 * * 0 |
|
||||||
|
| @daily (or @midnight) | Run once a day, midnight | 0 0 * * * |
|
||||||
|
| @hourly | Run once an hour, beginning of hour | 0 * * * * |
|
||||||
|
|
||||||
|
### Intervals
|
||||||
|
You may also schedule backup task at fixed intervals, starting at the time it's added or cron is run. This is supported by formatting the cron spec like this:
|
||||||
|
|
||||||
|
@every <duration>
|
||||||
|
where "duration" is a string accepted by time.
|
||||||
|
|
||||||
|
For example, "@every 1h30m10s" would indicate a schedule that activates after 1 hour, 30 minutes, 10 seconds, and then every interval after that.
|
||||||
@@ -6,10 +6,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup --storage s3 -d my-database"
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup --storage s3 -d my-database"
|
|
||||||
environment:
|
environment:
|
||||||
- DB_PORT=3306
|
- DB_PORT=3306
|
||||||
- DB_HOST=mysql
|
- DB_HOST=mysql
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
mysql-bkup:
|
mysql-bkup:
|
||||||
|
# In production, it is advised to lock your image tag to a proper
|
||||||
|
# release version instead of using `latest`.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup --dbname database_name
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup --dbname database_name --mode scheduled --period "0 1 * * *"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
- DB_PORT=3306
|
- DB_PORT=3306
|
||||||
- DB_HOST=mysql
|
- DB_HOST=mysql
|
||||||
- DB_USERNAME=userName
|
- DB_USERNAME=userName
|
||||||
- DB_PASSWORD=${DB_PASSWORD}
|
- DB_PASSWORD=${DB_PASSWORD}
|
||||||
|
# See: https://jkaninda.github.io/mysql-bkup/reference/#predefined-schedules
|
||||||
|
- BACKUP_CRON_EXPRESSION=@daily #@every 5m|@weekly | @monthly |0 1 * * *
|
||||||
@@ -6,10 +6,7 @@ services:
|
|||||||
# for a list of available releases.
|
# for a list of available releases.
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup --storage s3 -d my-database
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup --storage s3 -d my-database --mode scheduled --period "0 1 * * *"
|
|
||||||
environment:
|
environment:
|
||||||
- DB_PORT=3306
|
- DB_PORT=3306
|
||||||
- DB_HOST=mysql
|
- DB_HOST=mysql
|
||||||
@@ -24,6 +21,8 @@ services:
|
|||||||
- AWS_SECRET_KEY=xxxxx
|
- AWS_SECRET_KEY=xxxxx
|
||||||
## In case you are using S3 alternative such as Minio and your Minio instance is not secured, you change it to true
|
## In case you are using S3 alternative such as Minio and your Minio instance is not secured, you change it to true
|
||||||
- AWS_DISABLE_SSL="false"
|
- AWS_DISABLE_SSL="false"
|
||||||
|
# See: https://jkaninda.github.io/mysql-bkup/reference/#predefined-schedules
|
||||||
|
- BACKUP_CRON_EXPRESSION=@daily #@every 5m|@weekly | @monthly |0 1 * * *
|
||||||
# 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
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ services:
|
|||||||
mysql-bkup:
|
mysql-bkup:
|
||||||
image: jkaninda/mysql-bkup
|
image: jkaninda/mysql-bkup
|
||||||
container_name: mysql-bkup
|
container_name: mysql-bkup
|
||||||
command:
|
command: backup --dbname database_name
|
||||||
- /bin/sh
|
|
||||||
- -c
|
|
||||||
- mysql-bkup backup --dbname database_name
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./backup:/backup
|
- ./backup:/backup
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -1,44 +1,47 @@
|
|||||||
piVersion: batch/v1
|
apiVersion: batch/v1
|
||||||
kind: CronJob
|
kind: Job
|
||||||
metadata:
|
metadata:
|
||||||
name: bkup-job
|
name: backup
|
||||||
spec:
|
spec:
|
||||||
schedule: "0 1 * * *"
|
template:
|
||||||
jobTemplate:
|
|
||||||
spec:
|
spec:
|
||||||
template:
|
containers:
|
||||||
spec:
|
- name: mysql-bkup
|
||||||
containers:
|
# In production, it is advised to lock your image tag to a proper
|
||||||
- name: mysql-bkup
|
# release version instead of using `latest`.
|
||||||
image: jkaninda/mysql-bkup
|
# Check https://github.com/jkaninda/mysql-bkup/releases
|
||||||
command:
|
# for a list of available releases.
|
||||||
- /bin/sh
|
image: jkaninda/mysql-bkup
|
||||||
- -c
|
command:
|
||||||
- mysql-bkup backup -s s3 --path /custom_path
|
- /bin/sh
|
||||||
env:
|
- -c
|
||||||
- name: DB_PORT
|
- backup --storage s3
|
||||||
value: "3306"
|
resources:
|
||||||
- name: DB_HOST
|
limits:
|
||||||
value: ""
|
memory: "128Mi"
|
||||||
- name: DB_NAME
|
cpu: "500m"
|
||||||
value: ""
|
env:
|
||||||
- name: DB_USERNAME
|
- name: DB_PORT
|
||||||
value: ""
|
value: "3306"
|
||||||
# Please use secret!
|
- name: DB_HOST
|
||||||
- name: DB_PASSWORD
|
value: ""
|
||||||
value: ""
|
- name: DB_NAME
|
||||||
- name: ACCESS_KEY
|
value: "dbname"
|
||||||
value: ""
|
- name: DB_USERNAME
|
||||||
- name: AWS_S3_ENDPOINT
|
value: "username"
|
||||||
value: "https://s3.amazonaws.com"
|
# Please use secret!
|
||||||
- name: AWS_S3_BUCKET_NAME
|
- name: DB_PASSWORD
|
||||||
value: "xxx"
|
value: ""
|
||||||
- name: AWS_REGION
|
- name: AWS_S3_ENDPOINT
|
||||||
value: "us-west-2"
|
value: "https://s3.amazonaws.com"
|
||||||
- name: AWS_ACCESS_KEY
|
- name: AWS_S3_BUCKET_NAME
|
||||||
value: "xxxx"
|
value: "xxx"
|
||||||
- name: AWS_SECRET_KEY
|
- name: AWS_REGION
|
||||||
value: "xxxx"
|
value: "us-west-2"
|
||||||
- name: AWS_DISABLE_SSL
|
- name: AWS_ACCESS_KEY
|
||||||
value: "false"
|
value: "xxxx"
|
||||||
restartPolicy: OnFailure
|
- name: AWS_SECRET_KEY
|
||||||
|
value: "xxxx"
|
||||||
|
- name: AWS_DISABLE_SSL
|
||||||
|
value: "false"
|
||||||
|
restartPolicy: Never
|
||||||
5
go.mod
5
go.mod
@@ -10,12 +10,15 @@ require (
|
|||||||
github.com/hpcloud/tail v1.0.0
|
github.com/hpcloud/tail v1.0.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
golang.org/x/crypto v0.18.0
|
golang.org/x/crypto v0.18.0
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jlaffaye/ftp v0.2.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
|
|||||||
9
go.sum
9
go.sum
@@ -7,14 +7,23 @@ github.com/bramvdbogaerde/go-scp v1.5.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9Hu
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=
|
||||||
|
github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=
|
||||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
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 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
|
|||||||
13
main.go
13
main.go
@@ -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() {
|
||||||
|
|||||||
285
pkg/backup.go
285
pkg/backup.go
@@ -1,13 +1,15 @@
|
|||||||
// 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 (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hpcloud/tail"
|
|
||||||
"github.com/jkaninda/mysql-bkup/utils"
|
"github.com/jkaninda/mysql-bkup/utils"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -17,119 +19,77 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func StartBackup(cmd *cobra.Command) {
|
func StartBackup(cmd *cobra.Command) {
|
||||||
_, _ = cmd.Flags().GetString("operation")
|
intro()
|
||||||
//Set env
|
dbConf = initDbConfig(cmd)
|
||||||
utils.SetEnv("STORAGE_PATH", storagePath)
|
//Initialize backup configs
|
||||||
utils.GetEnv(cmd, "dbname", "DB_NAME")
|
config := initBackupConfig(cmd)
|
||||||
utils.GetEnv(cmd, "port", "DB_PORT")
|
|
||||||
utils.GetEnv(cmd, "period", "SCHEDULE_PERIOD")
|
|
||||||
|
|
||||||
//Get flag value and set env
|
if config.cronExpression == "" {
|
||||||
s3Path := utils.GetEnv(cmd, "path", "AWS_S3_PATH")
|
BackupTask(dbConf, config)
|
||||||
remotePath := utils.GetEnv(cmd, "path", "SSH_REMOTE_PATH")
|
|
||||||
storage = utils.GetEnv(cmd, "storage", "STORAGE")
|
|
||||||
file = utils.GetEnv(cmd, "file", "FILE_NAME")
|
|
||||||
backupRetention, _ := cmd.Flags().GetInt("keep-last")
|
|
||||||
prune, _ := cmd.Flags().GetBool("prune")
|
|
||||||
disableCompression, _ = cmd.Flags().GetBool("disable-compression")
|
|
||||||
executionMode, _ = cmd.Flags().GetString("mode")
|
|
||||||
dbName = os.Getenv("DB_NAME")
|
|
||||||
gpqPassphrase := os.Getenv("GPG_PASSPHRASE")
|
|
||||||
//
|
|
||||||
if gpqPassphrase != "" {
|
|
||||||
encryption = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//Generate file name
|
|
||||||
backupFileName := fmt.Sprintf("%s_%s.sql.gz", dbName, time.Now().Format("20060102_150405"))
|
|
||||||
if disableCompression {
|
|
||||||
backupFileName = fmt.Sprintf("%s_%s.sql", dbName, time.Now().Format("20060102_150405"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if executionMode == "default" {
|
|
||||||
switch storage {
|
|
||||||
case "s3":
|
|
||||||
s3Backup(backupFileName, s3Path, disableCompression, prune, backupRetention, encryption)
|
|
||||||
case "local":
|
|
||||||
localBackup(backupFileName, disableCompression, prune, backupRetention, encryption)
|
|
||||||
case "ssh", "remote":
|
|
||||||
sshBackup(backupFileName, remotePath, disableCompression, prune, backupRetention, encryption)
|
|
||||||
case "ftp":
|
|
||||||
utils.Fatal("Not supported storage type: %s", storage)
|
|
||||||
default:
|
|
||||||
localBackup(backupFileName, disableCompression, prune, backupRetention, encryption)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if executionMode == "scheduled" {
|
|
||||||
scheduledMode()
|
|
||||||
} else {
|
} else {
|
||||||
utils.Fatal("Error, unknown execution mode!")
|
if utils.IsValidCronExpression(config.cronExpression) {
|
||||||
|
scheduledMode(dbConf, config)
|
||||||
|
} else {
|
||||||
|
utils.Fatal("Cron expression is not valid: %s", config.cronExpression)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run in scheduled mode
|
// Run in scheduled mode
|
||||||
func scheduledMode() {
|
func scheduledMode(db *dbConfig, config *BackupConfig) {
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("**********************************")
|
|
||||||
fmt.Println(" Starting MySQL Bkup... ")
|
|
||||||
fmt.Println("***********************************")
|
|
||||||
utils.Info("Running in Scheduled mode")
|
utils.Info("Running in Scheduled mode")
|
||||||
utils.Info("Execution period ", os.Getenv("SCHEDULE_PERIOD"))
|
utils.Info("Backup cron expression: %s", config.cronExpression)
|
||||||
|
utils.Info("Storage type %s ", config.storage)
|
||||||
|
|
||||||
//Test database connexion
|
//Test database connexion
|
||||||
utils.TestDatabaseConnection()
|
testDatabaseConnection(db)
|
||||||
|
//Test backup
|
||||||
|
utils.Info("Testing backup configurations...")
|
||||||
|
BackupTask(db, config)
|
||||||
|
utils.Info("Testing backup configurations...done")
|
||||||
utils.Info("Creating backup job...")
|
utils.Info("Creating backup job...")
|
||||||
CreateCrontabScript(disableCompression, storage)
|
// Create a new cron instance
|
||||||
|
c := cron.New()
|
||||||
|
|
||||||
supervisorConfig := "/etc/supervisor/supervisord.conf"
|
_, err := c.AddFunc(config.cronExpression, func() {
|
||||||
|
BackupTask(db, config)
|
||||||
// Start Supervisor
|
})
|
||||||
cmd := exec.Command("supervisord", "-c", supervisorConfig)
|
|
||||||
err := cmd.Start()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal(fmt.Sprintf("Failed to start supervisord: %v", err))
|
return
|
||||||
}
|
}
|
||||||
|
// Start the cron scheduler
|
||||||
|
c.Start()
|
||||||
|
utils.Info("Creating backup job...done")
|
||||||
utils.Info("Backup job started")
|
utils.Info("Backup job started")
|
||||||
defer func() {
|
defer c.Stop()
|
||||||
if err := cmd.Process.Kill(); err != nil {
|
select {}
|
||||||
utils.Info("Failed to kill supervisord process: %v", err)
|
}
|
||||||
} else {
|
func BackupTask(db *dbConfig, config *BackupConfig) {
|
||||||
utils.Info("Supervisor stopped.")
|
//Generate backup file name
|
||||||
}
|
backupFileName := fmt.Sprintf("%s_%s.sql.gz", db.dbName, time.Now().Format("20060102_150405"))
|
||||||
}()
|
if config.disableCompression {
|
||||||
if _, err := os.Stat(cronLogFile); os.IsNotExist(err) {
|
backupFileName = fmt.Sprintf("%s_%s.sql", db.dbName, time.Now().Format("20060102_150405"))
|
||||||
utils.Fatal(fmt.Sprintf("Log file %s does not exist.", cronLogFile))
|
|
||||||
}
|
}
|
||||||
t, err := tail.TailFile(cronLogFile, tail.Config{Follow: true})
|
config.backupFileName = backupFileName
|
||||||
if err != nil {
|
switch config.storage {
|
||||||
utils.Fatal("Failed to tail file: %v", err)
|
case "local":
|
||||||
}
|
localBackup(db, config)
|
||||||
|
case "s3":
|
||||||
// Read and print new lines from the log file
|
s3Backup(db, config)
|
||||||
for line := range t.Lines {
|
case "ssh", "remote":
|
||||||
fmt.Println(line.Text)
|
sshBackup(db, config)
|
||||||
|
case "ftp":
|
||||||
|
ftpBackup(db, config)
|
||||||
|
default:
|
||||||
|
localBackup(db, config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackupDatabase backup database
|
// BackupDatabase backup database
|
||||||
func BackupDatabase(backupFileName string, disableCompression bool) {
|
func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) {
|
||||||
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")
|
storagePath = os.Getenv("STORAGE_PATH")
|
||||||
|
|
||||||
// dbHVars Required environment variables for database
|
|
||||||
var dbHVars = []string{
|
|
||||||
"DB_HOST",
|
|
||||||
"DB_PASSWORD",
|
|
||||||
"DB_USERNAME",
|
|
||||||
"DB_NAME",
|
|
||||||
}
|
|
||||||
err := utils.CheckEnvVars(dbHVars)
|
err := utils.CheckEnvVars(dbHVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("Please make sure all required environment variables for database are set")
|
utils.Error("Please make sure all required environment variables for database are set")
|
||||||
@@ -137,7 +97,11 @@ func BackupDatabase(backupFileName string, disableCompression bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
utils.Info("Starting database backup...")
|
utils.Info("Starting database backup...")
|
||||||
utils.TestDatabaseConnection()
|
err = os.Setenv("MYSQL_PWD", db.dbPassword)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
testDatabaseConnection(db)
|
||||||
|
|
||||||
// Backup Database database
|
// Backup Database database
|
||||||
utils.Info("Backing up database...")
|
utils.Info("Backing up database...")
|
||||||
@@ -145,11 +109,10 @@ func BackupDatabase(backupFileName string, disableCompression bool) {
|
|||||||
if disableCompression {
|
if disableCompression {
|
||||||
// Execute mysqldump
|
// Execute mysqldump
|
||||||
cmd := exec.Command("mysqldump",
|
cmd := exec.Command("mysqldump",
|
||||||
"-h", dbHost,
|
"-h", db.dbHost,
|
||||||
"-P", dbPort,
|
"-P", db.dbPort,
|
||||||
"-u", dbUserName,
|
"-u", db.dbUserName,
|
||||||
"--password="+dbPassword,
|
db.dbName,
|
||||||
dbName,
|
|
||||||
)
|
)
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -171,7 +134,7 @@ func BackupDatabase(backupFileName string, disableCompression bool) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Execute mysqldump
|
// Execute mysqldump
|
||||||
cmd := exec.Command("mysqldump", "-h", dbHost, "-P", dbPort, "-u", dbUserName, "--password="+dbPassword, 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)
|
||||||
@@ -194,67 +157,78 @@ func BackupDatabase(backupFileName string, disableCompression bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
func localBackup(backupFileName string, disableCompression bool, prune bool, backupRetention int, encrypt bool) {
|
func localBackup(db *dbConfig, config *BackupConfig) {
|
||||||
utils.Info("Backup database to local storage")
|
utils.Info("Backup database to local storage")
|
||||||
BackupDatabase(backupFileName, disableCompression)
|
BackupDatabase(db, config.backupFileName, disableCompression)
|
||||||
finalFileName := backupFileName
|
finalFileName := config.backupFileName
|
||||||
if encrypt {
|
if config.encryption {
|
||||||
encryptBackup(backupFileName)
|
encryptBackup(config.backupFileName, config.passphrase)
|
||||||
finalFileName = fmt.Sprintf("%s.%s", backupFileName, gpgExtension)
|
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, gpgExtension)
|
||||||
}
|
}
|
||||||
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 config.prune {
|
||||||
deleteOldBackup(backupRetention)
|
deleteOldBackup(config.backupRetention)
|
||||||
}
|
}
|
||||||
|
//Delete temp
|
||||||
|
deleteTemp()
|
||||||
}
|
}
|
||||||
|
|
||||||
func s3Backup(backupFileName string, s3Path string, disableCompression bool, prune bool, backupRetention int, encrypt bool) {
|
func s3Backup(db *dbConfig, config *BackupConfig) {
|
||||||
bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
|
bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
|
||||||
|
s3Path := utils.GetEnvVariable("AWS_S3_PATH", "S3_PATH")
|
||||||
utils.Info("Backup database to s3 storage")
|
utils.Info("Backup database to s3 storage")
|
||||||
//Backup database
|
//Backup database
|
||||||
BackupDatabase(backupFileName, disableCompression)
|
BackupDatabase(db, config.backupFileName, disableCompression)
|
||||||
finalFileName := backupFileName
|
finalFileName := config.backupFileName
|
||||||
if encrypt {
|
if config.encryption {
|
||||||
encryptBackup(backupFileName)
|
encryptBackup(config.backupFileName, config.passphrase)
|
||||||
finalFileName = fmt.Sprintf("%s.%s", backupFileName, "gpg")
|
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
|
||||||
}
|
}
|
||||||
utils.Info("Uploading backup file to S3 storage...")
|
utils.Info("Uploading backup archive to remote storage S3 ... ")
|
||||||
utils.Info("Backup name is %s", finalFileName)
|
utils.Info("Backup name is %s", finalFileName)
|
||||||
err := utils.UploadFileToS3(tmpPath, finalFileName, bucket, s3Path)
|
err := UploadFileToS3(tmpPath, finalFileName, bucket, s3Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal("Error uploading file to S3: %s ", err)
|
utils.Fatal("Error uploading file to S3: %s ", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Delete backup file from tmp folder
|
//Delete backup file from tmp folder
|
||||||
err = utils.DeleteFile(filepath.Join(tmpPath, backupFileName))
|
err = utils.DeleteFile(filepath.Join(tmpPath, config.backupFileName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error deleting file: ", err)
|
fmt.Println("Error deleting file: ", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
// Delete old backup
|
// Delete old backup
|
||||||
if prune {
|
if config.prune {
|
||||||
err := utils.DeleteOldBackup(bucket, s3Path, backupRetention)
|
err := DeleteOldBackup(bucket, s3Path, config.backupRetention)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal("Error deleting old backup from S3: %s ", err)
|
utils.Fatal("Error deleting old backup from S3: %s ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
utils.Done("Database has been backed up and uploaded to s3 ")
|
utils.Done("Uploading backup archive to remote storage S3 ... done ")
|
||||||
|
//Send notification
|
||||||
|
utils.NotifySuccess(finalFileName)
|
||||||
|
//Delete temp
|
||||||
|
deleteTemp()
|
||||||
}
|
}
|
||||||
func sshBackup(backupFileName, remotePath string, disableCompression bool, prune bool, backupRetention int, encrypt bool) {
|
|
||||||
|
// sshBackup backup database to SSH remote server
|
||||||
|
func sshBackup(db *dbConfig, config *BackupConfig) {
|
||||||
utils.Info("Backup database to Remote server")
|
utils.Info("Backup database to Remote server")
|
||||||
//Backup database
|
//Backup database
|
||||||
BackupDatabase(backupFileName, disableCompression)
|
BackupDatabase(db, config.backupFileName, disableCompression)
|
||||||
finalFileName := backupFileName
|
finalFileName := config.backupFileName
|
||||||
if encrypt {
|
if config.encryption {
|
||||||
encryptBackup(backupFileName)
|
encryptBackup(config.backupFileName, config.passphrase)
|
||||||
finalFileName = fmt.Sprintf("%s.%s", backupFileName, "gpg")
|
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
|
||||||
}
|
}
|
||||||
utils.Info("Uploading backup file to remote server...")
|
utils.Info("Uploading backup archive to remote storage ... ")
|
||||||
utils.Info("Backup name is %s", finalFileName)
|
utils.Info("Backup name is %s", finalFileName)
|
||||||
err := CopyToRemote(finalFileName, remotePath)
|
err := CopyToRemote(finalFileName, config.remotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal("Error uploading file to the remote server: %s ", err)
|
utils.Fatal("Error uploading file to the remote server: %s ", err)
|
||||||
|
|
||||||
@@ -266,18 +240,57 @@ func sshBackup(backupFileName, remotePath string, disableCompression bool, prune
|
|||||||
fmt.Println("Error deleting file: ", err)
|
fmt.Println("Error deleting file: ", err)
|
||||||
|
|
||||||
}
|
}
|
||||||
if prune {
|
if config.prune {
|
||||||
//TODO: Delete old backup from remote server
|
//TODO: Delete old backup from remote server
|
||||||
utils.Info("Deleting old backup from a remote server is not implemented yet")
|
utils.Info("Deleting old backup from a remote server is not implemented yet")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Done("Database has been backed up and uploaded to remote server ")
|
utils.Done("Uploading backup archive to remote storage ... done ")
|
||||||
|
//Send notification
|
||||||
|
utils.NotifySuccess(finalFileName)
|
||||||
|
//Delete temp
|
||||||
|
deleteTemp()
|
||||||
|
}
|
||||||
|
func ftpBackup(db *dbConfig, config *BackupConfig) {
|
||||||
|
utils.Info("Backup database to the remote FTP server")
|
||||||
|
//Backup database
|
||||||
|
BackupDatabase(db, config.backupFileName, disableCompression)
|
||||||
|
finalFileName := config.backupFileName
|
||||||
|
if config.encryption {
|
||||||
|
encryptBackup(config.backupFileName, config.passphrase)
|
||||||
|
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
|
||||||
|
}
|
||||||
|
utils.Info("Uploading backup archive to the remote FTP server ... ")
|
||||||
|
utils.Info("Backup name is %s", finalFileName)
|
||||||
|
err := CopyToFTP(finalFileName, config.remotePath)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatal("Error uploading file to the remote FTP server: %s ", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delete backup file from tmp folder
|
||||||
|
err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName))
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("Error deleting file: %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
if config.prune {
|
||||||
|
//TODO: Delete old backup from remote server
|
||||||
|
utils.Info("Deleting old backup from a remote server is not implemented yet")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Done("Uploading backup archive to the remote FTP server ... done ")
|
||||||
|
//Send notification
|
||||||
|
utils.NotifySuccess(finalFileName)
|
||||||
|
//Delete temp
|
||||||
|
deleteTemp()
|
||||||
}
|
}
|
||||||
|
|
||||||
func encryptBackup(backupFileName string) {
|
// encryptBackup encrypt backup
|
||||||
gpgPassphrase := os.Getenv("GPG_PASSPHRASE")
|
func encryptBackup(backupFileName, passphrase string) {
|
||||||
err := Encrypt(filepath.Join(tmpPath, backupFileName), gpgPassphrase)
|
err := Encrypt(filepath.Join(tmpPath, backupFileName), passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal("Error during encrypting backup %s", err)
|
utils.Fatal("Error during encrypting backup %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
157
pkg/config.go
157
pkg/config.go
@@ -1,4 +1,161 @@
|
|||||||
|
// Package pkg /
|
||||||
|
/*****
|
||||||
|
@author Jonas Kaninda
|
||||||
|
@license MIT License <https://opensource.org/licenses/MIT>
|
||||||
|
@Copyright © 2024 Jonas Kaninda
|
||||||
|
**/
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jkaninda/mysql-bkup/utils"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dbConfig struct {
|
||||||
|
dbHost string
|
||||||
|
dbPort string
|
||||||
|
dbName string
|
||||||
|
dbUserName string
|
||||||
|
dbPassword string
|
||||||
|
}
|
||||||
|
type targetDbConfig struct {
|
||||||
|
targetDbHost string
|
||||||
|
targetDbPort string
|
||||||
|
targetDbUserName string
|
||||||
|
targetDbPassword string
|
||||||
|
targetDbName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackupConfig struct {
|
||||||
|
backupFileName string
|
||||||
|
backupRetention int
|
||||||
|
disableCompression bool
|
||||||
|
prune bool
|
||||||
|
encryption bool
|
||||||
|
remotePath string
|
||||||
|
passphrase string
|
||||||
|
storage string
|
||||||
|
cronExpression string
|
||||||
|
}
|
||||||
|
type RestoreConfig struct {
|
||||||
|
s3Path string
|
||||||
|
remotePath string
|
||||||
|
storage string
|
||||||
|
file string
|
||||||
|
bucket string
|
||||||
|
gpqPassphrase string
|
||||||
|
}
|
||||||
|
type FTPConfig struct {
|
||||||
|
host string
|
||||||
|
user string
|
||||||
|
password string
|
||||||
|
port string
|
||||||
|
remotePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDbConfig(cmd *cobra.Command) *dbConfig {
|
||||||
|
//Set env
|
||||||
|
utils.GetEnv(cmd, "dbname", "DB_NAME")
|
||||||
|
dConf := dbConfig{}
|
||||||
|
dConf.dbHost = os.Getenv("DB_HOST")
|
||||||
|
dConf.dbPort = os.Getenv("DB_PORT")
|
||||||
|
dConf.dbName = os.Getenv("DB_NAME")
|
||||||
|
dConf.dbUserName = os.Getenv("DB_USERNAME")
|
||||||
|
dConf.dbPassword = os.Getenv("DB_PASSWORD")
|
||||||
|
|
||||||
|
err := utils.CheckEnvVars(dbHVars)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("Please make sure all required environment variables for database are set")
|
||||||
|
utils.Fatal("Error checking environment variables: %s", err)
|
||||||
|
}
|
||||||
|
return &dConf
|
||||||
|
}
|
||||||
|
func initBackupConfig(cmd *cobra.Command) *BackupConfig {
|
||||||
|
utils.SetEnv("STORAGE_PATH", storagePath)
|
||||||
|
utils.GetEnv(cmd, "cron-expression", "BACKUP_CRON_EXPRESSION")
|
||||||
|
utils.GetEnv(cmd, "period", "BACKUP_CRON_EXPRESSION")
|
||||||
|
utils.GetEnv(cmd, "path", "REMOTE_PATH")
|
||||||
|
remotePath := utils.GetEnvVariable("REMOTE_PATH", "SSH_REMOTE_PATH")
|
||||||
|
|
||||||
|
//Get flag value and set env
|
||||||
|
storage = utils.GetEnv(cmd, "storage", "STORAGE")
|
||||||
|
backupRetention, _ := cmd.Flags().GetInt("keep-last")
|
||||||
|
prune, _ := cmd.Flags().GetBool("prune")
|
||||||
|
disableCompression, _ = cmd.Flags().GetBool("disable-compression")
|
||||||
|
_, _ = cmd.Flags().GetString("mode")
|
||||||
|
passphrase := os.Getenv("GPG_PASSPHRASE")
|
||||||
|
_ = utils.GetEnv(cmd, "path", "AWS_S3_PATH")
|
||||||
|
cronExpression := os.Getenv("BACKUP_CRON_EXPRESSION")
|
||||||
|
|
||||||
|
if passphrase != "" {
|
||||||
|
encryption = true
|
||||||
|
}
|
||||||
|
//Initialize backup configs
|
||||||
|
config := BackupConfig{}
|
||||||
|
config.backupRetention = backupRetention
|
||||||
|
config.disableCompression = disableCompression
|
||||||
|
config.prune = prune
|
||||||
|
config.storage = storage
|
||||||
|
config.encryption = encryption
|
||||||
|
config.remotePath = remotePath
|
||||||
|
config.passphrase = passphrase
|
||||||
|
config.cronExpression = cronExpression
|
||||||
|
return &config
|
||||||
|
}
|
||||||
|
func initRestoreConfig(cmd *cobra.Command) *RestoreConfig {
|
||||||
|
utils.SetEnv("STORAGE_PATH", storagePath)
|
||||||
|
utils.GetEnv(cmd, "path", "REMOTE_PATH")
|
||||||
|
remotePath := utils.GetEnvVariable("REMOTE_PATH", "SSH_REMOTE_PATH")
|
||||||
|
|
||||||
|
//Get flag value and set env
|
||||||
|
s3Path := utils.GetEnv(cmd, "path", "AWS_S3_PATH")
|
||||||
|
storage = utils.GetEnv(cmd, "storage", "STORAGE")
|
||||||
|
file = utils.GetEnv(cmd, "file", "FILE_NAME")
|
||||||
|
_, _ = cmd.Flags().GetString("mode")
|
||||||
|
bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
|
||||||
|
gpqPassphrase := os.Getenv("GPG_PASSPHRASE")
|
||||||
|
//Initialize restore configs
|
||||||
|
rConfig := RestoreConfig{}
|
||||||
|
rConfig.s3Path = s3Path
|
||||||
|
rConfig.remotePath = remotePath
|
||||||
|
rConfig.storage = storage
|
||||||
|
rConfig.bucket = bucket
|
||||||
|
rConfig.file = file
|
||||||
|
rConfig.storage = storage
|
||||||
|
rConfig.gpqPassphrase = gpqPassphrase
|
||||||
|
return &rConfig
|
||||||
|
}
|
||||||
|
func initTargetDbConfig() *targetDbConfig {
|
||||||
|
tdbConfig := targetDbConfig{}
|
||||||
|
tdbConfig.targetDbHost = os.Getenv("TARGET_DB_HOST")
|
||||||
|
tdbConfig.targetDbPort = os.Getenv("TARGET_DB_PORT")
|
||||||
|
tdbConfig.targetDbName = os.Getenv("TARGET_DB_NAME")
|
||||||
|
tdbConfig.targetDbUserName = os.Getenv("TARGET_DB_USERNAME")
|
||||||
|
tdbConfig.targetDbPassword = os.Getenv("TARGET_DB_PASSWORD")
|
||||||
|
|
||||||
|
err := utils.CheckEnvVars(tdbRVars)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("Please make sure all required environment variables for the target database are set")
|
||||||
|
utils.Fatal("Error checking target database environment variables: %s", err)
|
||||||
|
}
|
||||||
|
return &tdbConfig
|
||||||
|
}
|
||||||
|
func initFtpConfig() *FTPConfig {
|
||||||
|
//Initialize backup configs
|
||||||
|
fConfig := FTPConfig{}
|
||||||
|
fConfig.host = os.Getenv("FTP_HOST_NAME")
|
||||||
|
fConfig.user = os.Getenv("FTP_USER")
|
||||||
|
fConfig.password = os.Getenv("FTP_PASSWORD")
|
||||||
|
fConfig.port = os.Getenv("FTP_PORT")
|
||||||
|
fConfig.remotePath = os.Getenv("REMOTE_PATH")
|
||||||
|
err := utils.CheckEnvVars(ftpVars)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("Please make sure all required environment variables for FTP are set")
|
||||||
|
utils.Fatal("Error checking environment variables: %s", err)
|
||||||
|
}
|
||||||
|
return &fConfig
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
|
// Package pkg /
|
||||||
|
/*****
|
||||||
|
@author Jonas Kaninda
|
||||||
|
@license MIT License <https://opensource.org/licenses/MIT>
|
||||||
|
@Copyright © 2024 Jonas Kaninda
|
||||||
|
**/
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/jkaninda/mysql-bkup/utils"
|
"github.com/jkaninda/mysql-bkup/utils"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -10,13 +15,18 @@ import (
|
|||||||
|
|
||||||
func Decrypt(inputFile string, passphrase string) error {
|
func Decrypt(inputFile string, passphrase string) error {
|
||||||
utils.Info("Decrypting backup file: " + inputFile + " ...")
|
utils.Info("Decrypting backup file: " + inputFile + " ...")
|
||||||
|
//Create gpg home dir
|
||||||
|
err := utils.MakeDirAll(gpgHome)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
utils.SetEnv("GNUPGHOME", gpgHome)
|
||||||
cmd := exec.Command("gpg", "--batch", "--passphrase", passphrase, "--output", RemoveLastExtension(inputFile), "--decrypt", inputFile)
|
cmd := exec.Command("gpg", "--batch", "--passphrase", passphrase, "--output", RemoveLastExtension(inputFile), "--decrypt", inputFile)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
err := cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,13 +36,18 @@ func Decrypt(inputFile string, passphrase string) error {
|
|||||||
|
|
||||||
func Encrypt(inputFile string, passphrase string) error {
|
func Encrypt(inputFile string, passphrase string) error {
|
||||||
utils.Info("Encrypting backup...")
|
utils.Info("Encrypting backup...")
|
||||||
|
//Create gpg home dir
|
||||||
|
err := utils.MakeDirAll(gpgHome)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
utils.SetEnv("GNUPGHOME", gpgHome)
|
||||||
cmd := exec.Command("gpg", "--batch", "--passphrase", passphrase, "--symmetric", "--cipher-algo", algorithm, inputFile)
|
cmd := exec.Command("gpg", "--batch", "--passphrase", passphrase, "--symmetric", "--cipher-algo", algorithm, inputFile)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
err := cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
81
pkg/ftp.go
Normal file
81
pkg/ftp.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/jlaffaye/ftp"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// initFtpClient initializes and authenticates an FTP client
|
||||||
|
func initFtpClient() (*ftp.ServerConn, error) {
|
||||||
|
ftpConfig := initFtpConfig()
|
||||||
|
ftpClient, err := ftp.Dial(fmt.Sprintf("%s:%s", ftpConfig.host, ftpConfig.port), ftp.DialWithTimeout(5*time.Second))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to FTP: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ftpClient.Login(ftpConfig.user, ftpConfig.password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to log in to FTP: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ftpClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyToFTP uploads a file to the remote FTP server
|
||||||
|
func CopyToFTP(fileName, remotePath string) (err error) {
|
||||||
|
ftpConfig := initFtpConfig()
|
||||||
|
ftpClient, err := initFtpClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ftpClient.Quit()
|
||||||
|
|
||||||
|
filePath := filepath.Join(tmpPath, fileName)
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open file %s: %w", fileName, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
remoteFilePath := filepath.Join(ftpConfig.remotePath, fileName)
|
||||||
|
err = ftpClient.Stor(remoteFilePath, file)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to upload file %s: %w", fileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFromFTP downloads a file from the remote FTP server
|
||||||
|
func CopyFromFTP(fileName, remotePath string) (err error) {
|
||||||
|
ftpClient, err := initFtpClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ftpClient.Quit()
|
||||||
|
|
||||||
|
remoteFilePath := filepath.Join(remotePath, fileName)
|
||||||
|
r, err := ftpClient.Retr(remoteFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to retrieve file %s: %w", fileName, err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
localFilePath := filepath.Join(tmpPath, fileName)
|
||||||
|
outFile, err := os.Create(localFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create local file %s: %w", fileName, err)
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(outFile, r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to copy data to local file %s: %w", fileName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
|
// Package pkg /
|
||||||
|
/*****
|
||||||
|
@author Jonas Kaninda
|
||||||
|
@license MIT License <https://opensource.org/licenses/MIT>
|
||||||
|
@Copyright © 2024 Jonas Kaninda
|
||||||
|
**/
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jkaninda/mysql-bkup/utils"
|
"github.com/jkaninda/mysql-bkup/utils"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -71,4 +79,53 @@ func deleteOldBackup(retentionDays int) {
|
|||||||
utils.Fatal(fmt.Sprintf("Error: %s", err))
|
utils.Fatal(fmt.Sprintf("Error: %s", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
utils.Done("Deleting old backups...done")
|
||||||
|
|
||||||
|
}
|
||||||
|
func deleteTemp() {
|
||||||
|
utils.Info("Deleting %s ...", tmpPath)
|
||||||
|
err := filepath.Walk(tmpPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Check if the current item is a file
|
||||||
|
if !info.IsDir() {
|
||||||
|
// Delete the file
|
||||||
|
err = os.Remove(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("Error deleting files: %v", err)
|
||||||
|
} else {
|
||||||
|
utils.Info("Deleting %s ... done", tmpPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDatabaseConnection tests the database connection
|
||||||
|
func testDatabaseConnection(db *dbConfig) {
|
||||||
|
err := os.Setenv("MYSQL_PWD", db.dbPassword)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
// Capture the output
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &out
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatal("Error testing database connection: %v\nOutput: %s", err, out.String())
|
||||||
|
|
||||||
|
}
|
||||||
|
utils.Info("Successfully connected to %s database", db.dbName)
|
||||||
|
|
||||||
|
}
|
||||||
|
func intro() {
|
||||||
|
utils.Info("Starting MySQL Backup...")
|
||||||
|
utils.Info("Copyright (c) 2024 Jonas Kaninda ")
|
||||||
}
|
}
|
||||||
|
|||||||
40
pkg/migrate.go
Normal file
40
pkg/migrate.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Package pkg /
|
||||||
|
/*****
|
||||||
|
@author Jonas Kaninda
|
||||||
|
@license MIT License <https://opensource.org/licenses/MIT>
|
||||||
|
@Copyright © 2024 Jonas Kaninda
|
||||||
|
**/
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/jkaninda/mysql-bkup/utils"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartMigration(cmd *cobra.Command) {
|
||||||
|
intro()
|
||||||
|
utils.Info("Starting database migration...")
|
||||||
|
//Get DB config
|
||||||
|
dbConf = initDbConfig(cmd)
|
||||||
|
targetDbConf = initTargetDbConfig()
|
||||||
|
|
||||||
|
//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
|
||||||
|
backupFileName := fmt.Sprintf("%s_%s.sql", dbConf.dbName, time.Now().Format("20060102_150405"))
|
||||||
|
//Backup source Database
|
||||||
|
BackupDatabase(dbConf, backupFileName, true)
|
||||||
|
//Restore source database into target database
|
||||||
|
utils.Info("Restoring [%s] database into [%s] database...", dbConf.dbName, targetDbConf.targetDbName)
|
||||||
|
RestoreDatabase(&newDbConfig, backupFileName)
|
||||||
|
utils.Info("[%s] database has been restored into [%s] database", dbConf.dbName, targetDbConf.targetDbName)
|
||||||
|
utils.Info("Database migration completed.")
|
||||||
|
}
|
||||||
103
pkg/restore.go
103
pkg/restore.go
@@ -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,71 +16,60 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func StartRestore(cmd *cobra.Command) {
|
func StartRestore(cmd *cobra.Command) {
|
||||||
|
intro()
|
||||||
|
dbConf = initDbConfig(cmd)
|
||||||
|
restoreConf := initRestoreConfig(cmd)
|
||||||
|
|
||||||
//Set env
|
switch restoreConf.storage {
|
||||||
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", "AWS_S3_PATH")
|
|
||||||
remotePath := utils.GetEnv(cmd, "path", "SSH_REMOTE_PATH")
|
|
||||||
storage = utils.GetEnv(cmd, "storage", "STORAGE")
|
|
||||||
file = utils.GetEnv(cmd, "file", "FILE_NAME")
|
|
||||||
executionMode, _ = cmd.Flags().GetString("mode")
|
|
||||||
bucket := utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
|
|
||||||
switch storage {
|
|
||||||
case "s3":
|
case "s3":
|
||||||
restoreFromS3(file, bucket, s3Path)
|
restoreFromS3(dbConf, restoreConf.file, restoreConf.bucket, restoreConf.s3Path)
|
||||||
case "local":
|
case "local":
|
||||||
utils.Info("Restore database from local")
|
utils.Info("Restore database from local")
|
||||||
copyToTmp(storagePath, file)
|
copyToTmp(storagePath, restoreConf.file)
|
||||||
RestoreDatabase(file)
|
RestoreDatabase(dbConf, restoreConf.file)
|
||||||
case "ssh":
|
case "ssh":
|
||||||
restoreFromRemote(file, remotePath)
|
restoreFromRemote(dbConf, restoreConf.file, restoreConf.remotePath)
|
||||||
case "ftp":
|
case "ftp":
|
||||||
utils.Fatal("Restore from FTP is not yet supported")
|
restoreFromFTP(dbConf, restoreConf.file, restoreConf.remotePath)
|
||||||
default:
|
default:
|
||||||
utils.Info("Restore database from local")
|
utils.Info("Restore database from local")
|
||||||
RestoreDatabase(file)
|
copyToTmp(storagePath, restoreConf.file)
|
||||||
|
RestoreDatabase(dbConf, restoreConf.file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreFromS3(file, bucket, s3Path string) {
|
func restoreFromS3(db *dbConfig, file, bucket, s3Path string) {
|
||||||
utils.Info("Restore database from s3")
|
utils.Info("Restore database from s3")
|
||||||
err := utils.DownloadFile(tmpPath, file, bucket, s3Path)
|
err := DownloadFile(tmpPath, file, bucket, s3Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal(fmt.Sprintf("Error download file from s3 %s %s", file, err))
|
utils.Fatal("Error download file from s3 %s %v", file, err)
|
||||||
}
|
}
|
||||||
RestoreDatabase(file)
|
RestoreDatabase(db, file)
|
||||||
}
|
}
|
||||||
func restoreFromRemote(file, remotePath string) {
|
func restoreFromRemote(db *dbConfig, file, remotePath string) {
|
||||||
utils.Info("Restore database from remote server")
|
utils.Info("Restore database from remote server")
|
||||||
err := CopyFromRemote(file, remotePath)
|
err := CopyFromRemote(file, remotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal(fmt.Sprintf("Error download file from remote server: ", filepath.Join(remotePath, file), err))
|
utils.Fatal("Error download file from remote server: %s %v ", filepath.Join(remotePath, file), err)
|
||||||
}
|
}
|
||||||
RestoreDatabase(file)
|
RestoreDatabase(db, file)
|
||||||
|
}
|
||||||
|
func restoreFromFTP(db *dbConfig, file, remotePath string) {
|
||||||
|
utils.Info("Restore database from FTP server")
|
||||||
|
err := CopyFromFTP(file, remotePath)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatal("Error download file from FTP server: %s %v", filepath.Join(remotePath, file), err)
|
||||||
|
}
|
||||||
|
RestoreDatabase(db, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreDatabase restore database
|
// RestoreDatabase restore database
|
||||||
func RestoreDatabase(file string) {
|
func RestoreDatabase(db *dbConfig, file string) {
|
||||||
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")
|
|
||||||
gpgPassphrase := os.Getenv("GPG_PASSPHRASE")
|
gpgPassphrase := os.Getenv("GPG_PASSPHRASE")
|
||||||
if file == "" {
|
if file == "" {
|
||||||
utils.Fatal("Error, file required")
|
utils.Fatal("Error, file required")
|
||||||
}
|
}
|
||||||
// dbHVars Required environment variables for database
|
|
||||||
var dbHVars = []string{
|
|
||||||
"DB_HOST",
|
|
||||||
"DB_PASSWORD",
|
|
||||||
"DB_USERNAME",
|
|
||||||
"DB_NAME",
|
|
||||||
}
|
|
||||||
err := utils.CheckEnvVars(dbHVars)
|
err := utils.CheckEnvVars(dbHVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("Please make sure all required environment variables for database are set")
|
utils.Error("Please make sure all required environment variables for database are set")
|
||||||
@@ -90,7 +85,7 @@ func RestoreDatabase(file string) {
|
|||||||
//Decrypt file
|
//Decrypt file
|
||||||
err := Decrypt(filepath.Join(tmpPath, file), gpgPassphrase)
|
err := Decrypt(filepath.Join(tmpPath, file), gpgPassphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatal("Error decrypting file ", file, err)
|
utils.Fatal("Error decrypting file %s %v", file, err)
|
||||||
}
|
}
|
||||||
//Update file name
|
//Update file name
|
||||||
file = RemoveLastExtension(file)
|
file = RemoveLastExtension(file)
|
||||||
@@ -99,36 +94,42 @@ func RestoreDatabase(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)
|
||||||
err := os.Setenv("mysqlPASSWORD", dbPassword)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.TestDatabaseConnection()
|
testDatabaseConnection(db)
|
||||||
|
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("sh", "-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.Done("Database has been restored")
|
utils.Done("Database has been restored")
|
||||||
|
//Delete temp
|
||||||
|
deleteTemp()
|
||||||
|
|
||||||
} 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("sh", "-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.Done("Database has been restored")
|
utils.Done("Database has been restored")
|
||||||
|
//Delete temp
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
package utils
|
// Package utils /
|
||||||
|
/*****
|
||||||
|
@author Jonas Kaninda
|
||||||
|
@license MIT License <https://opensource.org/licenses/MIT>
|
||||||
|
@Copyright © 2024 Jonas Kaninda
|
||||||
|
**/
|
||||||
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -8,7 +14,7 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
"github.com/aws/aws-sdk-go/service/s3"
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||||
"golang.org/x/exp/slog"
|
"github.com/jkaninda/mysql-bkup/utils"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -30,21 +36,20 @@ func CreateSession() (*session.Session, error) {
|
|||||||
"AWS_REGION",
|
"AWS_REGION",
|
||||||
}
|
}
|
||||||
|
|
||||||
endPoint := GetEnvVariable("AWS_S3_ENDPOINT", "S3_ENDPOINT")
|
endPoint := utils.GetEnvVariable("AWS_S3_ENDPOINT", "S3_ENDPOINT")
|
||||||
accessKey := GetEnvVariable("AWS_ACCESS_KEY", "ACCESS_KEY")
|
accessKey := utils.GetEnvVariable("AWS_ACCESS_KEY", "ACCESS_KEY")
|
||||||
secretKey := GetEnvVariable("AWS_SECRET_KEY", "SECRET_KEY")
|
secretKey := utils.GetEnvVariable("AWS_SECRET_KEY", "SECRET_KEY")
|
||||||
_ = GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
|
_ = utils.GetEnvVariable("AWS_S3_BUCKET_NAME", "BUCKET_NAME")
|
||||||
|
|
||||||
region := os.Getenv("AWS_REGION")
|
region := os.Getenv("AWS_REGION")
|
||||||
awsDisableSsl, err := strconv.ParseBool(os.Getenv("AWS_DISABLE_SSL"))
|
awsDisableSsl, err := strconv.ParseBool(os.Getenv("AWS_DISABLE_SSL"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatal("Unable to parse AWS_DISABLE_SSL env var: %s", err)
|
utils.Fatal("Unable to parse AWS_DISABLE_SSL env var: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = CheckEnvVars(awsVars)
|
err = utils.CheckEnvVars(awsVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(fmt.Sprintf("Error checking environment variables\n: %s", err))
|
utils.Fatal("Error checking environment variables\n: %s", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
// S3 Config
|
// S3 Config
|
||||||
s3Config := &aws.Config{
|
s3Config := &aws.Config{
|
||||||
@@ -104,7 +109,7 @@ func DownloadFile(destinationPath, key, bucket, prefix string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Info("Download backup from S3 storage...")
|
utils.Info("Download backup from S3 storage...")
|
||||||
file, err := os.Create(filepath.Join(destinationPath, key))
|
file, err := os.Create(filepath.Join(destinationPath, key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to create file", err)
|
fmt.Println("Failed to create file", err)
|
||||||
@@ -121,10 +126,10 @@ func DownloadFile(destinationPath, key, bucket, prefix string) error {
|
|||||||
Key: aws.String(objectKey),
|
Key: aws.String(objectKey),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to download file", err)
|
utils.Error("Failed to download file %s", key)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
Info(fmt.Sprintf("Backup downloaded: ", file.Name(), " bytes size ", numBytes))
|
utils.Info("Backup downloaded: %s bytes size %s ", file.Name(), numBytes)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
32
pkg/scp.go
32
pkg/scp.go
@@ -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,7 +14,6 @@ import (
|
|||||||
"github.com/bramvdbogaerde/go-scp/auth"
|
"github.com/bramvdbogaerde/go-scp/auth"
|
||||||
"github.com/jkaninda/mysql-bkup/utils"
|
"github.com/jkaninda/mysql-bkup/utils"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
@@ -20,16 +25,9 @@ func CopyToRemote(fileName, remotePath string) error {
|
|||||||
sshPort := os.Getenv("SSH_PORT")
|
sshPort := os.Getenv("SSH_PORT")
|
||||||
sshIdentifyFile := os.Getenv("SSH_IDENTIFY_FILE")
|
sshIdentifyFile := os.Getenv("SSH_IDENTIFY_FILE")
|
||||||
|
|
||||||
// SSSHVars Required environment variables for SSH remote server storage
|
|
||||||
var sshHVars = []string{
|
|
||||||
"SSH_USER",
|
|
||||||
"SSH_REMOTE_PATH",
|
|
||||||
"SSH_HOST_NAME",
|
|
||||||
"SSH_PORT",
|
|
||||||
}
|
|
||||||
err := utils.CheckEnvVars(sshHVars)
|
err := utils.CheckEnvVars(sshHVars)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(fmt.Sprintf("Error checking environment variables\n: %s", err))
|
utils.Error("Error checking environment variables: %s", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,9 +37,9 @@ func CopyToRemote(fileName, remotePath string) error {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
if sshPassword == "" {
|
if sshPassword == "" {
|
||||||
return errors.New("SSH_PASSWORD environment variable is required if SSH_IDENTIFY_FILE is empty\n")
|
return errors.New("SSH_PASSWORD environment variable is required if SSH_IDENTIFY_FILE is empty")
|
||||||
}
|
}
|
||||||
slog.Warn("Accessing the remote server using password, password is not recommended\n")
|
utils.Warn("Accessing the remote server using password, password is not recommended")
|
||||||
clientConfig, _ = auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey())
|
clientConfig, _ = auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey())
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -51,7 +49,7 @@ func CopyToRemote(fileName, remotePath string) error {
|
|||||||
// Connect to the remote server
|
// Connect to the remote server
|
||||||
err = client.Connect()
|
err = client.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Couldn't establish a connection to the remote server\n")
|
return errors.New("Couldn't establish a connection to the remote server")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a file
|
// Open a file
|
||||||
@@ -77,6 +75,12 @@ func CopyFromRemote(fileName, remotePath string) error {
|
|||||||
sshPort := os.Getenv("SSH_PORT")
|
sshPort := os.Getenv("SSH_PORT")
|
||||||
sshIdentifyFile := os.Getenv("SSH_IDENTIFY_FILE")
|
sshIdentifyFile := os.Getenv("SSH_IDENTIFY_FILE")
|
||||||
|
|
||||||
|
err := utils.CheckEnvVars(sshHVars)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("Error checking environment variables\n: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
clientConfig, _ := auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey())
|
clientConfig, _ := auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey())
|
||||||
if sshIdentifyFile != "" && utils.FileExists(sshIdentifyFile) {
|
if sshIdentifyFile != "" && utils.FileExists(sshIdentifyFile) {
|
||||||
clientConfig, _ = auth.PrivateKey(sshUser, sshIdentifyFile, ssh.InsecureIgnoreHostKey())
|
clientConfig, _ = auth.PrivateKey(sshUser, sshIdentifyFile, ssh.InsecureIgnoreHostKey())
|
||||||
@@ -85,7 +89,7 @@ func CopyFromRemote(fileName, remotePath string) error {
|
|||||||
if sshPassword == "" {
|
if sshPassword == "" {
|
||||||
return errors.New("SSH_PASSWORD environment variable is required if SSH_IDENTIFY_FILE is empty\n")
|
return errors.New("SSH_PASSWORD environment variable is required if SSH_IDENTIFY_FILE is empty\n")
|
||||||
}
|
}
|
||||||
slog.Warn("Accessing the remote server using password, password is not recommended\n")
|
utils.Warn("Accessing the remote server using password, password is not recommended")
|
||||||
clientConfig, _ = auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey())
|
clientConfig, _ = auth.PasswordKey(sshUser, sshPassword, ssh.InsecureIgnoreHostKey())
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -93,7 +97,7 @@ func CopyFromRemote(fileName, remotePath string) error {
|
|||||||
client := scp.NewClient(fmt.Sprintf("%s:%s", sshHostName, sshPort), &clientConfig)
|
client := scp.NewClient(fmt.Sprintf("%s:%s", sshHostName, sshPort), &clientConfig)
|
||||||
|
|
||||||
// Connect to the remote server
|
// Connect to the remote server
|
||||||
err := client.Connect()
|
err = client.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Couldn't establish a connection to the remote server\n")
|
return errors.New("Couldn't establish a connection to the remote server\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
package pkg
|
|
||||||
|
|
||||||
// Package pkg /*
|
|
||||||
/*
|
|
||||||
Copyright © 2024 Jonas Kaninda
|
|
||||||
*/
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/jkaninda/mysql-bkup/utils"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
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.Fatal("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.Fatal("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.Fatal("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.Fatal("Error creating symbolic link: %v\n", err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
touchLogCmd := exec.Command("touch", cronLogFile)
|
|
||||||
if err := touchLogCmd.Run(); err != nil {
|
|
||||||
utils.Fatal("Error creating file %s: %v\n", cronLogFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cronJob := "/etc/cron.d/backup_cron"
|
|
||||||
touchCronCmd := exec.Command("touch", cronJob)
|
|
||||||
if err := touchCronCmd.Run(); err != nil {
|
|
||||||
utils.Fatal("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.Fatal("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("Backup job created.")
|
|
||||||
}
|
|
||||||
46
pkg/var.go
46
pkg/var.go
@@ -1,21 +1,53 @@
|
|||||||
|
// 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"
|
||||||
const tmpPath = "/tmp/backup"
|
const tmpPath = "/tmp/backup"
|
||||||
const backupCronFile = "/usr/local/bin/backup_cron.sh"
|
|
||||||
const algorithm = "aes256"
|
const algorithm = "aes256"
|
||||||
|
const gpgHome = "/config/gnupg"
|
||||||
const gpgExtension = "gpg"
|
const gpgExtension = "gpg"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
storage = "local"
|
storage = "local"
|
||||||
file = ""
|
file = ""
|
||||||
dbPassword = ""
|
|
||||||
dbUserName = ""
|
|
||||||
dbName = ""
|
|
||||||
dbHost = ""
|
|
||||||
dbPort = "3306"
|
|
||||||
executionMode = "default"
|
|
||||||
storagePath = "/backup"
|
storagePath = "/backup"
|
||||||
disableCompression = false
|
disableCompression = false
|
||||||
encryption = false
|
encryption = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// dbHVars Required environment variables for database
|
||||||
|
var dbHVars = []string{
|
||||||
|
"DB_HOST",
|
||||||
|
"DB_PASSWORD",
|
||||||
|
"DB_USERNAME",
|
||||||
|
"DB_NAME",
|
||||||
|
}
|
||||||
|
var tdbRVars = []string{
|
||||||
|
"TARGET_DB_HOST",
|
||||||
|
"TARGET_DB_PORT",
|
||||||
|
"TARGET_DB_NAME",
|
||||||
|
"TARGET_DB_USERNAME",
|
||||||
|
"TARGET_DB_PASSWORD",
|
||||||
|
}
|
||||||
|
|
||||||
|
var dbConf *dbConfig
|
||||||
|
var targetDbConf *targetDbConfig
|
||||||
|
|
||||||
|
// sshHVars Required environment variables for SSH remote server storage
|
||||||
|
var sshHVars = []string{
|
||||||
|
"SSH_USER",
|
||||||
|
"REMOTE_PATH",
|
||||||
|
"SSH_HOST_NAME",
|
||||||
|
"SSH_PORT",
|
||||||
|
}
|
||||||
|
var ftpVars = []string{
|
||||||
|
"FTP_HOST_NAME",
|
||||||
|
"FTP_USER",
|
||||||
|
"FTP_PASSWORD",
|
||||||
|
"FTP_PORT",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
DB_USERNAME='db_username'
|
|
||||||
DB_PASSWORD='password'
|
|
||||||
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 backup -d $DB_NAME
|
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
|
// 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" +
|
||||||
"bkup restore --dbname database --storage s3 --path /custom-path --file db_20231219_022941.sql.gz"
|
"restore --dbname database --storage s3 --path /custom-path --file db_20231219_022941.sql.gz"
|
||||||
const BackupExample = "mysql-bkup backup --dbname database --disable-compression\n" +
|
const BackupExample = "mysql-bkup backup --dbname database --disable-compression\n" +
|
||||||
"mysql-bkup backup --dbname database --storage s3 --path /custom-path --disable-compression"
|
"backup --dbname database --storage s3 --path /custom-path --disable-compression"
|
||||||
|
|
||||||
const MainExample = "mysql-bkup backup --dbname database --disable-compression\n" +
|
const MainExample = "mysql-bkup backup --dbname database --disable-compression\n" +
|
||||||
"mysql-bkup backup --dbname database --storage s3 --path /custom-path\n" +
|
"backup --dbname database --storage s3 --path /custom-path\n" +
|
||||||
"mysql-bkup restore --dbname database --file db_20231219_022941.sql.gz"
|
"restore --dbname database --file db_20231219_022941.sql.gz"
|
||||||
|
|||||||
67
utils/logger.go
Normal file
67
utils/logger.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Package utils /
|
||||||
|
/*****
|
||||||
|
@author Jonas Kaninda
|
||||||
|
@license MIT License <https://opensource.org/licenses/MIT>
|
||||||
|
@Copyright © 2024 Jonas Kaninda
|
||||||
|
**/
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var currentTime = time.Now().Format("2006/01/02 15:04:05")
|
||||||
|
|
||||||
|
func Info(msg string, args ...any) {
|
||||||
|
formattedMessage := fmt.Sprintf(msg, args...)
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Printf("%s INFO: %s\n", currentTime, msg)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s INFO: %s\n", currentTime, formattedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn warning message
|
||||||
|
func Warn(msg string, args ...any) {
|
||||||
|
formattedMessage := fmt.Sprintf(msg, args...)
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Printf("%s WARN: %s\n", currentTime, msg)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s WARN: %s\n", currentTime, formattedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func Error(msg string, args ...any) {
|
||||||
|
formattedMessage := fmt.Sprintf(msg, args...)
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Printf("%s ERROR: %s\n", currentTime, msg)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s ERROR: %s\n", currentTime, formattedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func Done(msg string, args ...any) {
|
||||||
|
formattedMessage := fmt.Sprintf(msg, args...)
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Printf("%s INFO: %s\n", currentTime, msg)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s INFO: %s\n", currentTime, formattedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs an error message and exits the program
|
||||||
|
func Fatal(msg string, args ...any) {
|
||||||
|
// Fatal logs an error message and exits the program.
|
||||||
|
formattedMessage := fmt.Sprintf(msg, args...)
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Printf("%s ERROR: %s\n", currentTime, msg)
|
||||||
|
NotifyError(msg)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s ERROR: %s\n", currentTime, formattedMessage)
|
||||||
|
NotifyError(formattedMessage)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
os.Kill.Signal()
|
||||||
|
}
|
||||||
184
utils/utils.go
184
utils/utils.go
@@ -1,60 +1,25 @@
|
|||||||
|
// 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"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Info(msg string, args ...any) {
|
|
||||||
if len(args) == 0 {
|
|
||||||
slog.Info(msg)
|
|
||||||
} else {
|
|
||||||
slog.Info(fmt.Sprintf(msg, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func Worn(msg string, args ...any) {
|
|
||||||
if len(args) == 0 {
|
|
||||||
slog.Warn(msg)
|
|
||||||
} else {
|
|
||||||
slog.Warn(fmt.Sprintf(msg, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func Error(msg string, args ...any) {
|
|
||||||
if len(args) == 0 {
|
|
||||||
slog.Error(msg)
|
|
||||||
} else {
|
|
||||||
slog.Error(fmt.Sprintf(msg, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func Done(msg string, args ...any) {
|
|
||||||
if len(args) == 0 {
|
|
||||||
slog.Info(msg)
|
|
||||||
} else {
|
|
||||||
slog.Info(fmt.Sprintf(msg, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func Fatal(msg string, args ...any) {
|
|
||||||
// Fatal logs an error message and exits the program.
|
|
||||||
if len(args) == 0 {
|
|
||||||
slog.Error(msg)
|
|
||||||
} else {
|
|
||||||
slog.Error(fmt.Sprintf(msg, args...))
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FileExists(filename string) bool {
|
func FileExists(filename string) bool {
|
||||||
info, err := os.Stat(filename)
|
info, err := os.Stat(filename)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -129,34 +94,6 @@ func IsDirEmpty(name string) (bool, error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDatabaseConnection tests the database connection
|
|
||||||
func TestDatabaseConnection() {
|
|
||||||
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")
|
|
||||||
|
|
||||||
if os.Getenv("DB_HOST") == "" || os.Getenv("DB_NAME") == "" || os.Getenv("DB_USERNAME") == "" || os.Getenv("DB_PASSWORD") == "" {
|
|
||||||
Fatal("Please make sure all required database environment variables are set")
|
|
||||||
} else {
|
|
||||||
Info("Connecting to database ...")
|
|
||||||
|
|
||||||
cmd := exec.Command("mysql", "-h", dbHost, "-P", dbPort, "-u", dbUserName, "--password="+dbPassword, dbName, "-e", "quit")
|
|
||||||
|
|
||||||
// Capture the output
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &out
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
slog.Error(fmt.Sprintf("Error testing database connection: %v\nOutput: %s\n", err, out.String()))
|
|
||||||
os.Exit(1)
|
|
||||||
|
|
||||||
}
|
|
||||||
Info("Successfully connected to database")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func GetEnv(cmd *cobra.Command, flagName, envName string) string {
|
func GetEnv(cmd *cobra.Command, flagName, envName string) string {
|
||||||
value, _ := cmd.Flags().GetString(flagName)
|
value, _ := cmd.Flags().GetString(flagName)
|
||||||
if value != "" {
|
if value != "" {
|
||||||
@@ -196,7 +133,7 @@ func GetEnvVariable(envName, oldEnvName string) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
Worn("%s is deprecated, please use %s instead!\n", oldEnvName, envName)
|
Warn("%s is deprecated, please use %s instead!", oldEnvName, envName)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,3 +158,100 @@ func CheckEnvVars(vars []string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeDir create directory
|
||||||
|
func MakeDir(dirPath string) error {
|
||||||
|
err := os.Mkdir(dirPath, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeDirAll create directory
|
||||||
|
func MakeDirAll(dirPath string) error {
|
||||||
|
err := os.MkdirAll(dirPath, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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"))
|
||||||
|
|
||||||
|
}
|
||||||
|
func IsValidCronExpression(cronExpr string) bool {
|
||||||
|
_, err := cron.ParseStandard(cronExpr)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user