mirror of
https://github.com/offen/docker-volume-backup.git
synced 2025-12-05 17:18:02 +01:00
Compare commits
66 Commits
validate-c
...
v2.43.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89655e09ad | ||
|
|
016e470f5f | ||
|
|
0f30b959f8 | ||
|
|
eb4099debd | ||
|
|
d8ac5ae7e6 | ||
|
|
bad2d98ac8 | ||
|
|
2884d89f47 | ||
|
|
fcdaa09538 | ||
|
|
c48ac28626 | ||
|
|
22a4346c06 | ||
|
|
41d518a341 | ||
|
|
8f0a1c9809 | ||
|
|
75f94b0211 | ||
|
|
56f325a8bd | ||
|
|
7e6ed752f7 | ||
|
|
00cf059f4f | ||
|
|
cbbaa6ba7a | ||
|
|
2652e05169 | ||
|
|
23756074f9 | ||
|
|
40b12b9d07 | ||
|
|
e628f09122 | ||
|
|
7340e00dab | ||
|
|
958585336a | ||
|
|
68b7e4d678 | ||
|
|
857e4fc605 | ||
|
|
8d26194809 | ||
|
|
3063288d1e | ||
|
|
02fdfb363c | ||
|
|
2ee23a9384 | ||
|
|
16be0c0217 | ||
|
|
4799795f0a | ||
|
|
49b8d2f8d8 | ||
|
|
e4beef200a | ||
|
|
e75ab8bdd8 | ||
|
|
a4145352f9 | ||
|
|
615256cda9 | ||
|
|
40c4f11d70 | ||
|
|
2685571c58 | ||
|
|
04ad0777e0 | ||
|
|
50e41eac02 | ||
|
|
94e59a102e | ||
|
|
964a5e0342 | ||
|
|
2363c3c9cb | ||
|
|
6bc66db833 | ||
|
|
de40eae4de | ||
|
|
731421e359 | ||
|
|
d46918b13a | ||
|
|
2fb63059b3 | ||
|
|
e0fcbd27e5 | ||
|
|
f4884bf190 | ||
|
|
52787a1e42 | ||
|
|
6e08ae7c39 | ||
|
|
0183db831b | ||
|
|
f481fda848 | ||
|
|
f4cf4173e6 | ||
|
|
681983608f | ||
|
|
45335ffb67 | ||
|
|
01a595607d | ||
|
|
119391e8df | ||
|
|
dd5f7f5b66 | ||
|
|
c54a5bef5f | ||
|
|
8fac9608ff | ||
|
|
3ee40b6422 | ||
|
|
8b5c9a494f | ||
|
|
44ad3bbda2 | ||
|
|
74e065cbb9 |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
github: offen
|
|
||||||
patreon: offen
|
|
||||||
|
|
||||||
4
.github/workflows/deploy-docs.yml
vendored
4
.github/workflows/deploy-docs.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
JEKYLL_ENV: production
|
JEKYLL_ENV: production
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v1
|
uses: actions/upload-pages-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: 'docs/_site/'
|
path: 'docs/_site/'
|
||||||
|
|
||||||
@@ -52,4 +52,4 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v1
|
uses: actions/deploy-pages@v4
|
||||||
|
|||||||
4
.github/workflows/golangci-lint.yml
vendored
4
.github/workflows/golangci-lint.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.24'
|
||||||
cache: false
|
cache: false
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
# Require: The version of golangci-lint to use.
|
# Require: The version of golangci-lint to use.
|
||||||
# When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
|
# When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
|
||||||
# When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
|
# When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
|
||||||
version: v1.57
|
version: v1.64
|
||||||
|
|
||||||
# Optional: working directory, useful for monorepos
|
# Optional: working directory, useful for monorepos
|
||||||
# working-directory: somedir
|
# working-directory: somedir
|
||||||
|
|||||||
2
.github/workflows/unit.yml
vendored
2
.github/workflows/unit.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.22.x'
|
go-version: '1.24.x'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: go mod download
|
run: go mod download
|
||||||
- name: Test with the Go CLI
|
- name: Test with the Go CLI
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ linters:
|
|||||||
- staticcheck
|
- staticcheck
|
||||||
- govet
|
- govet
|
||||||
output:
|
output:
|
||||||
format: github-actions
|
formats:
|
||||||
|
- format: colored-line-number
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright 2022 - offen.software <hioffen@posteo.de>
|
# Copyright 2022 - offen.software <hioffen@posteo.de>
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
FROM golang:1.22-alpine as builder
|
FROM golang:1.24-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -9,7 +9,7 @@ RUN go mod download
|
|||||||
WORKDIR /app/cmd/backup
|
WORKDIR /app/cmd/backup
|
||||||
RUN go build -o backup .
|
RUN go build -o backup .
|
||||||
|
|
||||||
FROM alpine:3.20
|
FROM alpine:3.21
|
||||||
|
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ docker run --rm \
|
|||||||
offen/docker-volume-backup:v2
|
offen/docker-volume-backup:v2
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, pass a `--env-file` in order to use a full config as described below.
|
Alternatively, pass a `--env-file` in order to use a full config as described [in the docs](https://offen.github.io/docker-volume-backup/reference/).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ func (c *command) runAsCommand() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, config := range configurations {
|
for _, config := range configurations {
|
||||||
if err := config.validate(); err != nil {
|
|
||||||
return errwrap.Wrap(err, "error validating config")
|
|
||||||
}
|
|
||||||
if err := runScript(config); err != nil {
|
if err := runScript(config); err != nil {
|
||||||
return errwrap.Wrap(err, "error running script")
|
return errwrap.Wrap(err, "error running script")
|
||||||
}
|
}
|
||||||
@@ -104,12 +101,6 @@ func (c *command) schedule(strategy configStrategy) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cfg := range configurations {
|
for _, cfg := range configurations {
|
||||||
if err := cfg.validate(); err != nil {
|
|
||||||
return errwrap.Wrap(
|
|
||||||
err,
|
|
||||||
fmt.Sprintf("error validating config for schedule %s", cfg.BackupCronExpression),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
config := cfg
|
config := cfg
|
||||||
id, err := c.cr.AddFunc(config.BackupCronExpression, func() {
|
id, err := c.cr.AddFunc(config.BackupCronExpression, func() {
|
||||||
c.logger.Info(
|
c.logger.Info(
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
|
||||||
"github.com/offen/docker-volume-backup/internal/errwrap"
|
"github.com/offen/docker-volume-backup/internal/errwrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +48,8 @@ type Config struct {
|
|||||||
BackupSkipBackendsFromPrune []string `split_words:"true"`
|
BackupSkipBackendsFromPrune []string `split_words:"true"`
|
||||||
GpgPassphrase string `split_words:"true"`
|
GpgPassphrase string `split_words:"true"`
|
||||||
GpgPublicKeyRing string `split_words:"true"`
|
GpgPublicKeyRing string `split_words:"true"`
|
||||||
|
AgePassphrase string `split_words:"true"`
|
||||||
|
AgePublicKeys []string `split_words:"true"`
|
||||||
NotificationURLs []string `envconfig:"NOTIFICATION_URLS"`
|
NotificationURLs []string `envconfig:"NOTIFICATION_URLS"`
|
||||||
NotificationLevel string `split_words:"true" default:"error"`
|
NotificationLevel string `split_words:"true" default:"error"`
|
||||||
EmailNotificationRecipient string `split_words:"true"`
|
EmailNotificationRecipient string `split_words:"true"`
|
||||||
@@ -78,7 +79,7 @@ type Config struct {
|
|||||||
AzureStorageContainerName string `split_words:"true"`
|
AzureStorageContainerName string `split_words:"true"`
|
||||||
AzureStoragePath string `split_words:"true"`
|
AzureStoragePath string `split_words:"true"`
|
||||||
AzureStorageEndpoint string `split_words:"true" default:"https://{{ .AccountName }}.blob.core.windows.net/"`
|
AzureStorageEndpoint string `split_words:"true" default:"https://{{ .AccountName }}.blob.core.windows.net/"`
|
||||||
AzureStorageAccessTier AzureStorageAccessTier `split_words:"true"`
|
AzureStorageAccessTier string `split_words:"true"`
|
||||||
DropboxEndpoint string `split_words:"true" default:"https://api.dropbox.com/"`
|
DropboxEndpoint string `split_words:"true" default:"https://api.dropbox.com/"`
|
||||||
DropboxOAuth2Endpoint string `envconfig:"DROPBOX_OAUTH2_ENDPOINT" default:"https://api.dropbox.com/"`
|
DropboxOAuth2Endpoint string `envconfig:"DROPBOX_OAUTH2_ENDPOINT" default:"https://api.dropbox.com/"`
|
||||||
DropboxRefreshToken string `split_words:"true"`
|
DropboxRefreshToken string `split_words:"true"`
|
||||||
@@ -90,13 +91,6 @@ type Config struct {
|
|||||||
additionalEnvVars map[string]string
|
additionalEnvVars map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) validate() error {
|
|
||||||
if c.AzureStoragePrimaryAccountKey != "" && c.AzureStorageConnectionString != "" {
|
|
||||||
return errwrap.Wrap(nil, "using azure primary account key and connection string are mutually exclusive")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompressionType string
|
type CompressionType string
|
||||||
|
|
||||||
func (c *CompressionType) Decode(v string) error {
|
func (c *CompressionType) Decode(v string) error {
|
||||||
@@ -188,30 +182,6 @@ func (n *WholeNumber) Int() int {
|
|||||||
return int(*n)
|
return int(*n)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AzureStorageAccessTier string
|
|
||||||
|
|
||||||
func (t *AzureStorageAccessTier) Decode(v string) error {
|
|
||||||
if v == "" {
|
|
||||||
*t = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, a := range blob.PossibleAccessTierValues() {
|
|
||||||
if string(a) == v {
|
|
||||||
*t = AzureStorageAccessTier(v)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errwrap.Wrap(nil, fmt.Sprintf("%s is not a possible access tier value", v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *AzureStorageAccessTier) AccessTier() *blob.AccessTier {
|
|
||||||
if *t == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
a := blob.AccessTier(*t)
|
|
||||||
return &a
|
|
||||||
}
|
|
||||||
|
|
||||||
type envVarLookup struct {
|
type envVarLookup struct {
|
||||||
ok bool
|
ok bool
|
||||||
key string
|
key string
|
||||||
|
|||||||
@@ -10,110 +10,206 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"filippo.io/age"
|
||||||
|
"filippo.io/age/agessh"
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
openpgp "github.com/ProtonMail/go-crypto/openpgp/v2"
|
openpgp "github.com/ProtonMail/go-crypto/openpgp/v2"
|
||||||
"github.com/offen/docker-volume-backup/internal/errwrap"
|
"github.com/offen/docker-volume-backup/internal/errwrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *script) encryptAsymmetrically(outFile *os.File) (io.WriteCloser, func() error, error) {
|
func countTrue(b ...bool) int {
|
||||||
|
c := int(0)
|
||||||
entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(s.c.GpgPublicKeyRing)))
|
for _, v := range b {
|
||||||
if err != nil {
|
if v {
|
||||||
return nil, nil, errwrap.Wrap(err, "error parsing armored keyring")
|
c++
|
||||||
}
|
}
|
||||||
|
|
||||||
armoredWriter, err := armor.Encode(outFile, "PGP MESSAGE", nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errwrap.Wrap(err, "error preparing encryption")
|
|
||||||
}
|
}
|
||||||
|
return c
|
||||||
_, name := path.Split(s.file)
|
|
||||||
dst, err := openpgp.Encrypt(armoredWriter, entityList, nil, nil, &openpgp.FileHints{
|
|
||||||
FileName: name,
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dst, func() error {
|
|
||||||
if err := dst.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return armoredWriter.Close()
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *script) encryptSymmetrically(outFile *os.File) (io.WriteCloser, func() error, error) {
|
|
||||||
|
|
||||||
_, name := path.Split(s.file)
|
|
||||||
dst, err := openpgp.SymmetricallyEncrypt(outFile, []byte(s.c.GpgPassphrase), &openpgp.FileHints{
|
|
||||||
FileName: name,
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dst, dst.Close, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// encryptArchive encrypts the backup file using PGP and the configured passphrase or publickey(s).
|
// encryptArchive encrypts the backup file using PGP and the configured passphrase or publickey(s).
|
||||||
// In case no passphrase or publickey is given it returns early, leaving the backup file
|
// In case no passphrase or publickey is given it returns early, leaving the backup file
|
||||||
// untouched.
|
// untouched.
|
||||||
func (s *script) encryptArchive() error {
|
func (s *script) encryptArchive() error {
|
||||||
|
useGPGSymmetric := s.c.GpgPassphrase != ""
|
||||||
var encrypt func(outFile *os.File) (io.WriteCloser, func() error, error)
|
useGPGAsymmetric := s.c.GpgPublicKeyRing != ""
|
||||||
var cleanUpErr error
|
useAgeSymmetric := s.c.AgePassphrase != ""
|
||||||
|
useAgeAsymmetric := len(s.c.AgePublicKeys) > 0
|
||||||
switch {
|
switch nconfigured := countTrue(
|
||||||
case s.c.GpgPassphrase != "" && s.c.GpgPublicKeyRing != "":
|
useGPGSymmetric,
|
||||||
return errwrap.Wrap(nil, "error in selecting asymmetric and symmetric encryption methods: conflicting env vars are set")
|
useGPGAsymmetric,
|
||||||
case s.c.GpgPassphrase != "":
|
useAgeSymmetric,
|
||||||
encrypt = s.encryptSymmetrically
|
useAgeAsymmetric,
|
||||||
case s.c.GpgPublicKeyRing != "":
|
); nconfigured {
|
||||||
encrypt = s.encryptAsymmetrically
|
case 0:
|
||||||
default:
|
|
||||||
return nil
|
return nil
|
||||||
|
case 1:
|
||||||
|
// ok!
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"error in selecting archive encryption method: expected 0 or 1 to be configured, %d methods are configured",
|
||||||
|
nconfigured,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
gpgFile := fmt.Sprintf("%s.gpg", s.file)
|
if useGPGSymmetric {
|
||||||
|
return s.encryptWithGPGSymmetric()
|
||||||
|
} else if useGPGAsymmetric {
|
||||||
|
return s.encryptWithGPGAsymmetric()
|
||||||
|
} else if useAgeSymmetric || useAgeAsymmetric {
|
||||||
|
ar, err := s.getConfiguredAgeRecipients()
|
||||||
|
if err != nil {
|
||||||
|
return errwrap.Wrap(err, "failed to get configured age recipients")
|
||||||
|
}
|
||||||
|
return s.encryptWithAge(ar)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *script) getConfiguredAgeRecipients() ([]age.Recipient, error) {
|
||||||
|
if s.c.AgePassphrase == "" && len(s.c.AgePublicKeys) == 0 {
|
||||||
|
return nil, fmt.Errorf("no age recipients configured")
|
||||||
|
}
|
||||||
|
recipients := []age.Recipient{}
|
||||||
|
if len(s.c.AgePublicKeys) > 0 {
|
||||||
|
for _, pk := range s.c.AgePublicKeys {
|
||||||
|
pkr, err := parseAgeRecipient(pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrap(err, "failed to parse age public key")
|
||||||
|
}
|
||||||
|
recipients = append(recipients, pkr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.c.AgePassphrase != "" {
|
||||||
|
if len(recipients) != 0 {
|
||||||
|
return nil, fmt.Errorf("age encryption must only be enabled via passphrase or public key, not both")
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := age.NewScryptRecipient(s.c.AgePassphrase)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrap(err, "failed to create scrypt identity from age passphrase")
|
||||||
|
}
|
||||||
|
recipients = append(recipients, r)
|
||||||
|
}
|
||||||
|
return recipients, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAgeRecipient(arg string) (age.Recipient, error) {
|
||||||
|
// This logic is adapted from what the age CLI is doing
|
||||||
|
// stripping some special cases
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(arg, "age1"):
|
||||||
|
return age.ParseX25519Recipient(arg)
|
||||||
|
case strings.HasPrefix(arg, "ssh-"):
|
||||||
|
return agessh.ParseRecipient(arg)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unknown recipient type: %q", arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *script) encryptWithAge(rec []age.Recipient) error {
|
||||||
|
return s.doEncrypt("age", func(ciphertextWriter io.Writer) (io.WriteCloser, error) {
|
||||||
|
return age.Encrypt(ciphertextWriter, rec...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *script) encryptWithGPGSymmetric() error {
|
||||||
|
return s.doEncrypt("gpg", func(ciphertextWriter io.Writer) (io.WriteCloser, error) {
|
||||||
|
_, name := path.Split(s.file)
|
||||||
|
return openpgp.SymmetricallyEncrypt(ciphertextWriter, []byte(s.c.GpgPassphrase), &openpgp.FileHints{
|
||||||
|
FileName: name,
|
||||||
|
}, nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type closeAllWriter struct {
|
||||||
|
io.Writer
|
||||||
|
closers []io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closeAllWriter) Close() (err error) {
|
||||||
|
for _, cl := range c.closers {
|
||||||
|
err = errors.Join(err, cl.Close())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ io.WriteCloser = (*closeAllWriter)(nil)
|
||||||
|
|
||||||
|
func (s *script) encryptWithGPGAsymmetric() error {
|
||||||
|
return s.doEncrypt("gpg", func(ciphertextWriter io.Writer) (_ io.WriteCloser, outerr error) {
|
||||||
|
entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(s.c.GpgPublicKeyRing)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrap(err, "error parsing armored keyring")
|
||||||
|
}
|
||||||
|
|
||||||
|
armoredWriter, err := armor.Encode(ciphertextWriter, "PGP MESSAGE", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrap(err, "error preparing encryption")
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if outerr != nil {
|
||||||
|
_ = armoredWriter.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, name := path.Split(s.file)
|
||||||
|
encWriter, err := openpgp.Encrypt(armoredWriter, entityList, nil, nil, &openpgp.FileHints{
|
||||||
|
FileName: name,
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &closeAllWriter{
|
||||||
|
Writer: encWriter,
|
||||||
|
closers: []io.Closer{encWriter, armoredWriter},
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *script) doEncrypt(
|
||||||
|
extension string,
|
||||||
|
encryptor func(ciphertextWriter io.Writer) (io.WriteCloser, error),
|
||||||
|
) (outerr error) {
|
||||||
|
encFile := fmt.Sprintf("%s.%s", s.file, extension)
|
||||||
s.registerHook(hookLevelPlumbing, func(error) error {
|
s.registerHook(hookLevelPlumbing, func(error) error {
|
||||||
if err := remove(gpgFile); err != nil {
|
if err := remove(encFile); err != nil {
|
||||||
return errwrap.Wrap(err, "error removing gpg file")
|
return errwrap.Wrap(err, "error removing encrypted file")
|
||||||
}
|
}
|
||||||
s.logger.Info(
|
s.logger.Info(
|
||||||
fmt.Sprintf("Removed GPG file `%s`.", gpgFile),
|
fmt.Sprintf("Removed encrypted file `%s`.", encFile),
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
outFile, err := os.Create(gpgFile)
|
outFile, err := os.Create(encFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, "error opening out file")
|
return errwrap.Wrap(err, "error opening out file")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := outFile.Close(); err != nil {
|
if err := outFile.Close(); err != nil {
|
||||||
cleanUpErr = errors.Join(cleanUpErr, errwrap.Wrap(err, "error closing out file"))
|
outerr = errors.Join(outerr, errwrap.Wrap(err, "error closing out file"))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
dst, dstCloseCallback, err := encrypt(outFile)
|
dst, err := encryptor(outFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, "error encrypting backup file")
|
return errwrap.Wrap(err, "error encrypting backup file")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := dstCloseCallback(); err != nil {
|
if err := dst.Close(); err != nil {
|
||||||
cleanUpErr = errors.Join(cleanUpErr, errwrap.Wrap(err, "error closing encrypted backup file"))
|
outerr = errors.Join(outerr, errwrap.Wrap(err, "error closing encrypted backup file"))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
src, err := os.Open(s.file)
|
src, err := os.Open(s.file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, fmt.Sprintf("error opening backup file `%s`", s.file))
|
return errwrap.Wrap(err, fmt.Sprintf("error opening backup file %q", s.file))
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := src.Close(); err != nil {
|
if err := src.Close(); err != nil {
|
||||||
cleanUpErr = errors.Join(cleanUpErr, errwrap.Wrap(err, "error closing backup file"))
|
outerr = errors.Join(outerr, errwrap.Wrap(err, "error closing backup file"))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -121,9 +217,10 @@ func (s *script) encryptArchive() error {
|
|||||||
return errwrap.Wrap(err, "error writing ciphertext to file")
|
return errwrap.Wrap(err, "error writing ciphertext to file")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.file = gpgFile
|
s.file = encFile
|
||||||
s.logger.Info(
|
s.logger.Info(
|
||||||
fmt.Sprintf("Encrypted backup using gpg, saving as `%s`.", s.file),
|
fmt.Sprintf("Encrypted backup using %q, saving as %q", extension, s.file),
|
||||||
)
|
)
|
||||||
return cleanUpErr
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *script) exec(containerRef string, command string, user string) ([]byte, []byte, error) {
|
func (s *script) exec(containerRef string, command string, user string) ([]byte, []byte, error) {
|
||||||
args, _ := argv.Argv(command, nil, nil)
|
args, err := argv.Argv(command, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errwrap.Wrap(err, fmt.Sprintf("error parsing argv from '%s'", command))
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil, nil, errwrap.Wrap(nil, "received unexpected empty command")
|
||||||
|
}
|
||||||
|
|
||||||
commandEnv := []string{
|
commandEnv := []string{
|
||||||
fmt.Sprintf("COMMAND_RUNTIME_ARCHIVE_FILEPATH=%s", s.file),
|
fmt.Sprintf("COMMAND_RUNTIME_ARCHIVE_FILEPATH=%s", s.file),
|
||||||
}
|
}
|
||||||
|
|
||||||
execID, err := s.cli.ContainerExecCreate(context.Background(), containerRef, container.ExecOptions{
|
execID, err := s.cli.ContainerExecCreate(context.Background(), containerRef, container.ExecOptions{
|
||||||
Cmd: args[0],
|
Cmd: args[0],
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ func (s *script) init() error {
|
|||||||
Endpoint: s.c.AzureStorageEndpoint,
|
Endpoint: s.c.AzureStorageEndpoint,
|
||||||
RemotePath: s.c.AzureStoragePath,
|
RemotePath: s.c.AzureStoragePath,
|
||||||
ConnectionString: s.c.AzureStorageConnectionString,
|
ConnectionString: s.c.AzureStorageConnectionString,
|
||||||
AccessTier: s.c.AzureStorageAccessTier.AccessTier(),
|
AccessTier: s.c.AzureStorageAccessTier,
|
||||||
}
|
}
|
||||||
azureBackend, err := azure.NewStorageBackend(azureConfig, logFunc)
|
azureBackend, err := azure.NewStorageBackend(azureConfig, logFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -59,13 +59,11 @@ GEM
|
|||||||
rb-fsevent (0.11.2)
|
rb-fsevent (0.11.2)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
rexml (3.3.3)
|
rexml (3.3.9)
|
||||||
strscan
|
|
||||||
rouge (3.30.0)
|
rouge (3.30.0)
|
||||||
safe_yaml (1.0.5)
|
safe_yaml (1.0.5)
|
||||||
sassc (2.4.0)
|
sassc (2.4.0)
|
||||||
ffi (~> 1.9)
|
ffi (~> 1.9)
|
||||||
strscan (3.1.0)
|
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
unicode-display_width (2.4.2)
|
unicode-display_width (2.4.2)
|
||||||
|
|||||||
@@ -3,15 +3,7 @@ title: Encrypt backups using GPG
|
|||||||
layout: default
|
layout: default
|
||||||
parent: How Tos
|
parent: How Tos
|
||||||
nav_order: 7
|
nav_order: 7
|
||||||
|
nav_exclude: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# Encrypt backups using GPG
|
See: [Encrypt Backups](encrypt-backups)
|
||||||
|
|
||||||
The image supports encrypting backups using GPG out of the box.
|
|
||||||
In case a `GPG_PASSPHRASE` or `GPG_PUBLIC_KEY_RING` environment variable is set, the backup archive will be encrypted using the given key and saved as a `.gpg` file instead.
|
|
||||||
|
|
||||||
Assuming you have `gpg` installed, you can decrypt such a backup using (your OS will prompt for the passphrase before decryption can happen):
|
|
||||||
|
|
||||||
```console
|
|
||||||
gpg -o backup.tar.gz -d backup.tar.gz.gpg
|
|
||||||
```
|
|
||||||
|
|||||||
32
docs/how-tos/encrypt-backups.md
Normal file
32
docs/how-tos/encrypt-backups.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: Encrypting backups
|
||||||
|
layout: default
|
||||||
|
parent: How Tos
|
||||||
|
nav_order: 7
|
||||||
|
---
|
||||||
|
|
||||||
|
# Encrypting backups
|
||||||
|
|
||||||
|
The image supports encrypting backups using one of two available methods: **GPG** or **[age](https://age-encryption.org/)**
|
||||||
|
|
||||||
|
## Using GPG encryption
|
||||||
|
|
||||||
|
In case a `GPG_PASSPHRASE` or `GPG_PUBLIC_KEY_RING` environment variable is set, the backup archive will be encrypted using the given key and saved as a `.gpg` file instead.
|
||||||
|
|
||||||
|
Assuming you have `gpg` installed, you can decrypt such a backup using (your OS will prompt for the passphrase before decryption can happen):
|
||||||
|
|
||||||
|
```console
|
||||||
|
gpg -o backup.tar.gz -d backup.tar.gz.gpg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using age encryption
|
||||||
|
|
||||||
|
{: .note }
|
||||||
|
Even though the `age` CLI tools supports encryption using SSH keys, this is not supported by this tool.
|
||||||
|
`AGE_PUBLIC_KEYS` currently expects `age` keys to be given.
|
||||||
|
|
||||||
|
age allows backups to be encrypted with either a symmetric key (password) or a public key. One of those options are available for use.
|
||||||
|
|
||||||
|
Given `AGE_PASSPHRASE` being provided, the backup archive will be encrypted with the passphrase and saved as a `.age` file instead. Refer to age documentation for how to properly decrypt.
|
||||||
|
|
||||||
|
Given `AGE_PUBLIC_KEYS` being provided (allowing multiple by separating each public key with `,`), the backup archive will be encrypted with the provided public keys. It will also result in the archive being saved as a `.age` file.
|
||||||
@@ -33,7 +33,7 @@ services:
|
|||||||
- docker-volume-backup.copy-post=/bin/sh -c 'rsync $$COMMAND_RUNTIME_ARCHIVE_FILEPATH /destination'
|
- docker-volume-backup.copy-post=/bin/sh -c 'rsync $$COMMAND_RUNTIME_ARCHIVE_FILEPATH /destination'
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
# other services defined here ...
|
# other services defined here ...
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -33,5 +33,7 @@ Note: Using the "Generated access token" in the app console is not supported, as
|
|||||||
|
|
||||||
## Other parameters
|
## Other parameters
|
||||||
|
|
||||||
Important: If you chose `App folder` access during the creation of your Dropbox app in step 1 above, you can only write in the app's directory!
|
Important: If you chose `App folder` access during the creation of your Dropbox app in step 1 above, `DROPBOX_REMOTE_PATH` will be a relative path under the App folder!
|
||||||
This means, that `DROPBOX_REMOTE_PATH` must start with e.g. `/Apps/YOUR_APP_NAME` or `/Apps/YOUR_APP_NAME/some_sub_dir`
|
(_For example, DROPBOX_REMOTE_PATH=/somedir means the backup file will be uploaded to /Apps/myapp/somedir_)
|
||||||
|
On the other hand if you chose `Full Dropbox` access, the value for `DROPBOX_REMOTE_PATH` will represent an absolute path inside your Dropbox storage area.
|
||||||
|
(_Still considering the same example above, the backup file will be uploaded to /somedir in your Dropbox root_)
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ services:
|
|||||||
DROPBOX_REFRESH_TOKEN: REFRESH_KEY # replace
|
DROPBOX_REFRESH_TOKEN: REFRESH_KEY # replace
|
||||||
DROPBOX_APP_KEY: APP_KEY # replace
|
DROPBOX_APP_KEY: APP_KEY # replace
|
||||||
DROPBOX_APP_SECRET: APP_SECRET # replace
|
DROPBOX_APP_SECRET: APP_SECRET # replace
|
||||||
DROPBOX_REMOTE_PATH: /Apps/my-test-app/some_subdir # replace
|
DROPBOX_REMOTE_PATH: /somedir # replace
|
||||||
volumes:
|
volumes:
|
||||||
- data:/backup/my-app-backup:ro
|
- data:/backup/my-app-backup:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
@@ -338,7 +338,7 @@ volumes:
|
|||||||
data:
|
data:
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using mysqldump to prepare the backup
|
## Using mariadb-dump/mysqldump to prepare the backup
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
version: '3'
|
version: '3'
|
||||||
@@ -347,7 +347,7 @@ services:
|
|||||||
database:
|
database:
|
||||||
image: mariadb:latest
|
image: mariadb:latest
|
||||||
labels:
|
labels:
|
||||||
- docker-volume-backup.archive-pre=/bin/sh -c 'mysqldump -psecret --all-databases > /tmp/dumps/dump.sql'
|
- docker-volume-backup.archive-pre=/bin/sh -c 'mariadb-dump -psecret --all-databases > /tmp/dumps/dump.sql'
|
||||||
volumes:
|
volumes:
|
||||||
- data:/tmp/dumps
|
- data:/tmp/dumps
|
||||||
backup:
|
backup:
|
||||||
@@ -358,7 +358,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./local:/archive
|
- ./local:/archive
|
||||||
- data:/backup/data:ro
|
- data:/backup/data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
data:
|
data:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ nav_order: 2
|
|||||||
Backup targets, schedule and retention are configured using environment variables.
|
Backup targets, schedule and retention are configured using environment variables.
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
You can use any environment variable from below also with a `_FILE` suffix to be able to load the value from a file.
|
As per established convention, you can use any environment variable key from below with a `_FILE` suffix in order to load the value from a file instead.
|
||||||
This is typically useful when using [Docker Secrets](https://docs.docker.com/engine/swarm/secrets/) or similar.
|
This is typically useful when using [Docker Secrets](https://docs.docker.com/engine/swarm/secrets/) or similar.
|
||||||
Note that secrets will not be trimmed of leading or trailing whitespace.
|
Note that secrets will not be trimmed of leading or trailing whitespace.
|
||||||
|
|
||||||
@@ -17,13 +17,14 @@ Note that secrets will not be trimmed of leading or trailing whitespace.
|
|||||||
In case you encounter double quoted values in your runtime configuration you might still be using an [older version of `docker-compose`][compose-issue].
|
In case you encounter double quoted values in your runtime configuration you might still be using an [older version of `docker-compose`][compose-issue].
|
||||||
You can work around this by either updating `docker-compose` or unquoting your configuration values.
|
You can work around this by either updating `docker-compose` or unquoting your configuration values.
|
||||||
|
|
||||||
You can populate below template according to your requirements and use it as your `env_file`:
|
You can populate below template according to your requirements and use it as your `env_file`.
|
||||||
|
The values for each key currently match its default.
|
||||||
|
|
||||||
{% raw %}
|
{% raw %}
|
||||||
```
|
```
|
||||||
########### BACKUP SCHEDULE
|
########### BACKUP SCHEDULE
|
||||||
|
|
||||||
|
# Backups can be run on fixed scheduled that are defined as a cron expression.
|
||||||
# A cron expression represents a set of times, using 5 or 6 space-separated fields.
|
# A cron expression represents a set of times, using 5 or 6 space-separated fields.
|
||||||
#
|
#
|
||||||
# Field name | Mandatory? | Allowed values | Allowed special characters
|
# Field name | Mandatory? | Allowed values | Allowed special characters
|
||||||
@@ -37,10 +38,14 @@ You can populate below template according to your requirements and use it as you
|
|||||||
#
|
#
|
||||||
# Month and Day-of-week field values are case insensitive.
|
# Month and Day-of-week field values are case insensitive.
|
||||||
# "SUN", "Sun", and "sun" are equally accepted.
|
# "SUN", "Sun", and "sun" are equally accepted.
|
||||||
# If no value is set, `@daily` will be used.
|
|
||||||
# If you do not want the cron to ever run, use `0 0 5 31 2 ?`.
|
# If you do not want the cron to ever run, use `0 0 5 31 2 ?`.
|
||||||
|
# Refer to sites like <https://crontab.guru> for help.
|
||||||
|
# If no value is set, `@daily` will be used, which runs every
|
||||||
|
# day at midnight.
|
||||||
|
|
||||||
# BACKUP_CRON_EXPRESSION="0 2 * * *"
|
# BACKUP_CRON_EXPRESSION="@daily"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The compression algorithm used in conjunction with tar.
|
# The compression algorithm used in conjunction with tar.
|
||||||
# Valid options are: "gz" (Gzip), "zst" (Zstd) or "none" (tar only).
|
# Valid options are: "gz" (Gzip), "zst" (Zstd) or "none" (tar only).
|
||||||
@@ -48,15 +53,19 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# BACKUP_COMPRESSION="gz"
|
# BACKUP_COMPRESSION="gz"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Parallelism level for "gz" (Gzip) compression.
|
# Parallelism level for "gz" (Gzip) compression.
|
||||||
# Defines how many blocks of data are concurrently processed.
|
# Defines how many blocks of data are concurrently processed.
|
||||||
# Higher values result in faster compression. No effect on decompression
|
# Higher values result in faster compression. No effect on decompression
|
||||||
# Default = 1. Setting this to 0 will use all available threads.
|
# Default = 1. Setting this to 0 will use all available threads.
|
||||||
|
|
||||||
# GZIP_PARALLELISM=1
|
# GZIP_PARALLELISM="1"
|
||||||
|
|
||||||
# The name of the backup file including the extension.
|
# ---
|
||||||
# Format verbs will be replaced as in `strftime`. Omitting them
|
|
||||||
|
# The desired name of the backup file including the extension.
|
||||||
|
# Format verbs will be replaced as in `strftime`. Omitting all verbs
|
||||||
# will result in the same filename for every backup run, which means previous
|
# will result in the same filename for every backup run, which means previous
|
||||||
# versions will be overwritten on subsequent runs.
|
# versions will be overwritten on subsequent runs.
|
||||||
# Extension can be defined literally or via "{{ .Extension }}" template,
|
# Extension can be defined literally or via "{{ .Extension }}" template,
|
||||||
@@ -66,6 +75,8 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# BACKUP_FILENAME="backup-%Y-%m-%dT%H-%M-%S.{{ .Extension }}"
|
# BACKUP_FILENAME="backup-%Y-%m-%dT%H-%M-%S.{{ .Extension }}"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Setting BACKUP_FILENAME_EXPAND to true allows for environment variable
|
# Setting BACKUP_FILENAME_EXPAND to true allows for environment variable
|
||||||
# placeholders in BACKUP_FILENAME, BACKUP_LATEST_SYMLINK and in
|
# placeholders in BACKUP_FILENAME, BACKUP_LATEST_SYMLINK and in
|
||||||
# BACKUP_PRUNING_PREFIX that will get expanded at runtime,
|
# BACKUP_PRUNING_PREFIX that will get expanded at runtime,
|
||||||
@@ -76,10 +87,15 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# BACKUP_FILENAME_EXPAND="true"
|
# BACKUP_FILENAME_EXPAND="true"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# When storing local backups, a symlink to the latest backup can be created
|
# When storing local backups, a symlink to the latest backup can be created
|
||||||
# in case a value is given for this key. This has no effect on remote backups.
|
# in case a value is given for this key. This has no effect on remote backups.
|
||||||
|
# Example: "backup.latest.tar.gz"
|
||||||
|
|
||||||
# BACKUP_LATEST_SYMLINK="backup.latest.tar.gz"
|
# BACKUP_LATEST_SYMLINK=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# ************************************************************************
|
# ************************************************************************
|
||||||
# The BACKUP_FROM_SNAPSHOT option has been deprecated and will be removed
|
# The BACKUP_FROM_SNAPSHOT option has been deprecated and will be removed
|
||||||
@@ -93,203 +109,285 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# BACKUP_FROM_SNAPSHOT="false"
|
# BACKUP_FROM_SNAPSHOT="false"
|
||||||
|
|
||||||
# By default, the `/backup` directory inside the container will be backed up.
|
# ---
|
||||||
# In case you need to use a custom location, set `BACKUP_SOURCES`.
|
|
||||||
|
|
||||||
# BACKUP_SOURCES="/other/location"
|
# By default, the contents of the `/backup` directory inside the container
|
||||||
|
# will be backed up. In case you need to use a custom location, set `BACKUP_SOURCES`.
|
||||||
|
# Example: "/other/location"
|
||||||
|
|
||||||
# When given, all files in BACKUP_SOURCES whose full path matches the given
|
# BACKUP_SOURCES="/backup"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
|
# When a value is given, all files in BACKUP_SOURCES whose full path matches the
|
||||||
# regular expression will be excluded from the archive. Regular Expressions
|
# regular expression will be excluded from the archive. Regular Expressions
|
||||||
# can be used as from the Go standard library https://pkg.go.dev/regexp
|
# can be used as from the Go standard library https://pkg.go.dev/regexp
|
||||||
|
# Example: "\.log$"
|
||||||
|
|
||||||
# BACKUP_EXCLUDE_REGEXP="\.log$"
|
# BACKUP_EXCLUDE_REGEXP=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Exclude one or many storage backends from the pruning process.
|
# Exclude one or many storage backends from the pruning process.
|
||||||
|
# Available backends are: S3, WebDAV, SSH, Local, Dropbox, Azure
|
||||||
# E.g. with one backend excluded: BACKUP_SKIP_BACKENDS_FROM_PRUNE=s3
|
# E.g. with one backend excluded: BACKUP_SKIP_BACKENDS_FROM_PRUNE=s3
|
||||||
# E.g. with multiple backends excluded: BACKUP_SKIP_BACKENDS_FROM_PRUNE=s3,webdav
|
# E.g. with multiple backends excluded: BACKUP_SKIP_BACKENDS_FROM_PRUNE=s3,webdav
|
||||||
# Available backends are: S3, WebDAV, SSH, Local, Dropbox, Azure
|
# Note: The names of the backends are case insensitive.
|
||||||
# Note: The name of the backends is case insensitive.
|
|
||||||
# Default: All backends get pruned.
|
# Default: All backends get pruned.
|
||||||
|
|
||||||
# BACKUP_SKIP_BACKENDS_FROM_PRUNE=
|
# BACKUP_SKIP_BACKENDS_FROM_PRUNE=""
|
||||||
|
|
||||||
########### BACKUP STORAGE
|
########### S3 COMPATIBLE STORAGE
|
||||||
|
|
||||||
# The name of the remote bucket that should be used for storing backups. If
|
# The name of the remote bucket that should be used for storing backups. If
|
||||||
# this is not set, no remote backups will be stored.
|
# this is not set, no remote backups will be stored.
|
||||||
|
# Example: "backup-bucket"
|
||||||
|
|
||||||
# AWS_S3_BUCKET_NAME="backup-bucket"
|
# AWS_S3_BUCKET_NAME=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# If you want to store the backup in a non-root location on your bucket
|
# If you want to store the backup in a non-root location on your bucket
|
||||||
# you can provide a path. The path must not contain a leading slash.
|
# you can provide a path. The path must not contain a leading slash.
|
||||||
|
# Example: "my/backup/location"
|
||||||
|
|
||||||
# AWS_S3_PATH="my/backup/location"
|
# AWS_S3_PATH=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Define credentials for authenticating against the backup storage and a bucket
|
# Define credentials for authenticating against the backup storage and a bucket
|
||||||
# name. Although all of these keys are `AWS`-prefixed, the setup can be used
|
# name. Although all of these keys are `AWS`-prefixed, the setup can be used
|
||||||
# with any S3 compatible storage.
|
# with any S3 compatible storage.
|
||||||
|
|
||||||
# AWS_ACCESS_KEY_ID="<xxx>"
|
# AWS_ACCESS_KEY_ID=""
|
||||||
# AWS_SECRET_ACCESS_KEY="<xxx>"
|
# AWS_SECRET_ACCESS_KEY=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Instead of providing static credentials, you can also use IAM instance profiles
|
# Instead of providing static credentials, you can also use IAM instance profiles
|
||||||
# or similar to provide authentication. Some possible configuration options on AWS:
|
# or similar to provide authentication. Some possible configuration options on AWS:
|
||||||
# - EC2: http://169.254.169.254
|
# - EC2: http://169.254.169.254
|
||||||
# - ECS: http://169.254.170.2
|
# - ECS: http://169.254.170.2
|
||||||
|
|
||||||
# AWS_IAM_ROLE_ENDPOINT="http://169.254.169.254"
|
# AWS_IAM_ROLE_ENDPOINT=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# This is the FQDN of your storage server, e.g. `storage.example.com`.
|
# This is the FQDN of your storage server, e.g. `storage.example.com`.
|
||||||
# Do not set this when working against AWS S3 (the default value is
|
# If you need to set a specific (non-https) protocol, you will need to use the option below.
|
||||||
# `s3.amazonaws.com`). If you need to set a specific (non-https) protocol, you
|
# The default value points to the standard AWS S3 endpoint.
|
||||||
# will need to use the option below.
|
|
||||||
|
|
||||||
# AWS_ENDPOINT="storage.example.com"
|
# AWS_ENDPOINT="s3.amazonaws.com"
|
||||||
|
|
||||||
# The protocol to be used when communicating with your storage server.
|
# ---
|
||||||
|
|
||||||
|
# The protocol to be used when communicating with your S3 storage server.
|
||||||
# Defaults to "https". You can set this to "http" when communicating with
|
# Defaults to "https". You can set this to "http" when communicating with
|
||||||
# a different Docker container on the same host for example.
|
# a different Docker container in the same virtual network for example.
|
||||||
|
|
||||||
# AWS_ENDPOINT_PROTO="https"
|
# AWS_ENDPOINT_PROTO="https"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Setting this variable to `true` will disable verification of
|
# Setting this variable to `true` will disable verification of
|
||||||
# SSL certificates for AWS_ENDPOINT. You shouldn't use this unless you use
|
# SSL certificates for AWS_ENDPOINT. You shouldn't use this unless you use
|
||||||
# self-signed certificates for your remote storage backend. This can only be
|
# self-signed certificates for your remote storage backend. This can only be
|
||||||
# used when AWS_ENDPOINT_PROTO is set to `https`.
|
# used when AWS_ENDPOINT_PROTO is set to `https`.
|
||||||
|
|
||||||
# AWS_ENDPOINT_INSECURE="true"
|
# AWS_ENDPOINT_INSECURE="false"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# If you wish to use self signed certificates your S3 server, you can pass
|
# If you wish to use self signed certificates your S3 server, you can pass
|
||||||
# the location of a PEM encoded CA certificate and it will be used for
|
# the location of a PEM encoded CA certificate and it will be used for
|
||||||
# validating your certificates.
|
# validating your certificates. Alternatively, pass a PEM encoded string
|
||||||
# Alternatively, pass a PEM encoded string containing the certificate.
|
# containing the certificate.
|
||||||
|
# Example: "/path/to/cert.pem"
|
||||||
|
|
||||||
# AWS_ENDPOINT_CA_CERT="/path/to/cert.pem"
|
# AWS_ENDPOINT_CA_CERT=""
|
||||||
|
|
||||||
# Setting this variable will change the S3 storage class header.
|
# ---
|
||||||
# Defaults to "STANDARD", you can set this value according to your needs.
|
|
||||||
|
|
||||||
# AWS_STORAGE_CLASS="GLACIER"
|
# Setting a value for this key will change the S3 storage class header.
|
||||||
|
# Default behavior is to use the standard class when no value is given.
|
||||||
|
# Example: "GLACIER"
|
||||||
|
|
||||||
|
# AWS_STORAGE_CLASS=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Setting this variable will change the S3 default part size for the copy step.
|
# Setting this variable will change the S3 default part size for the copy step.
|
||||||
# This value is useful when you want to upload large files.
|
# This value is useful when you want to upload large files.
|
||||||
# NB : While using Scaleway as S3 provider, be aware that the parts counter is set to 1.000.
|
# NB: While using Scaleway as S3 provider, be aware that the parts counter is set to 1.000.
|
||||||
# While Minio uses a hard coded value to 10.000. As a workaround, try to set a higher value.
|
# While Minio uses a hard coded value to 10.000. As a workaround, try to set a higher value.
|
||||||
# Defaults to "16" (MB) if unset (from minio), you can set this value according to your needs.
|
# Defaults to "16" (MB) if unset (from minio), you can set this value according to your needs.
|
||||||
# The unit is in MB and an integer.
|
# The unit is in MB and an integer.
|
||||||
|
|
||||||
# AWS_PART_SIZE=16
|
# AWS_PART_SIZE="16"
|
||||||
|
|
||||||
# You can also backup files to any WebDAV server:
|
########### WEBDAV STORAGE
|
||||||
|
|
||||||
# The URL of the remote WebDAV server
|
# The URL of the remote WebDAV server
|
||||||
|
# Example: "https://webdav.example.com"
|
||||||
|
|
||||||
# WEBDAV_URL="https://webdav.example.com"
|
# WEBDAV_URL=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The Directory to place the backups to on the WebDAV server.
|
# The Directory to place the backups to on the WebDAV server.
|
||||||
# If the path is not present on the server it will be created.
|
# If the path is not present on the server it will be created.
|
||||||
|
# Example: "/my/directory/"
|
||||||
|
|
||||||
# WEBDAV_PATH="/my/directory/"
|
# WEBDAV_PATH=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The username for the WebDAV server
|
# The username for the WebDAV server
|
||||||
|
# Example: "user"
|
||||||
|
|
||||||
# WEBDAV_USERNAME="user"
|
# WEBDAV_USERNAME=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The password for the WebDAV server
|
# The password for the WebDAV server
|
||||||
|
# Example: "password"
|
||||||
|
|
||||||
# WEBDAV_PASSWORD="password"
|
# WEBDAV_PASSWORD=""
|
||||||
|
|
||||||
# Setting this variable to `true` will disable verification of
|
# ---
|
||||||
|
|
||||||
|
# Setting this variable to "true" will disable verification of
|
||||||
# SSL certificates for WEBDAV_URL. You shouldn't use this unless you use
|
# SSL certificates for WEBDAV_URL. You shouldn't use this unless you use
|
||||||
# self-signed certificates for your remote storage backend.
|
# self-signed certificates for your remote storage backend.
|
||||||
|
|
||||||
# WEBDAV_URL_INSECURE="true"
|
# WEBDAV_URL_INSECURE="false"
|
||||||
|
|
||||||
# You can also backup files to any SSH server:
|
########### SSH/SFTP STORAGE
|
||||||
|
|
||||||
# The URL of the remote SSH server
|
# The FQDN of the remote SSH server
|
||||||
|
# Example: "server.local"
|
||||||
|
|
||||||
# SSH_HOST_NAME="server.local"
|
# SSH_HOST_NAME=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The port of the remote SSH server
|
# The port of the remote SSH server
|
||||||
# Optional variable default value is `22`
|
|
||||||
|
|
||||||
# SSH_PORT=2222
|
# SSH_PORT="22"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The Directory to place the backups to on the SSH server.
|
# The Directory to place the backups to on the SSH server.
|
||||||
|
# Example: "/home/user/backups"
|
||||||
|
|
||||||
# SSH_REMOTE_PATH="/my/directory/"
|
# SSH_REMOTE_PATH=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The username for the SSH server
|
# The username for the SSH server
|
||||||
|
# Example: "user"
|
||||||
|
|
||||||
# SSH_USER="user"
|
# SSH_USER=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The password for the SSH server
|
# The password for the SSH server
|
||||||
|
# Example: "password"
|
||||||
|
|
||||||
# SSH_PASSWORD="password"
|
# SSH_PASSWORD=""
|
||||||
|
|
||||||
# The private key path in container for SSH server
|
# ---
|
||||||
# Default value: /root/.ssh/id_rsa
|
|
||||||
# If file is mounted to /root/.ssh/id_rsa path it will be used. Non-RSA keys will
|
# The private key path in container for SSH server.
|
||||||
# also work.
|
# Consumers can mount a file into /root/.ssh/id_rsa (or the respective value)
|
||||||
|
# in order to have it being used. Non-RSA keys (e.g. ed25519) will also work.
|
||||||
|
|
||||||
# SSH_IDENTITY_FILE="/root/.ssh/id_rsa"
|
# SSH_IDENTITY_FILE="/root/.ssh/id_rsa"
|
||||||
|
|
||||||
# The passphrase for the identity file
|
# ---
|
||||||
|
|
||||||
# SSH_IDENTITY_PASSPHRASE="pass"
|
# The passphrase for the identity file if applicable
|
||||||
|
# Example: "pass"
|
||||||
|
|
||||||
|
# SSH_IDENTITY_PASSPHRASE=""
|
||||||
|
|
||||||
|
########### AZURE BLOB STORAGE
|
||||||
|
|
||||||
# The credential's account name when using Azure Blob Storage. This has to be
|
# The credential's account name when using Azure Blob Storage. This has to be
|
||||||
# set when using Azure Blob Storage.
|
# set when using Azure Blob Storage.
|
||||||
|
# Example: "account-name"
|
||||||
|
|
||||||
# AZURE_STORAGE_ACCOUNT_NAME="account-name"
|
# AZURE_STORAGE_ACCOUNT_NAME=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The credential's primary account key when using Azure Blob Storage. If this
|
# The credential's primary account key when using Azure Blob Storage. If this
|
||||||
# is not given, the command tries to fall back to using a connection string
|
# is not given, the command tries to fall back to using a connection string
|
||||||
# (if given) or a managed identity (if nothing is given).
|
# (if given) or a managed identity (if neither is set).
|
||||||
|
|
||||||
# AZURE_STORAGE_PRIMARY_ACCOUNT_KEY="<xxx>"
|
# AZURE_STORAGE_PRIMARY_ACCOUNT_KEY=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# A connection string for accessing Azure Blob Storage. If this
|
# A connection string for accessing Azure Blob Storage. If this
|
||||||
# is not given, the command tries to fall back to using a primary account key
|
# is not given, the command tries to fall back to using a primary account key
|
||||||
# (if given) or a managed identity (if nothing is given).
|
# (if given) or a managed identity (if neither is set).
|
||||||
|
|
||||||
# AZURE_STORAGE_CONNECTION_STRING="<xxx>"
|
# AZURE_STORAGE_CONNECTION_STRING=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The container name when using Azure Blob Storage.
|
# The container name when using Azure Blob Storage.
|
||||||
|
# Example: "container-name"
|
||||||
|
|
||||||
# AZURE_STORAGE_CONTAINER_NAME="container-name"
|
# AZURE_STORAGE_CONTAINER_NAME=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# The service endpoint when using Azure Blob Storage. This is a template that
|
# The service endpoint when using Azure Blob Storage. This is a template that
|
||||||
# can be passed the account name as shown in the default value below.
|
# can be passed the account name as shown in the default value below.
|
||||||
|
|
||||||
# AZURE_STORAGE_ENDPOINT="https://{{ .AccountName }}.blob.core.windows.net/"
|
# AZURE_STORAGE_ENDPOINT="https://{{ .AccountName }}.blob.core.windows.net/"
|
||||||
|
|
||||||
# Absolute remote path in your Dropbox where the backups shall be stored.
|
# ---
|
||||||
# Note: Use your app's subpath in Dropbox, if it doesn't have global access.
|
|
||||||
# Consulte the README for further information.
|
|
||||||
|
|
||||||
# The access tier when using Azure Blob Storage. Possible values are
|
# The access tier when using Azure Blob Storage. Possible values are
|
||||||
# https://github.com/Azure/azure-sdk-for-go/blob/sdk/storage/azblob/v1.3.2/sdk/storage/azblob/internal/generated/zz_constants.go#L14-L30
|
# https://github.com/Azure/azure-sdk-for-go/blob/sdk/storage/azblob/v1.3.2/sdk/storage/azblob/internal/generated/zz_constants.go#L14-L30
|
||||||
|
# Example: "Cold"
|
||||||
|
|
||||||
# AZURE_STORAGE_ACCESS_TIER="Cold"
|
# AZURE_STORAGE_ACCESS_TIER=""
|
||||||
|
|
||||||
# DROPBOX_REMOTE_PATH="/my/directory"
|
########### DROPBOX STORAGE
|
||||||
|
|
||||||
# Number of concurrent chunked uploads for Dropbox.
|
# Absolute remote path in your Dropbox where the backups shall be stored.
|
||||||
# Values above 6 usually result in no enhancements.
|
# Note: Use your app's subpath in Dropbox, if it doesn't have global access.
|
||||||
|
# Consult the README for further information.
|
||||||
|
# Example: "/my/directory"
|
||||||
|
|
||||||
# DROPBOX_CONCURRENCY_LEVEL="6"
|
# DROPBOX_REMOTE_PATH=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# App key and app secret from your app created at https://www.dropbox.com/developers/apps/info
|
# App key and app secret from your app created at https://www.dropbox.com/developers/apps/info
|
||||||
|
|
||||||
# DROPBOX_APP_KEY=""
|
# DROPBOX_APP_KEY=""
|
||||||
# DROPBOX_APP_SECRET=""
|
# DROPBOX_APP_SECRET=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
|
# Number of concurrent chunked uploads for Dropbox.
|
||||||
|
# Values above 6 usually result in no enhancements.
|
||||||
|
|
||||||
|
# DROPBOX_CONCURRENCY_LEVEL="6"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Refresh token to request new short-lived tokens (OAuth2). Consult README to see how to get one.
|
# Refresh token to request new short-lived tokens (OAuth2). Consult README to see how to get one.
|
||||||
|
|
||||||
# DROPBOX_REFRESH_TOKEN=""
|
# DROPBOX_REFRESH_TOKEN=""
|
||||||
|
|
||||||
|
########### LOCAL FILE STORAGE
|
||||||
|
|
||||||
# In addition to storing backups remotely, you can also keep local copies.
|
# In addition to storing backups remotely, you can also keep local copies.
|
||||||
# Pass a container-local path to store your backups if needed. You also need to
|
# Pass a container-local path to store your backups if needed. You also need to
|
||||||
# mount a local folder or Docker volume into that location (`/archive`
|
# mount a local folder or Docker volume into that location (`/archive`
|
||||||
@@ -311,10 +409,12 @@ You can populate below template according to your requirements and use it as you
|
|||||||
# for such files, or to configure BACKUP_PRUNING_PREFIX to limit
|
# for such files, or to configure BACKUP_PRUNING_PREFIX to limit
|
||||||
# removal to certain files.
|
# removal to certain files.
|
||||||
|
|
||||||
# Define this value to enable automatic rotation of old backups. The value
|
# Pass zero or a positive integer value to enable automatic rotation of
|
||||||
# declares the number of days for which a backup is kept.
|
# old backups. The value declares the number of days for which a backup is kept.
|
||||||
|
|
||||||
# BACKUP_RETENTION_DAYS="7"
|
# BACKUP_RETENTION_DAYS="-1"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# In case the duration a backup takes fluctuates noticeably in your setup
|
# In case the duration a backup takes fluctuates noticeably in your setup
|
||||||
# you can adjust this setting to make sure there are no race conditions
|
# you can adjust this setting to make sure there are no race conditions
|
||||||
@@ -326,6 +426,8 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# BACKUP_PRUNING_LEEWAY="1m"
|
# BACKUP_PRUNING_LEEWAY="1m"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# In case your target bucket or directory contains other files than the ones
|
# In case your target bucket or directory contains other files than the ones
|
||||||
# managed by this container, you can limit the scope of rotation by setting
|
# managed by this container, you can limit the scope of rotation by setting
|
||||||
# a prefix value. This would usually be the non-parametrized part of your
|
# a prefix value. This would usually be the non-parametrized part of your
|
||||||
@@ -333,22 +435,37 @@ You can populate below template according to your requirements and use it as you
|
|||||||
# you can set BACKUP_PRUNING_PREFIX to `db-backup-` and make sure
|
# you can set BACKUP_PRUNING_PREFIX to `db-backup-` and make sure
|
||||||
# unrelated files are not affected by the rotation mechanism.
|
# unrelated files are not affected by the rotation mechanism.
|
||||||
|
|
||||||
# BACKUP_PRUNING_PREFIX="backup-"
|
# BACKUP_PRUNING_PREFIX=""
|
||||||
|
|
||||||
########### BACKUP ENCRYPTION
|
########### BACKUP ENCRYPTION
|
||||||
|
|
||||||
|
# All of the encryption options are mutually exclusive. Provide a single option
|
||||||
|
# for the encryption scheme of your choice.
|
||||||
|
|
||||||
# Backups can be encrypted symmetrically using gpg in case a passphrase is given.
|
# Backups can be encrypted symmetrically using gpg in case a passphrase is given.
|
||||||
|
|
||||||
# GPG_PASSPHRASE="<xxx>"
|
# GPG_PASSPHRASE=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Backups can be encrypted asymmetrically using gpg in case publickeys are given.
|
# Backups can be encrypted asymmetrically using gpg in case publickeys are given.
|
||||||
|
# You can use pipe syntax to pass a multiline value.
|
||||||
|
|
||||||
# GPG_PUBLIC_KEY_RING= |
|
# GPG_PUBLIC_KEY_RING=""
|
||||||
#-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
#
|
# ---
|
||||||
#D/cIHu6GH/0ghlcUVSbgMg5RRI5QKNNKh04uLAPxr75mKwUg0xPUaWgyyrAChVBi
|
|
||||||
#...
|
# Backups can be encrypted symmetrically using age in case a passphrase is given.
|
||||||
#-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
# AGE_PASSPHRASE=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
|
# Backups can be encrypted asymmetrically using age in case publickeys are given.
|
||||||
|
# Multiple keys need to be provided as a comma separated list. Right now, this
|
||||||
|
# supports `age` and `ssh` keys
|
||||||
|
|
||||||
|
# AGE_PUBLIC_KEYS=""
|
||||||
|
|
||||||
########### STOPPING CONTAINERS AND SERVICES DURING BACKUP
|
########### STOPPING CONTAINERS AND SERVICES DURING BACKUP
|
||||||
|
|
||||||
@@ -356,18 +473,17 @@ You can populate below template according to your requirements and use it as you
|
|||||||
# `docker-volume-backup.stop-during-backup` label. By default, all containers and
|
# `docker-volume-backup.stop-during-backup` label. By default, all containers and
|
||||||
# services that are labeled with `true` will be stopped. If you need more fine
|
# services that are labeled with `true` will be stopped. If you need more fine
|
||||||
# grained control (e.g. when running multiple containers based on this image),
|
# grained control (e.g. when running multiple containers based on this image),
|
||||||
# you can override this default by specifying a different value here.
|
# you can override this default by specifying a different string value here.
|
||||||
# BACKUP_STOP_DURING_BACKUP_LABEL="service1"
|
# BACKUP_STOP_DURING_BACKUP_LABEL="true"
|
||||||
|
|
||||||
# When trying to scale down Docker Swarm services, give up after
|
# When trying to scale down Docker Swarm services, give up after
|
||||||
# the specified amount of time in case the service has not converged yet.
|
# the specified amount of time in case the service has not converged yet.
|
||||||
# In case you need to adjust this timeout, supply a duration
|
# In case you need to adjust this timeout, supply a duration
|
||||||
# value as per https://pkg.go.dev/time#ParseDuration to `BACKUP_STOP_SERVICE_TIMEOUT`.
|
# value as per https://pkg.go.dev/time#ParseDuration to `BACKUP_STOP_SERVICE_TIMEOUT`.
|
||||||
# Defaults to 5 minutes.
|
|
||||||
|
|
||||||
# BACKUP_STOP_SERVICE_TIMEOUT="5m"
|
# BACKUP_STOP_SERVICE_TIMEOUT="5m"
|
||||||
|
|
||||||
########### EXECUTING COMMANDS IN CONTAINERS PRE/POST BACKUP
|
########### EXECUTING COMMANDS IN CONTAINERS DURING THE BACKUP LIFECYCLE
|
||||||
|
|
||||||
# It is possible to define commands to be run in any container before and after
|
# It is possible to define commands to be run in any container before and after
|
||||||
# a backup is conducted. The commands themselves are defined in labels like
|
# a backup is conducted. The commands themselves are defined in labels like
|
||||||
@@ -378,15 +494,17 @@ You can populate below template according to your requirements and use it as you
|
|||||||
# is configured to be "true", command execution output will be forwarded to
|
# is configured to be "true", command execution output will be forwarded to
|
||||||
# the backup container's stdout and stderr.
|
# the backup container's stdout and stderr.
|
||||||
|
|
||||||
# EXEC_FORWARD_OUTPUT="true"
|
# EXEC_FORWARD_OUTPUT="false"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Without any further configuration, all commands defined in labels will be
|
# Without any further configuration, all commands defined in labels will be
|
||||||
# run before and after a backup. If you need more fine grained control, you
|
# run before and after a backup. If you need more fine grained control, you
|
||||||
# can use this option to set a label that will be used for narrowing down
|
# can use this option to set a label that will be used for narrowing down
|
||||||
# the set of eligible containers. When set, an eligible container will also need
|
# the set of eligible containers. E.g. when setting this to `database`,
|
||||||
# to be labeled as `docker-volume-backup.exec-label=database`.
|
# an eligible container will also need to be labeled as `docker-volume-backup.exec-label=database`.
|
||||||
|
|
||||||
# EXEC_LABEL="database"
|
# EXEC_LABEL=""
|
||||||
|
|
||||||
########### NOTIFICATIONS
|
########### NOTIFICATIONS
|
||||||
|
|
||||||
@@ -397,10 +515,13 @@ You can populate below template according to your requirements and use it as you
|
|||||||
# on how to do this can be found in the README. When providing multiple URLs or
|
# on how to do this can be found in the README. When providing multiple URLs or
|
||||||
# an URL that contains a comma, the values can be URL encoded to avoid ambiguities.
|
# an URL that contains a comma, the values can be URL encoded to avoid ambiguities.
|
||||||
|
|
||||||
# The below URL demonstrates how to send an email using the provided SMTP
|
# The following example URL demonstrates how to send an email using the provided SMTP
|
||||||
# configuration and credentials.
|
# configuration and credentials.
|
||||||
|
# Example: "smtp://username:password@host:587/?fromAddress=sender@example.com&toAddresses=recipient@example.com"
|
||||||
|
|
||||||
# NOTIFICATION_URLS=smtp://username:password@host:587/?fromAddress=sender@example.com&toAddresses=recipient@example.com
|
# NOTIFICATION_URLS=""
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# By default, notifications would only be sent out when a backup run fails
|
# By default, notifications would only be sent out when a backup run fails
|
||||||
# To receive notifications for every run, set `NOTIFICATION_LEVEL` to `info`
|
# To receive notifications for every run, set `NOTIFICATION_LEVEL` to `info`
|
||||||
@@ -412,8 +533,9 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# If you are interfacing with Docker via TCP you can set the Docker host here
|
# If you are interfacing with Docker via TCP you can set the Docker host here
|
||||||
# instead of mounting the Docker socket as a volume. This is unset by default.
|
# instead of mounting the Docker socket as a volume. This is unset by default.
|
||||||
|
# Example: "tcp://docker_socket_proxy:2375"
|
||||||
|
|
||||||
# DOCKER_HOST="tcp://docker_socket_proxy:2375"
|
# DOCKER_HOST=""
|
||||||
|
|
||||||
########### LOCK_TIMEOUT
|
########### LOCK_TIMEOUT
|
||||||
|
|
||||||
@@ -440,20 +562,25 @@ You can populate below template according to your requirements and use it as you
|
|||||||
# The recipient(s) of the notification. Supply a comma separated list
|
# The recipient(s) of the notification. Supply a comma separated list
|
||||||
# of addresses if you want to notify multiple recipients. If this is
|
# of addresses if you want to notify multiple recipients. If this is
|
||||||
# not set, no emails will be sent.
|
# not set, no emails will be sent.
|
||||||
|
# Example: "you@example.com"
|
||||||
|
|
||||||
# EMAIL_NOTIFICATION_RECIPIENT="you@example.com"
|
# EMAIL_NOTIFICATION_RECIPIENT=""
|
||||||
|
|
||||||
# The "From" header of the sent email. Defaults to `noreply@nohost`.
|
# ---
|
||||||
|
|
||||||
# EMAIL_NOTIFICATION_SENDER="no-reply@example.com"
|
# The "From" header of the sent email.
|
||||||
|
# Example: "no-reply@example.com"
|
||||||
|
|
||||||
|
# EMAIL_NOTIFICATION_SENDER="noreply@nohost"
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
# Configuration and credentials for the SMTP server to be used.
|
# Configuration and credentials for the SMTP server to be used.
|
||||||
# EMAIL_SMTP_PORT defaults to 587.
|
|
||||||
|
|
||||||
# EMAIL_SMTP_HOST="posteo.de"
|
# EMAIL_SMTP_HOST=""
|
||||||
# EMAIL_SMTP_PASSWORD="<xxx>"
|
# EMAIL_SMTP_PASSWORD=""
|
||||||
# EMAIL_SMTP_USERNAME="no-reply@example.com"
|
# EMAIL_SMTP_USERNAME=""
|
||||||
# EMAIL_SMTP_PORT="<port>"
|
# EMAIL_SMTP_PORT="587"
|
||||||
```
|
```
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
|
|
||||||
|
|||||||
46
go.mod
46
go.mod
@@ -1,31 +1,33 @@
|
|||||||
module github.com/offen/docker-volume-backup
|
module github.com/offen/docker-volume-backup
|
||||||
|
|
||||||
go 1.22
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
|
filippo.io/age v1.2.1
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
|
||||||
github.com/containrrr/shoutrrr v0.8.0
|
github.com/containrrr/shoutrrr v0.8.0
|
||||||
github.com/cosiner/argv v0.1.0
|
github.com/cosiner/argv v0.1.0
|
||||||
github.com/docker/cli v27.1.1+incompatible
|
github.com/docker/cli v28.0.0+incompatible
|
||||||
github.com/docker/docker v27.1.1+incompatible
|
github.com/docker/docker v27.1.1+incompatible
|
||||||
github.com/gofrs/flock v0.12.1
|
github.com/gofrs/flock v0.12.1
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/klauspost/compress v1.17.9
|
github.com/klauspost/compress v1.18.0
|
||||||
github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d
|
github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d
|
||||||
github.com/minio/minio-go/v7 v7.0.74
|
github.com/minio/minio-go/v7 v7.0.87
|
||||||
github.com/offen/envconfig v1.5.0
|
github.com/offen/envconfig v1.5.0
|
||||||
github.com/otiai10/copy v1.14.0
|
github.com/otiai10/copy v1.14.1
|
||||||
github.com/pkg/sftp v1.13.6
|
github.com/pkg/sftp v1.13.7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/studio-b12/gowebdav v0.9.0
|
github.com/studio-b12/gowebdav v0.10.0
|
||||||
golang.org/x/crypto v0.25.0
|
golang.org/x/crypto v0.33.0
|
||||||
golang.org/x/oauth2 v0.22.0
|
golang.org/x/oauth2 v0.27.0
|
||||||
golang.org/x/sync v0.8.0
|
golang.org/x/sync v0.11.0
|
||||||
mvdan.cc/sh/v3 v3.8.0
|
mvdan.cc/sh/v3 v3.10.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
@@ -34,10 +36,12 @@ require (
|
|||||||
github.com/go-ini/ini v1.67.0 // indirect
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/otiai10/mint v1.6.3 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
go.opentelemetry.io/otel v1.26.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect
|
||||||
@@ -50,9 +54,9 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
|
||||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.1.0-alpha.1
|
github.com/ProtonMail/go-crypto v1.1.0-alpha.1
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
@@ -62,7 +66,7 @@ require (
|
|||||||
github.com/fatih/color v1.17.0 // indirect
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
github.com/klauspost/pgzip v1.2.6
|
github.com/klauspost/pgzip v1.2.6
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
@@ -75,10 +79,10 @@ require (
|
|||||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/rs/xid v1.5.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
golang.org/x/net v0.27.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
gotest.tools/v3 v3.0.3 // indirect
|
gotest.tools/v3 v3.0.3 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
142
go.sum
142
go.sum
@@ -1,3 +1,5 @@
|
|||||||
|
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
|
||||||
|
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
@@ -31,20 +33,28 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww=
|
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
|
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||||
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0 h1:Be6KInmFEKV81c0pOAEbRYehLMwmmGI1exuFj248AMk=
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0/go.mod h1:WCPBHsOXfBVnivScjs2ypRfimjEW0qPVLGgJkZlrIOA=
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
|
||||||
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
||||||
@@ -54,6 +64,8 @@ github.com/ProtonMail/go-crypto v1.1.0-alpha.1/go.mod h1:rA3QumHc/FZ8pAHreoekgiA
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -70,10 +82,12 @@ github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
|
github.com/docker/cli v28.0.0+incompatible h1:ido37VmLUqEp+5NFb9icd6BuBB+SNDgCn+5kPCr2buA=
|
||||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v28.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
@@ -92,8 +106,6 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
|||||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@@ -104,10 +116,12 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
|||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
@@ -178,13 +192,15 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
|
||||||
|
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
@@ -206,10 +222,12 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY=
|
||||||
|
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0=
|
github.com/minio/minio-go/v7 v7.0.87 h1:nkr9x0u53PespfxfUqxP3UYWiE2a41gaofgNnC4Y8WQ=
|
||||||
github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8=
|
github.com/minio/minio-go/v7 v7.0.87/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
|
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
|
||||||
@@ -226,27 +244,29 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
|||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
|
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
|
||||||
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
|
||||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
|
||||||
|
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
@@ -260,10 +280,10 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU=
|
github.com/studio-b12/gowebdav v0.10.0 h1:Yewz8FFiadcGEu4hxS/AAJQlHelndqln1bns3hcJIYc=
|
||||||
github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
github.com/studio-b12/gowebdav v0.10.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -296,9 +316,9 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@@ -330,6 +350,7 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
|||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -359,17 +380,18 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
|
|||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -380,8 +402,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -420,22 +443,28 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -485,8 +514,9 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
|
|||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
|
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -591,8 +621,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
mvdan.cc/sh/v3 v3.8.0 h1:ZxuJipLZwr/HLbASonmXtcvvC9HXY9d2lXZHnKGjFc8=
|
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
|
||||||
mvdan.cc/sh/v3 v3.8.0/go.mod h1:w04623xkgBVo7/IUK89E0g8hBykgEpN0vgOj3RJr6MY=
|
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func Wrap(err error, msg string) error {
|
|||||||
chunks := strings.Split(frame.Function, "/")
|
chunks := strings.Split(frame.Function, "/")
|
||||||
withCaller := fmt.Sprintf("%s: %s", chunks[len(chunks)-1], msg)
|
withCaller := fmt.Sprintf("%s: %s", chunks[len(chunks)-1], msg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Errorf(withCaller)
|
return errors.New(withCaller)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s: %w", withCaller, err)
|
return fmt.Errorf("%s: %w", withCaller, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -39,11 +40,15 @@ type Config struct {
|
|||||||
ConnectionString string
|
ConnectionString string
|
||||||
Endpoint string
|
Endpoint string
|
||||||
RemotePath string
|
RemotePath string
|
||||||
AccessTier *blob.AccessTier
|
AccessTier string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStorageBackend creates and initializes a new Azure Blob Storage backend.
|
// NewStorageBackend creates and initializes a new Azure Blob Storage backend.
|
||||||
func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error) {
|
func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error) {
|
||||||
|
if opts.PrimaryAccountKey != "" && opts.ConnectionString != "" {
|
||||||
|
return nil, errwrap.Wrap(nil, "using primary account key and connection string are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
endpointTemplate, err := template.New("endpoint").Parse(opts.Endpoint)
|
endpointTemplate, err := template.New("endpoint").Parse(opts.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrap(err, "error parsing endpoint template")
|
return nil, errwrap.Wrap(err, "error parsing endpoint template")
|
||||||
@@ -81,11 +86,25 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uploadStreamOptions *blockblob.UploadStreamOptions
|
||||||
|
if opts.AccessTier != "" {
|
||||||
|
var found bool
|
||||||
|
for _, t := range blob.PossibleAccessTierValues() {
|
||||||
|
if string(t) == opts.AccessTier {
|
||||||
|
found = true
|
||||||
|
uploadStreamOptions = &blockblob.UploadStreamOptions{
|
||||||
|
AccessTier: &t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, errwrap.Wrap(nil, fmt.Sprintf("%s is not a possible access tier value", opts.AccessTier))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
storage := azureBlobStorage{
|
storage := azureBlobStorage{
|
||||||
client: client,
|
client: client,
|
||||||
uploadStreamOptions: &blockblob.UploadStreamOptions{
|
uploadStreamOptions: uploadStreamOptions,
|
||||||
AccessTier: opts.AccessTier,
|
|
||||||
},
|
|
||||||
containerName: opts.ContainerName,
|
containerName: opts.ContainerName,
|
||||||
StorageBackend: &storage.StorageBackend{
|
StorageBackend: &storage.StorageBackend{
|
||||||
DestinationPath: opts.RemotePath,
|
DestinationPath: opts.RemotePath,
|
||||||
@@ -110,7 +129,7 @@ func (b *azureBlobStorage) Copy(file string) error {
|
|||||||
_, err = b.client.UploadStream(
|
_, err = b.client.UploadStream(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
b.containerName,
|
b.containerName,
|
||||||
filepath.Join(b.DestinationPath, filepath.Base(file)),
|
path.Join(b.DestinationPath, filepath.Base(file)),
|
||||||
fileReader,
|
fileReader,
|
||||||
b.uploadStreamOptions,
|
b.uploadStreamOptions,
|
||||||
)
|
)
|
||||||
@@ -123,7 +142,7 @@ func (b *azureBlobStorage) Copy(file string) error {
|
|||||||
// Prune rotates away backups according to the configuration and provided
|
// Prune rotates away backups according to the configuration and provided
|
||||||
// deadline for the Azure Blob storage backend.
|
// deadline for the Azure Blob storage backend.
|
||||||
func (b *azureBlobStorage) Prune(deadline time.Time, pruningPrefix string) (*storage.PruneStats, error) {
|
func (b *azureBlobStorage) Prune(deadline time.Time, pruningPrefix string) (*storage.PruneStats, error) {
|
||||||
lookupPrefix := filepath.Join(b.DestinationPath, pruningPrefix)
|
lookupPrefix := path.Join(b.DestinationPath, pruningPrefix)
|
||||||
pager := b.client.NewListBlobsFlatPager(b.containerName, &container.ListBlobsFlatOptions{
|
pager := b.client.NewListBlobsFlatPager(b.containerName, &container.ListBlobsFlatOptions{
|
||||||
Prefix: &lookupPrefix,
|
Prefix: &lookupPrefix,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -195,7 +194,7 @@ loop:
|
|||||||
_, err = b.client.UploadSessionFinish(
|
_, err = b.client.UploadSessionFinish(
|
||||||
files.NewUploadSessionFinishArg(
|
files.NewUploadSessionFinishArg(
|
||||||
files.NewUploadSessionCursor(sessionId, 0),
|
files.NewUploadSessionCursor(sessionId, 0),
|
||||||
files.NewCommitInfo(filepath.Join(b.DestinationPath, name)),
|
files.NewCommitInfo(path.Join(b.DestinationPath, name)),
|
||||||
), nil)
|
), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, "error finishing the upload session")
|
return errwrap.Wrap(err, "error finishing the upload session")
|
||||||
@@ -247,7 +246,7 @@ func (b *dropboxStorage) Prune(deadline time.Time, pruningPrefix string) (*stora
|
|||||||
|
|
||||||
pruneErr := b.DoPrune(b.Name(), len(matches), lenCandidates, deadline, func() error {
|
pruneErr := b.DoPrune(b.Name(), len(matches), lenCandidates, deadline, func() error {
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
if _, err := b.client.DeleteV2(files.NewDeleteArg(filepath.Join(b.DestinationPath, match.Name))); err != nil {
|
if _, err := b.client.DeleteV2(files.NewDeleteArg(path.Join(b.DestinationPath, match.Name))); err != nil {
|
||||||
return errwrap.Wrap(err, "error removing file from Dropbox storage")
|
return errwrap.Wrap(err, "error removing file from Dropbox storage")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func (b *localStorage) Prune(deadline time.Time, pruningPrefix string) (*storage
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
|
if !fi.IsDir() && fi.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||||
candidates = append(candidates, candidate)
|
candidates = append(candidates, candidate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
@@ -124,7 +123,7 @@ func (b *s3Storage) Copy(file string) error {
|
|||||||
putObjectOptions.PartSize = uint64(partSize)
|
putObjectOptions.PartSize = uint64(partSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := b.client.FPutObject(context.Background(), b.bucket, filepath.Join(b.DestinationPath, name), file, putObjectOptions); err != nil {
|
if _, err := b.client.FPutObject(context.Background(), b.bucket, path.Join(b.DestinationPath, name), file, putObjectOptions); err != nil {
|
||||||
if errResp := minio.ToErrorResponse(err); errResp.Message != "" {
|
if errResp := minio.ToErrorResponse(err); errResp.Message != "" {
|
||||||
return errwrap.Wrap(
|
return errwrap.Wrap(
|
||||||
nil,
|
nil,
|
||||||
@@ -147,7 +146,7 @@ func (b *s3Storage) Copy(file string) error {
|
|||||||
// Prune rotates away backups according to the configuration and provided deadline for the S3/Minio storage backend.
|
// Prune rotates away backups according to the configuration and provided deadline for the S3/Minio storage backend.
|
||||||
func (b *s3Storage) Prune(deadline time.Time, pruningPrefix string) (*storage.PruneStats, error) {
|
func (b *s3Storage) Prune(deadline time.Time, pruningPrefix string) (*storage.PruneStats, error) {
|
||||||
candidates := b.client.ListObjects(context.Background(), b.bucket, minio.ListObjectsOptions{
|
candidates := b.client.ListObjects(context.Background(), b.bucket, minio.ListObjectsOptions{
|
||||||
Prefix: filepath.Join(b.DestinationPath, pruningPrefix),
|
Prefix: path.Join(b.DestinationPath, pruningPrefix),
|
||||||
Recursive: true,
|
Recursive: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -115,7 +114,7 @@ func (b *sshStorage) Copy(file string) error {
|
|||||||
}
|
}
|
||||||
defer source.Close()
|
defer source.Close()
|
||||||
|
|
||||||
destination, err := b.sftpClient.Create(filepath.Join(b.DestinationPath, name))
|
destination, err := b.sftpClient.Create(path.Join(b.DestinationPath, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, "error creating file")
|
return errwrap.Wrap(err, "error creating file")
|
||||||
}
|
}
|
||||||
@@ -164,24 +163,28 @@ func (b *sshStorage) Prune(deadline time.Time, pruningPrefix string) (*storage.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
var matches []string
|
var matches []string
|
||||||
|
var numCandidates int
|
||||||
for _, candidate := range candidates {
|
for _, candidate := range candidates {
|
||||||
if !strings.HasPrefix(candidate.Name(), pruningPrefix) {
|
if candidate.IsDir() || !strings.HasPrefix(candidate.Name(), pruningPrefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
numCandidates++
|
||||||
if candidate.ModTime().Before(deadline) {
|
if candidate.ModTime().Before(deadline) {
|
||||||
matches = append(matches, candidate.Name())
|
matches = append(matches, candidate.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := &storage.PruneStats{
|
stats := &storage.PruneStats{
|
||||||
Total: uint(len(candidates)),
|
Total: uint(numCandidates),
|
||||||
Pruned: uint(len(matches)),
|
Pruned: uint(len(matches)),
|
||||||
}
|
}
|
||||||
|
|
||||||
pruneErr := b.DoPrune(b.Name(), len(matches), len(candidates), deadline, func() error {
|
pruneErr := b.DoPrune(b.Name(), len(matches), numCandidates, deadline, func() error {
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
if err := b.sftpClient.Remove(filepath.Join(b.DestinationPath, match)); err != nil {
|
p := path.Join(b.DestinationPath, match)
|
||||||
return errwrap.Wrap(err, "error removing file")
|
if err := b.sftpClient.Remove(p); err != nil {
|
||||||
|
return errwrap.Wrap(err, fmt.Sprintf("error removing file %s", p))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ func (b *webDavStorage) Copy(file string) error {
|
|||||||
return errwrap.Wrap(err, "error opening the file to be uploaded")
|
return errwrap.Wrap(err, "error opening the file to be uploaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.client.WriteStream(filepath.Join(b.DestinationPath, name), r, 0644); err != nil {
|
if err := b.client.WriteStream(path.Join(b.DestinationPath, name), r, 0644); err != nil {
|
||||||
return errwrap.Wrap(err, "error uploading the file")
|
return errwrap.Wrap(err, "error uploading the file")
|
||||||
}
|
}
|
||||||
b.Log(storage.LogLevelInfo, b.Name(), "Uploaded a copy of backup '%s' to '%s' at path '%s'.", file, b.url, b.DestinationPath)
|
b.Log(storage.LogLevelInfo, b.Name(), "Uploaded a copy of backup '%s' to '%s' at path '%s'.", file, b.url, b.DestinationPath)
|
||||||
@@ -91,26 +90,27 @@ func (b *webDavStorage) Prune(deadline time.Time, pruningPrefix string) (*storag
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrap(err, "error looking up candidates from remote storage")
|
return nil, errwrap.Wrap(err, "error looking up candidates from remote storage")
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches []fs.FileInfo
|
var matches []fs.FileInfo
|
||||||
var lenCandidates int
|
var numCandidates int
|
||||||
for _, candidate := range candidates {
|
for _, candidate := range candidates {
|
||||||
if !strings.HasPrefix(candidate.Name(), pruningPrefix) {
|
if candidate.IsDir() || !strings.HasPrefix(candidate.Name(), pruningPrefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
lenCandidates++
|
numCandidates++
|
||||||
if candidate.ModTime().Before(deadline) {
|
if candidate.ModTime().Before(deadline) {
|
||||||
matches = append(matches, candidate)
|
matches = append(matches, candidate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats := &storage.PruneStats{
|
stats := &storage.PruneStats{
|
||||||
Total: uint(lenCandidates),
|
Total: uint(numCandidates),
|
||||||
Pruned: uint(len(matches)),
|
Pruned: uint(len(matches)),
|
||||||
}
|
}
|
||||||
|
|
||||||
pruneErr := b.DoPrune(b.Name(), len(matches), lenCandidates, deadline, func() error {
|
pruneErr := b.DoPrune(b.Name(), len(matches), numCandidates, deadline, func() error {
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
if err := b.client.Remove(filepath.Join(b.DestinationPath, match.Name())); err != nil {
|
if err := b.client.Remove(path.Join(b.DestinationPath, match.Name())); err != nil {
|
||||||
return errwrap.Wrap(err, "error removing file")
|
return errwrap.Wrap(err, "error removing file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
FROM docker:27-dind
|
FROM docker:27-dind
|
||||||
|
|
||||||
RUN apk add \
|
RUN apk add \
|
||||||
|
age \
|
||||||
coreutils \
|
coreutils \
|
||||||
curl \
|
curl \
|
||||||
|
expect \
|
||||||
gpg \
|
gpg \
|
||||||
gpg-agent \
|
gpg-agent \
|
||||||
jq \
|
jq \
|
||||||
|
|||||||
24
test/age-passphrase/docker-compose.yml
Normal file
24
test/age-passphrase/docker-compose.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
services:
|
||||||
|
backup:
|
||||||
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ?
|
||||||
|
BACKUP_FILENAME: test.tar.gz
|
||||||
|
BACKUP_LATEST_SYMLINK: test-latest.tar.gz.age
|
||||||
|
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7}
|
||||||
|
AGE_PASSPHRASE: "Dance.0Tonight.Go.Typical"
|
||||||
|
volumes:
|
||||||
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
|
- app_data:/backup/app_data:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
|
offen:
|
||||||
|
image: offen/offen:latest
|
||||||
|
labels:
|
||||||
|
- docker-volume-backup.stop-during-backup=true
|
||||||
|
volumes:
|
||||||
|
- app_data:/var/opt/offen
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
app_data:
|
||||||
39
test/age-passphrase/run.sh
Executable file
39
test/age-passphrase/run.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
. ../util.sh
|
||||||
|
current_test=$(basename "$(pwd)")
|
||||||
|
|
||||||
|
export LOCAL_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
docker compose up -d --quiet-pull
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
docker compose exec backup backup
|
||||||
|
|
||||||
|
expect_running_containers "2"
|
||||||
|
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
|
|
||||||
|
# complex usage of expect(1) due to age not have a way to programmatically
|
||||||
|
# provide the passphrase
|
||||||
|
expect -i <<EOL
|
||||||
|
spawn age --decrypt -o "$LOCAL_DIR/decrypted.tar.gz" "$LOCAL_DIR/test.tar.gz.age"
|
||||||
|
expect -exact "Enter passphrase: "
|
||||||
|
send -- "Dance.0Tonight.Go.Typical\r"
|
||||||
|
sleep 1
|
||||||
|
EOL
|
||||||
|
tar -xf "$LOCAL_DIR/decrypted.tar.gz" -C "$TMP_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$TMP_DIR/backup/app_data/offen.db" ]; then
|
||||||
|
fail "Could not find expected file in untared archive."
|
||||||
|
fi
|
||||||
|
rm -vf "$LOCAL_DIR/decrypted.tar.gz"
|
||||||
|
|
||||||
|
pass "Found relevant files in decrypted and untared local backup."
|
||||||
|
|
||||||
|
if [ ! -L "$LOCAL_DIR/test-latest.tar.gz.age" ]; then
|
||||||
|
fail "Could not find local symlink to latest encrypted backup."
|
||||||
|
fi
|
||||||
1
test/age-publickey/.gitignore
vendored
Normal file
1
test/age-publickey/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pk-*.txt
|
||||||
24
test/age-publickey/docker-compose.yml
Normal file
24
test/age-publickey/docker-compose.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
services:
|
||||||
|
backup:
|
||||||
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ?
|
||||||
|
BACKUP_FILENAME: test.tar.gz
|
||||||
|
BACKUP_LATEST_SYMLINK: test-latest.tar.gz.age
|
||||||
|
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7}
|
||||||
|
AGE_PUBLIC_KEYS: "${BACKUP_AGE_PUBLIC_KEYS}"
|
||||||
|
volumes:
|
||||||
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
|
- app_data:/backup/app_data:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
|
offen:
|
||||||
|
image: offen/offen:latest
|
||||||
|
labels:
|
||||||
|
- docker-volume-backup.stop-during-backup=true
|
||||||
|
volumes:
|
||||||
|
- app_data:/var/opt/offen
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
app_data:
|
||||||
47
test/age-publickey/run.sh
Executable file
47
test/age-publickey/run.sh
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
. ../util.sh
|
||||||
|
current_test=$(basename "$(pwd)")
|
||||||
|
|
||||||
|
export LOCAL_DIR="$(mktemp -d)"
|
||||||
|
|
||||||
|
age-keygen >"$LOCAL_DIR/pk-a.txt"
|
||||||
|
PK_A="$(grep -E 'public key' <"$LOCAL_DIR/pk-a.txt" | cut -d: -f2 | xargs)"
|
||||||
|
age-keygen >"$LOCAL_DIR/pk-b.txt"
|
||||||
|
PK_B="$(grep -E 'public key' <"$LOCAL_DIR/pk-b.txt" | cut -d: -f2 | xargs)"
|
||||||
|
|
||||||
|
ssh-keygen -t ed25519 -m pem -f "$LOCAL_DIR/id_ed25519" -C "docker-volume-backup@local"
|
||||||
|
PK_C="$(cat $LOCAL_DIR/id_ed25519.pub)"
|
||||||
|
|
||||||
|
export BACKUP_AGE_PUBLIC_KEYS="$PK_A,$PK_B,$PK_C"
|
||||||
|
|
||||||
|
docker compose up -d --quiet-pull
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
docker compose exec backup backup
|
||||||
|
|
||||||
|
expect_running_containers "2"
|
||||||
|
|
||||||
|
do_decrypt() {
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
|
age --decrypt -i "$1" -o "$LOCAL_DIR/decrypted.tar.gz" "$LOCAL_DIR/test.tar.gz.age"
|
||||||
|
tar -xf "$LOCAL_DIR/decrypted.tar.gz" -C "$TMP_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$TMP_DIR/backup/app_data/offen.db" ]; then
|
||||||
|
fail "Could not find expected file in untared archive."
|
||||||
|
fi
|
||||||
|
rm -vf "$LOCAL_DIR/decrypted.tar.gz"
|
||||||
|
|
||||||
|
pass "Found relevant files in decrypted and untared local backup."
|
||||||
|
|
||||||
|
if [ ! -L "$LOCAL_DIR/test-latest.tar.gz.age" ]; then
|
||||||
|
fail "Could not find local symlink to latest encrypted backup."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
do_decrypt "$LOCAL_DIR/pk-a.txt"
|
||||||
|
do_decrypt "$LOCAL_DIR/pk-b.txt"
|
||||||
|
do_decrypt "$LOCAL_DIR/id_ed25519"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
storage:
|
storage:
|
||||||
image: mcr.microsoft.com/azure-storage/azurite:3.31.0
|
image: mcr.microsoft.com/azure-storage/azurite:3.33.0
|
||||||
volumes:
|
volumes:
|
||||||
- ${DATA_DIR:-./data}:/data
|
- ${DATA_DIR:-./data}:/data
|
||||||
command: azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --location /data
|
command: azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --location /data
|
||||||
@@ -42,7 +42,7 @@ services:
|
|||||||
BACKUP_PRUNING_PREFIX: test
|
BACKUP_PRUNING_PREFIX: test
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ services:
|
|||||||
BACKUP_PRUNING_LEEWAY: 5s
|
BACKUP_PRUNING_LEEWAY: 5s
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ${CERT_DIR:-.}/rootCA.crt:/root/minio-rootCA.crt
|
- ${CERT_DIR:-.}/rootCA.crt:/root/minio-rootCA.crt
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ docker run --rm -q \
|
|||||||
--network test_network \
|
--network test_network \
|
||||||
-v app_data:/backup/app_data \
|
-v app_data:/backup/app_data \
|
||||||
-v empty_data:/backup/empty_data \
|
-v empty_data:/backup/empty_data \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||||
--env AWS_ACCESS_KEY_ID=test \
|
--env AWS_ACCESS_KEY_ID=test \
|
||||||
--env AWS_SECRET_ACCESS_KEY=GMusLtUmILge2by+z890kQ \
|
--env AWS_SECRET_ACCESS_KEY=GMusLtUmILge2by+z890kQ \
|
||||||
--env AWS_ENDPOINT=minio:9000 \
|
--env AWS_ENDPOINT=minio:9000 \
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- offen_data:/backup/offen_data:ro
|
- offen_data:/backup/offen_data:ro
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
- app_data:/backup/data:ro
|
- app_data:/backup/data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
app_data:
|
app_data:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ services:
|
|||||||
- ./01backup.env:/etc/dockervolumebackup/conf.d/01backup.env
|
- ./01backup.env:/etc/dockervolumebackup/conf.d/01backup.env
|
||||||
- ./02backup.env:/etc/dockervolumebackup/conf.d/02backup.env
|
- ./02backup.env:/etc/dockervolumebackup/conf.d/02backup.env
|
||||||
- ./03never.env:/etc/dockervolumebackup/conf.d/03never.env
|
- ./03never.env:/etc/dockervolumebackup/conf.d/03never.env
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ services:
|
|||||||
DROPBOX_CONCURRENCY_LEVEL: 6
|
DROPBOX_CONCURRENCY_LEVEL: 6
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${LOCAL_DIR:-local}:/local
|
- ${LOCAL_DIR:-local}:/local
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ services:
|
|||||||
- ${KEY_DIR:-.}/public_key.asc:/keys/public_key.asc
|
- ${KEY_DIR:-.}/public_key.asc:/keys/public_key.asc
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ services:
|
|||||||
BACKUP_PRUNING_PREFIX: test
|
BACKUP_PRUNING_PREFIX: test
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
BACKUP_RETENTION_DAYS: '7'
|
BACKUP_RETENTION_DAYS: '7'
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ docker run --rm -q \
|
|||||||
--network test_network \
|
--network test_network \
|
||||||
-v app_data:/backup/app_data \
|
-v app_data:/backup/app_data \
|
||||||
-v $LOCAL_DIR:/archive \
|
-v $LOCAL_DIR:/archive \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||||
--env BACKUP_COMPRESSION=gz \
|
--env BACKUP_COMPRESSION=gz \
|
||||||
--env GZIP_PARALLELISM=0 \
|
--env GZIP_PARALLELISM=0 \
|
||||||
--env BACKUP_FILENAME='test.{{ .Extension }}' \
|
--env BACKUP_FILENAME='test.{{ .Extension }}' \
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ services:
|
|||||||
TASKS: ${ALLOW_TASKS:-1}
|
TASKS: ${ALLOW_TASKS:-1}
|
||||||
NODES: ${ALLOW_NODES:-1}
|
NODES: ${ALLOW_NODES:-1}
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
pg:
|
pg:
|
||||||
image: postgres:14-alpine
|
image: postgres:14-alpine
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ services:
|
|||||||
CONTAINERS: ${ALLOW_CONTAINERS:-1}
|
CONTAINERS: ${ALLOW_CONTAINERS:-1}
|
||||||
POST: ${ALLOW_POST:-1}
|
POST: ${ALLOW_POST:-1}
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
pg:
|
pg:
|
||||||
image: postgres:14-alpine
|
image: postgres:14-alpine
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ services:
|
|||||||
BACKUP_SKIP_BACKENDS_FROM_PRUNE: 's3'
|
BACKUP_SKIP_BACKENDS_FROM_PRUNE: 's3'
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ services:
|
|||||||
BACKUP_PRUNING_PREFIX: test
|
BACKUP_PRUNING_PREFIX: test
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ services:
|
|||||||
BACKUP_PRUNING_LEEWAY: 5s
|
BACKUP_PRUNING_LEEWAY: 5s
|
||||||
volumes:
|
volumes:
|
||||||
- pg_data:/backup/pg_data:ro
|
- pg_data:/backup/pg_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
secrets:
|
secrets:
|
||||||
- minio_root_user
|
- minio_root_user
|
||||||
- minio_root_password
|
- minio_root_password
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ services:
|
|||||||
BACKUP_PRUNING_LEEWAY: 5s
|
BACKUP_PRUNING_LEEWAY: 5s
|
||||||
volumes:
|
volumes:
|
||||||
- pg_data:/backup/pg_data:ro
|
- pg_data:/backup/pg_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${KEY_DIR:-.}/id_rsa:/root/.ssh/id_rsa
|
- ${KEY_DIR:-.}/id_rsa:/root/.ssh/id_rsa
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ services:
|
|||||||
BACKUP_PRUNING_LEEWAY: 5s
|
BACKUP_PRUNING_LEEWAY: 5s
|
||||||
volumes:
|
volumes:
|
||||||
- pg_data:/backup/pg_data:ro
|
- pg_data:/backup/pg_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
BACKUP_COMPRESSION: none
|
BACKUP_COMPRESSION: none
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ${LOCAL_DIR:-./local}:/archive
|
- ${LOCAL_DIR:-./local}:/archive
|
||||||
- app_data:/backup/data:ro
|
- app_data:/backup/data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
app_data:
|
app_data:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ services:
|
|||||||
WEBDAV_PASSWORD: test
|
WEBDAV_PASSWORD: test
|
||||||
volumes:
|
volumes:
|
||||||
- app_data:/backup/app_data:ro
|
- app_data:/backup/app_data:ro
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
offen:
|
offen:
|
||||||
image: offen/offen:latest
|
image: offen/offen:latest
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ docker run --rm -q \
|
|||||||
--network test_network \
|
--network test_network \
|
||||||
-v app_data:/backup/app_data \
|
-v app_data:/backup/app_data \
|
||||||
-v $LOCAL_DIR:/archive \
|
-v $LOCAL_DIR:/archive \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||||
--env BACKUP_COMPRESSION=zst \
|
--env BACKUP_COMPRESSION=zst \
|
||||||
--env BACKUP_FILENAME='test.{{ .Extension }}' \
|
--env BACKUP_FILENAME='test.{{ .Extension }}' \
|
||||||
--entrypoint backup \
|
--entrypoint backup \
|
||||||
|
|||||||
Reference in New Issue
Block a user