Scheduling Automatic ZFS Snapshots
A quick note with some helpul scripts.
          It's easy to snapshot a ZFS dataset with
          zfs-snapshot(8)
          but remembering to do this manually is error-prone. A better solution
          is to use
          systemd timers.
        
Create a Single Systemd Unit and Timer
Snapshots must have a unique name, and we'll need to generate that dynamically. Creating a simple script for this is helpul. The following is a simplified version of the script I use.
/usr/local/sbin/znap:
#!/bin/bash
# List all snapshots if no args are provided
if [[ $# -eq 0 ]]; then
    /usr/bin/zfs list -t snapshot
    exit
fi
# Treat each arg as a dataset and snapshot it
for dataset in "$@"; do
    snapname="$dataset@$(date --iso=s | head -c 19)"
    /usr/bin/zfs snapshot "$snapname"
done
          Now create a unit file to trigger snapshots. Let's assume the dataset
          my-data is in a pool called tank.
        
/etc/systemd/system/snapshot-my-data.service:
[Unit]
Description=Snapshot ZFS dataset "my-data"
[Service]
ExecStart=/usr/local/sbin/znap tank/my-data
Add a timer file.
/etc/systemd/system/snapshot-my-data.timer:
[Unit]
Description=Daily snapshot for my-data
[Timer]
OnCalendar=daily
AccuracySec=1h
Persistent=true
[Install]
WantedBy=timers.target
          Now we can activate the timer with
          systemctl daemon-reload && systemctl start
            snapshot-my-data.timer
          and confirm that the timer has started with
          systemctl list-timers.
        
Using an @ Service
          ZFS dataset names can't be used directly in an @ service because they
          contain / characters. The workaround I use is giving
          datasets aliases in a tab-separated configuration file. This is also
          helpul to avoid needing to remember the full paths to your important
          data sets when running znap manually!
        
/etc/zfs_alias:
my-data  mypool/my-data
Now update the znap script to parse the alias file.
/usr/local/bin/znap
#!/bin/bash
if [[ $# -eq 0 ]]; then
    /usr/bin/zfs list -t snapshot
    exic
fi
declare -A aliases
while read line; do
    if [[ -z "line" || "$line" = "\n" ]]; then
        continue
    fi
    line="$(sed 's/\t\t*/\t/' <<<"$line")"
    key="$(cut -f 1 <<<"$line")"
    val="$(cut -f 2 <<<"$line")"
    aliases["$key"]="$val"
done </etc/zfs_alias
for dataset in "$@"; do
    if ! /usr/bin/zfs list "$dataset"; then
        if [[ -z "${aliases["$dataset"]}" ]]; then
            echo "Neither dataset nor alias: $dataset"
            continue
        fi
        dataest="${aliases["$dataset"]}"
    fi
    snapname="$dataset@$(date --iso=s | head -c 19)"
    /usr/bin/zfs snapshot "$snapname"
done
The @ service is a simple modification to the unit service described above
/etc/systemd/system/znap@.service:
[Unit]
Description=Snapshot ZFS dataset "my-data"
[Service]
ExecStart=/usr/local/sbin/znap %i
Create a symlink to instantiate the @ service:
ln -s /etc/systemd/system/znap@.service /etc/systemd/system/znap@my-data.service
Now create a timer for the service:
/etc/systemd/system/znap@my-data.timer
[Unit]
Description=Daily snapshot for my-data
[Timer]
OnCalendar=daily
AccuracySec=1h
Persistent=true
[Install]
WantedBy=timers.target
          Finally activate the timer as above:
          systemctl daemon-reload && systemctl start
            znap@my-data.service.
        
Conclusion
          I hope you find the znap script and systemd tips helpful.
          Message me if you see any glaring errors or have
          suggestions!