backup revision 6cdc461aada609d57d50ff675d29b15378717ff2
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper#!/usr/bin/env ruby
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# Description
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# This backup script creates and restores backups of ontohub data. It includes:
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# * bare git repositories (data/repositories)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# * named symlinks to git repositories (data/git_daemon)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# * the postgres database
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper#
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# Usage
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# To create a backup, run this script with the argument `create`:
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# $ script/backup create
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# Then a backup named with the current date and time is created in the
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# backup directory (see below).
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper#
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# To restore a backup, run this script with the argument `restore <backup name>`
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# $ script/backup restore 2015-01-01_00-00
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# Then the selected backup is fully restored
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper#
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# Backup directory
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# For development machines, the backup directory is:
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# <rails root>/tmp/backup/
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# And for production machines, the backup directory is:
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# /home/ontohub/ontohub_data_backup
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper#
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# Super user privileges
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# To create and restore, we need root privileges. Otherwise file modes are not
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# preserved. This script will call `sudo` when needed and inform you about the
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# reason for calling `sudo`. If you don't allow sudo, a backup will be created
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# or restored anyway, but the file modes and ownership are not preserved.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# Then, you need to adjust them manually.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper#
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# Maintenance mode
5c39d8f041417518a02ce2c941d96c2d33b2a364Mark de Reeper# While backing up and restoring the data, the maintenance mode is activated.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper# This way we guarantee data consistency of the backup.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeperrequire 'tmpdir.rb'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeperrequire 'fileutils'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeperrequire 'pathname'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeperrequire 'open3'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reepermodule Backup
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper class Backup
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper # Amount of backups that have to be there at least
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper BACKUPS_COUNT = 30
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper # Backups are kept for at least 365 days
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper BACKUPS_VALIDITY_TIME = 365 * 60 * 60 * 24
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper MAINTENANCE_FILE = 'maintenance.txt'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper SQL_DUMP_FILE = 'ontohub_sql_dump.postgresql'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper REPOSITORY_FILE = 'ontohub_repositories.tar.gz'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper DATA_DIRS = %w(data/repositories data/git_daemon)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper attr_reader :db_name, :data_root, :backup_root, :backup_instance_dir
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper attr_reader :dry_run, :verbose, :sql_dump_as_postgres_user
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def initialize(db_name, data_root, backup_root,
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper verbose: false, dry_run: true, sql_dump_as_postgres_user: false)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper @db_name = db_name
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper @data_root = Pathname.new(data_root)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper @backup_root = Pathname.new(backup_root)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper @dry_run = dry_run
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper @verbose = verbose
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper @sql_dump_as_postgres_user = sql_dump_as_postgres_user
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def create
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts 'Creating backup...'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper enable_maintenance_mode
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper initialize_backup
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper create_sql_dump
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper create_repository_archive
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper # We needed to create the directory for the script to continue later on.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper Dir.rmdir(backup_instance_dir) if dry_run
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper disable_maintenance_mode
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts "Created backup in #{backup_instance_dir}"
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper self.class.prune(backup_root)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def restore(backup_name)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper enable_maintenance_mode
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper initialize_restore(backup_name)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper restore_sql_dump
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper restore_repository_archive
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper disable_maintenance_mode
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts "Restored backup from #{backup_instance_dir}"
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def self.prune(backup_root)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper if !Dir.exists?(backup_root)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper $stderr.puts "Nothing to prune: There is no backup directory."
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper return
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper now = Time.now
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper backup_dirs_allowed_to_delete(Dir.new(backup_root).entries).each do |dir|
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper backup = backup_root.join(dir)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper if now - File.new(backup).ctime > BACKUPS_VALIDITY_TIME
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts "removing old backup: #{dir}"
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper FileUtils.rm_r(backup)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper protected
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def new_backup_name
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper Time.now.strftime("%Y-%m-%d_%H-%M-%S")
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def initialize_backup
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper @backup_instance_dir = backup_root.join(new_backup_name)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts "FileUtils.mkdir_p #{backup_instance_dir}" if verbose
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper # Create directory even in dry run to let the script continue.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper FileUtils.mkdir_p(backup_instance_dir)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def create_sql_dump
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts 'Creating SQL dump...'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper Dir.chdir(backup_instance_dir) do
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper exec('pg_dump', *pg_user_switch, '-Fc', db_name,
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper file_dest: SQL_DUMP_FILE)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def create_repository_archive
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts 'Creating repository archive...'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper Dir.chdir(data_root.join('..')) do
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper archive_file = backup_instance_dir.join(REPOSITORY_FILE)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper exec('tar', verbose ? '-v' : '', '-czf', archive_file.to_s,
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper *DATA_DIRS.map(&:to_s))
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def initialize_restore(backup_name)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper @backup_instance_dir = backup_root.join(backup_name)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper unless Dir.exists?(backup_instance_dir)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper $stderr.puts (
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper "Error: Backup '#{backup_name}' does not exist in #{backup_root}.")
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper exit
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def restore_sql_dump
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper 'Restoring SQL dump...'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper Dir.chdir(backup_instance_dir) do
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper exec('pg_restore', '-c', *pg_user_switch, '-d', db_name, SQL_DUMP_FILE)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def restore_repository_archive
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts 'Restoring repository archive...'
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper Dir.chdir(data_root.join('..')) do
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper tmpdir = Dir.mktmpdir
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper move_data_dirs_to_tmpdir(tmpdir)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper begin
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper extract_archive
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper remove_tmpdir(tmpdir)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper rescue => e
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts <<-MSG
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperAn error occured while restoring the repositories:
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper#{e.message}
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperYou can find the pre-restore repositories at #{tmpdir}
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperDo something about it.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper MSG
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper raise e
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def move_data_dirs_to_tmpdir(tmpdir)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts "FileUtils.mv(#{DATA_DIRS}, #{tmpdir})" if verbose
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper FileUtils.mv(DATA_DIRS, tmpdir) unless dry_run
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper rescue Errno::EACCES
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts <<-MSG
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperAs the current user I have no access to move the repository data
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeperdirectories #{DATA_DIRS.join(' ')} to a temporary directory #{tmpdir}.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperThis is used as a backup for the case of an error while restoring.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperTo continue, I try the command again using sudo.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper MSG
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper exec('sudo', 'mv', *DATA_DIRS.map(&:to_s), tmpdir)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def extract_archive
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper archive_file = backup_instance_dir.join(REPOSITORY_FILE)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts <<-MSG
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperSuper user privileges are needed to reset the file permissions as
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeperthey were before the backup. If you refuse to enter the password
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper(Ctl-C) or enter a wrong password, only the permissions will not be
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeperrestored and all restored files will belong to the current user/group.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper MSG
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper try_as_sudo_with_fallback('tar', verbose ? '-v' : '', '-xzf',
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper archive_file.to_s, *DATA_DIRS.map(&:to_s))
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper end
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper def remove_tmpdir(tmpdir)
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts "FileUtils.remove_entry(#{tmpdir})" if verbose
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper FileUtils.remove_entry(tmpdir) # even do this in dry run
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper rescue Errno::EACCES
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper puts <<-MSG
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperAs the current user I have no access to remove the temporary
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeperdirectory #{tmpdir}.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de ReeperTo continue, I try the command again using sudo.
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper MSG
6406210b71fd4a97800f32f3613eea9b6a6a12ceMark de Reeper 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 ? %w(-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('..')
BACKUP_ROOT_PRODUCTION = '/home/ontohub/ontohub_data_backup'
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(BACKUP_ROOT_PRODUCTION)
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