diff --git a/Ansible/roles/debian_server/files/dvb b/Ansible/roles/debian_server/files/dvb new file mode 100755 index 0000000000000000000000000000000000000000..00d0ed59b3b7e4802c93371bcb46a7c304fc0c27 --- /dev/null +++ b/Ansible/roles/debian_server/files/dvb @@ -0,0 +1,77 @@ +#!/bin/sh +set -eu + +# Cleanup of snapshots, added as trap so it's always executed even in case of a +# failure or ctrl+c. +cleanup () { + btrfs subvolume delete "$snapshot" +} + +usage () { + echo "Usage: $(basename "$0") DESTINATION" >&2 + exit 1 +} + +get_mountpoint () { + df --output=target "$1" | tail +2 +} + +get_snapshot () { + name="$1" + mountpoint="$(docker volume inspect --format '{{ .Mountpoint }}' "$name" )" + snapshot="$(dirname "$mountpoint")/snapshot)" + echo "$snapshot" +} + +if [ "$#" -ne 1 ] +then + usage +elif [ ! -d "$1" ] +then + echo "Destinaton $1 not found or is not a directory." >&2 + exit 1 +elif [ "$(id -u)" -ne 0 ] +then + echo "This program must run as root." >&2 + exit 1 +elif ! command -v btrfs >/dev/null +then + echo 'btrfs-progs must be installed.' >&2 + exit 1 +elif ! docker info --format '{{ .ServerVersion }}' >/dev/null +then + echo 'Docker must be installed and running.' >&2 + exit 1 +elif ! df --type=btrfs /var/lib/docker/volumes >/dev/null +then + echo "Docker volumes are not on a btrfs filesystem." >&2 + exit 1 +elif ! df --type=btrfs "$1" >/dev/null +then + echo "Destination $1 is not on a btrfs filesystem." >&2 + exit 1 +elif [ "$(df --output=target /var/lib/docker/volumes)" != "$(df --output=target "$1")" ] +then + echo "The destination $1 is not on the same btrfs volume as the Docker volumes." >&2 + exit 1 +elif [ -z "$(docker volume ls --filter label=snapshot=true --format '{{ .Name }}')" ] +then + echo "No Docker volumes marked for backup, exiting." >&2 + exit 1 +fi + +dest="$1" +root_volume="$(get_mountpoint /var/lib/docker/volumes)" +snapshot="$(mktemp --dry-run "--tmpdir=$root_volume")" +trap 'cleanup' INT QUIT EXIT TERM +btrfs subvolume snapshot "$root_volume" "$snapshot" + +for name in $(docker volume ls --filter label=snapshot=true --format '{{ .Name }}') +do + echo "Backing up $name." + mountpoint="$(docker volume inspect --format '{{ .Mountpoint }}' "$name")" + src="$snapshot/${mountpoint#$root_volume/}" + # shellcheck disable=SC2115 + [ ! -e "$dest/$name" ] || rm -rf "$dest/$name" + cp --archive --force --reflink=always "$src" "$dest/$name" +done diff --git a/Ansible/roles/debian_server/files/dvb.service b/Ansible/roles/debian_server/files/dvb.service new file mode 100644 index 0000000000000000000000000000000000000000..37617a3ea35fc2d5d5708cda417ab4c209799aa9 --- /dev/null +++ b/Ansible/roles/debian_server/files/dvb.service @@ -0,0 +1,9 @@ +# vim: filetype=systemd +[Unit] +Description=Docker volume backup (dvb) +ConditionACPower=true +After=local-fs.target + +[Service] +Type=exec +ExecStart=dvb /var/backups/docker-volumes diff --git a/Ansible/roles/debian_server/files/dvb.timer b/Ansible/roles/debian_server/files/dvb.timer new file mode 100644 index 0000000000000000000000000000000000000000..5606bdd5a645d6c15b3254b560d9b75a60ec11ed --- /dev/null +++ b/Ansible/roles/debian_server/files/dvb.timer @@ -0,0 +1,10 @@ +# vim: filetype=systemd +[Unit] +Description=Docker volume backup (dvb) + +[Timer] +OnCalendar=weekly +RandomizedDelaySec=21600 + +[Install] +WantedBy=multi-user.target diff --git a/Ansible/roles/debian_server/tasks/docker.yml b/Ansible/roles/debian_server/tasks/docker.yml index f5d5e8fa01f3a4e8b9926ecfbbb02b704e8dec1d..9c15907411612f89f5967bb7c9d1c80ce23e6387 100644 --- a/Ansible/roles/debian_server/tasks/docker.yml +++ b/Ansible/roles/debian_server/tasks/docker.yml @@ -55,3 +55,35 @@ direction: in interface: docker0 rule: allow + +- name: Create the Docker volume backup destination directory + ansible.builtin.file: + group: backup + mode: 0o0750 + owner: root + path: /var/backups/docker-volumes + state: directory + +- name: Copy the Docker volume backup script + ansible.builtin.copy: + dest: /usr/local/bin/ + mode: preserve + src: dvb + +- name: Copy the Docker volume backup service and timer + loop: + - service + - timer + ansible.builtin.copy: + dest: /etc/systemd/system/dvb.{{ item }} + mode: 0o0644 + src: dvb.{{ item }} + notify: + - Systemd daemon reload + +- name: Enable the Docker volume backup timer + ansible.builtin.systemd: + enabled: true + name: dvb.timer + notify: + - Systemd daemon reload