boring-backup 0.6.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: 851d7814c77a411800884379a85ae1feeffb5c8183d3b524ab3851f1fc7d1dde
4
- data.tar.gz: e54bf1a64c28d237753b4eaa5c4bd7114c067a6b6f957b73ad4526f5332fc393
3
+ metadata.gz: 343fecddfbccfc03c5216673a667596b7e6af0ccd4e7edc6c03e864d80f7727b
4
+ data.tar.gz: cff420aa2b49fcae03d06d03c95d6f5e4d5813b112e66464b1829ef4c069ec0b
5
5
  SHA512:
6
- metadata.gz: 682eb6a513c065b1078ca528bebb9ea5cbc83924a873d47965e5ca463c8d94210a8f6e020242e45ea6fb8817e8c23e8c51a999070c1d5f2c5c63cf0b9ecc605a
7
- data.tar.gz: 7afd54ae737169eb4f44d4bfd575acd64b13177b8c1d77d33260d217742934cbe053822cd5ebf4a64aa00a92cbfa16751553042f8a4ff1e7da6861f245f99045
6
+ metadata.gz: 2f9c315742cb0c19493947d542e7657af569eb1ddc78890839cf239300276707e3b060ff9d02161f3535c5e687aa8facf14d684c859cb14d2d7187af69344a78
7
+ data.tar.gz: 1dbff91a051d57ab40703c9e760c435789a05c8b671742b795714847dd5385ac4308a803b6f4c36e6fa99f4d7cce5e63a4404ce9fbc56090d028f454c6181016
data/CHANGELOG.md CHANGED
@@ -1,10 +1,16 @@
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
+
3
9
  ## [0.6.0] - 2026-07-23
4
10
 
5
11
  - `bb doctor` - improved scheduler checks (now supports `sidekiq-crosidekiq-cron`)
6
- - support `PG*` env vars
7
- - support for Cloudflare R2 using their S3-compatible API
12
+ - Support `PG*` env vars
13
+ - Support for Cloudflare R2 using their S3-compatible API
8
14
 
9
15
  ## [0.5.0] - 2026-07-14
10
16
 
data/README.md CHANGED
@@ -72,7 +72,7 @@ anything to disk. Use any scheduler or even cron to run it periodically.
72
72
  All configuration options can be edited in the initializer:
73
73
 
74
74
  ```rb
75
- # config/initializers/boring-backup.rb
75
+ # config/initializers/boring_backup.rb
76
76
 
77
77
  BoringBackup.configure do |config|
78
78
  config.sentinel_key = '12345678-abcd-1234-abcd-abcdef123456'
@@ -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]
@@ -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
@@ -43,39 +43,27 @@ module BoringBackup::Commands
43
43
  end
44
44
 
45
45
  def pg_dump_path
46
- return @pg_dump_path if defined?(@pg_dump_path)
47
-
48
- @pg_dump_path = ENV.fetch("PATH", "").split(File::PATH_SEPARATOR)
49
- .map { |dir| File.join(dir, "pg_dump") }
50
- .find { |path| File.executable?(path) }
46
+ BoringBackup::Engine.pg_dump_path
51
47
  end
52
48
 
53
49
  def pg_dump_version
54
50
  return unless pg_dump_path
55
51
 
56
- client = major_version(`#{pg_dump_path} --version`)
57
- server = major_version(server_version)
52
+ client = BoringBackup::Engine.client_version
53
+ server = BoringBackup::Engine.server_version
58
54
 
59
55
  return check("versions", :skip, "could not read server version") unless server
60
56
  return check("versions", :skip, "could not read pg_dump version") unless client
61
57
 
62
- if client >= server
58
+ if major(client) >= major(server)
63
59
  check("versions", :ok, "pg_dump #{client}, server #{server}")
64
60
  else
65
61
  check("versions", :fail, "pg_dump #{client} is older than server #{server} — the dump may not restore")
66
62
  end
67
63
  end
68
64
 
69
- def major_version(string)
70
- string.to_s[/(\d+)/, 1]&.to_i
71
- end
72
-
73
- def server_version
74
- return unless defined?(::ActiveRecord)
75
-
76
- ::ActiveRecord::Base.connection.select_value("SHOW server_version")
77
- rescue
78
- nil
65
+ def major(version)
66
+ Gem::Version.new(version).segments.first
79
67
  end
80
68
 
81
69
  def database_resolved
@@ -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
@@ -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|
@@ -5,6 +5,10 @@ module BoringBackup::Stores
5
5
  class R2 < S3
6
6
  def name = :r2
7
7
 
8
+ def default_storage_class = :standard
9
+
10
+ def storage_classes = %i[standard standard_ia]
11
+
8
12
  def region
9
13
  value = super
10
14
 
@@ -2,22 +2,6 @@ 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
5
  attr_accessor :bucket, :region, :access_key_id, :secret_access_key, :part_size, :thread_count, :endpoint
22
6
  attr_writer :storage_class
23
7
 
@@ -27,7 +11,7 @@ module BoringBackup::Stores
27
11
  @secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
28
12
  @part_size = ENV.fetch("BB_S3_PART_SIZE", 8 * 1024 * 1024).to_i
29
13
  @thread_count = ENV.fetch("BB_S3_THREAD_COUNT", 2).to_i
30
- @storage_class = ENV.fetch("BB_S3_STORAGE_CLASS", DEFAULT_STORAGE_CLASS)
14
+ @storage_class = ENV.fetch("BB_S3_STORAGE_CLASS", default_storage_class)
31
15
  end
32
16
 
33
17
  def storage_class
@@ -36,6 +20,24 @@ module BoringBackup::Stores
36
20
  value.empty? ? nil : value.to_sym
37
21
  end
38
22
 
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
+
39
41
  def name = :s3
40
42
 
41
43
  def description
@@ -89,9 +91,9 @@ module BoringBackup::Stores
89
91
  "access_key_id and secret_access_key must both be set, or both left blank to use the default AWS credential chain"
90
92
  end
91
93
 
92
- if storage_class && !STORAGE_CLASSES.include?(storage_class)
94
+ if storage_class && !storage_classes.include?(storage_class)
93
95
  raise BoringBackup::ConfigurationError,
94
- "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)"
95
97
  end
96
98
  end
97
99
 
@@ -1,3 +1,3 @@
1
1
  module BoringBackup
2
- VERSION = "0.6.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.6.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Georgi Mitrev
@@ -85,6 +85,7 @@ 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
@@ -121,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
122
  - !ruby/object:Gem::Version
122
123
  version: '0'
123
124
  requirements: []
124
- rubygems_version: 4.0.10
125
+ rubygems_version: 4.0.17
125
126
  specification_version: 4
126
127
  summary: Hassle-free database backups
127
128
  test_files: []