ruborg 0.8.1 → 0.9.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 +33 -0
 - data/README.md +72 -17
 - data/lib/ruborg/backup.rb +31 -15
 - data/lib/ruborg/cli.rb +128 -80
 - data/lib/ruborg/config.rb +7 -5
 - data/lib/ruborg/repository.rb +15 -0
 - data/lib/ruborg/version.rb +1 -1
 - metadata +6 -3
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: a4d69dff5edaf281b1014e64653462df7d7af84be0f341db0c4fe389978f25b1
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: b206422e04dab022cd19a8d4ea6f9bd399988fc27b6e810fd673dfb7de0fb9ba
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: f881b5b0908afaa16729339263d1584d861cd3a3d6b613599cbdb120340a86a2dc69408dc3f630d347769b990ea6c36ced1b538d17ba1517bebfb347c5c9ef2b
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: ffd3ee5bd76b08ac9753d66ae95ac1aebaed2f4ba6db38ddc720e013970d2799a99202e4cf7295f969dd93ddf4f8b8b7df6d8730352a33f8ff86ce76eecbaff6
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 
     | 
|
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
            ## [Unreleased]
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
      
 10 
     | 
    
         
            +
            ## [0.9.0] - 2025-10-14
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            ### Changed
         
     | 
| 
      
 13 
     | 
    
         
            +
            - **Command Consolidation**: Unified validation commands for consistency
         
     | 
| 
      
 14 
     | 
    
         
            +
              - `ruborg validate` renamed to `ruborg validate config` (validates YAML configuration)
         
     | 
| 
      
 15 
     | 
    
         
            +
              - `ruborg check` renamed to `ruborg validate repo` (validates repository compatibility and integrity)
         
     | 
| 
      
 16 
     | 
    
         
            +
              - Both commands now use consistent `validate` terminology
         
     | 
| 
      
 17 
     | 
    
         
            +
              - `--verify-data` option remains available for `validate repo` to run full integrity checks
         
     | 
| 
      
 18 
     | 
    
         
            +
              - Eliminates confusion between `validate` (config) and `check` (repository)
         
     | 
| 
      
 19 
     | 
    
         
            +
              - Updated README with new command syntax and examples
         
     | 
| 
      
 20 
     | 
    
         
            +
              - Updated all tests to use new command format
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            ### Added
         
     | 
| 
      
 23 
     | 
    
         
            +
            - **Skip Hash Check Option**: New `skip_hash_check` configuration option for faster per-file backups
         
     | 
| 
      
 24 
     | 
    
         
            +
              - Skips expensive SHA256 content hash calculation when file already exists
         
     | 
| 
      
 25 
     | 
    
         
            +
              - Trusts file path, size, and modification time matching for duplicate detection
         
     | 
| 
      
 26 
     | 
    
         
            +
              - Significantly speeds up backups with many unchanged files
         
     | 
| 
      
 27 
     | 
    
         
            +
              - Configurable globally or per-repository
         
     | 
| 
      
 28 
     | 
    
         
            +
              - Default: `false` (paranoid mode - always verify content hash)
         
     | 
| 
      
 29 
     | 
    
         
            +
              - Use case: Large directories where mtime changes are reliable (most filesystems)
         
     | 
| 
      
 30 
     | 
    
         
            +
              - Example: `skip_hash_check: true` in YAML configuration
         
     | 
| 
      
 31 
     | 
    
         
            +
            - **Migration Help**: `ruborg check` now displays a helpful deprecation notice
         
     | 
| 
      
 32 
     | 
    
         
            +
              - Shows clear message explaining the command has been renamed
         
     | 
| 
      
 33 
     | 
    
         
            +
              - Provides examples of the new `ruborg validate repo` syntax
         
     | 
| 
      
 34 
     | 
    
         
            +
              - Exits with error to prevent confusion
         
     | 
| 
      
 35 
     | 
    
         
            +
              - Logs deprecation warning for audit trail
         
     | 
| 
      
 36 
     | 
    
         
            +
            - **Enhanced Version Command**: `ruborg version` now shows both Ruborg and Borg versions with path
         
     | 
| 
      
 37 
     | 
    
         
            +
              - Displays Ruborg version (gem version)
         
     | 
| 
      
 38 
     | 
    
         
            +
              - Displays installed Borg version and executable path
         
     | 
| 
      
 39 
     | 
    
         
            +
              - Example output: `borg 1.2.8 (/usr/local/bin/borg)`
         
     | 
| 
      
 40 
     | 
    
         
            +
              - Gracefully handles missing Borg installation
         
     | 
| 
      
 41 
     | 
    
         
            +
              - Helps users verify both tool versions and location at a glance
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
       10 
43 
     | 
    
         
             
            ## [0.8.1] - 2025-10-09
         
     | 
| 
       11 
44 
     | 
    
         | 
| 
       12 
45 
     | 
    
         
             
            ### Added
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -25,7 +25,7 @@ A friendly Ruby frontend for [Borg Backup](https://www.borgbackup.org/). Ruborg 
     | 
|
| 
       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 (297 examples, 0 failures)
         
     | 
| 
       29 
29 
     | 
    
         
             
            - 🔒 **Security-focused** - Path validation, safe YAML loading, command injection protection
         
     | 
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
            ## Prerequisites
         
     | 
| 
         @@ -163,13 +163,14 @@ repositories: 
     | 
|
| 
       163 
163 
     | 
    
         | 
| 
       164 
164 
     | 
    
         
             
            **Configuration Features:**
         
     | 
| 
       165 
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
         
     | 
| 
      
 166 
     | 
    
         
            +
            - **Validation Command**: Run `ruborg validate config` to check configuration files for errors
         
     | 
| 
       167 
167 
     | 
    
         
             
            - **Descriptions**: Add `description` field to document each repository's purpose
         
     | 
| 
       168 
168 
     | 
    
         
             
            - **Hostname Validation**: Optional `hostname` field to restrict backups to specific hosts (global or per-repository)
         
     | 
| 
       169 
169 
     | 
    
         
             
            - **Source Deletion Safety**: `allow_remove_source` flag to explicitly enable `--remove-source` option (default: disabled)
         
     | 
| 
      
 170 
     | 
    
         
            +
            - **Skip Hash Check**: Optional `skip_hash_check` flag to skip content hash verification for faster backups (per-file mode only)
         
     | 
| 
       170 
171 
     | 
    
         
             
            - **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)
         
     | 
| 
      
 172 
     | 
    
         
            +
            - **Global Settings**: Hostname, compression, encryption, auto_init, allow_remove_source, skip_hash_check, log_file, borg_path, borg_options, and retention apply to all repositories
         
     | 
| 
      
 173 
     | 
    
         
            +
            - **Per-Repository Overrides**: Any global setting can be overridden at the repository level (including hostname, allow_remove_source, skip_hash_check, and custom borg_path)
         
     | 
| 
       173 
174 
     | 
    
         
             
            - **Custom Borg Path**: Specify a custom Borg executable path if borg is not in PATH or to use a specific version
         
     | 
| 
       174 
175 
     | 
    
         
             
            - **Retention Policies**: Define how many backups to keep (hourly, daily, weekly, monthly, yearly)
         
     | 
| 
       175 
176 
     | 
    
         
             
            - **Multiple Sources**: Each repository can have multiple backup sources with their own exclude patterns
         
     | 
| 
         @@ -184,7 +185,7 @@ Ruborg automatically validates your configuration on startup. All commands check 
     | 
|
| 
       184 
185 
     | 
    
         
             
            Check your configuration file for errors:
         
     | 
| 
       185 
186 
     | 
    
         | 
| 
       186 
187 
     | 
    
         
             
            ```bash
         
     | 
| 
       187 
     | 
    
         
            -
            ruborg validate --config ruborg.yml
         
     | 
| 
      
 188 
     | 
    
         
            +
            ruborg validate config --config ruborg.yml
         
     | 
| 
       188 
189 
     | 
    
         
             
            ```
         
     | 
| 
       189 
190 
     | 
    
         | 
| 
       190 
191 
     | 
    
         
             
            **Validation checks:**
         
     | 
| 
         @@ -471,20 +472,20 @@ Group: postgres 
     | 
|
| 
       471 
472 
     | 
    
         
             
            Type: regular file
         
     | 
| 
       472 
473 
     | 
    
         
             
            ```
         
     | 
| 
       473 
474 
     | 
    
         | 
| 
       474 
     | 
    
         
            -
            ###  
     | 
| 
      
 475 
     | 
    
         
            +
            ### Validate Repository Compatibility
         
     | 
| 
       475 
476 
     | 
    
         | 
| 
       476 
477 
     | 
    
         
             
            ```bash
         
     | 
| 
       477 
478 
     | 
    
         
             
            # Check specific repository compatibility with installed Borg version
         
     | 
| 
       478 
     | 
    
         
            -
            ruborg  
     | 
| 
      
 479 
     | 
    
         
            +
            ruborg validate repo --repository documents
         
     | 
| 
       479 
480 
     | 
    
         | 
| 
       480 
481 
     | 
    
         
             
            # Check all repositories
         
     | 
| 
       481 
     | 
    
         
            -
            ruborg  
     | 
| 
      
 482 
     | 
    
         
            +
            ruborg validate repo --all
         
     | 
| 
       482 
483 
     | 
    
         | 
| 
       483 
484 
     | 
    
         
             
            # Check with data integrity verification (slower)
         
     | 
| 
       484 
     | 
    
         
            -
            ruborg  
     | 
| 
      
 485 
     | 
    
         
            +
            ruborg validate repo --repository documents --verify-data
         
     | 
| 
       485 
486 
     | 
    
         
             
            ```
         
     | 
| 
       486 
487 
     | 
    
         | 
| 
       487 
     | 
    
         
            -
            The ` 
     | 
| 
      
 488 
     | 
    
         
            +
            The `validate repo` command verifies:
         
     | 
| 
       488 
489 
     | 
    
         
             
            - Installed Borg version
         
     | 
| 
       489 
490 
     | 
    
         
             
            - Repository format version
         
     | 
| 
       490 
491 
     | 
    
         
             
            - Compatibility between Borg and repository versions
         
     | 
| 
         @@ -494,11 +495,11 @@ The `check` command verifies: 
     | 
|
| 
       494 
495 
     | 
    
         
             
            ```
         
     | 
| 
       495 
496 
     | 
    
         
             
            Borg version: 1.2.8
         
     | 
| 
       496 
497 
     | 
    
         | 
| 
       497 
     | 
    
         
            -
            ---  
     | 
| 
      
 498 
     | 
    
         
            +
            --- Validating repository: documents ---
         
     | 
| 
       498 
499 
     | 
    
         
             
              Repository version: 1
         
     | 
| 
       499 
500 
     | 
    
         
             
              ✓ Compatible with Borg 1.2.8
         
     | 
| 
       500 
501 
     | 
    
         | 
| 
       501 
     | 
    
         
            -
            ---  
     | 
| 
      
 502 
     | 
    
         
            +
            --- Validating repository: databases ---
         
     | 
| 
       502 
503 
     | 
    
         
             
              Repository version: 2
         
     | 
| 
       503 
504 
     | 
    
         
             
              ✗ INCOMPATIBLE with Borg 1.2.8
         
     | 
| 
       504 
505 
     | 
    
         
             
                Repository version 2 cannot be read by Borg 1.2.8
         
     | 
| 
         @@ -652,26 +653,26 @@ See [SECURITY.md](SECURITY.md) for detailed security information and best practi 
     | 
|
| 
       652 
653 
     | 
    
         
             
            | Command | Description | Options |
         
     | 
| 
       653 
654 
     | 
    
         
             
            |---------|-------------|---------|
         
     | 
| 
       654 
655 
     | 
    
         
             
            | `init REPOSITORY` | Initialize a new Borg repository | `--passphrase`, `--passbolt-id`, `--log` |
         
     | 
| 
       655 
     | 
    
         
            -
            | `validate` | Validate configuration file for type errors | `--config`, `--log` |
         
     | 
| 
      
 656 
     | 
    
         
            +
            | `validate config` | Validate configuration file for type errors | `--config`, `--log` |
         
     | 
| 
      
 657 
     | 
    
         
            +
            | `validate repo` | Validate repository compatibility and integrity | `--config`, `--repository`, `--all`, `--verify-data`, `--log` |
         
     | 
| 
       656 
658 
     | 
    
         
             
            | `backup` | Create a backup using config file | `--config`, `--repository`, `--all`, `--name`, `--remove-source`, `--log` |
         
     | 
| 
       657 
659 
     | 
    
         
             
            | `list` | List archives or files in repository | `--config`, `--repository`, `--archive`, `--log` |
         
     | 
| 
       658 
660 
     | 
    
         
             
            | `restore ARCHIVE` | Restore files from archive | `--config`, `--repository`, `--destination`, `--path`, `--log` |
         
     | 
| 
       659 
661 
     | 
    
         
             
            | `metadata ARCHIVE` | Get file metadata from archive | `--config`, `--repository`, `--file`, `--log` |
         
     | 
| 
       660 
662 
     | 
    
         
             
            | `info` | Show repository information | `--config`, `--repository`, `--log` |
         
     | 
| 
       661 
     | 
    
         
            -
            | `check` | Check repository integrity and compatibility | `--config`, `--repository`, `--all`, `--verify-data`, `--log` |
         
     | 
| 
       662 
663 
     | 
    
         
             
            | `version` | Show ruborg version | None |
         
     | 
| 
       663 
664 
     | 
    
         | 
| 
       664 
665 
     | 
    
         
             
            ### Options
         
     | 
| 
       665 
666 
     | 
    
         | 
| 
       666 
667 
     | 
    
         
             
            - `--config`: Path to configuration file (default: `ruborg.yml`)
         
     | 
| 
       667 
668 
     | 
    
         
             
            - `--log`: Path to log file (overrides config, default: `~/.ruborg/logs/ruborg.log`)
         
     | 
| 
       668 
     | 
    
         
            -
            - `--repository` / `-r`: Repository name (optional for info, required for backup/list/restore/ 
     | 
| 
       669 
     | 
    
         
            -
            - `--all`: Process all repositories (backup and  
     | 
| 
      
 669 
     | 
    
         
            +
            - `--repository` / `-r`: Repository name (optional for info, required for backup/list/restore/validate repo unless --all)
         
     | 
| 
      
 670 
     | 
    
         
            +
            - `--all`: Process all repositories (backup and validate repo commands)
         
     | 
| 
       670 
671 
     | 
    
         
             
            - `--name`: Custom archive name (backup command only)
         
     | 
| 
       671 
672 
     | 
    
         
             
            - `--remove-source`: Remove source files after successful backup (backup command only)
         
     | 
| 
       672 
673 
     | 
    
         
             
            - `--destination`: Destination directory for restore (restore command only)
         
     | 
| 
       673 
674 
     | 
    
         
             
            - `--path`: Specific file or directory to restore (restore command only)
         
     | 
| 
       674 
     | 
    
         
            -
            - `--verify-data`: Run full data integrity check ( 
     | 
| 
      
 675 
     | 
    
         
            +
            - `--verify-data`: Run full data integrity check (validate repo command only, slower)
         
     | 
| 
       675 
676 
     | 
    
         | 
| 
       676 
677 
     | 
    
         
             
            ## Retention Policies
         
     | 
| 
       677 
678 
     | 
    
         | 
| 
         @@ -822,6 +823,60 @@ repositories: 
     | 
|
| 
       822 
823 
     | 
    
         | 
| 
       823 
824 
     | 
    
         
             
            **Backup vs Retention:** The per-file `retention_mode` only affects how archives are created and pruned. Traditional backup commands still work normally - you can list, restore, and check per-file archives just like standard archives.
         
     | 
| 
       824 
825 
     | 
    
         | 
| 
      
 826 
     | 
    
         
            +
            ### Skip Hash Check for Faster Backups
         
     | 
| 
      
 827 
     | 
    
         
            +
             
     | 
| 
      
 828 
     | 
    
         
            +
            **NEW:** In per-file backup mode, you can optionally skip content hash verification for faster duplicate detection:
         
     | 
| 
      
 829 
     | 
    
         
            +
             
     | 
| 
      
 830 
     | 
    
         
            +
            ```yaml
         
     | 
| 
      
 831 
     | 
    
         
            +
            repositories:
         
     | 
| 
      
 832 
     | 
    
         
            +
              - name: project-files
         
     | 
| 
      
 833 
     | 
    
         
            +
                path: /mnt/backup/project-files
         
     | 
| 
      
 834 
     | 
    
         
            +
                retention_mode: per_file
         
     | 
| 
      
 835 
     | 
    
         
            +
                skip_hash_check: true    # Skip SHA256 content hash verification
         
     | 
| 
      
 836 
     | 
    
         
            +
                sources:
         
     | 
| 
      
 837 
     | 
    
         
            +
                  - name: projects
         
     | 
| 
      
 838 
     | 
    
         
            +
                    paths:
         
     | 
| 
      
 839 
     | 
    
         
            +
                      - /home/user/projects
         
     | 
| 
      
 840 
     | 
    
         
            +
            ```
         
     | 
| 
      
 841 
     | 
    
         
            +
             
     | 
| 
      
 842 
     | 
    
         
            +
            **How it works:**
         
     | 
| 
      
 843 
     | 
    
         
            +
            - **Default (paranoid mode)**: Ruborg calculates SHA256 hash of file content to verify files haven't changed (even when size and mtime are identical)
         
     | 
| 
      
 844 
     | 
    
         
            +
            - **With skip_hash_check: true**: Ruborg trusts file path, size, and modification time for duplicate detection (skips hash calculation)
         
     | 
| 
      
 845 
     | 
    
         
            +
             
     | 
| 
      
 846 
     | 
    
         
            +
            **When to use:**
         
     | 
| 
      
 847 
     | 
    
         
            +
            - ✅ **Large directories** with thousands of files where hash calculation is slow
         
     | 
| 
      
 848 
     | 
    
         
            +
            - ✅ **Reliable filesystems** where modification time changes are trustworthy
         
     | 
| 
      
 849 
     | 
    
         
            +
            - ✅ **Regular backups** where files are unlikely to be manually modified with `touch -t`
         
     | 
| 
      
 850 
     | 
    
         
            +
             
     | 
| 
      
 851 
     | 
    
         
            +
            **When NOT to use:**
         
     | 
| 
      
 852 
     | 
    
         
            +
            - ❌ **Security-critical data** where you want maximum verification
         
     | 
| 
      
 853 
     | 
    
         
            +
            - ❌ **Untrusted sources** where files might be tampered with
         
     | 
| 
      
 854 
     | 
    
         
            +
            - ❌ **Systems with unreliable mtime** (rare, but some network filesystems)
         
     | 
| 
      
 855 
     | 
    
         
            +
             
     | 
| 
      
 856 
     | 
    
         
            +
            **Performance impact:**
         
     | 
| 
      
 857 
     | 
    
         
            +
            ```yaml
         
     | 
| 
      
 858 
     | 
    
         
            +
            # Example: 10,000 unchanged files, average 50KB each
         
     | 
| 
      
 859 
     | 
    
         
            +
            # With skip_hash_check: false (default) - ~30 seconds (read + hash all files)
         
     | 
| 
      
 860 
     | 
    
         
            +
            # With skip_hash_check: true            - ~3 seconds  (read metadata only)
         
     | 
| 
      
 861 
     | 
    
         
            +
            ```
         
     | 
| 
      
 862 
     | 
    
         
            +
             
     | 
| 
      
 863 
     | 
    
         
            +
            **Console output:**
         
     | 
| 
      
 864 
     | 
    
         
            +
            ```
         
     | 
| 
      
 865 
     | 
    
         
            +
            # With skip_hash_check: true
         
     | 
| 
      
 866 
     | 
    
         
            +
            [1/10000] Backing up: /home/user/file1.txt - Archive already exists (skipped hash check)
         
     | 
| 
      
 867 
     | 
    
         
            +
            [2/10000] Backing up: /home/user/file2.txt - Archive already exists (skipped hash check)
         
     | 
| 
      
 868 
     | 
    
         
            +
            ...
         
     | 
| 
      
 869 
     | 
    
         
            +
            ✓ Per-file backup completed: 50 file(s) backed up, 9950 skipped (hash check skipped)
         
     | 
| 
      
 870 
     | 
    
         
            +
             
     | 
| 
      
 871 
     | 
    
         
            +
            # With skip_hash_check: false (default)
         
     | 
| 
      
 872 
     | 
    
         
            +
            [1/10000] Backing up: /home/user/file1.txt - Archive already exists (file unchanged)
         
     | 
| 
      
 873 
     | 
    
         
            +
            [2/10000] Backing up: /home/user/file2.txt - Archive already exists (file unchanged)
         
     | 
| 
      
 874 
     | 
    
         
            +
            ...
         
     | 
| 
      
 875 
     | 
    
         
            +
            ✓ Per-file backup completed: 50 file(s) backed up, 9950 skipped (unchanged)
         
     | 
| 
      
 876 
     | 
    
         
            +
            ```
         
     | 
| 
      
 877 
     | 
    
         
            +
             
     | 
| 
      
 878 
     | 
    
         
            +
            **Security note:** Even with `skip_hash_check: true`, files are still verified by path, size, and mtime. The only difference is skipping the SHA256 content hash verification, which catches rare edge cases like manual file tampering with preserved timestamps.
         
     | 
| 
      
 879 
     | 
    
         
            +
             
     | 
| 
       825 
880 
     | 
    
         
             
            ### Automatic Pruning
         
     | 
| 
       826 
881 
     | 
    
         | 
| 
       827 
882 
     | 
    
         
             
            Enable **automatic pruning** to remove old backups after each backup operation:
         
     | 
    
        data/lib/ruborg/backup.rb
    CHANGED
    
    | 
         @@ -3,12 +3,13 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            module Ruborg
         
     | 
| 
       4 
4 
     | 
    
         
             
              # Backup operations using Borg
         
     | 
| 
       5 
5 
     | 
    
         
             
              class Backup
         
     | 
| 
       6 
     | 
    
         
            -
                def initialize(repository, config:, retention_mode: "standard", repo_name: nil, logger: nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(repository, config:, retention_mode: "standard", repo_name: nil, logger: nil, skip_hash_check: false)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  @repository = repository
         
     | 
| 
       8 
8 
     | 
    
         
             
                  @config = config
         
     | 
| 
       9 
9 
     | 
    
         
             
                  @retention_mode = retention_mode
         
     | 
| 
       10 
10 
     | 
    
         
             
                  @repo_name = repo_name
         
     | 
| 
       11 
11 
     | 
    
         
             
                  @logger = logger
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @skip_hash_check = skip_hash_check
         
     | 
| 
       12 
13 
     | 
    
         
             
                end
         
     | 
| 
       13 
14 
     | 
    
         | 
| 
       14 
15 
     | 
    
         
             
                def create(name: nil, remove_source: false)
         
     | 
| 
         @@ -89,15 +90,13 @@ module Ruborg 
     | 
|
| 
       89 
90 
     | 
    
         
             
                        stored_size = stored_info[:size]
         
     | 
| 
       90 
91 
     | 
    
         | 
| 
       91 
92 
     | 
    
         
             
                        if current_size == stored_size
         
     | 
| 
       92 
     | 
    
         
            -
                          # Size same -> verify content hasn't changed (paranoid mode)
         
     | 
| 
       93 
     | 
    
         
            -
                           
     | 
| 
       94 
     | 
    
         
            -
             
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
                          if current_hash == stored_hash
         
     | 
| 
       97 
     | 
    
         
            -
                            # Content truly unchanged - file is already safely backed up
         
     | 
| 
       98 
     | 
    
         
            -
                            puts " - Archive already exists (file unchanged)"
         
     | 
| 
      
 93 
     | 
    
         
            +
                          # Size same -> verify content hasn't changed (paranoid mode) unless skip_hash_check is enabled
         
     | 
| 
      
 94 
     | 
    
         
            +
                          if @skip_hash_check
         
     | 
| 
      
 95 
     | 
    
         
            +
                            # Skip hash check - assume file is unchanged based on size and mtime
         
     | 
| 
      
 96 
     | 
    
         
            +
                            puts " - Archive already exists (skipped hash check)"
         
     | 
| 
       99 
97 
     | 
    
         
             
                            @logger&.info(
         
     | 
| 
       100 
     | 
    
         
            -
                              "[#{@repo_name}] Skipped #{file_path} - archive #{archive_name} already exists  
     | 
| 
      
 98 
     | 
    
         
            +
                              "[#{@repo_name}] Skipped #{file_path} - archive #{archive_name} already exists " \
         
     | 
| 
      
 99 
     | 
    
         
            +
                              "(hash check skipped)"
         
     | 
| 
       101 
100 
     | 
    
         
             
                            )
         
     | 
| 
       102 
101 
     | 
    
         
             
                            skipped_count += 1
         
     | 
| 
       103 
102 
     | 
    
         | 
| 
         @@ -106,12 +105,29 @@ module Ruborg 
     | 
|
| 
       106 
105 
     | 
    
         | 
| 
       107 
106 
     | 
    
         
             
                            next
         
     | 
| 
       108 
107 
     | 
    
         
             
                          else
         
     | 
| 
       109 
     | 
    
         
            -
                             
     | 
| 
       110 
     | 
    
         
            -
                             
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
                               
     | 
| 
       114 
     | 
    
         
            -
             
     | 
| 
      
 108 
     | 
    
         
            +
                            current_hash = calculate_file_hash(file_path)
         
     | 
| 
      
 109 
     | 
    
         
            +
                            stored_hash = stored_info[:hash]
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                            if current_hash == stored_hash
         
     | 
| 
      
 112 
     | 
    
         
            +
                              # Content truly unchanged - file is already safely backed up
         
     | 
| 
      
 113 
     | 
    
         
            +
                              puts " - Archive already exists (file unchanged)"
         
     | 
| 
      
 114 
     | 
    
         
            +
                              @logger&.info(
         
     | 
| 
      
 115 
     | 
    
         
            +
                                "[#{@repo_name}] Skipped #{file_path} - archive #{archive_name} already exists (file unchanged)"
         
     | 
| 
      
 116 
     | 
    
         
            +
                              )
         
     | 
| 
      
 117 
     | 
    
         
            +
                              skipped_count += 1
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                              # If remove_source is enabled, delete the file (it's already safely backed up)
         
     | 
| 
      
 120 
     | 
    
         
            +
                              remove_single_file(file_path) if remove_source
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                              next
         
     | 
| 
      
 123 
     | 
    
         
            +
                            else
         
     | 
| 
      
 124 
     | 
    
         
            +
                              # Size same but content changed (rare: edited + truncated/padded to same size)
         
     | 
| 
      
 125 
     | 
    
         
            +
                              archive_name = find_next_version_name(archive_name, existing_archives)
         
     | 
| 
      
 126 
     | 
    
         
            +
                              @logger&.warn(
         
     | 
| 
      
 127 
     | 
    
         
            +
                                "[#{@repo_name}] File content changed but size/mtime unchanged for #{file_path}, " \
         
     | 
| 
      
 128 
     | 
    
         
            +
                                "using #{archive_name}"
         
     | 
| 
      
 129 
     | 
    
         
            +
                              )
         
     | 
| 
      
 130 
     | 
    
         
            +
                            end
         
     | 
| 
       115 
131 
     | 
    
         
             
                          end
         
     | 
| 
       116 
132 
     | 
    
         
             
                        else
         
     | 
| 
       117 
133 
     | 
    
         
             
                          # Size changed but mtime same -> content changed, add version suffix
         
     | 
    
        data/lib/ruborg/cli.rb
    CHANGED
    
    | 
         @@ -184,8 +184,23 @@ module Ruborg 
     | 
|
| 
       184 
184 
     | 
    
         
             
                  raise
         
     | 
| 
       185 
185 
     | 
    
         
             
                end
         
     | 
| 
       186 
186 
     | 
    
         | 
| 
       187 
     | 
    
         
            -
                desc "validate", "Validate configuration file  
     | 
| 
       188 
     | 
    
         
            -
                 
     | 
| 
      
 187 
     | 
    
         
            +
                desc "validate TYPE", "Validate configuration file or repository (TYPE: config or repo)"
         
     | 
| 
      
 188 
     | 
    
         
            +
                option :verify_data, type: :boolean, default: false, desc: "Verify repository data (slower, only for 'repo' type)"
         
     | 
| 
      
 189 
     | 
    
         
            +
                option :all, type: :boolean, default: false, desc: "Validate all repositories (only for 'repo' type)"
         
     | 
| 
      
 190 
     | 
    
         
            +
                def validate(type)
         
     | 
| 
      
 191 
     | 
    
         
            +
                  case type
         
     | 
| 
      
 192 
     | 
    
         
            +
                  when "config"
         
     | 
| 
      
 193 
     | 
    
         
            +
                    validate_config_implementation
         
     | 
| 
      
 194 
     | 
    
         
            +
                  when "repo"
         
     | 
| 
      
 195 
     | 
    
         
            +
                    validate_repo_implementation
         
     | 
| 
      
 196 
     | 
    
         
            +
                  else
         
     | 
| 
      
 197 
     | 
    
         
            +
                    raise ConfigError, "Invalid validation type: #{type}. Use 'config' or 'repo'"
         
     | 
| 
      
 198 
     | 
    
         
            +
                  end
         
     | 
| 
      
 199 
     | 
    
         
            +
                end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
      
 201 
     | 
    
         
            +
                private
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                def validate_config_implementation
         
     | 
| 
       189 
204 
     | 
    
         
             
                  @logger.info("Validating configuration file: #{options[:config]}")
         
     | 
| 
       190 
205 
     | 
    
         
             
                  config = Config.new(options[:config])
         
     | 
| 
       191 
206 
     | 
    
         | 
| 
         @@ -201,6 +216,7 @@ module Ruborg 
     | 
|
| 
       201 
216 
     | 
    
         
             
                  errors.concat(validate_boolean_setting(global_settings, "auto_init", "global"))
         
     | 
| 
       202 
217 
     | 
    
         
             
                  errors.concat(validate_boolean_setting(global_settings, "auto_prune", "global"))
         
     | 
| 
       203 
218 
     | 
    
         
             
                  errors.concat(validate_boolean_setting(global_settings, "allow_remove_source", "global"))
         
     | 
| 
      
 219 
     | 
    
         
            +
                  errors.concat(validate_boolean_setting(global_settings, "skip_hash_check", "global"))
         
     | 
| 
       204 
220 
     | 
    
         | 
| 
       205 
221 
     | 
    
         
             
                  # Validate borg_options booleans
         
     | 
| 
       206 
222 
     | 
    
         
             
                  if global_settings["borg_options"]
         
     | 
| 
         @@ -214,6 +230,7 @@ module Ruborg 
     | 
|
| 
       214 
230 
     | 
    
         
             
                    errors.concat(validate_boolean_setting(repo, "auto_init", repo_name))
         
     | 
| 
       215 
231 
     | 
    
         
             
                    errors.concat(validate_boolean_setting(repo, "auto_prune", repo_name))
         
     | 
| 
       216 
232 
     | 
    
         
             
                    errors.concat(validate_boolean_setting(repo, "allow_remove_source", repo_name))
         
     | 
| 
      
 233 
     | 
    
         
            +
                    errors.concat(validate_boolean_setting(repo, "skip_hash_check", repo_name))
         
     | 
| 
       217 
234 
     | 
    
         | 
| 
       218 
235 
     | 
    
         
             
                    if repo["borg_options"]
         
     | 
| 
       219 
236 
     | 
    
         
             
                      warnings.concat(validate_borg_option(repo["borg_options"], "allow_relocated_repo", repo_name))
         
     | 
| 
         @@ -257,64 +274,8 @@ module Ruborg 
     | 
|
| 
       257 
274 
     | 
    
         
             
                  raise
         
     | 
| 
       258 
275 
     | 
    
         
             
                end
         
     | 
| 
       259 
276 
     | 
    
         | 
| 
       260 
     | 
    
         
            -
                 
     | 
| 
       261 
     | 
    
         
            -
             
     | 
| 
       262 
     | 
    
         
            -
                  require_relative "version"
         
     | 
| 
       263 
     | 
    
         
            -
                  puts "ruborg #{Ruborg::VERSION}"
         
     | 
| 
       264 
     | 
    
         
            -
                  @logger.info("Version checked: #{Ruborg::VERSION}")
         
     | 
| 
       265 
     | 
    
         
            -
                end
         
     | 
| 
       266 
     | 
    
         
            -
             
     | 
| 
       267 
     | 
    
         
            -
                desc "metadata ARCHIVE", "Get file metadata from an archive"
         
     | 
| 
       268 
     | 
    
         
            -
                option :file, type: :string, desc: "Specific file path (required for standard archives, auto for per-file)"
         
     | 
| 
       269 
     | 
    
         
            -
                def metadata(archive_name)
         
     | 
| 
       270 
     | 
    
         
            -
                  @logger.info("Getting metadata for archive: #{archive_name}")
         
     | 
| 
       271 
     | 
    
         
            -
                  config = Config.new(options[:config])
         
     | 
| 
       272 
     | 
    
         
            -
             
     | 
| 
       273 
     | 
    
         
            -
                  raise ConfigError, "Please specify --repository" unless options[:repository]
         
     | 
| 
       274 
     | 
    
         
            -
             
     | 
| 
       275 
     | 
    
         
            -
                  repo_config = config.get_repository(options[:repository])
         
     | 
| 
       276 
     | 
    
         
            -
                  raise ConfigError, "Repository '#{options[:repository]}' not found" unless repo_config
         
     | 
| 
       277 
     | 
    
         
            -
             
     | 
| 
       278 
     | 
    
         
            -
                  global_settings = config.global_settings
         
     | 
| 
       279 
     | 
    
         
            -
                  merged_config = global_settings.merge(repo_config)
         
     | 
| 
       280 
     | 
    
         
            -
                  validate_hostname(merged_config)
         
     | 
| 
       281 
     | 
    
         
            -
                  passphrase = fetch_passphrase_for_repo(merged_config)
         
     | 
| 
       282 
     | 
    
         
            -
                  borg_opts = merged_config["borg_options"] || {}
         
     | 
| 
       283 
     | 
    
         
            -
                  borg_path = merged_config["borg_path"]
         
     | 
| 
       284 
     | 
    
         
            -
             
     | 
| 
       285 
     | 
    
         
            -
                  repo = Repository.new(repo_config["path"], passphrase: passphrase, borg_options: borg_opts, borg_path: borg_path,
         
     | 
| 
       286 
     | 
    
         
            -
                                                             logger: @logger)
         
     | 
| 
       287 
     | 
    
         
            -
             
     | 
| 
       288 
     | 
    
         
            -
                  raise BorgError, "Repository does not exist at #{repo_config["path"]}" unless repo.exists?
         
     | 
| 
       289 
     | 
    
         
            -
             
     | 
| 
       290 
     | 
    
         
            -
                  # Get file metadata
         
     | 
| 
       291 
     | 
    
         
            -
                  metadata = repo.get_file_metadata(archive_name, file_path: options[:file])
         
     | 
| 
       292 
     | 
    
         
            -
             
     | 
| 
       293 
     | 
    
         
            -
                  # Display metadata
         
     | 
| 
       294 
     | 
    
         
            -
                  puts "\n═══════════════════════════════════════════════════════════════"
         
     | 
| 
       295 
     | 
    
         
            -
                  puts "  FILE METADATA"
         
     | 
| 
       296 
     | 
    
         
            -
                  puts "═══════════════════════════════════════════════════════════════\n\n"
         
     | 
| 
       297 
     | 
    
         
            -
                  puts "Archive: #{archive_name}"
         
     | 
| 
       298 
     | 
    
         
            -
                  puts "File: #{metadata["path"]}"
         
     | 
| 
       299 
     | 
    
         
            -
                  puts "Size: #{format_size(metadata["size"])}"
         
     | 
| 
       300 
     | 
    
         
            -
                  puts "Modified: #{metadata["mtime"]}"
         
     | 
| 
       301 
     | 
    
         
            -
                  puts "Mode: #{metadata["mode"]}"
         
     | 
| 
       302 
     | 
    
         
            -
                  puts "User: #{metadata["user"]}"
         
     | 
| 
       303 
     | 
    
         
            -
                  puts "Group: #{metadata["group"]}"
         
     | 
| 
       304 
     | 
    
         
            -
                  puts "Type: #{metadata["type"]}"
         
     | 
| 
       305 
     | 
    
         
            -
                  puts ""
         
     | 
| 
       306 
     | 
    
         
            -
             
     | 
| 
       307 
     | 
    
         
            -
                  @logger.info("Successfully retrieved metadata for #{metadata["path"]}")
         
     | 
| 
       308 
     | 
    
         
            -
                rescue Error => e
         
     | 
| 
       309 
     | 
    
         
            -
                  @logger.error("Failed to get metadata: #{e.message}")
         
     | 
| 
       310 
     | 
    
         
            -
                  raise
         
     | 
| 
       311 
     | 
    
         
            -
                end
         
     | 
| 
       312 
     | 
    
         
            -
             
     | 
| 
       313 
     | 
    
         
            -
                desc "check", "Check repository integrity and compatibility"
         
     | 
| 
       314 
     | 
    
         
            -
                option :verify_data, type: :boolean, default: false, desc: "Verify repository data (slower)"
         
     | 
| 
       315 
     | 
    
         
            -
                option :all, type: :boolean, default: false, desc: "Check all repositories"
         
     | 
| 
       316 
     | 
    
         
            -
                def check
         
     | 
| 
       317 
     | 
    
         
            -
                  @logger.info("Checking repository compatibility")
         
     | 
| 
      
 277 
     | 
    
         
            +
                def validate_repo_implementation
         
     | 
| 
      
 278 
     | 
    
         
            +
                  @logger.info("Validating repository compatibility")
         
     | 
| 
       318 
279 
     | 
    
         
             
                  config = Config.new(options[:config])
         
     | 
| 
       319 
280 
     | 
    
         
             
                  global_settings = config.global_settings
         
     | 
| 
       320 
281 
     | 
    
         
             
                  validate_hostname(global_settings)
         
     | 
| 
         @@ -323,31 +284,29 @@ module Ruborg 
     | 
|
| 
       323 
284 
     | 
    
         
             
                  borg_version = Repository.borg_version
         
     | 
| 
       324 
285 
     | 
    
         
             
                  puts "\nBorg version: #{borg_version}\n\n"
         
     | 
| 
       325 
286 
     | 
    
         | 
| 
       326 
     | 
    
         
            -
                   
     | 
| 
       327 
     | 
    
         
            -
             
     | 
| 
       328 
     | 
    
         
            -
             
     | 
| 
       329 
     | 
    
         
            -
             
     | 
| 
       330 
     | 
    
         
            -
             
     | 
| 
      
 287 
     | 
    
         
            +
                  repos_to_validate = if options[:all]
         
     | 
| 
      
 288 
     | 
    
         
            +
                                        config.repositories
         
     | 
| 
      
 289 
     | 
    
         
            +
                                      elsif options[:repository]
         
     | 
| 
      
 290 
     | 
    
         
            +
                                        repo_config = config.get_repository(options[:repository])
         
     | 
| 
      
 291 
     | 
    
         
            +
                                        raise ConfigError, "Repository '#{options[:repository]}' not found" unless repo_config
         
     | 
| 
       331 
292 
     | 
    
         | 
| 
       332 
     | 
    
         
            -
             
     | 
| 
       333 
     | 
    
         
            -
             
     | 
| 
       334 
     | 
    
         
            -
             
     | 
| 
       335 
     | 
    
         
            -
             
     | 
| 
      
 293 
     | 
    
         
            +
                                        [repo_config]
         
     | 
| 
      
 294 
     | 
    
         
            +
                                      else
         
     | 
| 
      
 295 
     | 
    
         
            +
                                        raise ConfigError, "Please specify --repository or --all"
         
     | 
| 
      
 296 
     | 
    
         
            +
                                      end
         
     | 
| 
       336 
297 
     | 
    
         | 
| 
       337 
     | 
    
         
            -
                   
     | 
| 
       338 
     | 
    
         
            -
                     
     | 
| 
      
 298 
     | 
    
         
            +
                  repos_to_validate.each do |repo_config|
         
     | 
| 
      
 299 
     | 
    
         
            +
                    validate_repository(repo_config, global_settings)
         
     | 
| 
       339 
300 
     | 
    
         
             
                  end
         
     | 
| 
       340 
301 
     | 
    
         
             
                rescue Error => e
         
     | 
| 
       341 
     | 
    
         
            -
                  @logger.error(" 
     | 
| 
      
 302 
     | 
    
         
            +
                  @logger.error("Validation failed: #{e.message}")
         
     | 
| 
       342 
303 
     | 
    
         
             
                  raise
         
     | 
| 
       343 
304 
     | 
    
         
             
                end
         
     | 
| 
       344 
305 
     | 
    
         | 
| 
       345 
     | 
    
         
            -
                 
     | 
| 
       346 
     | 
    
         
            -
             
     | 
| 
       347 
     | 
    
         
            -
                def check_repository(repo_config, global_settings)
         
     | 
| 
      
 306 
     | 
    
         
            +
                def validate_repository(repo_config, global_settings)
         
     | 
| 
       348 
307 
     | 
    
         
             
                  repo_name = repo_config["name"]
         
     | 
| 
       349 
     | 
    
         
            -
                  puts "---  
     | 
| 
       350 
     | 
    
         
            -
                  @logger.info(" 
     | 
| 
      
 308 
     | 
    
         
            +
                  puts "--- Validating repository: #{repo_name} ---"
         
     | 
| 
      
 309 
     | 
    
         
            +
                  @logger.info("Validating repository: #{repo_name}")
         
     | 
| 
       351 
310 
     | 
    
         | 
| 
       352 
311 
     | 
    
         
             
                  merged_config = global_settings.merge(repo_config)
         
     | 
| 
       353 
312 
     | 
    
         
             
                  validate_hostname(merged_config)
         
     | 
| 
         @@ -392,11 +351,96 @@ module Ruborg 
     | 
|
| 
       392 
351 
     | 
    
         | 
| 
       393 
352 
     | 
    
         
             
                  puts ""
         
     | 
| 
       394 
353 
     | 
    
         
             
                rescue BorgError => e
         
     | 
| 
       395 
     | 
    
         
            -
                  puts "  ✗  
     | 
| 
       396 
     | 
    
         
            -
                  @logger.error(" 
     | 
| 
      
 354 
     | 
    
         
            +
                  puts "  ✗ Validation failed: #{e.message}"
         
     | 
| 
      
 355 
     | 
    
         
            +
                  @logger.error("Validation failed for #{repo_name}: #{e.message}")
         
     | 
| 
       397 
356 
     | 
    
         
             
                  puts ""
         
     | 
| 
       398 
357 
     | 
    
         
             
                end
         
     | 
| 
       399 
358 
     | 
    
         | 
| 
      
 359 
     | 
    
         
            +
                public
         
     | 
| 
      
 360 
     | 
    
         
            +
             
     | 
| 
      
 361 
     | 
    
         
            +
                desc "version", "Show ruborg and borg versions"
         
     | 
| 
      
 362 
     | 
    
         
            +
                def version
         
     | 
| 
      
 363 
     | 
    
         
            +
                  require_relative "version"
         
     | 
| 
      
 364 
     | 
    
         
            +
                  puts "ruborg #{Ruborg::VERSION}"
         
     | 
| 
      
 365 
     | 
    
         
            +
                  @logger.info("Version checked: #{Ruborg::VERSION}")
         
     | 
| 
      
 366 
     | 
    
         
            +
             
     | 
| 
      
 367 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 368 
     | 
    
         
            +
                    borg_version = Repository.borg_version
         
     | 
| 
      
 369 
     | 
    
         
            +
                    borg_path = Repository.borg_path
         
     | 
| 
      
 370 
     | 
    
         
            +
                    puts "borg #{borg_version} (#{borg_path})"
         
     | 
| 
      
 371 
     | 
    
         
            +
                    @logger.info("Borg version: #{borg_version}, path: #{borg_path}")
         
     | 
| 
      
 372 
     | 
    
         
            +
                  rescue BorgError => e
         
     | 
| 
      
 373 
     | 
    
         
            +
                    puts "borg: not found or not executable"
         
     | 
| 
      
 374 
     | 
    
         
            +
                    @logger.warn("Could not determine Borg version: #{e.message}")
         
     | 
| 
      
 375 
     | 
    
         
            +
                  end
         
     | 
| 
      
 376 
     | 
    
         
            +
                end
         
     | 
| 
      
 377 
     | 
    
         
            +
             
     | 
| 
      
 378 
     | 
    
         
            +
                desc "check", "DEPRECATED: Use 'ruborg validate repo' instead"
         
     | 
| 
      
 379 
     | 
    
         
            +
                option :verify_data, type: :boolean, default: false, desc: "Verify repository data (slower)"
         
     | 
| 
      
 380 
     | 
    
         
            +
                option :all, type: :boolean, default: false, desc: "Validate all repositories"
         
     | 
| 
      
 381 
     | 
    
         
            +
                def check
         
     | 
| 
      
 382 
     | 
    
         
            +
                  puts "\n⚠️  DEPRECATED COMMAND"
         
     | 
| 
      
 383 
     | 
    
         
            +
                  puts "══════════════════════════════════════════════════════════════════\n\n"
         
     | 
| 
      
 384 
     | 
    
         
            +
                  puts "The 'ruborg check' command has been renamed for consistency.\n"
         
     | 
| 
      
 385 
     | 
    
         
            +
                  puts "Please use: ruborg validate repo\n\n"
         
     | 
| 
      
 386 
     | 
    
         
            +
                  puts "Examples:"
         
     | 
| 
      
 387 
     | 
    
         
            +
                  puts "  ruborg validate repo --repository documents"
         
     | 
| 
      
 388 
     | 
    
         
            +
                  puts "  ruborg validate repo --all"
         
     | 
| 
      
 389 
     | 
    
         
            +
                  puts "  ruborg validate repo --repository documents --verify-data\n\n"
         
     | 
| 
      
 390 
     | 
    
         
            +
                  puts "══════════════════════════════════════════════════════════════════\n"
         
     | 
| 
      
 391 
     | 
    
         
            +
             
     | 
| 
      
 392 
     | 
    
         
            +
                  @logger.warn("Deprecated command 'check' was called. User should use 'validate repo' instead.")
         
     | 
| 
      
 393 
     | 
    
         
            +
                  exit 1
         
     | 
| 
      
 394 
     | 
    
         
            +
                end
         
     | 
| 
      
 395 
     | 
    
         
            +
             
     | 
| 
      
 396 
     | 
    
         
            +
                desc "metadata ARCHIVE", "Get file metadata from an archive"
         
     | 
| 
      
 397 
     | 
    
         
            +
                option :file, type: :string, desc: "Specific file path (required for standard archives, auto for per-file)"
         
     | 
| 
      
 398 
     | 
    
         
            +
                def metadata(archive_name)
         
     | 
| 
      
 399 
     | 
    
         
            +
                  @logger.info("Getting metadata for archive: #{archive_name}")
         
     | 
| 
      
 400 
     | 
    
         
            +
                  config = Config.new(options[:config])
         
     | 
| 
      
 401 
     | 
    
         
            +
             
     | 
| 
      
 402 
     | 
    
         
            +
                  raise ConfigError, "Please specify --repository" unless options[:repository]
         
     | 
| 
      
 403 
     | 
    
         
            +
             
     | 
| 
      
 404 
     | 
    
         
            +
                  repo_config = config.get_repository(options[:repository])
         
     | 
| 
      
 405 
     | 
    
         
            +
                  raise ConfigError, "Repository '#{options[:repository]}' not found" unless repo_config
         
     | 
| 
      
 406 
     | 
    
         
            +
             
     | 
| 
      
 407 
     | 
    
         
            +
                  global_settings = config.global_settings
         
     | 
| 
      
 408 
     | 
    
         
            +
                  merged_config = global_settings.merge(repo_config)
         
     | 
| 
      
 409 
     | 
    
         
            +
                  validate_hostname(merged_config)
         
     | 
| 
      
 410 
     | 
    
         
            +
                  passphrase = fetch_passphrase_for_repo(merged_config)
         
     | 
| 
      
 411 
     | 
    
         
            +
                  borg_opts = merged_config["borg_options"] || {}
         
     | 
| 
      
 412 
     | 
    
         
            +
                  borg_path = merged_config["borg_path"]
         
     | 
| 
      
 413 
     | 
    
         
            +
             
     | 
| 
      
 414 
     | 
    
         
            +
                  repo = Repository.new(repo_config["path"], passphrase: passphrase, borg_options: borg_opts, borg_path: borg_path,
         
     | 
| 
      
 415 
     | 
    
         
            +
                                                             logger: @logger)
         
     | 
| 
      
 416 
     | 
    
         
            +
             
     | 
| 
      
 417 
     | 
    
         
            +
                  raise BorgError, "Repository does not exist at #{repo_config["path"]}" unless repo.exists?
         
     | 
| 
      
 418 
     | 
    
         
            +
             
     | 
| 
      
 419 
     | 
    
         
            +
                  # Get file metadata
         
     | 
| 
      
 420 
     | 
    
         
            +
                  metadata = repo.get_file_metadata(archive_name, file_path: options[:file])
         
     | 
| 
      
 421 
     | 
    
         
            +
             
     | 
| 
      
 422 
     | 
    
         
            +
                  # Display metadata
         
     | 
| 
      
 423 
     | 
    
         
            +
                  puts "\n═══════════════════════════════════════════════════════════════"
         
     | 
| 
      
 424 
     | 
    
         
            +
                  puts "  FILE METADATA"
         
     | 
| 
      
 425 
     | 
    
         
            +
                  puts "═══════════════════════════════════════════════════════════════\n\n"
         
     | 
| 
      
 426 
     | 
    
         
            +
                  puts "Archive: #{archive_name}"
         
     | 
| 
      
 427 
     | 
    
         
            +
                  puts "File: #{metadata["path"]}"
         
     | 
| 
      
 428 
     | 
    
         
            +
                  puts "Size: #{format_size(metadata["size"])}"
         
     | 
| 
      
 429 
     | 
    
         
            +
                  puts "Modified: #{metadata["mtime"]}"
         
     | 
| 
      
 430 
     | 
    
         
            +
                  puts "Mode: #{metadata["mode"]}"
         
     | 
| 
      
 431 
     | 
    
         
            +
                  puts "User: #{metadata["user"]}"
         
     | 
| 
      
 432 
     | 
    
         
            +
                  puts "Group: #{metadata["group"]}"
         
     | 
| 
      
 433 
     | 
    
         
            +
                  puts "Type: #{metadata["type"]}"
         
     | 
| 
      
 434 
     | 
    
         
            +
                  puts ""
         
     | 
| 
      
 435 
     | 
    
         
            +
             
     | 
| 
      
 436 
     | 
    
         
            +
                  @logger.info("Successfully retrieved metadata for #{metadata["path"]}")
         
     | 
| 
      
 437 
     | 
    
         
            +
                rescue Error => e
         
     | 
| 
      
 438 
     | 
    
         
            +
                  @logger.error("Failed to get metadata: #{e.message}")
         
     | 
| 
      
 439 
     | 
    
         
            +
                  raise
         
     | 
| 
      
 440 
     | 
    
         
            +
                end
         
     | 
| 
      
 441 
     | 
    
         
            +
             
     | 
| 
      
 442 
     | 
    
         
            +
                private
         
     | 
| 
      
 443 
     | 
    
         
            +
             
     | 
| 
       400 
444 
     | 
    
         
             
                def show_repositories_summary(config)
         
     | 
| 
       401 
445 
     | 
    
         
             
                  repositories = config.repositories
         
     | 
| 
       402 
446 
     | 
    
         
             
                  global_settings = config.global_settings
         
     | 
| 
         @@ -578,10 +622,14 @@ module Ruborg 
     | 
|
| 
       578 
622 
     | 
    
         
             
                    end
         
     | 
| 
       579 
623 
     | 
    
         
             
                  end
         
     | 
| 
       580 
624 
     | 
    
         | 
| 
      
 625 
     | 
    
         
            +
                  # Get skip_hash_check setting (defaults to false)
         
     | 
| 
      
 626 
     | 
    
         
            +
                  skip_hash_check = merged_config["skip_hash_check"]
         
     | 
| 
      
 627 
     | 
    
         
            +
                  skip_hash_check = false unless skip_hash_check == true
         
     | 
| 
      
 628 
     | 
    
         
            +
             
     | 
| 
       581 
629 
     | 
    
         
             
                  # Create backup config wrapper
         
     | 
| 
       582 
630 
     | 
    
         
             
                  backup_config = BackupConfig.new(repo_config, merged_config)
         
     | 
| 
       583 
631 
     | 
    
         
             
                  backup = Backup.new(repo, config: backup_config, retention_mode: retention_mode, repo_name: repo_name,
         
     | 
| 
       584 
     | 
    
         
            -
                                            logger: @logger)
         
     | 
| 
      
 632 
     | 
    
         
            +
                                            logger: @logger, skip_hash_check: skip_hash_check)
         
     | 
| 
       585 
633 
     | 
    
         | 
| 
       586 
634 
     | 
    
         
             
                  archive_name = options[:name] ? sanitize_archive_name(options[:name]) : nil
         
     | 
| 
       587 
635 
     | 
    
         
             
                  @logger.info("Creating archive#{"s" if retention_mode == "per_file"}: #{archive_name || "auto-generated"}")
         
     | 
    
        data/lib/ruborg/config.rb
    CHANGED
    
    | 
         @@ -41,7 +41,7 @@ module Ruborg 
     | 
|
| 
       41 
41 
     | 
    
         | 
| 
       42 
42 
     | 
    
         
             
                def global_settings
         
     | 
| 
       43 
43 
     | 
    
         
             
                  @data.slice("passbolt", "compression", "encryption", "auto_init", "borg_options", "log_file", "retention",
         
     | 
| 
       44 
     | 
    
         
            -
                              "auto_prune", "hostname", "allow_remove_source", "borg_path")
         
     | 
| 
      
 44 
     | 
    
         
            +
                              "auto_prune", "hostname", "allow_remove_source", "borg_path", "skip_hash_check")
         
     | 
| 
       45 
45 
     | 
    
         
             
                end
         
     | 
| 
       46 
46 
     | 
    
         | 
| 
       47 
47 
     | 
    
         
             
                private
         
     | 
| 
         @@ -54,12 +54,12 @@ module Ruborg 
     | 
|
| 
       54 
54 
     | 
    
         
             
                # Valid configuration keys at each level
         
     | 
| 
       55 
55 
     | 
    
         
             
                VALID_GLOBAL_KEYS = %w[
         
     | 
| 
       56 
56 
     | 
    
         
             
                  hostname compression encryption auto_init auto_prune allow_remove_source
         
     | 
| 
       57 
     | 
    
         
            -
                  log_file borg_path passbolt borg_options retention repositories
         
     | 
| 
      
 57 
     | 
    
         
            +
                  log_file borg_path passbolt borg_options retention repositories skip_hash_check
         
     | 
| 
       58 
58 
     | 
    
         
             
                ].freeze
         
     | 
| 
       59 
59 
     | 
    
         | 
| 
       60 
60 
     | 
    
         
             
                VALID_REPOSITORY_KEYS = %w[
         
     | 
| 
       61 
61 
     | 
    
         
             
                  name description path hostname retention_mode passbolt retention sources
         
     | 
| 
       62 
     | 
    
         
            -
                  compression encryption auto_init auto_prune borg_options allow_remove_source
         
     | 
| 
      
 62 
     | 
    
         
            +
                  compression encryption auto_init auto_prune borg_options allow_remove_source skip_hash_check
         
     | 
| 
       63 
63 
     | 
    
         
             
                ].freeze
         
     | 
| 
       64 
64 
     | 
    
         | 
| 
       65 
65 
     | 
    
         
             
                VALID_SOURCE_KEYS = %w[name paths exclude].freeze
         
     | 
| 
         @@ -122,8 +122,9 @@ module Ruborg 
     | 
|
| 
       122 
122 
     | 
    
         
             
                  errors.concat(validate_boolean_config(@data, "auto_init", "global"))
         
     | 
| 
       123 
123 
     | 
    
         
             
                  errors.concat(validate_boolean_config(@data, "auto_prune", "global"))
         
     | 
| 
       124 
124 
     | 
    
         
             
                  errors.concat(validate_boolean_config(@data, "allow_remove_source", "global"))
         
     | 
| 
      
 125 
     | 
    
         
            +
                  errors.concat(validate_boolean_config(@data, "skip_hash_check", "global"))
         
     | 
| 
       125 
126 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
                  #  
     | 
| 
      
 127 
     | 
    
         
            +
                  # NOTE: borg_options are validated as warnings in CLI validate command, not as errors here
         
     | 
| 
       127 
128 
     | 
    
         | 
| 
       128 
129 
     | 
    
         
             
                  # Validate global passbolt
         
     | 
| 
       129 
130 
     | 
    
         
             
                  errors.concat(validate_passbolt_config(@data["passbolt"], "global")) if @data["passbolt"]
         
     | 
| 
         @@ -151,6 +152,7 @@ module Ruborg 
     | 
|
| 
       151 
152 
     | 
    
         
             
                    errors.concat(validate_boolean_config(repo, "auto_init", repo_name))
         
     | 
| 
       152 
153 
     | 
    
         
             
                    errors.concat(validate_boolean_config(repo, "auto_prune", repo_name))
         
     | 
| 
       153 
154 
     | 
    
         
             
                    errors.concat(validate_boolean_config(repo, "allow_remove_source", repo_name))
         
     | 
| 
      
 155 
     | 
    
         
            +
                    errors.concat(validate_boolean_config(repo, "skip_hash_check", repo_name))
         
     | 
| 
       154 
156 
     | 
    
         | 
| 
       155 
157 
     | 
    
         
             
                    # Validate retention_mode
         
     | 
| 
       156 
158 
     | 
    
         
             
                    if repo["retention_mode"] && !VALID_RETENTION_MODES.include?(repo["retention_mode"])
         
     | 
| 
         @@ -158,7 +160,7 @@ module Ruborg 
     | 
|
| 
       158 
160 
     | 
    
         
             
                                "Must be one of: #{VALID_RETENTION_MODES.join(", ")}"
         
     | 
| 
       159 
161 
     | 
    
         
             
                    end
         
     | 
| 
       160 
162 
     | 
    
         | 
| 
       161 
     | 
    
         
            -
                    #  
     | 
| 
      
 163 
     | 
    
         
            +
                    # NOTE: borg_options are validated as warnings in CLI validate command, not as errors here
         
     | 
| 
       162 
164 
     | 
    
         | 
| 
       163 
165 
     | 
    
         
             
                    errors.concat(validate_passbolt_config(repo["passbolt"], repo_name)) if repo["passbolt"]
         
     | 
| 
       164 
166 
     | 
    
         | 
    
        data/lib/ruborg/repository.rb
    CHANGED
    
    | 
         @@ -477,6 +477,21 @@ module Ruborg 
     | 
|
| 
       477 
477 
     | 
    
         
             
                  match[1]
         
     | 
| 
       478 
478 
     | 
    
         
             
                end
         
     | 
| 
       479 
479 
     | 
    
         | 
| 
      
 480 
     | 
    
         
            +
                # Get Borg path (full path to executable)
         
     | 
| 
      
 481 
     | 
    
         
            +
                def self.borg_path(borg_command = "borg")
         
     | 
| 
      
 482 
     | 
    
         
            +
                  # If it's an absolute or relative path, expand it
         
     | 
| 
      
 483 
     | 
    
         
            +
                  return File.expand_path(borg_command) if borg_command.include?("/")
         
     | 
| 
      
 484 
     | 
    
         
            +
             
     | 
| 
      
 485 
     | 
    
         
            +
                  # Otherwise, search in PATH
         
     | 
| 
      
 486 
     | 
    
         
            +
                  ENV["PATH"].split(File::PATH_SEPARATOR).each do |directory|
         
     | 
| 
      
 487 
     | 
    
         
            +
                    path = File.join(directory, borg_command)
         
     | 
| 
      
 488 
     | 
    
         
            +
                    return path if File.executable?(path)
         
     | 
| 
      
 489 
     | 
    
         
            +
                  end
         
     | 
| 
      
 490 
     | 
    
         
            +
             
     | 
| 
      
 491 
     | 
    
         
            +
                  # Not found in PATH, return the command as-is
         
     | 
| 
      
 492 
     | 
    
         
            +
                  borg_command
         
     | 
| 
      
 493 
     | 
    
         
            +
                end
         
     | 
| 
      
 494 
     | 
    
         
            +
             
     | 
| 
       480 
495 
     | 
    
         
             
                # Execute borg version command (extracted for testing)
         
     | 
| 
       481 
496 
     | 
    
         
             
                def self.execute_version_command(borg_path = "borg")
         
     | 
| 
       482 
497 
     | 
    
         
             
                  require "open3"
         
     | 
    
        data/lib/ruborg/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,13 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: ruborg
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.9.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Michail Pantelelis
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire:
         
     | 
| 
       8 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       9 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       10 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2025-10-14 00:00:00.000000000 Z
         
     | 
| 
       11 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       12 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       13 
14 
     | 
    
         
             
              name: psych
         
     | 
| 
         @@ -160,6 +161,7 @@ metadata: 
     | 
|
| 
       160 
161 
     | 
    
         
             
              source_code_uri: https://github.com/mpantel/ruborg.git
         
     | 
| 
       161 
162 
     | 
    
         
             
              changelog_uri: https://github.com/mpantel/ruborg/blob/main/CHANGELOG.md
         
     | 
| 
       162 
163 
     | 
    
         
             
              rubygems_mfa_required: 'true'
         
     | 
| 
      
 164 
     | 
    
         
            +
            post_install_message:
         
     | 
| 
       163 
165 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       164 
166 
     | 
    
         
             
            require_paths:
         
     | 
| 
       165 
167 
     | 
    
         
             
            - lib
         
     | 
| 
         @@ -174,7 +176,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       174 
176 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       175 
177 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       176 
178 
     | 
    
         
             
            requirements: []
         
     | 
| 
       177 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
      
 179 
     | 
    
         
            +
            rubygems_version: 3.5.22
         
     | 
| 
      
 180 
     | 
    
         
            +
            signing_key:
         
     | 
| 
       178 
181 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       179 
182 
     | 
    
         
             
            summary: A friendly Ruby frontend for Borg backup
         
     | 
| 
       180 
183 
     | 
    
         
             
            test_files: []
         
     |