kamal-backup 0.3.0.beta20 → 0.3.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.
@@ -1,8 +1,11 @@
1
- require "fileutils"
2
- require "json"
3
- require "open3"
4
- require "time"
5
- require_relative "command"
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'fileutils'
5
+ require 'json'
6
+ require 'open3'
7
+ require 'time'
8
+ require_relative 'command'
6
9
 
7
10
  module KamalBackup
8
11
  class Restic
@@ -18,36 +21,42 @@ module KamalBackup
18
21
  def ensure_repository
19
22
  run(%w[snapshots --json], log_output: false)
20
23
  rescue CommandError => e
21
- if config.restic_init_if_missing?
22
- log("restic repository not ready, running restic init")
23
- run(%w[init])
24
- else
25
- raise e
26
- end
24
+ raise e unless config.restic_init_if_missing?
25
+
26
+ log('restic repository not ready, running restic init')
27
+ run(%w[init])
27
28
  end
28
29
 
29
30
  def backup_stream(command, filename:, tags:)
30
31
  restic_command = CommandSpec.new(
31
- argv: ["restic", "backup"] + host_args + ["--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
32
+ argv: %w[restic
33
+ backup] + host_args + ['--stdin', '--stdin-filename', filename] + tag_args(common_tags + tags),
32
34
  env: restic_env
33
35
  )
34
36
  log("backing up stream as #{filename}")
35
- pipe_commands(command, restic_command, producer_label: "dump", consumer_label: "restic backup")
37
+ pipe_commands(command, restic_command, producer_label: 'dump', consumer_label: 'restic backup')
36
38
  end
37
39
 
38
40
  def backup_file(path, filename:, tags:)
39
41
  command = CommandSpec.new(
40
- argv: ["restic", "backup"] + host_args + ["--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
42
+ argv: %w[restic
43
+ backup] + host_args + ['--stdin', '--stdin-filename', filename] + tag_args(common_tags + tags),
41
44
  env: restic_env
42
45
  )
43
46
  log("backing up file content as #{filename}")
44
47
 
45
- File.open(path, "rb") do |file|
48
+ File.open(path, 'rb') do |file|
46
49
  output = Command.output
47
50
  context = output&.command_start(command, redactor: redactor)
48
51
  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) }
52
+ stdout_reader = Thread.new do
53
+ Command.collect_stream(stdout, command_output: output, context: context, stream: :stdout,
54
+ redactor: redactor)
55
+ end
56
+ stderr_reader = Thread.new do
57
+ Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr,
58
+ redactor: redactor)
59
+ end
51
60
  copy_error = nil
52
61
  begin
53
62
  IO.copy_stream(file, stdin)
@@ -67,17 +76,19 @@ module KamalBackup
67
76
  end
68
77
  end
69
78
  rescue Errno::ENOENT => e
70
- raise CommandError.new("command not found: #{command.argv.first}", command: command, status: 127, stderr: e.message)
79
+ raise CommandError.new("command not found: #{command.argv.first}", command: command, status: 127,
80
+ stderr: e.message)
71
81
  end
72
82
 
73
83
  def backup_paths(paths, tags:)
74
84
  paths = Array(paths).compact.map(&:to_s).reject(&:empty?)
75
85
 
76
- if paths.any?
77
- path_tags = paths.map { |path| "path:#{config.backup_path_label(path)}" }
78
- log("backing up #{paths.size} file path(s): #{paths.join(", ")}")
79
- run(["backup"] + host_args + paths + tag_args(common_tags + tags + path_tags))
80
- end
86
+ return unless paths.any?
87
+
88
+ path_tags = paths.map { |path| "path:#{config.backup_path_label(path)}" }
89
+ excludes = config.backup_path_excludes(paths)
90
+ log("backing up #{paths.size} file path(s): #{paths.join(', ')}")
91
+ run(['backup'] + host_args + exclude_args(excludes) + paths + tag_args(common_tags + tags + path_tags))
81
92
  end
82
93
 
83
94
  def backup_path(path, tags:)
@@ -90,7 +101,7 @@ module KamalBackup
90
101
 
91
102
  def prune
92
103
  retention_tag_sets.map do |tags|
93
- args = ["forget", "--prune", "--group-by", "host"] + config.retention_args + filter_tag_args(tags)
104
+ args = ['forget', '--prune', '--group-by', 'host'] + config.retention_args + filter_tag_args(tags)
94
105
  log("running restic forget/prune with retention policy for #{retention_scope(tags)}")
95
106
  run(args)
96
107
  end
@@ -98,39 +109,40 @@ module KamalBackup
98
109
 
99
110
  def check
100
111
  args = %w[check]
101
- args.concat(["--read-data-subset", config.check_read_data_subset]) if config.check_read_data_subset
112
+ args.concat(['--read-data-subset', config.check_read_data_subset]) if config.check_read_data_subset
102
113
  started_at = Time.now.utc
103
114
  result = run(args)
104
- write_last_check(status: "ok", started_at: started_at, finished_at: Time.now.utc, output: result.stdout)
115
+ write_last_check(status: 'ok', started_at: started_at, finished_at: Time.now.utc, output: result.stdout)
105
116
  result
106
117
  rescue CommandError => e
107
- write_last_check(status: "failed", started_at: started_at || Time.now.utc, finished_at: Time.now.utc, error: e.message)
118
+ write_last_check(status: 'failed', started_at: started_at || Time.now.utc, finished_at: Time.now.utc,
119
+ error: e.message)
108
120
  raise
109
121
  end
110
122
 
111
123
  def snapshots(tags: common_tags)
112
- run(["snapshots"] + filter_tag_args(tags))
124
+ run(['snapshots'] + filter_tag_args(tags))
113
125
  end
114
126
 
115
127
  def snapshots_json(tags: common_tags)
116
- output = run(["snapshots", "--json"] + filter_tag_args(tags), log_output: false).stdout
128
+ output = run(['snapshots', '--json'] + filter_tag_args(tags), log_output: false).stdout
117
129
  snapshots = JSON.parse(output)
118
130
  required_tags = tags.compact
119
131
  snapshots.select do |snapshot|
120
- snapshot_tags = Array(snapshot["tags"])
132
+ snapshot_tags = Array(snapshot['tags'])
121
133
  required_tags.all? { |tag| snapshot_tags.include?(tag) }
122
134
  end
123
135
  end
124
136
 
125
137
  def latest_snapshot(tags:)
126
138
  snapshots = snapshots_json(tags: common_tags + tags)
127
- snapshots.max_by { |snapshot| Time.parse(snapshot.fetch("time")) }
139
+ snapshots.max_by { |snapshot| Time.parse(snapshot.fetch('time')) }
128
140
  rescue JSON::ParserError
129
141
  nil
130
142
  end
131
143
 
132
144
  def ls_json(snapshot)
133
- output = run(["ls", "--json", snapshot], log_output: false).stdout
145
+ output = run(['ls', '--json', snapshot], log_output: false).stdout
134
146
  output.lines.filter_map do |line|
135
147
  JSON.parse(line)
136
148
  rescue JSON::ParserError
@@ -140,49 +152,52 @@ module KamalBackup
140
152
 
141
153
  def database_file(snapshot, adapter, database_name: nil)
142
154
  legacy_prefix = "databases/#{config.app_name}/#{adapter}/"
143
- app = config.app_name.gsub(/[^A-Za-z0-9_.-]+/, "-")
144
- database = database_name.to_s.gsub(/[^A-Za-z0-9_.-]+/, "-")
155
+ app = config.app_name.gsub(/[^A-Za-z0-9_.-]+/, '-')
156
+ database = database_name.to_s.gsub(/[^A-Za-z0-9_.-]+/, '-')
145
157
  stable_prefix = database.empty? ? nil : "databases/#{app}/#{database}/#{adapter}."
146
158
  flat_prefix = "databases-#{app}-#{adapter}-"
147
159
  named_flat_prefix = database.empty? ? nil : "databases-#{app}-#{database}-#{adapter}-"
148
160
  ls_json(snapshot).find do |entry|
149
- next false unless entry["type"] == "file"
161
+ next false unless entry['type'] == 'file'
150
162
 
151
- normalized = entry["path"].to_s.sub(%r{\A/+}, "")
163
+ normalized = entry['path'].to_s.sub(%r{\A/+}, '')
152
164
  (stable_prefix && normalized.start_with?(stable_prefix)) ||
153
165
  normalized.start_with?(legacy_prefix) ||
154
166
  File.basename(normalized).start_with?(flat_prefix) ||
155
167
  (named_flat_prefix && File.basename(normalized).start_with?(named_flat_prefix))
156
- end&.fetch("path")
168
+ end&.fetch('path')
157
169
  end
158
170
 
159
171
  def pipe_dump_to_command(snapshot, filename, command)
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)
172
+ restic_command = CommandSpec.new(argv: ['restic', 'dump', snapshot, filename], env: restic_env)
173
+ pipe_commands(restic_command, command, producer_label: 'restic dump', consumer_label: command.argv.first)
162
174
  end
163
175
 
164
176
  def write_dump_to_path(snapshot, filename, target_path)
165
- command = CommandSpec.new(argv: ["restic", "dump", snapshot, filename], env: restic_env)
177
+ command = CommandSpec.new(argv: ['restic', 'dump', snapshot, filename], env: restic_env)
166
178
  target_path = File.expand_path(target_path)
167
179
  FileUtils.mkdir_p(File.dirname(target_path))
168
- temp_path = "#{target_path}.kamal-backup-#{$$}.tmp"
180
+ temp_path = "#{target_path}.kamal-backup-#{$PROCESS_ID}.tmp"
169
181
 
170
182
  output = Command.output
171
183
  context = output&.command_start(command, redactor: redactor)
172
184
  Open3.popen3(command.env, *command.argv) do |stdin, stdout, stderr, wait_thread|
173
185
  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) }
186
+ stderr_reader = Thread.new do
187
+ Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr, redactor: redactor)
188
+ end
189
+ File.open(temp_path, 'wb') { |file| IO.copy_stream(stdout, file) }
176
190
  err = stderr_reader.value
177
191
  status = wait_thread.value
178
192
  output&.command_exit(context, status.exitstatus)
179
- raise_command_error(command, status, "", err) unless status.success?
193
+ raise_command_error(command, status, '', err) unless status.success?
180
194
  end
181
195
  File.rename(temp_path, target_path)
182
196
  target_path
183
197
  rescue Errno::ENOENT => e
184
198
  FileUtils.rm_f(temp_path) if temp_path
185
- raise CommandError.new("command not found: #{command.argv.first}", command: command, status: 127, stderr: e.message)
199
+ raise CommandError.new("command not found: #{command.argv.first}", command: command, status: 127,
200
+ stderr: e.message)
186
201
  rescue StandardError
187
202
  FileUtils.rm_f(temp_path) if temp_path
188
203
  raise
@@ -190,158 +205,176 @@ module KamalBackup
190
205
 
191
206
  def restore_snapshot(snapshot, target)
192
207
  log("restoring file snapshot #{snapshot} to #{target}")
193
- run(["restore", snapshot, "--target", target])
208
+ run(['restore', snapshot, '--target', target])
194
209
  end
195
210
 
196
211
  def run(args, log_output: true)
197
212
  Command.capture(
198
- CommandSpec.new(argv: ["restic"] + args, env: restic_env),
213
+ CommandSpec.new(argv: ['restic'] + args, env: restic_env),
199
214
  redactor: redactor,
200
215
  log_output: log_output
201
216
  )
202
217
  end
203
218
 
204
219
  def common_tags
205
- ["kamal-backup", "app:#{config.app_name}"]
220
+ ['kamal-backup', "app:#{config.app_name}"]
206
221
  end
207
222
 
208
223
  private
209
- def retention_tag_sets
210
- database_retention_tag_sets + file_retention_tag_sets
211
- end
212
224
 
213
- def database_retention_tag_sets
214
- config.databases.group_by(&:database_adapter).flat_map do |adapter, databases|
215
- if databases.one?
216
- # Pre-0.3 database snapshots did not include database:<name>, so keep
217
- # the single-database filter broad enough for retention to prune them.
218
- [common_tags + ["type:database", "adapter:#{adapter}"]]
219
- else
220
- databases.map do |database|
221
- common_tags + ["type:database", "database:#{database.database_name}", "adapter:#{adapter}"]
222
- end
225
+ def retention_tag_sets
226
+ database_retention_tag_sets + file_retention_tag_sets
227
+ end
228
+
229
+ def database_retention_tag_sets
230
+ config.databases.group_by(&:database_adapter).flat_map do |adapter, databases|
231
+ 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.
234
+ [common_tags + ['type:database', "adapter:#{adapter}"]]
235
+ else
236
+ databases.map do |database|
237
+ common_tags + ['type:database', "database:#{database.database_name}", "adapter:#{adapter}"]
223
238
  end
224
239
  end
225
240
  end
241
+ end
226
242
 
227
- def file_retention_tag_sets
228
- config.backup_paths.any? ? [common_tags + ["type:files"]] : []
229
- end
243
+ def file_retention_tag_sets
244
+ config.backup_paths.any? ? [common_tags + ['type:files']] : []
245
+ end
230
246
 
231
- def retention_scope(tags)
232
- tags.reject { |tag| tag == "kamal-backup" || tag.start_with?("app:") }.join(", ")
233
- end
247
+ def retention_scope(tags)
248
+ tags.reject { |tag| tag == 'kamal-backup' || tag.start_with?('app:') }.join(', ')
249
+ end
234
250
 
235
- def tag_args(tags)
236
- tags.compact.each_with_object([]) { |tag, args| args.concat(["--tag", tag]) }
237
- end
251
+ def tag_args(tags)
252
+ tags.compact.each_with_object([]) { |tag, args| args.concat(['--tag', tag]) }
253
+ end
238
254
 
239
- def host_args
240
- ["--host", restic_host]
241
- end
255
+ def exclude_args(patterns)
256
+ patterns.compact.each_with_object([]) { |pattern, args| args.concat(['--exclude', pattern]) }
257
+ end
242
258
 
243
- def restic_host
244
- normalize_restic_host([config.app_name, config.accessory_name || "backup"].compact.join("-"))
245
- end
259
+ def host_args
260
+ ['--host', restic_host]
261
+ end
246
262
 
247
- def normalize_restic_host(value)
248
- normalized = value.to_s.gsub(/[^A-Za-z0-9_.-]+/, "-").gsub(/\A-+|-+\z/, "")
249
- normalized.empty? ? "kamal-backup" : normalized
250
- end
263
+ def restic_host
264
+ normalize_restic_host([config.app_name, config.accessory_name || 'backup'].compact.join('-'))
265
+ end
251
266
 
252
- def filter_tag_args(tags)
253
- tags = tags.compact
254
- tags.empty? ? [] : ["--tag", tags.join(",")]
255
- end
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
270
+ end
256
271
 
257
- def restic_env
258
- config.env.each_with_object({}) do |(key, value), env|
259
- env[key] = value if key.to_s.match?(RESTIC_ENV_PATTERN)
260
- end
272
+ def filter_tag_args(tags)
273
+ tags = tags.compact
274
+ tags.empty? ? [] : ['--tag', tags.join(',')]
275
+ end
276
+
277
+ def restic_env
278
+ config.env.each_with_object({}) do |(key, value), env|
279
+ env[key] = value if key.to_s.match?(RESTIC_ENV_PATTERN)
261
280
  end
281
+ end
262
282
 
263
- def pipe_commands(producer, consumer, producer_label:, consumer_label:)
264
- output = Command.output
265
- producer_context = output&.command_start(producer, redactor: redactor)
266
- Open3.popen3(producer.env, *producer.argv) do |producer_stdin, producer_stdout, producer_stderr, producer_wait|
267
- producer_stdin.close
268
-
269
- consumer_context = output&.command_start(consumer, redactor: redactor)
270
- Open3.popen3(consumer.env, *consumer.argv) do |consumer_stdin, consumer_stdout, consumer_stderr, consumer_wait|
271
- producer_err_reader = Thread.new { Command.collect_stream(producer_stderr, command_output: output, context: producer_context, stream: :stderr, redactor: redactor) }
272
- consumer_out_reader = Thread.new { Command.collect_stream(consumer_stdout, command_output: output, context: consumer_context, stream: :stdout, redactor: redactor) }
273
- consumer_err_reader = Thread.new { Command.collect_stream(consumer_stderr, command_output: output, context: consumer_context, stream: :stderr, redactor: redactor) }
274
-
275
- copy_error = nil
276
- copy_thread = Thread.new do
277
- IO.copy_stream(producer_stdout, consumer_stdin)
278
- rescue StandardError => e
279
- copy_error = e
280
- ensure
281
- consumer_stdin.close unless consumer_stdin.closed?
282
- end
283
-
284
- copy_thread.join
285
- producer_status = producer_wait.value
286
- consumer_status = consumer_wait.value
287
- output&.command_exit(producer_context, producer_status.exitstatus)
288
- output&.command_exit(consumer_context, consumer_status.exitstatus)
289
-
290
- producer_err = producer_err_reader.value
291
- consumer_out = consumer_out_reader.value
292
- consumer_err = consumer_err_reader.value
293
-
294
- if copy_error
295
- raise CommandError.new(
296
- "failed to pipe #{producer_label} to #{consumer_label}: #{copy_error.message}",
297
- command: consumer,
298
- stderr: copy_error.message
299
- )
300
- end
301
-
302
- raise_command_error(producer, producer_status, "", producer_err) unless producer_status.success?
303
- raise_command_error(consumer, consumer_status, consumer_out, consumer_err) unless consumer_status.success?
304
-
305
- CommandResult.new(stdout: consumer_out, stderr: consumer_err, status: consumer_status.exitstatus)
283
+ def pipe_commands(producer, consumer, producer_label:, consumer_label:)
284
+ output = Command.output
285
+ producer_context = output&.command_start(producer, redactor: redactor)
286
+ Open3.popen3(producer.env, *producer.argv) do |producer_stdin, producer_stdout, producer_stderr, producer_wait|
287
+ producer_stdin.close
288
+
289
+ consumer_context = output&.command_start(consumer, redactor: redactor)
290
+ Open3.popen3(consumer.env,
291
+ *consumer.argv) do |consumer_stdin, consumer_stdout, consumer_stderr, consumer_wait|
292
+ producer_err_reader = Thread.new do
293
+ Command.collect_stream(producer_stderr, command_output: output, context: producer_context, stream: :stderr,
294
+ redactor: redactor)
295
+ end
296
+ consumer_out_reader = Thread.new do
297
+ Command.collect_stream(consumer_stdout, command_output: output, context: consumer_context, stream: :stdout,
298
+ redactor: redactor)
306
299
  end
300
+ consumer_err_reader = Thread.new do
301
+ Command.collect_stream(consumer_stderr, command_output: output, context: consumer_context, stream: :stderr,
302
+ redactor: redactor)
303
+ end
304
+
305
+ copy_error = nil
306
+ copy_thread = Thread.new do
307
+ IO.copy_stream(producer_stdout, consumer_stdin)
308
+ rescue StandardError => e
309
+ copy_error = e
310
+ ensure
311
+ consumer_stdin.close unless consumer_stdin.closed?
312
+ end
313
+
314
+ copy_thread.join
315
+ producer_status = producer_wait.value
316
+ consumer_status = consumer_wait.value
317
+ output&.command_exit(producer_context, producer_status.exitstatus)
318
+ output&.command_exit(consumer_context, consumer_status.exitstatus)
319
+
320
+ producer_err = producer_err_reader.value
321
+ consumer_out = consumer_out_reader.value
322
+ consumer_err = consumer_err_reader.value
323
+
324
+ if copy_error
325
+ raise CommandError.new(
326
+ "failed to pipe #{producer_label} to #{consumer_label}: #{copy_error.message}",
327
+ command: consumer,
328
+ stderr: copy_error.message
329
+ )
330
+ end
331
+
332
+ raise_command_error(producer, producer_status, '', producer_err) unless producer_status.success?
333
+ raise_command_error(consumer, consumer_status, consumer_out, consumer_err) unless consumer_status.success?
334
+
335
+ CommandResult.new(stdout: consumer_out, stderr: consumer_err, status: consumer_status.exitstatus)
307
336
  end
308
- rescue Errno::ENOENT => e
309
- command = e.message.include?(producer.argv.first) ? producer : consumer
310
- raise CommandError.new("command not found: #{command.argv.first}", command: command, status: 127, stderr: e.message)
311
337
  end
338
+ rescue Errno::ENOENT => e
339
+ command = e.message.include?(producer.argv.first) ? producer : consumer
340
+ raise CommandError.new("command not found: #{command.argv.first}", command: command, status: 127,
341
+ stderr: e.message)
342
+ end
312
343
 
313
- def raise_command_error(command, status, stdout, stderr)
314
- raise CommandError.new(
315
- "command failed (#{status.exitstatus}): #{command.display(redactor)}\n#{redactor.redact_string(stderr)}",
316
- command: command,
317
- status: status.exitstatus,
318
- stdout: redactor.redact_string(stdout),
319
- stderr: redactor.redact_string(stderr)
320
- )
321
- end
344
+ def raise_command_error(command, status, stdout, stderr)
345
+ raise CommandError.new(
346
+ "command failed (#{status.exitstatus}): #{command.display(redactor)}\n#{redactor.redact_string(stderr)}",
347
+ command: command,
348
+ status: status.exitstatus,
349
+ stdout: redactor.redact_string(stdout),
350
+ stderr: redactor.redact_string(stderr)
351
+ )
352
+ end
322
353
 
323
- def raise_stream_error(command, error, stdout, stderr)
324
- raise CommandError.new(
325
- "failed to stream file to #{command.display(redactor)}: #{error.message}\n#{redactor.redact_string(stderr)}",
326
- command: command,
327
- stdout: redactor.redact_string(stdout),
328
- stderr: redactor.redact_string(stderr)
329
- )
330
- end
354
+ def raise_stream_error(command, error, stdout, stderr)
355
+ raise CommandError.new(
356
+ "failed to stream file to #{command.display(redactor)}: #{error.message}\n#{redactor.redact_string(stderr)}",
357
+ command: command,
358
+ stdout: redactor.redact_string(stdout),
359
+ stderr: redactor.redact_string(stderr)
360
+ )
361
+ end
331
362
 
332
- def write_last_check(payload)
333
- FileUtils.mkdir_p(config.state_dir)
334
- File.write(config.last_check_path, JSON.pretty_generate(payload.transform_values { |value| value.respond_to?(:iso8601) ? value.iso8601 : redactor.redact_string(value.to_s) }))
335
- rescue SystemCallError
336
- nil
337
- end
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
338
371
 
339
- def log(message)
340
- if Command.output
341
- Command.output.info(message, redactor: redactor)
342
- else
343
- $stdout.puts("[kamal-backup] #{redactor.redact_string(message)}")
344
- end
372
+ def log(message)
373
+ if Command.output
374
+ Command.output.info(message, redactor: redactor)
375
+ else
376
+ $stdout.puts("[kamal-backup] #{redactor.redact_string(message)}")
345
377
  end
378
+ end
346
379
  end
347
380
  end
@@ -1,4 +1,6 @@
1
- require "time"
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
2
4
 
3
5
  module KamalBackup
4
6
  class Scheduler
@@ -32,21 +34,22 @@ module KamalBackup
32
34
  end
33
35
 
34
36
  private
35
- def install_signal_handlers
36
- %w[TERM INT].each do |signal|
37
- Signal.trap(signal) { @stop = true }
38
- rescue ArgumentError
39
- nil
40
- end
41
- end
42
37
 
43
- def sleep_interruptibly(seconds)
44
- deadline = Time.now + seconds
45
- sleep([deadline - Time.now, 1].min) while !@stop && Time.now < deadline
38
+ def install_signal_handlers
39
+ %w[TERM INT].each do |signal|
40
+ Signal.trap(signal) { @stop = true }
41
+ rescue ArgumentError
42
+ nil
46
43
  end
44
+ end
47
45
 
48
- def log(message)
49
- $stdout.puts("[kamal-backup] #{message}")
50
- end
46
+ def sleep_interruptibly(seconds)
47
+ deadline = Time.now + seconds
48
+ sleep([deadline - Time.now, 1].min) while !@stop && Time.now < deadline
49
+ end
50
+
51
+ def log(message)
52
+ $stdout.puts("[kamal-backup] #{message}")
53
+ end
51
54
  end
52
55
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KamalBackup
2
4
  module Schema
3
5
  VERSION = 1
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KamalBackup
2
- VERSION = "0.3.0.beta20"
4
+ VERSION = '0.3.0'
3
5
  end
data/lib/kamal_backup.rb CHANGED
@@ -1,17 +1,19 @@
1
- require_relative "kamal_backup/version"
2
- require_relative "kamal_backup/schema"
3
- require_relative "kamal_backup/errors"
4
- require_relative "kamal_backup/command"
5
- require_relative "kamal_backup/redactor"
6
- require_relative "kamal_backup/config"
7
- require_relative "kamal_backup/rails_app"
8
- require_relative "kamal_backup/kamal_bridge"
9
- require_relative "kamal_backup/restic"
10
- require_relative "kamal_backup/evidence"
11
- require_relative "kamal_backup/scheduler"
12
- require_relative "kamal_backup/databases/base"
13
- require_relative "kamal_backup/databases/postgres"
14
- require_relative "kamal_backup/databases/mysql"
15
- require_relative "kamal_backup/databases/sqlite"
16
- require_relative "kamal_backup/app"
17
- require_relative "kamal_backup/cli"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'kamal_backup/version'
4
+ require_relative 'kamal_backup/schema'
5
+ require_relative 'kamal_backup/errors'
6
+ require_relative 'kamal_backup/command'
7
+ require_relative 'kamal_backup/redactor'
8
+ require_relative 'kamal_backup/config'
9
+ require_relative 'kamal_backup/rails_app'
10
+ require_relative 'kamal_backup/kamal_bridge'
11
+ require_relative 'kamal_backup/restic'
12
+ require_relative 'kamal_backup/evidence'
13
+ require_relative 'kamal_backup/scheduler'
14
+ require_relative 'kamal_backup/databases/base'
15
+ require_relative 'kamal_backup/databases/postgres'
16
+ require_relative 'kamal_backup/databases/mysql'
17
+ require_relative 'kamal_backup/databases/sqlite'
18
+ require_relative 'kamal_backup/app'
19
+ require_relative 'kamal_backup/cli'