kamal-backup 0.1.0.pre.10 → 0.1.1
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/README.md +8 -0
- data/lib/kamal_backup/app.rb +20 -0
- data/lib/kamal_backup/cli.rb +35 -2
- data/lib/kamal_backup/command.rb +7 -0
- data/lib/kamal_backup/databases/base.rb +1 -4
- data/lib/kamal_backup/kamal_bridge.rb +11 -0
- 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: 9a38b68b938b1d5d96996c2e0271cdff8e914600b8d134e39a0717aa8290bca7
|
|
4
|
+
data.tar.gz: 0451f17fefb476958074c0c11e37a4c0a020b8b4e6bf3fa352296369326a7925
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f3e92a83c2ead4ed47136253c730cfd5f47938f490de04b2d48df91a7673a3b4ac08e4eda0d951fa83dab3215b544e8f060d68b4285dd9f042323267a08cd5c
|
|
7
|
+
data.tar.gz: 899dfd47d6cefc53f87a72ca53ddda721bcf9c55ef019236b12a26bd56def82b5665c26958c5e8444a5168023ee49d0a51d17a632ac44e5db3f61861cb9518b7
|
data/README.md
CHANGED
|
@@ -59,6 +59,8 @@ For most Rails apps, that is enough. `restore local` and `drill local` can infer
|
|
|
59
59
|
|
|
60
60
|
Only create `config/kamal-backup.local.yml` if you need to override those local defaults.
|
|
61
61
|
|
|
62
|
+
Local restore and drill also require the `restic` binary on your machine. The backup accessory image already includes `restic` for production-side commands.
|
|
63
|
+
|
|
62
64
|
Then add the backup accessory to `config/deploy.yml`:
|
|
63
65
|
|
|
64
66
|
```yaml
|
|
@@ -143,6 +145,8 @@ kamal-backup version
|
|
|
143
145
|
|
|
144
146
|
Production-side commands shell out through Kamal when you pass `-d` or `-c`. Local commands run on your machine.
|
|
145
147
|
|
|
148
|
+
Remote-backed commands require the local gem version and the backup accessory version to match. If they drift, `kamal-backup` fails fast and tells you to reboot the accessory so it pulls the current `latest` image. `version` is the diagnostic exception: it shows both versions and the sync status.
|
|
149
|
+
|
|
146
150
|
Common examples:
|
|
147
151
|
|
|
148
152
|
```sh
|
|
@@ -193,6 +197,8 @@ You still provide the local secrets yourself in env:
|
|
|
193
197
|
- `POSTGRES_PASSWORD` or `MYSQL_PWD` when needed
|
|
194
198
|
- `RESTIC_REPOSITORY` when it is not visible through `kamal config`
|
|
195
199
|
|
|
200
|
+
And you need `restic` installed locally and available on `PATH`.
|
|
201
|
+
|
|
196
202
|
Example:
|
|
197
203
|
|
|
198
204
|
```sh
|
|
@@ -217,6 +223,8 @@ It prompts locally, then shells out through Kamal to the backup accessory.
|
|
|
217
223
|
bundle exec kamal-backup -d production drill local latest --check "bin/rails runner 'puts User.count'"
|
|
218
224
|
```
|
|
219
225
|
|
|
226
|
+
Like `restore local`, this runs on your machine and requires a local `restic` install.
|
|
227
|
+
|
|
220
228
|
`drill production` restores into scratch targets on production infrastructure. It does not touch the live production database:
|
|
221
229
|
|
|
222
230
|
```sh
|
data/lib/kamal_backup/app.rb
CHANGED
|
@@ -29,6 +29,7 @@ module KamalBackup
|
|
|
29
29
|
|
|
30
30
|
def backup
|
|
31
31
|
config.validate_backup
|
|
32
|
+
require_restic!
|
|
32
33
|
|
|
33
34
|
timestamp = current_timestamp
|
|
34
35
|
restic.ensure_repository
|
|
@@ -48,6 +49,7 @@ module KamalBackup
|
|
|
48
49
|
|
|
49
50
|
def restore_to_local_machine(snapshot = "latest")
|
|
50
51
|
validate_local_machine_restore
|
|
52
|
+
require_restic!
|
|
51
53
|
|
|
52
54
|
build_restore_result("local", snapshot) do |result|
|
|
53
55
|
adapter = database
|
|
@@ -63,6 +65,7 @@ module KamalBackup
|
|
|
63
65
|
|
|
64
66
|
def restore_to_production(snapshot = "latest")
|
|
65
67
|
validate_production_restore
|
|
68
|
+
require_restic!
|
|
66
69
|
|
|
67
70
|
build_restore_result("production", snapshot) do |result|
|
|
68
71
|
adapter = database
|
|
@@ -77,6 +80,7 @@ module KamalBackup
|
|
|
77
80
|
|
|
78
81
|
def drill_on_local_machine(snapshot = "latest", check_command: nil)
|
|
79
82
|
validate_local_machine_restore
|
|
83
|
+
require_restic!
|
|
80
84
|
|
|
81
85
|
run_drill("local", snapshot, check_command: check_command) do |result|
|
|
82
86
|
adapter = database
|
|
@@ -92,6 +96,7 @@ module KamalBackup
|
|
|
92
96
|
|
|
93
97
|
def drill_on_production(snapshot = "latest", database_name: nil, sqlite_path: nil, file_target: "/restore/files", check_command: nil)
|
|
94
98
|
validate_production_drill(file_target, database_name, sqlite_path)
|
|
99
|
+
require_restic!
|
|
95
100
|
|
|
96
101
|
run_drill("production", snapshot, check_command: check_command) do |result|
|
|
97
102
|
adapter = database
|
|
@@ -113,16 +118,19 @@ module KamalBackup
|
|
|
113
118
|
|
|
114
119
|
def snapshots
|
|
115
120
|
config.validate_restic
|
|
121
|
+
require_restic!
|
|
116
122
|
restic.snapshots.stdout
|
|
117
123
|
end
|
|
118
124
|
|
|
119
125
|
def check
|
|
120
126
|
config.validate_restic
|
|
127
|
+
require_restic!
|
|
121
128
|
restic.check.stdout
|
|
122
129
|
end
|
|
123
130
|
|
|
124
131
|
def evidence
|
|
125
132
|
config.validate_restic
|
|
133
|
+
require_restic!
|
|
126
134
|
@evidence_class.new(config, restic: restic, redactor: redactor).to_json
|
|
127
135
|
end
|
|
128
136
|
|
|
@@ -312,6 +320,14 @@ module KamalBackup
|
|
|
312
320
|
config.validate_local_database_restore_target(adapter.current_target_identifier)
|
|
313
321
|
end
|
|
314
322
|
|
|
323
|
+
def require_restic!
|
|
324
|
+
return unless using_builtin_restic?
|
|
325
|
+
return if Command.available?("restic")
|
|
326
|
+
|
|
327
|
+
raise ConfigurationError,
|
|
328
|
+
"restic is required on PATH for commands that run on this machine. Install restic locally and try again."
|
|
329
|
+
end
|
|
330
|
+
|
|
315
331
|
def run_drill_check(command)
|
|
316
332
|
result = Command.capture(
|
|
317
333
|
CommandSpec.new(argv: ["sh", "-lc", command]),
|
|
@@ -347,6 +363,10 @@ module KamalBackup
|
|
|
347
363
|
@restic ||= Restic.new(config, redactor: redactor)
|
|
348
364
|
end
|
|
349
365
|
|
|
366
|
+
def using_builtin_restic?
|
|
367
|
+
@restic.nil? || @restic.is_a?(Restic)
|
|
368
|
+
end
|
|
369
|
+
|
|
350
370
|
def database
|
|
351
371
|
@database ||= Databases::Base.build(config, redactor: redactor)
|
|
352
372
|
end
|
data/lib/kamal_backup/cli.rb
CHANGED
|
@@ -54,7 +54,13 @@ module KamalBackup
|
|
|
54
54
|
@accessory_name ||= bridge.accessory_name(preferred: local_preferences.accessory_name)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
def
|
|
57
|
+
def remote_version
|
|
58
|
+
@remote_version ||= bridge.remote_version(accessory_name: accessory_name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def exec_remote(argv, require_version_match: true)
|
|
62
|
+
ensure_remote_version_match! if require_version_match
|
|
63
|
+
|
|
58
64
|
result = bridge.execute_on_accessory(
|
|
59
65
|
accessory_name: accessory_name,
|
|
60
66
|
command: Shellwords.join(argv)
|
|
@@ -63,6 +69,32 @@ module KamalBackup
|
|
|
63
69
|
result
|
|
64
70
|
end
|
|
65
71
|
|
|
72
|
+
def ensure_remote_version_match!
|
|
73
|
+
return if remote_version == VERSION
|
|
74
|
+
|
|
75
|
+
raise ConfigurationError, <<~MESSAGE.strip
|
|
76
|
+
local gem version #{VERSION} does not match remote accessory version #{remote_version}.
|
|
77
|
+
Reboot the backup accessory to pick up the latest image:
|
|
78
|
+
#{accessory_reboot_command}
|
|
79
|
+
MESSAGE
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def accessory_reboot_command
|
|
83
|
+
argv = ["bin/kamal", "accessory", "reboot", accessory_name]
|
|
84
|
+
argv.concat(["-c", options[:config_file]]) if options[:config_file]
|
|
85
|
+
argv.concat(["-d", options[:destination]]) if options[:destination]
|
|
86
|
+
Shellwords.join(argv)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def print_remote_version_status
|
|
90
|
+
status = remote_version == VERSION ? "in sync" : "out of sync"
|
|
91
|
+
|
|
92
|
+
puts("local: #{VERSION}")
|
|
93
|
+
puts("remote: #{remote_version}")
|
|
94
|
+
puts("status: #{status}")
|
|
95
|
+
puts("fix: #{accessory_reboot_command}") if status == "out of sync"
|
|
96
|
+
end
|
|
97
|
+
|
|
66
98
|
def confirm!(message)
|
|
67
99
|
return if options[:yes]
|
|
68
100
|
|
|
@@ -329,6 +361,7 @@ module KamalBackup
|
|
|
329
361
|
puts deploy_snippet
|
|
330
362
|
puts
|
|
331
363
|
puts "For most Rails apps, restore local and drill local can infer the development database, storage path, and tmp state directory."
|
|
364
|
+
puts "Local restore and drill also require the restic binary on your machine."
|
|
332
365
|
puts "Create config/kamal-backup.local.yml only if you need to override those local defaults."
|
|
333
366
|
end
|
|
334
367
|
|
|
@@ -344,7 +377,7 @@ module KamalBackup
|
|
|
344
377
|
desc "version", "Print the running kamal-backup version"
|
|
345
378
|
def version
|
|
346
379
|
if deployment_mode?
|
|
347
|
-
|
|
380
|
+
print_remote_version_status
|
|
348
381
|
else
|
|
349
382
|
puts(VERSION)
|
|
350
383
|
end
|
data/lib/kamal_backup/command.rb
CHANGED
|
@@ -26,6 +26,13 @@ module KamalBackup
|
|
|
26
26
|
|
|
27
27
|
class Command
|
|
28
28
|
class << self
|
|
29
|
+
def available?(name)
|
|
30
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |dir|
|
|
31
|
+
path = File.join(dir, name)
|
|
32
|
+
File.executable?(path) && !File.directory?(path)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
29
36
|
def capture(spec, input: nil, redactor:)
|
|
30
37
|
stdout, stderr, status = Open3.capture3(spec.env, *spec.argv, stdin_data: input)
|
|
31
38
|
result = CommandResult.new(stdout: stdout, stderr: stderr, status: status.exitstatus)
|
|
@@ -88,10 +88,7 @@ module KamalBackup
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def executable_available?(name)
|
|
91
|
-
|
|
92
|
-
path = File.join(dir, name)
|
|
93
|
-
File.executable?(path) && !File.directory?(path)
|
|
94
|
-
end
|
|
91
|
+
Command.available?(name)
|
|
95
92
|
end
|
|
96
93
|
end
|
|
97
94
|
end
|
|
@@ -46,6 +46,17 @@ module KamalBackup
|
|
|
46
46
|
capture_kamal(kamal_exec_argv(accessory_name, command))
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
def remote_version(accessory_name:)
|
|
50
|
+
result = execute_on_accessory(accessory_name: accessory_name, command: "kamal-backup version")
|
|
51
|
+
version = result.stdout.to_s.strip
|
|
52
|
+
|
|
53
|
+
if version.empty?
|
|
54
|
+
raise ConfigurationError, "could not determine remote kamal-backup version from accessory #{accessory_name}"
|
|
55
|
+
else
|
|
56
|
+
version
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
49
60
|
private
|
|
50
61
|
def config
|
|
51
62
|
@config ||= begin
|
data/lib/kamal_backup/version.rb
CHANGED