kamal-backup 0.3.0.beta20 → 0.3.0.beta21
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 +1 -0
- data/lib/kamal_backup/config.rb +98 -2
- data/lib/kamal_backup/restic.rb +6 -1
- 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: fac561fdadaa41333d7fb9a45c57ccce8fad9d941d5e1bf1b6505d11ed4455e9
|
|
4
|
+
data.tar.gz: 62f685b06d6ad3360fe652b2d1642a910dc3438427110eef548748ef5bb8de55
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2fc20a82dfa6f13531a10f93d10c23d50bfdc35adee3fa387551b33d8e22ba223c1b6416d99c113489b62b51e9ea0437213b9b7eeaa0e2c6d09030dbdf68e86
|
|
7
|
+
data.tar.gz: 3eb54b5786843e6290b9db9a89e078780a86196577acc8927b25a4f2abfe847b5dceaa1279e24293a66b24cb413158fcbb81acb1edc921fa4f3f836d1b27089d
|
data/README.md
CHANGED
|
@@ -72,6 +72,7 @@ accessories:
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
For SQLite databases stored on the mounted storage volume, omit `:ro` from that volume.
|
|
75
|
+
When the configured SQLite database file lives under a configured file backup path, `kamal-backup` excludes the raw database, WAL, and shared-memory files from the restic file snapshot automatically.
|
|
75
76
|
|
|
76
77
|
Put the backup settings in `config/kamal-backup.yml`:
|
|
77
78
|
|
data/lib/kamal_backup/config.rb
CHANGED
|
@@ -51,6 +51,7 @@ module KamalBackup
|
|
|
51
51
|
new(env: {}, database_definitions: nil, path_definitions: nil, restore_from_definitions: nil)
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
|
+
PathDefinition = Struct.new(:path, :exclude, keyword_init: true)
|
|
54
55
|
|
|
55
56
|
class DatabaseSource
|
|
56
57
|
CONNECTION_KEYS = %w[
|
|
@@ -225,12 +226,18 @@ module KamalBackup
|
|
|
225
226
|
|
|
226
227
|
def backup_paths
|
|
227
228
|
if path_definitions?
|
|
228
|
-
@path_definitions
|
|
229
|
+
@path_definitions.map(&:path)
|
|
229
230
|
else
|
|
230
231
|
legacy_backup_paths
|
|
231
232
|
end
|
|
232
233
|
end
|
|
233
234
|
|
|
235
|
+
def backup_path_excludes(paths = backup_paths)
|
|
236
|
+
paths = Array(paths).compact.map(&:to_s).reject(&:empty?)
|
|
237
|
+
|
|
238
|
+
configured_backup_path_excludes(paths) + sqlite_backup_path_excludes(paths)
|
|
239
|
+
end
|
|
240
|
+
|
|
234
241
|
def local_restore_source_paths
|
|
235
242
|
if path_definitions?
|
|
236
243
|
@restore_from_definitions || legacy_local_restore_source_paths || backup_paths
|
|
@@ -500,7 +507,7 @@ module KamalBackup
|
|
|
500
507
|
when "databases"
|
|
501
508
|
result.database_definitions = normalize_yaml_databases(raw_value, raw_env: raw_env, path: path)
|
|
502
509
|
when "paths"
|
|
503
|
-
result.path_definitions =
|
|
510
|
+
result.path_definitions = normalize_yaml_backup_paths(raw_value, "#{path} paths")
|
|
504
511
|
when "restore_from"
|
|
505
512
|
result.restore_from_definitions = normalize_yaml_paths(raw_value, "#{path} restore_from")
|
|
506
513
|
when "restic"
|
|
@@ -684,6 +691,48 @@ module KamalBackup
|
|
|
684
691
|
normalize_yaml_value(raw_value)
|
|
685
692
|
end
|
|
686
693
|
|
|
694
|
+
def normalize_yaml_backup_paths(raw_value, context)
|
|
695
|
+
case raw_value
|
|
696
|
+
when Array
|
|
697
|
+
raw_value.map.with_index(1) do |entry, index|
|
|
698
|
+
normalize_yaml_backup_path(entry, "#{context}[#{index}]")
|
|
699
|
+
end.reject { |definition| definition.path.to_s.empty? }
|
|
700
|
+
when NilClass
|
|
701
|
+
[]
|
|
702
|
+
else
|
|
703
|
+
[normalize_yaml_backup_path(raw_value, "#{context}[1]")].reject { |definition| definition.path.to_s.empty? }
|
|
704
|
+
end
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
def normalize_yaml_backup_path(raw_value, context)
|
|
708
|
+
case raw_value
|
|
709
|
+
when Hash
|
|
710
|
+
hash = require_mapping(raw_value, context)
|
|
711
|
+
unknown_keys = hash.keys - %w[path exclude]
|
|
712
|
+
unless unknown_keys.empty?
|
|
713
|
+
raise ConfigurationError, "#{context} contains unknown key #{unknown_keys.first.inspect}; expected path and exclude"
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
path = required_yaml_string(hash, "path", context)
|
|
717
|
+
exclude = hash.key?("exclude") ? normalize_yaml_excludes(hash["exclude"], "#{context}.exclude") : []
|
|
718
|
+
PathDefinition.new(path: path, exclude: exclude)
|
|
719
|
+
when Array
|
|
720
|
+
raise ConfigurationError, "#{context} must be a path string or a mapping with path and optional exclude"
|
|
721
|
+
else
|
|
722
|
+
PathDefinition.new(path: normalize_yaml_value(raw_value), exclude: [])
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
def normalize_yaml_excludes(raw_value, context)
|
|
727
|
+
entries = require_array(raw_value, context)
|
|
728
|
+
entries.map.with_index(1) do |entry, index|
|
|
729
|
+
pattern = optional_yaml_string(entry, "#{context}[#{index}]")
|
|
730
|
+
raise ConfigurationError, "#{context}[#{index}] must not be empty" if pattern.to_s.strip.empty?
|
|
731
|
+
|
|
732
|
+
pattern
|
|
733
|
+
end
|
|
734
|
+
end
|
|
735
|
+
|
|
687
736
|
def resolve_yaml_value(raw_value, raw_env:, context:)
|
|
688
737
|
case raw_value
|
|
689
738
|
when Hash
|
|
@@ -743,6 +792,23 @@ module KamalBackup
|
|
|
743
792
|
stringify_keys(value)
|
|
744
793
|
end
|
|
745
794
|
|
|
795
|
+
def optional_yaml_string(value, context)
|
|
796
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
|
797
|
+
raise ConfigurationError, "#{context} must be a string"
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
normalize_yaml_value(value)
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
def required_yaml_string(hash, key, context)
|
|
804
|
+
raise ConfigurationError, "#{context} #{key} is required" unless hash.key?(key)
|
|
805
|
+
|
|
806
|
+
value = optional_yaml_string(hash[key], "#{context}.#{key}")
|
|
807
|
+
raise ConfigurationError, "#{context} #{key} is required" if value.to_s.strip.empty?
|
|
808
|
+
|
|
809
|
+
value
|
|
810
|
+
end
|
|
811
|
+
|
|
746
812
|
def required_yaml_scalar(hash, key, context)
|
|
747
813
|
value = normalize_yaml_value(hash[key])
|
|
748
814
|
raise ConfigurationError, "#{context} #{key} is required" if value.to_s.empty?
|
|
@@ -806,6 +872,36 @@ module KamalBackup
|
|
|
806
872
|
!@path_definitions.nil?
|
|
807
873
|
end
|
|
808
874
|
|
|
875
|
+
def backup_path_definitions
|
|
876
|
+
if path_definitions?
|
|
877
|
+
@path_definitions
|
|
878
|
+
else
|
|
879
|
+
legacy_backup_paths.map { |path| PathDefinition.new(path: path, exclude: []) }
|
|
880
|
+
end
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
def configured_backup_path_excludes(paths)
|
|
884
|
+
backup_path_definitions.each_with_object([]) do |definition, excludes|
|
|
885
|
+
excludes.concat(definition.exclude) if paths.include?(definition.path)
|
|
886
|
+
end
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
def sqlite_backup_path_excludes(paths)
|
|
890
|
+
databases.select { |database| database.database_adapter == "sqlite" }.flat_map do |database|
|
|
891
|
+
sqlite_database_path = database.value("SQLITE_DATABASE_PATH")
|
|
892
|
+
next [] if sqlite_database_path.to_s.empty?
|
|
893
|
+
next [] unless paths.any? { |path| path_contains?(path, sqlite_database_path) }
|
|
894
|
+
|
|
895
|
+
[sqlite_database_path, "#{sqlite_database_path}-wal", "#{sqlite_database_path}-shm"]
|
|
896
|
+
end.uniq
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
def path_contains?(parent, child)
|
|
900
|
+
expanded_parent = File.expand_path(parent)
|
|
901
|
+
expanded_child = File.expand_path(child)
|
|
902
|
+
expanded_child == expanded_parent || expanded_child.start_with?(expanded_parent + "/")
|
|
903
|
+
end
|
|
904
|
+
|
|
809
905
|
def legacy_database_adapter
|
|
810
906
|
if explicit = value("DATABASE_ADAPTER")
|
|
811
907
|
normalize_adapter(explicit)
|
data/lib/kamal_backup/restic.rb
CHANGED
|
@@ -75,8 +75,9 @@ module KamalBackup
|
|
|
75
75
|
|
|
76
76
|
if paths.any?
|
|
77
77
|
path_tags = paths.map { |path| "path:#{config.backup_path_label(path)}" }
|
|
78
|
+
excludes = config.backup_path_excludes(paths)
|
|
78
79
|
log("backing up #{paths.size} file path(s): #{paths.join(", ")}")
|
|
79
|
-
run(["backup"] + host_args + paths + tag_args(common_tags + tags + path_tags))
|
|
80
|
+
run(["backup"] + host_args + exclude_args(excludes) + paths + tag_args(common_tags + tags + path_tags))
|
|
80
81
|
end
|
|
81
82
|
end
|
|
82
83
|
|
|
@@ -236,6 +237,10 @@ module KamalBackup
|
|
|
236
237
|
tags.compact.each_with_object([]) { |tag, args| args.concat(["--tag", tag]) }
|
|
237
238
|
end
|
|
238
239
|
|
|
240
|
+
def exclude_args(patterns)
|
|
241
|
+
patterns.compact.each_with_object([]) { |pattern, args| args.concat(["--exclude", pattern]) }
|
|
242
|
+
end
|
|
243
|
+
|
|
239
244
|
def host_args
|
|
240
245
|
["--host", restic_host]
|
|
241
246
|
end
|
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.3.0.
|
|
4
|
+
version: 0.3.0.beta21
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- crmne
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|