backup revision d89f470f7da0b9f8295d0ac0defff09884894b8b
require 'tmpdir.rb'
require 'fileutils'
require 'pathname'
require 'open3'
require 'pry'
require 'pry-byebug'
# Amount of backups that have to be there at least
BACKUPS_COUNT = 30
# Backups are kept for at least 365 days
DATA_DIRS = %w(data/repositories data/git_daemon)
verbose: false, dry_run: true, sql_dump_as_postgres_user: false)
end
puts 'Creating backup...'
# We needed to create the directory for the script to continue later on.
puts "Created backup in #{backup_instance_dir}"
end
puts "Restored backup from #{backup_instance_dir}"
end
if !Dir.exists?(backup_root)
return
end
puts "removing old backup: #{dir}"
end
end
end
end
# Create directory even in dry run to let the script continue.
end
puts 'Creating SQL dump...'
end
end
puts 'Creating repository archive...'
end
end
exit
end
end
'Restoring SQL dump...'
end
end
puts 'Restoring repository archive...'
rescue => e
puts <<-MSG
An error occured while restoring the repositories:
#{e.message}
You can find the pre-restore repositories at #{tmpdir}
Do something about it.
MSG
raise e
end
end
end
def move_data_dirs_to_tmpdir(tmpdir)
puts "FileUtils.mv(#{DATA_DIRS}, #{tmpdir})" if verbose
FileUtils.mv(DATA_DIRS, tmpdir) unless dry_run
rescue Errno::EACCES
puts <<-MSG
As the current user I have no access to move the repository data
directories #{DATA_DIRS.join(' ')} to a temporary directory #{tmpdir}.
This is used as a backup for the case of an error while restoring.
To continue, I try the command again using sudo.
MSG
exec('sudo', 'mv', *DATA_DIRS.map(&:to_s), tmpdir)
end
def extract_archive
archive_file = backup_instance_dir.join(REPOSITORY_FILE)
puts <<-MSG
Super user privileges are needed to reset the file permissions as
they were before the backup. If you refuse to enter the password
(Crtl-C) or enter a wrong password, only the permissions will not be
restored and all restored files will belong to the current user/group.
MSG
try_as_sudo_with_fallback('tar', verbose ? '-v' : '', '-xzf',
archive_file.to_s, *DATA_DIRS.map(&:to_s))
end
def remove_tmpdir(tmpdir)
puts "FileUtils.remove_entry(#{tmpdir})" if verbose
FileUtils.remove_entry(tmpdir) # even do this in dry run
rescue Errno::EACCES
puts <<-MSG
As the current user I have no access to remove the temporary
directory #{tmpdir}.
To continue, I try the command again using sudo.
MSG
exec('sudo', 'rm', '-r', tmpdir)
end
def enable_maintenance_mode
puts 'Enabling maintenance mode...'
puts "FileUtils.touch #{maintenance_file}" if verbose
FileUtils.touch maintenance_file unless dry_run
end
def disable_maintenance_mode
puts 'Disabling maintenance mode...'
puts "FileUtils.rm #{maintenance_file}" if verbose
FileUtils.rm maintenance_file unless dry_run
end
def exec(*args, file_dest: nil)
puts "[executing next command in #{Dir.getwd}]" if verbose
out = args.join(' ')
out << " > #{file_dest}" if file_dest
puts out if verbose
Subprocess.run(*args, file_dest: file_dest) unless dry_run
end
def try_as_sudo_with_fallback(*args)
_out, _err, exit_code = exec('sudo', *args)
unless exit_code.success?
sudo_not_given_fallback(*args) # Wrong sudo password
end
rescue Exception => e
raise e unless e.is_a?(Interrupt) # Ctrl-C when asked for password
sudo_not_given_fallback(*args)
end
def sudo_not_given_fallback(*args)
puts 'Super user privileges not granted. Trying as normal user.'
exec(*args)
end
def maintenance_file
data_root.join(MAINTENANCE_FILE)
end
def pg_user_switch
sql_dump_as_postgres_user ? ' -U postgres' : ''
end
def self.backup_dirs_allowed_to_delete(entries)
entries.reject{ |entry| %w(. ..).include?(entry) }[0..-(BACKUPS_COUNT+1)]
end
end
class Subprocess
def self.run(*args, file_dest: nil)
stdin, stdout, stderr, wait_thr, io_dest = run_streaming(*args,
file_dest: file_dest)
exit_code = wait_thr.value # wait for the process to finish
io_dest.close if io_dest
[stdout.read, stderr.read, exit_code]
end
def self.run_streaming(*args, file_dest: nil)
stdin, stdout, stderr, wait_thr = Open3.popen3(*args)
io_dest = nil
if file_dest
io_dest = File.open(file_dest, 'w')
IO.copy_stream(stdout, io_dest)
end
[stdin, stdout, stderr, wait_thr, io_dest]
end
end
end
def data_root(rails_root)
File.realpath(rails_root.join('data'))
end
def on_development_system?(rails_root)
!File.symlink?(rails_root.join('data'))
end
# We assume, this script runs in "RAILS_ROOT/script/".
RAILS_ROOT = Pathname.new(__FILE__).dirname.join('..')
DATABASE = if on_development_system?(RAILS_ROOT)
'ontohub_development'
else
'ontohub'
end
BACKUP_ROOT = if on_development_system?(RAILS_ROOT)
RAILS_ROOT.join('tmp', 'backup')
else
File.realpath('/home/ontohub/ontohub_data_backup')
end
backup = Backup::Backup.new(DATABASE, data_root(RAILS_ROOT), BACKUP_ROOT,
sql_dump_as_postgres_user: on_development_system?(RAILS_ROOT),
dry_run: false, verbose: true)
case ARGV.first
when 'create'
backup.create
when 'restore'
if ARGV.length == 1
$stderr.puts 'To restore a backup, you need to specify one with the arguments'
$stderr.puts '"restore backup_name"'
exit
end
backup_name = ARGV[1]
backup.restore(backup_name)
when 'prune'
Backup::Backup.prune(BACKUP_ROOT)
else
$stderr.puts 'unknown or missing parameter'
$stderr.puts 'use parameter "create" or "restore <backup_name>" or "prune"'
exit
end