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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7b7f6a104e974d738b46ed67b5ece749627d8d33737845f9e9e7ea0e921ce21
4
- data.tar.gz: e95a651804aa74ec1146989f0db3428e4c8510dc247ef07383e8b0f3d0168f43
3
+ metadata.gz: 85d6ee6e04e1765b7dcb8666deb42f63b151437a57bccfbc79c18c94b53e0af7
4
+ data.tar.gz: 6d456b02c442c3316eb48f990d2b486410ed1541fd363abd4bed62e3d6018e68
5
5
  SHA512:
6
- metadata.gz: 447bbd9c378d92aef836e0f05cf751ef9b978121b9e3ff5d8675b943441f028aaa082809e9adf462d3380e6035e35dfd52cd74cac68c0ac6fef099632ff89429
7
- data.tar.gz: 7f414a27f4c452371b9c88fb91ac7aece2bd9771d92ae835997487fa252aeb2f053955cce3a76741be12cd4f6fe31c7c206a2d2ccbdf356a595f34e5d007986b
6
+ metadata.gz: 9b7bd98124f478da8b1ee5bea4382880f924ab44dd9cb2325dd86d862dfb8fdcaf8f851fbaaf4003ba205243e81bcbdb7bbec928571655239964495e8753731b
7
+ data.tar.gz: a70c403b8b36a474339bd30ee84b8ae22fd11ff60b42aca6459abafc4fb43d3cb431ba1ab9f477f66326c0133d760043c7d35b28fca831451dfa705197132de3
@@ -130,6 +130,12 @@ module KamalBackup
130
130
  restic.check.stdout
131
131
  end
132
132
 
133
+ def prune
134
+ config.validate_backup(check_files: false)
135
+ require_restic!
136
+ restic.prune
137
+ end
138
+
133
139
  def evidence
134
140
  config.validate_restic
135
141
  require_restic!
@@ -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?(SECRET_KEY_PATTERN)
20
+ return REDACTED if key.to_s.match?(SENSITIVE_KEY_PATTERN)
20
21
 
21
22
  redact_string(value.to_s)
22
23
  end
@@ -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
- with_restic_env do |env|
32
- restic_command = CommandSpec.new(
33
- argv: ["restic", "backup"] + host_args + ["--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
34
- env: env
35
- )
36
- log("backing up stream as #{filename}")
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 = nil
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
- with_restic_env do |env|
45
- command = CommandSpec.new(
46
- argv: ["restic", "backup"] + host_args + ["--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
47
- env: env
48
- )
49
- log("backing up file content as #{filename}")
50
-
51
- File.open(path, "rb") do |file|
52
- output = Command.output
53
- context = output&.command_start(command, redactor: redactor)
54
- Open3.popen3(command.env, *command.argv) do |stdin, stdout, stderr, wait_thread|
55
- stdout_reader = Thread.new { Command.collect_stream(stdout, command_output: output, context: context, stream: :stdout, redactor: redactor) }
56
- stderr_reader = Thread.new { Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr, redactor: redactor) }
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
- retention_tag_sets.each do |tags|
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
- with_restic_env do |env|
164
- restic_command = CommandSpec.new(argv: ["restic", "dump", snapshot, filename], env: env)
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 = nil
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
- with_restic_env do |env|
176
- command = CommandSpec.new(argv: ["restic", "dump", snapshot, filename], env: env)
177
- output = Command.output
178
- context = output&.command_start(command, redactor: redactor)
179
- Open3.popen3(command.env, *command.argv) do |stdin, stdout, stderr, wait_thread|
180
- stdin.close
181
- stderr_reader = Thread.new { Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr, redactor: redactor) }
182
- File.open(temp_path, "wb") { |file| IO.copy_stream(stdout, file) }
183
- err = stderr_reader.value
184
- status = wait_thread.value
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
- with_restic_env do |env|
206
- Command.capture(
207
- CommandSpec.new(argv: ["restic"] + args, env: env),
208
- redactor: redactor,
209
- log_output: log_output
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)
@@ -1,3 +1,3 @@
1
1
  module KamalBackup
2
- VERSION = "0.3.0.beta18"
2
+ VERSION = "0.3.0.beta20"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kamal-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0.beta18
4
+ version: 0.3.0.beta20
5
5
  platform: ruby
6
6
  authors:
7
7
  - crmne