microsandbox-rb 0.12.0 → 0.14.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.9`), 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
@@ -20,7 +20,13 @@ them. Our deepest thanks to the maintainers and community. 🙏
20
20
  [Rust](https://github.com/superradcompany/microsandbox/tree/main/sdk) ·
21
21
  [Python](https://github.com/superradcompany/microsandbox/tree/main/sdk/python) ·
22
22
  [TypeScript / Node](https://github.com/superradcompany/microsandbox/tree/main/sdk/node-ts) ·
23
- [Go](https://github.com/superradcompany/microsandbox/tree/main/sdk/go)
23
+ [Go](https://github.com/superradcompany/microsandbox/tree/main/sdk/go) ·
24
+ [Ruby](https://github.com/superradcompany/microsandbox/tree/main/sdk/ruby)
25
+ (since upstream `v0.6.9` there is an **official** `microsandbox` gem — a
26
+ compact veneer over the same Rust SDK. This gem predates it and covers a
27
+ larger surface (snapshots, SSH, streaming, volumes fs, network policy DSL,
28
+ RBS types); both define the `Microsandbox` module, so use one or the other,
29
+ not both, in a single process.)
24
30
  - **Agents** — [Agent Skills](https://github.com/superradcompany/skills) · [MCP server](https://github.com/superradcompany/microsandbox-mcp)
25
31
  - **Community** — [Discord](https://discord.gg/T95Y3XnEAK)
26
32
 
@@ -52,6 +58,10 @@ them. Our deepest thanks to the maintainers and community. 🙏
52
58
  - A **Rust** toolchain (stable >= 1.91) — needed only when installing the source
53
59
  gem (it compiles the native extension on install). Precompiled per-platform
54
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
55
65
 
56
66
  ## Installation
57
67
 
@@ -74,17 +84,49 @@ takes a few minutes and needs a Rust toolchain (`rustc >= 1.91`) on `PATH`. When
74
84
  a **precompiled platform gem** is available for your OS/architecture, RubyGems
75
85
  picks it automatically and no Rust toolchain is required.
76
86
 
77
- Either way the `msb` runtime and `libkrunfw` firmware are provisioned into
78
- `~/.microsandbox` automatically on first use (the first `Sandbox.create`/`start`
79
- downloads them if missing). To provision ahead of time — e.g. while baking a
80
- 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:
81
120
 
82
121
  ```ruby
83
122
  Microsandbox.install unless Microsandbox.installed?
84
123
  ```
85
124
 
86
125
  Set `MICROSANDBOX_NO_AUTO_INSTALL` to disable the automatic first-use download
87
- (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`.
88
130
 
89
131
  ## Quick start
90
132
 
@@ -193,6 +235,22 @@ end
193
235
  A non-zero exit is **not** an error — inspect `exit_code`/`success?`. Spawn-time
194
236
  failures (e.g. command not found) and timeouts raise typed errors (see below).
195
237
 
238
+ **Default workload** (runtime `v0.6.9`): `create` is strictly boot-only — it
239
+ never runs the image's `ENTRYPOINT`/`CMD`. Execute the image's own command
240
+ explicitly:
241
+
242
+ ```ruby
243
+ Microsandbox::Sandbox.create("worker", image: "example/worker:latest",
244
+ cmd: ["worker.py", "--once"]) do |sb| # cmd: overrides the durable image CMD
245
+ out = sb.exec_default(timeout: 300) # buffered; exec-style options
246
+ handle = sb.exec_default_stream # or streaming (returns an ExecHandle)
247
+ sb.attach_default # or interactive (host TTY)
248
+ end
249
+ ```
250
+
251
+ An image whose entrypoint and CMD resolve to no executable command raises
252
+ `Microsandbox::NoDefaultCommandError`.
253
+
196
254
  ### Guest filesystem
197
255
 
198
256
  ```ruby
@@ -256,6 +314,10 @@ Microsandbox::Sandbox.create("live", image: "public.ecr.aws/docker/library/alpin
256
314
  # Live resize — applies to the running VM under the default :no_restart policy:
257
315
  sb.modify(cpus: 2, memory: 1024)
258
316
 
317
+ # Grow the root disk (managed upper or flat, MiB — runtime v0.6.9). Applied
318
+ # while stopped; growth-only:
319
+ sb.modify(root_disk_size: 8192, policy: :next_start)
320
+
259
321
  # env/labels/workdir changes on a *running* sandbox require a restart, so the
260
322
  # default :no_restart policy rejects the whole apply (it raises rather than
261
323
  # partially applying). Persist them for the next start — or restart now —
@@ -402,8 +464,9 @@ end
402
464
  ## Runtime configuration
403
465
 
404
466
  The `msb` runtime path is resolved in this order: the `MSB_PATH` environment
405
- variable → an SDK-set override → the config file → `~/.microsandbox/bin/msb` →
406
- `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.
407
470
 
408
471
  ```ruby
409
472
  Microsandbox.installed? # => true/false
@@ -413,6 +476,16 @@ Microsandbox.runtime_path = "/opt/microsandbox/bin/msb" # override (set-once)
413
476
  Microsandbox.libkrunfw_path = "/opt/microsandbox/lib/libkrunfw.dylib" # override (set-once)
414
477
  ```
415
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
+
416
489
  ### Backend routing
417
490
 
418
491
  As of v0.5.8 every operation runs through a backend. The default is the local
@@ -430,9 +503,15 @@ Microsandbox.with_backend(:local) { Microsandbox::Sandbox.create("box", image: "
430
503
  ```
431
504
 
432
505
  Resolution order when no backend is set programmatically: `MSB_BACKEND`
433
- (`local`/`cloud`) → `MSB_API_URL` + `MSB_API_KEY` → `MSB_PROFILE` → the
434
- `active_profile` in `~/.microsandbox/config.json` (path overridable via
435
- `MSB_CONFIG_PATH`) → local. The cloud backend currently supports a subset of
506
+ (`local`/`cloud`) → `MSB_PROFILE` → the `active_profile` in
507
+ `~/.microsandbox/config.json` (path overridable via `MSB_CONFIG_PATH`) →
508
+ local. **Cloud intent must be explicit** (since runtime `v0.6.9`): a bare
509
+ `MSB_API_KEY` is treated as credential material, not backend intent, and no
510
+ longer selects the cloud on its own — pair it with `MSB_BACKEND=cloud` (which
511
+ reads `MSB_API_URL`/`MSB_API_KEY`), or select a cloud profile. Invalid cloud
512
+ configuration (e.g. `MSB_BACKEND=cloud` without a usable API key or cloud
513
+ profile) fails closed with `Microsandbox::InvalidConfigError` instead of
514
+ silently running locally. The cloud backend currently supports a subset of
436
515
  operations (create/start/stop/remove/get/list, one-shot exec, follow log
437
516
  streaming); unsupported operations raise `Microsandbox::UnsupportedError`.
438
517
 
@@ -445,10 +524,17 @@ change diverged the two numbers — the gem version is **not** a reliable indica
445
524
  of the embedded runtime version. To learn which runtime a build wraps, ask it:
446
525
 
447
526
  ```ruby
448
- Microsandbox::VERSION # => "0.12.0" (the gem's own version)
449
- Microsandbox.runtime_version # => "v0.6.8" (the embedded upstream runtime tag)
527
+ Microsandbox::VERSION # => "0.13.0" (the gem's own version)
528
+ Microsandbox.runtime_version # => "v0.6.9" (the embedded upstream runtime tag)
450
529
  ```
451
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
+
452
538
  | Gem version | Upstream runtime | Notes |
453
539
  |-------------|------------------|-------|
454
540
  | `0.5.7` | `v0.5.7` | initial release |
@@ -469,6 +555,8 @@ Microsandbox.runtime_version # => "v0.6.8" (the embedded upstream runtime tag
469
555
  | `0.10.0` | `v0.6.6` | `v0.6.6` API parity: live `modify`/resize, `ping`/`touch`, create `max_cpus`/`max_memory` |
470
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 |
471
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 |
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 |
472
560
 
473
561
  **Going forward** — the gem version moves on its own semver track and no longer
474
562
  mirrors the upstream tag:
@@ -522,7 +610,11 @@ or credential setup is needed.
522
610
  the number to mirror the upstream tag. If the release also adopts a new upstream
523
611
  runtime, bump the `tag = "vX.Y.Z"` on **both** the `microsandbox` and
524
612
  `microsandbox-network` git deps, update `Microsandbox::RUNTIME_VERSION` to match,
525
- and add a row to the Versioning table. Update `CHANGELOG.md`.
613
+ and add a row to the Versioning table. Also bump
614
+ `Microsandbox::Binaries::VERSION` (and, on a runtime adoption,
615
+ `::RUNTIME_VERSION`) in `binaries/lib/microsandbox/binaries.rb` — the
616
+ companion gem ships in lockstep and the specs assert both. Update
617
+ `CHANGELOG.md`.
526
618
  2. Push a `vX.Y.Z` tag. CI builds the **source gem** and pushes it to RubyGems
527
619
  via `rubygems/configure-rubygems-credentials` (OIDC, `id-token: write`) — no
528
620
  `RUBYGEMS_API_KEY` secret required.
@@ -535,13 +627,29 @@ or credential setup is needed.
535
627
  > prove otherwise — so promotion is manual after validating the artifact on each
536
628
  > platform. A precompiled gem ships the compiled extension (with the guest
537
629
  > `agentd` baked in by *target* arch); the host-side `msb` + `libkrunfw` runtime
538
- > is fetched into `~/.microsandbox` on first use by `Microsandbox.ensure_runtime!`
539
- > (libkrunfw is `dlopen`'d by `msb` at runtime, never linked into the gem). The
630
+ > is **not** in it — that comes from the companion `microsandbox-rb-binaries`
631
+ > gem, or, when that isn't installed, is fetched into `~/.microsandbox` on first
632
+ > use by `Microsandbox.ensure_runtime!` (libkrunfw is `dlopen`'d by `msb` at
633
+ > runtime, never linked into the gem). The
540
634
  > real cross-compile work is linking the *target* native libraries — `libcap-ng`
541
635
  > on Linux (handled via Debian multiarch in the workflow) and the Hypervisor +
542
636
  > Security frameworks on macOS (via osxcross; the one platform left to confirm).
543
637
  > Until promoted, users install the source gem (which compiles via `rb_sys`).
544
638
 
639
+ > **Runtime binaries gems** (`microsandbox-rb-binaries`, source in `binaries/`)
640
+ > are a *separate* artifact from the precompiled extension gems above: they carry
641
+ > no Ruby extension, only the upstream `msb` + `libkrunfw` for one platform,
642
+ > verified against the release's published `checksums.sha256` when vendored. CI's
643
+ > `binaries` job vendors and builds all three (`arm64-darwin`,
644
+ > `x86_64-linux-gnu`, `aarch64-linux-gnu`) on every run and smoke-tests the host
645
+ > one; `release.yml`'s `binaries-gems` job does the same on a tag and a separate
646
+ > `publish-binaries` job pushes them after the SDK gem is live (a companion
647
+ > failure is its own red job and never blocks the SDK release).
648
+ > They use their own RubyGems trusted-publisher entry (same repo + workflow,
649
+ > gem name `microsandbox-rb-binaries`). To build them by hand:
650
+ > `rake -C binaries vendor[<platform>]` then `rake -C binaries build[<platform>]`
651
+ > (→ `binaries/pkg/*.gem`).
652
+
545
653
  See [DESIGN.md](DESIGN.md) for the architecture and the implemented-surface
546
654
  section. The binding covers the official-SDK surface: sandbox
547
655
  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.7).
10
- version = "0.12.0"
9
+ # The core-crate dependency below stays pinned at its own tag (v0.6.9).
10
+ version = "0.14.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.8", default-features = true, features = ["ssh"] }
39
- microsandbox-network = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.8" }
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.9", default-features = false, features = ["keyring", "net", "ssh"] }
46
+ microsandbox-network = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.9" }
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.9", 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"] }
@@ -159,10 +159,32 @@ fn default_backend_kind() -> String {
159
159
  .to_string()
160
160
  }
161
161
 
162
+ /// Secret-safe description of the active default backend (v0.6.9):
163
+ /// {kind, api_url, source, profile}. `source` names what selected the backend
164
+ /// (e.g. "MSB_BACKEND", "programmatic", "default"); the API key is never
165
+ /// included. Like `default_backend_kind`, the first call freezes ambient
166
+ /// env/profile resolution for the process.
167
+ fn default_backend_info() -> magnus::RHash {
168
+ let info = microsandbox::default_backend_info();
169
+ let hash = crate::runtime::ruby().hash_new();
170
+ let _ = hash.aset(
171
+ "kind",
172
+ match info.kind {
173
+ microsandbox::BackendKind::Local => "local",
174
+ microsandbox::BackendKind::Cloud => "cloud",
175
+ },
176
+ );
177
+ let _ = hash.aset("api_url", info.api_url);
178
+ let _ = hash.aset("source", info.source.as_str());
179
+ let _ = hash.aset("profile", info.profile);
180
+ hash
181
+ }
182
+
162
183
  pub fn define(_ruby: &Ruby, native: &RModule) -> Result<(), Error> {
163
184
  native.define_singleton_method("set_default_backend", function!(set_default_backend, 4))?;
164
185
  native.define_singleton_method("push_default_backend", function!(push_default_backend, 4))?;
165
186
  native.define_singleton_method("pop_default_backend", function!(pop_default_backend, 1))?;
166
187
  native.define_singleton_method("default_backend_kind", function!(default_backend_kind, 0))?;
188
+ native.define_singleton_method("default_backend_info", function!(default_backend_info, 0))?;
167
189
  Ok(())
168
190
  }
@@ -60,6 +60,10 @@ fn class_name(err: &MicrosandboxError) -> &'static str {
60
60
  // unconditionally enables the core's `net` feature (default-features),
61
61
  // so this variant is always present.
62
62
  NetworkBuilder(_) => "NetworkPolicyError",
63
+ // v0.6.9: `exec_default`/`attach_default` on an image whose resolved
64
+ // ENTRYPOINT+CMD provide no executable command. Mirrors the Python
65
+ // SDK's `NoDefaultCommandError`.
66
+ NoDefaultCommand => "NoDefaultCommandError",
63
67
  _ => "Error",
64
68
  }
65
69
  }