kamal-backup 0.3.0 → 0.3.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.
@@ -91,14 +91,6 @@ module KamalBackup
91
91
  run(['backup'] + host_args + exclude_args(excludes) + paths + tag_args(common_tags + tags + path_tags))
92
92
  end
93
93
 
94
- def backup_path(path, tags:)
95
- backup_paths([path], tags: tags)
96
- end
97
-
98
- def forget_after_success
99
- prune
100
- end
101
-
102
94
  def prune
103
95
  retention_tag_sets.map do |tags|
104
96
  args = ['forget', '--prune', '--group-by', 'host'] + config.retention_args + filter_tag_args(tags)
@@ -150,21 +142,25 @@ module KamalBackup
150
142
  end
151
143
  end
152
144
 
153
- def database_file(snapshot, adapter, database_name: nil)
145
+ # Database dumps from 0.2 and earlier used different layouts. Keep matching
146
+ # them so snapshots taken before 0.3 stay restorable:
147
+ # databases/<app>/<database>/<adapter>.<ext> is current,
148
+ # databases/<app>/<adapter>/... is 0.2 (no database name), and the
149
+ # databases-<app>-... basenames are the earliest flat filenames.
150
+ def database_file(snapshot, adapter, database_name:)
154
151
  legacy_prefix = "databases/#{config.app_name}/#{adapter}/"
155
152
  app = config.app_name.gsub(/[^A-Za-z0-9_.-]+/, '-')
156
153
  database = database_name.to_s.gsub(/[^A-Za-z0-9_.-]+/, '-')
157
- stable_prefix = database.empty? ? nil : "databases/#{app}/#{database}/#{adapter}."
154
+ prefix = "databases/#{app}/#{database}/#{adapter}."
158
155
  flat_prefix = "databases-#{app}-#{adapter}-"
159
- named_flat_prefix = database.empty? ? nil : "databases-#{app}-#{database}-#{adapter}-"
156
+ named_flat_prefix = "databases-#{app}-#{database}-#{adapter}-"
160
157
  ls_json(snapshot).find do |entry|
161
158
  next false unless entry['type'] == 'file'
162
159
 
163
160
  normalized = entry['path'].to_s.sub(%r{\A/+}, '')
164
- (stable_prefix && normalized.start_with?(stable_prefix)) ||
161
+ normalized.start_with?(prefix) ||
165
162
  normalized.start_with?(legacy_prefix) ||
166
- File.basename(normalized).start_with?(flat_prefix) ||
167
- (named_flat_prefix && File.basename(normalized).start_with?(named_flat_prefix))
163
+ File.basename(normalized).start_with?(flat_prefix, named_flat_prefix)
168
164
  end&.fetch('path')
169
165
  end
170
166
 
@@ -208,6 +204,8 @@ module KamalBackup
208
204
  run(['restore', snapshot, '--target', target])
209
205
  end
210
206
 
207
+ private
208
+
211
209
  def run(args, log_output: true)
212
210
  Command.capture(
213
211
  CommandSpec.new(argv: ['restic'] + args, env: restic_env),
@@ -220,7 +218,26 @@ module KamalBackup
220
218
  ['kamal-backup', "app:#{config.app_name}"]
221
219
  end
222
220
 
223
- private
221
+ def host_args
222
+ ['--host', restic_host]
223
+ end
224
+
225
+ def restic_host
226
+ normalize_restic_host([config.app_name, config.accessory_name || 'backup'].compact.join('-'))
227
+ end
228
+
229
+ def normalize_restic_host(value)
230
+ normalized = value.to_s.gsub(/[^A-Za-z0-9_.-]+/, '-').gsub(/\A-+|-+\z/, '')
231
+ normalized.empty? ? 'kamal-backup' : normalized
232
+ end
233
+
234
+ def tag_args(tags)
235
+ tags.compact.each_with_object([]) { |tag, args| args.concat(['--tag', tag]) }
236
+ end
237
+
238
+ def exclude_args(patterns)
239
+ patterns.compact.each_with_object([]) { |pattern, args| args.concat(['--exclude', pattern]) }
240
+ end
224
241
 
225
242
  def retention_tag_sets
226
243
  database_retention_tag_sets + file_retention_tag_sets
@@ -229,8 +246,8 @@ module KamalBackup
229
246
  def database_retention_tag_sets
230
247
  config.databases.group_by(&:database_adapter).flat_map do |adapter, databases|
231
248
  if databases.one?
232
- # Pre-0.3 database snapshots did not include database:<name>, so keep
233
- # the single-database filter broad enough for retention to prune them.
249
+ # Snapshots from 0.2 carry no database:<name> tag, so keep the
250
+ # single-database filter broad enough for retention to prune them too.
234
251
  [common_tags + ['type:database', "adapter:#{adapter}"]]
235
252
  else
236
253
  databases.map do |database|
@@ -248,25 +265,13 @@ module KamalBackup
248
265
  tags.reject { |tag| tag == 'kamal-backup' || tag.start_with?('app:') }.join(', ')
249
266
  end
250
267
 
251
- def tag_args(tags)
252
- tags.compact.each_with_object([]) { |tag, args| args.concat(['--tag', tag]) }
253
- end
254
-
255
- def exclude_args(patterns)
256
- patterns.compact.each_with_object([]) { |pattern, args| args.concat(['--exclude', pattern]) }
257
- end
258
-
259
- def host_args
260
- ['--host', restic_host]
261
- end
262
-
263
- def restic_host
264
- normalize_restic_host([config.app_name, config.accessory_name || 'backup'].compact.join('-'))
265
- end
266
-
267
- def normalize_restic_host(value)
268
- normalized = value.to_s.gsub(/[^A-Za-z0-9_.-]+/, '-').gsub(/\A-+|-+\z/, '')
269
- normalized.empty? ? 'kamal-backup' : normalized
268
+ def write_last_check(payload)
269
+ FileUtils.mkdir_p(config.state_dir)
270
+ File.write(config.last_check_path, JSON.pretty_generate(payload.transform_values do |value|
271
+ value.respond_to?(:iso8601) ? value.iso8601 : redactor.redact_string(value.to_s)
272
+ end))
273
+ rescue SystemCallError
274
+ nil
270
275
  end
271
276
 
272
277
  def filter_tag_args(tags)
@@ -360,15 +365,6 @@ module KamalBackup
360
365
  )
361
366
  end
362
367
 
363
- def write_last_check(payload)
364
- FileUtils.mkdir_p(config.state_dir)
365
- File.write(config.last_check_path, JSON.pretty_generate(payload.transform_values do |value|
366
- value.respond_to?(:iso8601) ? value.iso8601 : redactor.redact_string(value.to_s)
367
- end))
368
- rescue SystemCallError
369
- nil
370
- end
371
-
372
368
  def log(message)
373
369
  if Command.output
374
370
  Command.output.info(message, redactor: redactor)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KamalBackup
4
- VERSION = '0.3.0'
4
+ VERSION = '0.3.1'
5
5
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KamalBackup
4
+ # Kamal and Rails YAML configs mix string and symbol keys depending on how
5
+ # they were rendered, so look up both.
6
+ module YamlAccess
7
+ private
8
+
9
+ def fetch(hash, key)
10
+ hash[key] || hash[key.to_s] || hash[key.to_sym]
11
+ end
12
+ end
13
+ end
data/lib/kamal_backup.rb CHANGED
@@ -3,8 +3,11 @@
3
3
  require_relative 'kamal_backup/version'
4
4
  require_relative 'kamal_backup/schema'
5
5
  require_relative 'kamal_backup/errors'
6
+ require_relative 'kamal_backup/yaml_access'
6
7
  require_relative 'kamal_backup/command'
8
+ require_relative 'kamal_backup/command_output'
7
9
  require_relative 'kamal_backup/redactor'
10
+ require_relative 'kamal_backup/config_file'
8
11
  require_relative 'kamal_backup/config'
9
12
  require_relative 'kamal_backup/rails_app'
10
13
  require_relative 'kamal_backup/kamal_bridge'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kamal-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - crmne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-06 00:00:00.000000000 Z
11
+ date: 2026-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -26,6 +26,20 @@ dependencies:
26
26
  version: '1.5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-mock
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -66,6 +80,34 @@ dependencies:
66
80
  - - ">="
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov-cobertura
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
69
111
  description: Back up PostgreSQL, MySQL, SQLite, and file-backed Active Storage files
70
112
  into restic on a schedule, then run restore drills and produce review evidence.
71
113
  email:
@@ -80,8 +122,11 @@ files:
80
122
  - lib/kamal_backup.rb
81
123
  - lib/kamal_backup/app.rb
82
124
  - lib/kamal_backup/cli.rb
125
+ - lib/kamal_backup/cli/helpers.rb
83
126
  - lib/kamal_backup/command.rb
127
+ - lib/kamal_backup/command_output.rb
84
128
  - lib/kamal_backup/config.rb
129
+ - lib/kamal_backup/config_file.rb
85
130
  - lib/kamal_backup/databases/base.rb
86
131
  - lib/kamal_backup/databases/mysql.rb
87
132
  - lib/kamal_backup/databases/postgres.rb
@@ -95,6 +140,7 @@ files:
95
140
  - lib/kamal_backup/scheduler.rb
96
141
  - lib/kamal_backup/schema.rb
97
142
  - lib/kamal_backup/version.rb
143
+ - lib/kamal_backup/yaml_access.rb
98
144
  homepage: https://github.com/crmne/kamal-backup
99
145
  licenses:
100
146
  - MIT