bsdkrun 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3cc7fd1dcbcb90243855217c4fe7852f6e98baf235fc6e0875ca3f7aeafeb7b
4
- data.tar.gz: 92aaa16edb2d6970912f64840835dbd78611cf9f30bc4c5830c1c4f0f537fa71
3
+ metadata.gz: b23de4b48a88b521c5909ad2aa610d8557e9b06661e450db3d862292cc3bb296
4
+ data.tar.gz: be6175dd5884c7ec861a57f355c3dff05d01e759355117d8e21e27904b717063
5
5
  SHA512:
6
- metadata.gz: 1505af9b58a1397e6de294f06d76b2a8ca81ae0a46ba80f61c12baa6e5de99d8e9fba3c973bee7157c663233e575468bbe175e435fe23a01258855a4278a00fa
7
- data.tar.gz: 38b533ac4692529528ce0c5c5739b1f62f8012f7ec6a2ac2814f866c99d7ec47a13ab26f1639e102012ca4e8161483cfe0731c8d987c23432da42133dfc61167
6
+ metadata.gz: 9b2b575fc9be711c375a053537e8ba687d162c4180c1e7075aaa5e3d27d8992655615c10c28ca5894a066213a3d8382c9f6f7c31fdca7118b8dc72707bddf86f
7
+ data.tar.gz: 38d9ba56709b1b870df9873d70c658e68534fb2eb90fa6c6793a2e87aa80d9fe1f076a6324579f4834a9241554960b35c6501ebc05e3896a781da3166be2c565
data/README.md CHANGED
@@ -1,9 +1,7 @@
1
1
  # bsdkrun (Ruby SDK)
2
2
 
3
- A Ruby SDK for [**bsdkrun**](https://github.com/tsirysndr/bsdkrun) — a
4
- Firecracker-style microVM launcher for **BSD and Linux** guests on macOS and
5
- Linux, built on [libkrun](https://github.com/containers/libkrun). Boot and drive
6
- microVMs programmatically, inspired by the **Vercel** and **Deno** Sandbox SDKs.
3
+ A Ruby SDK for [**bsdkrun**](https://github.com/tsirysndr/bsdkrun) — a Firecracker-style microVM launcher for **BSD, Linux, and unikernel** guests on macOS and
4
+ Linux, built on [libkrun](https://github.com/containers/libkrun). Boot and drive microVMs programmatically, inspired by the **Vercel** and **Deno** Sandbox SDKs.
7
5
 
8
6
  The SDK shells out to the `bsdkrun` binary, so it has **zero runtime
9
7
  dependencies** — just the Ruby standard library (`open3`, `json`, `pathname`).
@@ -191,6 +189,71 @@ Names resolve on Linux and FreeBSD via the network's DNS; **NetBSD** resolves
191
189
  via a synced `/etc/hosts` block — joins auto-sync, and `Networks.sync` refreshes
192
190
  an existing network without restarting members.
193
191
 
192
+ ## Connecting to a remote daemon
193
+
194
+ Everything above talks to a local `bsdkrun` binary. `Bsdkrun::Client` is the
195
+ network sibling: it drives the same operations against a remote
196
+ [`bsdkrund`](../../daemon/README.md) over its GraphQL API — no local binary
197
+ needed, just a URL and a bearer token.
198
+
199
+ ```ruby
200
+ require "bsdkrun"
201
+
202
+ client = Bsdkrun::Client.new(url: "http://vps.example.com:50052", token: "9f2c...")
203
+ # or, from BSDKRUN_URL / BSDKRUN_TOKEN:
204
+ client = Bsdkrun::Client.from_env
205
+
206
+ machines = client.list(all: true) # Array<SandboxInfo> — same type Sandbox.list returns
207
+ id = client.run_linux(image: "alpine", cpus: 2, mem: 1024, command: ["sleep", "300"])
208
+
209
+ result = client.exec(id, ["uname", "-a"])
210
+ puts result.output, result.exit_code
211
+
212
+ client.stop(id)
213
+ client.remove([id])
214
+ ```
215
+
216
+ `client.run_linux`/`run_bsd`/`run_nanos`/`run_unikraft`/`run_solo5`/`run_osv`/
217
+ `run_flavor` each take the same options as the corresponding GraphQL mutation
218
+ (`daemon/src/graphql.rs`) — `run_bsd(os: "freebsd", ...)`, etc. — and return
219
+ the new machine's id. `run_solo5` boots a MirageOS unikernel under the
220
+ `solo5-hvt` tender rather than libkrun:
221
+ `run_solo5(path: "dist/hello.hvt", args: ["--ipv4=10.0.0.2/24"])`.
222
+ `stop`/`start`/`remove`/`update`/`commit` return a
223
+ `CommandResult` (`exit_code`, `stdout`, `stderr`).
224
+
225
+ For a live terminal instead of a one-shot `exec`, use `shell`:
226
+
227
+ ```ruby
228
+ session = client.shell(id) # or shell(id, command: [...]) for a non-login command
229
+ session.on_output { |bytes| $stdout.write(bytes) }
230
+ session.on_exit { |code| puts "\nexited #{code}" }
231
+ session.write("ls -la\n")
232
+ session.resize(50, 120)
233
+ session.close
234
+ ```
235
+
236
+ `follow_logs(id) { |bytes| ... }` streams a machine's console live instead of
237
+ the one-shot `logs(id)`. Both `exec`/`shell` and `follow_logs` are built on
238
+ the same `openShell`/`shellOutput` shell-session protocol the daemon uses for
239
+ every interactive terminal — see [`daemon/README.md`](../../daemon/README.md#interactive-shells-over-graphql)
240
+ for the wire-level story.
241
+
242
+ Not every GraphQL operation has a typed method yet (flavor/network/volume
243
+ management, for instance) — `client.request(query, variables)` runs any raw
244
+ query or mutation, and `client.subscribe(query, variables, on_next: ...)` runs
245
+ any raw subscription, for anything not wrapped above.
246
+
247
+ Like the rest of this gem, `Client` uses **only the Ruby standard library** —
248
+ the HTTP transport is `Net::HTTP`, and subscriptions (used by `exec`/`shell`/
249
+ `follow_logs`) run over a hand-rolled `graphql-transport-ws` client on top of
250
+ `Socket`/`OpenSSL`, since Ruby's standard library has no WebSocket client of
251
+ its own.
252
+
253
+ `Client.new(url:, token:)` and `.from_env` both reject a URL configured
254
+ without a token rather than silently making an unauthenticated request — set
255
+ both `BSDKRUN_URL` and `BSDKRUN_TOKEN`, or pass both explicitly.
256
+
194
257
  ## Errors
195
258
 
196
259
  All errors extend `Bsdkrun::Error`:
@@ -200,6 +263,9 @@ All errors extend `Bsdkrun::Error`:
200
263
  `stdout`, `stderr`, `command`). Raised by `exec` with `throw_on_error: true`,
201
264
  by the lifecycle/namespace helpers, and by the agent helpers.
202
265
  - `Bsdkrun::SandboxNotFound` — `Sandbox.get` matched no machine.
266
+ - `Bsdkrun::GraphQLError` — a `Client` request failed (carries `code`, the
267
+ daemon's `extensions.code`, when there is one).
268
+ - `Bsdkrun::AuthError` (a `GraphQLError`) — the daemon rejected the bearer token.
203
269
 
204
270
  ## Try it interactively
205
271
 
data/lib/bsdkrun/args.rb CHANGED
@@ -65,6 +65,10 @@ module Bsdkrun
65
65
  when "netbsd" then netbsd_args(opts)
66
66
  when "firmware" then firmware_args(opts)
67
67
  when "kernel" then kernel_args(opts)
68
+ when "unikraft" then unikraft_args(opts)
69
+ when "solo5" then solo5_args(opts)
70
+ when "nanos" then nanos_args(opts)
71
+ when "osv" then osv_args(opts)
68
72
  else raise ArgumentError, "unknown os: #{opts[:os].inspect}"
69
73
  end
70
74
  end
@@ -121,5 +125,67 @@ module Bsdkrun
121
125
  a.concat(disk_args(opts)).concat(net_args(opts[:net]))
122
126
  .concat(name_args(opts)).concat(vm_args(opts))
123
127
  end
128
+
129
+ # @!visibility private
130
+ #
131
+ # Nanos: no agent (like unikraft), but it has a root disk, so +persist+
132
+ # is honored. +:image+ is a path or a ~/.ops/images name.
133
+ def nanos_args(opts)
134
+ a = ["nanos", "-d"]
135
+ a.push("--kernel", opts[:kernel]) if opts[:kernel]
136
+ a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
137
+ a.push("--persist") if opts[:persist]
138
+ a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
139
+ a.push(opts.fetch(:image).to_s)
140
+ end
141
+
142
+ # @!visibility private
143
+ #
144
+ # OSv: like nanos, no agent (no exec/shell/snapshot), but it does have a
145
+ # root filesystem, so +persist+ is honored. +:image+ is a loader.img, or on
146
+ # x86_64 the loader ELF plus a +:disk+.
147
+ def osv_args(opts)
148
+ a = ["osv", "-d"]
149
+ a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
150
+ a.push("--disk", opts[:disk]) if opts[:disk]
151
+ a.push("--gic", opts[:gic].to_s) if opts[:gic]
152
+ a.push("--persist") if opts[:persist]
153
+ a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
154
+ a.push(opts.fetch(:image).to_s)
155
+ end
156
+
157
+ # @!visibility private
158
+ #
159
+ # No +disk_args+: a unikernel has no disk, so there is nothing to persist,
160
+ # attach or clone. +:path+ is a kraft project dir or an image; default +.+.
161
+ def unikraft_args(opts)
162
+ a = ["unikraft", "-d"]
163
+ a.push("--cmdline", opts[:cmdline]) if opts[:cmdline]
164
+ a.push("--initramfs", opts[:initramfs]) if opts[:initramfs]
165
+ # Volumes are the exception to "no disk options": virtio-fs shares,
166
+ # which need neither a disk nor an agent.
167
+ Array(opts[:mounts]).each { |m| a.push("--mount", m) }
168
+ a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
169
+ a.push((opts[:path] || ".").to_s)
170
+ end
171
+
172
+ # @!visibility private
173
+ #
174
+ # Solo5 (MirageOS): runs under the +solo5-hvt+ tender rather than libkrun.
175
+ # The unikernel declares its devices in its own MFT1 manifest, so only the
176
+ # +:block+ backing files (+NAME=FILE+) are passed. +:path+ is a +.hvt+
177
+ # binary or a project dir whose +dist/+ holds one; default +.+. Guest
178
+ # +:args+ go last, after a literal +--+ — MirageOS options look like
179
+ # bsdkrun's own (e.g. +--ipv4=...+), so the CLI takes them as trailing
180
+ # args.
181
+ def solo5_args(opts)
182
+ a = ["solo5", "-d"]
183
+ Array(opts[:block]).each { |b| a.push("--block", b) }
184
+ a.concat(net_args(opts[:net])).concat(name_args(opts)).concat(vm_args(opts))
185
+ a.push((opts[:path] || ".").to_s)
186
+ args = Array(opts[:args])
187
+ a.push("--", *args) unless args.empty?
188
+ a
189
+ end
124
190
  end
125
191
  end