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 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 UnsupportedResticVersion.
Gem::Version.new("0.17.1")
- ALLOWED_ENV_VARS =
Env vars #inspect shows in the clear; anything else is redacted.
%w[ 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 =
"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
readonly
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) ⇒ Hash
execution, unchanged, on exit code 0/3.
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
A new instance of Restic.
- #inspect ⇒ String
-
#run(argv, extra_env: {}, skip_version_check: false, output: nil) {|message| ... } ⇒ Hash
Runs argv against this Restic binary, merging extra_env with this instance's own global env.
-
#run_dump(argv, extra_env: {}) {|io| ... } ⇒ Yobi::IOHandle, 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) ⇒ Yobi::MountHandle
-
#version ⇒ Yobi::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
Returns a new instance of Restic.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/yobi/restic.rb', line 91 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?
Returns $RESTIC_CACERT.
41 42 43 |
# File 'lib/yobi/restic.rb', line 41 def cacert @cacert end |
#cache_dir ⇒ String?
Returns $RESTIC_CACHE_DIR.
29 30 31 |
# File 'lib/yobi/restic.rb', line 29 def cache_dir @cache_dir end |
#cleanup_cache ⇒ Boolean
Returns --cleanup-cache.
57 58 59 |
# File 'lib/yobi/restic.rb', line 57 def cleanup_cache @cleanup_cache end |
#compression ⇒ String?
Returns $RESTIC_COMPRESSION.
31 32 33 |
# File 'lib/yobi/restic.rb', line 31 def compression @compression end |
#host ⇒ String?
Returns $RESTIC_HOST.
37 38 39 |
# File 'lib/yobi/restic.rb', line 37 def host @host end |
#http_user_agent ⇒ String?
Returns --http-user-agent.
65 66 67 |
# File 'lib/yobi/restic.rb', line 65 def http_user_agent @http_user_agent end |
#key_hint ⇒ String?
Returns $RESTIC_KEY_HINT.
45 46 47 |
# File 'lib/yobi/restic.rb', line 45 def key_hint @key_hint end |
#limit_download ⇒ String?
Returns --limit-download.
47 48 49 |
# File 'lib/yobi/restic.rb', line 47 def limit_download @limit_download end |
#limit_upload ⇒ String?
Returns --limit-upload.
49 50 51 |
# File 'lib/yobi/restic.rb', line 49 def limit_upload @limit_upload end |
#no_cache ⇒ Boolean
Returns --no-cache.
55 56 57 |
# File 'lib/yobi/restic.rb', line 55 def no_cache @no_cache end |
#no_extra_verify ⇒ Boolean
Returns --no-extra-verify.
59 60 61 |
# File 'lib/yobi/restic.rb', line 59 def no_extra_verify @no_extra_verify end |
#no_lock ⇒ Boolean
Returns --no-lock.
53 54 55 |
# File 'lib/yobi/restic.rb', line 53 def no_lock @no_lock end |
#options ⇒ Array<String>
Returns --option, one per element.
63 64 65 |
# File 'lib/yobi/restic.rb', line 63 def @options end |
#pack_size ⇒ Integer?
Returns $RESTIC_PACK_SIZE.
33 34 35 |
# File 'lib/yobi/restic.rb', line 33 def pack_size @pack_size end |
#progress_fps ⇒ Integer?
Returns $RESTIC_PROGRESS_FPS.
39 40 41 |
# File 'lib/yobi/restic.rb', line 39 def progress_fps @progress_fps end |
#quiet ⇒ Boolean
Returns --quiet.
67 68 69 |
# File 'lib/yobi/restic.rb', line 67 def quiet @quiet end |
#read_concurrency ⇒ Integer?
Returns $RESTIC_READ_CONCURRENCY.
35 36 37 |
# File 'lib/yobi/restic.rb', line 35 def read_concurrency @read_concurrency end |
#restic_path ⇒ String (readonly)
Returns path to the Restic binary.
27 28 29 |
# File 'lib/yobi/restic.rb', line 27 def restic_path @restic_path end |
#retry_lock ⇒ String?
Returns --retry-lock.
51 52 53 |
# File 'lib/yobi/restic.rb', line 51 def retry_lock @retry_lock end |
#stuck_request_timeout ⇒ String?
Returns --stuck-request-timeout.
61 62 63 |
# File 'lib/yobi/restic.rb', line 61 def stuck_request_timeout @stuck_request_timeout end |
#tls_client_cert ⇒ String?
Returns $RESTIC_TLS_CLIENT_CERT.
43 44 45 |
# File 'lib/yobi/restic.rb', line 43 def tls_client_cert @tls_client_cert end |
Class Method Details
.dispatch(execution) ⇒ Hash
Returns execution, unchanged, on exit code 0/3.
214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/yobi/restic.rb', line 214 def self.dispatch(execution) 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.
145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/yobi/restic.rb', line 145 def append_global_flags(a) 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.
179 180 181 182 183 184 185 186 187 |
# File 'lib/yobi/restic.rb', line 179 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.
276 277 278 279 280 281 282 283 284 |
# File 'lib/yobi/restic.rb', line 276 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.
125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/yobi/restic.rb', line 125 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 ⇒ String
160 161 162 |
# File 'lib/yobi/restic.rb', line 160 def inspect "#<#{self.class} restic_path=#{restic_path.inspect} env=#{redacted_env.inspect}>" end |
#run(argv, extra_env: {}, skip_version_check: false, output: nil) {|message| ... } ⇒ Hash
Runs argv against this Restic binary, merging extra_env with this instance's own global env.
201 202 203 204 205 206 207 208 209 |
# File 'lib/yobi/restic.rb', line 201 def run(argv, extra_env: {}, skip_version_check: false, output: nil, &block) 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 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.
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/yobi/restic.rb', line 244 def run_dump(argv, extra_env: {}) 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) ⇒ Yobi::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.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/yobi/repository/mount.rb', line 74 def run_mount(argv, mountpoint:, extra_env: {}, ready_timeout: 10) 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 ⇒ Yobi::ResticVersion
restic version: the installed binary's own version info.
167 168 169 170 |
# File 'lib/yobi/restic.rb', line 167 def version execution = run(build_argv("version"), skip_version_check: true) Yobi::ResticVersion.new(parse_version_output(execution[:output].to_s)) end |