kamal-backup 0.3.0 → 0.4.0

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)
@@ -107,6 +99,11 @@ module KamalBackup
107
99
  end
108
100
  end
109
101
 
102
+ def unlock
103
+ log('clearing stale restic locks')
104
+ run(%w[unlock])
105
+ end
106
+
110
107
  def check
111
108
  args = %w[check]
112
109
  args.concat(['--read-data-subset', config.check_read_data_subset]) if config.check_read_data_subset
@@ -150,21 +147,25 @@ module KamalBackup
150
147
  end
151
148
  end
152
149
 
153
- def database_file(snapshot, adapter, database_name: nil)
150
+ # Database dumps from 0.2 and earlier used different layouts. Keep matching
151
+ # them so snapshots taken before 0.3 stay restorable:
152
+ # databases/<app>/<database>/<adapter>.<ext> is current,
153
+ # databases/<app>/<adapter>/... is 0.2 (no database name), and the
154
+ # databases-<app>-... basenames are the earliest flat filenames.
155
+ def database_file(snapshot, adapter, database_name:)
154
156
  legacy_prefix = "databases/#{config.app_name}/#{adapter}/"
155
157
  app = config.app_name.gsub(/[^A-Za-z0-9_.-]+/, '-')
156
158
  database = database_name.to_s.gsub(/[^A-Za-z0-9_.-]+/, '-')
157
- stable_prefix = database.empty? ? nil : "databases/#{app}/#{database}/#{adapter}."
159
+ prefix = "databases/#{app}/#{database}/#{adapter}."
158
160
  flat_prefix = "databases-#{app}-#{adapter}-"
159
- named_flat_prefix = database.empty? ? nil : "databases-#{app}-#{database}-#{adapter}-"
161
+ named_flat_prefix = "databases-#{app}-#{database}-#{adapter}-"
160
162
  ls_json(snapshot).find do |entry|
161
163
  next false unless entry['type'] == 'file'
162
164
 
163
165
  normalized = entry['path'].to_s.sub(%r{\A/+}, '')
164
- (stable_prefix && normalized.start_with?(stable_prefix)) ||
166
+ normalized.start_with?(prefix) ||
165
167
  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))
168
+ File.basename(normalized).start_with?(flat_prefix, named_flat_prefix)
168
169
  end&.fetch('path')
169
170
  end
170
171
 
@@ -208,6 +209,8 @@ module KamalBackup
208
209
  run(['restore', snapshot, '--target', target])
209
210
  end
210
211
 
212
+ private
213
+
211
214
  def run(args, log_output: true)
212
215
  Command.capture(
213
216
  CommandSpec.new(argv: ['restic'] + args, env: restic_env),
@@ -220,7 +223,26 @@ module KamalBackup
220
223
  ['kamal-backup', "app:#{config.app_name}"]
221
224
  end
222
225
 
223
- private
226
+ def host_args
227
+ ['--host', restic_host]
228
+ end
229
+
230
+ def restic_host
231
+ normalize_restic_host([config.app_name, config.accessory_name || 'backup'].compact.join('-'))
232
+ end
233
+
234
+ def normalize_restic_host(value)
235
+ normalized = value.to_s.gsub(/[^A-Za-z0-9_.-]+/, '-').gsub(/\A-+|-+\z/, '')
236
+ normalized.empty? ? 'kamal-backup' : normalized
237
+ end
238
+
239
+ def tag_args(tags)
240
+ tags.compact.each_with_object([]) { |tag, args| args.concat(['--tag', tag]) }
241
+ end
242
+
243
+ def exclude_args(patterns)
244
+ patterns.compact.each_with_object([]) { |pattern, args| args.concat(['--exclude', pattern]) }
245
+ end
224
246
 
225
247
  def retention_tag_sets
226
248
  database_retention_tag_sets + file_retention_tag_sets
@@ -229,8 +251,8 @@ module KamalBackup
229
251
  def database_retention_tag_sets
230
252
  config.databases.group_by(&:database_adapter).flat_map do |adapter, databases|
231
253
  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.
254
+ # Snapshots from 0.2 carry no database:<name> tag, so keep the
255
+ # single-database filter broad enough for retention to prune them too.
234
256
  [common_tags + ['type:database', "adapter:#{adapter}"]]
235
257
  else
236
258
  databases.map do |database|
@@ -248,25 +270,13 @@ module KamalBackup
248
270
  tags.reject { |tag| tag == 'kamal-backup' || tag.start_with?('app:') }.join(', ')
249
271
  end
250
272
 
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
273
+ def write_last_check(payload)
274
+ FileUtils.mkdir_p(config.state_dir)
275
+ File.write(config.last_check_path, JSON.pretty_generate(payload.transform_values do |value|
276
+ value.respond_to?(:iso8601) ? value.iso8601 : redactor.redact_string(value.to_s)
277
+ end))
278
+ rescue SystemCallError
279
+ nil
270
280
  end
271
281
 
272
282
  def filter_tag_args(tags)
@@ -360,15 +370,6 @@ module KamalBackup
360
370
  )
361
371
  end
362
372
 
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
373
  def log(message)
373
374
  if Command.output
374
375
  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.4.0'
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.4.0
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-21 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