Module: BackupRestore

Defined in:
lib/backup_restore.rb,
lib/backup_restore/logger.rb,
lib/backup_restore/factory.rb,
lib/backup_restore/backuper.rb,
lib/backup_restore/restorer.rb,
lib/backup_restore/backup_store.rb,
lib/backup_restore/s3_backup_store.rb,
lib/backup_restore/system_interface.rb,
lib/backup_restore/uploads_restorer.rb,
lib/backup_restore/database_restorer.rb,
lib/backup_restore/meta_data_handler.rb,
lib/backup_restore/local_backup_store.rb,
lib/backup_restore/backup_file_handler.rb

Defined Under Namespace

Classes: BackupFileHandler, BackupStore, Backuper, DatabaseConfiguration, DatabaseRestorer, Factory, LocalBackupStore, Logger, MetaDataHandler, OperationRunningError, Restorer, RunningSidekiqJobsError, S3BackupStore, SystemInterface, UploadsRestorer

Constant Summary collapse

VERSION_PREFIX =
"v"
DUMP_FILE =
"dump.sql.gz"
LOGS_CHANNEL =
"/admin/backups/logs"
RestoreDisabledError =
Class.new(RuntimeError)
FilenameMissingError =
Class.new(RuntimeError)
UploadsRestoreError =
Class.new(RuntimeError)
DatabaseRestoreError =
Class.new(RuntimeError)
MetaDataError =
Class.new(RuntimeError)
MigrationRequiredError =
Class.new(RuntimeError)

Class Method Summary collapse

Class Method Details

.backup!(user_id, opts = {}) ⇒ Object



11
12
13
14
15
16
17
# File 'lib/backup_restore.rb', line 11

def self.backup!(user_id, opts = {})
  if opts[:fork] == false
    BackupRestore::Backuper.new(user_id, opts).run
  else
    spawn_process!(:backup, user_id, opts)
  end
end

.can_rollback?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/backup_restore.rb', line 51

def self.can_rollback?
  backup_tables_count > 0
end

.cancel!Object



28
29
30
31
# File 'lib/backup_restore.rb', line 28

def self.cancel!
  set_shutdown_signal!
  true
end

.current_versionObject



68
69
70
# File 'lib/backup_restore.rb', line 68

def self.current_version
  ActiveRecord::Migrator.current_version
end

.database_configurationObject



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/backup_restore.rb', line 119

def self.database_configuration
  config = ActiveRecord::Base.connection_pool.db_config.configuration_hash
  config = config.with_indifferent_access

  # credentials for PostgreSQL in CI environment
  if Rails.env.test?
    username = ENV["PGUSER"]
    password = ENV["PGPASSWORD"]
  end

  DatabaseConfiguration.new(
    config["backup_host"] || config["host"],
    config["backup_port"] || config["port"],
    config["username"] || username || ENV["USER"] || "postgres",
    config["password"] || password,
    config["database"],
  )
end

.is_operation_running?Boolean

Returns:

  • (Boolean)


39
40
41
# File 'lib/backup_restore.rb', line 39

def self.is_operation_running?
  !!Discourse.redis.get(running_key)
end

.logsObject



63
64
65
66
# File 'lib/backup_restore.rb', line 63

def self.logs
  id = start_logs_message_id
  MessageBus.backlog(LOGS_CHANNEL, id).map { |m| m.data }
end

.mark_as_not_running!Object



43
44
45
# File 'lib/backup_restore.rb', line 43

def self.mark_as_not_running!
  Discourse.redis.del(running_key)
end

.mark_as_running!Object



33
34
35
36
37
# File 'lib/backup_restore.rb', line 33

def self.mark_as_running!
  Discourse.redis.setex(running_key, 60, "1")
  save_start_logs_message_id
  keep_it_running
end

.move_tables_between_schemas(source, destination) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/backup_restore.rb', line 76

def self.move_tables_between_schemas(source, destination)
  owner = database_configuration.username

  ActiveRecord::Base.transaction do
    DB.exec(move_tables_between_schemas_sql(source, destination, owner))
  end
end

.move_tables_between_schemas_sql(source, destination, owner) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/backup_restore.rb', line 84

def self.move_tables_between_schemas_sql(source, destination, owner)
  <<~SQL
    DO $$DECLARE row record;
    BEGIN
      -- create <destination> schema if it does not exists already
      -- NOTE: DROP & CREATE SCHEMA is easier, but we don't want to drop the public schema
      -- otherwise extensions (like hstore & pg_trgm) won't work anymore...
      CREATE SCHEMA IF NOT EXISTS #{destination};
      -- move all <source> tables to <destination> schema
      FOR row IN SELECT tablename FROM pg_tables WHERE schemaname = '#{source}'  AND tableowner = '#{owner}'
      LOOP
        EXECUTE 'DROP TABLE IF EXISTS #{destination}.' || quote_ident(row.tablename) || ' CASCADE;';
        EXECUTE 'ALTER TABLE #{source}.' || quote_ident(row.tablename) || ' SET SCHEMA #{destination};';
      END LOOP;
      -- move all <source> views to <destination> schema
      FOR row IN SELECT viewname FROM pg_views WHERE schemaname = '#{source}' AND viewowner = '#{owner}'
      LOOP
        EXECUTE 'DROP VIEW IF EXISTS #{destination}.' || quote_ident(row.viewname) || ' CASCADE;';
        EXECUTE 'ALTER VIEW #{source}.' || quote_ident(row.viewname) || ' SET SCHEMA #{destination};';
      END LOOP;
      -- move all <source> enums to <destination> enums
      FOR row IN (
        SELECT typname FROM pg_type t
        LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
        WHERE typcategory = 'E' AND n.nspname = '#{source}' AND pg_catalog.pg_get_userbyid(typowner) = '#{owner}'
      ) LOOP
        EXECUTE 'DROP TYPE IF EXISTS #{destination}.' || quote_ident(row.typname) || ' CASCADE;';
        EXECUTE 'ALTER TYPE #{source}.' || quote_ident(row.typname) || ' SET SCHEMA #{destination};';
      END LOOP;
    END$$;
  SQL
end

.operations_statusObject



55
56
57
58
59
60
61
# File 'lib/backup_restore.rb', line 55

def self.operations_status
  {
    is_operation_running: is_operation_running?,
    can_rollback: can_rollback?,
    allow_restore: Rails.env.development? || SiteSetting.allow_restore,
  }
end

.postgresql_major_versionObject



72
73
74
# File 'lib/backup_restore.rb', line 72

def self.postgresql_major_version
  DB.query_single("SHOW server_version").first[/\d+/].to_i
end

.restore!(user_id, opts = {}) ⇒ Object



19
20
21
# File 'lib/backup_restore.rb', line 19

def self.restore!(user_id, opts = {})
  spawn_process!(:restore, user_id, opts)
end

.rollback!Object



23
24
25
26
# File 'lib/backup_restore.rb', line 23

def self.rollback!
  raise BackupRestore::OperationRunningError if BackupRestore.is_operation_running?
  move_tables_between_schemas("backup", "public") if can_rollback?
end

.should_shutdown?Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/backup_restore.rb', line 47

def self.should_shutdown?
  !!Discourse.redis.get(shutdown_signal_key)
end