microsandbox-rb 0.5.7 → 0.5.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d79b0ba5b32f44bda4e984d0a231c842a0ce938ffe6859776ead5270c1043561
4
- data.tar.gz: 96a97a6c3008070fdb3c32189973736f72d522942d94761d2656e0ffa0dfc030
3
+ metadata.gz: 045a5bf021edaf5afcfcf487cf0d980c38018560cc842c44018863570236f4ed
4
+ data.tar.gz: 25791e8bab4051711794cb8c1e08bbabc2b4dbc79bf080b02908b620ee0bd06e
5
5
  SHA512:
6
- metadata.gz: 75291b5c9e1de3fe6b8b444d327d1716be099846fb87121ed9d5d00bf514bae39168cb4f7d246dabe6e41e8383b975f10e2bb7d68e0080ee9fb4ff59d546fad5
7
- data.tar.gz: eaeb263b8b666f55f6614d88f9a2643379820d7f3be293c2b87ab1810bf164be3a7c6e5f5cb36860abcbd6ab03120cd7f7a91bdaeb1652e5048f369b396758ef
6
+ metadata.gz: 1b89148498194edb82241979432ee2e47599f2b657805b05718bfd18a8e1104318833ef84eb5f5e47a66f3cec8dc369f742897bbfe60cdb07fb613b597ef6ac5
7
+ data.tar.gz: c25b033291b4fb5195349030dcf2296006fa8b3563c7e1f6804441ed5e554e496583b22ddbca32828dff1b36127082b4cb1e995ab654573afc46b09e012031fd
data/CHANGELOG.md CHANGED
@@ -6,7 +6,60 @@ upstream microsandbox runtime.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- Closes the `Sandbox`-class lifecycle gap with the official Python/Node/Go SDKs.
9
+ ## [0.5.9] - 2026-06-18
10
+
11
+ Closes the remaining roadmap items, bringing the binding surface to parity with
12
+ the official Python/Node/Go SDKs (still wrapping the same upstream core,
13
+ `v0.5.7`).
14
+
15
+ ### Added
16
+
17
+ - **Rootfs patches** — `Sandbox.create(patches: [...])` applies modifications to
18
+ the root filesystem before boot, built with the new `Microsandbox::Patch`
19
+ factory: `Patch.text`/`file`/`append`/`copy_file`/`copy_dir`/`symlink`/`mkdir`/
20
+ `remove`. Mirrors the `Patch` factory in the official SDKs. (OverlayFS/bind
21
+ roots only — not disk images.)
22
+ - **Custom per-rule network policies** — `Sandbox.create(network:)` now accepts,
23
+ besides the existing preset names, a `Microsandbox::NetworkPolicy` or a Hash
24
+ describing an ordered allow/deny rule list with per-direction defaults and bulk
25
+ domain denials. New `Microsandbox::NetworkPolicy` (`public_only`/`none`/
26
+ `allow_all`/`non_local`/`custom`), `Microsandbox::Rule` (`allow`/`deny`), and
27
+ `Microsandbox::Destination` (`any`/`ip`/`cidr`/`domain`/`domain_suffix`/
28
+ `group`, plus shorthand-string classification) factories. Destination
29
+ classification and rule composition mirror the official binding exactly.
30
+ - **Raw agent client** — `Microsandbox::AgentClient.connect_sandbox`/
31
+ `connect_path`/`socket_path` open the byte-level transport to a sandbox's
32
+ `agentd` relay socket: `request`, `stream` (→ `Microsandbox::AgentStream`,
33
+ `Enumerable` over `Microsandbox::AgentFrame`), `send_frame`, `ready_bytes`,
34
+ `close`, with the `FLAG_TERMINAL`/`FLAG_SESSION_START`/`FLAG_SHUTDOWN` frame
35
+ flags. Mirrors the official `AgentClient`.
36
+ - **SSH** — `Sandbox#ssh` returns a `Microsandbox::SshOps` to `open_client`
37
+ (→ `Microsandbox::SshClient`: `exec` → `Microsandbox::SshOutput`, `attach`,
38
+ `sftp` → `Microsandbox::SftpClient` with `read`/`write`/`mkdir`/`remove_file`/
39
+ `remove_dir`/`rename`/`symlink`/`real_path`/`read_link`, `close`) or
40
+ `prepare_server` (→ `Microsandbox::SshServer`: `serve_connection`, `close`).
41
+ - **Interactive attach** — `Sandbox#attach(command, args, …)` and
42
+ `Sandbox#attach_shell` couple the host terminal (raw mode, SIGWINCH) to a
43
+ command (or the default shell) in the sandbox and return its exit code. For
44
+ CLI use — requires a real TTY.
45
+ - RBS signatures for all of the above.
46
+
47
+ ### Notes
48
+
49
+ - Network policy: a `preset` and custom `rules:`/`default_egress:`/`default_ingress:`
50
+ are mutually exclusive (a preset already defines its rules and defaults); a
51
+ preset may still be layered with `deny_domains:`/`deny_domain_suffixes:`. A
52
+ hand-written rule Hash accepts the singular `protocol:`/`port:` keys (the
53
+ spelling the Go/Python `PolicyRule` use) as well as the plural forms. The
54
+ deny-list-only shorthand (`network: { deny_domains: [...] }`) keeps the rest of
55
+ the network reachable (permissive defaults), matching the official SDKs.
56
+
57
+ ## [0.5.8] - 2026-06-17
58
+
59
+ Closes the `Sandbox`-class lifecycle gap with the official Python/Node/Go SDKs
60
+ and adds private/authenticated registry support plus first-use runtime
61
+ auto-provisioning (the keystone for precompiled gems). Wraps the same upstream
62
+ core (`v0.5.7`); this is a gem-only revision atop it.
10
63
 
11
64
  ### Added
12
65
 
@@ -42,6 +95,25 @@ Closes the `Sandbox`-class lifecycle gap with the official Python/Node/Go SDKs.
42
95
  - CI now runs the real-microVM integration suite (`spec/integration`) on a
43
96
  KVM-enabled runner, so the Rust↔core round-trip is exercised in automation —
44
97
  not just compilation and unit tests.
98
+ - Registry authentication for `Sandbox.create`: `registry_auth: { username:,
99
+ password: }` (the password may be a token) for private/authenticated
100
+ registries and to lift Docker Hub's anonymous rate limit, plus
101
+ `registry_insecure:` (plain HTTP) and `registry_ca_certs:` (a PEM String or
102
+ Array) for self-hosted registries. Mirrors the Python/Node `registry_auth`
103
+ surface; without it the core's default resolution (OS keyring, global config,
104
+ `~/.docker/config.json`) still applies.
105
+ - `Microsandbox.ensure_runtime!` — provisions the `msb` runtime + `libkrunfw` on
106
+ first use, called automatically by `Sandbox.create`/`start`. This makes
107
+ **precompiled platform gems** usable without a manual `install` step (a
108
+ precompiled-gem user never ran the source build, so the runtime is fetched
109
+ lazily by the running host's arch). Opt out with `MICROSANDBOX_NO_AUTO_INSTALL`.
110
+
111
+ ### Changed
112
+
113
+ - The `cross-gems` release job now installs `libcap-ng-dev:arm64` via Debian
114
+ multiarch for the `aarch64-linux` cross-build (the extension links `-lcap-ng`
115
+ for the target arch). Precompiled gems remain `workflow_dispatch`-only and are
116
+ promoted to the publish path manually after per-platform validation.
45
117
 
46
118
  ## [0.5.7] - 2026-06-17
47
119
 
@@ -91,4 +163,6 @@ microsandbox runtime, aligned with the official Python/Node/Go SDKs.
91
163
  core crate has Apple-native deps). Until precompiled gems are published,
92
164
  installing from source requires a Rust toolchain (stable >= 1.91).
93
165
 
166
+ [Unreleased]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.5.8...HEAD
167
+ [0.5.8]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.5.7...v0.5.8
94
168
  [0.5.7]: https://github.com/superradcompany/microsandbox/releases/tag/v0.5.7
data/Cargo.lock CHANGED
@@ -3232,7 +3232,7 @@ dependencies = [
3232
3232
 
3233
3233
  [[package]]
3234
3234
  name = "microsandbox_rb"
3235
- version = "0.5.7"
3235
+ version = "0.5.9"
3236
3236
  dependencies = [
3237
3237
  "chrono",
3238
3238
  "futures",
data/DESIGN.md CHANGED
@@ -76,6 +76,15 @@ SDK-set path (`Microsandbox.runtime_path=`) → config file → workspace build
76
76
  expose the core `setup::install`/`is_installed` for explicit, idempotent
77
77
  provisioning (mirrors the Python `install()`/`is_installed()`).
78
78
 
79
+ Build-time provisioning only helps the **source gem**, where `build.rs` runs on
80
+ the user's own machine. A **precompiled gem** is built in CI, so its build-time
81
+ download lands on the CI host, not the user's — the user's `~/.microsandbox` is
82
+ empty. `Microsandbox.ensure_runtime!` closes that gap: `Sandbox.create`/`start`
83
+ call it to fetch the runtime on first use (by the *running* host's arch, which is
84
+ always correct), at most once per process. `MICROSANDBOX_NO_AUTO_INSTALL` opts
85
+ out (air-gapped hosts that provision out of band). libkrunfw is `dlopen`'d by
86
+ `msb` at runtime and is never linked into the extension.
87
+
79
88
  ## Core-crate dependency (self-contained)
80
89
 
81
90
  `ext/microsandbox/Cargo.toml` depends on the core crate via a **pinned git tag**
@@ -90,14 +99,22 @@ git. The override must never be committed — it would break container builds.
90
99
 
91
100
  * **Source gem**: compiles the extension via `extconf.rb` (rb-sys
92
101
  `create_rust_makefile`); requires a Rust toolchain (MSRV below).
93
- * **Precompiled platform gems**: built in CI on a `vX.Y.Z` tag
102
+ * **Precompiled platform gems**: built best-effort by the `cross-gems` job
94
103
  (`.github/workflows/release.yml`) with `oxidize-rb/cross-gem-action`
95
104
  (`rake-compiler-dock`) per `Gem::Platform`, shipping multi-ABI
96
105
  `lib/microsandbox/<ruby_abi>/` native artifacts — the same model Node uses with
97
- per-platform packages. End users then install with no Rust toolchain. Published
106
+ per-platform packages. End users then install with no Rust toolchain, and the
107
+ runtime is fetched on first use (see above). The guest `agentd` is baked into
108
+ the extension by *target* arch (`filesystem/build.rs` uses
109
+ `CARGO_CFG_TARGET_ARCH` + `include_bytes!`), so it cross-compiles correctly;
110
+ the real cross work is linking the *target* native libs — `libcap-ng` on Linux
111
+ (via Debian multiarch for `aarch64-linux`) and the Hypervisor + Security
112
+ frameworks on macOS (via osxcross; `arm64-darwin` is the platform still to
113
+ confirm — if osxcross can't link them, move it to a native `macos-14` runner).
114
+ The job is gated to `workflow_dispatch` and **not** auto-published on tags:
115
+ since CI can't boot a microVM to prove a built gem actually works, gems are
116
+ promoted to the publish path manually after per-platform validation. Published
98
117
  to RubyGems via Trusted Publishing (OIDC). See [Releasing](README.md#releasing).
99
- Whether the heavy core cross-builds for `arm64-darwin` under osxcross is
100
- confirmed on first run; if not, that platform moves to a native macOS runner.
101
118
 
102
119
  ## Build requirements
103
120
 
@@ -120,22 +137,32 @@ API (`fs.read`/`write`/`list`/`mkdir`/`remove`/`stat`/…), `metrics`,
120
137
  **OCI image-cache management** (`Image.get`/`list`/`inspect`/`remove`/`prune`),
121
138
  **named volumes** (`Volume.create`/`get`/`list`/`remove` + `volumes:` mounts),
122
139
  **snapshots** (`Snapshot.create`/`get`/`list`/`remove`/`verify`/`export`/`import`
123
- + `from_snapshot:` boot), `version`/`install`/`installed?`, and
124
- the typed error hierarchy.
140
+ + `from_snapshot:` boot), **rootfs patches** (`Patch.text`/`file`/`append`/
141
+ `copy_file`/`copy_dir`/`symlink`/`mkdir`/`remove` via `create(patches:)`),
142
+ **custom per-rule network policies** (`NetworkPolicy`/`Rule`/`Destination` —
143
+ CIDR/IP/domain/suffix/group allow-deny rules with per-direction defaults and
144
+ bulk domain denials, alongside the presets), interactive **`attach`/
145
+ `attach_shell`** (host-TTY coupled — raw mode + SIGWINCH), **SSH**
146
+ (`Sandbox#ssh` → `SshClient`/`SftpClient`/`SshServer`), the **raw agent client**
147
+ (`AgentClient` → `AgentStream`/`AgentFrame`),
148
+ `version`/`install`/`installed?`/`ensure_runtime!`, **registry auth**
149
+ (`registry_auth`/`registry_insecure`/`registry_ca_certs` on `create`, for
150
+ private/authenticated registries), and the typed error hierarchy.
125
151
 
126
152
  Create options now cover `image`, `cpus`, `memory`, `oci_upper_size`, `env`,
127
153
  `workdir`, `shell`, `user`, `hostname`, `labels`, `scripts`, `entrypoint`,
128
- `ports`/`ports_udp`, `volumes`, `network` policy presets
129
- (`public_only`/`none`/`allow_all`/`non_local`), `log_level`, `quiet_logs`,
130
- `security`, `max_duration`, `idle_timeout`, `rlimits`, `pull_policy`, `secrets`,
131
- `from_snapshot`, `detached`, and `replace`/`replace_with_timeout`. `exec`/`shell`
132
- add per-call `rlimits`.
154
+ `ports`/`ports_udp`, `volumes`, `patches`, `network` (policy presets
155
+ `public_only`/`none`/`allow_all`/`non_local`, or a custom `NetworkPolicy`/Hash),
156
+ `log_level`, `quiet_logs`, `security`, `max_duration`, `idle_timeout`, `rlimits`,
157
+ `pull_policy`, `registry_auth`/`registry_insecure`/`registry_ca_certs`,
158
+ `secrets`, `from_snapshot`, `detached`, and `replace`/`replace_with_timeout`.
159
+ `exec`/`shell` add per-call `rlimits`.
133
160
 
134
161
  ## Verification
135
162
 
136
163
  The binding is verified at four levels:
137
164
 
138
- 1. **Unit** (130 examples) — the Ruby layer's option normalization and value
165
+ 1. **Unit** (192 examples) — the Ruby layer's option normalization and value
139
166
  objects, with the native layer stubbed.
140
167
  2. **Real-microVM integration** (`spec/integration`, opt-in via
141
168
  `MICROSANDBOX_INTEGRATION=1`) — boots actual sandboxes and round-trips
@@ -151,9 +178,10 @@ The binding is verified at four levels:
151
178
  shipped Rust source via `extconf.rb` and the installed gem boots a real
152
179
  microVM, confirming the gem manifest and source-install path are complete.
153
180
 
154
- **Roadmap:** custom per-rule network policies (CIDR/domain/group allow-deny
155
- rules the presets and secret-host allowances are covered), file patches,
156
- registry auth, interactive `attach`/`attach_shell` (host-TTY coupled raw mode,
157
- SIGWINCH), SSH (`SshClient`/`SftpClient`/`SshServer`), and the raw agent client.
158
- The native layer is structured so these slot in module-by-module, exactly as in
159
- the Python binding.
181
+ **Roadmap:** the v1 roadmap (custom per-rule network policies, file patches,
182
+ interactive `attach`/`attach_shell`, SSH, and the raw agent client) is now
183
+ implemented see the list above. What remains is upstream-gated rather than a
184
+ binding gap: surfacing newer core features as the pinned core-crate tag advances
185
+ (e.g. additional network knobs like DNS/TLS-proxy tuning and per-mount stat
186
+ virtualization), which slot in module-by-module exactly as the existing bindings
187
+ do.
data/README.md CHANGED
@@ -14,6 +14,10 @@ This is an **unofficial, community-maintained** Ruby implementation — not part
14
14
  - **Command execution** — run commands or shell scripts and collect output
15
15
  - **Guest filesystem access** — read, write, list, copy, stat files inside a running sandbox
16
16
  - **Metrics & logs** — CPU, memory, disk and network I/O; captured stdout/stderr/system logs
17
+ - **Rootfs patches** — inject files, dirs, and symlinks into the image before boot (`Microsandbox::Patch`)
18
+ - **Fine-grained networking** — policy presets *and* custom CIDR/domain/group allow-deny rules (`Microsandbox::NetworkPolicy`)
19
+ - **SSH & SFTP** — native in-process SSH client/server and file transfer (`Sandbox#ssh`)
20
+ - **Raw agent client** — byte-level access to the guest `agentd` protocol (`Microsandbox::AgentClient`)
17
21
  - **Idiomatic Ruby** — keyword arguments, block-scoped lifecycle, a typed error hierarchy
18
22
  - **Thread-friendly** — the GVL is released during sandbox calls, so other Ruby threads keep running
19
23
 
@@ -21,7 +25,9 @@ This is an **unofficial, community-maintained** Ruby implementation — not part
21
25
 
22
26
  - **Ruby** >= 3.1
23
27
  - **Linux** with KVM enabled, or **macOS** on Apple Silicon (M-series)
24
- - Building from source additionally needs a **Rust** toolchain (stable >= 1.91)
28
+ - A **Rust** toolchain (stable >= 1.91) — needed only when installing the source
29
+ gem (it compiles the native extension on install). Precompiled per-platform
30
+ gems, where available, require no Rust toolchain; see [Releasing](#releasing)
25
31
 
26
32
  ## Installation
27
33
 
@@ -39,19 +45,29 @@ bundle install
39
45
  gem install microsandbox-rb
40
46
  ```
41
47
 
42
- The first build downloads the `msb` runtime and `libkrunfw` firmware into
43
- `~/.microsandbox`. You can (re)provision them explicitly at any time:
48
+ Installing the **source gem** compiles the Rust extension, so the first install
49
+ takes a few minutes and needs a Rust toolchain (`rustc >= 1.91`) on `PATH`. When
50
+ a **precompiled platform gem** is available for your OS/architecture, RubyGems
51
+ picks it automatically and no Rust toolchain is required.
52
+
53
+ Either way the `msb` runtime and `libkrunfw` firmware are provisioned into
54
+ `~/.microsandbox` automatically on first use (the first `Sandbox.create`/`start`
55
+ downloads them if missing). To provision ahead of time — e.g. while baking a
56
+ container image, or to avoid the first-call latency — call `install` explicitly:
44
57
 
45
58
  ```ruby
46
59
  Microsandbox.install unless Microsandbox.installed?
47
60
  ```
48
61
 
62
+ Set `MICROSANDBOX_NO_AUTO_INSTALL` to disable the automatic first-use download
63
+ (e.g. on air-gapped hosts that provision the runtime out of band).
64
+
49
65
  ## Quick start
50
66
 
51
67
  ```ruby
52
68
  require "microsandbox"
53
69
 
54
- Microsandbox::Sandbox.create("hello", image: "python") do |sb|
70
+ Microsandbox::Sandbox.create("hello", image: "public.ecr.aws/docker/library/python:3-slim") do |sb|
55
71
  output = sb.exec("python", ["-c", "print('Hello, World!')"])
56
72
  puts output.stdout # => "Hello, World!\n"
57
73
  puts output.success? # => true
@@ -59,18 +75,25 @@ end
59
75
  # the sandbox is stopped automatically when the block returns
60
76
  ```
61
77
 
78
+ > **Why `public.ecr.aws/docker/library/...`?** The examples pull from AWS's
79
+ > public mirror of the Docker Library because anonymous **Docker Hub** pulls are
80
+ > rate-limited and often fail with `registry error: Not authorized`. Plain short
81
+ > names like `image: "python"` work too if you aren't rate-limited. For private
82
+ > or authenticated registries (including authenticated Docker Hub), pass
83
+ > `registry_auth:` — see [Private & authenticated registries](#private--authenticated-registries).
84
+
62
85
  ## Usage
63
86
 
64
87
  ### Lifecycle
65
88
 
66
89
  ```ruby
67
90
  # Block form — recommended; stops the sandbox automatically (even on error)
68
- Microsandbox::Sandbox.create("box", image: "alpine") do |sb|
91
+ Microsandbox::Sandbox.create("box", image: "public.ecr.aws/docker/library/alpine:latest") do |sb|
69
92
  # ...
70
93
  end
71
94
 
72
95
  # Manual form — you are responsible for stopping it
73
- sb = Microsandbox::Sandbox.create("box", image: "alpine")
96
+ sb = Microsandbox::Sandbox.create("box", image: "public.ecr.aws/docker/library/alpine:latest")
74
97
  begin
75
98
  # ...
76
99
  ensure
@@ -90,7 +113,7 @@ Microsandbox::Sandbox.remove("box") # remove a stopped sandbox
90
113
  ```ruby
91
114
  Microsandbox::Sandbox.create(
92
115
  "configured",
93
- image: "python",
116
+ image: "public.ecr.aws/docker/library/python:3-slim",
94
117
  cpus: 2,
95
118
  memory: 1024, # MiB
96
119
  env: { "API_BASE" => "https://example.com" },
@@ -107,7 +130,7 @@ end
107
130
  ### Executing commands
108
131
 
109
132
  ```ruby
110
- Microsandbox::Sandbox.create("exec-demo", image: "alpine") do |sb|
133
+ Microsandbox::Sandbox.create("exec-demo", image: "public.ecr.aws/docker/library/alpine:latest") do |sb|
111
134
  # Direct command (no shell)
112
135
  out = sb.exec("ls", ["-la", "/etc"], cwd: "/", timeout: 30)
113
136
  out.exit_code # => 0
@@ -130,7 +153,7 @@ failures (e.g. command not found) and timeouts raise typed errors (see below).
130
153
  ### Guest filesystem
131
154
 
132
155
  ```ruby
133
- Microsandbox::Sandbox.create("fs-demo", image: "alpine") do |sb|
156
+ Microsandbox::Sandbox.create("fs-demo", image: "public.ecr.aws/docker/library/alpine:latest") do |sb|
134
157
  sb.fs.write("/tmp/data.txt", "hello")
135
158
  sb.fs.read_text("/tmp/data.txt") # => "hello" (UTF-8)
136
159
  sb.fs.read("/tmp/data.txt") # => raw bytes (ASCII-8BIT)
@@ -151,7 +174,7 @@ end
151
174
  ### Metrics & logs
152
175
 
153
176
  ```ruby
154
- Microsandbox::Sandbox.create("obs", image: "alpine") do |sb|
177
+ Microsandbox::Sandbox.create("obs", image: "public.ecr.aws/docker/library/alpine:latest") do |sb|
155
178
  m = sb.metrics # => Microsandbox::Metrics
156
179
  m.cpu_percent
157
180
  m.memory_bytes
@@ -168,7 +191,7 @@ end
168
191
  For long-running commands, stream events as they arrive instead of waiting:
169
192
 
170
193
  ```ruby
171
- Microsandbox::Sandbox.create("stream", image: "python") do |sb|
194
+ Microsandbox::Sandbox.create("stream", image: "public.ecr.aws/docker/library/python:3-slim") do |sb|
172
195
  handle = sb.exec_stream("python", ["-u", "-c", "import time\nfor i in range(3): print(i); time.sleep(1)"])
173
196
  handle.each do |event| # ExecHandle is Enumerable
174
197
  print event.text if event.stdout?
@@ -186,13 +209,45 @@ Manage the local OCI image cache (images are pulled automatically on `create`):
186
209
 
187
210
  ```ruby
188
211
  Microsandbox::Image.list # => [Microsandbox::ImageInfo, ...]
189
- Microsandbox::Image.get("alpine") # => Microsandbox::ImageInfo
190
- Microsandbox::Image.inspect("alpine").layers # => [{...}, ...]
191
- Microsandbox::Image.remove("alpine", force: true)
212
+ Microsandbox::Image.get("public.ecr.aws/docker/library/alpine:latest") # => Microsandbox::ImageInfo
213
+ Microsandbox::Image.inspect("public.ecr.aws/docker/library/alpine:latest").layers # => [{...}, ...]
214
+ Microsandbox::Image.remove("public.ecr.aws/docker/library/alpine:latest", force: true)
192
215
  report = Microsandbox::Image.prune
193
216
  report.bytes_reclaimed
194
217
  ```
195
218
 
219
+ ### Private & authenticated registries
220
+
221
+ Images are pulled automatically on `create`. For a private registry — or to lift
222
+ Docker Hub's anonymous rate limit — pass `registry_auth:` with a username and a
223
+ password or token:
224
+
225
+ ```ruby
226
+ Microsandbox::Sandbox.create(
227
+ "private",
228
+ image: "registry.example.com/team/app:latest",
229
+ registry_auth: { username: "ci-bot", password: ENV.fetch("REGISTRY_TOKEN") }
230
+ ) do |sb|
231
+ # ...
232
+ end
233
+ ```
234
+
235
+ For self-hosted registries you can also reach the registry over plain HTTP and
236
+ trust a private CA:
237
+
238
+ ```ruby
239
+ Microsandbox::Sandbox.create(
240
+ "internal",
241
+ image: "registry.internal:5000/app:latest",
242
+ registry_insecure: true, # plain HTTP instead of HTTPS
243
+ registry_ca_certs: File.read("/etc/pki/internal-ca.pem") # String or Array of PEMs
244
+ )
245
+ ```
246
+
247
+ Without `registry_auth:`, the core's default credential resolution still applies
248
+ (OS keyring, global config, and `~/.docker/config.json`), so an existing
249
+ `docker login` is honored automatically.
250
+
196
251
  ### Named volumes
197
252
 
198
253
  Persistent storage that outlives individual sandboxes:
@@ -201,7 +256,7 @@ Persistent storage that outlives individual sandboxes:
201
256
  Microsandbox::Volume.create("cache", kind: "disk", size_mib: 512)
202
257
  Microsandbox::Volume.list # => [Microsandbox::VolumeInfo, ...]
203
258
 
204
- Microsandbox::Sandbox.create("with-vol", image: "alpine",
259
+ Microsandbox::Sandbox.create("with-vol", image: "public.ecr.aws/docker/library/alpine:latest",
205
260
  volumes: { "/data" => { named: "cache" } }) do |sb|
206
261
  sb.fs.write("/data/state.txt", "persisted")
207
262
  end
@@ -219,8 +274,8 @@ All errors descend from `Microsandbox::Error` and carry a stable `#code`:
219
274
 
220
275
  ```ruby
221
276
  begin
222
- Microsandbox::Sandbox.create("dup", image: "alpine")
223
- Microsandbox::Sandbox.create("dup", image: "alpine") # name clash
277
+ Microsandbox::Sandbox.create("dup", image: "public.ecr.aws/docker/library/alpine:latest")
278
+ Microsandbox::Sandbox.create("dup", image: "public.ecr.aws/docker/library/alpine:latest") # name clash
224
279
  rescue Microsandbox::SandboxAlreadyExistsError => e
225
280
  warn "#{e.code}: #{e.message}" # => "sandbox-already-exists: ..."
226
281
  rescue Microsandbox::Error => e
@@ -301,27 +356,42 @@ one bound to the gem.
301
356
  1. Bump `Microsandbox::VERSION` (and the `tag = "vX.Y.Z"` on the core-crate
302
357
  dependency in `ext/microsandbox/Cargo.toml`) to match the upstream runtime,
303
358
  update `CHANGELOG.md`.
304
- 2. *(Recommended first time)* run the workflow's manual `dry_run` dispatch to
305
- build all platform gems without publishing confirms the `arm64-darwin`
306
- cross-build succeeds.
307
- 3. Push a `vX.Y.Z` tag. CI builds precompiled, multi-ABI platform gems
308
- (`x86_64-linux`, `aarch64-linux`, `arm64-darwin`) with `rake-compiler-dock`,
309
- plus the source gem, and pushes them to RubyGems. The publish job uses
310
- `rubygems/configure-rubygems-credentials` (OIDC) with `id-token: write` — no
359
+ 2. Push a `vX.Y.Z` tag. CI builds the **source gem** and pushes it to RubyGems
360
+ via `rubygems/configure-rubygems-credentials` (OIDC, `id-token: write`)no
311
361
  `RUBYGEMS_API_KEY` secret required.
312
362
 
363
+ > **Precompiled per-platform gems** are built best-effort by the `cross-gems`
364
+ > job, gated to manual `workflow_dispatch` so you can iterate
365
+ > (`gh workflow run release.yml`) without failing tag releases. They are not
366
+ > auto-published on tags yet: a gem that *compiles but can't boot a microVM*
367
+ > would be served to users ahead of the source gem, and CI can't boot a VM to
368
+ > prove otherwise — so promotion is manual after validating the artifact on each
369
+ > platform. A precompiled gem ships the compiled extension (with the guest
370
+ > `agentd` baked in by *target* arch); the host-side `msb` + `libkrunfw` runtime
371
+ > is fetched into `~/.microsandbox` on first use by `Microsandbox.ensure_runtime!`
372
+ > (libkrunfw is `dlopen`'d by `msb` at runtime, never linked into the gem). The
373
+ > real cross-compile work is linking the *target* native libraries — `libcap-ng`
374
+ > on Linux (handled via Debian multiarch in the workflow) and the Hypervisor +
375
+ > Security frameworks on macOS (via osxcross; the one platform left to confirm).
376
+ > Until promoted, users install the source gem (which compiles via `rb_sys`).
377
+
313
378
  See [DESIGN.md](DESIGN.md) for the architecture and the implemented-surface
314
- section for what's covered today vs. on the roadmap. Covered: full sandbox
379
+ section. The binding now covers the full official-SDK surface: sandbox
315
380
  lifecycle (including the async `request_stop`/`request_kill`/`request_drain`/
316
381
  `wait_until_stopped`/`detach`/`owns_lifecycle?` controls and label-filtered
317
- `list_with`), `exec`/`shell` (collected and streaming), the full guest
318
- filesystem, metrics (per-sandbox, `Microsandbox.all_sandbox_metrics`, and
319
- streaming `metrics_stream`/`log_stream`), logs, OCI image-cache management,
320
- named volumes, and snapshots (create/list/verify/export/import +
321
- boot-from-snapshot). Create options span resources, network policy presets,
322
- `log_level`/`security`/`rlimits`/`pull_policy`/`secrets` and more; `exec`/`shell`
323
- take per-call `rlimits`. Still on the roadmap: custom per-rule network policies,
324
- file patches, registry auth, interactive `attach`, SSH, and the raw agent client.
382
+ `list_with`), `exec`/`shell` (collected and streaming), interactive `attach`/
383
+ `attach_shell`, the full guest filesystem, metrics (per-sandbox,
384
+ `Microsandbox.all_sandbox_metrics`, and streaming `metrics_stream`/`log_stream`),
385
+ logs, OCI image-cache management, named volumes, snapshots (create/list/verify/
386
+ export/import + boot-from-snapshot), **rootfs patches** (`Microsandbox::Patch`),
387
+ **custom per-rule network policies** (`Microsandbox::NetworkPolicy`/`Rule`/
388
+ `Destination`, alongside the presets), **SSH** (`Sandbox#ssh`
389
+ `SshClient`/`SftpClient`/`SshServer`), and the **raw agent client**
390
+ (`Microsandbox::AgentClient`). Create options span resources, network policy,
391
+ `log_level`/`security`/`rlimits`/`pull_policy`/`secrets`/`patches` and more;
392
+ `exec`/`shell` take per-call `rlimits`, and `create` accepts
393
+ `registry_auth`/`registry_insecure`/`registry_ca_certs` for private and
394
+ authenticated registries.
325
395
 
326
396
  ## License
327
397
 
@@ -4,7 +4,10 @@
4
4
  # `microsandbox/microsandbox_rb` and stays hidden behind `require "microsandbox"`.
5
5
  name = "microsandbox_rb"
6
6
  description = "Ruby SDK native extension for microsandbox — secure, fast microVM-based sandboxing."
7
- version = "0.5.7"
7
+ # Must equal Microsandbox::VERSION (lib/microsandbox/version.rb) Native.version
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.5.7).
10
+ version = "0.5.9"
8
11
  authors = ["Super Rad Company <development@superrad.company>"]
9
12
  repository = "https://github.com/superradcompany/microsandbox"
10
13
  license = "Apache-2.0"
@@ -3,6 +3,41 @@
3
3
  require "mkmf"
4
4
  require "rb_sys/mkmf"
5
5
 
6
+ # Preflight: the embedded microsandbox core is edition 2024 and pulls smoltcp,
7
+ # which sets a Minimum Supported Rust Version of 1.91. A `cargo` from an older
8
+ # toolchain (commonly Homebrew's rustc, which shadows a newer rustup on PATH and
9
+ # ignores this gem's rust-toolchain.toml) fails deep in the build with a cryptic
10
+ # "rustc X is not supported by smoltcp" error. Detect it up front and explain
11
+ # the fix instead.
12
+ MSRV = Gem::Version.new("1.91")
13
+ rustc_version = begin
14
+ out = `rustc --version 2>/dev/null`
15
+ out[/\d+\.\d+(\.\d+)?/] && Gem::Version.new(out[/\d+\.\d+(\.\d+)?/])
16
+ rescue StandardError
17
+ nil
18
+ end
19
+
20
+ if rustc_version && rustc_version < MSRV
21
+ which_rustc = (`which rustc 2>/dev/null`.strip rescue "")
22
+ abort(<<~MSG)
23
+
24
+ [microsandbox-rb] Rust #{rustc_version} is too old — the embedded core requires rustc >= #{MSRV}.
25
+ Found: #{which_rustc.empty? ? "rustc" : which_rustc} (#{rustc_version})
26
+
27
+ This usually means an older rustc (e.g. Homebrew's) is ahead of a newer
28
+ rustup toolchain on your PATH. Fixes:
29
+
30
+ • Put rustup's toolchain first for the install:
31
+ PATH="$HOME/.cargo/bin:$PATH" gem install microsandbox-rb
32
+ • Or make a recent stable the default and ensure no other rustc shadows it:
33
+ rustup install stable && rustup default stable
34
+ • Or upgrade your system Rust to >= #{MSRV}.
35
+
36
+ (This gem ships a rust-toolchain.toml pinning `stable`; the rustup `cargo`
37
+ shim honors it, but a non-rustup `cargo` does not.)
38
+ MSG
39
+ end
40
+
6
41
  # Builds the Rust cdylib and installs it as lib/microsandbox/microsandbox.{bundle,so}.
7
42
  # The Cargo profile is selected via the RB_SYS_CARGO_PROFILE env var (defaults to
8
43
  # release for installed gems, dev for `rake compile`).