Skip to content

Instantly share code, notes, and snippets.

@mafredri
Last active December 16, 2022 02:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mafredri/6864dd0a30dc4324b44131559f7adae2 to your computer and use it in GitHub Desktop.
Save mafredri/6864dd0a30dc4324b44131559f7adae2 to your computer and use it in GitHub Desktop.
Time Machine backup snapshotting on ZFS using zrepl for snapshot purging (for Samba server)
# /etc/cron.d/samba-timemachine-zfs-snapshot
# Create snapshots after completed Time Machine backups.
# Runs every two minutes to allow creating backups only after cleanup is
# completed.
*/2 * * * * root /usr/local/bin/timemachine-zfs-snapshot.sh rpool/share/timemachine
#!/usr/bin/env zsh
# /usr/local/bin/timemachine-zfs-snapshot.sh
setopt err_exit
PATH=/sbin:$PATH
DATASET=$1
# Custom logic for detecting when a client completed a Time Machine backup,
# we're not relying on Samba user disconnect scripts because Time Machine
# often leaves the connection open indefinitely.
#
# Dataset structure is [dataset]/[user], inside each user dataset there is
# expected to be only one device backup, multiple devices under a user may
# create snapshots at a point where one machines backup is incomplete.
for dir in $(zfs get mountpoint -o value -H $DATASET)/*; do
user=${dir:t}
for bundle in $dir/*.backupbundle; do
# Files must exist, Results for instance is present only after first completed backup.
if [[ ! -f $bundle/com.apple.TimeMachine.Results.plist ]] || [[ ! -f $bundle/com.apple.TimeMachine.SnapshotHistory.plist ]]; then
continue
fi
# Avoid creating backup before cleanup has been performed.
if (( $(stat -c '%Z' $bundle/com.apple.TimeMachine.Results.plist) < $(stat -c '%Z' $bundle/com.apple.TimeMachine.SnapshotHistory.plist) )); then
continue
fi
latest=$(grep -A1 com.apple.backupd.SnapshotCompletionDate $bundle/com.apple.TimeMachine.SnapshotHistory.plist | tail -n1 | cut -d'>' -f2 | cut -d'<' -f1 | tr T _ | cut -d: -f1-3 | tr -d : | tr -d Z | tr -d -)
latest=tm_${latest}_000 # zrepl prefix and standard timestamp suffix.
if zfs list -t snap -H $DATASET/$user@$latest 1>/dev/null 2>&1; then
continue
fi
# Time Machine can keep modifying files for a while after completion.
if (( $(find $bundle \( -mmin -3 -o -cmin -3 \) -print -quit | wc -l) > 0 )); then
continue
fi
zfs snap $DATASET/$user@$latest
done
done
# Trigger zrepl snapshot pruning and replication.
zrepl signal wakeup timemachine
# /etc/zrepl/zrepl.yml
jobs:
- name: timemachine
type: snap
filesystems:
"rpool/share/timemachine<": true
"rpool/share/timemachine": false
snapshotting:
type: manual
pruning:
keep:
- type: last_n
count: 15
regex: "^tm_.*"
- type: grid
grid: 7x1d | 2x1w
regex: "^tm_.*"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment