yobi 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +93 -124
- data/lib/yobi/argv_builder.rb +5 -0
- data/lib/yobi/errors.rb +6 -10
- data/lib/yobi/fancy_hash.rb +31 -0
- data/lib/yobi/io_handle.rb +9 -7
- data/lib/yobi/{mount.rb → mount_handle.rb} +11 -10
- data/lib/yobi/repository/backup.rb +75 -85
- data/lib/yobi/repository/cat.rb +10 -0
- data/lib/yobi/repository/check.rb +61 -45
- data/lib/yobi/repository/diff.rb +130 -47
- data/lib/yobi/repository/dump.rb +1 -1
- data/lib/yobi/repository/find.rb +11 -33
- data/lib/yobi/repository/forget.rb +6 -48
- data/lib/yobi/repository/init.rb +70 -0
- data/lib/yobi/repository/key.rb +59 -52
- data/lib/yobi/repository/ls.rb +48 -37
- data/lib/yobi/repository/mount.rb +16 -18
- data/lib/yobi/repository/restore.rb +42 -40
- data/lib/yobi/repository/snapshots.rb +2 -29
- data/lib/yobi/repository/stats.rb +47 -4
- data/lib/yobi/repository/tag.rb +50 -35
- data/lib/yobi/repository.rb +8 -43
- data/lib/yobi/restic.rb +73 -32
- data/lib/yobi/restic_output.rb +169 -12
- data/lib/yobi/snapshot.rb +81 -2
- data/lib/yobi/version.rb +2 -2
- data/lib/yobi.rb +2 -1
- data/sig/yobi.rbs +252 -139
- metadata +5 -7
- data/Rakefile +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edd02fb1bbb985e22d5e32e8558b37f469809e9b5cdc079c6b4885e5ada5a775
|
|
4
|
+
data.tar.gz: 4d6a90b324596e6fc375a1338fdba1c70d84a6bf9504b15513614c87458bd7c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c11e446c74d42a68b7a34c61b4952451f0929d56114a6439ec6462f854019ea40d7844dd730371026a679f744a464600c997ce76b884de538a38ee879348efc
|
|
7
|
+
data.tar.gz: e3245e59a13c6b9f8712824406f0844106e1495a543ff68cf2ac11210835ef480109755acf2019ca5b10708d46436bc1e58f7e9e122f450ce3dd4f40395695fb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [0.2.0] - 2026-08-03
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- `Repository#init_mirror` sets up a new destination repository, pre-initialized with chunker parameters copied from the source, so a later `#copy` between the two can deduplicate.
|
|
6
|
+
- `Repository#cat_config` aliased as `#config`.
|
|
7
|
+
- `CheckSummary` gained `#suggest_repair_index?` and `#suggest_prune?`.
|
|
8
|
+
- `Restic#version` now tolerates a too-old restic binary that ignores `--json` for the `version` subcommand, falling back to parsing its plain-text output instead of raising a JSON parse error.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Breaking:** `Repository#initialize`'s `password:` is now required - passing `nil` raises `ArgumentError`. Yobi no longer falls back to an ambient `RESTIC_PASSWORD`/`RESTIC_PASSWORD_COMMAND` env var on its own; pass `:insecure_no_password` explicitly if the repository truly has none. `Repository#init_mirror`'s `password:` is likewise now required.
|
|
13
|
+
- **Breaking:** `#key_add`/`#key_passwd`'s new-password kwargs and `#init`'s `from_password:` collapsed to one polymorphic kwarg each, accepting the same literal/`[:command, ...]`/`[:file, ...]`/callable shapes as `password:`, instead of separate shape-specific kwargs.
|
|
14
|
+
- **Breaking:** reworked how `#backup`/`#restore`/`#check`/`#diff`/`#tag`/`#forget`/`#ls` report results. Outcome objects (`BackupOutcome`, `RestoreOutcome`, `CheckOutcome`, `DiffOutcome`, `TagOutcome`, `LsOutcome`) are now plain classes wrapping a `ResticOutput` rather than `Struct.new(:exit_code, :output)`; per-message classes (`BackupError`, `BackupStatus`, `CheckError`, `DiffChange`, `TagChange`, and friends) are built on the new `Yobi::FancyHash` rather than `SimpleDelegator`; `#summary` (aliased `#report`) now returns a typed summary object (`BackupSummary`, `CheckSummary`, `TagSummary`) instead of a raw Hash; per-item accessors (`#errors`, `#changes`, `#entries`) are now lazy Enumerables instead of eagerly-built Arrays.
|
|
15
|
+
- **Breaking:** renamed `Yobi::LsEntry` to `Yobi::Node`, `Yobi::ForgetReason` to `Yobi::KeepReason`, and `Yobi::Mount` to `Yobi::MountHandle`.
|
|
16
|
+
|
|
1
17
|
## [0.1.0] - 2026-07-29
|
|
2
18
|
|
|
3
19
|
Initial release.
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Yobi is a Ruby interface for the [Restic](https://restic.net/) backup program.
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
Install the gem
|
|
7
|
+
### Install the gem
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
bundle add yobi
|
|
@@ -16,25 +16,11 @@ or, without Bundler:
|
|
|
16
16
|
gem install yobi
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
###
|
|
19
|
+
### Install Restic
|
|
20
20
|
|
|
21
21
|
Yobi shells out to a real `restic` binary; it doesn't install or bundle one. You need `restic` on `PATH` (or point Yobi at it explicitly, see below) before any of this works.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
# macOS
|
|
27
|
-
brew install restic
|
|
28
|
-
|
|
29
|
-
# Debian/Ubuntu
|
|
30
|
-
apt install restic
|
|
31
|
-
|
|
32
|
-
# Arch
|
|
33
|
-
pacman -S restic
|
|
34
|
-
|
|
35
|
-
# or download a binary directly
|
|
36
|
-
# https://github.com/restic/restic/releases
|
|
37
|
-
```
|
|
23
|
+
See [Restic's installation guide](https://restic.readthedocs.io/en/stable/020_installation.html) for how to install it on your platform.
|
|
38
24
|
|
|
39
25
|
Confirm it's reachable:
|
|
40
26
|
|
|
@@ -103,9 +89,9 @@ outcome = repo.backup(
|
|
|
103
89
|
excludes: ["*.tmp", "node_modules"],
|
|
104
90
|
tags: ["documents", "daily"]
|
|
105
91
|
)
|
|
106
|
-
outcome.
|
|
107
|
-
outcome.
|
|
108
|
-
outcome.
|
|
92
|
+
outcome.errors.none? # => true
|
|
93
|
+
outcome.summary["snapshot_id"]
|
|
94
|
+
outcome.summary["data_added"]
|
|
109
95
|
|
|
110
96
|
repo.snapshots.each do |snapshot|
|
|
111
97
|
puts "#{snapshot.short_id} #{snapshot.time} #{snapshot.paths.join(", ")}"
|
|
@@ -114,7 +100,7 @@ end
|
|
|
114
100
|
repo.restore(snapshot_id: "latest", target: "/tmp/restore")
|
|
115
101
|
```
|
|
116
102
|
|
|
117
|
-
|
|
103
|
+
Most verb methods return something typed rather than a raw Hash. `#backup` returns a `BackupOutcome` (`#summary`, `#errors`), `#snapshots` returns an Enumerable of `Snapshot` objects, and so on throughout the API - the low-level `cat_*` introspection commands are the exception, returning Restic's own raw JSON directly (see ["Low-level (`cat`) and object listing"](#low-level-cat-and-object-listing) below).
|
|
118
104
|
|
|
119
105
|
## Constructing a `Repository`
|
|
120
106
|
|
|
@@ -136,7 +122,7 @@ See [Restic's documentation](https://restic.readthedocs.io/en/stable/030_prepari
|
|
|
136
122
|
|
|
137
123
|
### `password:`
|
|
138
124
|
|
|
139
|
-
Identifies the repository's encryption password.
|
|
125
|
+
Identifies the repository's encryption password. Required - pass one of five shapes:
|
|
140
126
|
|
|
141
127
|
```ruby
|
|
142
128
|
# A literal password
|
|
@@ -159,8 +145,6 @@ password = :insecure_no_password
|
|
|
159
145
|
Yobi::Repository.new(url: "...", password: password)
|
|
160
146
|
```
|
|
161
147
|
|
|
162
|
-
If `password:` is left unspecified, Yobi adds nothing extra to the environment: Restic falls back to `RESTIC_PASSWORD`/`RESTIC_PASSWORD_COMMAND` if either happens to already be set in the calling process's own environment (a container, systemd unit, or parent shell outside Yobi's control). If nothing is set anywhere, Restic refuses a truly empty password (unless `--insecure-no-password` is given) and fails loudly and safely on its own.
|
|
163
|
-
|
|
164
148
|
### `backend_credentials:`
|
|
165
149
|
|
|
166
150
|
Separate from `password:`: this is whatever the storage *backend* needs to authenticate, unrelated to whether the repository's contents can be decrypted. A Hash of env vars, or a callable returning one, resolved fresh on every call the same way a callable `password:` is:
|
|
@@ -243,17 +227,17 @@ Creates the repository at `url`.
|
|
|
243
227
|
|
|
244
228
|
```ruby
|
|
245
229
|
repo.init
|
|
246
|
-
# =>
|
|
230
|
+
# => #<Yobi::Initialized id="..." repository="...">
|
|
247
231
|
```
|
|
248
232
|
|
|
249
|
-
`copy_chunker_params
|
|
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`).
|
|
250
234
|
|
|
251
|
-
### `#
|
|
235
|
+
### `#cat_config`
|
|
252
236
|
|
|
253
|
-
The repository's own config document (`cat config`), as a Hash. A cheap way to confirm a repository exists and the password is correct without doing anything else.
|
|
237
|
+
The repository's own config document (`cat config`), as a Hash. A cheap way to confirm a repository exists and the password is correct without doing anything else. Aliased `#config`.
|
|
254
238
|
|
|
255
239
|
```ruby
|
|
256
|
-
repo.
|
|
240
|
+
repo.cat_config
|
|
257
241
|
# => {"version"=>2, "id"=>"...", "chunker_polynomial"=>"..."}
|
|
258
242
|
```
|
|
259
243
|
|
|
@@ -269,43 +253,22 @@ outcome = repo.backup(
|
|
|
269
253
|
excludes: ["*.tmp", "node_modules"],
|
|
270
254
|
tags: ["documents", "daily"]
|
|
271
255
|
)
|
|
272
|
-
outcome.
|
|
273
|
-
outcome.
|
|
274
|
-
outcome.
|
|
275
|
-
outcome.
|
|
256
|
+
outcome.errors.none? # => true
|
|
257
|
+
outcome.summary["snapshot_id"]
|
|
258
|
+
outcome.summary["files_new"]
|
|
259
|
+
outcome.summary["data_added"]
|
|
276
260
|
```
|
|
277
261
|
|
|
278
262
|
`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).
|
|
279
263
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
- `excludes:`/`exclude_files:`/`iexcludes:`/`iexclude_files:` (case-insensitive): exclude by glob pattern or pattern file
|
|
283
|
-
- `exclude_if_present:`: skip a directory containing a named marker file
|
|
284
|
-
- `exclude_larger_than:`: skip by size
|
|
285
|
-
- `exclude_caches:`/`exclude_cloud_files:`: skip CACHEDIR.TAG-marked dirs and cloud-placeholder files respectively
|
|
286
|
-
- `files_from:`/`files_from_raw:`/`files_from_verbatim:`: read the backup list from a file instead of (or alongside) `source:`
|
|
287
|
-
|
|
288
|
-
**Behavior:**
|
|
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).
|
|
289
265
|
|
|
290
|
-
|
|
291
|
-
- `force:`: back up unchanged files anyway
|
|
292
|
-
- `one_file_system:`: don't cross filesystem boundaries
|
|
293
|
-
- `ignore_ctime:`/`ignore_inode:`: relax change detection
|
|
294
|
-
- `no_scan:`: skip the pre-backup scan, disabling percentage progress
|
|
295
|
-
- `skip_if_unchanged:`: don't create a snapshot if nothing changed
|
|
296
|
-
- `parent:`: pick a specific parent snapshot instead of the latest one
|
|
297
|
-
- `group_by:`: grouping used to find the parent snapshot instead, e.g. `"host,paths"`, when `parent:` isn't given
|
|
298
|
-
- `read_concurrency:`: number of concurrent file reads
|
|
299
|
-
- `time:`: record a specific timestamp instead of now
|
|
300
|
-
- `with_atime:`: also store files' access times
|
|
301
|
-
- `host:`: hostname recorded on the *new* snapshot (a single value: this is metadata being written, not a filter, unlike every `hosts:` elsewhere in this API)
|
|
266
|
+
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).
|
|
302
267
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
Also accepts a block, to receive typed progress objects as the backup runs instead of only getting the final `BackupOutcome`:
|
|
268
|
+
Also accepts a block, to receive typed progress objects as the backup runs, on top of (not instead of) the final `BackupOutcome` once it returns. The block also receives a `Yobi::BackupSummary`, the same object `outcome.summary` returns, once the run finishes:
|
|
306
269
|
|
|
307
270
|
```ruby
|
|
308
|
-
repo.backup(source: "/Users/zia/Documents") do |message|
|
|
271
|
+
outcome = repo.backup(source: "/Users/zia/Documents") do |message|
|
|
309
272
|
case message
|
|
310
273
|
when Yobi::BackupStatus
|
|
311
274
|
puts "#{((message.percent_done || 0) * 100).round}% (#{message.files_done}/#{message.total_files})"
|
|
@@ -313,8 +276,11 @@ repo.backup(source: "/Users/zia/Documents") do |message|
|
|
|
313
276
|
warn "#{message.item}: #{message.message}"
|
|
314
277
|
when Yobi::BackupVerboseStatus
|
|
315
278
|
puts "#{message.action} #{message.item}"
|
|
279
|
+
when Yobi::BackupSummary
|
|
280
|
+
puts "done: #{message["data_added"]} bytes added"
|
|
316
281
|
end
|
|
317
282
|
end
|
|
283
|
+
outcome.summary["snapshot_id"]
|
|
318
284
|
```
|
|
319
285
|
|
|
320
286
|
`Yobi::BackupVerboseStatus` (one message per file) only streams when `verbose: true` is passed; it's not emitted by default, since a large backup can otherwise produce hundreds of MB of these. Behavior without a block is identical, just without the live callbacks; the returned `BackupOutcome` is the same either way.
|
|
@@ -325,19 +291,9 @@ end
|
|
|
325
291
|
repo.restore(snapshot_id: "latest", target: "/tmp/restore")
|
|
326
292
|
```
|
|
327
293
|
|
|
328
|
-
`snapshot_id:` accepts `"latest"` or a real ID; `target:` is the destination directory.
|
|
329
|
-
|
|
330
|
-
- `excludes:`/`includes:` (and their `i`-prefixed case-insensitive/`_file` file-based variants): filter what gets restored. `includes:` has no backup-time equivalent, since restoring is the one direction where "only these" makes sense as well as "all but these."
|
|
331
|
-
- `exclude_xattrs:`/`include_xattrs:`: filter extended attributes specifically
|
|
332
|
-
- `dry_run:`: report what would happen without doing it
|
|
333
|
-
- `delete:`: removes files in `target:` not present in the snapshot
|
|
334
|
-
- `overwrite:` (`"always"`/`"if-changed"`/`"if-newer"`): controls what happens to files that already exist there
|
|
335
|
-
- `sparse:`: writes sparse files
|
|
336
|
-
- `ownership_by_name:`: maps ownership by user/group name instead of numeric ID
|
|
337
|
-
- `verify:`: re-reads restored content against the repository to confirm it matches
|
|
338
|
-
- `hosts:`/`paths:`/`tags:`: only matter when `snapshot_id:` is `"latest"`, same filters as everywhere else, used to resolve which snapshot "latest" means
|
|
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).
|
|
339
295
|
|
|
340
|
-
Returns a `RestoreOutcome` (`#report
|
|
296
|
+
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.
|
|
341
297
|
|
|
342
298
|
Accepts a block the same way `#backup` does, yielding `Yobi::RestoreStatus`/`Yobi::RestoreVerboseStatus` (the latter only when `verbose: true`) instead of `Yobi::BackupStatus`/`Yobi::BackupVerboseStatus`.
|
|
343
299
|
|
|
@@ -359,7 +315,7 @@ repo.dump(snapshot_id: "latest", file: "/var/www", target: "/tmp/www.tar")
|
|
|
359
315
|
repo.dump(snapshot_id: "latest", file: "/var/www", target: "/tmp/www.zip", archive: "zip")
|
|
360
316
|
```
|
|
361
317
|
|
|
362
|
-
Give at most one of `target:` or a block. Without either, returns a `Yobi::IOHandle` instead
|
|
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.
|
|
363
319
|
|
|
364
320
|
```ruby
|
|
365
321
|
handle = repo.dump(snapshot_id: "latest", file: "/var/www")
|
|
@@ -375,11 +331,13 @@ end
|
|
|
375
331
|
|
|
376
332
|
```ruby
|
|
377
333
|
outcome = repo.diff(from: older_snapshot.id, to: newer_snapshot.id)
|
|
378
|
-
outcome.
|
|
334
|
+
outcome.statistics.changed_files
|
|
379
335
|
outcome.changes.each { |change| puts "#{change.modifier} #{change.path}" }
|
|
380
336
|
```
|
|
381
337
|
|
|
382
|
-
`metadata: true` also reports metadata-only changes (permissions, timestamps) alongside content changes. `change.modifier` is Restic's own single-character
|
|
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.
|
|
339
|
+
|
|
340
|
+
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`).
|
|
383
341
|
|
|
384
342
|
## Snapshot management
|
|
385
343
|
|
|
@@ -391,7 +349,7 @@ repo.snapshots(tags: ["daily"], hosts: "web-1").each do |snapshot|
|
|
|
391
349
|
end
|
|
392
350
|
```
|
|
393
351
|
|
|
394
|
-
Returns an Enumerable of `Snapshot` (`#id`, `#short_id`, `#time`, `#host`, `#tags`, `#paths`, `#parent_id`
|
|
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.
|
|
395
353
|
|
|
396
354
|
### `#tag`
|
|
397
355
|
|
|
@@ -399,7 +357,9 @@ Returns an Enumerable of `Snapshot` (`#id`, `#short_id`, `#time`, `#host`, `#tag
|
|
|
399
357
|
repo.tag(snapshot_ids: snapshot.id, add: "verified")
|
|
400
358
|
```
|
|
401
359
|
|
|
402
|
-
`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.
|
|
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.
|
|
361
|
+
|
|
362
|
+
Returns a `TagOutcome`: `#summary` (aliased `#report`; a `TagSummary` - just `#changed_snapshots`) and lazy `#changes` (Enumerable of `TagChange`, the old-ID/new-ID pairs).
|
|
403
363
|
|
|
404
364
|
### `#forget`
|
|
405
365
|
|
|
@@ -407,30 +367,34 @@ repo.tag(snapshot_ids: snapshot.id, add: "verified")
|
|
|
407
367
|
repo.forget(keep_daily: 7, keep_weekly: 4, keep_monthly: 12, prune: true)
|
|
408
368
|
```
|
|
409
369
|
|
|
410
|
-
Applies a retention policy, removing snapshots that don't match any `keep_*` rule
|
|
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.
|
|
411
371
|
|
|
412
|
-
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 `
|
|
372
|
+
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"`).
|
|
413
373
|
|
|
414
374
|
### `#find`
|
|
415
375
|
|
|
416
376
|
```ruby
|
|
417
377
|
repo.find(patterns: "*.pem").each do |matches|
|
|
418
|
-
puts "in #{matches.
|
|
378
|
+
puts "in #{matches.snapshot_id}: #{matches.hits} hits"
|
|
419
379
|
matches.matches.each { |m| puts " #{m.path}" }
|
|
420
380
|
end
|
|
421
381
|
```
|
|
422
382
|
|
|
423
|
-
Searches for files/directories by name pattern across snapshots. `blob:`/`pack:`/`tree:` switch to matching object IDs instead, for low-level troubleshooting.
|
|
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.
|
|
384
|
+
|
|
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.).
|
|
424
386
|
|
|
425
387
|
### `#ls`
|
|
426
388
|
|
|
427
389
|
```ruby
|
|
428
390
|
outcome = repo.ls(snapshot_id: "latest", dirs: "/var/www")
|
|
429
391
|
outcome.snapshot.short_id
|
|
430
|
-
outcome.
|
|
392
|
+
outcome.nodes.each { |node| puts node.path }
|
|
431
393
|
```
|
|
432
394
|
|
|
433
|
-
Lists a snapshot's files/directories. `recursive:` descends into subdirectories
|
|
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.
|
|
396
|
+
|
|
397
|
+
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).
|
|
434
398
|
|
|
435
399
|
## Maintenance and health
|
|
436
400
|
|
|
@@ -438,11 +402,13 @@ Lists a snapshot's files/directories. `recursive:` descends into subdirectories;
|
|
|
438
402
|
|
|
439
403
|
```ruby
|
|
440
404
|
outcome = repo.check(read_data_subset: "5%")
|
|
441
|
-
outcome.
|
|
405
|
+
outcome.summary.num_errors
|
|
442
406
|
outcome.errors.each { |error| puts error.message }
|
|
443
407
|
```
|
|
444
408
|
|
|
445
|
-
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)
|
|
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.
|
|
410
|
+
|
|
411
|
+
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.
|
|
446
412
|
|
|
447
413
|
### `#prune`
|
|
448
414
|
|
|
@@ -450,7 +416,7 @@ Verifies repository integrity. `read_data:` also reads and verifies every pack f
|
|
|
450
416
|
repo.prune(max_unused: "5%")
|
|
451
417
|
```
|
|
452
418
|
|
|
453
|
-
Removes data no longer referenced by any snapshot. `dry_run:` reports what would happen without doing it
|
|
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`.
|
|
454
420
|
|
|
455
421
|
### `#repair_index`, `#repair_packs`, `#repair_snapshots`
|
|
456
422
|
|
|
@@ -460,7 +426,7 @@ repo.repair_packs(ids: damaged_pack_ids)
|
|
|
460
426
|
repo.repair_snapshots(snapshot_ids: affected_ids, forget: true)
|
|
461
427
|
```
|
|
462
428
|
|
|
463
|
-
`#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.
|
|
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.
|
|
464
430
|
|
|
465
431
|
### `#recover`
|
|
466
432
|
|
|
@@ -476,7 +442,7 @@ Builds a new snapshot from any data present in the repository but not referenced
|
|
|
476
442
|
repo.rewrite(snapshot_ids: old.id, excludes: "*.log", new_time: Time.now.iso8601)
|
|
477
443
|
```
|
|
478
444
|
|
|
479
|
-
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
|
|
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.
|
|
480
446
|
|
|
481
447
|
### `#migrate`
|
|
482
448
|
|
|
@@ -498,23 +464,30 @@ Removes stale locks left by other Restic processes. `remove_all: true` removes e
|
|
|
498
464
|
|
|
499
465
|
```ruby
|
|
500
466
|
repo.stats(mode: "raw-data")
|
|
501
|
-
# =>
|
|
467
|
+
# => #<Yobi::RepositoryStats total_size=... total_file_count=... snapshots_count=...>
|
|
502
468
|
```
|
|
503
469
|
|
|
504
|
-
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`.
|
|
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.
|
|
505
471
|
|
|
506
472
|
## Key management
|
|
507
473
|
|
|
508
474
|
```ruby
|
|
509
|
-
repo.key_add(user: "ci-runner")
|
|
475
|
+
repo.key_add(user: "ci-runner", new_password: [:file, "/etc/restic/ci-runner-password"])
|
|
510
476
|
repo.key_list.each { |key| puts "#{key.id} #{key.user_name}@#{key.host_name} current=#{key.current?}" }
|
|
511
|
-
repo.key_passwd(
|
|
477
|
+
repo.key_passwd(new_password: [:file, "/etc/restic/new-password"])
|
|
512
478
|
repo.key_remove(id: old_key.id)
|
|
513
479
|
```
|
|
514
480
|
|
|
515
|
-
`#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)'s note on `#cat_masterkey_and_game_over_if_this_leaks`
|
|
481
|
+
`#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`).
|
|
516
482
|
|
|
517
|
-
`#key_add`/`#key_passwd` share the same kwargs: `
|
|
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.
|
|
484
|
+
|
|
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:
|
|
486
|
+
|
|
487
|
+
```ruby
|
|
488
|
+
repo.key_passwd(new_password: "new-password-value")
|
|
489
|
+
repo.snapshots # already using the new password - no need to rebuild `repo`
|
|
490
|
+
```
|
|
518
491
|
|
|
519
492
|
Restic refuses to `#key_remove` the key currently in use.
|
|
520
493
|
|
|
@@ -534,13 +507,21 @@ repo.list(:packs) # => Array of pack IDs
|
|
|
534
507
|
|
|
535
508
|
## Across repositories
|
|
536
509
|
|
|
510
|
+
### `#init_mirror`
|
|
511
|
+
|
|
512
|
+
```ruby
|
|
513
|
+
mirror_repo = primary_repo.init_mirror(url: "b2:my-bucket:/", password: "...")
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
A shortcut for setting up a `#copy` destination: constructs a new `Repository` and initializes it with chunker parameters copied from `primary_repo`, so `#copy` between the two can deduplicate (skipped this way, Restic picks each repository's chunker polynomial independently at `#init` time, so nothing between them would ever dedupe). A one-time setup call, not an ongoing sync - pair it with repeated `#copy` calls below to actually move snapshots across.
|
|
517
|
+
|
|
537
518
|
### `#copy`
|
|
538
519
|
|
|
539
520
|
```ruby
|
|
540
|
-
|
|
521
|
+
mirror_repo.copy(from_repo: primary_repo, tags: "nightly")
|
|
541
522
|
```
|
|
542
523
|
|
|
543
|
-
Replicates snapshots from another repository into this one. `from_repo:` accepts a plain URL String or a `Repository` instance. Given the latter, its own `#url`/`#password` are used automatically unless `from_password:` is given explicitly.
|
|
524
|
+
Replicates snapshots from another repository into this one. `from_repo:` accepts a plain URL String 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`.
|
|
544
525
|
|
|
545
526
|
## Mounting
|
|
546
527
|
|
|
@@ -552,7 +533,7 @@ repo.mount(mountpoint: "/mnt/restic") do |mount|
|
|
|
552
533
|
end
|
|
553
534
|
```
|
|
554
535
|
|
|
555
|
-
Serves the repository as a read-only FUSE filesystem at `mountpoint` (which must already exist). With a block, yields a `Yobi::
|
|
536
|
+
Serves the repository as a read-only FUSE filesystem at `mountpoint` (which must already exist). With a block, yields a `Yobi::MountHandle` and stops it automatically afterward. Without one, returns the `MountHandle` for you to `#stop` yourself:
|
|
556
537
|
|
|
557
538
|
```ruby
|
|
558
539
|
mount = repo.mount(mountpoint: "/mnt/restic")
|
|
@@ -563,17 +544,17 @@ ensure
|
|
|
563
544
|
end
|
|
564
545
|
```
|
|
565
546
|
|
|
566
|
-
`#stop` is safe to call more than once, and `
|
|
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.
|
|
567
548
|
|
|
568
549
|
## Restic-level (not scoped to a repository)
|
|
569
550
|
|
|
570
551
|
```ruby
|
|
571
552
|
restic = Yobi::Restic.new
|
|
572
|
-
restic.version # =>
|
|
553
|
+
restic.version.version # => "0.19.1"
|
|
573
554
|
restic.cache(cleanup: true)
|
|
574
555
|
```
|
|
575
556
|
|
|
576
|
-
`#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`. `#cache` lists (or, with `cleanup: true`, cleans) local cache directories, returning `true`.
|
|
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.
|
|
577
558
|
|
|
578
559
|
## Global `Restic` settings
|
|
579
560
|
|
|
@@ -589,8 +570,10 @@ restic = Yobi::Restic.new(
|
|
|
589
570
|
|
|
590
571
|
Two genuinely different kinds, both exposed as plain `attr_accessor`s:
|
|
591
572
|
|
|
592
|
-
- **Env-var-backed
|
|
593
|
-
- **CLI-flag-only, no env var equivalent
|
|
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
|
+
|
|
576
|
+
See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Restic) for the full list of settings.
|
|
594
577
|
|
|
595
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.
|
|
596
579
|
|
|
@@ -642,9 +625,7 @@ outcome = repo.backup(
|
|
|
642
625
|
exclude_caches: true
|
|
643
626
|
)
|
|
644
627
|
|
|
645
|
-
|
|
646
|
-
outcome.errors.each { |e| log_error("backup: #{e.item}: #{e.message}") }
|
|
647
|
-
end
|
|
628
|
+
outcome.errors.each { |e| log_error("backup: #{e.item}: #{e.message}") }
|
|
648
629
|
|
|
649
630
|
repo.forget(
|
|
650
631
|
keep_daily: 7,
|
|
@@ -655,7 +636,7 @@ repo.forget(
|
|
|
655
636
|
)
|
|
656
637
|
```
|
|
657
638
|
|
|
658
|
-
`outcome.
|
|
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).
|
|
659
640
|
|
|
660
641
|
### Restore workflows
|
|
661
642
|
|
|
@@ -683,8 +664,8 @@ end
|
|
|
683
664
|
or list without mounting anything:
|
|
684
665
|
|
|
685
666
|
```ruby
|
|
686
|
-
repo.ls(snapshot_id: "latest", dirs: "/var/myapp/uploads").
|
|
687
|
-
puts "#{
|
|
667
|
+
repo.ls(snapshot_id: "latest", dirs: "/var/myapp/uploads").nodes.each do |node|
|
|
668
|
+
puts "#{node.type} #{node.size} #{node.path}"
|
|
688
669
|
end
|
|
689
670
|
```
|
|
690
671
|
|
|
@@ -812,11 +793,11 @@ end
|
|
|
812
793
|
workers.each(&:join)
|
|
813
794
|
```
|
|
814
795
|
|
|
815
|
-
**Copying between repositories** (e.g. replicating to a second, offsite backend for a 3-2-1 strategy):
|
|
796
|
+
**Copying between repositories** (e.g. replicating to a second, offsite backend for a 3-2-1 strategy), see `#init_mirror`/`#copy` under ["Across repositories"](#across-repositories):
|
|
816
797
|
|
|
817
798
|
```ruby
|
|
818
|
-
|
|
819
|
-
|
|
799
|
+
mirror_repo = primary_repo.init_mirror(url: "b2:my-bucket:/", password: "...") # once, before the first #copy
|
|
800
|
+
mirror_repo.copy(from_repo: primary_repo, tags: "nightly") # run nightly, e.g. from cron
|
|
820
801
|
```
|
|
821
802
|
|
|
822
803
|
### Repository maintenance and health checks
|
|
@@ -825,7 +806,7 @@ A separate, less-frequent job (weekly, say) than the nightly backup itself:
|
|
|
825
806
|
|
|
826
807
|
```ruby
|
|
827
808
|
check = repo.check(read_data_subset: "10%")
|
|
828
|
-
if check.
|
|
809
|
+
if check.summary.num_errors.positive?
|
|
829
810
|
check.errors.each { |e| AlertService.notify("Restic check: #{e.message}") }
|
|
830
811
|
end
|
|
831
812
|
|
|
@@ -834,20 +815,6 @@ repo.prune(max_unused: "5%")
|
|
|
834
815
|
|
|
835
816
|
`read_data_subset:` rotates through the repository over time (e.g. `"1/20"` today, `"2/20"` tomorrow) rather than reading everything in one run, so a full verification eventually happens without one job spending hours reading the entire dataset.
|
|
836
817
|
|
|
837
|
-
### Key rotation
|
|
838
|
-
|
|
839
|
-
```ruby
|
|
840
|
-
repo.key_passwd(new_password_file: "/etc/restic/new-password")
|
|
841
|
-
|
|
842
|
-
# repo itself still holds the OLD password - rebuild before continuing
|
|
843
|
-
rotated_repo = Yobi::Repository.new(
|
|
844
|
-
url: repo.url,
|
|
845
|
-
password: [:file, "/etc/restic/new-password"]
|
|
846
|
-
)
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
This rotates the *password*, not the underlying encryption key itself (see ["Key management"](#key-management) above for why `repo` still needs rebuilding afterward). See ["Low-level (`cat`) and object listing"](#low-level-cat-and-object-listing)'s note on `#cat_masterkey_and_game_over_if_this_leaks` if that distinction matters for your threat model.
|
|
850
|
-
|
|
851
818
|
### Graceful lock-contention handling
|
|
852
819
|
|
|
853
820
|
Two processes touching the same repository at once (a backup job and a prune job overlapping, say): retry instead of failing the whole run:
|
|
@@ -895,3 +862,5 @@ See `MAINTAINERS.md` for what to keep in sync as the API changes, and the releas
|
|
|
895
862
|
## License
|
|
896
863
|
|
|
897
864
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
865
|
+
|
|
866
|
+
☂️☔️☂️☔️☂️
|
data/lib/yobi/argv_builder.rb
CHANGED
|
@@ -24,6 +24,11 @@ module Yobi
|
|
|
24
24
|
@argv
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# @return [String]
|
|
28
|
+
def inspect
|
|
29
|
+
"#<#{self.class} #{@argv.inspect}>"
|
|
30
|
+
end
|
|
31
|
+
|
|
27
32
|
# Appends one or more bare positional values. `nil` is dropped; an
|
|
28
33
|
# Enumerable is flattened in; everything else is converted with `#to_s`.
|
|
29
34
|
#
|
data/lib/yobi/errors.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
-
require "delegate"
|
|
5
4
|
|
|
6
5
|
module Yobi
|
|
7
6
|
# Base class for all errors raised by Yobi.
|
|
@@ -9,8 +8,8 @@ module Yobi
|
|
|
9
8
|
|
|
10
9
|
# The `exit_error` message from a fatal Restic run, if one was printed.
|
|
11
10
|
# https://restic.readthedocs.io/en/stable/075_scripting.html#exit-errors
|
|
12
|
-
class ExitError <
|
|
13
|
-
#
|
|
11
|
+
class ExitError < Yobi::FancyHash
|
|
12
|
+
# @private
|
|
14
13
|
LINE_PATTERN = /"message_type"\s*:\s*"exit_error"/
|
|
15
14
|
|
|
16
15
|
# @param output [Yobi::ResticOutput]
|
|
@@ -37,8 +36,7 @@ module Yobi
|
|
|
37
36
|
# @return [String]
|
|
38
37
|
attr_reader :installed_version, :minimum_version
|
|
39
38
|
|
|
40
|
-
# @
|
|
41
|
-
# @param minimum_version [String]
|
|
39
|
+
# @private
|
|
42
40
|
def initialize(installed_version:, minimum_version:)
|
|
43
41
|
@installed_version = installed_version
|
|
44
42
|
@minimum_version = minimum_version
|
|
@@ -53,8 +51,7 @@ module Yobi
|
|
|
53
51
|
# @return [Array<String>]
|
|
54
52
|
attr_reader :argv
|
|
55
53
|
|
|
56
|
-
# @
|
|
57
|
-
# @param argv [Array<String>]
|
|
54
|
+
# @private
|
|
58
55
|
def initialize(restic_path:, argv:)
|
|
59
56
|
@restic_path = restic_path
|
|
60
57
|
@argv = argv
|
|
@@ -70,7 +67,7 @@ module Yobi
|
|
|
70
67
|
# @return [Yobi::ExitError, nil]
|
|
71
68
|
attr_reader :exit_error
|
|
72
69
|
|
|
73
|
-
# @
|
|
70
|
+
# @private
|
|
74
71
|
def initialize(execution)
|
|
75
72
|
@execution = execution
|
|
76
73
|
@exit_error = Yobi::ExitError.from_output(execution[:output])
|
|
@@ -98,8 +95,7 @@ module Yobi
|
|
|
98
95
|
# @return [Numeric]
|
|
99
96
|
attr_reader :timeout
|
|
100
97
|
|
|
101
|
-
# @
|
|
102
|
-
# @param timeout [Numeric]
|
|
98
|
+
# @private
|
|
103
99
|
def initialize(argv:, timeout:)
|
|
104
100
|
@argv = argv
|
|
105
101
|
@timeout = timeout
|