aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README64
-rw-r--r--com.kublai.zfs.make-snapshot.plist28
-rwxr-xr-xcull-snapshots3
-rwxr-xr-xmake-snapshot6
-rwxr-xr-xrrolback20
-rwxr-xr-xsend-backup15
-rwxr-xr-xsend-to-babar37
-rwxr-xr-xzbackup.pl47
-rwxr-xr-xzdest.pl7
9 files changed, 227 insertions, 0 deletions
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>Label</key>
+ <string>com.kublai.zfs.make-snapshot</string>
+
+ <key>RunAtLoad</key>
+ <true/>
+
+ <key>Disabled</key>
+ <false/>
+
+ <key>ProgramArguments</key>
+ <array>
+ <string>/Users/bjc/src/MyStuff/make-snapshot</string>
+ </array>
+
+ <key>StandardOutPath</key>
+ <string>/tmp/make-snapshot.out</string>
+ <key>StandardErrorPath</key>
+ <string>/tmp/make-snapshot.err</string>
+
+ <key>StartInterval</key>
+ <integer>3600</integer>
+ </dict>
+</plist>
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";
+}