yobi 0.2.0 → 0.3.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 +8 -0
- data/README.md +1 -1
- data/lib/yobi/repository/backup.rb +4 -0
- data/lib/yobi/repository/copy.rb +9 -11
- data/lib/yobi/repository/init.rb +12 -11
- data/lib/yobi/repository/key.rb +4 -2
- data/lib/yobi/repository/mount.rb +1 -1
- data/lib/yobi/repository.rb +50 -2
- data/lib/yobi/restic.rb +3 -3
- data/lib/yobi/version.rb +1 -1
- data/sig/yobi.rbs +4 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47dd32402940fcd7c2699d932ca4a4e0db0f6e1ca4e8342ac5d41abc1cbe6114
|
|
4
|
+
data.tar.gz: 888feb226907437769093ffbad5245f159e5879acb9a6a12de77e908ef14ed51
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9d683046fe0d910622731b5e527f2fda6a93ddf753bc849d9066ae2432667fe4feb2a3c404c48f7e0a54c7cacb5442bf7b1ab36cefd17d24e21388932342893
|
|
7
|
+
data.tar.gz: 46c31f00651a04b6fd67ec3ccba73ff1bca4b6f2f8deccd3f18233f274d0c8c1394f19d4472dd96013ac340adb225d20f4fc87e8a41d49eeeef9f34dd256b8d4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [0.3.0] - 2026-08-03
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
|
|
5
|
+
- **Breaking:** `#init`/`#copy`'s `from_repository_file:` kwarg removed - `from_repo:` is now polymorphic, additionally accepting a `[:file, "..."]` tuple to read the source repository's URL from a file, alongside the existing plain URL String/`Repository` instance shapes.
|
|
6
|
+
- Invalid shapes for `password:`, `backend_credentials:`, `restic:`, `source:`/a `stdin_from_command` command, `new_password:`, `from_repo:`, and `from_password:` now raise `ArgumentError` with a message like `"invalid password shape"`, instead of a raw `NoMatchingPatternError`/`NoMatchingPatternKeyError`.
|
|
7
|
+
- `Repository#initialize`'s `password:`/`backend_credentials:` shapes are now validated eagerly at construction (and `from_password:`/`from_repo:` eagerly in `#init`/`#copy`), instead of lazily on the first `#env`/`#inspect`/argv-building call.
|
|
8
|
+
|
|
1
9
|
## [0.2.0] - 2026-08-03
|
|
2
10
|
|
|
3
11
|
### Added
|
data/README.md
CHANGED
|
@@ -521,7 +521,7 @@ A shortcut for setting up a `#copy` destination: constructs a new `Repository` a
|
|
|
521
521
|
mirror_repo.copy(from_repo: primary_repo, tags: "nightly")
|
|
522
522
|
```
|
|
523
523
|
|
|
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`.
|
|
524
|
+
Replicates snapshots from another repository into this one. `from_repo:` accepts a plain URL String, a `[:file, "..."]` tuple reading the URL from a file, or a `Repository` instance. Given the latter, its own `#url`/`#password` are used automatically unless `from_password:` is given explicitly. Already-copied snapshots are skipped automatically on a repeat run. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#copy-instance_method) for the full list of options. Returns `true`.
|
|
525
525
|
|
|
526
526
|
## Mounting
|
|
527
527
|
|
|
@@ -83,6 +83,8 @@ module Yobi
|
|
|
83
83
|
a.flag(:stdin_from_command)
|
|
84
84
|
a.flag(:stdin_filename, filename)
|
|
85
85
|
a.end_of_options.append(tokenize(command))
|
|
86
|
+
else
|
|
87
|
+
raise ArgumentError, "invalid source shape"
|
|
86
88
|
end
|
|
87
89
|
end
|
|
88
90
|
|
|
@@ -99,6 +101,8 @@ module Yobi
|
|
|
99
101
|
Shellwords.split(value)
|
|
100
102
|
in Array => values
|
|
101
103
|
values.map(&:to_s)
|
|
104
|
+
else
|
|
105
|
+
raise ArgumentError, "invalid stdin_from_command command shape"
|
|
102
106
|
end
|
|
103
107
|
end
|
|
104
108
|
end
|
data/lib/yobi/repository/copy.rb
CHANGED
|
@@ -5,33 +5,31 @@ module Yobi
|
|
|
5
5
|
# `restic copy`: replicates snapshots from another repository into
|
|
6
6
|
# this one. Already-copied snapshots are skipped automatically.
|
|
7
7
|
#
|
|
8
|
-
# @param from_repo [String, Yobi::Repository] the source repository
|
|
9
|
-
#
|
|
8
|
+
# @param from_repo [String, Yobi::Repository, Array] the source repository's
|
|
9
|
+
# URL; a `Repository` instance (its own `#url`/`#password` are used
|
|
10
|
+
# automatically); or a `[:file, "..."]` tuple reading the URL from a file
|
|
10
11
|
# @param from_password [String, Array, Symbol, #call, nil] the source
|
|
11
12
|
# repository's password, same shape as {#initialize}'s `password:`
|
|
12
13
|
# @param snapshot_ids [Array<String>, String] snapshots to copy; all of them if empty
|
|
13
14
|
# @param from_key_hint [String, nil]
|
|
14
|
-
# @param from_repository_file [String, nil] read the source repository's URL from a file
|
|
15
15
|
# @param hosts [Array<String>, String] filter by hostname(s)
|
|
16
16
|
# @param paths [Array<String>, String] filter by originally backed-up path(s)
|
|
17
17
|
# @param tags [Array<String>, String] filter by tag(s)
|
|
18
18
|
# @return [true]
|
|
19
|
-
def copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil,
|
|
20
|
-
|
|
21
|
-
if
|
|
22
|
-
|
|
23
|
-
from_repo = from_repo.url
|
|
24
|
-
end
|
|
19
|
+
def copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, hosts: [], paths: [], tags: [])
|
|
20
|
+
from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo)
|
|
21
|
+
from_password = from_repo_password(from_repo) if from_password.nil?
|
|
22
|
+
validate_from_password_shape!(from_password)
|
|
25
23
|
|
|
26
24
|
argv = build_argv("copy", snapshot_ids) do |a|
|
|
27
|
-
a.flag(
|
|
25
|
+
a.flag(from_repo_flag, from_repo_value)
|
|
28
26
|
a.flag(:from_insecure_no_password) if from_password == :insecure_no_password
|
|
29
27
|
a.flag(:from_key_hint, from_key_hint) unless from_key_hint.nil?
|
|
30
|
-
a.flag(:from_repository_file, from_repository_file) unless from_repository_file.nil?
|
|
31
28
|
a.repeat_flag(:host, hosts)
|
|
32
29
|
a.repeat_flag(:path, paths)
|
|
33
30
|
a.repeat_flag(:tag, tags)
|
|
34
31
|
end
|
|
32
|
+
|
|
35
33
|
run_restic(argv, extra_env: password_env(from_password, "RESTIC_FROM_PASSWORD"))
|
|
36
34
|
true
|
|
37
35
|
end
|
data/lib/yobi/repository/init.rb
CHANGED
|
@@ -6,31 +6,32 @@ module Yobi
|
|
|
6
6
|
class Repository
|
|
7
7
|
# `restic init`: creates the repository at {#url}.
|
|
8
8
|
#
|
|
9
|
-
# @param copy_chunker_params [Boolean] copy chunker parameters from `from_repo
|
|
9
|
+
# @param copy_chunker_params [Boolean] copy chunker parameters from `from_repo:`
|
|
10
10
|
# @param from_key_hint [String, nil]
|
|
11
11
|
# @param from_password [String, Array, Symbol, #call, nil] the source
|
|
12
12
|
# repository's password (when copying chunker params from one), same
|
|
13
13
|
# shape as {#initialize}'s `password:`
|
|
14
|
-
# @param from_repo [String, Yobi::Repository, nil] the source repository
|
|
15
|
-
#
|
|
16
|
-
#
|
|
14
|
+
# @param from_repo [String, Yobi::Repository, Array, nil] the source repository's
|
|
15
|
+
# URL; a `Repository` instance (its own `#url`/`#password` are used
|
|
16
|
+
# automatically); or a `[:file, "..."]` tuple reading the URL from a file
|
|
17
17
|
# @param repository_version [String, nil]
|
|
18
18
|
# @return [Yobi::Initialized]
|
|
19
|
-
def init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
def init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil, repository_version: nil)
|
|
20
|
+
from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo) unless from_repo.nil?
|
|
21
|
+
|
|
22
|
+
if from_repo_value
|
|
23
|
+
from_password = from_repo_password(from_repo) if from_password.nil?
|
|
24
|
+
validate_from_password_shape!(from_password)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
argv = build_argv("init") do |a|
|
|
27
28
|
a.flag(:copy_chunker_params) if copy_chunker_params
|
|
28
29
|
a.flag(:from_insecure_no_password) if from_password == :insecure_no_password
|
|
29
30
|
a.flag(:from_key_hint, from_key_hint) unless from_key_hint.nil?
|
|
30
|
-
a.flag(
|
|
31
|
-
a.flag(:from_repository_file, from_repository_file) unless from_repository_file.nil?
|
|
31
|
+
a.flag(from_repo_flag, from_repo_value) if from_repo_flag
|
|
32
32
|
a.flag(:repository_version, repository_version) unless repository_version.nil?
|
|
33
33
|
end
|
|
34
|
+
|
|
34
35
|
execution = run_restic(argv, extra_env: password_env(from_password, "RESTIC_FROM_PASSWORD"))
|
|
35
36
|
Initialized.new(JSON.parse(execution[:output].to_s))
|
|
36
37
|
end
|
data/lib/yobi/repository/key.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Yobi
|
|
|
24
24
|
argv = build_argv("key", "add") do |a|
|
|
25
25
|
a.flag(:host, host) unless host.nil?
|
|
26
26
|
a.flag(:user, user) unless user.nil?
|
|
27
|
-
a.flag(flag_name, flag_value)
|
|
27
|
+
a.flag(flag_name, flag_value)
|
|
28
28
|
end
|
|
29
29
|
run_restic(argv)
|
|
30
30
|
end
|
|
@@ -55,7 +55,7 @@ module Yobi
|
|
|
55
55
|
argv = build_argv("key", "passwd") do |a|
|
|
56
56
|
a.flag(:host, host) unless host.nil?
|
|
57
57
|
a.flag(:user, user) unless user.nil?
|
|
58
|
-
a.flag(flag_name, flag_value)
|
|
58
|
+
a.flag(flag_name, flag_value)
|
|
59
59
|
end
|
|
60
60
|
run_restic(argv)
|
|
61
61
|
end
|
|
@@ -88,6 +88,8 @@ module Yobi
|
|
|
88
88
|
with_temp_password_file(literal) { |path| yield :new_password_file, path }
|
|
89
89
|
in callable if callable.respond_to?(:call)
|
|
90
90
|
with_temp_password_file(callable.call.to_s) { |path| yield :new_password_file, path }
|
|
91
|
+
else
|
|
92
|
+
raise ArgumentError, "invalid new_password shape"
|
|
91
93
|
end
|
|
92
94
|
end
|
|
93
95
|
|
data/lib/yobi/repository.rb
CHANGED
|
@@ -23,9 +23,10 @@ module Yobi
|
|
|
23
23
|
# responding to `#call` returning such a Hash
|
|
24
24
|
# @param restic [Yobi::Restic, String, nil] a `Restic` instance to share,
|
|
25
25
|
# a bare Restic binary path, or `nil` to create a default one
|
|
26
|
-
# @raise [ArgumentError] if `password:` is `nil`
|
|
26
|
+
# @raise [ArgumentError] if `password:` is `nil`, or if `password:`/`backend_credentials:`/`restic:` is an invalid shape
|
|
27
27
|
def initialize(url:, password:, backend_credentials: {}, restic: nil)
|
|
28
|
-
|
|
28
|
+
validate_password_shape!(password)
|
|
29
|
+
validate_backend_credentials_shape!(backend_credentials)
|
|
29
30
|
|
|
30
31
|
@url, @extracted_rest_credentials = extract_rest_credentials(url)
|
|
31
32
|
@password = password
|
|
@@ -119,6 +120,51 @@ module Yobi
|
|
|
119
120
|
end
|
|
120
121
|
end
|
|
121
122
|
|
|
123
|
+
def from_repo_password(from_repo)
|
|
124
|
+
from_repo.password if from_repo.is_a?(Repository)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def resolved_from_repo_flag(from_repo)
|
|
128
|
+
case from_repo
|
|
129
|
+
in Repository
|
|
130
|
+
[:from_repo, from_repo.url]
|
|
131
|
+
in String
|
|
132
|
+
[:from_repo, from_repo]
|
|
133
|
+
in [:file, String => path]
|
|
134
|
+
[:from_repository_file, path]
|
|
135
|
+
else
|
|
136
|
+
raise ArgumentError, "invalid from_repo shape"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def validate_password_shape!(value, name: "password")
|
|
141
|
+
case value
|
|
142
|
+
in nil
|
|
143
|
+
raise ArgumentError, "#{name}: is required (use :insecure_no_password if there truly is none)"
|
|
144
|
+
in :insecure_no_password | String | [:command, String] | [:file, String]
|
|
145
|
+
true
|
|
146
|
+
in callable if callable.respond_to?(:call)
|
|
147
|
+
true
|
|
148
|
+
else
|
|
149
|
+
raise ArgumentError, "invalid #{name} shape"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def validate_from_password_shape!(value)
|
|
154
|
+
validate_password_shape!(value, name: "from_password")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def validate_backend_credentials_shape!(value)
|
|
158
|
+
case value
|
|
159
|
+
in Hash
|
|
160
|
+
true
|
|
161
|
+
in callable if callable.respond_to?(:call)
|
|
162
|
+
true
|
|
163
|
+
else
|
|
164
|
+
raise ArgumentError, "invalid backend_credentials shape"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
122
168
|
def extract_rest_credentials(url)
|
|
123
169
|
return [url, {}] unless url.start_with?("rest:")
|
|
124
170
|
|
|
@@ -139,6 +185,8 @@ module Yobi
|
|
|
139
185
|
restic_or_restic_path
|
|
140
186
|
in String | nil
|
|
141
187
|
Yobi::Restic.new(restic_or_restic_path)
|
|
188
|
+
else
|
|
189
|
+
raise ArgumentError, "invalid restic shape"
|
|
142
190
|
end
|
|
143
191
|
end
|
|
144
192
|
end
|
data/lib/yobi/restic.rb
CHANGED
|
@@ -262,7 +262,7 @@ module Yobi
|
|
|
262
262
|
self.class.dispatch(exit_code: status.exitstatus, output: output, argv: argv)
|
|
263
263
|
result
|
|
264
264
|
rescue Errno::ENOENT
|
|
265
|
-
output
|
|
265
|
+
output.file.close
|
|
266
266
|
raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
|
|
267
267
|
end
|
|
268
268
|
|
|
@@ -324,7 +324,7 @@ module Yobi
|
|
|
324
324
|
_, status = Process.wait2(pid)
|
|
325
325
|
{exit_code: status.exitstatus, output: output, argv: argv}
|
|
326
326
|
rescue Errno::ENOENT
|
|
327
|
-
output
|
|
327
|
+
output.file.close
|
|
328
328
|
raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
|
|
329
329
|
end
|
|
330
330
|
|
|
@@ -352,7 +352,7 @@ module Yobi
|
|
|
352
352
|
{exit_code: wait_thr.value.exitstatus, output: output, argv: argv}
|
|
353
353
|
end
|
|
354
354
|
rescue Errno::ENOENT
|
|
355
|
-
output
|
|
355
|
+
output.file.close
|
|
356
356
|
raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
|
|
357
357
|
end
|
|
358
358
|
end
|
data/lib/yobi/version.rb
CHANGED
data/sig/yobi.rbs
CHANGED
|
@@ -221,6 +221,7 @@ module Yobi
|
|
|
221
221
|
type new_password = String | [:file, String] | :insecure_no_password | (^() -> String)
|
|
222
222
|
type backend_credentials = Hash[String, String] | (^() -> Hash[String, String])
|
|
223
223
|
type restic_or_path = Restic | String | nil
|
|
224
|
+
type from_repo = String | Repository | [:file, String]
|
|
224
225
|
|
|
225
226
|
class Restic
|
|
226
227
|
MINIMUM_VERSION: Gem::Version
|
|
@@ -295,7 +296,7 @@ module Yobi
|
|
|
295
296
|
def env: () -> Hash[String, String]
|
|
296
297
|
|
|
297
298
|
def init: (?copy_chunker_params: bool, ?from_key_hint: String?, ?from_password: from_password,
|
|
298
|
-
?from_repo:
|
|
299
|
+
?from_repo: from_repo?, ?repository_version: String?) -> Initialized
|
|
299
300
|
|
|
300
301
|
def init_mirror: (url: String, password: password, ?backend_credentials: backend_credentials,
|
|
301
302
|
?restic: restic_or_path, ?repository_version: String?) -> Repository
|
|
@@ -335,8 +336,8 @@ module Yobi
|
|
|
335
336
|
def check: (?hosts: Array[String] | String, ?paths: Array[String] | String, ?read_data: bool,
|
|
336
337
|
?read_data_subset: String?, ?tags: Array[String] | String, ?with_cache: bool) -> CheckOutcome
|
|
337
338
|
|
|
338
|
-
def copy: (from_repo:
|
|
339
|
-
?from_key_hint: String?, ?
|
|
339
|
+
def copy: (from_repo: from_repo, ?from_password: from_password, ?snapshot_ids: Array[String] | String,
|
|
340
|
+
?from_key_hint: String?, ?hosts: Array[String] | String,
|
|
340
341
|
?paths: Array[String] | String, ?tags: Array[String] | String) -> true
|
|
341
342
|
|
|
342
343
|
def diff: (from: String, to: String, ?metadata: bool) -> DiffOutcome
|
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: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Zia Perdana
|
|
@@ -61,7 +61,7 @@ metadata:
|
|
|
61
61
|
homepage_uri: https://codeberg.org/ukazap/yobi
|
|
62
62
|
source_code_uri: https://codeberg.org/ukazap/yobi
|
|
63
63
|
changelog_uri: https://codeberg.org/ukazap/yobi/src/branch/main/CHANGELOG.md
|
|
64
|
-
documentation_uri: https://rubydoc.info/gems/yobi
|
|
64
|
+
documentation_uri: https://rubydoc.info/gems/yobi/0.3.0
|
|
65
65
|
rdoc_options: []
|
|
66
66
|
require_paths:
|
|
67
67
|
- lib
|