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.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/lib/kamal_backup/app.rb +150 -202
- data/lib/kamal_backup/cli/helpers.rb +298 -0
- data/lib/kamal_backup/cli.rb +13 -294
- data/lib/kamal_backup/command.rb +11 -187
- data/lib/kamal_backup/command_output.rb +189 -0
- data/lib/kamal_backup/config.rb +77 -481
- data/lib/kamal_backup/config_file.rb +376 -0
- data/lib/kamal_backup/databases/base.rb +11 -0
- data/lib/kamal_backup/databases/postgres.rb +3 -3
- data/lib/kamal_backup/evidence.rb +0 -3
- data/lib/kamal_backup/kamal_bridge.rb +39 -27
- data/lib/kamal_backup/rails_app.rb +6 -17
- data/lib/kamal_backup/restic.rb +46 -45
- data/lib/kamal_backup/version.rb +1 -1
- data/lib/kamal_backup/yaml_access.rb +13 -0
- data/lib/kamal_backup.rb +3 -0
- metadata +48 -2
data/lib/kamal_backup/restic.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
159
|
+
prefix = "databases/#{app}/#{database}/#{adapter}."
|
|
158
160
|
flat_prefix = "databases-#{app}-#{adapter}-"
|
|
159
|
-
named_flat_prefix =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
233
|
-
#
|
|
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
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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)
|
data/lib/kamal_backup/version.rb
CHANGED
|
@@ -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.
|
|
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-
|
|
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
|