Class: Microsandbox::Sandbox

Inherits:
Object
  • Object
show all
Defined in:
lib/microsandbox/sandbox.rb

Overview

A running sandbox (microVM) — the primary entry point of the SDK.

Examples:

Block form (auto-stops on exit)

Microsandbox::Sandbox.create("hello", image: "python") do |sb|
  out = sb.exec("python", ["-c", "print('hi')"])
  puts out.stdout
end

Manual lifecycle

sb = Microsandbox::Sandbox.create("hello", image: "python")
begin
  sb.shell("echo hi")
ensure
  sb.stop
end

Constant Summary collapse

DISK_IMAGE_EXTENSIONS =

Recognized disk-image rootfs extensions, mirroring the upstream DiskImageFormat::from_extension/FromStr set. Used by disk_image_rootfs? to gate the fstype:-vs-OCI check; keep in sync on a runtime-tag bump.

%w[raw qcow2 vmdk].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(native) ⇒ Sandbox

Returns a new instance of Sandbox.



1023
1024
1025
# File 'lib/microsandbox/sandbox.rb', line 1023

def initialize(native)
  @native = native
end

Class Method Details

.build_create_opts(image: nil, cpus: nil, max_cpus: nil, memory: nil, max_memory: nil, env: nil, workdir: nil, shell: nil, user: nil, hostname: nil, labels: nil, scripts: nil, entrypoint: nil, ports: nil, ports_udp: nil, volumes: nil, network: nil, dns: nil, tls: nil, ipv4_pool: nil, ipv6_pool: nil, max_connections: nil, trust_host_cas: nil, patches: nil, from_snapshot: nil, fstype: nil, init: nil, ephemeral: false, log_level: nil, quiet_logs: false, security: nil, root_disk: nil, oci_upper_size: nil, max_duration: nil, idle_timeout: nil, rlimits: nil, pull_policy: nil, registry_auth: nil, registry_insecure: false, registry_ca_certs: nil, secrets: nil, on_secret_violation: nil, detached: false, replace: false, replace_with_timeout: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Shared keyword-option builder for create/create_with_progress.



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/microsandbox/sandbox.rb', line 376

def build_create_opts(image: nil, cpus: nil, max_cpus: nil, memory: nil, max_memory: nil,
  env: nil, workdir: nil,
  shell: nil, user: nil, hostname: nil, labels: nil, scripts: nil,
  entrypoint: nil, ports: nil, ports_udp: nil, volumes: nil, network: nil,
  dns: nil, tls: nil, ipv4_pool: nil, ipv6_pool: nil,
  max_connections: nil, trust_host_cas: nil,
  patches: nil,
  from_snapshot: nil, fstype: nil, init: nil, ephemeral: false,
  log_level: nil, quiet_logs: false, security: nil,
  root_disk: nil, oci_upper_size: nil, max_duration: nil, idle_timeout: nil, rlimits: nil,
  pull_policy: nil, registry_auth: nil, registry_insecure: false,
  registry_ca_certs: nil, secrets: nil, on_secret_violation: nil,
  detached: false, replace: false, replace_with_timeout: nil)
  # A sandbox boots from exactly one rootfs source. The core would reject a
  # contradictory pair, but only after a runtime round-trip; fail fast and
  # clearly here (the Python SDK validates this the same way).
  if image && from_snapshot
    raise ArgumentError, "provide either image: or from_snapshot:, not both"
  end
  if root_disk && oci_upper_size
    raise ArgumentError, "pass either root_disk: or oci_upper_size:, not both"
  end
  Microsandbox.ensure_runtime!
  # `fstype:` names the inner filesystem of a disk-image rootfs, so it only
  # applies when `image:` is a disk-image path (a local path ending in
  # .raw/.qcow2/.vmdk). Routing an OCI ref (e.g. "python") through the
  # disk-image builder would make the core treat it as a host disk path and
  # fail at boot, so reject the combination up front instead of forwarding a
  # value the native layer can't honour.
  if fstype && !disk_image_rootfs?(image)
    raise ArgumentError,
      "fstype: only applies to a disk-image rootfs; image: must be a local " \
      "path ending in .raw, .qcow2, or .vmdk (got #{image.inspect}). " \
      "OCI references auto-detect their filesystem — drop fstype:."
  end
  opts = {}
  opts["image"] = image.to_s if image
  opts["from_snapshot"] = from_snapshot.to_s if from_snapshot
  opts["fstype"] = fstype.to_s if fstype
  opts["cpus"] = Integer(cpus) if cpus
  opts["max_cpus"] = Integer(max_cpus) if max_cpus
  opts["memory"] = Integer(memory) if memory
  opts["max_memory"] = Integer(max_memory) if max_memory
  opts["workdir"] = workdir.to_s if workdir
  opts["shell"] = shell.to_s if shell
  opts["user"] = user.to_s if user
  opts["hostname"] = hostname.to_s if hostname
  opts["env"] = stringify(env) if env
  opts["labels"] = stringify(labels) if labels
  opts["scripts"] = stringify(scripts) if scripts
  opts["entrypoint"] = Array(entrypoint).map(&:to_s) if entrypoint
  opts["ports"] = intify_ports(ports) if ports
  opts["ports_udp"] = intify_ports(ports_udp) if ports_udp
  opts["volumes"] = normalize_volumes(volumes) if volumes
  opts["patches"] = normalize_patches(patches) if patches
  apply_network_opts(opts, network) unless network.nil?
  opts["dns"] = normalize_dns(dns) if dns
  opts["tls"] = normalize_tls(tls) if tls
  opts["ipv4_pool"] = ipv4_pool.to_s if ipv4_pool
  opts["ipv6_pool"] = ipv6_pool.to_s if ipv6_pool
  opts["max_connections"] = Integer(max_connections) if max_connections
  set_bool(opts, "trust_host_cas", trust_host_cas)
  opts["log_level"] = log_level.to_s if log_level
  opts["quiet_logs"] = true if quiet_logs
  opts["security"] = security.to_s if security
  if oci_upper_size
    warn "Microsandbox: oci_upper_size: is deprecated; use root_disk: " \
         "(an Integer size in MiB, or RootDisk.managed/tmpfs/disk)",
      uplevel: 2
    opts["root_disk"] = coerce_root_disk_size(oci_upper_size, "oci_upper_size:")
  elsif root_disk
    opts["root_disk"] = normalize_root_disk(root_disk)
  end
  opts["max_duration"] = Integer(max_duration) if max_duration
  opts["idle_timeout"] = Integer(idle_timeout) if idle_timeout
  opts["rlimits"] = normalize_rlimits(rlimits) if rlimits
  opts["pull_policy"] = pull_policy.to_s if pull_policy
  apply_registry_opts(opts, registry_auth, registry_insecure, registry_ca_certs)
  opts["secrets"] = normalize_secrets(secrets) if secrets
  opts["on_secret_violation"] = normalize_violation(on_secret_violation) if on_secret_violation
  opts["init"] = normalize_init(init) unless init.nil?
  opts["ephemeral"] = true if ephemeral
  opts["detached"] = true if detached
  if replace_with_timeout
    opts["replace_with_timeout"] = coerce_duration(replace_with_timeout, "replace_with_timeout")
  elsif replace
    opts["replace"] = true
  end

  opts
end

.create(name, **kwargs) {|sandbox| ... } ⇒ Sandbox, Object

Create and boot a sandbox.

When a block is given the sandbox is yielded and stopped automatically when the block returns (the block's value is returned); otherwise the live Microsandbox::Sandbox is returned and you are responsible for calling #stop.

Parameters:

  • name (String)

    sandbox name (max 128 UTF-8 bytes)

  • image (String, nil)

    OCI image reference (e.g. "python")

  • cpus (Integer, nil)

    number of vCPUs

  • max_cpus (Integer, nil)

    boot-time maximum vCPU ceiling a later live #modify can grow up to (defaults to cpus; must be >= cpus)

  • memory (Integer, nil)

    memory in MiB

  • max_memory (Integer, nil)

    boot-time maximum memory ceiling (MiB) a later live #modify can grow up to (defaults to memory; must be >= memory)

  • env (Hash, nil)

    environment variables

  • workdir (String, nil)

    working directory inside the guest

  • shell (String, nil)

    default shell (for #shell)

  • user (String, nil)

    default user

  • hostname (String, nil)

    guest hostname

  • labels (Hash, nil)

    metadata labels

  • scripts (Hash, nil)

    named scripts to install

  • entrypoint (Array<String>, nil)

    image entrypoint override

  • ports (Hash, nil)

    host_port => guest_port TCP publications

  • ports_udp (Hash, nil)

    host_port => guest_port UDP publications

  • volumes (Hash, nil)

    guest_path => mount spec. Each value is a host path String (a bind mount), or a Hash: { bind: "/host" }, { named: "vol" }, { tmpfs: true, size_mib: 64 }, or { disk: "/img.raw", format: "raw", fstype: "ext4" }. Any mount may add flags ro:/readonly:, noexec:, nosuid:, nodev:, and (bind/named only) stat_virtualization: (:strict/:relaxed/:off) and host_permissions: (:private/:mirror). A bind mount accepts quota_mib: to override the runtime's default guest-write budget (4 GiB as of v0.5.10); the core rejects it on tmpfs/disk/named (for a named volume, set its quota via Volume.create). Bind/named mounts also accept follow_root_symlinks: true to opt out of the default-on mount-root symlink protection (runtime v0.6.7).

  • network (Array, String, Symbol, NetworkPolicy, Hash, nil)

    network policy. Composable profiles ([:public] (the default), [:public, :private], :host, …), a terminal preset (:none, :allow_all), a NetworkPolicy (e.g. NetworkPolicy.from_profiles, NetworkPolicy.custom), or a Hash describing a custom policy (profiles:, default_egress:, default_ingress:, rules:, deny_domains:, deny_domain_suffixes:). See NetworkPolicy and Rule. The v0.6.6 public_only/non_local presets were removed in runtime v0.6.7 — use [:public] / [:public, :private].

  • dns (Hash, nil)

    custom DNS: { nameservers: [...], rebind_protection: true, query_timeout_ms: 2000 }

  • tls (Hash, nil)

    TLS-interception tuning: { bypass: [...patterns], verify_upstream: true, intercepted_ports: [443, 8443], block_quic: true, upstream_ca_cert:, intercept_ca_cert:, intercept_ca_key: } (paths). Use this to inject secrets: on non-443 ports or to trust a private CA.

  • ipv4_pool (String, nil)

    guest IPv4 address pool CIDR (e.g. "10.0.0.0/24")

  • ipv6_pool (String, nil)

    guest IPv6 address pool CIDR

  • max_connections (Integer, nil)

    cap on concurrent proxied connections

  • trust_host_cas (Boolean, nil)

    trust the host's CA bundle for upstream TLS

  • from_snapshot (String, nil)

    boot from a snapshot name or digest instead of an image (mutually exclusive with image:)

  • fstype (String, nil)

    inner filesystem type (e.g. "ext4") when image: is a disk-image rootfs path whose filesystem can't be auto-probed; ignored for OCI images

  • init (String, Hash, nil)

    hand guest PID 1 to an init system: a command path (e.g. "/lib/systemd/systemd" or "auto"), or a Hash { cmd:, args:, env: } when the init binary takes argv/extra env

  • ephemeral (Boolean)

    auto-remove the sandbox's stored state (DB row, disk, logs, captured output) once it reaches a terminal state (default: state is persisted until remove)

  • patches (Array<Hash>, nil)

    rootfs patches applied before boot, each built with the Patch factory (e.g. Patch.text(...), Patch.mkdir(...)). Not compatible with disk-image roots.

  • log_level ("error", "warn", "info", "debug", "trace", nil)

    guest log verbosity

  • quiet_logs (Boolean)

    suppress sandbox process logs

  • security ("default", "restricted", nil)

    exec security profile

  • root_disk (Integer, Hash, nil)

    the OCI sandbox's writable root disk (runtime v0.6.7). An Integer is the managed ext4 upper's size cap in MiB (default kind, 4 GiB when unset); a Hash picks a kind via the RootDisk factory — RootDisk.managed(8192), RootDisk.tmpfs(2048) (RAM-backed, pristine on every boot), or RootDisk.disk("./scratch.img", format: "raw", fstype: "ext4") (user-supplied image attached writable).

  • oci_upper_size (Integer, nil)

    deprecated alias for root_disk: <Integer> (the managed kind); warns, and conflicts with root_disk:

  • max_duration (Integer, nil)

    hard wall-clock lifetime, in seconds

  • idle_timeout (Integer, nil)

    stop after this many idle seconds

  • rlimits (Hash, nil)

    resource limits: { resource => limit } or { resource => [soft, hard] } (e.g. { nofile: 65_535 })

  • pull_policy ("always", "if-missing", "never", nil)

    image pull behavior

  • registry_auth (Hash, nil)

    credentials for a private/authenticated registry: { username:, password: } (the password may be a token). Without this the core's default resolution chain still applies (OS keyring, global config, ~/.docker/config.json).

  • registry_insecure (Boolean)

    reach the registry over plain HTTP instead of HTTPS (for local/self-hosted registries)

  • registry_ca_certs (String, Array<String>, nil)

    extra PEM-encoded CA root certificate(s) to trust (for a registry with a private CA)

  • secrets (Array<Hash>, nil)

    placeholder-protected secrets injected by the TLS proxy (auto-enables TLS interception). Each Hash needs env: and value: plus an allow list — host: (single), hosts: (Array), and/or host_patterns: (wildcards like "*.stripe.com"). Optional per-secret: placeholder:, require_tls:, injection toggles inject_headers: / inject_basic_auth: / inject_query: / inject_body:, and on_violation:.

  • on_secret_violation (String, Symbol, Hash, nil)

    sandbox-wide secret-leak policy: "block", "block_and_log", "block_and_terminate", "passthrough" (passthrough-all-hosts), or a Hash { passthrough_hosts:, passthrough_host_patterns:, passthrough_all_hosts: }

  • detached (Boolean)

    keep running after this process exits

  • replace (Boolean)

    replace an existing sandbox with the same name

  • replace_with_timeout (Numeric, nil)

    replace, waiting up to N seconds

Yield Parameters:

Returns:

  • (Sandbox, Object)

    the sandbox, or the block's return value



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/microsandbox/sandbox.rb', line 338

def create(name, **kwargs, &block)
  opts = build_create_opts(**kwargs)
  sandbox = new(Native::Sandbox.create(name.to_s, opts))
  return sandbox unless block_given?

  begin
    yield sandbox
  ensure
    begin
      sandbox.stop
    rescue Microsandbox::Error
      # best-effort cleanup; ignore stop failures during teardown
    end
  end
end

.create_with_progress(name, **kwargs) ⇒ PullSession

Create a sandbox while streaming image-pull progress. Accepts the same options as create; returns a PullSession — iterate it (an Enumerable of progress-event Hashes, each with a "kind"), then call PullSession#sandbox for the booted Microsandbox::Sandbox. Mirrors the Python create_with_progress / Node createWithPullProgress.

Returns:



360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/microsandbox/sandbox.rb', line 360

def create_with_progress(name, **kwargs)
  # Unlike {create}, this has no block form: the booted sandbox is reached
  # via {PullSession#sandbox} (after iterating progress) and stopped by the
  # caller. A block would be silently dropped — and the sandbox leaked — so
  # reject it loudly rather than let a `create`-style block call misfire.
  if block_given?
    raise ArgumentError,
      "create_with_progress takes no block; iterate the returned PullSession " \
      "for progress, then call #sandbox and stop it when done"
  end
  opts = build_create_opts(**kwargs)
  PullSession.new(Native::Sandbox.create_with_progress(name.to_s, opts))
end

.get(name) ⇒ SandboxHandle

Fetch a controllable handle for a sandbox by name (running or not).

Returns:



477
478
479
# File 'lib/microsandbox/sandbox.rb', line 477

def get(name)
  SandboxHandle.new(Native::Sandbox.get(name.to_s))
end

.listArray<SandboxHandle>

List all sandboxes as controllable handles.

Returns:



483
484
485
# File 'lib/microsandbox/sandbox.rb', line 483

def list
  Native::Sandbox.list.map { |h| SandboxHandle.new(h) }
end

.list_with(labels: {}) ⇒ Array<SandboxHandle>

List sandboxes carrying all of the given labels (AND-matched).

Parameters:

  • labels (Hash) (defaults to: {})

    required key => value labels

Returns:



490
491
492
493
# File 'lib/microsandbox/sandbox.rb', line 490

def list_with(labels: {})
  opts = {"labels" => stringify(labels)}
  Native::Sandbox.list_with(opts).map { |h| SandboxHandle.new(h) }
end

.remove(name) ⇒ nil

Remove a (stopped) sandbox by name.

Returns:

  • (nil)


497
498
499
500
# File 'lib/microsandbox/sandbox.rb', line 497

def remove(name)
  Native::Sandbox.remove(name.to_s)
  nil
end

.start(name, detached: false) ⇒ Sandbox

Restart a previously-defined sandbox by name.

Returns:



470
471
472
473
# File 'lib/microsandbox/sandbox.rb', line 470

def start(name, detached: false)
  Microsandbox.ensure_runtime!
  new(Native::Sandbox.start(name.to_s, {"detached" => detached}))
end

Instance Method Details

#attach(command, args = [], cwd: nil, user: nil, env: nil, detach_keys: nil, rlimits: nil) ⇒ Integer

Attach an interactive terminal to a command in the sandbox.

Puts the host terminal into raw mode and forwards keystrokes (and SIGWINCH resizes) to the guest until the command exits or the detach sequence is typed. Requires a real TTY on stdin/stdout, so it is for CLI use, not library/automation code (use #exec/#exec_stream there). Blocks until the session ends. Mirrors the official SDKs' attach.

Parameters:

  • command (String)

    the program to run

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

    its arguments

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

    working directory

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

    user to run as

  • env (Hash, nil) (defaults to: nil)

    extra environment variables

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

    detach sequence (e.g. "ctrl-p,ctrl-q"; default "ctrl-]")

  • rlimits (Hash, nil) (defaults to: nil)

    resource limits (see #exec)

Returns:

  • (Integer)

    the command's exit code (or the code at detach)



1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
# File 'lib/microsandbox/sandbox.rb', line 1104

def attach(command, args = [], cwd: nil, user: nil, env: nil, detach_keys: nil, rlimits: nil)
  opts = {}
  opts["cwd"] = cwd.to_s if cwd
  opts["user"] = user.to_s if user
  opts["env"] = env.each_with_object({}) { |(k, v), a| a[k.to_s] = v.to_s } if env
  opts["detach_keys"] = detach_keys.to_s if detach_keys
  if rlimits
    opts["rlimits"] = rlimits.map do |resource, limit|
      soft, hard = limit.is_a?(Array) ? [limit[0], limit[1]] : [limit, limit]
      [resource.to_s, Integer(soft), Integer(hard)]
    end
  end
  @native.attach(command.to_s, Array(args).map(&:to_s), opts)
end

#attach_shellInteger

Attach an interactive terminal running the sandbox's default shell. See #attach for the host-TTY requirements.

Returns:

  • (Integer)

    the shell's exit code (or the code at detach)



1122
1123
1124
# File 'lib/microsandbox/sandbox.rb', line 1122

def attach_shell
  @native.attach_shell
end

#detachnil

Detach this handle: disarm the stop-on-drop safety net so the sandbox keeps running after this handle is gone (and after this process exits).

Returns:

  • (nil)


1310
1311
1312
1313
# File 'lib/microsandbox/sandbox.rb', line 1310

def detach
  @native.detach
  nil
end

#drainnil

Trigger a graceful drain (SIGUSR1).

Returns:

  • (nil)


1283
1284
1285
1286
# File 'lib/microsandbox/sandbox.rb', line 1283

def drain
  @native.drain
  nil
end

#exec(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecOutput

Run a command (no shell interpretation) and collect its output.

Parameters:

  • command (String)

    the executable

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

    arguments

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

    working directory

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

    user to run as

  • env (Hash, nil) (defaults to: nil)

    extra environment variables

  • timeout (Numeric, nil) (defaults to: nil)

    kill the command after N seconds and raise ExecTimeoutError. Omit or pass nil for no timeout. Note the asymmetry: 0 is not "disable" — it is an immediate (zero) deadline, so the command is killed before producing any output and ExecTimeoutError is raised. Use +nil+/omit, never 0, to mean "no limit".

  • tty (Boolean) (defaults to: false)

    allocate a pseudo-terminal

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

    bytes to feed to stdin, or :pipe to open a streaming stdin pipe (write/close it via ExecHandle#stdin; only useful with the streaming variants)

Returns:



1049
1050
1051
1052
# File 'lib/microsandbox/sandbox.rb', line 1049

def exec(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil)
  ExecOutput.new(@native.exec(command.to_s, Array(args).map(&:to_s),
    exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:)))
end

#exec_stream(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecHandle

Note:

timeout: is accepted for signature symmetry with #exec but is not applied on the streaming path (the runtime discards it). Enforce a deadline yourself around the iteration, or use blocking #exec with timeout: for the kill-after-N-seconds behavior.

Run a command and stream its output as it arrives.

Pass stdin: :pipe to feed the process interactively: ExecHandle#stdin then returns a writable sink; close it to send EOF (a process like cat that reads until EOF will otherwise block forever).

Returns:

See Also:



1073
1074
1075
1076
# File 'lib/microsandbox/sandbox.rb', line 1073

def exec_stream(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil)
  ExecHandle.new(@native.exec_stream(command.to_s, Array(args).map(&:to_s),
    exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:, pipe_ok: true)))
end

#fsFS

Guest filesystem operations.

Returns:



1128
1129
1130
# File 'lib/microsandbox/sandbox.rb', line 1128

def fs
  @fs ||= FS.new(@native)
end

#inspectObject



1315
1316
1317
# File 'lib/microsandbox/sandbox.rb', line 1315

def inspect
  "#<Microsandbox::Sandbox name=#{name.inspect}>"
end

#killnil

Force-kill the sandbox (SIGKILL).

Returns:

  • (nil)


1276
1277
1278
1279
# File 'lib/microsandbox/sandbox.rb', line 1276

def kill
  @native.kill
  nil
end

#log_stream(sources: nil, since_ms: nil, from_cursor: nil, until_ms: nil, follow: false) ⇒ LogStream

Stream captured logs as they appear.

Parameters:

  • sources (Array<String,Symbol>, nil) (defaults to: nil)

    filter by source ("stdout"/"stderr"/"output"/"system"/"all")

  • since_ms (Numeric, nil) (defaults to: nil)

    start at the first entry at/after this Unix ms

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

    resume exactly after a prior LogEntry#cursor (mutually exclusive with since_ms; takes precedence if both given)

  • until_ms (Numeric, nil) (defaults to: nil)

    stop before any entry at/after this Unix ms

  • follow (Boolean) (defaults to: false)

    keep the stream open for new entries past current EOF

Returns:



1249
1250
1251
1252
1253
1254
1255
1256
1257
# File 'lib/microsandbox/sandbox.rb', line 1249

def log_stream(sources: nil, since_ms: nil, from_cursor: nil, until_ms: nil, follow: false)
  opts = {}
  opts["sources"] = Array(sources).map(&:to_s) if sources
  opts["since_ms"] = Float(since_ms) if since_ms
  opts["from_cursor"] = from_cursor.to_s if from_cursor
  opts["until_ms"] = Float(until_ms) if until_ms
  opts["follow"] = true if follow
  LogStream.new(@native.log_stream(opts))
end

#logs(tail: nil, since_ms: nil, until_ms: nil, sources: nil) ⇒ Array<LogEntry>

Read captured logs.

Parameters:

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

    only the last N entries

  • since_ms (Numeric, nil) (defaults to: nil)

    only entries at/after this Unix ms

  • until_ms (Numeric, nil) (defaults to: nil)

    only entries before this Unix ms

  • sources (Array<String,Symbol>, nil) (defaults to: nil)

    filter by source ("stdout"/"stderr"/"output"/"system"/"all")

Returns:



1214
1215
1216
1217
1218
1219
1220
1221
# File 'lib/microsandbox/sandbox.rb', line 1214

def logs(tail: nil, since_ms: nil, until_ms: nil, sources: nil)
  opts = {}
  opts["tail"] = Integer(tail) if tail
  opts["since_ms"] = Float(since_ms) if since_ms
  opts["until_ms"] = Float(until_ms) if until_ms
  opts["sources"] = Array(sources).map(&:to_s) if sources
  @native.logs(opts).map { |entry| LogEntry.new(entry) }
end

#metricsMetrics

Latest resource-usage snapshot.

Raises a Error ("sandbox N has no live metrics slot") when called in the brief window right after create returns, before the runtime has registered the sandbox's metrics slot. On v0.6.x runtimes the spawn handshake no longer blocks create until the first sample is written, so the slot goes live a beat after boot (within a few hundred milliseconds); retry for that window rather than treating the first failure as fatal.

Returns:



1151
1152
1153
# File 'lib/microsandbox/sandbox.rb', line 1151

def metrics
  Metrics.new(@native.metrics)
end

#metrics_stream(interval: 1.0) ⇒ MetricsStream

Stream resource-usage snapshots, one per interval tick, until the sandbox stops. Requires metrics to be enabled for the sandbox.

The first tick fires immediately, so opening the stream right after create can hit the same metrics-slot startup window as #metrics and yield a transient "no live metrics slot" error on that first tick. Because the stream is single-pass (a drained or errored stream is spent), make sure the slot is live before opening it — e.g. retry #metrics until it succeeds (the slot goes live within a few hundred milliseconds of boot), then call #metrics_stream.

Parameters:

  • interval (Numeric) (defaults to: 1.0)

    seconds between snapshots

Returns:



1235
1236
1237
# File 'lib/microsandbox/sandbox.rb', line 1235

def metrics_stream(interval: 1.0)
  MetricsStream.new(@native.metrics_stream(coerce_duration(interval, "interval")))
end

#modify(**kwargs) ⇒ ModificationPlan

Plan or apply a live modification of this sandbox: resize CPU/memory, adjust env/labels/workdir, and rotate/remove secrets, without recreating the sandbox. The apply is all-or-nothing under the chosen policy:: the default :no_restart applies only changes that are live-capable right now and raises if any requested change needs a restart (on a running sandbox that includes env/labels/workdir and adding a secret — rotating or removing an existing one is live). Pass policy: :next_start to persist restart-required changes for the next start, or :restart to restart and apply them now; use dry_run: true to preview each change's disposition without applying anything. Mirrors the official SDKs' modify. Returns a ModificationPlan classifying each change.

Parameters:

  • cpus (Integer, nil)

    desired effective vCPU count

  • max_cpus (Integer, nil)

    desired boot-time maximum vCPU ceiling

  • memory (Integer, nil)

    desired effective guest memory in MiB

  • max_memory (Integer, nil)

    desired boot-time maximum memory (MiB)

  • env (Hash, nil)

    environment variables to set for future execs

  • remove_env (Array<String>, nil)

    environment variable names to remove

  • labels (Hash, nil)

    labels to set

  • remove_labels (Array<String>, nil)

    label keys to remove

  • workdir (String, nil)

    desired working directory for future execs

  • secrets (Hash, nil)

    desired secret specs keyed by secret name. Each spec is a Hash with at most one of env: (host env var to read), store: (host secret-store reference), or value: (raw material), plus optional placeholder: and allowed_hosts: (Array of host patterns).

  • remove_secrets (Array<String>, nil)

    secret names to remove

  • policy (Symbol, String, nil)

    :no_restart (default — apply only changes that need no restart), :next_start (persist for the next start), or :restart (restart to apply restart-required changes)

  • dry_run (Boolean)

    compute the plan without applying anything

Returns:



1201
1202
1203
1204
# File 'lib/microsandbox/sandbox.rb', line 1201

def modify(**kwargs)
  opts = Sandbox.send(:build_modify_opts, **kwargs)
  ModificationPlan.new(JSON.parse(@native.modify(opts)))
end

#nameString

Returns the sandbox name.

Returns:

  • (String)

    the sandbox name



1028
1029
1030
# File 'lib/microsandbox/sandbox.rb', line 1028

def name
  @native.name
end

#owns_lifecycle?Boolean

Returns whether this handle owns the sandbox process lifecycle (i.e. stopping it or dropping the handle terminates the sandbox).

Returns:

  • (Boolean)

    whether this handle owns the sandbox process lifecycle (i.e. stopping it or dropping the handle terminates the sandbox)



1303
1304
1305
# File 'lib/microsandbox/sandbox.rb', line 1303

def owns_lifecycle?
  @native.owns_lifecycle
end

#pingPingResult

Health-check the guest agent without refreshing the idle timer. Sends core.ping to the running sandbox and reports the round-trip latency. Mirrors the official SDKs' ping.

Returns:



1159
1160
1161
# File 'lib/microsandbox/sandbox.rb', line 1159

def ping
  PingResult.new(@native.ping)
end

#shell(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecOutput

Run a shell script (pipes, redirects, etc. allowed) and collect output.

Returns:



1056
1057
1058
1059
# File 'lib/microsandbox/sandbox.rb', line 1056

def shell(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil)
  ExecOutput.new(@native.shell(script.to_s,
    exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:)))
end

#shell_stream(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil) ⇒ ExecHandle

Note:

Like #exec_stream, timeout: is accepted but not applied on the streaming path.

Run a shell script and stream its output as it arrives.

Returns:



1082
1083
1084
1085
# File 'lib/microsandbox/sandbox.rb', line 1082

def shell_stream(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil)
  ExecHandle.new(@native.shell_stream(script.to_s,
    exec_opts(cwd:, user:, env:, timeout:, tty:, stdin:, rlimits:, pipe_ok: true)))
end

#sshSshOps

SSH access to the sandbox — open a native in-process SSH client or prepare a reusable server endpoint.

Examples:

sb.ssh.open_client { |c| puts c.exec("hostname").stdout }

Returns:



1137
1138
1139
# File 'lib/microsandbox/sandbox.rb', line 1137

def ssh
  SshOps.new(@native)
end

#statusSymbol

The live status, fetched from the backend (a round-trip per call).

Returns:

  • (Symbol)

    :created, :starting, :running, :draining, :paused, :stopped, or :crashed



1297
1298
1299
# File 'lib/microsandbox/sandbox.rb', line 1297

def status
  @native.status.to_sym
end

#stopnil

Gracefully stop the sandbox (SIGTERM→SIGKILL escalation, 10s default) and wait for it to terminate. For a custom timeout or fire-and-return request_* control, fetch a Microsandbox::SandboxHandle via get.

Returns:

  • (nil)


1263
1264
1265
1266
# File 'lib/microsandbox/sandbox.rb', line 1263

def stop
  @native.stop
  nil
end

#stop_and_waitExitStatus

Gracefully stop, then wait for the process to exit.

Returns:



1270
1271
1272
# File 'lib/microsandbox/sandbox.rb', line 1270

def stop_and_wait
  ExitStatus.new(@native.stop_and_wait)
end

#touchTouchResult

Explicitly refresh this sandbox's idle-activity timer (resetting any idle_timeout: countdown). Mirrors the official SDKs' touch.

Returns:



1166
1167
1168
# File 'lib/microsandbox/sandbox.rb', line 1166

def touch
  TouchResult.new(@native.touch)
end

#waitExitStatus

Wait for the sandbox process to exit.

Returns:



1290
1291
1292
# File 'lib/microsandbox/sandbox.rb', line 1290

def wait
  ExitStatus.new(@native.wait)
end