backup revision 4ec9d8b62c3c1a001548eb0883b6f81e00c391a0
842ae4bd224140319ae7feec1872b93dfd491143fieldingrequire 'fileutils'
842ae4bd224140319ae7feec1872b93dfd491143fieldingrequire 'pathname'
842ae4bd224140319ae7feec1872b93dfd491143fieldingrequire 'open3'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkrequire 'pry'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkrequire 'pry-byebug'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk # Amount of backups that have to be there at least
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk # Backups are valid for 7 days
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk attr_reader :db_name, :data_root, :backup_root, :backup_instance_dir
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk attr_reader :dry_run, :verbose, :sql_dump_as_postgres_user
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk verbose: false, dry_run: true, sql_dump_as_postgres_user: false)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk @sql_dump_as_postgres_user = sql_dump_as_postgres_user
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk # We needed to create the directory for the script to continue later on.
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk $stderr.puts "Nothing to prune: There is no backup directory."
8a4550aaf4cab45fa059d40a998e25df322c5641niq backup_dirs_allowed_to_delete(Dir.new(backup_root).entries).each do |dir|
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk @backup_instance_dir = backup_root.join(new_backup_name)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk puts "FileUtils.mkdir_p #{backup_instance_dir}" if verbose
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk # Create directory even in dry run to let the script continue.
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk archive_file = backup_instance_dir.join(REPOSITORY_FILE)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk exec('tar', verbose ? '-v' : '', '-czf', archive_file.to_s,
8a4550aaf4cab45fa059d40a998e25df322c5641niq $stderr.puts "Error: Backup '#{backup_name}' does not exist in #{backup_root}."
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk 'Restoring SQL dump...'
8a4550aaf4cab45fa059d40a998e25df322c5641niq exec('pg_restore', '-c', pg_user_switch, '-d', db_name, SQL_DUMP_FILE)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkAn error occured while restoring the repositories:
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk#{e.message}
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkYou can find the pre-restore repositories at #{tmpdir}
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkDo something about it.
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk def move_data_dirs_to_tmpdir(tmpdir)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk puts "FileUtils.mv(#{DATA_DIRS}, #{tmpdir})" if verbose
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk FileUtils.mv(DATA_DIRS, tmpdir) unless dry_run
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk rescue Errno::EACCES
e8f95a682820a599fe41b22977010636be5c2717jim puts <<-MSG
8a4550aaf4cab45fa059d40a998e25df322c5641niqAs the current user I have no access to move the repository data
8a4550aaf4cab45fa059d40a998e25df322c5641niqdirectories #{DATA_DIRS.join(' ')} to a temporary directory #{tmpdir}.
8a4550aaf4cab45fa059d40a998e25df322c5641niqThis is used as a backup for the case of an error while restoring.
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkTo continue, I try the command again using sudo.
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk exec('sudo', 'mv', *DATA_DIRS.map(&:to_s), tmpdir)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk def extract_archive
d045ce8e2b813c57d5b6e28b2485c02b91613759rpluem archive_file = backup_instance_dir.join(REPOSITORY_FILE)
d045ce8e2b813c57d5b6e28b2485c02b91613759rpluem puts <<-MSG
d045ce8e2b813c57d5b6e28b2485c02b91613759rpluemSuper user privileges are needed to reset the file permissions as
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkthey were before the backup. If you refuse to enter the password
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk(Crtl-C) or enter a wrong password, only the permissions will not be
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkrestored and all restored files will belong to the current user/group.
8a4550aaf4cab45fa059d40a998e25df322c5641niq try_as_sudo_with_fallback('tar', verbose ? '-v' : '', '-xzf',
f02523893633e6b12ce7fa344d9c3fef3f2e488cniq archive_file.to_s, *DATA_DIRS.map(&:to_s))
8a4550aaf4cab45fa059d40a998e25df322c5641niq def remove_tmpdir(tmpdir)
8a4550aaf4cab45fa059d40a998e25df322c5641niq puts "FileUtils.remove_entry(#{tmpdir})" if verbose
8a4550aaf4cab45fa059d40a998e25df322c5641niq FileUtils.remove_entry(tmpdir) # even do this in dry run
8a4550aaf4cab45fa059d40a998e25df322c5641niq rescue Errno::EACCES
8a4550aaf4cab45fa059d40a998e25df322c5641niq puts <<-MSG
8a4550aaf4cab45fa059d40a998e25df322c5641niqAs the current user I have no access to remove the temporary
8a4550aaf4cab45fa059d40a998e25df322c5641niqdirectory #{tmpdir}.
8a4550aaf4cab45fa059d40a998e25df322c5641niqTo continue, I try the command again using sudo.
8a4550aaf4cab45fa059d40a998e25df322c5641niq exec('sudo', 'rm', '-r', tmpdir)
8a4550aaf4cab45fa059d40a998e25df322c5641niq def enable_maintenance_mode
8a4550aaf4cab45fa059d40a998e25df322c5641niq puts 'Enabling maintenance mode...'
8a4550aaf4cab45fa059d40a998e25df322c5641niq puts "FileUtils.touch #{maintenance_file}" if verbose
8a4550aaf4cab45fa059d40a998e25df322c5641niq FileUtils.touch maintenance_file unless dry_run
8a4550aaf4cab45fa059d40a998e25df322c5641niq def disable_maintenance_mode
8a4550aaf4cab45fa059d40a998e25df322c5641niq puts 'Disabling maintenance mode...'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk puts "FileUtils.rm #{maintenance_file}" if verbose
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk FileUtils.rm maintenance_file unless dry_run
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk def exec(*args, file_dest: nil)
8a4550aaf4cab45fa059d40a998e25df322c5641niq puts "[executing next command in #{Dir.getwd}]" if verbose
8a4550aaf4cab45fa059d40a998e25df322c5641niq out = args.join(' ')
8a4550aaf4cab45fa059d40a998e25df322c5641niq out << " > #{file_dest}" if file_dest
8a4550aaf4cab45fa059d40a998e25df322c5641niq puts out if verbose
8a4550aaf4cab45fa059d40a998e25df322c5641niq Subprocess.run(*args, file_dest: file_dest) unless dry_run
418ee053321d0ee451bb482a9becdfcd3344201fjim def try_as_sudo_with_fallback(*args)
418ee053321d0ee451bb482a9becdfcd3344201fjim unless exec('sudo', *args)
8a4550aaf4cab45fa059d40a998e25df322c5641niq sudo_not_given_fallback(*args) # Wrong sudo password
8a4550aaf4cab45fa059d40a998e25df322c5641niq rescue Exception => e
8a4550aaf4cab45fa059d40a998e25df322c5641niq raise e unless e.is_a?(Interrupt) # Ctrl-C when asked for password
8a4550aaf4cab45fa059d40a998e25df322c5641niq sudo_not_given_fallback(*args)
8a4550aaf4cab45fa059d40a998e25df322c5641niq def sudo_not_given_fallback(*args)
8a4550aaf4cab45fa059d40a998e25df322c5641niq puts 'Super user privileges not granted. Trying as normal user.'
8a4550aaf4cab45fa059d40a998e25df322c5641niq exec(*args)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk def maintenance_file
8a4550aaf4cab45fa059d40a998e25df322c5641niq data_root.join(MAINTENANCE_FILE)
8a4550aaf4cab45fa059d40a998e25df322c5641niq def pg_user_switch
8a4550aaf4cab45fa059d40a998e25df322c5641niq sql_dump_as_postgres_user ? ' -U postgres' : ''
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk def self.backup_dirs_allowed_to_delete(entries)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk entries.reject{ |entry| %w(. ..).include?(entry) }[0..-(BACKUPS_COUNT+1)]
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk class Subprocess
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk def self.run(*args, file_dest: nil)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk stdin, stdout, stderr, wait_thr, io_dest = run_streaming(*args,
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk file_dest: file_dest)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk exit_code = wait_thr.value # wait for the process to finish
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk io_dest.close if io_dest
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk [stdout.read, stderr.read, exit_code]
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk def self.run_streaming(*args, file_dest: nil)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk stdin, stdout, stderr, wait_thr = Open3.popen3(*args)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk io_dest = nil
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk if file_dest
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk io_dest = File.open(file_dest, 'w')
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk IO.copy_stream(stdout, io_dest)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk [stdin, stdout, stderr, wait_thr, io_dest]
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkdef data_root(rails_root)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk File.realpath(rails_root.join('data'))
8a4550aaf4cab45fa059d40a998e25df322c5641niqdef on_development_system?(rails_root)
8a4550aaf4cab45fa059d40a998e25df322c5641niq !File.symlink?(rails_root.join('data'))
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk# We assume, this script runs in "RAILS_ROOT/script/".
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkRAILS_ROOT = Pathname.new(__FILE__).dirname.join('..')
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkDATABASE = if on_development_system?(RAILS_ROOT)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk 'ontohub_development'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkBACKUP_ROOT = if on_development_system?(RAILS_ROOT)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk RAILS_ROOT.join('tmp', 'backup')
8a4550aaf4cab45fa059d40a998e25df322c5641niq File.realpath('/home/ontohub/ontohub_data_backup')
8a4550aaf4cab45fa059d40a998e25df322c5641niqbackup = Backup::Backup.new(DATABASE, data_root(RAILS_ROOT), BACKUP_ROOT,
8a4550aaf4cab45fa059d40a998e25df322c5641niq sql_dump_as_postgres_user: on_development_system?(RAILS_ROOT),
8a4550aaf4cab45fa059d40a998e25df322c5641niq dry_run: false, verbose: true)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkcase ARGV.first
8a4550aaf4cab45fa059d40a998e25df322c5641niqwhen 'create'
8a4550aaf4cab45fa059d40a998e25df322c5641niq backup.create
8a4550aaf4cab45fa059d40a998e25df322c5641niqwhen 'restore'
8a4550aaf4cab45fa059d40a998e25df322c5641niq if ARGV.length == 1
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk $stderr.puts 'To restore a backup, you need to specify one with the arguments'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk $stderr.puts '"restore backup_name"'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk backup_name = ARGV[1]
8a4550aaf4cab45fa059d40a998e25df322c5641niq backup.restore(backup_name)
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturkwhen 'prune'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk Backup::Backup.prune(BACKUP_ROOT)
d045ce8e2b813c57d5b6e28b2485c02b91613759rpluem $stderr.puts 'unknown or missing parameter'
13852d30fd6e3ffee07702f9222a0dd5aeec75ebmturk $stderr.puts 'use parameter "create" or "restore <backup_name>" or "prune"'