yobi 0.3.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +11 -0
- data/README.md +42 -45
- data/lib/yobi/argv_builder.rb +1 -35
- data/lib/yobi/errors.rb +18 -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 +77 -24
- data/lib/yobi/restic.rb +73 -110
- data/lib/yobi/restic_output.rb +2 -74
- data/lib/yobi/snapshot.rb +15 -27
- data/lib/yobi/version.rb +1 -2
- data/sig/yobi.rbs +4 -4
- metadata +3 -2
data/lib/yobi/repository/copy.rb
CHANGED
|
@@ -2,20 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# +restic copy+: replicates snapshots from another repository into this
|
|
6
|
+
# one. Already-copied snapshots are skipped automatically.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# @param tags [Array<String>, String] filter by tag(s)
|
|
18
|
-
# @return [true]
|
|
8
|
+
# +from_repo:+ is the source repository's URL, a Yobi::Repository (its
|
|
9
|
+
# own #url/#password are used automatically), or a +[:file, "..."]+
|
|
10
|
+
# tuple reading the URL from a file. +from_password:+ takes the same
|
|
11
|
+
# shape as #initialize's +password:+, and defaults to the source
|
|
12
|
+
# Repository's own password when +from_repo:+ is one.
|
|
13
|
+
#
|
|
14
|
+
# +snapshot_ids:+ names the snapshots to copy; all of them if empty.
|
|
15
|
+
# +hosts:+, +paths:+, and +tags:+ each accept a single value or an
|
|
16
|
+
# Array to filter by.
|
|
19
17
|
def copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, hosts: [], paths: [], tags: [])
|
|
20
18
|
from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo)
|
|
21
19
|
from_password = from_repo_password(from_repo) if from_password.nil?
|
data/lib/yobi/repository/diff.rb
CHANGED
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Yobi
|
|
4
4
|
class Repository
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# @param to [String] snapshot ID
|
|
9
|
-
# @param metadata [Boolean] also report metadata-only changes (access mode, timestamps, ...)
|
|
10
|
-
# @return [Yobi::DiffOutcome]
|
|
5
|
+
# +restic diff+: compares two snapshots, +from:+ and +to:+ (both
|
|
6
|
+
# snapshot IDs). +metadata: true+ also reports metadata-only changes
|
|
7
|
+
# (access mode, timestamps, ...). Returns a Yobi::DiffOutcome.
|
|
11
8
|
def diff(from:, to:, metadata: false)
|
|
12
9
|
argv = build_argv("diff", from, to) do |a|
|
|
13
10
|
a.flag(:metadata) if metadata
|
|
@@ -18,16 +15,7 @@ module Yobi
|
|
|
18
15
|
end
|
|
19
16
|
end
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
# it in the matching typed class. Used both as the {Yobi::ResticOutput}
|
|
23
|
-
# `transform:` for {Yobi::Repository#diff} and by {Yobi::DiffOutcome}'s
|
|
24
|
-
# own post-hoc accessors.
|
|
25
|
-
#
|
|
26
|
-
# @private
|
|
27
|
-
module DiffMessageWrapper
|
|
28
|
-
# @param raw [Hash]
|
|
29
|
-
# @return [Yobi::DiffChange, Yobi::DiffStatistics, Hash]
|
|
30
|
-
# the raw Hash itself for a message_type this version of Yobi doesn't recognize
|
|
18
|
+
module DiffMessageWrapper # :nodoc:
|
|
31
19
|
def self.call(raw)
|
|
32
20
|
case raw["message_type"]
|
|
33
21
|
when "change" then DiffChange.new(raw)
|
|
@@ -37,28 +25,26 @@ module Yobi
|
|
|
37
25
|
end
|
|
38
26
|
end
|
|
39
27
|
|
|
40
|
-
# The outcome of one
|
|
28
|
+
# The outcome of one Yobi::Repository#diff call.
|
|
41
29
|
class DiffOutcome
|
|
42
|
-
#
|
|
30
|
+
# The Yobi::ResticOutput backing this outcome.
|
|
43
31
|
attr_reader :output
|
|
44
32
|
|
|
45
|
-
#
|
|
46
|
-
def initialize(execution)
|
|
33
|
+
def initialize(execution) # :nodoc:
|
|
47
34
|
@output = execution[:output]
|
|
48
35
|
end
|
|
49
36
|
|
|
50
|
-
#
|
|
37
|
+
# The Yobi::DiffStatistics of Restic's own +"statistics"+ fields.
|
|
51
38
|
def statistics
|
|
52
39
|
@statistics ||= output.messages("statistics").first || DiffStatistics.new({})
|
|
53
40
|
end
|
|
54
41
|
alias_method :report, :statistics
|
|
55
42
|
|
|
56
|
-
#
|
|
43
|
+
# Every Yobi::DiffChange from the run.
|
|
57
44
|
def changes
|
|
58
45
|
@changes ||= output.messages("change")
|
|
59
46
|
end
|
|
60
47
|
|
|
61
|
-
# @return [void]
|
|
62
48
|
def pretty_print(q)
|
|
63
49
|
q.object_group(self) do
|
|
64
50
|
q.breakable
|
|
@@ -67,109 +53,92 @@ module Yobi
|
|
|
67
53
|
end
|
|
68
54
|
end
|
|
69
55
|
|
|
70
|
-
# One
|
|
71
|
-
# concatenation of single-character codes:
|
|
72
|
-
#
|
|
73
|
-
#
|
|
56
|
+
# One +"change"+ message from a diff run. #modifier is Restic's own
|
|
57
|
+
# concatenation of single-character codes: +"+"+ added, +"-"+ removed,
|
|
58
|
+
# +"U"+ metadata updated, +"M"+ content modified, +"T"+ type changed,
|
|
59
|
+
# +"?"+ bitrot detected.
|
|
74
60
|
class DiffChange < Yobi::FancyHash
|
|
75
|
-
# @return [String]
|
|
76
61
|
def path
|
|
77
62
|
self["path"]
|
|
78
63
|
end
|
|
79
64
|
|
|
80
|
-
# @return [String]
|
|
81
65
|
def modifier
|
|
82
66
|
self["modifier"]
|
|
83
67
|
end
|
|
84
68
|
|
|
85
|
-
# @return [Boolean]
|
|
86
69
|
def added?
|
|
87
70
|
modifier.include?("+")
|
|
88
71
|
end
|
|
89
72
|
|
|
90
|
-
# @return [Boolean]
|
|
91
73
|
def removed?
|
|
92
74
|
modifier.include?("-")
|
|
93
75
|
end
|
|
94
76
|
|
|
95
|
-
# @return [Boolean]
|
|
96
77
|
def type_changed?
|
|
97
78
|
modifier.include?("T")
|
|
98
79
|
end
|
|
99
80
|
|
|
100
|
-
# @return [Boolean]
|
|
101
81
|
def modified?
|
|
102
82
|
modifier.include?("M")
|
|
103
83
|
end
|
|
104
84
|
|
|
105
|
-
# @return [Boolean]
|
|
106
85
|
def metadata_updated?
|
|
107
86
|
modifier.include?("U")
|
|
108
87
|
end
|
|
109
88
|
|
|
110
|
-
# @return [Boolean]
|
|
111
89
|
def bitrot?
|
|
112
90
|
modifier.include?("?")
|
|
113
91
|
end
|
|
114
92
|
end
|
|
115
93
|
|
|
116
|
-
# The
|
|
117
|
-
# command finishes. Dispatched to
|
|
94
|
+
# The +"statistics"+ message from a diff run, the final result once the
|
|
95
|
+
# command finishes. Dispatched to Yobi::DiffOutcome#statistics.
|
|
118
96
|
class DiffStatistics < Yobi::FancyHash
|
|
119
|
-
# @return [String, nil]
|
|
120
97
|
def source_snapshot
|
|
121
98
|
self["source_snapshot"]
|
|
122
99
|
end
|
|
123
100
|
|
|
124
|
-
# @return [String, nil]
|
|
125
101
|
def target_snapshot
|
|
126
102
|
self["target_snapshot"]
|
|
127
103
|
end
|
|
128
104
|
|
|
129
|
-
# @return [Integer]
|
|
130
105
|
def changed_files
|
|
131
106
|
self["changed_files"] || 0
|
|
132
107
|
end
|
|
133
108
|
|
|
134
|
-
#
|
|
109
|
+
# A Yobi::DiffStat of what was added.
|
|
135
110
|
def added
|
|
136
111
|
@added ||= DiffStat.new(self["added"] || {})
|
|
137
112
|
end
|
|
138
113
|
|
|
139
|
-
#
|
|
114
|
+
# A Yobi::DiffStat of what was removed.
|
|
140
115
|
def removed
|
|
141
116
|
@removed ||= DiffStat.new(self["removed"] || {})
|
|
142
117
|
end
|
|
143
118
|
end
|
|
144
119
|
|
|
145
|
-
# The
|
|
120
|
+
# The +"added"+ / +"removed"+ sub-object of a Yobi::DiffStatistics message.
|
|
146
121
|
class DiffStat < Yobi::FancyHash
|
|
147
|
-
# @return [Integer]
|
|
148
122
|
def files
|
|
149
123
|
self["files"] || 0
|
|
150
124
|
end
|
|
151
125
|
|
|
152
|
-
# @return [Integer]
|
|
153
126
|
def dirs
|
|
154
127
|
self["dirs"] || 0
|
|
155
128
|
end
|
|
156
129
|
|
|
157
|
-
# @return [Integer]
|
|
158
130
|
def others
|
|
159
131
|
self["others"] || 0
|
|
160
132
|
end
|
|
161
133
|
|
|
162
|
-
# @return [Integer]
|
|
163
134
|
def data_blobs
|
|
164
135
|
self["data_blobs"] || 0
|
|
165
136
|
end
|
|
166
137
|
|
|
167
|
-
# @return [Integer]
|
|
168
138
|
def tree_blobs
|
|
169
139
|
self["tree_blobs"] || 0
|
|
170
140
|
end
|
|
171
141
|
|
|
172
|
-
# @return [Integer]
|
|
173
142
|
def bytes
|
|
174
143
|
self["bytes"] || 0
|
|
175
144
|
end
|
data/lib/yobi/repository/dump.rb
CHANGED
|
@@ -4,24 +4,23 @@ require "tempfile"
|
|
|
4
4
|
|
|
5
5
|
module Yobi
|
|
6
6
|
class Repository
|
|
7
|
-
#
|
|
8
|
-
ARCHIVE_TYPES = Set.new(%w[tar zip].flat_map { [_1, _1.to_sym] }).freeze
|
|
7
|
+
ARCHIVE_TYPES = Set.new(%w[tar zip].flat_map { [_1, _1.to_sym] }).freeze # :nodoc:
|
|
9
8
|
|
|
10
|
-
#
|
|
9
|
+
# +restic dump+: extracts a file/folder from a snapshot. A single file's
|
|
11
10
|
# raw bytes are written as-is; a folder is written as a tar/zip archive.
|
|
12
|
-
# Give at most one of
|
|
11
|
+
# Give at most one of +target:+ or a block.
|
|
13
12
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
13
|
+
# +snapshot_id:+ is a snapshot ID, or Restic's own
|
|
14
|
+
# +"snapshotID:subfolder"+ form. +file:+ is the path within the snapshot
|
|
15
|
+
# to extract; +"/"+ dumps the whole snapshot. +archive:+ is +"tar"+
|
|
16
|
+
# (default) or +"zip"+, and only applies when +file+ is a folder.
|
|
17
|
+
#
|
|
18
|
+
# +hosts:+, +paths:+ and +tags:+ are only relevant when +snapshot_id+
|
|
19
|
+
# is +"latest"+.
|
|
20
|
+
#
|
|
21
|
+
# With +target:+, writes straight to that file path and returns +true+.
|
|
22
|
+
# Without a block, returns a Yobi::IOHandle. Raises ArgumentError if
|
|
23
|
+
# given both +target:+ and a block.
|
|
25
24
|
def dump(snapshot_id:, file:, target: nil, archive: nil, hosts: [], paths: [], tags: [], &block)
|
|
26
25
|
raise ArgumentError, "give at most one of target: or a block, not both" if target && block
|
|
27
26
|
|
data/lib/yobi/repository/find.rb
CHANGED
|
@@ -4,25 +4,20 @@ require "time"
|
|
|
4
4
|
|
|
5
5
|
module Yobi
|
|
6
6
|
class Repository
|
|
7
|
-
#
|
|
7
|
+
# +restic find+: searches for files/directories across snapshots by
|
|
8
8
|
# name pattern.
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# @param reverse [Boolean] reverse sort order
|
|
22
|
-
# @param show_pack_id [Boolean] include the pack ID each match is stored in
|
|
23
|
-
# @param snapshot_ids [Array<String>, String] restrict to these snapshot ID(s)
|
|
24
|
-
# @param tags [Array<String>, String] filter by tag(s)
|
|
25
|
-
# @return [Array<Yobi::MatchesPerSnapshot>]
|
|
10
|
+
# +patterns:+ accepts a single glob or an Array. +blob:+, +pack:+ and
|
|
11
|
+
# +tree:+ match blob/pack/tree IDs instead of file names.
|
|
12
|
+
#
|
|
13
|
+
# +hosts:+, +paths:+, +snapshot_ids:+, and +tags:+ each accept a single
|
|
14
|
+
# value or an Array to filter by. +newest:+ / +oldest:+ restrict the
|
|
15
|
+
# snapshot window.
|
|
16
|
+
#
|
|
17
|
+
# +ignore_case:+, +long:+, +human_readable:+, +reverse:+ and
|
|
18
|
+
# +show_pack_id:+ toggle formatting/matching options.
|
|
19
|
+
#
|
|
20
|
+
# Returns an Array of Yobi::MatchesPerSnapshot.
|
|
26
21
|
def find(patterns:, blob: false, pack: false, tree: false, hosts: [], human_readable: false,
|
|
27
22
|
ignore_case: false, long: false, newest: nil, oldest: nil, paths: [], reverse: false,
|
|
28
23
|
show_pack_id: false, snapshot_ids: [], tags: [])
|
|
@@ -48,88 +43,73 @@ module Yobi
|
|
|
48
43
|
end
|
|
49
44
|
end
|
|
50
45
|
|
|
51
|
-
# One matched file/directory from a
|
|
46
|
+
# One matched file/directory from a Yobi::Repository#find call.
|
|
52
47
|
class FindMatch < Yobi::FancyHash
|
|
53
|
-
# @return [String]
|
|
54
48
|
def path
|
|
55
49
|
self["path"]
|
|
56
50
|
end
|
|
57
51
|
|
|
58
|
-
# @return [String]
|
|
59
52
|
def type
|
|
60
53
|
self["type"]
|
|
61
54
|
end
|
|
62
55
|
|
|
63
|
-
# @return [Integer]
|
|
64
56
|
def size
|
|
65
57
|
self["size"]
|
|
66
58
|
end
|
|
67
59
|
|
|
68
|
-
# @return [String]
|
|
69
60
|
def permissions
|
|
70
61
|
self["permissions"]
|
|
71
62
|
end
|
|
72
63
|
|
|
73
|
-
# @return [Integer]
|
|
74
64
|
def uid
|
|
75
65
|
self["uid"]
|
|
76
66
|
end
|
|
77
67
|
|
|
78
|
-
# @return [Integer]
|
|
79
68
|
def gid
|
|
80
69
|
self["gid"]
|
|
81
70
|
end
|
|
82
71
|
|
|
83
|
-
# @return [String]
|
|
84
72
|
def user
|
|
85
73
|
self["user"]
|
|
86
74
|
end
|
|
87
75
|
|
|
88
|
-
# @return [String]
|
|
89
76
|
def group
|
|
90
77
|
self["group"]
|
|
91
78
|
end
|
|
92
79
|
|
|
93
|
-
# @return [Integer]
|
|
94
80
|
def inode
|
|
95
81
|
self["inode"]
|
|
96
82
|
end
|
|
97
83
|
|
|
98
|
-
# @return [Time]
|
|
99
84
|
def mtime
|
|
100
85
|
@mtime ||= Time.parse(self["mtime"])
|
|
101
86
|
end
|
|
102
87
|
|
|
103
|
-
# @return [Time]
|
|
104
88
|
def atime
|
|
105
89
|
@atime ||= Time.parse(self["atime"])
|
|
106
90
|
end
|
|
107
91
|
|
|
108
|
-
# @return [Time]
|
|
109
92
|
def ctime
|
|
110
93
|
@ctime ||= Time.parse(self["ctime"])
|
|
111
94
|
end
|
|
112
95
|
end
|
|
113
96
|
|
|
114
|
-
# One snapshot's worth of matches from a
|
|
97
|
+
# One snapshot's worth of matches from a Yobi::Repository#find call.
|
|
115
98
|
class MatchesPerSnapshot < Yobi::FancyHash
|
|
116
|
-
# Restic's own
|
|
99
|
+
# Restic's own +find --json+ output only gives the snapshot's ID here,
|
|
117
100
|
# not a full record - unlike every other place in this API where
|
|
118
|
-
# "snapshot" means a
|
|
101
|
+
# "snapshot" means a Yobi::Snapshot. Call Yobi::Repository#snapshots
|
|
119
102
|
# yourself if you need the full record.
|
|
120
|
-
#
|
|
121
|
-
# @return [String]
|
|
122
103
|
def snapshot
|
|
123
104
|
self["snapshot"]
|
|
124
105
|
end
|
|
125
106
|
alias_method :snapshot_id, :snapshot
|
|
126
107
|
|
|
127
|
-
# @return [Integer]
|
|
128
108
|
def hits
|
|
129
109
|
self["hits"]
|
|
130
110
|
end
|
|
131
111
|
|
|
132
|
-
#
|
|
112
|
+
# An Array of Yobi::FindMatch objects for this snapshot.
|
|
133
113
|
def matches
|
|
134
114
|
@matches ||= (self["matches"] || []).map { |raw| Yobi::FindMatch.new(raw) }
|
|
135
115
|
end
|
|
@@ -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
|