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.
- checksums.yaml +4 -4
- data/exe/kamal-backup +7 -6
- data/lib/kamal_backup/app.rb +350 -356
- data/lib/kamal_backup/cli.rb +107 -111
- data/lib/kamal_backup/command.rb +165 -161
- data/lib/kamal_backup/config.rb +533 -511
- data/lib/kamal_backup/databases/base.rb +17 -14
- data/lib/kamal_backup/databases/mysql.rb +68 -67
- data/lib/kamal_backup/databases/postgres.rb +59 -58
- data/lib/kamal_backup/databases/sqlite.rb +21 -20
- data/lib/kamal_backup/errors.rb +3 -1
- data/lib/kamal_backup/evidence.rb +62 -61
- data/lib/kamal_backup/kamal_bridge.rb +254 -250
- data/lib/kamal_backup/rails_app.rb +102 -101
- data/lib/kamal_backup/redactor.rb +18 -13
- data/lib/kamal_backup/restic.rb +195 -167
- data/lib/kamal_backup/scheduler.rb +17 -14
- data/lib/kamal_backup/schema.rb +2 -0
- data/lib/kamal_backup/version.rb +3 -1
- data/lib/kamal_backup.rb +19 -17
- metadata +30 -2
data/lib/kamal_backup/cli.rb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'shellwords'
|
|
6
|
+
require 'thor'
|
|
7
|
+
require_relative 'app'
|
|
8
|
+
require_relative 'config'
|
|
9
|
+
require_relative 'kamal_bridge'
|
|
10
|
+
require_relative 'redactor'
|
|
11
|
+
require_relative 'version'
|
|
10
12
|
|
|
11
13
|
module KamalBackup
|
|
12
14
|
class CLI < Thor
|
|
@@ -38,17 +40,15 @@ module KamalBackup
|
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def local_command_config
|
|
41
|
-
@local_command_config ||=
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
51
|
-
end
|
|
43
|
+
@local_command_config ||= if deployment_mode?
|
|
44
|
+
Config.new(
|
|
45
|
+
env: command_env,
|
|
46
|
+
defaults: production_source_defaults,
|
|
47
|
+
config_paths: [Config::LOCAL_CONFIG_PATH]
|
|
48
|
+
)
|
|
49
|
+
else
|
|
50
|
+
Config.new(env: command_env)
|
|
51
|
+
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def production_source_defaults
|
|
@@ -59,10 +59,10 @@ module KamalBackup
|
|
|
59
59
|
config = Config.new(env: {}, config_paths: [Config::SHARED_CONFIG_PATH], load_project_defaults: false)
|
|
60
60
|
|
|
61
61
|
{}.tap do |defaults|
|
|
62
|
-
defaults[
|
|
63
|
-
defaults[
|
|
64
|
-
defaults[
|
|
65
|
-
defaults[
|
|
62
|
+
defaults['APP_NAME'] = config.app_name if config.app_name
|
|
63
|
+
defaults['DATABASE_ADAPTER'] = config.database_adapter if config.database_adapter
|
|
64
|
+
defaults['RESTIC_REPOSITORY'] = config.restic_repository if config.restic_repository
|
|
65
|
+
defaults['LOCAL_RESTORE_SOURCE_PATHS'] = config.backup_paths.join("\n") if config.backup_paths.any?
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
@@ -121,27 +121,27 @@ module KamalBackup
|
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
def accessory_reboot_command
|
|
124
|
-
argv = [
|
|
125
|
-
argv.concat([
|
|
126
|
-
argv.concat([
|
|
124
|
+
argv = ['bin/kamal', 'accessory', 'reboot', accessory_name]
|
|
125
|
+
argv.concat(['-c', options[:config_file]]) if options[:config_file]
|
|
126
|
+
argv.concat(['-d', options[:destination]]) if options[:destination]
|
|
127
127
|
Shellwords.join(argv)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
def print_remote_version_status
|
|
131
|
-
status = remote_version == VERSION ?
|
|
132
|
-
status_color = status ==
|
|
131
|
+
status = remote_version == VERSION ? 'in sync' : 'out of sync'
|
|
132
|
+
status_color = status == 'in sync' ? :green : :red
|
|
133
133
|
status_output = CommandOutput.new(io: $stdout, env: command_env)
|
|
134
134
|
|
|
135
135
|
puts("local: #{VERSION}")
|
|
136
136
|
puts("remote: #{remote_version}")
|
|
137
137
|
puts("status: #{status_output.decorate(status, status_color, :bold)}")
|
|
138
|
-
puts("fix: #{status_output.decorate(accessory_reboot_command, :yellow, :bold)}") if status ==
|
|
138
|
+
puts("fix: #{status_output.decorate(accessory_reboot_command, :yellow, :bold)}") if status == 'out of sync'
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
def print_backup_result(result)
|
|
142
142
|
return unless result.is_a?(Hash)
|
|
143
143
|
|
|
144
|
-
if result[:status] ==
|
|
144
|
+
if result[:status] == 'skipped'
|
|
145
145
|
puts("No backup due. Last backup finished at #{result.fetch(:last_backup_at)}.")
|
|
146
146
|
puts("Next backup is due at #{result.fetch(:next_backup_at)}.")
|
|
147
147
|
puts("Run `#{result.fetch(:force_command)}` to force a backup now.")
|
|
@@ -153,7 +153,7 @@ module KamalBackup
|
|
|
153
153
|
puts("database #{database.fetch(:database)}: #{database.fetch(:snapshot)} at #{database.fetch(:time)}")
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
-
if files = result[:files]
|
|
156
|
+
if (files = result[:files])
|
|
157
157
|
puts("files: #{files.fetch(:snapshot)} at #{files.fetch(:time)}")
|
|
158
158
|
end
|
|
159
159
|
end
|
|
@@ -162,7 +162,7 @@ module KamalBackup
|
|
|
162
162
|
output = Array(results).map(&:stdout).join
|
|
163
163
|
|
|
164
164
|
if output.empty?
|
|
165
|
-
puts(
|
|
165
|
+
puts('Prune completed')
|
|
166
166
|
else
|
|
167
167
|
print(output)
|
|
168
168
|
puts unless output.end_with?("\n")
|
|
@@ -181,30 +181,28 @@ module KamalBackup
|
|
|
181
181
|
def confirm!(message)
|
|
182
182
|
return if options[:yes]
|
|
183
183
|
|
|
184
|
-
unless $stdin.tty?
|
|
185
|
-
raise ConfigurationError, "confirmation required; rerun with --yes"
|
|
186
|
-
end
|
|
184
|
+
raise ConfigurationError, 'confirmation required; rerun with --yes' unless $stdin.tty?
|
|
187
185
|
|
|
188
|
-
unless yes?("#{message} [y/N]")
|
|
189
|
-
raise ConfigurationError, "aborted"
|
|
190
|
-
end
|
|
186
|
+
raise ConfigurationError, 'aborted' unless yes?("#{message} [y/N]")
|
|
191
187
|
end
|
|
192
188
|
|
|
193
189
|
def confirm_production_restore!(snapshot)
|
|
194
190
|
return if options[:"confirm-production-restore"]
|
|
195
191
|
|
|
196
192
|
if options[:yes]
|
|
197
|
-
raise ConfigurationError,
|
|
193
|
+
raise ConfigurationError,
|
|
194
|
+
'--yes does not bypass restore production; use --confirm-production-restore only for deliberate automation'
|
|
198
195
|
end
|
|
199
196
|
|
|
200
197
|
unless $stdin.tty?
|
|
201
|
-
raise ConfigurationError,
|
|
198
|
+
raise ConfigurationError,
|
|
199
|
+
'production restore confirmation required; rerun interactively or pass --confirm-production-restore only for deliberate automation'
|
|
202
200
|
end
|
|
203
201
|
|
|
204
202
|
app_name = production_restore_confirmation_config.required_app_name
|
|
205
203
|
say "This will overwrite the production database and file paths for #{app_name} from backup #{snapshot}.", :red
|
|
206
|
-
require_typed_confirmation(
|
|
207
|
-
require_typed_confirmation(
|
|
204
|
+
require_typed_confirmation('Type the app name to continue', app_name)
|
|
205
|
+
require_typed_confirmation('Type RESTORE PRODUCTION to continue', 'RESTORE PRODUCTION')
|
|
208
206
|
confirm!("Restore #{snapshot} into production now? This will overwrite production data.")
|
|
209
207
|
end
|
|
210
208
|
|
|
@@ -212,7 +210,7 @@ module KamalBackup
|
|
|
212
210
|
answer = ask("#{prompt}:").to_s.strip
|
|
213
211
|
return if answer == expected
|
|
214
212
|
|
|
215
|
-
raise ConfigurationError,
|
|
213
|
+
raise ConfigurationError, 'aborted'
|
|
216
214
|
end
|
|
217
215
|
|
|
218
216
|
def production_restore_confirmation_config
|
|
@@ -228,16 +226,12 @@ module KamalBackup
|
|
|
228
226
|
end
|
|
229
227
|
|
|
230
228
|
def prompt_required(label)
|
|
231
|
-
unless $stdin.tty?
|
|
232
|
-
raise ConfigurationError, "#{label.downcase} is required; pass it on the command line"
|
|
233
|
-
end
|
|
229
|
+
raise ConfigurationError, "#{label.downcase} is required; pass it on the command line" unless $stdin.tty?
|
|
234
230
|
|
|
235
231
|
value = ask("#{label}:").to_s.strip
|
|
236
|
-
if value.empty?
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
value
|
|
240
|
-
end
|
|
232
|
+
raise ConfigurationError, "#{label.downcase} is required" if value.empty?
|
|
233
|
+
|
|
234
|
+
value
|
|
241
235
|
end
|
|
242
236
|
|
|
243
237
|
def init_config_root
|
|
@@ -246,7 +240,7 @@ module KamalBackup
|
|
|
246
240
|
end
|
|
247
241
|
|
|
248
242
|
def shared_config_path
|
|
249
|
-
File.join(init_config_root,
|
|
243
|
+
File.join(init_config_root, 'kamal-backup.yml')
|
|
250
244
|
end
|
|
251
245
|
|
|
252
246
|
def write_init_file(path, contents)
|
|
@@ -305,9 +299,9 @@ module KamalBackup
|
|
|
305
299
|
class CommandBase < Thor
|
|
306
300
|
include Helpers
|
|
307
301
|
|
|
308
|
-
class_option :yes, aliases:
|
|
309
|
-
class_option :config_file, aliases:
|
|
310
|
-
class_option :destination, aliases:
|
|
302
|
+
class_option :yes, aliases: '-y', type: :boolean, default: false, desc: 'Skip confirmation prompt'
|
|
303
|
+
class_option :config_file, aliases: '-c', type: :string, desc: 'Path to Kamal deploy config file'
|
|
304
|
+
class_option :destination, aliases: '-d', type: :string, desc: 'Kamal destination to use'
|
|
311
305
|
remove_command :tree
|
|
312
306
|
end
|
|
313
307
|
|
|
@@ -316,19 +310,20 @@ module KamalBackup
|
|
|
316
310
|
CLI.basename
|
|
317
311
|
end
|
|
318
312
|
|
|
319
|
-
desc
|
|
320
|
-
def local(snapshot =
|
|
313
|
+
desc 'local [SNAPSHOT]', 'Restore the backup into the local database and Active Storage path'
|
|
314
|
+
def local(snapshot = 'latest')
|
|
321
315
|
confirm!("Restore #{snapshot} into the local database and Active Storage path? This will overwrite local data.")
|
|
322
316
|
puts(JSON.pretty_generate(local_restore_app.restore_to_local_machine(snapshot)))
|
|
323
317
|
end
|
|
324
318
|
|
|
325
|
-
method_option :"confirm-production-restore", type: :boolean, default: false,
|
|
326
|
-
|
|
327
|
-
|
|
319
|
+
method_option :"confirm-production-restore", type: :boolean, default: false,
|
|
320
|
+
desc: 'Confirm production restore without interactive prompts'
|
|
321
|
+
desc 'production [SNAPSHOT]', 'Restore the backup into the production database and Active Storage path'
|
|
322
|
+
def production(snapshot = 'latest')
|
|
328
323
|
confirm_production_restore!(snapshot)
|
|
329
324
|
|
|
330
325
|
if deployment_mode?
|
|
331
|
-
exec_remote([
|
|
326
|
+
exec_remote(['kamal-backup', 'restore', 'production', snapshot, '--confirm-production-restore'])
|
|
332
327
|
else
|
|
333
328
|
puts(JSON.pretty_generate(direct_app.restore_to_production(snapshot)))
|
|
334
329
|
end
|
|
@@ -340,28 +335,29 @@ module KamalBackup
|
|
|
340
335
|
CLI.basename
|
|
341
336
|
end
|
|
342
337
|
|
|
343
|
-
method_option :check, type: :string, desc:
|
|
344
|
-
desc
|
|
345
|
-
def local(snapshot =
|
|
338
|
+
method_option :check, type: :string, desc: 'Run a verification command after the restore'
|
|
339
|
+
desc 'local [SNAPSHOT]', 'Run a restore drill on the local machine'
|
|
340
|
+
def local(snapshot = 'latest')
|
|
346
341
|
confirm!("Run a local restore drill for #{snapshot}? This will overwrite local data.")
|
|
347
342
|
result = local_restore_app.drill_on_local_machine(snapshot, check_command: options[:check])
|
|
348
343
|
puts(JSON.pretty_generate(result))
|
|
349
344
|
exit(1) if local_restore_app.drill_failed?(result)
|
|
350
345
|
end
|
|
351
346
|
|
|
352
|
-
method_option :database, type: :string, desc:
|
|
353
|
-
method_option :"sqlite-path", type: :string, desc:
|
|
354
|
-
method_option :files, type: :string, default:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
347
|
+
method_option :database, type: :string, desc: 'Scratch database name for PostgreSQL or MySQL'
|
|
348
|
+
method_option :"sqlite-path", type: :string, desc: 'Scratch SQLite path for production-side drills'
|
|
349
|
+
method_option :files, type: :string, default: '/restore/files',
|
|
350
|
+
desc: 'Scratch Active Storage target for the drill'
|
|
351
|
+
method_option :check, type: :string, desc: 'Run a verification command after the restore'
|
|
352
|
+
desc 'production [SNAPSHOT]', 'Run a restore drill on production infrastructure using scratch targets'
|
|
353
|
+
def production(snapshot = 'latest')
|
|
358
354
|
confirm!("Run a production-side restore drill for #{snapshot}? This will restore into scratch targets on production infrastructure.")
|
|
359
355
|
|
|
360
356
|
if deployment_mode?
|
|
361
|
-
argv = [
|
|
362
|
-
argv.concat([
|
|
363
|
-
argv.concat([
|
|
364
|
-
argv.concat([
|
|
357
|
+
argv = ['kamal-backup', 'drill', 'production', snapshot, '--files', options[:files], '--yes']
|
|
358
|
+
argv.concat(['--database', production_database_name]) if production_database_name
|
|
359
|
+
argv.concat(['--sqlite-path', options[:"sqlite-path"]]) if options[:"sqlite-path"]
|
|
360
|
+
argv.concat(['--check', options[:check]]) if options[:check]
|
|
365
361
|
exec_remote(argv)
|
|
366
362
|
else
|
|
367
363
|
result = direct_app.drill_on_production(
|
|
@@ -378,10 +374,10 @@ module KamalBackup
|
|
|
378
374
|
|
|
379
375
|
no_commands do
|
|
380
376
|
def production_database_name
|
|
381
|
-
if local_command_config.database_adapter ==
|
|
377
|
+
if local_command_config.database_adapter == 'sqlite'
|
|
382
378
|
nil
|
|
383
379
|
else
|
|
384
|
-
options[:database] || prompt_required(
|
|
380
|
+
options[:database] || prompt_required('Scratch database name')
|
|
385
381
|
end
|
|
386
382
|
end
|
|
387
383
|
end
|
|
@@ -398,7 +394,7 @@ module KamalBackup
|
|
|
398
394
|
token = tokens.first
|
|
399
395
|
|
|
400
396
|
case token
|
|
401
|
-
when
|
|
397
|
+
when '-d', '--destination', '-c', '--config-file'
|
|
402
398
|
leading << tokens.shift
|
|
403
399
|
leading << tokens.shift if tokens.any?
|
|
404
400
|
when /\A--destination=.+\z/, /\A--config-file=.+\z/
|
|
@@ -416,18 +412,18 @@ module KamalBackup
|
|
|
416
412
|
end
|
|
417
413
|
end
|
|
418
414
|
|
|
419
|
-
package_name
|
|
415
|
+
package_name 'kamal-backup'
|
|
420
416
|
map %w[-v --version] => :version
|
|
421
|
-
class_option :config_file, aliases:
|
|
422
|
-
class_option :destination, aliases:
|
|
417
|
+
class_option :config_file, aliases: '-c', type: :string, desc: 'Path to Kamal deploy config file'
|
|
418
|
+
class_option :destination, aliases: '-d', type: :string, desc: 'Kamal destination to use'
|
|
423
419
|
remove_command :tree
|
|
424
|
-
desc
|
|
425
|
-
subcommand
|
|
426
|
-
desc
|
|
427
|
-
subcommand
|
|
420
|
+
desc 'restore SUBCOMMAND ...ARGS', 'Restore a database and Active Storage backup locally or into production'
|
|
421
|
+
subcommand 'restore', RestoreCLI
|
|
422
|
+
desc 'drill SUBCOMMAND ...ARGS', 'Run a restore drill on the local machine or on production infrastructure'
|
|
423
|
+
subcommand 'drill', DrillCLI
|
|
428
424
|
|
|
429
425
|
def self.basename
|
|
430
|
-
|
|
426
|
+
'kamal-backup'
|
|
431
427
|
end
|
|
432
428
|
|
|
433
429
|
def self.start(argv = ARGV, env: ENV)
|
|
@@ -446,7 +442,7 @@ module KamalBackup
|
|
|
446
442
|
exit(1)
|
|
447
443
|
rescue Interrupt
|
|
448
444
|
output ||= CommandOutput.new(io: $stderr, env: env)
|
|
449
|
-
output.error(
|
|
445
|
+
output.error('(Interrupt): interrupted', redactor: Redactor.new(env: env))
|
|
450
446
|
exit(130)
|
|
451
447
|
ensure
|
|
452
448
|
self.command_env = nil
|
|
@@ -454,55 +450,56 @@ module KamalBackup
|
|
|
454
450
|
|
|
455
451
|
include Helpers
|
|
456
452
|
|
|
457
|
-
method_option :force, type: :boolean, default: false,
|
|
458
|
-
|
|
453
|
+
method_option :force, type: :boolean, default: false,
|
|
454
|
+
desc: 'Run a backup even if the configured schedule is not due'
|
|
455
|
+
desc 'backup', 'Run a due database and Active Storage backup'
|
|
459
456
|
def backup
|
|
460
457
|
if remote_command_mode?
|
|
461
|
-
argv = [
|
|
462
|
-
argv <<
|
|
458
|
+
argv = %w[kamal-backup backup]
|
|
459
|
+
argv << '--force' if options[:force]
|
|
463
460
|
exec_remote(argv)
|
|
464
461
|
else
|
|
465
462
|
print_backup_result(direct_app.backup(force: options[:force]))
|
|
466
463
|
end
|
|
467
464
|
end
|
|
468
465
|
|
|
469
|
-
desc
|
|
466
|
+
desc 'list', 'List matching restic snapshots'
|
|
470
467
|
def list
|
|
471
468
|
if remote_command_mode?
|
|
472
|
-
exec_remote([
|
|
469
|
+
exec_remote(%w[kamal-backup list])
|
|
473
470
|
else
|
|
474
471
|
puts(direct_app.snapshots)
|
|
475
472
|
end
|
|
476
473
|
end
|
|
477
474
|
|
|
478
|
-
desc
|
|
475
|
+
desc 'check', 'Run restic check and record the latest result'
|
|
479
476
|
def check
|
|
480
477
|
if remote_command_mode?
|
|
481
|
-
exec_remote([
|
|
478
|
+
exec_remote(%w[kamal-backup check])
|
|
482
479
|
else
|
|
483
480
|
puts(direct_app.check)
|
|
484
481
|
end
|
|
485
482
|
end
|
|
486
483
|
|
|
487
|
-
desc
|
|
484
|
+
desc 'prune', 'Apply the configured restic retention policy and prune unneeded data'
|
|
488
485
|
def prune
|
|
489
486
|
if remote_command_mode?
|
|
490
|
-
exec_remote([
|
|
487
|
+
exec_remote(%w[kamal-backup prune])
|
|
491
488
|
else
|
|
492
489
|
print_prune_result(direct_app.prune)
|
|
493
490
|
end
|
|
494
491
|
end
|
|
495
492
|
|
|
496
|
-
desc
|
|
493
|
+
desc 'evidence', 'Print redacted backup, check, and restore-drill evidence as JSON'
|
|
497
494
|
def evidence
|
|
498
495
|
if remote_command_mode?
|
|
499
|
-
exec_remote([
|
|
496
|
+
exec_remote(%w[kamal-backup evidence])
|
|
500
497
|
else
|
|
501
498
|
puts(direct_app.evidence)
|
|
502
499
|
end
|
|
503
500
|
end
|
|
504
501
|
|
|
505
|
-
desc
|
|
502
|
+
desc 'validate', 'Validate backup configuration without running a backup'
|
|
506
503
|
def validate
|
|
507
504
|
if remote_command_mode?
|
|
508
505
|
validate_deploy_config
|
|
@@ -510,34 +507,34 @@ module KamalBackup
|
|
|
510
507
|
direct_app.validate
|
|
511
508
|
end
|
|
512
509
|
|
|
513
|
-
puts(
|
|
510
|
+
puts('ok')
|
|
514
511
|
end
|
|
515
512
|
|
|
516
|
-
desc
|
|
513
|
+
desc 'init', 'Create config and print the scheduled backup accessory snippet'
|
|
517
514
|
def init
|
|
518
515
|
write_init_file(shared_config_path, shared_config_template)
|
|
519
516
|
|
|
520
517
|
puts
|
|
521
|
-
puts
|
|
518
|
+
puts 'Add this accessory block to your Kamal deploy config:'
|
|
522
519
|
puts
|
|
523
520
|
puts deploy_snippet
|
|
524
521
|
puts
|
|
525
|
-
puts
|
|
526
|
-
puts
|
|
527
|
-
puts
|
|
528
|
-
puts
|
|
522
|
+
puts 'The accessory runs scheduled database and file backups with backup.schedule.'
|
|
523
|
+
puts 'For most Rails apps, restore local and drill local can infer the development database, Active Storage path, and tmp state directory.'
|
|
524
|
+
puts 'Local restore and drill also require the restic binary on your machine.'
|
|
525
|
+
puts 'Create config/kamal-backup.local.yml only if you need to override those local defaults.'
|
|
529
526
|
end
|
|
530
527
|
|
|
531
|
-
desc
|
|
528
|
+
desc 'schedule', 'Run the foreground scheduler loop'
|
|
532
529
|
def schedule
|
|
533
530
|
if deployment_mode?
|
|
534
|
-
exec_remote([
|
|
531
|
+
exec_remote(%w[kamal-backup schedule])
|
|
535
532
|
else
|
|
536
533
|
direct_app.schedule
|
|
537
534
|
end
|
|
538
535
|
end
|
|
539
536
|
|
|
540
|
-
desc
|
|
537
|
+
desc 'version', 'Print the running kamal-backup version'
|
|
541
538
|
def version
|
|
542
539
|
if remote_command_mode?
|
|
543
540
|
print_remote_version_status
|
|
@@ -545,6 +542,5 @@ module KamalBackup
|
|
|
545
542
|
puts(VERSION)
|
|
546
543
|
end
|
|
547
544
|
end
|
|
548
|
-
|
|
549
545
|
end
|
|
550
546
|
end
|