@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.
- package/README.md +83 -1
- package/backend.d.ts +28 -7
- package/backend.d.ts.map +1 -1
- package/backend.js +18 -521
- package/backend.js.map +1 -1
- package/events.d.ts +3 -0
- package/events.d.ts.map +1 -1
- package/events.js.map +1 -1
- package/host.d.ts +32 -1
- package/host.d.ts.map +1 -1
- package/host.js +141 -23
- package/host.js.map +1 -1
- package/index.d.ts +3 -0
- package/index.d.ts.map +1 -1
- package/index.js +3 -0
- package/index.js.map +1 -1
- package/machine.d.ts +145 -0
- package/machine.d.ts.map +1 -0
- package/machine.js +518 -0
- package/machine.js.map +1 -0
- package/network.d.ts +37 -0
- package/network.d.ts.map +1 -0
- package/network.js +173 -0
- package/network.js.map +1 -0
- package/package.json +2 -2
- package/plan.d.ts +129 -31
- package/plan.d.ts.map +1 -1
- package/plan.js +165 -9
- package/plan.js.map +1 -1
- package/port-relay.d.ts +13 -0
- package/port-relay.d.ts.map +1 -0
- package/port-relay.js +56 -0
- package/port-relay.js.map +1 -0
- package/virtualization.d.ts +16 -0
- package/virtualization.d.ts.map +1 -0
- package/virtualization.js +148 -0
- package/virtualization.js.map +1 -0
- package/vz/Package.swift +23 -0
- package/vz/Sources/DeskNet/Addresses.swift +106 -0
- package/vz/Sources/DeskNet/DHCP.swift +111 -0
- package/vz/Sources/DeskNet/Fence.swift +57 -0
- package/vz/Sources/DeskNet/NetworkStack.swift +547 -0
- package/vz/Sources/DeskNet/Packets.swift +355 -0
- package/vz/Sources/DeskNet/Plumbing.swift +312 -0
- package/vz/Sources/DeskNet/Sockets.swift +221 -0
- package/vz/Sources/DeskNet/TCPFlow.swift +448 -0
- package/vz/Sources/appkit-desk-vz/Control.swift +152 -0
- package/vz/Sources/appkit-desk-vz/Helper.swift +505 -0
- package/vz/Sources/appkit-desk-vz/main.swift +38 -0
- package/vz/Tests/DeskNetTests/DeskNetTests.swift +426 -0
- package/vz/appkit-desk-vz.entitlements +8 -0
- package/vz/build.sh +23 -0
- 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.
|
|
4
|
-
"description": "Per-agent Debian microVMs
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
92
|
+
/** Cloud Hypervisor only: host TAP device name; derived from the CID when omitted. */
|
|
48
93
|
tapDevice?: string;
|
|
49
|
-
/**
|
|
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
|
-
|
|
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
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
|
|
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
|
|
130
|
-
*
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
|