module ZfsMgmt::Restic
Public Class Methods
backup(backup_level: 2, options: {})
click to toggle source
# File lib/zfs_mgmt/restic.rb, line 65 def self.backup(backup_level: 2, options: {}) ZfsMgmt.zfs_managed_list(filter: options['filter'], properties: ['name', 'zfsmgmt:restic_backup', 'zfsmgmt:restic_repository', 'userrefs', ], property_match: { 'zfsmgmt:restic_backup' => ['on','true'] }).each do |blob| zfs,props,zfs_snapshots = blob last_zfs_snapshot = zfs_snapshots.keys.sort { |a,b| zfs_snapshots[a]['creation'] <=> zfs_snapshots[b]['creation'] }.last zfs_snap_time = Time.at(zfs_snapshots[last_zfs_snapshot]['creation']) level = 0 chain = [] zfs_snap_parent = '' restic_snap_parent = '' (restic_snapshots,restic_snapshot_zfs_snapshot_index) = restic_snapshots(zfs,options,props) if restic_snapshot_zfs_snapshot_index.has_key?(last_zfs_snapshot) $logger.warn("backup of this snapshot #{last_zfs_snapshot} already exists in restic, cannot continue with backup of #{zfs}") next # next zfs filesystem to be backed up end if backup_level > 0 and restic_snapshots.count > 0 # reverse (oldest first) sorted restic snapshots restic_snap_parent = restic_snapshots.filter { |rsnap| rsnap.has_key?('zfsmgmt:zfs') and rsnap['zfsmgmt:zfs'] == zfs and rsnap.has_key?('zfsmgmt:level') and rsnap['zfsmgmt:level'] < backup_level }.sort { |a,b| a['date_time'] <=> b['date_time'] }.last if restic_snap_parent and zfs_snapshots.has_key?(restic_snap_parent['zfsmgmt:snapshot']) and chain = valid_chain(restic_snap_parent,restic_snapshots,restic_snapshot_zfs_snapshot_index,[]) and chain.length > 0 level = restic_snap_parent['zfsmgmt:level'] + 1 zfs_snap_parent = restic_snap_parent['zfsmgmt:snapshot'] $logger.debug("restic_snap_parent: level: #{restic_snap_parent['zfsmgmt:level']} snapshot: #{zfs_snap_parent}") else $logger.error("restic_snap_parent rejected: level: #{restic_snap_parent['zfsmgmt:level']} snapshot: #{restic_snap_parent['zfsmgmt:snapshot']}") end $logger.debug("chain of snapshots: #{chain}") end tags = [ 'zfsmgmt', "zfsmgmt:snapshot=#{last_zfs_snapshot}", "zfsmgmt:zfs=#{zfs}", "zfsmgmt:level=#{level}" ] com = [ ZfsMgmt.global_options['zfs_binary'], 'send', '-w', '-h', '-p' ] if level > 0 if options[:intermediary] com.push('-I') else com.push('-i') end com.push(zfs_snap_parent) tags.push("zfsmgmt:parent=#{zfs_snap_parent}") end com.push( last_zfs_snapshot ) com.push( '|', 'mbuffer', '-m', options[:buffer], '-q' ) com.push( '|', options[:restic_binary], 'backup', '--stdin', '--stdin-filename', zfs, '--time', "\"#{zfs_snap_time.strftime('%F %T')}\"" ) tags.each do |tag| com.push( '--tag', "\"#{tag}\"" ) end if options.has_key?('limit_upload') com.push('--limit-upload', options['limit_upload']) end if options.has_key?('password_file') com.push('-p',options['password_file']) end if options.has_key?('repo') com.push('--repo', options['repo']) elsif props.has_key?('zfsmgmt:restic_repository') com.push( '--repo', props['zfsmgmt:restic_repository'] ) end if options[:verbose] com.push('--verbose',options[:verbose]) elsif $stdout.isatty com.push('-v') end unless ZfsMgmt.zfs_holds(last_zfs_snapshot).include?('zfsmgmt_restic') ZfsMgmt.zfs_hold('zfsmgmt_restic',last_zfs_snapshot) end ZfsMgmt.system_com(com) chain_snaps = chain.map do |rsnap| rsnap['zfsmgmt:snapshot'] end zfs_snapshots.each do |s,d| d['userrefs'] == 0 and next chain_snaps.include?(s) and next s == last_zfs_snapshot and next if ZfsMgmt.zfs_holds(s).include?('zfsmgmt_restic') ZfsMgmt.zfs_release('zfsmgmt_restic',s) end end end end
restic_snapshots(zfs,options,props)
click to toggle source
# File lib/zfs_mgmt/restic.rb, line 4 def self.restic_snapshots(zfs,options,props) # query the restic database com = [ options[:restic_binary], 'snapshots', '--json', '--tag', 'zfsmgmt', '--path', "/#{zfs}", ] if options.has_key?('password_file') com.push('-p',options['password_file']) end if options.has_key?('repo') com.push('--repo', options['repo']) elsif props.has_key?('zfsmgmt:restic_repository') com.push( '--repo', props['zfsmgmt:restic_repository'] ) end $logger.info("#{com.join(' ')}") restic_output = %x(#{com.join(' ')}) unless $?.success? $logger.error("unable to query the restic database") raise "unable to query the restic database" end restic_snapshots = JSON.parse(restic_output) restic_snapshot_zfs_snapshot_index = {} restic_snapshots.each do |snappy| snappy['date_time'] = DateTime.parse(snappy['time']) if snappy.has_key?('tags') snappy['tags'].each do |t| if m = /^(zfsmgmt:.+?)=(.+)/.match(t) if ['zfsmgmt:level'].include?(m[1]) snappy[m[1]] = m[2].to_i else snappy[m[1]] = m[2] end if m[1] == 'zfsmgmt:snapshot' restic_snapshot_zfs_snapshot_index[m[2]] = snappy end end end end end return([restic_snapshots,restic_snapshot_zfs_snapshot_index]) end
valid_chain(snap,restic_snapshots,restic_snapshot_zfs_snapshot_index,a)
click to toggle source
# File lib/zfs_mgmt/restic.rb, line 49 def self.valid_chain(snap,restic_snapshots,restic_snapshot_zfs_snapshot_index,a) if snap['zfsmgmt:level'] == 0 a.push(snap) $logger.debug("found complete chain culminating in full backup of: #{snap['zfsmgmt:snapshot']}") return a elsif restic_snapshot_zfs_snapshot_index.has_key?(snap['zfsmgmt:parent']) a.push(snap) $logger.debug("found another link in the chain: #{snap['zfsmgmt:snapshot']} => #{snap['zfsmgmt:parent']}") return valid_chain(restic_snapshot_zfs_snapshot_index[snap['zfsmgmt:parent']],restic_snapshots,restic_snapshot_zfs_snapshot_index,a) else $logger.error("broken chain: looking for the parent of #{snap['zfsmgmt:snapshot']} (#{snap['zfsmgmt:parent']}) and failed to find") return [] end end