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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/lib/kamal_backup/app.rb +145 -202
- data/lib/kamal_backup/cli/helpers.rb +298 -0
- data/lib/kamal_backup/cli.rb +4 -294
- data/lib/kamal_backup/command.rb +0 -185
- 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 +41 -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)
|
|
@@ -150,21 +142,25 @@ module KamalBackup
|
|
|
150
142
|
end
|
|
151
143
|
end
|
|
152
144
|
|
|
153
|
-
|
|
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
|
-
|
|
154
|
+
prefix = "databases/#{app}/#{database}/#{adapter}."
|
|
158
155
|
flat_prefix = "databases-#{app}-#{adapter}-"
|
|
159
|
-
named_flat_prefix =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
233
|
-
#
|
|
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
|
|
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
|
|
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)
|
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.3.
|
|
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-
|
|
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
|