microsandbox-rb 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/DESIGN.md CHANGED
@@ -85,28 +85,106 @@ and the Rust→class mapping in `sdk/python/src/error.rs`.
85
85
 
86
86
  ## Runtime binary (`msb` + `libkrunfw`)
87
87
 
88
- The core crate's `prebuilt` feature (on by default) downloads the `msb` microVM
89
- runtime and `libkrunfw` firmware into `~/.microsandbox/{bin,lib}` **at build
90
- time** (`build.rs`). The path resolver checks, in order: `$MSB_PATH` →
91
- SDK-set path (`Microsandbox.runtime_path=`) → config file → workspace build →
92
- `~/.microsandbox/bin/msb` → `which msb`. `Microsandbox.install` / `.installed?`
93
- expose the core `setup::install`/`is_installed` for explicit, idempotent
94
- provisioning (mirrors the Python `install()`/`is_installed()`).
95
-
96
- Build-time provisioning only helps the **source gem**, where `build.rs` runs on
97
- the user's own machine. A **precompiled gem** is built in CI, so its build-time
98
- download lands on the CI host, not the user's — the user's `~/.microsandbox` is
99
- empty. `Microsandbox.ensure_runtime!` closes that gap: `Sandbox.create`/`start`
100
- call it to fetch the runtime on first use (by the *running* host's arch, which is
101
- always correct), at most once per process. `MICROSANDBOX_NO_AUTO_INSTALL` opts
102
- out (air-gapped hosts that provision out of band). libkrunfw is `dlopen`'d by
103
- `msb` at runtime and is never linked into the extension.
88
+ The gem is **SDK-only**: nothing is provisioned while it is built or installed.
89
+ The core crate's `prebuilt` feature — whose `build.rs` downloads the `msb`
90
+ microVM runtime and `libkrunfw` firmware into `~/.microsandbox/{bin,lib}` at
91
+ build time — is deliberately **off** (`default-features = false`, features
92
+ `keyring`/`net`/`ssh`, the set the official SDKs ship). Build-time provisioning
93
+ only ever helped the source gem: for a precompiled gem the download lands on the
94
+ CI host, and even for a source install it couples "compile a Ruby extension" to
95
+ "fetch 50 MB of host binaries", which is not the compiler's business.
96
+
97
+ The *guest* agent is a different story and is still embedded at build time: the
98
+ `agentd` binary that runs as PID 1 inside every microVM is `include_bytes!`-d
99
+ into the extension by `microsandbox-filesystem`'s `build.rs`, keyed by *target*
100
+ arch. Without its `prebuilt` feature that build script demands a locally built
101
+ `build/agentd` (workspace-only), so `ext/microsandbox/Cargo.toml` enables exactly
102
+ that one sub-feature through a direct `microsandbox-runtime` dependency
103
+ (`default-features = false, features = ["prebuilt"]`, whose `prebuilt` is just
104
+ `microsandbox-filesystem/prebuilt`) while the SDK-level host download stays off.
105
+
106
+ ### The companion gem
107
+
108
+ The host runtime ships as its own gem, **`microsandbox-rb-binaries`** (source in
109
+ `binaries/`), one platform gem per `arm64-darwin` / `x86_64-linux-gnu` /
110
+ `aarch64-linux-gnu` (Linux binaries are glibc-linked, hence
111
+ `required_rubygems_version >= 3.3.11`). It carries `vendor/bin/msb`,
112
+ `vendor/lib/libkrunfw.*` and a `vendor/manifest.json` — a layout that mirrors
113
+ both the upstream release bundle and `~/.microsandbox`, so the core finds the
114
+ firmware by `../lib` adjacency to `msb` and only the binary path needs handing
115
+ over (same trick as the Python and Node SDKs). Everything is downloaded from the
116
+ upstream GitHub release named by `Microsandbox::Binaries::RUNTIME_VERSION` and
117
+ sha256-verified fail-closed against that release's `checksums.sha256` — the copy
118
+ committed as `binaries/checksums/<tag>.sha256` when the runtime was adopted
119
+ (a GitHub release is mutable, so the live file must agree with the reviewed
120
+ one, not replace it) — at vendoring time; the gem build re-verifies the staged
121
+ tree against the manifest (regular files only), so nothing unverified is ever
122
+ packaged. Build pipeline:
123
+ `rake -C binaries vendor[<platform>] build[<platform>] verify`.
124
+
125
+ There is **no dependency edge in either direction**. RubyGems has no optional
126
+ dependencies, and cloud-only users (`MSB_BACKEND=cloud`) must not be forced to
127
+ download ~50 MB of binaries they will never execute — so the SDK discovers the
128
+ companion gem instead of depending on it, and the companion gem stays a pure
129
+ payload. The two are versioned in lockstep (`Binaries::VERSION` ==
130
+ `Microsandbox::VERSION`, `Binaries::RUNTIME_VERSION` == `RUNTIME_VERSION`, both
131
+ asserted by `spec/unit/version_spec.rb`).
132
+
133
+ ### Activation and the resolver ladder
134
+
135
+ `lib/microsandbox.rb` runs a private `activate_bundled_runtime!` once, at
136
+ `require "microsandbox"` time: `gem "microsandbox-rb-binaries", "= VERSION"`
137
+ (pins the lockstep version when RubyGems, not Bundler, picks the gem; a failure
138
+ here is tolerated) → `require "microsandbox/binaries"` (`LoadError` → the tier is
139
+ simply absent, silently) → `Binaries::VERSION` must equal `VERSION` and
140
+ `Binaries::RUNTIME_VERSION` must equal `RUNTIME_VERSION` (the `gem` pin above
141
+ cannot enforce lockstep: under Bundler it raises whenever the bundle picked any
142
+ other version, and that is swallowed) **and** both `msb_path` and
143
+ `libkrunfw_path` must exist →
144
+ `Native.set_runtime_msb_path(msb)`. The version gate is the load-bearing part: a
145
+ runtime from a different upstream release passes any exists-check and then fails
146
+ every `create` on a wire-protocol mismatch, so a mismatch is reported with a
147
+ warning and skipped rather than handed to the core. The whole path is
148
+ non-raising — a broken companion gem must never take `require "microsandbox"`
149
+ down with it.
150
+
151
+ Activation is **eager** rather than lazy, mirroring the Node SDK (`napi.ts`
152
+ pushes its platform package's `msb` into the same set-once slot at module load).
153
+ A lazy hook inside `ensure_runtime!` would only cover `Sandbox.create`/`start`
154
+ and silently miss every other entry point that resolves or spawns `msb`.
155
+
156
+ The resolver order is therefore: `$MSB_PATH` → SDK-set path (claimed by the
157
+ companion gem at load; `Microsandbox.runtime_path=` targets this same set-once
158
+ slot, so with the gem installed the setter is a **no-op** — override via
159
+ `MSB_PATH`) → config file → workspace build → `~/.microsandbox/bin/msb` →
160
+ `which msb`. `Microsandbox.runtime_path` reports the winner.
161
+ `Microsandbox.install` / `.installed?` expose the core `setup::install`/
162
+ `is_installed` for explicit, idempotent provisioning (mirrors the Python
163
+ `install()`/`is_installed()`).
164
+
165
+ Auto-provisioning stays as the **lowest tier**, deliberately (npx-style
166
+ first-use download; see upstream discussion
167
+ [superradcompany/microsandbox#1305](https://github.com/superradcompany/microsandbox/issues/1305)):
168
+ `Sandbox.create`/`start` call `Microsandbox.ensure_runtime!`, which fetches a
169
+ missing or version-stale runtime into `~/.microsandbox` on first use — by the
170
+ *running* host's arch, which is always correct — at most once per process.
171
+ `MICROSANDBOX_NO_AUTO_INSTALL` opts out (air-gapped hosts that provision out of
172
+ band). When the companion gem won the resolver (`bundled_runtime_active?`),
173
+ `ensure_runtime!` returns immediately: those binaries are already the matching
174
+ version, so nothing is downloaded or touched in `~/.microsandbox`. Note the
175
+ asymmetry in trust: the companion gem's payload is digest-verified when it is
176
+ vendored, while the first-use download is not yet content-verified (pending
177
+ upstream
178
+ [superradcompany/microsandbox#1300](https://github.com/superradcompany/microsandbox/issues/1300)).
179
+
180
+ libkrunfw is `dlopen`'d by `msb` at runtime and is never linked into the
181
+ extension.
104
182
 
105
183
  ## Core-crate dependency (self-contained)
106
184
 
107
185
  `ext/microsandbox/Cargo.toml` depends on the core crate via a **pinned git tag**
108
186
  (`microsandbox` / `microsandbox-network`, pinned to the same tag as
109
- `Microsandbox::RUNTIME_VERSION` — currently `v0.6.2`), so the gem builds anywhere
187
+ `Microsandbox::RUNTIME_VERSION` — currently `v0.6.14`), so the gem builds anywhere
110
188
  — CI, `rake-compiler-dock` release containers, and end-user source installs —
111
189
  without an adjacent checkout. For fast local development against a sibling
112
190
  microsandbox checkout, copy `.cargo/config.toml.example` to `.cargo/config.toml`
@@ -122,7 +200,8 @@ git. The override must never be committed — it would break container builds.
122
200
  (`rake-compiler-dock`) per `Gem::Platform`, shipping multi-ABI
123
201
  `lib/microsandbox/<ruby_abi>/` native artifacts — the same model Node uses with
124
202
  per-platform packages. End users then install with no Rust toolchain, and the
125
- runtime is fetched on first use (see above). The guest `agentd` is baked into
203
+ host runtime comes from the `microsandbox-rb-binaries` gem — or, without it, is
204
+ fetched on first use (see above). The guest `agentd` is baked into
126
205
  the extension by *target* arch (`filesystem/build.rs` uses
127
206
  `CARGO_CFG_TARGET_ARCH` + `include_bytes!`), so it cross-compiles correctly;
128
207
  the real cross work is linking the *target* native libs — `libcap-ng` on Linux
@@ -133,6 +212,16 @@ git. The override must never be committed — it would break container builds.
133
212
  since CI can't boot a microVM to prove a built gem actually works, gems are
134
213
  promoted to the publish path manually after per-platform validation. Published
135
214
  to RubyGems via Trusted Publishing (OIDC). See [Releasing](README.md#releasing).
215
+ * **Runtime binaries gems** (`microsandbox-rb-binaries`): a distinct artifact
216
+ from the precompiled *extension* gems above — no Ruby code beyond a small
217
+ locator module, just the verified upstream `msb` + `libkrunfw` for one
218
+ platform. CI's `binaries` job vendors and builds all three platforms on every
219
+ run and smoke-tests the host one; the `package` job installs the source gem
220
+ together with the host binaries gem and asserts `Microsandbox.runtime_path`
221
+ resolves into it, and the real-microVM integration job runs twice — once
222
+ against a `~/.microsandbox` provision (the fallback tier) and once booting from
223
+ the gem's vendored runtime. Publishing them is a follow-up (it needs a pending
224
+ trusted publisher on rubygems.org) and lands with the next release.
136
225
 
137
226
  ## Build requirements
138
227
 
data/README.md CHANGED
@@ -58,6 +58,10 @@ them. Our deepest thanks to the maintainers and community. 🙏
58
58
  - A **Rust** toolchain (stable >= 1.91) — needed only when installing the source
59
59
  gem (it compiles the native extension on install). Precompiled per-platform
60
60
  gems, where available, require no Rust toolchain; see [Releasing](#releasing)
61
+ - The **`msb` runtime + `libkrunfw` firmware** — shipped by the optional
62
+ companion gem `microsandbox-rb-binaries`, or downloaded into `~/.microsandbox`
63
+ on first use; see [The runtime binaries](#the-runtime-binaries). Cloud-only
64
+ users (`MSB_BACKEND=cloud`) need no local runtime at all
61
65
 
62
66
  ## Installation
63
67
 
@@ -80,17 +84,49 @@ takes a few minutes and needs a Rust toolchain (`rustc >= 1.91`) on `PATH`. When
80
84
  a **precompiled platform gem** is available for your OS/architecture, RubyGems
81
85
  picks it automatically and no Rust toolchain is required.
82
86
 
83
- Either way the `msb` runtime and `libkrunfw` firmware are provisioned into
84
- `~/.microsandbox` automatically on first use (the first `Sandbox.create`/`start`
85
- downloads them if missing). To provision ahead of time — e.g. while baking a
86
- container image, or to avoid the first-call latency — call `install` explicitly:
87
+ ### The runtime binaries
88
+
89
+ `microsandbox-rb` is **SDK-only** — it wraps the microVM runtime, it doesn't
90
+ carry it. The host-side `msb` runtime and the `libkrunfw` firmware come from an
91
+ optional companion gem, **`microsandbox-rb-binaries`**, published as one gem per
92
+ platform (`arm64-darwin`, `x86_64-linux-gnu`, `aarch64-linux-gnu`) and
93
+ versioned in lockstep with this gem:
94
+
95
+ ```ruby
96
+ # Gemfile — install both gems at the same version
97
+ gem "microsandbox-rb", require: "microsandbox"
98
+ gem "microsandbox-rb-binaries"
99
+ ```
100
+
101
+ That's all the wiring there is: `require "microsandbox"` finds the companion
102
+ gem, checks it is the same version and built for the same upstream runtime, and points the resolver
103
+ at its vendored `msb` — so your bundle carries the runtime and nothing is
104
+ downloaded at install time or on first call. **Recommended whenever you boot
105
+ local microVMs.** Cloud-only users (`MSB_BACKEND=cloud`) should skip it: it is a
106
+ separate, optional gem precisely so nobody has to fetch ~50 MB of binaries they
107
+ won't run. Neither gem depends on the other.
108
+
109
+ > **Availability.** The binaries gems are published on every release tag
110
+ > alongside `microsandbox-rb` (first release to ship them: the one after
111
+ > 0.13.0). On an older SDK version, or a platform without a bundle, use the
112
+ > fallback below (or build them yourself from `binaries/` — see that
113
+ > directory's README).
114
+
115
+ **Fallback — first-use download.** Without the companion gem, the `msb` runtime
116
+ and `libkrunfw` firmware are provisioned into `~/.microsandbox` automatically on
117
+ first use (the first `Sandbox.create`/`start` downloads them if missing). To
118
+ provision ahead of time — e.g. while baking a container image, or to avoid the
119
+ first-call latency — call `install` explicitly:
87
120
 
88
121
  ```ruby
89
122
  Microsandbox.install unless Microsandbox.installed?
90
123
  ```
91
124
 
92
125
  Set `MICROSANDBOX_NO_AUTO_INSTALL` to disable the automatic first-use download
93
- (e.g. on air-gapped hosts that provision the runtime out of band).
126
+ (e.g. on air-gapped hosts that provision the runtime out of band). None of this
127
+ applies when the companion gem supplies the runtime: its binaries are already the
128
+ matching version, so `ensure_runtime!` skips the installer entirely and nothing
129
+ is written to `~/.microsandbox`.
94
130
 
95
131
  ## Quick start
96
132
 
@@ -428,8 +464,9 @@ end
428
464
  ## Runtime configuration
429
465
 
430
466
  The `msb` runtime path is resolved in this order: the `MSB_PATH` environment
431
- variable → an SDK-set override → the config file → `~/.microsandbox/bin/msb` →
432
- `msb` on `PATH`.
467
+ variable → the `microsandbox-rb-binaries` gem (or another SDK-set override) →
468
+ the config file → `~/.microsandbox/bin/msb` → `msb` on `PATH`.
469
+ `Microsandbox.runtime_path` reports the winner.
433
470
 
434
471
  ```ruby
435
472
  Microsandbox.installed? # => true/false
@@ -439,6 +476,16 @@ Microsandbox.runtime_path = "/opt/microsandbox/bin/msb" # override (set-once)
439
476
  Microsandbox.libkrunfw_path = "/opt/microsandbox/lib/libkrunfw.dylib" # override (set-once)
440
477
  ```
441
478
 
479
+ When [`microsandbox-rb-binaries`](#the-runtime-binaries) is installed,
480
+ `require "microsandbox"` claims that SDK-set slot with the gem's vendored `msb`
481
+ (the firmware is found alongside it), and `runtime_path` points into the gem.
482
+ The two gems are versioned in lockstep and the companion gem must be the same
483
+ version **and** built for the same upstream runtime — a mismatch of either is reported with a warning and
484
+ skipped, and the SDK falls back to `~/.microsandbox` rather than driving a
485
+ runtime it doesn't match. Because the slot is **set-once**,
486
+ `Microsandbox.runtime_path=` is then a no-op: use the `MSB_PATH` environment
487
+ variable, which outranks it, to point at a different runtime.
488
+
442
489
  ### Backend routing
443
490
 
444
491
  As of v0.5.8 every operation runs through a backend. The default is the local
@@ -477,10 +524,17 @@ change diverged the two numbers — the gem version is **not** a reliable indica
477
524
  of the embedded runtime version. To learn which runtime a build wraps, ask it:
478
525
 
479
526
  ```ruby
480
- Microsandbox::VERSION # => "0.13.0" (the gem's own version)
481
- Microsandbox.runtime_version # => "v0.6.9" (the embedded upstream runtime tag)
527
+ Microsandbox::VERSION # => "0.15.0" (the gem's own version)
528
+ Microsandbox.runtime_version # => "v0.6.14" (the embedded upstream runtime tag)
482
529
  ```
483
530
 
531
+ The companion [`microsandbox-rb-binaries`](#the-runtime-binaries) gem is
532
+ versioned in **lockstep** with this gem (same number, released together) and
533
+ pins the same upstream runtime — install both at the same version. The gem's
534
+ `Microsandbox::Binaries::VERSION` and `::RUNTIME_VERSION` are asserted against
535
+ this gem's constants by the test suite, so a companion gem can't silently go
536
+ stale.
537
+
484
538
  | Gem version | Upstream runtime | Notes |
485
539
  |-------------|------------------|-------|
486
540
  | `0.5.7` | `v0.5.7` | initial release |
@@ -502,6 +556,8 @@ Microsandbox.runtime_version # => "v0.6.9" (the embedded upstream runtime tag
502
556
  | `0.11.0` | `v0.6.7` | adopts upstream `v0.6.7` (**breaking**): network profiles replace `public_only`/`non_local`, structured `root_disk:` replaces `oci_upper_size:` (deprecated alias kept), snapshot descriptor contract (`create` re-keyed by name, `save`/`load` rename, `snapshot_to` removed, on-disk auto-migration), `Image.load`/`Image.save`, `follow_root_symlinks:`; runtime carries the GHSA-4vq3-cjpp-v7fg `msb copy` fix |
503
557
  | `0.12.0` | `v0.6.8` | adopts upstream `v0.6.8` (**breaking**): `Sandbox.list`/`.list_with` return a cursor-paginated `SandboxPage` (`limit:`/`cursor:` keywords), `UnsupportedError` re-keyed by structured operations with `#operation`/`#hint`; runtime adds a shared log registry for followed streams and cloud exec/ssh reconnects |
504
558
  | `0.13.0` | `v0.6.9` | adopts upstream `v0.6.9` (**breaking**): a bare `MSB_API_KEY` no longer selects the cloud backend (explicit `MSB_BACKEND=cloud` or a cloud profile required; invalid cloud config fails closed with `InvalidConfigError`); snapshot payload integrity becomes opt-in (`record_integrity:`, `verify` can report `:not_recorded`). Parity: default-workload execution (`exec_default`/`exec_default_stream`/`attach_default`, `cmd:`), flat root disks (`RootDisk.flat`), `modify(root_disk_size:)`, `rate_limiter:`, `vsock:`, `default_backend_info`, `Volume.get_default` |
559
+ | `0.14.0` | `v0.6.9` | two-gem split: SDK-only gem (no build-time runtime download) + companion `microsandbox-rb-binaries` platform gems |
560
+ | `0.15.0` | `v0.6.14` | adopts upstream `v0.6.10`–`v0.6.14` step by step: bind-mount correctness, guest bootstrap off the kernel command line, DNS pins for deferred domain allows, Linux glibc 2.28 baseline for the prebuilt runtime, legacy ext4 upper-disk resize, `msb_krun` 0.1.32. Parity: `ssh.open_client`/`prepare_server` accept `inactivity_timeout:` (seconds; `0` disables, `nil` inherits the 600s global default) |
505
561
 
506
562
  **Going forward** — the gem version moves on its own semver track and no longer
507
563
  mirrors the upstream tag:
@@ -555,7 +611,11 @@ or credential setup is needed.
555
611
  the number to mirror the upstream tag. If the release also adopts a new upstream
556
612
  runtime, bump the `tag = "vX.Y.Z"` on **both** the `microsandbox` and
557
613
  `microsandbox-network` git deps, update `Microsandbox::RUNTIME_VERSION` to match,
558
- and add a row to the Versioning table. Update `CHANGELOG.md`.
614
+ and add a row to the Versioning table. Also bump
615
+ `Microsandbox::Binaries::VERSION` (and, on a runtime adoption,
616
+ `::RUNTIME_VERSION`) in `binaries/lib/microsandbox/binaries.rb` — the
617
+ companion gem ships in lockstep and the specs assert both. Update
618
+ `CHANGELOG.md`.
559
619
  2. Push a `vX.Y.Z` tag. CI builds the **source gem** and pushes it to RubyGems
560
620
  via `rubygems/configure-rubygems-credentials` (OIDC, `id-token: write`) — no
561
621
  `RUBYGEMS_API_KEY` secret required.
@@ -568,13 +628,29 @@ or credential setup is needed.
568
628
  > prove otherwise — so promotion is manual after validating the artifact on each
569
629
  > platform. A precompiled gem ships the compiled extension (with the guest
570
630
  > `agentd` baked in by *target* arch); the host-side `msb` + `libkrunfw` runtime
571
- > is fetched into `~/.microsandbox` on first use by `Microsandbox.ensure_runtime!`
572
- > (libkrunfw is `dlopen`'d by `msb` at runtime, never linked into the gem). The
631
+ > is **not** in it — that comes from the companion `microsandbox-rb-binaries`
632
+ > gem, or, when that isn't installed, is fetched into `~/.microsandbox` on first
633
+ > use by `Microsandbox.ensure_runtime!` (libkrunfw is `dlopen`'d by `msb` at
634
+ > runtime, never linked into the gem). The
573
635
  > real cross-compile work is linking the *target* native libraries — `libcap-ng`
574
636
  > on Linux (handled via Debian multiarch in the workflow) and the Hypervisor +
575
637
  > Security frameworks on macOS (via osxcross; the one platform left to confirm).
576
638
  > Until promoted, users install the source gem (which compiles via `rb_sys`).
577
639
 
640
+ > **Runtime binaries gems** (`microsandbox-rb-binaries`, source in `binaries/`)
641
+ > are a *separate* artifact from the precompiled extension gems above: they carry
642
+ > no Ruby extension, only the upstream `msb` + `libkrunfw` for one platform,
643
+ > verified against the release's published `checksums.sha256` when vendored. CI's
644
+ > `binaries` job vendors and builds all three (`arm64-darwin`,
645
+ > `x86_64-linux-gnu`, `aarch64-linux-gnu`) on every run and smoke-tests the host
646
+ > one; `release.yml`'s `binaries-gems` job does the same on a tag and a separate
647
+ > `publish-binaries` job pushes them after the SDK gem is live (a companion
648
+ > failure is its own red job and never blocks the SDK release).
649
+ > They use their own RubyGems trusted-publisher entry (same repo + workflow,
650
+ > gem name `microsandbox-rb-binaries`). To build them by hand:
651
+ > `rake -C binaries vendor[<platform>]` then `rake -C binaries build[<platform>]`
652
+ > (→ `binaries/pkg/*.gem`).
653
+
578
654
  See [DESIGN.md](DESIGN.md) for the architecture and the implemented-surface
579
655
  section. The binding covers the official-SDK surface: sandbox
580
656
  lifecycle (the live `Sandbox` `stop`/`stop_and_wait`/`kill`/`drain`/`wait`/
@@ -6,8 +6,8 @@ name = "microsandbox_rb"
6
6
  description = "Ruby SDK native extension for microsandbox — secure, fast microVM-based sandboxing."
7
7
  # Must equal Microsandbox::VERSION (lib/microsandbox/version.rb) — Native.version
8
8
  # returns this via env!("CARGO_PKG_VERSION") and version_spec.rb asserts equality.
9
- # The core-crate dependency below stays pinned at its own tag (v0.6.9).
10
- version = "0.13.0"
9
+ # The core-crate dependency below stays pinned at its own tag (v0.6.14).
10
+ version = "0.15.0"
11
11
  authors = ["Super Rad Company <development@superrad.company>"]
12
12
  repository = "https://github.com/superradcompany/microsandbox"
13
13
  license = "Apache-2.0"
@@ -32,11 +32,27 @@ rb-sys = "0.9"
32
32
  # gem is self-contained and buildable anywhere (CI, release containers, end-user
33
33
  # source installs) without an adjacent checkout. For local development against a
34
34
  # sibling checkout, use the `paths` override in `.cargo/config.toml` (see
35
- # `.cargo/config.toml.example`). "ssh" matches the feature set the Python/Node
36
- # SDKs ship with; default features add "prebuilt" (provisions msb + libkrunfw at
37
- # build time), "net", and "keyring".
38
- microsandbox = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.9", default-features = true, features = ["ssh"] }
39
- microsandbox-network = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.9" }
35
+ # `.cargo/config.toml.example`).
36
+ #
37
+ # Features are listed explicitly (default-features = false) so the SDK crate's
38
+ # own "prebuilt" feature stays OFF: that feature makes its build.rs download
39
+ # the msb + libkrunfw *host runtime* into ~/.microsandbox at compile time. This
40
+ # gem is SDK-only — the host runtime comes from the companion
41
+ # `microsandbox-rb-binaries` gem (see binaries/) or, as the lowest tier, the
42
+ # first-use download into ~/.microsandbox (`setup::install`, which is not
43
+ # gated on "prebuilt"). "net"/"ssh"/"keyring" are the features the official
44
+ # SDKs ship with.
45
+ microsandbox = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.14", default-features = false, features = ["keyring", "net", "ssh"] }
46
+ microsandbox-network = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.14" }
47
+ # The *guest* agent (`agentd`, PID 1 inside every microVM) is embedded into the
48
+ # extension by microsandbox-filesystem's build.rs, which without its "prebuilt"
49
+ # feature demands a locally built `build/agentd` (workspace-only, `just
50
+ # build-deps`). Enable that one sub-feature directly — via microsandbox-runtime,
51
+ # whose "prebuilt" is exactly `microsandbox-filesystem/prebuilt` — so the
52
+ # prebuilt agentd for the target arch is fetched (same behaviour as before)
53
+ # while the SDK-level msb download above stays off. Feature unification means
54
+ # this adds no new crates; keep it on the same tag as the deps above.
55
+ microsandbox-runtime = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.14", default-features = false, features = ["prebuilt"] }
40
56
 
41
57
  # Async core bridged to Ruby's synchronous API via a blocking tokio runtime.
42
58
  tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] }
@@ -866,11 +866,14 @@ impl Sandbox {
866
866
  //----------------------------------------------------------------------
867
867
 
868
868
  /// Open a native in-process SSH client to this sandbox. `opts`: user, term,
869
- /// sftp (bool, default true).
869
+ /// sftp (bool, default true), inactivity_timeout (seconds, f64; 0 disables).
870
870
  fn ssh_open_client(&self, opts: RHash) -> Result<crate::ssh::SshClient, Error> {
871
871
  let user = conv::opt_string(opts, "user")?;
872
872
  let term = conv::opt_string(opts, "term")?;
873
873
  let sftp = conv::opt::<bool>(opts, "sftp")?.unwrap_or(true);
874
+ let inactivity_timeout = conv::opt_f64(opts, "inactivity_timeout")?
875
+ .map(secs_to_duration)
876
+ .transpose()?;
874
877
  let ssh = self.inner.ssh();
875
878
  let client = block_on(ssh.open_client_with(move |mut b| {
876
879
  if let Some(u) = user {
@@ -879,6 +882,9 @@ impl Sandbox {
879
882
  if let Some(t) = term {
880
883
  b = b.term(t);
881
884
  }
885
+ if let Some(t) = inactivity_timeout {
886
+ b = b.inactivity_timeout(t);
887
+ }
882
888
  b.sftp(sftp)
883
889
  }))
884
890
  .map_err(error::to_ruby)?;
@@ -886,12 +892,16 @@ impl Sandbox {
886
892
  }
887
893
 
888
894
  /// Prepare a reusable SSH server endpoint. `opts`: host_key_path,
889
- /// authorized_keys_path, user, sftp (bool, default true).
895
+ /// authorized_keys_path, user, sftp (bool, default true),
896
+ /// inactivity_timeout (seconds, f64; 0 disables).
890
897
  fn ssh_prepare_server(&self, opts: RHash) -> Result<crate::ssh::SshServer, Error> {
891
898
  let host_key_path = conv::opt_string(opts, "host_key_path")?;
892
899
  let authorized_keys_path = conv::opt_string(opts, "authorized_keys_path")?;
893
900
  let user = conv::opt_string(opts, "user")?;
894
901
  let sftp = conv::opt::<bool>(opts, "sftp")?.unwrap_or(true);
902
+ let inactivity_timeout = conv::opt_f64(opts, "inactivity_timeout")?
903
+ .map(secs_to_duration)
904
+ .transpose()?;
895
905
  let ssh = self.inner.ssh();
896
906
  let server = block_on(ssh.prepare_server_with(move |mut b| {
897
907
  if let Some(p) = host_key_path {
@@ -903,6 +913,9 @@ impl Sandbox {
903
913
  if let Some(u) = user {
904
914
  b = b.user(u);
905
915
  }
916
+ if let Some(t) = inactivity_timeout {
917
+ b = b.inactivity_timeout(t);
918
+ }
906
919
  b.sftp(sftp)
907
920
  }))
908
921
  .map_err(error::to_ruby)?;
@@ -231,11 +231,17 @@ module Microsandbox
231
231
  # @param user [String] guest user to authenticate as (default "root")
232
232
  # @param term [String, nil] TERM value for the session
233
233
  # @param sftp [Boolean] enable the SFTP subsystem (default true)
234
+ # @param inactivity_timeout [Numeric, nil] per-session inactivity timeout in
235
+ # seconds; `nil` inherits the global config (default 600s), `0` disables it
234
236
  # @yieldparam client [SshClient]
235
237
  # @return [SshClient, Object]
236
- def open_client(user: "root", term: nil, sftp: true)
238
+ def open_client(user: "root", term: nil, sftp: true, inactivity_timeout: nil)
237
239
  opts = {"user" => user.to_s, "sftp" => sftp ? true : false}
238
240
  opts["term"] = term.to_s if term
241
+ unless inactivity_timeout.nil?
242
+ opts["inactivity_timeout"] =
243
+ Sandbox.send(:coerce_duration, inactivity_timeout, "inactivity_timeout")
244
+ end
239
245
  client = SshClient.new(@native.ssh_open_client(opts))
240
246
  return client unless block_given?
241
247
 
@@ -251,12 +257,19 @@ module Microsandbox
251
257
  # @param authorized_keys_path [String, nil] authorized_keys file path
252
258
  # @param user [String, nil] guest user connections run as
253
259
  # @param sftp [Boolean] enable the SFTP subsystem (default true)
260
+ # @param inactivity_timeout [Numeric, nil] per-session inactivity timeout in
261
+ # seconds; `nil` inherits the global config (default 600s), `0` disables it
254
262
  # @return [SshServer]
255
- def prepare_server(host_key_path: nil, authorized_keys_path: nil, user: nil, sftp: true)
263
+ def prepare_server(host_key_path: nil, authorized_keys_path: nil, user: nil, sftp: true,
264
+ inactivity_timeout: nil)
256
265
  opts = {"sftp" => sftp ? true : false}
257
266
  opts["host_key_path"] = host_key_path.to_s if host_key_path
258
267
  opts["authorized_keys_path"] = authorized_keys_path.to_s if authorized_keys_path
259
268
  opts["user"] = user.to_s if user
269
+ unless inactivity_timeout.nil?
270
+ opts["inactivity_timeout"] =
271
+ Sandbox.send(:coerce_duration, inactivity_timeout, "inactivity_timeout")
272
+ end
260
273
  SshServer.new(@native.ssh_prepare_server(opts))
261
274
  end
262
275
  end
@@ -8,12 +8,12 @@ module Microsandbox
8
8
  # Versioning section of the README for the full gem-to-runtime map. Must equal
9
9
  # the native ext's Cargo crate version (`Native.version`), enforced by
10
10
  # spec/unit/version_spec.rb.
11
- VERSION = "0.13.0"
11
+ VERSION = "0.15.0"
12
12
 
13
13
  # The upstream microsandbox runtime release this gem build embeds — the `tag`
14
14
  # pinned on the `microsandbox`/`microsandbox-network` git deps in
15
15
  # ext/microsandbox/Cargo.toml. Exposed at runtime as
16
16
  # {Microsandbox.runtime_version}. spec/unit/version_spec.rb asserts it stays in
17
17
  # sync with the Cargo tag so it can't silently drift out of date.
18
- RUNTIME_VERSION = "v0.6.9"
18
+ RUNTIME_VERSION = "v0.6.14"
19
19
  end