Skip to content

Commit

Permalink
Merge pull request #26 from Nsttt/add-mysql
Browse files Browse the repository at this point in the history
Add Mysql support
  • Loading branch information
TheDevMinerTV authored Apr 14, 2024
2 parents e50464f + ce2cf99 commit f5aab4b
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 150 deletions.
18 changes: 6 additions & 12 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
S3_ENDPOINT=
S3_BUCKET=postgres-backups
S3_ACCESS_KEY=
S3_SECRET_KEY=

POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=postgres

EVERY=24h
URLS="postgres://user:password@host:port/dbname,mysql://user:password@host:port/dbname"
S3_ENDPOINT="your_s3_endpoint"
S3_BUCKET="your_s3_bucket"
S3_ACCESS_KEY="your_s3_access_key"
S3_SECRET_KEY="your_s3_secret_key"
INTERVAL="24h"
2 changes: 1 addition & 1 deletion .github/workflows/ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:

env:
REGISTRY: ghcr.io
FQDN: "ghcr.io/thedevminertv/postgres-s3-backup"
FQDN: "ghcr.io/thedevminertv/database-s3-backup"

jobs:
build:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:

env:
REGISTRY: ghcr.io
FQDN: "ghcr.io/thedevminertv/postgres-s3-backup"
FQDN: "ghcr.io/thedevminertv/database-s3-backup"

jobs:
build:
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM alpine:latest
WORKDIR /root/

RUN apk --no-cache add ca-certificates postgresql-client
RUN apk --no-cache add ca-certificates postgresql-client mysql-client

COPY --from=builder /app/main /bin/postgres-s3-backup
COPY --from=builder /app/main /bin/database-s3-backup

CMD ["/bin/postgres-s3-backup"]
CMD ["/bin/database-s3-backup"]
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# PostgreSQL Backup to S3 with Docker
# Database Backup to S3 with Docker

This application automates the process of backing up PostgreSQL databases and uploading them to an S3-compatible storage service, utilizing Docker for easy deployment and scheduling.
This application automates the process of backing up PostgreSQL and MySQL databases and uploading them to an S3-compatible storage service, utilizing Docker for easy deployment and scheduling.

## Features

- Easy deployment with Docker and Docker Compose.
- Support for multiple PostgreSQL databases.
- Support for multiple PostgreSQL and MySQL databases.
- Customizable backup intervals.
- Direct upload of backups to an S3-compatible storage bucket.
- Environment variable and command-line configuration for flexibility.
Expand All @@ -14,7 +14,7 @@ This application automates the process of backing up PostgreSQL databases and up
## Prerequisites

- Docker and Docker Compose installed on your system.
- Access to a PostgreSQL database.
- Access to PostgreSQL and/or MySQL databases.
- Access to an S3-compatible storage service.

## Configuration
Expand All @@ -25,7 +25,7 @@ Before running the application, you need to configure it either by setting envir

Create a `.env` file in the project directory with the following variables:

- `URLS`: Comma-separated list of PostgreSQL database URLs to backup. Format: `postgres://<user>:<password>@<host>[:<port>]/<dbname>`
- `URLS`: Comma-separated list of database URLs to backup. Format for PostgreSQL: `postgres://<user>:<password>@<host>[:<port>]/<dbname>` and for MySQL: `mysql://<user>:<password>@<host>[:<port>]/<dbname>`
- `S3_ENDPOINT`: The endpoint URL of your S3-compatible storage service.
- `S3_BUCKET`: The name of the bucket where backups will be stored.
- `S3_ACCESS_KEY`: Your S3 access key.
Expand All @@ -41,7 +41,7 @@ services:
app:
build: .
environment:
URLS: "postgres://user:password@host:port/dbname"
URLS: "postgres://user:password@host:port/dbname,mysql://user:password@host:port/dbname"
S3_ENDPOINT: "your_s3_endpoint"
S3_BUCKET: "your_s3_bucket"
S3_ACCESS_KEY: "your_s3_access_key"
Expand All @@ -51,7 +51,7 @@ services:
## Running the Application with Docker
There is an image available on `ghcr.io/thedevminertv/postgres_s3_backup` that you can use.
There is an image available on `ghcr.io/thedevminertv/database-s3-backup` that you can use.

Alternatively, you can build the image yourself:

Expand All @@ -67,7 +67,7 @@ Alternatively, you can build the image yourself:
docker compose up -d
```

This will start the application in the background. It will automatically perform backups based on the configured interval and upload them to the specified S3 bucket.
This will start the application in the background. It will automatically perform backups for both PostgreSQL and MySQL databases based on the configured interval and upload them to the specified S3 bucket.

## Monitoring and Logs

Expand Down
118 changes: 118 additions & 0 deletions dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package main

import (
"bufio"
"errors"
"fmt"
"os/exec"
"strconv"
"time"
)

type connectionOptions struct {
Host string
DbType string
Port int
Database string
Username string
Password string
}

var (
PGDumpCmd = "pg_dump"
pgDumpStdOpts = []string{"--no-owner", "--no-acl", "--clean", "--blobs", "-v"}
pgDumpDefaultFormat = "c"
ErrPgDumpNotFound = errors.New("pg_dump not found")

MysqlDumpCmd = "mysqldump"
mysqlDumpStdOpts = []string{"--compact", "--skip-add-drop-table", "--skip-add-locks", "--skip-disable-keys", "--skip-set-charset", "-v"}
ErrMySqlDumpNotFound = errors.New("mysqldump not found")

ErrUnsupportedType = errors.New("unsupported database type")
)

func RunDump(connectionOpts *connectionOptions, outFile string) error {
cmd, err := buildDumpCommand(connectionOpts, outFile)
if err != nil {
return err
}

return executeCommand(cmd)
}

func buildDumpCommand(opts *connectionOptions, outFile string) (*exec.Cmd, error) {
switch opts.DbType {
case "postgres":
if !commandExist(PGDumpCmd) {
return nil, ErrPgDumpNotFound
}
options := append(
pgDumpStdOpts,
fmt.Sprintf("-f%s", outFile),
fmt.Sprintf("--dbname=%s", opts.Database),
fmt.Sprintf("--host=%s", opts.Host),
fmt.Sprintf("--port=%d", opts.Port),
fmt.Sprintf("--username=%s", opts.Username),
fmt.Sprintf("--format=%s", pgDumpDefaultFormat),
)
return exec.Command(PGDumpCmd, options...), nil

case "mysql":
mysqldumpCmd := "mysqldump"
if !commandExist(mysqldumpCmd) {
return nil, ErrMySqlDumpNotFound
}
options := append(
mysqlDumpStdOpts,
"-h", opts.Host,
"-P", strconv.Itoa(opts.Port),
"-u", opts.Username,
fmt.Sprintf("--password=%s", opts.Password),
"--databases", opts.Database,
"-r", outFile,
)

return exec.Command(mysqldumpCmd, options...), nil

default:
return nil, ErrUnsupportedType
}
}

func executeCommand(cmd *exec.Cmd) error {
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}

if err := cmd.Start(); err != nil {
return err
}

go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()

if err := cmd.Wait(); err != nil {
return err
}
return nil
}

func commandExist(command string) bool {
_, err := exec.LookPath(command)
return err == nil
}

func newFileName(db string, dbType string) string {
switch dbType {
case "postgres":
return fmt.Sprintf(`%v_%v.pgdump`, db, time.Now().Unix())
case "mysql":
return fmt.Sprintf(`%v_%v.sql`, db, time.Now().Unix())
}
return fmt.Sprintf(`%v_%v`, db, time.Now().Unix())
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
Expand Down
41 changes: 0 additions & 41 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,19 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ=
github.com/minio/minio-go/v7 v7.0.63/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4=
github.com/minio/minio-go/v7 v7.0.64 h1:Zdza8HwOzkld0ZG/og50w56fKi6AAyfqfifmasD9n2Q=
github.com/minio/minio-go/v7 v7.0.64/go.mod h1:R4WVUR6ZTedlCcGwZRauLMIKjgyaWxhs4Mqi/OMPmEc=
github.com/minio/minio-go/v7 v7.0.65 h1:sOlB8T3nQK+TApTpuN3k4WD5KasvZIE3vVFzyyCa0go=
github.com/minio/minio-go/v7 v7.0.65/go.mod h1:R4WVUR6ZTedlCcGwZRauLMIKjgyaWxhs4Mqi/OMPmEc=
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
github.com/minio/minio-go/v7 v7.0.67 h1:BeBvZWAS+kRJm1vGTMJYVjKUNoo0FoEt/wUWdUtfmh8=
github.com/minio/minio-go/v7 v7.0.67/go.mod h1:+UXocnUeZ3wHvVh5s95gcrA4YjMIbccT6ubB+1m054A=
github.com/minio/minio-go/v7 v7.0.68 h1:hTqSIfLlpXaKuNy4baAp4Jjy2sqZEN9hRxD0M4aOfrQ=
github.com/minio/minio-go/v7 v7.0.68/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0=
github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
Expand All @@ -52,36 +30,17 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
19 changes: 13 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package main
import (
"context"
"flag"
"github.com/joho/godotenv"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"log"
"net/url"
"os"
"strconv"
"strings"
"time"

"github.com/joho/godotenv"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)

func main() {
Expand Down Expand Up @@ -42,7 +43,13 @@ func main() {
log.Fatalf("Failed to parse url %s: %s", rawUrl, err)
}

port := 5432
port := 0
switch parsedUrl.Scheme {
case "postgres":
port = 5432
case "mysql":
port = 3306
}
rawPort := parsedUrl.Port()
if rawPort != "" {
port, err = strconv.Atoi(rawPort)
Expand All @@ -58,6 +65,7 @@ func main() {

urls[i] = connectionOptions{
Host: parsedUrl.Hostname(),
DbType: parsedUrl.Scheme,
Port: port,
Database: strings.TrimPrefix(parsedUrl.Path, "/"),
Username: parsedUrl.User.Username(),
Expand All @@ -79,8 +87,7 @@ func main() {
for {
for _, u := range urls {
log.Printf("Backing up %s", u.Database)

file := newFileName(u.Database)
file := newFileName(u.Database, u.DbType)

if err = RunDump(&u, file); err != nil {
log.Printf("WARNING: Failed to dump database: %s", err)
Expand Down
Loading

0 comments on commit f5aab4b

Please sign in to comment.