kamal-backup 0.2.5 → 0.2.7
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 +5 -5
- data/lib/kamal_backup/cli.rb +7 -11
- data/lib/kamal_backup/databases/sqlite.rb +36 -4
- data/lib/kamal_backup/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8322ff9eeead7c145127274766d96c54677333fafd7a94b57c6eb7b395cfeae6
|
|
4
|
+
data.tar.gz: b9b1d442c93b808b4d0e04c9fdd3eb757ff97df2470610769bd7e430836107e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d4031c8ffaa652db3f151c3b21cbf2e170571278a57d815880fa4bbe5a06d6dfce9690d9f59fbe5e68de77c97c6e1ac6a1da90c4c400ccb8af2acaf7d18cec7
|
|
7
|
+
data.tar.gz: f037233779d710baadf2c6c5e4100b8caccda9069d95b781411fbe35f5c8e4b93899c41d0652f6baa69f38f7e8107c83b65ef19d3ee988397067e5c40e0ae0b7
|
data/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Most self-hosted Rails apps need the same things:
|
|
|
30
30
|
- file-backed Active Storage backups from mounted volumes
|
|
31
31
|
- a fast way to restore production data locally
|
|
32
32
|
- restore drills that do not touch the live production database
|
|
33
|
-
- evidence that says more than "the backup
|
|
33
|
+
- evidence that says more than "the backup ran"
|
|
34
34
|
|
|
35
35
|
`kamal-backup` packages that workflow into a small Ruby gem, a production accessory image, and a restic repository.
|
|
36
36
|
|
|
@@ -93,12 +93,12 @@ bin/kamal accessory boot backup
|
|
|
93
93
|
bin/kamal accessory logs backup
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
Run the first backup and print evidence:
|
|
96
|
+
Run the first backup and print evidence. From an app checkout with `config/deploy.yml`, these commands shell out through Kamal to the backup accessory:
|
|
97
97
|
|
|
98
98
|
```sh
|
|
99
|
-
bundle exec kamal-backup
|
|
100
|
-
bundle exec kamal-backup
|
|
101
|
-
bundle exec kamal-backup
|
|
99
|
+
bundle exec kamal-backup backup
|
|
100
|
+
bundle exec kamal-backup list
|
|
101
|
+
bundle exec kamal-backup evidence
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
## What you get
|
data/lib/kamal_backup/cli.rb
CHANGED
|
@@ -77,11 +77,7 @@ module KamalBackup
|
|
|
77
77
|
File.file?(File.expand_path(KamalBridge::DEFAULT_CONFIG_FILE))
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
def
|
|
81
|
-
deployment_mode? || default_deploy_config?
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def validate_deploy_mode?
|
|
80
|
+
def remote_command_mode?
|
|
85
81
|
deployment_mode? || default_deploy_config?
|
|
86
82
|
end
|
|
87
83
|
|
|
@@ -362,7 +358,7 @@ module KamalBackup
|
|
|
362
358
|
|
|
363
359
|
desc "backup", "Run one database and Active Storage backup immediately"
|
|
364
360
|
def backup
|
|
365
|
-
if
|
|
361
|
+
if remote_command_mode?
|
|
366
362
|
exec_remote(["kamal-backup", "backup"])
|
|
367
363
|
else
|
|
368
364
|
direct_app.backup
|
|
@@ -371,7 +367,7 @@ module KamalBackup
|
|
|
371
367
|
|
|
372
368
|
desc "list", "List matching restic snapshots"
|
|
373
369
|
def list
|
|
374
|
-
if
|
|
370
|
+
if remote_command_mode?
|
|
375
371
|
exec_remote(["kamal-backup", "list"])
|
|
376
372
|
else
|
|
377
373
|
puts(direct_app.snapshots)
|
|
@@ -380,7 +376,7 @@ module KamalBackup
|
|
|
380
376
|
|
|
381
377
|
desc "check", "Run restic check and record the latest result"
|
|
382
378
|
def check
|
|
383
|
-
if
|
|
379
|
+
if remote_command_mode?
|
|
384
380
|
exec_remote(["kamal-backup", "check"])
|
|
385
381
|
else
|
|
386
382
|
puts(direct_app.check)
|
|
@@ -389,7 +385,7 @@ module KamalBackup
|
|
|
389
385
|
|
|
390
386
|
desc "evidence", "Print redacted backup, check, and restore-drill evidence as JSON"
|
|
391
387
|
def evidence
|
|
392
|
-
if
|
|
388
|
+
if remote_command_mode?
|
|
393
389
|
exec_remote(["kamal-backup", "evidence"])
|
|
394
390
|
else
|
|
395
391
|
puts(direct_app.evidence)
|
|
@@ -398,7 +394,7 @@ module KamalBackup
|
|
|
398
394
|
|
|
399
395
|
desc "validate", "Validate backup configuration without running a backup"
|
|
400
396
|
def validate
|
|
401
|
-
if
|
|
397
|
+
if remote_command_mode?
|
|
402
398
|
validate_deploy_config
|
|
403
399
|
else
|
|
404
400
|
direct_app.validate
|
|
@@ -433,7 +429,7 @@ module KamalBackup
|
|
|
433
429
|
|
|
434
430
|
desc "version", "Print the running kamal-backup version"
|
|
435
431
|
def version
|
|
436
|
-
if
|
|
432
|
+
if remote_command_mode?
|
|
437
433
|
print_remote_version_status
|
|
438
434
|
else
|
|
439
435
|
puts(VERSION)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "fileutils"
|
|
2
2
|
require "tempfile"
|
|
3
|
+
require "uri"
|
|
3
4
|
require_relative "base"
|
|
4
5
|
|
|
5
6
|
module KamalBackup
|
|
@@ -17,10 +18,7 @@ module KamalBackup
|
|
|
17
18
|
source = sqlite_source
|
|
18
19
|
Tempfile.create(["kamal-backup-", ".sqlite3"]) do |tempfile|
|
|
19
20
|
tempfile.close
|
|
20
|
-
|
|
21
|
-
CommandSpec.new(argv: ["sqlite3", source, ".backup #{sqlite_literal(tempfile.path)}"]),
|
|
22
|
-
redactor: redactor
|
|
23
|
-
)
|
|
21
|
+
backup_to_file(source, tempfile.path)
|
|
24
22
|
restic.backup_file(
|
|
25
23
|
tempfile.path,
|
|
26
24
|
filename: database_filename(timestamp),
|
|
@@ -55,6 +53,40 @@ module KamalBackup
|
|
|
55
53
|
config.required_value("SQLITE_DATABASE_PATH")
|
|
56
54
|
end
|
|
57
55
|
|
|
56
|
+
def backup_to_file(source, target)
|
|
57
|
+
run_backup(source, target)
|
|
58
|
+
rescue CommandError => e
|
|
59
|
+
raise unless immutable_retry_safe?(source, e)
|
|
60
|
+
|
|
61
|
+
# Immutable mode skips WAL change detection, so only use it when no WAL sidecar exists.
|
|
62
|
+
run_backup(sqlite_immutable_uri(source), target)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def run_backup(source, target)
|
|
66
|
+
Command.capture(
|
|
67
|
+
CommandSpec.new(argv: ["sqlite3", source, ".backup #{sqlite_literal(target)}"]),
|
|
68
|
+
redactor: redactor
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def immutable_retry_safe?(source, error)
|
|
73
|
+
readonly_database_error?(error) &&
|
|
74
|
+
!File.exist?("#{source}-wal") &&
|
|
75
|
+
!File.exist?("#{source}-shm")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def readonly_database_error?(error)
|
|
79
|
+
error.stderr.include?("readonly database") || error.message.include?("readonly database")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def sqlite_immutable_uri(source)
|
|
83
|
+
path = File.expand_path(source).split("/").map do |part|
|
|
84
|
+
URI.encode_www_form_component(part).gsub("+", "%20")
|
|
85
|
+
end.join("/")
|
|
86
|
+
|
|
87
|
+
"file:#{path}?immutable=1"
|
|
88
|
+
end
|
|
89
|
+
|
|
58
90
|
def validate_scratch_restore_target(target)
|
|
59
91
|
if File.expand_path(sqlite_source) == File.expand_path(target)
|
|
60
92
|
raise ConfigurationError, "scratch SQLite path must differ from SQLITE_DATABASE_PATH"
|
data/lib/kamal_backup/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kamal-backup
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- crmne
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|