yobi 1.0.0 → 1.1.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: 4daa53c0b99656d99f3b81b808ad6940ad0fa6b8d202f0915b769721972a40a2
4
- data.tar.gz: c1931310b0b9081099dedd3251cc304f01f8938ca0de13be82801289af1080db
3
+ metadata.gz: cea1c8c245d4ba7aec5a218441f19921606c7bdb5d2b1ddc9912c2d9519ba15e
4
+ data.tar.gz: 66bf1c1c5e4ce94706948db27352889b8af41e6967c0c00406c303515181f710
5
5
  SHA512:
6
- metadata.gz: dbb9ae518fd0181e5c7cff763f74f113936cefa996eb2ab5bd741e5c61550b5a254cb6b795aadc67363cfdc064351aa2b77f830cb8f6dadde4372974dc1dffc5
7
- data.tar.gz: 888067f9352c2a6bfe4493e7ea28770abc9df716c88ead2228b319ef7f07604884e977c1205e8726df61673ce0c79cf7f71d613296a6145d51af92ef72353ccf
6
+ metadata.gz: 89d14f78cd56cfcd7e48e3d56b94a2f5159ac5de5877872797b106307730d0d8c98702fd897ac5572f72d294fede4726e59cecfeab942e1fc29c1e15fe4184f3
7
+ data.tar.gz: d2c8ae8d702cebc1186c0f117abb542d1e80e84d4dc5e0ccac7ced97b278cd0762c0b70dfe2db84d968529aa235f12fbfd34b07252aa140d6bc2841cab372221
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [1.1.0] - 2026-09-02
2
+
3
+ ### Added
4
+
5
+ - `Repository#with_cancellation(token)` and `Yobi::Cancellation`: stop a long-running `#backup`/`#restore`/`#check`/`#prune`/`#forget`/`#copy` from another thread via `token.cancel!`, raising `Yobi::Cancelled` if Restic exited with code 130.
6
+
1
7
  ## [1.0.0] - 2026-08-29
2
8
 
3
9
  ### Changed
data/README.md CHANGED
@@ -220,6 +220,7 @@ The full hierarchy, all under `Yobi::Error < StandardError`:
220
220
  - `Yobi::RepositoryNotFound`, `Yobi::RepositoryLocked`, `Yobi::AuthenticationFailed`: Restic's own typed exit codes (10/11/12).
221
221
  - `Yobi::ResticCommandFailed`: any other non-zero exit, for a failure that doesn't fit one of the above.
222
222
  - `Yobi::MountTimeout`: `#mount` didn't report itself ready within its timeout.
223
+ - `Yobi::Cancelled`: the run was stopped through a `Yobi::Cancellation` token (see ["Cancelling a running command"](#cancelling-a-running-command) below).
223
224
 
224
225
  Every method below lives on `Yobi::Repository` unless noted otherwise.
225
226
 
@@ -234,7 +235,7 @@ repo.init
234
235
  # => #<Yobi::Initialized id="..." repository="...">
235
236
  ```
236
237
 
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`).
238
+ `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.1.0/Yobi/Repository#init-instance_method) for the full list of options. Returns an `Initialized` (`#id`/`#repository`).
238
239
 
239
240
  ### `#cat_config`
240
241
 
@@ -265,7 +266,7 @@ outcome.summary["data_added"]
265
266
 
266
267
  `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).
267
268
 
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).
269
+ `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.1.0/Yobi/Repository#backup-instance_method) for the full list of options (filtering, retention, timing, and more).
269
270
 
270
271
  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).
271
272
 
@@ -295,7 +296,7 @@ outcome.summary["snapshot_id"]
295
296
  repo.restore(snapshot_id: "latest", target: "/tmp/restore")
296
297
  ```
297
298
 
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).
299
+ `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.1.0/Yobi/Repository#restore-instance_method) for the full list of options (filtering, `overwrite:` behavior, and more).
299
300
 
300
301
  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.
301
302
 
@@ -319,7 +320,7 @@ repo.dump(snapshot_id: "latest", file: "/var/www", target: "/tmp/www.tar")
319
320
  repo.dump(snapshot_id: "latest", file: "/var/www", target: "/tmp/www.zip", archive: "zip")
320
321
  ```
321
322
 
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.
323
+ 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.1.0/Yobi/Repository#dump-instance_method) for the full list of options.
323
324
 
324
325
  ```ruby
325
326
  handle = repo.dump(snapshot_id: "latest", file: "/var/www")
@@ -339,7 +340,7 @@ outcome.statistics.changed_files
339
340
  outcome.changes.each { |change| puts "#{change.modifier} #{change.path}" }
340
341
  ```
341
342
 
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.
343
+ `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.1.0/Yobi/Repository#diff-instance_method) for the full list of options.
343
344
 
344
345
  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`).
345
346
 
@@ -353,7 +354,7 @@ repo.snapshots(tags: ["daily"], hosts: "web-1").each do |snapshot|
353
354
  end
354
355
  ```
355
356
 
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.
357
+ 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.1.0/Yobi/Repository#snapshots-instance_method) for the full list of options.
357
358
 
358
359
  ### `#tag`
359
360
 
@@ -361,7 +362,7 @@ Returns an Enumerable of `Snapshot` (`#id`, `#short_id`, `#time`, `#host`, `#tag
361
362
  repo.tag(snapshot_ids: snapshot.id, add: "verified")
362
363
  ```
363
364
 
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.
365
+ `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.1.0/Yobi/Repository#tag-instance_method) for the full list of options.
365
366
 
366
367
  Returns a `TagOutcome`: `#summary` (aliased `#report`; a `TagSummary` - just `#changed_snapshots`) and lazy `#changes` (Enumerable of `TagChange`, the old-ID/new-ID pairs).
367
368
 
@@ -371,7 +372,7 @@ Returns a `TagOutcome`: `#summary` (aliased `#report`; a `TagSummary` - just `#c
371
372
  repo.forget(keep_daily: 7, keep_weekly: 4, keep_monthly: 12, prune: true)
372
373
  ```
373
374
 
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.
375
+ 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.1.0/Yobi/Repository#forget-instance_method) for the full list of options.
375
376
 
376
377
  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"`).
377
378
 
@@ -384,7 +385,7 @@ repo.find(patterns: "*.pem").each do |matches|
384
385
  end
385
386
  ```
386
387
 
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.
388
+ 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.1.0/Yobi/Repository#find-instance_method) for the full list of options.
388
389
 
389
390
  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.).
390
391
 
@@ -396,7 +397,7 @@ outcome.snapshot.short_id
396
397
  outcome.nodes.each { |node| puts node.path }
397
398
  ```
398
399
 
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.
400
+ Lists a snapshot's files/directories. `recursive:` descends into subdirectories. See the [docs](https://rubydoc.info/gems/yobi/1.1.0/Yobi/Repository#ls-instance_method) for the full list of options.
400
401
 
401
402
  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).
402
403
 
@@ -410,7 +411,7 @@ outcome.summary.num_errors
410
411
  outcome.errors.each { |error| puts error.message }
411
412
  ```
412
413
 
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.
414
+ 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.1.0/Yobi/Repository#check-instance_method) for the full list of options.
414
415
 
415
416
  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.
416
417
 
@@ -420,7 +421,7 @@ Returns a `CheckOutcome`: `#summary` (aliased `#report`; a `CheckSummary` coveri
420
421
  repo.prune(max_unused: "5%")
421
422
  ```
422
423
 
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`.
424
+ 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.1.0/Yobi/Repository#prune-instance_method) for the full list of options. Returns `true`.
424
425
 
425
426
  ### `#repair_index`, `#repair_packs`, `#repair_snapshots`
426
427
 
@@ -430,7 +431,7 @@ repo.repair_packs(ids: damaged_pack_ids)
430
431
  repo.repair_snapshots(snapshot_ids: affected_ids, forget: true)
431
432
  ```
432
433
 
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.
434
+ `#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.1.0/Yobi/Repository#repair_snapshots-instance_method) for the full list of options.
434
435
 
435
436
  ### `#recover`
436
437
 
@@ -446,7 +447,7 @@ Builds a new snapshot from any data present in the repository but not referenced
446
447
  repo.rewrite(snapshot_ids: old.id, excludes: "*.log", new_time: Time.now.iso8601)
447
448
  ```
448
449
 
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.
450
+ 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.1.0/Yobi/Repository#rewrite-instance_method) for the full list of options.
450
451
 
451
452
  ### `#migrate`
452
453
 
@@ -471,7 +472,7 @@ repo.stats(mode: "raw-data")
471
472
  # => #<Yobi::RepositoryStats total_size=... total_file_count=... snapshots_count=...>
472
473
  ```
473
474
 
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.
475
+ 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.1.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.
475
476
 
476
477
  ## Key management
477
478
 
@@ -484,7 +485,7 @@ repo.key_remove(id: old_key.id)
484
485
 
485
486
  `#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`).
486
487
 
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.
488
+ `#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.1.0/Yobi/Repository#key_add-instance_method) for the full list of options.
488
489
 
489
490
  `#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:
490
491
 
@@ -525,7 +526,7 @@ A shortcut for setting up a `#copy` destination: constructs a new `Repository` a
525
526
  mirror_repo.copy(from_repo: primary_repo, tags: "nightly")
526
527
  ```
527
528
 
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`.
529
+ 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.1.0/Yobi/Repository#copy-instance_method) for the full list of options. Returns `true`.
529
530
 
530
531
  ## Mounting
531
532
 
@@ -548,7 +549,7 @@ ensure
548
549
  end
549
550
  ```
550
551
 
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.
552
+ `#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.1.0/Yobi/Repository#mount-instance_method) for the full list of options.
552
553
 
553
554
  ## Restic-level (not scoped to a repository)
554
555
 
@@ -558,7 +559,7 @@ restic.version.version # => "0.19.1"
558
559
  restic.cache(cleanup: true)
559
560
  ```
560
561
 
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.
562
+ `#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.1.0/Yobi/Restic#cache-instance_method) for its remaining options.
562
563
 
563
564
  ## Global `Restic` settings
564
565
 
@@ -574,7 +575,7 @@ restic = Yobi::Restic.new(
574
575
 
575
576
  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`.
576
577
 
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).
578
+ 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.1.0/Yobi/Restic).
578
579
 
579
580
  ### Sharing a `Restic` across repositories
580
581
 
@@ -589,6 +590,47 @@ Both use the same settings. Changing `restic.limit_upload` later affects every r
589
590
 
590
591
  `--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.
591
592
 
593
+ ## Cancelling a running command
594
+
595
+ Call `Repository#with_cancellation(token)` before `#backup`, `#restore`, `#check`, `#prune`, `#forget`, or `#copy`, then `token.cancel!` from another thread to stop it:
596
+
597
+ ```ruby
598
+ token = Yobi::Cancellation.new
599
+
600
+ worker = Thread.new do
601
+ repo.with_cancellation(token).backup(source: "/Users/zia/Documents")
602
+ rescue Yobi::Cancelled
603
+ # the user stopped it; not a backup failure
604
+ end
605
+
606
+ token.cancel! # => true
607
+ worker.join
608
+ ```
609
+
610
+ `#cancel!` sends **SIGINT**, not SIGKILL - Restic removes its own repository lock on the way out, so no `#unlock` is needed. Override with `cancel!(signal: "TERM")` only if you have a reason to.
611
+
612
+ Restic exits with its own documented code 130 when killed by a signal; Yobi raises `Yobi::Cancelled` for that instead of `Yobi::ResticCommandFailed`.
613
+
614
+ Safe at any point, from any thread; `#cancel!` is idempotent, returning `true` the first time and `false` after. Cancelling before the command starts means Restic never spawns; `Yobi::Cancelled#before_start?` tells the two cases apart:
615
+
616
+ ```ruby
617
+ begin
618
+ repo.with_cancellation(token).backup(source: "/data")
619
+ rescue Yobi::Cancelled => error
620
+ error.before_start? # => true if Restic never ran
621
+ error.execution[:exit_code] # => nil in that case, otherwise the real exit code
622
+ end
623
+ ```
624
+
625
+ What an interrupted run leaves behind:
626
+
627
+ - `#backup`: no snapshot is written; data already uploaded is generally left *unreferenced* rather than reused by the next run, and orphaned packs are only reclaimed by a later `#prune`.
628
+ - `#restore`: the target directory may be left partially written (some files complete, others missing).
629
+ - `#check`: read-only, nothing is written; safe to cancel at any point.
630
+ - `#prune`: Restic's repacking is designed to be crash-safe, but repacking done so far may not be reused (the next `#prune` can end up starting over).
631
+ - `#forget`: some snapshots may already be removed while the rest are intact, re-run to finish.
632
+ - `#copy`: snapshots already copied should stay in the destination and be skipped on a re-run.
633
+
592
634
  ## Redaction
593
635
 
594
636
  Both `Repository#inspect` and `Restic#inspect` redact sensitive values, so a stray `pp`/`puts`/log call never prints a credential in plaintext:
@@ -0,0 +1,48 @@
1
+ module Yobi
2
+ # Returned by Repository#with_cancellation. Exposes only the six
3
+ # long-running methods a Yobi::Cancellation token can stop; every other
4
+ # Repository method (#snapshots, #mount, #init, ...) is unreachable here.
5
+ #
6
+ # +token+ reaches the repository via a thread-local, set only for the
7
+ # duration of each call and only on the calling thread.
8
+ class CancellableProxy
9
+ def initialize(repository, token)
10
+ @repository = repository
11
+ @token = token
12
+ end
13
+
14
+ def backup(...)
15
+ with_token { @repository.backup(...) }
16
+ end
17
+
18
+ def restore(...)
19
+ with_token { @repository.restore(...) }
20
+ end
21
+
22
+ def check(...)
23
+ with_token { @repository.check(...) }
24
+ end
25
+
26
+ def prune(...)
27
+ with_token { @repository.prune(...) }
28
+ end
29
+
30
+ def forget(...)
31
+ with_token { @repository.forget(...) }
32
+ end
33
+
34
+ def copy(...)
35
+ with_token { @repository.copy(...) }
36
+ end
37
+
38
+ private
39
+
40
+ def with_token
41
+ previous = Thread.current[:yobi_cancellation]
42
+ Thread.current[:yobi_cancellation] = @token
43
+ yield
44
+ ensure
45
+ Thread.current[:yobi_cancellation] = previous
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yobi
4
+ # A cancellation token for a long-running Restic run.
5
+ #
6
+ # Pass one to Yobi::Repository#with_cancellation, then call #cancel! from
7
+ # another thread to stop whichever long-running method (#backup,
8
+ # #restore, #check, #prune, #forget, #copy) is running on the repository
9
+ # it returns:
10
+ #
11
+ # token = Yobi::Cancellation.new
12
+ # Thread.new { token.cancel! if user_clicked_cancel }
13
+ # repo.with_cancellation(token).backup(source: "/data")
14
+ # # => raises Yobi::Cancelled
15
+ #
16
+ # #cancel! sends the Restic process SIGINT rather than SIGKILL, so Restic
17
+ # removes its own repository lock on the way out and no +restic unlock+ is
18
+ # needed afterwards. An interrupted +backup+ writes no snapshot; data it had
19
+ # already uploaded is generally left unreferenced until the next +prune+
20
+ # rather than being reused, so cancelling does discard in-progress work.
21
+ #
22
+ # Safe to call #cancel! before the run starts (the run then raises without
23
+ # spawning Restic at all), after it has finished (a no-op), and from any
24
+ # thread. A token tracks one run at a time and is not reusable once
25
+ # cancelled.
26
+ class Cancellation
27
+ # The signal sent to Restic by #cancel! unless overridden.
28
+ DEFAULT_SIGNAL = "INT"
29
+
30
+ def initialize
31
+ @mutex = Mutex.new
32
+ @cancelled = false
33
+ @signal = DEFAULT_SIGNAL
34
+ @pid = nil
35
+ end
36
+
37
+ # True once #cancel! has been called.
38
+ def cancelled?
39
+ @mutex.synchronize { @cancelled }
40
+ end
41
+
42
+ # Requests cancellation, signalling the attached Restic process if one is
43
+ # currently running. Returns true the first time, false if this token was
44
+ # already cancelled. Idempotent and thread-safe.
45
+ #
46
+ # +signal:+ overrides the signal sent; the SIGINT default is what lets
47
+ # Restic clean up its lock, so override it only when you have a reason to.
48
+ def cancel!(signal: DEFAULT_SIGNAL)
49
+ pid = nil
50
+ @mutex.synchronize do
51
+ return false if @cancelled
52
+
53
+ @cancelled = true
54
+ @signal = signal
55
+ pid = @pid
56
+ end
57
+ signal_pid(pid, signal)
58
+ true
59
+ end
60
+
61
+ # Attaches a freshly spawned Restic process. If #cancel! already landed
62
+ # before the spawn, the process is signalled immediately. Called by
63
+ # Yobi::Restic; not part of the public API.
64
+ def attach(pid) # :nodoc:
65
+ cancelled, signal = @mutex.synchronize do
66
+ @pid = pid
67
+ [@cancelled, @signal]
68
+ end
69
+ signal_pid(pid, signal) if cancelled
70
+ end
71
+
72
+ # Detaches the current process once it has been reaped, so a later
73
+ # #cancel! can't signal a recycled PID. Called by Yobi::Restic; not part
74
+ # of the public API.
75
+ def detach # :nodoc:
76
+ @mutex.synchronize { @pid = nil }
77
+ end
78
+
79
+ def inspect
80
+ "#<#{self.class} cancelled=#{cancelled?}>"
81
+ end
82
+
83
+ private
84
+
85
+ # Errno::ESRCH means Restic already exited between the cancel and the
86
+ # signal, which is exactly the outcome cancelling wanted.
87
+ def signal_pid(pid, signal)
88
+ return if pid.nil?
89
+
90
+ begin
91
+ Process.kill(signal, pid)
92
+ rescue Errno::ESRCH
93
+ nil
94
+ end
95
+ end
96
+ end
97
+ end
data/lib/yobi/errors.rb CHANGED
@@ -41,6 +41,33 @@ module Yobi
41
41
  end
42
42
  end
43
43
 
44
+ # Raised when a run was stopped through a Yobi::Cancellation token.
45
+ #
46
+ # Distinct from Yobi::ResticExecutionError and its subclasses: the run did
47
+ # not fail, the caller stopped it on purpose. Rescue this to record a
48
+ # cancellation without treating it as a backup failure.
49
+ class Cancelled < Error
50
+ # The raw +{exit_code:, output:, argv:}+ result. +exit_code:+ and
51
+ # +output:+ are +nil+ when the token was already cancelled before
52
+ # Restic was spawned at all.
53
+ attr_reader :execution
54
+
55
+ def initialize(execution) # :nodoc:
56
+ @execution = execution
57
+ super("Restic run was cancelled")
58
+ end
59
+
60
+ # The argv of the run that was cancelled.
61
+ def argv
62
+ execution[:argv]
63
+ end
64
+
65
+ # True when the token was cancelled before Restic even started.
66
+ def before_start?
67
+ execution[:exit_code].nil?
68
+ end
69
+ end
70
+
44
71
  # Raised when the Restic binary itself can't be found or executed.
45
72
  class ResticNotFound < Error
46
73
  attr_reader :restic_path, :argv
@@ -73,10 +73,28 @@ module Yobi
73
73
  "#<#{self.class} url=#{url.inspect} password=#{redacted_password.inspect} backend_credentials=#{redacted_backend_credentials.inspect}>"
74
74
  end
75
75
 
76
+ # Returns a Yobi::CancellableProxy wrapping this repository, exposing
77
+ # only its long-running methods (#backup, #restore, #check, #prune,
78
+ # #forget, #copy) with +token+ threaded through them, so #cancel! can
79
+ # stop whichever one is called:
80
+ #
81
+ # token = Yobi::Cancellation.new
82
+ # Thread.new { token.cancel! if user_clicked_cancel }
83
+ # repo.with_cancellation(token).backup(source: "/data")
84
+ def with_cancellation(token)
85
+ CancellableProxy.new(self, token)
86
+ end
87
+
76
88
  private
77
89
 
90
+ # Set for the duration of one call by a Yobi::CancellableProxy, on the
91
+ # calling thread only.
92
+ def cancellation
93
+ Thread.current[:yobi_cancellation]
94
+ end
95
+
78
96
  def run_restic(argv, extra_env: {}, output: nil, &block)
79
- @restic.run(argv, extra_env: env.merge(extra_env), output: output, &block)
97
+ @restic.run(argv, extra_env: env.merge(extra_env), output: output, cancellation: cancellation, &block)
80
98
  end
81
99
 
82
100
  def build_argv(*base)
data/lib/yobi/restic.rb CHANGED
@@ -169,14 +169,22 @@ module Yobi
169
169
  #
170
170
  # Returns +{exit_code:, output:, argv:}+ on exit code 0 or 3.
171
171
  # Raises Yobi::RepositoryNotFound, Yobi::RepositoryLocked,
172
- # Yobi::AuthenticationFailed, or Yobi::ResticCommandFailed otherwise.
173
- def run(argv, extra_env: {}, skip_version_check: false, output: nil, &block) # :nodoc:
172
+ # Yobi::AuthenticationFailed, Yobi::Cancelled (exit code 130, e.g. from
173
+ # a Yobi::Cancellation token's #cancel!), or Yobi::ResticCommandFailed
174
+ # otherwise.
175
+ def run(argv, extra_env: {}, skip_version_check: false, output: nil, cancellation: nil, &block) # :nodoc:
174
176
  ensure_minimum_version! unless skip_version_check
177
+ raise Yobi::Cancelled.new({exit_code: nil, output: nil, argv: argv}) if cancellation&.cancelled?
178
+
179
+ # Only forwarded when a token was actually supplied, so the common
180
+ # short metadata commands keep their original call shape.
181
+ cancel_kwarg = cancellation ? {cancellation: cancellation} : {}
175
182
  execution = if output || block
176
- execute_with_streaming(argv, extra_env, output: output, &block)
183
+ execute_with_streaming(argv, extra_env, output: output, **cancel_kwarg, &block)
177
184
  else
178
- execute(argv, extra_env)
185
+ execute(argv, extra_env, **cancel_kwarg)
179
186
  end
187
+
180
188
  self.class.dispatch(execution)
181
189
  end
182
190
 
@@ -192,6 +200,8 @@ module Yobi
192
200
  raise Yobi::RepositoryLocked, execution
193
201
  when 12
194
202
  raise Yobi::AuthenticationFailed, execution
203
+ when 130
204
+ raise Yobi::Cancelled, execution
195
205
  else
196
206
  raise Yobi::ResticCommandFailed, execution
197
207
  end
@@ -281,21 +291,25 @@ module Yobi
281
291
  end
282
292
  end
283
293
 
284
- def execute(argv, extra_env)
294
+ def execute(argv, extra_env, cancellation: nil)
285
295
  output = Yobi::ResticOutput.new
286
296
  pid = Process.spawn(env.merge(extra_env), restic_path, *argv, out: output.file, err: output.file)
297
+ cancellation&.attach(pid)
287
298
  _, status = Process.wait2(pid)
288
299
  {exit_code: status.exitstatus, output: output, argv: argv}
289
300
  rescue Errno::ENOENT
290
301
  output.file.close
291
302
  raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
303
+ ensure
304
+ cancellation&.detach
292
305
  end
293
306
 
294
- def execute_with_streaming(argv, extra_env, output: nil)
307
+ def execute_with_streaming(argv, extra_env, output: nil, cancellation: nil)
295
308
  output ||= Yobi::ResticOutput.new
296
309
 
297
310
  Open3.popen3(env.merge(extra_env), restic_path, *argv) do |stdin, stdout, stderr, wait_thr|
298
311
  stdin.close
312
+ cancellation&.attach(wait_thr.pid)
299
313
  readers = {stdout => :stdout, stderr => :stderr}
300
314
 
301
315
  until readers.empty?
@@ -317,6 +331,8 @@ module Yobi
317
331
  rescue Errno::ENOENT
318
332
  output.file.close
319
333
  raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
334
+ ensure
335
+ cancellation&.detach
320
336
  end
321
337
  end
322
338
 
data/lib/yobi/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  # A Ruby library for the Restic backup program, wrapping the `restic` CLI in
4
4
  # plain Ruby objects instead of shelling out to flags and raw JSON by hand.
5
5
  module Yobi
6
- VERSION = "1.0.0"
6
+ VERSION = "1.1.0"
7
7
  end
data/lib/yobi.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "yobi/version"
4
4
  require_relative "yobi/fancy_hash"
5
+ require_relative "yobi/cancellation"
5
6
  require_relative "yobi/errors"
6
7
  require_relative "yobi/argv_builder"
7
8
  require_relative "yobi/restic_output"
@@ -10,5 +11,6 @@ require_relative "yobi/mount_handle"
10
11
  require_relative "yobi/restic"
11
12
  require_relative "yobi/snapshot"
12
13
  require_relative "yobi/repository"
14
+ require_relative "yobi/cancellable_proxy"
13
15
 
14
16
  Dir[File.join(__dir__, "yobi", "repository", "*.rb")].sort.each { |file| require_relative file }
data/sig/yobi.rbs CHANGED
@@ -31,6 +31,32 @@ module Yobi
31
31
  def initialize: (installed_version: String, minimum_version: String) -> void
32
32
  end
33
33
 
34
+ class Cancellation
35
+ DEFAULT_SIGNAL: String
36
+
37
+ def initialize: () -> void
38
+
39
+ def cancelled?: () -> bool
40
+
41
+ def cancel!: (?signal: String) -> bool
42
+
43
+ def attach: (Integer pid) -> void
44
+
45
+ def detach: () -> void
46
+
47
+ def inspect: () -> String
48
+ end
49
+
50
+ class Cancelled < Error
51
+ attr_reader execution: cancelled_execution
52
+
53
+ def initialize: (cancelled_execution execution) -> void
54
+
55
+ def argv: () -> Array[String]
56
+
57
+ def before_start?: () -> bool
58
+ end
59
+
34
60
  class ResticNotFound < Error
35
61
  attr_reader restic_path: String
36
62
  attr_reader argv: Array[String]
@@ -39,6 +65,7 @@ module Yobi
39
65
  end
40
66
 
41
67
  type execution = { exit_code: Integer, output: ResticOutput, argv: Array[String] }
68
+ type cancelled_execution = { exit_code: Integer?, output: ResticOutput?, argv: Array[String] }
42
69
 
43
70
  class ResticExecutionError < Error
44
71
  attr_reader execution: execution
@@ -264,7 +291,7 @@ module Yobi
264
291
 
265
292
  def cache: (?cleanup: bool, ?max_age: String?, ?no_size: bool) -> true
266
293
 
267
- def run: (Array[String] argv, ?extra_env: Hash[String, String], ?skip_version_check: bool, ?output: ResticOutput?) ?{ (untyped message) -> void } -> execution
294
+ def run: (Array[String] argv, ?extra_env: Hash[String, String], ?skip_version_check: bool, ?output: ResticOutput?, ?cancellation: Cancellation?) ?{ (untyped message) -> void } -> execution
268
295
 
269
296
  def self.dispatch: (execution execution) -> execution
270
297
 
@@ -305,6 +332,8 @@ module Yobi
305
332
 
306
333
  alias config cat_config
307
334
 
335
+ def with_cancellation: (Cancellation token) -> CancellableProxy
336
+
308
337
  def backup: (source: String | [:stdin_from_command, String | Array[String]] | [:stdin_from_command, String | Array[String], String],
309
338
  ?excludes: Array[String] | String, ?exclude_files: Array[String] | String,
310
339
  ?exclude_if_present: Array[String] | String, ?exclude_larger_than: String?,
@@ -418,7 +447,8 @@ module Yobi
418
447
  ?include_files: Array[String] | String, ?include_xattrs: Array[String] | String,
419
448
  ?paths: Array[String] | String, ?tags: Array[String] | String, ?delete: bool, ?dry_run: bool,
420
449
  ?overwrite: String | Symbol | nil, ?ownership_by_name: bool, ?sparse: bool, ?verbose: bool,
421
- ?verify: bool) ?{ (RestoreStatus | RestoreVerboseStatus message) -> void } -> RestoreOutcome
450
+ ?verify: bool)
451
+ ?{ (RestoreStatus | RestoreVerboseStatus message) -> void } -> RestoreOutcome
422
452
 
423
453
  def rewrite: (?snapshot_ids: Array[String] | String, ?hosts: Array[String] | String,
424
454
  ?tags: Array[String] | String, ?paths: Array[String] | String, ?excludes: Array[String] | String,
@@ -441,6 +471,53 @@ module Yobi
441
471
  def unlock: (?remove_all: bool) -> true
442
472
  end
443
473
 
474
+ class CancellableProxy
475
+ def initialize: (Repository repository, Cancellation token) -> void
476
+
477
+ def backup: (source: String | [:stdin_from_command, String | Array[String]] | [:stdin_from_command, String | Array[String], String],
478
+ ?excludes: Array[String] | String, ?exclude_files: Array[String] | String,
479
+ ?exclude_if_present: Array[String] | String, ?exclude_larger_than: String?,
480
+ ?files_from: Array[String] | String, ?files_from_raw: Array[String] | String,
481
+ ?files_from_verbatim: Array[String] | String, ?iexcludes: Array[String] | String,
482
+ ?iexclude_files: Array[String] | String, ?tags: Array[String] | String, ?dry_run: bool,
483
+ ?exclude_caches: bool, ?exclude_cloud_files: bool, ?force: bool, ?group_by: String?,
484
+ ?host: String?, ?ignore_ctime: bool, ?ignore_inode: bool, ?no_scan: bool,
485
+ ?one_file_system: bool, ?parent: String?, ?read_concurrency: Integer?,
486
+ ?skip_if_unchanged: bool, ?time: String?, ?verbose: bool, ?with_atime: bool)
487
+ ?{ (BackupStatus | BackupError | BackupVerboseStatus | BackupSummary message) -> void } -> BackupOutcome
488
+
489
+ def restore: (snapshot_id: String, target: String, ?excludes: Array[String] | String,
490
+ ?exclude_files: Array[String] | String, ?exclude_xattrs: Array[String] | String,
491
+ ?hosts: Array[String] | String, ?iexcludes: Array[String] | String,
492
+ ?iexclude_files: Array[String] | String, ?iincludes: Array[String] | String,
493
+ ?iinclude_files: Array[String] | String, ?includes: Array[String] | String,
494
+ ?include_files: Array[String] | String, ?include_xattrs: Array[String] | String,
495
+ ?paths: Array[String] | String, ?tags: Array[String] | String, ?delete: bool, ?dry_run: bool,
496
+ ?overwrite: String | Symbol | nil, ?ownership_by_name: bool, ?sparse: bool, ?verbose: bool,
497
+ ?verify: bool)
498
+ ?{ (RestoreStatus | RestoreVerboseStatus message) -> void } -> RestoreOutcome
499
+
500
+ def check: (?hosts: Array[String] | String, ?paths: Array[String] | String, ?read_data: bool,
501
+ ?read_data_subset: String?, ?tags: Array[String] | String, ?with_cache: bool) -> CheckOutcome
502
+
503
+ def prune: (?dry_run: bool, ?max_repack_size: String?, ?max_unused: String?,
504
+ ?repack_cacheable_only: bool, ?repack_smaller_than: String?, ?repack_uncompressed: bool,
505
+ ?unsafe_recover_no_free_space: String?) -> true
506
+
507
+ def forget: (?keep_last: Integer?, ?keep_hourly: Integer?, ?keep_daily: Integer?, ?keep_weekly: Integer?,
508
+ ?keep_monthly: Integer?, ?keep_yearly: Integer?, ?keep_within: String?, ?keep_within_hourly: String?,
509
+ ?keep_within_daily: String?, ?keep_within_weekly: String?, ?keep_within_monthly: String?,
510
+ ?keep_within_yearly: String?, ?keep_tags: Array[String] | String, ?hosts: Array[String] | String,
511
+ ?tags: Array[String] | String, ?paths: Array[String] | String, ?compact: bool, ?group_by: String?,
512
+ ?dry_run: bool, ?prune: bool, ?unsafe_allow_remove_all: bool, ?max_unused: String?,
513
+ ?max_repack_size: String?, ?repack_cacheable_only: bool, ?repack_uncompressed: bool,
514
+ ?repack_smaller_than: String?) -> Array[ForgetGroup]
515
+
516
+ def copy: (from_repo: from_repo, ?from_password: from_password, ?snapshot_ids: Array[String] | String,
517
+ ?from_key_hint: String?, ?hosts: Array[String] | String,
518
+ ?paths: Array[String] | String, ?tags: Array[String] | String) -> true
519
+ end
520
+
444
521
  module BackupMessageWrapper
445
522
  def self.call: (Hash[String, untyped] raw) -> (BackupStatus | BackupError | BackupVerboseStatus | BackupSummary | Hash[String, untyped])
446
523
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yobi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zia Perdana
@@ -22,6 +22,8 @@ files:
22
22
  - README.md
23
23
  - lib/yobi.rb
24
24
  - lib/yobi/argv_builder.rb
25
+ - lib/yobi/cancellable_proxy.rb
26
+ - lib/yobi/cancellation.rb
25
27
  - lib/yobi/errors.rb
26
28
  - lib/yobi/fancy_hash.rb
27
29
  - lib/yobi/io_handle.rb
@@ -62,7 +64,7 @@ metadata:
62
64
  homepage_uri: https://codeberg.org/ukazap/yobi
63
65
  source_code_uri: https://codeberg.org/ukazap/yobi
64
66
  changelog_uri: https://codeberg.org/ukazap/yobi/src/branch/main/CHANGELOG.md
65
- documentation_uri: https://rubydoc.info/gems/yobi/1.0.0
67
+ documentation_uri: https://rubydoc.info/gems/yobi/1.1.0
66
68
  rdoc_options: []
67
69
  require_paths:
68
70
  - lib