Compare commits

...

18 Commits

Author SHA1 Message Date
7ff9a32f08 Merge pull request #155 from jkaninda/develop
chore: update notification template
2024-12-07 20:29:13 +01:00
95a81cb6b7 fix: SSH storage key identitify file 2024-12-07 20:14:30 +01:00
057d5277b0 fix: deprecation warning message, replace mysql by mariadb command 2024-12-07 17:54:44 +01:00
8e58d7a4c3 chore: update notification template 2024-12-07 17:36:05 +01:00
4bd7d9fa72 Merge pull request #154 from jkaninda/refactor
chore: update .env.example
2024-12-07 03:24:35 +01:00
Jonas Kaninda
156f22f1e5 chore: update .env.example 2024-12-07 03:24:06 +01:00
fd444293b4 Merge pull request #153 from jkaninda/refactor
fix: S3 remote path when backing up multiple databases
2024-12-07 02:34:42 +01:00
Jonas Kaninda
1940ceba9a fix: S3 remote path when backing up multiple databases 2024-12-07 02:25:22 +01:00
Jonas Kaninda
07d580a8a9 refactoring of code 2024-12-07 02:23:38 +01:00
9a261b22ec Merge pull request #152 from jkaninda/refactor
docs: update features
2024-12-06 22:09:12 +01:00
Jonas Kaninda
e7a58f0569 docs: update features 2024-12-06 22:08:51 +01:00
1b529725d7 Merge pull request #151 from jkaninda/refactor
fix: fatal logger notification
2024-12-06 21:03:47 +01:00
Jonas Kaninda
d8c73560b8 fix: fatal logger notification 2024-12-06 21:00:26 +01:00
Jonas Kaninda
d5a0adc981 refactoring of code 2024-12-06 20:53:46 +01:00
6df3bae9e2 Merge pull request #150 from jkaninda/feature/azure-blob
chore: update base image tag version
2024-12-06 20:23:46 +01:00
Jonas Kaninda
f7d624fd15 chore: update base image tag version 2024-12-06 20:23:08 +01:00
1e9e1ed951 Merge pull request #149 from jkaninda/feature/azure-blob
chore: update app package
2024-12-06 20:17:57 +01:00
Jonas Kaninda
917ba8947f chore: update app package 2024-12-06 20:16:56 +01:00
31 changed files with 508 additions and 406 deletions

View File

@@ -15,6 +15,7 @@ TZ=Europe/Paris
### Backup restoration
#FILE_NAME=
### AWS S3 Storage
#ACCESS_KEY=
#SECRET_KEY=
@@ -43,19 +44,30 @@ TZ=Europe/Paris
#FTP_USER=
#FTP_PORT=21
#REMOTE_PATH=
## Azure Blob storage
AZURE_STORAGE_CONTAINER_NAME=
AZURE_STORAGE_ACCOUNT_NAME=
AZURE_STORAGE_ACCOUNT_KEY=
#### Backup encryption
#GPG_PUBLIC_KEY=/config/public_key.asc
#GPG_PRIVATE_KEY=/config/private_key.asc
#GPG_PASSPHRASE=Your strong passphrase
## For multiple database backup on Docker or Docker in Swarm mode
#BACKUP_CONFIG_FILE=/config/config.yaml
### Database restoration
#FILE_NAME=
### Notification
#BACKUP_REFERENCE=K8s/Paris cluster
## Telegram
#TG_TOKEN=
#TG_CHAT_ID=
### Email
#MAIL_HOST=
#MAIL_PORT=

View File

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

View File

@@ -24,7 +24,7 @@ It supports a variety of storage options and ensures data security through GPG e
- **Deployment Flexibility:**
- Available as the [jkaninda/mysql-bkup](https://hub.docker.com/r/jkaninda/mysql-bkup) Docker image.
- Deployable on **Docker**, **Docker Swarm**, and **Kubernetes**.
- Supports recurring backups of PostgreSQL databases when deployed:
- Supports recurring backups of MySQL databases when deployed:
- On Docker for automated backup schedules.
- As a **Job** or **CronJob** on Kubernetes.
@@ -35,9 +35,9 @@ It supports a variety of storage options and ensures data security through GPG e
## Use Cases
- **Automated Recurring Backups:** Schedule regular backups for PostgreSQL databases.
- **Cross-Environment Migration:** Easily migrate your PostgreSQL databases across different environments using supported storage options.
- **Secure Backup Management:** Protect your data with Gmysql encryption.
- **Automated Recurring Backups:** Schedule regular backups for MySQL databases.
- **Cross-Environment Migration:** Easily migrate your MySQL databases across different environments using supported storage options.
- **Secure Backup Management:** Protect your data with GPG encryption.
Successfully tested on:

View File

@@ -1,4 +1,3 @@
// Package cmd /
/*
MIT License
@@ -22,11 +21,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cmd
import (
"github.com/jkaninda/mysql-bkup/internal"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/pkg"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
)
@@ -37,9 +36,9 @@ var BackupCmd = &cobra.Command{
Example: utils.BackupExample,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
internal.StartBackup(cmd)
pkg.StartBackup(cmd)
} else {
logger.Fatal(`"backup" accepts no argument %q`, args)
utils.Fatal(`"backup" accepts no argument %q`, args)
}
},
}

View File

@@ -1,4 +1,3 @@
// Package cmd /
/*
MIT License
@@ -22,11 +21,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cmd
import (
"github.com/jkaninda/mysql-bkup/internal"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/pkg"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
)
@@ -35,9 +35,9 @@ var MigrateCmd = &cobra.Command{
Short: "Migrate database from a source database to a target database",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
internal.StartMigration(cmd)
pkg.StartMigration(cmd)
} else {
logger.Fatal(`"migrate" accepts no argument %q`, args)
utils.Fatal(`"migrate" accepts no argument %q`, args)
}

View File

@@ -1,5 +1,3 @@
package cmd
/*
MIT License
@@ -23,9 +21,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cmd
import (
"github.com/jkaninda/mysql-bkup/internal"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/pkg"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
)
@@ -36,9 +36,9 @@ var RestoreCmd = &cobra.Command{
Example: utils.RestoreExample,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
internal.StartRestore(cmd)
pkg.StartRestore(cmd)
} else {
logger.Fatal(`"restore" accepts no argument %q`, args)
utils.Fatal(`"restore" accepts no argument %q`, args)
}

View File

@@ -1,4 +1,3 @@
// Package cmd /
/*
MIT License
@@ -22,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cmd
import (

View File

@@ -1,4 +1,3 @@
// Package cmd /
/*
MIT License
@@ -22,10 +21,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package cmd
import (
"fmt"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
"os"
)
@@ -41,6 +42,6 @@ var VersionCmd = &cobra.Command{
}
func Version() {
fmt.Printf("Version: %s \n", appVersion)
fmt.Printf("Version: %s \n", utils.Version)
fmt.Println()
}

View File

@@ -25,7 +25,7 @@ It supports a variety of storage options and ensures data security through GPG e
- **Deployment Flexibility:**
- Available as the [jkaninda/mysql-bkup](https://hub.docker.com/r/jkaninda/mysql-bkup) Docker image.
- Deployable on **Docker**, **Docker Swarm**, and **Kubernetes**.
- Supports recurring backups of PostgreSQL databases when deployed:
- Supports recurring backups of MySQL databases when deployed:
- On Docker for automated backup schedules.
- As a **Job** or **CronJob** on Kubernetes.
@@ -36,9 +36,9 @@ It supports a variety of storage options and ensures data security through GPG e
## Use Cases
- **Automated Recurring Backups:** Schedule regular backups for PostgreSQL databases.
- **Cross-Environment Migration:** Easily migrate your PostgreSQL databases across different environments using supported storage options.
- **Secure Backup Management:** Protect your data with Gmysql encryption.
- **Automated Recurring Backups:** Schedule regular backups for MySQL databases.
- **Cross-Environment Migration:** Easily migrate your MySQL databases across different environments using supported storage options.
- **Secure Backup Management:** Protect your data with GPG encryption.

10
go.mod
View File

@@ -6,8 +6,8 @@ require github.com/spf13/pflag v1.0.5 // indirect
require (
github.com/go-mail/mail v2.3.1+incompatible
github.com/jkaninda/encryptor v0.0.0-20241013064832-ed4bd6a1b221
github.com/jkaninda/go-storage v0.1.2
github.com/jkaninda/encryptor v0.0.0-20241111100652-926393c9437e
github.com/jkaninda/go-storage v0.1.3
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.1
gopkg.in/yaml.v3 v3.0.1
@@ -17,12 +17,12 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect
github.com/ProtonMail/go-crypto v1.1.0 // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/ProtonMail/gopenpgp/v2 v2.7.5 // indirect
github.com/ProtonMail/gopenpgp/v2 v2.8.0 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/bramvdbogaerde/go-scp v1.5.0 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
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

64
go.sum
View File

@@ -1,30 +1,37 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0 h1:mlmW46Q0B79I+Aj4azKC6xDMFN9a9SyZWESlGWYXbFs=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.5.0/go.mod h1:PXe2h+LKcWTX9afWdZoHyODqR4fBa5boUM/8uJfZ0Jo=
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs=
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/ProtonMail/go-crypto v1.1.0 h1:OnlSGxXflfrWJESDsGQOmACNQRM9IflG3q8XTrOqvbE=
github.com/ProtonMail/go-crypto v1.1.0/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.7.5 h1:STOY3vgES59gNgoOt2w0nyHBjKViB/qSg7NjbQWPJkA=
github.com/ProtonMail/gopenpgp/v2 v2.7.5/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g=
github.com/ProtonMail/gopenpgp/v2 v2.8.0 h1:WvMv3CMcFsqKSM4/Qf8sf3tgyQkzDqQmoSE49bnBuP4=
github.com/ProtonMail/gopenpgp/v2 v2.8.0/go.mod h1:qb2GUSnmA9ipBW5GVtCtEhkummSlqs2A8Ar3S0HBgSY=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/bramvdbogaerde/go-scp v1.5.0 h1:a9BinAjTfQh273eh7vd3qUgmBC+bx+3TRDtkZWmIpzM=
github.com/bramvdbogaerde/go-scp v1.5.0/go.mod h1:on2aH5AxaFb2G0N5Vsdy6B0Ml7k9HuHSwfo1y0QzAbQ=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ=
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM=
github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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=
@@ -32,38 +39,43 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jkaninda/encryptor v0.0.0-20241013064832-ed4bd6a1b221 h1:AwkCf7el1kzeCJ89A+gUAK0ero5JYnvLOKsYMzq+rs4=
github.com/jkaninda/encryptor v0.0.0-20241013064832-ed4bd6a1b221/go.mod h1:9F8ZJ+ZXE8DZBo77+aneGj8LMjrYXX6eFUCC/uqZOUo=
github.com/jkaninda/go-storage v0.1.1 h1:vjpdD/fh39S5HGyfHvLE5HGYOEPIukINlOX3OnM3GW4=
github.com/jkaninda/go-storage v0.1.1/go.mod h1:7VK5gQISQaLxtLfBtc+een8spcgLVSBAKTRuyF1N81I=
github.com/jkaninda/go-storage v0.1.2 h1:d7+TRPjmHXdSqO0wne3KAB8zt9ih8lf5D8aL4n7/Dds=
github.com/jkaninda/go-storage v0.1.2/go.mod h1:zVRnLprBk/9AUz2+za6Y03MgoNYrqKLy3edVtjqMaps=
github.com/jkaninda/encryptor v0.0.0-20241111100652-926393c9437e h1:jtFKZHt/PLGQWXNgjEFTEwVbxiQQRMoJ7m37trbkJGw=
github.com/jkaninda/encryptor v0.0.0-20241111100652-926393c9437e/go.mod h1:Y1EXpPWQ9PNd7y7E6ez3xgnzZc8fuDWXwX/1/dXNCE4=
github.com/jkaninda/go-storage v0.1.3 h1:lEpHVgFLKSvjsi/6tAek96Y07za3vxmsXF2/+jiCMZU=
github.com/jkaninda/go-storage v0.1.3/go.mod h1:zVRnLprBk/9AUz2+za6Y03MgoNYrqKLy3edVtjqMaps=
github.com/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/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -71,9 +83,7 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -84,23 +94,17 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
@@ -112,12 +116,12 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,4 +1,3 @@
// Package main /
/*
MIT License
@@ -22,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package main
import "github.com/jkaninda/mysql-bkup/cmd"

View File

@@ -22,12 +22,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
package pkg
import (
"fmt"
"github.com/jkaninda/go-storage/pkg/azure"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/utils"
"os"
@@ -36,7 +35,7 @@ import (
)
func azureBackup(db *dbConfig, config *BackupConfig) {
logger.Info("Backup database to the remote FTP server")
utils.Info("Backup database to the remote FTP server")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
@@ -46,8 +45,8 @@ func azureBackup(db *dbConfig, config *BackupConfig) {
encryptBackup(config)
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
logger.Info("Uploading backup archive to Azure Blob storage ...")
logger.Info("Backup name is %s", finalFileName)
utils.Info("Uploading backup archive to Azure Blob storage ...")
utils.Info("Backup name is %s", finalFileName)
azureConfig := loadAzureConfig()
azureStorage, err := azure.NewStorage(azure.Config{
ContainerName: azureConfig.containerName,
@@ -57,34 +56,34 @@ func azureBackup(db *dbConfig, config *BackupConfig) {
LocalPath: tmpPath,
})
if err != nil {
logger.Fatal("Error creating SSH storage: %s", err)
utils.Fatal("Error creating Azure storage: %s", err)
}
err = azureStorage.Copy(finalFileName)
if err != nil {
logger.Fatal("Error copying backup file: %s", err)
utils.Fatal("Error copying backup file: %s", err)
}
logger.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
// Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
logger.Error("Error: %s", err)
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
// Delete backup file from tmp folder
err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName))
if err != nil {
logger.Error("Error deleting file: %v", err)
utils.Error("Error deleting file: %v", err)
}
if config.prune {
err := azureStorage.Prune(config.backupRetention)
if err != nil {
logger.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
logger.Info("Uploading backup archive to Azure Blob storage ... done ")
utils.Info("Uploading backup archive to Azure Blob storage ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
@@ -98,10 +97,10 @@ func azureBackup(db *dbConfig, config *BackupConfig) {
})
// Delete temp
deleteTemp()
logger.Info("Backup completed successfully")
utils.Info("Backup completed successfully")
}
func azureRestore(db *dbConfig, conf *RestoreConfig) {
logger.Info("Restore database from Azure Blob storage")
utils.Info("Restore database from Azure Blob storage")
azureConfig := loadAzureConfig()
azureStorage, err := azure.NewStorage(azure.Config{
ContainerName: azureConfig.containerName,
@@ -111,12 +110,12 @@ func azureRestore(db *dbConfig, conf *RestoreConfig) {
LocalPath: tmpPath,
})
if err != nil {
logger.Fatal("Error creating SSH storage: %s", err)
utils.Fatal("Error creating SSH storage: %s", err)
}
err = azureStorage.CopyFrom(conf.file)
if err != nil {
logger.Fatal("Error downloading backup file: %s", err)
utils.Fatal("Error downloading backup file: %s", err)
}
RestoreDatabase(db, conf)
}

View File

@@ -22,13 +22,13 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
package pkg
import (
"fmt"
"github.com/jkaninda/encryptor"
"github.com/jkaninda/go-storage/pkg/local"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/robfig/cron/v3"
"github.com/spf13/cobra"
@@ -53,7 +53,7 @@ func StartBackup(cmd *cobra.Command) {
if utils.IsValidCronExpression(config.cronExpression) {
scheduledMode(dbConf, config)
} else {
logger.Fatal("Cron expression is not valid: %s", config.cronExpression)
utils.Fatal("Cron expression is not valid: %s", config.cronExpression)
}
}
} else {
@@ -64,22 +64,22 @@ func StartBackup(cmd *cobra.Command) {
// scheduledMode Runs backup in scheduled mode
func scheduledMode(db *dbConfig, config *BackupConfig) {
logger.Info("Running in Scheduled mode")
logger.Info("Backup cron expression: %s", config.cronExpression)
logger.Info("The next scheduled time is: %v", utils.CronNextTime(config.cronExpression).Format(timeFormat))
logger.Info("Storage type %s ", config.storage)
utils.Info("Running in Scheduled mode")
utils.Info("Backup cron expression: %s", config.cronExpression)
utils.Info("The next scheduled time is: %v", utils.CronNextTime(config.cronExpression).Format(timeFormat))
utils.Info("Storage type %s ", config.storage)
// Test backup
logger.Info("Testing backup configurations...")
utils.Info("Testing backup configurations...")
testDatabaseConnection(db)
logger.Info("Testing backup configurations...done")
logger.Info("Creating backup job...")
utils.Info("Testing backup configurations...done")
utils.Info("Creating backup job...")
// Create a new cron instance
c := cron.New()
_, err := c.AddFunc(config.cronExpression, func() {
BackupTask(db, config)
logger.Info("Next backup time is: %v", utils.CronNextTime(config.cronExpression).Format(timeFormat))
utils.Info("Next backup time is: %v", utils.CronNextTime(config.cronExpression).Format(timeFormat))
})
if err != nil {
@@ -87,8 +87,8 @@ func scheduledMode(db *dbConfig, config *BackupConfig) {
}
// Start the cron scheduler
c.Start()
logger.Info("Creating backup job...done")
logger.Info("Backup job started")
utils.Info("Creating backup job...done")
utils.Info("Backup job started")
defer c.Stop()
select {}
}
@@ -106,7 +106,7 @@ func multiBackupTask(databases []Database, bkConfig *BackupConfig) {
// BackupTask backups database
func BackupTask(db *dbConfig, config *BackupConfig) {
logger.Info("Starting backup task...")
utils.Info("Starting backup task...")
// Generate file name
backupFileName := fmt.Sprintf("%s_%s.sql.gz", db.dbName, time.Now().Format("20060102_150405"))
if config.disableCompression {
@@ -129,17 +129,17 @@ func BackupTask(db *dbConfig, config *BackupConfig) {
}
}
func startMultiBackup(bkConfig *BackupConfig, configFile string) {
logger.Info("Starting backup task...")
utils.Info("Starting backup task...")
conf, err := readConf(configFile)
if err != nil {
logger.Fatal("Error reading config file: %s", err)
utils.Fatal("Error reading config file: %s", err)
}
// Check if cronExpression is defined in config file
if conf.CronExpression != "" {
bkConfig.cronExpression = conf.CronExpression
}
if len(conf.Databases) == 0 {
logger.Fatal("No databases found")
utils.Fatal("No databases found")
}
// Check if cronExpression is defined
if bkConfig.cronExpression == "" {
@@ -147,24 +147,24 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) {
} else {
// Check if cronExpression is valid
if utils.IsValidCronExpression(bkConfig.cronExpression) {
logger.Info("Running backup in Scheduled mode")
logger.Info("Backup cron expression: %s", bkConfig.cronExpression)
logger.Info("The next scheduled time is: %v", utils.CronNextTime(bkConfig.cronExpression).Format(timeFormat))
logger.Info("Storage type %s ", bkConfig.storage)
utils.Info("Running backup in Scheduled mode")
utils.Info("Backup cron expression: %s", bkConfig.cronExpression)
utils.Info("The next scheduled time is: %v", utils.CronNextTime(bkConfig.cronExpression).Format(timeFormat))
utils.Info("Storage type %s ", bkConfig.storage)
// Test backup
logger.Info("Testing backup configurations...")
utils.Info("Testing backup configurations...")
for _, db := range conf.Databases {
testDatabaseConnection(getDatabase(db))
}
logger.Info("Testing backup configurations...done")
logger.Info("Creating backup job...")
utils.Info("Testing backup configurations...done")
utils.Info("Creating backup job...")
// Create a new cron instance
c := cron.New()
_, err := c.AddFunc(bkConfig.cronExpression, func() {
multiBackupTask(conf.Databases, bkConfig)
logger.Info("Next backup time is: %v", utils.CronNextTime(bkConfig.cronExpression).Format(timeFormat))
utils.Info("Next backup time is: %v", utils.CronNextTime(bkConfig.cronExpression).Format(timeFormat))
})
if err != nil {
@@ -172,13 +172,13 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) {
}
// Start the cron scheduler
c.Start()
logger.Info("Creating backup job...done")
logger.Info("Backup job started")
utils.Info("Creating backup job...done")
utils.Info("Backup job started")
defer c.Stop()
select {}
} else {
logger.Fatal("Cron expression is not valid: %s", bkConfig.cronExpression)
utils.Fatal("Cron expression is not valid: %s", bkConfig.cronExpression)
}
}
@@ -188,7 +188,7 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) {
func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) {
storagePath = os.Getenv("STORAGE_PATH")
logger.Info("Starting database backup...")
utils.Info("Starting database backup...")
err := os.Setenv("MYSQL_PWD", db.dbPassword)
if err != nil {
@@ -196,7 +196,7 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
}
testDatabaseConnection(db)
// Backup Database database
logger.Info("Backing up database...")
utils.Info("Backing up database...")
// Verify is compression is disabled
if disableCompression {
@@ -209,26 +209,26 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
)
output, err := cmd.Output()
if err != nil {
logger.Fatal(err.Error())
utils.Fatal(err.Error())
}
// save output
file, err := os.Create(filepath.Join(tmpPath, backupFileName))
if err != nil {
logger.Fatal(err.Error())
utils.Fatal(err.Error())
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
logger.Fatal(err.Error())
utils.Fatal(err.Error())
}
}(file)
_, err = file.Write(output)
if err != nil {
logger.Fatal(err.Error())
utils.Fatal(err.Error())
}
logger.Info("Database has been backed up")
utils.Info("Database has been backed up")
} else {
// Execute mysqldump
@@ -250,12 +250,12 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool
if err := gzipCmd.Wait(); err != nil {
log.Fatal(err)
}
logger.Info("Database has been backed up")
utils.Info("Database has been backed up")
}
}
func localBackup(db *dbConfig, config *BackupConfig) {
logger.Info("Backup database to local storage")
utils.Info("Backup database to local storage")
startTime = time.Now().Format(utils.TimeFormat())
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
@@ -265,19 +265,19 @@ func localBackup(db *dbConfig, config *BackupConfig) {
}
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
logger.Error("Error: %s", err)
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
logger.Info("Backup name is %s", finalFileName)
utils.Info("Backup name is %s", finalFileName)
localStorage := local.NewStorage(local.Config{
LocalPath: tmpPath,
RemotePath: storagePath,
})
err = localStorage.Copy(finalFileName)
if err != nil {
logger.Fatal("Error copying backup file: %s", err)
utils.Fatal("Error copying backup file: %s", err)
}
logger.Info("Backup saved in %s", filepath.Join(storagePath, finalFileName))
utils.Info("Backup saved in %s", filepath.Join(storagePath, finalFileName))
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
@@ -292,40 +292,40 @@ func localBackup(db *dbConfig, config *BackupConfig) {
if config.prune {
err = localStorage.Prune(config.backupRetention)
if err != nil {
logger.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
// Delete temp
deleteTemp()
logger.Info("Backup completed successfully")
utils.Info("Backup completed successfully")
}
func encryptBackup(config *BackupConfig) {
backupFile, err := os.ReadFile(filepath.Join(tmpPath, config.backupFileName))
outputFile := fmt.Sprintf("%s.%s", filepath.Join(tmpPath, config.backupFileName), gpgExtension)
if err != nil {
logger.Fatal("Error reading backup file: %s ", err)
utils.Fatal("Error reading backup file: %s ", err)
}
if config.usingKey {
logger.Info("Encrypting backup using public key...")
utils.Info("Encrypting backup using public key...")
pubKey, err := os.ReadFile(config.publicKey)
if err != nil {
logger.Fatal("Error reading public key: %s ", err)
utils.Fatal("Error reading public key: %s ", err)
}
err = encryptor.EncryptWithPublicKey(backupFile, fmt.Sprintf("%s.%s", filepath.Join(tmpPath, config.backupFileName), gpgExtension), pubKey)
if err != nil {
logger.Fatal("Error encrypting backup file: %v ", err)
utils.Fatal("Error encrypting backup file: %v ", err)
}
logger.Info("Encrypting backup using public key...done")
utils.Info("Encrypting backup using public key...done")
} else if config.passphrase != "" {
logger.Info("Encrypting backup using passphrase...")
utils.Info("Encrypting backup using passphrase...")
err := encryptor.Encrypt(backupFile, outputFile, config.passphrase)
if err != nil {
logger.Fatal("error during encrypting backup %v", err)
utils.Fatal("error during encrypting backup %v", err)
}
logger.Info("Encrypting backup using passphrase...done")
utils.Info("Encrypting backup using passphrase...done")
}

View File

@@ -1,4 +1,3 @@
// Package internal /
/*
MIT License
@@ -22,11 +21,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
package pkg
import (
"fmt"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
"os"
@@ -81,7 +80,7 @@ type FTPConfig struct {
host string
user string
password string
port string
port int
remotePath string
}
type AzureConfig struct {
@@ -95,7 +94,7 @@ type SSHConfig struct {
user string
password string
hostName string
port string
port int
identifyFile string
}
type AWSConfig struct {
@@ -121,8 +120,8 @@ func initDbConfig(cmd *cobra.Command) *dbConfig {
err := utils.CheckEnvVars(dbHVars)
if err != nil {
logger.Error("Please make sure all required environment variables for database are set")
logger.Fatal("Error checking environment variables: %s", err)
utils.Error("Please make sure all required environment variables for database are set")
utils.Fatal("Error checking environment variables: %s", err)
}
return &dConf
}
@@ -150,7 +149,7 @@ func loadSSHConfig() (*SSHConfig, error) {
user: os.Getenv("SSH_USER"),
password: os.Getenv("SSH_PASSWORD"),
hostName: os.Getenv("SSH_HOST"),
port: os.Getenv("SSH_PORT"),
port: utils.GetIntEnv("SSH_PORT"),
identifyFile: os.Getenv("SSH_IDENTIFY_FILE"),
}, nil
}
@@ -160,12 +159,12 @@ func loadFtpConfig() *FTPConfig {
fConfig.host = utils.GetEnvVariable("FTP_HOST", "FTP_HOST_NAME")
fConfig.user = os.Getenv("FTP_USER")
fConfig.password = os.Getenv("FTP_PASSWORD")
fConfig.port = os.Getenv("FTP_PORT")
fConfig.port = utils.GetIntEnv("FTP_PORT")
fConfig.remotePath = os.Getenv("REMOTE_PATH")
err := utils.CheckEnvVars(ftpVars)
if err != nil {
logger.Error("Please make sure all required environment variables for FTP are set")
logger.Fatal("Error missing environment variables: %s", err)
utils.Error("Please make sure all required environment variables for FTP are set")
utils.Fatal("Error missing environment variables: %s", err)
}
return &fConfig
}
@@ -178,8 +177,8 @@ func loadAzureConfig() *AzureConfig {
err := utils.CheckEnvVars(azureVars)
if err != nil {
logger.Error("Please make sure all required environment variables for Azure Blob storage are set")
logger.Fatal("Error missing environment variables: %s", err)
utils.Error("Please make sure all required environment variables for Azure Blob storage are set")
utils.Fatal("Error missing environment variables: %s", err)
}
return &aConfig
}
@@ -206,8 +205,8 @@ func initAWSConfig() *AWSConfig {
aConfig.forcePathStyle = forcePathStyle
err = utils.CheckEnvVars(awsVars)
if err != nil {
logger.Error("Please make sure all required environment variables for AWS S3 are set")
logger.Fatal("Error checking environment variables: %s", err)
utils.Error("Please make sure all required environment variables for AWS S3 are set")
utils.Fatal("Error checking environment variables: %s", err)
}
return &aConfig
}
@@ -304,8 +303,8 @@ func initTargetDbConfig() *targetDbConfig {
err := utils.CheckEnvVars(tdbRVars)
if err != nil {
logger.Error("Please make sure all required environment variables for the target database are set")
logger.Fatal("Error checking target database environment variables: %s", err)
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
}

View File

@@ -1,4 +1,3 @@
// Package internal /
/*
MIT License
@@ -22,12 +21,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
package pkg
import (
"bytes"
"fmt"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/utils"
"gopkg.in/yaml.v3"
"os"
@@ -44,7 +43,7 @@ func intro() {
// copyToTmp copy file to temporary directory
func deleteTemp() {
logger.Info("Deleting %s ...", tmpPath)
utils.Info("Deleting %s ...", tmpPath)
err := filepath.Walk(tmpPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
@@ -60,9 +59,9 @@ func deleteTemp() {
return nil
})
if err != nil {
logger.Error("Error deleting files: %v", err)
utils.Error("Error deleting files: %v", err)
} else {
logger.Info("Deleting %s ... done", tmpPath)
utils.Info("Deleting %s ... done", tmpPath)
}
}
@@ -72,18 +71,20 @@ func testDatabaseConnection(db *dbConfig) {
if err != nil {
return
}
logger.Info("Connecting to %s database ...", db.dbName)
cmd := exec.Command("mysql", "-h", db.dbHost, "-P", db.dbPort, "-u", db.dbUserName, db.dbName, "-e", "quit")
utils.Info("Connecting to %s database ...", db.dbName)
// Set database name for notification error
utils.DatabaseName = db.dbName
cmd := exec.Command("mariadb", "-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 {
logger.Fatal("Error testing database connection: %v\nOutput: %s", err, out.String())
utils.Fatal("Error testing database connection: %v\nOutput: %s", err, out.String())
}
logger.Info("Successfully connected to %s database", db.dbName)
utils.Info("Successfully connected to %s database", db.dbName)
}

View File

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

View File

@@ -1,4 +1,3 @@
// Package internal /
/*
MIT License
@@ -22,18 +21,19 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
package pkg
import (
"fmt"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
"time"
)
func StartMigration(cmd *cobra.Command) {
intro()
logger.Info("Starting database migration...")
utils.Info("Starting database migration...")
// Get DB config
dbConf = initDbConfig(cmd)
targetDbConf = initTargetDbConfig()
@@ -53,8 +53,8 @@ func StartMigration(cmd *cobra.Command) {
// Backup source Database
BackupDatabase(dbConf, backupFileName, true)
// Restore source database into target database
logger.Info("Restoring [%s] database into [%s] database...", dbConf.dbName, targetDbConf.targetDbName)
utils.Info("Restoring [%s] database into [%s] database...", dbConf.dbName, targetDbConf.targetDbName)
RestoreDatabase(&newDbConfig, conf)
logger.Info("[%s] database has been restored into [%s] database", dbConf.dbName, targetDbConf.targetDbName)
logger.Info("Database migration completed.")
utils.Info("[%s] database has been restored into [%s] database", dbConf.dbName, targetDbConf.targetDbName)
utils.Info("Database migration completed.")
}

View File

@@ -22,13 +22,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
package pkg
import (
"fmt"
"github.com/jkaninda/go-storage/pkg/ftp"
"github.com/jkaninda/go-storage/pkg/ssh"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/utils"
"os"
@@ -37,7 +36,7 @@ import (
)
func sshBackup(db *dbConfig, config *BackupConfig) {
logger.Info("Backup database to Remote server")
utils.Info("Backup database to Remote server")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
BackupDatabase(db, config.backupFileName, disableCompression)
@@ -46,50 +45,51 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
encryptBackup(config)
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
logger.Info("Uploading backup archive to remote storage ... ")
logger.Info("Backup name is %s", finalFileName)
utils.Info("Uploading backup archive to remote storage ... ")
utils.Info("Backup name is %s", finalFileName)
sshConfig, err := loadSSHConfig()
if err != nil {
logger.Fatal("Error loading ssh config: %s", err)
utils.Fatal("Error loading ssh config: %s", err)
}
sshStorage, err := ssh.NewStorage(ssh.Config{
Host: sshConfig.hostName,
Port: sshConfig.port,
User: sshConfig.user,
Password: sshConfig.password,
RemotePath: config.remotePath,
LocalPath: tmpPath,
Host: sshConfig.hostName,
Port: sshConfig.port,
User: sshConfig.user,
Password: sshConfig.password,
IdentifyFile: sshConfig.identifyFile,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
if err != nil {
logger.Fatal("Error creating SSH storage: %s", err)
utils.Fatal("Error creating SSH storage: %s", err)
}
err = sshStorage.Copy(finalFileName)
if err != nil {
logger.Fatal("Error copying backup file: %s", err)
utils.Fatal("Error copying backup file: %s", err)
}
// Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
logger.Error("Error: %s", err)
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
logger.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
// Delete backup file from tmp folder
err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName))
if err != nil {
logger.Error("Error deleting file: %v", err)
utils.Error("Error deleting file: %v", err)
}
if config.prune {
err := sshStorage.Prune(config.backupRetention)
if err != nil {
logger.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
logger.Info("Uploading backup archive to remote storage ... done ")
utils.Info("Uploading backup archive to remote storage ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
@@ -102,80 +102,14 @@ func sshBackup(db *dbConfig, config *BackupConfig) {
})
// Delete temp
deleteTemp()
logger.Info("Backup completed successfully")
utils.Info("Backup completed successfully")
}
func ftpBackup(db *dbConfig, config *BackupConfig) {
logger.Info("Backup database to the remote FTP server")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
logger.Info("Uploading backup archive to the remote FTP server ... ")
logger.Info("Backup name is %s", finalFileName)
ftpConfig := loadFtpConfig()
ftpStorage, err := ftp.NewStorage(ftp.Config{
Host: ftpConfig.host,
Port: ftpConfig.port,
User: ftpConfig.user,
Password: ftpConfig.password,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
if err != nil {
logger.Fatal("Error creating SSH storage: %s", err)
}
err = ftpStorage.Copy(finalFileName)
if err != nil {
logger.Fatal("Error copying backup file: %s", err)
}
logger.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
// Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
logger.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
// Delete backup file from tmp folder
err = utils.DeleteFile(filepath.Join(tmpPath, finalFileName))
if err != nil {
logger.Error("Error deleting file: %v", err)
}
if config.prune {
err := ftpStorage.Prune(config.backupRetention)
if err != nil {
logger.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
logger.Info("Uploading backup archive to the remote FTP server ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: backupSize,
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete temp
deleteTemp()
logger.Info("Backup completed successfully")
}
func remoteRestore(db *dbConfig, conf *RestoreConfig) {
logger.Info("Restore database from remote server")
utils.Info("Restore database from remote server")
sshConfig, err := loadSSHConfig()
if err != nil {
logger.Fatal("Error loading ssh config: %s", err)
utils.Fatal("Error loading ssh config: %s", err)
}
sshStorage, err := ssh.NewStorage(ssh.Config{
@@ -188,16 +122,16 @@ func remoteRestore(db *dbConfig, conf *RestoreConfig) {
LocalPath: tmpPath,
})
if err != nil {
logger.Fatal("Error creating SSH storage: %s", err)
utils.Fatal("Error creating SSH storage: %s", err)
}
err = sshStorage.CopyFrom(conf.file)
if err != nil {
logger.Fatal("Error copying backup file: %s", err)
utils.Fatal("Error copying backup file: %s", err)
}
RestoreDatabase(db, conf)
}
func ftpRestore(db *dbConfig, conf *RestoreConfig) {
logger.Info("Restore database from FTP server")
utils.Info("Restore database from FTP server")
ftpConfig := loadFtpConfig()
ftpStorage, err := ftp.NewStorage(ftp.Config{
Host: ftpConfig.host,
@@ -208,11 +142,77 @@ func ftpRestore(db *dbConfig, conf *RestoreConfig) {
LocalPath: tmpPath,
})
if err != nil {
logger.Fatal("Error creating SSH storage: %s", err)
utils.Fatal("Error creating SSH storage: %s", err)
}
err = ftpStorage.CopyFrom(conf.file)
if err != nil {
logger.Fatal("Error copying backup file: %s", err)
utils.Fatal("Error copying backup file: %s", err)
}
RestoreDatabase(db, conf)
}
func ftpBackup(db *dbConfig, config *BackupConfig) {
utils.Info("Backup database to the remote FTP server")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
BackupDatabase(db, config.backupFileName, disableCompression)
finalFileName := config.backupFileName
if config.encryption {
encryptBackup(config)
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)
ftpConfig := loadFtpConfig()
ftpStorage, err := ftp.NewStorage(ftp.Config{
Host: ftpConfig.host,
Port: ftpConfig.port,
User: ftpConfig.user,
Password: ftpConfig.password,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
if err != nil {
utils.Fatal("Error creating SSH storage: %s", err)
}
err = ftpStorage.Copy(finalFileName)
if err != nil {
utils.Fatal("Error copying backup file: %s", err)
}
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
// Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
// 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 {
err := ftpStorage.Prune(config.backupRetention)
if err != nil {
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
utils.Info("Uploading backup archive to the remote FTP server ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
BackupSize: backupSize,
Database: db.dbName,
Storage: config.storage,
BackupLocation: filepath.Join(config.remotePath, finalFileName),
StartTime: startTime,
EndTime: time.Now().Format(utils.TimeFormat()),
})
// Delete temp
deleteTemp()
utils.Info("Backup completed successfully")
}

View File

@@ -1,6 +1,3 @@
// Package internal /
package internal
/*
MIT License
@@ -24,10 +21,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package pkg
import (
"github.com/jkaninda/encryptor"
"github.com/jkaninda/go-storage/pkg/local"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/utils"
"github.com/spf13/cobra"
"os"
@@ -56,14 +55,14 @@ func StartRestore(cmd *cobra.Command) {
}
}
func localRestore(dbConf *dbConfig, restoreConf *RestoreConfig) {
logger.Info("Restore database from local")
utils.Info("Restore database from local")
localStorage := local.NewStorage(local.Config{
RemotePath: storagePath,
LocalPath: tmpPath,
})
err := localStorage.CopyFrom(restoreConf.file)
if err != nil {
logger.Fatal("Error copying backup file: %s", err)
utils.Fatal("Error copying backup file: %s", err)
}
RestoreDatabase(dbConf, restoreConf)
@@ -72,41 +71,41 @@ func localRestore(dbConf *dbConfig, restoreConf *RestoreConfig) {
// RestoreDatabase restore database
func RestoreDatabase(db *dbConfig, conf *RestoreConfig) {
if conf.file == "" {
logger.Fatal("Error, file required")
utils.Fatal("Error, file required")
}
extension := filepath.Ext(filepath.Join(tmpPath, conf.file))
rFile, err := os.ReadFile(filepath.Join(tmpPath, conf.file))
outputFile := RemoveLastExtension(filepath.Join(tmpPath, conf.file))
if err != nil {
logger.Fatal("Error reading backup file: %s ", err)
utils.Fatal("Error reading backup file: %s ", err)
}
if extension == ".gpg" {
if conf.usingKey {
logger.Info("Decrypting backup using private key...")
logger.Warn("Backup decryption using a private key is not fully supported")
utils.Info("Decrypting backup using private key...")
utils.Warn("Backup decryption using a private key is not fully supported")
prKey, err := os.ReadFile(conf.privateKey)
if err != nil {
logger.Fatal("Error reading public key: %s ", err)
utils.Fatal("Error reading public key: %s ", err)
}
err = encryptor.DecryptWithPrivateKey(rFile, outputFile, prKey, conf.passphrase)
if err != nil {
logger.Fatal("error during decrypting backup %v", err)
utils.Fatal("error during decrypting backup %v", err)
}
logger.Info("Decrypting backup using private key...done")
utils.Info("Decrypting backup using private key...done")
} else {
if conf.passphrase == "" {
logger.Error("Error, passphrase or private key required")
logger.Fatal("Your file seems to be a GPG file.\nYou need to provide GPG keys. GPG_PASSPHRASE or GPG_PRIVATE_KEY environment variable is required.")
utils.Error("Error, passphrase or private key required")
utils.Fatal("Your file seems to be a GPG file.\nYou need to provide GPG keys. GPG_PASSPHRASE or GPG_PRIVATE_KEY environment variable is required.")
} else {
logger.Info("Decrypting backup using passphrase...")
utils.Info("Decrypting backup using passphrase...")
// decryptWithGPG file
err := encryptor.Decrypt(rFile, outputFile, conf.passphrase)
if err != nil {
logger.Fatal("Error decrypting file %s %v", file, err)
utils.Fatal("Error decrypting file %s %v", file, err)
}
logger.Info("Decrypting backup using passphrase...done")
utils.Info("Decrypting backup using passphrase...done")
// Update file name
conf.file = RemoveLastExtension(file)
}
@@ -120,37 +119,37 @@ func RestoreDatabase(db *dbConfig, conf *RestoreConfig) {
return
}
testDatabaseConnection(db)
logger.Info("Restoring database...")
utils.Info("Restoring database...")
extension := filepath.Ext(filepath.Join(tmpPath, conf.file))
// Restore from compressed file / .sql.gz
if extension == ".gz" {
str := "zcat " + filepath.Join(tmpPath, conf.file) + " | mysql -h " + db.dbHost + " -P " + db.dbPort + " -u " + db.dbUserName + " " + db.dbName
str := "zcat " + filepath.Join(tmpPath, conf.file) + " | mariadb -h " + db.dbHost + " -P " + db.dbPort + " -u " + db.dbUserName + " " + db.dbName
_, err := exec.Command("sh", "-c", str).Output()
if err != nil {
logger.Fatal("Error, in restoring the database %v", err)
utils.Fatal("Error, in restoring the database %v", err)
}
logger.Info("Restoring database... done")
logger.Info("Database has been restored")
utils.Info("Restoring database... done")
utils.Info("Database has been restored")
// Delete temp
deleteTemp()
} else if extension == ".sql" {
// Restore from sql file
str := "cat " + filepath.Join(tmpPath, conf.file) + " | mysql -h " + db.dbHost + " -P " + db.dbPort + " -u " + db.dbUserName + " " + db.dbName
str := "cat " + filepath.Join(tmpPath, conf.file) + " | mariadb -h " + db.dbHost + " -P " + db.dbPort + " -u " + db.dbUserName + " " + db.dbName
_, err := exec.Command("sh", "-c", str).Output()
if err != nil {
logger.Fatal("Error in restoring the database %v", err)
utils.Fatal("Error in restoring the database %v", err)
}
logger.Info("Restoring database... done")
logger.Info("Database has been restored")
utils.Info("Restoring database... done")
utils.Info("Database has been restored")
// Delete temp
deleteTemp()
} else {
logger.Fatal("Unknown file extension %s", extension)
utils.Fatal("Unknown file extension %s", extension)
}
} else {
logger.Fatal("File not found in %s", filepath.Join(tmpPath, conf.file))
utils.Fatal("File not found in %s", filepath.Join(tmpPath, conf.file))
}
}

View File

@@ -22,12 +22,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
package pkg
import (
"fmt"
"github.com/jkaninda/go-storage/pkg/s3"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/jkaninda/mysql-bkup/utils"
"os"
@@ -37,7 +36,7 @@ import (
func s3Backup(db *dbConfig, config *BackupConfig) {
logger.Info("Backup database to s3 storage")
utils.Info("Backup database to s3 storage")
startTime = time.Now().Format(utils.TimeFormat())
// Backup database
BackupDatabase(db, config.backupFileName, disableCompression)
@@ -46,12 +45,12 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
encryptBackup(config)
finalFileName = fmt.Sprintf("%s.%s", config.backupFileName, "gpg")
}
logger.Info("Uploading backup archive to remote storage S3 ... ")
utils.Info("Uploading backup archive to remote storage S3 ... ")
awsConfig := initAWSConfig()
if config.remotePath == "" {
config.remotePath = awsConfig.remotePath
}
logger.Info("Backup name is %s", finalFileName)
utils.Info("Backup name is %s", finalFileName)
s3Storage, err := s3.NewStorage(s3.Config{
Endpoint: awsConfig.endpoint,
Bucket: awsConfig.bucket,
@@ -60,20 +59,20 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
Region: awsConfig.region,
DisableSsl: awsConfig.disableSsl,
ForcePathStyle: awsConfig.forcePathStyle,
RemotePath: awsConfig.remotePath,
RemotePath: config.remotePath,
LocalPath: tmpPath,
})
if err != nil {
logger.Fatal("Error creating s3 storage: %s", err)
utils.Fatal("Error creating s3 storage: %s", err)
}
err = s3Storage.Copy(finalFileName)
if err != nil {
logger.Fatal("Error copying backup file: %s", err)
utils.Fatal("Error copying backup file: %s", err)
}
// Get backup info
fileInfo, err := os.Stat(filepath.Join(tmpPath, finalFileName))
if err != nil {
logger.Error("Error: %s", err)
utils.Error("Error: %s", err)
}
backupSize = fileInfo.Size()
@@ -87,11 +86,11 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
if config.prune {
err := s3Storage.Prune(config.backupRetention)
if err != nil {
logger.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
utils.Fatal("Error deleting old backup from %s storage: %s ", config.storage, err)
}
}
logger.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
logger.Info("Uploading backup archive to remote storage S3 ... done ")
utils.Info("Backup saved in %s", filepath.Join(config.remotePath, finalFileName))
utils.Info("Uploading backup archive to remote storage S3 ... done ")
// Send notification
utils.NotifySuccess(&utils.NotificationData{
File: finalFileName,
@@ -104,11 +103,11 @@ func s3Backup(db *dbConfig, config *BackupConfig) {
})
// Delete temp
deleteTemp()
logger.Info("Backup completed successfully")
utils.Info("Backup completed successfully")
}
func s3Restore(db *dbConfig, conf *RestoreConfig) {
logger.Info("Restore database from s3")
utils.Info("Restore database from s3")
awsConfig := initAWSConfig()
if conf.remotePath == "" {
conf.remotePath = awsConfig.remotePath
@@ -121,15 +120,15 @@ func s3Restore(db *dbConfig, conf *RestoreConfig) {
Region: awsConfig.region,
DisableSsl: awsConfig.disableSsl,
ForcePathStyle: awsConfig.forcePathStyle,
RemotePath: awsConfig.remotePath,
RemotePath: conf.remotePath,
LocalPath: tmpPath,
})
if err != nil {
logger.Fatal("Error creating s3 storage: %s", err)
utils.Fatal("Error creating s3 storage: %s", err)
}
err = s3Storage.CopyFrom(conf.file)
if err != nil {
logger.Fatal("Error download file from S3 storage: %s", err)
utils.Fatal("Error download file from S3 storage: %s", err)
}
RestoreDatabase(db, conf)
}

View File

@@ -1,4 +1,3 @@
// Package internal /
/*
MIT License
@@ -22,7 +21,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package internal
package pkg
const tmpPath = "/tmp/backup"
const gpgHome = "/config/gnupg"

View File

@@ -1,18 +1,69 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>🔴 Urgent: Database Backup Failure Notification</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔴 Urgent: Database Backup Failure</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
color: #333;
margin: 0;
padding: 20px;
}
h2 {
color: #d9534f;
}
.details {
background-color: #ffffff;
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
margin-top: 10px;
}
.details ul {
list-style-type: none;
padding: 0;
}
.details li {
margin: 5px 0;
}
a {
color: #0275d8;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
footer {
margin-top: 20px;
font-size: 0.9em;
color: #6c757d;
}
</style>
</head>
<body>
<h2>Hi,</h2>
<p>An error occurred during database backup.</p>
<h3>Failure Details:</h3>
<ul>
<li>Error Message: {{.Error}}</li>
<li>Date: {{.EndTime}}</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul>
<p>©2024 <a href="https://github.com/jkaninda/mysql-bkup">mysql-bkup</a></p>
<h2>🔴 Urgent: Database Backup Failure Notification</h2>
<p>Dear Team,</p>
<p>An error occurred during the database backup process. Please review the details below and take the necessary actions:</p>
<div class="details">
<h3>Failure Details:</h3>
<ul>
<li><strong>Database Name:</strong> {{.DatabaseName}}</li>
<li><strong>Error Message:</strong> {{.Error}}</li>
<li><strong>Date:</strong> {{.EndTime}}</li>
<li><strong>Backup Reference:</strong> {{.BackupReference}}</li>
</ul>
</div>
<p>We recommend investigating the issue as soon as possible to prevent potential data loss or service disruptions.</p>
<p>For more information, visit the <a href="https://jkaninda.github.io/pg-bkup">pg-bkup documentation</a>.</p>
<footer>
&copy; 2024 <a href="https://github.com/jkaninda/pg-bkup">pg-bkup</a> | Automated Backup System
</footer>
</body>
</html>
</html>

View File

@@ -1,24 +1,70 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>✅ Database Backup Notification {{.Database}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>✅ Database Backup Successful {{.Database}}</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
color: #333;
margin: 0;
padding: 20px;
}
h2 {
color: #5cb85c;
}
.details {
background-color: #ffffff;
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
margin-top: 10px;
}
.details ul {
list-style-type: none;
padding: 0;
}
.details li {
margin: 5px 0;
}
a {
color: #0275d8;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
footer {
margin-top: 20px;
font-size: 0.9em;
color: #6c757d;
}
</style>
</head>
<body>
<h2>Hi,</h2>
<p>Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.</p>
<h3>Backup Details:</h3>
<ul>
<li>Database Name: {{.Database}}</li>
<li>Backup Start Time: {{.StartTime}}</li>
<li>Backup End Time: {{.EndTime}}</li>
<li>Backup Storage: {{.Storage}}</li>
<li>Backup Location: {{.BackupLocation}}</li>
<li>Backup Size: {{.BackupSize}} bytes</li>
<li>Backup Reference: {{.BackupReference}} </li>
</ul>
<p>Best regards,</p>
<p>©2024 <a href="https://github.com/jkaninda/mysql-bkup">mysql-bkup</a></p>
<href>
<h2>✅ Database Backup Successful</h2>
<p>Dear Team,</p>
<p>The backup process for the <strong>{{.Database}}</strong> database was successfully completed. Please find the details below:</p>
<div class="details">
<h3>Backup Details:</h3>
<ul>
<li><strong>Database Name:</strong> {{.Database}}</li>
<li><strong>Backup Start Time:</strong> {{.StartTime}}</li>
<li><strong>Backup End Time:</strong> {{.EndTime}}</li>
<li><strong>Backup Storage:</strong> {{.Storage}}</li>
<li><strong>Backup Location:</strong> {{.BackupLocation}}</li>
<li><strong>Backup Size:</strong> {{.BackupSize}} bytes</li>
<li><strong>Backup Reference:</strong> {{.BackupReference}}</li>
</ul>
</div>
<p>You can access the backup at the specified location if needed. Thank you for using <a href="https://jkaninda.github.io/mysql-bkup/">mysql-bkup</a>.</p>
<footer>
&copy; 2024 <a href="https://github.com/jkaninda/mysql-bkup">mysql-bkup</a> | Automated Backup System
</footer>
</body>
</html>
</html>

View File

@@ -1,8 +1,11 @@
🔴 Urgent: Database Backup Failure Notification
Hi,
An error occurred during database backup.
Dear Team,
An error occurred during the database backup process.
Please review the details below and take the necessary actions:
Failure Details:
- Database Name: {{.DatabaseName}}
- Date: {{.EndTime}}
- Backup Reference: {{.BackupReference}}
- Error Message: {{.Error}}
We recommend investigating the issue as soon as possible to prevent potential data loss or service disruptions.

View File

@@ -1,6 +1,8 @@
[✅ Database Backup Notification {{.Database}}
Hi,
Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.
Database Backup Successful
Dear Team,
The backup process for the {{.Database}} database was successfully completed.
Please find the details below:
Backup Details:
- Database Name: {{.Database}}
@@ -10,3 +12,5 @@ Backup Details:
- Backup Location: {{.BackupLocation}}
- Backup Size: {{.BackupSize}} bytes
- Backup Reference: {{.BackupReference}}
You can access the backup at the specified location if needed.

View File

@@ -1,5 +1,3 @@
package utils
/*
MIT License
@@ -23,6 +21,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package utils
import "os"
type MailConfig struct {
@@ -49,6 +50,7 @@ type ErrorMessage struct {
EndTime string
Error string
BackupReference string
DatabaseName string
}
// loadMailConfig gets mail environment variables and returns MailConfig
@@ -80,3 +82,5 @@ func backupReference() string {
}
const templatePath = "/config/templates"
var DatabaseName = ""

View File

@@ -1,4 +1,3 @@
// Package utils /
/*
MIT License
@@ -22,6 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package utils
const RestoreExample = "restore --dbname database --file db_20231219_022941.sql.gz\n" +
@@ -32,3 +32,4 @@ const BackupExample = "backup --dbname database --disable-compression\n" +
const MainExample = "mysql-bkup backup --dbname database --disable-compression\n" +
"backup --dbname database --storage s3 --path /custom-path\n" +
"restore --dbname database --file db_20231219_022941.sql.gz"
const traceLog = "trace"

View File

@@ -1,13 +1,3 @@
package logger
import (
"fmt"
"log"
"os"
"runtime"
"strings"
)
/*
MIT License
@@ -32,6 +22,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package utils
import (
"fmt"
"log"
"os"
"runtime"
"strings"
)
// Info returns info log
func Info(msg string, args ...interface{}) {
log.SetOutput(getStd("/dev/stdout"))
@@ -54,7 +54,13 @@ func Error(msg string, args ...interface{}) {
func Fatal(msg string, args ...interface{}) {
log.SetOutput(os.Stdout)
// Format message if there are additional arguments
formattedMessage := msg
if len(args) > 0 {
formattedMessage = fmt.Sprintf(msg, args...)
}
logWithCaller("ERROR", msg, args...)
NotifyError(formattedMessage)
os.Exit(1)
}

View File

@@ -1,5 +1,3 @@
package utils
/*
MIT License
@@ -24,13 +22,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package utils
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"github.com/go-mail/mail"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"html/template"
"io"
"net/http"
@@ -56,7 +55,7 @@ func parseTemplate[T any](data T, fileName string) (string, error) {
}
func SendEmail(subject, body string) error {
logger.Info("Start sending email notification....")
Info("Start sending email notification....")
config := loadMailConfig()
emails := strings.Split(config.MailTo, ",")
m := mail.NewMessage()
@@ -68,16 +67,16 @@ func SendEmail(subject, body string) error {
d.TLSConfig = &tls.Config{InsecureSkipVerify: config.SkipTls}
if err := d.DialAndSend(m); err != nil {
logger.Error("Error could not send email : %v", err)
Error("Error could not send email : %v", err)
return err
}
logger.Info("Email notification has been sent")
Info("Email notification has been sent")
return nil
}
func sendMessage(msg string) error {
logger.Info("Sending Telegram notification... ")
Info("Sending Telegram notification... ")
chatId := os.Getenv("TG_CHAT_ID")
body, _ := json.Marshal(map[string]string{
"chat_id": chatId,
@@ -97,11 +96,11 @@ func sendMessage(msg string) error {
}
code := response.StatusCode
if code == 200 {
logger.Info("Telegram notification has been sent")
Info("Telegram notification has been sent")
return nil
} else {
body, _ := io.ReadAll(response.Body)
logger.Error("Error could not send message, error: %s", string(body))
Error("Error could not send message, error: %s", string(body))
return fmt.Errorf("error could not send message %s", string(body))
}
@@ -126,11 +125,11 @@ func NotifySuccess(notificationData *NotificationData) {
if err == nil {
body, err := parseTemplate(*notificationData, "email.tmpl")
if err != nil {
logger.Error("Could not parse email template: %v", err)
Error("Could not parse email template: %v", err)
}
err = SendEmail(fmt.Sprintf("✅ Database Backup Notification %s", notificationData.Database), body)
if err != nil {
logger.Error("Could not send email: %v", err)
Error("Could not send email: %v", err)
}
}
// Telegram notification
@@ -138,12 +137,12 @@ func NotifySuccess(notificationData *NotificationData) {
if err == nil {
message, err := parseTemplate(*notificationData, "telegram.tmpl")
if err != nil {
logger.Error("Could not parse telegram template: %v", err)
Error("Could not parse telegram template: %v", err)
}
err = sendMessage(message)
if err != nil {
logger.Error("Could not send Telegram message: %v", err)
Error("Could not send Telegram message: %v", err)
}
}
}
@@ -168,13 +167,14 @@ func NotifyError(error string) {
Error: error,
EndTime: time.Now().Format(TimeFormat()),
BackupReference: os.Getenv("BACKUP_REFERENCE"),
DatabaseName: DatabaseName,
}, "email-error.tmpl")
if err != nil {
logger.Error("Could not parse error template: %v", err)
Error("Could not parse error template: %v", err)
}
err = SendEmail("🔴 Urgent: Database Backup Failure Notification", body)
if err != nil {
logger.Error("Could not send email: %v", err)
Error("Could not send email: %v", err)
}
}
// Telegram notification
@@ -184,15 +184,16 @@ func NotifyError(error string) {
Error: error,
EndTime: time.Now().Format(TimeFormat()),
BackupReference: os.Getenv("BACKUP_REFERENCE"),
DatabaseName: DatabaseName,
}, "telegram-error.tmpl")
if err != nil {
logger.Error("Could not parse error template: %v", err)
Error("Could not parse error template: %v", err)
}
err = sendMessage(message)
if err != nil {
logger.Error("Could not send telegram message: %v", err)
Error("Could not send telegram message: %v", err)
}
}
}

View File

@@ -1,4 +1,3 @@
// Package utils /
/*
MIT License
@@ -22,11 +21,11 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package utils
import (
"fmt"
"github.com/jkaninda/mysql-bkup/pkg/logger"
"github.com/robfig/cron/v3"
"github.com/spf13/cobra"
"io"
@@ -36,7 +35,7 @@ import (
"time"
)
var Version = "development"
var Version = ""
// FileExists checks if the file does exist
func FileExists(filename string) bool {
@@ -112,7 +111,7 @@ func CopyFile(src, dst string) error {
}
func ChangePermission(filePath string, mod int) {
if err := os.Chmod(filePath, fs.FileMode(mod)); err != nil {
logger.Fatal("Error changing permissions of %s: %v\n", filePath, err)
Fatal("Error changing permissions of %s: %v\n", filePath, err)
}
}
@@ -174,7 +173,7 @@ func GetEnvVariable(envName, oldEnvName string) string {
if err != nil {
return value
}
logger.Warn("%s is deprecated, please use %s instead! ", oldEnvName, envName)
Warn("%s is deprecated, please use %s instead! ", oldEnvName, envName)
}
}
return value
@@ -221,7 +220,7 @@ func GetIntEnv(envName string) int {
}
ret, err := strconv.Atoi(val)
if err != nil {
logger.Error("Error: %v", err)
Error("Error: %v", err)
}
return ret
}
@@ -246,7 +245,7 @@ func CronNextTime(cronExpr string) time.Time {
// Parse the cron expression
schedule, err := cron.ParseStandard(cronExpr)
if err != nil {
logger.Error("Error parsing cron expression: %s", err)
Error("Error parsing cron expression: %s", err)
return time.Time{}
}
// Get the current time