diff --git a/cmd/backup/config.go b/cmd/backup/config.go index 7e59ff5..d6805f9 100644 --- a/cmd/backup/config.go +++ b/cmd/backup/config.go @@ -16,72 +16,84 @@ import ( // Config holds all configuration values that are expected to be set // by users. type Config struct { - AwsS3BucketName string - AwsS3Path string - AwsEndpoint string `default:"s3.amazonaws.com"` - AwsEndpointProto string - AwsEndpointInsecure bool - AwsEndpointCACert CertDecoder - AwsStorageClass string - AwsAccessKeyID string - AwsSecretAccessKey string - AwsIamRoleEndpoint string - AwsPartSize int64 - BackupCompression CompressionType `default:"gz"` - BackupSources string `default:"/backup"` - BackupFilename string `default:"backup-%Y-%m-%dT%H-%M-%S.{{ .Extension }}"` - BackupFilenameExpand bool - BackupLatestSymlink string - BackupArchive string `default:"/archive"` - BackupRetentionDays int32 `default:"-1"` - BackupPruningLeeway time.Duration `default:"1m"` - BackupPruningPrefix string - BackupStopContainerLabel string `default:"true"` - BackupFromSnapshot bool - BackupExcludeRegexp RegexpDecoder - BackupSkipBackendsFromPrune []string - GpgPassphrase string - NotificationURLs []string - NotificationLevel string `default:"error"` - EmailNotificationRecipient string - EmailNotificationSender string `default:"noreply@nohost"` - EmailSMTPHost string - EmailSMTPPort int `default:"587"` - EmailSMTPUsername string - EmailSMTPPassword string - WebdavUrl string - WebdavUrlInsecure bool - WebdavPath string `default:"/"` - WebdavUsername string - WebdavPassword string - SSHHostName string - SSHPort string `default:"22"` - SSHUser string - SSHPassword string - SSHIdentityFile string `default:"/root/.ssh/id_rsa"` - SSHIdentityPassphrase string - SSHRemotePath string - ExecLabel string - ExecForwardOutput bool - LockTimeout time.Duration `default:"60m"` - AzureStorageAccountName string - AzureStoragePrimaryAccountKey string - AzureStorageContainerName string - AzureStoragePath string - AzureStorageEndpoint string `default:"https://{{ .AccountName }}.blob.core.windows.net/"` - DropboxEndpoint string `default:"https://api.dropbox.com/"` - DropboxOAuth2Endpoint string `default:"https://api.dropbox.com/"` - DropboxRefreshToken string - DropboxAppKey string - DropboxAppSecret string - DropboxRemotePath string - DropboxConcurrencyLevel NaturalNumber `default:"6"` + AwsS3BucketName string `split_words:"true"` + AwsS3Path string `split_words:"true"` + AwsEndpoint string `split_words:"true" default:"s3.amazonaws.com"` + AwsEndpointProto string `split_words:"true" default:"https"` + AwsEndpointInsecure bool `split_words:"true"` + AwsEndpointCACert CertDecoder `envconfig:"AWS_ENDPOINT_CA_CERT"` + AwsStorageClass string `split_words:"true"` + AwsAccessKeyID string `envconfig:"AWS_ACCESS_KEY_ID"` + AwsAccessKeyIDFile string `envconfig:"AWS_ACCESS_KEY_ID_FILE"` + AwsSecretAccessKey string `split_words:"true"` + AwsSecretAccessKeyFile string `split_words:"true"` + AwsIamRoleEndpoint string `split_words:"true"` + AwsPartSize int64 `split_words:"true"` + BackupCompression CompressionType `split_words:"true" default:"gz"` + BackupSources string `split_words:"true" default:"/backup"` + BackupFilename string `split_words:"true" default:"backup-%Y-%m-%dT%H-%M-%S.{{ .Extension }}"` + BackupFilenameExpand bool `split_words:"true"` + BackupLatestSymlink string `split_words:"true"` + BackupArchive string `split_words:"true" default:"/archive"` + BackupRetentionDays int32 `split_words:"true" default:"-1"` + BackupPruningLeeway time.Duration `split_words:"true" default:"1m"` + BackupPruningPrefix string `split_words:"true"` + BackupStopContainerLabel string `split_words:"true" default:"true"` + BackupFromSnapshot bool `split_words:"true"` + BackupExcludeRegexp RegexpDecoder `split_words:"true"` + BackupSkipBackendsFromPrune []string `split_words:"true"` + GpgPassphrase string `split_words:"true"` + NotificationURLs []string `envconfig:"NOTIFICATION_URLS"` + NotificationLevel string `split_words:"true" default:"error"` + EmailNotificationRecipient string `split_words:"true"` + EmailNotificationSender string `split_words:"true" default:"noreply@nohost"` + EmailSMTPHost string `envconfig:"EMAIL_SMTP_HOST"` + EmailSMTPPort int `envconfig:"EMAIL_SMTP_PORT" default:"587"` + EmailSMTPUsername string `envconfig:"EMAIL_SMTP_USERNAME"` + EmailSMTPPassword string `envconfig:"EMAIL_SMTP_PASSWORD"` + WebdavUrl string `split_words:"true"` + WebdavUrlInsecure bool `split_words:"true"` + WebdavPath string `split_words:"true" default:"/"` + WebdavUsername string `split_words:"true"` + WebdavPassword string `split_words:"true"` + SSHHostName string `split_words:"true"` + SSHPort string `split_words:"true" default:"22"` + SSHUser string `split_words:"true"` + SSHPassword string `split_words:"true"` + SSHIdentityFile string `split_words:"true" default:"/root/.ssh/id_rsa"` + SSHIdentityPassphrase string `split_words:"true"` + SSHRemotePath string `split_words:"true"` + ExecLabel string `split_words:"true"` + ExecForwardOutput bool `split_words:"true"` + LockTimeout time.Duration `split_words:"true" default:"60m"` + AzureStorageAccountName string `split_words:"true"` + AzureStoragePrimaryAccountKey string `split_words:"true"` + AzureStorageContainerName string `split_words:"true"` + AzureStoragePath string `split_words:"true"` + AzureStorageEndpoint string `split_words:"true" default:"https://{{ .AccountName }}.blob.core.windows.net/"` + DropboxEndpoint string `split_words:"true" default:"https://api.dropbox.com/"` + DropboxOAuth2Endpoint string `envconfig:"DROPBOX_OAUTH2_ENDPOINT" default:"https://api.dropbox.com/"` + DropboxRefreshToken string `split_words:"true"` + DropboxAppKey string `split_words:"true"` + DropboxAppSecret string `split_words:"true"` + DropboxRemotePath string `split_words:"true"` + DropboxConcurrencyLevel NaturalNumber `split_words:"true" default:"6"` +} + +func (c *Config) resolveSecret(envVar string, secretPath string) (string, error) { + if secretPath == "" { + return envVar, nil + } + data, err := os.ReadFile(secretPath) + if err != nil { + return "", fmt.Errorf("resolveSecret: error reading secret path: %w", err) + } + return string(data), nil } type CompressionType string -func (c *CompressionType) UnmarshalText(text []byte) error { - v := string(text) +func (c *CompressionType) Decode(v string) error { switch v { case "gz", "zst": *c = CompressionType(v) @@ -99,8 +111,7 @@ type CertDecoder struct { Cert *x509.Certificate } -func (c *CertDecoder) UnmarshalText(text []byte) error { - v := string(text) +func (c *CertDecoder) Decode(v string) error { if v == "" { return nil } @@ -121,8 +132,7 @@ type RegexpDecoder struct { Re *regexp.Regexp } -func (r *RegexpDecoder) UnmarshalText(text []byte) error { - v := string(text) +func (r *RegexpDecoder) Decode(v string) error { if v == "" { return nil } @@ -136,8 +146,7 @@ func (r *RegexpDecoder) UnmarshalText(text []byte) error { type NaturalNumber int -func (n *NaturalNumber) UnmarshalText(text []byte) error { - v := string(text) +func (n *NaturalNumber) Decode(v string) error { asInt, err := strconv.Atoi(v) if err != nil { return fmt.Errorf("config: error converting %s to int", v) diff --git a/cmd/backup/script.go b/cmd/backup/script.go index 6b0a3b6..5a455c3 100644 --- a/cmd/backup/script.go +++ b/cmd/backup/script.go @@ -35,7 +35,7 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" - "github.com/johnstairs/pathenvconfig" + "github.com/kelseyhightower/envconfig" "github.com/leekchan/timeutil" "github.com/otiai10/copy" "golang.org/x/sync/errgroup" @@ -89,12 +89,10 @@ func newScript() (*script, error) { return nil }) - if err := pathenvconfig.Process("", s.c); err != nil { + if err := envconfig.Process("", s.c); err != nil { return nil, fmt.Errorf("newScript: failed to process configuration values: %w", err) } - fmt.Printf("Using configuration: %+v\n", s.c) // Debug - s.file = path.Join("/tmp", s.c.BackupFilename) tmplFileName, tErr := template.New("extension").Parse(s.file) @@ -139,10 +137,18 @@ func newScript() (*script, error) { } if s.c.AwsS3BucketName != "" { + accessKeyID, err := s.c.resolveSecret(s.c.AwsAccessKeyID, s.c.AwsAccessKeyIDFile) + if err != nil { + return nil, fmt.Errorf("newScript: error resolving AwsAccessKeyID: %w", err) + } + secretAccessKey, err := s.c.resolveSecret(s.c.AwsSecretAccessKey, s.c.AwsSecretAccessKeyFile) + if err != nil { + return nil, fmt.Errorf("newScript: error resolving AwsSecretAccessKey: %w", err) + } s3Config := s3.Config{ Endpoint: s.c.AwsEndpoint, - AccessKeyID: s.c.AwsAccessKeyID, - SecretAccessKey: s.c.AwsSecretAccessKey, + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, IamRoleEndpoint: s.c.AwsIamRoleEndpoint, EndpointProto: s.c.AwsEndpointProto, EndpointInsecure: s.c.AwsEndpointInsecure, diff --git a/go.mod b/go.mod index e24a399..08d739e 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/cosiner/argv v0.1.0 github.com/docker/docker v24.0.5+incompatible github.com/gofrs/flock v0.8.1 + github.com/kelseyhightower/envconfig v1.4.0 github.com/klauspost/compress v1.16.7 github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d github.com/minio/minio-go/v7 v7.0.62 @@ -22,7 +23,6 @@ require ( require ( github.com/cloudflare/circl v1.3.3 // indirect - github.com/dlclark/regexp2 v1.4.0 // indirect github.com/golang/protobuf v1.5.2 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect @@ -43,7 +43,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/johnstairs/pathenvconfig v0.2.1 github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/fs v0.1.0 // indirect diff --git a/go.sum b/go.sum index 7e4aec8..b66a019 100644 --- a/go.sum +++ b/go.sum @@ -252,8 +252,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= -github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= @@ -442,8 +440,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1: github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jarcoal/httpmock v1.2.0 h1:gSvTxxFR/MEMfsGrvRbdfpRUMBStovlSRLw0Ep1bwwc= github.com/jarcoal/httpmock v1.2.0/go.mod h1:oCoTsnAz4+UoOUIf5lJOWV2QQIW5UoeUI6aM2YnWAZk= -github.com/johnstairs/pathenvconfig v0.2.1 h1:hay0C2ddNdej569b4GXwjwZyjRagei97ppjRW8XcvMQ= -github.com/johnstairs/pathenvconfig v0.2.1/go.mod h1:XqBReihWnlfmCkHqvJAfsRCfa/JJCYXZds8fwtuz8cM= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -455,6 +451,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= 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/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=