Cron based ZFS snapshot

Snapshot

ZFS snapshots are a simple and secure way to store a read-only state of a file system that can be restored in the event of accidental data loss, transferred, and replicated to another machine for backup.

I wanted the NAS to take a snapshot every day and keep the last 7 snapshots. Here’s how I did it:

# cat zfs_snapshot_create.sh
#!/usr/bin/env zsh

# Daily ZFS Snapshot with Pushover Notification

# CONFIGURATION
DATE=$(date +%Y-%m-%d)
SNAPSHOT_PREFIX="autobackup_${DATE}"
PUSHOVER_USER_KEY="${PUSHOVER_USER_KEY:-}"     # set via env
PUSHOVER_API_TOKEN="${PUSHOVER_API_TOKEN:-}"   # set via env
PUSHOVER_API_URL="https://api.pushover.net:443/1/messages.json"
LOGFILE="/var/log/zfs_snapshot_create.log"
HOSTNAME="$(hostname)"

# Function: Send pushover notification
send_pushover() {
    local title="$1"
    local message="$2"
    [[ -n "$PUSHOVER_USER_KEY" && -n "$PUSHOVER_API_TOKEN" ]] || return

    curl -s \
        -F "token=${PUSHOVER_API_TOKEN}" \
        -F "user=${PUSHOVER_USER_KEY}" \
        -F "title=${title}" \
        -F "message=${message}" \
        ${PUSHOVER_API_URL} >/dev/null
}

# Start Logging
{
    echo "[$(date)] Starting ZFS snapshot creation..."

    for pool in "tank" "rpool"; do
        SNAPSHOT_NAME="${pool}@${SNAPSHOT_PREFIX}"
        echo "Creating snapshot: ${SNAPSHOT_NAME}"

        if /usr/sbin/zfs snapshot -r "${SNAPSHOT_NAME}"; then
            echo "Snapshot ${SNAPSHOT_NAME} created successfully."
            send_pushover "ZFS Snapshot Success (${HOSTNAME})" \
                "Snapshot created: ${SNAPSHOT_NAME}"
        else
            echo "Error: Failed to create snapshot ${SNAPSHOT_NAME}"
            send_pushover "ZFS Snapshot FAILED (${HOSTNAME})" \
                "Snapshot failed: ${SNAPSHOT_NAME}"
        fi
    done

    echo "[$(date)] Snapshot creation finished."

} >> "$LOGFILE" 2>&1

This script creates autobackup_$currentdate snapshots recursively for the tank and rpool pools.

# cat zfs_snapshot_delete.sh

#!/usr/bin/zsh

# Configuration
PUSHOVER_USER_KEY="${PUSHOVER_USER_KEY:-}"     # set via env
PUSHOVER_API_TOKEN="${PUSHOVER_API_TOKEN:-}"   # set via env
PUSHOVER_API_URL="https://api.pushover.net:443/1/messages.json"
LOG_FILE="/var/log/zfs_snapshot_delete.log"
RETENTION=7  # Number of snapshots to retain

# Function to send Pushover notification
send_pushover_notification() {
    local message=$1
    local title=$2
    curl -s \
         -F "token=${PUSHOVER_API_TOKEN}" \
         -F "user=${PUSHOVER_USER_KEY}" \
         -F "message=${message}" \
         -F "title=${title}" \
         ${PUSHOVER_API_URL} > /dev/null 2>&1
}

# Function to delete old snapshots for a given ZFS pool
delete_old_snapshots() {
    local pool_name=$1
    local pool_label=$2

    echo "Checking snapshots for ${pool_name}..." | tee -a ${LOG_FILE}

    # Get the snapshots and delete all except the last RETENTION number of snapshots
    local snapshots
    snapshots=$(/usr/sbin/zfs list -d 1 -o name -t snapshot | grep "${pool_name}@autobackup" | tail -n +$(($RETENTION + 1)))

    if [ -n "$snapshots" ]; then
        echo "Deleting the following snapshots:" | tee -a ${LOG_FILE}
        echo "$snapshots" | tee -a ${LOG_FILE}
        echo "$snapshots" | xargs /usr/sbin/zfs destroy -r
        
        if [ $? -eq 0 ]; then
            local message="Successfully deleted old snapshots for ${pool_label}. Retained ${RETENTION} most recent snapshots."
            echo "$message" | tee -a ${LOG_FILE}
            send_pushover_notification "$message" "ZFS Snapshot Cleanup"
        else
            local message="Failed to delete snapshots for ${pool_label}. Please check the logs."
            echo "$message" | tee -a ${LOG_FILE}
            send_pushover_notification "$message" "ZFS Snapshot Cleanup"
        fi
    else
        # No snapshots to delete
        local message="No snapshots to delete for ${pool_label}. All snapshots are within the retention limit."
        echo "$message" | tee -a ${LOG_FILE}
        send_pushover_notification "$message" "ZFS Snapshot Cleanup"
    fi
}

# Main process
clear
echo "Starting ZFS snapshot cleanup process..." | tee -a ${LOG_FILE}
# send_pushover_notification "ZFS Snapshot Cleanup started." "ZFS Snapshot Cleanup"

# Delete snapshots for each pool
delete_old_snapshots "tank" "Tank Pool"
delete_old_snapshots "rpool" "Root Pool"

# Completion message
echo "ZFS snapshot cleanup completed." | tee -a ${LOG_FILE}
# send_pushover_notification "ZFS Snapshot Cleanup completed successfully." "ZFS Snapshot Cleanup"

To automatically run it, add the following to your root crontab:

# Create daily ZFS anapshots
10 6 * * * PUSHOVER_USER_KEY=xxxxx PUSHOVER_API_TOKEN=yyyyyyy /root/zfs_snapshot_create.sh

# Keep the last 7 snapshots and delete the rest:
20 6 * * * PUSHOVER_USER_KEY=xxxxx PUSHOVER_API_TOKEN=yyyyyyy /root/zfs_snapshot_delete.sh

This will create the snapshots every day at 6:10 AM and delete them at 6:20AM.

Pic