boring-backup 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90fcf205730a58bf45284b04cd2a7be2604158b5bcd983c2bd3bdaada0fb06c0
4
- data.tar.gz: b17bae14e94295a24d68da3592b3baa238bc1a10fb76bcad464ae515bd7cc46c
3
+ metadata.gz: 343fecddfbccfc03c5216673a667596b7e6af0ccd4e7edc6c03e864d80f7727b
4
+ data.tar.gz: cff420aa2b49fcae03d06d03c95d6f5e4d5813b112e66464b1829ef4c069ec0b
5
5
  SHA512:
6
- metadata.gz: 0317b2fbf1d5768a0b0cfe28e351817d51065579dca37007d6236956c07f474c529297237358c36d4cd572d017bcb1f79209b5c5c4827fb036b169bb2ebc1339
7
- data.tar.gz: a3481f64a9c95894830fb1bc92964c154a79796febe716c8e8c27ea09b5de110ce0613386adcdf3703d619a8794730a7470dd09189d504b55cc682b64bea3695
6
+ metadata.gz: 2f9c315742cb0c19493947d542e7657af569eb1ddc78890839cf239300276707e3b060ff9d02161f3535c5e687aa8facf14d684c859cb14d2d7187af69344a78
7
+ data.tar.gz: 1dbff91a051d57ab40703c9e760c435789a05c8b671742b795714847dd5385ac4308a803b6f4c36e6fa99f4d7cce5e63a4404ce9fbc56090d028f454c6181016
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.1] - 2026-08-12
4
+
5
+ - The `:r2` store now defaults to the `standard` storage class instead of inheriting `standard_ia` from
6
+ `:s3`. R2's free tier does not cover Infrequent Access, and its per-request charges round up to a full
7
+ million, so a single backup a month cost $9.00.
8
+
9
+ ## [0.6.0] - 2026-07-23
10
+
11
+ - `bb doctor` - improved scheduler checks (now supports `sidekiq-crosidekiq-cron`)
12
+ - Support `PG*` env vars
13
+ - Support for Cloudflare R2 using their S3-compatible API
14
+
3
15
  ## [0.5.0] - 2026-07-14
4
16
 
5
17
  - **`aws-sdk-s3` is no longer a dependency of the gem.** apps using the `:s3` store must add `gem
data/README.md CHANGED
@@ -21,12 +21,18 @@ gem install boring-backup
21
21
  The gem requires `pg_dump` to be installed on the machine that is running it.
22
22
 
23
23
  Storage backends bring their own dependencies, which are not installed by default. To use
24
- the `:s3` store, add the AWS SDK to your `Gemfile` as well:
24
+ the `:s3` and `:r2` stores, add the AWS SDK to your `Gemfile` as well:
25
25
 
26
26
  ```bash
27
27
  bundle add aws-sdk-s3
28
28
  ```
29
29
 
30
+ Run the install command to set up the initializer:
31
+
32
+ ```bash
33
+ bundle exec bb install
34
+ ```
35
+
30
36
  ## Usage
31
37
 
32
38
  ### Automatic
@@ -66,9 +72,11 @@ anything to disk. Use any scheduler or even cron to run it periodically.
66
72
  All configuration options can be edited in the initializer:
67
73
 
68
74
  ```rb
69
- # config/initializers/boring-backup.rb
75
+ # config/initializers/boring_backup.rb
70
76
 
71
77
  BoringBackup.configure do |config|
78
+ config.sentinel_key = '12345678-abcd-1234-abcd-abcdef123456'
79
+
72
80
  config.register(:s3) do |store|
73
81
  store.bucket = Settings.aws.bucket
74
82
  store.region = Settings.aws.region
@@ -76,6 +84,13 @@ BoringBackup.configure do |config|
76
84
  store.secret_access_key = Settings.aws.secret_access_key
77
85
  end
78
86
 
87
+ config.register(:r2) do |store|
88
+ store.endpoint = Settings.r2.endpoint
89
+ store.bucket = Settings.r2.bucket
90
+ store.access_key_id = Settings.r2.access_key_id
91
+ store.secret_access_key = Settings.r2.secret_access_key
92
+ end
93
+
79
94
  config.notifier :slack do |slack|
80
95
  slack.webhook_url = 'https://hooks.slack.com/services/whatever'
81
96
  end
@@ -108,12 +123,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/gmitre
108
123
 
109
124
  ## Wishlist
110
125
 
111
- - [ ] S3-compatible backends
126
+ - [X] S3-compatible backends
127
+ - [X] doctor command
112
128
  - [ ] file backend
113
- - [ ] email notifier
114
- - [ ] SMS notifier
115
129
  - [ ] restore command
116
- - [ ] test command
117
130
  - [ ] encryption
118
131
  - [ ] delete stale
119
132
 
@@ -140,7 +140,7 @@ module BoringBackup
140
140
  def check_line(check, width)
141
141
  mark, colour = CHECK_MARKS.fetch(check.status)
142
142
 
143
- "#{pastel.decorate(mark, colour)} #{pastel.bold(check.name.ljust(width))} #{pastel.dim(check.detail.to_s)}"
143
+ "#{pastel.decorate(mark, colour)} #{pastel.bold(check.name.to_s.ljust(width))} #{pastel.dim(check.detail.to_s)}"
144
144
  end
145
145
 
146
146
  def doctor_verdict(result)
@@ -210,19 +210,12 @@ module BoringBackup
210
210
  clear_line
211
211
  end
212
212
 
213
- def env_store?
214
- !ENV["AWS_S3_BUCKET"].to_s.empty?
215
- end
216
-
217
213
  def configured_store_summary
218
- return "S3 — s3://#{ENV["AWS_S3_BUCKET"]} (from ENV)" if env_store?
219
-
220
214
  "S3 — #{INITIALIZER}" if File.exist?(INITIALIZER)
221
215
  end
222
216
 
223
217
  def configure_store
224
218
  return if File.exist?(INITIALIZER)
225
- return if env_store? && prompt.yes?("Use the S3 bucket already in ENV (#{ENV["AWS_S3_BUCKET"]})?")
226
219
 
227
220
  prompt.select("Where should backups go?") do |menu|
228
221
  menu.choice "S3 (or S3-compatible: R2, MinIO, Spaces)", :s3
@@ -236,12 +229,7 @@ module BoringBackup
236
229
 
237
230
  def write_initializer(store)
238
231
  unless store
239
- if File.exist?(INITIALIZER)
240
- say_status :identical, INITIALIZER, :blue
241
- else
242
- say_status :skip, "#{INITIALIZER} not needed — the S3 store reads its config from ENV", :blue
243
- end
244
-
232
+ say_status :identical, INITIALIZER, :blue
245
233
  return
246
234
  end
247
235
 
@@ -1,7 +1,7 @@
1
1
  require "open3"
2
2
 
3
3
  module BoringBackup::Commands
4
- CommandResult = Struct.new(:error, :store_results, keyword_init: true) do
4
+ CommandResult = Struct.new(:error, :store_results, :engine, keyword_init: true) do
5
5
  def success?
6
6
  status == :success
7
7
  end
@@ -42,6 +42,8 @@ module BoringBackup::Commands
42
42
  end
43
43
 
44
44
  def execute
45
+ @engine = BoringBackup::Engine.info
46
+
45
47
  perform_sanity_check!
46
48
 
47
49
  commands = [config.dump_command]
@@ -57,7 +59,7 @@ module BoringBackup::Commands
57
59
  store.backup!(@key, reader)
58
60
  rescue => e
59
61
  # *always* return a Result, even if unexpected. Add store name for debugging.
60
- BoringBackup::Stores::Result.new(success: false, error: e, store: store.class.name, key: @key)
62
+ BoringBackup::Stores::Result.new(success: false, error: e, store: store.name, key: @key)
61
63
  ensure
62
64
  reader.close
63
65
  end
@@ -80,7 +82,7 @@ module BoringBackup::Commands
80
82
  raise BoringBackup::DumpFailedError, "pipeline failed" unless wait_threads.all? { |t| t.value.success? }
81
83
  end
82
84
 
83
- CommandResult.new(store_results: @store_results)
85
+ CommandResult.new(store_results: @store_results, engine: @engine)
84
86
  rescue BoringBackup::DumpFailedError => error
85
87
  # The dump stream itself failed, so uploads that finished are truncated copies of a bad stream — delete them.
86
88
  # Collect the results first: if copy_stream raised, the collection line above never ran. Sinks are already
@@ -89,9 +91,9 @@ module BoringBackup::Commands
89
91
 
90
92
  cleanup_uploaded_stores!
91
93
 
92
- CommandResult.new(error:, store_results: @store_results)
94
+ CommandResult.new(error:, store_results: @store_results, engine: @engine)
93
95
  rescue => error
94
- CommandResult.new(error:, store_results: @store_results)
96
+ CommandResult.new(error:, store_results: @store_results, engine: @engine)
95
97
  end
96
98
 
97
99
  private
@@ -1,6 +1,7 @@
1
1
  module BoringBackup::Commands
2
2
  Check = Struct.new(:name, :status, :detail, keyword_init: true) do
3
3
  def failed? = status == :fail
4
+ def ok? = status == :ok
4
5
  end
5
6
 
6
7
  DoctorResult = Struct.new(:checks, keyword_init: true) do
@@ -10,8 +11,8 @@ module BoringBackup::Commands
10
11
  end
11
12
 
12
13
  class Doctor
13
- RECURRING = "config/recurring.yml"
14
-
14
+ SOLID_QUEUE_SCHEDULE_FILE = "config/recurring.yml"
15
+ SIDEKIQ_CRON_SCHEDULE_FILE = "config/schedule.yml"
15
16
  def self.execute = new.execute
16
17
 
17
18
  def execute
@@ -21,7 +22,7 @@ module BoringBackup::Commands
21
22
  database_resolved,
22
23
  stores_registered,
23
24
  *config.stores.flat_map { |store| store_checks(store) },
24
- schedule_registered
25
+ *scheduler_checks
25
26
  ]
26
27
 
27
28
  DoctorResult.new(checks: checks.compact)
@@ -42,39 +43,27 @@ module BoringBackup::Commands
42
43
  end
43
44
 
44
45
  def pg_dump_path
45
- return @pg_dump_path if defined?(@pg_dump_path)
46
-
47
- @pg_dump_path = ENV.fetch("PATH", "").split(File::PATH_SEPARATOR)
48
- .map { |dir| File.join(dir, "pg_dump") }
49
- .find { |path| File.executable?(path) }
46
+ BoringBackup::Engine.pg_dump_path
50
47
  end
51
48
 
52
49
  def pg_dump_version
53
50
  return unless pg_dump_path
54
51
 
55
- client = major_version(`#{pg_dump_path} --version`)
56
- server = major_version(server_version)
52
+ client = BoringBackup::Engine.client_version
53
+ server = BoringBackup::Engine.server_version
57
54
 
58
55
  return check("versions", :skip, "could not read server version") unless server
59
56
  return check("versions", :skip, "could not read pg_dump version") unless client
60
57
 
61
- if client >= server
58
+ if major(client) >= major(server)
62
59
  check("versions", :ok, "pg_dump #{client}, server #{server}")
63
60
  else
64
61
  check("versions", :fail, "pg_dump #{client} is older than server #{server} — the dump may not restore")
65
62
  end
66
63
  end
67
64
 
68
- def major_version(string)
69
- string.to_s[/(\d+)/, 1]&.to_i
70
- end
71
-
72
- def server_version
73
- return unless defined?(::ActiveRecord)
74
-
75
- ::ActiveRecord::Base.connection.select_value("SHOW server_version")
76
- rescue
77
- nil
65
+ def major(version)
66
+ Gem::Version.new(version).segments.first
78
67
  end
79
68
 
80
69
  def database_resolved
@@ -103,29 +92,26 @@ module BoringBackup::Commands
103
92
  [check(store.name, :fail, e.message)]
104
93
  end
105
94
 
106
- def schedule_registered
107
- return check("schedule", :skip, "no #{RECURRING} — using cron or another scheduler?") unless File.exist?(RECURRING)
95
+ def scheduler_checks
96
+ checks = [solid_queue_check, sidekiq_cron_check].compact
108
97
 
109
- unless File.read(RECURRING, mode: "r:UTF-8").include?("BoringBackup::BackupJob")
110
- return check("schedule", :fail, "#{RECURRING} does not run BoringBackup::BackupJob")
98
+ if checks.none?(&:ok?)
99
+ [check("scheduler detected", :skip, "no schedulers detected. Make sure BackupJob is executed in a cron or within a scheduler.")]
100
+ else
101
+ checks.select(&:ok?)
111
102
  end
103
+ end
112
104
 
113
- unless backup_job_loadable?
114
- return check("schedule", :fail, "BoringBackup::BackupJob won't load — is activejob in the Gemfile?")
105
+ def solid_queue_check
106
+ if File.exist?(SOLID_QUEUE_SCHEDULE_FILE) && File.read(SOLID_QUEUE_SCHEDULE_FILE, mode: "r:UTF-8").include?("BoringBackup::BackupJob")
107
+ check("scheduler detected", :ok, "solid-queue: #{SOLID_QUEUE_SCHEDULE_FILE}")
115
108
  end
116
-
117
- check("schedule", :ok, "#{RECURRING} runs BoringBackup::BackupJob")
118
109
  end
119
110
 
120
- def backup_job_loadable?
121
- return false unless defined?(::ActiveJob)
122
-
123
- Object.const_get("ActiveJob::Base")
124
- require "boring_backup/jobs/backup_job"
125
-
126
- true
127
- rescue LoadError, NameError
128
- false
111
+ def sidekiq_cron_check
112
+ if File.exist?(SIDEKIQ_CRON_SCHEDULE_FILE) && File.read(SIDEKIQ_CRON_SCHEDULE_FILE, mode: "r:UTF-8").include?("BoringBackup::BackupJob")
113
+ check("scheduler detected", :ok, "sidekiq-cron: #{SIDEKIQ_CRON_SCHEDULE_FILE}")
114
+ end
129
115
  end
130
116
 
131
117
  def config
@@ -69,6 +69,7 @@ module BoringBackup
69
69
  store =
70
70
  case store_type.to_sym
71
71
  when :s3 then build_s3_store
72
+ when :r2 then build_r2_store
72
73
  else raise BoringBackup::ConfigurationError, "unknown store type: #{store_type}"
73
74
  end
74
75
 
@@ -100,13 +101,22 @@ module BoringBackup
100
101
  "the :s3 store needs the aws-sdk-s3 gem. Add `gem \"aws-sdk-s3\"` to your Gemfile."
101
102
  end
102
103
 
104
+ def build_r2_store
105
+ require_relative "stores/r2"
106
+
107
+ BoringBackup::Stores::R2.new
108
+ rescue LoadError
109
+ raise BoringBackup::ConfigurationError,
110
+ "the :r2 store needs the aws-sdk-s3 gem. Add `gem \"aws-sdk-s3\"` to your Gemfile."
111
+ end
112
+
103
113
  def pg_env
104
114
  {
105
- "PGHOST" => (pg_host || db_config[:host])&.to_s,
106
- "PGPORT" => (pg_port || db_config[:port])&.to_s,
107
- "PGUSER" => (pg_user || db_config[:username])&.to_s,
108
- "PGPASSWORD" => (pg_password || db_config[:password])&.to_s,
109
- "PGDATABASE" => (pg_database || db_config[:database])&.to_s
115
+ "PGHOST" => (pg_host || db_config[:host] || ENV["PGHOST"])&.to_s,
116
+ "PGPORT" => (pg_port || db_config[:port] || ENV["PGPORT"])&.to_s,
117
+ "PGUSER" => (pg_user || db_config[:username] || ENV["PGUSER"])&.to_s,
118
+ "PGPASSWORD" => (pg_password || db_config[:password] || ENV["PGPASSWORD"])&.to_s,
119
+ "PGDATABASE" => (pg_database || db_config[:database] || ENV["PGDATABASE"])&.to_s
110
120
  }.compact
111
121
  end
112
122
 
@@ -0,0 +1,46 @@
1
+ module BoringBackup
2
+ module Engine
3
+ extend self
4
+
5
+ NAME = "postgresql"
6
+
7
+ def info
8
+ client = client_version
9
+ server = server_version
10
+
11
+ return if client.nil? && server.nil?
12
+
13
+ {name: NAME, client: client, server: server}
14
+ end
15
+
16
+ def client_version
17
+ return unless pg_dump_path
18
+
19
+ version(`#{pg_dump_path} --version`)
20
+ rescue
21
+ nil
22
+ end
23
+
24
+ def server_version
25
+ return unless defined?(::ActiveRecord)
26
+
27
+ version(::ActiveRecord::Base.connection.select_value("SHOW server_version"))
28
+ rescue
29
+ nil
30
+ end
31
+
32
+ def pg_dump_path
33
+ return @pg_dump_path if defined?(@pg_dump_path)
34
+
35
+ @pg_dump_path = ENV.fetch("PATH", "").split(File::PATH_SEPARATOR)
36
+ .map { |dir| File.join(dir, "pg_dump") }
37
+ .find { |path| File.executable?(path) }
38
+ end
39
+
40
+ private
41
+
42
+ def version(string)
43
+ string.to_s[/\d+(?:\.\d+)*/]
44
+ end
45
+ end
46
+ end
@@ -52,11 +52,11 @@ module BoringBackup::Notifiers
52
52
  private
53
53
 
54
54
  def normalized_host
55
- uri = URI.parse(host)
55
+ return host if host.include?("://")
56
56
 
57
- return host if uri.is_a?(URI::HTTP)
57
+ scheme = LOCAL_HOSTS.include?(host.split(":").first) ? "http" : "https"
58
58
 
59
- "#{LOCAL_HOSTS.include?(uri.scheme) ? "http" : "https"}://#{host}"
59
+ "#{scheme}://#{host}"
60
60
  end
61
61
 
62
62
  def post(body)
@@ -85,6 +85,7 @@ module BoringBackup::Notifiers
85
85
  status: result.status,
86
86
  error: result.error&.message,
87
87
  database: BoringBackup.config.pg_env["PGDATABASE"],
88
+ engine: result.engine,
88
89
  bytes: store_results.filter_map(&:bytes).max,
89
90
  duration: store_results.filter_map(&:duration).max&.round(3),
90
91
  stores: store_results.map do |store_result|
@@ -0,0 +1,24 @@
1
+ require "aws-sdk-s3"
2
+ require_relative "s3"
3
+
4
+ module BoringBackup::Stores
5
+ class R2 < S3
6
+ def name = :r2
7
+
8
+ def default_storage_class = :standard
9
+
10
+ def storage_classes = %i[standard standard_ia]
11
+
12
+ def region
13
+ value = super
14
+
15
+ value.to_s.empty? ? "auto" : value
16
+ end
17
+
18
+ def validate!
19
+ super
20
+
21
+ raise BoringBackup::ConfigurationError, "endpoint is not configured" if endpoint.to_s.empty?
22
+ end
23
+ end
24
+ end
@@ -2,33 +2,16 @@ require "aws-sdk-s3"
2
2
 
3
3
  module BoringBackup::Stores
4
4
  class S3 < Store
5
- DEFAULT_STORAGE_CLASS = :standard_ia
6
-
7
- STORAGE_CLASSES = %i[
8
- standard
9
- standard_ia
10
- onezone_ia
11
- intelligent_tiering
12
- glacier_ir
13
- glacier
14
- deep_archive
15
- reduced_redundancy
16
- express_onezone
17
- outposts
18
- snow
19
- ].freeze
20
-
21
- attr_accessor :bucket, :region, :access_key_id, :secret_access_key, :part_size, :thread_count
5
+ attr_accessor :bucket, :region, :access_key_id, :secret_access_key, :part_size, :thread_count, :endpoint
22
6
  attr_writer :storage_class
23
7
 
24
8
  def initialize
25
- @bucket = ENV["AWS_S3_BUCKET"]
26
9
  @region = ENV["AWS_REGION"]
27
10
  @access_key_id = ENV["AWS_ACCESS_KEY_ID"]
28
11
  @secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
29
12
  @part_size = ENV.fetch("BB_S3_PART_SIZE", 8 * 1024 * 1024).to_i
30
13
  @thread_count = ENV.fetch("BB_S3_THREAD_COUNT", 2).to_i
31
- @storage_class = ENV.fetch("BB_S3_STORAGE_CLASS", DEFAULT_STORAGE_CLASS)
14
+ @storage_class = ENV.fetch("BB_S3_STORAGE_CLASS", default_storage_class)
32
15
  end
33
16
 
34
17
  def storage_class
@@ -37,10 +20,28 @@ module BoringBackup::Stores
37
20
  value.empty? ? nil : value.to_sym
38
21
  end
39
22
 
40
- def name = "s3"
23
+ def default_storage_class = :standard_ia
24
+
25
+ def storage_classes
26
+ %i[
27
+ standard
28
+ standard_ia
29
+ onezone_ia
30
+ intelligent_tiering
31
+ glacier_ir
32
+ glacier
33
+ deep_archive
34
+ reduced_redundancy
35
+ express_onezone
36
+ outposts
37
+ snow
38
+ ]
39
+ end
40
+
41
+ def name = :s3
41
42
 
42
43
  def description
43
- [bucket.to_s.empty? ? "no bucket" : "s3://#{bucket}", region].compact.join(" ")
44
+ [bucket.to_s.empty? ? "no bucket" : "#{name}://#{bucket}", region].compact.join(" ")
44
45
  end
45
46
 
46
47
  # Prefer Aws::S3::TransferManager for streaming uploads if available.
@@ -73,13 +74,13 @@ module BoringBackup::Stores
73
74
  raise BoringBackup::DumpTooSmallError, "backup too small: #{BoringBackup.utils.human_size(bytes)} < min_size (#{BoringBackup.utils.human_size(config.min_size)})"
74
75
  end
75
76
 
76
- Result.new(success: true, store: :s3, bytes:, key:, duration:)
77
+ Result.new(success: true, store: name, bytes:, key:, duration:)
77
78
  rescue => e
78
79
  duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
79
80
 
80
81
  cleanup!(key) if upload_attempted
81
82
 
82
- Result.new(success: false, error: e, store: :s3, bytes:, key:, duration:)
83
+ Result.new(success: false, error: e, store: name, bytes:, key:, duration:)
83
84
  end
84
85
 
85
86
  def validate!
@@ -90,9 +91,9 @@ module BoringBackup::Stores
90
91
  "access_key_id and secret_access_key must both be set, or both left blank to use the default AWS credential chain"
91
92
  end
92
93
 
93
- if storage_class && !STORAGE_CLASSES.include?(storage_class)
94
+ if storage_class && !storage_classes.include?(storage_class)
94
95
  raise BoringBackup::ConfigurationError,
95
- "unknown storage class: #{storage_class.inspect} (expected one of: #{STORAGE_CLASSES.map(&:inspect).join(", ")}, or nil to use the bucket default)"
96
+ "unknown storage class: #{storage_class.inspect} (expected one of: #{storage_classes.map(&:inspect).join(", ")}, or nil to use the bucket default)"
96
97
  end
97
98
  end
98
99
 
@@ -116,9 +117,10 @@ module BoringBackup::Stores
116
117
 
117
118
  def s3_config
118
119
  {
119
- region: region,
120
- access_key_id: access_key_id,
121
- secret_access_key: secret_access_key
120
+ endpoint:,
121
+ region:,
122
+ access_key_id:,
123
+ secret_access_key:
122
124
  }.compact
123
125
  end
124
126
  end
@@ -1,7 +1,7 @@
1
1
  module BoringBackup::Stores
2
2
  class Store
3
3
  def name
4
- self.class.name.split("::").last.downcase
4
+ self.class.name.split("::").last.downcase.to_sym
5
5
  end
6
6
 
7
7
  def description
@@ -1,3 +1,3 @@
1
1
  module BoringBackup
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.1"
3
3
  end
data/lib/boring_backup.rb CHANGED
@@ -2,6 +2,7 @@ require_relative "boring_backup/version"
2
2
  require_relative "boring_backup/configuration"
3
3
  require_relative "boring_backup/tee"
4
4
  require_relative "boring_backup/utils"
5
+ require_relative "boring_backup/engine"
5
6
  require_relative "boring_backup/stores"
6
7
  require_relative "boring_backup/commands/backup"
7
8
  require_relative "boring_backup/commands/doctor"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boring-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georgi Mitrev
@@ -85,12 +85,14 @@ files:
85
85
  - lib/boring_backup/commands/backup.rb
86
86
  - lib/boring_backup/commands/doctor.rb
87
87
  - lib/boring_backup/configuration.rb
88
+ - lib/boring_backup/engine.rb
88
89
  - lib/boring_backup/jobs/backup_job.rb
89
90
  - lib/boring_backup/notifiers/sentinel.rb
90
91
  - lib/boring_backup/notifiers/slack.rb
91
92
  - lib/boring_backup/notifiers/stdout.rb
92
93
  - lib/boring_backup/plugins/rails.rb
93
94
  - lib/boring_backup/stores.rb
95
+ - lib/boring_backup/stores/r2.rb
94
96
  - lib/boring_backup/stores/result.rb
95
97
  - lib/boring_backup/stores/s3.rb
96
98
  - lib/boring_backup/stores/store.rb
@@ -120,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
122
  - !ruby/object:Gem::Version
121
123
  version: '0'
122
124
  requirements: []
123
- rubygems_version: 4.0.10
125
+ rubygems_version: 4.0.17
124
126
  specification_version: 4
125
127
  summary: Hassle-free database backups
126
128
  test_files: []