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/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 =

Valid archive: values for #dump, as both String and Symbol.

Set.new(%w[tar zip].flat_map { [_1, _1.to_sym] }).freeze
STATS_MODES =

Maps every valid mode: value for #stats, as both String and Symbol, to the String Restic itself expects.

%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

Instance Method Summary collapse

Constructor Details

#initialize(url:, password: nil, backend_credentials: {}, restic: nil) ⇒ Repository

Returns a new instance of Repository.

Parameters:

  • url (String)

    the repository location, e.g. "s3:s3.amazonaws.com/bucket"

  • password (String, Array, Symbol, #call, nil) (defaults to: nil)

    a literal password; a [:command, "..."]/[:file, "..."] tuple, resolved natively by Restic itself; :insecure_no_password; or anything responding to #call (invoked fresh immediately before every Restic invocation)

  • backend_credentials (Hash{String => String}, #call) (defaults to: {})

    the storage backend's own env vars (AWS_*/AZURE_*/etc.), or anything responding to #call returning such a Hash

  • restic (Yobi::Restic, String, nil) (defaults to: nil)

    a Restic instance to share, a bare Restic binary path, or nil to create a default one

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


27
28
29
30
31
32
# File 'lib/yobi/repository.rb', line 27

def initialize(url:, password: nil, backend_credentials: {}, restic: nil)
  @url, @extracted_rest_credentials = extract_rest_credentials(url)
  @password = password
  @backend_credentials = backend_credentials
  @restic = initialize_restic(restic)
end

Instance Attribute Details

#backend_credentialsHash, #call (readonly)

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

Returns:

  • (Hash, #call)

    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

#passwordString, ... (readonly)

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

Returns:

  • (String, Array, Symbol, #call, nil)

    the repository's encryption password, as given to #initialize



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

def password
  @password
end

#urlString (readonly)

Returns the repository location.

Returns:

  • (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) {|message| ... } ⇒ Yobi::BackupOutcome

restic backup: creates a new snapshot from a source path.

Parameters:

  • source (String, Array)

    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 (Array<String>, String) (defaults to: [])

    exclude files matching these glob patterns

  • exclude_files (Array<String>, String) (defaults to: [])

    path(s) to file(s) listing exclude patterns, one per line

  • exclude_if_present (Array<String>, String) (defaults to: [])

    skip a directory containing a marker file with this name (optionally name:content)

  • exclude_larger_than (String, nil) (defaults to: nil)

    skip files larger than this size, e.g. "1G"

  • files_from (Array<String>, String) (defaults to: [])

    read the files/dirs to back up from a file, one per line

  • files_from_raw (Array<String>, String) (defaults to: [])

    like files_from, NUL-separated

  • files_from_verbatim (Array<String>, String) (defaults to: [])

    like files_from, paths taken literally with no glob expansion

  • iexcludes (Array<String>, String) (defaults to: [])

    like excludes, case-insensitive

  • iexclude_files (Array<String>, String) (defaults to: [])

    like exclude_files, case-insensitive

  • tags (Array<String>, String) (defaults to: [])

    tags to attach to the new snapshot

  • dry_run (Boolean) (defaults to: false)

    report what would happen without doing it

  • exclude_caches (Boolean) (defaults to: false)

    skip directories containing a CACHEDIR.TAG marker

  • exclude_cloud_files (Boolean) (defaults to: false)

    skip files not fully present on disk (e.g. OneDrive placeholders)

  • force (Boolean) (defaults to: false)

    back up unchanged files instead of skipping them

  • group_by (String, nil) (defaults to: nil)

    grouping used to find the parent snapshot, e.g. "host,paths"

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

    hostname to record on the new snapshot, instead of the OS hostname

  • ignore_ctime (Boolean) (defaults to: false)

    relax change detection to ignore ctime

  • ignore_inode (Boolean) (defaults to: false)

    relax change detection to ignore inode number

  • no_scan (Boolean) (defaults to: false)

    skip the pre-backup scan (disables percentage progress)

  • one_file_system (Boolean) (defaults to: false)

    don't cross filesystem boundaries

  • parent (String, nil) (defaults to: nil)

    snapshot ID to use as the parent instead of the latest one

  • read_concurrency (Integer, nil) (defaults to: nil)

    number of concurrent file reads

  • skip_if_unchanged (Boolean) (defaults to: false)

    don't create a snapshot if nothing changed

  • time (String, nil) (defaults to: nil)

    timestamp to record instead of now

  • verbose (Boolean) (defaults to: false)

    stream a BackupVerboseStatus per file to the block

  • with_atime (Boolean) (defaults to: false)

    also store files' access times

Yield Parameters:

Returns:



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
95
96
97
# File 'lib/yobi/repository/backup.rb', line 45

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

  execution = if block
    run_restic(argv) { |raw| dispatch_backup_message(raw, &block) }
  else
    run_restic(argv)
  end
  BackupOutcome.new(execution[:exit_code], execution[:output])
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:

Parameters:

  • id (String)

Yields:

Yield Parameters:

  • io (IO)

Yield Returns:

  • (Object)

Returns:



71
72
73
# File 'lib/yobi/repository/cat.rb', line 71

def cat_blob(id, &block)
  run_restic_dump(build_argv("cat", "blob", id), &block)
end

#cat_index(id) ⇒ Hash

restic cat index ID: one index file's own raw contents. IDs come from list(:index).

Parameters:

  • id (String)

Returns:

  • (Hash)


22
23
24
25
# File 'lib/yobi/repository/cat.rb', line 22

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.

Parameters:

Returns:

  • (Hash)


31
32
33
34
35
# File 'lib/yobi/repository/cat.rb', line 31

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_leaksHash

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)


82
83
84
85
# File 'lib/yobi/repository/cat.rb', line 82

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.

Overloads:

  • #cat_pack(id) ⇒ Object

    Parameters:

    • id (String)

    Returns:

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

    Parameters:

    • id (String)

    Returns:

Parameters:

  • id (String)

Yields:

Yield Parameters:

  • io (IO)

Yield Returns:

  • (Object)

Returns:



60
61
62
# File 'lib/yobi/repository/cat.rb', line 60

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.

Parameters:

Returns:

  • (Hash)


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

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.

Parameters:

  • snapshot_id (String, Yobi::Snapshot)

    also accepts Restic's own "snapshotID:subfolder" form directly

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

Returns:

  • (Hash)


43
44
45
46
47
48
49
50
51
52
# File 'lib/yobi/repository/cat.rb', line 43

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.

Parameters:

  • hosts (Array<String>, String) (defaults to: [])

    filter by hostname(s)

  • paths (Array<String>, String) (defaults to: [])

    filter by originally backed-up path(s)

  • read_data (Boolean) (defaults to: false)

    read and verify pack file contents, not just structure

  • read_data_subset (String, nil) (defaults to: nil)

    read and verify only a subset of packs, e.g. "5%"

  • tags (Array<String>, String) (defaults to: [])

    filter by tag(s)

  • with_cache (Boolean) (defaults to: false)

    use the local cache

Returns:



17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/yobi/repository/check.rb', line 17

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
  execution = run_restic(argv)
  CheckOutcome.new(execution[:exit_code], execution[:output])
end

#configHash

restic cat config: the repository's own config document.



78
79
80
81
# File 'lib/yobi/repository.rb', line 78

def config
  execution = run_restic(build_argv("cat", "config"))
  JSON.parse(execution[:output].to_s)
end

#copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, from_repository_file: nil, hosts: [], paths: [], tags: []) ⇒ true

restic copy: replicates snapshots from another repository into this one. Already-copied snapshots are skipped automatically.

Parameters:

  • from_repo (String, Yobi::Repository)

    the source repository, or its URL (given a Repository, its own #url/#password are used automatically)

  • from_password (String, Array, Symbol, #call, nil) (defaults to: nil)

    the source repository's password, same shape as #initialize's password:

  • snapshot_ids (Array<String>, String) (defaults to: [])

    snapshots to copy; all of them if empty

  • from_key_hint (String, nil) (defaults to: nil)
  • from_repository_file (String, nil) (defaults to: nil)

    read the source repository's URL from a file

  • hosts (Array<String>, String) (defaults to: [])

    filter by hostname(s)

  • paths (Array<String>, String) (defaults to: [])

    filter by originally backed-up path(s)

  • tags (Array<String>, String) (defaults to: [])

    filter by tag(s)

Returns:

  • (true)


19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/yobi/repository/copy.rb', line 19

def copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil,
  from_repository_file: nil, hosts: [], paths: [], tags: [])
  if from_repo.is_a?(Repository)
    from_password = from_repo.password if from_password.nil?
    from_repo = from_repo.url
  end

  argv = build_argv("copy", snapshot_ids) do |a|
    a.flag(:from_repo, from_repo)
    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_repository_file, from_repository_file) unless from_repository_file.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) ⇒ Yobi::DiffOutcome

restic diff: compares two snapshots.

Parameters:

  • from (String)

    snapshot ID

  • to (String)

    snapshot ID

  • metadata (Boolean) (defaults to: false)

    also report metadata-only changes (access mode, timestamps, ...)

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

Returns:



14
15
16
17
18
19
20
# File 'lib/yobi/repository/diff.rb', line 14

def diff(from:, to:, metadata: false)
  argv = build_argv("diff", from, to) do |a|
    a.flag(:metadata) if 
  end
  execution = run_restic(argv)
  DiffOutcome.new(execution[:exit_code], execution[:output])
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.

Parameters:

  • snapshot_id (String)

    also accepts Restic's own "snapshotID:subfolder" form directly

  • file (String)

    path within the snapshot to extract; "/" dumps the whole snapshot

  • target (String, nil) (defaults to: nil)

    write straight to this file path instead of yielding a block

  • archive (String, Symbol, nil) (defaults to: nil)

    "tar" (default) or "zip", when file is a folder

  • hosts (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id is "latest"

  • paths (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id is "latest"

  • tags (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id is "latest"

Yield Parameters:

  • io (IO)

Returns:

  • (true)

    if target: was given

  • (Yobi::IOHandle)

    if no block is given

Raises:

  • (ArgumentError)

    if given both target: and 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, 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}

Returns:

  • (Hash{String => String})


35
36
37
38
39
# File 'lib/yobi/repository.rb', line 35

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: []) ⇒ Enumerable<Yobi::MatchesPerSnapshot>

restic find: searches for files/directories across snapshots by name pattern.

Parameters:

  • patterns (Array<String>, String)

    glob patterns to match

  • blob (Boolean) (defaults to: false)

    match blob IDs instead of file names

  • pack (Boolean) (defaults to: false)

    match pack IDs instead of file names

  • tree (Boolean) (defaults to: false)

    match tree IDs instead of file names

  • hosts (Array<String>, String) (defaults to: [])

    filter by hostname(s)

  • human_readable (Boolean) (defaults to: false)

    format sizes for humans

  • ignore_case (Boolean) (defaults to: false)

    case-insensitive matching

  • long (Boolean) (defaults to: false)

    include long listing format

  • newest (String, nil) (defaults to: nil)

    only consider snapshots at or before this time

  • oldest (String, nil) (defaults to: nil)

    only consider snapshots at or after this time

  • paths (Array<String>, String) (defaults to: [])

    filter by originally backed-up path(s)

  • reverse (Boolean) (defaults to: false)

    reverse sort order

  • show_pack_id (Boolean) (defaults to: false)

    include the pack ID each match is stored in

  • snapshot_ids (Array<String>, String) (defaults to: [])

    restrict to these snapshot ID(s)

  • tags (Array<String>, String) (defaults to: [])

    filter by tag(s)

Returns:



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/yobi/repository/find.rb', line 28

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)
  FindMatches.new(execution[:output])
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) ⇒ Enumerable<Yobi::ForgetGroup>

restic forget: applies a retention policy, removing snapshots that don't match any keep_* rule.

Parameters:

  • keep_last (Integer, nil) (defaults to: nil)

    always keep this many of the most recent snapshots

  • keep_hourly (Integer, nil) (defaults to: nil)
  • keep_daily (Integer, nil) (defaults to: nil)
  • keep_weekly (Integer, nil) (defaults to: nil)
  • keep_monthly (Integer, nil) (defaults to: nil)
  • keep_yearly (Integer, nil) (defaults to: nil)
  • keep_within (String, nil) (defaults to: nil)

    keep all snapshots within this duration, e.g. "30d"

  • keep_within_hourly (String, nil) (defaults to: nil)
  • keep_within_daily (String, nil) (defaults to: nil)
  • keep_within_weekly (String, nil) (defaults to: nil)
  • keep_within_monthly (String, nil) (defaults to: nil)
  • keep_within_yearly (String, nil) (defaults to: nil)
  • keep_tags (Array<String>, String) (defaults to: [])

    always keep snapshots carrying any of these tags

  • hosts (Array<String>, String) (defaults to: [])

    filter by hostname(s)

  • tags (Array<String>, String) (defaults to: [])

    filter by tag(s)

  • paths (Array<String>, String) (defaults to: [])

    filter by originally backed-up path(s)

  • compact (Boolean) (defaults to: false)

    compact the printed policy summary

  • group_by (String, nil) (defaults to: nil)

    grouping used to apply the policy, e.g. "host,paths"

  • dry_run (Boolean) (defaults to: false)

    report what would happen without doing it

  • prune (Boolean) (defaults to: false)

    also reclaim disk space (equivalent to restic forget --prune)

  • unsafe_allow_remove_all (Boolean) (defaults to: false)

    allow removing every snapshot

  • max_unused (String, nil) (defaults to: nil)

    passed through to the implied prune, e.g. "10%"

  • max_repack_size (String, nil) (defaults to: nil)

    passed through to the implied prune

  • repack_cacheable_only (Boolean) (defaults to: false)

    passed through to the implied prune

  • repack_uncompressed (Boolean) (defaults to: false)

    passed through to the implied prune

  • repack_smaller_than (String, nil) (defaults to: nil)

    passed through to the implied prune

Returns:



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
74
# File 'lib/yobi/repository/forget.rb', line 38

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)
  ForgetGroups.new(execution[:exit_code], execution[:output])
end

#init(copy_chunker_params: false, from_insecure_no_password: false, from_key_hint: nil, from_password_command: nil, from_password_file: nil, from_repo: nil, from_repository_file: nil, repository_version: nil) ⇒ Hash

restic init: creates the repository at #url.

Parameters:

  • copy_chunker_params (Boolean) (defaults to: false)

    copy chunker parameters from from_repo:/from_repository_file:

  • from_insecure_no_password (Boolean) (defaults to: false)
  • from_key_hint (String, nil) (defaults to: nil)
  • from_password_command (String, nil) (defaults to: nil)
  • from_password_file (String, nil) (defaults to: nil)
  • from_repo (String, nil) (defaults to: nil)
  • from_repository_file (String, nil) (defaults to: nil)
  • repository_version (String, nil) (defaults to: nil)

Returns:

  • (Hash)

    Restic's own "initialized" message



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/yobi/repository.rb', line 57

def init(copy_chunker_params: false, from_insecure_no_password: false, from_key_hint: nil,
  from_password_command: nil, from_password_file: nil, from_repo: nil, from_repository_file: nil,
  repository_version: nil)
  argv = build_argv("init") do |a|
    a.flag(:copy_chunker_params) if copy_chunker_params
    a.flag(:from_insecure_no_password) if from_insecure_no_password
    a.flag(:from_key_hint, from_key_hint) unless from_key_hint.nil?
    a.flag(:from_password_command, from_password_command) unless from_password_command.nil?
    a.flag(:from_password_file, from_password_file) unless from_password_file.nil?
    a.flag(:from_repo, from_repo) unless from_repo.nil?
    a.flag(:from_repository_file, from_repository_file) unless from_repository_file.nil?
    a.flag(:repository_version, repository_version) unless repository_version.nil?
  end
  execution = run_restic(argv)
  JSON.parse(execution[:output].to_s)
end

#inspectString

Returns:

  • (String)


42
43
44
# File 'lib/yobi/repository.rb', line 42

def inspect
  "#<#{self.class} url=#{url.inspect} password=#{redacted_password.inspect} backend_credentials=#{redacted_backend_credentials.inspect}>"
end

#key_add(host: nil, user: nil, new_insecure_no_password: false, new_password_file: nil) ⇒ true Also known as: add_key

restic key add: creates a new key (password) for this repository.

Parameters:

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

    hostname to record on the new key

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

    username to record on the new key

  • new_insecure_no_password (Boolean) (defaults to: false)

    create the new key with an empty password

  • new_password_file (String, nil) (defaults to: nil)

    path to a file containing the new key's password

  • host: (String, nil) (defaults to: nil)
  • user: (String, nil) (defaults to: nil)
  • new_insecure_no_password: (Boolean) (defaults to: false)
  • new_password_file: (String, nil) (defaults to: nil)

Returns:

  • (true)


16
17
18
19
20
21
22
23
24
25
# File 'lib/yobi/repository/key.rb', line 16

def key_add(host: nil, user: nil, new_insecure_no_password: false, new_password_file: nil)
  argv = build_argv("key", "add") do |a|
    a.flag(:host, host) unless host.nil?
    a.flag(:user, user) unless user.nil?
    a.flag(:new_insecure_no_password) if new_insecure_no_password
    a.flag(:new_password_file, new_password_file) unless new_password_file.nil?
  end
  run_restic(argv)
  true
end

#key_listEnumerable<Yobi::Key> Also known as: keys

restic key list: lists every key (password) associated with this repository.

Returns:



31
32
33
34
# File 'lib/yobi/repository/key.rb', line 31

def key_list
  execution = run_restic(build_argv("key", "list"))
  Keys.new(execution[:output])
end

#key_passwd(host: nil, user: nil, new_insecure_no_password: false, new_password_file: nil) ⇒ true Also known as: change_password

restic key passwd: creates a new key for this repository and removes the key currently in use. Doesn't mutate this Repository instance's own password:. Build a new Repository with the rotated password to keep talking to this repository afterward.

Parameters:

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

    hostname to record on the new key

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

    username to record on the new key

  • new_insecure_no_password (Boolean) (defaults to: false)

    set the new key's password to empty

  • new_password_file (String, nil) (defaults to: nil)

    path to a file containing the new key's password

  • host: (String, nil) (defaults to: nil)
  • user: (String, nil) (defaults to: nil)
  • new_insecure_no_password: (Boolean) (defaults to: false)
  • new_password_file: (String, nil) (defaults to: nil)

Returns:

  • (true)


47
48
49
50
51
52
53
54
55
56
# File 'lib/yobi/repository/key.rb', line 47

def key_passwd(host: nil, user: nil, new_insecure_no_password: false, new_password_file: nil)
  argv = build_argv("key", "passwd") do |a|
    a.flag(:host, host) unless host.nil?
    a.flag(:user, user) unless user.nil?
    a.flag(:new_insecure_no_password) if new_insecure_no_password
    a.flag(:new_password_file, new_password_file) unless new_password_file.nil?
  end
  run_restic(argv)
  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.

Parameters:

  • id (String)
  • id: (String)

Returns:

  • (true)


65
66
67
68
# File 'lib/yobi/repository/key.rb', line 65

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

Parameters:

  • type (Symbol, String)

    :blobs, :packs, :index, :snapshots, :keys, or :locks

Returns:

  • (Array<String>)


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.

Parameters:

  • snapshot_id (String)

    also accepts "latest"

  • dirs (Array<String>, String) (defaults to: [])

    restrict to these directories within the snapshot

  • hosts (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id is "latest"

  • human_readable (Boolean) (defaults to: false)

    format sizes for humans

  • long (Boolean) (defaults to: false)

    include long listing format

  • paths (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id is "latest"

  • recursive (Boolean) (defaults to: false)

    recurse into subdirectories

  • reverse (Boolean) (defaults to: false)

    reverse sort order

  • sort (String, nil) (defaults to: nil)

    sort key, e.g. "size"

  • tags (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id is "latest"

Returns:



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/yobi/repository/ls.rb', line 22

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
  execution = run_restic(argv)
  LsOutcome.new(execution[:exit_code], execution[:output])
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.

Parameters:

  • names (Array<String>, String) (defaults to: [])

    migration names to apply; only lists available ones if empty

  • force (Boolean) (defaults to: false)

    reapply a migration already marked as applied

  • 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) {|mount| ... } ⇒ Yobi::Mount, Object

restic mount: serves this repository as a read-only FUSE filesystem at mountpoint, which must already exist.

Without a block, returns a Mount once Restic reports itself ready; call #stop yourself once done. With one, yields the Mount and stops it automatically once the block returns or raises.

Parameters:

  • mountpoint (String)

    must already exist

  • hosts (Array<String>, String) (defaults to: [])

    restrict which snapshots appear under snapshots/

  • paths (Array<String>, String) (defaults to: [])

    restrict which snapshots appear under snapshots/

  • tags (Array<String>, String) (defaults to: [])

    restrict which snapshots appear under snapshots/

  • allow_other (Boolean) (defaults to: false)

    allow other users to access the mount

  • no_default_permissions (Boolean) (defaults to: false)

    don't check file permissions

  • owner_root (Boolean) (defaults to: false)

    mount files as owned by root

  • path_templates (Array<String>, String) (defaults to: [])

    directory naming scheme(s) under snapshots/

  • time_template (String, nil) (defaults to: nil)

    directory naming scheme for time-based paths

  • ready_timeout (Numeric) (defaults to: 10)

    seconds to wait for Restic's readiness message before raising

Yield Parameters:

Returns:

  • (Yobi::Mount)

    if no block is given

  • (Object)

    the block's own return value, otherwise

Raises:



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/yobi/repository/mount.rb', line 29

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) ⇒ true

restic prune: removes data no longer referenced by any snapshot.

Parameters:

  • dry_run (Boolean) (defaults to: false)

    report what would happen without doing it

  • max_repack_size (String, nil) (defaults to: nil)

    limit the amount of data repacked in this run

  • max_unused (String, nil) (defaults to: nil)

    target maximum unused space after pruning, e.g. "10%"

  • repack_cacheable_only (Boolean) (defaults to: false)

    only repack packs that are cached locally

  • repack_smaller_than (String, nil) (defaults to: nil)

    also repack packs smaller than this size

  • repack_uncompressed (Boolean) (defaults to: false)

    repack packs not yet using compression

  • unsafe_recover_no_free_space (String, nil) (defaults to: nil)

    proceed even without enough free space, at the given risk acknowledgement

Returns:

  • (true)


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

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


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.

Parameters:

  • read_all_packs (Boolean) (defaults to: false)

    read every pack file fully instead of just headers

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

Returns:

  • (true)


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.

Parameters:

  • ids (Array<String>, String)

    pack IDs

  • ids: (Array[String], String)

Returns:

  • (true)


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.

Parameters:

  • snapshot_ids (Array<String>, String) (defaults to: [])

    snapshots to repair; all of them if empty

  • dry_run (Boolean) (defaults to: false)

    report what would happen without doing it

  • forget (Boolean) (defaults to: false)

    remove the original damaged snapshots after repairing

  • hosts (Array<String>, String) (defaults to: [])

    filter by hostname(s)

  • paths (Array<String>, String) (defaults to: [])

    filter by originally backed-up path(s)

  • tags (Array<String>, String) (defaults to: [])

    filter by tag(s)

Returns:

  • (true)


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, 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) {|message| ... } ⇒ Yobi::RestoreOutcome

restic restore: extracts a snapshot's contents to a target directory.

Parameters:

  • snapshot_id (String)

    also accepts "latest"

  • target (String)

    directory to restore into

  • excludes (Array<String>, String) (defaults to: [])

    exclude files matching these glob patterns

  • exclude_files (Array<String>, String) (defaults to: [])

    path(s) to file(s) listing exclude patterns

  • exclude_xattrs (Array<String>, String) (defaults to: [])

    exclude extended attributes matching these pattern(s)

  • hosts (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id: is "latest"

  • iexcludes (Array<String>, String) (defaults to: [])

    like excludes, case-insensitive

  • iexclude_files (Array<String>, String) (defaults to: [])

    like exclude_files, case-insensitive

  • iincludes (Array<String>, String) (defaults to: [])

    like includes, case-insensitive

  • iinclude_files (Array<String>, String) (defaults to: [])

    like include_files, case-insensitive

  • includes (Array<String>, String) (defaults to: [])

    only restore files matching these glob patterns

  • include_files (Array<String>, String) (defaults to: [])

    path(s) to file(s) listing include patterns

  • include_xattrs (Array<String>, String) (defaults to: [])

    only restore extended attributes matching these pattern(s)

  • paths (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id: is "latest"

  • tags (Array<String>, String) (defaults to: [])

    only relevant when snapshot_id: is "latest"

  • delete (Boolean) (defaults to: false)

    delete files in target: not present in the snapshot

  • dry_run (Boolean) (defaults to: false)

    report what would happen without doing it

  • overwrite (String, Symbol, nil) (defaults to: nil)

    "always", "if-changed", or "if-newer"

  • ownership_by_name (Boolean) (defaults to: false)

    map ownership by user/group name instead of numeric ID

  • sparse (Boolean) (defaults to: false)

    write sparse files

  • verbose (Boolean) (defaults to: false)

    stream a Yobi::RestoreVerboseStatus per file to the block

  • verify (Boolean) (defaults to: false)

    verify restored file content against the repository

Yield Parameters:

Returns:



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 34

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
  execution = if block
    run_restic(argv) { |raw| dispatch_restore_message(raw, &block) }
  else
    run_restic(argv)
  end
  RestoreOutcome.new(execution[:exit_code], execution[:output])
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.

Parameters:

  • snapshot_ids (Array<String>, String) (defaults to: [])

    snapshots to rewrite; all of them if empty

  • hosts (Array<String>, String) (defaults to: [])

    filter by hostname(s)

  • tags (Array<String>, String) (defaults to: [])

    filter by tag(s)

  • paths (Array<String>, String) (defaults to: [])

    filter by originally backed-up path(s)

  • excludes (Array<String>, String) (defaults to: [])

    exclude files matching these glob patterns

  • exclude_files (Array<String>, String) (defaults to: [])

    path(s) to file(s) listing exclude patterns

  • iexcludes (Array<String>, String) (defaults to: [])

    like excludes, case-insensitive

  • iexclude_files (Array<String>, String) (defaults to: [])

    like exclude_files, case-insensitive

  • includes (Array<String>, String) (defaults to: [])

    only include files matching these glob patterns

  • include_files (Array<String>, String) (defaults to: [])

    path(s) to file(s) listing include patterns

  • iincludes (Array<String>, String) (defaults to: [])

    like includes, case-insensitive

  • iinclude_files (Array<String>, String) (defaults to: [])

    like include_files, case-insensitive

  • dry_run (Boolean) (defaults to: false)

    report what would happen without doing it

  • forget (Boolean) (defaults to: false)

    remove the original snapshots afterward, instead of tagging the new ones "rewrite" and keeping both

  • new_host (String, nil) (defaults to: nil)

    change the recorded hostname

  • new_time (String, nil) (defaults to: nil)

    change the recorded timestamp

  • snapshot_summary (Boolean) (defaults to: false)

    regenerate the snapshot summary

Returns:

  • (true)


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, 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) ⇒ Enumerable<Yobi::Snapshot>

restic snapshots: lists snapshots, optionally filtered.

Parameters:

  • tags (Array<String>, String) (defaults to: [])

    filter by tag(s)

  • hosts (Array<String>, String) (defaults to: [])

    filter by hostname(s)

  • paths (Array<String>, String) (defaults to: [])

    filter by originally backed-up path(s)

  • compact (Boolean) (defaults to: false)

    compact the printed listing

  • group_by (String, nil) (defaults to: nil)

    group results, e.g. "host"

  • latest (Integer, nil) (defaults to: nil)

    limit to the N most recent per group

Returns:



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, 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)
  Snapshots.new(execution[:output])
end

#stats(snapshot_ids: [], hosts: [], mode: nil, paths: [], tags: []) ⇒ Hash

restic stats: accumulates statistics about the repository's data.

Parameters:

  • snapshot_ids (Array<String>, String) (defaults to: [])

    restrict to these snapshot ID(s); the whole repository if empty

  • hosts (Array<String>, String) (defaults to: [])

    filter by hostname(s)

  • mode (String, Symbol, nil) (defaults to: nil)

    "restore-size"/:restore_size (default), "files-by-contents"/:files_by_contents, "blobs-per-file"/:blobs_per_file, or "raw-data"/:raw_data

  • paths (Array<String>, String) (defaults to: [])

    filter by originally backed-up path(s)

  • tags (Array<String>, String) (defaults to: [])

    filter by tag(s)

Returns:

  • (Hash)

    "total_size", "total_file_count", "snapshots_count", ...

Raises:

  • (ArgumentError)

    if mode: isn't one of the values listed above



23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/yobi/repository/stats.rb', line 23

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

Parameters:

  • snapshot_ids (Array<String>, String) (defaults to: [])

    snapshots to modify; all matching hosts:/paths: if empty

  • add (Array<String>, String) (defaults to: [])

    tags to add, keeping existing ones

  • remove (Array<String>, String) (defaults to: [])

    tags to remove

  • set (Array<String>, String) (defaults to: [])

    replace all tags with exactly this set (exclusive with add:/remove:)

  • tags (Array<String>, String) (defaults to: [])

    filter which snapshots to modify, by their current tags

  • hosts (Array<String>, String) (defaults to: [])

    filter which snapshots to modify, by hostname

  • paths (Array<String>, String) (defaults to: [])

    filter which snapshots to modify, by originally backed-up path

Returns:



20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/yobi/repository/tag.rb', line 20

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
  execution = run_restic(argv)
  TagOutcome.new(execution[:exit_code], execution[:output])
end

#unlock(remove_all: false) ⇒ true

restic unlock: removes stale locks left by other Restic processes.

Parameters:

  • remove_all (Boolean) (defaults to: false)

    remove every lock, not just stale ones

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

Returns:

  • (true)


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