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 +4 -4
- data/CHANGELOG.md +75 -1
- data/Cargo.lock +1 -1
- data/DESIGN.md +46 -18
- data/README.md +103 -33
- data/ext/microsandbox/Cargo.toml +4 -1
- data/ext/microsandbox/extconf.rb +35 -0
- data/ext/microsandbox/src/agent.rs +166 -0
- data/ext/microsandbox/src/conv.rs +19 -1
- data/ext/microsandbox/src/lib.rs +4 -0
- data/ext/microsandbox/src/sandbox.rs +562 -3
- data/ext/microsandbox/src/ssh.rs +317 -0
- data/lib/microsandbox/agent.rb +181 -0
- data/lib/microsandbox/network.rb +300 -0
- data/lib/microsandbox/patch.rb +98 -0
- data/lib/microsandbox/sandbox.rb +119 -4
- data/lib/microsandbox/ssh.rb +247 -0
- data/lib/microsandbox/version.rb +5 -3
- data/lib/microsandbox.rb +47 -2
- data/sig/microsandbox.rbs +136 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 045a5bf021edaf5afcfcf487cf0d980c38018560cc842c44018863570236f4ed
|
|
4
|
+
data.tar.gz: 25791e8bab4051711794cb8c1e08bbabc2b4dbc79bf080b02908b620ee0bd06e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
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
|
|
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
|
|
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), `
|
|
124
|
-
|
|
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
|
-
|
|
130
|
-
`
|
|
131
|
-
`
|
|
132
|
-
|
|
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** (
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
the
|
|
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
|
-
-
|
|
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
|
-
|
|
43
|
-
|
|
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.
|
|
305
|
-
|
|
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
|
|
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),
|
|
318
|
-
filesystem, metrics (per-sandbox,
|
|
319
|
-
streaming `metrics_stream`/`log_stream`),
|
|
320
|
-
named volumes,
|
|
321
|
-
boot-from-snapshot)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
data/ext/microsandbox/Cargo.toml
CHANGED
|
@@ -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
|
|
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"
|
data/ext/microsandbox/extconf.rb
CHANGED
|
@@ -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`).
|