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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85d6ee6e04e1765b7dcb8666deb42f63b151437a57bccfbc79c18c94b53e0af7
4
- data.tar.gz: 6d456b02c442c3316eb48f990d2b486410ed1541fd363abd4bed62e3d6018e68
3
+ metadata.gz: fac561fdadaa41333d7fb9a45c57ccce8fad9d941d5e1bf1b6505d11ed4455e9
4
+ data.tar.gz: 62f685b06d6ad3360fe652b2d1642a910dc3438427110eef548748ef5bb8de55
5
5
  SHA512:
6
- metadata.gz: 9b7bd98124f478da8b1ee5bea4382880f924ab44dd9cb2325dd86d862dfb8fdcaf8f851fbaaf4003ba205243e81bcbdb7bbec928571655239964495e8753731b
7
- data.tar.gz: a70c403b8b36a474339bd30ee84b8ae22fd11ff60b42aca6459abafc4fb43d3cb431ba1ab9f477f66326c0133d760043c7d35b28fca831451dfa705197132de3
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
 
@@ -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 = normalize_yaml_paths(raw_value, "#{path} paths")
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)
@@ -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
@@ -1,3 +1,3 @@
1
1
  module KamalBackup
2
- VERSION = "0.3.0.beta20"
2
+ VERSION = "0.3.0.beta21"
3
3
  end
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.beta20
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-02 00:00:00.000000000 Z
11
+ date: 2026-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor