@braedonsaunders/appkit-desk 0.7.0 → 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.
Files changed (46) hide show
  1. package/README.md +126 -1
  2. package/backend.d.ts +32 -7
  3. package/backend.d.ts.map +1 -1
  4. package/backend.js +107 -524
  5. package/backend.js.map +1 -1
  6. package/host.d.ts +27 -1
  7. package/host.d.ts.map +1 -1
  8. package/host.js +47 -19
  9. package/host.js.map +1 -1
  10. package/index.d.ts +2 -0
  11. package/index.d.ts.map +1 -1
  12. package/index.js +2 -0
  13. package/index.js.map +1 -1
  14. package/machine.d.ts +154 -0
  15. package/machine.d.ts.map +1 -0
  16. package/machine.js +573 -0
  17. package/machine.js.map +1 -0
  18. package/package.json +2 -2
  19. package/plan.d.ts +192 -31
  20. package/plan.d.ts.map +1 -1
  21. package/plan.js +298 -12
  22. package/plan.js.map +1 -1
  23. package/port-relay.d.ts +13 -0
  24. package/port-relay.d.ts.map +1 -0
  25. package/port-relay.js +56 -0
  26. package/port-relay.js.map +1 -0
  27. package/virtualization.d.ts +16 -0
  28. package/virtualization.d.ts.map +1 -0
  29. package/virtualization.js +156 -0
  30. package/virtualization.js.map +1 -0
  31. package/vz/Package.swift +23 -0
  32. package/vz/Sources/DeskNet/Addresses.swift +106 -0
  33. package/vz/Sources/DeskNet/DHCP.swift +111 -0
  34. package/vz/Sources/DeskNet/Fence.swift +57 -0
  35. package/vz/Sources/DeskNet/NetworkStack.swift +547 -0
  36. package/vz/Sources/DeskNet/Packets.swift +355 -0
  37. package/vz/Sources/DeskNet/Plumbing.swift +312 -0
  38. package/vz/Sources/DeskNet/Sockets.swift +221 -0
  39. package/vz/Sources/DeskNet/TCPFlow.swift +448 -0
  40. package/vz/Sources/appkit-desk-vz/Control.swift +152 -0
  41. package/vz/Sources/appkit-desk-vz/Helper.swift +550 -0
  42. package/vz/Sources/appkit-desk-vz/main.swift +38 -0
  43. package/vz/Tests/DeskNetTests/DeskNetTests.swift +426 -0
  44. package/vz/appkit-desk-vz.entitlements +8 -0
  45. package/vz/build.sh +23 -0
  46. package/vz/check/extract-boot.py +221 -0
package/README.md CHANGED
@@ -257,4 +257,129 @@ DESK_CHECK_IMAGE_ROOT=/data/desk-image DESK_CHECK_BASE_IMAGE=/data/desk-image/ba
257
257
  DESK_CHECK_TMPDIR=/data/tmp pnpm --filter @braedonsaunders/appkit-desk exec tsx scripts/check-kvm.mts
258
258
  ```
259
259
 
260
- Set `DESK_CHECK_KERNEL_CMDLINE` for images that require a custom root device. The check creates and removes a disposable VM, overlay, snapshot, TAP and firewall rules. It verifies a guest HTTP stream before and after memory restore; it does not verify the proxy or cross-CPU migration. Choose addresses that do not overlap existing routes.
260
+ Set `DESK_CHECK_KERNEL_CMDLINE` for images that require a custom root device. The check creates and removes a disposable VM, overlay, snapshot, TAP and firewall rules. It verifies a guest HTTP stream before and after memory restore; it does not verify the proxy or cross-CPU migration. Choose addresses that do not overlap existing routes. Set `DESK_CHECK_VIRTIOFSD` to a `virtiofsd` binary to also check a read-only share: the guest mounts it, a write fails, and a file held open across the park reads back after the restore.
261
+
262
+ ## Host directory shares
263
+
264
+ A desk can mount host directories over virtio-fs. Pass `shares` to
265
+ `host.start` (or `buildDeskLaunchPlan`):
266
+
267
+ ```ts
268
+ const desk = await host.start({
269
+ deskId: 'agent-7',
270
+ baseImage: '/data/agent-disks/base.img',
271
+ overlayPath: '/data/agent-disks/overlays/agent-7.img',
272
+ shares: [
273
+ { tag: 'tools', path: '/srv/desk-tools', readOnly: true },
274
+ { tag: 'build', path: '/srv/builds/agent-7', readOnly: false },
275
+ ],
276
+ })
277
+ await desk.exec({ command: '/bin/sh', args: ['-c', 'mkdir -p /opt/tools && mount -t virtiofs tools /opt/tools'] })
278
+ ```
279
+
280
+ A `tag` is 1–36 lowercase letters, digits or hyphens, starting with a letter or
281
+ digit, and unique within the desk; it is the name the guest mounts. `path` is an
282
+ absolute path to an existing host directory. `readOnly` is required. A read-only
283
+ share is enforced by the host's file server, so remounting it read-write in the
284
+ guest does not make it writable. The guest kernel needs virtio-fs
285
+ (`CONFIG_VIRTIO_FS`; Debian's cloud kernels ship it as the `virtiofs` module).
286
+
287
+ On Cloud Hypervisor, each share is served by its own `virtiofsd` (the Rust
288
+ implementation, 1.13 or later). `virtiofsdPath` on the host options names the
289
+ binary; without it the first `virtiofsd` on `PATH` is used, then
290
+ `/usr/libexec/virtiofsd`. A desk without shares does not need `virtiofsd`. Each
291
+ daemon runs as the launcher identity with:
292
+
293
+ ```sh
294
+ virtiofsd --socket-path=<runtimeDir>/desk-<deskId>.<tag>.fs.sock --shared-dir=<path> \
295
+ --sandbox=<namespace|chroot> --cache=auto \
296
+ --migration-mode=find-paths --migration-on-error=guest-error [--readonly]
297
+ ```
298
+
299
+ and the VMM gets `--fs tag=<tag>,socket=<socket>` with its memory as
300
+ `size=<n>M,shared=on`, which vhost-user devices require; a desk without shares
301
+ keeps private memory. `--cache=auto` lets the guest cache for about a second,
302
+ so files the host changes under a share appear in running guests.
303
+ `virtiofsdSandbox` picks how the daemon confines itself to the directory:
304
+ `namespace` (the default) needs CAP_SYS_ADMIN when the launcher is root and
305
+ unprivileged user namespaces otherwise; `chroot` needs root and suits a
306
+ container that cannot create namespaces. Both prevent symlink escapes. The
307
+ backend starts every daemon and waits for its socket before starting the VMM,
308
+ on a boot and on a restore, and stops the daemons after the VMM. A daemon that
309
+ exits while its VMM runs stops the VMM, so the desk is lost rather than left
310
+ with a share that no longer answers. `virtiofsd` also exits when its VMM
311
+ disconnects. The share socket path must fit in 107 bytes.
312
+
313
+ On Virtualization.framework, each share is a
314
+ `VZVirtioFileSystemDeviceConfiguration` with a `VZSingleDirectoryShare`, and the
315
+ framework enforces `readOnly`. `virtiofsdPath` and `virtiofsdSandbox` are
316
+ refused there. When Virtualization.framework reports that a configuration
317
+ cannot be saved, the desk parks by shutting down, as for any other device.
318
+
319
+ Parking keeps a desk's shares with its memory. The backend records them in
320
+ `shares.json` beside the snapshot, and a restore uses that record instead of the
321
+ start options: a snapshot holds its devices, so a share added since joins the
322
+ desk at its next cold boot (`host.shutdown` forces one), and a backend refuses a
323
+ restore plan whose shares differ from the record. A recorded share whose
324
+ directory no longer exists fails the restore; the host discards the snapshot,
325
+ cold boots with the current shares, and reports it in `lastError`. A snapshot
326
+ holds no share contents. On Cloud Hypervisor it carries each daemon's state,
327
+ which the new daemon loads on restore by reopening every file and directory the
328
+ guest held by its path, so open files survive. Paths are the link: a file the
329
+ host unlinks or moves while the desk is parked returns errors to the guest for
330
+ that file only, and a file the host puts at its path is the one the guest gets.
331
+
332
+ ## macOS Virtualization.framework backend
333
+
334
+ On Apple silicon with macOS 14 or later, a host can supply a signed
335
+ `appkit-desk-vz` helper to `createVirtualizationBackend` and pass that backend
336
+ into `createDeskHost`:
337
+
338
+ ```ts
339
+ import { createDeskHost, createVirtualizationBackend, isDeskSupported } from '@braedonsaunders/appkit-desk'
340
+
341
+ const helperPath = '/opt/appkit/bin/appkit-desk-vz'
342
+ if (!isDeskSupported({ helperPath })) throw new Error('This host cannot run desks.')
343
+ const backend = createVirtualizationBackend({ helperPath })
344
+ const host = createDeskHost({
345
+ imageRoot: '/var/lib/appkit/desk',
346
+ backend,
347
+ guestNetwork: { allowLan: false },
348
+ ports,
349
+ })
350
+ ```
351
+
352
+ The guest needs an arm64 Linux kernel, raw base image and the same desk guest
353
+ agent as the Linux backend. Use the kernel command line appropriate to the
354
+ image. Per-desk disks use macOS `cp -c`; provision the overlay directory on a
355
+ filesystem that supports cloning. The helper creates a userspace IPv4 network
356
+ with DHCP, defaults to `10.0.2.15/24` and gateway `10.0.2.2`, and blocks
357
+ private, link-local, shared and host addresses by default. RFC 5737 documentation
358
+ subnets (`192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`) are always blocked,
359
+ including when LAN access is allowed. Set `allowLan` only
360
+ when the host policy explicitly permits local network access. `DeskHandle.connect`
361
+ uses the helper's TCP relay; no routable guest IP on the Mac is required.
362
+
363
+ The npm package includes the Swift source under `vz/`. Build it on macOS with
364
+ Xcode and `vz/build.sh`; the script signs with the required virtualization
365
+ entitlement. Its default ad hoc signature is for local use. Distributors must
366
+ sign with their own Developer ID (`APPKIT_CODESIGN_IDENTITY`) and handle
367
+ notarization and distribution. The caller supplies the absolute installed
368
+ helper path; installation does not download or execute a binary. Save and
369
+ restore use a stable desk MAC and machine identifier; the host removes the
370
+ snapshot while the restored VM is paused before resuming it. The userspace
371
+ network is recreated on restore, so existing TCP and UDP flows must reconnect.
372
+
373
+ For a disposable macOS integration check, supply a prepared guest image with
374
+ its desk agent and Python 3, its matching `vmlinux` and `initrd`, and the signed
375
+ helper. In a source checkout, run `packages/desk/scripts/check-vz.mts` with `DESK_CHECK_IMAGE_ROOT`,
376
+ `DESK_CHECK_BASE_IMAGE`, and `DESK_CHECK_VZ_HELPER`; set
377
+ `DESK_CHECK_KERNEL_CMDLINE` if the image uses a different root device. The
378
+ check creates a temporary guest and tests guest execution, a private-host
379
+ network fence, guest TCP access, and memory save/restore. It removes the guest
380
+ and its temporary files on exit.
381
+
382
+ For clients that require a local TCP URL, `await listenDeskPort(desk, port)`
383
+ returns a loopback-only `{ host, port, close }` listener. Each incoming
384
+ connection opens a guest stream through `desk.connect`; call `close()` when the
385
+ lease ends to close the listener and its connections.
package/backend.d.ts CHANGED
@@ -1,18 +1,19 @@
1
1
  /**
2
- * The backend port and its default Cloud Hypervisor implementation.
2
+ * The backend port.
3
3
  *
4
4
  * `DeskBackend` is the seam that keeps the package testable without a
5
5
  * hypervisor: the host asks a backend to boot a `DeskLaunchPlan` and gets a
6
6
  * `DeskMachine` — a request/response channel to the in-guest agent plus an
7
7
  * event subscription. CI and non-KVM developer machines inject an in-memory
8
- * fake; production uses `cloudHypervisorBackend`, whose process-spawning glue
9
- * is deliberately thin because everything that can be pure lives in the plan
10
- * builder and the protocol module.
8
+ * fake; production uses `cloudHypervisorBackend` on Linux or
9
+ * `createVirtualizationBackend` on an Apple silicon Mac, whose process glue is
10
+ * deliberately thin because everything that can be pure lives in the plan
11
+ * builder, the protocol module, and the shared guest channel.
11
12
  */
12
- import { type ChildProcess } from 'node:child_process';
13
+ import type { ChildProcess } from 'node:child_process';
13
14
  import type { Duplex } from 'node:stream';
14
- import { DeskError, type DeskLaunchPlan, type DeskLauncherIdentity } from './plan.js';
15
- import { type GuestCommand, type GuestEventMessage } from './protocol.js';
15
+ import { DeskError, type DeskHypervisor, type DeskLaunchPlan, type DeskLauncherIdentity } from './plan.js';
16
+ import type { GuestCommand, GuestEventMessage } from './protocol.js';
16
17
  /**
17
18
  * A request that was in flight when the guest connection dropped.
18
19
  *
@@ -88,6 +89,16 @@ export interface DeskMachine {
88
89
  * misbehaves for reasons nobody can find.
89
90
  */
90
91
  onConnectionChange?(listener: (change: DeskConnectionChange) => void): () => void;
92
+ /**
93
+ * Open a TCP stream from the host to `port` on the guest.
94
+ *
95
+ * Optional: a backend whose guest has a routable address (Cloud Hypervisor's
96
+ * TAP) leaves this out and the host dials the guest address directly. A
97
+ * backend whose guest sits behind a userspace NAT (Virtualization.framework)
98
+ * provides it, because there is no address on the Mac to dial. Rejects with
99
+ * `DeskError` when the guest refuses or does not answer.
100
+ */
101
+ connect?(port: number): Promise<Duplex>;
91
102
  /** Stop the VM. Idempotent; resolves once the VMM process has exited. */
92
103
  shutdown(): Promise<void>;
93
104
  /**
@@ -123,6 +134,16 @@ export interface DeskBootHooks {
123
134
  onRestored?: () => Promise<void>;
124
135
  }
125
136
  export interface DeskBackend {
137
+ /**
138
+ * Which hypervisor this backend boots, and so which plan the host builds
139
+ * for it. Absent means Cloud Hypervisor.
140
+ */
141
+ readonly hypervisor?: DeskHypervisor;
142
+ /**
143
+ * The binary the backend launches — the VMM or the helper — for readiness
144
+ * reporting. Optional; `verifyDeskHost` falls back to its own option.
145
+ */
146
+ readonly vmmPath?: string;
126
147
  boot(plan: DeskLaunchPlan, hooks?: DeskBootHooks): Promise<DeskMachine>;
127
148
  }
128
149
  export type DeskProcessLauncher = (command: string, args: readonly string[], options: {
@@ -163,6 +184,10 @@ export interface CloudHypervisorBackendOptions {
163
184
  reconnectRetryDelayMs?: number;
164
185
  killGraceMs?: number;
165
186
  guestAgentPort?: number;
187
+ /** Whether a share daemon's socket exists yet; injectable for tests. */
188
+ pathExists?: (path: string) => boolean;
189
+ /** Removes a stale share socket before its daemon starts; injectable for tests. */
190
+ removeFile?: (path: string) => Promise<void>;
166
191
  }
167
192
  export declare function createCloudHypervisorBackend(options?: CloudHypervisorBackendOptions): DeskBackend;
168
193
  /**
package/backend.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}