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 +4 -4
 - data/CHANGELOG.md +28 -0
 - data/README.md +83 -4
 - data/SECURITY.md +34 -0
 - data/lib/ruborg/cli.rb +123 -4
 - data/lib/ruborg/config.rb +77 -2
 - data/lib/ruborg/repository.rb +8 -0
 - data/lib/ruborg/version.rb +1 -1
 - data/ruborg.gemspec +46 -0
 - data/ruborg.yml.example +1 -0
 - metadata +2 -1
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 821f1707180870ec6b2ffccda4ef4d901b3ee274d7402c26170ca294e80efb8b
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 62e2a5ee53cd75024b78e08e32295c34cf1829cd3bd34be9d6403c7ec1dfb810
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 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 ( 
     | 
| 
      
 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 
     | 
    
         
            -
            - ** 
     | 
| 
       168 
     | 
    
         
            -
            - ** 
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
      
 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
         
     | 
    
        data/lib/ruborg/repository.rb
    CHANGED
    
    | 
         @@ -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"
         
     | 
    
        data/lib/ruborg/version.rb
    CHANGED
    
    
    
        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. 
     | 
| 
      
 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:
         
     |