ruborg 0.8.0 → 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 +88 -0
 - data/PER_DIRECTORY_RETENTION.md +297 -0
 - data/README.md +74 -17
 - data/lib/ruborg/backup.rb +60 -31
 - data/lib/ruborg/cli.rb +128 -80
 - data/lib/ruborg/config.rb +7 -5
 - data/lib/ruborg/repository.rb +189 -23
 - data/lib/ruborg/version.rb +1 -1
 - data/ruborg.gemspec +46 -0
 - metadata +8 -3
 
    
        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)
         
     | 
| 
         @@ -62,7 +63,10 @@ module Ruborg 
     | 
|
| 
       62 
63 
     | 
    
         
             
                  skipped_count = 0
         
     | 
| 
       63 
64 
     | 
    
         | 
| 
       64 
65 
     | 
    
         
             
                  # rubocop:disable Metrics/BlockLength
         
     | 
| 
       65 
     | 
    
         
            -
                  files_to_backup.each_with_index do | 
     | 
| 
      
 66 
     | 
    
         
            +
                  files_to_backup.each_with_index do |file_info, index|
         
     | 
| 
      
 67 
     | 
    
         
            +
                    file_path = file_info[:path]
         
     | 
| 
      
 68 
     | 
    
         
            +
                    source_dir = file_info[:source_dir]
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
       66 
70 
     | 
    
         
             
                    # Generate hash-based archive name with filename
         
     | 
| 
       67 
71 
     | 
    
         
             
                    path_hash = generate_path_hash(file_path)
         
     | 
| 
       68 
72 
     | 
    
         
             
                    filename = File.basename(file_path)
         
     | 
| 
         @@ -86,15 +90,13 @@ module Ruborg 
     | 
|
| 
       86 
90 
     | 
    
         
             
                        stored_size = stored_info[:size]
         
     | 
| 
       87 
91 
     | 
    
         | 
| 
       88 
92 
     | 
    
         
             
                        if current_size == stored_size
         
     | 
| 
       89 
     | 
    
         
            -
                          # Size same -> verify content hasn't changed (paranoid mode)
         
     | 
| 
       90 
     | 
    
         
            -
                           
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
       93 
     | 
    
         
            -
                          if current_hash == stored_hash
         
     | 
| 
       94 
     | 
    
         
            -
                            # Content truly unchanged - file is already safely backed up
         
     | 
| 
       95 
     | 
    
         
            -
                            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)"
         
     | 
| 
       96 
97 
     | 
    
         
             
                            @logger&.info(
         
     | 
| 
       97 
     | 
    
         
            -
                              "[#{@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)"
         
     | 
| 
       98 
100 
     | 
    
         
             
                            )
         
     | 
| 
       99 
101 
     | 
    
         
             
                            skipped_count += 1
         
     | 
| 
       100 
102 
     | 
    
         | 
| 
         @@ -103,12 +105,29 @@ module Ruborg 
     | 
|
| 
       103 
105 
     | 
    
         | 
| 
       104 
106 
     | 
    
         
             
                            next
         
     | 
| 
       105 
107 
     | 
    
         
             
                          else
         
     | 
| 
       106 
     | 
    
         
            -
                             
     | 
| 
       107 
     | 
    
         
            -
                             
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
             
     | 
| 
       110 
     | 
    
         
            -
                               
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
      
 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
         
     | 
| 
       112 
131 
     | 
    
         
             
                          end
         
     | 
| 
       113 
132 
     | 
    
         
             
                        else
         
     | 
| 
       114 
133 
     | 
    
         
             
                          # Size changed but mtime same -> content changed, add version suffix
         
     | 
| 
         @@ -126,8 +145,8 @@ module Ruborg 
     | 
|
| 
       126 
145 
     | 
    
         
             
                      end
         
     | 
| 
       127 
146 
     | 
    
         
             
                    end
         
     | 
| 
       128 
147 
     | 
    
         | 
| 
       129 
     | 
    
         
            -
                    # Create archive for single file with  
     | 
| 
       130 
     | 
    
         
            -
                    cmd = build_per_file_create_command(archive_name, file_path)
         
     | 
| 
      
 148 
     | 
    
         
            +
                    # Create archive for single file with source directory in metadata
         
     | 
| 
      
 149 
     | 
    
         
            +
                    cmd = build_per_file_create_command(archive_name, file_path, source_dir)
         
     | 
| 
       131 
150 
     | 
    
         | 
| 
       132 
151 
     | 
    
         
             
                    execute_borg_command(cmd)
         
     | 
| 
       133 
152 
     | 
    
         
             
                    puts ""
         
     | 
| 
         @@ -157,13 +176,13 @@ module Ruborg 
     | 
|
| 
       157 
176 
     | 
    
         
             
                    base_path = File.expand_path(base_path)
         
     | 
| 
       158 
177 
     | 
    
         | 
| 
       159 
178 
     | 
    
         
             
                    if File.file?(base_path)
         
     | 
| 
       160 
     | 
    
         
            -
                      files << base_path unless excluded?(base_path, exclude_patterns)
         
     | 
| 
      
 179 
     | 
    
         
            +
                      files << { path: base_path, source_dir: base_path } unless excluded?(base_path, exclude_patterns)
         
     | 
| 
       161 
180 
     | 
    
         
             
                    elsif File.directory?(base_path)
         
     | 
| 
       162 
181 
     | 
    
         
             
                      Find.find(base_path) do |path|
         
     | 
| 
       163 
182 
     | 
    
         
             
                        next unless File.file?(path)
         
     | 
| 
       164 
183 
     | 
    
         
             
                        next if excluded?(path, exclude_patterns)
         
     | 
| 
       165 
184 
     | 
    
         | 
| 
       166 
     | 
    
         
            -
                        files << path
         
     | 
| 
      
 185 
     | 
    
         
            +
                        files << { path: path, source_dir: base_path }
         
     | 
| 
       167 
186 
     | 
    
         
             
                      end
         
     | 
| 
       168 
187 
     | 
    
         
             
                    end
         
     | 
| 
       169 
188 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -248,15 +267,15 @@ module Ruborg 
     | 
|
| 
       248 
267 
     | 
    
         
             
                  Digest::SHA256.file(file_path).hexdigest
         
     | 
| 
       249 
268 
     | 
    
         
             
                end
         
     | 
| 
       250 
269 
     | 
    
         | 
| 
       251 
     | 
    
         
            -
                def build_per_file_create_command(archive_name, file_path)
         
     | 
| 
      
 270 
     | 
    
         
            +
                def build_per_file_create_command(archive_name, file_path, source_dir)
         
     | 
| 
       252 
271 
     | 
    
         
             
                  cmd = [@repository.borg_path, "create"]
         
     | 
| 
       253 
272 
     | 
    
         
             
                  cmd += ["--compression", @config.compression]
         
     | 
| 
       254 
273 
     | 
    
         | 
| 
       255 
     | 
    
         
            -
                  # Store file metadata (path + size + hash) in archive comment 
     | 
| 
       256 
     | 
    
         
            -
                  # Format: path|||size|||hash (using ||| as delimiter to avoid conflicts with paths)
         
     | 
| 
      
 274 
     | 
    
         
            +
                  # Store file metadata (path + size + hash + source_dir) in archive comment
         
     | 
| 
      
 275 
     | 
    
         
            +
                  # Format: path|||size|||hash|||source_dir (using ||| as delimiter to avoid conflicts with paths)
         
     | 
| 
       257 
276 
     | 
    
         
             
                  file_size = File.size(file_path)
         
     | 
| 
       258 
277 
     | 
    
         
             
                  file_hash = calculate_file_hash(file_path)
         
     | 
| 
       259 
     | 
    
         
            -
                  metadata = "#{file_path}|||#{file_size}|||#{file_hash}"
         
     | 
| 
      
 278 
     | 
    
         
            +
                  metadata = "#{file_path}|||#{file_size}|||#{file_hash}|||#{source_dir}"
         
     | 
| 
       260 
279 
     | 
    
         
             
                  cmd += ["--comment", metadata]
         
     | 
| 
       261 
280 
     | 
    
         | 
| 
       262 
281 
     | 
    
         
             
                  cmd << "#{@repository.path}::#{archive_name}"
         
     | 
| 
         @@ -470,7 +489,7 @@ module Ruborg 
     | 
|
| 
       470 
489 
     | 
    
         | 
| 
       471 
490 
     | 
    
         
             
                    unless info_status.success?
         
     | 
| 
       472 
491 
     | 
    
         
             
                      # If we can't get info for this archive, skip it with defaults
         
     | 
| 
       473 
     | 
    
         
            -
                      hash[archive_name] = { path: "", size: 0, hash: "" }
         
     | 
| 
      
 492 
     | 
    
         
            +
                      hash[archive_name] = { path: "", size: 0, hash: "", source_dir: "" }
         
     | 
| 
       474 
493 
     | 
    
         
             
                      next
         
     | 
| 
       475 
494 
     | 
    
         
             
                    end
         
     | 
| 
       476 
495 
     | 
    
         | 
| 
         @@ -479,34 +498,44 @@ module Ruborg 
     | 
|
| 
       479 
498 
     | 
    
         
             
                    comment = archive_info["comment"] || ""
         
     | 
| 
       480 
499 
     | 
    
         | 
| 
       481 
500 
     | 
    
         
             
                    # Parse comment based on format
         
     | 
| 
       482 
     | 
    
         
            -
                    # The comment field stores metadata as: path|||size|||hash (using ||| as delimiter)
         
     | 
| 
      
 501 
     | 
    
         
            +
                    # The comment field stores metadata as: path|||size|||hash|||source_dir (using ||| as delimiter)
         
     | 
| 
       483 
502 
     | 
    
         
             
                    # For backward compatibility, handle old formats:
         
     | 
| 
       484 
503 
     | 
    
         
             
                    #   - Old format 1: plain path (no |||)
         
     | 
| 
       485 
504 
     | 
    
         
             
                    #   - Old format 2: path|||hash (2 parts)
         
     | 
| 
       486 
     | 
    
         
            -
                    #   -  
     | 
| 
      
 505 
     | 
    
         
            +
                    #   - Old format 3: path|||size|||hash (3 parts)
         
     | 
| 
      
 506 
     | 
    
         
            +
                    #   - New format: path|||size|||hash|||source_dir (4 parts)
         
     | 
| 
       487 
507 
     | 
    
         
             
                    if comment.include?("|||")
         
     | 
| 
       488 
508 
     | 
    
         
             
                      parts = comment.split("|||")
         
     | 
| 
       489 
509 
     | 
    
         
             
                      file_path = parts[0]
         
     | 
| 
       490 
     | 
    
         
            -
                      if parts.length >=  
     | 
| 
       491 
     | 
    
         
            -
                        # New format: path|||size|||hash
         
     | 
| 
      
 510 
     | 
    
         
            +
                      if parts.length >= 4
         
     | 
| 
      
 511 
     | 
    
         
            +
                        # New format: path|||size|||hash|||source_dir
         
     | 
| 
      
 512 
     | 
    
         
            +
                        file_size = parts[1].to_i
         
     | 
| 
      
 513 
     | 
    
         
            +
                        file_hash = parts[2] || ""
         
     | 
| 
      
 514 
     | 
    
         
            +
                        source_dir = parts[3] || ""
         
     | 
| 
      
 515 
     | 
    
         
            +
                      elsif parts.length >= 3
         
     | 
| 
      
 516 
     | 
    
         
            +
                        # Format 3: path|||size|||hash (no source_dir)
         
     | 
| 
       492 
517 
     | 
    
         
             
                        file_size = parts[1].to_i
         
     | 
| 
       493 
518 
     | 
    
         
             
                        file_hash = parts[2] || ""
         
     | 
| 
      
 519 
     | 
    
         
            +
                        source_dir = ""
         
     | 
| 
       494 
520 
     | 
    
         
             
                      else
         
     | 
| 
       495 
     | 
    
         
            -
                        # Old format: path|||hash (size not available)
         
     | 
| 
      
 521 
     | 
    
         
            +
                        # Old format: path|||hash (size and source_dir not available)
         
     | 
| 
       496 
522 
     | 
    
         
             
                        file_size = 0
         
     | 
| 
       497 
523 
     | 
    
         
             
                        file_hash = parts[1] || ""
         
     | 
| 
      
 524 
     | 
    
         
            +
                        source_dir = ""
         
     | 
| 
       498 
525 
     | 
    
         
             
                      end
         
     | 
| 
       499 
526 
     | 
    
         
             
                    else
         
     | 
| 
       500 
527 
     | 
    
         
             
                      # Oldest format: comment is just the path string
         
     | 
| 
       501 
528 
     | 
    
         
             
                      file_path = comment
         
     | 
| 
       502 
529 
     | 
    
         
             
                      file_size = 0
         
     | 
| 
       503 
530 
     | 
    
         
             
                      file_hash = ""
         
     | 
| 
      
 531 
     | 
    
         
            +
                      source_dir = ""
         
     | 
| 
       504 
532 
     | 
    
         
             
                    end
         
     | 
| 
       505 
533 
     | 
    
         | 
| 
       506 
534 
     | 
    
         
             
                    hash[archive_name] = {
         
     | 
| 
       507 
535 
     | 
    
         
             
                      path: file_path,
         
     | 
| 
       508 
536 
     | 
    
         
             
                      size: file_size,
         
     | 
| 
       509 
     | 
    
         
            -
                      hash: file_hash
         
     | 
| 
      
 537 
     | 
    
         
            +
                      hash: file_hash,
         
     | 
| 
      
 538 
     | 
    
         
            +
                      source_dir: source_dir
         
     | 
| 
       510 
539 
     | 
    
         
             
                    }
         
     | 
| 
       511 
540 
     | 
    
         
             
                  end
         
     | 
| 
       512 
541 
     | 
    
         
             
                rescue JSON::ParserError => e
         
     | 
    
        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 
     | 
    
         |