Skip to content

Commit 085f799

Browse files
committed
FEATURE: Upload backups to S3 when complete.
1 parent 3175c85 commit 085f799

File tree

5 files changed

+82
-0
lines changed

5 files changed

+82
-0
lines changed

Diff for: app/models/backup.rb

+38
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ def self.[](filename)
2525

2626
def remove
2727
File.delete(@path) if File.exists?(path)
28+
after_remove_hook
29+
end
30+
31+
def after_create_hook
32+
upload_to_s3 if SiteSetting.enable_s3_backups?
33+
end
34+
35+
def after_remove_hook
36+
remove_from_s3 if SiteSetting.enable_s3_backups?
37+
end
38+
39+
def upload_to_s3
40+
return unless fog_directory
41+
fog_directory.files.create(key: @filename, public: false, body: File.read(@path))
42+
end
43+
44+
def remove_from_s3
45+
return unless fog
46+
fog.delete_object(SiteSetting.s3_backup_bucket, @filename)
2847
end
2948

3049
def self.base_directory
@@ -49,4 +68,23 @@ def self.remove_old
4968
all_backups[SiteSetting.maximum_backups..-1].each {|b| b.remove}
5069
end
5170

71+
private
72+
73+
def fog
74+
return @fog if @fog
75+
return unless SiteSetting.s3_access_key_id.present? &&
76+
SiteSetting.s3_secret_access_key.present? &&
77+
SiteSetting.s3_backup_bucket.present?
78+
require 'fog'
79+
@fog = Fog::Storage.new(provider: 'AWS',
80+
aws_access_key_id: SiteSetting.s3_access_key_id,
81+
aws_secret_access_key: SiteSetting.s3_secret_access_key)
82+
end
83+
84+
def fog_directory
85+
return @fog_directory if @fog_directory
86+
return unless fog
87+
@fog_directory ||= fog.directories.get(SiteSetting.s3_backup_bucket)
88+
end
89+
5290
end

Diff for: config/locales/server.en.yml

+2
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,8 @@ en:
714714
allow_restore: "Allow restore, which can replace ALL site data! Leave false unless you plan to do restore a backup"
715715
maximum_backups: "The maximum amount of backups to keep on disk. Older backups are automatically deleted"
716716
backup_daily: "Automatically create a site backup once a day"
717+
enable_s3_backups: "Upload backups to S3 when complete. Make sure you have filled in your s3 credentials."
718+
s3_backup_bucket: "The remote bucket to hold backups. WARNING: Make sure it is a private bucket."
717719

718720
active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds"
719721
previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours"

Diff for: config/site_settings.yml

+5
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,11 @@ backups:
419419
backup_daily:
420420
client: false
421421
default: false
422+
enable_s3_backups:
423+
client: false
424+
default: false
425+
s3_backup_bucket:
426+
client: false
422427

423428
uncategorized:
424429

Diff for: lib/export/exporter.rb

+8
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def run
3838

3939
create_archive
4040

41+
after_create_hook
42+
4143
remove_old
4244

4345
notify_user
@@ -236,6 +238,12 @@ def create_archive
236238
`gzip --best #{tar_filename}`
237239
end
238240

241+
def after_create_hook
242+
log "Executing the after_create_hook for the backup"
243+
backup = Backup.create_from_filename("#{File.basename(@archive_basename)}.tar.gz")
244+
backup.after_create_hook
245+
end
246+
239247
def notify_user
240248
log "Notifying '#{@user.username}' of the success of the backup..."
241249
# NOTE: will only notify if @user != Discourse.site_contact_user

Diff for: spec/models/backup_spec.rb

+29
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,34 @@
2727
Backup.remove_old
2828
end
2929
end
30+
31+
context ".after_create_hook" do
32+
it "calls upload_to_s3 if the SiteSetting is true" do
33+
SiteSetting.stubs(:enable_s3_backups?).returns(true)
34+
b1.expects(:upload_to_s3).once
35+
b1.after_create_hook
36+
end
37+
38+
it "calls upload_to_s3 if the SiteSetting is false" do
39+
SiteSetting.stubs(:enable_s3_backups?).returns(false)
40+
b1.expects(:upload_to_s3).never
41+
b1.after_create_hook
42+
end
43+
end
44+
45+
context ".after_remove_hook" do
46+
it "calls remove_from_s3 if the SiteSetting is true" do
47+
SiteSetting.stubs(:enable_s3_backups?).returns(true)
48+
b1.expects(:remove_from_s3).once
49+
b1.after_remove_hook
50+
end
51+
52+
it "calls remove_from_s3 if the SiteSetting is false" do
53+
SiteSetting.stubs(:enable_s3_backups?).returns(false)
54+
b1.expects(:remove_from_s3).never
55+
b1.after_remove_hook
56+
end
57+
end
58+
3059
end
3160

0 commit comments

Comments
 (0)