├── .github ├── dependabot.yml ├── FUNDING.yml └── workflows │ ├── goreleaser.yml │ ├── testing.yml │ ├── codeql.yml │ └── docker.yml ├── docker ├── Dockerfile.postgres10 ├── Dockerfile.postgres11 ├── Dockerfile.postgres12 ├── Dockerfile.postgres9 ├── Dockerfile.mysql8 ├── Dockerfile.mysql9 ├── Dockerfile.postgres13 ├── Dockerfile.postgres14 ├── Dockerfile.postgres15 ├── Dockerfile.postgres16 ├── Dockerfile.postgres17 └── Dockerfile.mongo4.4 ├── .gitignore ├── LICENSE ├── .golangci.yml ├── Makefile ├── pkg ├── config │ └── config.go ├── dbdump │ ├── dbdmp.go │ ├── mongo │ │ └── mongo.go │ ├── mysql │ │ └── mysql.go │ └── postgres │ │ └── postgres.go └── helper │ └── cmd.go ├── go.mod ├── .goreleaser.yaml ├── docker-compose.yml ├── cmd └── docker-backup-database │ ├── config.go │ └── main.go ├── README_zh-CN.md ├── README_zh-TW.md ├── README.md └── go.sum /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres10: -------------------------------------------------------------------------------- 1 | FROM postgres:10-alpine 2 | 3 | LABEL maintainer="Bo-Yi Wu " \ 4 | org.label-schema.name="docker-backup-database" \ 5 | org.label-schema.vendor="Bo-Yi Wu" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | COPY release/linux/amd64/docker-backup-database /bin/ 9 | 10 | ENTRYPOINT ["/bin/docker-backup-database"] 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres11: -------------------------------------------------------------------------------- 1 | FROM postgres:11-alpine 2 | 3 | LABEL maintainer="Bo-Yi Wu " \ 4 | org.label-schema.name="docker-backup-database" \ 5 | org.label-schema.vendor="Bo-Yi Wu" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | COPY release/linux/amd64/docker-backup-database /bin/ 9 | 10 | ENTRYPOINT ["/bin/docker-backup-database"] 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres12: -------------------------------------------------------------------------------- 1 | FROM postgres:12-alpine 2 | 3 | LABEL maintainer="Bo-Yi Wu " \ 4 | org.label-schema.name="docker-backup-database" \ 5 | org.label-schema.vendor="Bo-Yi Wu" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | COPY release/linux/amd64/docker-backup-database /bin/ 9 | 10 | ENTRYPOINT ["/bin/docker-backup-database"] 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres9: -------------------------------------------------------------------------------- 1 | FROM postgres:9-alpine 2 | 3 | LABEL maintainer="Bo-Yi Wu " \ 4 | org.label-schema.name="docker-backup-database" \ 5 | org.label-schema.vendor="Bo-Yi Wu" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | COPY release/linux/amd64/docker-backup-database /bin/ 9 | 10 | ENTRYPOINT ["/bin/docker-backup-database"] 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | bin 18 | dist 19 | .env 20 | coverage.txt 21 | -------------------------------------------------------------------------------- /docker/Dockerfile.mysql8: -------------------------------------------------------------------------------- 1 | FROM mysql:8 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | LABEL maintainer="Bo-Yi Wu " \ 7 | org.label-schema.name="docker-backup-database" \ 8 | org.label-schema.vendor="Bo-Yi Wu" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | COPY release/${TARGETOS}/${TARGETARCH}/docker-backup-database /bin/ 12 | 13 | ENTRYPOINT ["/bin/docker-backup-database"] 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.mysql9: -------------------------------------------------------------------------------- 1 | FROM mysql:9 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | LABEL maintainer="Bo-Yi Wu " \ 7 | org.label-schema.name="docker-backup-database" \ 8 | org.label-schema.vendor="Bo-Yi Wu" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | COPY release/${TARGETOS}/${TARGETARCH}/docker-backup-database /bin/ 12 | 13 | ENTRYPOINT ["/bin/docker-backup-database"] 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres13: -------------------------------------------------------------------------------- 1 | FROM postgres:13-alpine 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | LABEL maintainer="Bo-Yi Wu " \ 7 | org.label-schema.name="docker-backup-database" \ 8 | org.label-schema.vendor="Bo-Yi Wu" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | COPY release/${TARGETOS}/${TARGETARCH}/docker-backup-database /bin/ 12 | 13 | ENTRYPOINT ["/bin/docker-backup-database"] 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres14: -------------------------------------------------------------------------------- 1 | FROM postgres:14-alpine 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | LABEL maintainer="Bo-Yi Wu " \ 7 | org.label-schema.name="docker-backup-database" \ 8 | org.label-schema.vendor="Bo-Yi Wu" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | COPY release/${TARGETOS}/${TARGETARCH}/docker-backup-database /bin/ 12 | 13 | ENTRYPOINT ["/bin/docker-backup-database"] 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres15: -------------------------------------------------------------------------------- 1 | FROM postgres:15-alpine 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | LABEL maintainer="Bo-Yi Wu " \ 7 | org.label-schema.name="docker-backup-database" \ 8 | org.label-schema.vendor="Bo-Yi Wu" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | COPY release/${TARGETOS}/${TARGETARCH}/docker-backup-database /bin/ 12 | 13 | ENTRYPOINT ["/bin/docker-backup-database"] 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres16: -------------------------------------------------------------------------------- 1 | FROM postgres:16-alpine 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | LABEL maintainer="Bo-Yi Wu " \ 7 | org.label-schema.name="docker-backup-database" \ 8 | org.label-schema.vendor="Bo-Yi Wu" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | COPY release/${TARGETOS}/${TARGETARCH}/docker-backup-database /bin/ 12 | 13 | ENTRYPOINT ["/bin/docker-backup-database"] 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.postgres17: -------------------------------------------------------------------------------- 1 | FROM postgres:17-alpine 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | LABEL maintainer="Bo-Yi Wu " \ 7 | org.label-schema.name="docker-backup-database" \ 8 | org.label-schema.vendor="Bo-Yi Wu" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | COPY release/${TARGETOS}/${TARGETARCH}/docker-backup-database /bin/ 12 | 13 | ENTRYPOINT ["/bin/docker-backup-database"] 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.mongo4.4: -------------------------------------------------------------------------------- 1 | FROM mongodb/mongodb-community-server:4.4.0-ubuntu2004 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | LABEL maintainer="Bo-Yi Wu " \ 7 | org.label-schema.name="docker-backup-database" \ 8 | org.label-schema.vendor="Bo-Yi Wu" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | COPY release/${TARGETOS}/${TARGETARCH}/docker-backup-database /bin/ 12 | 13 | ENTRYPOINT ["/bin/docker-backup-database"] 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://www.paypal.me/appleboy46'] 14 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: Goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Setup go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version-file: go.mod 24 | check-latest: true 25 | 26 | - name: Run GoReleaser 27 | uses: goreleaser/goreleaser-action@v6 28 | with: 29 | # either 'goreleaser' (default) or 'goreleaser-pro' 30 | distribution: goreleaser 31 | version: latest 32 | args: release --clean 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bo-Yi Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - bodyclose 5 | - dogsled 6 | - errcheck 7 | - exhaustive 8 | - gochecknoinits 9 | - goconst 10 | - gocritic 11 | - gocyclo 12 | - gofmt 13 | - goimports 14 | - goprintffuncname 15 | - gosec 16 | - gosimple 17 | - govet 18 | - ineffassign 19 | - misspell 20 | - nakedret 21 | - noctx 22 | - nolintlint 23 | - staticcheck 24 | - stylecheck 25 | - typecheck 26 | - unconvert 27 | - unparam 28 | - unused 29 | - whitespace 30 | - gofumpt 31 | - sloglint 32 | 33 | issues: 34 | exclude-rules: 35 | # Exclude `lll` issues for long lines with `go:generate`. 36 | - linters: 37 | - lll 38 | source: "^//go:generate " 39 | 40 | linters-settings: 41 | unparam: 42 | # Inspect exported functions. 43 | # 44 | # Set to true if no external program/library imports your code. 45 | # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: 46 | # if it's called for subdir of a project it can't find external interfaces. All text editor integrations 47 | # with golangci-lint call it on a directory with the changed file. 48 | # 49 | # Default: false 50 | check-exported: true 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= go 2 | EXECUTABLE := docker-backup-database 3 | GOFILES := $(shell find . -type f -name "*.go") 4 | TAGS ?= 5 | LDFLAGS ?= -X 'main.Version=$(VERSION)' -X 'main.Commit=$(COMMIT)' 6 | 7 | ifneq ($(shell uname), Darwin) 8 | EXTLDFLAGS = -extldflags "-static" $(null) 9 | else 10 | EXTLDFLAGS = 11 | endif 12 | 13 | ifneq ($(DRONE_TAG),) 14 | VERSION ?= $(DRONE_TAG) 15 | else 16 | VERSION ?= $(shell git describe --tags --always || git rev-parse --short HEAD) 17 | endif 18 | COMMIT ?= $(shell git rev-parse --short HEAD) 19 | 20 | build: $(EXECUTABLE) 21 | 22 | $(EXECUTABLE): $(GOFILES) 23 | $(GO) build -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o bin/$@ ./cmd/$(EXECUTABLE) 24 | 25 | install: $(GOFILES) 26 | $(GO) install -v -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' 27 | 28 | test: 29 | @$(GO) test -v -cover -coverprofile coverage.txt ./... && echo "\n==>\033[32m Ok\033[m\n" || exit 1 30 | 31 | build_linux_amd64: 32 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/amd64/$(EXECUTABLE) ./cmd/$(EXECUTABLE) 33 | 34 | build_linux_arm64: 35 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -a -tags '$(TAGS)' -ldflags '$(EXTLDFLAGS)-s -w $(LDFLAGS)' -o release/linux/arm64/$(EXECUTABLE) ./cmd/$(EXECUTABLE) 36 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type ( 4 | // Config provides the system configuration. 5 | Config struct { 6 | Server Server 7 | Database Database 8 | Storage Storage 9 | File File 10 | Webhook Webhook 11 | } 12 | 13 | Webhook struct { 14 | URL string 15 | Insecure bool 16 | } 17 | 18 | // Storage config 19 | Storage struct { 20 | Endpoint string 21 | AccessID string 22 | SecretKey string 23 | SSL bool 24 | Region string 25 | Bucket string 26 | Path string 27 | Driver string 28 | DumpName string 29 | SkipVerify bool 30 | Days int 31 | } 32 | 33 | // Server provides the server configuration. 34 | Server struct { 35 | Addr string 36 | Host string 37 | Proto string 38 | Port string 39 | Pprof bool 40 | Root string 41 | Debug bool 42 | Schedule string 43 | Location string 44 | } 45 | 46 | // Database config 47 | Database struct { 48 | Driver string 49 | Username string 50 | Password string 51 | Name string 52 | Host string 53 | Opts string 54 | UseSQLite3 bool 55 | UseMySQL bool 56 | UseMSSQL bool 57 | UsePostgreSQL bool 58 | } 59 | 60 | // File struct 61 | File struct { 62 | Prefix string 63 | Format string 64 | Suffix string 65 | } 66 | ) 67 | -------------------------------------------------------------------------------- /pkg/dbdump/dbdmp.go: -------------------------------------------------------------------------------- 1 | package dbdump 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/appleboy/docker-backup-database/pkg/config" 8 | "github.com/appleboy/docker-backup-database/pkg/dbdump/mongo" 9 | "github.com/appleboy/docker-backup-database/pkg/dbdump/mysql" 10 | "github.com/appleboy/docker-backup-database/pkg/dbdump/postgres" 11 | ) 12 | 13 | // Backup database interface 14 | type Backup interface { 15 | // Exec backup database 16 | Exec(context.Context) error 17 | } 18 | 19 | // NewEngine return storage interface 20 | func NewEngine(cfg config.Config) Backup { 21 | switch cfg.Database.Driver { 22 | case "postgres": 23 | return postgres.NewEngine( 24 | cfg.Database.Host, 25 | cfg.Database.Username, 26 | cfg.Database.Password, 27 | cfg.Database.Name, 28 | cfg.Storage.DumpName, 29 | cfg.Database.Opts, 30 | ) 31 | case "mysql": 32 | return mysql.NewEngine( 33 | cfg.Database.Host, 34 | cfg.Database.Username, 35 | cfg.Database.Password, 36 | cfg.Database.Name, 37 | cfg.Storage.DumpName, 38 | cfg.Database.Opts, 39 | ) 40 | case "mongo": 41 | return mongo.NewEngine( 42 | cfg.Database.Host, 43 | cfg.Database.Username, 44 | cfg.Database.Password, 45 | cfg.Database.Name, 46 | cfg.Storage.DumpName, 47 | cfg.Database.Opts, 48 | ) 49 | default: 50 | panic(errors.New("unsupported database driver")) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/appleboy/docker-backup-database 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/appleboy/go-storage v1.1.1 7 | github.com/joho/godotenv v1.5.1 8 | github.com/robfig/cron/v3 v3.0.1 9 | github.com/urfave/cli/v2 v2.27.5 10 | ) 11 | 12 | require ( 13 | github.com/VividCortex/ewma v1.2.0 // indirect 14 | github.com/cheggaaa/pb/v3 v3.1.7 // indirect 15 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect 16 | github.com/dustin/go-humanize v1.0.1 // indirect 17 | github.com/fatih/color v1.18.0 // indirect 18 | github.com/go-ini/ini v1.67.0 // indirect 19 | github.com/goccy/go-json v0.10.5 // indirect 20 | github.com/google/uuid v1.6.0 // indirect 21 | github.com/h2non/filetype v1.1.3 // indirect 22 | github.com/klauspost/compress v1.18.0 // indirect 23 | github.com/klauspost/cpuid/v2 v2.2.10 // indirect 24 | github.com/mattn/go-colorable v0.1.14 // indirect 25 | github.com/mattn/go-isatty v0.0.20 // indirect 26 | github.com/mattn/go-runewidth v0.0.16 // indirect 27 | github.com/minio/crc64nvme v1.0.1 // indirect 28 | github.com/minio/md5-simd v1.1.2 // indirect 29 | github.com/minio/minio-go/v7 v7.0.87 // indirect 30 | github.com/rivo/uniseg v0.4.7 // indirect 31 | github.com/rs/xid v1.6.0 // indirect 32 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 33 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 34 | golang.org/x/crypto v0.35.0 // indirect 35 | golang.org/x/net v0.35.0 // indirect 36 | golang.org/x/sys v0.30.0 // indirect 37 | golang.org/x/text v0.22.0 // indirect 38 | ) 39 | -------------------------------------------------------------------------------- /pkg/helper/cmd.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "bytes" 5 | "os/exec" 6 | ) 7 | 8 | // Pipeline strings together the given exec.Cmd commands in a similar fashion 9 | // to the Unix pipeline. Each command's standard output is connected to the 10 | // standard input of the next command, and the output of the final command in 11 | // the pipeline is returned, along with the collected standard error of all 12 | // commands and the first error found (if any). 13 | // 14 | // To provide input to the pipeline, assign an io.Reader to the first's Stdin. 15 | // ref: https://gist.github.com/kylelemons/1525278 16 | func Pipeline(cmds ...*exec.Cmd) (pipeLineOutput, collectedStandardError []byte, pipeLineError error) { 17 | // Require at least one command 18 | if len(cmds) < 1 { 19 | return nil, nil, nil 20 | } 21 | 22 | // Collect the output from the command(s) 23 | var output bytes.Buffer 24 | var stderr bytes.Buffer 25 | 26 | last := len(cmds) - 1 27 | for i, cmd := range cmds[:last] { 28 | var err error 29 | // Connect each command's stdin to the previous command's stdout 30 | if cmds[i+1].Stdin, err = cmd.StdoutPipe(); err != nil { 31 | return nil, nil, err 32 | } 33 | // Connect each command's stderr to a buffer 34 | cmd.Stderr = &stderr 35 | } 36 | 37 | // Connect the output and error for the last command 38 | cmds[last].Stdout, cmds[last].Stderr = &output, &stderr 39 | 40 | // Start each command 41 | for _, cmd := range cmds { 42 | if err := cmd.Start(); err != nil { 43 | return output.Bytes(), stderr.Bytes(), err 44 | } 45 | } 46 | 47 | // Wait for each command to complete 48 | for _, cmd := range cmds { 49 | if err := cmd.Wait(); err != nil { 50 | return output.Bytes(), stderr.Bytes(), err 51 | } 52 | } 53 | 54 | // Return the pipeline output and the collected standard error 55 | return output.Bytes(), stderr.Bytes(), nil 56 | } 57 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Lint and Testing 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | permissions: 8 | actions: read 9 | contents: read 10 | statuses: write 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | lint: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Setup go 24 | uses: actions/setup-go@v5 25 | with: 26 | go-version-file: go.mod 27 | check-latest: true 28 | - name: Setup golangci-lint 29 | uses: golangci/golangci-lint-action@v6 30 | with: 31 | version: latest 32 | args: --verbose 33 | 34 | test: 35 | strategy: 36 | matrix: 37 | os: [ubuntu-latest] 38 | go: [1.23, 1.24] 39 | include: 40 | - os: ubuntu-latest 41 | go-build: ~/.cache/go-build 42 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 43 | runs-on: ${{ matrix.os }} 44 | env: 45 | GO111MODULE: on 46 | GOPROXY: https://proxy.golang.org 47 | steps: 48 | - name: Set up Go ${{ matrix.go }} 49 | uses: actions/setup-go@v5 50 | with: 51 | go-version: ${{ matrix.go }} 52 | 53 | - name: Checkout Code 54 | uses: actions/checkout@v4 55 | with: 56 | ref: ${{ github.ref }} 57 | 58 | - uses: actions/cache@v4 59 | with: 60 | path: | 61 | ${{ matrix.go-build }} 62 | ~/go/pkg/mod 63 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 64 | restore-keys: | 65 | ${{ runner.os }}-go- 66 | - name: Run Tests 67 | run: | 68 | make test 69 | 70 | - name: Upload coverage to Codecov 71 | uses: codecov/codecov-action@v4 72 | with: 73 | flags: ${{ matrix.os }},go-${{ matrix.go }} 74 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [master] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [master] 20 | schedule: 21 | - cron: "41 23 * * 6" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["go"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | - name: Perform CodeQL Analysis 54 | uses: github/codeql-action/analyze@v3 55 | -------------------------------------------------------------------------------- /pkg/dbdump/mongo/mongo.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | // Dump provides dump execution arguments. 12 | type Dump struct { 13 | Host string 14 | Username string 15 | Password string 16 | Name string 17 | Opts string 18 | DumpName string 19 | } 20 | 21 | func getHostPort(h string) (string, string) { 22 | data := strings.Split(h, ":") 23 | host := data[0] 24 | port := "27017" 25 | if len(data) > 1 { 26 | port = data[1] 27 | } 28 | 29 | return host, port 30 | } 31 | 32 | // Exec for dump command 33 | func (d Dump) Exec(ctx context.Context) error { 34 | envs := os.Environ() 35 | 36 | // Print the version number for the command line tools 37 | cmd := exec.CommandContext(ctx, "mongodump", "--version") 38 | cmd.Env = envs 39 | cmd.Stdout = os.Stdout 40 | cmd.Stderr = os.Stderr 41 | trace(cmd) 42 | if err := cmd.Run(); err != nil { 43 | return fmt.Errorf("failed to get mongodump version: %w", err) 44 | } 45 | 46 | flags := []string{} 47 | host, port := getHostPort(d.Host) 48 | if host != "" { 49 | flags = append(flags, "-h", host) 50 | } 51 | if port != "" { 52 | flags = append(flags, "--port", port) 53 | } 54 | 55 | if d.Username != "" { 56 | flags = append(flags, "-u", d.Username) 57 | } 58 | 59 | if d.Password != "" { 60 | envs = append(envs, "MONGO_PWD="+d.Password) 61 | } 62 | 63 | if d.Name != "" { 64 | flags = append(flags, "-d", d.Name) 65 | } 66 | 67 | // Compresses the output. If mongodump outputs to the dump directory, 68 | // the new feature compresses the individual files. The files have the suffix .gz. 69 | flags = append(flags, "--gzip") 70 | flags = append(flags, "--archive="+d.DumpName) 71 | 72 | if d.Opts != "" { 73 | flags = append(flags, d.Opts) 74 | } 75 | 76 | cmd = exec.CommandContext(ctx, "mongodump", flags...) 77 | cmd.Env = envs 78 | cmd.Stdout = os.Stdout 79 | cmd.Stderr = os.Stderr 80 | trace(cmd) 81 | if err := cmd.Start(); err != nil { 82 | return fmt.Errorf("failed to start mongodump: %w", err) 83 | } 84 | 85 | if err := cmd.Wait(); err != nil { 86 | return fmt.Errorf("mongodump failed: %w", err) 87 | } 88 | 89 | return nil 90 | } 91 | 92 | // trace prints the command to the stdout. 93 | func trace(cmd *exec.Cmd) { 94 | fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) 95 | } 96 | 97 | // NewEngine struct 98 | func NewEngine(host, username, password, name, dumpName, opts string) *Dump { 99 | return &Dump{ 100 | Host: host, 101 | Username: username, 102 | Password: password, 103 | Name: name, 104 | Opts: opts, 105 | DumpName: dumpName, 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - "v*" 9 | 10 | jobs: 11 | build-docker: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | docker: 17 | [ 18 | mysql9, 19 | mysql8, 20 | mongo4.4, 21 | postgres17, 22 | postgres16, 23 | postgres15, 24 | postgres14, 25 | postgres13, 26 | postgres12, 27 | postgres11, 28 | postgres10, 29 | postgres9, 30 | ] 31 | name: ${{ matrix.os }} @ build ${{ matrix.docker }} 32 | steps: 33 | - name: Checkout repository 34 | uses: actions/checkout@v4 35 | 36 | - name: Setup go 37 | uses: actions/setup-go@v5 38 | with: 39 | go-version-file: go.mod 40 | check-latest: true 41 | 42 | - name: Build binary 43 | run: | 44 | make build_linux_amd64 45 | make build_linux_arm64 46 | - name: Set up QEMU 47 | uses: docker/setup-qemu-action@v3 48 | 49 | - name: Set up Docker Buildx 50 | uses: docker/setup-buildx-action@v3 51 | 52 | - name: Login to Docker Hub 53 | uses: docker/login-action@v3 54 | with: 55 | username: ${{ secrets.DOCKERHUB_USERNAME }} 56 | password: ${{ secrets.DOCKERHUB_TOKEN }} 57 | 58 | - name: Login to GitHub Container Registry 59 | uses: docker/login-action@v3 60 | with: 61 | registry: ghcr.io 62 | username: ${{ github.repository_owner }} 63 | password: ${{ secrets.GITHUB_TOKEN }} 64 | 65 | - name: Docker meta 66 | id: docker-meta 67 | uses: docker/metadata-action@v5 68 | with: 69 | images: | 70 | ${{ github.repository }} 71 | ghcr.io/${{ github.repository }} 72 | tags: | 73 | type=semver,pattern={{version}}-${{ matrix.docker }} 74 | type=semver,pattern={{major}}.{{minor}}-${{ matrix.docker }} 75 | type=semver,pattern={{major}}-${{ matrix.docker }} 76 | type=raw,value=${{ matrix.docker }},enable={{is_default_branch}} 77 | 78 | - name: downcase REPO 79 | run: | 80 | echo "REPO=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV} 81 | 82 | - name: Build and push 83 | uses: docker/build-push-action@v6 84 | with: 85 | context: . 86 | platforms: linux/amd64,linux/arm64 87 | file: docker/Dockerfile.${{ matrix.docker }} 88 | push: ${{ github.event_name != 'pull_request' }} 89 | tags: ${{ steps.docker-meta.outputs.tags }} 90 | labels: ${{ steps.docker-meta.outputs.labels }} 91 | -------------------------------------------------------------------------------- /pkg/dbdump/mysql/mysql.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | // Dump provides dump execution arguments. 12 | type Dump struct { 13 | Host string 14 | Username string 15 | Password string 16 | Name string 17 | Opts string 18 | DumpName string 19 | } 20 | 21 | func getHostPort(h string) (string, string) { 22 | data := strings.Split(h, ":") 23 | host := data[0] 24 | port := "3306" 25 | if len(data) > 1 { 26 | port = data[1] 27 | } 28 | 29 | return host, port 30 | } 31 | 32 | // Exec for dump command 33 | func (d Dump) Exec(ctx context.Context) error { 34 | envs := os.Environ() 35 | 36 | // Print the version number for the command line tools 37 | cmd := exec.CommandContext(ctx, "mysqldump", "--version") 38 | cmd.Env = envs 39 | cmd.Stdout = os.Stdout 40 | cmd.Stderr = os.Stderr 41 | trace(cmd) 42 | if err := cmd.Run(); err != nil { 43 | return fmt.Errorf("failed to get mysqldump version: %w", err) 44 | } 45 | 46 | flags := []string{} 47 | host, port := getHostPort(d.Host) 48 | if host != "" { 49 | flags = append(flags, "-h", host) 50 | } 51 | if port != "" { 52 | flags = append(flags, "-P", port) 53 | } 54 | 55 | if d.Username != "" { 56 | flags = append(flags, "-u", d.Username) 57 | } 58 | 59 | if d.Opts != "" { 60 | flags = append(flags, d.Opts) 61 | } 62 | 63 | if d.Name != "" { 64 | flags = append(flags, d.Name) 65 | } 66 | 67 | if d.Password != "" { 68 | envs = append(envs, "MYSQL_PWD="+d.Password) 69 | } 70 | 71 | cmd = exec.CommandContext(ctx, "mysqldump", flags...) 72 | cmd.Env = envs 73 | 74 | // Use a pipe to gzip the output 75 | gzipCmd := exec.CommandContext(ctx, "gzip") 76 | gzipCmd.Stdin, _ = cmd.StdoutPipe() 77 | gzipCmd.Stdout = os.Stdout 78 | gzipCmd.Stderr = os.Stderr 79 | 80 | trace(cmd) 81 | if err := cmd.Start(); err != nil { 82 | return fmt.Errorf("failed to start mysqldump: %w", err) 83 | } 84 | 85 | trace(gzipCmd) 86 | if err := gzipCmd.Start(); err != nil { 87 | return fmt.Errorf("failed to start gzip: %w", err) 88 | } 89 | 90 | if err := cmd.Wait(); err != nil { 91 | return fmt.Errorf("mysqldump failed: %w", err) 92 | } 93 | 94 | if err := gzipCmd.Wait(); err != nil { 95 | return fmt.Errorf("gzip failed: %w", err) 96 | } 97 | 98 | return nil 99 | } 100 | 101 | // trace prints the command to the stdout. 102 | func trace(cmd *exec.Cmd) { 103 | fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) 104 | } 105 | 106 | // NewEngine struct 107 | func NewEngine(host, username, password, name, dumpName, opts string) *Dump { 108 | return &Dump{ 109 | Host: host, 110 | Username: username, 111 | Password: password, 112 | Name: name, 113 | Opts: opts, 114 | DumpName: dumpName, 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pkg/dbdump/postgres/postgres.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | // Dump provides dump execution arguments. 12 | type Dump struct { 13 | Host string 14 | Username string 15 | Password string 16 | Name string 17 | Opts string 18 | DumpName string 19 | } 20 | 21 | func getHostPort(h string) (string, string) { 22 | data := strings.Split(h, ":") 23 | host := data[0] 24 | port := "5432" 25 | if len(data) > 1 { 26 | port = data[1] 27 | } 28 | 29 | return host, port 30 | } 31 | 32 | // Exec for dump command 33 | func (d Dump) Exec(ctx context.Context) error { 34 | envs := os.Environ() 35 | 36 | // Print the version number for the command line tools 37 | cmd := exec.CommandContext(ctx, "pg_dump", "--version") 38 | cmd.Env = envs 39 | cmd.Stdout = os.Stdout 40 | cmd.Stderr = os.Stderr 41 | trace(cmd) 42 | if err := cmd.Run(); err != nil { 43 | return fmt.Errorf("failed to get pg_dump version: %w", err) 44 | } 45 | 46 | flags := []string{} 47 | host, port := getHostPort(d.Host) 48 | if host != "" { 49 | flags = append(flags, "-h", host) 50 | } 51 | if port != "" { 52 | flags = append(flags, "-p", port) 53 | } 54 | 55 | if d.Username != "" { 56 | flags = append(flags, "-U", d.Username) 57 | } 58 | 59 | if d.Opts != "" { 60 | flags = append(flags, d.Opts) 61 | } 62 | 63 | if d.Name != "" { 64 | flags = append(flags, d.Name) 65 | } 66 | 67 | if d.Password != "" { 68 | envs = append(envs, "PGPASSWORD="+d.Password) 69 | } 70 | 71 | cmd = exec.CommandContext(ctx, "pg_dump", flags...) 72 | cmd.Env = envs 73 | cmd.Stdout = os.Stdout 74 | 75 | // Use a pipe to gzip the output 76 | gzipCmd := exec.CommandContext(ctx, "gzip") 77 | gzipCmd.Stdin, _ = cmd.StdoutPipe() 78 | gzipCmd.Stdout = os.Stdout 79 | gzipCmd.Stderr = os.Stderr 80 | 81 | trace(cmd) 82 | if err := cmd.Start(); err != nil { 83 | return fmt.Errorf("failed to start pg_dump: %w", err) 84 | } 85 | 86 | trace(gzipCmd) 87 | if err := gzipCmd.Start(); err != nil { 88 | return fmt.Errorf("failed to start gzip: %w", err) 89 | } 90 | 91 | if err := cmd.Wait(); err != nil { 92 | return fmt.Errorf("pg_dump failed: %w", err) 93 | } 94 | 95 | if err := gzipCmd.Wait(); err != nil { 96 | return fmt.Errorf("gzip failed: %w", err) 97 | } 98 | 99 | return nil 100 | } 101 | 102 | // trace prints the command to the stdout. 103 | func trace(cmd *exec.Cmd) { 104 | fmt.Printf("$ %s\n", strings.Join(cmd.Args, " ")) 105 | } 106 | 107 | // NewEngine struct 108 | func NewEngine(host, username, password, name, dumpName, opts string) *Dump { 109 | return &Dump{ 110 | Host: host, 111 | Username: username, 112 | Password: password, 113 | Name: name, 114 | Opts: opts, 115 | DumpName: dumpName, 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - darwin 10 | - linux 11 | - windows 12 | - freebsd 13 | goarch: 14 | - amd64 15 | - arm 16 | - arm64 17 | goarm: 18 | - "5" 19 | - "6" 20 | - "7" 21 | ignore: 22 | - goos: darwin 23 | goarch: arm 24 | - goos: darwin 25 | goarch: ppc64le 26 | - goos: darwin 27 | goarch: s390x 28 | - goos: windows 29 | goarch: ppc64le 30 | - goos: windows 31 | goarch: s390x 32 | - goos: windows 33 | goarch: arm 34 | goarm: "5" 35 | - goos: windows 36 | goarch: arm 37 | goarm: "6" 38 | - goos: windows 39 | goarch: arm 40 | goarm: "7" 41 | - goos: windows 42 | goarch: arm64 43 | - goos: freebsd 44 | goarch: ppc64le 45 | - goos: freebsd 46 | goarch: s390x 47 | - goos: freebsd 48 | goarch: arm 49 | goarm: "5" 50 | - goos: freebsd 51 | goarch: arm 52 | goarm: "6" 53 | - goos: freebsd 54 | goarch: arm 55 | goarm: "7" 56 | - goos: freebsd 57 | goarch: arm64 58 | flags: 59 | - -trimpath 60 | ldflags: 61 | - -s -w 62 | - -X main.Version={{.Version}} 63 | - -X main.Commit={{.ShortCommit}} 64 | binary: >- 65 | {{ .ProjectName }}- 66 | {{- if .IsSnapshot }}{{ .Branch }}- 67 | {{- else }}{{- .Version }}-{{ end }} 68 | {{- .Os }}- 69 | {{- if eq .Arch "amd64" }}amd64 70 | {{- else if eq .Arch "amd64_v1" }}amd64 71 | {{- else if eq .Arch "386" }}386 72 | {{- else }}{{ .Arch }}{{ end }} 73 | {{- if .Arm }}-{{ .Arm }}{{ end }} 74 | no_unique_dist_dir: true 75 | hooks: 76 | post: 77 | - cmd: xz -k -9 {{ .Path }} 78 | dir: ./dist/ 79 | main: ./cmd/docker-backup-database 80 | 81 | archives: 82 | - format: binary 83 | name_template: "{{ .Binary }}" 84 | allow_different_binary_count: true 85 | 86 | checksum: 87 | name_template: "checksums.txt" 88 | extra_files: 89 | - glob: ./**.xz 90 | 91 | snapshot: 92 | name_template: "{{ incpatch .Version }}" 93 | 94 | release: 95 | # You can add extra pre-existing files to the release. 96 | # The filename on the release will be the last part of the path (base). 97 | # If another file with the same name exists, the last one found will be used. 98 | # 99 | # Templates: allowed 100 | extra_files: 101 | - glob: ./**.xz 102 | 103 | changelog: 104 | use: github 105 | groups: 106 | - title: Features 107 | regexp: "^.*feat[(\\w)]*:+.*$" 108 | order: 0 109 | - title: "Bug fixes" 110 | regexp: "^.*fix[(\\w)]*:+.*$" 111 | order: 1 112 | - title: "Enhancements" 113 | regexp: "^.*chore[(\\w)]*:+.*$" 114 | order: 2 115 | - title: "Refactor" 116 | regexp: "^.*refactor[(\\w)]*:+.*$" 117 | order: 3 118 | - title: "Build process updates" 119 | regexp: ^.*?(build|ci)(\(.+\))??!?:.+$ 120 | order: 4 121 | - title: "Documentation updates" 122 | regexp: ^.*?docs?(\(.+\))??!?:.+$ 123 | order: 4 124 | - title: Others 125 | order: 999 126 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | minio: 5 | image: quay.io/minio/minio 6 | restart: always 7 | volumes: 8 | - data1-1:/data1 9 | ports: 10 | - 9000:9000 11 | - 9001:9001 12 | environment: 13 | MINIO_ROOT_USER: minioadmin 14 | MINIO_ROOT_PASSWORD: minioadmin 15 | command: server /data --console-address ":9001" 16 | healthcheck: 17 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] 18 | interval: 30s 19 | timeout: 20s 20 | retries: 3 21 | 22 | postgres: 23 | image: postgres:16 24 | restart: always 25 | volumes: 26 | - pg-data:/var/lib/postgresql/data 27 | logging: 28 | options: 29 | max-size: "100k" 30 | max-file: "3" 31 | environment: 32 | POSTGRES_USER: db 33 | POSTGRES_DB: db 34 | POSTGRES_PASSWORD: db 35 | 36 | mysql: 37 | image: mysql:9 38 | restart: always 39 | volumes: 40 | - mysql-data:/var/lib/mysql 41 | logging: 42 | options: 43 | max-size: "100k" 44 | max-file: "3" 45 | environment: 46 | MYSQL_ROOT_PASSWORD: db 47 | MYSQL_DATABASE: db 48 | MYSQL_USER: db 49 | MYSQL_PASSWORD: db 50 | 51 | mongo: 52 | image: mongodb/mongodb-community-server:4.4.0-ubuntu2004 53 | restart: always 54 | logging: 55 | options: 56 | max-size: "100k" 57 | max-file: "3" 58 | volumes: 59 | - mongodb-data:/data/db 60 | environment: 61 | MONGO_INITDB_ROOT_USERNAME: db 62 | MONGO_INITDB_ROOT_PASSWORD: db 63 | MONGO_INITDB_DATABASE: db 64 | 65 | backup_postgres: 66 | image: ghcr.io/appleboy/docker-backup-database:postgres16 67 | logging: 68 | options: 69 | max-size: "100k" 70 | max-file: "3" 71 | environment: 72 | STORAGE_DRIVER: s3 73 | STORAGE_ENDPOINT: minio:9000 74 | STORAGE_BUCKET: test 75 | STORAGE_REGION: ap-northeast-1 76 | STORAGE_PATH: backup_postgres 77 | STORAGE_SSL: "false" 78 | STORAGE_INSECURE_SKIP_VERIFY: "false" 79 | ACCESS_KEY_ID: minioadmin 80 | SECRET_ACCESS_KEY: minioadmin 81 | 82 | DATABASE_DRIVER: postgres 83 | DATABASE_HOST: postgres:5432 84 | DATABASE_USERNAME: db 85 | DATABASE_PASSWORD: db 86 | DATABASE_NAME: db 87 | DATABASE_OPTS: 88 | 89 | backup_mysql: 90 | image: appleboy/docker-backup-database:mysql9 91 | logging: 92 | options: 93 | max-size: "100k" 94 | max-file: "3" 95 | environment: 96 | STORAGE_DRIVER: s3 97 | STORAGE_ENDPOINT: minio:9000 98 | STORAGE_BUCKET: test 99 | STORAGE_REGION: ap-northeast-1 100 | STORAGE_PATH: backup_mysql 101 | STORAGE_SSL: "false" 102 | STORAGE_INSECURE_SKIP_VERIFY: "false" 103 | ACCESS_KEY_ID: minioadmin 104 | SECRET_ACCESS_KEY: minioadmin 105 | 106 | DATABASE_DRIVER: mysql 107 | DATABASE_HOST: mysql:3306 108 | DATABASE_USERNAME: root 109 | DATABASE_PASSWORD: db 110 | DATABASE_NAME: db 111 | DATABASE_OPTS: 112 | 113 | backup_mysql_schedule: 114 | image: ghcr.io/appleboy/appleboy/docker-backup-database:mysql9 115 | logging: 116 | options: 117 | max-size: "100k" 118 | max-file: "3" 119 | environment: 120 | STORAGE_DRIVER: s3 121 | STORAGE_ENDPOINT: minio:9000 122 | STORAGE_BUCKET: test 123 | STORAGE_REGION: ap-northeast-1 124 | STORAGE_PATH: backup_mysql 125 | STORAGE_SSL: "false" 126 | STORAGE_INSECURE_SKIP_VERIFY: "false" 127 | ACCESS_KEY_ID: minioadmin 128 | SECRET_ACCESS_KEY: minioadmin 129 | 130 | DATABASE_DRIVER: mysql 131 | DATABASE_HOST: mysql:3306 132 | DATABASE_USERNAME: root 133 | DATABASE_PASSWORD: db 134 | DATABASE_NAME: db 135 | DATABASE_OPTS: 136 | 137 | TIME_SCHEDULE: "@daily" 138 | TIME_LOCATION: Asia/Taipei 139 | 140 | ## By default this config uses default local driver, 141 | ## For custom volumes replace with volume driver configuration. 142 | volumes: 143 | data1-1: 144 | pg-data: 145 | mysql-data: 146 | mongodb-data: 147 | -------------------------------------------------------------------------------- /cmd/docker-backup-database/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/appleboy/docker-backup-database/pkg/config" 5 | 6 | "github.com/urfave/cli/v2" 7 | ) 8 | 9 | // settingsFlags has the cli.Flags for the plugin.Settings. 10 | func settingsFlags(cfg *config.Config) []cli.Flag { 11 | return []cli.Flag{ 12 | &cli.StringFlag{ 13 | Name: "database.driver", 14 | Value: "postgres", 15 | Usage: "Database type to back up (postgres or mysql)", 16 | EnvVars: []string{"PLUGIN_DATABASE_DRIVER", "INPUT_DATABASE_DRIVER", "DATABASE_DRIVER"}, 17 | Destination: &cfg.Database.Driver, 18 | }, 19 | &cli.StringFlag{ 20 | Name: "database.username", 21 | Usage: "Username for database authentication", 22 | EnvVars: []string{"PLUGIN_DATABASE_USERNAME", "INPUT_DATABASE_USERNAME", "DATABASE_USERNAME"}, 23 | Destination: &cfg.Database.Username, 24 | }, 25 | &cli.StringFlag{ 26 | Name: "database.password", 27 | Usage: "Password for database authentication", 28 | EnvVars: []string{"PLUGIN_DATABASE_PASSWORD", "INPUT_DATABASE_PASSWORD", "DATABASE_PASSWORD"}, 29 | Destination: &cfg.Database.Password, 30 | }, 31 | &cli.StringFlag{ 32 | Name: "database.name", 33 | Usage: "Name of the database to back up", 34 | Value: "postgres", 35 | EnvVars: []string{"PLUGIN_DATABASE_NAME", "INPUT_DATABASE_NAME", "DATABASE_NAME"}, 36 | Destination: &cfg.Database.Name, 37 | }, 38 | &cli.StringFlag{ 39 | Name: "database.host", 40 | Value: "localhost:5432", 41 | Usage: "Database host address with port (e.g., localhost:5432)", 42 | EnvVars: []string{"PLUGIN_DATABASE_HOST", "INPUT_DATABASE_HOST", "DATABASE_HOST"}, 43 | Destination: &cfg.Database.Host, 44 | }, 45 | &cli.StringFlag{ 46 | Name: "database.opts", 47 | Usage: "Additional database connection options", 48 | EnvVars: []string{"PLUGIN_DATABASE_OPTS", "INPUT_DATABASE_OPTS", "DATABASE_OPTS"}, 49 | Destination: &cfg.Database.Opts, 50 | }, 51 | 52 | // storage 53 | &cli.StringFlag{ 54 | Name: "storage.driver", 55 | Value: "s3", 56 | Usage: "Storage service type (s3 or gcs)", 57 | EnvVars: []string{"PLUGIN_STORAGE_DRIVER", "INPUT_STORAGE_DRIVER", "STORAGE_DRIVER"}, 58 | Destination: &cfg.Storage.Driver, 59 | }, 60 | &cli.StringFlag{ 61 | Name: "storage.access_id", 62 | Usage: "Access key ID for storage authentication", 63 | EnvVars: []string{"PLUGIN_ACCESS_KEY_ID", "INPUT_ACCESS_KEY_ID", "ACCESS_KEY_ID"}, 64 | Destination: &cfg.Storage.AccessID, 65 | }, 66 | &cli.StringFlag{ 67 | Name: "storage.secret_key", 68 | Usage: "Secret access key for storage authentication", 69 | EnvVars: []string{"PLUGIN_SECRET_ACCESS_KEY", "INPUT_SECRET_ACCESS_KEY", "SECRET_ACCESS_KEY"}, 70 | Destination: &cfg.Storage.SecretKey, 71 | }, 72 | &cli.StringFlag{ 73 | Name: "storage.endpoint", 74 | Value: "s3.amazonaws.com", 75 | Usage: "Storage service endpoint URL", 76 | EnvVars: []string{"PLUGIN_STORAGE_ENDPOINT", "INPUT_STORAGE_ENDPOINT", "STORAGE_ENDPOINT"}, 77 | Destination: &cfg.Storage.Endpoint, 78 | }, 79 | &cli.StringFlag{ 80 | Name: "storage.bucket", 81 | Usage: "Bucket name to store backup files", 82 | EnvVars: []string{"PLUGIN_STORAGE_BUCKET", "INPUT_STORAGE_BUCKET", "STORAGE_BUCKET"}, 83 | Destination: &cfg.Storage.Bucket, 84 | }, 85 | &cli.StringFlag{ 86 | Name: "storage.region", 87 | Value: "ap-northeast-1", 88 | Usage: "Region where your storage bucket is located", 89 | EnvVars: []string{"PLUGIN_STORAGE_REGION", "INPUT_STORAGE_REGION", "STORAGE_REGION"}, 90 | Destination: &cfg.Storage.Region, 91 | }, 92 | &cli.StringFlag{ 93 | Name: "storage.path", 94 | Value: "backup", 95 | Usage: "Path/folder within the bucket to store backups", 96 | EnvVars: []string{"PLUGIN_STORAGE_PATH", "INPUT_STORAGE_PATH", "STORAGE_PATH"}, 97 | Destination: &cfg.Storage.Path, 98 | }, 99 | &cli.BoolFlag{ 100 | Name: "storage.ssl", 101 | Usage: "Use SSL for secure storage connection", 102 | EnvVars: []string{"PLUGIN_STORAGE_SSL", "INPUT_STORAGE_SSL", "STORAGE_SSL"}, 103 | Destination: &cfg.Storage.SSL, 104 | }, 105 | &cli.StringFlag{ 106 | Name: "storage.dump_name", 107 | Usage: "Filename for the database dump", 108 | EnvVars: []string{"PLUGIN_STORAGE_DUMP_NAME", "INPUT_STORAGE_DUMP_NAME", "STORAGE_DUMP_NAME"}, 109 | Destination: &cfg.Storage.DumpName, 110 | Value: "dump.sql.gz", 111 | }, 112 | &cli.BoolFlag{ 113 | Name: "storage.insecure_skip_verify", 114 | Usage: "Skip SSL certificate verification (not recommended for production)", 115 | EnvVars: []string{ 116 | "PLUGIN_STORAGE_INSECURE_SKIP_VERIFY", 117 | "INPUT_STORAGE_INSECURE_SKIP_VERIFY", 118 | "STORAGE_INSECURE_SKIP_VERIFY", 119 | }, 120 | Destination: &cfg.Storage.SkipVerify, 121 | }, 122 | &cli.IntFlag{ 123 | Name: "storage.days", 124 | Usage: "Number of days to keep backup files (0 = keep forever)", 125 | EnvVars: []string{"PLUGIN_STORAGE_DAYS", "INPUT_STORAGE_DAYS", "STORAGE_DAYS"}, 126 | Destination: &cfg.Storage.Days, 127 | Value: 0, 128 | }, 129 | 130 | // SCHEDULE 131 | &cli.StringFlag{ 132 | Name: "time.schedule", 133 | Usage: "Backup schedule in cron format (e.g., @daily, @hourly, or '0 0 * * *')", 134 | EnvVars: []string{"PLUGIN_TIME_SCHEDULE", "INPUT_TIME_SCHEDULE", "TIME_SCHEDULE"}, 135 | Destination: &cfg.Server.Schedule, 136 | }, 137 | &cli.StringFlag{ 138 | Name: "time.location", 139 | Usage: "Timezone for scheduling (e.g., Asia/Tokyo, America/New_York)", 140 | EnvVars: []string{"PLUGIN_TIME_LOCATION", "INPUT_TIME_LOCATION", "TIME_LOCATION"}, 141 | Destination: &cfg.Server.Location, 142 | }, 143 | 144 | // File Format 145 | &cli.StringFlag{ 146 | Name: "file.prefix", 147 | Usage: "Text to add before the backup filename", 148 | EnvVars: []string{"PLUGIN_FILE_PREFIX", "INPUT_FILE_PREFIX", "FILE_PREFIX"}, 149 | Destination: &cfg.File.Prefix, 150 | }, 151 | &cli.StringFlag{ 152 | Name: "file.suffix", 153 | Usage: "Text to add after the backup filename", 154 | EnvVars: []string{"PLUGIN_FILE_SUFFIX", "INPUT_FILE_SUFFIX", "FILE_SUFFIX"}, 155 | Destination: &cfg.File.Suffix, 156 | }, 157 | &cli.StringFlag{ 158 | Name: "file.format", 159 | Usage: "Date/time format for backup filenames (Go time format)", 160 | Value: "20060102150405", 161 | EnvVars: []string{"PLUGIN_FILE_FORMAT", "INPUT_FILE_FORMAT", "FILE_FORMAT"}, 162 | Destination: &cfg.File.Format, 163 | }, 164 | &cli.StringFlag{ 165 | Name: "webhook.url", 166 | Usage: "URL to notify when backup completes", 167 | EnvVars: []string{"PLUGIN_WEBHOOK_URL", "INPUT_WEBHOOK_URL", "WEBHOOK_URL"}, 168 | Destination: &cfg.Webhook.URL, 169 | }, 170 | &cli.BoolFlag{ 171 | Name: "webhook.insecure", 172 | Usage: "Allow insecure (HTTP) webhook connections", 173 | EnvVars: []string{"PLUGIN_WEBHOOK_INSECURE", "INPUT_WEBHOOK_INSECURE", "WEBHOOK_INSECURE"}, 174 | Destination: &cfg.Webhook.Insecure, 175 | }, 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /cmd/docker-backup-database/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "errors" 7 | "fmt" 8 | "log/slog" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "os/signal" 13 | "path" 14 | "strconv" 15 | "strings" 16 | "syscall" 17 | "time" 18 | 19 | "github.com/appleboy/docker-backup-database/pkg/config" 20 | "github.com/appleboy/docker-backup-database/pkg/dbdump" 21 | 22 | "github.com/appleboy/go-storage" 23 | "github.com/appleboy/go-storage/core" 24 | "github.com/joho/godotenv" 25 | _ "github.com/joho/godotenv/autoload" 26 | "github.com/robfig/cron/v3" 27 | "github.com/urfave/cli/v2" 28 | ) 29 | 30 | // Version set at compile-time 31 | var ( 32 | Version string 33 | ) 34 | 35 | func main() { 36 | // Load env-file if it exists first 37 | if filename, found := os.LookupEnv("PLUGIN_ENV_FILE"); found { 38 | _ = godotenv.Load(filename) 39 | } 40 | 41 | if _, err := os.Stat("/run/drone/env"); err == nil { 42 | _ = godotenv.Overload("/run/drone/env") 43 | } 44 | 45 | cfg := &config.Config{} 46 | app := &cli.App{ 47 | Name: "docker-backup-datavase", 48 | Usage: "Docker image to periodically backup your database", 49 | Copyright: "Copyright (c) " + strconv.Itoa(time.Now().Year()) + " Bo-Yi Wu", 50 | Version: Version, 51 | Flags: settingsFlags(cfg), 52 | Action: run(cfg), 53 | } 54 | 55 | if err := app.Run(os.Args); err != nil { 56 | slog.Error("can't run app", "err", err.Error()) 57 | } 58 | } 59 | 60 | func run(cfg *config.Config) cli.ActionFunc { 61 | return func(ctx *cli.Context) error { 62 | // initial storage interface 63 | s3, err := storage.NewEngine(storage.Config{ 64 | Endpoint: cfg.Storage.Endpoint, 65 | AccessID: cfg.Storage.AccessID, 66 | SecretKey: cfg.Storage.SecretKey, 67 | SSL: cfg.Storage.SSL, 68 | Region: cfg.Storage.Region, 69 | Path: cfg.Storage.Path, 70 | Bucket: cfg.Storage.Bucket, 71 | Addr: cfg.Server.Addr, 72 | Driver: cfg.Storage.Driver, 73 | }) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | // get context 79 | appCtx := ctx.Context 80 | 81 | // check bucket exist 82 | if exist, err := s3.BucketExists(appCtx, cfg.Storage.Bucket); !exist { 83 | if err != nil { 84 | return errors.New("bucket not exist or you don't have permission: " + err.Error()) 85 | } 86 | 87 | // create new bucket 88 | if err := s3.CreateBucket(appCtx, cfg.Storage.Bucket, cfg.Storage.Region); err != nil { 89 | return errors.New("can't create bucket: " + err.Error()) 90 | } 91 | } 92 | 93 | // Set lifecycle on bucket or an object prefix. 94 | if cfg.Storage.Days > 0 && cfg.Storage.Path != "" { 95 | if err := s3.SetLifeCycle(appCtx, cfg.Storage.Bucket, &core.LifecycleConfig{ 96 | Days: cfg.Storage.Days, 97 | Prefix: cfg.Storage.Path, 98 | }); err != nil { 99 | return errors.New("can't set bucket lifecycle: " + err.Error()) 100 | } 101 | slog.Info("set bucket lifecycle successfully", 102 | "days", cfg.Storage.Days, 103 | "prefix", cfg.Storage.Path, 104 | "bucket", cfg.Storage.Bucket, 105 | ) 106 | } 107 | 108 | if cfg.Server.Schedule == "" { 109 | slog.Info("no schedule found, backup database now") 110 | return backupDB(appCtx, cfg, s3) 111 | } 112 | 113 | // start cron job 114 | c := cron.New() 115 | if cfg.Server.Location != "" { 116 | if loc, err := time.LoadLocation(cfg.Server.Location); err == nil { 117 | c = cron.New(cron.WithLocation(loc)) 118 | } else { 119 | return err 120 | } 121 | } 122 | 123 | // backup task 124 | backupTask := func() { 125 | slog.Info("start backup database now", "schedule", cfg.Server.Schedule) 126 | if err := backupDB(appCtx, cfg, s3); err != nil { 127 | slog.Error("can't backup database", "err", err.Error()) 128 | return 129 | } 130 | slog.Info("backup database successfully") 131 | 132 | // call webhook if configured 133 | if cfg.Webhook.URL != "" { 134 | if err := callWebhook(appCtx, cfg.Webhook.URL, cfg.Webhook.Insecure); err != nil { 135 | slog.Error("can't call webhook", "err", err.Error()) 136 | return 137 | } 138 | slog.Info("call webhook successfully") 139 | } 140 | } 141 | 142 | if _, err := c.AddFunc(cfg.Server.Schedule, backupTask); err != nil { 143 | return fmt.Errorf("crontab schedule error: %w", err) 144 | } 145 | c.Start() 146 | 147 | // Register shutdown signal notifications 148 | sig := make(chan os.Signal, 1) 149 | signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) 150 | <-sig 151 | slog.Info("shutting down backup service") 152 | 153 | // stop cron job 154 | c.Stop() 155 | 156 | return nil 157 | } 158 | } 159 | 160 | func backupDB(ctx context.Context, cfg *config.Config, s3 core.Storage) error { 161 | // Initialize database dump interface 162 | backup := dbdump.NewEngine(*cfg) 163 | if err := backup.Exec(ctx); err != nil { 164 | return err 165 | } 166 | 167 | // Read the dump file 168 | content, err := os.ReadFile(cfg.Storage.DumpName) 169 | if err != nil { 170 | return errors.New("can't open the gzip file: " + err.Error()) 171 | } 172 | 173 | // Construct the filename 174 | filenameParts := []string{} 175 | if cfg.File.Prefix == "" { 176 | cfg.File.Prefix = cfg.Database.Driver 177 | } 178 | filenameParts = append(filenameParts, cfg.File.Prefix) 179 | 180 | timeFormat := time.Now().Format(cfg.File.Format) 181 | if cfg.Server.Location != "" { 182 | loc, _ := time.LoadLocation(cfg.Server.Location) 183 | timeFormat = time.Now().In(loc).Format("20060102150405") 184 | } 185 | filenameParts = append(filenameParts, timeFormat) 186 | 187 | if cfg.File.Suffix != "" { 188 | filenameParts = append(filenameParts, cfg.File.Suffix) 189 | } 190 | 191 | filePath := path.Join(cfg.Storage.Path, strings.Join(filenameParts, "-")+".sql.gz") 192 | 193 | // Upload the file to S3 194 | return s3.UploadFile(ctx, cfg.Storage.Bucket, filePath, content, nil) 195 | } 196 | 197 | // callWebhook sends a POST request to the specified webhook URL. 198 | // It accepts a context for request cancellation, the target URL, and a boolean flag to disable SSL certificate verification. 199 | // 200 | // Parameters: 201 | // - ctx: context.Context - The context to control the request lifetime. 202 | // - target: string - The webhook URL to send the POST request to. 203 | // - insecure: bool - If true, disables SSL certificate verification. 204 | // 205 | // Returns: 206 | // - error: An error if the request fails or the response status code is not 200 OK. 207 | func callWebhook(ctx context.Context, target string, insecure bool) error { 208 | parsedURL, err := url.Parse(target) 209 | if err != nil { 210 | return fmt.Errorf("invalid webhook URL: %w", err) 211 | } 212 | 213 | req, err := http.NewRequestWithContext(ctx, http.MethodPost, parsedURL.String(), nil) 214 | if err != nil { 215 | return fmt.Errorf("failed to create request: %w", err) 216 | } 217 | 218 | // Set a useful User-Agent header 219 | req.Header.Set("User-Agent", "Docker-Backup-Database/"+Version) 220 | 221 | transport := http.DefaultTransport.(*http.Transport).Clone() 222 | if insecure { 223 | transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // #nosec 224 | } 225 | 226 | client := &http.Client{ 227 | Timeout: 10 * time.Second, 228 | Transport: transport, 229 | } 230 | 231 | resp, err := client.Do(req) 232 | if err != nil { 233 | return fmt.Errorf("webhook request failed: %w", err) 234 | } 235 | defer resp.Body.Close() 236 | 237 | if resp.StatusCode < 200 || resp.StatusCode >= 300 { 238 | return fmt.Errorf("webhook returned non-success status code: %d", resp.StatusCode) 239 | } 240 | 241 | return nil 242 | } 243 | -------------------------------------------------------------------------------- /README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # docker-backup-database 2 | 3 | [![GoDoc](https://godoc.org/github.com/appleboy/docker-backup-database?status.svg)](https://godoc.org/github.com/appleboy/docker-backup-database) 4 | [![codecov](https://codecov.io/gh/appleboy/docker-backup-database/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/docker-backup-database) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/docker-backup-database)](https://goreportcard.com/report/github.com/appleboy/docker-backup-database) 6 | [![Docker Image](https://github.com/appleboy/docker-backup-database/actions/workflows/docker.yml/badge.svg)](https://github.com/appleboy/docker-backup-database/actions/workflows/docker.yml) 7 | 8 | [English](README.md) | [繁體中文](README_zh-TW.md) | 简体中文 9 | 10 | Docker 镜像定期备份您的数据库(MySQL、Postgres 或 MongoDB)到本地磁盘或 S3([AWS S3](https://aws.amazon.com/free/storage/s3) 或 [Minio](https://min.io/))。 11 | 12 | [中文 Youtube 影片](https://www.youtube.com/watch?v=nsiKKSy5fUA) 13 | 14 | ## 支持的数据库 15 | 16 | 请参阅 [docker hub 页面](https://hub.docker.com/repository/docker/appleboy/docker-backup-database)。 17 | 18 | - Postgres (9, 10, 11, 12, 13, 14, 15, 16, 17) 19 | - 9: appleboy/docker-backup-database:postgres9 20 | - 10: appleboy/docker-backup-database:postgres10 21 | - 11: appleboy/docker-backup-database:postgres11 22 | - 12: appleboy/docker-backup-database:postgres12 23 | - 13: appleboy/docker-backup-database:postgres13 24 | - 14: appleboy/docker-backup-database:postgres14 25 | - 15: appleboy/docker-backup-database:postgres15 26 | - 16: appleboy/docker-backup-database:postgres16 27 | - 17: appleboy/docker-backup-database:postgres17 28 | - MySQL (8, 9) 29 | - 8: appleboy/docker-backup-database:mysql8 30 | - 9: appleboy/docker-backup-database:mysql9 31 | - Mongo (4.4) 32 | - 4.4: appleboy/docker-backup-database:mongo4.4 33 | 34 | ## Docker 镜像 35 | 36 | 您可以从 Docker Hub 注册表中拉取该项目的最新镜像。 37 | 38 | ```sh 39 | docker pull appleboy/docker-backup-database:postgres12 40 | ``` 41 | 42 | 或者,您可以从 GitHub 容器注册表中拉取该项目的最新镜像。 43 | 44 | ```sh 45 | docker pull ghcr.io/appleboy/docker-backup-database:postgres12 46 | ``` 47 | 48 | ## 使用方法 49 | 50 | 第一步:使用 docker-compose 命令设置 Minio 和 Postgres 12 服务器。 51 | 52 | ```yaml 53 | services: 54 | minio: 55 | image: quay.io/minio/minio 56 | restart: always 57 | volumes: 58 | - data1-1:/data1 59 | ports: 60 | - 9000:9000 61 | - 9001:9001 62 | environment: 63 | MINIO_ROOT_USER: minioadmin 64 | MINIO_ROOT_PASSWORD: minioadmin 65 | command: server /data --console-address ":9001" 66 | healthcheck: 67 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] 68 | interval: 30s 69 | timeout: 20s 70 | retries: 3 71 | 72 | postgres: 73 | image: postgres:12 74 | restart: always 75 | volumes: 76 | - pg-data:/var/lib/postgresql/data 77 | logging: 78 | options: 79 | max-size: "100k" 80 | max-file: "3" 81 | environment: 82 | POSTGRES_USER: db 83 | POSTGRES_DB: db 84 | POSTGRES_PASSWORD: db 85 | ``` 86 | 87 | 第二步:备份您的数据库并将转储文件上传到 S3 存储。 88 | 89 | ```yaml 90 | backup_postgres: 91 | image: appleboy/docker-backup-database:postgres12 92 | logging: 93 | options: 94 | max-size: "100k" 95 | max-file: "3" 96 | environment: 97 | STORAGE_DRIVER: s3 98 | STORAGE_ENDPOINT: minio:9000 99 | STORAGE_BUCKET: test 100 | STORAGE_REGION: ap-northeast-1 101 | STORAGE_PATH: backup_postgres 102 | STORAGE_SSL: "false" 103 | STORAGE_INSECURE_SKIP_VERIFY: "false" 104 | ACCESS_KEY_ID: minioadmin 105 | SECRET_ACCESS_KEY: minioadmin 106 | 107 | DATABASE_DRIVER: postgres 108 | DATABASE_HOST: postgres:5432 109 | DATABASE_USERNAME: db 110 | DATABASE_PASSWORD: db 111 | DATABASE_NAME: db 112 | DATABASE_OPTS: 113 | ``` 114 | 115 | 默认的生命周期策略是禁用的。您可以通过设置 `STORAGE_DAYS` 环境变量来启用它。您可以更改 `STORAGE_DAYS` 环境变量以保持备份文件的天数。您还可以更改 `STORAGE_PATH` 环境变量以将备份文件保存在不同的目录中。 116 | 117 | ```yaml 118 | STORAGE_DAYS: 30 119 | STORAGE_PATH: backup_postgres 120 | ``` 121 | 122 | 使用 Cron 调度定期备份。请参阅 `TIME_SCHEDULE` 和 `TIME_LOCATION` 123 | 124 | ```yaml 125 | backup_mysql: 126 | image: appleboy/docker-backup-database:mysql8 127 | logging: 128 | options: 129 | max-size: "100k" 130 | max-file: "3" 131 | environment: 132 | STORAGE_DRIVER: s3 133 | STORAGE_ENDPOINT: minio:9000 134 | STORAGE_BUCKET: test 135 | STORAGE_REGION: ap-northeast-1 136 | STORAGE_PATH: backup_mysql 137 | STORAGE_SSL: "false" 138 | STORAGE_INSECURE_SKIP_VERIFY: "false" 139 | ACCESS_KEY_ID: 1234567890 140 | SECRET_ACCESS_KEY: 1234567890 141 | 142 | DATABASE_DRIVER: mysql 143 | DATABASE_HOST: mysql:3306 144 | DATABASE_USERNAME: root 145 | DATABASE_PASSWORD: db 146 | DATABASE_NAME: db 147 | DATABASE_OPTS: 148 | 149 | TIME_SCHEDULE: "@daily" 150 | TIME_LOCATION: Asia/Taipei 151 | ``` 152 | 153 | crontab 文件的每一行代表一个作业,如下所示: 154 | 155 | ```sh 156 | # ┌───────────── 分钟 (0 - 59) 157 | # │ ┌───────────── 小时 (0 - 23) 158 | # │ │ ┌───────────── 月中的某天 (1 - 31) 159 | # │ │ │ ┌───────────── 月 (1 - 12) 160 | # │ │ │ │ ┌───────────── 星期几 (0 - 6) (星期天到星期六; 161 | # │ │ │ │ │ 在某些系统中,7 也是星期天) 162 | # │ │ │ │ │ 163 | # │ │ │ │ │ 164 | # * * * * * <要执行的命令> 165 | ``` 166 | 167 | cron 表达式表示一组时间,使用 5 个空格分隔的字段。 168 | 169 | | 字段名称 | 必需的? | 允许的值 | 允许的特殊字符 | 170 | | ---------- | -------- | --------------- | -------------- | 171 | | 分钟 | 是 | 0-59 | \* / , - | 172 | | 小时 | 是 | 0-23 | \* / , - | 173 | | 月中的某天 | 是 | 1-31 | \* / , - ? | 174 | | 月 | 是 | 1-12 或 JAN-DEC | \* / , - | 175 | | 星期几 | 是 | 0-6 或 SUN-SAT | \* / , - ? | 176 | 177 | 您可以使用几个预定义的调度之一来代替 cron 表达式。 178 | 179 | ```sh 180 | | 条目 | 描述 | 等效于 | 181 | | ---------------------- | ------------------------------------------ | ------------- | 182 | | @yearly (或 @annually) | 每年运行一次,午夜,1 月 1 日 | 0 0 1 1 * | 183 | | @monthly | 每月运行一次,午夜,月初 | 0 0 1 * * | 184 | | @weekly | 每周运行一次,星期六/星期天之间的午夜 | 0 0 * * 0 | 185 | | @daily (或 @midnight) | 每天运行一次,午夜 | 0 0 * * * | 186 | | @hourly | 每小时运行一次,整点 | 0 * * * * | 187 | ``` 188 | 189 | ### 设置 Webhook 通知 190 | 191 | 您可以设置 webhook 通知,将备份状态发送到 slack 频道。 192 | 193 | ```diff 194 | backup_mysql: 195 | image: appleboy/docker-backup-database:mysql8 196 | logging: 197 | options: 198 | max-size: "100k" 199 | max-file: "3" 200 | environment: 201 | STORAGE_DRIVER: s3 202 | STORAGE_ENDPOINT: minio:9000 203 | STORAGE_BUCKET: test 204 | STORAGE_REGION: ap-northeast-1 205 | STORAGE_PATH: backup_mysql 206 | STORAGE_SSL: "false" 207 | STORAGE_INSECURE_SKIP_VERIFY: "false" 208 | ACCESS_KEY_ID: 1234567890 209 | SECRET_ACCESS_KEY: 1234567890 210 | 211 | DATABASE_DRIVER: mysql 212 | DATABASE_HOST: mysql:3306 213 | DATABASE_USERNAME: root 214 | DATABASE_PASSWORD: db 215 | DATABASE_NAME: db 216 | DATABASE_OPTS: 217 | 218 | TIME_SCHEDULE: "@daily" 219 | TIME_LOCATION: Asia/Taipei 220 | 221 | + WEBHOOK_URL: https://example.com/webhook 222 | + WEBHOOK_INSECURE: "false" 223 | ``` 224 | 225 | ## 环境变量 226 | 227 | ### 数据库部分 228 | 229 | - DATABASE_DRIVER - 支持 `postgres`、`mysql` 或 `mongo`。默认是 `postgres` 230 | - DATABASE_USERNAME - 数据库用户名 231 | - DATABASE_PASSWORD - 数据库密码 232 | - DATABASE_NAME - 数据库名称 233 | - DATABASE_HOST - 数据库主机 234 | - DATABASE_OPTS - 请参阅 `pg_dump`、`mysqldump` 或 `mongodump` 命令 235 | 236 | ### 存储部分 237 | 238 | - STORAGE_DRIVER - 支持 `s3` 或 `disk`。默认是 `s3` 239 | - ACCESS_KEY_ID - Minio 或 AWS S3 访问密钥 ID 240 | - SECRET_ACCESS_KEY - Minio 或 AWS S3 秘密访问密钥 241 | - STORAGE_ENDPOINT - S3 端点。默认是 `s3.amazonaws.com` 242 | - STORAGE_BUCKET - S3 存储桶名称 243 | - STORAGE_REGION - S3 区域。默认是 `ap-northeast-1` 244 | - STORAGE_PATH - 存储桶中的备份文件夹路径。默认是 `backup`,所有转储文件将保存在 `bucket_name/backup` 目录中 245 | - STORAGE_SSL - 默认是 `false` 246 | - STORAGE_INSECURE_SKIP_VERIFY - 默认是 `false` 247 | - STORAGE_DAYS - 保留备份文件的天数。默认是 `7` 248 | 249 | ### 调度部分 250 | 251 | - TIME_SCHEDULE - 您可以使用几个预定义的调度之一来代替 cron 表达式。 252 | - TIME_LOCATION - 默认情况下,所有解释和调度都在机器的本地时区进行。您可以在构建时指定不同的时区。 253 | 254 | ## 文件部分 255 | 256 | - FILE_PREFIX - 文件的前缀名称,默认是 `存储驱动` 名称。 257 | - FILE_SUFFIX - 文件的后缀名称 258 | - FILE_FORMAT - 文件的格式字符串,默认是 `20060102150405`。 259 | 260 | ## Webhook 部分 261 | 262 | - WEBHOOK_URL - Webhook URL 263 | - WEBHOOK_INSECURE - 默认是 `false` 264 | -------------------------------------------------------------------------------- /README_zh-TW.md: -------------------------------------------------------------------------------- 1 | # docker-backup-database 2 | 3 | [![GoDoc](https://godoc.org/github.com/appleboy/docker-backup-database?status.svg)](https://godoc.org/github.com/appleboy/docker-backup-database) 4 | [![codecov](https://codecov.io/gh/appleboy/docker-backup-database/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/docker-backup-database) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/docker-backup-database)](https://goreportcard.com/report/github.com/appleboy/docker-backup-database) 6 | [![Docker Image](https://github.com/appleboy/docker-backup-database/actions/workflows/docker.yml/badge.svg)](https://github.com/appleboy/docker-backup-database/actions/workflows/docker.yml) 7 | 8 | [English](README.md) | 繁體中文 | [简体中文](README_zh-CN.md) 9 | 10 | Docker 映像檔,用於定期備份您的資料庫(MySQL、Postgres 或 MongoDB)到本地磁碟或 S3([AWS S3](https://aws.amazon.com/free/storage/s3) 或 [Minio](https://min.io/))。 11 | 12 | [中文 Youtube 影片](https://www.youtube.com/watch?v=nsiKKSy5fUA) 13 | 14 | ## 支援的資料庫 15 | 16 | 請參閱 [docker hub 頁面](https://hub.docker.com/repository/docker/appleboy/docker-backup-database)。 17 | 18 | - Postgres (9, 10, 11, 12, 13, 14, 15, 16, 17) 19 | - 9: appleboy/docker-backup-database:postgres9 20 | - 10: appleboy/docker-backup-database:postgres10 21 | - 11: appleboy/docker-backup-database:postgres11 22 | - 12: appleboy/docker-backup-database:postgres12 23 | - 13: appleboy/docker-backup-database:postgres13 24 | - 14: appleboy/docker-backup-database:postgres14 25 | - 15: appleboy/docker-backup-database:postgres15 26 | - 16: appleboy/docker-backup-database:postgres16 27 | - 17: appleboy/docker-backup-database:postgres17 28 | - MySQL (8, 9) 29 | - 8: appleboy/docker-backup-database:mysql8 30 | - 9: appleboy/docker-backup-database:mysql9 31 | - Mongo (4.4) 32 | - 4.4: appleboy/docker-backup-database:mongo4.4 33 | 34 | ## Docker 映像檔 35 | 36 | 您可以從 Docker Hub Registry 拉取此專案的最新映像檔。 37 | 38 | ```sh 39 | docker pull appleboy/docker-backup-database:postgres12 40 | ``` 41 | 42 | 或者,您可以從 GitHub Container Registry 拉取此專案的最新映像檔。 43 | 44 | ```sh 45 | docker pull ghcr.io/appleboy/docker-backup-database:postgres12 46 | ``` 47 | 48 | ## 使用方法 49 | 50 | 第一步:使用 docker-compose 指令設置 Minio 和 Postgres 12 伺服器。 51 | 52 | ```yaml 53 | services: 54 | minio: 55 | image: quay.io/minio/minio 56 | restart: always 57 | volumes: 58 | - data1-1:/data1 59 | ports: 60 | - 9000:9000 61 | - 9001:9001 62 | environment: 63 | MINIO_ROOT_USER: minioadmin 64 | MINIO_ROOT_PASSWORD: minioadmin 65 | command: server /data --console-address ":9001" 66 | healthcheck: 67 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] 68 | interval: 30s 69 | timeout: 20s 70 | retries: 3 71 | 72 | postgres: 73 | image: postgres:12 74 | restart: always 75 | volumes: 76 | - pg-data:/var/lib/postgresql/data 77 | logging: 78 | options: 79 | max-size: "100k" 80 | max-file: "3" 81 | environment: 82 | POSTGRES_USER: db 83 | POSTGRES_DB: db 84 | POSTGRES_PASSWORD: db 85 | ``` 86 | 87 | 第二步:備份您的資料庫並將轉儲檔案上傳到 S3 儲存。 88 | 89 | ```yaml 90 | backup_postgres: 91 | image: appleboy/docker-backup-database:postgres12 92 | logging: 93 | options: 94 | max-size: "100k" 95 | max-file: "3" 96 | environment: 97 | STORAGE_DRIVER: s3 98 | STORAGE_ENDPOINT: minio:9000 99 | STORAGE_BUCKET: test 100 | STORAGE_REGION: ap-northeast-1 101 | STORAGE_PATH: backup_postgres 102 | STORAGE_SSL: "false" 103 | STORAGE_INSECURE_SKIP_VERIFY: "false" 104 | ACCESS_KEY_ID: minioadmin 105 | SECRET_ACCESS_KEY: minioadmin 106 | 107 | DATABASE_DRIVER: postgres 108 | DATABASE_HOST: postgres:5432 109 | DATABASE_USERNAME: db 110 | DATABASE_PASSWORD: db 111 | DATABASE_NAME: db 112 | DATABASE_OPTS: 113 | ``` 114 | 115 | 預設的生命週期策略是禁用的。您可以通過設置 `STORAGE_DAYS` 環境變數來啟用它。您可以更改 `STORAGE_DAYS` 環境變數來保留備份檔案的天數。您也可以更改 `STORAGE_PATH` 環境變數來將備份檔案保存到不同的目錄。 116 | 117 | ```yaml 118 | STORAGE_DAYS: 30 119 | STORAGE_PATH: backup_postgres 120 | ``` 121 | 122 | 使用 Cron 排程來定期備份。請參閱 `TIME_SCHEDULE` 和 `TIME_LOCATION` 123 | 124 | ```yaml 125 | backup_mysql: 126 | image: appleboy/docker-backup-database:mysql8 127 | logging: 128 | options: 129 | max-size: "100k" 130 | max-file: "3" 131 | environment: 132 | STORAGE_DRIVER: s3 133 | STORAGE_ENDPOINT: minio:9000 134 | STORAGE_BUCKET: test 135 | STORAGE_REGION: ap-northeast-1 136 | STORAGE_PATH: backup_mysql 137 | STORAGE_SSL: "false" 138 | STORAGE_INSECURE_SKIP_VERIFY: "false" 139 | ACCESS_KEY_ID: 1234567890 140 | SECRET_ACCESS_KEY: 1234567890 141 | 142 | DATABASE_DRIVER: mysql 143 | DATABASE_HOST: mysql:3306 144 | DATABASE_USERNAME: root 145 | DATABASE_PASSWORD: db 146 | DATABASE_NAME: db 147 | DATABASE_OPTS: 148 | 149 | TIME_SCHEDULE: "@daily" 150 | TIME_LOCATION: Asia/Taipei 151 | ``` 152 | 153 | 每行 crontab 檔案代表一個工作,格式如下: 154 | 155 | ```sh 156 | # ┌───────────── 分鐘 (0 - 59) 157 | # │ ┌───────────── 小時 (0 - 23) 158 | # │ │ ┌───────────── 每月的第幾天 (1 - 31) 159 | # │ │ │ ┌───────────── 月份 (1 - 12) 160 | # │ │ │ │ ┌───────────── 每週的第幾天 (0 - 6) (星期日到星期六; 161 | # │ │ │ │ │ 7 在某些系統中也是星期日) 162 | # │ │ │ │ │ 163 | # │ │ │ │ │ 164 | # * * * * * <要執行的命令> 165 | ``` 166 | 167 | Cron 表達式表示一組時間,使用 5 個空格分隔的欄位。 168 | 169 | | 欄位名稱 | 必填? | 允許的值 | 允許的特殊字符 | 170 | | ------------ | ------ | --------------- | -------------- | 171 | | 分鐘 | 是 | 0-59 | \* / , - | 172 | | 小時 | 是 | 0-23 | \* / , - | 173 | | 每月的第幾天 | 是 | 1-31 | \* / , - ? | 174 | | 月份 | 是 | 1-12 或 JAN-DEC | \* / , - | 175 | | 每週的第幾天 | 是 | 0-6 或 SUN-SAT | \* / , - ? | 176 | 177 | 您可以使用幾個預定義的排程之一來代替 Cron 表達式。 178 | 179 | ```sh 180 | | 項目 | 描述 | 等同於 | 181 | | ---------------------- | ------------------------------------------ | ------------- | 182 | | @yearly (或 @annually) | 每年運行一次,午夜,1 月 1 日 | 0 0 1 1 * | 183 | | @monthly | 每月運行一次,午夜,每月的第一天 | 0 0 1 * * | 184 | | @weekly | 每週運行一次,午夜,週六/週日之間 | 0 0 * * 0 | 185 | | @daily (或 @midnight) | 每天運行一次,午夜 | 0 0 * * * | 186 | | @hourly | 每小時運行一次,整點 | 0 * * * * | 187 | ``` 188 | 189 | ### 設置 Webhook 通知 190 | 191 | 您可以設置 Webhook 通知將備份狀態發送到 Slack 頻道。 192 | 193 | ```diff 194 | backup_mysql: 195 | image: appleboy/docker-backup-database:mysql8 196 | logging: 197 | options: 198 | max-size: "100k" 199 | max-file: "3" 200 | environment: 201 | STORAGE_DRIVER: s3 202 | STORAGE_ENDPOINT: minio:9000 203 | STORAGE_BUCKET: test 204 | STORAGE_REGION: ap-northeast-1 205 | STORAGE_PATH: backup_mysql 206 | STORAGE_SSL: "false" 207 | STORAGE_INSECURE_SKIP_VERIFY: "false" 208 | ACCESS_KEY_ID: 1234567890 209 | SECRET_ACCESS_KEY: 1234567890 210 | 211 | DATABASE_DRIVER: mysql 212 | DATABASE_HOST: mysql:3306 213 | DATABASE_USERNAME: root 214 | DATABASE_PASSWORD: db 215 | DATABASE_NAME: db 216 | DATABASE_OPTS: 217 | 218 | TIME_SCHEDULE: "@daily" 219 | TIME_LOCATION: Asia/Taipei 220 | 221 | + WEBHOOK_URL: https://example.com/webhook 222 | + WEBHOOK_INSECURE: "false" 223 | ``` 224 | 225 | ## 環境變數 226 | 227 | ### 資料庫部分 228 | 229 | - DATABASE_DRIVER - 支援 `postgres`、`mysql` 或 `mongo`。預設為 `postgres` 230 | - DATABASE_USERNAME - 資料庫使用者名稱 231 | - DATABASE_PASSWORD - 資料庫密碼 232 | - DATABASE_NAME - 資料庫名稱 233 | - DATABASE_HOST - 資料庫主機 234 | - DATABASE_OPTS - 請參閱 `pg_dump`、`mysqldump` 或 `mongodump` 命令 235 | 236 | ### 儲存部分 237 | 238 | - STORAGE_DRIVER - 支援 `s3` 或 `disk`。預設為 `s3` 239 | - ACCESS_KEY_ID - Minio 或 AWS S3 ACCESS Key ID 240 | - SECRET_ACCESS_KEY - Minio 或 AWS S3 SECRET ACCESS Key 241 | - STORAGE_ENDPOINT - S3 端點。預設為 `s3.amazonaws.com` 242 | - STORAGE_BUCKET - S3 桶名稱 243 | - STORAGE_REGION - S3 區域。預設為 `ap-northeast-1` 244 | - STORAGE_PATH - 桶中的備份資料夾路徑。預設為 `backup`,所有轉儲檔案將保存在 `bucket_name/backup` 目錄中 245 | - STORAGE_SSL - 預設為 `false` 246 | - STORAGE_INSECURE_SKIP_VERIFY - 預設為 `false` 247 | - STORAGE_DAYS - 保留備份檔案的天數。預設為 `7` 248 | 249 | ### 排程部分 250 | 251 | - TIME_SCHEDULE - 您可以使用幾個預定義的排程之一來代替 Cron 表達式。 252 | - TIME_LOCATION - 預設情況下,所有解釋和排程都在機器的本地時區進行。您可以在構建時指定不同的時區。 253 | 254 | ## 檔案部分 255 | 256 | - FILE_PREFIX - 檔案的前綴名稱,預設為 `storage driver` 名稱。 257 | - FILE_SUFFIX - 檔案的後綴名稱 258 | - FILE_FORMAT - 檔案的格式字串,預設為 `20060102150405`。 259 | 260 | ## Webhook 部分 261 | 262 | - WEBHOOK_URL - Webhook URL 263 | - WEBHOOK_INSECURE - 預設為 `false` 264 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-backup-database 2 | 3 | [![GoDoc](https://godoc.org/github.com/appleboy/docker-backup-database?status.svg)](https://godoc.org/github.com/appleboy/docker-backup-database) 4 | [![codecov](https://codecov.io/gh/appleboy/docker-backup-database/branch/master/graph/badge.svg)](https://codecov.io/gh/appleboy/docker-backup-database) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/appleboy/docker-backup-database)](https://goreportcard.com/report/github.com/appleboy/docker-backup-database) 6 | [![Docker Image](https://github.com/appleboy/docker-backup-database/actions/workflows/docker.yml/badge.svg)](https://github.com/appleboy/docker-backup-database/actions/workflows/docker.yml) 7 | 8 | English | [繁體中文](README_zh-TW.md) | [簡體中文](README_zh-CN.md) 9 | 10 | Docker image to periodically backup a your database (MySQL, Postgres or MongoDB) to Local Disk or S3 ([AWS S3](https://aws.amazon.com/free/storage/s3) or [Minio](https://min.io/)). 11 | 12 | [中文 Youtube 影片](https://www.youtube.com/watch?v=nsiKKSy5fUA) 13 | 14 | ## Support Database 15 | 16 | see the [docker hub page](https://hub.docker.com/repository/docker/appleboy/docker-backup-database). 17 | 18 | - Postgres (9, 10, 11, 12, 13, 14, 15, 16, 17) 19 | - 9: appleboy/docker-backup-database:postgres9 20 | - 10: appleboy/docker-backup-database:postgres10 21 | - 11: appleboy/docker-backup-database:postgres11 22 | - 12: appleboy/docker-backup-database:postgres12 23 | - 13: appleboy/docker-backup-database:postgres13 24 | - 14: appleboy/docker-backup-database:postgres14 25 | - 15: appleboy/docker-backup-database:postgres15 26 | - 16: appleboy/docker-backup-database:postgres16 27 | - 17: appleboy/docker-backup-database:postgres17 28 | - MySQL (8, 9) 29 | - 8: appleboy/docker-backup-database:mysql8 30 | - 9: appleboy/docker-backup-database:mysql9 31 | - Mongo (4.4) 32 | - 4.4: appleboy/docker-backup-database:mongo4.4 33 | 34 | ## Docker Image 35 | 36 | You can pull the latest image of the project from the Docker Hub Registry. 37 | 38 | ```sh 39 | docker pull appleboy/docker-backup-database:postgres12 40 | ``` 41 | 42 | Or you can pull the latest image of the project from the GitHub Container Registry. 43 | 44 | ```sh 45 | docker pull ghcr.io/appleboy/docker-backup-database:postgres12 46 | ``` 47 | 48 | ## Usage 49 | 50 | First steps: Setup the Minio and Postgres 12 Server using docker-compose command. 51 | 52 | ```yaml 53 | services: 54 | minio: 55 | image: quay.io/minio/minio 56 | restart: always 57 | volumes: 58 | - data1-1:/data1 59 | ports: 60 | - 9000:9000 61 | - 9001:9001 62 | environment: 63 | MINIO_ROOT_USER: minioadmin 64 | MINIO_ROOT_PASSWORD: minioadmin 65 | command: server /data --console-address ":9001" 66 | healthcheck: 67 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] 68 | interval: 30s 69 | timeout: 20s 70 | retries: 3 71 | 72 | postgres: 73 | image: postgres:12 74 | restart: always 75 | volumes: 76 | - pg-data:/var/lib/postgresql/data 77 | logging: 78 | options: 79 | max-size: "100k" 80 | max-file: "3" 81 | environment: 82 | POSTGRES_USER: db 83 | POSTGRES_DB: db 84 | POSTGRES_PASSWORD: db 85 | ``` 86 | 87 | Second Steps: Backup your database and upload the dump file to S3 storage. 88 | 89 | ```yaml 90 | backup_postgres: 91 | image: appleboy/docker-backup-database:postgres12 92 | logging: 93 | options: 94 | max-size: "100k" 95 | max-file: "3" 96 | environment: 97 | STORAGE_DRIVER: s3 98 | STORAGE_ENDPOINT: minio:9000 99 | STORAGE_BUCKET: test 100 | STORAGE_REGION: ap-northeast-1 101 | STORAGE_PATH: backup_postgres 102 | STORAGE_SSL: "false" 103 | STORAGE_INSECURE_SKIP_VERIFY: "false" 104 | ACCESS_KEY_ID: minioadmin 105 | SECRET_ACCESS_KEY: minioadmin 106 | 107 | DATABASE_DRIVER: postgres 108 | DATABASE_HOST: postgres:5432 109 | DATABASE_USERNAME: db 110 | DATABASE_PASSWORD: db 111 | DATABASE_NAME: db 112 | DATABASE_OPTS: 113 | ``` 114 | 115 | The default lifecycle policy is disabled. You can enable it by setting the `STORAGE_DAYS` environment variable. You can change the `STORAGE_DAYS` environment variable to keep the backup files for a different number of days. You also can change the `STORAGE_PATH` environment variable to save the backup files in a different directory. 116 | 117 | ```yaml 118 | STORAGE_DAYS: 30 119 | STORAGE_PATH: backup_postgres 120 | ``` 121 | 122 | Cron schedule to run periodic backups. See the `TIME_SCHEDULE` and `TIME_LOCATION` 123 | 124 | ```yaml 125 | backup_mysql: 126 | image: appleboy/docker-backup-database:mysql8 127 | logging: 128 | options: 129 | max-size: "100k" 130 | max-file: "3" 131 | environment: 132 | STORAGE_DRIVER: s3 133 | STORAGE_ENDPOINT: minio:9000 134 | STORAGE_BUCKET: test 135 | STORAGE_REGION: ap-northeast-1 136 | STORAGE_PATH: backup_mysql 137 | STORAGE_SSL: "false" 138 | STORAGE_INSECURE_SKIP_VERIFY: "false" 139 | ACCESS_KEY_ID: 1234567890 140 | SECRET_ACCESS_KEY: 1234567890 141 | 142 | DATABASE_DRIVER: mysql 143 | DATABASE_HOST: mysql:3306 144 | DATABASE_USERNAME: root 145 | DATABASE_PASSWORD: db 146 | DATABASE_NAME: db 147 | DATABASE_OPTS: 148 | 149 | TIME_SCHEDULE: "@daily" 150 | TIME_LOCATION: Asia/Taipei 151 | ``` 152 | 153 | Each line of a crontab file represents a job, and looks like this: 154 | 155 | ```sh 156 | # ┌───────────── minute (0 - 59) 157 | # │ ┌───────────── hour (0 - 23) 158 | # │ │ ┌───────────── day of the month (1 - 31) 159 | # │ │ │ ┌───────────── month (1 - 12) 160 | # │ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday; 161 | # │ │ │ │ │ 7 is also Sunday on some systems) 162 | # │ │ │ │ │ 163 | # │ │ │ │ │ 164 | # * * * * * 165 | ``` 166 | 167 | A cron expression represents a set of times, using 5 space-separated fields. 168 | 169 | | Field name | Mandatory? | Allowed values | Allowed special characters | 170 | | ------------ | ---------- | --------------- | -------------------------- | 171 | | Minutes | Yes | 0-59 | \* / , - | 172 | | Hours | Yes | 0-23 | \* / , - | 173 | | Day of month | Yes | 1-31 | \* / , - ? | 174 | | Month | Yes | 1-12 or JAN-DEC | \* / , - | 175 | | Day of week | Yes | 0-6 or SUN-SAT | \* / , - ? | 176 | 177 | You may use one of several pre-defined schedules in place of a cron expression. 178 | 179 | ```sh 180 | | Entry | Description | Equivalent To | 181 | | ---------------------- | ------------------------------------------ | ------------- | 182 | | @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 1 1 * | 183 | | @monthly | Run once a month, midnight, first of month | 0 0 1 * * | 184 | | @weekly | Run once a week, midnight between Sat/Sun | 0 0 * * 0 | 185 | | @daily (or @midnight) | Run once a day, midnight | 0 0 * * * | 186 | | @hourly | Run once an hour, beginning of hour | 0 * * * * | 187 | ``` 188 | 189 | ### Setup Webhook Notification 190 | 191 | You can setup the webhook notification to send the backup status to the slack channel. 192 | 193 | ```diff 194 | backup_mysql: 195 | image: appleboy/docker-backup-database:mysql8 196 | logging: 197 | options: 198 | max-size: "100k" 199 | max-file: "3" 200 | environment: 201 | STORAGE_DRIVER: s3 202 | STORAGE_ENDPOINT: minio:9000 203 | STORAGE_BUCKET: test 204 | STORAGE_REGION: ap-northeast-1 205 | STORAGE_PATH: backup_mysql 206 | STORAGE_SSL: "false" 207 | STORAGE_INSECURE_SKIP_VERIFY: "false" 208 | ACCESS_KEY_ID: 1234567890 209 | SECRET_ACCESS_KEY: 1234567890 210 | 211 | DATABASE_DRIVER: mysql 212 | DATABASE_HOST: mysql:3306 213 | DATABASE_USERNAME: root 214 | DATABASE_PASSWORD: db 215 | DATABASE_NAME: db 216 | DATABASE_OPTS: 217 | 218 | TIME_SCHEDULE: "@daily" 219 | TIME_LOCATION: Asia/Taipei 220 | 221 | + WEBHOOK_URL: https://example.com/webhook 222 | + WEBHOOK_INSECURE: "false" 223 | ``` 224 | 225 | ## Envionment Variables 226 | 227 | ### Database Section 228 | 229 | - DATABASE_DRIVER - support `postgres`, `mysql` or `mongo`. default is `postgres` 230 | - DATABASE_USERNAME - database username 231 | - DATABASE_PASSWORD - database password 232 | - DATABASE_NAME - database name 233 | - DATABASE_HOST - database host 234 | - DATABASE_OPTS - see the `pg_dump`, `mysqldump` or `mongodump` command 235 | 236 | ### Storage Section 237 | 238 | - STORAGE_DRIVER - support `s3` or `disk`. default is `s3` 239 | - ACCESS_KEY_ID - Minio or AWS S3 ACCESS Key ID 240 | - SECRET_ACCESS_KEY - Minio or AWS S3 SECRET ACCESS Key 241 | - STORAGE_ENDPOINT - S3 Endpoint. default is `s3.amazonaws.com` 242 | - STORAGE_BUCKET - S3 bucket name 243 | - STORAGE_REGION - S3 Region. default is `ap-northeast-1` 244 | - STORAGE_PATH - backup folder path in bucket. default is `backup` and all dump file will save in `bucket_name/backup` directory 245 | - STORAGE_SSL - default is `false` 246 | - STORAGE_INSECURE_SKIP_VERIFY - default is `false` 247 | - STORAGE_DAYS - The number of days to keep the backup files. default is `7` 248 | 249 | ### Schedule Section 250 | 251 | - TIME_SCHEDULE - You may use one of several pre-defined schedules in place of a cron expression. 252 | - TIME_LOCATION - By default, all interpretation and scheduling is done in the machine's local time zone. You can specify a different time zone on construction. 253 | 254 | ## File Section 255 | 256 | - FILE_PREFIX - Prefix name of file, default is `storage driver` name. 257 | - FILE_SUFFIX - Suffix name of file 258 | - FILE_FORMAT - Format string of file, default is `20060102150405`. 259 | 260 | ## Webhook Section 261 | 262 | - WEBHOOK_URL - Webhook URL 263 | - WEBHOOK_INSECURE - default is `false` 264 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 4 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 5 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 6 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 7 | github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= 8 | github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= 9 | github.com/appleboy/go-storage v1.1.1 h1:vQ/2AhaIr49hu2Ggf7Jc9TLS22dEiEnbc7KGEQXsA+U= 10 | github.com/appleboy/go-storage v1.1.1/go.mod h1:spmxXutDRI9wS5O0skglVmsn5pbtZxovWnoIiXgIfLw= 11 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 12 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 13 | github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI= 14 | github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ= 15 | github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= 16 | github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= 17 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 18 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 19 | github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 20 | github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 21 | github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= 22 | github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 23 | github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= 24 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 25 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 26 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 27 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 28 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 29 | github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= 30 | github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 31 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 32 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 33 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 34 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 35 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 36 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 37 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 38 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 39 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 40 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 41 | github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= 42 | github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 43 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 44 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 45 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 46 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 47 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 48 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 49 | github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 50 | github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 51 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 52 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 53 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 54 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 55 | github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= 56 | github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= 57 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 58 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 59 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 60 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 61 | github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 62 | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= 63 | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= 64 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 65 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 66 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 67 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 68 | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 69 | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= 70 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 71 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 72 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 73 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 74 | github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY= 75 | github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= 76 | github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= 77 | github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= 78 | github.com/minio/minio-go/v7 v7.0.87 h1:nkr9x0u53PespfxfUqxP3UYWiE2a41gaofgNnC4Y8WQ= 79 | github.com/minio/minio-go/v7 v7.0.87/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg= 80 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 81 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 82 | github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= 83 | github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 84 | github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= 85 | github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= 86 | github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= 87 | github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= 88 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 89 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 90 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 91 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 92 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 93 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 94 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 95 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 96 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 97 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 98 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 99 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 100 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 101 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 102 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 103 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 104 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 105 | github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= 106 | github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 107 | github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= 108 | github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= 109 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 110 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 111 | github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= 112 | github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= 113 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 114 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 115 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 116 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 117 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 118 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 119 | github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= 120 | github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= 121 | github.com/testcontainers/testcontainers-go/modules/minio v0.33.0 h1:lHhjYlm0Oh+PfM03NIwCqNg2zSz9VuNTwUKi4MQfYAA= 122 | github.com/testcontainers/testcontainers-go/modules/minio v0.33.0/go.mod h1:3WRFF6lLI3IqXb7lvOx6OpEcH1jgs59mbzZiPTJeEJg= 123 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 124 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 125 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 126 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 127 | github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= 128 | github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= 129 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 130 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 131 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 132 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 133 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= 134 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= 135 | go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= 136 | go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= 137 | go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= 138 | go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= 139 | go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= 140 | go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= 141 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 142 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 143 | golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= 144 | golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 145 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 146 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 147 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 148 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 149 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 150 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 151 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 152 | --------------------------------------------------------------------------------