@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/network.js ADDED
@@ -0,0 +1,173 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import { isIP } from 'node:net';
4
+ import { promisify } from 'node:util';
5
+ const execFileAsync = promisify(execFile);
6
+ const defaultRunner = {
7
+ async run(command, args) {
8
+ try {
9
+ return (await execFileAsync(command, [...args], { timeout: 15_000 })).stdout;
10
+ }
11
+ catch (error) {
12
+ const failure = error;
13
+ throw new Error(`${command} ${args.join(' ')}: ${(failure.stderr || failure.message || String(error)).trim()}`);
14
+ }
15
+ },
16
+ };
17
+ async function forwarding() {
18
+ const path = '/proc/sys/net/ipv4/ip_forward';
19
+ if ((await readFile(path, 'utf8').catch(() => '')).trim() !== '1')
20
+ await writeFile(path, '1');
21
+ }
22
+ function port(value) {
23
+ if (!Number.isInteger(value) || value < 1 || value > 65535)
24
+ throw new Error('Invalid TCP port');
25
+ }
26
+ function address(value) {
27
+ if (isIP(value) !== 4)
28
+ throw new Error(`Invalid IPv4 address: ${value}`);
29
+ }
30
+ /** Deterministic TAP and locally administered MAC for an unsigned desk index. */
31
+ export function deskNetworkIdentity(index) {
32
+ if (!Number.isInteger(index) || index < 2 || index > 999999999)
33
+ throw new Error('Invalid desk network index');
34
+ const octets = [6, 0, (index >>> 24) & 255, (index >>> 16) & 255, (index >>> 8) & 255, index & 255];
35
+ return { tap: `dsk${index}`, macAddress: octets.map((octet) => octet.toString(16).padStart(2, '0')).join(':') };
36
+ }
37
+ /** Point-to-point TAP with either open or transparent-proxy egress. */
38
+ export function createDeskNetwork(options) {
39
+ address(options.gateway);
40
+ address(options.dns);
41
+ const egress = options.egress;
42
+ if (egress.mode === 'proxied') {
43
+ port(egress.httpProxyPort);
44
+ port(egress.httpsProxyPort);
45
+ }
46
+ else if (egress.mode !== 'open')
47
+ throw new Error('Invalid egress mode');
48
+ const chainPrefix = options.chainPrefix ?? 'desk';
49
+ if (!/^[A-Za-z0-9_-]+$/.test(chainPrefix))
50
+ throw new Error('Invalid iptables chain prefix');
51
+ const hostPorts = [...(options.allowHostPorts ?? [])];
52
+ hostPorts.forEach(port);
53
+ const prefix = options.prefixLength ?? 24;
54
+ if (!Number.isInteger(prefix) || prefix < 1 || prefix > 32)
55
+ throw new Error('Invalid guest prefix length');
56
+ const runner = options.runner ?? defaultRunner;
57
+ const enableForwarding = options.enableForwarding ?? forwarding;
58
+ const work = new Map();
59
+ const run = (command, args) => runner.run(command, args);
60
+ const succeeds = async (command, args) => { try {
61
+ await run(command, args);
62
+ return true;
63
+ }
64
+ catch {
65
+ return false;
66
+ } };
67
+ function validate(value) {
68
+ if (!/^dsk[0-9]{1,9}$/.test(value.tap))
69
+ throw new Error('Invalid TAP name');
70
+ if (`${chainPrefix}-post-${value.tap}`.length > 28)
71
+ throw new Error('iptables chain name exceeds 28 characters');
72
+ address(value.guest);
73
+ if (value.guest === options.gateway)
74
+ throw new Error('Guest and gateway addresses must differ');
75
+ }
76
+ function chains({ tap, guest }) {
77
+ const { gateway, dns } = options;
78
+ const proxyRules = egress.mode === 'proxied' ? [
79
+ ['-p', 'tcp', '--dport', '80', '-j', 'DNAT', '--to-destination', `${gateway}:${egress.httpProxyPort}`],
80
+ ['-p', 'tcp', '--dport', '443', '-j', 'DNAT', '--to-destination', `${gateway}:${egress.httpsProxyPort}`],
81
+ ...['udp', 'tcp'].map((p) => ['-p', p, '--dport', '53', '-j', 'DNAT', '--to-destination', `${dns}:53`]),
82
+ ] : [];
83
+ const proxyPorts = egress.mode === 'proxied'
84
+ ? [egress.httpProxyPort, egress.httpsProxyPort].map((p) => ['-p', 'tcp', '-d', gateway, '--dport', String(p), '-j', 'ACCEPT'])
85
+ : [];
86
+ const extraPorts = hostPorts.flatMap((p) => ['tcp', 'udp'].map((protocol) => ['-p', protocol, '-d', gateway, '--dport', String(p), '-j', 'ACCEPT']));
87
+ const forwardRules = egress.mode === 'proxied' ? [
88
+ ['-m', 'conntrack', '--ctstate', 'ESTABLISHED,RELATED', '-j', 'ACCEPT'],
89
+ ...['udp', 'tcp'].map((p) => ['-i', tap, '-p', p, '-d', dns, '--dport', '53', '-j', 'ACCEPT']),
90
+ ['-j', 'DROP'],
91
+ ] : [
92
+ ['-o', tap, '-d', `${guest}/32`, '-m', 'conntrack', '--ctstate', 'ESTABLISHED,RELATED', '-j', 'ACCEPT'],
93
+ ['-i', tap, '-s', `${guest}/32`, '-j', 'ACCEPT'],
94
+ ['-j', 'DROP'],
95
+ ];
96
+ return [
97
+ { table: 'nat', parent: 'PREROUTING', chain: `${chainPrefix}-pre-${tap}`, jumps: [['-i', tap]], rules: proxyRules },
98
+ { table: 'nat', parent: 'POSTROUTING', chain: `${chainPrefix}-post-${tap}`, jumps: [['-s', `${guest}/32`]], rules: [['-j', 'MASQUERADE']] },
99
+ { table: 'filter', parent: 'INPUT', chain: `${chainPrefix}-in-${tap}`, jumps: [['-i', tap]], rules: [
100
+ ['-m', 'conntrack', '--ctstate', 'ESTABLISHED,RELATED', '-j', 'ACCEPT'],
101
+ ...proxyPorts, ...extraPorts, ['-j', 'DROP'],
102
+ ] },
103
+ { table: 'filter', parent: 'FORWARD', chain: `${chainPrefix}-fwd-${tap}`, jumps: [['-i', tap], ['-o', tap]], rules: forwardRules },
104
+ ...(egress.mode === 'open' ? [
105
+ { command: 'ip6tables', table: 'filter', parent: 'INPUT', chain: `${chainPrefix}-6in-${tap}`, jumps: [['-i', tap]], rules: [['-j', 'DROP']] },
106
+ { command: 'ip6tables', table: 'filter', parent: 'FORWARD', chain: `${chainPrefix}-6fwd-${tap}`, jumps: [['-i', tap], ['-o', tap]], rules: [['-j', 'DROP']] },
107
+ ] : []),
108
+ ];
109
+ }
110
+ function serialize(tap, fn) {
111
+ const next = (work.get(tap) ?? Promise.resolve()).then(fn);
112
+ const settled = next.then(() => undefined, () => undefined);
113
+ work.set(tap, settled);
114
+ void settled.then(() => { if (work.get(tap) === settled)
115
+ work.delete(tap); });
116
+ return next;
117
+ }
118
+ return {
119
+ ensure(value) {
120
+ validate(value);
121
+ return serialize(value.tap, async () => {
122
+ const { tap, guest } = value;
123
+ if (!(await succeeds('ip', ['link', 'show', 'dev', tap])))
124
+ await run('ip', ['tuntap', 'add', 'dev', tap, 'mode', 'tap']);
125
+ await run('ip', ['addr', 'replace', `${options.gateway}/32`, 'dev', tap]);
126
+ await run('ip', ['link', 'set', 'dev', tap, 'up']);
127
+ await run('ip', ['route', 'replace', `${guest}/32`, 'dev', tap]);
128
+ await enableForwarding();
129
+ for (const { table, parent, chain, jumps, rules, command = 'iptables' } of chains(value)) {
130
+ await succeeds(command, ['-t', table, '-N', chain]);
131
+ await run(command, ['-t', table, '-F', chain]);
132
+ for (const rule of rules)
133
+ await run(command, ['-t', table, '-A', chain, ...rule]);
134
+ for (const jump of jumps) {
135
+ const target = [...jump, '-j', chain];
136
+ if (!(await succeeds(command, ['-t', table, '-C', parent, ...target])))
137
+ await run(command, ['-t', table, '-I', parent, '1', ...target]);
138
+ }
139
+ }
140
+ });
141
+ },
142
+ teardown(value) {
143
+ validate(value);
144
+ return serialize(value.tap, async () => {
145
+ for (const { table, parent, chain, jumps, command = 'iptables' } of chains(value)) {
146
+ for (const jump of jumps)
147
+ while (await succeeds(command, ['-t', table, '-C', parent, ...jump, '-j', chain])) {
148
+ if (!(await succeeds(command, ['-t', table, '-D', parent, ...jump, '-j', chain])))
149
+ break;
150
+ }
151
+ await succeeds(command, ['-t', table, '-F', chain]);
152
+ await succeeds(command, ['-t', table, '-X', chain]);
153
+ }
154
+ await succeeds('ip', ['link', 'del', 'dev', value.tap]);
155
+ });
156
+ },
157
+ async configureGuest(desk, value) {
158
+ validate(value);
159
+ const script = ['set -e', 'dev=$(ls /sys/class/net | grep -E "^(en|eth)" | head -n 1)', 'test -n "$dev"', `ip addr replace ${value.guest}/${prefix} dev "$dev"`, 'ip link set dev "$dev" up', `ip route replace default via ${options.gateway} dev "$dev"`, 'rm -f /etc/resolv.conf', `printf 'nameserver %s\\n' ${options.dns} > /etc/resolv.conf`].join('\n');
160
+ const result = await desk.exec({ command: '/bin/sh', args: ['-c', script], timeoutMs: 20_000 });
161
+ if (result.exitCode !== 0)
162
+ throw new Error(`The guest's network could not be configured: ${result.stderr.trim() || 'no reason given'}`);
163
+ },
164
+ };
165
+ }
166
+ export async function syncDeskGuestClock(desk, now = Date.now()) {
167
+ if (!Number.isFinite(now) || now < 0)
168
+ throw new Error('Invalid clock time');
169
+ const result = await desk.exec({ command: '/bin/sh', args: ['-c', `date -u -s @${(now / 1000).toFixed(3)} > /dev/null`], timeoutMs: 5_000 });
170
+ if (result.exitCode !== 0)
171
+ throw new Error(`The guest's clock could not be set: ${result.stderr.trim() || 'no reason given'}`);
172
+ }
173
+ //# sourceMappingURL=network.js.map
package/network.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from 'node:child_process'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { isIP } from 'node:net'\nimport { promisify } from 'node:util'\nimport type { DeskHandle } from './host'\n\nexport interface DeskNetworkCommandRunner {\n run(command: string, args: readonly string[]): Promise<string>\n}\nexport interface DeskNetworkOptions {\n gateway: string\n dns: string\n egress: { mode: 'proxied'; httpProxyPort: number; httpsProxyPort: number } | { mode: 'open' }\n chainPrefix?: string\n allowHostPorts?: readonly number[]\n prefixLength?: number\n runner?: DeskNetworkCommandRunner\n enableForwarding?: () => Promise<void>\n}\nexport interface DeskNetworkAddress { tap: string; guest: string }\nconst execFileAsync = promisify(execFile)\nconst defaultRunner: DeskNetworkCommandRunner = {\n async run(command, args) {\n try { return (await execFileAsync(command, [...args], { timeout: 15_000 })).stdout }\n catch (error) {\n const failure = error as { stderr?: string; message?: string }\n throw new Error(`${command} ${args.join(' ')}: ${(failure.stderr || failure.message || String(error)).trim()}`)\n }\n },\n}\nasync function forwarding(): Promise<void> {\n const path = '/proc/sys/net/ipv4/ip_forward'\n if ((await readFile(path, 'utf8').catch(() => '')).trim() !== '1') await writeFile(path, '1')\n}\nfunction port(value: number): void {\n if (!Number.isInteger(value) || value < 1 || value > 65535) throw new Error('Invalid TCP port')\n}\nfunction address(value: string): void {\n if (isIP(value) !== 4) throw new Error(`Invalid IPv4 address: ${value}`)\n}\n\n/** Deterministic TAP and locally administered MAC for an unsigned desk index. */\nexport function deskNetworkIdentity(index: number): { tap: string; macAddress: string } {\n if (!Number.isInteger(index) || index < 2 || index > 999999999) throw new Error('Invalid desk network index')\n const octets = [6, 0, (index >>> 24) & 255, (index >>> 16) & 255, (index >>> 8) & 255, index & 255]\n return { tap: `dsk${index}`, macAddress: octets.map((octet) => octet.toString(16).padStart(2, '0')).join(':') }\n}\n\ntype Rule = readonly string[]\ninterface Chain { table: 'nat' | 'filter'; parent: string; chain: string; jumps: Rule[]; rules: Rule[]; command?: 'ip6tables' }\n/** Point-to-point TAP with either open or transparent-proxy egress. */\nexport function createDeskNetwork(options: DeskNetworkOptions) {\n address(options.gateway); address(options.dns)\n const egress = options.egress\n if (egress.mode === 'proxied') { port(egress.httpProxyPort); port(egress.httpsProxyPort) }\n else if (egress.mode !== 'open') throw new Error('Invalid egress mode')\n const chainPrefix = options.chainPrefix ?? 'desk'\n if (!/^[A-Za-z0-9_-]+$/.test(chainPrefix)) throw new Error('Invalid iptables chain prefix')\n const hostPorts = [...(options.allowHostPorts ?? [])]\n hostPorts.forEach(port)\n const prefix = options.prefixLength ?? 24\n if (!Number.isInteger(prefix) || prefix < 1 || prefix > 32) throw new Error('Invalid guest prefix length')\n const runner = options.runner ?? defaultRunner\n const enableForwarding = options.enableForwarding ?? forwarding\n const work = new Map<string, Promise<void>>()\n const run = (command: string, args: readonly string[]) => runner.run(command, args)\n const succeeds = async (command: string, args: readonly string[]) => { try { await run(command, args); return true } catch { return false } }\n function validate(value: DeskNetworkAddress) {\n if (!/^dsk[0-9]{1,9}$/.test(value.tap)) throw new Error('Invalid TAP name')\n if (`${chainPrefix}-post-${value.tap}`.length > 28) throw new Error('iptables chain name exceeds 28 characters')\n address(value.guest)\n if (value.guest === options.gateway) throw new Error('Guest and gateway addresses must differ')\n }\n function chains({ tap, guest }: DeskNetworkAddress): Chain[] {\n const { gateway, dns } = options\n const proxyRules: Rule[] = egress.mode === 'proxied' ? [\n ['-p', 'tcp', '--dport', '80', '-j', 'DNAT', '--to-destination', `${gateway}:${egress.httpProxyPort}`],\n ['-p', 'tcp', '--dport', '443', '-j', 'DNAT', '--to-destination', `${gateway}:${egress.httpsProxyPort}`],\n ...(['udp', 'tcp'] as const).map((p) => ['-p', p, '--dport', '53', '-j', 'DNAT', '--to-destination', `${dns}:53`]),\n ] : []\n const proxyPorts: Rule[] = egress.mode === 'proxied'\n ? [egress.httpProxyPort, egress.httpsProxyPort].map((p) => ['-p', 'tcp', '-d', gateway, '--dport', String(p), '-j', 'ACCEPT'])\n : []\n const extraPorts: Rule[] = hostPorts.flatMap((p) => (['tcp', 'udp'] as const).map((protocol) =>\n ['-p', protocol, '-d', gateway, '--dport', String(p), '-j', 'ACCEPT']))\n const forwardRules: Rule[] = egress.mode === 'proxied' ? [\n ['-m', 'conntrack', '--ctstate', 'ESTABLISHED,RELATED', '-j', 'ACCEPT'],\n ...(['udp', 'tcp'] as const).map((p) => ['-i', tap, '-p', p, '-d', dns, '--dport', '53', '-j', 'ACCEPT']),\n ['-j', 'DROP'],\n ] : [\n ['-o', tap, '-d', `${guest}/32`, '-m', 'conntrack', '--ctstate', 'ESTABLISHED,RELATED', '-j', 'ACCEPT'],\n ['-i', tap, '-s', `${guest}/32`, '-j', 'ACCEPT'],\n ['-j', 'DROP'],\n ]\n return [\n { table: 'nat', parent: 'PREROUTING', chain: `${chainPrefix}-pre-${tap}`, jumps: [['-i', tap]], rules: proxyRules },\n { table: 'nat', parent: 'POSTROUTING', chain: `${chainPrefix}-post-${tap}`, jumps: [['-s', `${guest}/32`]], rules: [['-j', 'MASQUERADE']] },\n { table: 'filter', parent: 'INPUT', chain: `${chainPrefix}-in-${tap}`, jumps: [['-i', tap]], rules: [\n ['-m', 'conntrack', '--ctstate', 'ESTABLISHED,RELATED', '-j', 'ACCEPT'],\n ...proxyPorts, ...extraPorts, ['-j', 'DROP'],\n ] },\n { table: 'filter', parent: 'FORWARD', chain: `${chainPrefix}-fwd-${tap}`, jumps: [['-i', tap], ['-o', tap]], rules: forwardRules },\n ...(egress.mode === 'open' ? [\n { command: 'ip6tables' as const, table: 'filter' as const, parent: 'INPUT', chain: `${chainPrefix}-6in-${tap}`, jumps: [['-i', tap]], rules: [['-j', 'DROP']] },\n { command: 'ip6tables' as const, table: 'filter' as const, parent: 'FORWARD', chain: `${chainPrefix}-6fwd-${tap}`, jumps: [['-i', tap], ['-o', tap]], rules: [['-j', 'DROP']] },\n ] : []),\n ]\n }\n\n function serialize(tap: string, fn: () => Promise<void>): Promise<void> {\n const next = (work.get(tap) ?? Promise.resolve()).then(fn)\n const settled = next.then(() => undefined, () => undefined)\n work.set(tap, settled)\n void settled.then(() => { if (work.get(tap) === settled) work.delete(tap) })\n return next\n }\n return {\n ensure(value: DeskNetworkAddress): Promise<void> {\n validate(value)\n return serialize(value.tap, async () => {\n const { tap, guest } = value\n if (!(await succeeds('ip', ['link', 'show', 'dev', tap]))) await run('ip', ['tuntap', 'add', 'dev', tap, 'mode', 'tap'])\n await run('ip', ['addr', 'replace', `${options.gateway}/32`, 'dev', tap])\n await run('ip', ['link', 'set', 'dev', tap, 'up'])\n await run('ip', ['route', 'replace', `${guest}/32`, 'dev', tap])\n await enableForwarding()\n for (const { table, parent, chain, jumps, rules, command = 'iptables' } of chains(value)) {\n await succeeds(command, ['-t', table, '-N', chain])\n await run(command, ['-t', table, '-F', chain])\n for (const rule of rules) await run(command, ['-t', table, '-A', chain, ...rule])\n for (const jump of jumps) {\n const target = [...jump, '-j', chain]\n if (!(await succeeds(command, ['-t', table, '-C', parent, ...target]))) await run(command, ['-t', table, '-I', parent, '1', ...target])\n }\n }\n })\n },\n teardown(value: DeskNetworkAddress): Promise<void> {\n validate(value)\n return serialize(value.tap, async () => {\n for (const { table, parent, chain, jumps, command = 'iptables' } of chains(value)) {\n for (const jump of jumps) while (await succeeds(command, ['-t', table, '-C', parent, ...jump, '-j', chain])) {\n if (!(await succeeds(command, ['-t', table, '-D', parent, ...jump, '-j', chain]))) break\n }\n await succeeds(command, ['-t', table, '-F', chain]); await succeeds(command, ['-t', table, '-X', chain])\n }\n await succeeds('ip', ['link', 'del', 'dev', value.tap])\n })\n },\n async configureGuest(desk: Pick<DeskHandle, 'exec'>, value: DeskNetworkAddress): Promise<void> {\n validate(value)\n const script = ['set -e', 'dev=$(ls /sys/class/net | grep -E \"^(en|eth)\" | head -n 1)', 'test -n \"$dev\"', `ip addr replace ${value.guest}/${prefix} dev \"$dev\"`, 'ip link set dev \"$dev\" up', `ip route replace default via ${options.gateway} dev \"$dev\"`, 'rm -f /etc/resolv.conf', `printf 'nameserver %s\\\\n' ${options.dns} > /etc/resolv.conf`].join('\\n')\n const result = await desk.exec({ command: '/bin/sh', args: ['-c', script], timeoutMs: 20_000 })\n if (result.exitCode !== 0) throw new Error(`The guest's network could not be configured: ${result.stderr.trim() || 'no reason given'}`)\n },\n }\n}\nexport async function syncDeskGuestClock(desk: Pick<DeskHandle, 'exec'>, now = Date.now()): Promise<void> {\n if (!Number.isFinite(now) || now < 0) throw new Error('Invalid clock time')\n const result = await desk.exec({ command: '/bin/sh', args: ['-c', `date -u -s @${(now / 1000).toFixed(3)} > /dev/null`], timeoutMs: 5_000 })\n if (result.exitCode !== 0) throw new Error(`The guest's clock could not be set: ${result.stderr.trim() || 'no reason given'}`)\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@braedonsaunders/appkit-desk",
3
- "version": "0.6.0",
4
- "description": "Per-agent Debian microVMs under Cloud Hypervisor — headless machines with an on-demand screen, a framed vsock guest-agent protocol, leases with idle suspend and a bounded queue, and load-bearing handover masking behind consumer-supplied policy and recording ports.",
3
+ "version": "0.8.0",
4
+ "description": "Per-agent Debian microVMs on Linux and macOS, with a framed guest-agent protocol, leases, snapshots, and governed networking.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "type": "module",
7
7
  "exports": {
package/plan.d.ts CHANGED
@@ -5,6 +5,17 @@ export declare const DEFAULT_RUNTIME_DIR = "/run/appkit-desk";
5
5
  export declare const DEFAULT_MEMORY_MB = 384;
6
6
  export declare const DEFAULT_VCPUS = 2;
7
7
  export declare const DEFAULT_KERNEL_CMDLINE = "root=/dev/vda rw quiet";
8
+ /**
9
+ * The guest network Virtualization.framework desks get from the helper's NAT:
10
+ * the same addresses QEMU's user networking uses, so they are unsurprising in
11
+ * a guest. Nothing outside the helper ever sees them.
12
+ */
13
+ export declare const DEFAULT_VZ_GUEST_ADDRESS = "10.0.2.15";
14
+ export declare const DEFAULT_VZ_GATEWAY_ADDRESS = "10.0.2.2";
15
+ export declare const DEFAULT_VZ_PREFIX_LENGTH = 24;
16
+ export declare const DEFAULT_VZ_DNS: readonly string[];
17
+ /** Virtualization.framework always gives the guest vsock context ID 3. */
18
+ export declare const VZ_GUEST_CID = 3;
8
19
  /**
9
20
  * Every failure this package raises. The `name` is annotated as `string`
10
21
  * rather than left to infer its literal so a subclass can narrow the meaning
@@ -20,9 +31,35 @@ export interface DeskLauncherIdentity {
20
31
  /** Host/container GID used to invoke the VMM and image tooling. */
21
32
  gid: number;
22
33
  }
34
+ /**
35
+ * Which hypervisor a plan is for: Cloud Hypervisor over KVM on Linux, or
36
+ * Apple's Virtualization.framework through the `appkit-desk-vz` helper on an
37
+ * Apple silicon Mac.
38
+ */
39
+ export type DeskHypervisor = 'cloud-hypervisor' | 'virtualization';
40
+ /**
41
+ * The guest network of a Virtualization.framework desk. The helper runs a
42
+ * userspace NAT: the guest reaches the public internet and nothing else unless
43
+ * `allowLan` is set — no RFC 1918 space, link-local, CGNAT, or the Mac itself.
44
+ */
45
+ export interface DeskGuestNetworkOptions {
46
+ /** Let the guest reach the Mac's LAN, link-local and CGNAT space, and the Mac. */
47
+ allowLan?: boolean;
48
+ /** The address the helper's DHCP server leases the guest. */
49
+ guestAddress?: string;
50
+ /** The helper's own address on the link: the guest's router. */
51
+ gatewayAddress?: string;
52
+ prefixLength?: number;
53
+ /** Resolvers the guest is handed; the first also answers DNS sent to the gateway. */
54
+ dns?: readonly string[];
55
+ }
23
56
  export interface DeskLaunchPlanOptions {
24
57
  deskId: string;
58
+ /** Defaults to `cloud-hypervisor`. */
59
+ hypervisor?: DeskHypervisor;
60
+ /** Cloud Hypervisor only: the VMM binary. */
25
61
  vmmPath?: string;
62
+ /** Cloud Hypervisor only: the KVM device. */
26
63
  kvmPath?: string;
27
64
  qemuImgPath?: string;
28
65
  /** Absolute path of the kernel image the microVM boots. */
@@ -40,14 +77,27 @@ export interface DeskLaunchPlanOptions {
40
77
  overlayPath: string;
41
78
  memoryMb?: number;
42
79
  vcpus?: number;
43
- /** Guest vsock context ID; must be unique among resident desks. */
80
+ /**
81
+ * Guest vsock context ID; must be unique among resident desks. Cloud
82
+ * Hypervisor only — a Virtualization.framework guest is always CID 3, and
83
+ * its vsock is private to its helper.
84
+ */
44
85
  vsockCid: number;
45
- /** Directory holding the vsock and API control sockets. */
86
+ /**
87
+ * Directory holding the desk's control sockets. Defaults to
88
+ * `/run/appkit-desk` for Cloud Hypervisor and `<os tmpdir>/appkit-desk` for
89
+ * Virtualization.framework.
90
+ */
46
91
  runtimeDir?: string;
47
- /** Host TAP device name; derived from the CID when omitted. */
92
+ /** Cloud Hypervisor only: host TAP device name; derived from the CID when omitted. */
48
93
  tapDevice?: string;
49
- /** Guest MAC address; derived from the CID when omitted. */
94
+ /**
95
+ * Guest MAC address. Derived from the CID for Cloud Hypervisor, and from the
96
+ * desk ID for Virtualization.framework, whose restore needs the same NIC.
97
+ */
50
98
  macAddress?: string;
99
+ /** Virtualization.framework only: the helper's NAT network and fence. */
100
+ guestNetwork?: DeskGuestNetworkOptions;
51
101
  kernelCmdline?: string;
52
102
  launcherIdentity?: DeskLauncherIdentity;
53
103
  /**
@@ -67,17 +117,9 @@ export interface DeskLaunchPlanOptions {
67
117
  */
68
118
  restore?: boolean;
69
119
  }
70
- /**
71
- * The complete Cloud Hypervisor invocation as inspectable data. Nothing here
72
- * has been spawned: tests, audit logs, and readiness screens can assert on the
73
- * exact argv a desk would boot with.
74
- */
75
- export interface DeskLaunchPlan {
120
+ /** What every plan carries, whichever hypervisor runs it. */
121
+ export interface DeskLaunchPlanBase {
76
122
  deskId: string;
77
- vmm: {
78
- command: string;
79
- args: string[];
80
- };
81
123
  overlay: {
82
124
  path: string;
83
125
  basePath: string;
@@ -87,17 +129,15 @@ export interface DeskLaunchPlan {
87
129
  args: string[];
88
130
  } | null;
89
131
  };
132
+ /**
133
+ * The host Unix socket the guest's vsock is reached through, with Cloud
134
+ * Hypervisor's hybrid-vsock preamble: write `CONNECT <port>\n`, read
135
+ * `OK <n>\n`, then the stream is the guest connection.
136
+ */
90
137
  vsock: {
91
138
  cid: number;
92
139
  socketPath: string;
93
140
  };
94
- api: {
95
- socketPath: string;
96
- };
97
- tap: {
98
- device: string;
99
- mac: string;
100
- };
101
141
  /** Where a memory snapshot of this desk is written, and read back from. */
102
142
  snapshot: {
103
143
  dir: string;
@@ -112,27 +152,85 @@ export interface DeskLaunchPlan {
112
152
  launcherIdentity?: DeskLauncherIdentity;
113
153
  }
114
154
  /**
115
- * Whether this host can run desks at all: Linux, a usable `/dev/kvm`, and the
116
- * Cloud Hypervisor binary. All three checks are injectable and the answer is
117
- * fail-closed — a host missing any of them gets no desk capability rather
118
- * than a degraded one.
155
+ * The complete Cloud Hypervisor invocation as inspectable data. Nothing here
156
+ * has been spawned: tests, audit logs, and readiness screens can assert on the
157
+ * exact argv a desk would boot with.
158
+ */
159
+ export interface CloudHypervisorLaunchPlan extends DeskLaunchPlanBase {
160
+ hypervisor: 'cloud-hypervisor';
161
+ vmm: {
162
+ command: string;
163
+ args: string[];
164
+ };
165
+ api: {
166
+ socketPath: string;
167
+ };
168
+ tap: {
169
+ device: string;
170
+ mac: string;
171
+ };
172
+ }
173
+ /**
174
+ * A Virtualization.framework desk as inspectable data: what the helper is
175
+ * asked to boot. The helper path belongs to the backend, not the plan.
176
+ */
177
+ export interface VirtualizationLaunchPlan extends DeskLaunchPlanBase {
178
+ hypervisor: 'virtualization';
179
+ /**
180
+ * The host Unix socket guest TCP ports are reached through, with the same
181
+ * preamble as `vsock`: `CONNECT <tcp port>\n` answered by `OK <n>\n` once
182
+ * the guest accepts, or a close with nothing written when it refuses.
183
+ */
184
+ ports: {
185
+ socketPath: string;
186
+ };
187
+ network: {
188
+ mac: string;
189
+ allowLan: boolean;
190
+ guestAddress: string;
191
+ gatewayAddress: string;
192
+ prefixLength: number;
193
+ dns: string[];
194
+ };
195
+ }
196
+ export type DeskLaunchPlan = CloudHypervisorLaunchPlan | VirtualizationLaunchPlan;
197
+ /**
198
+ * Whether this host can run desks at all. On Linux: a usable `/dev/kvm` and
199
+ * the Cloud Hypervisor binary. On macOS: Apple silicon, macOS 14 or later (the
200
+ * first with Virtualization.framework save and restore), and an existing
201
+ * `appkit-desk-vz` helper at `helperPath`. Signature and entitlement are checked
202
+ * by macOS on execution, not by this probe. Every check is injectable and the
203
+ * answer is fail-closed — a host missing any of them gets no desk capability
204
+ * rather than a degraded one.
119
205
  */
120
206
  export declare function isDeskSupported(options?: {
121
207
  platform?: NodeJS.Platform;
122
208
  kvmPath?: string;
123
209
  vmmPath?: string;
210
+ /** macOS: the signed `appkit-desk-vz` helper. Required there. */
211
+ helperPath?: string;
212
+ arch?: string;
213
+ /** The kernel release, as `os.release()` reports it. */
214
+ osRelease?: string;
124
215
  pathExists?: (path: string) => boolean;
125
216
  }): boolean;
126
217
  /** Validate a desk identifier, which is used in socket paths and device names. */
127
218
  export declare function cleanDeskId(value: string): string;
219
+ export interface DeskLaunchPlanDependencies {
220
+ pathExists?: (path: string) => boolean;
221
+ deviceExists?: (path: string) => boolean;
222
+ }
128
223
  /**
129
- * Build the deterministic Cloud Hypervisor launch plan without spawning
130
- * anything. Fail closed: a missing `/dev/kvm`, VMM binary, kernel, base image,
224
+ * Build the deterministic launch plan without spawning anything. Fail closed:
225
+ * a missing `/dev/kvm` or VMM binary (Cloud Hypervisor), kernel, base image,
131
226
  * or overlay directory throws instead of producing a plan that cannot boot.
132
227
  */
133
- export declare function buildDeskLaunchPlan(options: DeskLaunchPlanOptions, dependencies?: {
134
- pathExists?: (path: string) => boolean;
135
- deviceExists?: (path: string) => boolean;
136
- }): DeskLaunchPlan;
228
+ export declare function buildDeskLaunchPlan(options: DeskLaunchPlanOptions & {
229
+ hypervisor: 'virtualization';
230
+ }, dependencies?: DeskLaunchPlanDependencies): VirtualizationLaunchPlan;
231
+ export declare function buildDeskLaunchPlan(options: DeskLaunchPlanOptions & {
232
+ hypervisor?: 'cloud-hypervisor';
233
+ }, dependencies?: DeskLaunchPlanDependencies): CloudHypervisorLaunchPlan;
234
+ export declare function buildDeskLaunchPlan(options: DeskLaunchPlanOptions, dependencies?: DeskLaunchPlanDependencies): DeskLaunchPlan;
137
235
  export declare function cleanLauncherIdentity(identity: DeskLauncherIdentity | undefined): DeskLauncherIdentity | undefined;
138
236
  //# sourceMappingURL=plan.d.ts.map
package/plan.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}