backup.rake 4.24 KB
task :load_backup_config do
  db_file = Rails.root.join('config', 'database.yml')
  $config = YAML.load(ERB.new(File.read(db_file)).result)
end

task :check_backup_support => :load_backup_config do
  if $config['production']['adapter'] != 'postgresql'
    fail("Only PostgreSQL is supported for backups at the moment")
  end
end

backup_dirs = [
  'public/image_uploads',
  'public/articles',
  'public/thumbnails',
  'public/user_themes',
]

desc "Creates a backup of the database and uploaded files"
task :backup => :check_backup_support do
  dirs = backup_dirs.select { |d| File.exists?(d) }
  rails_env = ENV["RAILS_ENV"] || 'production'

  backup_name = Time.now.strftime('%Y-%m-%d-%R')
  backup_file = File.join('tmp/backup', backup_name) + '.tar.gz'
  mkdir_p 'tmp/backup'
  dump = File.join('tmp/backup', backup_name) + '.sql'

  database = $config[rails_env]['database']
  host = $config[rails_env]['host']
  host = host && "-h #{host}" || ""
  sh "pg_dump #{host} #{database} > #{dump}"

  sh 'tar', 'chaf', backup_file, dump, *dirs
  rm_f dump

  puts "****************************************************"
  puts "Backup in #{backup_file} !"
  puts
  puts "To restore, use:"
  puts "$ rake restore BACKUP=#{backup_file}"
  puts "****************************************************"
end

def invalid_backup!(message, items=[])
  puts "E: #{message}"
  items.each do |i|
    puts "E: - #{i}"
  end
  puts "E: Is this a backup archive created by Noosfero with \`rake backup\`?"
  exit 1
end

desc "Restores a backup created previousy with \`rake backup\`"
task :restore => :check_backup_support do
  backup = ENV["BACKUP"]
  rails_env = ENV["RAILS_ENV"] || 'production'
  unless backup
    puts "usage: rake restore BACKUP=/path/to/backup"
    exit 1
  end

  files = `tar taf #{backup}`.split

  # validate files in the backup
  invalid_files = []
  files.each do |f|
    if f !~ /tmp\/backup\// && (backup_dirs.none? { |d| f =~ /^#{d}\// })
      invalid_files << f
    end
  end
  if invalid_files.size > 0
    invalid_backup!("Invalid files found in the backup archive", invalid_files)
  end

  # find database dump in the archive
  dumps = files.select do |f|
    File.dirname(f) == 'tmp/backup' && f =~ /\.sql$/
  end
  if dumps.size == 0
    invalid_backup!("Could not find a database dump in the archive.")
  elsif dumps.size > 1
    invalid_backup!("Multiple database dumps found in the archive:", dumps)
  end
  dump = dumps.first

  database = $config[rails_env]['database']
  username = $config[rails_env]['username']
  host = $config[rails_env]['host']
  host = host && "-h #{host}" || ""

  puts "WARNING: backups should be restored to an empty database, otherwise"
  puts "data from the backup may not be loaded properly."
  puts
  puts 'You can remove the existing database and create a new one with:'
  puts
  puts "$ sudo -u postgres dropdb #{host} #{database}"
  puts "$ sudo -u postgres createdb #{host} #{database} --owner #{username}"
  puts
  print "Are you sure you want to continue (y/N)? "
  response = $stdin.gets.strip
  unless ['y', 'yes'].include?(response.downcase)
    puts "*** ABORTED."
    exit 1
  end

  sh 'tar', 'xaf', backup
  sh "rails dbconsole #{rails_env} < #{dump}"
  rm_f dump

  puts "****************************************************"
  puts "Backup restored!"
  puts "****************************************************"
end

desc 'Removes emails from database'
task 'restore:remove_emails' => :environment do
  connection = ActiveRecord::Base.connection
  [
    "UPDATE users SET email = concat('user', id, '@localhost.localdomain')",
    "UPDATE environments SET contact_email = concat('environment', id, '@localhost.localdomain')",
  ].each do |update|
    puts update
    connection.execute(update)
  end

  profiles = connection.execute("select id, data from profiles")
  profiles.each do |profile|
    if profile['data']
      data = YAML.load(profile['data'])
      if data[:contact_email] && data[:contact_email] !~ /@localhost.localdomain$/
        data[:contact_email] = ['profile', profile['id'], '@localhost.localdomain'].join
        sql = Environment.send(:sanitize_sql, [
          "UPDATE profiles SET data = ? WHERE id = ?",
          YAML.dump(data),
          profile['id'],
        ])
        puts sql
        connection.execute(sql)
      end
    end
  end
end