Created
September 26, 2021 21:27
-
-
Save mafredri/b412d63f86e7518dc8c3303c2e8f287a to your computer and use it in GitHub Desktop.
Restic backup from ZFS dataset snapshots mounted via Docker to mirror real directory structure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM alpine:3.14.2 | |
LABEL maintainer="Mathias Fredriksson <mafredri@gmail.com>" | |
RUN apk add --no-cache \ | |
bzip2 \ | |
ca-certificates | |
ARG RESTIC_VERSION='0.12.1' | |
RUN wget -O - https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}/restic_${RESTIC_VERSION}_linux_amd64.bz2 \ | |
| bzip2 -d >/usr/local/bin/restic \ | |
&& chmod +x /usr/local/bin/restic | |
ARG RCLONE_VERSION='1.56.1' | |
RUN cd /usr/local/bin \ | |
&& wget -O - https://downloads.rclone.org/v${RCLONE_VERSION}/rclone-v${RCLONE_VERSION}-linux-amd64.zip \ | |
| busybox unzip -j - '*/rclone' \ | |
&& chmod +x /usr/local/bin/rclone | |
VOLUME ["/root/.config/rclone", "/root/.cache/restic"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zsh | |
emulate -R zsh | |
# List of root datasets, recursive snapshots will be applied to these. | |
typeset -a root_dataset=( | |
rpool/data | |
) | |
# List of datasets to be backed up, must be within a root dataset. | |
typeset -a datasets=( | |
${(f)"$(zfs list -r -o name -H rpool/data/backup)"} | |
rpool/data/private | |
rpool/data/share | |
rpool/data/share/home/user | |
rpool/data/share/public | |
) | |
cleanup() { | |
# Clean up the recursive snapshots. | |
for rd in $root_dataset; do | |
zfs destroy -r -v $rd@restic | |
done | |
} | |
# Create recursive snapshots of the entire root dataset for accurate | |
# point-in-time state. | |
for rd in $root_dataset; do | |
zfs snapshot -r $rd@restic | |
done | |
trap 'exit $?' INT HUP | |
trap 'cleanup' EXIT | |
# List of mountpoints for the datasets. | |
dirs=(${(f)"$(zfs get -H mountpoint -o value ${(o)datasets})"}) | |
typeset -a vol_args=() included=() | |
for d in $dirs; do | |
# Simple logic to try to determine hierarchy, may not work | |
# properly with some fringe combination of zfs mountpoints. | |
d2=$(zfs get mountpoint -o value -H ${d:h}) | |
if (( ${included[(I)$d2]} )); then | |
typeset -a parents=() | |
# Parent not included, check if it must be (to avoid the | |
# need to create a directory on a read-only filesystem). | |
while [[ $d2 != / ]]; do | |
if (( ${included[(I)$d2]} )); then | |
parents+=($d2) | |
else | |
for parent in ${(o)parents}; do | |
print Including parent $parent | |
vol_args+=(-v ${parent}:${parent}:ro) | |
included=+($parent) | |
done | |
break | |
fi | |
d2=$(zfs get mountpoint -o value -H ${d2:h}) | |
done | |
fi | |
vol_args+=(-v ${d}/.zfs/snapshot/restic:${d}:ro) | |
included=+($d) | |
done | |
if ! docker volume ls --format '{{.Name}}' | grep -q '^restic-cache$'; then | |
# Create the restic cache volume. | |
docker volume create restic-cache | |
fi | |
# NOTE: restic-rclone.env sets the RESTIC_PASSWORD environment variable. | |
docker run -it --rm \ | |
--env-file /root/.config/restic/restic-rclone.env \ | |
-v /root/.config/rclone:/root/.config/rclone \ | |
-v /root/.config/restic/restic-rclone.include:/root/.config/restic/restic-rclone.include:ro \ | |
-v /root/.config/restic/restic-rclone.exclude:/root/.config/restic/restic-rclone.exclude:ro \ | |
-v restic-cache:/root/.cache/restic \ | |
$vol_args \ | |
mafredri/restic-rclone restic -r rclone:remote:restic-repo backup \ | |
--one-file-system \ | |
--files-from /root/.config/restic/restic-rclone.include \ | |
--exclude-file /root/.config/restic/restic-rclone.exclude \ | |
$dirs | |
# Give time to settle. Hopefully avoid: | |
# cannot destroy snapshot rpool/...@restic: dataset is busy. | |
sleep 5 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment