mirror of
https://github.com/discourse/discourse.git
synced 2024-12-05 10:03:43 +08:00
e2c415457c
Discourse automatically sends a private message after backup or restore finished. The private message used to contain the log inline even when it was very long. A very long log can create issues because the length of the post will be over the maximum allowed length of a post. When that happens, Discourse will try to create an upload with the logs. If that fails, it will trim the log and inline it.
189 lines
5.3 KiB
Ruby
189 lines
5.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module BackupRestore
|
|
RestoreDisabledError = Class.new(RuntimeError)
|
|
FilenameMissingError = Class.new(RuntimeError)
|
|
|
|
class Restorer
|
|
delegate :log, to: :@logger, private: true
|
|
|
|
attr_reader :success
|
|
|
|
def initialize(user_id:, filename:, factory:, disable_emails: true, location: nil)
|
|
@user_id = user_id
|
|
@filename = filename
|
|
@factory = factory
|
|
@logger = factory.logger
|
|
@disable_emails = disable_emails
|
|
|
|
ensure_restore_is_enabled
|
|
ensure_we_have_a_user
|
|
ensure_we_have_a_filename
|
|
|
|
@success = false
|
|
@current_db = RailsMultisite::ConnectionManagement.current_db
|
|
|
|
@system = factory.create_system_interface
|
|
@backup_file_handler = factory.create_backup_file_handler(@filename, @current_db, location)
|
|
@database_restorer = factory.create_database_restorer(@current_db)
|
|
@uploads_restorer = factory.create_uploads_restorer
|
|
end
|
|
|
|
def run
|
|
log "[STARTED]"
|
|
log "'#{@user_info[:username]}' has started the restore!"
|
|
|
|
# FIXME not atomic!
|
|
ensure_no_operation_is_running
|
|
@system.mark_restore_as_running
|
|
|
|
@system.listen_for_shutdown_signal
|
|
|
|
@tmp_directory, db_dump_path = @backup_file_handler.decompress
|
|
validate_backup_metadata
|
|
|
|
@system.enable_readonly_mode
|
|
@system.pause_sidekiq("restore")
|
|
@system.wait_for_sidekiq
|
|
@system.flush_redis
|
|
@system.clear_sidekiq_queues
|
|
|
|
@database_restorer.restore(db_dump_path)
|
|
|
|
reload_site_settings
|
|
|
|
@system.disable_readonly_mode
|
|
|
|
clear_category_cache
|
|
clear_stats
|
|
reload_translations
|
|
|
|
@uploads_restorer.restore(@tmp_directory)
|
|
|
|
clear_emoji_cache
|
|
clear_theme_cache
|
|
|
|
after_restore_hook
|
|
rescue Compression::Strategy::ExtractFailed
|
|
log 'ERROR: The uncompressed file is too big. Consider increasing the hidden ' \
|
|
'"decompressed_backup_max_file_size_mb" setting.'
|
|
@database_restorer.rollback
|
|
rescue SystemExit
|
|
log "Restore process was cancelled!"
|
|
@database_restorer.rollback
|
|
rescue => ex
|
|
log "EXCEPTION: " + ex.message
|
|
log ex.backtrace.join("\n")
|
|
@database_restorer.rollback
|
|
else
|
|
@success = true
|
|
ensure
|
|
clean_up
|
|
notify_user
|
|
log "Finished!"
|
|
|
|
@success ? log("[SUCCESS]") : log("[FAILED]")
|
|
end
|
|
|
|
protected
|
|
|
|
def ensure_restore_is_enabled
|
|
return if Rails.env.development? || SiteSetting.allow_restore?
|
|
raise BackupRestore::RestoreDisabledError
|
|
end
|
|
|
|
def ensure_no_operation_is_running
|
|
raise BackupRestore::OperationRunningError if BackupRestore.is_operation_running?
|
|
end
|
|
|
|
def ensure_we_have_a_user
|
|
user = User.find_by(id: @user_id)
|
|
raise Discourse::InvalidParameters.new(:user_id) if user.blank?
|
|
|
|
# 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
|
|
raise BackupRestore::FilenameMissingError if @filename.nil?
|
|
end
|
|
|
|
def validate_backup_metadata
|
|
@factory.create_meta_data_handler(@filename, @tmp_directory).validate
|
|
end
|
|
|
|
def reload_site_settings
|
|
log "Reloading site settings..."
|
|
SiteSetting.refresh!
|
|
|
|
DiscourseEvent.trigger(:site_settings_restored)
|
|
|
|
if @disable_emails && SiteSetting.disable_emails == 'no'
|
|
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
|
|
end
|
|
|
|
def clear_category_cache
|
|
log "Clearing category cache..."
|
|
Category.reset_topic_ids_cache
|
|
Category.clear_subcategory_ids
|
|
end
|
|
|
|
def clear_emoji_cache
|
|
log "Clearing emoji cache..."
|
|
Emoji.clear_cache
|
|
rescue => ex
|
|
log "Something went wrong while clearing emoji cache.", ex
|
|
end
|
|
|
|
def reload_translations
|
|
log "Reloading translations..."
|
|
TranslationOverride.reload_all_overrides!
|
|
end
|
|
|
|
def notify_user
|
|
if user = User.find_by_email(@user_info[:email])
|
|
log "Notifying '#{user.username}' of the end of the restore..."
|
|
status = @success ? :restore_succeeded : :restore_failed
|
|
|
|
logs = Discourse::Utils.logs_markdown(@logger.logs, user: user)
|
|
post = SystemMessage.create_from_system_user(user, status, logs: logs)
|
|
else
|
|
log "Could not send notification to '#{@user_info[:username]}' " \
|
|
"(#{@user_info[:email]}), because the user does not exist."
|
|
end
|
|
rescue => ex
|
|
log "Something went wrong while notifying user.", ex
|
|
end
|
|
|
|
def clean_up
|
|
log "Cleaning stuff up..."
|
|
@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
|
|
end
|
|
|
|
def clear_theme_cache
|
|
log "Clear theme cache"
|
|
ThemeField.force_recompilation!
|
|
Theme.expire_site_cache!
|
|
Stylesheet::Manager.cache.clear
|
|
rescue => ex
|
|
log "Something went wrong while clearing theme cache.", ex
|
|
end
|
|
|
|
def clear_stats
|
|
Discourse.stats.remove("missing_s3_uploads")
|
|
end
|
|
|
|
def after_restore_hook
|
|
log "Executing the after_restore_hook..."
|
|
DiscourseEvent.trigger(:restore_complete)
|
|
end
|
|
end
|
|
end
|