2019-05-02 18:17:27 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-10-10 14:04:07 -04:00
|
|
|
module BackupRestore
|
2020-01-12 18:12:27 -05:00
|
|
|
RestoreDisabledError = Class.new(RuntimeError)
|
|
|
|
FilenameMissingError = Class.new(RuntimeError)
|
2014-02-12 23:32:58 -05:00
|
|
|
|
2014-10-10 14:04:07 -04:00
|
|
|
class Restorer
|
2020-01-12 18:12:27 -05:00
|
|
|
delegate :log, to: :@logger, private: true
|
2014-08-04 11:55:09 -04:00
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
attr_reader :success
|
2018-03-15 18:09:06 -04:00
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
def initialize(user_id:, filename:, factory:, disable_emails: true)
|
2015-08-27 14:02:13 -04:00
|
|
|
@user_id = user_id
|
2020-01-12 18:12:27 -05:00
|
|
|
@filename = filename
|
|
|
|
@factory = factory
|
|
|
|
@logger = factory.logger
|
|
|
|
@disable_emails = disable_emails
|
2014-02-12 23:32:58 -05:00
|
|
|
|
2014-10-10 14:04:07 -04:00
|
|
|
ensure_restore_is_enabled
|
2014-02-12 23:32:58 -05:00
|
|
|
ensure_we_have_a_user
|
|
|
|
ensure_we_have_a_filename
|
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
@success = false
|
|
|
|
@current_db = RailsMultisite::ConnectionManagement.current_db
|
|
|
|
|
|
|
|
@system = factory.create_system_interface
|
|
|
|
@backup_file_handler = factory.create_backup_file_handler(@filename, @current_db)
|
|
|
|
@database_restorer = factory.create_database_restorer(@current_db)
|
|
|
|
@uploads_restorer = factory.create_uploads_restorer
|
2014-02-12 23:32:58 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def run
|
|
|
|
log "[STARTED]"
|
|
|
|
log "'#{@user_info[:username]}' has started the restore!"
|
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
# FIXME not atomic!
|
|
|
|
ensure_no_operation_is_running
|
|
|
|
@system.mark_restore_as_running
|
2014-02-12 23:32:58 -05:00
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
@system.listen_for_shutdown_signal
|
2014-02-12 23:32:58 -05:00
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
@tmp_directory, db_dump_path = @backup_file_handler.decompress
|
|
|
|
validate_backup_metadata
|
2014-02-12 23:32:58 -05:00
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
@system.enable_readonly_mode
|
2020-10-16 09:19:02 -04:00
|
|
|
@system.pause_sidekiq("restore")
|
2020-01-12 18:12:27 -05:00
|
|
|
@system.wait_for_sidekiq
|
2020-10-16 09:19:02 -04:00
|
|
|
@system.flush_redis
|
|
|
|
@system.clear_sidekiq_queues
|
2014-02-12 23:32:58 -05:00
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
@database_restorer.restore(db_dump_path)
|
2014-02-12 23:32:58 -05:00
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
reload_site_settings
|
2014-04-08 12:06:53 -04:00
|
|
|
|
2020-10-16 09:19:02 -04:00
|
|
|
@system.unpause_sidekiq
|
2020-01-12 18:12:27 -05:00
|
|
|
@system.disable_readonly_mode
|
2014-02-12 23:32:58 -05:00
|
|
|
|
2020-04-09 08:42:24 -04:00
|
|
|
clear_category_cache
|
2018-11-19 21:37:58 -05:00
|
|
|
clear_emoji_cache
|
|
|
|
clear_theme_cache
|
2020-09-10 15:37:42 -04:00
|
|
|
clear_stats
|
2020-05-18 12:45:47 -04:00
|
|
|
reload_translations
|
2018-11-19 21:37:58 -05:00
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
@uploads_restorer.restore(@tmp_directory)
|
2019-02-18 05:48:03 -05:00
|
|
|
|
|
|
|
after_restore_hook
|
2019-10-11 13:38:10 -04:00
|
|
|
rescue Compression::Strategy::ExtractFailed
|
2020-01-12 18:12:27 -05:00
|
|
|
log 'ERROR: The uncompressed file is too big. Consider increasing the hidden ' \
|
|
|
|
'"decompressed_backup_max_file_size_mb" setting.'
|
|
|
|
@database_restorer.rollback
|
2014-02-12 23:32:58 -05:00
|
|
|
rescue SystemExit
|
|
|
|
log "Restore process was cancelled!"
|
2020-01-12 18:12:27 -05:00
|
|
|
@database_restorer.rollback
|
2014-08-18 02:42:48 -04:00
|
|
|
rescue => ex
|
2014-02-12 23:32:58 -05:00
|
|
|
log "EXCEPTION: " + ex.message
|
|
|
|
log ex.backtrace.join("\n")
|
2020-01-12 18:12:27 -05:00
|
|
|
@database_restorer.rollback
|
2014-02-12 23:32:58 -05:00
|
|
|
else
|
|
|
|
@success = true
|
|
|
|
ensure
|
2018-09-19 14:35:43 -04:00
|
|
|
clean_up
|
|
|
|
notify_user
|
|
|
|
log "Finished!"
|
2016-07-22 00:14:35 -04:00
|
|
|
|
2014-02-12 23:32:58 -05:00
|
|
|
@success ? log("[SUCCESS]") : log("[FAILED]")
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
2014-10-10 14:04:07 -04:00
|
|
|
def ensure_restore_is_enabled
|
2020-01-12 18:12:27 -05:00
|
|
|
return if Rails.env.development? || SiteSetting.allow_restore?
|
|
|
|
raise BackupRestore::RestoreDisabledError
|
2014-02-12 23:32:58 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def ensure_no_operation_is_running
|
|
|
|
raise BackupRestore::OperationRunningError if BackupRestore.is_operation_running?
|
|
|
|
end
|
|
|
|
|
|
|
|
def ensure_we_have_a_user
|
2014-05-06 09:41:59 -04:00
|
|
|
user = User.find_by(id: @user_id)
|
2020-01-12 18:12:27 -05:00
|
|
|
raise Discourse::InvalidParameters.new(:user_id) if user.blank?
|
|
|
|
|
2014-02-12 23:32:58 -05:00
|
|
|
# keep some user data around to check them against the newly restored database
|
|
|
|
@user_info = { id: user.id, username: user.username, email: user.email }
|
|
|
|
end
|
|
|
|
|
|
|
|
def ensure_we_have_a_filename
|
2015-06-11 02:42:01 -04:00
|
|
|
raise BackupRestore::FilenameMissingError if @filename.nil?
|
2014-02-12 23:32:58 -05:00
|
|
|
end
|
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
def validate_backup_metadata
|
|
|
|
@factory.create_meta_data_handler(@filename, @tmp_directory).validate
|
2014-04-08 12:06:53 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def reload_site_settings
|
|
|
|
log "Reloading site settings..."
|
|
|
|
SiteSetting.refresh!
|
2019-02-25 10:06:33 -05:00
|
|
|
|
2019-08-22 16:41:19 -04:00
|
|
|
DiscourseEvent.trigger(:site_settings_restored)
|
|
|
|
|
2019-04-16 05:48:07 -04:00
|
|
|
if @disable_emails && SiteSetting.disable_emails == 'no'
|
2019-03-07 15:48:26 -05:00
|
|
|
log "Disabling outgoing emails for non-staff users..."
|
|
|
|
user = User.find_by_email(@user_info[:email]) || Discourse.system_user
|
|
|
|
SiteSetting.set_and_log(:disable_emails, 'non-staff', user)
|
|
|
|
end
|
2014-02-12 23:32:58 -05:00
|
|
|
end
|
|
|
|
|
2020-04-09 08:42:24 -04:00
|
|
|
def clear_category_cache
|
|
|
|
log "Clearing category cache..."
|
|
|
|
Category.reset_topic_ids_cache
|
|
|
|
Category.clear_subcategory_ids
|
|
|
|
end
|
|
|
|
|
2015-03-17 12:29:18 -04:00
|
|
|
def clear_emoji_cache
|
|
|
|
log "Clearing emoji cache..."
|
|
|
|
Emoji.clear_cache
|
|
|
|
end
|
|
|
|
|
2020-05-18 12:45:47 -04:00
|
|
|
def reload_translations
|
|
|
|
log "Reloading translations..."
|
|
|
|
TranslationOverride.reload_all_overrides!
|
|
|
|
end
|
|
|
|
|
2014-03-24 14:34:16 -04:00
|
|
|
def notify_user
|
2017-04-26 14:47:36 -04:00
|
|
|
if user = User.find_by_email(@user_info[:email])
|
2014-03-24 14:34:16 -04:00
|
|
|
log "Notifying '#{user.username}' of the end of the restore..."
|
2017-03-17 02:21:30 -04:00
|
|
|
status = @success ? :restore_succeeded : :restore_failed
|
|
|
|
|
2020-01-12 18:12:27 -05:00
|
|
|
SystemMessage.create_from_system_user(
|
|
|
|
user, status,
|
2020-01-25 16:07:41 -05:00
|
|
|
logs: Discourse::Utils.pretty_logs(@logger.logs)
|
2017-03-17 02:21:30 -04:00
|
|
|
)
|
2014-03-24 14:34:16 -04:00
|
|
|
else
|
2020-01-12 18:12:27 -05:00
|
|
|
log "Could not send notification to '#{@user_info[:username]}' " \
|
|
|
|
"(#{@user_info[:email]}), because the user does not exist."
|
2014-03-24 14:34:16 -04:00
|
|
|
end
|
2018-09-19 14:35:43 -04:00
|
|
|
rescue => ex
|
|
|
|
log "Something went wrong while notifying user.", ex
|
2014-03-24 14:34:16 -04:00
|
|
|
end
|
|
|
|
|
2014-02-12 23:32:58 -05:00
|
|
|
def clean_up
|
|
|
|
log "Cleaning stuff up..."
|
2020-01-12 18:12:27 -05:00
|
|
|
@database_restorer.clean_up
|
|
|
|
@backup_file_handler.clean_up
|
|
|
|
@system.unpause_sidekiq
|
|
|
|
@system.disable_readonly_mode if Discourse.readonly_mode?
|
|
|
|
@system.mark_restore_as_not_running
|
2014-02-12 23:32:58 -05:00
|
|
|
end
|
|
|
|
|
2018-11-19 21:37:58 -05:00
|
|
|
def clear_theme_cache
|
|
|
|
log "Clear theme cache"
|
2019-01-10 05:06:01 -05:00
|
|
|
ThemeField.force_recompilation!
|
2018-11-19 21:37:58 -05:00
|
|
|
Theme.expire_site_cache!
|
2019-05-07 11:00:26 -04:00
|
|
|
Stylesheet::Manager.cache.clear
|
2018-11-19 21:37:58 -05:00
|
|
|
end
|
|
|
|
|
2020-09-10 15:37:42 -04:00
|
|
|
def clear_stats
|
|
|
|
Discourse.stats.remove("missing_s3_uploads")
|
|
|
|
end
|
|
|
|
|
2019-02-18 05:48:03 -05:00
|
|
|
def after_restore_hook
|
|
|
|
log "Executing the after_restore_hook..."
|
|
|
|
DiscourseEvent.trigger(:restore_complete)
|
|
|
|
end
|
2014-02-12 23:32:58 -05:00
|
|
|
end
|
|
|
|
end
|