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