#!/usr/bin/env ruby require 'ftools' require 'lsync' require 'lsync/version' require 'optparse' OPTIONS = { :ConfigFiles => [], :Plan => nil, :LogPath => "/var/log/lsync/backup.log" } ARGV.options do |o| script_name = File.basename($0) o.set_summary_indent(' ') o.banner = "Usage: #{script_name} [options] [directory | config]" o.define_head "This script is used to run backups. If you specify a directory, files ending in .conf will all be processed." o.separator "" o.separator "Help and Copyright information" o.on("-p plan", String, "Run the specified backup plan") { |plan| OPTIONS[:Plan] = plan } o.on("-l log_path", String, "Set the directory for backup logs") { |log_path| OPTIONS[:LogPath] = log_path } o.on_tail("--copy", "Display copyright information") do puts "#{script_name} v#{LSync::VERSION::STRING}. Copyright (c) 2008-2009 Samuel Williams. Released under the GPLv3." puts "See http://www.oriontransfer.co.nz/ for more information." exit end o.on_tail("-h", "--help", "Show this help message.") { puts o; exit } end.parse! # ============================== Main Scripts / Plans ============================== class LogPathChecker def initialize(directory, filename) full_path = File.join(directory, filename) @directory_writable = File.directory?(directory) && File.writable?(directory) @file_not_writable = File.exist?(full_path) && !File.writable?(full_path) @accessable = @directory_writable && !@file_not_writable @full_path = full_path end attr :accessable attr :full_path end def setup_logging # Console log output $console_log = Logger.new($stdout) $console_log.formatter = MinimalLogFormat.new # File log output filename = File.basename(OPTIONS[:LogPath]) directory = File.dirname(OPTIONS[:LogPath]) if filename == nil || filename == "" filename = "backup.log" end check = LogPathChecker.new(directory, filename) unless check.accessable $console_log.warn "Could not write to #{check.full_path.dump} - changing log path to #{Dir.pwd.dump}" directory = Dir.pwd check = LogPathChecker.new(directory, filename) end if check.accessable $backup_log = Logger.new(check.full_path, 'weekly') else $console_log.warn "Could not write to #{check.full_path.dump} - not logging to disk" $backup_log = nil end $logger = TeeLogger.new($console_log, $backup_log) end def process_config_files ARGV.each do |p| unless File.exists? p $logger.error "Could not process #{p}" exit(20) end if File.directory? p OPTIONS[:ConfigFiles] += Dir[File.join(p, "*.conf")] else OPTIONS[:ConfigFiles] << p end end end def validate_options if OPTIONS[:Plan] && OPTIONS[:ConfigFiles].size > 0 $logger.error "Please specify either a backup plan or a set of backup scripts, but not both in one run." exit(27) end end def run_backups OPTIONS[:ConfigFiles].each do |c| config = LSync::BackupScript.load_from_file(c) config.logger = $logger config.run_backup end if OPTIONS[:Plan] config = LSync::BackupPlan.load_from_file(OPTIONS[:Plan]) config.logger = $logger config.run_backup end end def dump_exception_backtrace ex ex.backtrace.each do |bt| $logger.error bt end end setup_logging process_config_files validate_options $logger.info " Backup beginning at #{Time.now.to_s} ".center(96, "=") begin run_backups rescue LSync::BackupError $logger.error "#{$!.class.name}: #{$!.to_s}. Dumping backtrace:" dump_exception_backtrace($!) exit(230) rescue Interrupt $logger.error "Backup process recevied interrupt (Ctrl-C). Dumping backtrace:" dump_exception_backtrace($!) exit(240) rescue $logger.error "Unknown exception #{$!.class.name} thrown: #{$!.to_s}. Dumping backtrace:" dump_exception_backtrace($!) exit(250) ensure $logger.info " Backup finishing at #{Time.now.to_s} ".center(96, "=") end