diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index dd1488a33..9fdcd7c71 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1885,9 +1885,16 @@ en: backup_succeeded: subject_template: "Backup completed successfully" - text_body_template: "The backup was successful. + text_body_template: | + The backup was successful. - Visit the [admin > backup section](%{base_url}/admin/backups) to download your new backup." + Visit the [admin > backup section](%{base_url}/admin/backups) to download your new backup." + + Here's the log: + + ```text + %{logs} + ``` backup_failed: subject_template: "Backup failed" @@ -1896,13 +1903,20 @@ en: Here's the log: - ``` + ```text %{logs} ``` restore_succeeded: subject_template: "Restore completed successfully" - text_body_template: "The restore was successful." + text_body_template: | + The restore was successful. + + Here's the log: + + ```text + %{logs} + ``` restore_failed: subject_template: "Restore failed" @@ -1911,7 +1925,7 @@ en: Here's the log: - ``` + ```text %{logs} ``` diff --git a/config/routes.rb b/config/routes.rb index 236f08c25..37a93cd26 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,7 +9,7 @@ require_dependency "permalink_constraint" # and makes Guard not work properly. USERNAME_ROUTE_FORMAT = /[\w.\-]+/ unless defined? USERNAME_ROUTE_FORMAT -BACKUP_ROUTE_FORMAT = /.+\.(tar\.gz|tgz)/i unless defined? BACKUP_ROUTE_FORMAT +BACKUP_ROUTE_FORMAT = /.+\.(sql\.gz|tar\.gz|tgz)/i unless defined? BACKUP_ROUTE_FORMAT Discourse::Application.routes.draw do diff --git a/lib/backup_restore/backup_restore.rb b/lib/backup_restore/backup_restore.rb index 54c572ece..af4670fb1 100644 --- a/lib/backup_restore/backup_restore.rb +++ b/lib/backup_restore/backup_restore.rb @@ -6,6 +6,7 @@ module BackupRestore class OperationRunningError < RuntimeError; end + VERSION_PREFIX = "v".freeze DUMP_FILE = "dump.sql" METADATA_FILE = "meta.json" LOGS_CHANNEL = "/admin/backups/logs" diff --git a/lib/backup_restore/backuper.rb b/lib/backup_restore/backuper.rb index d83364392..daf328c63 100644 --- a/lib/backup_restore/backuper.rb +++ b/lib/backup_restore/backuper.rb @@ -28,8 +28,6 @@ module BackupRestore ensure_directory_exists(@tmp_directory) ensure_directory_exists(@archive_directory) - write_metadata - ### READ-ONLY / START ### enable_readonly_mode @@ -43,7 +41,7 @@ module BackupRestore log "Finalizing backup..." - create_archive + @with_uploads ? create_archive : move_dump_backup after_create_hook rescue SystemExit @@ -54,7 +52,7 @@ module BackupRestore @success = false else @success = true - "#{@archive_basename}.tar.gz" + @backup_filename ensure begin notify_user @@ -84,9 +82,16 @@ module BackupRestore @timestamp = Time.now.strftime("%Y-%m-%d-%H%M%S") @tmp_directory = File.join(Rails.root, "tmp", "backups", @current_db, @timestamp) @dump_filename = "#{File.join(@tmp_directory, BackupRestore::DUMP_FILE)}.gz" - @meta_filename = File.join(@tmp_directory, BackupRestore::METADATA_FILE) @archive_directory = File.join(Rails.root, "public", "backups", @current_db) - @archive_basename = File.join(@archive_directory, "#{SiteSetting.title.parameterize}-#{@timestamp}") + @archive_basename = File.join(@archive_directory, "#{SiteSetting.title.parameterize}-#{@timestamp}-#{BackupRestore::VERSION_PREFIX}#{BackupRestore.current_version}") + + @backup_filename = + if @with_uploads + "#{File.basename(@archive_basename)}.tar.gz" + else + "#{File.basename(@archive_basename)}.sql.gz" + end + @logs = [] @readonly_mode_was_enabled = Discourse.readonly_mode? || !SiteSetting.readonly_mode_during_backup end @@ -137,15 +142,6 @@ module BackupRestore false end - def write_metadata - log "Writing metadata to '#{@meta_filename}'..." - metadata = { - source: "discourse", - version: BackupRestore.current_version - } - File.write(@meta_filename, metadata.to_json) - end - def dump_public_schema log "Dumping the public schema of the database..." @@ -199,8 +195,19 @@ module BackupRestore ].join(" ") end + def move_dump_backup + log "Finalizing database dump file: #{@backup_filename}" + + execute_command( + "mv #{@dump_filename} #{File.join(@archive_directory, @backup_filename)}", + "Failed to move database dump file." + ) + + remove_tmp_directory + end + def create_archive - log "Creating archive: #{File.basename(@archive_basename)}.tar.gz" + log "Creating archive: #{@backup_filename}" tar_filename = "#{@archive_basename}.tar" @@ -219,23 +226,17 @@ module BackupRestore ) end - log "Archiving metadata..." - FileUtils.cd(File.dirname(@meta_filename)) do - execute_command( - "tar --append --dereference --file #{tar_filename} #{File.basename(@meta_filename)}", - "Failed to archive metadata." - ) - end + upload_directory = "uploads/" + @current_db - if @with_uploads - upload_directory = "uploads/" + @current_db - - log "Archiving uploads..." - FileUtils.cd(File.join(Rails.root, "public")) do + log "Archiving uploads..." + FileUtils.cd(File.join(Rails.root, "public")) do + if File.directory?(upload_directory) execute_command( "tar --append --dereference --file #{tar_filename} #{upload_directory}", "Failed to archive uploads." ) + else + log "No uploads found, skipping archiving uploads..." end end @@ -247,7 +248,7 @@ module BackupRestore def after_create_hook log "Executing the after_create_hook for the backup..." - backup = Backup.create_from_filename("#{File.basename(@archive_basename)}.tar.gz") + backup = Backup.create_from_filename(@backup_filename) backup.after_create_hook end @@ -259,9 +260,9 @@ module BackupRestore def notify_user log "Notifying '#{@user.username}' of the end of the backup..." if @success - SystemMessage.create_from_system_user(@user, :backup_succeeded) + SystemMessage.create_from_system_user(@user, :backup_succeeded, logs: pretty_logs(@logs)) else - SystemMessage.create_from_system_user(@user, :backup_failed, logs: @logs.join("\n")) + SystemMessage.create_from_system_user(@user, :backup_failed, logs: pretty_logs(@logs)) end end diff --git a/lib/backup_restore/restorer.rb b/lib/backup_restore/restorer.rb index b2858bc0c..1ea204157 100644 --- a/lib/backup_restore/restorer.rb +++ b/lib/backup_restore/restorer.rb @@ -110,13 +110,18 @@ module BackupRestore @archive_filename = File.join(@tmp_directory, @filename) @tar_filename = @archive_filename[0...-3] @meta_filename = File.join(@tmp_directory, BackupRestore::METADATA_FILE) + @is_archive = !(@filename =~ /.sql.gz$/) # For backwards compatibility @dump_filename = - if system("tar --list --file #{@source_filename} #{BackupRestore::DUMP_FILE}") - File.join(@tmp_directory, BackupRestore::DUMP_FILE) + if @is_archive + if system("tar --list --file #{@source_filename} #{BackupRestore::DUMP_FILE}") + File.join(@tmp_directory, BackupRestore::DUMP_FILE) + else + File.join(@tmp_directory, "#{BackupRestore::DUMP_FILE}.gz") + end else - File.join(@tmp_directory, "#{BackupRestore::DUMP_FILE}.gz") + File.join(@tmp_directory, @filename) end @logs = [] @@ -175,7 +180,10 @@ module BackupRestore end def unzip_archive + return unless @is_archive + log "Unzipping archive, this may take a while..." + FileUtils.cd(@tmp_directory) do execute_command("gzip --decompress '#{@archive_filename}'", "Failed to unzip archive.") end @@ -184,14 +192,23 @@ module BackupRestore def extract_metadata log "Extracting metadata file..." - FileUtils.cd(@tmp_directory) do - execute_command( - "tar --extract --file '#{@tar_filename}' #{BackupRestore::METADATA_FILE}", - "Failed to extract metadata file." - ) - end + @metadata = + if system("tar --list --file #{@source_filename} #{BackupRestore::METADATA_FILE}") + FileUtils.cd(@tmp_directory) do + execute_command( + "tar --extract --file '#{@tar_filename}' #{BackupRestore::METADATA_FILE}", + "Failed to extract metadata file." + ) + end - @metadata = Oj.load_file(@meta_filename) + Oj.load_file(@meta_filename) + else + if @filename =~ /-#{BackupRestore::VERSION_PREFIX}(\d{14})/ + { "version" => Regexp.last_match[1].to_i } + else + raise "Migration version is missing from the filename." + end + end end def validate_metadata @@ -204,6 +221,8 @@ module BackupRestore end def extract_dump + return unless @is_archive + log "Extracting dump file..." FileUtils.cd(@tmp_directory) do @@ -334,7 +353,7 @@ module BackupRestore end def extract_uploads - if `tar --list --file '#{@tar_filename}' | grep 'uploads/'`.present? + if system("tar --list --file '#{@tar_filename}' 'uploads'") log "Extracting uploads..." FileUtils.cd(File.join(Rails.root, "public")) do execute_command( @@ -359,9 +378,9 @@ module BackupRestore if user = User.find_by(email: @user_info[:email]) log "Notifying '#{user.username}' of the end of the restore..." if @success - SystemMessage.create_from_system_user(user, :restore_succeeded) + SystemMessage.create_from_system_user(user, :restore_succeeded, logs: pretty_logs(@logs)) else - SystemMessage.create_from_system_user(user, :restore_failed, logs: @logs.join("\n")) + SystemMessage.create_from_system_user(user, :restore_failed, logs: pretty_logs(@logs)) end else log "Could not send notification to '#{@user_info[:username]}' (#{@user_info[:email]}), because the user does not exists..." diff --git a/lib/backup_restore/utils.rb b/lib/backup_restore/utils.rb index 0d958b906..f5021f1d3 100644 --- a/lib/backup_restore/utils.rb +++ b/lib/backup_restore/utils.rb @@ -10,5 +10,9 @@ module BackupRestore output end + + def pretty_logs(logs) + logs.join("\n") + end end end