kamal-backup 0.3.0.beta21 → 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,18 +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
- excludes = config.backup_path_excludes(paths)
79
- log("backing up #{paths.size} file path(s): #{paths.join(", ")}")
80
- run(["backup"] + host_args + exclude_args(excludes) + paths + tag_args(common_tags + tags + path_tags))
81
- 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))
82
92
  end
83
93
 
84
94
  def backup_path(path, tags:)
@@ -91,7 +101,7 @@ module KamalBackup
91
101
 
92
102
  def prune
93
103
  retention_tag_sets.map do |tags|
94
- 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)
95
105
  log("running restic forget/prune with retention policy for #{retention_scope(tags)}")
96
106
  run(args)
97
107
  end
@@ -99,39 +109,40 @@ module KamalBackup
99
109
 
100
110
  def check
101
111
  args = %w[check]
102
- 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
103
113
  started_at = Time.now.utc
104
114
  result = run(args)
105
- 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)
106
116
  result
107
117
  rescue CommandError => e
108
- 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)
109
120
  raise
110
121
  end
111
122
 
112
123
  def snapshots(tags: common_tags)
113
- run(["snapshots"] + filter_tag_args(tags))
124
+ run(['snapshots'] + filter_tag_args(tags))
114
125
  end
115
126
 
116
127
  def snapshots_json(tags: common_tags)
117
- 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
118
129
  snapshots = JSON.parse(output)
119
130
  required_tags = tags.compact
120
131
  snapshots.select do |snapshot|
121
- snapshot_tags = Array(snapshot["tags"])
132
+ snapshot_tags = Array(snapshot['tags'])
122
133
  required_tags.all? { |tag| snapshot_tags.include?(tag) }
123
134
  end
124
135
  end
125
136
 
126
137
  def latest_snapshot(tags:)
127
138
  snapshots = snapshots_json(tags: common_tags + tags)
128
- snapshots.max_by { |snapshot| Time.parse(snapshot.fetch("time")) }
139
+ snapshots.max_by { |snapshot| Time.parse(snapshot.fetch('time')) }
129
140
  rescue JSON::ParserError
130
141
  nil
131
142
  end
132
143
 
133
144
  def ls_json(snapshot)
134
- output = run(["ls", "--json", snapshot], log_output: false).stdout
145
+ output = run(['ls', '--json', snapshot], log_output: false).stdout
135
146
  output.lines.filter_map do |line|
136
147
  JSON.parse(line)
137
148
  rescue JSON::ParserError
@@ -141,49 +152,52 @@ module KamalBackup
141
152
 
142
153
  def database_file(snapshot, adapter, database_name: nil)
143
154
  legacy_prefix = "databases/#{config.app_name}/#{adapter}/"
144
- app = config.app_name.gsub(/[^A-Za-z0-9_.-]+/, "-")
145
- 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_.-]+/, '-')
146
157
  stable_prefix = database.empty? ? nil : "databases/#{app}/#{database}/#{adapter}."
147
158
  flat_prefix = "databases-#{app}-#{adapter}-"
148
159
  named_flat_prefix = database.empty? ? nil : "databases-#{app}-#{database}-#{adapter}-"
149
160
  ls_json(snapshot).find do |entry|
150
- next false unless entry["type"] == "file"
161
+ next false unless entry['type'] == 'file'
151
162
 
152
- normalized = entry["path"].to_s.sub(%r{\A/+}, "")
163
+ normalized = entry['path'].to_s.sub(%r{\A/+}, '')
153
164
  (stable_prefix && normalized.start_with?(stable_prefix)) ||
154
165
  normalized.start_with?(legacy_prefix) ||
155
166
  File.basename(normalized).start_with?(flat_prefix) ||
156
167
  (named_flat_prefix && File.basename(normalized).start_with?(named_flat_prefix))
157
- end&.fetch("path")
168
+ end&.fetch('path')
158
169
  end
159
170
 
160
171
  def pipe_dump_to_command(snapshot, filename, command)
161
- restic_command = CommandSpec.new(argv: ["restic", "dump", snapshot, filename], env: restic_env)
162
- 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)
163
174
  end
164
175
 
165
176
  def write_dump_to_path(snapshot, filename, target_path)
166
- command = CommandSpec.new(argv: ["restic", "dump", snapshot, filename], env: restic_env)
177
+ command = CommandSpec.new(argv: ['restic', 'dump', snapshot, filename], env: restic_env)
167
178
  target_path = File.expand_path(target_path)
168
179
  FileUtils.mkdir_p(File.dirname(target_path))
169
- temp_path = "#{target_path}.kamal-backup-#{$$}.tmp"
180
+ temp_path = "#{target_path}.kamal-backup-#{$PROCESS_ID}.tmp"
170
181
 
171
182
  output = Command.output
172
183
  context = output&.command_start(command, redactor: redactor)
173
184
  Open3.popen3(command.env, *command.argv) do |stdin, stdout, stderr, wait_thread|
174
185
  stdin.close
175
- stderr_reader = Thread.new { Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr, redactor: redactor) }
176
- 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) }
177
190
  err = stderr_reader.value
178
191
  status = wait_thread.value
179
192
  output&.command_exit(context, status.exitstatus)
180
- raise_command_error(command, status, "", err) unless status.success?
193
+ raise_command_error(command, status, '', err) unless status.success?
181
194
  end
182
195
  File.rename(temp_path, target_path)
183
196
  target_path
184
197
  rescue Errno::ENOENT => e
185
198
  FileUtils.rm_f(temp_path) if temp_path
186
- 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)
187
201
  rescue StandardError
188
202
  FileUtils.rm_f(temp_path) if temp_path
189
203
  raise
@@ -191,162 +205,176 @@ module KamalBackup
191
205
 
192
206
  def restore_snapshot(snapshot, target)
193
207
  log("restoring file snapshot #{snapshot} to #{target}")
194
- run(["restore", snapshot, "--target", target])
208
+ run(['restore', snapshot, '--target', target])
195
209
  end
196
210
 
197
211
  def run(args, log_output: true)
198
212
  Command.capture(
199
- CommandSpec.new(argv: ["restic"] + args, env: restic_env),
213
+ CommandSpec.new(argv: ['restic'] + args, env: restic_env),
200
214
  redactor: redactor,
201
215
  log_output: log_output
202
216
  )
203
217
  end
204
218
 
205
219
  def common_tags
206
- ["kamal-backup", "app:#{config.app_name}"]
220
+ ['kamal-backup', "app:#{config.app_name}"]
207
221
  end
208
222
 
209
223
  private
210
- def retention_tag_sets
211
- database_retention_tag_sets + file_retention_tag_sets
212
- end
213
224
 
214
- def database_retention_tag_sets
215
- config.databases.group_by(&:database_adapter).flat_map do |adapter, databases|
216
- if databases.one?
217
- # Pre-0.3 database snapshots did not include database:<name>, so keep
218
- # the single-database filter broad enough for retention to prune them.
219
- [common_tags + ["type:database", "adapter:#{adapter}"]]
220
- else
221
- databases.map do |database|
222
- common_tags + ["type:database", "database:#{database.database_name}", "adapter:#{adapter}"]
223
- 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}"]
224
238
  end
225
239
  end
226
240
  end
241
+ end
227
242
 
228
- def file_retention_tag_sets
229
- config.backup_paths.any? ? [common_tags + ["type:files"]] : []
230
- end
243
+ def file_retention_tag_sets
244
+ config.backup_paths.any? ? [common_tags + ['type:files']] : []
245
+ end
231
246
 
232
- def retention_scope(tags)
233
- tags.reject { |tag| tag == "kamal-backup" || tag.start_with?("app:") }.join(", ")
234
- end
247
+ def retention_scope(tags)
248
+ tags.reject { |tag| tag == 'kamal-backup' || tag.start_with?('app:') }.join(', ')
249
+ end
235
250
 
236
- def tag_args(tags)
237
- tags.compact.each_with_object([]) { |tag, args| args.concat(["--tag", tag]) }
238
- end
251
+ def tag_args(tags)
252
+ tags.compact.each_with_object([]) { |tag, args| args.concat(['--tag', tag]) }
253
+ end
239
254
 
240
- def exclude_args(patterns)
241
- patterns.compact.each_with_object([]) { |pattern, args| args.concat(["--exclude", pattern]) }
242
- end
255
+ def exclude_args(patterns)
256
+ patterns.compact.each_with_object([]) { |pattern, args| args.concat(['--exclude', pattern]) }
257
+ end
243
258
 
244
- def host_args
245
- ["--host", restic_host]
246
- end
259
+ def host_args
260
+ ['--host', restic_host]
261
+ end
247
262
 
248
- def restic_host
249
- normalize_restic_host([config.app_name, config.accessory_name || "backup"].compact.join("-"))
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 normalize_restic_host(value)
253
- normalized = value.to_s.gsub(/[^A-Za-z0-9_.-]+/, "-").gsub(/\A-+|-+\z/, "")
254
- normalized.empty? ? "kamal-backup" : normalized
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 filter_tag_args(tags)
258
- tags = tags.compact
259
- tags.empty? ? [] : ["--tag", tags.join(",")]
260
- end
272
+ def filter_tag_args(tags)
273
+ tags = tags.compact
274
+ tags.empty? ? [] : ['--tag', tags.join(',')]
275
+ end
261
276
 
262
- def restic_env
263
- config.env.each_with_object({}) do |(key, value), env|
264
- env[key] = value if key.to_s.match?(RESTIC_ENV_PATTERN)
265
- end
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)
266
280
  end
281
+ end
267
282
 
268
- def pipe_commands(producer, consumer, producer_label:, consumer_label:)
269
- output = Command.output
270
- producer_context = output&.command_start(producer, redactor: redactor)
271
- Open3.popen3(producer.env, *producer.argv) do |producer_stdin, producer_stdout, producer_stderr, producer_wait|
272
- producer_stdin.close
273
-
274
- consumer_context = output&.command_start(consumer, redactor: redactor)
275
- Open3.popen3(consumer.env, *consumer.argv) do |consumer_stdin, consumer_stdout, consumer_stderr, consumer_wait|
276
- producer_err_reader = Thread.new { Command.collect_stream(producer_stderr, command_output: output, context: producer_context, stream: :stderr, redactor: redactor) }
277
- consumer_out_reader = Thread.new { Command.collect_stream(consumer_stdout, command_output: output, context: consumer_context, stream: :stdout, redactor: redactor) }
278
- consumer_err_reader = Thread.new { Command.collect_stream(consumer_stderr, command_output: output, context: consumer_context, stream: :stderr, redactor: redactor) }
279
-
280
- copy_error = nil
281
- copy_thread = Thread.new do
282
- IO.copy_stream(producer_stdout, consumer_stdin)
283
- rescue StandardError => e
284
- copy_error = e
285
- ensure
286
- consumer_stdin.close unless consumer_stdin.closed?
287
- end
288
-
289
- copy_thread.join
290
- producer_status = producer_wait.value
291
- consumer_status = consumer_wait.value
292
- output&.command_exit(producer_context, producer_status.exitstatus)
293
- output&.command_exit(consumer_context, consumer_status.exitstatus)
294
-
295
- producer_err = producer_err_reader.value
296
- consumer_out = consumer_out_reader.value
297
- consumer_err = consumer_err_reader.value
298
-
299
- if copy_error
300
- raise CommandError.new(
301
- "failed to pipe #{producer_label} to #{consumer_label}: #{copy_error.message}",
302
- command: consumer,
303
- stderr: copy_error.message
304
- )
305
- end
306
-
307
- raise_command_error(producer, producer_status, "", producer_err) unless producer_status.success?
308
- raise_command_error(consumer, consumer_status, consumer_out, consumer_err) unless consumer_status.success?
309
-
310
- 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)
311
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)
312
336
  end
313
- rescue Errno::ENOENT => e
314
- command = e.message.include?(producer.argv.first) ? producer : consumer
315
- raise CommandError.new("command not found: #{command.argv.first}", command: command, status: 127, stderr: e.message)
316
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
317
343
 
318
- def raise_command_error(command, status, stdout, stderr)
319
- raise CommandError.new(
320
- "command failed (#{status.exitstatus}): #{command.display(redactor)}\n#{redactor.redact_string(stderr)}",
321
- command: command,
322
- status: status.exitstatus,
323
- stdout: redactor.redact_string(stdout),
324
- stderr: redactor.redact_string(stderr)
325
- )
326
- 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
327
353
 
328
- def raise_stream_error(command, error, stdout, stderr)
329
- raise CommandError.new(
330
- "failed to stream file to #{command.display(redactor)}: #{error.message}\n#{redactor.redact_string(stderr)}",
331
- command: command,
332
- stdout: redactor.redact_string(stdout),
333
- stderr: redactor.redact_string(stderr)
334
- )
335
- 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
336
362
 
337
- def write_last_check(payload)
338
- FileUtils.mkdir_p(config.state_dir)
339
- 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) }))
340
- rescue SystemCallError
341
- nil
342
- 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
343
371
 
344
- def log(message)
345
- if Command.output
346
- Command.output.info(message, redactor: redactor)
347
- else
348
- $stdout.puts("[kamal-backup] #{redactor.redact_string(message)}")
349
- 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)}")
350
377
  end
378
+ end
351
379
  end
352
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.beta21"
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'