@braedonsaunders/appkit-desk 0.6.0 → 0.8.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 (53) hide show
  1. package/README.md +83 -1
  2. package/backend.d.ts +28 -7
  3. package/backend.d.ts.map +1 -1
  4. package/backend.js +18 -521
  5. package/backend.js.map +1 -1
  6. package/events.d.ts +3 -0
  7. package/events.d.ts.map +1 -1
  8. package/events.js.map +1 -1
  9. package/host.d.ts +32 -1
  10. package/host.d.ts.map +1 -1
  11. package/host.js +141 -23
  12. package/host.js.map +1 -1
  13. package/index.d.ts +3 -0
  14. package/index.d.ts.map +1 -1
  15. package/index.js +3 -0
  16. package/index.js.map +1 -1
  17. package/machine.d.ts +145 -0
  18. package/machine.d.ts.map +1 -0
  19. package/machine.js +518 -0
  20. package/machine.js.map +1 -0
  21. package/network.d.ts +37 -0
  22. package/network.d.ts.map +1 -0
  23. package/network.js +173 -0
  24. package/network.js.map +1 -0
  25. package/package.json +2 -2
  26. package/plan.d.ts +129 -31
  27. package/plan.d.ts.map +1 -1
  28. package/plan.js +165 -9
  29. package/plan.js.map +1 -1
  30. package/port-relay.d.ts +13 -0
  31. package/port-relay.d.ts.map +1 -0
  32. package/port-relay.js +56 -0
  33. package/port-relay.js.map +1 -0
  34. package/virtualization.d.ts +16 -0
  35. package/virtualization.d.ts.map +1 -0
  36. package/virtualization.js +148 -0
  37. package/virtualization.js.map +1 -0
  38. package/vz/Package.swift +23 -0
  39. package/vz/Sources/DeskNet/Addresses.swift +106 -0
  40. package/vz/Sources/DeskNet/DHCP.swift +111 -0
  41. package/vz/Sources/DeskNet/Fence.swift +57 -0
  42. package/vz/Sources/DeskNet/NetworkStack.swift +547 -0
  43. package/vz/Sources/DeskNet/Packets.swift +355 -0
  44. package/vz/Sources/DeskNet/Plumbing.swift +312 -0
  45. package/vz/Sources/DeskNet/Sockets.swift +221 -0
  46. package/vz/Sources/DeskNet/TCPFlow.swift +448 -0
  47. package/vz/Sources/appkit-desk-vz/Control.swift +152 -0
  48. package/vz/Sources/appkit-desk-vz/Helper.swift +505 -0
  49. package/vz/Sources/appkit-desk-vz/main.swift +38 -0
  50. package/vz/Tests/DeskNetTests/DeskNetTests.swift +426 -0
  51. package/vz/appkit-desk-vz.entitlements +8 -0
  52. package/vz/build.sh +23 -0
  53. package/vz/check/extract-boot.py +221 -0
package/index.d.ts CHANGED
@@ -7,4 +7,7 @@ export * from './host.js';
7
7
  export * from './frames.js';
8
8
  export * from './portable-home.js';
9
9
  export * from './conformance.js';
10
+ export * from './network.js';
11
+ export * from './virtualization.js';
12
+ export * from './port-relay.js';
10
13
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,QAAQ,CAAA;AACtB,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AACzB,cAAc,QAAQ,CAAA;AACtB,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,QAAQ,CAAA;AACtB,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AACzB,cAAc,QAAQ,CAAA;AACtB,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AAEzB,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA"}
package/index.js CHANGED
@@ -7,4 +7,7 @@ export * from './host.js';
7
7
  export * from './frames.js';
8
8
  export * from './portable-home.js';
9
9
  export * from './conformance.js';
10
+ export * from './network.js';
11
+ export * from './virtualization.js';
12
+ export * from './port-relay.js';
10
13
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,QAAQ,CAAA;AACtB,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AACzB,cAAc,QAAQ,CAAA;AACtB,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA","sourcesContent":["export * from './events'\nexport * from './protocol'\nexport * from './plan'\nexport * from './guest-agent'\nexport * from './backend'\nexport * from './host'\nexport * from './frames'\nexport * from './portable-home'\nexport * from './conformance'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,QAAQ,CAAA;AACtB,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AACzB,cAAc,QAAQ,CAAA;AACtB,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AAEzB,cAAc,kBAAkB,CAAA;AAChC,cAAc,cAAc,CAAA","sourcesContent":["export * from './events'\nexport * from './protocol'\nexport * from './plan'\nexport * from './guest-agent'\nexport * from './backend'\nexport * from './host'\nexport * from './frames'\nexport * from './portable-home'\nexport * from './conformance'\nexport * from './network'\n\nexport * from './virtualization'\nexport * from './port-relay'\n"]}
package/machine.d.ts ADDED
@@ -0,0 +1,145 @@
1
+ /**
2
+ * The guest channel every backend shares: the hybrid-vsock handshake, the
3
+ * proof that a guest is really behind it, the reconnecting machine, and the
4
+ * child-process plumbing around a VMM. Cloud Hypervisor and the
5
+ * Virtualization.framework helper both expose the guest agent through a host
6
+ * Unix socket with the same `CONNECT <port>` preamble, so everything from the
7
+ * socket inwards is one implementation. Internal to the package.
8
+ */
9
+ import { type ChildProcess } from 'node:child_process';
10
+ import type { Duplex } from 'node:stream';
11
+ import { type DeskMachine } from './backend.js';
12
+ import { type DeskLauncherIdentity } from './plan.js';
13
+ export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
14
+ export declare const DEFAULT_CONNECT_TIMEOUT_MS = 20000;
15
+ export declare const DEFAULT_CONNECT_RETRY_DELAY_MS = 100;
16
+ export declare const DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;
17
+ export declare const DEFAULT_RECONNECT_WINDOW_MS = 30000;
18
+ export declare const DEFAULT_RECONNECT_RETRY_DELAY_MS = 250;
19
+ /**
20
+ * How far the reconnect delay is allowed to double. A guest agent that systemd
21
+ * is restarting is not helped by being hammered four times a second for the
22
+ * whole window, and an unbounded doubling would spend most of the window
23
+ * asleep after two failures.
24
+ */
25
+ export declare const RECONNECT_BACKOFF_CEILING = 8;
26
+ export declare const DEFAULT_KILL_GRACE_MS = 2000;
27
+ /**
28
+ * The machine, and the small state machine that keeps it connected.
29
+ *
30
+ * A vsock channel is not a fact the host learns once at boot — the transport
31
+ * can lie about being connected, and it can go away mid-lease for reasons that
32
+ * have nothing to do with the desk being unusable: the guest agent restarts,
33
+ * the socat bridge drops, a transient vsock error, the guest wedges for a
34
+ * moment. Treating any of those as terminal stranded the desk for the rest of
35
+ * its lease with a perfectly healthy guest behind it, answering every call
36
+ * with "no longer connected". So a live connection is something this machine
37
+ * RE-ESTABLISHES:
38
+ *
39
+ * live ──unexpected drop──▶ reconnecting ──confirmed guest──▶ live
40
+ * │ │
41
+ * │ └──window exhausted / VMM exited──▶ closed
42
+ * └──shutdown(), VMM already exited, protocol violation───────────▶ closed
43
+ *
44
+ * `closed` is terminal. Nothing reconnects out of it, which is what makes
45
+ * `shutdown()` and a dead VMM final rather than the start of a retry loop.
46
+ *
47
+ * What differs between hypervisors is only how the VM stops, parks, and
48
+ * reaches guest ports, and those arrive as functions.
49
+ */
50
+ export declare function createGuestMachine(options: {
51
+ deskId: string;
52
+ exit: ExitWatcher;
53
+ stream: Duplex;
54
+ /** Re-establishes the channel; resolves only once a guest has answered. */
55
+ reconnect: (abandoned: () => boolean) => Promise<Duplex>;
56
+ reconnectWindowMs: number;
57
+ now: () => number;
58
+ idFactory: () => string;
59
+ requestTimeoutMs: number;
60
+ /** Stop the VMM; resolves once its process has exited. */
61
+ stop: () => Promise<void>;
62
+ /**
63
+ * Pause, snapshot into `snapshotDir`, and stop the VMM. Called after the
64
+ * guest channel has been closed. Must leave the VMM stopped on failure too.
65
+ */
66
+ park?: (snapshotDir: string) => Promise<void>;
67
+ /** Open a host-to-guest TCP stream, for backends with no routable guest. */
68
+ connect?: (port: number) => Promise<Duplex>;
69
+ }): DeskMachine;
70
+ export interface ExitWatcher {
71
+ done: boolean;
72
+ promise: Promise<void>;
73
+ }
74
+ /**
75
+ * Read a child's output and keep only the last few kilobytes.
76
+ *
77
+ * This is not for logging — it is load-bearing. A child spawned with its
78
+ * streams as PIPES blocks once a pipe nobody reads fills, after about 64KB.
79
+ * Cloud Hypervisor logs while it runs, so an unread pipe freezes the guest
80
+ * partway through boot: the process is alive, the disk is untouched, and the
81
+ * guest agent never reaches vsock. Every symptom points at the image and none
82
+ * of them are its fault.
83
+ *
84
+ * Draining costs nothing and buys the tail of what the process said, which is
85
+ * the difference between "the VMM exited" and knowing why. `streams` names the
86
+ * pipes to drain: a helper that speaks a protocol on stdout drains only stderr.
87
+ */
88
+ export declare function drainOutput(child: ChildProcess, limit?: number, streams?: readonly ('stdout' | 'stderr')[]): {
89
+ tail: () => string;
90
+ };
91
+ export declare function watchExit(child: ChildProcess): ExitWatcher;
92
+ export declare function terminate(child: ChildProcess, exit: ExitWatcher, killGraceMs: number): Promise<void>;
93
+ export declare function runToCompletion(child: ChildProcess, label: string): Promise<void>;
94
+ /**
95
+ * Cloud Hypervisor exposes guest vsock ports through a host Unix socket with
96
+ * a line-oriented preamble: the client writes `CONNECT <port>\n` and the VMM
97
+ * answers `OK <assigned>\n` before the byte stream becomes the guest
98
+ * connection. The VMM creates the socket during boot, so connection attempts
99
+ * retry until the deadline. The Virtualization.framework helper speaks the
100
+ * same preamble on its own socket.
101
+ *
102
+ * The same loop serves the first connection and every later reconnect, so a
103
+ * reconnect gets `confirmGuest` too — a mid-lease reconnect that trusted the
104
+ * banner would re-adopt exactly the hollow stream the boot path learned not to.
105
+ */
106
+ export declare function connectWithRetry(options: {
107
+ connect: (socketPath: string) => Duplex;
108
+ now: () => number;
109
+ socketPath: string;
110
+ port: number;
111
+ timeoutMs: number;
112
+ retryDelayMs: number;
113
+ /** Ceiling the delay doubles toward; omitted keeps the delay constant. */
114
+ maxRetryDelayMs?: number;
115
+ attemptTimeoutMs: number;
116
+ exited: ExitWatcher;
117
+ /** A dead VMM means something different at boot than it does mid-lease. */
118
+ exitedMessage?: string;
119
+ /** Stops the loop early when the caller no longer wants the connection. */
120
+ abandoned?: () => boolean;
121
+ }): Promise<Duplex>;
122
+ /**
123
+ * One handshake attempt, which MUST settle.
124
+ *
125
+ * Cloud Hypervisor accepts a connection on the vsock socket whether or not
126
+ * anything in the guest is listening on the port, and when nothing is it may
127
+ * answer nothing at all: no reply, no error, no close. Without a deadline of
128
+ * its own this promise then never settles — and because the caller awaits it
129
+ * inside the retry loop, the loop stops iterating, the overall deadline is
130
+ * never re-checked, and the boot hangs forever rather than failing. That is
131
+ * indistinguishable from a wedged host and it is why every attempt is bounded
132
+ * here as well as in aggregate.
133
+ *
134
+ * A close before the reply line is a refusal: that is how a port with nothing
135
+ * listening presents through Cloud Hypervisor, and the Virtualization.framework
136
+ * helper presents a refused vsock or TCP port the same way.
137
+ */
138
+ export declare function attemptHandshake(connect: (socketPath: string) => Duplex, socketPath: string, port: number, attemptTimeoutMs: number): Promise<Duplex>;
139
+ export declare function defaultLauncher(command: string, args: readonly string[], options: {
140
+ identity?: DeskLauncherIdentity;
141
+ }): ChildProcess;
142
+ export declare function defaultConnect(socketPath: string): Duplex;
143
+ export declare function delay(ms: number): Promise<void>;
144
+ export declare function errorMessage(error: unknown): string;
145
+ //# sourceMappingURL=machine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine.d.ts","sourceRoot":"","sources":["../src/machine.ts"],"names":[],"mappings":"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"}