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 =
Restic's own stdout line signaling a mount is ready to serve.
"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) {|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::Mount
-
#version ⇒ Hash
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.
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 119 |
# File 'lib/yobi/restic.rb', line 92 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.
42 43 44 |
# File 'lib/yobi/restic.rb', line 42 def cacert @cacert end |
#cache_dir ⇒ String?
Returns $RESTIC_CACHE_DIR.
30 31 32 |
# File 'lib/yobi/restic.rb', line 30 def cache_dir @cache_dir end |
#cleanup_cache ⇒ Boolean
Returns --cleanup-cache.
58 59 60 |
# File 'lib/yobi/restic.rb', line 58 def cleanup_cache @cleanup_cache end |
#compression ⇒ String?
Returns $RESTIC_COMPRESSION.
32 33 34 |
# File 'lib/yobi/restic.rb', line 32 def compression @compression end |
#host ⇒ String?
Returns $RESTIC_HOST.
38 39 40 |
# File 'lib/yobi/restic.rb', line 38 def host @host end |
#http_user_agent ⇒ String?
Returns --http-user-agent.
66 67 68 |
# File 'lib/yobi/restic.rb', line 66 def http_user_agent @http_user_agent end |
#key_hint ⇒ String?
Returns $RESTIC_KEY_HINT.
46 47 48 |
# File 'lib/yobi/restic.rb', line 46 def key_hint @key_hint end |
#limit_download ⇒ String?
Returns --limit-download.
48 49 50 |
# File 'lib/yobi/restic.rb', line 48 def limit_download @limit_download end |
#limit_upload ⇒ String?
Returns --limit-upload.
50 51 52 |
# File 'lib/yobi/restic.rb', line 50 def limit_upload @limit_upload end |
#no_cache ⇒ Boolean
Returns --no-cache.
56 57 58 |
# File 'lib/yobi/restic.rb', line 56 def no_cache @no_cache end |
#no_extra_verify ⇒ Boolean
Returns --no-extra-verify.
60 61 62 |
# File 'lib/yobi/restic.rb', line 60 def no_extra_verify @no_extra_verify end |
#no_lock ⇒ Boolean
Returns --no-lock.
54 55 56 |
# File 'lib/yobi/restic.rb', line 54 def no_lock @no_lock end |
#options ⇒ Array<String>
Returns --option, one per element.
64 65 66 |
# File 'lib/yobi/restic.rb', line 64 def @options end |
#pack_size ⇒ Integer?
Returns $RESTIC_PACK_SIZE.
34 35 36 |
# File 'lib/yobi/restic.rb', line 34 def pack_size @pack_size end |
#progress_fps ⇒ Integer?
Returns $RESTIC_PROGRESS_FPS.
40 41 42 |
# File 'lib/yobi/restic.rb', line 40 def progress_fps @progress_fps end |
#quiet ⇒ Boolean
Returns --quiet.
68 69 70 |
# File 'lib/yobi/restic.rb', line 68 def quiet @quiet end |
#read_concurrency ⇒ Integer?
Returns $RESTIC_READ_CONCURRENCY.
36 37 38 |
# File 'lib/yobi/restic.rb', line 36 def read_concurrency @read_concurrency end |
#restic_path ⇒ String (readonly)
Returns path to the Restic binary.
28 29 30 |
# File 'lib/yobi/restic.rb', line 28 def restic_path @restic_path end |
#retry_lock ⇒ String?
Returns --retry-lock.
52 53 54 |
# File 'lib/yobi/restic.rb', line 52 def retry_lock @retry_lock end |
#stuck_request_timeout ⇒ String?
Returns --stuck-request-timeout.
62 63 64 |
# File 'lib/yobi/restic.rb', line 62 def stuck_request_timeout @stuck_request_timeout end |
#tls_client_cert ⇒ String?
Returns $RESTIC_TLS_CLIENT_CERT.
44 45 46 |
# File 'lib/yobi/restic.rb', line 44 def tls_client_cert @tls_client_cert end |
Class Method Details
.dispatch(execution) ⇒ Hash
Returns execution, unchanged, on exit code 0/3.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/yobi/restic.rb', line 212 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.
146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/yobi/restic.rb', line 146 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.
180 181 182 183 184 185 186 187 188 |
# File 'lib/yobi/restic.rb', line 180 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.
275 276 277 278 279 280 281 282 283 |
# File 'lib/yobi/restic.rb', line 275 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.
126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/yobi/restic.rb', line 126 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
161 162 163 |
# File 'lib/yobi/restic.rb', line 161 def inspect "#<#{self.class} restic_path=#{restic_path.inspect} env=#{redacted_env.inspect}>" end |
#run(argv, extra_env: {}, skip_version_check: false) {|message| ... } ⇒ Hash
Runs argv against this Restic binary, merging extra_env with this instance's own global env.
199 200 201 202 203 204 205 206 207 |
# File 'lib/yobi/restic.rb', line 199 def run(argv, extra_env: {}, skip_version_check: false, &block) ensure_minimum_version! unless skip_version_check execution = if block execute_with_streaming(argv, extra_env, &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.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/yobi/restic.rb', line 242 def run_dump(argv, extra_env: {}) ensure_minimum_version! file = Tempfile.new("yobi-restic-output") file.unlink read_end, write_end = IO.pipe read_end.binmode pid = Process.spawn(env.merge(extra_env), restic_path, *argv, out: write_end, err: file) write_end.close return Yobi::IOHandle.new(read_end, pid: pid, output_file: file, 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: Yobi::ResticOutput.new(file), argv: argv) result rescue Errno::ENOENT file&.close raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv) end |
#run_mount(argv, mountpoint:, extra_env: {}, ready_timeout: 10) ⇒ Yobi::Mount
For Yobi::Repository#mount. Spawns Restic and waits for its
readiness line (or ready_timeout: seconds, or Restic exiting on
its own) before returning.
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/mount.rb', line 75 def run_mount(argv, mountpoint:, extra_env: {}, ready_timeout: 10) ensure_minimum_version! file = Tempfile.new("yobi-restic-output") file.unlink stdin, output, wait_thr = Open3.popen2e(env.merge(extra_env), restic_path, *argv) stdin.close case wait_for_ready(output, file, mountpoint, ready_timeout) when :ready Yobi::Mount.new(wait_thr: wait_thr, mountpoint: mountpoint, output: output, output_file: file, 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: Yobi::ResticOutput.new(file), argv: argv) end rescue Errno::ENOENT file&.close raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv) end |
#version ⇒ Hash
restic version: the installed binary's own version info.
168 169 170 171 |
# File 'lib/yobi/restic.rb', line 168 def version execution = run(build_argv("version"), skip_version_check: true) JSON.parse(execution[:output].to_s) end |