kamal-backup 0.3.0.beta18 → 0.3.0.beta20
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/lib/kamal_backup/app.rb +6 -0
- data/lib/kamal_backup/cli.rb +20 -1
- data/lib/kamal_backup/redactor.rb +2 -1
- data/lib/kamal_backup/restic.rb +55 -84
- data/lib/kamal_backup/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85d6ee6e04e1765b7dcb8666deb42f63b151437a57bccfbc79c18c94b53e0af7
|
|
4
|
+
data.tar.gz: 6d456b02c442c3316eb48f990d2b486410ed1541fd363abd4bed62e3d6018e68
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b7bd98124f478da8b1ee5bea4382880f924ab44dd9cb2325dd86d862dfb8fdcaf8f851fbaaf4003ba205243e81bcbdb7bbec928571655239964495e8753731b
|
|
7
|
+
data.tar.gz: a70c403b8b36a474339bd30ee84b8ae22fd11ff60b42aca6459abafc4fb43d3cb431ba1ab9f477f66326c0133d760043c7d35b28fca831451dfa705197132de3
|
data/lib/kamal_backup/app.rb
CHANGED
data/lib/kamal_backup/cli.rb
CHANGED
|
@@ -158,6 +158,17 @@ module KamalBackup
|
|
|
158
158
|
end
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
+
def print_prune_result(results)
|
|
162
|
+
output = Array(results).map(&:stdout).join
|
|
163
|
+
|
|
164
|
+
if output.empty?
|
|
165
|
+
puts("Prune completed")
|
|
166
|
+
else
|
|
167
|
+
print(output)
|
|
168
|
+
puts unless output.end_with?("\n")
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
161
172
|
def validate_deploy_config
|
|
162
173
|
config = Config.new(
|
|
163
174
|
env: bridge.accessory_environment(accessory_name: accessory_name),
|
|
@@ -473,6 +484,15 @@ module KamalBackup
|
|
|
473
484
|
end
|
|
474
485
|
end
|
|
475
486
|
|
|
487
|
+
desc "prune", "Apply the configured restic retention policy and prune unneeded data"
|
|
488
|
+
def prune
|
|
489
|
+
if remote_command_mode?
|
|
490
|
+
exec_remote(["kamal-backup", "prune"])
|
|
491
|
+
else
|
|
492
|
+
print_prune_result(direct_app.prune)
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
|
|
476
496
|
desc "evidence", "Print redacted backup, check, and restore-drill evidence as JSON"
|
|
477
497
|
def evidence
|
|
478
498
|
if remote_command_mode?
|
|
@@ -503,7 +523,6 @@ module KamalBackup
|
|
|
503
523
|
puts deploy_snippet
|
|
504
524
|
puts
|
|
505
525
|
puts "The accessory runs scheduled database and file backups with backup.schedule."
|
|
506
|
-
puts "kamal-backup passes RESTIC_PASSWORD to restic through a private temporary password file."
|
|
507
526
|
puts "For most Rails apps, restore local and drill local can infer the development database, Active Storage path, and tmp state directory."
|
|
508
527
|
puts "Local restore and drill also require the restic binary on your machine."
|
|
509
528
|
puts "Create config/kamal-backup.local.yml only if you need to override those local defaults."
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module KamalBackup
|
|
2
2
|
class Redactor
|
|
3
3
|
SECRET_KEY_PATTERN = /(pass|password|secret|token|key|credential|authorization)/i
|
|
4
|
+
SENSITIVE_KEY_PATTERN = /(?:pass|password|secret|token|key|credential|authorization)|\A(?:user|username|pguser|.*_user|.*_username)\z/i
|
|
4
5
|
REDACTED = "[REDACTED]"
|
|
5
6
|
|
|
6
7
|
def initialize(secret_values: [], env: ENV)
|
|
@@ -16,7 +17,7 @@ module KamalBackup
|
|
|
16
17
|
|
|
17
18
|
def redact_value(key, value)
|
|
18
19
|
return nil if value.nil?
|
|
19
|
-
return REDACTED if key.to_s.match?(
|
|
20
|
+
return REDACTED if key.to_s.match?(SENSITIVE_KEY_PATTERN)
|
|
20
21
|
|
|
21
22
|
redact_string(value.to_s)
|
|
22
23
|
end
|
data/lib/kamal_backup/restic.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
require "fileutils"
|
|
2
2
|
require "json"
|
|
3
3
|
require "open3"
|
|
4
|
-
require "tempfile"
|
|
5
4
|
require "time"
|
|
6
5
|
require_relative "command"
|
|
7
6
|
|
|
@@ -28,49 +27,43 @@ module KamalBackup
|
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
def backup_stream(command, filename:, tags:)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
pipe_commands(command, restic_command, producer_label: "dump", consumer_label: "restic backup")
|
|
38
|
-
end
|
|
30
|
+
restic_command = CommandSpec.new(
|
|
31
|
+
argv: ["restic", "backup"] + host_args + ["--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
|
|
32
|
+
env: restic_env
|
|
33
|
+
)
|
|
34
|
+
log("backing up stream as #{filename}")
|
|
35
|
+
pipe_commands(command, restic_command, producer_label: "dump", consumer_label: "restic backup")
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
def backup_file(path, filename:, tags:)
|
|
42
|
-
command =
|
|
39
|
+
command = CommandSpec.new(
|
|
40
|
+
argv: ["restic", "backup"] + host_args + ["--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
|
|
41
|
+
env: restic_env
|
|
42
|
+
)
|
|
43
|
+
log("backing up file content as #{filename}")
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
copy_error = nil
|
|
58
|
-
begin
|
|
59
|
-
IO.copy_stream(file, stdin)
|
|
60
|
-
rescue Errno::EPIPE => e
|
|
61
|
-
copy_error = e
|
|
62
|
-
ensure
|
|
63
|
-
stdin.close unless stdin.closed?
|
|
64
|
-
end
|
|
65
|
-
out = stdout_reader.value
|
|
66
|
-
err = stderr_reader.value
|
|
67
|
-
status = wait_thread.value
|
|
68
|
-
output&.command_exit(context, status.exitstatus)
|
|
69
|
-
raise_command_error(command, status, out, err) unless status.success?
|
|
70
|
-
raise_stream_error(command, copy_error, out, err) if copy_error
|
|
71
|
-
|
|
72
|
-
CommandResult.new(stdout: out, stderr: err, status: status.exitstatus)
|
|
45
|
+
File.open(path, "rb") do |file|
|
|
46
|
+
output = Command.output
|
|
47
|
+
context = output&.command_start(command, redactor: redactor)
|
|
48
|
+
Open3.popen3(command.env, *command.argv) do |stdin, stdout, stderr, wait_thread|
|
|
49
|
+
stdout_reader = Thread.new { Command.collect_stream(stdout, command_output: output, context: context, stream: :stdout, redactor: redactor) }
|
|
50
|
+
stderr_reader = Thread.new { Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr, redactor: redactor) }
|
|
51
|
+
copy_error = nil
|
|
52
|
+
begin
|
|
53
|
+
IO.copy_stream(file, stdin)
|
|
54
|
+
rescue Errno::EPIPE => e
|
|
55
|
+
copy_error = e
|
|
56
|
+
ensure
|
|
57
|
+
stdin.close unless stdin.closed?
|
|
73
58
|
end
|
|
59
|
+
out = stdout_reader.value
|
|
60
|
+
err = stderr_reader.value
|
|
61
|
+
status = wait_thread.value
|
|
62
|
+
output&.command_exit(context, status.exitstatus)
|
|
63
|
+
raise_command_error(command, status, out, err) unless status.success?
|
|
64
|
+
raise_stream_error(command, copy_error, out, err) if copy_error
|
|
65
|
+
|
|
66
|
+
CommandResult.new(stdout: out, stderr: err, status: status.exitstatus)
|
|
74
67
|
end
|
|
75
68
|
end
|
|
76
69
|
rescue Errno::ENOENT => e
|
|
@@ -92,7 +85,11 @@ module KamalBackup
|
|
|
92
85
|
end
|
|
93
86
|
|
|
94
87
|
def forget_after_success
|
|
95
|
-
|
|
88
|
+
prune
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def prune
|
|
92
|
+
retention_tag_sets.map do |tags|
|
|
96
93
|
args = ["forget", "--prune", "--group-by", "host"] + config.retention_args + filter_tag_args(tags)
|
|
97
94
|
log("running restic forget/prune with retention policy for #{retention_scope(tags)}")
|
|
98
95
|
run(args)
|
|
@@ -160,31 +157,26 @@ module KamalBackup
|
|
|
160
157
|
end
|
|
161
158
|
|
|
162
159
|
def pipe_dump_to_command(snapshot, filename, command)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
pipe_commands(restic_command, command, producer_label: "restic dump", consumer_label: command.argv.first)
|
|
166
|
-
end
|
|
160
|
+
restic_command = CommandSpec.new(argv: ["restic", "dump", snapshot, filename], env: restic_env)
|
|
161
|
+
pipe_commands(restic_command, command, producer_label: "restic dump", consumer_label: command.argv.first)
|
|
167
162
|
end
|
|
168
163
|
|
|
169
164
|
def write_dump_to_path(snapshot, filename, target_path)
|
|
170
|
-
command =
|
|
165
|
+
command = CommandSpec.new(argv: ["restic", "dump", snapshot, filename], env: restic_env)
|
|
171
166
|
target_path = File.expand_path(target_path)
|
|
172
167
|
FileUtils.mkdir_p(File.dirname(target_path))
|
|
173
168
|
temp_path = "#{target_path}.kamal-backup-#{$$}.tmp"
|
|
174
169
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
output&.command_exit(context, status.exitstatus)
|
|
186
|
-
raise_command_error(command, status, "", err) unless status.success?
|
|
187
|
-
end
|
|
170
|
+
output = Command.output
|
|
171
|
+
context = output&.command_start(command, redactor: redactor)
|
|
172
|
+
Open3.popen3(command.env, *command.argv) do |stdin, stdout, stderr, wait_thread|
|
|
173
|
+
stdin.close
|
|
174
|
+
stderr_reader = Thread.new { Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr, redactor: redactor) }
|
|
175
|
+
File.open(temp_path, "wb") { |file| IO.copy_stream(stdout, file) }
|
|
176
|
+
err = stderr_reader.value
|
|
177
|
+
status = wait_thread.value
|
|
178
|
+
output&.command_exit(context, status.exitstatus)
|
|
179
|
+
raise_command_error(command, status, "", err) unless status.success?
|
|
188
180
|
end
|
|
189
181
|
File.rename(temp_path, target_path)
|
|
190
182
|
target_path
|
|
@@ -202,13 +194,11 @@ module KamalBackup
|
|
|
202
194
|
end
|
|
203
195
|
|
|
204
196
|
def run(args, log_output: true)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
)
|
|
211
|
-
end
|
|
197
|
+
Command.capture(
|
|
198
|
+
CommandSpec.new(argv: ["restic"] + args, env: restic_env),
|
|
199
|
+
redactor: redactor,
|
|
200
|
+
log_output: log_output
|
|
201
|
+
)
|
|
212
202
|
end
|
|
213
203
|
|
|
214
204
|
def common_tags
|
|
@@ -270,25 +260,6 @@ module KamalBackup
|
|
|
270
260
|
end
|
|
271
261
|
end
|
|
272
262
|
|
|
273
|
-
def with_restic_env
|
|
274
|
-
env = restic_env
|
|
275
|
-
password_file = nil
|
|
276
|
-
|
|
277
|
-
if env["RESTIC_PASSWORD_FILE"] || env["RESTIC_PASSWORD_COMMAND"]
|
|
278
|
-
env.delete("RESTIC_PASSWORD")
|
|
279
|
-
elsif env["RESTIC_PASSWORD"]
|
|
280
|
-
password_file = Tempfile.new("kamal-backup-restic-password")
|
|
281
|
-
password_file.write(env.delete("RESTIC_PASSWORD"))
|
|
282
|
-
password_file.flush
|
|
283
|
-
File.chmod(0o600, password_file.path)
|
|
284
|
-
env["RESTIC_PASSWORD_FILE"] = password_file.path
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
yield env
|
|
288
|
-
ensure
|
|
289
|
-
password_file&.close!
|
|
290
|
-
end
|
|
291
|
-
|
|
292
263
|
def pipe_commands(producer, consumer, producer_label:, consumer_label:)
|
|
293
264
|
output = Command.output
|
|
294
265
|
producer_context = output&.command_start(producer, redactor: redactor)
|
data/lib/kamal_backup/version.rb
CHANGED