kamal-backup 0.4.1 → 0.5.0

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: 2402ed466c957ae450d1e3196018d7099da3e01c59e7cc27e0a8dd7db0dbec5b
4
- data.tar.gz: 43d46f5648d0a2e410ea3fee13876a65c032b42a7260adb7a9210f8e8452ae6c
3
+ metadata.gz: aa8276219db8c5d8aa820e85e0f289f87715889cb5ea18ae2c99e3e12bbfe6b9
4
+ data.tar.gz: 626824ed56029bf3b713676d3376ab38d571942ee3809e7cf18abbfde9c24a45
5
5
  SHA512:
6
- metadata.gz: 645f1b5d3a0da81237a3391efde6f22747447b93504580693ebe2dd0e7fb2d81024a00d54d427890992234687034948b25053d741e8a974ee379a385b9e6a9d3
7
- data.tar.gz: 83577eddd32f3cd423408fea71c75769138d5b67041d555b369478129f20045eea2d51fd031b45067e6862a53dc6b4a2f7218a374e57a879d9ff606a41258c2d
6
+ metadata.gz: 75d362d35409c0447c29994a7bf4d23ac47d287fc88621eb46f32a56296537217cee76a28799983f45154361ecd61e4f553b609144969bed2b10016bf0ecc09c
7
+ data.tar.gz: e5f664a15123066441b940d945caed2335d8b50356acaa64d9ae6f49d87a7705894234e55db42ec6508aa188f727b01d4e4d4c71688ecd93b4dbf4c12d53ef5d
data/README.md CHANGED
@@ -99,6 +99,8 @@ backup:
99
99
  schedule: 1d
100
100
  ```
101
101
 
102
+ Only paths explicitly listed under `paths` are included in file snapshots. Omit `paths` for a database-only backup; `kamal-backup` never infers `storage` from Rails.
103
+
102
104
  Boot it. The container runs `kamal-backup schedule` by default:
103
105
 
104
106
  ```sh
@@ -32,6 +32,7 @@ module KamalBackup
32
32
  def backup(force: false)
33
33
  started_at = Time.now.utc
34
34
  config.validate_backup
35
+ return disabled_backup_result(started_at) unless config.backup_enabled? || force
35
36
  return skipped_backup_result(started_at) unless force || backup_due?(started_at)
36
37
 
37
38
  restic.ensure_repository
@@ -136,6 +137,18 @@ module KamalBackup
136
137
  due_at.nil? || now >= due_at
137
138
  end
138
139
 
140
+ def disabled_backup_result(now)
141
+ Schema.record(
142
+ kind: 'backup_result',
143
+ status: 'skipped',
144
+ reason: 'disabled',
145
+ last_backup_at: last_backup_finished_at&.iso8601,
146
+ next_backup_at: nil,
147
+ force_command: 'kamal-backup backup --force',
148
+ finished_at: now.iso8601
149
+ )
150
+ end
151
+
139
152
  def skipped_backup_result(now)
140
153
  Schema.record(
141
154
  kind: 'backup_result',
@@ -140,6 +140,13 @@ module KamalBackup
140
140
  end
141
141
 
142
142
  def print_backup_result(result)
143
+ if result[:status] == 'skipped' && result[:reason] == 'disabled'
144
+ puts('Backups are disabled for this app (backup.enabled is false).')
145
+ puts('Set backup.enabled to true and redeploy the accessory to resume scheduled backups.')
146
+ puts("Run `#{result.fetch(:force_command)}` to take one anyway.")
147
+ return
148
+ end
149
+
143
150
  if result[:status] == 'skipped'
144
151
  puts("No backup due. Last backup finished at #{result.fetch(:last_backup_at)}.")
145
152
  puts("Next backup is due at #{result.fetch(:next_backup_at)}.")
@@ -26,15 +26,15 @@ module KamalBackup
26
26
  CLI.basename
27
27
  end
28
28
 
29
- desc 'local [SNAPSHOT]', 'Restore the backup into the local database and Active Storage path'
29
+ desc 'local [SNAPSHOT]', 'Restore the backup into the local database and configured file paths'
30
30
  def local(snapshot = 'latest')
31
- confirm!("Restore #{snapshot} into the local database and Active Storage path? This will overwrite local data.")
31
+ confirm!("Restore #{snapshot} into the local database and configured file paths? This will overwrite local data.")
32
32
  puts(JSON.pretty_generate(local_restore_app.restore_to_local_machine(snapshot)))
33
33
  end
34
34
 
35
35
  method_option :"confirm-production-restore", type: :boolean, default: false,
36
36
  desc: 'Confirm production restore without interactive prompts'
37
- desc 'production [SNAPSHOT]', 'Restore the backup into the production database and Active Storage path'
37
+ desc 'production [SNAPSHOT]', 'Restore the backup into the production database and configured file paths'
38
38
  def production(snapshot = 'latest')
39
39
  confirm_production_restore!(snapshot)
40
40
 
@@ -133,7 +133,7 @@ module KamalBackup
133
133
  class_option :config_file, aliases: '-c', type: :string, desc: 'Path to Kamal deploy config file'
134
134
  class_option :destination, aliases: '-d', type: :string, desc: 'Kamal destination to use'
135
135
  remove_command :tree
136
- desc 'restore SUBCOMMAND ...ARGS', 'Restore a database and Active Storage backup locally or into production'
136
+ desc 'restore SUBCOMMAND ...ARGS', 'Restore database and configured file backups locally or into production'
137
137
  subcommand 'restore', RestoreCLI
138
138
  desc 'drill SUBCOMMAND ...ARGS', 'Run a restore drill on the local machine or on production infrastructure'
139
139
  subcommand 'drill', DrillCLI
@@ -162,7 +162,7 @@ module KamalBackup
162
162
 
163
163
  method_option :force, type: :boolean, default: false,
164
164
  desc: 'Run a backup even if the configured schedule is not due'
165
- desc 'backup', 'Run a due database and Active Storage backup'
165
+ desc 'backup', 'Run a due database and configured file backup'
166
166
  def backup
167
167
  if remote_command_mode?
168
168
  argv = %w[kamal-backup backup]
@@ -238,10 +238,11 @@ module KamalBackup
238
238
  puts
239
239
  puts deploy_snippet
240
240
  puts
241
- puts 'The accessory runs scheduled database and file backups with backup.schedule.'
242
- puts 'For most Rails apps, restore local and drill local can infer the development database, Active Storage path, and tmp state directory.'
241
+ puts 'The accessory runs scheduled database and configured file backups with backup.schedule.'
242
+ puts 'File backups run only for paths explicitly configured in config/kamal-backup.yml.'
243
+ puts 'Rails local restores infer the development database and tmp state directory, but never file paths.'
243
244
  puts 'Local restore and drill also require the restic binary on your machine.'
244
- puts 'Create config/kamal-backup.local.yml only if you need to override those local defaults.'
245
+ puts 'When production paths are configured, set their local targets in config/kamal-backup.local.yml.'
245
246
  end
246
247
 
247
248
  desc 'schedule', 'Run the foreground scheduler loop'
@@ -168,6 +168,10 @@ module KamalBackup
168
168
  truthy?('KAMAL_BACKUP_ALLOW_SUSPICIOUS_PATHS')
169
169
  end
170
170
 
171
+ def backup_enabled?
172
+ !falsey?('BACKUP_ENABLED')
173
+ end
174
+
171
175
  def backup_schedule_seconds
172
176
  integer('BACKUP_SCHEDULE_SECONDS', 86_400, minimum: 1)
173
177
  end
@@ -214,7 +218,10 @@ module KamalBackup
214
218
  source_paths = local_restore_source_paths
215
219
  target_paths = backup_paths
216
220
 
217
- raise ConfigurationError, 'local restore source paths must contain the same number of paths as file paths' unless source_paths.size == target_paths.size
221
+ unless source_paths.size == target_paths.size
222
+ raise ConfigurationError,
223
+ 'local file paths must be explicitly configured and match the number of production restore source paths'
224
+ end
218
225
 
219
226
  source_paths.zip(target_paths)
220
227
  end
@@ -271,6 +271,7 @@ module KamalBackup
271
271
  hash = require_mapping(raw_value, "#{@path} backup")
272
272
  env = {}
273
273
  env['BACKUP_SCHEDULE_SECONDS'] = normalize_duration(hash['schedule'], "#{@path} backup.schedule") if hash.key?('schedule')
274
+ env['BACKUP_ENABLED'] = normalize_value(hash['enabled']) if hash.key?('enabled')
274
275
  env.compact
275
276
  end
276
277
 
@@ -22,9 +22,6 @@ module KamalBackup
22
22
  {}.tap do |defaults|
23
23
  defaults.merge!(deploy_defaults)
24
24
  defaults.merge!(database_defaults)
25
-
26
- defaults['BACKUP_PATHS'] = local_storage_path if local_storage_path
27
-
28
25
  defaults['KAMAL_BACKUP_STATE_DIR'] = File.join(@cwd, 'tmp', 'kamal-backup')
29
26
  end
30
27
  end
@@ -90,10 +87,6 @@ module KamalBackup
90
87
  end
91
88
  end
92
89
 
93
- def local_storage_path
94
- File.join(@cwd, 'storage')
95
- end
96
-
97
90
  def local_database_config
98
91
  environment = fetch(parsed_yaml(database_config_path), DEVELOPMENT_ENV)
99
92
  return nil unless environment.is_a?(Hash)
@@ -15,6 +15,17 @@ module KamalBackup
15
15
  $stderr.sync = true
16
16
 
17
17
  install_signal_handlers
18
+
19
+ unless @config.backup_enabled?
20
+ # Stay up rather than exit: the accessory is still how restore, list, check and
21
+ # evidence reach the repository, and exiting would restart-loop under Kamal's
22
+ # `--restart unless-stopped`. Set backup.enabled to true and redeploy to resume.
23
+ log('scheduled backups are disabled (backup.enabled is false); no backups will be taken')
24
+ idle_until_stopped
25
+ log("scheduler stopped at #{Time.now.utc.iso8601}")
26
+ return
27
+ end
28
+
18
29
  sleep_interruptibly(@config.backup_start_delay_seconds)
19
30
 
20
31
  until @stop
@@ -43,6 +54,10 @@ module KamalBackup
43
54
  end
44
55
  end
45
56
 
57
+ def idle_until_stopped
58
+ sleep 1 until @stop
59
+ end
60
+
46
61
  def sleep_interruptibly(seconds)
47
62
  deadline = Time.now + seconds
48
63
  sleep([deadline - Time.now, 1].min) while !@stop && Time.now < deadline
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KamalBackup
4
- VERSION = '0.4.1'
4
+ VERSION = '0.5.0'
5
5
  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.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - crmne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-25 00:00:00.000000000 Z
11
+ date: 2026-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor