Class: Yobi::Repository
- Inherits:
-
Object
- Object
- Yobi::Repository
- Defined in:
- lib/yobi/repository.rb,
lib/yobi/repository/ls.rb,
lib/yobi/repository/cat.rb,
lib/yobi/repository/key.rb,
lib/yobi/repository/tag.rb,
lib/yobi/repository/copy.rb,
lib/yobi/repository/diff.rb,
lib/yobi/repository/dump.rb,
lib/yobi/repository/find.rb,
lib/yobi/repository/init.rb,
lib/yobi/repository/list.rb,
lib/yobi/repository/check.rb,
lib/yobi/repository/mount.rb,
lib/yobi/repository/prune.rb,
lib/yobi/repository/stats.rb,
lib/yobi/repository/backup.rb,
lib/yobi/repository/forget.rb,
lib/yobi/repository/repair.rb,
lib/yobi/repository/unlock.rb,
lib/yobi/repository/migrate.rb,
lib/yobi/repository/recover.rb,
lib/yobi/repository/restore.rb,
lib/yobi/repository/rewrite.rb,
lib/yobi/repository/snapshots.rb,
sig/yobi.rbs
Overview
One Restic repository. Every Restic subcommand that operates on a repository is a method here.
Constant Summary collapse
- ARCHIVE_TYPES =
:nodoc:
Set.new(%w[tar zip].flat_map { [_1, _1.to_sym] }).freeze
- STATS_MODES =
:nodoc:
%w[restore-size files-by-contents blobs-per-file raw-data].each_with_object({}) do |value, hash| # :nodoc: hash[value] = value hash[value.tr("-", "_").to_sym] = value end.freeze
Instance Attribute Summary collapse
-
#backend_credentials ⇒ backend_credentials
The storage backend's own credentials, as given to #initialize.
-
#password ⇒ password
The repository's encryption password, as given to #initialize.
-
#url ⇒ String
The repository location.
Instance Method Summary collapse
-
#backup(source:, excludes: [], exclude_files: [], exclude_if_present: [], exclude_larger_than: nil, files_from: [], files_from_raw: [], files_from_verbatim: [], iexcludes: [], iexclude_files: [], tags: [], dry_run: false, exclude_caches: false, exclude_cloud_files: false, force: false, group_by: nil, host: nil, ignore_ctime: false, ignore_inode: false, no_scan: false, one_file_system: false, parent: nil, read_concurrency: nil, skip_if_unchanged: false, time: nil, verbose: false, with_atime: false, &block) ⇒ Object
restic backup: creates a new snapshot from a source path. -
#cat_blob(id) {|io| ... } ⇒ Object
restic cat blob ID: one data blob's raw, decrypted bytes. -
#cat_config ⇒ Hash[String, untyped]
(also: #config)
restic cat config: the repository's own config document, as a Hash. -
#cat_index(id) ⇒ Hash[String, untyped]
restic cat index ID: one index file's own raw contents, as a Hash. -
#cat_key(id) ⇒ Hash[String, untyped]
restic cat key ID: one key's own raw stored record, as a Hash. -
#cat_masterkey_and_game_over_if_this_leaks ⇒ Hash[String, untyped]
restic cat masterkey: this repository's own encryption/MAC key material. -
#cat_pack(id) {|io| ... } ⇒ Object
restic cat pack ID: one pack file's raw, still-encrypted bytes. -
#cat_snapshot(id) ⇒ Hash[String, untyped]
restic cat snapshot ID: one snapshot's own raw stored record, as a Hash. -
#cat_tree(snapshot_id, subfolder: nil) ⇒ Hash[String, untyped]
restic cat tree snapshot:subfolder: the raw tree object at a snapshot's root, or atsubfolderwithin it. -
#check(hosts: [], paths: [], read_data: false, read_data_subset: nil, tags: [], with_cache: false) ⇒ Object
restic check: tests the repository for errors. -
#copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, hosts: [], paths: [], tags: []) ⇒ Object
restic copy: replicates snapshots from another repository into this one. -
#diff(from:, to:, metadata: false) ⇒ DiffOutcome
restic diff: compares two snapshots,from:andto:(both snapshot IDs). -
#dump(snapshot_id:, file:, target: nil, archive: nil, hosts: [], paths: [], tags: [], &block) ⇒ Object
restic dump: extracts a file/folder from a snapshot. -
#env ⇒ Hash[String, String]
The full env Hash Restic will see for this repository:
RESTIC_REPOSITORYplus resolved password and backend credentials. -
#find(patterns:, blob: false, pack: false, tree: false, hosts: [], human_readable: false, ignore_case: false, long: false, newest: nil, oldest: nil, paths: [], reverse: false, show_pack_id: false, snapshot_ids: [], tags: []) ⇒ Object
restic find: searches for files/directories across snapshots by name pattern. -
#forget(keep_last: nil, keep_hourly: nil, keep_daily: nil, keep_weekly: nil, keep_monthly: nil, keep_yearly: nil, keep_within: nil, keep_within_hourly: nil, keep_within_daily: nil, keep_within_weekly: nil, keep_within_monthly: nil, keep_within_yearly: nil, keep_tags: [], hosts: [], tags: [], paths: [], compact: false, group_by: nil, dry_run: false, prune: false, unsafe_allow_remove_all: false, max_unused: nil, max_repack_size: nil, repack_cacheable_only: false, repack_uncompressed: false, repack_smaller_than: nil) ⇒ Object
restic forget: applies a retention policy, removing snapshots that don't match anykeep_*rule. -
#init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil, repository_version: nil) ⇒ Object
restic init: creates the repository at #url. -
#init_mirror(url:, password:, backend_credentials: {}, restic: @restic, repository_version: nil) ⇒ Object
Constructs a new Yobi::Repository at
url, already initialized with chunker parameters copied from this one (+#init(copy_chunker_params: true, from_repo: self)+), so a later #copy between the two can deduplicate. -
#initialize(url:, password:, backend_credentials: {}, restic: nil) ⇒ Repository
constructor
Builds a repository handle.
-
#inspect ⇒ Object
Redacts sensitive values so a stray +pp+/+puts+/log call never prints a credential in plaintext.
-
#key_add(new_password:, host: nil, user: nil) ⇒ true
(also: #add_key)
restic key add: creates a new key (password) for this repository. -
#key_list ⇒ Array[Key]
(also: #keys)
restic key list: every Yobi::Key associated with this repository. -
#key_passwd(new_password:, host: nil, user: nil) ⇒ true
(also: #change_password)
restic key passwd: creates a new key for this repository and removes the key currently in use. -
#key_remove(id:) ⇒ true
(also: #remove_key)
restic key remove: removes the given keyidfrom this repository. -
#list(type) ⇒ Array[String]
restic list: every object ID of the giventypein this repository. -
#ls(snapshot_id:, dirs: [], hosts: [], human_readable: false, long: false, paths: [], recursive: false, reverse: false, sort: nil, tags: []) ⇒ Object
restic ls: lists a snapshot's files/directories. -
#migrate(names: [], force: false) ⇒ true
restic migrate: checks which migrations can be applied to this repository, or applies the given ones. -
#mount(mountpoint:, hosts: [], paths: [], tags: [], allow_other: false, no_default_permissions: false, owner_root: false, path_templates: [], time_template: nil, ready_timeout: 10) ⇒ Object
restic mount: serves this repository as a read-only FUSE filesystem atmountpoint:, which must already exist. -
#prune(dry_run: false, max_repack_size: nil, max_unused: nil, repack_cacheable_only: false, repack_smaller_than: nil, repack_uncompressed: false, unsafe_recover_no_free_space: nil) ⇒ Object
restic prune: removes data no longer referenced by any snapshot. -
#recover ⇒ true
restic recover: builds a new snapshot from any data found in this repository that isn't referenced by an existing snapshot (e.g. after an accidental #forget). -
#repair_index(read_all_packs: false) ⇒ true
restic repair index: creates a new index based on the pack files present in this repository. -
#repair_packs(ids:) ⇒ true
restic repair packs: extracts intact blobs from the given packids:, rebuilds the index to drop the damaged packs, and removes them from the repository. -
#repair_snapshots(snapshot_ids: [], dry_run: false, forget: false, hosts: [], paths: [], tags: []) ⇒ Object
restic repair snapshots: scans the given snapshots and generates new ones with damaged directories/file contents removed. -
#restore(snapshot_id:, target:, excludes: [], exclude_files: [], exclude_xattrs: [], hosts: [], iexcludes: [], iexclude_files: [], iincludes: [], iinclude_files: [], includes: [], include_files: [], include_xattrs: [], paths: [], tags: [], delete: false, dry_run: false, overwrite: nil, ownership_by_name: false, sparse: false, verbose: false, verify: false, &block) ⇒ Object
restic restore: extracts a snapshot's contents to a target directory. -
#rewrite(snapshot_ids: [], hosts: [], tags: [], paths: [], excludes: [], exclude_files: [], iexcludes: [], iexclude_files: [], includes: [], include_files: [], iincludes: [], iinclude_files: [], dry_run: false, forget: false, new_host: nil, new_time: nil, snapshot_summary: false) ⇒ Object
restic rewrite: creates new snapshots from existing ones with exclude/include filters applied, or metadata changed. -
#snapshots(tags: [], hosts: [], paths: [], compact: false, group_by: nil, latest: nil) ⇒ Object
restic snapshots: lists snapshots, optionally filtered. -
#stats(snapshot_ids: [], hosts: [], mode: nil, paths: [], tags: []) ⇒ Object
restic stats: accumulates statistics about the repository's data. -
#tag(snapshot_ids: [], add: [], remove: [], set: [], tags: [], hosts: [], paths: []) ⇒ Object
restic tag: modifies tags on existing snapshots. -
#unlock(remove_all: false) ⇒ true
restic unlock: removes stale locks left by other Restic processes.
Constructor Details
#initialize(url:, password:, backend_credentials: {}, restic: nil) ⇒ Repository
Builds a repository handle. Does not touch the remote or spawn Restic.
url is a String repository location, e.g. "s3:s3.amazonaws.com/bucket".
password is the repository's encryption password. Accepts one of:
a literal String; a [:command, "..."] or [:file, "..."]
tuple, resolved natively by Restic itself; :insecure_no_password;
or any object responding to #call (invoked fresh immediately before every
Restic invocation).
backend_credentials is the storage backend's own env vars
(+AWS_+/+AZURE_+/etc.) as a Hash, or a callable returning such a Hash.
restic is a Yobi::Restic instance to share, a bare Restic binary path,
or nil to create a default one.
Raises ArgumentError when password: is nil, or when password:,
backend_credentials:, or restic: has an invalid shape.
52 53 54 55 56 57 58 59 60 |
# File 'lib/yobi/repository.rb', line 52 def initialize(url:, password:, backend_credentials: {}, restic: nil) validate_password_shape!(password) validate_backend_credentials_shape!(backend_credentials) @url, @extracted_rest_credentials = extract_rest_credentials(url) @password = password @backend_credentials = backend_credentials @restic = initialize_restic(restic) end |
Instance Attribute Details
#backend_credentials ⇒ backend_credentials
The storage backend's own credentials, as given to #initialize.
15 16 17 |
# File 'lib/yobi/repository.rb', line 15 def backend_credentials @backend_credentials end |
#password ⇒ password
The repository's encryption password, as given to #initialize.
13 14 15 |
# File 'lib/yobi/repository.rb', line 13 def password @password end |
#url ⇒ String
The repository location.
11 12 13 |
# File 'lib/yobi/repository.rb', line 11 def url @url end |
Instance Method Details
#backup(source:, excludes: [], exclude_files: [], exclude_if_present: [], exclude_larger_than: nil, files_from: [], files_from_raw: [], files_from_verbatim: [], iexcludes: [], iexclude_files: [], tags: [], dry_run: false, exclude_caches: false, exclude_cloud_files: false, force: false, group_by: nil, host: nil, ignore_ctime: false, ignore_inode: false, no_scan: false, one_file_system: false, parent: nil, read_concurrency: nil, skip_if_unchanged: false, time: nil, verbose: false, with_atime: false, &block) ⇒ Object
restic backup: creates a new snapshot from a source path.
source: is either a path to back up, or a
[:stdin_from_command, command] / [:stdin_from_command, command, filename]
tuple. Restic spawns and executes command itself (a String tokenized
with Shellwords.split, or an Array of already-discrete arguments),
capturing its stdout as the backup content.
excludes:, exclude_files:, exclude_if_present:, iexcludes: and
iexclude_files: each accept a single value or an Array. +i+-prefixed
variants are case-insensitive.
exclude_larger_than: skips files larger than a given size (e.g.
"1G"). exclude_caches: skips directories containing a
CACHEDIR.TAG marker. exclude_cloud_files: skips files not fully
present on disk (e.g. OneDrive placeholders).
files_from:, files_from_raw: and files_from_verbatim: read the
files/dirs to back up from a file, one per line - _raw NUL-separated,
_verbatim taken literally with no glob expansion.
tags: attaches tags to the new snapshot. host: overrides the OS
hostname recorded on it. time: overrides its creation timestamp.
parent: pins the parent snapshot ID; group_by: (e.g. "host,paths")
picks the grouping used to find the parent otherwise.
dry_run: reports what would happen without doing it. force: backs
up unchanged files instead of skipping them. skip_if_unchanged:
doesn't create a snapshot if nothing changed. ignore_ctime: and
ignore_inode: relax change detection. with_atime: also stores
files' access times.
one_file_system: doesn't cross filesystem boundaries. no_scan:
skips the pre-backup scan (which disables percentage progress).
read_concurrency: sets the number of concurrent file reads.
When verbose: true, a Yobi::BackupVerboseStatus is streamed to the
block per file. The block also receives Yobi::BackupStatus,
Yobi::BackupError, and Yobi::BackupSummary messages as they arrive.
Returns a Yobi::BackupOutcome.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/yobi/repository/backup.rb', line 48 def backup(source:, excludes: [], exclude_files: [], exclude_if_present: [], exclude_larger_than: nil, files_from: [], files_from_raw: [], files_from_verbatim: [], iexcludes: [], iexclude_files: [], tags: [], dry_run: false, exclude_caches: false, exclude_cloud_files: false, force: false, group_by: nil, host: nil, ignore_ctime: false, ignore_inode: false, no_scan: false, one_file_system: false, parent: nil, read_concurrency: nil, skip_if_unchanged: false, time: nil, verbose: false, with_atime: false, &block) argv = build_argv("backup") do |a| a.repeat_flag(:exclude, excludes) a.repeat_flag(:exclude_file, exclude_files) a.repeat_flag(:exclude_if_present, exclude_if_present) a.flag(:exclude_larger_than, exclude_larger_than) unless exclude_larger_than.nil? a.repeat_flag(:files_from, files_from) a.repeat_flag(:files_from_raw, files_from_raw) a.repeat_flag(:files_from_verbatim, files_from_verbatim) a.repeat_flag(:iexclude, iexcludes) a.repeat_flag(:iexclude_file, iexclude_files) a.repeat_flag(:tag, ) a.flag(:dry_run) if dry_run a.flag(:exclude_caches) if exclude_caches a.flag(:exclude_cloud_files) if exclude_cloud_files a.flag(:force) if force a.flag(:group_by, group_by) unless group_by.nil? a.flag(:host, host) unless host.nil? a.flag(:ignore_ctime) if ignore_ctime a.flag(:ignore_inode) if ignore_inode a.flag(:no_scan) if no_scan a.flag(:one_file_system) if one_file_system a.flag(:parent, parent) unless parent.nil? a.flag(:read_concurrency, read_concurrency) unless read_concurrency.nil? a.flag(:skip_if_unchanged) if skip_if_unchanged a.flag(:time, time) unless time.nil? a.short_flag(:v) if verbose a.flag(:with_atime) if with_atime case source in String => path a..append(path) in [:stdin_from_command, command] a.flag(:stdin_from_command) a..append(tokenize(command)) in [:stdin_from_command, command, String => filename] a.flag(:stdin_from_command) a.flag(:stdin_filename, filename) a..append(tokenize(command)) else raise ArgumentError, "invalid source shape" end end output = Yobi::ResticOutput.new(transform: Yobi::BackupMessageWrapper) execution = run_restic(argv, output: output, &block) BackupOutcome.new(execution) end |
#cat_blob(id) ⇒ Object #cat_blob(id) ⇒ IOHandle
restic cat blob ID: one data blob's raw, decrypted bytes. IDs come
from #cat_tree's own node "content" arrays. Same block/handle shape
as #dump.
61 62 63 |
# File 'lib/yobi/repository/cat.rb', line 61 def cat_blob(id, &block) run_restic_dump(build_argv("cat", "blob", id), &block) end |
#cat_config ⇒ Hash[String, untyped] Also known as: config
restic cat config: the repository's own config document, as a Hash.
Raises Yobi::RepositoryNotFound or Yobi::AuthenticationFailed.
7 8 9 10 |
# File 'lib/yobi/repository/cat.rb', line 7 def cat_config execution = run_restic(build_argv("cat", "config")) parse_json_output(execution) end |
#cat_index(id) ⇒ Hash[String, untyped]
restic cat index ID: one index file's own raw contents, as a Hash.
IDs come from list(:index).
23 24 25 26 |
# File 'lib/yobi/repository/cat.rb', line 23 def cat_index(id) execution = run_restic(build_argv("cat", "index", id)) parse_json_output(execution) end |
#cat_key(id) ⇒ Hash[String, untyped]
restic cat key ID: one key's own raw stored record, as a Hash. id
can be a String or a Yobi::Key.
30 31 32 33 34 |
# File 'lib/yobi/repository/cat.rb', line 30 def cat_key(id) id = id.id if id.is_a?(Yobi::Key) execution = run_restic(build_argv("cat", "key", id)) parse_json_output(execution) end |
#cat_masterkey_and_game_over_if_this_leaks ⇒ Hash[String, untyped]
restic cat masterkey: this repository's own encryption/MAC key
material. Extremely sensitive: this is the actual key, not a redacted
reference to it, and there is no operation that rotates it; every
other key/password management method here only manages different ways
to unlock this same master key.
70 71 72 73 |
# File 'lib/yobi/repository/cat.rb', line 70 def cat_masterkey_and_game_over_if_this_leaks execution = run_restic(build_argv("cat", "masterkey")) parse_json_output(execution) end |
#cat_pack(id) ⇒ Object #cat_pack(id) ⇒ IOHandle
restic cat pack ID: one pack file's raw, still-encrypted bytes. IDs
come from list(:packs). Same block/handle shape as #dump: without a
block, returns a Yobi::IOHandle.
54 55 56 |
# File 'lib/yobi/repository/cat.rb', line 54 def cat_pack(id, &block) run_restic_dump(build_argv("cat", "pack", id), &block) end |
#cat_snapshot(id) ⇒ Hash[String, untyped]
restic cat snapshot ID: one snapshot's own raw stored record, as a
Hash. id can be a String or a Yobi::Snapshot.
15 16 17 18 19 |
# File 'lib/yobi/repository/cat.rb', line 15 def cat_snapshot(id) id = id.id if id.is_a?(Yobi::Snapshot) execution = run_restic(build_argv("cat", "snapshot", id)) parse_json_output(execution) end |
#cat_tree(snapshot_id, subfolder: nil) ⇒ Hash[String, untyped]
restic cat tree snapshot:subfolder: the raw tree object at a
snapshot's root, or at subfolder within it. snapshot_id can be a
String, a Yobi::Snapshot, or Restic's own "snapshotID:subfolder"
form directly.
40 41 42 43 44 45 46 47 48 49 |
# File 'lib/yobi/repository/cat.rb', line 40 def cat_tree(snapshot_id, subfolder: nil) snapshot_id = snapshot_id.id if snapshot_id.is_a?(Yobi::Snapshot) target = if subfolder.nil? snapshot_id else "#{snapshot_id}:#{subfolder}" end execution = run_restic(build_argv("cat", "tree", target)) parse_json_output(execution) end |
#check(hosts: [], paths: [], read_data: false, read_data_subset: nil, tags: [], with_cache: false) ⇒ Object
restic check: tests the repository for errors.
hosts:, paths: and tags: each accept a single value or an Array
to filter by.
read_data: reads and verifies pack file contents, not just structure.
read_data_subset: narrows that to a subset of packs, e.g. "5%".
with_cache: uses the local cache.
Returns a Yobi::CheckOutcome.
15 16 17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/yobi/repository/check.rb', line 15 def check(hosts: [], paths: [], read_data: false, read_data_subset: nil, tags: [], with_cache: false) argv = build_argv("check") do |a| a.repeat_flag(:host, hosts) a.repeat_flag(:path, paths) a.flag(:read_data) if read_data a.flag(:read_data_subset, read_data_subset) unless read_data_subset.nil? a.repeat_flag(:tag, ) a.flag(:with_cache) if with_cache end output = Yobi::ResticOutput.new(transform: Yobi::CheckMessageWrapper) execution = run_restic(argv, output: output) CheckOutcome.new(execution) end |
#copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, hosts: [], paths: [], tags: []) ⇒ Object
restic copy: replicates snapshots from another repository into this
one. Already-copied snapshots are skipped automatically.
from_repo: is the source repository's URL, a Yobi::Repository (its
own #url/#password are used automatically), or a [:file, "..."]
tuple reading the URL from a file. from_password: takes the same
shape as #initialize's password:, and defaults to the source
Repository's own password when from_repo: is one.
snapshot_ids: names the snapshots to copy; all of them if empty.
hosts:, paths:, and tags: each accept a single value or an
Array to filter by.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/yobi/repository/copy.rb', line 17 def copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, hosts: [], paths: [], tags: []) from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo) from_password = from_repo_password(from_repo) if from_password.nil? validate_from_password_shape!(from_password) argv = build_argv("copy", snapshot_ids) do |a| a.flag(from_repo_flag, from_repo_value) a.flag(:from_insecure_no_password) if from_password == :insecure_no_password a.flag(:from_key_hint, from_key_hint) unless from_key_hint.nil? a.repeat_flag(:host, hosts) a.repeat_flag(:path, paths) a.repeat_flag(:tag, ) end run_restic(argv, extra_env: password_env(from_password, "RESTIC_FROM_PASSWORD")) true end |
#diff(from:, to:, metadata: false) ⇒ DiffOutcome
restic diff: compares two snapshots, from: and to: (both
snapshot IDs). metadata: true also reports metadata-only changes
(access mode, timestamps, ...). Returns a Yobi::DiffOutcome.
8 9 10 11 12 13 14 15 |
# File 'lib/yobi/repository/diff.rb', line 8 def diff(from:, to:, metadata: false) argv = build_argv("diff", from, to) do |a| a.flag(:metadata) if end output = Yobi::ResticOutput.new(transform: Yobi::DiffMessageWrapper) execution = run_restic(argv, output: output) DiffOutcome.new(execution) end |
#dump(snapshot_id:, file:, target: nil, archive: nil, hosts: [], paths: [], tags: [], &block) ⇒ Object
restic dump: extracts a file/folder from a snapshot. A single file's
raw bytes are written as-is; a folder is written as a tar/zip archive.
Give at most one of target: or a block.
snapshot_id: is a snapshot ID, or Restic's own
"snapshotID:subfolder" form. file: is the path within the snapshot
to extract; "/" dumps the whole snapshot. archive: is "tar"
(default) or "zip", and only applies when file is a folder.
hosts:, paths: and tags: are only relevant when snapshot_id
is "latest".
With target:, writes straight to that file path and returns true.
Without a block, returns a Yobi::IOHandle. Raises ArgumentError if
given both target: and a block.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/yobi/repository/dump.rb', line 24 def dump(snapshot_id:, file:, target: nil, archive: nil, hosts: [], paths: [], tags: [], &block) raise ArgumentError, "give at most one of target: or a block, not both" if target && block argv = build_argv("dump", snapshot_id) do |a| a.flag(:archive, archive) if ARCHIVE_TYPES.include?(archive) a.repeat_flag(:host, hosts) a.repeat_flag(:path, paths) a.repeat_flag(:tag, ) a.flag(:target, target) unless target.nil? a..append(file) end if target run_restic(argv) true else run_restic_dump(argv, &block) end end |
#env ⇒ Hash[String, String]
The full env Hash Restic will see for this repository: RESTIC_REPOSITORY
plus resolved password and backend credentials.
64 65 66 67 68 |
# File 'lib/yobi/repository.rb', line 64 def env {"RESTIC_REPOSITORY" => url} .merge(resolved_password) .merge(resolved_backend_credentials) end |
#find(patterns:, blob: false, pack: false, tree: false, hosts: [], human_readable: false, ignore_case: false, long: false, newest: nil, oldest: nil, paths: [], reverse: false, show_pack_id: false, snapshot_ids: [], tags: []) ⇒ Object
restic find: searches for files/directories across snapshots by
name pattern.
patterns: accepts a single glob or an Array. blob:, pack: and
tree: match blob/pack/tree IDs instead of file names.
hosts:, paths:, snapshot_ids:, and tags: each accept a single
value or an Array to filter by. newest: / oldest: restrict the
snapshot window.
ignore_case:, long:, human_readable:, reverse: and
show_pack_id: toggle formatting/matching options.
Returns an Array of Yobi::MatchesPerSnapshot.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/yobi/repository/find.rb', line 21 def find(patterns:, blob: false, pack: false, tree: false, hosts: [], human_readable: false, ignore_case: false, long: false, newest: nil, oldest: nil, paths: [], reverse: false, show_pack_id: false, snapshot_ids: [], tags: []) argv = build_argv("find") do |a| a.flag(:blob) if blob a.flag(:pack) if pack a.flag(:tree) if tree a.repeat_flag(:host, hosts) a.flag(:human_readable) if human_readable a.flag(:ignore_case) if ignore_case a.flag(:long) if long a.flag(:newest, newest) unless newest.nil? a.flag(:oldest, oldest) unless oldest.nil? a.repeat_flag(:path, paths) a.flag(:reverse) if reverse a.flag(:show_pack_id) if show_pack_id a.repeat_flag(:snapshot, snapshot_ids) a.repeat_flag(:tag, ) a..append(patterns) end execution = run_restic(argv) parse_json_output(execution).map { |raw| MatchesPerSnapshot.new(raw) } end |
#forget(keep_last: nil, keep_hourly: nil, keep_daily: nil, keep_weekly: nil, keep_monthly: nil, keep_yearly: nil, keep_within: nil, keep_within_hourly: nil, keep_within_daily: nil, keep_within_weekly: nil, keep_within_monthly: nil, keep_within_yearly: nil, keep_tags: [], hosts: [], tags: [], paths: [], compact: false, group_by: nil, dry_run: false, prune: false, unsafe_allow_remove_all: false, max_unused: nil, max_repack_size: nil, repack_cacheable_only: false, repack_uncompressed: false, repack_smaller_than: nil) ⇒ Object
restic forget: applies a retention policy, removing snapshots that
don't match any keep_* rule.
keep_last: always keeps that many of the most recent snapshots.
keep_hourly:, keep_daily:, keep_weekly:, keep_monthly:, and
keep_yearly: each keep that many of the most recent per period.
keep_within: keeps all snapshots within a given duration (e.g.
"30d"); the keep_within_hourly: ... keep_within_yearly: variants
apply the same duration within their respective periods.
keep_tags: always keeps snapshots carrying any of the listed tags.
hosts:, tags:, and paths: each accept a single value or an
Array to filter by.
compact: compacts the printed policy summary. group_by: (e.g.
"host,paths") picks the grouping used to apply the policy.
dry_run: reports what would happen without doing it.
unsafe_allow_remove_all: allows removing every snapshot.
prune: true also reclaims disk space. max_unused:, max_repack_size:,
repack_cacheable_only:, repack_uncompressed: and
repack_smaller_than: are passed through to that implied prune.
Returns an Array of Yobi::ForgetGroup.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/yobi/repository/forget.rb', line 29 def forget(keep_last: nil, keep_hourly: nil, keep_daily: nil, keep_weekly: nil, keep_monthly: nil, keep_yearly: nil, keep_within: nil, keep_within_hourly: nil, keep_within_daily: nil, keep_within_weekly: nil, keep_within_monthly: nil, keep_within_yearly: nil, keep_tags: [], hosts: [], tags: [], paths: [], compact: false, group_by: nil, dry_run: false, prune: false, unsafe_allow_remove_all: false, max_unused: nil, max_repack_size: nil, repack_cacheable_only: false, repack_uncompressed: false, repack_smaller_than: nil) argv = build_argv("forget") do |a| a.flag(:keep_last, keep_last) unless keep_last.nil? a.flag(:keep_hourly, keep_hourly) unless keep_hourly.nil? a.flag(:keep_daily, keep_daily) unless keep_daily.nil? a.flag(:keep_weekly, keep_weekly) unless keep_weekly.nil? a.flag(:keep_monthly, keep_monthly) unless keep_monthly.nil? a.flag(:keep_yearly, keep_yearly) unless keep_yearly.nil? a.flag(:keep_within, keep_within) unless keep_within.nil? a.flag(:keep_within_hourly, keep_within_hourly) unless keep_within_hourly.nil? a.flag(:keep_within_daily, keep_within_daily) unless keep_within_daily.nil? a.flag(:keep_within_weekly, keep_within_weekly) unless keep_within_weekly.nil? a.flag(:keep_within_monthly, keep_within_monthly) unless keep_within_monthly.nil? a.flag(:keep_within_yearly, keep_within_yearly) unless keep_within_yearly.nil? a.repeat_flag(:keep_tag, ) a.repeat_flag(:host, hosts) a.repeat_flag(:tag, ) a.repeat_flag(:path, paths) a.flag(:compact) if compact a.flag(:group_by, group_by) unless group_by.nil? a.flag(:dry_run) if dry_run a.flag(:prune) if prune a.flag(:unsafe_allow_remove_all) if unsafe_allow_remove_all a.flag(:max_unused, max_unused) unless max_unused.nil? a.flag(:max_repack_size, max_repack_size) unless max_repack_size.nil? a.flag(:repack_cacheable_only) if repack_cacheable_only a.flag(:repack_uncompressed) if repack_uncompressed a.flag(:repack_smaller_than, repack_smaller_than) unless repack_smaller_than.nil? end execution = run_restic(argv) # Permissive because `restic forget` can surround its JSON summary # with non-JSON lines (prune chatter on <= 0.18.1, or a # "Remove(...) failed" line on partial-removal). See # #parse_json_output_permissively for the full rundown. parse_json_output_permissively(execution).map { |raw| ForgetGroup.new(raw) } end |
#init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil, repository_version: nil) ⇒ Object
restic init: creates the repository at #url.
copy_chunker_params: true copies chunker parameters from from_repo:
(so a later #copy between the two can deduplicate). from_repo: is
the source repository's URL, a Yobi::Repository (its own #url/#password
are used automatically), or a [:file, "..."] tuple reading the URL
from a file. from_password: takes the same shape as #initialize's
password:, and defaults to the source Repository's own password
when from_repo: is one.
Returns a Yobi::Initialized.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/yobi/repository/init.rb', line 16 def init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil, repository_version: nil) from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo) unless from_repo.nil? if from_repo_value from_password = from_repo_password(from_repo) if from_password.nil? validate_from_password_shape!(from_password) end argv = build_argv("init") do |a| a.flag(:copy_chunker_params) if copy_chunker_params a.flag(:from_insecure_no_password) if from_password == :insecure_no_password a.flag(:from_key_hint, from_key_hint) unless from_key_hint.nil? a.flag(from_repo_flag, from_repo_value) if from_repo_flag a.flag(:repository_version, repository_version) unless repository_version.nil? end execution = run_restic(argv, extra_env: password_env(from_password, "RESTIC_FROM_PASSWORD")) Initialized.new(parse_json_output(execution)) end |
#init_mirror(url:, password:, backend_credentials: {}, restic: @restic, repository_version: nil) ⇒ Object
Constructs a new Yobi::Repository at url, already initialized with
chunker parameters copied from this one (+#init(copy_chunker_params:
true, from_repo: self)+), so a later #copy between the two can
deduplicate. A one-time setup call, not an ongoing sync: nothing keeps
the two repositories in sync afterward, and this doesn't mutate this
Repository instance or copy any snapshots itself.
url:, password:, and backend_credentials: take the same shapes
as #initialize. restic: defaults to this repository's own.
45 46 47 48 49 |
# File 'lib/yobi/repository/init.rb', line 45 def init_mirror(url:, password:, backend_credentials: {}, restic: @restic, repository_version: nil) Repository.new(url: url, password: password, backend_credentials: backend_credentials, restic: restic).tap do |mirror| mirror.init(copy_chunker_params: true, from_repo: self, repository_version: repository_version) end end |
#inspect ⇒ Object
Redacts sensitive values so a stray +pp+/+puts+/log call never prints a credential in plaintext.
72 73 74 |
# File 'lib/yobi/repository.rb', line 72 def inspect "#<#{self.class} url=#{url.inspect} password=#{redacted_password.inspect} backend_credentials=#{redacted_backend_credentials.inspect}>" end |
#key_add(new_password:, host: nil, user: nil) ⇒ true Also known as: add_key
restic key add: creates a new key (password) for this repository.
new_password: accepts the same shapes as #initialize's password:
minus [:command, ...]: a literal String; a [:file, "..."] tuple,
resolved natively by Restic itself; :insecure_no_password; or
anything responding to #call. A literal String or callable is
written to a briefly-lived, 0600-permissioned tempfile, since Restic
itself only accepts a new password by file or interactive prompt.
host: and user: optionally override the hostname/username
recorded on the new key.
20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/yobi/repository/key.rb', line 20 def key_add(new_password:, host: nil, user: nil) with_new_password_flag(new_password) do |flag_name, flag_value| argv = build_argv("key", "add") do |a| a.flag(:host, host) unless host.nil? a.flag(:user, user) unless user.nil? a.flag(flag_name, flag_value) end run_restic(argv) end true end |
#key_list ⇒ Array[Key] Also known as: keys
restic key list: every Yobi::Key associated with this repository.
34 35 36 37 |
# File 'lib/yobi/repository/key.rb', line 34 def key_list execution = run_restic(build_argv("key", "list")) parse_json_output(execution).map { |raw| Key.new(raw) } end |
#key_passwd(new_password:, host: nil, user: nil) ⇒ true Also known as: change_password
restic key passwd: creates a new key for this repository and
removes the key currently in use. On success, also updates this
Repository instance's own password: to new_password:, so it
keeps working against this same repository afterward.
new_password:, host: and user: take the same shapes as #key_add.
46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/yobi/repository/key.rb', line 46 def key_passwd(new_password:, host: nil, user: nil) with_new_password_flag(new_password) do |flag_name, flag_value| argv = build_argv("key", "passwd") do |a| a.flag(:host, host) unless host.nil? a.flag(:user, user) unless user.nil? a.flag(flag_name, flag_value) end run_restic(argv) end @password = new_password true end |
#key_remove(id:) ⇒ true Also known as: remove_key
restic key remove: removes the given key id from this repository.
Restic refuses to remove the key currently being used to access it.
62 63 64 65 |
# File 'lib/yobi/repository/key.rb', line 62 def key_remove(id:) run_restic(build_argv("key", "remove", id)) true end |
#list(type) ⇒ Array[String]
restic list: every object ID of the given type in this repository.
type is :blobs, :packs, :index, :snapshots, :keys, or
:locks. Restic ignores --json for this command; each line is a
bare ID, except for :blobs, where each line is "data <id>" or
"tree <id>".
10 11 12 13 14 |
# File 'lib/yobi/repository/list.rb', line 10 def list(type) argv = build_argv("list", type) execution = run_restic(argv) execution[:output].to_s.each_line.map(&:strip).reject(&:empty?) end |
#ls(snapshot_id:, dirs: [], hosts: [], human_readable: false, long: false, paths: [], recursive: false, reverse: false, sort: nil, tags: []) ⇒ Object
restic ls: lists a snapshot's files/directories.
snapshot_id: is a snapshot ID, or "latest". dirs: restricts the
listing to specific directories within the snapshot.
hosts:, paths: and tags: each accept a single value or an Array
and are only relevant when snapshot_id is "latest".
recursive: recurses into subdirectories. human_readable:, long:,
reverse: and sort: (e.g. "size") toggle formatting/sort options.
Returns a Yobi::LsOutcome.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/yobi/repository/ls.rb', line 19 def ls(snapshot_id:, dirs: [], hosts: [], human_readable: false, long: false, paths: [], recursive: false, reverse: false, sort: nil, tags: []) argv = build_argv("ls", snapshot_id) do |a| a.repeat_flag(:host, hosts) a.flag(:human_readable) if human_readable a.flag(:long) if long a.repeat_flag(:path, paths) a.flag(:recursive) if recursive a.flag(:reverse) if reverse a.flag(:sort, sort) unless sort.nil? a.repeat_flag(:tag, ) a..append(dirs) end output = Yobi::ResticOutput.new(transform: Yobi::LsMessageWrapper) execution = run_restic(argv, output: output) LsOutcome.new(execution) end |
#migrate(names: [], force: false) ⇒ true
restic migrate: checks which migrations can be applied to this
repository, or applies the given ones. Restic ignores --json for
this command, so there's no programmatic way to discover which
migrations are available - run restic migrate directly for that.
names: names the migrations to apply, or only lists available ones
if empty. force: true reapplies a migration already marked as
applied.
13 14 15 16 17 18 19 |
# File 'lib/yobi/repository/migrate.rb', line 13 def migrate(names: [], force: false) argv = build_argv("migrate", names) do |a| a.flag(:force) if force end run_restic(argv) true end |
#mount(mountpoint:, hosts: [], paths: [], tags: [], allow_other: false, no_default_permissions: false, owner_root: false, path_templates: [], time_template: nil, ready_timeout: 10) ⇒ Object
restic mount: serves this repository as a read-only FUSE filesystem
at mountpoint:, which must already exist.
Without a block, returns a Yobi::MountHandle once Restic reports
itself ready; call #stop yourself once done. With one, yields the
MountHandle and stops it automatically once the block returns or
raises, returning the block's own value.
hosts:, paths:, and tags: each accept a single value or an
Array to restrict which snapshots appear under snapshots/.
allow_other: allows other users to access the mount.
no_default_permissions: skips file permission checks.
owner_root: mounts files as owned by root. path_templates: and
time_template: control the directory naming schemes under
snapshots/.
ready_timeout: is the number of seconds to wait for Restic's
readiness message before raising Yobi::MountTimeout.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/yobi/repository/mount.rb', line 26 def mount(mountpoint:, hosts: [], paths: [], tags: [], allow_other: false, no_default_permissions: false, owner_root: false, path_templates: [], time_template: nil, ready_timeout: 10) argv = build_argv("mount") do |a| a.flag(:allow_other) if allow_other a.repeat_flag(:host, hosts) a.flag(:no_default_permissions) if a.flag(:owner_root) if owner_root a.repeat_flag(:path, paths) a.repeat_flag(:path_template, path_templates) a.repeat_flag(:tag, ) a.flag(:time_template, time_template) unless time_template.nil? a..append(mountpoint) end mount = run_restic_mount(argv, mountpoint: mountpoint.to_s, ready_timeout: ready_timeout) return mount unless block_given? begin yield mount ensure mount.stop end end |
#prune(dry_run: false, max_repack_size: nil, max_unused: nil, repack_cacheable_only: false, repack_smaller_than: nil, repack_uncompressed: false, unsafe_recover_no_free_space: nil) ⇒ Object
restic prune: removes data no longer referenced by any snapshot.
dry_run: reports what would happen without doing it. max_unused:
targets a maximum unused space after pruning (e.g. "10%").
max_repack_size: caps the amount of data repacked in one run.
repack_cacheable_only: only repacks packs cached locally.
repack_smaller_than: also repacks packs smaller than a given size.
repack_uncompressed: repacks packs not yet using compression.
unsafe_recover_no_free_space: proceeds even without enough free
space, at the given risk acknowledgement string.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/yobi/repository/prune.rb', line 17 def prune(dry_run: false, max_repack_size: nil, max_unused: nil, repack_cacheable_only: false, repack_smaller_than: nil, repack_uncompressed: false, unsafe_recover_no_free_space: nil) argv = build_argv("prune") do |a| a.flag(:dry_run) if dry_run a.flag(:max_repack_size, max_repack_size) unless max_repack_size.nil? a.flag(:max_unused, max_unused) unless max_unused.nil? a.flag(:repack_cacheable_only) if repack_cacheable_only a.flag(:repack_smaller_than, repack_smaller_than) unless repack_smaller_than.nil? a.flag(:repack_uncompressed) if repack_uncompressed a.flag(:unsafe_recover_no_free_space, unsafe_recover_no_free_space) unless unsafe_recover_no_free_space.nil? end run_restic(argv) true end |
#recover ⇒ true
restic recover: builds a new snapshot from any data found in this
repository that isn't referenced by an existing snapshot (e.g. after
an accidental #forget). Call #snapshots afterward to find the
recovered snapshot, if any was created.
9 10 11 12 |
# File 'lib/yobi/repository/recover.rb', line 9 def recover run_restic(build_argv("recover")) true end |
#repair_index(read_all_packs: false) ⇒ true
restic repair index: creates a new index based on the pack files
present in this repository. Successor to the deprecated
rebuild-index command. read_all_packs: true reads every pack file
fully instead of just headers.
9 10 11 12 13 14 15 |
# File 'lib/yobi/repository/repair.rb', line 9 def repair_index(read_all_packs: false) argv = build_argv("repair", "index") do |a| a.flag(:read_all_packs) if read_all_packs end run_restic(argv) true end |
#repair_packs(ids:) ⇒ true
restic repair packs: extracts intact blobs from the given pack
ids:, rebuilds the index to drop the damaged packs, and removes
them from the repository. Restic also writes a backup copy of each
given pack file (named pack-<id>) into the calling process's
current working directory before removing it. There's no flag to
disable this.
23 24 25 26 27 |
# File 'lib/yobi/repository/repair.rb', line 23 def repair_packs(ids:) argv = build_argv("repair", "packs", ids) run_restic(argv) true end |
#repair_snapshots(snapshot_ids: [], dry_run: false, forget: false, hosts: [], paths: [], tags: []) ⇒ Object
restic repair snapshots: scans the given snapshots and generates
new ones with damaged directories/file contents removed. This causes
data loss for the content actually removed; prefer a fresh
Yobi::Repository#backup where the source data is still available.
Depends on a correct index - call #repair_index first.
snapshot_ids: names the snapshots to repair, or all of them if
empty. dry_run: reports what would happen without doing it.
forget: removes the original damaged snapshots after repairing.
hosts:, paths:, and tags: each accept a single value or an
Array to filter by.
40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/yobi/repository/repair.rb', line 40 def repair_snapshots(snapshot_ids: [], dry_run: false, forget: false, hosts: [], paths: [], tags: []) argv = build_argv("repair", "snapshots", snapshot_ids) do |a| a.flag(:dry_run) if dry_run a.flag(:forget) if forget a.repeat_flag(:host, hosts) a.repeat_flag(:path, paths) a.repeat_flag(:tag, ) end run_restic(argv) true end |
#restore(snapshot_id:, target:, excludes: [], exclude_files: [], exclude_xattrs: [], hosts: [], iexcludes: [], iexclude_files: [], iincludes: [], iinclude_files: [], includes: [], include_files: [], include_xattrs: [], paths: [], tags: [], delete: false, dry_run: false, overwrite: nil, ownership_by_name: false, sparse: false, verbose: false, verify: false, &block) ⇒ Object
restic restore: extracts a snapshot's contents to a target directory.
snapshot_id: is a snapshot ID or "latest". target: is the
directory to restore into.
excludes:, exclude_files:, exclude_xattrs:, includes:,
include_files:, include_xattrs: and their +i+-prefixed
(case-insensitive) variants each accept a single value or an Array.
hosts:, paths: and tags: each accept a single value or an
Array and are only relevant when snapshot_id is "latest".
delete: deletes files in target not present in the snapshot.
dry_run: reports what would happen without doing it.
overwrite: is "always", "if-changed", or "if-newer".
ownership_by_name: maps ownership by user/group name instead of
numeric ID. sparse: writes sparse files. verify: verifies restored
file content against the repository.
When verbose: true, a Yobi::RestoreVerboseStatus is streamed to the
block per file. The block also receives Yobi::RestoreStatus messages
as they arrive. Returns a Yobi::RestoreOutcome.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/yobi/repository/restore.rb', line 27 def restore(snapshot_id:, target:, excludes: [], exclude_files: [], exclude_xattrs: [], hosts: [], iexcludes: [], iexclude_files: [], iincludes: [], iinclude_files: [], includes: [], include_files: [], include_xattrs: [], paths: [], tags: [], delete: false, dry_run: false, overwrite: nil, ownership_by_name: false, sparse: false, verbose: false, verify: false, &block) argv = build_argv("restore", snapshot_id) do |a| a.flag(:target, target) a.repeat_flag(:exclude, excludes) a.repeat_flag(:exclude_file, exclude_files) a.repeat_flag(:exclude_xattr, exclude_xattrs) a.repeat_flag(:host, hosts) a.repeat_flag(:iexclude, iexcludes) a.repeat_flag(:iexclude_file, iexclude_files) a.repeat_flag(:iinclude, iincludes) a.repeat_flag(:iinclude_file, iinclude_files) a.repeat_flag(:include, includes) a.repeat_flag(:include_file, include_files) a.repeat_flag(:include_xattr, include_xattrs) a.repeat_flag(:path, paths) a.repeat_flag(:tag, ) a.flag(:delete) if delete a.flag(:dry_run) if dry_run a.flag(:overwrite, overwrite) unless overwrite.nil? a.flag(:ownership_by_name) if ownership_by_name a.flag(:sparse) if sparse a.short_flag(:vv) if verbose a.flag(:verify) if verify end output = Yobi::ResticOutput.new(transform: Yobi::RestoreMessageWrapper) # Restic's own restore summary isn't meant for the live block - only # RestoreOutcome#summary's post-hoc access sees it - but it shares # the same @transform (needed there since it isn't a status/verbose_status # message), so it has to be filtered back out here instead. live_block = block && proc { || block.call() if .is_a?(RestoreStatus) || .is_a?(RestoreVerboseStatus) } execution = run_restic(argv, output: output, &live_block) RestoreOutcome.new(execution) end |
#rewrite(snapshot_ids: [], hosts: [], tags: [], paths: [], excludes: [], exclude_files: [], iexcludes: [], iexclude_files: [], includes: [], include_files: [], iincludes: [], iinclude_files: [], dry_run: false, forget: false, new_host: nil, new_time: nil, snapshot_summary: false) ⇒ Object
restic rewrite: creates new snapshots from existing ones with
exclude/include filters applied, or metadata changed. With
snapshot_ids: and the other filters all left at their defaults,
rewrites every snapshot in the repository.
snapshot_ids: names the snapshots to rewrite, or all of them if
empty. hosts:, tags:, and paths: each accept a single value or
an Array to filter by.
excludes:, exclude_files:, includes:, include_files: and
their +i+-prefixed (case-insensitive) variants each accept a single
value or an Array.
dry_run: reports what would happen without doing it. forget:
removes the original snapshots afterward, instead of tagging the new
ones "rewrite" and keeping both. new_host: / new_time: change
the recorded hostname/timestamp. snapshot_summary: regenerates the
snapshot summary.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/yobi/repository/rewrite.rb', line 23 def rewrite(snapshot_ids: [], hosts: [], tags: [], paths: [], excludes: [], exclude_files: [], iexcludes: [], iexclude_files: [], includes: [], include_files: [], iincludes: [], iinclude_files: [], dry_run: false, forget: false, new_host: nil, new_time: nil, snapshot_summary: false) argv = build_argv("rewrite", snapshot_ids) do |a| a.repeat_flag(:host, hosts) a.repeat_flag(:tag, ) a.repeat_flag(:path, paths) a.repeat_flag(:exclude, excludes) a.repeat_flag(:exclude_file, exclude_files) a.repeat_flag(:iexclude, iexcludes) a.repeat_flag(:iexclude_file, iexclude_files) a.repeat_flag(:include, includes) a.repeat_flag(:include_file, include_files) a.repeat_flag(:iinclude, iincludes) a.repeat_flag(:iinclude_file, iinclude_files) a.flag(:dry_run) if dry_run a.flag(:forget) if forget a.flag(:new_host, new_host) unless new_host.nil? a.flag(:new_time, new_time) unless new_time.nil? a.flag(:snapshot_summary) if snapshot_summary end run_restic(argv) true end |
#snapshots(tags: [], hosts: [], paths: [], compact: false, group_by: nil, latest: nil) ⇒ Object
restic snapshots: lists snapshots, optionally filtered.
tags:, hosts:, and paths: each accept a single value or an
Array to filter by. compact: compacts the printed listing.
group_by: (e.g. "host") groups results. latest: limits to the
N most recent per group.
Returns an Array of Yobi::Snapshot.
15 16 17 18 19 20 21 22 23 24 25 26 |
# File 'lib/yobi/repository/snapshots.rb', line 15 def snapshots(tags: [], hosts: [], paths: [], compact: false, group_by: nil, latest: nil) argv = build_argv("snapshots") do |a| a.repeat_flag(:tag, ) a.repeat_flag(:host, hosts) a.repeat_flag(:path, paths) a.flag(:compact) if compact a.flag(:group_by, group_by) unless group_by.nil? a.flag(:latest, latest) unless latest.nil? end execution = run_restic(argv) parse_json_output(execution).map { |raw| Snapshot.new(raw) } end |
#stats(snapshot_ids: [], hosts: [], mode: nil, paths: [], tags: []) ⇒ Object
restic stats: accumulates statistics about the repository's data.
snapshot_ids: restricts to specific snapshot IDs, or the whole
repository if empty. hosts:, paths:, and tags: each accept a
single value or an Array to filter by.
mode: is "restore-size" / :restore_size (default),
"files-by-contents" / :files_by_contents, "blobs-per-file" /
:blobs_per_file, or "raw-data" / :raw_data. Anything else
raises ArgumentError.
Returns a Yobi::RepositoryStats.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/yobi/repository/stats.rb', line 22 def stats(snapshot_ids: [], hosts: [], mode: nil, paths: [], tags: []) argv = build_argv("stats", snapshot_ids) do |a| a.repeat_flag(:host, hosts) unless mode.nil? a.flag(:mode, STATS_MODES.fetch(mode) { raise ArgumentError, "invalid mode: #{mode.inspect}" }) end a.repeat_flag(:path, paths) a.repeat_flag(:tag, ) end execution = run_restic(argv) # Permissive because Restic 0.19.0 prints a "[0:00] 100.00% ..." # progress line before the JSON summary. Fixed in 0.19.1. RepositoryStats.new(parse_json_output_permissively(execution)) end |
#tag(snapshot_ids: [], add: [], remove: [], set: [], tags: [], hosts: [], paths: []) ⇒ Object
restic tag: modifies tags on existing snapshots. Tags are part of a
snapshot's content-addressed identity, so changing them produces a
new snapshot ID for each affected snapshot.
snapshot_ids: names the snapshots to modify, or all matching
+hosts:+/+paths:+ if empty.
add: appends tags, keeping existing ones. remove: strips tags.
set: replaces all tags with exactly the given set (exclusive with
+add:+/+remove:+).
tags:, hosts:, and paths: each accept a single value or an
Array to filter which snapshots to modify (by current tags,
hostname, or originally backed-up path respectively).
Returns a Yobi::TagOutcome.
21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/yobi/repository/tag.rb', line 21 def tag(snapshot_ids: [], add: [], remove: [], set: [], tags: [], hosts: [], paths: []) argv = build_argv("tag", snapshot_ids) do |a| a.repeat_flag(:add, add) a.repeat_flag(:remove, remove) a.repeat_flag(:set, set) a.repeat_flag(:tag, ) a.repeat_flag(:host, hosts) a.repeat_flag(:path, paths) end output = Yobi::ResticOutput.new(transform: Yobi::TagMessageWrapper) execution = run_restic(argv, output: output) TagOutcome.new(execution) end |
#unlock(remove_all: false) ⇒ true
restic unlock: removes stale locks left by other Restic processes.
remove_all: true removes every lock, not just stale ones.
7 8 9 10 11 12 13 |
# File 'lib/yobi/repository/unlock.rb', line 7 def unlock(remove_all: false) argv = build_argv("unlock") do |a| a.flag(:remove_all) if remove_all end run_restic(argv) true end |