aboutsummaryrefslogtreecommitdiff
path: root/systemd-zfs-partition-backup.sh
blob: f012451e9ca46a8810d2eb380ec9245f8ea27702 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/bash
# Written in 2020 by Max Christian Pohle <webmaster@coderonline.de>

# check for a configuration file and source it in if its there
test -f /etc/default/systemd-zfs-partition-backup &&\
   source /etc/default/systemd-zfs-partition-backup

DISK_NAME=$1
logger -p debug "disk: $DISK_NAME" 

function beep() {
    test -f /usr/bin/beep &&\
        env -u SUDO_GID -u SUDO_COMMAND -u SUDO_USER -u SUDO_UID /usr/bin/beep $@
}

function fail() {
    # logger -p debug -t "$0" "Backup '$1' -> '$2' failed."
    logger -p debug "$@"
    beep -f 40 -l 500
    # sudo -u max beep -f 523 -l 150 -n -f 54 -l 600
}

function start() {
    UNIT_NAME=$(systemd-escape --suffix=automount $1)
    systemctl start "$UNIT_NAME" || (logger -p info "$UNIT_NAME not found." ; exit 1)
}

function stop() {
    UNIT_NAME=$(systemd-escape --suffix=automount $1)
    systemctl stop "$UNIT_NAME" || (logger -p info "$UNIT_NAME not found." ; exit 1)
}


# we assume, that we can mount the device somewhere under media
start "media/$DISK_NAME" # without leading slash for the unit file

DATASETS_WITH_DISKNAME=($(zfs list -H -o name | grep $DISK_NAME))

if [ ${#DATASETS_WITH_DISKNAME[@]} -eq 0 ]; then
    fail "No dataset found with '$DISK_NAME' as last part of its name."\
         "Please create such if you want to backup that partition."
    exit 0
fi

logger -p debug "${#DATASETS_WITH_DISKNAME[@]} Datasets: $DATASETS_WITH_DISKNAME"

# iterate over all datasets
for i in ${DATASETS_WITH_DISKNAME[@]};
do
    logger -p debug "assertion: "$i" =~ .*/$DISK_NAME"

    # compare the 'tail' of the PATH against the disk name...
    if [[ $i =~ .*/$DISK_NAME ]]; then

        DISK_MOUNTPOINT="/media/$DISK_NAME"
        

        if ! findmnt $DISK_MOUNTPOINT; then
            logger -p debug "Expected to find disk '$DISK_NAME' mounted under '$DISK_MOUNTPOINT'. But it is not there. Aborting backup."
            beep -f 40 -l 500
            exit 1
        fi


        DATASET=$i
        DATASET_LAST_SNAPSHOT=$(zfs list -t snap -o name -s creation "$DATASET"  | tail -n1) ||\
            (fail "we could not find a previous backup. Backup may still be possible (first time backup?)")

        ZFS_MOUNTPOINT=$(zfs get mountpoint -H -o value "$DATASET") ||\
            (fail "could not determine mountpoint for dataset '$DATASET'"; exit 1)

        # sanity check: is it really mounted?
        findmnt -no source "$ZFS_MOUNTPOINT" ||\
            (fail "Dataset $DATASET is not mounted at $ZFS_MOUNTPOINT" ; exit 1)

        logger -p debug "Backup from '$DISK_MOUNTPOINT' -> '$ZFS_MOUNTPOINT' possible. Starting..."
        beep -f 147 -l 120 -n -f 294 -l 200 -n -f 831

        set -x
        rsync -ahoi -x --delete --info=all \
            "$DISK_MOUNTPOINT/" "$ZFS_MOUNTPOINT/" \
            2>&1 | tee $ZFS_MOUNTPOINT/backup-report.txt
        set +x

        zfs snap $DATASET@$(date +%Y-%m-%d--%H-%M-%S) ||\
            (fail "Backup successful, but a snapshot of the dataset '$DATASET' was impossible :(")


        if [ -n "$SYSTEMD_ZFS_PARTITION_BACKUP_DIFF_FILENAME" ]; then
            zfs diff $DATASET_LAST_SNAPSHOT > $ZFS_MOUNTPOINT_VERIFIED/"$SYSTEMD_ZFS_PARTITION_BACKUP_DIFF_FILENAME"

            if [ -n "$SYSTEMD_ZFS_PARTITION_BACKUP_EMAIL" ]; then
                logger -p debug "Here comes a list of changes since your last backup: $LAST_SNAPSHOT" |\
                    mailx -A typesafe -a $ZFS_MOUNTPOINT_VERIFIED/"$SYSTEMD_ZFS_PARTITION_BACKUP_DIFF_FILENAME" -s 'Backup overview' "$SYSTEMD_ZFS_PARTITION_BACKUP_EMAIL"
            fi
        fi


        beep -f 831 -l 120 -n -f 294 -l 200 -n -f 147
    else
        logger -p error "assertion failed: [[ $i =~ .*/$DISK_NAME ]] => false"
    fi
done

stop "media/$DISK_NAME" # without leading slash for the unit file
..