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 =
Set.new(%w[tar zip].flat_map { [_1, _1.to_sym] }).freeze
- STATS_MODES =
%w[restore-size files-by-contents blobs-per-file raw-data].each_with_object({}) do |value, hash| hash[value] = value hash[value.tr("-", "_").to_sym] = value end.freeze
Instance Attribute Summary collapse
-
#backend_credentials ⇒ Hash, #call
readonly
The storage backend's own credentials, as given to #initialize.
-
#password ⇒ String, ...
readonly
The repository's encryption password, as given to #initialize.
-
#url ⇒ String
readonly
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) {|message| ... } ⇒ Yobi::BackupOutcome
restic backup: creates a new snapshot from a source path. -
#cat_blob(id) {|io| ... } ⇒ Yobi::IOHandle
restic cat blob ID: one data blob's raw, decrypted bytes. -
#cat_config ⇒ Hash
(also: #config)
restic cat config: the repository's own config document. -
#cat_index(id) ⇒ Hash
restic cat index ID: one index file's own raw contents. -
#cat_key(id) ⇒ Hash
restic cat key ID: one key's own raw stored record. -
#cat_masterkey_and_game_over_if_this_leaks ⇒ Hash
restic cat masterkey: this repository's own encryption/MAC key material. -
#cat_pack(id) {|io| ... } ⇒ Yobi::IOHandle
restic cat pack ID: one pack file's raw, still-encrypted bytes. -
#cat_snapshot(id) ⇒ Hash
restic cat snapshot ID: one snapshot's own raw stored record. -
#cat_tree(snapshot_id, subfolder: nil) ⇒ Hash
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) ⇒ Yobi::CheckOutcome
restic check: tests the repository for errors. -
#copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, hosts: [], paths: [], tags: []) ⇒ true
restic copy: replicates snapshots from another repository into this one. -
#diff(from:, to:, metadata: false) ⇒ Yobi::DiffOutcome
restic diff: compares two snapshots. -
#dump(snapshot_id:, file:, target: nil, archive: nil, hosts: [], paths: [], tags: []) {|io| ... } ⇒ true, Yobi::IOHandle
restic dump: extracts a file/folder from a snapshot. - #env ⇒ Hash{String => String}
-
#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: []) ⇒ Array<Yobi::MatchesPerSnapshot>
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) ⇒ Array<Yobi::ForgetGroup>
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) ⇒ Yobi::Initialized
restic init: creates the repository at #url. -
#init_mirror(url:, password:, backend_credentials: {}, restic: @restic, repository_version: nil) ⇒ Yobi::Repository
Constructs a new 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
A new instance of Repository.
- #inspect ⇒ String
-
#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<Yobi::Key>
(also: #keys)
restic key list: lists every key (password) 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 key ID from this repository. -
#list(type) ⇒ Array<String>
restic list: lists every object ID of the given type in this repository. -
#ls(snapshot_id:, dirs: [], hosts: [], human_readable: false, long: false, paths: [], recursive: false, reverse: false, sort: nil, tags: []) ⇒ Yobi::LsOutcome
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) {|mount| ... } ⇒ Yobi::MountHandle, 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) ⇒ true
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 pack files, 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: []) ⇒ true
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) {|message| ... } ⇒ Yobi::RestoreOutcome
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) ⇒ true
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) ⇒ Array<Yobi::Snapshot>
restic snapshots: lists snapshots, optionally filtered. -
#stats(snapshot_ids: [], hosts: [], mode: nil, paths: [], tags: []) ⇒ Yobi::RepositoryStats
restic stats: accumulates statistics about the repository's data. -
#tag(snapshot_ids: [], add: [], remove: [], set: [], tags: [], hosts: [], paths: []) ⇒ Yobi::TagOutcome
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
Returns a new instance of Repository.
27 28 29 30 31 32 33 34 35 |
# File 'lib/yobi/repository.rb', line 27 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 ⇒ Hash, #call (readonly)
Returns the storage backend's own credentials, as given to #initialize.
14 15 16 |
# File 'lib/yobi/repository.rb', line 14 def backend_credentials @backend_credentials end |
#password ⇒ String, ... (readonly)
Returns the repository's encryption password, as given to #initialize.
12 13 14 |
# File 'lib/yobi/repository.rb', line 12 def password @password end |
#url ⇒ String (readonly)
Returns the repository location.
10 11 12 |
# File 'lib/yobi/repository.rb', line 10 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) {|message| ... } ⇒ Yobi::BackupOutcome
restic backup: creates a new snapshot from a source path.
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 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 |
# File 'lib/yobi/repository/backup.rb', line 43 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
81 82 83 |
# File 'lib/yobi/repository/cat.rb', line 81 def cat_blob(id, &block) run_restic_dump(build_argv("cat", "blob", id), &block) end |
#cat_config ⇒ Hash Also known as: config
restic cat config: the repository's own config document.
11 12 13 14 |
# File 'lib/yobi/repository/cat.rb', line 11 def cat_config execution = run_restic(build_argv("cat", "config")) JSON.parse(execution[:output].to_s) end |
#cat_index(id) ⇒ Hash
restic cat index ID: one index file's own raw contents. IDs come
from list(:index).
32 33 34 35 |
# File 'lib/yobi/repository/cat.rb', line 32 def cat_index(id) execution = run_restic(build_argv("cat", "index", id)) JSON.parse(execution[:output].to_s) end |
#cat_key(id) ⇒ Hash
restic cat key ID: one key's own raw stored record.
41 42 43 44 45 |
# File 'lib/yobi/repository/cat.rb', line 41 def cat_key(id) id = id.id if id.is_a?(Yobi::Key) execution = run_restic(build_argv("cat", "key", id)) JSON.parse(execution[:output].to_s) end |
#cat_masterkey_and_game_over_if_this_leaks ⇒ Hash
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.
92 93 94 95 |
# File 'lib/yobi/repository/cat.rb', line 92 def cat_masterkey_and_game_over_if_this_leaks execution = run_restic(build_argv("cat", "masterkey")) JSON.parse(execution[:output].to_s) 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.
70 71 72 |
# File 'lib/yobi/repository/cat.rb', line 70 def cat_pack(id, &block) run_restic_dump(build_argv("cat", "pack", id), &block) end |
#cat_snapshot(id) ⇒ Hash
restic cat snapshot ID: one snapshot's own raw stored record.
21 22 23 24 25 |
# File 'lib/yobi/repository/cat.rb', line 21 def cat_snapshot(id) id = id.id if id.is_a?(Yobi::Snapshot) execution = run_restic(build_argv("cat", "snapshot", id)) JSON.parse(execution[:output].to_s) end |
#cat_tree(snapshot_id, subfolder: nil) ⇒ Hash
restic cat tree snapshot:subfolder: the raw tree object at a
snapshot's root, or at subfolder within it.
53 54 55 56 57 58 59 60 61 62 |
# File 'lib/yobi/repository/cat.rb', line 53 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)) JSON.parse(execution[:output].to_s) end |
#check(hosts: [], paths: [], read_data: false, read_data_subset: nil, tags: [], with_cache: false) ⇒ Yobi::CheckOutcome
restic check: tests the repository for errors.
14 15 16 17 18 19 20 21 22 23 24 25 26 |
# File 'lib/yobi/repository/check.rb', line 14 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: []) ⇒ true
restic copy: replicates snapshots from another repository into
this one. Already-copied snapshots are skipped automatically.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/yobi/repository/copy.rb', line 19 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) ⇒ Yobi::DiffOutcome
restic diff: compares two snapshots.
11 12 13 14 15 16 17 18 |
# File 'lib/yobi/repository/diff.rb', line 11 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: []) {|io| ... } ⇒ true, Yobi::IOHandle
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.
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/yobi/repository/dump.rb', line 25 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}
38 39 40 41 42 |
# File 'lib/yobi/repository.rb', line 38 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: []) ⇒ Array<Yobi::MatchesPerSnapshot>
restic find: searches for files/directories across snapshots by
name pattern.
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/find.rb', line 27 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) JSON.parse(execution[:output].to_s).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) ⇒ Array<Yobi::ForgetGroup>
restic forget: applies a retention policy, removing snapshots
that don't match any keep_* rule.
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 70 71 72 73 |
# File 'lib/yobi/repository/forget.rb', line 37 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) JSON.parse(execution[:output].to_s).map { |raw| ForgetGroup.new(raw) } end |
#init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil, repository_version: nil) ⇒ Yobi::Initialized
restic init: creates the repository at #url.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/yobi/repository/init.rb', line 19 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(JSON.parse(execution[:output].to_s)) end |
#init_mirror(url:, password:, backend_credentials: {}, restic: @restic, repository_version: nil) ⇒ Yobi::Repository
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.
52 53 54 55 56 |
# File 'lib/yobi/repository/init.rb', line 52 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 ⇒ String
45 46 47 |
# File 'lib/yobi/repository.rb', line 45 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.
22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/yobi/repository/key.rb', line 22 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<Yobi::Key> Also known as: keys
restic key list: lists every key (password) associated with this repository.
38 39 40 41 |
# File 'lib/yobi/repository/key.rb', line 38 def key_list execution = run_restic(build_argv("key", "list")) JSON.parse(execution[:output].to_s).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.
53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/yobi/repository/key.rb', line 53 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 the
repository.
73 74 75 76 |
# File 'lib/yobi/repository/key.rb', line 73 def key_remove(id:) run_restic(build_argv("key", "remove", id)) true end |
#list(type) ⇒ Array<String>
restic list: lists every object ID of the given type in this
repository. Restic ignores --json for this command; each line is a
bare ID, except for :blobs, where each line is "data <id>" or "tree <id>".
11 12 13 14 15 |
# File 'lib/yobi/repository/list.rb', line 11 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: []) ⇒ Yobi::LsOutcome
restic ls: lists a snapshot's files/directories.
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/yobi/repository/ls.rb', line 20 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.
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) {|mount| ... } ⇒ Yobi::MountHandle, Object
restic mount: serves this repository as a read-only FUSE
filesystem at mountpoint, which must already exist.
Without a block, returns a 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.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/yobi/repository/mount.rb', line 28 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) ⇒ true
restic prune: removes data no longer referenced by any snapshot.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/yobi/repository/prune.rb', line 15 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.
11 12 13 14 |
# File 'lib/yobi/repository/recover.rb', line 11 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.
11 12 13 14 15 16 17 |
# File 'lib/yobi/repository/repair.rb', line 11 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
files, 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.
28 29 30 31 32 |
# File 'lib/yobi/repository/repair.rb', line 28 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: []) ⇒ true
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
#backup where the source data is still available.
Depends on a correct index. Call #repair_index first.
47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/yobi/repository/repair.rb', line 47 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) {|message| ... } ⇒ Yobi::RestoreOutcome
restic restore: extracts a snapshot's contents to a target directory.
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 |
# File 'lib/yobi/repository/restore.rb', line 31 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) ⇒ true
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.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/yobi/repository/rewrite.rb', line 28 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) ⇒ Array<Yobi::Snapshot>
restic snapshots: lists snapshots, optionally filtered.
16 17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/yobi/repository/snapshots.rb', line 16 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) JSON.parse(execution[:output].to_s).map { |raw| Snapshot.new(raw) } end |
#stats(snapshot_ids: [], hosts: [], mode: nil, paths: [], tags: []) ⇒ Yobi::RepositoryStats
restic stats: accumulates statistics about the repository's data.
22 23 24 25 26 27 28 29 30 31 32 33 |
# 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) RepositoryStats.new(JSON.parse(execution[:output].to_s)) end |
#tag(snapshot_ids: [], add: [], remove: [], set: [], tags: [], hosts: [], paths: []) ⇒ Yobi::TagOutcome
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.
17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/yobi/repository/tag.rb', line 17 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.
9 10 11 12 13 14 15 |
# File 'lib/yobi/repository/unlock.rb', line 9 def unlock(remove_all: false) argv = build_argv("unlock") do |a| a.flag(:remove_all) if remove_all end run_restic(argv) true end |