├── LICENSE ├── README.md ├── zfsbackup.sh └── zfscron.sh /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Michael Shirk 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zfsbackup 2 | ========= 3 | 4 | This is a collection of scripts that simplify the management of ZFS snapshots for filesystem backups. 5 | 6 | *zfsbackup.sh 7 | ------------- 8 | 9 | This script will maintain an incremental zfs snapshot that is mirrored on a remote system (today/yesterday). 10 | The script assumes the dataset exists on the remote system, and the steps are documented on how to setup 11 | this functionality if the remote dataset is missing. 12 | 13 | *zfscron.sh 14 | ----------- 15 | 16 | This script is designed to be run every half hour, and maintains a snapshot for the previous hour. 17 | To setup this script, add the following to the user's cronjob with permissions to perform snapshots 18 | 19 | `*/30 * * * * /usr/home/test/zfscron.sh` 20 | 21 | 22 | Note: 23 | ---- 24 | These scripts should be used by a non-root user setup and configured to create/destroy snapshots. 25 | The following will setup the necessary permissions for the zfs-user to manage snapshots for zroot: 26 | 27 | `zfs allow -du zfs-user create,compression,destroy,snapshot,snapdir,hold,mount,mountpoint,send,rename,receive,quota,refquota zroot` 28 | 29 | -------------------------------------------------------------------------------- /zfsbackup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/csh 2 | # 3 | # Daily ZFS Backup Script 4 | # Version 1.5 5 | # 6 | # Works on FreeBSD 10+ 7 | # Based on the FreeBSD Handbook entry for zfs send with ssh 8 | # https://www.freebsd.org/doc/handbook/zfs-zfs.html#zfs-send-ssh 9 | # 10 | # NOTE: This script assumes a snapshot has been created and 11 | # already sent over to the remote system before this script is run. 12 | # If this snapshot is not available, the script provides the steps 13 | # you need to create the initial snapshots. This script also 14 | # assumes the destination dataset is not the main pool (ex. zroot) 15 | # so you do not give an underprivileged user access to destroy your 16 | # zroot. There is also no method to handle users messing with the 17 | # destination pool, which will break the incremental snapshot being 18 | # sent over. 19 | # 20 | # WARNING: With this backup script setup, the backed-up dataset 21 | # will not be mountable, you have the choice of either cloning the 22 | # dataset to access your data, or temporarily mounting your dataset 23 | # and getting access to your data. You can use the following to 24 | # mount the data set read only: 25 | # zfs mount -oro dataset /mnt 26 | # 27 | # Copyright (c) 2021, Michael Shirk 28 | # All rights reserved. 29 | # 30 | # Redistribution and use in source and binary forms, with or without modification, 31 | # are permitted provided that the following conditions are met: 32 | # 33 | # 1. Redistributions of source code must retain the above copyright notice, this 34 | # list of conditions and the following disclaimer. 35 | # 36 | # 2. Redistributions in binary form must reproduce the above copyright notice, 37 | # this list of conditions and the following disclaimer in the documentation 38 | # and/or other materials provided with the distribution. 39 | # 40 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 41 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 42 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 43 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 44 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 45 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 46 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 47 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 48 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 49 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 50 | 51 | setenv PATH "/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/root/bin" 52 | 53 | #Modify the variables for your configuration (datasets need leading slashes added) 54 | set SRCPOOL = "zroot" 55 | set SRCDATASET = "/usr/home/zfs-user" 56 | set DSTPOOL = "zroot" 57 | set DSTDATASET = "/homeback" 58 | set USERNAME = "zfs-user" 59 | set REMOTE = "" 60 | #Set this to the SSH key to be used. 61 | set SSHKEY = "" 62 | 63 | #Test to verify we are not running as root. 64 | set USERTEST = `id -u` 65 | if ($USERTEST == 0) then 66 | echo 67 | echo "Error: root user should not run this script. Setup a non-root user and grant" 68 | echo "the necessary zfs permissions with zfs allow." 69 | echo "Example:" 70 | echo "# zfs allow -du $USERNAME create,compression,destroy,snapshot,snapdir,hold,mount,mountpoint,send,rename,receive,quota,refquota $SRCPOOL$SRCDATASET" 71 | echo 72 | exit 13 73 | endif 74 | 75 | #Test to verify the user account has the proper permissions to create/destroy snaphots 76 | set ZFSSNAP = `zfs allow $SRCPOOL$SRCDATASET|grep snapshot|grep mount|grep create \ 77 | |grep destroy|grep hold|grep send|grep receive|grep rename|head -n1|grep -oE $USERNAME` 78 | 79 | if ($ZFSSNAP != $USERNAME) then 80 | echo 81 | echo "Error: User account $USERNAME does not have permissions to create/destroy snapshots." 82 | echo "Adjust the permissions on $SRCPOOL$SRCDATASET and try again." 83 | echo "Example:" 84 | echo "# zfs allow -du $USERNAME create,compression,destroy,snapshot,snapdir,hold,mount,mountpoint,send,rename,receive,quota,refquota $SRCPOOL$SRCDATASET" 85 | echo 86 | exit 13 87 | endif 88 | 89 | #Test to see if the user has been setup with SSH keys 90 | clear 91 | echo "Verifying that SSH keys have been setup" 92 | if (-e $SSHKEY) then 93 | echo "Success." 94 | else 95 | echo 96 | echo "Error: SSH key has not been setup on the local system." 97 | echo 98 | exit 13 99 | endif 100 | 101 | #Test to ensure the remote system is available 102 | echo 103 | echo "Testing connectivity to $REMOTE" 104 | set TEST = `ssh -i $SSHKEY $USERNAME@$REMOTE hostname` 105 | 106 | if ($status != 0) then 107 | echo 108 | echo "Error: Unable to connect to remote system. Check for network/SSH issues and try again" 109 | echo 110 | exit 13 111 | else 112 | echo "Success." 113 | endif 114 | 115 | #Test to ensure the DSTPOOL exists, otherwise the zfs send will fail 116 | echo 117 | echo "Validating the destination zpool $DSTPOOL$DSTDATASET exists" 118 | set TEST = `ssh -i $SSHKEY $USERNAME@$REMOTE zfs list $DSTPOOL$DSTDATASET` 119 | if ($status != 0) then 120 | echo 121 | echo "Error: $DSTPOOL$DSTDATASET does not exist. You have to run the following commands to have" 122 | echo "an initial setup of the dataset, which is required before using this backup script" 123 | echo 124 | echo "Run the following on $REMOTE" 125 | echo "# zfs create -po mountpoint=none -o canmount=noauto $DSTPOOL$DSTDATASET$SRCDATASET" 126 | echo "# zfs allow -du $USERNAME create,compression,destroy,snapshot,snapdir,hold,mount,mountpoint,send,rename,receive,quota,refquota $DSTPOOL$DSTDATASET" 127 | echo "Run the following on your local system (if the snapshots do not exist)" 128 | echo "$ zfs snapshot -r $SRCPOOL$SRCDATASET@today" 129 | echo "$ zfs rename -r $SRCPOOL$SRCDATASET@today @yesterday" 130 | echo "$ zfs snapshot -r $SRCPOOL$SRCDATASET@today" 131 | echo "$ zfs send -vR $SRCPOOL$SRCDATASET@today | ssh -i $SSHKEY $USERNAME@$REMOTE zfs recv -dvuF $DSTPOOL$DSTDATASET" 132 | echo 133 | exit 13 134 | else 135 | echo "Success." 136 | endif 137 | 138 | #Test to verify the remote user account has the proper permissions to create/destroy snaphots 139 | set ZFSSNAP = `ssh -i $SSHKEY $USERNAME@$REMOTE zfs allow $DSTPOOL$DSTDATASET|grep snapshot|grep mount|grep create \ 140 | |grep destroy|grep hold|grep send|grep receive|grep rename|head -n1|grep -oE $USERNAME` 141 | 142 | if ($ZFSSNAP != $USERNAME) then 143 | echo 144 | echo "Error: User account $USERNAME does not have permissions to create snapshots." 145 | echo "Adjust the permissions on the $DSTPOOL$DSTDATASET dataset on $REMOTE and try again." 146 | echo "Example:" 147 | echo "# zfs allow -du $USERNAME create,destroy,snapshot,hold,mount,mountpoint,send,rename,receive,quota,refquota $DSTPOOL$DSTDATASET" 148 | echo 149 | exit 13 150 | endif 151 | 152 | #Test to verify the vfs.usermount setting is correct 153 | set VFSMOUNT=`ssh -i $SSHKEY $USERNAME@$REMOTE sysctl -n vfs.usermount` 154 | 155 | if ($VFSMOUNT != 1) then 156 | echo 157 | echo "Error: Unprivileged users must have permissions to mount file systems." 158 | echo "Ensure vfs.usermount is set to 1 on the $REMOTE system." 159 | echo 160 | exit 13 161 | endif 162 | 163 | #check that the current snapshot even exists before deleting the yesterday snapshot 164 | echo 165 | echo "Checking for current snapshot on the remote system" 166 | ssh -i $SSHKEY $USERNAME@$REMOTE zfs list $DSTPOOL$DSTDATASET$SRCDATASET@today > /dev/null 167 | if ($status != 0) then 168 | echo 169 | echo "Error: today snapshot missing from $REMOTE system." 170 | echo "An initial setup of the dataset is required before using this" 171 | echo "backup script, otherwise this script will not properly handle deleting the old" 172 | echo "snapshots." 173 | echo "Run the following on your local system (if the snapshots do not exist)" 174 | echo "$ zfs snapshot -r $SRCPOOL$SRCDATASET@today" 175 | echo "$ zfs send -vR $SRCPOOL$SRCDATASET@today | ssh -i $SSHKEY $USERNAME@$REMOTE zfs recv -dvuF $DSTPOOL$DSTDATASET" 176 | exit 13 177 | else 178 | echo "Success." 179 | endif 180 | 181 | #Everything appears to be working, now to continue with the backup 182 | echo 183 | echo "Removing yesterday's snapshot from the remote system" 184 | ssh -i $SSHKEY $USERNAME@$REMOTE zfs destroy $DSTPOOL$DSTDATASET$SRCDATASET@yesterday 185 | if ($status != 0) then 186 | echo 187 | echo "Error: Unable to remove yesterday snapshot from $REMOTE system." 188 | echo "An initial setup of the dataset is required before using this " 189 | echo "backup script, otherwise this script will not properly handle deleting the old" 190 | echo "snapshots." 191 | echo 192 | echo "Run the following on your local system (if the snapshots do not exist)" 193 | echo "$ zfs snapshot -r $SRCPOOL$SRCDATASET@today" 194 | echo "$ zfs rename -r $SRCPOOL$SRCDATASET@today @yesterday" 195 | echo "$ zfs snapshot -r $SRCPOOL$SRCDATASET@today" 196 | echo "$ zfs send -vR $SRCPOOL$SRCDATASET@today | ssh -i $SSHKEY $USERNAME@$REMOTE zfs recv -dvu $DSTPOOL$DSTDATASET" 197 | echo 198 | exit 13 199 | else 200 | echo "Success." 201 | endif 202 | 203 | echo 204 | echo "Removing yesterday's snapshot from the local system" 205 | zfs destroy $SRCPOOL$SRCDATASET@yesterday 206 | if ($status != 0) then 207 | echo 208 | echo "Error: Unable to remove yesterday snapshot from local system." 209 | echo 210 | exit 13 211 | else 212 | echo "Success." 213 | endif 214 | 215 | #Renaming the previous snapshot to yesterday 216 | echo 217 | echo "Renaming snapshot on the local system" 218 | zfs rename -r $SRCPOOL$SRCDATASET@today @yesterday 219 | if ($status != 0) then 220 | echo 221 | echo "Error: Unable to rename today snapshot on local system" 222 | echo 223 | exit 13 224 | else 225 | echo "Success." 226 | endif 227 | 228 | echo 229 | echo "Renaming snapshot on the remote system" 230 | ssh -i $SSHKEY $USERNAME@$REMOTE zfs rename -r $DSTPOOL$DSTDATASET$SRCDATASET@today @yesterday 231 | if ($status != 0) then 232 | echo 233 | echo "Error: Unable to rename today snapshot on $REMOTE target $DSTPOOL$DSTDATASET$SRCDATASET" 234 | echo 235 | exit 13 236 | else 237 | echo "Success." 238 | endif 239 | 240 | #Create the snapshot for today 241 | echo 242 | echo "Running snapshot for today"; 243 | zfs snapshot -r $SRCPOOL$SRCDATASET@today 244 | if ($status != 0) then 245 | echo 246 | echo "Error: Unable to create today snapshot on local system" 247 | echo 248 | exit 13 249 | else 250 | echo "Success." 251 | endif 252 | 253 | #Sending the backup of the snapshot to the remote system 254 | echo 255 | echo "Backup snapshot for today"; 256 | zfs send -R -i $SRCPOOL$SRCDATASET@yesterday $SRCPOOL$SRCDATASET@today | ssh -i $SSHKEY $USERNAME@$REMOTE zfs recv -du $DSTPOOL$DSTDATASET 257 | if ($status != 0) then 258 | echo 259 | echo "Error: Unable to send snapshots to $REMOTE target $DSTPOOL$DSTDATASET" 260 | echo 261 | exit 13 262 | else 263 | echo "Success." 264 | endif 265 | 266 | echo 267 | echo "Daily Backup Completed." 268 | echo 269 | exit 270 | -------------------------------------------------------------------------------- /zfscron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/csh 2 | # 3 | # ZFS Snapshots with Cron 4 | # Version 0.1 5 | # 6 | # Based on FreeBSD 10.1 7 | # 8 | # Copyright (c) 2015, Michael Shirk 9 | # All rights reserved. 10 | # 11 | # Redistribution and use in source and binary forms, with or without modification, 12 | # are permitted provided that the following conditions are met: 13 | # 14 | # 1. Redistributions of source code must retain the above copyright notice, this 15 | # list of conditions and the following disclaimer. 16 | # 17 | # 2. Redistributions in binary form must reproduce the above copyright notice, 18 | # this list of conditions and the following disclaimer in the documentation 19 | # and/or other materials provided with the distribution. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | # Add the following to the user's cronjob with permissions to perform snapshots 33 | # */30 * * * * /usr/home/test/zfscron.sh 34 | 35 | setenv PATH "/sbin:/bin:/usr/sbin:/usr/bin:/usr/games:/usr/local/sbin:/usr/local/bin:/root/bin" 36 | 37 | # Modify the variables for your configuration 38 | set POOL = "tank/root" 39 | 40 | # Verify the current user has permissions for snapshots 41 | set ZFSSNAP = `zfs allow $POOL|grep snapshot|grep mount|grep create \ 42 | |grep destroy|grep hold|grep send|grep receive|grep rename|head -n1|grep -oE $USER` 43 | 44 | if ($ZFSSNAP != $USER) then 45 | echo "Error: User account $USER does not have permissions to create/destroy snapshots on $POOL." 46 | exit 13 47 | endif 48 | 49 | # The script is based on running cron every half hour, adjust as needed 50 | 51 | set TEST60 = `zfs list -t snapshot $POOL@60 >& /dev/null` 52 | 53 | if ($status == 0) then 54 | set TEST30 = `zfs list -t snapshot $POOL@30 >& /dev/null` 55 | if ($status == 0) then 56 | # Remove the 1 hour backup 57 | zfs destroy $POOL@60 58 | # Rename the 30 minute as 1 hour 59 | zfs rename $POOL@30 @60 60 | # Create the 30 minute snapshot 61 | zfs snapshot -r $POOL@30 62 | else 63 | # Rename the 1 hour snapshot to 30 64 | zfs rename $POOL@60 @30 65 | endif 66 | else 67 | set TEST30 = `zfs list -t snapshot $POOL@30 >& /dev/null` 68 | if ($status == 0) then 69 | # Rename the 30 minute as 1 hour 70 | zfs rename $POOL@30 @60 71 | # Create the 30 minute snapshot 72 | zfs snapshot -r $POOL@30 73 | else 74 | # First time run, create the 30 minute snapshot 75 | zfs snapshot -r $POOL@30 76 | endif 77 | 78 | endif 79 | 80 | --------------------------------------------------------------------------------