Skip to content

Instantly share code, notes, and snippets.

@stackcoder
Last active September 6, 2025 15:06
Show Gist options
  • Select an option

  • Save stackcoder/ccb3b17812ed11700ee83d762b970b98 to your computer and use it in GitHub Desktop.

Select an option

Save stackcoder/ccb3b17812ed11700ee83d762b970b98 to your computer and use it in GitHub Desktop.

Revisions

  1. stackcoder revised this gist Jun 9, 2024. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,7 @@ Inspired by:
    - http://web.archive.org/web/20231229002833/https://www.aarsen.me/posts/2022-02-15-sweet-unattended-backups.html#pulling-it-together

    See Also:
    - https://github.com/Freaky/zfsnapr
    - https://github.com/restic/restic/pull/3200
    - https://github.com/restic/restic/pull/3599
    - https://github.com/restic/restic/issues/4026
  2. stackcoder revised this gist Jun 9, 2024. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    # Atomic restic backup using zfs snapshots

    Inspired by:
    - http://web.archive.org/web/20231229002833/https://www.aarsen.me/posts/2022-02-15-sweet-unattended-backups.html#pulling-it-together

  3. stackcoder revised this gist Jun 9, 2024. 1 changed file with 7 additions and 0 deletions.
    7 changes: 7 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    Inspired by:
    - http://web.archive.org/web/20231229002833/https://www.aarsen.me/posts/2022-02-15-sweet-unattended-backups.html#pulling-it-together

    See Also:
    - https://github.com/restic/restic/pull/3200
    - https://github.com/restic/restic/pull/3599
    - https://github.com/restic/restic/issues/4026
  4. stackcoder revised this gist Jun 9, 2024. No changes.
  5. stackcoder created this gist Jun 9, 2024.
    38 changes: 38 additions & 0 deletions restic-zfs-no-mount.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,38 @@
    #!/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[@]}"
    60 changes: 60 additions & 0 deletions restic-zfs.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,60 @@
    #!/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"