commit 501e4213571843cde2c55d52a72c005f3332c0ef Author: Stefano Pigozzi Date: Tue Mar 14 19:32:14 2023 +0100 v0.1.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c8ab97f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,11 @@ +name: "Release new version" + +on: + # Creation of a new tag starting with v + push: + tags: + - "v*" + +jobs: + ghcrio: + uses: Steffo99/.github/.github/workflows/buildrelease-docker.yml@main diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a309a99 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "duplicity"] + path = duplicity + url = https://gitlab.com/duplicity/duplicity.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..63f1116 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# FROM archlinux:latest +FROM alpine:latest + +# Install duplicity +# RUN pacman --noconfirm -Syu duplicity python-pip python-pydrive2 +RUN apk add duplicity +RUN apk add py3-pip +RUN apk add python3-dev +RUN apk add gcc +RUN apk add libffi-dev +RUN apk add musl-dev +RUN pip install pydrive2 + +WORKDIR /var/lib/duplicity +ENV HOME="/var/lib/duplicity" + +# Configure entrypoint and command +ENTRYPOINT ["crond", "-f", "-d", "5"] +CMD [] + +# Add image labels +LABEL org.opencontainers.image.title="docker-backup-duplicity" +LABEL org.opencontainers.image.description="Backup solution for Docker volumes based on Duplicity" +LABEL org.opencontainers.image.licenses="AGPL-3.0-or-later" +LABEL org.opencontainers.image.url="https://github.com/Steffo99/docker-backup-duplicity" +LABEL org.opencontainers.image.authors="Stefano Pigozzi " + +# Add duplicity to cron +COPY ./backup.sh /etc/periodic/daily/backup.sh + +# Configure duplicity +ENV DUPLICITY_FULL_IF_OLDER_THAN=1M diff --git a/README.md b/README.md new file mode 100644 index 0000000..9244aa9 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# docker-backup-duplicity + +Backup solution for Docker volumes based on Duplicity + +## Usage + +> **Note**: The following instructions assume Google Drive is used as a storage backend; refer to [duplicity's man page](https://duplicity.us/stable/duplicity.1.html) to find out how to configure different backends! + +1. Create a new volume in Docker with the name `duplicity_credentials`: + + ```console + # docker volume create duplicity_credentials + ``` + +2. Create a new file in the host system with the name `/root/secrets/backup/passphrase.txt`, and enter in it a secure passphrase to use to encrypt files: + + ```console + # echo 'CorrectHorseBatteryStaple' >> /root/secrets/backup/passphrase.txt + ``` + +3. [Obtain *Desktop Application* OAuth credentials from the Google Cloud Console.](https://console.cloud.google.com/apis/credentials) + +4. Create a new file in the host system with the name `/root/secrets/backup/client_config.yml`, and enter the following content in it: + + ```console + # edit /root/secrets/backup/client_config.yml + ``` + + ```yml + client_config_backend: settings + client_config: + client_id: "YOUR_GOOGLE_CLIENT_ID_GOES_HERE" + client_secret: "YOUR_GOOGLE_CLIENT_SECRET_GOES_HERE" + save_credentials: True + save_credentials_backend: file + save_credentials_file: "/var/lib/duplicity/credentials" + get_refresh_token: True + ``` + +5. Add the following keys to the `docker-compose.yml` file of the project you want to backup: + + ```console + # edit ./docker-compose.yml + ``` + + 1. If you haven't already, upgrade your `docker-compose.yml` file to version 3.9: + + ```yml + version: "3.9" + ``` + + 2. Connect the previously created `duplicity_credentials` volume to the project: + + ```yml + volumes: + duplicity_credentials: + external: true + ``` + + 3. Setup the two previously created files as Docker secrets: + + ```yml + secrets: + duplicity_passphrase: + file: "/root/secrets/backup/passphrase.txt" + google_client_config: + file: "/root/secrets/backup/client_config.yml" + ``` + + 4. Add the following service: + + ```yml + services: + backup: + image: "ghcr.io/steffo99/backup-duplicity:latest" + restart: unless-stopped + secrets: + - google_client_config + - duplicity_passphrase + volumes: + - "duplicity_credentials:/var/lib/duplicity" + # Mount whatever you want to backup in subdirectories of /mnt + - ".:/mnt/compose" # Backup the current directory? + - "data:/mnt/data" # Backup a named volume? + environment: + MODE: "backup" # Change this to "restore" to restore the latest backup + DUPLICITY_TARGET_URL: "pydrive://YOUR_GOOGLE_CLIENT_ID_GOES_HERE/Duplicity/this" # Change this to the Drive directory you want to backup files to https://man.archlinux.org/man/duplicity.1.en#URL_FORMAT + # Don't touch these, they allow the program to read the secrets + DUPLICITY_PASSPHRASE_FILE: "/run/secrets/duplicity_passphrase" + GOOGLE_DRIVE_SETTINGS: "/run/secrets/google_client_config" + ``` + +6. Log in to Google Drive and perform an initial backup with: + + ```console + # docker compose run -i backup --entrypoint=/bin/sh /etc/periodic/daily/backup.sh + ``` + +7. Properly start the container with: + + ```console + # docker compose up -d && docker compose logs -f + ``` \ No newline at end of file diff --git a/backup.sh b/backup.sh new file mode 100644 index 0000000..ac56b5e --- /dev/null +++ b/backup.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +set -e + +# Get secrets from files +# Insecure, but there's not much I can do about it +# It's duplicity's fault! +export PASSPHRASE=$(cat ${DUPLICITY_PASSPHRASE_FILE}) + +case "$MODE" in + backup) + echo "Launched in backup mode, performing backup..." >> /dev/stderr + duplicity \ + --allow-source-mismatch \ + --full-if-older-than ${DUPLICITY_FULL_IF_OLDER_THAN} \ + /mnt \ + ${DUPLICITY_TARGET_URL} + ;; + restore) + echo "Launched in restore mode, restoring backup..." >> /dev/stderr + duplicity \ + --force \ + --allow-source-mismatch \ + ${DUPLICITY_TARGET_URL} \ + /mnt + ;; + *) + echo "No such mode." >> /dev/stderr + ;; +esac diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/test.docker-compose.yml b/test.docker-compose.yml new file mode 100644 index 0000000..884669e --- /dev/null +++ b/test.docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.9" + +secrets: + google_client_config: + file: "/root/secrets/backup/client_config.yml" + duplicity_passphrase: + file: "/root/secrets/backup/passphrase.txt" + +volumes: + duplicity_credentials: + external: true + +services: + backup: + image: "ghcr.io/steffo99/backup-duplicity:latest" + entrypoint: "/bin/sh" + command: "/etc/periodic/daily/backup.sh" + restart: unless-stopped + volumes: + - "./example:/mnt/example" + - "duplicity_credentials:/var/lib/duplicity" + environment: + MODE: "backup" + DUPLICITY_PASSPHRASE_FILE: "/run/secrets/duplicity_passphrase" + DUPLICITY_TARGET_URL: "pydrive://641079776729-90s4tnli0ao913ajrpv8cp3c4kkk77j5.apps.googleusercontent.com/Duplicity/this" + GOOGLE_DRIVE_SETTINGS: "/run/secrets/google_client_config" + secrets: + - google_client_config + - duplicity_passphrase