#!/usr/bin/env ruby # Synchronizes ZFS filesystems. # # Takes three args: source pool, destination pool, and filesystem name. # # Syncs most recent snapshot of filesystem from source to destination pool, # using incremental transfer if possible. # Take a snapshot line from zfs list -t snapshot and emit a struct class Snapshot attr_reader :pool attr_reader :fs attr_reader :snap def initialize(str) str =~ /\A(.+?)\/(.+?)@(.+?)\s+/ @pool, @fs, @snap = $1, $2, $3 end # Map back into the same representation ZFS tools use def to_s "#{pool}/#{fs}@#{snap}" end end # Parse args src_pool, dest_pool, fs = ARGV unless src_pool puts "No source pool given." exit 1 end unless dest_pool puts "No destination pool given." exit 1 end unless fs puts "No filesystem given." exit 1 end # All snapshots for the given filesystem snapshots = `zfs list -t snapshot`.lines.map do |l| Snapshot.new l end.select do |s| s.fs == fs end # Find most recent snapshots from both pools last_src = snapshots.select { |s| s.pool == src_pool }.last last_dest = snapshots.select { |s| s.pool == dest_pool }.last unless last_src puts "No snapshots for filesystem #{fs} on source pool #{src_pool}" exit 2 end unless last_dest puts "No snapshots for filesystem #{fs} on dest pool #{dest_pool}" exit 2 end if last_src.snap == last_dest.snap puts "Up to date! :D" exit 0 end # Find corresponding snapshot in source pool prev_src = snapshots.find do |s| s.pool == src_pool and s.snap == last_dest.snap end if prev_src # Incremental puts "Incremental sync" cmd = "zfs send -i '#{prev_src}' '#{last_src}' | zfs recv '#{dest_pool}/#{fs}'" else # Full puts "Full sync" cmd = "zfs send '#{last_src}' | zfs recv '#{dest_pool}/#{fs}'" end puts cmd exec cmd