Skip to content

Instantly share code, notes, and snippets.

@stackcoder
Last active June 9, 2024 23:43
Show Gist options
  • Save stackcoder/ccb3b17812ed11700ee83d762b970b98 to your computer and use it in GitHub Desktop.
Save stackcoder/ccb3b17812ed11700ee83d762b970b98 to your computer and use it in GitHub Desktop.
Atomic restic backup using zfs snapshots
#!/usr/bin/env bash
set -euo pipefail
# dataset to backup
dataset="my-pool/data"
# destroy leftover snapshots, usually already cleaned up by
for snap in $(zfs list -rt snap -Ho name "${dataset}"); do
if [[ "${snap}" =~ @restic-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$ ]]; then
zfs destroy "${snap}" && echo "leftover zfs snapshot '${snap}' destroyed"
fi
done
# create temporary snapshot for backup
snapshot="restic-$(cat /proc/sys/kernel/random/uuid)"
zfs snap -r "${dataset}@$snapshot"
# clean up temporary snapshot
_clean() {
zfs destroy -r "${dataset}@$snapshot" && echo "zfs snapshot '${dataset}@$snapshot' destroyed"
}
trap _clean EXIT
# get snapshot mountpoints of (sub) datasets
restic_args=()
for ds in $(zfs list -r -Ho name "${dataset}"); do
if [[ "$(zfs get -Ho value mountpoint "$ds")" == "none" ]]; then
continue
fi
path=$(findmnt -nr -o target -S "$ds")
snap="${path}/.zfs/snapshot/$snapshot"
restic_args+=( "${snap}" )
done
# backup temporary snapshot
restic backup \
--exclude-caches \
"${restic_args[@]}"
#!/usr/bin/env bash
set -euo pipefail
# dataset to backup
export ZFS_DATASET="my-pool/data"
# destroy leftover snapshots, usually already cleaned up
if [ -z "${EXEC_UNSHARED:-}" ]; then
for snap in $(zfs list -rt snap -Ho name "${ZFS_DATASET}"); do
if [[ "${snap}" =~ @restic-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$ ]]; then
zfs destroy "${snap}" && echo "leftover zfs snapshot '${snap}' destroyed"
fi
done
fi
# create temporary snapshot for backup
if [[ -z "${ZFS_SNAP:-}" ]]; then
ZFS_SNAP="restic-$(cat /proc/sys/kernel/random/uuid)"
zfs snap -r "${ZFS_DATASET}@${ZFS_SNAP}"
# clean up temporary snapshot on exit
_clean_snap() {
zfs destroy -r "${ZFS_DATASET}@${ZFS_SNAP}" && echo "zfs snapshot '${ZFS_DATASET}@${ZFS_SNAP}' destroyed"
}
trap _clean_snap EXIT
export ZFS_SNAP
fi
# mount snapshot and backup within private mount namespace
if [ -z "${EXEC_UNSHARED:-}" ]; then
export EXEC_UNSHARED=1
echo "re-executing '$0' in new private mount namespace.."
unshare --mount --propagation private "$0"
exit 0
fi
# use tmpfs for further mountpoints
root_mnt="${TMPDIR:-/var/tmp}/zsnapmounts"
mount -t tmpfs -o X-mount.mkdir tmpfs "${root_mnt}"
# mount snapshot of (sub) datasets
snap_mnts=()
for ds in $(zfs list -r -Ho name "${ZFS_DATASET}"); do
if [[ "$(zfs get -Ho value mountpoint "$ds")" == "none" ]]; then
continue
fi
mount -t zfs -o X-mount.mkdir "$ds@${ZFS_SNAP}" "${root_mnt}/$ds"
snap_mnts+=( "$ds" )
echo "mounted dataset '$ds@${ZFS_SNAP}'"
done
# backup temporary snapshot
echo -e "start restic backup\n"
pushd "${root_mnt}" > /dev/null
restic backup -v \
--exclude-caches \
"${snap_mnts[@]}"
echo -e "\nrestic backup done"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment