7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa#!/local/usr/ruby/shims/ruby
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa# The first line is for deployment machines only. For local machines, use:
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa#!/usr/bin/env ruby
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
abc834c020080fe44a1ea4e34278327e99e3e12eEugen Kuksa# You can find more extensive documentation of this script at
abc834c020080fe44a1ea4e34278327e99e3e12eEugen Kuksa# https://github.com/ontohub/ontohub/blob/staging/doc/backup_and_restore_of_ontohub_data.md
abc834c020080fe44a1ea4e34278327e99e3e12eEugen Kuksa
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# Description
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# This backup script creates and restores backups of ontohub data. It includes:
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# * bare git repositories (data/repositories)
6bbb71b52f053c148669e5b128e236781150da1fEugen Kuksa# * named symlinks to git repositories (data/git_daemon and data/git_ssh)
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# * the postgres database
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa#
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# Usage
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa# First note: Run this as the root user, e.g. with sudo.
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# To create a backup, run this script with the argument `create`:
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa# # script/backup create
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# Then a backup named with the current date and time is created in the
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# backup directory (see below).
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa#
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# To restore a backup, run this script with the argument `restore <backup name>`
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa# # script/backup restore 2015-01-01_00-00
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# Then the selected backup is fully restored
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa#
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# Backup directory
8230471f68bf2a663d94872531d0bcf16d426bbeEugen Kuksa# For production machines, the backup directory is:
8230471f68bf2a663d94872531d0bcf16d426bbeEugen Kuksa# /data/ontohub_data_backup
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa#
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# Super user privileges
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# To create and restore, we need root privileges. Otherwise file modes are not
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# preserved. This script will call `sudo` when needed and inform you about the
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# reason for calling `sudo`. If you don't allow sudo, a backup will be created
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# or restored anyway, but the file modes and ownership are not preserved.
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# Then, you need to adjust them manually.
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa#
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# Maintenance mode
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# While backing up and restoring the data, the maintenance mode is activated.
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa# This way we guarantee data consistency of the backup.
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa
f0264afd33a980b6584747fc8159ee950805d9e3Eugen Kuksa
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksarequire 'tmpdir.rb'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksarequire 'fileutils'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksarequire 'pathname'
28001d576e67ba46ed481c5695f1e0827ff26007Eugen Kuksarequire 'open3'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen KuksaEXIT_CODES = {
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa backup_missing: 1,
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa maintenance_already_enabled: 2,
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa wrong_user: 3,
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa target_not_a_directory: 4,
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa bad_arguments: 5,
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa}
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa
b1fe9054ad7c7192fe4c474363247dad15963e99Eugen Kuksamodule Backup
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa class Backup
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa # Amount of backups that have to be there at least
d89f470f7da0b9f8295d0ac0defff09884894b8bEugen Kuksa BACKUPS_COUNT = 30
d89f470f7da0b9f8295d0ac0defff09884894b8bEugen Kuksa # Backups are kept for at least 365 days
d89f470f7da0b9f8295d0ac0defff09884894b8bEugen Kuksa BACKUPS_VALIDITY_TIME = 365 * 60 * 60 * 24
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa MAINTENANCE_FILE = 'maintenance.txt'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa SQL_DUMP_FILE = 'ontohub_sql_dump.postgresql'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa REPOSITORY_FILE = 'ontohub_repositories.tar.gz'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
6bbb71b52f053c148669e5b128e236781150da1fEugen Kuksa DATA_DIRS = %w(repositories git_daemon git_ssh)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa # Use 'sudo' on most systems
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa SUDO_BINARY = '+'
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa attr_reader :db_name, :data_root, :backup_root, :backup_instance_dir
67daeb8066e4460cb820db60c45138dd48309bb9Eugen Kuksa attr_reader :dry_run, :verbose, :sql_dump_as_db_user
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def initialize(db_name, data_root, backup_root,
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa verbose: false, dry_run: true, sql_dump_as_db_user: nil,
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa user: nil, group: nil)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa @db_name = db_name
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa @backup_root = Pathname.new(backup_root)
a8028fd2789e323040de08827a0fe1f7d36fde2bEugen Kuksa @data_root = Pathname.new(data_root)
a8028fd2789e323040de08827a0fe1f7d36fde2bEugen Kuksa @data_root_basename = @data_root.basename.to_s
a8028fd2789e323040de08827a0fe1f7d36fde2bEugen Kuksa @data_dirs = DATA_DIRS.map { |dir| File.join(@data_root_basename, dir) }
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa @user = user
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa @group = group
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa @dry_run = dry_run
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa @verbose = verbose
67daeb8066e4460cb820db60c45138dd48309bb9Eugen Kuksa @sql_dump_as_db_user = sql_dump_as_db_user
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def create
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa puts 'Creating backup...'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa enable_maintenance_mode
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa initialize_backup
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa create_sql_dump
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa create_repository_archive
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa # We needed to create the directory for the script to continue later on.
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa Dir.rmdir(backup_instance_dir) if dry_run
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa disable_maintenance_mode
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa puts "Created backup in #{backup_instance_dir}"
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa self.class.prune(backup_root)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def restore(backup_name)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa enable_maintenance_mode
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa initialize_restore(backup_name)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa restore_sql_dump
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa restore_repository_archive
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa disable_maintenance_mode
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa puts "Restored backup from #{backup_instance_dir}"
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa def self.prune(backup_root)
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa if !Dir.exists?(backup_root)
923d69139038e74c0936e826bbfdc8717fbbc7b3Eugen Kuksa $stderr.puts "Nothing to prune: There is no backup directory."
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa return
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa end
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa now = Time.now
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa backup_dirs_allowed_to_delete(Dir.new(backup_root).entries).each do |dir|
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa backup = backup_root.join(dir)
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa if now - File.new(backup).ctime > BACKUPS_VALIDITY_TIME
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa puts "removing old backup: #{dir}"
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa FileUtils.rm_r(backup)
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa end
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa end
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa end
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa protected
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def new_backup_name
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa Time.now.strftime("%Y-%m-%d_%H-%M-%S")
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def initialize_backup
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa @backup_instance_dir = backup_root.join(new_backup_name)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa puts "FileUtils.mkdir_p #{backup_instance_dir}" if verbose
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa # Create directory even in dry run to let the script continue.
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa FileUtils.mkdir_p(backup_instance_dir)
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa puts "FileUtils.chown #{@user} #{@group} #{backup_instance_dir}" if verbose
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa FileUtils.chown(@user, @group, backup_instance_dir)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def create_sql_dump
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa puts 'Creating SQL dump...'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa Dir.chdir(backup_instance_dir) do
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa exec('pg_dump', *pg_user_switch, '-Fc', db_name,
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa '-f', backup_instance_dir.join(SQL_DUMP_FILE), user: @user)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def create_repository_archive
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa puts 'Creating repository archive...'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa Dir.chdir(data_root.join('..')) do
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa archive_file = backup_instance_dir.join(REPOSITORY_FILE)
035247355c21663281553a063a130bcc8c37b43cEugen Kuksa exec('tar', verbose ? '-v' : '', '-czf', archive_file.to_s, *@data_dirs,
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa user: @user)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def initialize_restore(backup_name)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa @backup_instance_dir = backup_root.join(backup_name)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa unless Dir.exists?(backup_instance_dir)
4634cde5d3428bd5ab34b8212ac2f4637cdfff6fEugen Kuksa $stderr.puts (
4634cde5d3428bd5ab34b8212ac2f4637cdfff6fEugen Kuksa "Error: Backup '#{backup_name}' does not exist in #{backup_root}.")
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa exit EXIT_CODES[:backup_missing]
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def restore_sql_dump
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa 'Restoring SQL dump...'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa Dir.chdir(backup_instance_dir) do
f9e467b8fe6fef705eec2989b20e92eaa9d917e1Eugen Kuksa exec('pg_restore', '-n', 'public',
f9e467b8fe6fef705eec2989b20e92eaa9d917e1Eugen Kuksa '-c', *pg_user_switch,
f9e467b8fe6fef705eec2989b20e92eaa9d917e1Eugen Kuksa '-d', db_name,
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa SQL_DUMP_FILE,
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa user: @user)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def restore_repository_archive
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa puts 'Restoring repository archive...'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa Dir.chdir(data_root.join('..')) do
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa tmpdir = Dir.mktmpdir
2dbc668d1e44c95db1857d3968bcde7517852beaEugen Kuksa move_data_dirs_to_tmpdir(tmpdir)
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa begin
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa extract_archive
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa remove_tmpdir(tmpdir)
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa rescue => e
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa puts <<-MSG
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaAn error occured while restoring the repositories:
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa#{e.message}
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaYou can find the pre-restore repositories at #{tmpdir}
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaDo something about it.
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa MSG
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa raise e
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa def move_data_dirs_to_tmpdir(tmpdir)
a8028fd2789e323040de08827a0fe1f7d36fde2bEugen Kuksa puts "FileUtils.mv(#{@data_dirs}, #{tmpdir})" if verbose
a8028fd2789e323040de08827a0fe1f7d36fde2bEugen Kuksa FileUtils.mv(@data_dirs, tmpdir) unless dry_run
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa rescue Errno::EACCES
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa puts <<-MSG
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaAs the current user I have no access to move the repository data
a8028fd2789e323040de08827a0fe1f7d36fde2bEugen Kuksadirectories #{@data_dirs.join(' ')} to a temporary directory #{tmpdir}.
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaThis is used as a backup for the case of an error while restoring.
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaTo continue, I try the command again using sudo.
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa MSG
108ccf954b02c757f7d267e5eb9260f71ff39862Eugen Kuksa exec('mv', *@data_dirs, tmpdir, user: 'root') unless dry_run
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa end
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa def extract_archive
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa archive_file = backup_instance_dir.join(REPOSITORY_FILE)
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa puts <<-MSG
284432981d641cf3d679841f75acbcf039d83062Eugen Kuksa
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaSuper user privileges are needed to reset the file permissions as
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksathey were before the backup. If you refuse to enter the password
6cdc461aada609d57d50ff675d29b15378717ff2Eugen Kuksa(Ctl-C) or enter a wrong password, only the permissions will not be
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksarestored and all restored files will belong to the current user/group.
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa MSG
a9b1a876554e3539bd8e70e6081888c8a0134f22Eugen Kuksa exec('tar', verbose ? 'vxf' : 'xf', archive_file.to_s, *@data_dirs,
a9b1a876554e3539bd8e70e6081888c8a0134f22Eugen Kuksa user: 'root')
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa end
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa def remove_tmpdir(tmpdir)
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa puts "FileUtils.remove_entry(#{tmpdir})" if verbose
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa FileUtils.remove_entry(tmpdir) # even do this in dry run
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa rescue Errno::EACCES
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa puts <<-MSG
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaAs the current user I have no access to remove the temporary
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksadirectory #{tmpdir}.
b51057b860560bf3ee454c03a121af3d5d34f482Eugen KuksaTo continue, I try the command again using sudo.
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa MSG
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa exec('rm', '-r', tmpdir, user: 'root')
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa end
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def enable_maintenance_mode
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa puts 'Enabling maintenance mode...'
bacf95fddc53ab3107f380c4c816fa8072358bd9Eugen Kuksa if File.exist?(maintenance_file)
bacf95fddc53ab3107f380c4c816fa8072358bd9Eugen Kuksa $stderr.puts 'Maintenance mode was already enabled.'
bacf95fddc53ab3107f380c4c816fa8072358bd9Eugen Kuksa $stderr.puts "Please check the file #{maintenance_file}"
bacf95fddc53ab3107f380c4c816fa8072358bd9Eugen Kuksa $stderr.puts 'Aborting.'
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa exit EXIT_CODES[:maintenance_already_enabled]
bacf95fddc53ab3107f380c4c816fa8072358bd9Eugen Kuksa end
486df98bbf3348cfb96e93c3e499d12435880bb5Eugen Kuksa puts "FileUtils.touch #{maintenance_file}" if verbose
486df98bbf3348cfb96e93c3e499d12435880bb5Eugen Kuksa FileUtils.touch maintenance_file unless dry_run
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa def disable_maintenance_mode
4ec9d8b62c3c1a001548eb0883b6f81e00c391a0Eugen Kuksa puts 'Disabling maintenance mode...'
486df98bbf3348cfb96e93c3e499d12435880bb5Eugen Kuksa puts "FileUtils.rm #{maintenance_file}" if verbose
486df98bbf3348cfb96e93c3e499d12435880bb5Eugen Kuksa FileUtils.rm maintenance_file unless dry_run
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa # Execute a command as the given user.
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa def exec(*args, user: nil)
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa print "[executing next command in #{Dir.getwd}" if verbose
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa print " as user #{user}" if verbose && user
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa puts "]" if verbose
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa
28001d576e67ba46ed481c5695f1e0827ff26007Eugen Kuksa out = args.join(' ')
28001d576e67ba46ed481c5695f1e0827ff26007Eugen Kuksa puts out if verbose
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa if !dry_run
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa if user == 'root'
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa exec_system(*[sudo, *args])
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa elsif user
ab0efd0754ad1487560385983a635086fdd09084Eugen Kuksa # This looks strange because of the combination of + and sudo.
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa # It is needed on our deployment machines to get the environment right.
ab0efd0754ad1487560385983a635086fdd09084Eugen Kuksa # On other machines, remove the call of +.
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa exec_system(*['+', 'sudo', '-u', user, 'bash', '-c',
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa "cd #{Dir.getwd} && #{escape_arguments(args)}"])
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa else
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa exec_system(*args)
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa end
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa end
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa end
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa def sudo
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa SUDO_BINARY
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa end
4fbbf628eda08ffdeb98cb41fb7e9001050eec3aEugen Kuksa
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa def exec_system(*args)
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa # puts args.join(' ') # For debugging
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa system(*args)
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa end
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa def escape_arguments(args)
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa rest = args[1..-1].map do |arg|
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa if arg.to_s.include?(' ')
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa %("#{arg.gsub('"', '\"')}")
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa else
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa arg
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa end
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa end
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa ([args[0]] + rest).join(' ')
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa end
b51057b860560bf3ee454c03a121af3d5d34f482Eugen Kuksa
486df98bbf3348cfb96e93c3e499d12435880bb5Eugen Kuksa def maintenance_file
486df98bbf3348cfb96e93c3e499d12435880bb5Eugen Kuksa data_root.join(MAINTENANCE_FILE)
486df98bbf3348cfb96e93c3e499d12435880bb5Eugen Kuksa end
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa
6d055d16c7620b7804b6a46cb481d00b3dbb5007Eugen Kuksa def pg_user_switch
57c81a32dff6182f040b4f852892144641a62fe5Eugen Kuksa sql_dump_as_db_user ? %W(-U #{sql_dump_as_db_user}) : []
6d055d16c7620b7804b6a46cb481d00b3dbb5007Eugen Kuksa end
6d055d16c7620b7804b6a46cb481d00b3dbb5007Eugen Kuksa
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa def self.backup_dirs_allowed_to_delete(entries)
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa entries.reject{ |entry| %w(. ..).include?(entry) }[0..-(BACKUPS_COUNT+1)]
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksaend
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksadef data_root(rails_root)
b84d89b1aa3faa108105fc68925e33dc7eaf9d01Eugen Kuksa ENV['DATA_ROOT'] ||'/data/git'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksaend
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
0978722f6ca27f5d5f5ed4ec8400703dfe211184Eugen Kuksa# Don't allow this to be run as the root user.
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksaif ENV['USER'] != 'root'
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa puts 'Running this script as a normal user is disabled.'
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa puts 'Please run it as root.'
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa exit EXIT_CODES[:wrong_user]
0978722f6ca27f5d5f5ed4ec8400703dfe211184Eugen Kuksaend
0978722f6ca27f5d5f5ed4ec8400703dfe211184Eugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa# We assume, this script runs in "RAILS_ROOT/script/".
daf3e28fff47a65b53d6fb65155301763b9f166eEugen KuksaRAILS_ROOT = Pathname.new(__FILE__).dirname.join('..')
8230471f68bf2a663d94872531d0bcf16d426bbeEugen KuksaBACKUP_ROOT_PRODUCTION = '/data/ontohub_data_backup'
0b99be5ced371cca00283694e1bd53a8ac0d7b5dEugen Kuksa
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen KuksaUSER = 'ontohub'
7e19fab3af21714e410863295a27b859111ba40dEugen KuksaGROUP = 'webservd'
255e2c2477c832c7a785ae0ae77fd25add1e58e6Eugen Kuksa
b84d89b1aa3faa108105fc68925e33dc7eaf9d01Eugen KuksaDATABASE = 'ontohub'
1724834ae596f84e8237a3bba5a8af15916a8ee9Eugen Kuksa
3edb9eac22c71a937953dd2ad4911d12fb22c6c6Eugen Kuksaunless File.exist?(BACKUP_ROOT_PRODUCTION)
3edb9eac22c71a937953dd2ad4911d12fb22c6c6Eugen Kuksa FileUtils.mkdir_p(BACKUP_ROOT_PRODUCTION)
3edb9eac22c71a937953dd2ad4911d12fb22c6c6Eugen Kuksaend
b84d89b1aa3faa108105fc68925e33dc7eaf9d01Eugen KuksaBACKUP_ROOT = File.realpath(BACKUP_ROOT_PRODUCTION)
706a446ea5c5c1644b53a82fcadd8c9080ba3b4fEugen Kuksaunless File.directory?(BACKUP_ROOT)
706a446ea5c5c1644b53a82fcadd8c9080ba3b4fEugen Kuksa $stderr.puts "Target path is not a directory: #{BACKUP_ROOT}"
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa exit EXIT_CODES[:target_not_a_directory]
706a446ea5c5c1644b53a82fcadd8c9080ba3b4fEugen Kuksaend
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
b1fe9054ad7c7192fe4c474363247dad15963e99Eugen Kuksabackup = Backup::Backup.new(DATABASE, data_root(RAILS_ROOT), BACKUP_ROOT,
b84d89b1aa3faa108105fc68925e33dc7eaf9d01Eugen Kuksa sql_dump_as_db_user: 'ontohub',
7e19fab3af21714e410863295a27b859111ba40dEugen Kuksa user: USER, group: GROUP,
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa dry_run: false, verbose: true)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksacase ARGV.first
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksawhen 'create'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa backup.create
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksawhen 'restore'
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa if ARGV.length == 1
4634cde5d3428bd5ab34b8212ac2f4637cdfff6fEugen Kuksa $stderr.puts(
4634cde5d3428bd5ab34b8212ac2f4637cdfff6fEugen Kuksa 'To restore a backup, you need to specify one with the arguments')
4634cde5d3428bd5ab34b8212ac2f4637cdfff6fEugen Kuksa $stderr.puts('"restore backup_name"')
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa exit EXIT_CODES[:bad_arguments]
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa end
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa backup_name = ARGV[1]
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksa backup.restore(backup_name)
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksawhen 'prune'
5efadb4662f2a63d5f5f1a5b303ab7c3371069a8Eugen Kuksa Backup::Backup.prune(BACKUP_ROOT)
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksaelse
923d69139038e74c0936e826bbfdc8717fbbc7b3Eugen Kuksa $stderr.puts 'unknown or missing parameter'
923d69139038e74c0936e826bbfdc8717fbbc7b3Eugen Kuksa $stderr.puts 'use parameter "create" or "restore <backup_name>" or "prune"'
1435372847bd34c5e811e1a7dae3dbd84a43c15cEugen Kuksa exit EXIT_CODES[:bad_arguments]
daf3e28fff47a65b53d6fb65155301763b9f166eEugen Kuksaend