Class: Yobi::Restic
- Inherits:
-
Object
- Object
- Yobi::Restic
- Defined in:
- lib/yobi/restic.rb,
lib/yobi/repository/mount.rb,
sig/yobi.rbs
Overview
A specific Restic binary, plus its process-level settings that apply regardless of which repository is being operated on. Repository-specific identity (url, credentials) lives on Yobi::Repository instead.
Constant Summary collapse
- MINIMUM_VERSION =
The oldest Restic version #run/#run_dump/#run_mount verify the installed binary meets before executing a real command. See Yobi::UnsupportedResticVersion.
Gem::Version.new("0.18.0")
- ALLOWED_ENV_VARS =
Env vars #inspect shows in the clear; anything else is redacted.
%w[ # :nodoc: RESTIC_REPOSITORY RESTIC_CACHE_DIR RESTIC_COMPRESSION RESTIC_PACK_SIZE RESTIC_READ_CONCURRENCY RESTIC_HOST RESTIC_PROGRESS_FPS RESTIC_CACERT RESTIC_TLS_CLIENT_CERT RESTIC_KEY_HINT TMPDIR TMP AWS_DEFAULT_REGION AWS_SHARED_CREDENTIALS_FILE AZURE_ENDPOINT_SUFFIX AZURE_FORCE_CLI_CREDENTIAL RCLONE_BWLIMIT].to_set.freeze
- READY_LINE =
:nodoc:
"Now serving the repository at"
Instance Attribute Summary collapse
-
#cacert ⇒ String?
$RESTIC_CACERT. -
#cache_dir ⇒ String?
$RESTIC_CACHE_DIR. -
#cleanup_cache ⇒ Boolean
--cleanup-cache. -
#compression ⇒ String?
$RESTIC_COMPRESSION. -
#host ⇒ String?
$RESTIC_HOST. -
#http_user_agent ⇒ String?
--http-user-agent. -
#key_hint ⇒ String?
$RESTIC_KEY_HINT. -
#limit_download ⇒ String?
--limit-download. -
#limit_upload ⇒ String?
--limit-upload. -
#no_cache ⇒ Boolean
--no-cache. -
#no_extra_verify ⇒ Boolean
--no-extra-verify. -
#no_lock ⇒ Boolean
--no-lock. -
#options ⇒ Array[String]
--option, one per element. -
#pack_size ⇒ Integer?
$RESTIC_PACK_SIZE. -
#progress_fps ⇒ Integer?
$RESTIC_PROGRESS_FPS. -
#quiet ⇒ Boolean
--quiet. -
#read_concurrency ⇒ Integer?
$RESTIC_READ_CONCURRENCY. -
#restic_path ⇒ String
Path to the Restic binary.
-
#retry_lock ⇒ String?
--retry-lock. -
#stuck_request_timeout ⇒ String?
--stuck-request-timeout. -
#tls_client_cert ⇒ String?
$RESTIC_TLS_CLIENT_CERT.
Class Method Summary collapse
-
.dispatch(execution) ⇒ execution
Maps Restic's exit codes to Yobi's typed errors.
Instance Method Summary collapse
-
#append_global_flags(a) ⇒ void
Appends the CLI-only global flags (the ones above with no env var equivalent) to a builder.
-
#cache(cleanup: false, max_age: nil, no_size: false) ⇒ true
restic cache: lists and optionally cleans local cache directories. -
#ensure_minimum_version! ⇒ void
Verifies the installed Restic binary meets MINIMUM_VERSION, once per instance (memoized).
-
#env ⇒ Hash[String, String]
The env-var-backed settings above, computed fresh from their current accessor values on every call.
-
#initialize(restic_path = nil, env: {}, cache_dir: nil, compression: nil, pack_size: nil, read_concurrency: nil, host: nil, progress_fps: nil, cacert: nil, tls_client_cert: nil, key_hint: nil, limit_download: nil, limit_upload: nil, retry_lock: nil, no_lock: false, no_cache: false, cleanup_cache: false, no_extra_verify: false, stuck_request_timeout: nil, options: [], http_user_agent: nil, quiet: false) ⇒ Restic
constructor
Builds a Restic executor.
-
#inspect ⇒ Object
Redacts sensitive env values so a stray +pp+/+puts+/log call never prints a credential in plaintext.
-
#run(argv, extra_env: {}, skip_version_check: false, output: nil, &block) ⇒ void
Runs
argvagainst this Restic binary, mergingextra_env:with this instance's own global env. -
#run_dump(argv, extra_env: {}) {|io| ... } ⇒ Object
For commands whose success output is raw bytes with no JSON message framing (currently only Repository#dump).
-
#run_mount(argv, mountpoint:, extra_env: {}, ready_timeout: 10) ⇒ MountHandle
For Yobi::Repository#mount.
-
#version ⇒ ResticVersion
restic version: the installed binary's own version info.
Constructor Details
#initialize(restic_path = nil, env: {}, cache_dir: nil, compression: nil, pack_size: nil, read_concurrency: nil, host: nil, progress_fps: nil, cacert: nil, tls_client_cert: nil, key_hint: nil, limit_download: nil, limit_upload: nil, retry_lock: nil, no_lock: false, no_cache: false, cleanup_cache: false, no_extra_verify: false, stuck_request_timeout: nil, options: [], http_user_agent: nil, quiet: false) ⇒ Restic
Builds a Restic executor. restic_path defaults to $RESTIC_PATH,
then "restic". env: is extra env vars merged over the named
settings below (env-var-backed accessors like cache_dir:,
compression:, etc.).
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/restic.rb', line 72 def initialize(restic_path = nil, env: {}, cache_dir: nil, compression: nil, pack_size: nil, read_concurrency: nil, host: nil, progress_fps: nil, cacert: nil, tls_client_cert: nil, key_hint: nil, limit_download: nil, limit_upload: nil, retry_lock: nil, no_lock: false, no_cache: false, cleanup_cache: false, no_extra_verify: false, stuck_request_timeout: nil, options: [], http_user_agent: nil, quiet: false) @restic_path = restic_path || ENV.fetch("RESTIC_PATH", "restic") @env = env @cache_dir = cache_dir @compression = compression @pack_size = pack_size @read_concurrency = read_concurrency @host = host @progress_fps = progress_fps @cacert = cacert @tls_client_cert = tls_client_cert @key_hint = key_hint @limit_download = limit_download @limit_upload = limit_upload @retry_lock = retry_lock @no_lock = no_lock @no_cache = no_cache @cleanup_cache = cleanup_cache @no_extra_verify = no_extra_verify @stuck_request_timeout = stuck_request_timeout @options = @http_user_agent = http_user_agent @quiet = quiet end |
Instance Attribute Details
#cacert ⇒ String?
$RESTIC_CACERT
40 41 42 |
# File 'lib/yobi/restic.rb', line 40 def cacert @cacert end |
#cache_dir ⇒ String?
$RESTIC_CACHE_DIR
28 29 30 |
# File 'lib/yobi/restic.rb', line 28 def cache_dir @cache_dir end |
#cleanup_cache ⇒ Boolean
--cleanup-cache
56 57 58 |
# File 'lib/yobi/restic.rb', line 56 def cleanup_cache @cleanup_cache end |
#compression ⇒ String?
$RESTIC_COMPRESSION
30 31 32 |
# File 'lib/yobi/restic.rb', line 30 def compression @compression end |
#host ⇒ String?
$RESTIC_HOST
36 37 38 |
# File 'lib/yobi/restic.rb', line 36 def host @host end |
#http_user_agent ⇒ String?
--http-user-agent
64 65 66 |
# File 'lib/yobi/restic.rb', line 64 def http_user_agent @http_user_agent end |
#key_hint ⇒ String?
$RESTIC_KEY_HINT
44 45 46 |
# File 'lib/yobi/restic.rb', line 44 def key_hint @key_hint end |
#limit_download ⇒ String?
--limit-download
46 47 48 |
# File 'lib/yobi/restic.rb', line 46 def limit_download @limit_download end |
#limit_upload ⇒ String?
--limit-upload
48 49 50 |
# File 'lib/yobi/restic.rb', line 48 def limit_upload @limit_upload end |
#no_cache ⇒ Boolean
--no-cache
54 55 56 |
# File 'lib/yobi/restic.rb', line 54 def no_cache @no_cache end |
#no_extra_verify ⇒ Boolean
--no-extra-verify
58 59 60 |
# File 'lib/yobi/restic.rb', line 58 def no_extra_verify @no_extra_verify end |
#no_lock ⇒ Boolean
--no-lock
52 53 54 |
# File 'lib/yobi/restic.rb', line 52 def no_lock @no_lock end |
#options ⇒ Array[String]
--option, one per element.
62 63 64 |
# File 'lib/yobi/restic.rb', line 62 def @options end |
#pack_size ⇒ Integer?
$RESTIC_PACK_SIZE
32 33 34 |
# File 'lib/yobi/restic.rb', line 32 def pack_size @pack_size end |
#progress_fps ⇒ Integer?
$RESTIC_PROGRESS_FPS
38 39 40 |
# File 'lib/yobi/restic.rb', line 38 def progress_fps @progress_fps end |
#quiet ⇒ Boolean
--quiet
66 67 68 |
# File 'lib/yobi/restic.rb', line 66 def quiet @quiet end |
#read_concurrency ⇒ Integer?
$RESTIC_READ_CONCURRENCY
34 35 36 |
# File 'lib/yobi/restic.rb', line 34 def read_concurrency @read_concurrency end |
#restic_path ⇒ String
Path to the Restic binary.
26 27 28 |
# File 'lib/yobi/restic.rb', line 26 def restic_path @restic_path end |
#retry_lock ⇒ String?
--retry-lock
50 51 52 |
# File 'lib/yobi/restic.rb', line 50 def retry_lock @retry_lock end |
#stuck_request_timeout ⇒ String?
--stuck-request-timeout
60 61 62 |
# File 'lib/yobi/restic.rb', line 60 def stuck_request_timeout @stuck_request_timeout end |
#tls_client_cert ⇒ String?
$RESTIC_TLS_CLIENT_CERT
42 43 44 |
# File 'lib/yobi/restic.rb', line 42 def tls_client_cert @tls_client_cert end |
Class Method Details
.dispatch(execution) ⇒ execution
Maps Restic's exit codes to Yobi's typed errors. Returns execution
unchanged on 0/3.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/yobi/restic.rb', line 185 def self.dispatch(execution) # :nodoc: case execution[:exit_code] when 0, 3 execution when 10 raise Yobi::RepositoryNotFound, execution when 11 raise Yobi::RepositoryLocked, execution when 12 raise Yobi::AuthenticationFailed, execution else raise Yobi::ResticCommandFailed, execution end end |
Instance Method Details
#append_global_flags(a) ⇒ void
This method returns an undefined value.
Appends the CLI-only global flags (the ones above with no env var equivalent) to a builder. Called from both #build_argv and Repository#build_argv.
121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/yobi/restic.rb', line 121 def append_global_flags(a) # :nodoc: a.flag(:limit_download, limit_download) unless limit_download.nil? a.flag(:limit_upload, limit_upload) unless limit_upload.nil? a.flag(:retry_lock, retry_lock) unless retry_lock.nil? a.flag(:no_lock) if no_lock a.flag(:no_cache) if no_cache a.flag(:cleanup_cache) if cleanup_cache a.flag(:no_extra_verify) if no_extra_verify a.flag(:stuck_request_timeout, stuck_request_timeout) unless stuck_request_timeout.nil? a.repeat_flag(:option, ) a.flag(:http_user_agent, http_user_agent) unless http_user_agent.nil? a.flag(:quiet) if quiet end |
#cache(cleanup: false, max_age: nil, no_size: false) ⇒ true
restic cache: lists and optionally cleans local cache directories.
Not repository-scoped. cleanup: triggers --cleanup, max_age:
sets --max-age, no_size: triggers --no-size. Returns true.
151 152 153 154 155 156 157 158 159 |
# File 'lib/yobi/restic.rb', line 151 def cache(cleanup: false, max_age: nil, no_size: false) argv = build_argv("cache") do |a| a.flag(:cleanup) if cleanup a.flag(:max_age, max_age) unless max_age.nil? a.flag(:no_size) if no_size end run(argv) true end |
#ensure_minimum_version! ⇒ void
This method returns an undefined value.
Verifies the installed Restic binary meets MINIMUM_VERSION, once per instance (memoized). #run/#run_dump/#run_mount call this automatically before executing a real command; public so a caller can also call it explicitly to fail fast. Raises Yobi::UnsupportedResticVersion when too old.
239 240 241 242 243 244 245 246 247 |
# File 'lib/yobi/restic.rb', line 239 def ensure_minimum_version! return if defined?(@version_checked) @version_checked = true installed = Gem::Version.new(version["version"]) return if installed >= MINIMUM_VERSION raise Yobi::UnsupportedResticVersion.new(installed_version: installed.to_s, minimum_version: MINIMUM_VERSION.to_s) end |
#env ⇒ Hash[String, String]
The env-var-backed settings above, computed fresh from their current
accessor values on every call. env: given at construction wins over
any of these on key collision.
104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/yobi/restic.rb', line 104 def env { "RESTIC_CACHE_DIR" => cache_dir, "RESTIC_COMPRESSION" => compression, "RESTIC_PACK_SIZE" => pack_size&.to_s, "RESTIC_READ_CONCURRENCY" => read_concurrency&.to_s, "RESTIC_HOST" => host, "RESTIC_PROGRESS_FPS" => progress_fps&.to_s, "RESTIC_CACERT" => cacert, "RESTIC_TLS_CLIENT_CERT" => tls_client_cert, "RESTIC_KEY_HINT" => key_hint }.compact.merge(@env) end |
#inspect ⇒ Object
Redacts sensitive env values so a stray +pp+/+puts+/log call never prints a credential in plaintext.
137 138 139 |
# File 'lib/yobi/restic.rb', line 137 def inspect "#<#{self.class} restic_path=#{restic_path.inspect} env=#{redacted_env.inspect}>" end |
#run(argv, extra_env: {}, skip_version_check: false, output: nil, &block) ⇒ void
This method returns an undefined value.
Runs argv against this Restic binary, merging extra_env: with
this instance's own global env. output: takes a caller-configured
Yobi::ResticOutput (e.g. with its own transform:); passing one forces
streaming even without a block. When a block is given, each parsed
message is yielded live as the command runs.
skip_version_check: is used internally by #version to avoid recursing
into #ensure_minimum_version!
Returns {exit_code:, output:, argv:} on exit code 0 or 3.
Raises Yobi::RepositoryNotFound, Yobi::RepositoryLocked,
Yobi::AuthenticationFailed, or Yobi::ResticCommandFailed otherwise.
173 174 175 176 177 178 179 180 181 |
# File 'lib/yobi/restic.rb', line 173 def run(argv, extra_env: {}, skip_version_check: false, output: nil, &block) # :nodoc: ensure_minimum_version! unless skip_version_check execution = if output || block execute_with_streaming(argv, extra_env, output: output, &block) else execute(argv, extra_env) end self.class.dispatch(execution) end |
#run_dump(argv, extra_env:) ⇒ Object #run_dump(argv, extra_env:) ⇒ IOHandle
For commands whose success output is raw bytes with no JSON message framing (currently only Repository#dump). Spawns Restic with stdout wired to a pipe.
Without a block, returns a Yobi::IOHandle immediately; closing, reaping, and exit-code dispatch are the caller's own responsibility. With one, yields the pipe's read end; it's always closed and the process always reaped once the block returns or raises, before any exit-code dispatch runs.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/yobi/restic.rb', line 209 def run_dump(argv, extra_env: {}) # :nodoc: ensure_minimum_version! output = Yobi::ResticOutput.new read_end, write_end = IO.pipe read_end.binmode pid = Process.spawn(env.merge(extra_env), restic_path, *argv, out: write_end, err: output.file) write_end.close return Yobi::IOHandle.new(read_end, pid: pid, output: output, argv: argv) unless block_given? begin result = yield read_end ensure read_end.close unless read_end.closed? _, status = Process.wait2(pid) end self.class.dispatch(exit_code: status.exitstatus, output: output, argv: argv) result rescue Errno::ENOENT output.file.close raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv) end |
#run_mount(argv, mountpoint:, extra_env: {}, ready_timeout: 10) ⇒ MountHandle
For Yobi::Repository#mount. Spawns Restic and waits for its readiness
line (or ready_timeout: seconds, or Restic exiting on its own)
before returning a Yobi::MountHandle. Raises Yobi::MountTimeout.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/yobi/repository/mount.rb', line 64 def run_mount(argv, mountpoint:, extra_env: {}, ready_timeout: 10) # :nodoc: ensure_minimum_version! output = Yobi::ResticOutput.new stdin, pipe, wait_thr = Open3.popen2e(env.merge(extra_env), restic_path, *argv) stdin.close case wait_for_ready(pipe, output.file, mountpoint, ready_timeout) when :ready Yobi::MountHandle.new(wait_thr: wait_thr, mountpoint: mountpoint, pipe: pipe, output: output, argv: argv) when :timeout Process.kill("INT", wait_thr.pid) wait_thr.value raise Yobi::MountTimeout.new(argv: argv, timeout: ready_timeout) when :exited status = wait_thr.value self.class.dispatch(exit_code: status.exitstatus, output: output, argv: argv) end rescue Errno::ENOENT output.file.close raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv) end |
#version ⇒ ResticVersion
restic version: the installed binary's own version info. Returns a
Yobi::ResticVersion.
143 144 145 146 |
# File 'lib/yobi/restic.rb', line 143 def version execution = run(build_argv("version"), skip_version_check: true) Yobi::ResticVersion.new(parse_version_output(execution[:output].to_s)) end |