yobi 0.3.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56739c72c75a1d5c856bbd619b781dd3986cf1b3c1447d4864edd19b9543d8d9
4
- data.tar.gz: a5321fe628cab2f74011ce257a4eee380ed1fcb804456f8afa5482ac84c96b6f
3
+ metadata.gz: 4daa53c0b99656d99f3b81b808ad6940ad0fa6b8d202f0915b769721972a40a2
4
+ data.tar.gz: c1931310b0b9081099dedd3251cc304f01f8938ca0de13be82801289af1080db
5
5
  SHA512:
6
- metadata.gz: 9b2253d54dd64aa88658c7ddd53734a2edbcaf2623bb98258bc14d71d0d4a82bb3bcb447e6434bfb28505d2bfcd329988841d8f491ce7c3d1b13865b4a50b0f7
7
- data.tar.gz: 7cd169b509d216a99b258180381c9caa9cd92837b822467781b01c3788cc37af4252d825b4de50a511b71a521a4e8d9bff8317a7f9c0da11b82847c6b65c2b8c
6
+ metadata.gz: dbb9ae518fd0181e5c7cff763f74f113936cefa996eb2ab5bd741e5c61550b5a254cb6b795aadc67363cfdc064351aa2b77f830cb8f6dadde4372974dc1dffc5
7
+ data.tar.gz: 888067f9352c2a6bfe4493e7ea28770abc9df716c88ead2228b319ef7f07604884e977c1205e8726df61673ce0c79cf7f71d613296a6145d51af92ef72353ccf
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [1.0.0] - 2026-08-29
2
+
3
+ ### Changed
4
+
5
+ - **Breaking:** minimum supported Restic bumped from 0.17.1 to 0.18.0. Restic 0.17.x completely ignored `--json` for `tag` and `check`, so those commands ran without error but returned empty `TagOutcome`/`CheckOutcome` objects. 0.18.0 is the first version emitting structured JSON for both. `Yobi::UnsupportedResticVersion` is now raised against any 0.17.x binary.
6
+
7
+ ### Added
8
+
9
+ - `Restic#restic_path=` setter.
10
+ - `Repository#url=`, `#password=`, and `#backend_credentials=` setters, validated on assignment with the same shapes `#initialize` accepts.
11
+
1
12
  ## [0.3.1] - 2026-08-10
2
13
 
3
14
  ### Fixed
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Yobi
2
2
 
3
- Yobi is a Ruby interface for the [Restic](https://restic.net/) backup program.
3
+ [![Gem Version](https://badge.fury.io/rb/yobi.svg)](https://badge.fury.io/rb/yobi)
4
+
5
+ A Ruby interface for the [Restic](https://restic.net/) backup program.
6
+
7
+ Tested against Restic 0.18.0, 0.18.1, 0.19.0, 0.19.1.
4
8
 
5
9
  ## Installation
6
10
 
@@ -30,7 +34,7 @@ restic version
30
34
 
31
35
  ### Minimum supported version
32
36
 
33
- Yobi requires Restic **0.17.1** or newer. This isn't arbitrary: `Yobi::Restic`'s exit-code-based error dispatch (mapping Restic's own exit codes to `Yobi::RepositoryNotFound`/`RepositoryLocked`/`AuthenticationFailed`) depends on a convention that's only guaranteed since that version. An older Restic could reuse those same exit codes for different failures, so rather than risk silently misclassifying an error, Yobi checks the installed version before running any real command and raises `Yobi::UnsupportedResticVersion` if it's too old:
37
+ Yobi requires Restic **0.18.0** or newer. Its typed errors (`Yobi::RepositoryNotFound`, `RepositoryLocked`, `AuthenticationFailed`) rely on stable exit codes, and its `#tag`/`#check` outcomes rely on structured `--json` output that older restic didn't emit for those commands. Yobi checks the installed version before running any real command and raises `Yobi::UnsupportedResticVersion` if it's too old:
34
38
 
35
39
  ```ruby
36
40
  begin
@@ -40,7 +44,7 @@ rescue Yobi::UnsupportedResticVersion => e
40
44
  end
41
45
  ```
42
46
 
43
- See ["Minimum-version enforcement, in detail"](#minimum-version-enforcement-in-detail) below for the mechanics. If you'd rather fail fast at application startup instead of on the first backup attempt, call it explicitly:
47
+ If you'd rather fail fast at application startup instead of on the first backup attempt, call it explicitly:
44
48
 
45
49
  ```ruby
46
50
  RESTIC = Yobi::Restic.new
@@ -230,7 +234,7 @@ repo.init
230
234
  # => #<Yobi::Initialized id="..." repository="...">
231
235
  ```
232
236
 
233
- `copy_chunker_params:` copies chunker parameters from another repository (`from_repo:`/`from_password:`/etc.), so a later `#copy` between the two can deduplicate - see `#init_mirror` under ["Across repositories"](#across-repositories) for a shortcut that sets this up in one call. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#init-instance_method) for the full list of options. Returns an `Initialized` (`#id`/`#repository`).
237
+ `copy_chunker_params:` copies chunker parameters from another repository (`from_repo:`/`from_password:`/etc.), so a later `#copy` between the two can deduplicate - see `#init_mirror` under ["Across repositories"](#across-repositories) for a shortcut that sets this up in one call. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#init-instance_method) for the full list of options. Returns an `Initialized` (`#id`/`#repository`).
234
238
 
235
239
  ### `#cat_config`
236
240
 
@@ -261,7 +265,7 @@ outcome.summary["data_added"]
261
265
 
262
266
  `source:` is either a path String, or a `[:stdin_from_command, command]`/`[:stdin_from_command, command, filename]` tuple: Restic spawns and executes `command` itself, capturing its stdout as the backup content (see ["Database dumps via `stdin_from_command`"](#database-dumps-via-stdin_from_command) below for a `pg_dump` example). `command` can be a String (tokenized with `Shellwords.split`, so quoted arguments survive) or an Array of already-discrete arguments (used as-is; needed when an argument itself contains a literal space, which `Shellwords` would otherwise split incorrectly).
263
267
 
264
- `host:` records a hostname on the *new* snapshot - a single value, since this is metadata being written, not a filter, unlike every `hosts:` elsewhere in this API. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#backup-instance_method) for the full list of options (filtering, retention, timing, and more).
268
+ `host:` records a hostname on the *new* snapshot - a single value, since this is metadata being written, not a filter, unlike every `hosts:` elsewhere in this API. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#backup-instance_method) for the full list of options (filtering, retention, timing, and more).
265
269
 
266
270
  Returns a `BackupOutcome`: `#summary` (aliased `#report`; a `BackupSummary`, Restic's own summary fields plus `#backup_start`/`#backup_end` parsed into `Time`), `#errors` (lazy Enumerable of `BackupError` - empty unless some files were skipped, e.g. permission errors; a full failure raises before an outcome exists at all, see ["Error handling"](#error-handling) below), `#command_output` (the `source: [:stdin_from_command, ...]` subprocess's own stderr, de-prefixed, if any).
267
271
 
@@ -291,7 +295,7 @@ outcome.summary["snapshot_id"]
291
295
  repo.restore(snapshot_id: "latest", target: "/tmp/restore")
292
296
  ```
293
297
 
294
- `snapshot_id:` accepts `"latest"` or a real ID; `target:` is the destination directory. `delete:` removes files in `target:` not present in the snapshot - worth knowing about since it's the one option here that can destroy data outside the snapshot itself. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#restore-instance_method) for the full list of options (filtering, `overwrite:` behavior, and more).
298
+ `snapshot_id:` accepts `"latest"` or a real ID; `target:` is the destination directory. `delete:` removes files in `target:` not present in the snapshot - the one option here that can destroy data outside the snapshot itself. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#restore-instance_method) for the full list of options (filtering, `overwrite:` behavior, and more).
295
299
 
296
300
  Returns a `RestoreOutcome`: `#summary` (aliased `#report`; Restic's own summary fields as a plain `Hash`) - restore has no exit code 3, so any item-level failure is a hard error that raises before an outcome exists.
297
301
 
@@ -315,7 +319,7 @@ repo.dump(snapshot_id: "latest", file: "/var/www", target: "/tmp/www.tar")
315
319
  repo.dump(snapshot_id: "latest", file: "/var/www", target: "/tmp/www.zip", archive: "zip")
316
320
  ```
317
321
 
318
- Give at most one of `target:` or a block. Without either, returns a `Yobi::IOHandle` instead. `hosts:`/`paths:`/`tags:` filters are also available when `snapshot_id:` is `"latest"` - see the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#dump-instance_method) for the full list of options.
322
+ Give at most one of `target:` or a block. Without either, returns a `Yobi::IOHandle` instead. `hosts:`/`paths:`/`tags:` filters are also available when `snapshot_id:` is `"latest"` - see the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#dump-instance_method) for the full list of options.
319
323
 
320
324
  ```ruby
321
325
  handle = repo.dump(snapshot_id: "latest", file: "/var/www")
@@ -335,7 +339,7 @@ outcome.statistics.changed_files
335
339
  outcome.changes.each { |change| puts "#{change.modifier} #{change.path}" }
336
340
  ```
337
341
 
338
- `metadata: true` also reports metadata-only changes (permissions, timestamps) alongside content changes. `change.modifier` is Restic's own concatenation of single-character codes: `+` added, `-` removed, `U` metadata updated, `M` content modified, `T` type changed, `?` bitrot detected. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#diff-instance_method) for the full list of options.
342
+ `metadata: true` also reports metadata-only changes (permissions, timestamps) alongside content changes. `change.modifier` is Restic's own concatenation of single-character codes: `+` added, `-` removed, `U` metadata updated, `M` content modified, `T` type changed, `?` bitrot detected. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#diff-instance_method) for the full list of options.
339
343
 
340
344
  Returns a `DiffOutcome`: `#statistics` (aliased `#report`; a `DiffStatistics` with `#changed_files` plus `#added`/`#removed` `DiffStat` breakdowns) and lazy `#changes` (Enumerable of `DiffChange`, with predicates like `#modified?`/`#added?` alongside the raw `#modifier`).
341
345
 
@@ -349,7 +353,7 @@ repo.snapshots(tags: ["daily"], hosts: "web-1").each do |snapshot|
349
353
  end
350
354
  ```
351
355
 
352
- Returns an Enumerable of `Snapshot` (`#id`, `#short_id`, `#time`, `#host`, `#tags`, `#paths`, `#parent_id`, `#summary` - a `SnapshotSummary` with the same stats fields `#backup`'s own summary has). `group_by:`/`latest:` group and limit results, e.g. `latest: 1` per group for "the most recent backup of each host." See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#snapshots-instance_method) for the full list of options.
356
+ Returns an Enumerable of `Snapshot` (`#id`, `#short_id`, `#time`, `#host`, `#tags`, `#paths`, `#parent_id`, `#summary` - a `SnapshotSummary` with the same stats fields `#backup`'s own summary has). `group_by:`/`latest:` group and limit results, e.g. `latest: 1` per group for "the most recent backup of each host." See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#snapshots-instance_method) for the full list of options.
353
357
 
354
358
  ### `#tag`
355
359
 
@@ -357,7 +361,7 @@ Returns an Enumerable of `Snapshot` (`#id`, `#short_id`, `#time`, `#host`, `#tag
357
361
  repo.tag(snapshot_ids: snapshot.id, add: "verified")
358
362
  ```
359
363
 
360
- `add:`/`remove:`/`set:` (mutually exclusive with `add:`/`remove:` in Restic itself) modify tags on snapshots matched by `snapshot_ids:` (or by `hosts:`/`paths:`/`tags:` filters when no explicit IDs are given). Since tags are part of a snapshot's content-addressed identity, tagging produces a *new* snapshot ID for every snapshot touched. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#tag-instance_method) for the full list of options.
364
+ `add:`/`remove:`/`set:` (mutually exclusive with `add:`/`remove:` in Restic itself) modify tags on snapshots matched by `snapshot_ids:` (or by `hosts:`/`paths:`/`tags:` filters when no explicit IDs are given). Since tags are part of a snapshot's content-addressed identity, tagging produces a *new* snapshot ID for every snapshot touched. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#tag-instance_method) for the full list of options.
361
365
 
362
366
  Returns a `TagOutcome`: `#summary` (aliased `#report`; a `TagSummary` - just `#changed_snapshots`) and lazy `#changes` (Enumerable of `TagChange`, the old-ID/new-ID pairs).
363
367
 
@@ -367,7 +371,7 @@ Returns a `TagOutcome`: `#summary` (aliased `#report`; a `TagSummary` - just `#c
367
371
  repo.forget(keep_daily: 7, keep_weekly: 4, keep_monthly: 12, prune: true)
368
372
  ```
369
373
 
370
- Applies a retention policy, removing snapshots that don't match any `keep_*` rule (counts like `keep_daily:`, or durations like `keep_within:`). `prune: true` also reclaims the disk space the forgotten snapshots held (equivalent to a separate `#prune` call afterward). See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#forget-instance_method) for the full list of options.
374
+ Applies a retention policy, removing snapshots that don't match any `keep_*` rule (counts like `keep_daily:`, or durations like `keep_within:`). `prune: true` also reclaims the disk space the forgotten snapshots held (equivalent to a separate `#prune` call afterward). See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#forget-instance_method) for the full list of options.
371
375
 
372
376
  Returns an Enumerable of `ForgetGroup` (Restic evaluates the policy per group, by default grouped by host+paths). Each exposes `#keep`/`#remove` (Arrays of `Snapshot`) and `#reasons` (an Array of `KeepReason` explaining which rule kept each surviving snapshot, e.g. `"daily snapshot"`).
373
377
 
@@ -380,9 +384,9 @@ repo.find(patterns: "*.pem").each do |matches|
380
384
  end
381
385
  ```
382
386
 
383
- Searches for files/directories by name pattern across snapshots. `blob:`/`pack:`/`tree:` switch to matching object IDs instead, for low-level troubleshooting. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#find-instance_method) for the full list of options.
387
+ Searches for files/directories by name pattern across snapshots. `blob:`/`pack:`/`tree:` switch to matching object IDs instead, for low-level troubleshooting. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#find-instance_method) for the full list of options.
384
388
 
385
- Returns an Enumerable of `MatchesPerSnapshot` (Restic's own docs describe find's output as "organized by snapshot," not a flat list). Each has `#snapshot`/`#snapshot_id` (just the matched snapshot's ID as a String - unlike elsewhere in this API, Restic's own `find` output doesn't include the full record), `#hits`, and `#matches` (an Array of `FindMatch`, with the usual file metadata: `#path`, `#size`, `#mtime`, etc.).
389
+ Returns an Enumerable of `MatchesPerSnapshot`, one per snapshot with matches. Each has `#snapshot`/`#snapshot_id` (just the matched snapshot's ID as a String - unlike elsewhere in this API, Restic's own `find` output doesn't include the full record), `#hits`, and `#matches` (an Array of `FindMatch`, with the usual file metadata: `#path`, `#size`, `#mtime`, etc.).
386
390
 
387
391
  ### `#ls`
388
392
 
@@ -392,7 +396,7 @@ outcome.snapshot.short_id
392
396
  outcome.nodes.each { |node| puts node.path }
393
397
  ```
394
398
 
395
- Lists a snapshot's files/directories. `recursive:` descends into subdirectories. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#ls-instance_method) for the full list of options.
399
+ Lists a snapshot's files/directories. `recursive:` descends into subdirectories. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#ls-instance_method) for the full list of options.
396
400
 
397
401
  Returns an `LsOutcome`: `#snapshot` (the resolved `Snapshot`, useful to see what `"latest"` actually resolved to) and lazy `#nodes` (aliased `#entries`; Enumerable of `Node` - Restic's own term for a file/directory entry).
398
402
 
@@ -406,7 +410,7 @@ outcome.summary.num_errors
406
410
  outcome.errors.each { |error| puts error.message }
407
411
  ```
408
412
 
409
- Verifies repository integrity. `read_data:` also reads and verifies every pack file's actual contents, not just structure (slow, thorough); `read_data_subset:` does a partial version of the same (e.g. `"5%"`, or `"1/20"` for a fifth each day in rotation). See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#check-instance_method) for the full list of options.
413
+ Verifies repository integrity. `read_data:` also reads and verifies every pack file's actual contents, not just structure (slow, thorough); `read_data_subset:` does a partial version of the same (e.g. `"5%"`, or `"1/20"` for a fifth each day in rotation). See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#check-instance_method) for the full list of options.
410
414
 
411
415
  Returns a `CheckOutcome`: `#summary` (aliased `#report`; a `CheckSummary` covering the error count and what to run next - repair or prune) and `#errors` (lazy Enumerable of `CheckError`) for problems found.
412
416
 
@@ -416,7 +420,7 @@ Returns a `CheckOutcome`: `#summary` (aliased `#report`; a `CheckSummary` coveri
416
420
  repo.prune(max_unused: "5%")
417
421
  ```
418
422
 
419
- Removes data no longer referenced by any snapshot. `dry_run:` reports what would happen without doing it; `max_unused:` targets a maximum acceptable unused-space ratio after pruning. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#prune-instance_method) for the full list of options. Returns `true`.
423
+ Removes data no longer referenced by any snapshot. `dry_run:` reports what would happen without doing it; `max_unused:` targets a maximum acceptable unused-space ratio after pruning. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#prune-instance_method) for the full list of options. Returns `true`.
420
424
 
421
425
  ### `#repair_index`, `#repair_packs`, `#repair_snapshots`
422
426
 
@@ -426,7 +430,7 @@ repo.repair_packs(ids: damaged_pack_ids)
426
430
  repo.repair_snapshots(snapshot_ids: affected_ids, forget: true)
427
431
  ```
428
432
 
429
- `#repair_index` rebuilds the index from the pack files present (the modern successor to Restic's now-deprecated `rebuild-index`). `#repair_packs` extracts intact blobs from damaged pack files and drops the rest. Restic also writes a backup copy of each given pack (named `pack-<id>`) into the *calling process's own current working directory* first, with no flag to disable this; be aware of where your process runs from before calling it. `#repair_snapshots` regenerates snapshots with damaged content removed. This is real data loss for whatever gets removed, so prefer a fresh `#backup` where the source data is still available, and run `#repair_index` first since this depends on a correct index. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#repair_snapshots-instance_method) for the full list of options.
433
+ `#repair_index` rebuilds the index from the pack files present (the modern successor to Restic's now-deprecated `rebuild-index`). `#repair_packs` extracts intact blobs from damaged pack files and drops the rest. Restic also writes a backup copy of each given pack (named `pack-<id>`) into the *calling process's own current working directory* first, with no flag to disable this; be aware of where your process runs from before calling it. `#repair_snapshots` regenerates snapshots with damaged content removed - depends on a correct index, so run `#repair_index` first. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#repair_snapshots-instance_method) for the full list of options.
430
434
 
431
435
  ### `#recover`
432
436
 
@@ -434,7 +438,7 @@ repo.repair_snapshots(snapshot_ids: affected_ids, forget: true)
434
438
  repo.recover
435
439
  ```
436
440
 
437
- Builds a new snapshot from any data present in the repository but not referenced by an existing snapshot (e.g. after an accidental `#forget`). Call `#snapshots` afterward to see whether anything was actually recovered. Restic reports "nothing to do" as plain text with no structured way to tell the two cases apart.
441
+ Builds a new snapshot from any data present in the repository but not referenced by an existing snapshot (e.g. after an accidental `#forget`). Call `#snapshots` afterward to see whether anything was actually recovered.
438
442
 
439
443
  ### `#rewrite`
440
444
 
@@ -442,7 +446,7 @@ Builds a new snapshot from any data present in the repository but not referenced
442
446
  repo.rewrite(snapshot_ids: old.id, excludes: "*.log", new_time: Time.now.iso8601)
443
447
  ```
444
448
 
445
- Creates new snapshots from existing ones with filters applied or metadata changed. With no `snapshot_ids:`/`hosts:`/`tags:`/`paths:` given, rewrites every snapshot in the repository - Restic's own default, not something Yobi adds. `dry_run:` reports what would happen without doing it. `forget: false` (the default) tags the new snapshots `"rewrite"` and keeps the originals; `forget: true` removes the originals instead (their data isn't reclaimed until a later `#prune`). See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#rewrite-instance_method) for the full list of options.
449
+ Creates new snapshots from existing ones with filters applied or metadata changed. With no `snapshot_ids:`/`hosts:`/`tags:`/`paths:` given, rewrites every snapshot in the repository. `dry_run:` reports what would happen without doing it. `forget: false` (the default) tags the new snapshots `"rewrite"` and keeps the originals; `forget: true` removes the originals instead (their data isn't reclaimed until a later `#prune`). See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#rewrite-instance_method) for the full list of options.
446
450
 
447
451
  ### `#migrate`
448
452
 
@@ -467,7 +471,7 @@ repo.stats(mode: "raw-data")
467
471
  # => #<Yobi::RepositoryStats total_size=... total_file_count=... snapshots_count=...>
468
472
  ```
469
473
 
470
- Repository size/file-count statistics. `mode:` picks the counting strategy, as a String or Symbol: `"restore-size"`/`:restore_size` (default), `"files-by-contents"`/`:files_by_contents`, `"blobs-per-file"`/`:blobs_per_file`, `"raw-data"`/`:raw_data` (actual on-disk size, accounting for deduplication). Anything else raises `ArgumentError`. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#stats-instance_method) for more. Returns a `RepositoryStats` covering size, file/blob counts, and (for a repository using compression) how much space it's saving.
474
+ Repository size/file-count statistics. `mode:` picks the counting strategy, as a String or Symbol: `"restore-size"`/`:restore_size` (default), `"files-by-contents"`/`:files_by_contents`, `"blobs-per-file"`/`:blobs_per_file`, `"raw-data"`/`:raw_data` (actual on-disk size, accounting for deduplication). Anything else raises `ArgumentError`. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#stats-instance_method) for more. Returns a `RepositoryStats` covering size, file/blob counts, and (for a repository using compression) how much space it's saving.
471
475
 
472
476
  ## Key management
473
477
 
@@ -480,9 +484,9 @@ repo.key_remove(id: old_key.id)
480
484
 
481
485
  `#key_add`/`#key_list`/`#key_passwd`/`#key_remove` (aliased `#add_key`/`#keys`/`#change_password`/`#remove_key`) manage passwords. Every key here is a different way to unlock the same underlying master key, not a separate encryption key of its own (see ["Low-level (`cat`) and object listing"](#low-level-cat-and-object-listing) below's note on `#cat_masterkey_and_game_over_if_this_leaks`).
482
486
 
483
- `#key_add`/`#key_passwd` share the same kwargs: `new_password:` is required, accepting the same shapes as `#initialize`'s `password:` minus `[:command, ...]`: a literal String; a `[:file, "..."]` tuple, resolved natively by Restic; `:insecure_no_password`; or anything responding to `#call`. Restic itself only accepts a new password by file, unlike the repository's own `password:`, so a literal String or callable is written to a briefly-lived, 0600-permissioned tempfile by Yobi first. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#key_add-instance_method) for the full list of options.
487
+ `#key_add`/`#key_passwd` share the same kwargs: `new_password:` is required, accepting the same shapes as `#initialize`'s `password:` minus `[:command, ...]`: a literal String; a `[:file, "..."]` tuple, resolved natively by Restic; `:insecure_no_password`; or anything responding to `#call`. Restic itself only accepts a new password by file, unlike the repository's own `password:`, so a literal String or callable is written to a briefly-lived, 0600-permissioned tempfile by Yobi first. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#key_add-instance_method) for the full list of options.
484
488
 
485
- `#key_passwd` rotates the *password*, not the underlying master key itself (see the note on `#cat_masterkey_and_game_over_if_this_leaks` below if that distinction matters for your threat model). On success, it also updates this same `Repository` instance's own `password:` to match, so it keeps working against the repository right afterward:
489
+ `#key_passwd` rotates the *password*, not the underlying master key itself (see the note on `#cat_masterkey_and_game_over_if_this_leaks` below). On success, it also updates this same `Repository` instance's own `password:` to match, so it keeps working against the repository right afterward:
486
490
 
487
491
  ```ruby
488
492
  repo.key_passwd(new_password: "new-password-value")
@@ -503,7 +507,7 @@ repo.list(:packs) # => Array of pack IDs
503
507
 
504
508
  `#cat_snapshot`/`#cat_index`/`#cat_key`/`#cat_tree` return a repository object's raw stored record as a Hash, lower-level than `#snapshots`/`#key_list`, which wrap entries in `Snapshot`/`Key` instead. `#cat_snapshot`/`#cat_key`/`#cat_tree` each accept either a bare ID String or the corresponding wrapper object (`Snapshot`/`Key`/`Snapshot`) directly; `#cat_index` only takes a bare ID String, since index entries have no wrapper class of their own. `#cat_pack`/`#cat_blob` are raw-bytes commands with the same block/`IOHandle` shape as `#dump`. `#list(type)` enumerates every object ID of a given type (`:blobs`/`:packs`/`:index`/`:snapshots`/`:keys`/`:locks`) as plain strings. Restic ignores `--json` for this command, so these come back exactly as Restic prints them.
505
509
 
506
- `#cat_masterkey_and_game_over_if_this_leaks` returns the repository's actual encryption/MAC key material: not a redacted reference, the real thing. There is no operation that rotates this key; every password change made afterward protects nothing already backed up if this value ever leaks. Treat the return value with more care than you'd give `password:` itself: unlike a password, this can't be rotated away after a leak.
510
+ `#cat_masterkey_and_game_over_if_this_leaks` returns the repository's actual encryption/MAC key material: not a redacted reference, the real thing. There is no operation that rotates this key; a leak means every past snapshot in the repository stays decryptable forever, no password change fixes that.
507
511
 
508
512
  ## Across repositories
509
513
 
@@ -521,7 +525,7 @@ A shortcut for setting up a `#copy` destination: constructs a new `Repository` a
521
525
  mirror_repo.copy(from_repo: primary_repo, tags: "nightly")
522
526
  ```
523
527
 
524
- Replicates snapshots from another repository into this one. `from_repo:` accepts a plain URL String, a `[:file, "..."]` tuple reading the URL from a file, or a `Repository` instance. Given the latter, its own `#url`/`#password` are used automatically unless `from_password:` is given explicitly. Already-copied snapshots are skipped automatically on a repeat run. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#copy-instance_method) for the full list of options. Returns `true`.
528
+ Replicates snapshots from another repository into this one. `from_repo:` accepts a plain URL String, a `[:file, "..."]` tuple reading the URL from a file, or a `Repository` instance. Given the latter, its own `#url`/`#password` are used automatically unless `from_password:` is given explicitly. Already-copied snapshots are skipped automatically on a repeat run. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#copy-instance_method) for the full list of options. Returns `true`.
525
529
 
526
530
  ## Mounting
527
531
 
@@ -544,7 +548,7 @@ ensure
544
548
  end
545
549
  ```
546
550
 
547
- `#stop` is safe to call more than once, and `MountHandle#pid`/`MountHandle#stop` are safe even if something *else* already triggered the unmount externally (`kill -INT <pid>`, or the OS's own `umount`/`fusermount` directly). `hosts:`/`paths:`/`tags:` restrict which snapshots appear under the mount's own `snapshots/` directory. `ready_timeout:` (default 10 seconds) bounds how long `#mount` waits for Restic's own readiness message before raising `Yobi::MountTimeout`. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#mount-instance_method) for the full list of options.
551
+ `#stop` is safe to call more than once, and `MountHandle#pid`/`MountHandle#stop` are safe even if something *else* already triggered the unmount externally (`kill -INT <pid>`, or the OS's own `umount`/`fusermount` directly). `hosts:`/`paths:`/`tags:` restrict which snapshots appear under the mount's own `snapshots/` directory. `ready_timeout:` (default 10 seconds) bounds how long `#mount` waits for Restic's own readiness message before raising `Yobi::MountTimeout`. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Repository#mount-instance_method) for the full list of options.
548
552
 
549
553
  ## Restic-level (not scoped to a repository)
550
554
 
@@ -554,11 +558,11 @@ restic.version.version # => "0.19.1"
554
558
  restic.cache(cleanup: true)
555
559
  ```
556
560
 
557
- `#version` and `#cache` are the two Restic subcommands that don't touch a repository at all, so they live on `Yobi::Restic` rather than `Repository`. `#version` returns a `Yobi::ResticVersion` (`#version`, `#go_version`, `#go_os`, `#go_arch`); if the installed binary is old enough to ignore `--json` for this command entirely, Yobi falls back to parsing its plain-text output for the version number instead of raising a JSON parse error. `#cache` lists (or, with `cleanup: true`, cleans) local cache directories, returning `true`. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Restic#cache-instance_method) for its remaining options.
561
+ `#version` and `#cache` are the two Restic subcommands that don't touch a repository at all, so they live on `Yobi::Restic` rather than `Repository`. `#version` returns a `Yobi::ResticVersion` (`#version`, `#go_version`, `#go_os`, `#go_arch`); if the installed binary is old enough to ignore `--json` for this command entirely, Yobi falls back to parsing its plain-text output for the version number instead of raising a JSON parse error. `#cache` lists (or, with `cleanup: true`, cleans) local cache directories, returning `true`. See the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Restic#cache-instance_method) for its remaining options.
558
562
 
559
563
  ## Global `Restic` settings
560
564
 
561
- Settings that apply regardless of which repository is being operated on live on `Yobi::Restic`, not `Repository`:
565
+ `Yobi::Restic` holds settings that apply to every command, regardless of repository:
562
566
 
563
567
  ```ruby
564
568
  restic = Yobi::Restic.new(
@@ -568,14 +572,9 @@ restic = Yobi::Restic.new(
568
572
  )
569
573
  ```
570
574
 
571
- Two genuinely different kinds, both exposed as plain `attr_accessor`s:
572
-
573
- - **Env-var-backed** (e.g. `cache_dir:`, `compression:`, `pack_size:`): `Restic#env` computes the corresponding `RESTIC_*` vars fresh from the current accessor values on every call. Mutating one after construction is picked up by the very next command.
574
- - **CLI-flag-only, no env var equivalent** (e.g. `limit_upload:`, `no_lock:`, `options:` - Restic's own `--option`/`-o`, an escape hatch for backend-specific tuning like `s3.connections=10`).
575
+ Some map to `RESTIC_*` env vars (`cache_dir:`, `compression:`, `pack_size:`). Others are CLI flags with no env equivalent (`limit_upload:`, `no_lock:`, `options:`, etc.). `options:` maps to Restic's `-o` for backend tuning like `s3.connections=10`.
575
576
 
576
- See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Restic) for the full list of settings.
577
-
578
- "Picked up by the very next command" assumes calls happen one at a time. If you mutate one of these accessors on a `Restic` shared across threads while another thread has a command already in flight on it, which value that in-flight command actually uses is a race with no ordering guarantee, though not a crash: each accessor read/write is still atomic, so you won't get a corrupted argv, just an unpredictable choice between the old and new value.
577
+ Every setting is an `attr_accessor`. Change one and the next command sees the new value. Full list in the [docs](https://rubydoc.info/gems/yobi/1.0.0/Yobi/Restic).
579
578
 
580
579
  ### Sharing a `Restic` across repositories
581
580
 
@@ -586,11 +585,9 @@ customer_a = Yobi::Repository.new(url: "s3:.../customer-a", password: "...", res
586
585
  customer_b = Yobi::Repository.new(url: "s3:.../customer-b", password: "...", restic: restic)
587
586
  ```
588
587
 
589
- Both repositories now use the same `limit_upload:` value, and changing `restic.limit_upload` later affects every repository built from it. This isn't a shared, pooled cap: Restic applies `--limit-upload` per invocation, so two backups running concurrently through this same `restic` are each independently capped at 5000, not splitting one combined 5000 between them. Useful when one process backs up many repositories under one operational policy, without repeating the same settings on every `Repository.new` call.
590
-
591
- ### Minimum-version enforcement, in detail
588
+ Both use the same settings. Changing `restic.limit_upload` later affects every repository built from it.
592
589
 
593
- `Yobi::Restic#run`/`#run_dump`/`#run_mount` all call `#ensure_minimum_version!` before executing a real command. It's memoized per `Restic` instance. The check runs once, not on every call, since the binary on disk isn't expected to change mid-process. See ["Minimum supported version"](#minimum-supported-version) above for what triggers it and why 0.17.1 specifically.
590
+ `--limit-upload` is per-invocation. Two backups running concurrently through the same `restic` are each capped at 5000. They don't share one 5000 budget.
594
591
 
595
592
  ## Redaction
596
593
 
@@ -601,7 +598,7 @@ repo.inspect
601
598
  # => #<Yobi::Repository url="s3:..." password="[FILTERED]" backend_credentials={"AWS_ACCESS_KEY_ID"=>"[FILTERED]", "AWS_SECRET_ACCESS_KEY"=>"[FILTERED]"}>
602
599
  ```
603
600
 
604
- A literal or command/file-sourced `password:` shows as `"[FILTERED]"`; anything callable shows as `"[RESOLVER]"` **without ever being called**. Printing a `Repository` never triggers a real secret-store lookup as a side effect. `:insecure_no_password` shows plainly, since it's an explicit "there is no secret" declaration, not a value that could leak anything. `backend_credentials:` keys not in Restic's own documented list of non-identity operational env vars (`Yobi::Restic::ALLOWED_ENV_VARS`) are filtered the same way; the allowed ones (`RESTIC_CACHE_DIR`, `AWS_DEFAULT_REGION`, and similar non-secret operational vars) stay visible.
601
+ A literal or command/file-sourced `password:` shows as `"[FILTERED]"`; anything callable shows as `"[RESOLVER]"` **without ever being called**. `:insecure_no_password` shows plainly, since it's an explicit "there is no secret" declaration, not a value that could leak anything. `backend_credentials:` keys not in Restic's own documented list of non-identity operational env vars (`Yobi::Restic::ALLOWED_ENV_VARS`) are filtered the same way; the allowed ones (`RESTIC_CACHE_DIR`, `AWS_DEFAULT_REGION`, and similar non-secret operational vars) stay visible.
605
602
 
606
603
  `#env` itself is never redacted. It's the method that actually builds what gets passed to `Process.spawn`, so it has to return the real values. Only `#inspect` (the thing a stray debug print might accidentally call) filters.
607
604
 
@@ -636,7 +633,7 @@ repo.forget(
636
633
  )
637
634
  ```
638
635
 
639
- A non-empty `outcome.errors` (exit code 3) means some files were skipped but the snapshot was still created (often fine to continue past, e.g. permission-denied on a handful of files); a full failure raises before `outcome` even exists via the usual `Yobi::ResticCommandFailed`/`Yobi::RepositoryLocked`/etc. hierarchy, so wrap the whole thing in the usual `rescue` if you want to alert on that separately (see ["Graceful lock-contention handling"](#graceful-lock-contention-handling) below).
636
+ A non-empty `outcome.errors` (exit code 3) means some files were skipped but the snapshot was still created - often fine to continue past. A full failure raises `Yobi::ResticCommandFailed`/`Yobi::RepositoryLocked`/etc. before `outcome` exists at all; handle those with a `rescue` around the block (see ["Graceful lock-contention handling"](#graceful-lock-contention-handling)).
640
637
 
641
638
  ### Restore workflows
642
639
 
@@ -704,7 +701,7 @@ password: -> {
704
701
  }
705
702
  ```
706
703
 
707
- If the resolver itself is slow or rate-limited (a network round-trip per Restic invocation adds up over many commands), add your own caching inside the lambda; how long the result stays valid is entirely your call.
704
+ If the resolver itself is slow or rate-limited (a network round-trip per Restic invocation adds up over many commands), add your own caching inside the lambda.
708
705
 
709
706
  ### Database dumps via `stdin_from_command`
710
707
 
@@ -717,7 +714,7 @@ repo.backup(
717
714
  )
718
715
  ```
719
716
 
720
- The third element (`"mydb.sql"`) names the resulting virtual file inside the snapshot. Omit it to fall back to Restic's own `"stdin"` default. If the command needs an argument containing a literal space (a password with a space in it, an unusual path), pass it as an Array of already-discrete arguments instead of a single String, since `Shellwords`-splitting a string would otherwise break it apart incorrectly. There's no shell involved either way (Restic execs the tokenized argv directly), but if any part of the String form is ever built from untrusted input rather than a hardcoded command like above, `Shellwords.split` will still tokenize whatever that input contains, letting it inject extra arguments into what gets executed. Use the Array form with untrusted values kept to their own discrete elements instead.
717
+ The third element (`"mydb.sql"`) names the resulting virtual file inside the snapshot. Omit it for Restic's own `"stdin"` default. Use the Array form (`["mysqldump", "-u", user, db]`) when any argument contains spaces, or when any part comes from untrusted input - the String form is tokenized with `Shellwords.split`, which will split spaces inside an argument and, on untrusted input, let it inject extra arguments.
721
718
 
722
719
  ```ruby
723
720
  repo.backup(source: [:stdin_from_command, ["mysqldump", "-u", "backup", "my app db"]])
@@ -772,7 +769,7 @@ Customer.find_each do |customer|
772
769
  end
773
770
  ```
774
771
 
775
- Running commands concurrently through a shared `Repository`/`Restic` is safe (see ["Global `Restic` settings"](#global-restic-settings) above for the one caveat, mutating `restic`'s tuning accessors from another thread while backups are in flight), so backing up many repositories doesn't have to run strictly one at a time. A `Thread.new` per customer would spawn an unbounded number of concurrent `restic` subprocesses, and Ruby silently drops an unhandled exception from a thread that's never joined, so a single customer's failure could vanish instead of getting logged. A small fixed-size worker pool avoids both:
772
+ To run backups concurrently, use a bounded worker pool. It caps how many `restic` subprocesses run at once and catches per-customer errors that would otherwise disappear with the thread:
776
773
 
777
774
  ```ruby
778
775
  queue = Queue.new
@@ -836,7 +833,7 @@ end
836
833
  with_retry { repo.backup(source: source_path) }
837
834
  ```
838
835
 
839
- For a one-off manual fix instead of retrying, `repo.unlock` removes stale locks left by a process that crashed without cleaning up after itself, but don't call it reflexively inside a retry loop that might be racing a *legitimately still-running* second process; that would double-run the backup.
836
+ For a one-off manual fix, `repo.unlock` removes stale locks left by a process that crashed without cleaning up. Don't put it inside the retry loop above - a lock might belong to a live second process, not a dead one.
840
837
 
841
838
  ## Non-goals
842
839
 
@@ -855,7 +852,7 @@ After checking out the repo, run `bin/setup` to install dependencies.
855
852
  - `bin/test`: both, in parallel.
856
853
  - `bin/standardrb`: lint.
857
854
  - `bin/console`: an interactive prompt with the gem loaded.
858
- - `bin/docs`: serves YARD docs at http://localhost:8808, reparsing on every request.
855
+ - `bin/docs`: regenerates RDoc into `./doc` and opens the index in the default browser.
859
856
 
860
857
  See `MAINTAINERS.md` for what to keep in sync as the API changes, and the release checklist.
861
858
 
@@ -1,17 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yobi
4
- # Builds a Restic command's argv incrementally. Not an Array subclass:
5
- # only the methods below are available; {#to_a} hands back the plain
6
- # Array once building is done.
7
- #
8
- # @private
9
- class ArgvBuilder
10
- # Maps a flag name Symbol to its "--dashed-string" form, e.g.
11
- # `:read_data_subset` to `"--read-data-subset"`.
4
+ class ArgvBuilder # :nodoc:
12
5
  FLAGS = Hash.new { |flags, name| flags[name] = "--#{name.to_s.tr("_", "-")}" }
13
-
14
- # Maps a short flag name Symbol to its "-name" form, e.g. `:vv` to `"-vv"`.
15
6
  SHORT_FLAGS = Hash.new { |flags, name| flags[name] = "-#{name}" }
16
7
 
17
8
  def initialize
@@ -19,21 +10,14 @@ module Yobi
19
10
  @end_of_options_called = false
20
11
  end
21
12
 
22
- # @return [Array<String>] the argv built so far
23
13
  def to_a
24
14
  @argv
25
15
  end
26
16
 
27
- # @return [String]
28
17
  def inspect
29
18
  "#<#{self.class} #{@argv.inspect}>"
30
19
  end
31
20
 
32
- # Appends one or more bare positional values. `nil` is dropped; an
33
- # Enumerable is flattened in; everything else is converted with `#to_s`.
34
- #
35
- # @param values [Array<Object>]
36
- # @return [self]
37
21
  def append(*values)
38
22
  values.each do |v|
39
23
  case v
@@ -49,41 +33,23 @@ module Yobi
49
33
  self
50
34
  end
51
35
 
52
- # Appends a flag, plus its value when one is given.
53
- #
54
- # @param name [Symbol] flag name, looked up in {FLAGS}
55
- # @param value [Object, nil] the flag's value; omit for a boolean flag
56
- # @return [self]
57
36
  def flag(name, value = nil)
58
37
  @argv << FLAGS[name]
59
38
  @argv << value.to_s unless value.nil?
60
39
  self
61
40
  end
62
41
 
63
- # Appends a repeatable flag once per value, e.g. `--host a --host b`.
64
- #
65
- # @param name [Symbol] flag name, looked up in {FLAGS}
66
- # @param values [Array<Object>, Object, nil]
67
- # @return [self]
68
42
  def repeat_flag(name, values)
69
43
  name = FLAGS[name]
70
44
  array_of_strings(values).each { |value| @argv << name << value }
71
45
  self
72
46
  end
73
47
 
74
- # Appends a short flag, e.g. `short_flag(:vv)` for `-vv`.
75
- #
76
- # @param name [Symbol] flag name, looked up in {SHORT_FLAGS}
77
- # @return [self]
78
48
  def short_flag(name)
79
49
  @argv << SHORT_FLAGS[name]
80
50
  self
81
51
  end
82
52
 
83
- # Appends Restic's `--` end-of-options marker.
84
- #
85
- # @return [self]
86
- # @raise [RuntimeError] if called more than once on the same builder
87
53
  def end_of_options
88
54
  raise "end_of_options already called - Restic only honors the first \"--\"" if @end_of_options_called
89
55
 
data/lib/yobi/errors.rb CHANGED
@@ -6,38 +6,35 @@ module Yobi
6
6
  # Base class for all errors raised by Yobi.
7
7
  class Error < StandardError; end
8
8
 
9
- # The `exit_error` message from a fatal Restic run, if one was printed.
10
- # https://restic.readthedocs.io/en/stable/075_scripting.html#exit-errors
9
+ # The +exit_error+ message from a fatal Restic run, if one was printed.
10
+ # See https://restic.readthedocs.io/en/stable/075_scripting.html#exit-errors.
11
11
  class ExitError < Yobi::FancyHash
12
- # @private
13
- LINE_PATTERN = /"message_type"\s*:\s*"exit_error"/
12
+ LINE_PATTERN = /"message_type"\s*:\s*"exit_error"/ # :nodoc:
14
13
 
15
- # @param output [Yobi::ResticOutput]
16
- # @return [Yobi::ExitError, nil] `nil` if no `exit_error` line is present
14
+ # Builds an ExitError from a Yobi::ResticOutput, or +nil+ if no
15
+ # +exit_error+ line is present in it.
17
16
  def self.from_output(output)
18
17
  line = output.each_line.find { |candidate| LINE_PATTERN.match?(candidate) }
19
18
  new(JSON.parse(line)) if line
20
19
  end
21
20
 
22
- # @return [String, nil]
21
+ # Restic's own +exit_error+ code String, if present.
23
22
  def code
24
23
  self["code"]
25
24
  end
26
25
 
27
- # @return [String, nil]
26
+ # Restic's own +exit_error+ message String, if present.
28
27
  def message
29
28
  self["message"]
30
29
  end
31
30
  end
32
31
 
33
32
  # Raised when the installed Restic binary is older than
34
- # {Yobi::Restic::MINIMUM_VERSION}.
33
+ # Yobi::Restic::MINIMUM_VERSION.
35
34
  class UnsupportedResticVersion < Error
36
- # @return [String]
37
35
  attr_reader :installed_version, :minimum_version
38
36
 
39
- # @private
40
- def initialize(installed_version:, minimum_version:)
37
+ def initialize(installed_version:, minimum_version:) # :nodoc:
41
38
  @installed_version = installed_version
42
39
  @minimum_version = minimum_version
43
40
  super("Restic #{installed_version} does not meet the minimum supported version #{minimum_version}")
@@ -46,13 +43,9 @@ module Yobi
46
43
 
47
44
  # Raised when the Restic binary itself can't be found or executed.
48
45
  class ResticNotFound < Error
49
- # @return [String]
50
- attr_reader :restic_path
51
- # @return [Array<String>]
52
- attr_reader :argv
46
+ attr_reader :restic_path, :argv
53
47
 
54
- # @private
55
- def initialize(restic_path:, argv:)
48
+ def initialize(restic_path:, argv:) # :nodoc:
56
49
  @restic_path = restic_path
57
50
  @argv = argv
58
51
  super("Restic binary not found: #{restic_path.inspect} (tried to run #{argv.inspect})")
@@ -62,13 +55,12 @@ module Yobi
62
55
  # Raised when Restic exits with a failure code. Base class for the
63
56
  # specific typed errors below.
64
57
  class ResticExecutionError < Error
65
- # @return [Hash] the raw `{exit_code:, output:, argv:}` execution result
58
+ # The raw +{exit_code:, output:, argv:}+ execution result.
66
59
  attr_reader :execution
67
- # @return [Yobi::ExitError, nil]
60
+ # A Yobi::ExitError parsed out of the output, if one is present.
68
61
  attr_reader :exit_error
69
62
 
70
- # @private
71
- def initialize(execution)
63
+ def initialize(execution) # :nodoc:
72
64
  @execution = execution
73
65
  @exit_error = Yobi::ExitError.from_output(execution[:output])
74
66
  super(exit_error&.message || "Restic exited with status #{execution[:exit_code]}: #{execution[:output]}")
@@ -87,16 +79,12 @@ module Yobi
87
79
  # Raised when Restic exits with a failure code not otherwise classified above.
88
80
  class ResticCommandFailed < ResticExecutionError; end
89
81
 
90
- # Raised by {Yobi::Repository#mount} when Restic doesn't report itself
91
- # ready within `ready_timeout:` seconds.
82
+ # Raised by Yobi::Repository#mount when Restic doesn't report itself
83
+ # ready within +ready_timeout:+ seconds.
92
84
  class MountTimeout < Error
93
- # @return [Array<String>]
94
- attr_reader :argv
95
- # @return [Numeric]
96
- attr_reader :timeout
85
+ attr_reader :argv, :timeout
97
86
 
98
- # @private
99
- def initialize(argv:, timeout:)
87
+ def initialize(argv:, timeout:) # :nodoc:
100
88
  @argv = argv
101
89
  @timeout = timeout
102
90
  super("Restic mount didn't report readiness within #{timeout}s (tried to run #{argv.inspect})")
@@ -1,20 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yobi
4
- # Base class for every wrapper around one raw Restic JSON object.
5
- class FancyHash < Hash
6
- # @private
4
+ class FancyHash < Hash # :nodoc:
7
5
  def initialize(raw)
8
6
  super()
9
7
  replace(raw)
10
8
  end
11
9
 
12
- # @return [String]
13
10
  def inspect
14
11
  "#<#{self.class} #{super}>"
15
12
  end
16
13
 
17
- # @return [void]
18
14
  def pretty_print(q)
19
15
  q.object_group(self) do
20
16
  each do |key, value|