mirror of
https://github.com/offen/docker-volume-backup.git
synced 2025-12-05 09:08:02 +01:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
336e12f874 | ||
|
|
016c6c8307 | ||
|
|
e22f317fbb | ||
|
|
e04bd2f066 | ||
|
|
c4eeaad813 | ||
|
|
5840f1c5dc | ||
|
|
d71b7304c2 | ||
|
|
fbc7f85d9f | ||
|
|
2af5bdf4d9 | ||
|
|
631ca3e07d | ||
|
|
3d35d7c00e | ||
|
|
954bde73fb | ||
|
|
ab46e96706 | ||
|
|
ab4ce94534 | ||
|
|
e4170addb6 | ||
|
|
b8410bbdc5 | ||
|
|
24e1341589 | ||
|
|
3d0286472b | ||
|
|
bb11ae035b | ||
|
|
9209037ed9 | ||
|
|
2e73dea4f7 | ||
|
|
7dc3ae17e7 | ||
|
|
9d5ea718a0 | ||
|
|
272495ae7d | ||
|
|
8beb28d4f8 | ||
|
|
0ec2e68076 | ||
|
|
b85afa6008 | ||
|
|
4cb47a4818 | ||
|
|
9b5ba8958d | ||
|
|
0327701e2d | ||
|
|
58f26ba004 | ||
|
|
f62ef6e05a | ||
|
|
40924434e4 | ||
|
|
e613f6046f | ||
|
|
292d47eb19 | ||
|
|
7637975e3f | ||
|
|
c47a14c53a | ||
|
|
9f795761d6 |
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@@ -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.54
|
version: v1.57
|
||||||
|
|
||||||
# Optional: working directory, useful for monorepos
|
# Optional: working directory, useful for monorepos
|
||||||
# working-directory: somedir
|
# working-directory: somedir
|
||||||
|
|||||||
@@ -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.19
|
FROM alpine:3.20
|
||||||
|
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func createArchive(files []string, inputFilePath, outputFilePath string, compression string, compressionConcurrency int) error {
|
func createArchive(files []string, inputFilePath, outputFilePath string, compression string, compressionConcurrency int) error {
|
||||||
inputFilePath = stripTrailingSlashes(inputFilePath)
|
_, outputFilePath, err := makeAbsolute(stripTrailingSlashes(inputFilePath), outputFilePath)
|
||||||
inputFilePath, outputFilePath, err := makeAbsolute(inputFilePath, outputFilePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, "error transposing given file paths")
|
return errwrap.Wrap(err, "error transposing given file paths")
|
||||||
}
|
}
|
||||||
@@ -31,7 +30,7 @@ func createArchive(files []string, inputFilePath, outputFilePath string, compres
|
|||||||
return errwrap.Wrap(err, "error creating output file path")
|
return errwrap.Wrap(err, "error creating output file path")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := compress(files, outputFilePath, filepath.Dir(inputFilePath), compression, compressionConcurrency); err != nil {
|
if err := compress(files, outputFilePath, compression, compressionConcurrency); err != nil {
|
||||||
return errwrap.Wrap(err, "error creating archive")
|
return errwrap.Wrap(err, "error creating archive")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ func makeAbsolute(inputFilePath, outputFilePath string) (string, string, error)
|
|||||||
return inputFilePath, outputFilePath, err
|
return inputFilePath, outputFilePath, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func compress(paths []string, outFilePath, subPath string, algo string, concurrency int) error {
|
func compress(paths []string, outFilePath, algo string, concurrency int) error {
|
||||||
file, err := os.Create(outFilePath)
|
file, err := os.Create(outFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, "error creating out file")
|
return errwrap.Wrap(err, "error creating out file")
|
||||||
|
|||||||
@@ -131,12 +131,8 @@ func (c *command) schedule(strategy configStrategy) error {
|
|||||||
c.logger.Warn(
|
c.logger.Warn(
|
||||||
fmt.Sprintf("Scheduled cron expression %s will never run, is this intentional?", config.BackupCronExpression),
|
fmt.Sprintf("Scheduled cron expression %s will never run, is this intentional?", config.BackupCronExpression),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errwrap.Wrap(err, "error scheduling")
|
|
||||||
}
|
|
||||||
c.schedules = append(c.schedules, id)
|
|
||||||
}
|
}
|
||||||
|
c.schedules = append(c.schedules, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -76,6 +76,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 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"`
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ func (s *script) encryptArchive() error {
|
|||||||
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 `%s`", s.file))
|
||||||
}
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
if _, err := io.Copy(dst, src); err != nil {
|
if _, err := io.Copy(dst, src); err != nil {
|
||||||
return errwrap.Wrap(err, "error writing ciphertext to file")
|
return errwrap.Wrap(err, "error writing ciphertext to file")
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cosiner/argv"
|
"github.com/cosiner/argv"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/offen/docker-volume-backup/internal/errwrap"
|
"github.com/offen/docker-volume-backup/internal/errwrap"
|
||||||
@@ -28,7 +28,7 @@ func (s *script) exec(containerRef string, command string, user string) ([]byte,
|
|||||||
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, types.ExecConfig{
|
execID, err := s.cli.ContainerExecCreate(context.Background(), containerRef, container.ExecOptions{
|
||||||
Cmd: args[0],
|
Cmd: args[0],
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
@@ -39,7 +39,7 @@ func (s *script) exec(containerRef string, command string, user string) ([]byte,
|
|||||||
return nil, nil, errwrap.Wrap(err, "error creating container exec")
|
return nil, nil, errwrap.Wrap(err, "error creating container exec")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := s.cli.ContainerExecAttach(context.Background(), execID.ID, types.ExecStartCheck{})
|
resp, err := s.cli.ContainerExecAttach(context.Background(), execID.ID, container.ExecStartOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errwrap.Wrap(err, "error attaching container exec")
|
return nil, nil, errwrap.Wrap(err, "error attaching container exec")
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ func (s *script) runLabeledCommands(label string) error {
|
|||||||
Value: fmt.Sprintf("docker-volume-backup.exec-label=%s", s.c.ExecLabel),
|
Value: fmt.Sprintf("docker-volume-backup.exec-label=%s", s.c.ExecLabel),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
containersWithCommand, err := s.cli.ContainerList(context.Background(), types.ContainerListOptions{
|
containersWithCommand, err := s.cli.ContainerList(context.Background(), container.ListOptions{
|
||||||
Filters: filters.NewArgs(f...),
|
Filters: filters.NewArgs(f...),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,7 +109,7 @@ func (s *script) runLabeledCommands(label string) error {
|
|||||||
Key: "label",
|
Key: "label",
|
||||||
Value: "docker-volume-backup.exec-pre",
|
Value: "docker-volume-backup.exec-pre",
|
||||||
}
|
}
|
||||||
deprecatedContainers, err := s.cli.ContainerList(context.Background(), types.ContainerListOptions{
|
deprecatedContainers, err := s.cli.ContainerList(context.Background(), container.ListOptions{
|
||||||
Filters: filters.NewArgs(f...),
|
Filters: filters.NewArgs(f...),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -126,7 +126,7 @@ func (s *script) runLabeledCommands(label string) error {
|
|||||||
Key: "label",
|
Key: "label",
|
||||||
Value: "docker-volume-backup.exec-post",
|
Value: "docker-volume-backup.exec-post",
|
||||||
}
|
}
|
||||||
deprecatedContainers, err := s.cli.ContainerList(context.Background(), types.ContainerListOptions{
|
deprecatedContainers, err := s.cli.ContainerList(context.Background(), container.ListOptions{
|
||||||
Filters: filters.NewArgs(f...),
|
Filters: filters.NewArgs(f...),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -194,6 +194,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,
|
||||||
}
|
}
|
||||||
azureBackend, err := azure.NewStorageBackend(azureConfig, logFunc)
|
azureBackend, err := azure.NewStorageBackend(azureConfig, logFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli/command/service/progress"
|
"github.com/docker/cli/cli/command/service/progress"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
ctr "github.com/docker/docker/api/types/container"
|
ctr "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
"github.com/docker/docker/api/types/system"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/offen/docker-volume-backup/internal/errwrap"
|
"github.com/offen/docker-volume-backup/internal/errwrap"
|
||||||
)
|
)
|
||||||
@@ -65,7 +67,7 @@ func awaitContainerCountForService(cli *client.Client, serviceID string, count i
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
case <-poll.C:
|
case <-poll.C:
|
||||||
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{
|
containers, err := cli.ContainerList(context.Background(), container.ListOptions{
|
||||||
Filters: filters.NewArgs(filters.KeyValuePair{
|
Filters: filters.NewArgs(filters.KeyValuePair{
|
||||||
Key: "label",
|
Key: "label",
|
||||||
Value: fmt.Sprintf("com.docker.swarm.service.id=%s", serviceID),
|
Value: fmt.Sprintf("com.docker.swarm.service.id=%s", serviceID),
|
||||||
@@ -82,7 +84,7 @@ func awaitContainerCountForService(cli *client.Client, serviceID string, count i
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isSwarm(c interface {
|
func isSwarm(c interface {
|
||||||
Info(context.Context) (types.Info, error)
|
Info(context.Context) (system.Info, error)
|
||||||
}) (bool, error) {
|
}) (bool, error) {
|
||||||
info, err := c.Info(context.Background())
|
info, err := c.Info(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -123,11 +125,11 @@ func (s *script) stopContainersAndServices() (func() error, error) {
|
|||||||
labelValue,
|
labelValue,
|
||||||
)
|
)
|
||||||
|
|
||||||
allContainers, err := s.cli.ContainerList(context.Background(), types.ContainerListOptions{})
|
allContainers, err := s.cli.ContainerList(context.Background(), container.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return noop, errwrap.Wrap(err, "error querying for containers")
|
return noop, errwrap.Wrap(err, "error querying for containers")
|
||||||
}
|
}
|
||||||
containersToStop, err := s.cli.ContainerList(context.Background(), types.ContainerListOptions{
|
containersToStop, err := s.cli.ContainerList(context.Background(), container.ListOptions{
|
||||||
Filters: filters.NewArgs(filters.KeyValuePair{
|
Filters: filters.NewArgs(filters.KeyValuePair{
|
||||||
Key: "label",
|
Key: "label",
|
||||||
Value: filterMatchLabel,
|
Value: filterMatchLabel,
|
||||||
@@ -309,7 +311,7 @@ func (s *script) stopContainersAndServices() (func() error, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.cli.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{}); err != nil {
|
if err := s.cli.ContainerStart(context.Background(), container.ID, ctr.StartOptions{}); err != nil {
|
||||||
restartErrors = append(restartErrors, err)
|
restartErrors = append(restartErrors, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
"github.com/docker/docker/api/types/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockInfoClient struct {
|
type mockInfoClient struct {
|
||||||
result types.Info
|
result system.Info
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockInfoClient) Info(context.Context) (types.Info, error) {
|
func (m *mockInfoClient) Info(context.Context) (system.Info, error) {
|
||||||
return m.result, m.err
|
return m.result, m.err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ func TestIsSwarm(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"swarm",
|
"swarm",
|
||||||
&mockInfoClient{
|
&mockInfoClient{
|
||||||
result: types.Info{
|
result: system.Info{
|
||||||
Swarm: swarm.Info{
|
Swarm: swarm.Info{
|
||||||
LocalNodeState: swarm.LocalNodeStateActive,
|
LocalNodeState: swarm.LocalNodeStateActive,
|
||||||
},
|
},
|
||||||
@@ -40,7 +40,7 @@ func TestIsSwarm(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"compose",
|
"compose",
|
||||||
&mockInfoClient{
|
&mockInfoClient{
|
||||||
result: types.Info{
|
result: system.Info{
|
||||||
Swarm: swarm.Info{
|
Swarm: swarm.Info{
|
||||||
LocalNodeState: swarm.LocalNodeStateInactive,
|
LocalNodeState: swarm.LocalNodeStateInactive,
|
||||||
},
|
},
|
||||||
@@ -52,7 +52,7 @@ func TestIsSwarm(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"balena",
|
"balena",
|
||||||
&mockInfoClient{
|
&mockInfoClient{
|
||||||
result: types.Info{
|
result: system.Info{
|
||||||
Swarm: swarm.Info{
|
Swarm: swarm.Info{
|
||||||
LocalNodeState: "",
|
LocalNodeState: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -59,11 +59,13 @@ 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.2.6)
|
rexml (3.3.3)
|
||||||
|
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)
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ parent: How Tos
|
|||||||
|
|
||||||
In certain scenarios it can be required to run specific commands before and after a backup is taken (e.g. dumping a database).
|
In certain scenarios it can be required to run specific commands before and after a backup is taken (e.g. dumping a database).
|
||||||
When mounting the Docker socket into the `docker-volume-backup` container, you can define pre- and post-commands that will be run in the context of the target container (it is also possible to run commands inside the `docker-volume-backup` container itself using this feature).
|
When mounting the Docker socket into the `docker-volume-backup` container, you can define pre- and post-commands that will be run in the context of the target container (it is also possible to run commands inside the `docker-volume-backup` container itself using this feature).
|
||||||
|
|
||||||
|
{: .important }
|
||||||
|
In a multi-node Swarm setup, commands can currently only be run on the node the `offen/docker-volume-backup` container is running on.
|
||||||
|
Labeled containers on other nodes are not visible to the backup command.
|
||||||
|
|
||||||
Such commands are defined by specifying the command in a `docker-volume-backup.[step]-[pre|post]` label where `step` can be any of the following phases of a backup lifecycle:
|
Such commands are defined by specifying the command in a `docker-volume-backup.[step]-[pre|post]` label where `step` can be any of the following phases of a backup lifecycle:
|
||||||
|
|
||||||
- `archive` (the tar archive is created)
|
- `archive` (the tar archive is created)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ services:
|
|||||||
Notification backends other than email are also supported.
|
Notification backends other than email are also supported.
|
||||||
Refer to the documentation of [shoutrrr][shoutrrr-docs] to find out about options and configuration.
|
Refer to the documentation of [shoutrrr][shoutrrr-docs] to find out about options and configuration.
|
||||||
|
|
||||||
[shoutrrr-docs]: https://containrrr.dev/shoutrrr/0.7/services/overview/
|
[shoutrrr-docs]: https://containrrr.dev/shoutrrr/v0.8/services/overview/
|
||||||
|
|
||||||
{: .note }
|
{: .note }
|
||||||
If you also want notifications on successful executions, set `NOTIFICATION_LEVEL` to `info`.
|
If you also want notifications on successful executions, set `NOTIFICATION_LEVEL` to `info`.
|
||||||
|
|||||||
@@ -269,6 +269,11 @@ You can populate below template according to your requirements and use it as you
|
|||||||
# Note: Use your app's subpath in Dropbox, if it doesn't have global access.
|
# Note: Use your app's subpath in Dropbox, if it doesn't have global access.
|
||||||
# Consulte the README for further information.
|
# Consulte the README for further information.
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# AZURE_STORAGE_ACCESS_TIER="Cold"
|
||||||
|
|
||||||
# DROPBOX_REMOTE_PATH="/my/directory"
|
# DROPBOX_REMOTE_PATH="/my/directory"
|
||||||
|
|
||||||
# Number of concurrent chunked uploads for Dropbox.
|
# Number of concurrent chunked uploads for Dropbox.
|
||||||
@@ -378,7 +383,7 @@ You can populate below template according to your requirements and use it as you
|
|||||||
|
|
||||||
# Notifications (email, Slack, etc.) can be sent out when a backup run finishes.
|
# Notifications (email, Slack, etc.) can be sent out when a backup run finishes.
|
||||||
# Configuration is provided as a comma-separated list of URLs as consumed
|
# Configuration is provided as a comma-separated list of URLs as consumed
|
||||||
# by `shoutrrr`: https://containrrr.dev/shoutrrr/0.7/services/overview/
|
# by `shoutrrr`: https://containrrr.dev/shoutrrr/v0.8/services/overview/
|
||||||
# The content of such notifications can be customized. Dedicated documentation
|
# The content of such notifications can be customized. Dedicated documentation
|
||||||
# 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.
|
||||||
|
|||||||
69
go.mod
69
go.mod
@@ -3,75 +3,82 @@ module github.com/offen/docker-volume-backup
|
|||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0
|
||||||
github.com/containrrr/shoutrrr v0.7.1
|
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 v24.0.9+incompatible
|
github.com/docker/cli v27.1.1+incompatible
|
||||||
github.com/docker/docker v24.0.7+incompatible
|
github.com/docker/docker v27.1.1+incompatible
|
||||||
github.com/gofrs/flock v0.8.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.7
|
github.com/klauspost/compress v1.17.9
|
||||||
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.69
|
github.com/minio/minio-go/v7 v7.0.74
|
||||||
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.0
|
||||||
github.com/pkg/sftp v1.13.6
|
github.com/pkg/sftp v1.13.6
|
||||||
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.9.0
|
||||||
golang.org/x/crypto v0.21.0
|
golang.org/x/crypto v0.25.0
|
||||||
golang.org/x/oauth2 v0.19.0
|
golang.org/x/oauth2 v0.22.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.8.0
|
||||||
mvdan.cc/sh/v3 v3.8.0
|
mvdan.cc/sh/v3 v3.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
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/golang-jwt/jwt/v5 v5.2.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/go-ini/ini v1.67.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // 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/exporters/otlp/otlptrace/otlptracehttp v1.26.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||||
google.golang.org/protobuf v1.33.0 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // 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/distribution v2.8.2+incompatible // indirect
|
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5
|
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fatih/color v1.13.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/json-iterator/go v1.1.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // 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
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
|
||||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd // indirect
|
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
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.5.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
golang.org/x/net v0.22.0 // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.org/x/sys v0.18.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
|
||||||
gotest.tools/v3 v3.0.3 // indirect
|
gotest.tools/v3 v3.0.3 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import (
|
|||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob"
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
|
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
|
||||||
"github.com/offen/docker-volume-backup/internal/errwrap"
|
"github.com/offen/docker-volume-backup/internal/errwrap"
|
||||||
"github.com/offen/docker-volume-backup/internal/storage"
|
"github.com/offen/docker-volume-backup/internal/storage"
|
||||||
@@ -24,8 +26,9 @@ import (
|
|||||||
|
|
||||||
type azureBlobStorage struct {
|
type azureBlobStorage struct {
|
||||||
*storage.StorageBackend
|
*storage.StorageBackend
|
||||||
client *azblob.Client
|
client *azblob.Client
|
||||||
containerName string
|
uploadStreamOptions *blockblob.UploadStreamOptions
|
||||||
|
containerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config contains values that define the configuration of an Azure Blob Storage.
|
// Config contains values that define the configuration of an Azure Blob Storage.
|
||||||
@@ -36,6 +39,7 @@ type Config struct {
|
|||||||
ConnectionString string
|
ConnectionString string
|
||||||
Endpoint string
|
Endpoint string
|
||||||
RemotePath string
|
RemotePath string
|
||||||
|
AccessTier string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStorageBackend creates and initializes a new Azure Blob Storage backend.
|
// NewStorageBackend creates and initializes a new Azure Blob Storage backend.
|
||||||
@@ -81,9 +85,26 @@ 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,
|
||||||
containerName: opts.ContainerName,
|
uploadStreamOptions: uploadStreamOptions,
|
||||||
|
containerName: opts.ContainerName,
|
||||||
StorageBackend: &storage.StorageBackend{
|
StorageBackend: &storage.StorageBackend{
|
||||||
DestinationPath: opts.RemotePath,
|
DestinationPath: opts.RemotePath,
|
||||||
Log: logFunc,
|
Log: logFunc,
|
||||||
@@ -103,12 +124,13 @@ func (b *azureBlobStorage) Copy(file string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, fmt.Sprintf("error opening file %s", file))
|
return errwrap.Wrap(err, fmt.Sprintf("error opening file %s", file))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = b.client.UploadStream(
|
_, err = b.client.UploadStream(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
b.containerName,
|
b.containerName,
|
||||||
filepath.Join(b.DestinationPath, filepath.Base(file)),
|
filepath.Join(b.DestinationPath, filepath.Base(file)),
|
||||||
fileReader,
|
fileReader,
|
||||||
nil,
|
b.uploadStreamOptions,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrap(err, fmt.Sprintf("error uploading file %s", file))
|
return errwrap.Wrap(err, fmt.Sprintf("error uploading file %s", file))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM docker:26-dind
|
FROM docker:27-dind
|
||||||
|
|
||||||
RUN apk add \
|
RUN apk add \
|
||||||
coreutils \
|
coreutils \
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
storage:
|
storage:
|
||||||
image: mcr.microsoft.com/azure-storage/azurite:3.26.0
|
image: mcr.microsoft.com/azure-storage/azurite:3.31.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
|
||||||
@@ -36,6 +34,7 @@ services:
|
|||||||
AZURE_STORAGE_CONTAINER_NAME: test-container
|
AZURE_STORAGE_CONTAINER_NAME: test-container
|
||||||
AZURE_STORAGE_ENDPOINT: http://storage:10000/{{ .AccountName }}/
|
AZURE_STORAGE_ENDPOINT: http://storage:10000/{{ .AccountName }}/
|
||||||
AZURE_STORAGE_PATH: 'path/to/backup'
|
AZURE_STORAGE_PATH: 'path/to/backup'
|
||||||
|
AZURE_STORAGE_ACCESS_TIER: Hot
|
||||||
BACKUP_FILENAME: test.tar.gz
|
BACKUP_FILENAME: test.tar.gz
|
||||||
BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ?
|
BACKUP_CRON_EXPRESSION: 0 0 5 31 2 ?
|
||||||
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7}
|
BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
hostname: minio.local
|
hostname: minio.local
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
||||||
# SPDX-License-Identifier: Unlicense
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: mariadb:10.7
|
image: mariadb:10.7
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
openapi_mock:
|
openapi_mock:
|
||||||
image: muonsoft/openapi-mock:0.3.9
|
image: muonsoft/openapi-mock:0.3.9
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:14-alpine
|
image: postgres:14-alpine
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
||||||
# SPDX-License-Identifier: Unlicense
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
||||||
# SPDX-License-Identifier: Unlicense
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backup:
|
backup:
|
||||||
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
image: offen/docker-volume-backup:${TEST_VERSION:-canary}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
||||||
# SPDX-License-Identifier: Unlicense
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
||||||
# SPDX-License-Identifier: Unlicense
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
ssh:
|
ssh:
|
||||||
image: linuxserver/openssh-server:version-8.6_p1-r3
|
image: linuxserver/openssh-server:version-8.6_p1-r3
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
# Copyright 2020-2021 - offen.software <hioffen@posteo.de>
|
||||||
# SPDX-License-Identifier: Unlicense
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
minio:
|
minio:
|
||||||
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
image: minio/minio:RELEASE.2020-08-04T23-10-51Z
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '2.4'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
alpine:
|
alpine:
|
||||||
image: alpine:3.17.3
|
image: alpine:3.17.3
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
webdav:
|
webdav:
|
||||||
image: bytemark/webdav:2.4
|
image: bytemark/webdav:2.4
|
||||||
|
|||||||
Reference in New Issue
Block a user