@namzu/sandbox 24.0.0 → 26.0.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 (158) hide show
  1. package/CHANGELOG.md +284 -0
  2. package/dist/backends/http-worker-client.d.ts +3 -1
  3. package/dist/backends/http-worker-client.d.ts.map +1 -1
  4. package/dist/backends/http-worker-client.js +15 -4
  5. package/dist/backends/http-worker-client.js.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/local-virtual-computer/client.d.ts +21 -0
  11. package/dist/local-virtual-computer/client.d.ts.map +1 -0
  12. package/dist/local-virtual-computer/client.js +503 -0
  13. package/dist/local-virtual-computer/client.js.map +1 -0
  14. package/dist/local-virtual-computer/detached-process.d.ts +15 -0
  15. package/dist/local-virtual-computer/detached-process.d.ts.map +1 -0
  16. package/dist/local-virtual-computer/detached-process.js +111 -0
  17. package/dist/local-virtual-computer/detached-process.js.map +1 -0
  18. package/dist/local-virtual-computer/detached-worker.d.ts +11 -0
  19. package/dist/local-virtual-computer/detached-worker.d.ts.map +1 -0
  20. package/dist/local-virtual-computer/detached-worker.js +97 -0
  21. package/dist/local-virtual-computer/detached-worker.js.map +1 -0
  22. package/dist/local-virtual-computer/dialect.d.ts +26 -0
  23. package/dist/local-virtual-computer/dialect.d.ts.map +1 -0
  24. package/dist/local-virtual-computer/dialect.js +61 -0
  25. package/dist/local-virtual-computer/dialect.js.map +1 -0
  26. package/dist/local-virtual-computer/engine.d.ts +63 -0
  27. package/dist/local-virtual-computer/engine.d.ts.map +1 -0
  28. package/dist/local-virtual-computer/engine.js +125 -0
  29. package/dist/local-virtual-computer/engine.js.map +1 -0
  30. package/dist/local-virtual-computer/guest-stdio.d.ts +15 -0
  31. package/dist/local-virtual-computer/guest-stdio.d.ts.map +1 -0
  32. package/dist/local-virtual-computer/guest-stdio.js +330 -0
  33. package/dist/local-virtual-computer/guest-stdio.js.map +1 -0
  34. package/dist/local-virtual-computer/index.d.ts +36 -0
  35. package/dist/local-virtual-computer/index.d.ts.map +1 -0
  36. package/dist/local-virtual-computer/index.js +341 -0
  37. package/dist/local-virtual-computer/index.js.map +1 -0
  38. package/dist/local-virtual-computer/podman-machine.d.ts +17 -0
  39. package/dist/local-virtual-computer/podman-machine.d.ts.map +1 -0
  40. package/dist/local-virtual-computer/podman-machine.js +181 -0
  41. package/dist/local-virtual-computer/podman-machine.js.map +1 -0
  42. package/dist/local-virtual-computer/setup/contract.d.ts +360 -0
  43. package/dist/local-virtual-computer/setup/contract.d.ts.map +1 -0
  44. package/dist/local-virtual-computer/setup/contract.js +3 -0
  45. package/dist/local-virtual-computer/setup/contract.js.map +1 -0
  46. package/dist/local-virtual-computer/setup/elevated.d.ts +78 -0
  47. package/dist/local-virtual-computer/setup/elevated.d.ts.map +1 -0
  48. package/dist/local-virtual-computer/setup/elevated.js +548 -0
  49. package/dist/local-virtual-computer/setup/elevated.js.map +1 -0
  50. package/dist/local-virtual-computer/setup/host.d.ts +12 -0
  51. package/dist/local-virtual-computer/setup/host.d.ts.map +1 -0
  52. package/dist/local-virtual-computer/setup/host.js +93 -0
  53. package/dist/local-virtual-computer/setup/host.js.map +1 -0
  54. package/dist/local-virtual-computer/setup/idle.d.ts +18 -0
  55. package/dist/local-virtual-computer/setup/idle.d.ts.map +1 -0
  56. package/dist/local-virtual-computer/setup/idle.js +38 -0
  57. package/dist/local-virtual-computer/setup/idle.js.map +1 -0
  58. package/dist/local-virtual-computer/setup/image.d.ts +7 -0
  59. package/dist/local-virtual-computer/setup/image.d.ts.map +1 -0
  60. package/dist/local-virtual-computer/setup/image.js +124 -0
  61. package/dist/local-virtual-computer/setup/image.js.map +1 -0
  62. package/dist/local-virtual-computer/setup/index.d.ts +17 -0
  63. package/dist/local-virtual-computer/setup/index.d.ts.map +1 -0
  64. package/dist/local-virtual-computer/setup/index.js +17 -0
  65. package/dist/local-virtual-computer/setup/index.js.map +1 -0
  66. package/dist/local-virtual-computer/setup/limits.d.ts +30 -0
  67. package/dist/local-virtual-computer/setup/limits.d.ts.map +1 -0
  68. package/dist/local-virtual-computer/setup/limits.js +58 -0
  69. package/dist/local-virtual-computer/setup/limits.js.map +1 -0
  70. package/dist/local-virtual-computer/setup/plan.d.ts +8 -0
  71. package/dist/local-virtual-computer/setup/plan.d.ts.map +1 -0
  72. package/dist/local-virtual-computer/setup/plan.js +131 -0
  73. package/dist/local-virtual-computer/setup/plan.js.map +1 -0
  74. package/dist/local-virtual-computer/setup/readiness.d.ts +11 -0
  75. package/dist/local-virtual-computer/setup/readiness.d.ts.map +1 -0
  76. package/dist/local-virtual-computer/setup/readiness.js +320 -0
  77. package/dist/local-virtual-computer/setup/readiness.js.map +1 -0
  78. package/dist/local-virtual-computer/setup/registry.d.ts +18 -0
  79. package/dist/local-virtual-computer/setup/registry.d.ts.map +1 -0
  80. package/dist/local-virtual-computer/setup/registry.js +50 -0
  81. package/dist/local-virtual-computer/setup/registry.js.map +1 -0
  82. package/dist/local-virtual-computer/setup/restart.d.ts +8 -0
  83. package/dist/local-virtual-computer/setup/restart.d.ts.map +1 -0
  84. package/dist/local-virtual-computer/setup/restart.js +60 -0
  85. package/dist/local-virtual-computer/setup/restart.js.map +1 -0
  86. package/dist/local-virtual-computer/setup/resume.d.ts +14 -0
  87. package/dist/local-virtual-computer/setup/resume.d.ts.map +1 -0
  88. package/dist/local-virtual-computer/setup/resume.js +58 -0
  89. package/dist/local-virtual-computer/setup/resume.js.map +1 -0
  90. package/dist/local-virtual-computer/setup/run.d.ts +8 -0
  91. package/dist/local-virtual-computer/setup/run.d.ts.map +1 -0
  92. package/dist/local-virtual-computer/setup/run.js +200 -0
  93. package/dist/local-virtual-computer/setup/run.js.map +1 -0
  94. package/dist/local-virtual-computer/setup/windows-folder.d.ts +11 -0
  95. package/dist/local-virtual-computer/setup/windows-folder.d.ts.map +1 -0
  96. package/dist/local-virtual-computer/setup/windows-folder.js +14 -0
  97. package/dist/local-virtual-computer/setup/windows-folder.js.map +1 -0
  98. package/dist/local-virtual-computer/setup/wsl-package.d.ts +19 -0
  99. package/dist/local-virtual-computer/setup/wsl-package.d.ts.map +1 -0
  100. package/dist/local-virtual-computer/setup/wsl-package.js +242 -0
  101. package/dist/local-virtual-computer/setup/wsl-package.js.map +1 -0
  102. package/dist/local-virtual-computer/setup/wslc-shared.d.ts +27 -0
  103. package/dist/local-virtual-computer/setup/wslc-shared.d.ts.map +1 -0
  104. package/dist/local-virtual-computer/setup/wslc-shared.js +137 -0
  105. package/dist/local-virtual-computer/setup/wslc-shared.js.map +1 -0
  106. package/dist/local-virtual-computer/ssh-forward.d.ts +19 -0
  107. package/dist/local-virtual-computer/ssh-forward.d.ts.map +1 -0
  108. package/dist/local-virtual-computer/ssh-forward.js +179 -0
  109. package/dist/local-virtual-computer/ssh-forward.js.map +1 -0
  110. package/dist/local-virtual-computer/wslc-engine.d.ts +27 -0
  111. package/dist/local-virtual-computer/wslc-engine.d.ts.map +1 -0
  112. package/dist/local-virtual-computer/wslc-engine.js +98 -0
  113. package/dist/local-virtual-computer/wslc-engine.js.map +1 -0
  114. package/local-computer/Dockerfile +99 -0
  115. package/local-computer/app-catalog.cjs +240 -0
  116. package/local-computer/app-native-host.cjs +144 -0
  117. package/local-computer/browser-home.cjs +249 -0
  118. package/local-computer/build-image.mjs +310 -0
  119. package/local-computer/desktop-worker.cjs +477 -0
  120. package/local-computer/entrypoint.sh +161 -0
  121. package/local-computer/home.html +28 -0
  122. package/local-computer/new-tab/home.css +255 -0
  123. package/local-computer/new-tab/home.js +65 -0
  124. package/local-computer/new-tab/manifest.json +12 -0
  125. package/local-computer/new-tab/wordmark.svg +33 -0
  126. package/local-computer/openbox-rc.xml +41 -0
  127. package/local-computer/prepare-home.cjs +121 -0
  128. package/local-computer/tint2rc +44 -0
  129. package/package.json +13 -4
  130. package/src/backends/http-worker-client.ts +31 -3
  131. package/src/index.ts +13 -0
  132. package/src/local-virtual-computer/client.ts +573 -0
  133. package/src/local-virtual-computer/detached-process.ts +133 -0
  134. package/src/local-virtual-computer/detached-worker.ts +124 -0
  135. package/src/local-virtual-computer/dialect.ts +83 -0
  136. package/src/local-virtual-computer/engine.ts +197 -0
  137. package/src/local-virtual-computer/guest-stdio.ts +332 -0
  138. package/src/local-virtual-computer/index.ts +447 -0
  139. package/src/local-virtual-computer/podman-machine.ts +264 -0
  140. package/src/local-virtual-computer/setup/contract.ts +395 -0
  141. package/src/local-virtual-computer/setup/elevated.ts +655 -0
  142. package/src/local-virtual-computer/setup/host.ts +100 -0
  143. package/src/local-virtual-computer/setup/idle.ts +39 -0
  144. package/src/local-virtual-computer/setup/image.ts +159 -0
  145. package/src/local-virtual-computer/setup/index.ts +16 -0
  146. package/src/local-virtual-computer/setup/limits.ts +68 -0
  147. package/src/local-virtual-computer/setup/plan.ts +144 -0
  148. package/src/local-virtual-computer/setup/readiness.ts +386 -0
  149. package/src/local-virtual-computer/setup/registry.ts +50 -0
  150. package/src/local-virtual-computer/setup/restart.ts +64 -0
  151. package/src/local-virtual-computer/setup/resume.ts +65 -0
  152. package/src/local-virtual-computer/setup/run.ts +225 -0
  153. package/src/local-virtual-computer/setup/windows-folder.ts +13 -0
  154. package/src/local-virtual-computer/setup/wsl-package.ts +242 -0
  155. package/src/local-virtual-computer/setup/wslc-shared.ts +147 -0
  156. package/src/local-virtual-computer/ssh-forward.ts +195 -0
  157. package/src/local-virtual-computer/wslc-engine.ts +124 -0
  158. package/worker/server.js +1499 -0
@@ -0,0 +1,137 @@
1
+ import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, win32 } from 'node:path';
3
+ /** The label every Pal computer image carries; it names the guest protocol. */
4
+ export const PROTOCOL_LABEL = 'org.namzu.local-computer.protocol';
5
+ /** Where the WSL package puts wslc. */
6
+ export function wslcBinaryOf(host) {
7
+ // A 32-bit process sees its own Program Files; WSL installs into the 64-bit one.
8
+ const programFiles = host.env.ProgramW6432 ?? host.env.ProgramFiles ?? 'C:\\Program Files';
9
+ return win32.join(programFiles, 'WSL', 'wslc.exe');
10
+ }
11
+ /** Why a manifest was refused, in words safe to show. */
12
+ export class ManifestError extends Error {
13
+ name = 'ManifestError';
14
+ }
15
+ const FORMATS = ['zst', 'xz', 'gz', 'tar'];
16
+ function fail(why) {
17
+ throw new ManifestError(`The Pal computer image manifest is not valid: ${why}.`);
18
+ }
19
+ /** Validate a parsed manifest. Throws {@link ManifestError}. */
20
+ export function validateManifest(value) {
21
+ if (typeof value !== 'object' || value === null)
22
+ fail('it is not an object');
23
+ const m = value;
24
+ const text = (key) => {
25
+ const v = m[key];
26
+ if (typeof v !== 'string' || v === '')
27
+ fail(`${key} is missing`);
28
+ return v;
29
+ };
30
+ const whole = (key) => {
31
+ const v = m[key];
32
+ if (typeof v !== 'number' || !Number.isInteger(v) || v <= 0)
33
+ fail(`${key} is missing`);
34
+ return v;
35
+ };
36
+ const revision = whole('revision');
37
+ const imageRef = text('imageRef');
38
+ if (imageRef !== `namzu-local-computer:${revision}`)
39
+ fail('imageRef does not match the revision');
40
+ const imageId = text('imageId');
41
+ if (!/^sha256:[0-9a-f]{64}$/.test(imageId))
42
+ fail('imageId is not a digest');
43
+ const file = text('file');
44
+ if (/[\\/]/.test(file) || file === '.' || file === '..' || file.includes('\0'))
45
+ fail('file must be a plain file name');
46
+ const sha256 = text('sha256');
47
+ const tarSha256 = text('tarSha256');
48
+ for (const digest of [sha256, tarSha256])
49
+ if (!/^[0-9a-f]{64}$/.test(digest))
50
+ fail('a digest is not 64 hex characters');
51
+ const stage = m.stage;
52
+ if (stage !== 'base' && stage !== 'creative')
53
+ fail('stage is missing');
54
+ const format = m.format;
55
+ if (typeof format !== 'string' || !FORMATS.includes(format))
56
+ fail('format is missing');
57
+ return {
58
+ revision,
59
+ stage,
60
+ imageRef,
61
+ imageId,
62
+ protocol: text('protocol'),
63
+ file,
64
+ sha256,
65
+ tarSha256,
66
+ sizeBytes: whole('sizeBytes'),
67
+ compressedBytes: whole('compressedBytes'),
68
+ format: format,
69
+ };
70
+ }
71
+ export async function readManifest(manifestPath) {
72
+ let text;
73
+ try {
74
+ text = await readFile(manifestPath, 'utf8');
75
+ }
76
+ catch {
77
+ throw new ManifestError('The Pal computer image manifest could not be read.');
78
+ }
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(text);
82
+ }
83
+ catch {
84
+ throw new ManifestError('The Pal computer image manifest is not valid JSON.');
85
+ }
86
+ return validateManifest(parsed);
87
+ }
88
+ /** The archive lies beside its manifest. */
89
+ export function archivePathOf(manifestPath, manifest) {
90
+ return join(dirname(manifestPath), manifest.file);
91
+ }
92
+ /** `[{ Id, Config: { Labels } }]` from `wslc image inspect -f json`. */
93
+ export function parseImageInspect(output) {
94
+ let value;
95
+ try {
96
+ value = JSON.parse(output.replace(/^\uFEFF/, '').trim());
97
+ }
98
+ catch {
99
+ return undefined;
100
+ }
101
+ const first = Array.isArray(value) ? value[0] : value;
102
+ if (typeof first !== 'object' || first === null)
103
+ return undefined;
104
+ const { Id, Config } = first;
105
+ if (typeof Id !== 'string')
106
+ return undefined;
107
+ const label = Config?.Labels?.[PROTOCOL_LABEL];
108
+ return { id: Id, protocol: typeof label === 'string' ? label : undefined };
109
+ }
110
+ function recordPath(host) {
111
+ return join(host.stateDirectory, 'image.json');
112
+ }
113
+ export async function readImageRecord(host) {
114
+ try {
115
+ const value = JSON.parse(await readFile(recordPath(host), 'utf8'));
116
+ if (typeof value.imageRef === 'string' && typeof value.imageId === 'string')
117
+ return value;
118
+ }
119
+ catch {
120
+ // Absent or unreadable: nothing is recorded.
121
+ }
122
+ return undefined;
123
+ }
124
+ export async function writeImageRecord(host, record) {
125
+ await mkdir(host.stateDirectory, { recursive: true });
126
+ const target = recordPath(host);
127
+ const temp = `${target}.${process.pid}.tmp`;
128
+ await writeFile(temp, `${JSON.stringify(record)}\n`, 'utf8');
129
+ try {
130
+ await rename(temp, target);
131
+ }
132
+ catch (error) {
133
+ await rm(temp, { force: true });
134
+ throw error;
135
+ }
136
+ }
137
+ //# sourceMappingURL=wslc-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wslc-shared.js","sourceRoot":"","sources":["../../../src/local-virtual-computer/setup/wslc-shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { LocalPortForward } from './engine.js';
2
+ /** Internal retained authority for retrying an unconfirmed owned startup cleanup. */
3
+ export declare class LocalForwardCleanupError extends Error {
4
+ readonly forward: LocalPortForward;
5
+ constructor(forward: LocalPortForward, cause: unknown);
6
+ }
7
+ interface LocalSshForwardOptions {
8
+ readonly identity: string;
9
+ readonly port: number;
10
+ readonly user: string;
11
+ readonly hostPublicKey: string;
12
+ readonly ports: readonly number[];
13
+ readonly signal?: AbortSignal;
14
+ readonly onLost: () => void;
15
+ }
16
+ /** An owned loopback tunnel to an already verified local WSL machine only. */
17
+ export declare function startLocalSshForward(options: LocalSshForwardOptions): Promise<LocalPortForward>;
18
+ export {};
19
+ //# sourceMappingURL=ssh-forward.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-forward.d.ts","sourceRoot":"","sources":["../../src/local-virtual-computer/ssh-forward.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEnD,qFAAqF;AACrF,qBAAa,wBAAyB,SAAQ,KAAK;IAEjD,QAAQ,CAAC,OAAO,EAAE,gBAAgB;gBAAzB,OAAO,EAAE,gBAAgB,EAClC,KAAK,EAAE,OAAO;CAIf;AAaD,UAAU,sBAAsB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAC3B;AAkBD,8EAA8E;AAC9E,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,sBAAsB,GAC7B,OAAO,CAAC,gBAAgB,CAAC,CAyI3B"}
@@ -0,0 +1,179 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdtemp, rmdir, unlink, writeFile } from 'node:fs/promises';
3
+ import { createServer } from 'node:net';
4
+ import { tmpdir } from 'node:os';
5
+ import { join, win32 } from 'node:path';
6
+ /** Internal retained authority for retrying an unconfirmed owned startup cleanup. */
7
+ export class LocalForwardCleanupError extends Error {
8
+ forward;
9
+ constructor(forward, cause) {
10
+ super('The owned local computer forward requires cleanup recovery', { cause });
11
+ this.forward = forward;
12
+ }
13
+ }
14
+ async function removeIfPresent(path, remove) {
15
+ try {
16
+ await remove(path);
17
+ }
18
+ catch (error) {
19
+ if (error.code !== 'ENOENT')
20
+ throw error;
21
+ }
22
+ }
23
+ async function reservePort() {
24
+ const server = createServer();
25
+ return await new Promise((resolve, reject) => {
26
+ server.once('error', reject);
27
+ server.listen(0, '127.0.0.1', () => {
28
+ const address = server.address();
29
+ server.close((error) => {
30
+ if (error)
31
+ reject(error);
32
+ else if (!address || typeof address === 'string')
33
+ reject(new Error('No local forwarding port'));
34
+ else
35
+ resolve(address.port);
36
+ });
37
+ });
38
+ });
39
+ }
40
+ /** An owned loopback tunnel to an already verified local WSL machine only. */
41
+ export async function startLocalSshForward(options) {
42
+ options.signal?.throwIfAborted();
43
+ if (!/^ssh-ed25519 [A-Za-z0-9+/=]+$/.test(options.hostPublicKey))
44
+ throw new Error('The registered local machine did not provide its SSH host public key');
45
+ const ports = await Promise.all(options.ports.map(() => reservePort()));
46
+ const directory = await mkdtemp(join(tmpdir(), 'namzu-pal-forward-'));
47
+ const knownHosts = join(directory, 'known_hosts');
48
+ await writeFile(knownHosts, `[127.0.0.1]:${options.port} ${options.hostPublicKey}\n`, {
49
+ mode: 0o600,
50
+ flag: 'wx',
51
+ });
52
+ const child = spawn(win32.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'OpenSSH', 'ssh.exe'), [
53
+ '-N',
54
+ '-T',
55
+ '-v',
56
+ '-F',
57
+ 'NUL',
58
+ '-o',
59
+ 'BatchMode=yes',
60
+ '-o',
61
+ 'ExitOnForwardFailure=yes',
62
+ '-o',
63
+ 'StrictHostKeyChecking=yes',
64
+ '-o',
65
+ `UserKnownHostsFile=${knownHosts}`,
66
+ '-o',
67
+ 'HostKeyAlgorithms=ssh-ed25519',
68
+ '-o',
69
+ 'IdentitiesOnly=yes',
70
+ '-o',
71
+ 'ProxyCommand=none',
72
+ '-o',
73
+ 'ProxyJump=none',
74
+ '-i',
75
+ options.identity,
76
+ '-p',
77
+ String(options.port),
78
+ ...ports.flatMap((port, index) => [
79
+ '-L',
80
+ `127.0.0.1:${port}:127.0.0.1:${options.ports[index]}`,
81
+ ]),
82
+ `${options.user}@127.0.0.1`,
83
+ ], { windowsHide: true, stdio: ['ignore', 'ignore', 'pipe'] });
84
+ let ended = false;
85
+ let closing = false;
86
+ let ready = false;
87
+ let cleanup;
88
+ const closed = new Promise((resolve) => {
89
+ child.once('close', () => {
90
+ ended = true;
91
+ if (ready && !closing) {
92
+ try {
93
+ options.onLost();
94
+ }
95
+ catch {
96
+ // A host callback must not crash the process-close event dispatch.
97
+ // The ended handle remains fenced and close() retains recovery.
98
+ }
99
+ }
100
+ resolve();
101
+ });
102
+ });
103
+ const close = () => {
104
+ if (cleanup)
105
+ return cleanup;
106
+ closing = true;
107
+ cleanup = (async () => {
108
+ if (!ended &&
109
+ child.pid !== undefined &&
110
+ child.exitCode === null &&
111
+ child.signalCode === null &&
112
+ !child.kill())
113
+ throw new Error('The owned local computer forwarding process could not be stopped');
114
+ await closed;
115
+ await removeIfPresent(knownHosts, unlink);
116
+ await removeIfPresent(directory, rmdir);
117
+ })().catch((error) => {
118
+ cleanup = undefined;
119
+ throw error;
120
+ });
121
+ return cleanup;
122
+ };
123
+ const forward = {
124
+ ports,
125
+ assertAlive() {
126
+ if (ended || closing)
127
+ throw new Error('The local computer forwarding process has ended');
128
+ },
129
+ close,
130
+ };
131
+ try {
132
+ await new Promise((resolve, reject) => {
133
+ let diagnostic = '';
134
+ const opened = new Set();
135
+ const detach = () => {
136
+ options.signal?.removeEventListener('abort', abort);
137
+ child.stderr.removeListener('data', data);
138
+ child.removeListener('close', fail);
139
+ };
140
+ const abort = () => {
141
+ detach();
142
+ reject(options.signal?.reason);
143
+ };
144
+ const fail = () => {
145
+ detach();
146
+ reject(new Error('The verified local machine SSH forwarding process failed'));
147
+ };
148
+ const data = (chunk) => {
149
+ diagnostic = (diagnostic + chunk.toString('utf8')).slice(-16_384);
150
+ for (const port of ports)
151
+ if (diagnostic.includes(`Local forwarding listening on 127.0.0.1 port ${port}.`))
152
+ opened.add(port);
153
+ if (opened.size === ports.length) {
154
+ ready = true;
155
+ detach();
156
+ resolve();
157
+ }
158
+ };
159
+ child.stderr.on('data', data);
160
+ child.once('error', fail);
161
+ child.once('close', fail);
162
+ options.signal?.addEventListener('abort', abort, { once: true });
163
+ if (options.signal?.aborted)
164
+ abort();
165
+ }).finally(() => child.stderr.resume());
166
+ options.signal?.throwIfAborted();
167
+ return forward;
168
+ }
169
+ catch (error) {
170
+ try {
171
+ await close();
172
+ }
173
+ catch {
174
+ throw new LocalForwardCleanupError(forward, error);
175
+ }
176
+ throw error;
177
+ }
178
+ }
179
+ //# sourceMappingURL=ssh-forward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-forward.js","sourceRoot":"","sources":["../../src/local-virtual-computer/ssh-forward.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { type LocalComputerCommandRunner, type LocalComputerSetupCode, LocalComputerSetupError, type LocalEngine } from './engine.js';
2
+ /** The oldest wslc runtime the provider is verified against. */
3
+ export declare const WSLC_MINIMUM_VERSION: readonly [2, 9, 3];
4
+ export interface LocalWslcEngineOptions {
5
+ /** Path to wslc.exe. Defaults to the WSL install directory, then PATH. */
6
+ readonly wslcBinary?: string;
7
+ readonly runner?: LocalComputerCommandRunner;
8
+ }
9
+ export declare function wslcSetupError(code: LocalComputerSetupCode, cause?: unknown): LocalComputerSetupError;
10
+ /**
11
+ * Turn the machine-readable codes of a failed wslc command into a typed
12
+ * setup error. Anything unrecognised stays an ordinary failure.
13
+ */
14
+ export declare function mapWslcFailure(error: unknown): LocalComputerSetupError | undefined;
15
+ /** Parse the first line of `wslc version`, for example `wslc 3.0.1.0`. */
16
+ export declare function parseWslcVersion(output: string): [number, number, number] | undefined;
17
+ export interface WslcHost {
18
+ readonly platform?: NodeJS.Platform;
19
+ readonly env?: NodeJS.ProcessEnv;
20
+ readonly exists?: (path: string) => Promise<boolean>;
21
+ }
22
+ /**
23
+ * Find a working wslc and prove its session starts. Never installs or updates
24
+ * anything: setup is a separate, consented operation.
25
+ */
26
+ export declare function resolveLocalWslcEngine(options: LocalWslcEngineOptions, signal?: AbortSignal, host?: WslcHost): Promise<LocalEngine>;
27
+ //# sourceMappingURL=wslc-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wslc-engine.d.ts","sourceRoot":"","sources":["../../src/local-virtual-computer/wslc-engine.ts"],"names":[],"mappings":"AAEA,OAAO,EAEN,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC3B,uBAAuB,EACvB,KAAK,WAAW,EAEhB,MAAM,aAAa,CAAA;AAEpB,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,oBAAqB,CAAA;AAEtD,MAAM,WAAW,sBAAsB;IACtC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,0BAA0B,CAAA;CAC5C;AAaD,wBAAgB,cAAc,CAAC,IAAI,EAAE,sBAAsB,EAAE,KAAK,CAAC,EAAE,OAAO,2BAE3E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,GAAG,SAAS,CAYlF;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAIrF;AAkBD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CACpD;AAcD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC3C,OAAO,EAAE,sBAAsB,EAC/B,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,GAAE,QAAa,GACjB,OAAO,CAAC,WAAW,CAAC,CAqBtB"}
@@ -0,0 +1,98 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { win32 } from 'node:path';
3
+ import { LocalCommandError, LocalComputerSetupError, nodeCommandRunner, } from './engine.js';
4
+ /** The oldest wslc runtime the provider is verified against. */
5
+ export const WSLC_MINIMUM_VERSION = [2, 9, 3];
6
+ const MESSAGES = {
7
+ 'engine-missing': 'Linux container support is not installed on this PC.',
8
+ 'engine-too-old': 'Linux container support on this PC is too old for Namzu.',
9
+ 'virtualization-off': 'This PC has virtualization switched off. Turn it on in the PC startup settings, then try again.',
10
+ 'blocked-by-policy': 'Your organisation does not allow Linux containers on this PC.',
11
+ 'image-missing': 'The Pal computer is not set up on this PC yet. Namzu sets it up; finish the setup from the Pal computer and try again.',
12
+ 'sdk-mismatch': "Namzu needs an update to work with this version of Windows' Linux support.",
13
+ };
14
+ export function wslcSetupError(code, cause) {
15
+ return new LocalComputerSetupError(MESSAGES[code], code, cause ? { cause } : undefined);
16
+ }
17
+ /**
18
+ * Turn the machine-readable codes of a failed wslc command into a typed
19
+ * setup error. Anything unrecognised stays an ordinary failure.
20
+ */
21
+ export function mapWslcFailure(error) {
22
+ if (error instanceof LocalComputerSetupError)
23
+ return undefined;
24
+ const codes = new Set(error instanceof LocalCommandError ? error.errorCodes : []);
25
+ if (codes.has('0x80370102') || codes.has('HCS_E_HYPERV_NOT_INSTALLED'))
26
+ return wslcSetupError('virtualization-off', error);
27
+ if (codes.has('0x8004060C') || codes.has('WSLC_E_CONTAINER_DISABLED'))
28
+ return wslcSetupError('blocked-by-policy', error);
29
+ if (codes.has('0x8004060B') || codes.has('WSLC_E_SDK_UPDATE_NEEDED'))
30
+ return wslcSetupError('sdk-mismatch', error);
31
+ if (codes.has('WSLC_E_IMAGE_NOT_FOUND'))
32
+ return wslcSetupError('image-missing', error);
33
+ if (codes.has('ENOENT'))
34
+ return wslcSetupError('engine-missing', error);
35
+ return undefined;
36
+ }
37
+ /** Parse the first line of `wslc version`, for example `wslc 3.0.1.0`. */
38
+ export function parseWslcVersion(output) {
39
+ const match = /^\s*wslc\s+(\d+)\.(\d+)\.(\d+)/im.exec(output);
40
+ if (!match)
41
+ return undefined;
42
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
43
+ }
44
+ function atLeast(found, floor) {
45
+ for (let i = 0; i < floor.length; i++) {
46
+ if ((found[i] ?? 0) !== floor[i])
47
+ return (found[i] ?? 0) > (floor[i] ?? 0);
48
+ }
49
+ return true;
50
+ }
51
+ async function exists(path) {
52
+ try {
53
+ await access(path);
54
+ return true;
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ }
60
+ async function resolveBinary(options, host) {
61
+ if (options.wslcBinary)
62
+ return options.wslcBinary;
63
+ const platform = host.platform ?? process.platform;
64
+ const env = host.env ?? process.env;
65
+ const programFiles = env.ProgramW6432 ?? env.ProgramFiles;
66
+ if (platform === 'win32' && programFiles) {
67
+ const installed = win32.join(programFiles, 'WSL', 'wslc.exe');
68
+ if (await (host.exists ?? exists)(installed))
69
+ return installed;
70
+ }
71
+ return 'wslc';
72
+ }
73
+ /**
74
+ * Find a working wslc and prove its session starts. Never installs or updates
75
+ * anything: setup is a separate, consented operation.
76
+ */
77
+ export async function resolveLocalWslcEngine(options, signal, host = {}) {
78
+ const binary = await resolveBinary(options, host);
79
+ const runner = options.runner ?? nodeCommandRunner;
80
+ const run = async (args, call) => {
81
+ try {
82
+ return await runner.run(binary, args, { ...call, env: { ...process.env, ...call?.env } });
83
+ }
84
+ catch (error) {
85
+ throw mapWslcFailure(error) ?? error;
86
+ }
87
+ };
88
+ const version = parseWslcVersion(await run(['version'], { signal }));
89
+ if (!version)
90
+ throw wslcSetupError('engine-missing');
91
+ if (!atLeast(version, WSLC_MINIMUM_VERSION))
92
+ throw new LocalComputerSetupError(`Linux container support on this PC (${version.join('.')}) is too old; ${WSLC_MINIMUM_VERSION.join('.')} or newer is required.`, 'engine-too-old');
93
+ // The first command that needs the session starts its virtual machine, so
94
+ // this is where "virtualization is off" and "blocked by policy" surface.
95
+ await run(['ps', '-a', '-q'], { signal });
96
+ return { endpoint: 'wslc://local', dialect: 'wslc', run };
97
+ }
98
+ //# sourceMappingURL=wslc-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wslc-engine.js","sourceRoot":"","sources":["../../src/local-virtual-computer/wslc-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ # Build explicitly, one stage at a time (the build context is packages/sandbox):
2
+ # base docker build -f packages/sandbox/local-computer/Dockerfile --target base -t namzu-local-computer:2 packages/sandbox
3
+ # creative docker build -f packages/sandbox/local-computer/Dockerfile --target creative -t namzu-local-computer:2-creative packages/sandbox
4
+ # `node packages/sandbox/local-computer/build-image.mjs` builds, saves and compresses a stage.
5
+ # A Linux guest desktop inside a container; it shares the engine's kernel.
6
+ #
7
+ # base the light Pal computer: desktop, browser, Terminal, Files, Text Editor, guest worker.
8
+ # creative FROM base, adds the heavy creative and engineering applications and their launchers.
9
+
10
+ FROM docker.io/library/node:22-bookworm-slim AS base
11
+
12
+ LABEL org.namzu.local-computer.protocol="1"
13
+ ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 LC_ALL=C.UTF-8
14
+
15
+ RUN apt-get update && apt-get install -y --no-install-recommends \
16
+ xvfb openbox chromium xdotool maim xterm dbus-x11 x11vnc \
17
+ tint2 pcmanfm hsetroot adwaita-icon-theme \
18
+ mousepad libgl1-mesa-dri \
19
+ fonts-dejavu fonts-noto-color-emoji ca-certificates \
20
+ bash curl git ripgrep python3 dumb-init file \
21
+ && rm -rf /var/lib/apt/lists/* \
22
+ && groupadd --gid 1001 namzu \
23
+ && useradd --uid 1001 --gid 1001 --create-home --shell /bin/bash namzu \
24
+ && mkdir -p /home/namzu/workspace /home/namzu/.config/chromium \
25
+ && chown -R 1001:1001 /home/namzu
26
+
27
+ # Authenticated binary WebSocket transport only; VNC input remains disabled.
28
+ RUN npm install --prefix /opt/namzu-computer --omit=dev --ignore-scripts \
29
+ --no-audit --no-fund ws@8.21.3
30
+
31
+ COPY worker/server.js /opt/namzu-computer/execution-worker.cjs
32
+ COPY local-computer/desktop-worker.cjs /opt/namzu-computer/desktop-worker.cjs
33
+ COPY local-computer/entrypoint.sh /opt/namzu-computer/entrypoint.sh
34
+ COPY local-computer/home.html /opt/namzu-computer/new-tab/home.html
35
+ COPY local-computer/new-tab /opt/namzu-computer/new-tab
36
+ COPY local-computer/app-catalog.cjs local-computer/app-native-host.cjs local-computer/prepare-home.cjs local-computer/browser-home.cjs /opt/namzu-computer/
37
+ COPY local-computer/tint2rc /opt/namzu-computer/tint2rc
38
+ COPY local-computer/openbox-rc.xml /opt/namzu-computer/openbox-rc.xml
39
+
40
+ # Catalogue/icon assets come only from installed Debian desktop entries, for the
41
+ # applications this stage installs. The native bridge is accepted solely from the
42
+ # bundled, fixed-id New Tab extension. Setuid and setgid bits are removed: nothing
43
+ # in the guest needs them, the worker runs as an unprivileged user, and wslc does not
44
+ # offer no-new-privileges.
45
+ RUN chmod 0755 /opt/namzu-computer/app-native-host.cjs \
46
+ && node /opt/namzu-computer/prepare-home.cjs --stage base \
47
+ && find / -xdev -type f -perm /6000 -exec chmod a-s {} +
48
+
49
+ ENV HOME=/home/namzu DISPLAY=:99 GTK_THEME=Adwaita:dark \
50
+ NAMZU_SANDBOX_WORKSPACE=/home/namzu/workspace \
51
+ NAMZU_SANDBOX_READ_ROOTS=/home/namzu \
52
+ NAMZU_SANDBOX_WRITE_ROOTS=/home/namzu \
53
+ NAMZU_SANDBOX_IDLE_TIMEOUT_MS=0 \
54
+ NAMZU_SANDBOX_NORMAL_EXIT_POLICY=computer-lifetime \
55
+ NAMZU_SANDBOX_MAX_TIMEOUT_MS=2147000000 \
56
+ NAMZU_SANDBOX_PORT=2024
57
+
58
+ WORKDIR /home/namzu/workspace
59
+ USER 1001:1001
60
+ EXPOSE 2024 2025
61
+ ENTRYPOINT ["/usr/bin/dumb-init", "--single-child", "--", "/bin/sh", "/opt/namzu-computer/entrypoint.sh"]
62
+
63
+ FROM base AS creative
64
+ USER root
65
+
66
+ RUN apt-get update && apt-get install -y --no-install-recommends \
67
+ blender freecad gimp inkscape libreoffice-draw \
68
+ openscad kdenlive frei0r-plugins godot3 aisleriot qgis kicad paraview \
69
+ && rm -rf /var/lib/apt/lists/*
70
+
71
+ # Use checksum-pinned supported application releases rather than Bookworm's
72
+ # Godot 3 and Blender 3 launchers. Retain the distribution tools for explicit
73
+ # legacy-project use; normal launches and the home catalogue use these binaries.
74
+ RUN mkdir -p /opt/namzu-apps \
75
+ && curl --fail --location --retry 3 --output /tmp/godot.zip \
76
+ https://github.com/godotengine/godot-builds/releases/download/4.7.2-stable/Godot_v4.7.2-stable_linux.x86_64.zip \
77
+ && printf '%s %s\n' cadd3204e728a35d3f13adb7fd0d7902636b79f6b95c40c265eb73b6c35329e4 /tmp/godot.zip | sha256sum --check --strict \
78
+ && python3 -c "import zipfile; zipfile.ZipFile('/tmp/godot.zip').extractall('/opt/namzu-apps/godot-4.7.2')" \
79
+ && chmod 0755 /opt/namzu-apps/godot-4.7.2/Godot_v4.7.2-stable_linux.x86_64 \
80
+ && ln -s /opt/namzu-apps/godot-4.7.2/Godot_v4.7.2-stable_linux.x86_64 /usr/local/bin/godot \
81
+ && ln -s /opt/namzu-apps/godot-4.7.2/Godot_v4.7.2-stable_linux.x86_64 /usr/local/bin/godot4 \
82
+ && curl --fail --location --retry 3 --output /tmp/blender.tar.xz \
83
+ https://download.blender.org/release/Blender5.2/blender-5.2.2-linux-x64.tar.xz \
84
+ && printf '%s %s\n' 84098912789dc450e95697c4184fb8a90acbe5111c2ba4aede3fecb57806a168 /tmp/blender.tar.xz | sha256sum --check --strict \
85
+ && python3 -c "import tarfile; tarfile.open('/tmp/blender.tar.xz').extractall('/opt/namzu-apps')" \
86
+ && ln -s /opt/namzu-apps/blender-5.2.2-linux-x64/blender /usr/local/bin/blender \
87
+ && /usr/local/bin/godot4 --version \
88
+ && /usr/local/bin/blender --version \
89
+ && rm /tmp/godot.zip /tmp/blender.tar.xz
90
+
91
+ # Blender MCP automatic addon update checks and telemetry stay off in the guest.
92
+ ENV BLENDERMCP_NO_UPDATE_CHECK=1 BLENDER_MCP_DISABLE_TELEMETRY=1
93
+
94
+ # The catalogue now lists the creative applications too; setuid bits come off again
95
+ # because the packages above installed new ones.
96
+ RUN node /opt/namzu-computer/prepare-home.cjs --stage creative \
97
+ && find / -xdev -type f -perm /6000 -exec chmod a-s {} +
98
+
99
+ USER 1001:1001