ruborg 0.5.0 → 0.6.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: 1233e04a2f95e8e8aadb9ad97d043b2d9b52c446821ff89495073a6a8762b6cc
4
- data.tar.gz: ee3fd1ae2256299120d7f78aac3243c2de44f8f3c072c4c29dd44cb761caf769
3
+ metadata.gz: 821f1707180870ec6b2ffccda4ef4d901b3ee274d7402c26170ca294e80efb8b
4
+ data.tar.gz: 62e2a5ee53cd75024b78e08e32295c34cf1829cd3bd34be9d6403c7ec1dfb810
5
5
  SHA512:
6
- metadata.gz: 59d6ad7e5797cbbd964390d0425bf1392695c24f938a40978ddea371552da84604bf0a8eb189b207318ed68dba966311d45659f2b6abd3a59711f766465a5b36
7
- data.tar.gz: 6c77a312c9b820dd22d7f0396dbb24fac70da4654023c89eebc94c962d367b963a448a923ce23574862276452c441e3ebd723601fd58a2634e3337d402a9c066
6
+ metadata.gz: acd1bcd0b6da0914888c11d50299ea2f365e2bf5a81423edbfc7e935a17d020b841de9043900ca86ed1df2ab8e50e1359806ad2857de3bd849b8f040c2909549
7
+ data.tar.gz: 799d52c64a3bf858b2324f5101255a77dd428778b1eced9bfa11e54d9c2ef23642645a886902f83c05336c89a61d1a80b02ad83185fecf3cf0cac866d18e7c09
data/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2025-10-08
11
+
12
+ ### Added
13
+ - **Configuration Validation Command**: New `ruborg validate` command to check configuration files for type errors
14
+ - **Automatic Schema Validation**: All commands now validate configuration on startup to catch errors early
15
+ - **Strict Boolean Type Checking**: All boolean config values (auto_init, auto_prune, allow_remove_source, etc.) now require actual boolean types
16
+ - Prevents type confusion attacks where strings like `'true'` or `"false"` bypass security checks
17
+ - Clear error messages show actual type vs expected type
18
+ - Validation runs automatically on config load
19
+ - Comprehensive validation test suite (10 new test cases)
20
+ - Documentation for configuration validation in README
21
+
22
+ ### Changed
23
+ - Boolean configuration values now use strict type checking throughout the codebase
24
+ - `auto_init`: only boolean `true` enables, everything else disables
25
+ - `auto_prune`: only boolean `true` enables, everything else disables
26
+ - `allow_remove_source`: strict checking - only `TrueClass` enables (security-critical)
27
+ - `allow_relocated_repo`: permissive normalization - only `false` disables (backward compatible)
28
+ - `allow_unencrypted_repo`: permissive normalization - only `false` disables (backward compatible)
29
+ - Config class now validates schema by default (can be disabled with `validate_types: false`)
30
+
31
+ ### Security
32
+ - **Type Confusion Protection (CWE-843)**: Strict boolean type checking prevents configuration bypass attacks
33
+ - Before: `allow_remove_source: 'false'` (string) would be truthy and enable deletion
34
+ - After: Only `allow_remove_source: true` (boolean) enables the dangerous operation
35
+ - Enhanced error messages guide users to fix type errors correctly
36
+ - SECURITY.md updated with type confusion findings and mitigations
37
+
10
38
  ## [0.5.0] - 2025-10-08
11
39
 
12
40
  ### Added
data/README.md CHANGED
@@ -25,7 +25,7 @@ e- ⏰ **Retention Policies** - Configure backup retention (hourly, daily, weekl
25
25
  - 📈 **Summary View** - Quick overview of all repositories and their configurations
26
26
  - 🔧 **Custom Borg Path** - Support for custom Borg executable paths per repository
27
27
  - 🏠 **Hostname Validation** - NEW! Restrict backups to specific hosts (global or per-repository)
28
- - ✅ **Well-tested** - Comprehensive test suite with RSpec (153 tests)
28
+ - ✅ **Well-tested** - Comprehensive test suite with RSpec (178+ tests)
29
29
  - 🔒 **Security-focused** - Path validation, safe YAML loading, command injection protection
30
30
 
31
31
  ## Prerequisites
@@ -162,15 +162,55 @@ repositories:
162
162
  ```
163
163
 
164
164
  **Configuration Features:**
165
+ - **Automatic Type Validation**: Configuration is validated on startup to catch type errors early
166
+ - **Validation Command**: Run `ruborg validate` to check configuration files for errors
165
167
  - **Descriptions**: Add `description` field to document each repository's purpose
166
168
  - **Hostname Validation**: Optional `hostname` field to restrict backups to specific hosts (global or per-repository)
167
- - **Global Settings**: Hostname, compression, encryption, auto_init, log_file, borg_path, borg_options, and retention apply to all repositories
168
- - **Per-Repository Overrides**: Any global setting can be overridden at the repository level (including hostname and custom borg_path)
169
+ - **Source Deletion Safety**: `allow_remove_source` flag to explicitly enable `--remove-source` option (default: disabled)
170
+ - **Type-Safe Booleans**: Strict boolean validation prevents configuration errors (must use `true`/`false`, not strings)
171
+ - **Global Settings**: Hostname, compression, encryption, auto_init, allow_remove_source, log_file, borg_path, borg_options, and retention apply to all repositories
172
+ - **Per-Repository Overrides**: Any global setting can be overridden at the repository level (including hostname, allow_remove_source, and custom borg_path)
169
173
  - **Custom Borg Path**: Specify a custom Borg executable path if borg is not in PATH or to use a specific version
170
174
  - **Retention Policies**: Define how many backups to keep (hourly, daily, weekly, monthly, yearly)
171
175
  - **Multiple Sources**: Each repository can have multiple backup sources with their own exclude patterns
172
176
  - **Flexible Organization**: Organize backups by type (documents, databases, media) with different policies
173
177
 
178
+ ## Configuration Validation
179
+
180
+ Ruborg automatically validates your configuration on startup. All commands check for type errors and structural issues before executing.
181
+
182
+ ### Validate Configuration
183
+
184
+ Check your configuration file for errors:
185
+
186
+ ```bash
187
+ ruborg validate --config ruborg.yml
188
+ ```
189
+
190
+ **Validation checks:**
191
+ - Boolean types (must be `true` or `false`, not strings like `'true'`)
192
+ - Valid compression values (lz4, zstd, zlib, lzma, none)
193
+ - Valid encryption modes
194
+ - Required repository fields (name, path)
195
+ - Correct borg_options values
196
+
197
+ **Example validation output:**
198
+
199
+ ```
200
+ ✓ Configuration is valid
201
+ No type errors or warnings found
202
+ ```
203
+
204
+ Or with errors:
205
+
206
+ ```
207
+ ❌ ERRORS FOUND (2):
208
+ - global/auto_init: must be boolean (true or false), got String: "true"
209
+ - test-repo/allow_remove_source: must be boolean (true or false), got Integer: 1
210
+
211
+ Configuration has errors that must be fixed.
212
+ ```
213
+
174
214
  ## Usage
175
215
 
176
216
  ### Initialize a Repository
@@ -198,10 +238,48 @@ ruborg backup --repository databases --name "db-backup-2025-10-05"
198
238
  # Using custom configuration file
199
239
  ruborg backup --config /path/to/config.yml --repository documents
200
240
 
201
- # Remove source files after successful backup
241
+ # Remove source files after successful backup (requires allow_remove_source: true)
202
242
  ruborg backup --repository documents --remove-source
203
243
  ```
204
244
 
245
+ **IMPORTANT: Source File Deletion Safety**
246
+
247
+ The `--remove-source` option is disabled by default for safety. To use it, you must explicitly enable it in your configuration:
248
+
249
+ ```yaml
250
+ # Global setting - applies to all repositories
251
+ allow_remove_source: true
252
+
253
+ # OR per-repository setting
254
+ repositories:
255
+ - name: temp-backups
256
+ allow_remove_source: true # Only for this repository
257
+ ...
258
+ ```
259
+
260
+ **⚠️ TYPE SAFETY WARNING:** The value MUST be a boolean `true`, not a string:
261
+
262
+ ```yaml
263
+ # ✅ CORRECT - Boolean true
264
+ allow_remove_source: true
265
+
266
+ # ❌ WRONG - String 'true' (will be rejected)
267
+ allow_remove_source: 'true'
268
+ allow_remove_source: "true"
269
+
270
+ # ❌ WRONG - Other truthy values (will be rejected)
271
+ allow_remove_source: 1
272
+ allow_remove_source: yes
273
+ ```
274
+
275
+ Ruborg uses strict type checking to prevent configuration errors. Only the boolean value `true` (unquoted) will enable source deletion. Any other value, including string `'true'` or `"true"`, will be rejected with a detailed error message showing the actual type received.
276
+
277
+ Without `allow_remove_source: true` configured, using `--remove-source` will result in an error:
278
+ ```
279
+ Error: Cannot use --remove-source: 'allow_remove_source' must be true (boolean).
280
+ Current value: "true" (String). Set 'allow_remove_source: true' in configuration.
281
+ ```
282
+
205
283
  ### List Archives
206
284
 
207
285
  ```bash
@@ -437,6 +515,7 @@ See [SECURITY.md](SECURITY.md) for detailed security information and best practi
437
515
  | Command | Description | Options |
438
516
  |---------|-------------|---------|
439
517
  | `init REPOSITORY` | Initialize a new Borg repository | `--passphrase`, `--passbolt-id`, `--log` |
518
+ | `validate` | Validate configuration file for type errors | `--config`, `--log` |
440
519
  | `backup` | Create a backup using config file | `--config`, `--repository`, `--all`, `--name`, `--remove-source`, `--log` |
441
520
  | `list` | List all archives in repository | `--config`, `--repository`, `--log` |
442
521
  | `restore ARCHIVE` | Restore files from archive | `--config`, `--repository`, `--destination`, `--path`, `--log` |
data/SECURITY.md CHANGED
@@ -66,6 +66,15 @@ Ruborg implements several security measures to protect your backup operations:
66
66
  - Integrated into development workflow
67
67
  - Run: `bundle exec bundle-audit check`
68
68
 
69
+ ### 13. Boolean Type Safety (Type Confusion Protection)
70
+ - **Critical safety flag validation** for `allow_remove_source` configuration
71
+ - Uses strict type checking (`is_a?(TrueClass)`) to prevent type confusion attacks
72
+ - Rejects truthy values like strings `'true'`, `"false"`, integers `1`, or `"yes"`
73
+ - Only boolean `true` enables dangerous operations like `--remove-source`
74
+ - Provides detailed error messages showing actual type received vs expected
75
+ - Prevents configuration errors that could lead to unintended data loss
76
+ - **CWE-843 Mitigation**: Protects against type confusion vulnerabilities
77
+
69
78
  ## Security Best Practices
70
79
 
71
80
  ### When Using `--remove-source`
@@ -76,6 +85,16 @@ Ruborg implements several security measures to protect your backup operations:
76
85
  2. **Never use on symlinks** to critical system directories
77
86
  3. **Verify backups** before using this flag in production
78
87
  4. **Use absolute paths** in configuration to avoid ambiguity
88
+ 5. **Use boolean values** for `allow_remove_source` - NEVER use quoted strings:
89
+ ```yaml
90
+ # ✅ CORRECT
91
+ allow_remove_source: true
92
+
93
+ # ❌ WRONG - Will be rejected
94
+ allow_remove_source: 'true'
95
+ allow_remove_source: "true"
96
+ allow_remove_source: 1
97
+ ```
79
98
 
80
99
  ### Configuration File Security
81
100
 
@@ -144,6 +163,19 @@ We will respond within 48 hours and work with you to address the issue.
144
163
 
145
164
  ## Security Audit History
146
165
 
166
+ - **v0.6.0** (2025-10-08): Configuration validation and type confusion protection
167
+ - **SECURITY FIX**: Implemented strict boolean type checking for `allow_remove_source`
168
+ - Prevents type confusion attacks (CWE-843) where string values bypass safety checks
169
+ - Added configuration validation command (`ruborg validate`) for proactive error detection
170
+ - Automatic schema validation on config load catches type errors early
171
+ - Added 10 comprehensive test cases for validation and type confusion scenarios
172
+ - Enhanced error messages to show actual type vs expected type
173
+ - Updated documentation with type safety warnings and examples
174
+
175
+ - **v0.5.0** (2025-10-08): Hostname validation
176
+ - Added hostname validation feature (optional global or per-repository)
177
+ - Prevents accidental execution of backups on wrong machines
178
+
147
179
  - **v0.4.0** (2025-10-06): Complete command injection elimination
148
180
  - **CRITICAL**: Fixed all remaining command injection vulnerabilities in repository.rb
149
181
  - Replaced all backtick execution with Open3.capture3/capture2e methods
@@ -210,3 +242,5 @@ Before deploying ruborg in production:
210
242
  - [ ] Configure borg_options for your security requirements
211
243
  - [ ] Use default archive names or sanitized custom names only
212
244
  - [ ] Ensure backup paths don't contain empty or nil values
245
+ - [ ] Use boolean `true` (not strings) for `allow_remove_source` configuration
246
+ - [ ] Configure hostname validation for multi-server environments
data/lib/ruborg/cli.rb CHANGED
@@ -81,7 +81,9 @@ module Ruborg
81
81
  repo = Repository.new(repo_config["path"], passphrase: passphrase, borg_options: borg_opts, borg_path: borg_path)
82
82
 
83
83
  # Auto-initialize repository if configured
84
- auto_init = merged_config["auto_init"] || false
84
+ # Use strict boolean checking: only true enables, everything else disables
85
+ auto_init = merged_config["auto_init"]
86
+ auto_init = false unless auto_init == true
85
87
  if auto_init && !repo.exists?
86
88
  @logger.info("Auto-initializing repository at #{repo_config["path"]}")
87
89
  repo.create
@@ -157,7 +159,9 @@ module Ruborg
157
159
  repo = Repository.new(repo_config["path"], passphrase: passphrase, borg_options: borg_opts, borg_path: borg_path)
158
160
 
159
161
  # Auto-initialize repository if configured
160
- auto_init = merged_config["auto_init"] || false
162
+ # Use strict boolean checking: only true enables, everything else disables
163
+ auto_init = merged_config["auto_init"]
164
+ auto_init = false unless auto_init == true
161
165
  if auto_init && !repo.exists?
162
166
  @logger.info("Auto-initializing repository at #{repo_config["path"]}")
163
167
  repo.create
@@ -171,6 +175,78 @@ module Ruborg
171
175
  error_exit(e)
172
176
  end
173
177
 
178
+ desc "validate", "Validate configuration file for errors and type issues"
179
+ def validate_config
180
+ @logger.info("Validating configuration file: #{options[:config]}")
181
+ config = Config.new(options[:config])
182
+
183
+ puts "\n═══════════════════════════════════════════════════════════════"
184
+ puts " CONFIGURATION VALIDATION"
185
+ puts "═══════════════════════════════════════════════════════════════\n\n"
186
+
187
+ errors = []
188
+ warnings = []
189
+
190
+ # Validate global boolean settings
191
+ global_settings = config.global_settings
192
+ errors.concat(validate_boolean_setting(global_settings, "auto_init", "global"))
193
+ errors.concat(validate_boolean_setting(global_settings, "auto_prune", "global"))
194
+ errors.concat(validate_boolean_setting(global_settings, "allow_remove_source", "global"))
195
+
196
+ # Validate borg_options booleans
197
+ if global_settings["borg_options"]
198
+ warnings.concat(validate_borg_option(global_settings["borg_options"], "allow_relocated_repo", "global"))
199
+ warnings.concat(validate_borg_option(global_settings["borg_options"], "allow_unencrypted_repo", "global"))
200
+ end
201
+
202
+ # Validate per-repository settings
203
+ config.repositories.each do |repo|
204
+ repo_name = repo["name"]
205
+ errors.concat(validate_boolean_setting(repo, "auto_init", repo_name))
206
+ errors.concat(validate_boolean_setting(repo, "auto_prune", repo_name))
207
+ errors.concat(validate_boolean_setting(repo, "allow_remove_source", repo_name))
208
+
209
+ if repo["borg_options"]
210
+ warnings.concat(validate_borg_option(repo["borg_options"], "allow_relocated_repo", repo_name))
211
+ warnings.concat(validate_borg_option(repo["borg_options"], "allow_unencrypted_repo", repo_name))
212
+ end
213
+ end
214
+
215
+ # Display results
216
+ if errors.empty? && warnings.empty?
217
+ puts "✓ Configuration is valid"
218
+ puts " No type errors or warnings found\n\n"
219
+ else
220
+ unless errors.empty?
221
+ puts "❌ ERRORS FOUND (#{errors.size}):"
222
+ errors.each do |error|
223
+ puts " - #{error}"
224
+ end
225
+ puts ""
226
+ end
227
+
228
+ unless warnings.empty?
229
+ puts "⚠️ WARNINGS (#{warnings.size}):"
230
+ warnings.each do |warning|
231
+ puts " - #{warning}"
232
+ end
233
+ puts ""
234
+ end
235
+
236
+ if errors.any?
237
+ puts "Configuration has errors that must be fixed.\n\n"
238
+ exit 1
239
+ else
240
+ puts "Configuration is valid but has warnings.\n\n"
241
+ end
242
+ end
243
+
244
+ @logger.info("Configuration validation completed")
245
+ rescue Error => e
246
+ @logger.error("Validation failed: #{e.message}")
247
+ error_exit(e)
248
+ end
249
+
174
250
  desc "check", "Check repository integrity and compatibility"
175
251
  option :verify_data, type: :boolean, default: false, desc: "Verify repository data (slower)"
176
252
  option :all, type: :boolean, default: false, desc: "Check all repositories"
@@ -404,7 +480,9 @@ module Ruborg
404
480
  repo = Repository.new(repo_config["path"], passphrase: passphrase, borg_options: borg_opts, borg_path: borg_path)
405
481
 
406
482
  # Auto-initialize if configured
407
- auto_init = merged_config["auto_init"] || false
483
+ # Use strict boolean checking: only true enables, everything else disables
484
+ auto_init = merged_config["auto_init"]
485
+ auto_init = false unless auto_init == true
408
486
  if auto_init && !repo.exists?
409
487
  @logger.info("Auto-initializing repository at #{repo_config["path"]}")
410
488
  repo.create
@@ -414,6 +492,17 @@ module Ruborg
414
492
  # Get retention mode (defaults to standard)
415
493
  retention_mode = merged_config["retention_mode"] || "standard"
416
494
 
495
+ # Validate remove_source permission with strict type checking
496
+ if options[:remove_source]
497
+ allow_remove_source = merged_config["allow_remove_source"]
498
+ unless allow_remove_source.is_a?(TrueClass)
499
+ raise ConfigError,
500
+ "Cannot use --remove-source: 'allow_remove_source' must be true (boolean). " \
501
+ "Current value: #{allow_remove_source.inspect} (#{allow_remove_source.class}). " \
502
+ "Set 'allow_remove_source: true' in configuration to allow source deletion."
503
+ end
504
+ end
505
+
417
506
  # Create backup config wrapper
418
507
  backup_config = BackupConfig.new(repo_config, merged_config)
419
508
  backup = Backup.new(repo, config: backup_config, retention_mode: retention_mode, repo_name: repo_name)
@@ -435,7 +524,9 @@ module Ruborg
435
524
  puts " Sources removed" if options[:remove_source]
436
525
 
437
526
  # Auto-prune if configured and retention policy exists
438
- auto_prune = merged_config["auto_prune"] || false
527
+ # Use strict boolean checking: only true enables, everything else disables
528
+ auto_prune = merged_config["auto_prune"]
529
+ auto_prune = false unless auto_prune == true
439
530
  retention_policy = merged_config["retention"]
440
531
 
441
532
  return unless auto_prune && retention_policy && !retention_policy.empty?
@@ -480,6 +571,34 @@ module Ruborg
480
571
  "but current hostname is '#{current_hostname}'"
481
572
  end
482
573
 
574
+ # Validate boolean configuration settings
575
+ def validate_boolean_setting(config, key, context)
576
+ errors = []
577
+ value = config[key]
578
+
579
+ return errors if value.nil? # Not set is OK
580
+
581
+ unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
582
+ errors << "#{context}/#{key}: must be boolean (true/false), got #{value.class}: #{value.inspect}"
583
+ end
584
+
585
+ errors
586
+ end
587
+
588
+ # Validate borg_options boolean settings (these have different defaults)
589
+ def validate_borg_option(borg_options, key, context)
590
+ warnings = []
591
+ value = borg_options[key]
592
+
593
+ return warnings if value.nil? # Not set is OK (uses default)
594
+
595
+ unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
596
+ warnings << "#{context}/borg_options/#{key}: should be boolean (true/false), got #{value.class}: #{value.inspect}"
597
+ end
598
+
599
+ warnings
600
+ end
601
+
483
602
  # Wrapper class to adapt repository config to existing Backup class
484
603
  class BackupConfig
485
604
  def initialize(repo_config, merged_settings)
data/lib/ruborg/config.rb CHANGED
@@ -9,10 +9,12 @@ module Ruborg
9
9
  class Config
10
10
  attr_reader :data
11
11
 
12
- def initialize(config_path)
12
+ def initialize(config_path, validate_types: true)
13
13
  @config_path = config_path
14
+ @validate_types = validate_types
14
15
  load_config
15
16
  validate_format
17
+ validate_schema if @validate_types
16
18
  end
17
19
 
18
20
  def load_config
@@ -39,7 +41,7 @@ module Ruborg
39
41
 
40
42
  def global_settings
41
43
  @data.slice("passbolt", "compression", "encryption", "auto_init", "borg_options", "log_file", "retention",
42
- "auto_prune", "hostname")
44
+ "auto_prune", "hostname", "allow_remove_source")
43
45
  end
44
46
 
45
47
  private
@@ -84,5 +86,78 @@ module Ruborg
84
86
 
85
87
  patterns
86
88
  end
89
+
90
+ # Validate YAML schema for type correctness
91
+ def validate_schema
92
+ errors = []
93
+
94
+ # Validate global boolean settings
95
+ errors.concat(validate_boolean_config(@data, "auto_init", "global"))
96
+ errors.concat(validate_boolean_config(@data, "auto_prune", "global"))
97
+ errors.concat(validate_boolean_config(@data, "allow_remove_source", "global"))
98
+
99
+ # Validate global borg_options
100
+ if @data["borg_options"]
101
+ errors.concat(validate_boolean_config(@data["borg_options"], "allow_relocated_repo", "global/borg_options"))
102
+ errors.concat(validate_boolean_config(@data["borg_options"], "allow_unencrypted_repo", "global/borg_options"))
103
+ end
104
+
105
+ # Validate compression and encryption if present
106
+ if @data["compression"] && !VALID_COMPRESSION.include?(@data["compression"])
107
+ errors << "global/compression: invalid value '#{@data["compression"]}'"
108
+ end
109
+
110
+ if @data["encryption"] && !VALID_ENCRYPTION.include?(@data["encryption"])
111
+ errors << "global/encryption: invalid value '#{@data["encryption"]}'"
112
+ end
113
+
114
+ # Validate per-repository settings
115
+ repositories.each do |repo|
116
+ repo_name = repo["name"] || "unnamed"
117
+
118
+ errors.concat(validate_boolean_config(repo, "auto_init", repo_name))
119
+ errors.concat(validate_boolean_config(repo, "auto_prune", repo_name))
120
+ errors.concat(validate_boolean_config(repo, "allow_remove_source", repo_name))
121
+
122
+ if repo["borg_options"]
123
+ errors.concat(validate_boolean_config(repo["borg_options"], "allow_relocated_repo",
124
+ "#{repo_name}/borg_options"))
125
+ errors.concat(validate_boolean_config(repo["borg_options"], "allow_unencrypted_repo",
126
+ "#{repo_name}/borg_options"))
127
+ end
128
+
129
+ # Validate compression and encryption if present
130
+ if repo["compression"] && !VALID_COMPRESSION.include?(repo["compression"])
131
+ errors << "#{repo_name}/compression: invalid value '#{repo["compression"]}'"
132
+ end
133
+
134
+ if repo["encryption"] && !VALID_ENCRYPTION.include?(repo["encryption"])
135
+ errors << "#{repo_name}/encryption: invalid value '#{repo["encryption"]}'"
136
+ end
137
+
138
+ # Validate repository structure
139
+ errors << "#{repo_name}: missing 'path' key" unless repo["path"]
140
+ errors << "#{repo_name}: 'sources' must be an array" if repo["sources"] && !repo["sources"].is_a?(Array)
141
+ end
142
+
143
+ return if errors.empty?
144
+
145
+ raise ConfigError,
146
+ "Configuration validation failed:\n - #{errors.join("\n - ")}\n\n" \
147
+ "Run 'ruborg validate' for detailed validation information."
148
+ end
149
+
150
+ def validate_boolean_config(config, key, context)
151
+ errors = []
152
+ value = config[key]
153
+
154
+ return errors if value.nil? # Not set is OK
155
+
156
+ unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
157
+ errors << "#{context}/#{key}: must be boolean (true or false), got #{value.class}: #{value.inspect}"
158
+ end
159
+
160
+ errors
161
+ end
87
162
  end
88
163
  end
@@ -189,8 +189,12 @@ module Ruborg
189
189
  env = {}
190
190
  env["BORG_PASSPHRASE"] = @passphrase if @passphrase
191
191
 
192
+ # Use strict boolean checking (only true/false allowed, default to true for backward compatibility)
192
193
  allow_relocated = @borg_options.fetch("allow_relocated_repo", true)
194
+ allow_relocated = true unless allow_relocated == false # Normalize: only false disables, everything else enables
195
+
193
196
  allow_unencrypted = @borg_options.fetch("allow_unencrypted_repo", true)
197
+ allow_unencrypted = true unless allow_unencrypted == false # Normalize: only false disables, everything else enables
194
198
 
195
199
  env["BORG_RELOCATED_REPO_ACCESS_IS_OK"] = allow_relocated ? "yes" : "no"
196
200
  env["BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK"] = allow_unencrypted ? "yes" : "no"
@@ -327,8 +331,12 @@ module Ruborg
327
331
  env["BORG_PASSPHRASE"] = @passphrase if @passphrase
328
332
 
329
333
  # Apply Borg environment options from config (defaults to yes for backward compatibility)
334
+ # Use strict boolean checking (only true/false allowed, default to true for backward compatibility)
330
335
  allow_relocated = @borg_options.fetch("allow_relocated_repo", true)
336
+ allow_relocated = true unless allow_relocated == false # Normalize: only false disables, everything else enables
337
+
331
338
  allow_unencrypted = @borg_options.fetch("allow_unencrypted_repo", true)
339
+ allow_unencrypted = true unless allow_unencrypted == false # Normalize: only false disables, everything else enables
332
340
 
333
341
  env["BORG_RELOCATED_REPO_ACCESS_IS_OK"] = allow_relocated ? "yes" : "no"
334
342
  env["BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK"] = allow_unencrypted ? "yes" : "no"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ruborg
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/ruborg.gemspec ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/ruborg/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "ruborg"
7
+ spec.version = Ruborg::VERSION
8
+ spec.authors = ["Michail Pantelelis"]
9
+ spec.email = ["mpantel@aegean.gr"]
10
+
11
+ spec.summary = "A friendly Ruby frontend for Borg backup"
12
+ spec.description = "Ruborg provides a user-friendly interface to Borg backup. " \
13
+ "It reads YAML configuration files and orchestrates backup operations, " \
14
+ "supporting repository creation, backup management, and Passbolt integration."
15
+ spec.homepage = "https://github.com/mpantel/ruborg"
16
+ spec.license = "MIT"
17
+ spec.required_ruby_version = ">= 3.2.0"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
21
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
22
+ spec.metadata["rubygems_mfa_required"] = "true"
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
29
+ end
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ # Dependencies
36
+ spec.add_dependency "psych", "~> 5.0"
37
+ spec.add_dependency "thor", "~> 1.3"
38
+
39
+ # Development dependencies
40
+ spec.add_development_dependency "bundler", "~> 2.0"
41
+ spec.add_development_dependency "bundler-audit", "~> 0.9"
42
+ spec.add_development_dependency "rake", "~> 13.0"
43
+ spec.add_development_dependency "rspec", "~> 3.0"
44
+ spec.add_development_dependency "rubocop", "~> 1.0"
45
+ spec.add_development_dependency "rubocop-rspec", "~> 3.0"
46
+ end
data/ruborg.yml.example CHANGED
@@ -11,6 +11,7 @@ compression: lz4 # Options: lz4 (fast), zstd (balanced), lzma (high com
11
11
  encryption: repokey # Options: repokey, keyfile, none (NOT recommended)
12
12
  auto_init: true # Automatically initialize repositories on first use
13
13
  auto_prune: true # Automatically prune old backups after each backup
14
+ allow_remove_source: false # Allow --remove-source flag to delete source files after backup (default: false)
14
15
  log_file: ~/.ruborg/logs/ruborg.log # Log file path (optional)
15
16
 
16
17
  # Custom Borg executable path (optional)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruborg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michail Pantelelis
@@ -148,6 +148,7 @@ files:
148
148
  - lib/ruborg/passbolt.rb
149
149
  - lib/ruborg/repository.rb
150
150
  - lib/ruborg/version.rb
151
+ - ruborg.gemspec
151
152
  - ruborg.yml.example
152
153
  homepage: https://github.com/mpantel/ruborg
153
154
  licenses: