microsandbox-rb 0.8.1 → 0.9.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 +163 -2
- data/Cargo.lock +88 -98
- data/DESIGN.md +22 -4
- data/README.md +15 -3
- data/ext/microsandbox/Cargo.toml +4 -4
- data/ext/microsandbox/extconf.rb +33 -14
- data/ext/microsandbox/src/error.rs +16 -0
- data/ext/microsandbox/src/sandbox.rs +32 -9
- data/lib/microsandbox/agent.rb +4 -0
- data/lib/microsandbox/errors.rb +15 -0
- data/lib/microsandbox/exec_handle.rb +7 -0
- data/lib/microsandbox/fs.rb +5 -0
- data/lib/microsandbox/sandbox.rb +41 -3
- data/lib/microsandbox/ssh.rb +15 -4
- data/lib/microsandbox/streams.rb +13 -0
- data/lib/microsandbox/version.rb +2 -2
- data/lib/microsandbox.rb +9 -5
- data/sig/microsandbox.rbs +14 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3395a95418c2c6e159365140455797f0b40318aa585aea8b0b4ac0b93f521957
|
|
4
|
+
data.tar.gz: e39164f8143656a0f069720f3359e133bffe2cb5438146dab3b3e629abbe9647
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d07ddd920c69d73c86372176745047ed27c515a5ba3dd30bb1c9fe5ddcf2de39009dc670d1f44965d38ea6c8399e84224640e51fe4caf792f40291ab49befbe1
|
|
7
|
+
data.tar.gz: 2eb6879b20289edbcebf80eee934bfd81d176bf6f8e3c577f3da21d8e987e15315db486c612f5f1bd8fa296a35ba4f3244e293496116bce321ae7bd9a9201530
|
data/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,164 @@ All notable changes to this gem are documented here. The format is based on
|
|
|
6
6
|
microsandbox runtime it embeds; each release notes the upstream runtime tag it
|
|
7
7
|
wraps, and the README's Versioning section keeps the full gem→runtime map.
|
|
8
8
|
|
|
9
|
-
## [
|
|
9
|
+
## [0.9.0] - 2026-06-29
|
|
10
|
+
|
|
11
|
+
Adopts upstream runtime **`v0.5.10` → `v0.6.1`** (spanning the upstream `v0.6.0`
|
|
12
|
+
and `v0.6.1` releases). The upstream public SDK surface is purely additive — no
|
|
13
|
+
items were removed or re-signatured — so the gem's Ruby API is unchanged and the
|
|
14
|
+
existing bindings compile against `v0.6.1` untouched. Per the README's versioning
|
|
15
|
+
policy, adopting a new upstream runtime moves the gem onto its own `0.9` line.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Embedded runtime is now `v0.6.1`** (`Microsandbox::RUNTIME_VERSION` /
|
|
20
|
+
`Microsandbox.runtime_version`). Upstream fixes inherited by the synchronous
|
|
21
|
+
Ruby API:
|
|
22
|
+
- **Zombie sandbox runtimes no longer block** (upstream #1036): the SDK stops
|
|
23
|
+
waiting on a sandbox runtime that has already exited, so lifecycle calls
|
|
24
|
+
return promptly instead of hanging on a dead child.
|
|
25
|
+
- **Secrets are substituted through `CONNECT` proxies** (upstream #1022):
|
|
26
|
+
`microsandbox-network` now applies secret injection on tunnelled (HTTPS
|
|
27
|
+
`CONNECT`) requests, not only on plain-HTTP ones.
|
|
28
|
+
- **Stale sandboxes are stopped and cleaned up** (upstream #1050).
|
|
29
|
+
- Windows host support, `msb` CLI additions (`--no-tty`, self-downgrade,
|
|
30
|
+
cross-platform `doctor`), and the `msb_krun` `0.1.17 → 0.1.19` bump are
|
|
31
|
+
inherited but do not affect the macOS/Linux Ruby build or API surface.
|
|
32
|
+
|
|
33
|
+
### Notes
|
|
34
|
+
|
|
35
|
+
- The new upstream **host-directory bind rootfs** (`ImageBuilder::bind`, upstream
|
|
36
|
+
#1021) is intentionally **not** exposed in this release: it requires a real
|
|
37
|
+
microVM boot to exercise, and the Python SDK parity reference only stubs the
|
|
38
|
+
type without wiring it into the sandbox builder. Tracked as a possible
|
|
39
|
+
follow-up.
|
|
40
|
+
|
|
41
|
+
## [0.8.2] - 2026-06-29
|
|
42
|
+
|
|
43
|
+
Gem-only release on the `v0.5.10` runtime (unchanged). Bundles the post-`0.8.1`
|
|
44
|
+
audit follow-ups: a secret-leak fix, typed snapshot errors, panic-free duration
|
|
45
|
+
parsing, the precompiled fat-gem loader + `extconf` preflight corrections, and a
|
|
46
|
+
sweep of threading/streaming/SSH documentation.
|
|
47
|
+
|
|
48
|
+
### Documentation
|
|
49
|
+
|
|
50
|
+
- **Calling-thread non-preemption is now documented** (issue #24). The GVL is
|
|
51
|
+
released during native calls so *other* threads keep running, but the
|
|
52
|
+
*calling* thread blocks uninterruptibly until the call returns —
|
|
53
|
+
`Timeout::timeout`/`Thread#kill`/Ctrl-C can't interrupt it. README and
|
|
54
|
+
DESIGN.md now state this and steer callers to the genuinely-bounding
|
|
55
|
+
`exec(timeout:)` / `shell(timeout:)` knobs, and clarify that
|
|
56
|
+
`AgentClient.connect_sandbox`/`connect_path`'s `timeout:` bounds only the
|
|
57
|
+
connect handshake while `AgentClient#request`/`#stream` and the streaming
|
|
58
|
+
paths have no timeout knob and can block indefinitely — rather than reaching
|
|
59
|
+
for `Timeout::timeout`.
|
|
60
|
+
- **`exec` `timeout: 0` semantics clarified** (issue #29). The `@param timeout`
|
|
61
|
+
doc now notes the asymmetry: omit or `nil` means *no* timeout, while `0` is an
|
|
62
|
+
immediate (zero) deadline that kills the command before any output and raises
|
|
63
|
+
`ExecTimeoutError` — so use `nil`/omit, never `0`, for "no limit". Also noted
|
|
64
|
+
that `exec_stream`/`shell_stream` accept `timeout:` but do **not** apply it
|
|
65
|
+
(the streaming path discards it).
|
|
66
|
+
- **Streaming classes documented as single-pass / single-consumer** (issues #34,
|
|
67
|
+
#31). `ExecHandle`, `LogStream`, `MetricsStream`, `FsReadStream`,
|
|
68
|
+
`PullSession`, and `AgentStream` are `Enumerable` but drain a one-shot native
|
|
69
|
+
channel: forward-only, not rewindable, and meant for one consumer on one
|
|
70
|
+
thread. A second `each` (or a combinator after a partial drain) silently
|
|
71
|
+
yields nothing. Noted on each class and in a README streaming caveat.
|
|
72
|
+
- **SSH `close` disconnect behavior documented** (issue #33). `SshClient#close` /
|
|
73
|
+
`SftpClient#close` send the graceful protocol disconnect; relying on GC skips
|
|
74
|
+
it (only the in-process server task is aborted). The block-less
|
|
75
|
+
`open_client`/`sftp` docs now tell callers to `close` (or use the block form)
|
|
76
|
+
for a clean disconnect.
|
|
77
|
+
|
|
78
|
+
### Internal
|
|
79
|
+
|
|
80
|
+
- **`DESIGN.md` refreshed** (issue #35). The stale runtime-pin references
|
|
81
|
+
(`v0.5.7`/`v0.5.8`) now point at `v0.5.10` via `RUNTIME_VERSION`, and the
|
|
82
|
+
hard-coded unit-example count is replaced with rot-proof phrasing.
|
|
83
|
+
- **RBS gains a note on SDK-constructed types** (issue #36). `sig/microsandbox.rbs`
|
|
84
|
+
now carries a top-of-file note explaining that native-backed value/handle/stream
|
|
85
|
+
types are constructed by the SDK from an internal native handle or data hash, not
|
|
86
|
+
by user code. Their `initialize` signatures are kept: RBS derives `new` from
|
|
87
|
+
`initialize`, so omitting them would not hide the constructor — it would
|
|
88
|
+
synthesize a misleading zero-arg `() -> instance` that Ruby actually rejects.
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- **Precompiled fat-gem loader now finds the staged binary** (issue #25). The
|
|
93
|
+
native-extension require used `RbConfig::CONFIG["ruby_version"]` — the API
|
|
94
|
+
string `"3.4.0"` — but rake-compiler stages a multi-version fat gem's binaries
|
|
95
|
+
under the **major.minor** subdir (`3.4`), so the versioned require always
|
|
96
|
+
missed and fell to the flat-path rescue, which is **absent** in a precompiled
|
|
97
|
+
gem (only the versioned binary is packed). Every fat-gem install would have
|
|
98
|
+
failed at `require "microsandbox"` the moment precompiled gems are promoted.
|
|
99
|
+
The loader now derives the subdir from `RUBY_VERSION[/\d+\.\d+/]` (`"3.4"`),
|
|
100
|
+
matching the staged path; the flat-path rescue still covers source builds.
|
|
101
|
+
- **`extconf` MSRV preflight probes the compiler the build actually runs**
|
|
102
|
+
(issue #39). The preflight ran a bare `rustc --version` and hard-aborted when
|
|
103
|
+
`< 1.91`, but the build is driven by `cargo`, which resolves its compiler from
|
|
104
|
+
`$RUSTC` if set, otherwise the bare `rustc` on PATH — it never uses the `rustc`
|
|
105
|
+
beside the `cargo` binary, and the rustup `cargo` shim neither sets `$RUSTC` nor
|
|
106
|
+
reorders PATH. The preflight now mirrors that exact resolution (`$RUSTC`, else
|
|
107
|
+
PATH `rustc`), so it neither false-passes when a stale non-rustup `rustc`
|
|
108
|
+
shadows a rustup `cargo` (the build would compile with that stale `rustc` and
|
|
109
|
+
fail deep in smoltcp) nor false-aborts when `$RUSTC` points at a newer compiler.
|
|
110
|
+
|
|
111
|
+
### Internal
|
|
112
|
+
|
|
113
|
+
- **CI now installs the packed gem from source** (issue #37). A new `package`
|
|
114
|
+
job runs `rake build`, `gem install`s the packed gem (exercising the gemspec
|
|
115
|
+
`spec.files` glob and the full from-gem `extconf` + `cargo` compile against the
|
|
116
|
+
packed `Cargo.toml`/`Cargo.lock`/`rust-toolchain.toml`), and requires it from
|
|
117
|
+
outside the repo. Previously every job compiled the working tree in place, so
|
|
118
|
+
a packaging regression could reach RubyGems undetected.
|
|
119
|
+
- **`version_spec` now guards the `Cargo.lock` version** (issue #38). The spec
|
|
120
|
+
already asserted `Native.version == VERSION` and the runtime-tag pin, but
|
|
121
|
+
nothing checked the `microsandbox_rb` version in the committed `Cargo.lock`,
|
|
122
|
+
which the gemspec packs. A release that bumped `version.rb` + `Cargo.toml` but
|
|
123
|
+
forgot to refresh the lock would ship a stale lock (and a `--locked` build
|
|
124
|
+
would reject it) — a recurring release mistake this now catches.
|
|
125
|
+
### Security
|
|
126
|
+
|
|
127
|
+
- **Secret values no longer leak into `ArgumentError` messages** (issue #23).
|
|
128
|
+
`Sandbox.create(secrets:)` validation interpolated the whole secret spec via
|
|
129
|
+
`spec.inspect` into two error messages — and because the `:value`-present
|
|
130
|
+
guard runs first, the "needs `:host`/`:hosts`/`:host_patterns`" error *always*
|
|
131
|
+
embedded the cleartext secret value (and the env/value error did whenever a
|
|
132
|
+
value was supplied). Such messages routinely reach logs and error trackers.
|
|
133
|
+
Both messages now report the spec's keys only (`spec.keys.inspect`), mirroring
|
|
134
|
+
the existing `registry_auth` handling, with a unit spec asserting the value
|
|
135
|
+
is never present in the raised message.
|
|
136
|
+
|
|
137
|
+
### Fixed
|
|
138
|
+
|
|
139
|
+
- **Native duration parsing is panic-free regardless of the Ruby layer**
|
|
140
|
+
(issue #30). The native binding called `Duration::from_secs_f64` directly at
|
|
141
|
+
five sites (`exec`/`shell` timeout, `stop_with_timeout`, `kill_with_timeout`,
|
|
142
|
+
`metrics_stream` interval, `replace_with_timeout`); that panics on NaN/Inf/
|
|
143
|
+
negative *and on finite-but-out-of-range* values (e.g. `Float::MAX`), which
|
|
144
|
+
surfaced as an ugly panic-turned-exception. The Ruby `coerce_duration` guard
|
|
145
|
+
set no upper bound, so a large finite value still reached and panicked the
|
|
146
|
+
native layer. All five sites now route through a `secs_to_duration` helper
|
|
147
|
+
(`try_from_secs_f64` + a clean `Microsandbox::Error`), matching the existing
|
|
148
|
+
agent-client pattern — defense in depth so the native layer is panic-free on
|
|
149
|
+
its own.
|
|
150
|
+
### Added
|
|
151
|
+
|
|
152
|
+
- **Typed snapshot error classes** (issue #28). The five core snapshot error
|
|
153
|
+
variants — reachable through the gem's fully-wired `Snapshot` API — previously
|
|
154
|
+
collapsed to the base `Microsandbox::Error`, forcing callers to string-match
|
|
155
|
+
the message. They now raise typed subclasses:
|
|
156
|
+
`SnapshotNotFoundError` (`snapshot-not-found`),
|
|
157
|
+
`SnapshotAlreadyExistsError` (`snapshot-already-exists`),
|
|
158
|
+
`SnapshotSandboxRunningError` (`snapshot-sandbox-running`),
|
|
159
|
+
`SnapshotImageMissingError` (`snapshot-image-missing`), and
|
|
160
|
+
`SnapshotIntegrityError` (`snapshot-integrity`). This goes **beyond** the
|
|
161
|
+
Python SDK mirror (which defines no snapshot classes), matching the Go SDK's
|
|
162
|
+
per-variant coverage — a deliberate divergence. Additionally, the previously
|
|
163
|
+
orphaned `NetworkPolicyError` now also carries the core's `NetworkBuilder`
|
|
164
|
+
build/validation error (a `network(|n| ...)` failure), which previously fell
|
|
165
|
+
through to the base `Error`. All additive — existing `rescue Microsandbox::Error`
|
|
166
|
+
handlers still catch them.
|
|
10
167
|
|
|
11
168
|
## [0.8.1] - 2026-06-25
|
|
12
169
|
|
|
@@ -455,7 +612,11 @@ microsandbox runtime, aligned with the official Python/Node/Go SDKs.
|
|
|
455
612
|
core crate has Apple-native deps). Until precompiled gems are published,
|
|
456
613
|
installing from source requires a Rust toolchain (stable >= 1.91).
|
|
457
614
|
|
|
458
|
-
[Unreleased]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.
|
|
615
|
+
[Unreleased]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.9.0...HEAD
|
|
616
|
+
[0.9.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.8.2...v0.9.0
|
|
617
|
+
[0.8.2]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.8.1...v0.8.2
|
|
618
|
+
[0.8.1]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.8.0...v0.8.1
|
|
619
|
+
[0.8.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.7.0...v0.8.0
|
|
459
620
|
[0.7.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.6.0...v0.7.0
|
|
460
621
|
[0.6.0]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.5.12...v0.6.0
|
|
461
622
|
[0.5.12]: https://github.com/ya-luotao/microsandbox-rb/compare/v0.5.11...v0.5.12
|
data/Cargo.lock
CHANGED
|
@@ -2460,26 +2460,6 @@ dependencies = [
|
|
|
2460
2460
|
"icu_properties",
|
|
2461
2461
|
]
|
|
2462
2462
|
|
|
2463
|
-
[[package]]
|
|
2464
|
-
name = "imago"
|
|
2465
|
-
version = "0.2.3"
|
|
2466
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2467
|
-
checksum = "ae7cfee876c698a1a2ed9c705ab18f21acbed82110f19b51cc458de73426fe2c"
|
|
2468
|
-
dependencies = [
|
|
2469
|
-
"async-trait",
|
|
2470
|
-
"bincode",
|
|
2471
|
-
"cfg-if",
|
|
2472
|
-
"libc",
|
|
2473
|
-
"miniz_oxide",
|
|
2474
|
-
"nix 0.30.1",
|
|
2475
|
-
"page_size",
|
|
2476
|
-
"rustc_version",
|
|
2477
|
-
"tokio",
|
|
2478
|
-
"tracing",
|
|
2479
|
-
"vm-memory 0.18.0",
|
|
2480
|
-
"windows-sys 0.61.2",
|
|
2481
|
-
]
|
|
2482
|
-
|
|
2483
2463
|
[[package]]
|
|
2484
2464
|
name = "indexmap"
|
|
2485
2465
|
version = "2.14.0"
|
|
@@ -2875,15 +2855,6 @@ dependencies = [
|
|
|
2875
2855
|
"libc",
|
|
2876
2856
|
]
|
|
2877
2857
|
|
|
2878
|
-
[[package]]
|
|
2879
|
-
name = "linux-loader"
|
|
2880
|
-
version = "0.13.0"
|
|
2881
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2882
|
-
checksum = "870c3814345f050991f99869417779f6062542bcf4ed81db7a1b926ad1306638"
|
|
2883
|
-
dependencies = [
|
|
2884
|
-
"vm-memory 0.16.2",
|
|
2885
|
-
]
|
|
2886
|
-
|
|
2887
2858
|
[[package]]
|
|
2888
2859
|
name = "linux-raw-sys"
|
|
2889
2860
|
version = "0.12.1"
|
|
@@ -3003,8 +2974,8 @@ dependencies = [
|
|
|
3003
2974
|
|
|
3004
2975
|
[[package]]
|
|
3005
2976
|
name = "microsandbox"
|
|
3006
|
-
version = "0.
|
|
3007
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
2977
|
+
version = "0.6.1"
|
|
2978
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3008
2979
|
dependencies = [
|
|
3009
2980
|
"anyhow",
|
|
3010
2981
|
"astral-tokio-tar",
|
|
@@ -3052,12 +3023,13 @@ dependencies = [
|
|
|
3052
3023
|
"tracing",
|
|
3053
3024
|
"typed-builder",
|
|
3054
3025
|
"which",
|
|
3026
|
+
"windows-sys 0.61.2",
|
|
3055
3027
|
]
|
|
3056
3028
|
|
|
3057
3029
|
[[package]]
|
|
3058
3030
|
name = "microsandbox-agent-client"
|
|
3059
|
-
version = "0.
|
|
3060
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3031
|
+
version = "0.6.1"
|
|
3032
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3061
3033
|
dependencies = [
|
|
3062
3034
|
"ciborium",
|
|
3063
3035
|
"microsandbox-protocol",
|
|
@@ -3069,8 +3041,8 @@ dependencies = [
|
|
|
3069
3041
|
|
|
3070
3042
|
[[package]]
|
|
3071
3043
|
name = "microsandbox-db"
|
|
3072
|
-
version = "0.
|
|
3073
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3044
|
+
version = "0.6.1"
|
|
3045
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3074
3046
|
dependencies = [
|
|
3075
3047
|
"async-trait",
|
|
3076
3048
|
"sea-orm",
|
|
@@ -3081,8 +3053,8 @@ dependencies = [
|
|
|
3081
3053
|
|
|
3082
3054
|
[[package]]
|
|
3083
3055
|
name = "microsandbox-filesystem"
|
|
3084
|
-
version = "0.
|
|
3085
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3056
|
+
version = "0.6.1"
|
|
3057
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3086
3058
|
dependencies = [
|
|
3087
3059
|
"libc",
|
|
3088
3060
|
"microsandbox-utils",
|
|
@@ -3094,8 +3066,8 @@ dependencies = [
|
|
|
3094
3066
|
|
|
3095
3067
|
[[package]]
|
|
3096
3068
|
name = "microsandbox-image"
|
|
3097
|
-
version = "0.
|
|
3098
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3069
|
+
version = "0.6.1"
|
|
3070
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3099
3071
|
dependencies = [
|
|
3100
3072
|
"astral-tokio-tar",
|
|
3101
3073
|
"async-compression",
|
|
@@ -3115,24 +3087,25 @@ dependencies = [
|
|
|
3115
3087
|
"tokio",
|
|
3116
3088
|
"tokio-util",
|
|
3117
3089
|
"tracing",
|
|
3118
|
-
"
|
|
3090
|
+
"windows-sys 0.61.2",
|
|
3119
3091
|
]
|
|
3120
3092
|
|
|
3121
3093
|
[[package]]
|
|
3122
3094
|
name = "microsandbox-metrics"
|
|
3123
|
-
version = "0.
|
|
3124
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3095
|
+
version = "0.6.1"
|
|
3096
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3125
3097
|
dependencies = [
|
|
3126
3098
|
"chrono",
|
|
3127
3099
|
"libc",
|
|
3128
3100
|
"thiserror 2.0.18",
|
|
3129
3101
|
"tracing",
|
|
3102
|
+
"windows-sys 0.61.2",
|
|
3130
3103
|
]
|
|
3131
3104
|
|
|
3132
3105
|
[[package]]
|
|
3133
3106
|
name = "microsandbox-migration"
|
|
3134
|
-
version = "0.
|
|
3135
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3107
|
+
version = "0.6.1"
|
|
3108
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3136
3109
|
dependencies = [
|
|
3137
3110
|
"sea-orm-migration",
|
|
3138
3111
|
"serde_json",
|
|
@@ -3140,8 +3113,8 @@ dependencies = [
|
|
|
3140
3113
|
|
|
3141
3114
|
[[package]]
|
|
3142
3115
|
name = "microsandbox-network"
|
|
3143
|
-
version = "0.
|
|
3144
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3116
|
+
version = "0.6.1"
|
|
3117
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3145
3118
|
dependencies = [
|
|
3146
3119
|
"base64",
|
|
3147
3120
|
"bytes",
|
|
@@ -3159,6 +3132,7 @@ dependencies = [
|
|
|
3159
3132
|
"microsandbox-protocol",
|
|
3160
3133
|
"microsandbox-utils",
|
|
3161
3134
|
"msb_krun",
|
|
3135
|
+
"msb_krun_utils",
|
|
3162
3136
|
"parking_lot",
|
|
3163
3137
|
"pem",
|
|
3164
3138
|
"percent-encoding",
|
|
@@ -3176,12 +3150,13 @@ dependencies = [
|
|
|
3176
3150
|
"tokio",
|
|
3177
3151
|
"tokio-rustls",
|
|
3178
3152
|
"tracing",
|
|
3153
|
+
"windows-sys 0.61.2",
|
|
3179
3154
|
]
|
|
3180
3155
|
|
|
3181
3156
|
[[package]]
|
|
3182
3157
|
name = "microsandbox-protocol"
|
|
3183
|
-
version = "0.
|
|
3184
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3158
|
+
version = "0.6.1"
|
|
3159
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3185
3160
|
dependencies = [
|
|
3186
3161
|
"chrono",
|
|
3187
3162
|
"ciborium",
|
|
@@ -3195,8 +3170,8 @@ dependencies = [
|
|
|
3195
3170
|
|
|
3196
3171
|
[[package]]
|
|
3197
3172
|
name = "microsandbox-runtime"
|
|
3198
|
-
version = "0.
|
|
3199
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3173
|
+
version = "0.6.1"
|
|
3174
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3200
3175
|
dependencies = [
|
|
3201
3176
|
"bytes",
|
|
3202
3177
|
"chrono",
|
|
@@ -3221,12 +3196,13 @@ dependencies = [
|
|
|
3221
3196
|
"thiserror 2.0.18",
|
|
3222
3197
|
"tokio",
|
|
3223
3198
|
"tracing",
|
|
3199
|
+
"windows-sys 0.61.2",
|
|
3224
3200
|
]
|
|
3225
3201
|
|
|
3226
3202
|
[[package]]
|
|
3227
3203
|
name = "microsandbox-types"
|
|
3228
|
-
version = "0.
|
|
3229
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3204
|
+
version = "0.6.1"
|
|
3205
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3230
3206
|
dependencies = [
|
|
3231
3207
|
"chrono",
|
|
3232
3208
|
"serde",
|
|
@@ -3237,19 +3213,20 @@ dependencies = [
|
|
|
3237
3213
|
|
|
3238
3214
|
[[package]]
|
|
3239
3215
|
name = "microsandbox-utils"
|
|
3240
|
-
version = "0.
|
|
3241
|
-
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.
|
|
3216
|
+
version = "0.6.1"
|
|
3217
|
+
source = "git+https://github.com/superradcompany/microsandbox?tag=v0.6.1#868ac5a8f04fadca357b377537e7a5740a856509"
|
|
3242
3218
|
dependencies = [
|
|
3243
3219
|
"dirs",
|
|
3244
3220
|
"libc",
|
|
3245
3221
|
"reflink-copy",
|
|
3246
3222
|
"scopeguard",
|
|
3247
3223
|
"ureq",
|
|
3224
|
+
"windows-sys 0.61.2",
|
|
3248
3225
|
]
|
|
3249
3226
|
|
|
3250
3227
|
[[package]]
|
|
3251
3228
|
name = "microsandbox_rb"
|
|
3252
|
-
version = "0.
|
|
3229
|
+
version = "0.9.0"
|
|
3253
3230
|
dependencies = [
|
|
3254
3231
|
"chrono",
|
|
3255
3232
|
"futures",
|
|
@@ -3321,11 +3298,31 @@ dependencies = [
|
|
|
3321
3298
|
"num-traits",
|
|
3322
3299
|
]
|
|
3323
3300
|
|
|
3301
|
+
[[package]]
|
|
3302
|
+
name = "msb-imago"
|
|
3303
|
+
version = "0.1.0"
|
|
3304
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3305
|
+
checksum = "16db000f7280d4c8a78b8b95f64a41006d447fd116ecd69d984d93774cb85d09"
|
|
3306
|
+
dependencies = [
|
|
3307
|
+
"async-trait",
|
|
3308
|
+
"bincode",
|
|
3309
|
+
"cfg-if",
|
|
3310
|
+
"libc",
|
|
3311
|
+
"miniz_oxide",
|
|
3312
|
+
"nix 0.30.1",
|
|
3313
|
+
"page_size",
|
|
3314
|
+
"rustc_version",
|
|
3315
|
+
"tokio",
|
|
3316
|
+
"tracing",
|
|
3317
|
+
"vm-memory",
|
|
3318
|
+
"windows-sys 0.61.2",
|
|
3319
|
+
]
|
|
3320
|
+
|
|
3324
3321
|
[[package]]
|
|
3325
3322
|
name = "msb_krun"
|
|
3326
|
-
version = "0.1.
|
|
3323
|
+
version = "0.1.19"
|
|
3327
3324
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3328
|
-
checksum = "
|
|
3325
|
+
checksum = "c0fb3437734ef4103d3891d3cbd51a8726f196af350c68cb044a1860207c64d3"
|
|
3329
3326
|
dependencies = [
|
|
3330
3327
|
"crossbeam-channel",
|
|
3331
3328
|
"kvm-bindings",
|
|
@@ -3338,14 +3335,14 @@ dependencies = [
|
|
|
3338
3335
|
"msb_krun_polly",
|
|
3339
3336
|
"msb_krun_utils",
|
|
3340
3337
|
"msb_krun_vmm",
|
|
3341
|
-
"vm-memory
|
|
3338
|
+
"vm-memory",
|
|
3342
3339
|
]
|
|
3343
3340
|
|
|
3344
3341
|
[[package]]
|
|
3345
3342
|
name = "msb_krun_arch"
|
|
3346
|
-
version = "0.1.
|
|
3343
|
+
version = "0.1.19"
|
|
3347
3344
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3348
|
-
checksum = "
|
|
3345
|
+
checksum = "e25fe665643d66900ec9407ef0d3afdb5cd4ca97ad394c46b8243bfa6344bfc0"
|
|
3349
3346
|
dependencies = [
|
|
3350
3347
|
"kvm-bindings",
|
|
3351
3348
|
"kvm-ioctls",
|
|
@@ -3353,21 +3350,20 @@ dependencies = [
|
|
|
3353
3350
|
"msb_krun_arch_gen",
|
|
3354
3351
|
"msb_krun_smbios",
|
|
3355
3352
|
"msb_krun_utils",
|
|
3356
|
-
"vm-memory
|
|
3357
|
-
"vmm-sys-util",
|
|
3353
|
+
"vm-memory",
|
|
3358
3354
|
]
|
|
3359
3355
|
|
|
3360
3356
|
[[package]]
|
|
3361
3357
|
name = "msb_krun_arch_gen"
|
|
3362
|
-
version = "0.1.
|
|
3358
|
+
version = "0.1.19"
|
|
3363
3359
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3364
|
-
checksum = "
|
|
3360
|
+
checksum = "7e50dfac228ad088917f8ffb2920c4a56d762ebc5a90950ee7bf5414253b02aa"
|
|
3365
3361
|
|
|
3366
3362
|
[[package]]
|
|
3367
3363
|
name = "msb_krun_cpuid"
|
|
3368
|
-
version = "0.1.
|
|
3364
|
+
version = "0.1.19"
|
|
3369
3365
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3370
|
-
checksum = "
|
|
3366
|
+
checksum = "fd5221749c0224858b075b61f3f2a0964dbfe367093e9ee650b6b0e3d690c3b2"
|
|
3371
3367
|
dependencies = [
|
|
3372
3368
|
"kvm-bindings",
|
|
3373
3369
|
"kvm-ioctls",
|
|
@@ -3376,37 +3372,39 @@ dependencies = [
|
|
|
3376
3372
|
|
|
3377
3373
|
[[package]]
|
|
3378
3374
|
name = "msb_krun_devices"
|
|
3379
|
-
version = "0.1.
|
|
3375
|
+
version = "0.1.19"
|
|
3380
3376
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3381
|
-
checksum = "
|
|
3377
|
+
checksum = "4fee50bfdf1a1258e33bcf0ee8631bb805db79b8d442161ab63d700689a992a8"
|
|
3382
3378
|
dependencies = [
|
|
3383
3379
|
"bitflags 1.3.2",
|
|
3384
3380
|
"capng",
|
|
3385
3381
|
"caps",
|
|
3386
3382
|
"crossbeam-channel",
|
|
3387
|
-
"imago",
|
|
3388
3383
|
"kvm-bindings",
|
|
3389
3384
|
"kvm-ioctls",
|
|
3390
3385
|
"libc",
|
|
3391
3386
|
"libloading",
|
|
3392
3387
|
"log",
|
|
3393
3388
|
"lru",
|
|
3389
|
+
"msb-imago",
|
|
3394
3390
|
"msb_krun_arch",
|
|
3395
3391
|
"msb_krun_hvf",
|
|
3396
3392
|
"msb_krun_polly",
|
|
3397
3393
|
"msb_krun_utils",
|
|
3398
3394
|
"nix 0.30.1",
|
|
3399
3395
|
"rand 0.9.4",
|
|
3396
|
+
"tokio",
|
|
3400
3397
|
"virtio-bindings",
|
|
3401
3398
|
"vm-fdt",
|
|
3402
|
-
"vm-memory
|
|
3399
|
+
"vm-memory",
|
|
3400
|
+
"windows-sys 0.61.2",
|
|
3403
3401
|
]
|
|
3404
3402
|
|
|
3405
3403
|
[[package]]
|
|
3406
3404
|
name = "msb_krun_hvf"
|
|
3407
|
-
version = "0.1.
|
|
3405
|
+
version = "0.1.19"
|
|
3408
3406
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3409
|
-
checksum = "
|
|
3407
|
+
checksum = "3f2300bbe4dbd56bdc3ffc2384baf82e299fff8a45e844f41c4a0be64210ed47"
|
|
3410
3408
|
dependencies = [
|
|
3411
3409
|
"crossbeam-channel",
|
|
3412
3410
|
"libloading",
|
|
@@ -3416,19 +3414,19 @@ dependencies = [
|
|
|
3416
3414
|
|
|
3417
3415
|
[[package]]
|
|
3418
3416
|
name = "msb_krun_kernel"
|
|
3419
|
-
version = "0.1.
|
|
3417
|
+
version = "0.1.19"
|
|
3420
3418
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3421
|
-
checksum = "
|
|
3419
|
+
checksum = "3402b8879bd5b91f33d069fefa00fd0416f12cd660cfd7538da8e77201f2dda5"
|
|
3422
3420
|
dependencies = [
|
|
3423
3421
|
"msb_krun_utils",
|
|
3424
|
-
"vm-memory
|
|
3422
|
+
"vm-memory",
|
|
3425
3423
|
]
|
|
3426
3424
|
|
|
3427
3425
|
[[package]]
|
|
3428
3426
|
name = "msb_krun_polly"
|
|
3429
|
-
version = "0.1.
|
|
3427
|
+
version = "0.1.19"
|
|
3430
3428
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3431
|
-
checksum = "
|
|
3429
|
+
checksum = "da550bf268cb9217255ed152bfca9f61275a2796237fd180989958c7f54dc137"
|
|
3432
3430
|
dependencies = [
|
|
3433
3431
|
"libc",
|
|
3434
3432
|
"msb_krun_utils",
|
|
@@ -3436,18 +3434,18 @@ dependencies = [
|
|
|
3436
3434
|
|
|
3437
3435
|
[[package]]
|
|
3438
3436
|
name = "msb_krun_smbios"
|
|
3439
|
-
version = "0.1.
|
|
3437
|
+
version = "0.1.19"
|
|
3440
3438
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3441
|
-
checksum = "
|
|
3439
|
+
checksum = "2c0737818e69346348f8ba2a7197cbec2336e807e5f7f094c80e0ca1635ca054"
|
|
3442
3440
|
dependencies = [
|
|
3443
|
-
"vm-memory
|
|
3441
|
+
"vm-memory",
|
|
3444
3442
|
]
|
|
3445
3443
|
|
|
3446
3444
|
[[package]]
|
|
3447
3445
|
name = "msb_krun_utils"
|
|
3448
|
-
version = "0.1.
|
|
3446
|
+
version = "0.1.19"
|
|
3449
3447
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3450
|
-
checksum = "
|
|
3448
|
+
checksum = "fb370035195bfd6d60e0b4ad8b0d68dcf9d138f144d033087a960117920a126e"
|
|
3451
3449
|
dependencies = [
|
|
3452
3450
|
"bitflags 1.3.2",
|
|
3453
3451
|
"crossbeam-channel",
|
|
@@ -3456,13 +3454,14 @@ dependencies = [
|
|
|
3456
3454
|
"log",
|
|
3457
3455
|
"nix 0.30.1",
|
|
3458
3456
|
"vmm-sys-util",
|
|
3457
|
+
"windows-sys 0.61.2",
|
|
3459
3458
|
]
|
|
3460
3459
|
|
|
3461
3460
|
[[package]]
|
|
3462
3461
|
name = "msb_krun_vmm"
|
|
3463
|
-
version = "0.1.
|
|
3462
|
+
version = "0.1.19"
|
|
3464
3463
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3465
|
-
checksum = "
|
|
3464
|
+
checksum = "68e9f485f720981fda9071b2711d4f8406b6c67ebf6bfa2bb8e83f3cc6c2aaea"
|
|
3466
3465
|
dependencies = [
|
|
3467
3466
|
"bzip2",
|
|
3468
3467
|
"crossbeam-channel",
|
|
@@ -3470,7 +3469,7 @@ dependencies = [
|
|
|
3470
3469
|
"kvm-bindings",
|
|
3471
3470
|
"kvm-ioctls",
|
|
3472
3471
|
"libc",
|
|
3473
|
-
"
|
|
3472
|
+
"libloading",
|
|
3474
3473
|
"log",
|
|
3475
3474
|
"msb_krun_arch",
|
|
3476
3475
|
"msb_krun_arch_gen",
|
|
@@ -3481,8 +3480,8 @@ dependencies = [
|
|
|
3481
3480
|
"msb_krun_polly",
|
|
3482
3481
|
"msb_krun_utils",
|
|
3483
3482
|
"nix 0.30.1",
|
|
3484
|
-
"vm-memory
|
|
3485
|
-
"
|
|
3483
|
+
"vm-memory",
|
|
3484
|
+
"windows-sys 0.61.2",
|
|
3486
3485
|
"zstd",
|
|
3487
3486
|
]
|
|
3488
3487
|
|
|
@@ -6396,24 +6395,15 @@ version = "0.3.0"
|
|
|
6396
6395
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
6397
6396
|
checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23"
|
|
6398
6397
|
|
|
6399
|
-
[[package]]
|
|
6400
|
-
name = "vm-memory"
|
|
6401
|
-
version = "0.16.2"
|
|
6402
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
6403
|
-
checksum = "1fd5e56d48353c5f54ef50bd158a0452fc82f5383da840f7b8efc31695dd3b9d"
|
|
6404
|
-
dependencies = [
|
|
6405
|
-
"libc",
|
|
6406
|
-
"thiserror 1.0.69",
|
|
6407
|
-
"winapi",
|
|
6408
|
-
]
|
|
6409
|
-
|
|
6410
6398
|
[[package]]
|
|
6411
6399
|
name = "vm-memory"
|
|
6412
6400
|
version = "0.18.0"
|
|
6413
6401
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
6414
6402
|
checksum = "9b55e753c7725603745cb32b2287ef7ef3da05c03c7702cda3fa8abe25ae0465"
|
|
6415
6403
|
dependencies = [
|
|
6404
|
+
"libc",
|
|
6416
6405
|
"thiserror 2.0.18",
|
|
6406
|
+
"winapi",
|
|
6417
6407
|
]
|
|
6418
6408
|
|
|
6419
6409
|
[[package]]
|
data/DESIGN.md
CHANGED
|
@@ -58,6 +58,23 @@ only (never touches the Ruby C API) and uses `catch_unwind` so a Rust panic is
|
|
|
58
58
|
captured and re-raised *after* the GVL is re-acquired rather than unwinding
|
|
59
59
|
across the C frame (which would be UB).
|
|
60
60
|
|
|
61
|
+
**The *calling* thread is not preemptible during the call.** `nogvl` passes a
|
|
62
|
+
null unblock-function to `rb_thread_call_without_gvl`, and Ruby only checks
|
|
63
|
+
pending interrupts *before* and *after* the GVL-released region — so while a
|
|
64
|
+
native call blocks, the thread that issued it cannot be interrupted:
|
|
65
|
+
`Timeout::timeout` (which relies on `Thread#raise`), `Thread#kill`, and `SIGINT`
|
|
66
|
+
(Ctrl-C) are all deferred until the call returns on its own. The GVL release
|
|
67
|
+
keeps *other* threads live; it does **not** make the calling thread
|
|
68
|
+
cancelable. This is harmless for the bounded calls (`exec`/`shell` with a
|
|
69
|
+
`timeout:`, `AgentClient` with a `timeout:`) because the deadline fires inside
|
|
70
|
+
the future, but the unbounded/streaming paths — `exec`/`shell` with
|
|
71
|
+
`timeout: nil`, `ExecHandle#recv`/`#wait`, `log_stream`/`metrics_stream` `recv`
|
|
72
|
+
(especially `follow: true`), `Sandbox#wait`/`SandboxHandle#wait_until_stopped`,
|
|
73
|
+
and `AgentClient#request` with no timeout — can block their caller indefinitely
|
|
74
|
+
if the guest wedges or a relay drops. **Bound such calls with the explicit
|
|
75
|
+
`timeout:` knobs rather than wrapping them in `Timeout::timeout`**, which will
|
|
76
|
+
not fire while the native call is in flight.
|
|
77
|
+
|
|
61
78
|
## Error mapping
|
|
62
79
|
|
|
63
80
|
The core returns one big `MicrosandboxError` enum. `error.rs` maps each variant
|
|
@@ -88,7 +105,8 @@ out (air-gapped hosts that provision out of band). libkrunfw is `dlopen`'d by
|
|
|
88
105
|
## Core-crate dependency (self-contained)
|
|
89
106
|
|
|
90
107
|
`ext/microsandbox/Cargo.toml` depends on the core crate via a **pinned git tag**
|
|
91
|
-
(`microsandbox` / `microsandbox-network
|
|
108
|
+
(`microsandbox` / `microsandbox-network`, pinned to the same tag as
|
|
109
|
+
`Microsandbox::RUNTIME_VERSION` — currently `v0.6.1`), so the gem builds anywhere
|
|
92
110
|
— CI, `rake-compiler-dock` release containers, and end-user source installs —
|
|
93
111
|
without an adjacent checkout. For fast local development against a sibling
|
|
94
112
|
microsandbox checkout, copy `.cargo/config.toml.example` to `.cargo/config.toml`
|
|
@@ -166,8 +184,8 @@ Create options now cover `image`, `cpus`, `memory`, `oci_upper_size`, `env`,
|
|
|
166
184
|
|
|
167
185
|
The binding is verified at four levels:
|
|
168
186
|
|
|
169
|
-
1. **Unit** (
|
|
170
|
-
objects, with the native layer stubbed.
|
|
187
|
+
1. **Unit** (several hundred examples) — the Ruby layer's option normalization
|
|
188
|
+
and value objects, with the native layer stubbed.
|
|
171
189
|
2. **Real-microVM integration** (`spec/integration`, opt-in via
|
|
172
190
|
`MICROSANDBOX_INTEGRATION=1`) — boots actual sandboxes and round-trips
|
|
173
191
|
`exec`/`shell`/`fs`/`metrics`/`logs`/streaming/snapshots. Run locally on
|
|
@@ -193,7 +211,7 @@ create options, full mount options (tmpfs/disk + stat-virtualization/
|
|
|
193
211
|
host-permissions), and snapshot inspection (`open`/`list_dir`/`reindex`).
|
|
194
212
|
|
|
195
213
|
A few **secondary** upstream knobs remain unexposed (a genuine binding gap, not
|
|
196
|
-
upstream-gated — they exist at the pinned `v0.
|
|
214
|
+
upstream-gated — they exist at the pinned `v0.6.1` runtime): per-published-port host
|
|
197
215
|
**bind address** (ports always bind loopback), network **interface overrides**,
|
|
198
216
|
and inline **named-volume create-mode** (pre-create with `Volume.create`, then
|
|
199
217
|
mount with `{ named: }`). These slot in module-by-module exactly as the existing
|
data/README.md
CHANGED
|
@@ -43,7 +43,7 @@ them. Our deepest thanks to the maintainers and community. 🙏
|
|
|
43
43
|
- **SSH & SFTP** — native in-process SSH client/server and file transfer (`Sandbox#ssh`)
|
|
44
44
|
- **Raw agent client** — byte-level access to the guest `agentd` protocol (`Microsandbox::AgentClient`)
|
|
45
45
|
- **Idiomatic Ruby** — keyword arguments, block-scoped lifecycle, a typed error hierarchy
|
|
46
|
-
- **Thread-friendly** — the GVL is released during sandbox calls, so
|
|
46
|
+
- **Thread-friendly** — the GVL is released during sandbox calls, so _other_ Ruby threads keep running. The _calling_ thread blocks uninterruptibly until the call returns (`Timeout::timeout`/`Thread#kill`/Ctrl-C can't interrupt a blocked native call), so bound long-running work with a real deadline where one exists: `exec(timeout:)` / `shell(timeout:)` kill the guest command after N seconds, and `AgentClient.connect_sandbox`/`connect_path` take a `timeout:` that bounds only the connect handshake. The streaming paths and `AgentClient#request`/`#stream` have no timeout knob and can block indefinitely if the guest wedges — see DESIGN.md
|
|
47
47
|
|
|
48
48
|
## Requirements
|
|
49
49
|
|
|
@@ -215,6 +215,9 @@ end
|
|
|
215
215
|
|
|
216
216
|
```ruby
|
|
217
217
|
Microsandbox::Sandbox.create("obs", image: "public.ecr.aws/docker/library/alpine:latest") do |sb|
|
|
218
|
+
# On the v0.6.1 runtime the metrics slot goes live a beat after create returns,
|
|
219
|
+
# so `metrics` can briefly raise "no live metrics slot" right after boot —
|
|
220
|
+
# retry for a few hundred ms rather than treating the first failure as fatal.
|
|
218
221
|
m = sb.metrics # => Microsandbox::Metrics
|
|
219
222
|
m.cpu_percent
|
|
220
223
|
m.memory_bytes
|
|
@@ -244,6 +247,13 @@ Microsandbox::Sandbox.create("stream", image: "public.ecr.aws/docker/library/pyt
|
|
|
244
247
|
end
|
|
245
248
|
```
|
|
246
249
|
|
|
250
|
+
> **Streams are single-pass.** `ExecHandle`, `LogStream`, `MetricsStream`,
|
|
251
|
+
> `FsReadStream`, `PullSession`, and `AgentStream` are `Enumerable`, but `each`
|
|
252
|
+
> drains a one-shot native channel — they are forward-only, not rewindable, and
|
|
253
|
+
> meant for a single consumer. Iterate (or `collect`/`read`) exactly once: a
|
|
254
|
+
> second `each`, or a combinator after a partial drain (`count` then `each`,
|
|
255
|
+
> `to_a` twice), silently yields nothing. Don't share one handle across threads.
|
|
256
|
+
|
|
247
257
|
### Images
|
|
248
258
|
|
|
249
259
|
Manage the local OCI image cache (images are pulled automatically on `create`):
|
|
@@ -383,8 +393,8 @@ change diverged the two numbers — the gem version is **not** a reliable indica
|
|
|
383
393
|
of the embedded runtime version. To learn which runtime a build wraps, ask it:
|
|
384
394
|
|
|
385
395
|
```ruby
|
|
386
|
-
Microsandbox::VERSION # => "0.
|
|
387
|
-
Microsandbox.runtime_version # => "v0.
|
|
396
|
+
Microsandbox::VERSION # => "0.9.0" (the gem's own version)
|
|
397
|
+
Microsandbox.runtime_version # => "v0.6.1" (the embedded upstream runtime tag)
|
|
388
398
|
```
|
|
389
399
|
|
|
390
400
|
| Gem version | Upstream runtime | Notes |
|
|
@@ -399,6 +409,8 @@ Microsandbox.runtime_version # => "v0.5.10" (the embedded upstream runtime ta
|
|
|
399
409
|
| `0.7.0` | `v0.5.8` | SDK parity release (large binding-gap closure) |
|
|
400
410
|
| `0.8.0` | `v0.5.10` | adopts upstream `v0.5.10` (idle-only heartbeat, config-fd hardening, **4 GiB default bind-mount quota**); supersedes the reverted `v0.5.9` attempt |
|
|
401
411
|
| `0.8.1` | `v0.5.10` | gem-only: re-provision a stale local runtime; per-bind-mount `quota_mib:` override |
|
|
412
|
+
| `0.8.2` | `v0.5.10` | gem-only: redact secrets from errors, typed snapshot errors, panic-free durations, fat-gem loader + `extconf` preflight fixes, threading/streaming docs |
|
|
413
|
+
| `0.9.0` | `v0.6.1` | adopts upstream `v0.6.0`+`v0.6.1` (zombie-runtime wait fix, secret substitution through CONNECT proxies, stale-sandbox cleanup); upstream public API is additive — no Ruby surface change |
|
|
402
414
|
|
|
403
415
|
**Going forward** — the gem version moves on its own semver track and no longer
|
|
404
416
|
mirrors the upstream tag:
|
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.
|
|
10
|
-
version = "0.
|
|
9
|
+
# The core-crate dependency below stays pinned at its own tag (v0.6.1).
|
|
10
|
+
version = "0.9.0"
|
|
11
11
|
authors = ["Super Rad Company <development@superrad.company>"]
|
|
12
12
|
repository = "https://github.com/superradcompany/microsandbox"
|
|
13
13
|
license = "Apache-2.0"
|
|
@@ -35,8 +35,8 @@ rb-sys = "0.9"
|
|
|
35
35
|
# `.cargo/config.toml.example`). "ssh" matches the feature set the Python/Node
|
|
36
36
|
# SDKs ship with; default features add "prebuilt" (provisions msb + libkrunfw at
|
|
37
37
|
# build time), "net", and "keyring".
|
|
38
|
-
microsandbox = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.
|
|
39
|
-
microsandbox-network = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.
|
|
38
|
+
microsandbox = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.1", default-features = true, features = ["ssh"] }
|
|
39
|
+
microsandbox-network = { git = "https://github.com/superradcompany/microsandbox", tag = "v0.6.1" }
|
|
40
40
|
|
|
41
41
|
# Async core bridged to Ruby's synchronous API via a blocking tokio runtime.
|
|
42
42
|
tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] }
|
data/ext/microsandbox/extconf.rb
CHANGED
|
@@ -2,31 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
require "mkmf"
|
|
4
4
|
require "rb_sys/mkmf"
|
|
5
|
+
require "shellwords"
|
|
5
6
|
|
|
6
7
|
# Preflight: the embedded microsandbox core is edition 2024 and pulls smoltcp,
|
|
7
|
-
# which sets a Minimum Supported Rust Version of 1.91.
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
8
|
+
# which sets a Minimum Supported Rust Version of 1.91. An older rustc (commonly
|
|
9
|
+
# Homebrew's, which shadows a newer rustup on PATH and ignores this gem's
|
|
10
|
+
# rust-toolchain.toml) fails deep in the build with a cryptic "rustc X is not
|
|
11
|
+
# supported by smoltcp" error. Detect it up front and explain the fix instead.
|
|
12
|
+
#
|
|
13
|
+
# Probe the *same* rustc the build will invoke. create_rust_makefile drives the
|
|
14
|
+
# build through `cargo`, and cargo resolves its compiler exactly as: the `RUSTC`
|
|
15
|
+
# env var if set, otherwise the bare `rustc` found on PATH. It does NOT use the
|
|
16
|
+
# `rustc` sitting beside the `cargo` binary, and the rustup `cargo` shim neither
|
|
17
|
+
# sets `RUSTC` nor prepends its toolchain bin to PATH — toolchain selection
|
|
18
|
+
# survives only because the PATH `rustc` is normally itself a rustup shim that
|
|
19
|
+
# honors rust-toolchain.toml. So a non-rustup rustc earlier on PATH (which reads
|
|
20
|
+
# neither RUSTUP_TOOLCHAIN nor the toolchain file) is what the build actually
|
|
21
|
+
# runs. Mirroring cargo's RUSTC-then-PATH resolution is the only probe that
|
|
22
|
+
# neither false-passes (the trap of checking the cargo sibling, which stays a
|
|
23
|
+
# valid rustup shim while the build silently uses the stale PATH rustc) nor
|
|
24
|
+
# false-aborts (when `RUSTC` points at a newer compiler than the PATH `rustc`).
|
|
12
25
|
MSRV = Gem::Version.new("1.91")
|
|
26
|
+
|
|
27
|
+
def build_rustc
|
|
28
|
+
rustc = ENV["RUSTC"].to_s.strip
|
|
29
|
+
rustc.empty? ? "rustc" : rustc
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
rustc = build_rustc
|
|
13
33
|
rustc_version = begin
|
|
14
|
-
out =
|
|
34
|
+
out = `#{rustc.shellescape} --version 2>/dev/null`
|
|
15
35
|
out[/\d+\.\d+(\.\d+)?/] && Gem::Version.new(out[/\d+\.\d+(\.\d+)?/])
|
|
16
36
|
rescue
|
|
17
37
|
nil
|
|
18
38
|
end
|
|
19
39
|
|
|
20
40
|
if rustc_version && rustc_version < MSRV
|
|
21
|
-
which_rustc = begin
|
|
22
|
-
`which rustc 2>/dev/null`.strip
|
|
23
|
-
rescue
|
|
24
|
-
""
|
|
25
|
-
end
|
|
26
41
|
abort(<<~MSG)
|
|
27
42
|
|
|
28
43
|
[microsandbox-rb] Rust #{rustc_version} is too old — the embedded core requires rustc >= #{MSRV}.
|
|
29
|
-
Found: #{
|
|
44
|
+
Found: #{rustc} (#{rustc_version})
|
|
30
45
|
|
|
31
46
|
This usually means an older rustc (e.g. Homebrew's) is ahead of a newer
|
|
32
47
|
rustup toolchain on your PATH. Fixes:
|
|
@@ -37,8 +52,12 @@ if rustc_version && rustc_version < MSRV
|
|
|
37
52
|
rustup install stable && rustup default stable
|
|
38
53
|
• Or upgrade your system Rust to >= #{MSRV}.
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
• Or point `RUSTC` at a recent compiler (cargo honors it over PATH):
|
|
56
|
+
RUSTC="$HOME/.cargo/bin/rustc" gem install microsandbox-rb
|
|
57
|
+
|
|
58
|
+
(This gem ships a rust-toolchain.toml pinning `stable`, but only a rustup
|
|
59
|
+
`rustc` shim reads it — a non-rustup `rustc` ahead on PATH ignores it, and
|
|
60
|
+
cargo invokes that PATH `rustc` unless `RUSTC` says otherwise.)
|
|
42
61
|
MSG
|
|
43
62
|
end
|
|
44
63
|
|
|
@@ -34,6 +34,22 @@ fn class_name(err: &MicrosandboxError) -> &'static str {
|
|
|
34
34
|
// client's `UnsupportedOperation` above.
|
|
35
35
|
CloudHttp { .. } => "CloudHttpError",
|
|
36
36
|
Unsupported { .. } => "UnsupportedError",
|
|
37
|
+
// Snapshot operations, all reachable through the gem's fully-wired
|
|
38
|
+
// `Snapshot` API. Upstream raises these un-wrapped, so without a mapping
|
|
39
|
+
// they collapse to the base `Error` and callers must string-match the
|
|
40
|
+
// message. This goes BEYOND the Python mirror (which has no Snapshot
|
|
41
|
+
// classes and matches the Go SDK's per-variant coverage instead) — a
|
|
42
|
+
// deliberate divergence noted in `lib/microsandbox/errors.rb`.
|
|
43
|
+
SnapshotNotFound(_) => "SnapshotNotFoundError",
|
|
44
|
+
SnapshotAlreadyExists(_) => "SnapshotAlreadyExistsError",
|
|
45
|
+
SnapshotSandboxRunning(_) => "SnapshotSandboxRunningError",
|
|
46
|
+
SnapshotImageMissing(_) => "SnapshotImageMissingError",
|
|
47
|
+
SnapshotIntegrity(_) => "SnapshotIntegrityError",
|
|
48
|
+
// Give the already-defined-but-orphaned `NetworkPolicyError` a mapping:
|
|
49
|
+
// a builder parse/validation error from `network(|n| ...)`. The gem
|
|
50
|
+
// unconditionally enables the core's `net` feature (default-features),
|
|
51
|
+
// so this variant is always present.
|
|
52
|
+
NetworkBuilder(_) => "NetworkPolicyError",
|
|
37
53
|
_ => "Error",
|
|
38
54
|
}
|
|
39
55
|
}
|
|
@@ -40,6 +40,21 @@ use crate::exec::ExecHandle;
|
|
|
40
40
|
use crate::runtime::{block_on, ruby};
|
|
41
41
|
use crate::stream::{LogStream, MetricsStream};
|
|
42
42
|
|
|
43
|
+
/// Convert a seconds `f64` into a `Duration`, surfacing a clean Ruby error
|
|
44
|
+
/// instead of the panic `Duration::from_secs_f64` raises on NaN/Inf/negative —
|
|
45
|
+
/// *and on finite-but-out-of-range* values (e.g. `Float::MAX`). The Ruby
|
|
46
|
+
/// `coerce_duration` already guards the public paths, but it sets no upper
|
|
47
|
+
/// bound, so a large finite value would still reach (and panic) the native
|
|
48
|
+
/// layer; this keeps the native layer panic-free regardless of the Ruby layer,
|
|
49
|
+
/// mirroring `agent::dur`.
|
|
50
|
+
fn secs_to_duration(secs: f64) -> Result<Duration, Error> {
|
|
51
|
+
Duration::try_from_secs_f64(secs).map_err(|e| {
|
|
52
|
+
error::base_error(format!(
|
|
53
|
+
"duration must be a non-negative, finite number of seconds in range (got {secs}: {e})"
|
|
54
|
+
))
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
#[magnus::wrap(class = "Microsandbox::Native::Sandbox", free_immediately, size)]
|
|
44
59
|
pub struct Sandbox {
|
|
45
60
|
inner: microsandbox::Sandbox,
|
|
@@ -380,7 +395,7 @@ impl Sandbox {
|
|
|
380
395
|
b = b.detached(true);
|
|
381
396
|
}
|
|
382
397
|
if let Some(secs) = conv::opt_f64(opts, "replace_with_timeout")? {
|
|
383
|
-
b = b.replace_with_timeout(
|
|
398
|
+
b = b.replace_with_timeout(secs_to_duration(secs)?);
|
|
384
399
|
} else if conv::opt_bool(opts, "replace")? {
|
|
385
400
|
b = b.replace();
|
|
386
401
|
}
|
|
@@ -579,14 +594,22 @@ impl Sandbox {
|
|
|
579
594
|
|
|
580
595
|
/// Stream metrics snapshots at `interval` seconds. Returns a MetricsStream
|
|
581
596
|
/// to pull snapshots from.
|
|
582
|
-
fn metrics_stream(&self, interval: f64) -> MetricsStream {
|
|
583
|
-
|
|
597
|
+
fn metrics_stream(&self, interval: f64) -> Result<MetricsStream, Error> {
|
|
598
|
+
// `interval <= 0.0` (0 or negative) keeps the prior 1s-default behavior;
|
|
599
|
+
// NaN (for which `<= 0.0` is false) and finite-but-out-of-range values
|
|
600
|
+
// fall through to `secs_to_duration`, which errors cleanly rather than
|
|
601
|
+
// letting `from_secs_f64` panic across the FFI boundary.
|
|
602
|
+
let dur = if interval <= 0.0 {
|
|
603
|
+
Duration::from_secs(1)
|
|
604
|
+
} else {
|
|
605
|
+
secs_to_duration(interval)?
|
|
606
|
+
};
|
|
584
607
|
// `metrics_stream` is synchronous but builds a `tokio::time::interval`,
|
|
585
608
|
// which panics ("no reactor running") unless constructed inside the
|
|
586
609
|
// runtime context — so build it under `block_on`. (`log_stream` is async
|
|
587
610
|
// and already runs inside `block_on`, so it needs no such wrapper.)
|
|
588
611
|
let stream = block_on(async { self.inner.metrics_stream(dur) });
|
|
589
|
-
MetricsStream::from_stream(stream)
|
|
612
|
+
Ok(MetricsStream::from_stream(stream))
|
|
590
613
|
}
|
|
591
614
|
|
|
592
615
|
/// Stream captured logs as they appear. `opts`: sources, since_ms,
|
|
@@ -1544,7 +1567,9 @@ impl ExecOpts {
|
|
|
1544
1567
|
cwd: conv::opt_string(opts, "cwd")?,
|
|
1545
1568
|
user: conv::opt_string(opts, "user")?,
|
|
1546
1569
|
env: conv::opt_string_map(opts, "env")?,
|
|
1547
|
-
timeout: conv::opt_f64(opts, "timeout")
|
|
1570
|
+
timeout: conv::opt_f64(opts, "timeout")?
|
|
1571
|
+
.map(secs_to_duration)
|
|
1572
|
+
.transpose()?,
|
|
1548
1573
|
tty: conv::opt_bool(opts, "tty")?,
|
|
1549
1574
|
stdin,
|
|
1550
1575
|
stdin_pipe: conv::opt_bool(opts, "stdin_pipe")?,
|
|
@@ -1907,8 +1932,7 @@ impl SbHandle {
|
|
|
1907
1932
|
|
|
1908
1933
|
/// Graceful stop with a custom escalation timeout (seconds).
|
|
1909
1934
|
fn stop_with_timeout(&self, secs: f64) -> Result<(), Error> {
|
|
1910
|
-
block_on(self.inner.stop_with_timeout(
|
|
1911
|
-
.map_err(error::to_ruby)
|
|
1935
|
+
block_on(self.inner.stop_with_timeout(secs_to_duration(secs)?)).map_err(error::to_ruby)
|
|
1912
1936
|
}
|
|
1913
1937
|
|
|
1914
1938
|
/// Force kill (SIGKILL) and wait.
|
|
@@ -1918,8 +1942,7 @@ impl SbHandle {
|
|
|
1918
1942
|
|
|
1919
1943
|
/// Force kill, waiting up to `secs` for the process to disappear.
|
|
1920
1944
|
fn kill_with_timeout(&self, secs: f64) -> Result<(), Error> {
|
|
1921
|
-
block_on(self.inner.kill_with_timeout(
|
|
1922
|
-
.map_err(error::to_ruby)
|
|
1945
|
+
block_on(self.inner.kill_with_timeout(secs_to_duration(secs)?)).map_err(error::to_ruby)
|
|
1923
1946
|
}
|
|
1924
1947
|
|
|
1925
1948
|
/// Send the graceful-shutdown request and return without waiting.
|
data/lib/microsandbox/agent.rb
CHANGED
|
@@ -35,6 +35,10 @@ module Microsandbox
|
|
|
35
35
|
# consume {AgentFrame}s until the stream ends (the terminal frame is delivered,
|
|
36
36
|
# then iteration stops). Mirrors the official SDKs' `AgentStream`.
|
|
37
37
|
#
|
|
38
|
+
# @note **Single-pass, forward-only, single-consumer.** `each` drains a
|
|
39
|
+
# one-shot native channel — not rewindable, iterate once from a single
|
|
40
|
+
# thread; a second pass or a post-drain combinator yields nothing.
|
|
41
|
+
#
|
|
38
42
|
# @example
|
|
39
43
|
# stream = client.stream(0, request_body)
|
|
40
44
|
# stream.each { |frame| handle(frame) }
|
data/lib/microsandbox/errors.rb
CHANGED
|
@@ -51,7 +51,22 @@ module Microsandbox
|
|
|
51
51
|
define_error(:ImageInUseError, "image-in-use")
|
|
52
52
|
define_error(:ImagePullFailedError, "image-pull-failed")
|
|
53
53
|
|
|
54
|
+
# Snapshot errors ---------------------------------------------------------
|
|
55
|
+
# These go BEYOND the Python SDK mirror (which has no Snapshot classes and
|
|
56
|
+
# collapses all five to its base error) — a deliberate divergence matching the
|
|
57
|
+
# Go SDK's per-variant coverage, so callers can rescue a missing/duplicate/
|
|
58
|
+
# running-source/missing-image/corrupt snapshot specifically. The native layer
|
|
59
|
+
# (ext/microsandbox/src/error.rs) maps the five core Snapshot* variants here.
|
|
60
|
+
define_error(:SnapshotNotFoundError, "snapshot-not-found")
|
|
61
|
+
define_error(:SnapshotAlreadyExistsError, "snapshot-already-exists")
|
|
62
|
+
define_error(:SnapshotSandboxRunningError, "snapshot-sandbox-running")
|
|
63
|
+
define_error(:SnapshotImageMissingError, "snapshot-image-missing")
|
|
64
|
+
define_error(:SnapshotIntegrityError, "snapshot-integrity")
|
|
65
|
+
|
|
54
66
|
# Networking / secrets errors ---------------------------------------------
|
|
67
|
+
# NetworkPolicyError now also carries the core's `NetworkBuilder` build/parse
|
|
68
|
+
# error (a `network(|n| ...)` validation failure), which was previously
|
|
69
|
+
# unmapped and fell through to the base Error.
|
|
55
70
|
define_error(:NetworkPolicyError, "network-policy-error")
|
|
56
71
|
define_error(:SecretViolationError, "secret-violation")
|
|
57
72
|
define_error(:TlsError, "tls-error")
|
|
@@ -86,6 +86,13 @@ module Microsandbox
|
|
|
86
86
|
# Iterate it (it is {Enumerable}) to consume {ExecEvent}s as they arrive, or
|
|
87
87
|
# call {#collect} to drain it into an {ExecOutput}.
|
|
88
88
|
#
|
|
89
|
+
# @note **Single-pass, forward-only, single-consumer.** `each`/`collect` drain
|
|
90
|
+
# a one-shot native event channel, so the handle is *not* rewindable: a
|
|
91
|
+
# second `each`, or `collect` after a partial `each` (and vice versa),
|
|
92
|
+
# yields only what is left. Consume it once, from one thread. (An
|
|
93
|
+
# out-of-band {#kill}/{#signal} via the control channel is the exception —
|
|
94
|
+
# it can unblock a parked `each` from another thread.)
|
|
95
|
+
#
|
|
89
96
|
# @example
|
|
90
97
|
# handle = sb.exec_stream("python", ["-u", "script.py"])
|
|
91
98
|
# handle.each do |event|
|
data/lib/microsandbox/fs.rb
CHANGED
|
@@ -199,6 +199,11 @@ module Microsandbox
|
|
|
199
199
|
# A streaming reader over a guest file, from {FS#read_stream}. Iterate it (it
|
|
200
200
|
# is {Enumerable}) to consume byte chunks (ASCII-8BIT) as they arrive, or call
|
|
201
201
|
# {#read} to drain it into one String.
|
|
202
|
+
#
|
|
203
|
+
# @note **Single-pass, forward-only, single-consumer.** `each`/`read` drain a
|
|
204
|
+
# one-shot native channel — not rewindable, and not safe to share across
|
|
205
|
+
# threads. Consume it once: a second `each`, or `read` after a partial
|
|
206
|
+
# `each`, yields only the remaining bytes.
|
|
202
207
|
class FsReadStream
|
|
203
208
|
include Enumerable
|
|
204
209
|
|
data/lib/microsandbox/sandbox.rb
CHANGED
|
@@ -526,7 +526,10 @@ module Microsandbox
|
|
|
526
526
|
env = spec[:env] || spec["env"]
|
|
527
527
|
value = spec[:value] || spec["value"]
|
|
528
528
|
unless env && value
|
|
529
|
-
|
|
529
|
+
# Report only the keys given, never the values — a secret spec carries
|
|
530
|
+
# the cleartext :value, and exception messages routinely land in logs
|
|
531
|
+
# and error trackers. Mirrors apply_registry_opts above.
|
|
532
|
+
raise ArgumentError, "secret spec needs :env and :value (got keys: #{spec.keys.inspect})"
|
|
530
533
|
end
|
|
531
534
|
out = {"env" => env.to_s, "value" => value.to_s}
|
|
532
535
|
hosts = Array(spec[:hosts] || spec["hosts"]).map(&:to_s)
|
|
@@ -534,8 +537,10 @@ module Microsandbox
|
|
|
534
537
|
hosts << single.to_s if single
|
|
535
538
|
patterns = Array(spec[:host_patterns] || spec["host_patterns"]).map(&:to_s)
|
|
536
539
|
if hosts.empty? && patterns.empty?
|
|
540
|
+
# Keys only — this branch runs after :value is confirmed present, so
|
|
541
|
+
# spec.inspect would always embed the cleartext secret value.
|
|
537
542
|
raise ArgumentError,
|
|
538
|
-
"secret spec needs :host, :hosts, or :host_patterns (got #{spec.inspect})"
|
|
543
|
+
"secret spec needs :host, :hosts, or :host_patterns (got keys: #{spec.keys.inspect})"
|
|
539
544
|
end
|
|
540
545
|
out["hosts"] = hosts unless hosts.empty?
|
|
541
546
|
out["host_patterns"] = patterns unless patterns.empty?
|
|
@@ -820,7 +825,11 @@ module Microsandbox
|
|
|
820
825
|
# @param cwd [String, nil] working directory
|
|
821
826
|
# @param user [String, nil] user to run as
|
|
822
827
|
# @param env [Hash, nil] extra environment variables
|
|
823
|
-
# @param timeout [Numeric, nil] kill after N seconds
|
|
828
|
+
# @param timeout [Numeric, nil] kill the command after N seconds and raise
|
|
829
|
+
# {ExecTimeoutError}. Omit or pass +nil+ for *no* timeout. Note the
|
|
830
|
+
# asymmetry: +0+ is **not** "disable" — it is an immediate (zero) deadline,
|
|
831
|
+
# so the command is killed before producing any output and {ExecTimeoutError}
|
|
832
|
+
# is raised. Use +nil+/omit, never +0+, to mean "no limit".
|
|
824
833
|
# @param tty [Boolean] allocate a pseudo-terminal
|
|
825
834
|
# @param stdin [String, Symbol, nil] bytes to feed to stdin, or +:pipe+ to
|
|
826
835
|
# open a streaming stdin pipe (write/close it via {ExecHandle#stdin}; only
|
|
@@ -843,6 +852,11 @@ module Microsandbox
|
|
|
843
852
|
# Pass +stdin: :pipe+ to feed the process interactively: {ExecHandle#stdin}
|
|
844
853
|
# then returns a writable sink; close it to send EOF (a process like +cat+
|
|
845
854
|
# that reads until EOF will otherwise block forever).
|
|
855
|
+
#
|
|
856
|
+
# @note +timeout:+ is accepted for signature symmetry with {#exec} but is
|
|
857
|
+
# **not applied** on the streaming path (the runtime discards it). Enforce a
|
|
858
|
+
# deadline yourself around the iteration, or use blocking {#exec} with
|
|
859
|
+
# +timeout:+ for the kill-after-N-seconds behavior.
|
|
846
860
|
# @return [ExecHandle]
|
|
847
861
|
# @see ExecHandle
|
|
848
862
|
def exec_stream(command, args = [], cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil)
|
|
@@ -851,6 +865,8 @@ module Microsandbox
|
|
|
851
865
|
end
|
|
852
866
|
|
|
853
867
|
# Run a shell script and stream its output as it arrives.
|
|
868
|
+
# @note Like {#exec_stream}, +timeout:+ is accepted but **not applied** on
|
|
869
|
+
# the streaming path.
|
|
854
870
|
# @return [ExecHandle]
|
|
855
871
|
def shell_stream(script, cwd: nil, user: nil, env: nil, timeout: nil, tty: false, stdin: nil, rlimits: nil)
|
|
856
872
|
ExecHandle.new(@native.shell_stream(script.to_s,
|
|
@@ -912,6 +928,14 @@ module Microsandbox
|
|
|
912
928
|
end
|
|
913
929
|
|
|
914
930
|
# Latest resource-usage snapshot.
|
|
931
|
+
#
|
|
932
|
+
# Raises a {Microsandbox::Error} ("sandbox N has no live metrics slot") when
|
|
933
|
+
# called in the brief window right after {Sandbox.create} returns, before the
|
|
934
|
+
# runtime has registered the sandbox's metrics slot. On the `v0.6.1` runtime
|
|
935
|
+
# the spawn handshake no longer blocks create until the first sample is
|
|
936
|
+
# written, so the slot goes live a beat *after* boot (within a few hundred
|
|
937
|
+
# milliseconds); retry for that window rather than treating the first failure
|
|
938
|
+
# as fatal.
|
|
915
939
|
# @return [Metrics]
|
|
916
940
|
def metrics
|
|
917
941
|
Metrics.new(@native.metrics)
|
|
@@ -936,6 +960,14 @@ module Microsandbox
|
|
|
936
960
|
|
|
937
961
|
# Stream resource-usage snapshots, one per interval tick, until the sandbox
|
|
938
962
|
# stops. Requires metrics to be enabled for the sandbox.
|
|
963
|
+
#
|
|
964
|
+
# The first tick fires immediately, so opening the stream right after
|
|
965
|
+
# {Sandbox.create} can hit the same metrics-slot startup window as {#metrics}
|
|
966
|
+
# and yield a transient "no live metrics slot" error on that first tick.
|
|
967
|
+
# Because the stream is single-pass (a drained or errored stream is spent),
|
|
968
|
+
# make sure the slot is live *before* opening it — e.g. retry {#metrics}
|
|
969
|
+
# until it succeeds (the slot goes live within a few hundred milliseconds of
|
|
970
|
+
# boot), then call {#metrics_stream}.
|
|
939
971
|
# @param interval [Numeric] seconds between snapshots
|
|
940
972
|
# @return [MetricsStream] an {Enumerable} of {Metrics}
|
|
941
973
|
def metrics_stream(interval: 1.0)
|
|
@@ -1083,6 +1115,12 @@ module Microsandbox
|
|
|
1083
1115
|
# "kind" key (e.g. "resolving", "resolved", "layer_download_progress",
|
|
1084
1116
|
# "layer_materialize_progress", "complete") plus kind-specific fields.
|
|
1085
1117
|
#
|
|
1118
|
+
# @note **Single-pass, forward-only, single-consumer.** `each` drains a
|
|
1119
|
+
# one-shot native progress channel — not rewindable, iterate once from a
|
|
1120
|
+
# single thread. {#sandbox} works whether or not you iterated (it awaits the
|
|
1121
|
+
# create either way), so you can skip `each` entirely; but don't expect a
|
|
1122
|
+
# second `each` to replay the events.
|
|
1123
|
+
#
|
|
1086
1124
|
# @example
|
|
1087
1125
|
# session = Microsandbox::Sandbox.create_with_progress("box", image: "python")
|
|
1088
1126
|
# session.each { |ev| puts "#{ev["kind"]} #{ev["downloaded_bytes"]}" }
|
data/lib/microsandbox/ssh.rb
CHANGED
|
@@ -121,7 +121,10 @@ module Microsandbox
|
|
|
121
121
|
@native.read_link(path.to_s)
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
# Close the SFTP session. Idempotent.
|
|
124
|
+
# Close the SFTP session. Idempotent. Sends the graceful SFTP close;
|
|
125
|
+
# relying on GC to reclaim an unclosed session drops it abruptly (no
|
|
126
|
+
# graceful close), so call this — or use the block form of
|
|
127
|
+
# {SshClient#sftp} — for a clean shutdown.
|
|
125
128
|
# @return [nil]
|
|
126
129
|
def close
|
|
127
130
|
@native.close
|
|
@@ -161,7 +164,9 @@ module Microsandbox
|
|
|
161
164
|
end
|
|
162
165
|
|
|
163
166
|
# Open an SFTP session over this connection. With a block, the session is
|
|
164
|
-
# yielded and closed when the block returns.
|
|
167
|
+
# yielded and closed when the block returns. Without a block you own the
|
|
168
|
+
# returned {SftpClient} and must call {SftpClient#close} (letting it GC skips
|
|
169
|
+
# the graceful disconnect).
|
|
165
170
|
# @yieldparam sftp [SftpClient]
|
|
166
171
|
# @return [SftpClient, Object]
|
|
167
172
|
def sftp
|
|
@@ -175,7 +180,11 @@ module Microsandbox
|
|
|
175
180
|
end
|
|
176
181
|
end
|
|
177
182
|
|
|
178
|
-
# Close the SSH client session. Idempotent.
|
|
183
|
+
# Close the SSH client session. Idempotent. Sends the graceful protocol
|
|
184
|
+
# disconnect (russh `Disconnect::ByApplication`); if the client is instead
|
|
185
|
+
# left to GC, only the in-process server task is aborted and that disconnect
|
|
186
|
+
# is skipped. Prefer the block form of {SshOps#open_client}, or call this in
|
|
187
|
+
# an `ensure`, so the session closes cleanly.
|
|
179
188
|
# @return [nil]
|
|
180
189
|
def close
|
|
181
190
|
@native.close
|
|
@@ -216,7 +225,9 @@ module Microsandbox
|
|
|
216
225
|
end
|
|
217
226
|
|
|
218
227
|
# Open a native in-process SSH client to the sandbox. With a block, the
|
|
219
|
-
# client is yielded and closed when the block returns.
|
|
228
|
+
# client is yielded and closed when the block returns. Without a block you
|
|
229
|
+
# own the returned {SshClient} and must call {SshClient#close} for a clean
|
|
230
|
+
# protocol disconnect (letting it GC only aborts the in-process server task).
|
|
220
231
|
# @param user [String] guest user to authenticate as (default "root")
|
|
221
232
|
# @param term [String, nil] TERM value for the session
|
|
222
233
|
# @param sftp [Boolean] enable the SFTP subsystem (default true)
|
data/lib/microsandbox/streams.rb
CHANGED
|
@@ -6,6 +6,14 @@ module Microsandbox
|
|
|
6
6
|
# iteration blocks for new entries until the sandbox stops; otherwise it ends
|
|
7
7
|
# once the historical log is drained.
|
|
8
8
|
#
|
|
9
|
+
# @note **Single-pass, forward-only, single-consumer.** `each` drains a
|
|
10
|
+
# one-shot native channel, so it is *not* rewindable: a second `each` — or
|
|
11
|
+
# any `Enumerable` combinator after a partial drain (`to_a` twice, `count`
|
|
12
|
+
# then `each`, `first(n)` then `each`) — silently yields nothing. Iterate
|
|
13
|
+
# exactly once, from one thread. With `follow: true`, `each` blocks the
|
|
14
|
+
# calling thread uninterruptibly until the sandbox stops (see DESIGN.md on
|
|
15
|
+
# GVL release).
|
|
16
|
+
#
|
|
9
17
|
# @example
|
|
10
18
|
# sb.log_stream(follow: true).each { |entry| print entry.text }
|
|
11
19
|
class LogStream
|
|
@@ -31,6 +39,11 @@ module Microsandbox
|
|
|
31
39
|
# Enumerable: iteration yields one snapshot per interval tick until the
|
|
32
40
|
# sandbox stops.
|
|
33
41
|
#
|
|
42
|
+
# @note **Single-pass, forward-only, single-consumer.** Like {LogStream},
|
|
43
|
+
# `each` drains a one-shot native channel — not rewindable, iterate once
|
|
44
|
+
# from a single thread; a second pass or a post-drain combinator yields
|
|
45
|
+
# nothing.
|
|
46
|
+
#
|
|
34
47
|
# @example
|
|
35
48
|
# sb.metrics_stream(interval: 0.5).each { |m| puts m.cpu_percent }
|
|
36
49
|
class MetricsStream
|
data/lib/microsandbox/version.rb
CHANGED
|
@@ -8,12 +8,12 @@ module Microsandbox
|
|
|
8
8
|
# Versioning section of the README for the full gem-to-runtime map. Must equal
|
|
9
9
|
# the native ext's Cargo crate version (`Native.version`), enforced by
|
|
10
10
|
# spec/unit/version_spec.rb.
|
|
11
|
-
VERSION = "0.
|
|
11
|
+
VERSION = "0.9.0"
|
|
12
12
|
|
|
13
13
|
# The upstream microsandbox runtime release this gem build embeds — the `tag`
|
|
14
14
|
# pinned on the `microsandbox`/`microsandbox-network` git deps in
|
|
15
15
|
# ext/microsandbox/Cargo.toml. Exposed at runtime as
|
|
16
16
|
# {Microsandbox.runtime_version}. spec/unit/version_spec.rb asserts it stays in
|
|
17
17
|
# sync with the Cargo tag so it can't silently drift out of date.
|
|
18
|
-
RUNTIME_VERSION = "v0.
|
|
18
|
+
RUNTIME_VERSION = "v0.6.1"
|
|
19
19
|
end
|
data/lib/microsandbox.rb
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "microsandbox/version"
|
|
4
4
|
|
|
5
|
-
# Load the compiled native extension. Precompiled platform gems
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# Load the compiled native extension. Precompiled platform gems stage the
|
|
6
|
+
# binary under a major.minor subdirectory (e.g.
|
|
7
|
+
# lib/microsandbox/3.4/microsandbox_rb.bundle) — that is the directory
|
|
8
|
+
# rake-compiler builds (it matches the `ruby_version` against /(\d+\.\d+)/), NOT
|
|
9
|
+
# the API string "3.4.0" that RbConfig::CONFIG["ruby_version"] returns. Use
|
|
10
|
+
# RUBY_VERSION's major.minor so the require hits the staged path; fall back to
|
|
11
|
+
# the flat path source builds produce.
|
|
8
12
|
begin
|
|
9
|
-
|
|
10
|
-
require_relative "microsandbox/#{
|
|
13
|
+
abi_version = RUBY_VERSION[/\d+\.\d+/]
|
|
14
|
+
require_relative "microsandbox/#{abi_version}/microsandbox_rb"
|
|
11
15
|
rescue LoadError
|
|
12
16
|
require_relative "microsandbox/microsandbox_rb"
|
|
13
17
|
end
|
data/sig/microsandbox.rbs
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
# Type signatures for the public microsandbox Ruby API.
|
|
2
|
+
#
|
|
3
|
+
# Note: value, stream, and handle types (e.g. ExecOutput, Metrics, SandboxHandle,
|
|
4
|
+
# the Ssh* clients, AgentClient/AgentStream, PullSession, Snapshot*/Image*/Volume*)
|
|
5
|
+
# are constructed by the SDK from an internal native handle or data hash, not by
|
|
6
|
+
# user code — you receive them from SDK calls. Their `initialize` is documented
|
|
7
|
+
# below only to keep `new`'s synthesized signature accurate (RBS derives `new`
|
|
8
|
+
# from `initialize`; omitting it does NOT hide the constructor — it falls back to
|
|
9
|
+
# a misleading zero-arg `() -> instance` that Ruby actually rejects). Treat these
|
|
10
|
+
# constructors as internal.
|
|
2
11
|
|
|
3
12
|
module Microsandbox
|
|
4
13
|
VERSION: String
|
|
@@ -38,6 +47,11 @@ module Microsandbox
|
|
|
38
47
|
class ImageNotFoundError < Error end
|
|
39
48
|
class ImageInUseError < Error end
|
|
40
49
|
class ImagePullFailedError < Error end
|
|
50
|
+
class SnapshotNotFoundError < Error end
|
|
51
|
+
class SnapshotAlreadyExistsError < Error end
|
|
52
|
+
class SnapshotSandboxRunningError < Error end
|
|
53
|
+
class SnapshotImageMissingError < Error end
|
|
54
|
+
class SnapshotIntegrityError < Error end
|
|
41
55
|
class NetworkPolicyError < Error end
|
|
42
56
|
class SecretViolationError < Error end
|
|
43
57
|
class TlsError < Error end
|