kamal-backup 0.3.0.beta3 → 0.3.0.beta5

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: a1361a5b9b6637aa96a0de2f2407bca63f2dd6d253cede1595c2ebac8f55b578
4
- data.tar.gz: 74f156851335e88d5a36756fb43018e5f498b152cd2a00742cdd6eb425f623dd
3
+ metadata.gz: 0b92197e0f2d480ca2cebd5e2505304686b739ca0ed4c61c24b4b4caca39054d
4
+ data.tar.gz: ff2b00e495d6690956d1ed8070bc43989ca1a9e4b9846b6503ababa26adbd01f
5
5
  SHA512:
6
- metadata.gz: 43b1f8c585ddd0e4c9f0df96ea243d736ac5e9e565c12382816b6329ad3054746c47b42c72e269bc199daba7985af8d75efcfbeb5ec2bd09b54c2d9877b511aa
7
- data.tar.gz: b08fe68ed2474db14330819700af6f6d499d2d53d27721becf92cf03cc59a7239fba2c30087bd3bc7bdc552de87ec3a4cad9411fbda71b148488b91a8aea7d23
6
+ metadata.gz: c8f93d59add03f4bf66b48ae0ea61cf46c7682c527ac604fcbd98a8c0790e6cc77d9d45759fabbfb5d6fcbce25da0e82b421f56cc8771820c57b1933539634c2
7
+ data.tar.gz: ebd54357f4bdc1829a71cb76fcef4e60000661ce7eb8f4d1a974fb22438b1054fe96bfa26095509d74fd50e5a13d5e234fa0c845440c8f33ddfd49e798b307f4
data/README.md CHANGED
@@ -141,7 +141,7 @@ Run the release helper from a clean `master` checkout:
141
141
  bin/release 0.2.9
142
142
  ```
143
143
 
144
- It updates `lib/kamal_backup/version.rb`, runs the test suite and docs build, commits `Release 0.2.9`, and pushes `master`. CI publishes the RubyGem and Docker image tags first, then creates `v0.2.9`, the GitHub release, and the docs deployment from the release commit.
144
+ It updates `lib/kamal_backup/version.rb`, syncs `Gemfile.lock`, commits `Release 0.2.9`, and pushes `master`. CI runs the test suite and docs build, publishes the RubyGem and Docker image tags, then creates `v0.2.9`, the GitHub release, and the docs deployment from the release commit.
145
145
 
146
146
  Use `bin/release 0.2.9 --no-push` to prepare the commit locally without publishing.
147
147
 
@@ -31,10 +31,9 @@ module KamalBackup
31
31
  config.validate_backup
32
32
  require_restic!
33
33
 
34
- timestamp = current_timestamp
35
34
  restic.ensure_repository
36
- databases.each { |database| database.backup(restic, timestamp) }
37
- restic.backup_paths(config.backup_paths, tags: ["type:files", "run:#{timestamp}"])
35
+ databases.each { |database| database.backup(restic) }
36
+ restic.backup_paths(config.backup_paths, tags: ["type:files"])
38
37
 
39
38
  if config.forget_after_backup?
40
39
  restic.forget_after_success
@@ -135,10 +134,6 @@ module KamalBackup
135
134
  end
136
135
 
137
136
  private
138
- def current_timestamp
139
- Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
140
- end
141
-
142
137
  def build_restore_result(scope, snapshot)
143
138
  started_at = Time.now.utc
144
139
  result = Schema.record(
@@ -24,11 +24,11 @@ module KamalBackup
24
24
  @redactor = redactor
25
25
  end
26
26
 
27
- def backup(restic, timestamp)
27
+ def backup(restic)
28
28
  restic.backup_stream(
29
29
  dump_command,
30
- filename: database_filename(timestamp),
31
- tags: backup_tags(timestamp)
30
+ filename: database_filename,
31
+ tags: backup_tags
32
32
  )
33
33
  end
34
34
 
@@ -41,14 +41,14 @@ module KamalBackup
41
41
  restic.pipe_dump_to_command(snapshot, filename, scratch_restore_command(target))
42
42
  end
43
43
 
44
- def database_filename(timestamp)
44
+ def database_filename
45
45
  app = config.app_name.gsub(/[^A-Za-z0-9_.-]+/, "-")
46
46
  database = config.database_name.gsub(/[^A-Za-z0-9_.-]+/, "-")
47
- "databases-#{app}-#{database}-#{adapter_name}-#{timestamp}.#{dump_extension}"
47
+ "databases/#{app}/#{database}/#{adapter_name}.#{dump_extension}"
48
48
  end
49
49
 
50
- def backup_tags(timestamp)
51
- ["type:database", "database:#{config.database_name}", "adapter:#{adapter_name}", "run:#{timestamp}"]
50
+ def backup_tags
51
+ ["type:database", "database:#{config.database_name}", "adapter:#{adapter_name}"]
52
52
  end
53
53
 
54
54
  def adapter_name
@@ -13,7 +13,7 @@ module KamalBackup
13
13
  "sqlite3"
14
14
  end
15
15
 
16
- def backup(restic, timestamp)
16
+ def backup(restic)
17
17
  source = sqlite_source
18
18
  Tempfile.create(["kamal-backup-", ".sqlite3"]) do |tempfile|
19
19
  tempfile.close
@@ -23,8 +23,8 @@ module KamalBackup
23
23
  )
24
24
  restic.backup_file(
25
25
  tempfile.path,
26
- filename: database_filename(timestamp),
27
- tags: backup_tags(timestamp)
26
+ filename: database_filename,
27
+ tags: backup_tags
28
28
  )
29
29
  end
30
30
  end
@@ -28,7 +28,7 @@ module KamalBackup
28
28
 
29
29
  def backup_stream(command, filename:, tags:)
30
30
  restic_command = CommandSpec.new(
31
- argv: ["restic", "backup", "--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
31
+ argv: ["restic", "backup"] + host_args + ["--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
32
32
  env: restic_env
33
33
  )
34
34
  log("backing up stream as #{filename}")
@@ -37,7 +37,7 @@ module KamalBackup
37
37
 
38
38
  def backup_file(path, filename:, tags:)
39
39
  command = CommandSpec.new(
40
- argv: ["restic", "backup", "--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
40
+ argv: ["restic", "backup"] + host_args + ["--stdin", "--stdin-filename", filename] + tag_args(common_tags + tags),
41
41
  env: restic_env
42
42
  )
43
43
  log("backing up file content as #{filename}")
@@ -66,7 +66,7 @@ module KamalBackup
66
66
  if paths.any?
67
67
  path_tags = paths.map { |path| "path:#{config.backup_path_label(path)}" }
68
68
  log("backing up #{paths.size} file path(s): #{paths.join(", ")}")
69
- run(["backup"] + paths + tag_args(common_tags + tags + path_tags))
69
+ run(["backup"] + host_args + paths + tag_args(common_tags + tags + path_tags))
70
70
  end
71
71
  end
72
72
 
@@ -75,9 +75,11 @@ module KamalBackup
75
75
  end
76
76
 
77
77
  def forget_after_success
78
- args = ["forget", "--prune"] + config.retention_args + tag_args(common_tags)
79
- log("running restic forget/prune with retention policy")
80
- run(args)
78
+ retention_tag_sets.each do |tags|
79
+ args = ["forget", "--prune", "--group-by", "host"] + config.retention_args + filter_tag_args(tags)
80
+ log("running restic forget/prune with retention policy for #{retention_scope(tags)}")
81
+ run(args)
82
+ end
81
83
  end
82
84
 
83
85
  def check
@@ -93,11 +95,11 @@ module KamalBackup
93
95
  end
94
96
 
95
97
  def snapshots(tags: common_tags)
96
- run(["snapshots"] + tag_args(tags))
98
+ run(["snapshots"] + filter_tag_args(tags))
97
99
  end
98
100
 
99
101
  def snapshots_json(tags: common_tags)
100
- output = run(["snapshots", "--json"] + tag_args(tags)).stdout
102
+ output = run(["snapshots", "--json"] + filter_tag_args(tags)).stdout
101
103
  snapshots = JSON.parse(output)
102
104
  required_tags = tags.compact
103
105
  snapshots.select do |snapshot|
@@ -126,13 +128,15 @@ module KamalBackup
126
128
  legacy_prefix = "databases/#{config.app_name}/#{adapter}/"
127
129
  app = config.app_name.gsub(/[^A-Za-z0-9_.-]+/, "-")
128
130
  database = database_name.to_s.gsub(/[^A-Za-z0-9_.-]+/, "-")
131
+ stable_prefix = database.empty? ? nil : "databases/#{app}/#{database}/#{adapter}."
129
132
  flat_prefix = "databases-#{app}-#{adapter}-"
130
133
  named_flat_prefix = database.empty? ? nil : "databases-#{app}-#{database}-#{adapter}-"
131
134
  ls_json(snapshot).find do |entry|
132
135
  next false unless entry["type"] == "file"
133
136
 
134
137
  normalized = entry["path"].to_s.sub(%r{\A/+}, "")
135
- normalized.start_with?(legacy_prefix) ||
138
+ (stable_prefix && normalized.start_with?(stable_prefix)) ||
139
+ normalized.start_with?(legacy_prefix) ||
136
140
  File.basename(normalized).start_with?(flat_prefix) ||
137
141
  (named_flat_prefix && File.basename(normalized).start_with?(named_flat_prefix))
138
142
  end&.fetch("path")
@@ -181,10 +185,54 @@ module KamalBackup
181
185
  end
182
186
 
183
187
  private
188
+ def retention_tag_sets
189
+ database_retention_tag_sets + file_retention_tag_sets
190
+ end
191
+
192
+ def database_retention_tag_sets
193
+ config.databases.group_by(&:database_adapter).flat_map do |adapter, databases|
194
+ if databases.one?
195
+ # Pre-0.3 database snapshots did not include database:<name>, so keep
196
+ # the single-database filter broad enough for retention to prune them.
197
+ [common_tags + ["type:database", "adapter:#{adapter}"]]
198
+ else
199
+ databases.map do |database|
200
+ common_tags + ["type:database", "database:#{database.database_name}", "adapter:#{adapter}"]
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ def file_retention_tag_sets
207
+ config.backup_paths.any? ? [common_tags + ["type:files"]] : []
208
+ end
209
+
210
+ def retention_scope(tags)
211
+ tags.reject { |tag| tag == "kamal-backup" || tag.start_with?("app:") }.join(", ")
212
+ end
213
+
184
214
  def tag_args(tags)
185
215
  tags.compact.each_with_object([]) { |tag, args| args.concat(["--tag", tag]) }
186
216
  end
187
217
 
218
+ def host_args
219
+ ["--host", restic_host]
220
+ end
221
+
222
+ def restic_host
223
+ normalize_restic_host([config.app_name, config.accessory_name || "backup"].compact.join("-"))
224
+ end
225
+
226
+ def normalize_restic_host(value)
227
+ normalized = value.to_s.gsub(/[^A-Za-z0-9_.-]+/, "-").gsub(/\A-+|-+\z/, "")
228
+ normalized.empty? ? "kamal-backup" : normalized
229
+ end
230
+
231
+ def filter_tag_args(tags)
232
+ tags = tags.compact
233
+ tags.empty? ? [] : ["--tag", tags.join(",")]
234
+ end
235
+
188
236
  def restic_env
189
237
  config.env.each_with_object({}) do |(key, value), env|
190
238
  env[key] = value if key.to_s.match?(RESTIC_ENV_PATTERN)
@@ -1,3 +1,3 @@
1
1
  module KamalBackup
2
- VERSION = "0.3.0.beta3"
2
+ VERSION = "0.3.0.beta5"
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.beta3
4
+ version: 0.3.0.beta5
5
5
  platform: ruby
6
6
  authors:
7
7
  - crmne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-27 00:00:00.000000000 Z
11
+ date: 2026-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor