From b6e77390279ca0b5ae4953df8f651311420f6238 Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Wed, 11 Oct 2017 23:36:34 -0400 Subject: Initial commit. --- README | 64 ++++++++++++++++++++++++++++++++++++++ com.kublai.zfs.make-snapshot.plist | 28 +++++++++++++++++ cull-snapshots | 3 ++ make-snapshot | 6 ++++ rrolback | 20 ++++++++++++ send-backup | 15 +++++++++ send-to-babar | 37 ++++++++++++++++++++++ zbackup.pl | 47 ++++++++++++++++++++++++++++ zdest.pl | 7 +++++ 9 files changed, 227 insertions(+) create mode 100644 README create mode 100644 com.kublai.zfs.make-snapshot.plist create mode 100755 cull-snapshots create mode 100755 make-snapshot create mode 100755 rrolback create mode 100755 send-backup create mode 100755 send-to-babar create mode 100755 zbackup.pl create mode 100755 zdest.pl diff --git a/README b/README new file mode 100644 index 0000000..e9a6a66 --- /dev/null +++ b/README @@ -0,0 +1,64 @@ +Names are wacky. This is for my personal use, so it's not exactly +config-file-ified. Just edit the scripts. For the record, my setup is: + + * macOS laptop running OpenZFSonOSX that houses a zpool for my home + directory on a Core Storage encrypted volume. + + hostname: dialga + pool: zhome + filesystems: bjc + + * FreeBSD NAS which accepts snapshots from macOS laptop for backup + purposes, as well as housing long-term archival stuff (music, + random software, movies, etc.,) on a 5 drive RAIDZ1 setup. + + hostname: ditto + pool: babar + filesystems: bjc (unmounted snapshots from zhome) + shared (media) + various snapshots from when I was moving data + around that I haven't needed to delete yet. + + * External USB drive for full backup of RAIDZ1 pool from FreeBSD NAS + (or, at least as much of the most recent data it can get once it + fills up). + + hostname: ditto + pool: backup + filesystems: all of the above (unmounted snapshots) + +Permissions on zhome: +Local+Descendent permissions: + user bjc compression,create,hold,mount,mountpoint,receive,send,snapshot +Permissions on babar/bjc: +Local+Descendent permissions: + user bjc compression,create,hold,mount,mountpoint,receive,send,snapshot + +It's a good idea to run send-to-babar before running cull-snapshots, +because only the most recent snapshot is kept after a run of +send-backup, so if you accidentally delete that snapshot from the NAS, +you're going to have a bad time. At some point this should migrate to +bookmarks, rather than snapshots, so that's no longer possible, but I +haven't done that yet. I probably will after I screw up following my +own instructions and hate my life for a week. + +cull-snapshots tries to be Time Machine like, and keep hourly +snapshots for 24 hours, then daily snapshots for a month, then go +weekly forever. There's no logic for removing old weekly snapshots, +because I've never come close to running out of space, so it can just +be done by hand. + +send-backup is used approximately once per week or so, when I plug in +an external USB drive to sync it up with the NAS, in case of +catastrophic NAS failure. Scrubs are run approximately every month on +both the NAS and the USB. + +Locations for files: + +On Mac: + * com.kublai.zfs.make-snapshot.plist -> ~/Library/LaunchAgents + * make-snapshot -> wherever it's referenced by launch agent .plist + * send-to-babar -> somewhere in $PATH + +On NAS: + * zbackup.pl, zdest.pl, cull-snapshots send-backup -> /usr/local/bin diff --git a/com.kublai.zfs.make-snapshot.plist b/com.kublai.zfs.make-snapshot.plist new file mode 100644 index 0000000..0bc6e60 --- /dev/null +++ b/com.kublai.zfs.make-snapshot.plist @@ -0,0 +1,28 @@ + + + + + Label + com.kublai.zfs.make-snapshot + + RunAtLoad + + + Disabled + + + ProgramArguments + + /Users/bjc/src/MyStuff/make-snapshot + + + StandardOutPath + /tmp/make-snapshot.out + StandardErrorPath + /tmp/make-snapshot.err + + StartInterval + 3600 + + diff --git a/cull-snapshots b/cull-snapshots new file mode 100755 index 0000000..692eee9 --- /dev/null +++ b/cull-snapshots @@ -0,0 +1,3 @@ +#!/bin/sh + +zfs list -t snap | /usr/local/bin/zbackup.pl | /usr/local/bin/zdest.pl diff --git a/make-snapshot b/make-snapshot new file mode 100755 index 0000000..f2930ac --- /dev/null +++ b/make-snapshot @@ -0,0 +1,6 @@ +#!/bin/sh + +PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin + +stamp=`date +%Y-%m-%d-%H%M%S` +zfs snapshot zhome/bjc@$stamp diff --git a/rrolback b/rrolback new file mode 100755 index 0000000..7a83aa8 --- /dev/null +++ b/rrolback @@ -0,0 +1,20 @@ +#!/usr/bin/bash +# +# Dr. Martin Menzel +# Dr. Menzel IT - www.dr-menzel-it.de +# 11.08.2013 +# +# Use at your own risk. No warranty. No fee. +# +# parameter list: +# (1) the filesystem to be used to start decendant recursion +# example: apool/zones/webzone +# (2) the snapshot to which the filesystems should be rolled back +# example: @2013-07-21-083500 +# +for snap in `zfs list -H -t snapshot -r $1 | grep "$2" | cut -f 1`; do + # -r : also destroys the snapshots newer than the specified one + # -R : also destroys the snapshots newer than the one specified and their clones + # -f : forces an unmount of any clone file systems that are to be destroyed + echo -n "rolling back to [$snap] : ";zfs rollback -r -R -f $snap; echo " Done." +done diff --git a/send-backup b/send-backup new file mode 100755 index 0000000..520cf0b --- /dev/null +++ b/send-backup @@ -0,0 +1,15 @@ +#!/bin/sh + +src=babar +dst=backup + +datasets=`zfs list -Ho name -r $src | tail -n +2 | cut -d/ -f2-` +for fs in $datasets; do + from=`zfs list -Ht snap -d 1 -o name -s creation $dst/$fs | tail -1 | cut -d@ -f2` + to=`zfs list -Ht snap -d 1 -o name -s creation $src/$fs | tail -1 | cut -d@ -f2` + + echo send $fs@$from to $fs@$to + if [ "x$from" != "x$to" ]; then + zfs send -RI babar/$fs@$from babar/$fs@$to | zfs recv backup/$fs + fi +done diff --git a/send-to-babar b/send-to-babar new file mode 100755 index 0000000..139e058 --- /dev/null +++ b/send-to-babar @@ -0,0 +1,37 @@ +#!/bin/sh + +src=zhome/bjc +dst=babar/bjc + +host=ditto.local + +first=`ssh $host zfs list -Hd1 -t snap -o name -s creation $dst | cut -d@ -f2 | tail -1` +last=`zfs list -Hd1 -t snap -o name -s creation $src | cut -d@ -f2 | tail -1` + +if [ $first != $last ]; then + echo "Sending $src from $first to $last" + zfs send -I $src@$first $src@$last | ssh $host zfs recv $dst + if [ $? -ne 0 ]; then + echo "Couldn't send snapshot stream" 1>&2 + exit 1 + fi +else + echo "Skipping snapshot replication: $host:$dst@$last already exists." +fi + +echo "Verifying final snapshot is $dst@$last" +verify=`ssh $host zfs list -Hd1 -t snap -o name $dst | grep $last` +if [ x"$verify" == x ]; then + echo "Last snapshot not sent on $host:$dst" 1>&2 + exit 2 +fi + +echo "Removing local snapshots up to $src@$last" +for ds in `zfs list -Hd1 -t snap -o name -s creation $src | cut -d@ -f2`; do + if [ $ds == $last ]; then + break + fi + sudo zfs destroy $src@$ds +done + +echo "Finished. Last snapshot sent: $src@$last" diff --git a/zbackup.pl b/zbackup.pl new file mode 100755 index 0000000..a47b22f --- /dev/null +++ b/zbackup.pl @@ -0,0 +1,47 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Date::Calc qw(Today_and_Now Delta_YMD Date_to_Time); + +# Time machine policy +# hourly for last 24 hours, daily for month, weekly for everything else. + +my $FUDGE = 900; # Number of seconds to allow for something to be w/i a day. + +my %vols; +my %today; +($today{year}, $today{mon}, $today{day}, $today{hour}, $today{min}, $today{sec}) = Today_and_Now(); +my $now = Date_to_Time($today{year}, $today{mon}, $today{day}, $today{hour}, $today{min}, $today{sec}); + +while (my $line = <>) { + next unless $line =~ /([^@]+)@(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})(\d{2})/; + my($volname, $year, $mon, $day, $hour, $min, $sec) = ($1, $2, $3, $4, $5, $6, $7); + my $then = Date_to_Time($year, $mon, $day, $hour, $min, $sec); + + my %volinfo; + %volinfo = %{$vols{$volname}} if defined($vols{$volname}); + + my $shouldkeep = 0; + my($dy, $dm, $dd) = Delta_YMD($year, $mon, $day, $today{year}, $today{mon}, $today{day}); + $dm += $dy * 12; + if ($now - $then <= (86400 + $FUDGE)) { + # Keep everything less than a day old. + $shouldkeep = 1; + } elsif ($dm == 0 || ($dm == 1 && $dd >= 0)) { + # Less than a month old: only keep dailies. + if (!$volinfo{firstday} || $then - $volinfo{firstday} >= 86400) { + $volinfo{firstday} = $then; + $shouldkeep = 1; + } + } else { + # More than a month old: keep weeklies. + if (!$volinfo{firstweek} || $then - $volinfo{firstweek} >= (86400 * 7 - $FUDGE)) { + $volinfo{firstweek} = $then; + $shouldkeep = 1; + } + } + + $vols{$volname} = \%volinfo; + print $line unless $shouldkeep; +} diff --git a/zdest.pl b/zdest.pl new file mode 100755 index 0000000..b49a1b9 --- /dev/null +++ b/zdest.pl @@ -0,0 +1,7 @@ +#!/usr/bin/env perl + +while (<>) { + my ($fs) = split /\s+/; + print "Destroying $fs\n"; + system "zfs destroy $fs"; +} -- cgit v1.2.3