backup revision 366ce8d807067a97613cb23d49105d8a093c5015
#!/usr/bin/env ruby
require 'fileutils'
require 'pathname'
module Backup
class Backup
# Amount of backups that have to be there at least
BACKUPS_COUNT = 3
# Backups are valid for 7 days
BACKUPS_VALIDITY_TIME = 7 * 60 * 60 * 24
MAINTENANCE_FILE = 'maintenance.txt'
SQL_DUMP_FILE = 'ontohub_sql_dump.postgresql'
REPOSITORY_FILE = 'ontohub_repositories.tar.gz'
DATA_DIRS = %w(data/repositories data/git_daemon)
attr_reader :db_name, :data_root, :backup_root, :backup_instance_dir
attr_reader :dry_run, :verbose, :sql_dump_as_postgres_user
def initialize(db_name, data_root, backup_root,
verbose: false, dry_run: true, sql_dump_as_postgres_user: false)
@db_name = db_name
@data_root = Pathname.new(data_root)
@backup_root = Pathname.new(backup_root)
@dry_run = dry_run
@verbose = verbose
@sql_dump_as_postgres_user = sql_dump_as_postgres_user
end
def create
enable_maintenance_mode
initialize_backup
create_sql_dump
create_repository_archive
# We needed to create the directory for the script to continue later on.
Dir.rmdir(backup_instance_dir) if dry_run
disable_maintenance_mode
puts "created backup in #{backup_instance_dir}"
self.class.prune(backup_root)
end
def restore(backup_name)
enable_maintenance_mode
initialize_restore(backup_name)
restore_sql_dump
restore_repository_archive
disable_maintenance_mode
puts "restored backup from #{backup_instance_dir}"
end
def self.prune(backup_root)
if !Dir.exists?(backup_root)
puts "Nothing to prune: There is no backup directory."
return
end
now = Time.now
backup_dirs_allowed_to_delete(Dir.new(backup_root).entries).each do |dir|
backup = backup_root.join(dir)
if now - File.new(backup).ctime > BACKUPS_VALIDITY_TIME
puts "removing old backup: #{dir}"
FileUtils.rm_r(backup)
end
end
end
protected
def new_backup_name
Time.now.strftime("%Y-%m-%d_%H-%M-%S")
end
def initialize_backup
@backup_instance_dir = backup_root.join(new_backup_name)
puts "FileUtils.mkdir_p #{backup_instance_dir}" if verbose
# Create directory even in dry run to let the script continue.
FileUtils.mkdir_p(backup_instance_dir)
end
def create_sql_dump
Dir.chdir(backup_instance_dir) do
if sql_dump_as_postgres_user
exec "pg_dump -U postgres -Fc #{db_name} > #{SQL_DUMP_FILE}"
else
exec "pg_dump -Fc #{db_name} > #{SQL_DUMP_FILE}"
end
end
end
def create_repository_archive
Dir.chdir(data_root.join('..')) do
archive_file = backup_instance_dir.join(REPOSITORY_FILE)
exec(["tar -czvf #{archive_file}", *DATA_DIRS].join(' '))
end
end
def initialize_restore(backup_name)
@backup_instance_dir = backup_root.join(backup_name)
unless Dir.exists?(backup_instance_dir)
puts "Error: Backup '#{backup_name}' does not exist in #{backup_root}."
exit
end
end
def restore_sql_dump
Dir.chdir(backup_instance_dir) do
if sql_dump_as_postgres_user
exec "pg_restore -c -U postgres -d #{db_name} #{SQL_DUMP_FILE}"
else
exec "pg_restore -c -d #{db_name} #{SQL_DUMP_FILE}"
end
end
end
def restore_repository_archive
Dir.chdir(data_root.join('..')) do
puts "FileUtils.rm_r #{DATA_DIRS}" if verbose
FileUtils.rm_r DATA_DIRS unless dry_run
archive_file = backup_instance_dir.join(REPOSITORY_FILE)
exec(["tar -xzvf #{archive_file}", *DATA_DIRS].join(' '))
end
end
def enable_maintenance_mode
puts "FileUtils.touch #{maintenance_file}" if verbose
FileUtils.touch maintenance_file unless dry_run
end
def disable_maintenance_mode
puts "FileUtils.rm #{maintenance_file}" if verbose
FileUtils.rm maintenance_file unless dry_run
end
def exec(command)
puts "[executing next command in #{Dir.getwd}]" if verbose
puts command if verbose
system(command) unless dry_run
end
def maintenance_file
data_root.join(MAINTENANCE_FILE)
end
def self.backup_dirs_allowed_to_delete(entries)
entries.reject{ |entry| %w(. ..).include?(entry) }[0..-(BACKUPS_COUNT+1)]
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
puts 'To restore a backup, you need to specify one with the arguments'
puts '"restore backup_name"'
exit
end
backup_name = ARGV[1]
backup.restore(backup_name)
when 'prune'
Backup::Backup.prune(BACKUP_ROOT)
else
puts 'unknown or missing parameter'
puts 'use parameter "create" or "restore <backup_name>" or "prune"'
exit
end