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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +152 -1
- data/Cargo.lock +1035 -731
- data/DESIGN.md +107 -18
- data/README.md +124 -16
- data/ext/microsandbox/Cargo.toml +23 -7
- data/ext/microsandbox/src/backend.rs +22 -0
- data/ext/microsandbox/src/error.rs +4 -0
- data/ext/microsandbox/src/sandbox.rs +198 -9
- data/ext/microsandbox/src/snapshot.rs +10 -3
- data/ext/microsandbox/src/volume.rs +9 -0
- data/lib/microsandbox/backend_info.rb +46 -0
- data/lib/microsandbox/errors.rb +4 -0
- data/lib/microsandbox/root_disk.rb +24 -0
- data/lib/microsandbox/sandbox.rb +181 -9
- data/lib/microsandbox/snapshot.rb +12 -8
- data/lib/microsandbox/version.rb +2 -2
- data/lib/microsandbox/volume.rb +16 -0
- data/lib/microsandbox.rb +144 -13
- data/sig/microsandbox.rbs +29 -2
- metadata +2 -1
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
`
|
|
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.
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 →
|
|
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`) → `
|
|
434
|
-
|
|
435
|
-
|
|
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.
|
|
449
|
-
Microsandbox.runtime_version # => "v0.6.
|
|
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.
|
|
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
|
|
539
|
-
>
|
|
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`/
|
data/ext/microsandbox/Cargo.toml
CHANGED
|
@@ -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.
|
|
10
|
-
version = "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`).
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
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
|
}
|