Class: Yobi::Repository

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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.

Parameters:

  • url: (String)
  • password: (password)
  • backend_credentials: (backend_credentials) (defaults to: {})
  • restic: (restic_or_path) (defaults to: nil)


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_credentialsbackend_credentials

The storage backend's own credentials, as given to #initialize.

Returns:



15
16
17
# File 'lib/yobi/repository.rb', line 15

def backend_credentials
  @backend_credentials
end

#passwordpassword

The repository's encryption password, as given to #initialize.

Returns:



13
14
15
# File 'lib/yobi/repository.rb', line 13

def password
  @password
end

#urlString

The repository location.

Returns:

  • (String)


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, tags)
    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.end_of_options.append(path)
    in [:stdin_from_command, command]
      a.flag(:stdin_from_command)
      a.end_of_options.append(tokenize(command))
    in [:stdin_from_command, command, String => filename]
      a.flag(:stdin_from_command)
      a.flag(:stdin_filename, filename)
      a.end_of_options.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.

Overloads:

  • #cat_blob(id) ⇒ Object

    Parameters:

    • id (String)

    Returns:

    • (Object)
  • #cat_blob(id) ⇒ IOHandle

    Parameters:

    • id (String)

    Returns:

Yields:

Yield Parameters:

  • io (IO)

Yield Returns:

  • (Object)


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_configHash[String, untyped] Also known as: config

restic cat config: the repository's own config document, as a Hash. Raises Yobi::RepositoryNotFound or Yobi::AuthenticationFailed.

Returns:

  • (Hash[String, untyped])


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).

Parameters:

  • id (String)

Returns:

  • (Hash[String, untyped])


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.

Parameters:

  • id (String, Key)

Returns:

  • (Hash[String, untyped])


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_leaksHash[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.

Returns:

  • (Hash[String, untyped])


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.

Overloads:

  • #cat_pack(id) ⇒ Object

    Parameters:

    • id (String)

    Returns:

    • (Object)
  • #cat_pack(id) ⇒ IOHandle

    Parameters:

    • id (String)

    Returns:

Yields:

Yield Parameters:

  • io (IO)

Yield Returns:

  • (Object)


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.

Parameters:

Returns:

  • (Hash[String, untyped])


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.

Parameters:

  • snapshot_id (String, Snapshot)
  • subfolder: (String, nil) (defaults to: nil)

Returns:

  • (Hash[String, untyped])


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, tags)
    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, tags)
  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.

Parameters:

  • from: (String)
  • to: (String)
  • metadata: (Boolean) (defaults to: false)

Returns:



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.

Raises:

  • (ArgumentError)


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, tags)
    a.flag(:target, target) unless target.nil?
    a.end_of_options.append(file)
  end

  if target
    run_restic(argv)
    true
  else
    run_restic_dump(argv, &block)
  end
end

#envHash[String, String]

The full env Hash Restic will see for this repository: RESTIC_REPOSITORY plus resolved password and backend credentials.

Returns:

  • (Hash[String, String])


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, tags)
    a.end_of_options.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, keep_tags)
    a.repeat_flag(:host, hosts)
    a.repeat_flag(:tag, tags)
    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

#inspectObject

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.

Parameters:

  • new_password: (new_password)
  • host: (String, nil) (defaults to: nil)
  • user: (String, nil) (defaults to: nil)

Returns:

  • (true)


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_listArray[Key] Also known as: keys

restic key list: every Yobi::Key associated with this repository.

Returns:



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.

Parameters:

  • new_password: (new_password)
  • host: (String, nil) (defaults to: nil)
  • user: (String, nil) (defaults to: nil)

Returns:

  • (true)


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.

Parameters:

  • id: (String)

Returns:

  • (true)


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>".

Parameters:

  • type (Symbol, String)

Returns:

  • (Array[String])


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, tags)
    a.end_of_options.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.

Parameters:

  • names: (Array[String], String) (defaults to: [])
  • force: (Boolean) (defaults to: false)

Returns:

  • (true)


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 no_default_permissions
    a.flag(:owner_root) if owner_root
    a.repeat_flag(:path, paths)
    a.repeat_flag(:path_template, path_templates)
    a.repeat_flag(:tag, tags)
    a.flag(:time_template, time_template) unless time_template.nil?
    a.end_of_options.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

#recovertrue

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.

Returns:

  • (true)


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.

Parameters:

  • read_all_packs: (Boolean) (defaults to: false)

Returns:

  • (true)


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.

Parameters:

  • ids: (Array[String], String)

Returns:

  • (true)


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, tags)
  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, tags)
    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 { |message| block.call(message) if message.is_a?(RestoreStatus) || message.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, tags)
    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, tags)
    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, tags)
  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, tags)
    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.

Parameters:

  • remove_all: (Boolean) (defaults to: false)

Returns:

  • (true)


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