mirror of
https://github.com/offen/docker-volume-backup.git
synced 2025-12-06 01:28:03 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb2ae1893 | ||
|
|
95bf34280d | ||
|
|
186953f666 | ||
|
|
16b3a7170a |
@@ -177,10 +177,20 @@ func (s *script) init() error {
|
|||||||
IdentityPassphrase: s.c.SSHIdentityPassphrase,
|
IdentityPassphrase: s.c.SSHIdentityPassphrase,
|
||||||
RemotePath: s.c.SSHRemotePath,
|
RemotePath: s.c.SSHRemotePath,
|
||||||
}
|
}
|
||||||
sshBackend, err := ssh.NewStorageBackend(sshConfig, logFunc)
|
|
||||||
|
sshBackend, closeSSHConnection, err := ssh.NewStorageBackend(sshConfig, logFunc)
|
||||||
|
|
||||||
|
s.registerHook(hookLevelPlumbing, func(err error) error {
|
||||||
|
if err := closeSSHConnection(); err != nil {
|
||||||
|
return errwrap.Wrap(err, "failed to close ssh connection")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, "error creating ssh storage backend")
|
return errwrap.Wrap(err, "error creating ssh storage backend")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.storages = append(s.storages, sshBackend)
|
s.storages = append(s.storages, sshBackend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -14,7 +14,7 @@ require (
|
|||||||
github.com/klauspost/compress v1.18.1
|
github.com/klauspost/compress v1.18.1
|
||||||
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.95
|
github.com/minio/minio-go/v7 v7.0.95
|
||||||
github.com/nicholas-fedor/shoutrrr v0.11.0
|
github.com/nicholas-fedor/shoutrrr v0.11.1
|
||||||
github.com/offen/envconfig v1.5.0
|
github.com/offen/envconfig v1.5.0
|
||||||
github.com/otiai10/copy v1.14.1
|
github.com/otiai10/copy v1.14.1
|
||||||
github.com/pkg/sftp v1.13.10
|
github.com/pkg/sftp v1.13.10
|
||||||
@@ -23,7 +23,7 @@ require (
|
|||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.43.0
|
||||||
golang.org/x/oauth2 v0.32.0
|
golang.org/x/oauth2 v0.32.0
|
||||||
golang.org/x/sync v0.17.0
|
golang.org/x/sync v0.17.0
|
||||||
google.golang.org/api v0.253.0
|
google.golang.org/api v0.254.0
|
||||||
mvdan.cc/sh/v3 v3.12.0
|
mvdan.cc/sh/v3 v3.12.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ require (
|
|||||||
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
||||||
golang.org/x/term v0.36.0 // indirect
|
golang.org/x/term v0.36.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
|
||||||
google.golang.org/grpc v1.76.0 // indirect
|
google.golang.org/grpc v1.76.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
18
go.sum
18
go.sum
@@ -304,16 +304,16 @@ github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWam
|
|||||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/nicholas-fedor/shoutrrr v0.11.0 h1:hAMv2uM8OfFXkMHVP977elkP3Wgw5/YpVX5GxXQwiWA=
|
github.com/nicholas-fedor/shoutrrr v0.11.1 h1:DND1gW8UM8GYG8c0bUZ5fPFAnm3id8noPdfaFBUmezk=
|
||||||
github.com/nicholas-fedor/shoutrrr v0.11.0/go.mod h1:0kRF9ral22xUn/0BlxfhLQUeJDTySCPsuNvaclyagb4=
|
github.com/nicholas-fedor/shoutrrr v0.11.1/go.mod h1:RZuSZSEaSimS47zTOLXb6HJDwLjDHiuJ9SrzxsDcWaQ=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/offen/envconfig v1.5.0 h1:LHL4wYIDVeoGxSDI40MShmWfss3gYUlCdstfSiSq4Fk=
|
github.com/offen/envconfig v1.5.0 h1:LHL4wYIDVeoGxSDI40MShmWfss3gYUlCdstfSiSq4Fk=
|
||||||
github.com/offen/envconfig v1.5.0/go.mod h1:L7ny7R+4JWH3VVnZ+ARHvZysWUiZ2eQcm3L0imU9ACY=
|
github.com/offen/envconfig v1.5.0/go.mod h1:L7ny7R+4JWH3VVnZ+ARHvZysWUiZ2eQcm3L0imU9ACY=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
||||||
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
||||||
github.com/onsi/ginkgo/v2 v2.27.1 h1:0LJC8MpUSQnfnp4n/3W3GdlmJP3ENGF0ZPzjQGLPP7s=
|
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||||
github.com/onsi/ginkgo/v2 v2.27.1/go.mod h1:wmy3vCqiBjirARfVhAqFpYt8uvX0yaFe+GudAqqcCqA=
|
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||||
@@ -423,8 +423,6 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx
|
|||||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||||
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
|
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
|
||||||
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
|
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
|
||||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
|
||||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
@@ -637,8 +635,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
|||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
google.golang.org/api v0.253.0 h1:apU86Eq9Q2eQco3NsUYFpVTfy7DwemojL7LmbAj7g/I=
|
google.golang.org/api v0.254.0 h1:jl3XrGj7lRjnlUvZAbAdhINTLbsg5dbjmR90+pTQvt4=
|
||||||
google.golang.org/api v0.253.0/go.mod h1:PX09ad0r/4du83vZVAaGg7OaeyGnaUmT/CYPNvtLCbw=
|
google.golang.org/api v0.254.0/go.mod h1:5BkSURm3D9kAqjGvBNgf0EcbX6Rnrf6UArKkwBzAyqQ=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@@ -678,8 +676,8 @@ google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuO
|
|||||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
|||||||
@@ -36,8 +36,10 @@ type Config struct {
|
|||||||
RemotePath string
|
RemotePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var noop = func() error { return nil }
|
||||||
|
|
||||||
// NewStorageBackend creates and initializes a new SSH storage backend.
|
// NewStorageBackend creates and initializes a new SSH storage backend.
|
||||||
func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error) {
|
func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, func() error, error) {
|
||||||
var authMethods []ssh.AuthMethod
|
var authMethods []ssh.AuthMethod
|
||||||
|
|
||||||
if opts.Password != "" {
|
if opts.Password != "" {
|
||||||
@@ -47,20 +49,20 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error
|
|||||||
if _, err := os.Stat(opts.IdentityFile); err == nil {
|
if _, err := os.Stat(opts.IdentityFile); err == nil {
|
||||||
key, err := os.ReadFile(opts.IdentityFile)
|
key, err := os.ReadFile(opts.IdentityFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrap(nil, "error reading the private key")
|
return nil, noop, errwrap.Wrap(nil, "error reading the private key")
|
||||||
}
|
}
|
||||||
|
|
||||||
var signer ssh.Signer
|
var signer ssh.Signer
|
||||||
if opts.IdentityPassphrase != "" {
|
if opts.IdentityPassphrase != "" {
|
||||||
signer, err = ssh.ParsePrivateKeyWithPassphrase(key, []byte(opts.IdentityPassphrase))
|
signer, err = ssh.ParsePrivateKeyWithPassphrase(key, []byte(opts.IdentityPassphrase))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrap(nil, "error parsing the encrypted private key")
|
return nil, noop, errwrap.Wrap(nil, "error parsing the encrypted private key")
|
||||||
}
|
}
|
||||||
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
||||||
} else {
|
} else {
|
||||||
signer, err = ssh.ParsePrivateKey(key)
|
signer, err = ssh.ParsePrivateKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrap(nil, "error parsing the private key")
|
return nil, noop, errwrap.Wrap(nil, "error parsing the private key")
|
||||||
}
|
}
|
||||||
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
authMethods = append(authMethods, ssh.PublicKeys(signer))
|
||||||
}
|
}
|
||||||
@@ -72,13 +74,12 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error
|
|||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
}
|
}
|
||||||
sshClient, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", opts.HostName, opts.Port), sshClientConfig)
|
sshClient, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", opts.HostName, opts.Port), sshClientConfig)
|
||||||
|
if err != nil || sshClient == nil {
|
||||||
if err != nil {
|
return nil, noop, errwrap.Wrap(err, "error creating ssh client")
|
||||||
return nil, errwrap.Wrap(err, "error creating ssh client")
|
|
||||||
}
|
}
|
||||||
_, _, err = sshClient.SendRequest("keepalive", false, nil)
|
_, _, err = sshClient.SendRequest("keepalive", false, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, sshClient.Close, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sftpClient, err := sftp.NewClient(sshClient,
|
sftpClient, err := sftp.NewClient(sshClient,
|
||||||
@@ -87,7 +88,7 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error
|
|||||||
sftp.MaxConcurrentRequestsPerFile(64),
|
sftp.MaxConcurrentRequestsPerFile(64),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrap(err, "error creating sftp client")
|
return nil, sshClient.Close, errwrap.Wrap(err, "error creating sftp client")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &sshStorage{
|
return &sshStorage{
|
||||||
@@ -98,7 +99,7 @@ func NewStorageBackend(opts Config, logFunc storage.Log) (storage.Backend, error
|
|||||||
client: sshClient,
|
client: sshClient,
|
||||||
sftpClient: sftpClient,
|
sftpClient: sftpClient,
|
||||||
hostName: opts.HostName,
|
hostName: opts.HostName,
|
||||||
}, nil
|
}, sshClient.Close, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the name of the storage backend
|
// Name returns the name of the storage backend
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
hostname: hostnametoken
|
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
BACKUP_FILENAME_EXPAND: 'true'
|
BACKUP_FILENAME: test.tar.gz
|
||||||
BACKUP_FILENAME: test-$$HOSTNAME.tar.gz
|
|
||||||
BACKUP_LATEST_SYMLINK: test-$$HOSTNAME.latest.tar.gz.gpg
|
|
||||||
BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ?
|
BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ?
|
||||||
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7}
|
|
||||||
BACKUP_PRUNING_LEEWAY: 5s
|
|
||||||
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:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ export LOCAL_DIR=$(mktemp -d)
|
|||||||
docker compose up -d --quiet-pull
|
docker compose up -d --quiet-pull
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
# A symlink for a known file in the volume is created so the test can check
|
|
||||||
# whether symlinks are preserved on backup.
|
|
||||||
docker compose exec offen ln -s /var/opt/offen/offen.db /var/opt/offen/db.link
|
|
||||||
docker compose exec backup backup
|
docker compose exec backup backup
|
||||||
|
|
||||||
sleep 5
|
sleep 5
|
||||||
@@ -21,56 +18,7 @@ sleep 5
|
|||||||
expect_running_containers "1"
|
expect_running_containers "1"
|
||||||
|
|
||||||
tmp_dir=$(mktemp -d)
|
tmp_dir=$(mktemp -d)
|
||||||
tar -xvf "$LOCAL_DIR/test-hostnametoken.tar.gz" -C $tmp_dir
|
tar -xvf "$LOCAL_DIR/test.tar.gz" -C $tmp_dir
|
||||||
if [ ! -f "$tmp_dir/backup/app_data/offen.db" ]; then
|
if [ ! -f "$tmp_dir/backup/app_data/offen.db" ]; then
|
||||||
fail "Could not find expected file in untared archive."
|
fail "Could not find expected file in untared archive."
|
||||||
fi
|
fi
|
||||||
rm -f "$LOCAL_DIR/test-hostnametoken.tar.gz"
|
|
||||||
|
|
||||||
if [ ! -L "$tmp_dir/backup/app_data/db.link" ]; then
|
|
||||||
fail "Could not find expected symlink in untared archive."
|
|
||||||
fi
|
|
||||||
|
|
||||||
pass "Found relevant files in decrypted and untared local backup."
|
|
||||||
|
|
||||||
if [ ! -L "$LOCAL_DIR/test-hostnametoken.latest.tar.gz.gpg" ]; then
|
|
||||||
fail "Could not find symlink to latest version."
|
|
||||||
fi
|
|
||||||
|
|
||||||
pass "Found symlink to latest version in local backup."
|
|
||||||
|
|
||||||
# The second part of this test checks if backups get deleted when the retention
|
|
||||||
# is set to 0 days (which it should not as it would mean all backups get deleted)
|
|
||||||
BACKUP_RETENTION_DAYS="0" docker compose up -d
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
docker compose exec backup backup
|
|
||||||
|
|
||||||
if [ "$(find "$LOCAL_DIR" -type f | wc -l)" != "1" ]; then
|
|
||||||
fail "Backups should not have been deleted, instead seen: "$(find "$local_dir" -type f)""
|
|
||||||
fi
|
|
||||||
pass "Local backups have not been deleted."
|
|
||||||
|
|
||||||
# The third part of this test checks if old backups get deleted when the retention
|
|
||||||
# is set to 7 days (which it should)
|
|
||||||
|
|
||||||
BACKUP_RETENTION_DAYS="7" docker compose up -d
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
info "Create first backup with no prune"
|
|
||||||
docker compose exec backup backup
|
|
||||||
|
|
||||||
touch -r "$LOCAL_DIR/test-hostnametoken.tar.gz" -d "14 days ago" "$LOCAL_DIR/test-hostnametoken-old.tar.gz"
|
|
||||||
|
|
||||||
info "Create second backup and prune"
|
|
||||||
docker compose exec backup backup
|
|
||||||
|
|
||||||
if [ -f "$LOCAL_DIR/test-hostnametoken-old.tar.gz" ]; then
|
|
||||||
fail "Backdated file has not been deleted."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "$LOCAL_DIR/test-hostnametoken.tar.gz" ]; then
|
|
||||||
fail "Recent file has been deleted."
|
|
||||||
fi
|
|
||||||
|
|
||||||
pass "Old remote backup has been pruned, new one is still present."
|
|
||||||
|
|||||||
Reference in New Issue
Block a user