yobi 0.3.1 → 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 +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +17 -0
- data/README.md +83 -44
- data/lib/yobi/argv_builder.rb +1 -35
- data/lib/yobi/cancellable_proxy.rb +48 -0
- data/lib/yobi/cancellation.rb +97 -0
- data/lib/yobi/errors.rb +45 -30
- data/lib/yobi/fancy_hash.rb +1 -5
- data/lib/yobi/io_handle.rb +9 -17
- data/lib/yobi/mount_handle.rb +7 -11
- data/lib/yobi/repository/backup.rb +59 -90
- data/lib/yobi/repository/cat.rb +23 -43
- data/lib/yobi/repository/check.rb +21 -34
- data/lib/yobi/repository/copy.rb +11 -13
- data/lib/yobi/repository/diff.rb +18 -49
- data/lib/yobi/repository/dump.rb +14 -15
- data/lib/yobi/repository/find.rb +17 -37
- data/lib/yobi/repository/forget.rb +35 -39
- data/lib/yobi/repository/init.rb +19 -24
- data/lib/yobi/repository/key.rb +18 -33
- data/lib/yobi/repository/list.rb +5 -6
- data/lib/yobi/repository/ls.rb +20 -43
- data/lib/yobi/repository/migrate.rb +6 -6
- data/lib/yobi/repository/mount.rb +22 -32
- data/lib/yobi/repository/prune.rb +11 -9
- data/lib/yobi/repository/recover.rb +2 -4
- data/lib/yobi/repository/repair.rb +16 -23
- data/lib/yobi/repository/restore.rb +31 -54
- data/lib/yobi/repository/rewrite.rb +15 -20
- data/lib/yobi/repository/snapshots.rb +7 -8
- data/lib/yobi/repository/stats.rb +26 -21
- data/lib/yobi/repository/tag.rb +23 -33
- data/lib/yobi/repository/unlock.rb +2 -4
- data/lib/yobi/repository.rb +96 -25
- data/lib/yobi/restic.rb +93 -114
- data/lib/yobi/restic_output.rb +2 -74
- data/lib/yobi/snapshot.rb +15 -27
- data/lib/yobi/version.rb +1 -2
- data/lib/yobi.rb +2 -0
- data/sig/yobi.rbs +83 -6
- metadata +5 -2
|
@@ -2,36 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# +restic forget+: applies a retention policy, removing snapshots that
|
|
6
|
+
# don't match any +keep_*+ rule.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# @param max_unused [String, nil] passed through to the implied prune, e.g. `"10%"`
|
|
30
|
-
# @param max_repack_size [String, nil] passed through to the implied prune
|
|
31
|
-
# @param repack_cacheable_only [Boolean] passed through to the implied prune
|
|
32
|
-
# @param repack_uncompressed [Boolean] passed through to the implied prune
|
|
33
|
-
# @param repack_smaller_than [String, nil] passed through to the implied prune
|
|
34
|
-
# @return [Array<Yobi::ForgetGroup>]
|
|
8
|
+
# +keep_last:+ always keeps that many of the most recent snapshots.
|
|
9
|
+
# +keep_hourly:+, +keep_daily:+, +keep_weekly:+, +keep_monthly:+, and
|
|
10
|
+
# +keep_yearly:+ each keep that many of the most recent per period.
|
|
11
|
+
# +keep_within:+ keeps all snapshots within a given duration (e.g.
|
|
12
|
+
# +"30d"+); the +keep_within_hourly:+ ... +keep_within_yearly:+ variants
|
|
13
|
+
# apply the same duration within their respective periods.
|
|
14
|
+
# +keep_tags:+ always keeps snapshots carrying any of the listed tags.
|
|
15
|
+
#
|
|
16
|
+
# +hosts:+, +tags:+, and +paths:+ each accept a single value or an
|
|
17
|
+
# Array to filter by.
|
|
18
|
+
#
|
|
19
|
+
# +compact:+ compacts the printed policy summary. +group_by:+ (e.g.
|
|
20
|
+
# +"host,paths"+) picks the grouping used to apply the policy.
|
|
21
|
+
# +dry_run:+ reports what would happen without doing it.
|
|
22
|
+
# +unsafe_allow_remove_all:+ allows removing every snapshot.
|
|
23
|
+
#
|
|
24
|
+
# +prune: true+ also reclaims disk space. +max_unused:+, +max_repack_size:+,
|
|
25
|
+
# +repack_cacheable_only:+, +repack_uncompressed:+ and
|
|
26
|
+
# +repack_smaller_than:+ are passed through to that implied prune.
|
|
27
|
+
#
|
|
28
|
+
# Returns an Array of Yobi::ForgetGroup.
|
|
35
29
|
def forget(keep_last: nil, keep_hourly: nil, keep_daily: nil, keep_weekly: nil, keep_monthly: nil,
|
|
36
30
|
keep_yearly: nil, keep_within: nil, keep_within_hourly: nil, keep_within_daily: nil,
|
|
37
31
|
keep_within_weekly: nil, keep_within_monthly: nil, keep_within_yearly: nil, keep_tags: [],
|
|
@@ -67,52 +61,54 @@ module Yobi
|
|
|
67
61
|
a.flag(:repack_smaller_than, repack_smaller_than) unless repack_smaller_than.nil?
|
|
68
62
|
end
|
|
69
63
|
execution = run_restic(argv)
|
|
70
|
-
|
|
64
|
+
# Permissive because `restic forget` can surround its JSON summary
|
|
65
|
+
# with non-JSON lines (prune chatter on <= 0.18.1, or a
|
|
66
|
+
# "Remove(...) failed" line on partial-removal). See
|
|
67
|
+
# #parse_json_output_permissively for the full rundown.
|
|
68
|
+
parse_json_output_permissively(execution).map { |raw| ForgetGroup.new(raw) }
|
|
71
69
|
end
|
|
72
70
|
end
|
|
73
71
|
|
|
74
72
|
# One grouping Restic's forget policy was evaluated against (by default,
|
|
75
73
|
# grouped by host+paths).
|
|
76
74
|
class ForgetGroup < Yobi::FancyHash
|
|
77
|
-
# @return [String]
|
|
78
75
|
def host
|
|
79
76
|
self["host"]
|
|
80
77
|
end
|
|
81
78
|
|
|
82
|
-
# @return [Array<String>]
|
|
83
79
|
def tags
|
|
84
80
|
@tags ||= self["tags"] || []
|
|
85
81
|
end
|
|
86
82
|
|
|
87
|
-
# @return [Array<String>]
|
|
88
83
|
def paths
|
|
89
84
|
self["paths"]
|
|
90
85
|
end
|
|
91
86
|
|
|
92
|
-
#
|
|
87
|
+
# Yobi::Snapshot instances kept by the policy.
|
|
93
88
|
def keep
|
|
94
89
|
@keep ||= (self["keep"] || []).map { |entry| Yobi::Snapshot.new(entry) }
|
|
95
90
|
end
|
|
96
91
|
|
|
97
|
-
#
|
|
92
|
+
# Yobi::Snapshot instances removed by the policy.
|
|
98
93
|
def remove
|
|
99
94
|
@remove ||= (self["remove"] || []).map { |entry| Yobi::Snapshot.new(entry) }
|
|
100
95
|
end
|
|
101
96
|
|
|
102
|
-
#
|
|
97
|
+
# One Yobi::KeepReason per kept snapshot, explaining which policy
|
|
98
|
+
# rule(s) kept it.
|
|
103
99
|
def reasons
|
|
104
100
|
@reasons ||= (self["reasons"] || []).map { |entry| Yobi::KeepReason.new(entry) }
|
|
105
101
|
end
|
|
106
102
|
end
|
|
107
103
|
|
|
108
|
-
# Why one snapshot survived a
|
|
104
|
+
# Why one snapshot survived a Yobi::Repository#forget run.
|
|
109
105
|
class KeepReason < Yobi::FancyHash
|
|
110
|
-
#
|
|
106
|
+
# The Yobi::Snapshot that was kept.
|
|
111
107
|
def snapshot
|
|
112
108
|
@snapshot ||= Yobi::Snapshot.new(self["snapshot"])
|
|
113
109
|
end
|
|
114
110
|
|
|
115
|
-
#
|
|
111
|
+
# Restic's own human-readable rule-match strings, e.g. +"last snapshot"+.
|
|
116
112
|
def matches
|
|
117
113
|
@matches ||= self["matches"] || []
|
|
118
114
|
end
|
data/lib/yobi/repository/init.rb
CHANGED
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
5
|
+
# +restic init+: creates the repository at #url.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# @return [Yobi::Initialized]
|
|
7
|
+
# +copy_chunker_params: true+ copies chunker parameters from +from_repo:+
|
|
8
|
+
# (so a later #copy between the two can deduplicate). +from_repo:+ is
|
|
9
|
+
# the source repository's URL, a Yobi::Repository (its own #url/#password
|
|
10
|
+
# are used automatically), or a +[:file, "..."]+ tuple reading the URL
|
|
11
|
+
# from a file. +from_password:+ takes the same shape as #initialize's
|
|
12
|
+
# +password:+, and defaults to the source Repository's own password
|
|
13
|
+
# when +from_repo:+ is one.
|
|
14
|
+
#
|
|
15
|
+
# Returns a Yobi::Initialized.
|
|
17
16
|
def init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil, repository_version: nil)
|
|
18
17
|
from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo) unless from_repo.nil?
|
|
19
18
|
|
|
@@ -34,19 +33,15 @@ module Yobi
|
|
|
34
33
|
Initialized.new(parse_json_output(execution))
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
# Constructs a new
|
|
38
|
-
# chunker parameters copied from this one (
|
|
39
|
-
# true, from_repo: self)
|
|
36
|
+
# Constructs a new Yobi::Repository at +url+, already initialized with
|
|
37
|
+
# chunker parameters copied from this one (+#init(copy_chunker_params:
|
|
38
|
+
# true, from_repo: self)+), so a later #copy between the two can
|
|
40
39
|
# deduplicate. A one-time setup call, not an ongoing sync: nothing keeps
|
|
41
40
|
# the two repositories in sync afterward, and this doesn't mutate this
|
|
42
|
-
#
|
|
41
|
+
# Repository instance or copy any snapshots itself.
|
|
43
42
|
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# @param backend_credentials [Hash{String => String}, #call] the new repository's own backend credentials
|
|
47
|
-
# @param restic [Yobi::Restic, String, nil] a `Restic` instance to share, defaults to this repository's own
|
|
48
|
-
# @param repository_version [String, nil]
|
|
49
|
-
# @return [Yobi::Repository] the newly initialized mirror
|
|
43
|
+
# +url:+, +password:+, and +backend_credentials:+ take the same shapes
|
|
44
|
+
# as #initialize. +restic:+ defaults to this repository's own.
|
|
50
45
|
def init_mirror(url:, password:, backend_credentials: {}, restic: @restic, repository_version: nil)
|
|
51
46
|
Repository.new(url: url, password: password, backend_credentials: backend_credentials, restic: restic).tap do |mirror|
|
|
52
47
|
mirror.init(copy_chunker_params: true, from_repo: self, repository_version: repository_version)
|
|
@@ -54,14 +49,14 @@ module Yobi
|
|
|
54
49
|
end
|
|
55
50
|
end
|
|
56
51
|
|
|
57
|
-
# The
|
|
52
|
+
# The +"initialized"+ message from a Yobi::Repository#init run.
|
|
58
53
|
class Initialized < Yobi::FancyHash
|
|
59
|
-
#
|
|
54
|
+
# ID of the created repository.
|
|
60
55
|
def id
|
|
61
56
|
self["id"]
|
|
62
57
|
end
|
|
63
58
|
|
|
64
|
-
#
|
|
59
|
+
# URL of the repository.
|
|
65
60
|
def repository
|
|
66
61
|
self["repository"]
|
|
67
62
|
end
|
data/lib/yobi/repository/key.rb
CHANGED
|
@@ -6,19 +6,17 @@ require "tempfile"
|
|
|
6
6
|
|
|
7
7
|
module Yobi
|
|
8
8
|
class Repository
|
|
9
|
-
#
|
|
9
|
+
# +restic key add+: creates a new key (password) for this repository.
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# `password:`
|
|
21
|
-
# @return [true]
|
|
11
|
+
# +new_password:+ accepts the same shapes as #initialize's +password:+
|
|
12
|
+
# minus +[:command, ...]+: a literal String; a +[:file, "..."]+ tuple,
|
|
13
|
+
# resolved natively by Restic itself; +:insecure_no_password+; or
|
|
14
|
+
# anything responding to +#call+. A literal String or callable is
|
|
15
|
+
# written to a briefly-lived, 0600-permissioned tempfile, since Restic
|
|
16
|
+
# itself only accepts a new password by file or interactive prompt.
|
|
17
|
+
#
|
|
18
|
+
# +host:+ and +user:+ optionally override the hostname/username
|
|
19
|
+
# recorded on the new key.
|
|
22
20
|
def key_add(new_password:, host: nil, user: nil)
|
|
23
21
|
with_new_password_flag(new_password) do |flag_name, flag_value|
|
|
24
22
|
argv = build_argv("key", "add") do |a|
|
|
@@ -32,24 +30,19 @@ module Yobi
|
|
|
32
30
|
end
|
|
33
31
|
alias_method :add_key, :key_add
|
|
34
32
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# @return [Array<Yobi::Key>]
|
|
33
|
+
# +restic key list+: every Yobi::Key associated with this repository.
|
|
38
34
|
def key_list
|
|
39
35
|
execution = run_restic(build_argv("key", "list"))
|
|
40
36
|
parse_json_output(execution).map { |raw| Key.new(raw) }
|
|
41
37
|
end
|
|
42
38
|
alias_method :keys, :key_list
|
|
43
39
|
|
|
44
|
-
#
|
|
40
|
+
# +restic key passwd+: creates a new key for this repository and
|
|
45
41
|
# removes the key currently in use. On success, also updates this
|
|
46
|
-
#
|
|
42
|
+
# Repository instance's own +password:+ to +new_password:+, so it
|
|
47
43
|
# keeps working against this same repository afterward.
|
|
48
44
|
#
|
|
49
|
-
#
|
|
50
|
-
# @param user [String, nil] username to record on the new key
|
|
51
|
-
# @param new_password [String, Array, Symbol, #call] see {#key_add}
|
|
52
|
-
# @return [true]
|
|
45
|
+
# +new_password:+, +host:+ and +user:+ take the same shapes as #key_add.
|
|
53
46
|
def key_passwd(new_password:, host: nil, user: nil)
|
|
54
47
|
with_new_password_flag(new_password) do |flag_name, flag_value|
|
|
55
48
|
argv = build_argv("key", "passwd") do |a|
|
|
@@ -64,12 +57,8 @@ module Yobi
|
|
|
64
57
|
end
|
|
65
58
|
alias_method :change_password, :key_passwd
|
|
66
59
|
|
|
67
|
-
#
|
|
68
|
-
# Restic refuses to remove the key currently being used to access
|
|
69
|
-
# repository.
|
|
70
|
-
#
|
|
71
|
-
# @param id [String]
|
|
72
|
-
# @return [true]
|
|
60
|
+
# +restic key remove+: removes the given key +id+ from this repository.
|
|
61
|
+
# Restic refuses to remove the key currently being used to access it.
|
|
73
62
|
def key_remove(id:)
|
|
74
63
|
run_restic(build_argv("key", "remove", id))
|
|
75
64
|
true
|
|
@@ -105,29 +94,25 @@ module Yobi
|
|
|
105
94
|
end
|
|
106
95
|
end
|
|
107
96
|
|
|
108
|
-
# One key (password) associated with the repository, from
|
|
97
|
+
# One key (password) associated with the repository, from Yobi::Repository#key_list.
|
|
109
98
|
class Key < Yobi::FancyHash
|
|
110
|
-
# @return [String]
|
|
111
99
|
def id
|
|
112
100
|
self["id"]
|
|
113
101
|
end
|
|
114
102
|
|
|
115
|
-
# @return [String]
|
|
116
103
|
def user_name
|
|
117
104
|
self["userName"]
|
|
118
105
|
end
|
|
119
106
|
|
|
120
|
-
# @return [String]
|
|
121
107
|
def host_name
|
|
122
108
|
self["hostName"]
|
|
123
109
|
end
|
|
124
110
|
|
|
125
|
-
#
|
|
111
|
+
# Whether this is the key currently in use.
|
|
126
112
|
def current?
|
|
127
113
|
self["current"]
|
|
128
114
|
end
|
|
129
115
|
|
|
130
|
-
# @return [Time]
|
|
131
116
|
def created
|
|
132
117
|
@created ||= Time.parse(self["created"])
|
|
133
118
|
end
|
data/lib/yobi/repository/list.rb
CHANGED
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# @return [Array<String>]
|
|
5
|
+
# +restic list+: every object ID of the given +type+ in this repository.
|
|
6
|
+
# +type+ is +:blobs+, +:packs+, +:index+, +:snapshots+, +:keys+, or
|
|
7
|
+
# +:locks+. Restic ignores +--json+ for this command; each line is a
|
|
8
|
+
# bare ID, except for +:blobs+, where each line is +"data <id>"+ or
|
|
9
|
+
# +"tree <id>"+.
|
|
11
10
|
def list(type)
|
|
12
11
|
argv = build_argv("list", type)
|
|
13
12
|
execution = run_restic(argv)
|
data/lib/yobi/repository/ls.rb
CHANGED
|
@@ -4,19 +4,18 @@ require "time"
|
|
|
4
4
|
|
|
5
5
|
module Yobi
|
|
6
6
|
class Repository
|
|
7
|
-
#
|
|
7
|
+
# +restic ls+: lists a snapshot's files/directories.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# @return [Yobi::LsOutcome]
|
|
9
|
+
# +snapshot_id:+ is a snapshot ID, or +"latest"+. +dirs:+ restricts the
|
|
10
|
+
# listing to specific directories within the snapshot.
|
|
11
|
+
#
|
|
12
|
+
# +hosts:+, +paths:+ and +tags:+ each accept a single value or an Array
|
|
13
|
+
# and are only relevant when +snapshot_id+ is +"latest"+.
|
|
14
|
+
#
|
|
15
|
+
# +recursive:+ recurses into subdirectories. +human_readable:+, +long:+,
|
|
16
|
+
# +reverse:+ and +sort:+ (e.g. +"size"+) toggle formatting/sort options.
|
|
17
|
+
#
|
|
18
|
+
# Returns a Yobi::LsOutcome.
|
|
20
19
|
def ls(snapshot_id:, dirs: [], hosts: [], human_readable: false, long: false, paths: [],
|
|
21
20
|
recursive: false, reverse: false, sort: nil, tags: [])
|
|
22
21
|
argv = build_argv("ls", snapshot_id) do |a|
|
|
@@ -36,16 +35,7 @@ module Yobi
|
|
|
36
35
|
end
|
|
37
36
|
end
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
# in the matching typed class. Used both as the {Yobi::ResticOutput}
|
|
41
|
-
# `transform:` for {Yobi::Repository#ls} and by {Yobi::LsOutcome}'s own
|
|
42
|
-
# post-hoc accessors.
|
|
43
|
-
#
|
|
44
|
-
# @private
|
|
45
|
-
module LsMessageWrapper
|
|
46
|
-
# @param raw [Hash]
|
|
47
|
-
# @return [Yobi::Snapshot, Yobi::Node, Hash]
|
|
48
|
-
# the raw Hash itself for a message_type this version of Yobi doesn't recognize
|
|
38
|
+
module LsMessageWrapper # :nodoc:
|
|
49
39
|
def self.call(raw)
|
|
50
40
|
case raw["message_type"]
|
|
51
41
|
when "snapshot" then Snapshot.new(raw)
|
|
@@ -55,28 +45,26 @@ module Yobi
|
|
|
55
45
|
end
|
|
56
46
|
end
|
|
57
47
|
|
|
58
|
-
# The outcome of one
|
|
48
|
+
# The outcome of one Yobi::Repository#ls call.
|
|
59
49
|
class LsOutcome
|
|
60
|
-
#
|
|
50
|
+
# The Yobi::ResticOutput backing this outcome.
|
|
61
51
|
attr_reader :output
|
|
62
52
|
|
|
63
|
-
#
|
|
64
|
-
def initialize(execution)
|
|
53
|
+
def initialize(execution) # :nodoc:
|
|
65
54
|
@output = execution[:output]
|
|
66
55
|
end
|
|
67
56
|
|
|
68
|
-
#
|
|
57
|
+
# The resolved Yobi::Snapshot, useful when +snapshot_id:+ was +"latest"+.
|
|
69
58
|
def snapshot
|
|
70
59
|
@snapshot ||= output.messages("snapshot").first
|
|
71
60
|
end
|
|
72
61
|
|
|
73
|
-
#
|
|
62
|
+
# Every Yobi::Node from the run.
|
|
74
63
|
def nodes
|
|
75
64
|
@nodes ||= output.messages("node")
|
|
76
65
|
end
|
|
77
66
|
alias_method :entries, :nodes
|
|
78
67
|
|
|
79
|
-
# @return [void]
|
|
80
68
|
def pretty_print(q)
|
|
81
69
|
q.object_group(self) do
|
|
82
70
|
q.breakable
|
|
@@ -87,61 +75,50 @@ module Yobi
|
|
|
87
75
|
end
|
|
88
76
|
end
|
|
89
77
|
|
|
90
|
-
# One file/directory entry from a
|
|
91
|
-
#
|
|
92
|
-
#
|
|
78
|
+
# One file/directory entry from a Yobi::Repository#ls call. Restic's own
|
|
79
|
+
# term for this is a "node" - the same structure it uses internally for
|
|
80
|
+
# every entry in a snapshot's tree, not just ones +ls+ happens to show.
|
|
93
81
|
class Node < Yobi::FancyHash
|
|
94
|
-
# @return [String]
|
|
95
82
|
def name
|
|
96
83
|
self["name"]
|
|
97
84
|
end
|
|
98
85
|
|
|
99
|
-
# @return [String]
|
|
100
86
|
def type
|
|
101
87
|
self["type"]
|
|
102
88
|
end
|
|
103
89
|
|
|
104
|
-
# @return [String]
|
|
105
90
|
def path
|
|
106
91
|
self["path"]
|
|
107
92
|
end
|
|
108
93
|
|
|
109
|
-
# @return [Integer]
|
|
110
94
|
def size
|
|
111
95
|
self["size"]
|
|
112
96
|
end
|
|
113
97
|
|
|
114
|
-
# @return [String]
|
|
115
98
|
def permissions
|
|
116
99
|
self["permissions"]
|
|
117
100
|
end
|
|
118
101
|
|
|
119
|
-
# @return [Integer]
|
|
120
102
|
def uid
|
|
121
103
|
self["uid"]
|
|
122
104
|
end
|
|
123
105
|
|
|
124
|
-
# @return [Integer]
|
|
125
106
|
def gid
|
|
126
107
|
self["gid"]
|
|
127
108
|
end
|
|
128
109
|
|
|
129
|
-
# @return [Integer]
|
|
130
110
|
def inode
|
|
131
111
|
self["inode"]
|
|
132
112
|
end
|
|
133
113
|
|
|
134
|
-
# @return [Time]
|
|
135
114
|
def mtime
|
|
136
115
|
@mtime ||= Time.parse(self["mtime"])
|
|
137
116
|
end
|
|
138
117
|
|
|
139
|
-
# @return [Time]
|
|
140
118
|
def atime
|
|
141
119
|
@atime ||= Time.parse(self["atime"])
|
|
142
120
|
end
|
|
143
121
|
|
|
144
|
-
# @return [Time]
|
|
145
122
|
def ctime
|
|
146
123
|
@ctime ||= Time.parse(self["ctime"])
|
|
147
124
|
end
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
# repository, or applies the given ones. Restic ignores
|
|
5
|
+
# +restic migrate+: checks which migrations can be applied to this
|
|
6
|
+
# repository, or applies the given ones. Restic ignores +--json+ for
|
|
7
7
|
# this command, so there's no programmatic way to discover which
|
|
8
|
-
# migrations are available
|
|
8
|
+
# migrations are available - run +restic migrate+ directly for that.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# +names:+ names the migrations to apply, or only lists available ones
|
|
11
|
+
# if empty. +force: true+ reapplies a migration already marked as
|
|
12
|
+
# applied.
|
|
13
13
|
def migrate(names: [], force: false)
|
|
14
14
|
argv = build_argv("migrate", names) do |a|
|
|
15
15
|
a.flag(:force) if force
|
|
@@ -4,27 +4,25 @@ require "open3"
|
|
|
4
4
|
|
|
5
5
|
module Yobi
|
|
6
6
|
class Repository
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# +restic mount+: serves this repository as a read-only FUSE filesystem
|
|
8
|
+
# at +mountpoint:+, which must already exist.
|
|
9
9
|
#
|
|
10
|
-
# Without a block, returns a
|
|
11
|
-
# itself ready; call
|
|
12
|
-
#
|
|
10
|
+
# Without a block, returns a Yobi::MountHandle once Restic reports
|
|
11
|
+
# itself ready; call +#stop+ yourself once done. With one, yields the
|
|
12
|
+
# MountHandle and stops it automatically once the block returns or
|
|
13
|
+
# raises, returning the block's own value.
|
|
13
14
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# @return [Yobi::MountHandle] if no block is given
|
|
26
|
-
# @return [Object] the block's own return value, otherwise
|
|
27
|
-
# @raise [Yobi::MountTimeout]
|
|
15
|
+
# +hosts:+, +paths:+, and +tags:+ each accept a single value or an
|
|
16
|
+
# Array to restrict which snapshots appear under +snapshots/+.
|
|
17
|
+
#
|
|
18
|
+
# +allow_other:+ allows other users to access the mount.
|
|
19
|
+
# +no_default_permissions:+ skips file permission checks.
|
|
20
|
+
# +owner_root:+ mounts files as owned by root. +path_templates:+ and
|
|
21
|
+
# +time_template:+ control the directory naming schemes under
|
|
22
|
+
# +snapshots/+.
|
|
23
|
+
#
|
|
24
|
+
# +ready_timeout:+ is the number of seconds to wait for Restic's
|
|
25
|
+
# readiness message before raising Yobi::MountTimeout.
|
|
28
26
|
def mount(mountpoint:, hosts: [], paths: [], tags: [], allow_other: false,
|
|
29
27
|
no_default_permissions: false, owner_root: false, path_templates: [], time_template: nil,
|
|
30
28
|
ready_timeout: 10)
|
|
@@ -58,20 +56,12 @@ module Yobi
|
|
|
58
56
|
end
|
|
59
57
|
|
|
60
58
|
class Restic
|
|
61
|
-
#
|
|
62
|
-
READY_LINE = "Now serving the repository at"
|
|
59
|
+
READY_LINE = "Now serving the repository at" # :nodoc:
|
|
63
60
|
|
|
64
|
-
# For
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
# @param argv [Array<String>]
|
|
69
|
-
# @param mountpoint [String]
|
|
70
|
-
# @param extra_env [Hash{String => String}]
|
|
71
|
-
# @param ready_timeout [Numeric]
|
|
72
|
-
# @return [Yobi::MountHandle]
|
|
73
|
-
# @raise [Yobi::MountTimeout]
|
|
74
|
-
def run_mount(argv, mountpoint:, extra_env: {}, ready_timeout: 10)
|
|
61
|
+
# For Yobi::Repository#mount. Spawns Restic and waits for its readiness
|
|
62
|
+
# line (or +ready_timeout:+ seconds, or Restic exiting on its own)
|
|
63
|
+
# before returning a Yobi::MountHandle. Raises Yobi::MountTimeout.
|
|
64
|
+
def run_mount(argv, mountpoint:, extra_env: {}, ready_timeout: 10) # :nodoc:
|
|
75
65
|
ensure_minimum_version!
|
|
76
66
|
output = Yobi::ResticOutput.new
|
|
77
67
|
|
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
5
|
+
# +restic prune+: removes data no longer referenced by any snapshot.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
7
|
+
# +dry_run:+ reports what would happen without doing it. +max_unused:+
|
|
8
|
+
# targets a maximum unused space after pruning (e.g. +"10%"+).
|
|
9
|
+
# +max_repack_size:+ caps the amount of data repacked in one run.
|
|
10
|
+
#
|
|
11
|
+
# +repack_cacheable_only:+ only repacks packs cached locally.
|
|
12
|
+
# +repack_smaller_than:+ also repacks packs smaller than a given size.
|
|
13
|
+
# +repack_uncompressed:+ repacks packs not yet using compression.
|
|
14
|
+
#
|
|
15
|
+
# +unsafe_recover_no_free_space:+ proceeds even without enough free
|
|
16
|
+
# space, at the given risk acknowledgement string.
|
|
15
17
|
def prune(dry_run: false, max_repack_size: nil, max_unused: nil, repack_cacheable_only: false,
|
|
16
18
|
repack_smaller_than: nil, repack_uncompressed: false, unsafe_recover_no_free_space: nil)
|
|
17
19
|
argv = build_argv("prune") do |a|
|
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
5
|
+
# +restic recover+: builds a new snapshot from any data found in this
|
|
6
6
|
# repository that isn't referenced by an existing snapshot (e.g. after
|
|
7
|
-
# an accidental
|
|
7
|
+
# an accidental #forget). Call #snapshots afterward to find the
|
|
8
8
|
# recovered snapshot, if any was created.
|
|
9
|
-
#
|
|
10
|
-
# @return [true]
|
|
11
9
|
def recover
|
|
12
10
|
run_restic(build_argv("recover"))
|
|
13
11
|
true
|