@namzu/sandbox 24.0.0 → 25.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.
- package/CHANGELOG.md +274 -0
- package/dist/backends/http-worker-client.d.ts +3 -1
- package/dist/backends/http-worker-client.d.ts.map +1 -1
- package/dist/backends/http-worker-client.js +15 -4
- package/dist/backends/http-worker-client.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/local-virtual-computer/client.d.ts +21 -0
- package/dist/local-virtual-computer/client.d.ts.map +1 -0
- package/dist/local-virtual-computer/client.js +503 -0
- package/dist/local-virtual-computer/client.js.map +1 -0
- package/dist/local-virtual-computer/detached-process.d.ts +15 -0
- package/dist/local-virtual-computer/detached-process.d.ts.map +1 -0
- package/dist/local-virtual-computer/detached-process.js +111 -0
- package/dist/local-virtual-computer/detached-process.js.map +1 -0
- package/dist/local-virtual-computer/detached-worker.d.ts +11 -0
- package/dist/local-virtual-computer/detached-worker.d.ts.map +1 -0
- package/dist/local-virtual-computer/detached-worker.js +97 -0
- package/dist/local-virtual-computer/detached-worker.js.map +1 -0
- package/dist/local-virtual-computer/dialect.d.ts +26 -0
- package/dist/local-virtual-computer/dialect.d.ts.map +1 -0
- package/dist/local-virtual-computer/dialect.js +61 -0
- package/dist/local-virtual-computer/dialect.js.map +1 -0
- package/dist/local-virtual-computer/engine.d.ts +63 -0
- package/dist/local-virtual-computer/engine.d.ts.map +1 -0
- package/dist/local-virtual-computer/engine.js +125 -0
- package/dist/local-virtual-computer/engine.js.map +1 -0
- package/dist/local-virtual-computer/guest-stdio.d.ts +15 -0
- package/dist/local-virtual-computer/guest-stdio.d.ts.map +1 -0
- package/dist/local-virtual-computer/guest-stdio.js +330 -0
- package/dist/local-virtual-computer/guest-stdio.js.map +1 -0
- package/dist/local-virtual-computer/index.d.ts +36 -0
- package/dist/local-virtual-computer/index.d.ts.map +1 -0
- package/dist/local-virtual-computer/index.js +341 -0
- package/dist/local-virtual-computer/index.js.map +1 -0
- package/dist/local-virtual-computer/podman-machine.d.ts +17 -0
- package/dist/local-virtual-computer/podman-machine.d.ts.map +1 -0
- package/dist/local-virtual-computer/podman-machine.js +181 -0
- package/dist/local-virtual-computer/podman-machine.js.map +1 -0
- package/dist/local-virtual-computer/setup/contract.d.ts +360 -0
- package/dist/local-virtual-computer/setup/contract.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/contract.js +3 -0
- package/dist/local-virtual-computer/setup/contract.js.map +1 -0
- package/dist/local-virtual-computer/setup/elevated.d.ts +78 -0
- package/dist/local-virtual-computer/setup/elevated.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/elevated.js +548 -0
- package/dist/local-virtual-computer/setup/elevated.js.map +1 -0
- package/dist/local-virtual-computer/setup/host.d.ts +12 -0
- package/dist/local-virtual-computer/setup/host.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/host.js +93 -0
- package/dist/local-virtual-computer/setup/host.js.map +1 -0
- package/dist/local-virtual-computer/setup/idle.d.ts +18 -0
- package/dist/local-virtual-computer/setup/idle.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/idle.js +38 -0
- package/dist/local-virtual-computer/setup/idle.js.map +1 -0
- package/dist/local-virtual-computer/setup/image.d.ts +7 -0
- package/dist/local-virtual-computer/setup/image.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/image.js +124 -0
- package/dist/local-virtual-computer/setup/image.js.map +1 -0
- package/dist/local-virtual-computer/setup/index.d.ts +17 -0
- package/dist/local-virtual-computer/setup/index.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/index.js +17 -0
- package/dist/local-virtual-computer/setup/index.js.map +1 -0
- package/dist/local-virtual-computer/setup/limits.d.ts +30 -0
- package/dist/local-virtual-computer/setup/limits.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/limits.js +58 -0
- package/dist/local-virtual-computer/setup/limits.js.map +1 -0
- package/dist/local-virtual-computer/setup/plan.d.ts +8 -0
- package/dist/local-virtual-computer/setup/plan.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/plan.js +131 -0
- package/dist/local-virtual-computer/setup/plan.js.map +1 -0
- package/dist/local-virtual-computer/setup/readiness.d.ts +11 -0
- package/dist/local-virtual-computer/setup/readiness.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/readiness.js +320 -0
- package/dist/local-virtual-computer/setup/readiness.js.map +1 -0
- package/dist/local-virtual-computer/setup/registry.d.ts +18 -0
- package/dist/local-virtual-computer/setup/registry.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/registry.js +50 -0
- package/dist/local-virtual-computer/setup/registry.js.map +1 -0
- package/dist/local-virtual-computer/setup/restart.d.ts +8 -0
- package/dist/local-virtual-computer/setup/restart.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/restart.js +60 -0
- package/dist/local-virtual-computer/setup/restart.js.map +1 -0
- package/dist/local-virtual-computer/setup/resume.d.ts +14 -0
- package/dist/local-virtual-computer/setup/resume.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/resume.js +58 -0
- package/dist/local-virtual-computer/setup/resume.js.map +1 -0
- package/dist/local-virtual-computer/setup/run.d.ts +8 -0
- package/dist/local-virtual-computer/setup/run.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/run.js +200 -0
- package/dist/local-virtual-computer/setup/run.js.map +1 -0
- package/dist/local-virtual-computer/setup/windows-folder.d.ts +11 -0
- package/dist/local-virtual-computer/setup/windows-folder.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/windows-folder.js +14 -0
- package/dist/local-virtual-computer/setup/windows-folder.js.map +1 -0
- package/dist/local-virtual-computer/setup/wsl-package.d.ts +19 -0
- package/dist/local-virtual-computer/setup/wsl-package.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/wsl-package.js +242 -0
- package/dist/local-virtual-computer/setup/wsl-package.js.map +1 -0
- package/dist/local-virtual-computer/setup/wslc-shared.d.ts +27 -0
- package/dist/local-virtual-computer/setup/wslc-shared.d.ts.map +1 -0
- package/dist/local-virtual-computer/setup/wslc-shared.js +137 -0
- package/dist/local-virtual-computer/setup/wslc-shared.js.map +1 -0
- package/dist/local-virtual-computer/ssh-forward.d.ts +19 -0
- package/dist/local-virtual-computer/ssh-forward.d.ts.map +1 -0
- package/dist/local-virtual-computer/ssh-forward.js +179 -0
- package/dist/local-virtual-computer/ssh-forward.js.map +1 -0
- package/dist/local-virtual-computer/wslc-engine.d.ts +27 -0
- package/dist/local-virtual-computer/wslc-engine.d.ts.map +1 -0
- package/dist/local-virtual-computer/wslc-engine.js +98 -0
- package/dist/local-virtual-computer/wslc-engine.js.map +1 -0
- package/local-computer/Dockerfile +99 -0
- package/local-computer/app-catalog.cjs +240 -0
- package/local-computer/app-native-host.cjs +144 -0
- package/local-computer/browser-home.cjs +249 -0
- package/local-computer/build-image.mjs +310 -0
- package/local-computer/desktop-worker.cjs +477 -0
- package/local-computer/entrypoint.sh +161 -0
- package/local-computer/home.html +28 -0
- package/local-computer/new-tab/home.css +255 -0
- package/local-computer/new-tab/home.js +65 -0
- package/local-computer/new-tab/manifest.json +12 -0
- package/local-computer/new-tab/wordmark.svg +33 -0
- package/local-computer/openbox-rc.xml +41 -0
- package/local-computer/prepare-home.cjs +121 -0
- package/local-computer/tint2rc +44 -0
- package/package.json +13 -4
- package/src/backends/http-worker-client.ts +31 -3
- package/src/index.ts +13 -0
- package/src/local-virtual-computer/client.ts +573 -0
- package/src/local-virtual-computer/detached-process.ts +133 -0
- package/src/local-virtual-computer/detached-worker.ts +124 -0
- package/src/local-virtual-computer/dialect.ts +83 -0
- package/src/local-virtual-computer/engine.ts +197 -0
- package/src/local-virtual-computer/guest-stdio.ts +332 -0
- package/src/local-virtual-computer/index.ts +447 -0
- package/src/local-virtual-computer/podman-machine.ts +264 -0
- package/src/local-virtual-computer/setup/contract.ts +395 -0
- package/src/local-virtual-computer/setup/elevated.ts +655 -0
- package/src/local-virtual-computer/setup/host.ts +100 -0
- package/src/local-virtual-computer/setup/idle.ts +39 -0
- package/src/local-virtual-computer/setup/image.ts +159 -0
- package/src/local-virtual-computer/setup/index.ts +16 -0
- package/src/local-virtual-computer/setup/limits.ts +68 -0
- package/src/local-virtual-computer/setup/plan.ts +144 -0
- package/src/local-virtual-computer/setup/readiness.ts +386 -0
- package/src/local-virtual-computer/setup/registry.ts +50 -0
- package/src/local-virtual-computer/setup/restart.ts +64 -0
- package/src/local-virtual-computer/setup/resume.ts +65 -0
- package/src/local-virtual-computer/setup/run.ts +225 -0
- package/src/local-virtual-computer/setup/windows-folder.ts +13 -0
- package/src/local-virtual-computer/setup/wsl-package.ts +242 -0
- package/src/local-virtual-computer/setup/wslc-shared.ts +147 -0
- package/src/local-virtual-computer/ssh-forward.ts +195 -0
- package/src/local-virtual-computer/wslc-engine.ts +124 -0
- package/worker/server.js +1499 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { lstat, realpath } from 'node:fs/promises'
|
|
2
|
+
import { createConnection } from 'node:net'
|
|
3
|
+
import { win32 } from 'node:path'
|
|
4
|
+
import {
|
|
5
|
+
type LocalComputerCommandRunner,
|
|
6
|
+
LocalComputerSetupError,
|
|
7
|
+
type LocalEngine,
|
|
8
|
+
nodeCommandRunner,
|
|
9
|
+
} from './engine.js'
|
|
10
|
+
import { startLocalSshForward } from './ssh-forward.js'
|
|
11
|
+
|
|
12
|
+
interface PodmanMachineOptions {
|
|
13
|
+
readonly podmanBinary?: string
|
|
14
|
+
readonly podmanMachine?: string
|
|
15
|
+
readonly podmanConnection?: string
|
|
16
|
+
readonly engineEndpoint?: string
|
|
17
|
+
readonly runner?: LocalComputerCommandRunner
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function confirmLocalPipe(path: string, signal?: AbortSignal): Promise<void> {
|
|
21
|
+
signal?.throwIfAborted()
|
|
22
|
+
await new Promise<void>((resolve, reject) => {
|
|
23
|
+
const socket = createConnection(path)
|
|
24
|
+
const abort = () => {
|
|
25
|
+
socket.destroy()
|
|
26
|
+
reject(signal?.reason)
|
|
27
|
+
}
|
|
28
|
+
const finish = (error?: Error) => {
|
|
29
|
+
signal?.removeEventListener('abort', abort)
|
|
30
|
+
socket.destroy()
|
|
31
|
+
if (error)
|
|
32
|
+
reject(
|
|
33
|
+
new LocalComputerSetupError(
|
|
34
|
+
'The selected local Podman machine pipe is unavailable; start that machine explicitly',
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
else resolve()
|
|
38
|
+
}
|
|
39
|
+
socket.once('connect', () => finish())
|
|
40
|
+
socket.once('error', finish)
|
|
41
|
+
signal?.addEventListener('abort', abort, { once: true })
|
|
42
|
+
if (signal?.aborted) abort()
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function insideProfile(path: string, profile: string): boolean {
|
|
47
|
+
const relative = win32.relative(profile, path)
|
|
48
|
+
return !win32.isAbsolute(relative) && relative !== '..' && !relative.startsWith('..\\')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Admit only a registered Windows WSL Podman machine. Its SSH connection is a
|
|
53
|
+
* local machine transport, not permission to select an arbitrary remote daemon.
|
|
54
|
+
* Capture its verified URL and identity so later named-connection edits cannot
|
|
55
|
+
* redirect an allocation. Never start, initialize or configure the machine.
|
|
56
|
+
*/
|
|
57
|
+
export async function resolveLocalPodmanEngine(
|
|
58
|
+
options: PodmanMachineOptions,
|
|
59
|
+
signal?: AbortSignal,
|
|
60
|
+
platform: NodeJS.Platform = process.platform,
|
|
61
|
+
): Promise<LocalEngine> {
|
|
62
|
+
if (platform !== 'win32')
|
|
63
|
+
throw new LocalComputerSetupError(
|
|
64
|
+
'The Podman computer adapter requires a locally registered Windows WSL machine',
|
|
65
|
+
)
|
|
66
|
+
const machineName = options.podmanMachine
|
|
67
|
+
const connectionName = options.podmanConnection
|
|
68
|
+
if (
|
|
69
|
+
!machineName ||
|
|
70
|
+
!connectionName ||
|
|
71
|
+
!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(machineName) ||
|
|
72
|
+
!/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(connectionName)
|
|
73
|
+
)
|
|
74
|
+
throw new LocalComputerSetupError(
|
|
75
|
+
'Select an existing Podman machine and its registered connection explicitly',
|
|
76
|
+
)
|
|
77
|
+
if (options.engineEndpoint)
|
|
78
|
+
throw new LocalComputerSetupError(
|
|
79
|
+
'Podman machine transport is verified from local registration; an arbitrary engine endpoint is refused',
|
|
80
|
+
)
|
|
81
|
+
const selectedBinary = options.podmanBinary ?? 'podman'
|
|
82
|
+
const binary = /[\\/]|^[a-zA-Z]:/.test(selectedBinary)
|
|
83
|
+
? win32.resolve(process.cwd(), selectedBinary)
|
|
84
|
+
: selectedBinary
|
|
85
|
+
// Native Podman probes /proc/self/uid_map even in remote mode. A WSL UNC
|
|
86
|
+
// caller directory produces ERROR_DIRECTORY rather than a missing file,
|
|
87
|
+
// which Podman treats as fatal. Pin this transport to a native directory
|
|
88
|
+
// without changing the embedding application's working directory.
|
|
89
|
+
const profile = process.env.USERPROFILE
|
|
90
|
+
if (!profile || !/^[a-zA-Z]:[\\/]/.test(profile))
|
|
91
|
+
throw new LocalComputerSetupError(
|
|
92
|
+
'The Podman machine requires an existing native Windows operator profile directory',
|
|
93
|
+
)
|
|
94
|
+
const realProfile = await realpath(profile)
|
|
95
|
+
if (!/^[a-zA-Z]:[\\/]/.test(realProfile) || !(await lstat(realProfile)).isDirectory())
|
|
96
|
+
throw new LocalComputerSetupError(
|
|
97
|
+
'The Podman machine requires an existing native Windows operator profile directory',
|
|
98
|
+
)
|
|
99
|
+
const runner = options.runner ?? nodeCommandRunner
|
|
100
|
+
const machines = JSON.parse(
|
|
101
|
+
await runner.run(binary, ['machine', 'list', '--format', 'json'], {
|
|
102
|
+
signal,
|
|
103
|
+
cwd: realProfile,
|
|
104
|
+
}),
|
|
105
|
+
) as { Name?: string; VMType?: string }[]
|
|
106
|
+
if (machines.find((machine) => machine.Name === machineName)?.VMType !== 'wsl')
|
|
107
|
+
throw new LocalComputerSetupError(
|
|
108
|
+
'The selected Podman machine must be registered locally with the Windows WSL provider',
|
|
109
|
+
)
|
|
110
|
+
const inspected = JSON.parse(
|
|
111
|
+
await runner.run(binary, ['machine', 'inspect', machineName, '--format', '{{json .}}'], {
|
|
112
|
+
signal,
|
|
113
|
+
cwd: realProfile,
|
|
114
|
+
}),
|
|
115
|
+
) as {
|
|
116
|
+
Name?: string
|
|
117
|
+
State?: string
|
|
118
|
+
Rootful?: boolean
|
|
119
|
+
ConfigDir?: { Path?: string }
|
|
120
|
+
SSHConfig?: { IdentityPath?: string; Port?: number; RemoteUsername?: string }
|
|
121
|
+
ConnectionInfo?: { PodmanPipe?: { Path?: string } }
|
|
122
|
+
}
|
|
123
|
+
if (inspected.Name !== machineName || inspected.State !== 'running')
|
|
124
|
+
throw new LocalComputerSetupError(
|
|
125
|
+
'The selected Podman machine is stopped or unavailable; start it explicitly',
|
|
126
|
+
)
|
|
127
|
+
const registrations = await runner.run(
|
|
128
|
+
win32.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'wsl.exe'),
|
|
129
|
+
['--list', '--quiet'],
|
|
130
|
+
{ signal, cwd: realProfile },
|
|
131
|
+
)
|
|
132
|
+
if (
|
|
133
|
+
!registrations
|
|
134
|
+
.split(/\r?\n/)
|
|
135
|
+
.some((name) => name.trim().toLowerCase() === machineName.toLowerCase())
|
|
136
|
+
)
|
|
137
|
+
throw new LocalComputerSetupError(
|
|
138
|
+
'The selected Podman machine is not a registered local WSL distribution',
|
|
139
|
+
)
|
|
140
|
+
const pipe = inspected.ConnectionInfo?.PodmanPipe?.Path
|
|
141
|
+
if (pipe !== `\\\\.\\pipe\\${machineName}`)
|
|
142
|
+
throw new LocalComputerSetupError(
|
|
143
|
+
'The Podman machine must own its matching local Windows named pipe',
|
|
144
|
+
)
|
|
145
|
+
const identity = inspected.SSHConfig?.IdentityPath
|
|
146
|
+
const configDir = inspected.ConfigDir?.Path
|
|
147
|
+
if (!identity || !configDir || !win32.isAbsolute(identity) || !win32.isAbsolute(configDir))
|
|
148
|
+
throw new LocalComputerSetupError(
|
|
149
|
+
'The Podman machine must belong to the current Windows operator profile',
|
|
150
|
+
)
|
|
151
|
+
const [realIdentity, realConfig] = await Promise.all([
|
|
152
|
+
realpath(win32.normalize(identity)),
|
|
153
|
+
realpath(win32.normalize(configDir)),
|
|
154
|
+
])
|
|
155
|
+
if (
|
|
156
|
+
!insideProfile(realIdentity, realProfile) ||
|
|
157
|
+
!insideProfile(realConfig, realProfile) ||
|
|
158
|
+
!(await lstat(realIdentity)).isFile()
|
|
159
|
+
)
|
|
160
|
+
throw new LocalComputerSetupError(
|
|
161
|
+
'The Podman machine identity and configuration must remain inside the current Windows operator profile',
|
|
162
|
+
)
|
|
163
|
+
const connections = JSON.parse(
|
|
164
|
+
await runner.run(binary, ['system', 'connection', 'list', '--format', 'json'], {
|
|
165
|
+
signal,
|
|
166
|
+
cwd: realProfile,
|
|
167
|
+
}),
|
|
168
|
+
) as {
|
|
169
|
+
Name?: string
|
|
170
|
+
URI?: string
|
|
171
|
+
Identity?: string
|
|
172
|
+
IsMachine?: boolean
|
|
173
|
+
ReadWrite?: boolean
|
|
174
|
+
}[]
|
|
175
|
+
const connection = connections.find((candidate) => candidate.Name === connectionName)
|
|
176
|
+
let url: URL
|
|
177
|
+
try {
|
|
178
|
+
url = new URL(connection?.URI ?? '')
|
|
179
|
+
} catch {
|
|
180
|
+
throw new LocalComputerSetupError('The selected Podman machine connection is invalid')
|
|
181
|
+
}
|
|
182
|
+
const expectedUser = inspected.Rootful ? 'root' : inspected.SSHConfig?.RemoteUsername
|
|
183
|
+
const port = inspected.SSHConfig?.Port
|
|
184
|
+
if (
|
|
185
|
+
!connection?.IsMachine ||
|
|
186
|
+
connection.ReadWrite !== true ||
|
|
187
|
+
!connection.Identity ||
|
|
188
|
+
win32.normalize(connection.Identity).toLowerCase() !==
|
|
189
|
+
win32.normalize(realIdentity).toLowerCase() ||
|
|
190
|
+
!Number.isSafeInteger(port) ||
|
|
191
|
+
Number(port) < 1 ||
|
|
192
|
+
Number(port) > 65535 ||
|
|
193
|
+
url.protocol !== 'ssh:' ||
|
|
194
|
+
url.hostname !== '127.0.0.1' ||
|
|
195
|
+
Number(url.port) !== port ||
|
|
196
|
+
!expectedUser ||
|
|
197
|
+
url.username !== expectedUser ||
|
|
198
|
+
url.password ||
|
|
199
|
+
url.search ||
|
|
200
|
+
url.hash ||
|
|
201
|
+
(inspected.Rootful
|
|
202
|
+
? url.pathname !== '/run/podman/podman.sock'
|
|
203
|
+
: !/^\/run\/user\/\d+\/podman\/podman\.sock$/.test(url.pathname))
|
|
204
|
+
)
|
|
205
|
+
throw new LocalComputerSetupError(
|
|
206
|
+
'The Podman connection must match the selected local WSL machine identity, port, user and socket; remote connections are refused',
|
|
207
|
+
)
|
|
208
|
+
await confirmLocalPipe(pipe, signal)
|
|
209
|
+
const endpoint = `npipe:////./pipe/${machineName}`
|
|
210
|
+
const run: LocalEngine['run'] = (args, call) => {
|
|
211
|
+
const env = { ...process.env, ...call?.env }
|
|
212
|
+
for (const name of [
|
|
213
|
+
'CONTAINER_HOST',
|
|
214
|
+
'CONTAINER_CONNECTION',
|
|
215
|
+
'CONTAINER_SSHKEY',
|
|
216
|
+
'DOCKER_HOST',
|
|
217
|
+
'DOCKER_CONTEXT',
|
|
218
|
+
])
|
|
219
|
+
env[name] = undefined
|
|
220
|
+
return runner.run(binary, ['--url', url.href, '--identity', realIdentity, ...args], {
|
|
221
|
+
...call,
|
|
222
|
+
env,
|
|
223
|
+
cwd: realProfile,
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
const info = JSON.parse(await run(['info', '--format', 'json'], { signal })) as {
|
|
227
|
+
host?: { os?: string }
|
|
228
|
+
}
|
|
229
|
+
if (info.host?.os !== 'linux')
|
|
230
|
+
throw new LocalComputerSetupError('The selected local Podman machine must run Linux containers')
|
|
231
|
+
return {
|
|
232
|
+
endpoint,
|
|
233
|
+
run,
|
|
234
|
+
async forwardPorts(ports, call) {
|
|
235
|
+
const key = (
|
|
236
|
+
await runner.run(
|
|
237
|
+
win32.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'wsl.exe'),
|
|
238
|
+
[
|
|
239
|
+
'--distribution',
|
|
240
|
+
machineName,
|
|
241
|
+
'--user',
|
|
242
|
+
'root',
|
|
243
|
+
'--exec',
|
|
244
|
+
'cat',
|
|
245
|
+
'/etc/ssh/ssh_host_ed25519_key.pub',
|
|
246
|
+
],
|
|
247
|
+
{ signal: call.signal, cwd: realProfile },
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
.trim()
|
|
251
|
+
.split(/\s+/)
|
|
252
|
+
.slice(0, 2)
|
|
253
|
+
.join(' ')
|
|
254
|
+
return await startLocalSshForward({
|
|
255
|
+
identity: realIdentity,
|
|
256
|
+
port: Number(port),
|
|
257
|
+
user: expectedUser,
|
|
258
|
+
hostPublicKey: key,
|
|
259
|
+
ports,
|
|
260
|
+
...call,
|
|
261
|
+
})
|
|
262
|
+
},
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract of the Windows Pal computer setup: what a readiness inspection
|
|
3
|
+
* reports, the plan a person consents to, and how running that plan reports
|
|
4
|
+
* progress and ends. The provider never installs, updates, loads or starts
|
|
5
|
+
* anything; everything here is a separate operation an application runs after
|
|
6
|
+
* the person agrees.
|
|
7
|
+
*/
|
|
8
|
+
import type { LocalComputerCommandRunner } from '../engine.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One state per machine, checked in this order: the first that applies wins.
|
|
12
|
+
* - `unsupported`: not Windows, not x64, or a Windows build below 19041.
|
|
13
|
+
* - `blocked-by-policy`: `AllowWSL` or `AllowWSLContainer` is 0 under
|
|
14
|
+
* `HKLM\Software\Policies\WSL`.
|
|
15
|
+
* - `restart-pending`: setup recorded that Windows needs a restart, and the PC
|
|
16
|
+
* has not restarted since; or Virtual Machine Platform is on, no hypervisor
|
|
17
|
+
* runs yet, and Windows itself reports a restart pending (no marker then).
|
|
18
|
+
* A marker that cannot be read, or a boot time that cannot be fetched while
|
|
19
|
+
* a marker exists, also reads `restart-pending`: the steps still to run
|
|
20
|
+
* after the restart are then unknown and treated as none. So does a marker
|
|
21
|
+
* that can be neither read nor dated (no file date): `restart` is then
|
|
22
|
+
* absent, since there is no record time to report.
|
|
23
|
+
* - `platform-off`: Virtual Machine Platform is off, or (Windows 10 only) the
|
|
24
|
+
* Windows Subsystem for Linux optional component is.
|
|
25
|
+
* - `wsl-missing`: no WSL package with wslc.
|
|
26
|
+
* - `wsl-outdated`: WSL is present, but wslc is missing or below the floor.
|
|
27
|
+
* - `sdk-mismatch`: the installed WSL refuses this client (`0x8004060B`).
|
|
28
|
+
* - `virtualization-off`: the session's virtual machine cannot start
|
|
29
|
+
* (`0x80370102`), or (without a session probe) no hypervisor runs although
|
|
30
|
+
* the platform is on and Windows reports no restart pending.
|
|
31
|
+
* - `image-missing`: wslc works, but the Pal computer image is not loaded.
|
|
32
|
+
* - `ready`: wslc works and the image is loaded and matches its manifest.
|
|
33
|
+
*/
|
|
34
|
+
export type WslcReadinessState =
|
|
35
|
+
| 'unsupported'
|
|
36
|
+
| 'blocked-by-policy'
|
|
37
|
+
| 'restart-pending'
|
|
38
|
+
| 'platform-off'
|
|
39
|
+
| 'wsl-missing'
|
|
40
|
+
| 'wsl-outdated'
|
|
41
|
+
| 'sdk-mismatch'
|
|
42
|
+
| 'virtualization-off'
|
|
43
|
+
| 'image-missing'
|
|
44
|
+
| 'ready'
|
|
45
|
+
|
|
46
|
+
/** Why a machine is `unsupported`. */
|
|
47
|
+
export type WslcUnsupportedReason = 'platform' | 'architecture' | 'windows-build'
|
|
48
|
+
|
|
49
|
+
/** What a readiness inspection found. Plain data; safe to print as JSON. */
|
|
50
|
+
export interface WslcReadiness {
|
|
51
|
+
readonly state: WslcReadinessState
|
|
52
|
+
/** Set only when `state` is `unsupported`. */
|
|
53
|
+
readonly unsupportedReason?: WslcUnsupportedReason
|
|
54
|
+
readonly windows?: {
|
|
55
|
+
readonly build: number
|
|
56
|
+
readonly architecture: 'x64' | 'arm64' | 'other'
|
|
57
|
+
readonly isWindows11: boolean
|
|
58
|
+
}
|
|
59
|
+
readonly policy: {
|
|
60
|
+
readonly allowWsl: boolean
|
|
61
|
+
readonly allowWslContainer: boolean
|
|
62
|
+
}
|
|
63
|
+
readonly components: {
|
|
64
|
+
readonly virtualMachinePlatform: boolean | 'unknown'
|
|
65
|
+
/** Windows 11 does not need the optional component. */
|
|
66
|
+
readonly wslOptionalComponent: boolean | 'unknown' | 'not-needed'
|
|
67
|
+
readonly hypervisorPresent: boolean | 'unknown'
|
|
68
|
+
}
|
|
69
|
+
readonly wsl: {
|
|
70
|
+
/** `x.y.z.w` from `wsl.exe --version`, when a WSL package answers. */
|
|
71
|
+
readonly version?: string
|
|
72
|
+
/** The wslc binary found, if any. */
|
|
73
|
+
readonly wslcBinary?: string
|
|
74
|
+
/** `x.y.z` from `wslc version`. */
|
|
75
|
+
readonly wslcVersion?: string
|
|
76
|
+
}
|
|
77
|
+
readonly image: {
|
|
78
|
+
/** The reference the inspection looked for, e.g. `namzu-local-computer:2`. */
|
|
79
|
+
readonly ref: string
|
|
80
|
+
readonly loaded: boolean
|
|
81
|
+
/** A manifest and archive the application carries, if it has them. */
|
|
82
|
+
readonly bundled?: { readonly manifestPath: string; readonly archivePath: string }
|
|
83
|
+
}
|
|
84
|
+
/** Set when setup recorded a required restart (see {@link WslcRestartMarker}). */
|
|
85
|
+
readonly restart?: { readonly recordedAt: string; readonly bootTime?: string }
|
|
86
|
+
/** ISO time of the inspection. */
|
|
87
|
+
readonly checkedAt: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The image manifest `local-computer/build-image.mjs` writes and the Windows
|
|
92
|
+
* installer carries in `resources/pal-computer/manifest.json`.
|
|
93
|
+
*/
|
|
94
|
+
export interface WslcImageManifest {
|
|
95
|
+
readonly revision: number
|
|
96
|
+
readonly stage: 'base' | 'creative'
|
|
97
|
+
/** `namzu-local-computer:<revision>`; a test tag is never bundled. */
|
|
98
|
+
readonly imageRef: string
|
|
99
|
+
readonly imageId: string
|
|
100
|
+
/** The image's `org.namzu.local-computer.protocol` label. */
|
|
101
|
+
readonly protocol: string
|
|
102
|
+
/** The archive's file name, beside the manifest. */
|
|
103
|
+
readonly file: string
|
|
104
|
+
/** SHA-256 of the archive as shipped. */
|
|
105
|
+
readonly sha256: string
|
|
106
|
+
/** SHA-256 of the uncompressed tar. */
|
|
107
|
+
readonly tarSha256: string
|
|
108
|
+
/** Size of the uncompressed tar. */
|
|
109
|
+
readonly sizeBytes: number
|
|
110
|
+
/** Size of the archive. */
|
|
111
|
+
readonly compressedBytes: number
|
|
112
|
+
readonly format: 'zst' | 'xz' | 'gz' | 'tar'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The WSL release setup installs when WSL is missing or too old. */
|
|
116
|
+
export interface PinnedWslPackage {
|
|
117
|
+
/** `x.y.z.w`, for example `3.0.1.0`. */
|
|
118
|
+
readonly version: string
|
|
119
|
+
readonly architecture: 'x64'
|
|
120
|
+
/** An `https://github.com/microsoft/WSL/releases/download/...` URL. */
|
|
121
|
+
readonly url: string
|
|
122
|
+
readonly sha256: string
|
|
123
|
+
readonly sizeBytes: number
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* One step of a plan, in the order it runs.
|
|
128
|
+
* - `download-wsl`: fetch the pinned WSL package into the setup directory and
|
|
129
|
+
* check its size and SHA-256. Not elevated.
|
|
130
|
+
* - `enable-windows-features`: turn on Virtual Machine Platform (and on
|
|
131
|
+
* Windows 10 the WSL optional component). Elevated.
|
|
132
|
+
* - `install-wsl`: install the downloaded WSL package. Elevated.
|
|
133
|
+
* - `restart`: the person restarts the PC; setup continues after sign-in.
|
|
134
|
+
* - `load-image`: check the bundled archive against its manifest and load it
|
|
135
|
+
* with `wslc load -i`. Not elevated.
|
|
136
|
+
*
|
|
137
|
+
* Every elevated step of one plan runs in ONE elevated process, so a plan
|
|
138
|
+
* causes at most one Windows permission prompt.
|
|
139
|
+
*/
|
|
140
|
+
export type WslcSetupStepKind =
|
|
141
|
+
| 'download-wsl'
|
|
142
|
+
| 'enable-windows-features'
|
|
143
|
+
| 'install-wsl'
|
|
144
|
+
| 'restart'
|
|
145
|
+
| 'load-image'
|
|
146
|
+
|
|
147
|
+
export interface WslcSetupStep {
|
|
148
|
+
readonly kind: WslcSetupStepKind
|
|
149
|
+
readonly elevated: boolean
|
|
150
|
+
/** Bytes this step downloads, if any. */
|
|
151
|
+
readonly downloadBytes?: number
|
|
152
|
+
/** Bytes this step adds on disk, if known. */
|
|
153
|
+
readonly diskBytes?: number
|
|
154
|
+
/** A rough duration for the sentence the person reads. */
|
|
155
|
+
readonly estimatedSeconds: number
|
|
156
|
+
readonly mayRequireRestart: boolean
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Why a plan has nothing Namzu can do. */
|
|
160
|
+
export type WslcSetupBlock =
|
|
161
|
+
| 'unsupported'
|
|
162
|
+
| 'blocked-by-policy'
|
|
163
|
+
| 'virtualization-off'
|
|
164
|
+
| 'sdk-mismatch'
|
|
165
|
+
/** No bundled image and no other way to get one. */
|
|
166
|
+
| 'image-unavailable'
|
|
167
|
+
|
|
168
|
+
export interface WslcSetupPlan {
|
|
169
|
+
/**
|
|
170
|
+
* A digest of the readiness state and the steps. Consent names it, and
|
|
171
|
+
* running refuses a plan whose id no longer matches a fresh inspection.
|
|
172
|
+
*/
|
|
173
|
+
readonly id: string
|
|
174
|
+
readonly state: WslcReadinessState
|
|
175
|
+
readonly steps: readonly WslcSetupStep[]
|
|
176
|
+
/** 0 or 1: all elevated steps share one elevated process. */
|
|
177
|
+
readonly elevationPrompts: 0 | 1
|
|
178
|
+
readonly mayRequireRestart: boolean
|
|
179
|
+
/** Set when setup cannot proceed; `steps` is then empty. */
|
|
180
|
+
readonly blocked?: WslcSetupBlock
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** The person's agreement to one specific plan. */
|
|
184
|
+
export interface WslcSetupConsent {
|
|
185
|
+
readonly planId: string
|
|
186
|
+
/** ISO time of the click or the typed answer. */
|
|
187
|
+
readonly approvedAt: string
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Progress, emitted in order. Byte counts are raw; the caller humanises them. */
|
|
191
|
+
export type WslcSetupProgress =
|
|
192
|
+
| { readonly step: WslcSetupStepKind; readonly phase: 'started' }
|
|
193
|
+
| {
|
|
194
|
+
readonly step: WslcSetupStepKind
|
|
195
|
+
readonly phase: 'progress'
|
|
196
|
+
readonly done: number
|
|
197
|
+
readonly total?: number
|
|
198
|
+
readonly unit: 'bytes' | 'percent'
|
|
199
|
+
}
|
|
200
|
+
| {
|
|
201
|
+
readonly step: WslcSetupStepKind
|
|
202
|
+
readonly phase: 'finished'
|
|
203
|
+
readonly result: 'ok' | 'restart-required'
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Stable reasons a failed setup step carries. */
|
|
207
|
+
export type WslcSetupFailureCode =
|
|
208
|
+
| 'download-failed'
|
|
209
|
+
| 'download-corrupt'
|
|
210
|
+
| 'elevation-failed'
|
|
211
|
+
| 'feature-enable-failed'
|
|
212
|
+
| 'wsl-install-failed'
|
|
213
|
+
| 'image-corrupt'
|
|
214
|
+
| 'image-load-failed'
|
|
215
|
+
| 'image-mismatch'
|
|
216
|
+
| 'engine-failed'
|
|
217
|
+
|
|
218
|
+
/** How running a plan ended. */
|
|
219
|
+
export type WslcSetupResult =
|
|
220
|
+
| { readonly outcome: 'ready' }
|
|
221
|
+
| { readonly outcome: 'restart-required' }
|
|
222
|
+
/** The person declined the Windows permission prompt. */
|
|
223
|
+
| { readonly outcome: 'cancelled' }
|
|
224
|
+
/** The account cannot elevate and no administrator approved. */
|
|
225
|
+
| { readonly outcome: 'needs-administrator' }
|
|
226
|
+
/** The consent names a plan that no longer matches the machine. */
|
|
227
|
+
| { readonly outcome: 'plan-changed'; readonly plan: WslcSetupPlan }
|
|
228
|
+
| { readonly outcome: 'blocked'; readonly blocked: WslcSetupBlock }
|
|
229
|
+
| {
|
|
230
|
+
readonly outcome: 'failed'
|
|
231
|
+
readonly step: WslcSetupStepKind
|
|
232
|
+
readonly code: WslcSetupFailureCode
|
|
233
|
+
/** Plain words for a Details fold. Never engine output or paths with user names. */
|
|
234
|
+
readonly detail?: string
|
|
235
|
+
/** The Windows exit code of the failed step, when Windows gave one. */
|
|
236
|
+
readonly exitCode?: number
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Written to `<stateDirectory>/restart.json` when an elevated step reports
|
|
241
|
+
* that Windows needs a restart; cleared once readiness sees a boot later than
|
|
242
|
+
* `recordedAt`.
|
|
243
|
+
*/
|
|
244
|
+
export interface WslcRestartMarker {
|
|
245
|
+
readonly recordedAt: string
|
|
246
|
+
readonly planId: string
|
|
247
|
+
/** Steps still to run after the restart. */
|
|
248
|
+
readonly remaining: readonly WslcSetupStepKind[]
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Exit status of the elevated process, before it is mapped to a result. */
|
|
252
|
+
export type WslcElevatedOutcome =
|
|
253
|
+
| { readonly outcome: 'ok' }
|
|
254
|
+
| { readonly outcome: 'restart-required' }
|
|
255
|
+
| { readonly outcome: 'cancelled' }
|
|
256
|
+
| { readonly outcome: 'needs-administrator' }
|
|
257
|
+
| {
|
|
258
|
+
readonly outcome: 'failed'
|
|
259
|
+
readonly step: 'enable-windows-features' | 'install-wsl'
|
|
260
|
+
readonly code: 'feature-enable-failed' | 'wsl-install-failed' | 'elevation-failed'
|
|
261
|
+
/** The Windows exit code, for a Details fold. */
|
|
262
|
+
readonly exitCode?: number
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Everything the setup touches on the machine, injected so tests can stand in
|
|
267
|
+
* for Windows. Implementations for a real Windows host live beside the
|
|
268
|
+
* functions that use them.
|
|
269
|
+
*/
|
|
270
|
+
export interface WslcSetupHost {
|
|
271
|
+
readonly platform: NodeJS.Platform
|
|
272
|
+
readonly arch: string
|
|
273
|
+
/** Windows build number, e.g. 19045; undefined off Windows. */
|
|
274
|
+
readonly windowsBuild: number | undefined
|
|
275
|
+
readonly env: NodeJS.ProcessEnv
|
|
276
|
+
readonly runner: LocalComputerCommandRunner
|
|
277
|
+
/** A DWORD from the registry; undefined when the value or key is absent. */
|
|
278
|
+
readDword(key: string, name: string): Promise<number | undefined>
|
|
279
|
+
/** Whether a registry key exists; `unknown` when the check failed or timed out. */
|
|
280
|
+
keyExists(key: string): Promise<boolean | 'unknown'>
|
|
281
|
+
/** The last boot time of Windows. */
|
|
282
|
+
bootTime(): Promise<Date>
|
|
283
|
+
now(): Date
|
|
284
|
+
/** `%LOCALAPPDATA%\Namzu\setup` unless the caller chooses otherwise. */
|
|
285
|
+
readonly stateDirectory: string
|
|
286
|
+
/**
|
|
287
|
+
* The registry key the resume command is written under. Defaults to
|
|
288
|
+
* {@link RESUME_AT_SIGN_IN_KEY}; a trial run on a real PC points it at a
|
|
289
|
+
* scratch key so nothing runs at the next sign-in.
|
|
290
|
+
*/
|
|
291
|
+
readonly resumeRegistryKey: string
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Where Windows keeps the command it runs once at the next sign-in. */
|
|
295
|
+
export const RESUME_AT_SIGN_IN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce'
|
|
296
|
+
|
|
297
|
+
/** Options every setup entry point takes. */
|
|
298
|
+
export interface WslcSetupOptions {
|
|
299
|
+
/** The image the application runs, e.g. `namzu-local-computer:2`. */
|
|
300
|
+
readonly imageRef: string
|
|
301
|
+
/** The manifest the application carries, with its archive beside it. */
|
|
302
|
+
readonly bundledManifestPath?: string
|
|
303
|
+
/** Defaults to the release pinned in this package. */
|
|
304
|
+
readonly wslPackage?: PinnedWslPackage
|
|
305
|
+
readonly host?: Partial<WslcSetupHost>
|
|
306
|
+
readonly signal?: AbortSignal
|
|
307
|
+
/**
|
|
308
|
+
* Start the wslc session to check the loaded image and the virtual machine
|
|
309
|
+
* itself. Off by default, so opening a panel never starts a virtual machine
|
|
310
|
+
* of about 2 GB; without it, `image.loaded` comes from the record setup
|
|
311
|
+
* writes after a successful load (`<stateDirectory>/image.json`).
|
|
312
|
+
* `runWslcSetup` always probes.
|
|
313
|
+
*/
|
|
314
|
+
readonly probeSession?: boolean
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Run the one elevated process for the elevated steps of a plan. */
|
|
318
|
+
export type RunElevatedWslcSteps = (request: {
|
|
319
|
+
readonly steps: readonly ('enable-windows-features' | 'install-wsl')[]
|
|
320
|
+
/** Required when `install-wsl` is among the steps; re-verified while elevated. */
|
|
321
|
+
readonly wslPackage?: { readonly path: string; readonly sha256: string }
|
|
322
|
+
readonly host: WslcSetupHost
|
|
323
|
+
readonly onProgress?: (progress: WslcSetupProgress) => void
|
|
324
|
+
readonly signal?: AbortSignal
|
|
325
|
+
}) => Promise<WslcElevatedOutcome>
|
|
326
|
+
|
|
327
|
+
/** The entry points, as the CLI and Desktop call them. */
|
|
328
|
+
export type InspectWslcReadiness = (options: WslcSetupOptions) => Promise<WslcReadiness>
|
|
329
|
+
export type PlanWslcSetup = (
|
|
330
|
+
readiness: WslcReadiness,
|
|
331
|
+
options: Pick<WslcSetupOptions, 'wslPackage'> & { readonly manifest?: WslcImageManifest },
|
|
332
|
+
) => WslcSetupPlan
|
|
333
|
+
export type RunWslcSetup = (
|
|
334
|
+
consent: WslcSetupConsent,
|
|
335
|
+
options: WslcSetupOptions & {
|
|
336
|
+
readonly onProgress?: (progress: WslcSetupProgress) => void
|
|
337
|
+
/** Defaults to the real elevated runner; tests pass a stand-in. */
|
|
338
|
+
readonly runElevated?: RunElevatedWslcSteps
|
|
339
|
+
/** The command Windows runs once at the next sign-in to continue setup. */
|
|
340
|
+
readonly resumeCommand?: string
|
|
341
|
+
},
|
|
342
|
+
) => Promise<WslcSetupResult>
|
|
343
|
+
/**
|
|
344
|
+
* Stop the wslc session's virtual machine when nothing runs in it. Answers
|
|
345
|
+
* `busy` and does nothing while any container runs, Namzu's or not.
|
|
346
|
+
*/
|
|
347
|
+
export type StopIdleWslcSession = (
|
|
348
|
+
options: Pick<WslcSetupOptions, 'host' | 'signal'>,
|
|
349
|
+
) => Promise<{ readonly outcome: 'stopped' | 'busy' | 'not-running' }>
|
|
350
|
+
|
|
351
|
+
/** Check the bundled archive against its manifest, load it, and check what loaded. */
|
|
352
|
+
export type LoadWslcImage = (options: {
|
|
353
|
+
readonly manifestPath: string
|
|
354
|
+
readonly host?: Partial<WslcSetupHost>
|
|
355
|
+
readonly onProgress?: (progress: WslcSetupProgress) => void
|
|
356
|
+
readonly signal?: AbortSignal
|
|
357
|
+
}) => Promise<
|
|
358
|
+
| { readonly outcome: 'loaded' | 'already-loaded' }
|
|
359
|
+
| {
|
|
360
|
+
readonly outcome: 'failed'
|
|
361
|
+
readonly code: 'image-corrupt' | 'image-load-failed' | 'image-mismatch'
|
|
362
|
+
readonly detail?: string
|
|
363
|
+
}
|
|
364
|
+
>
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Fetch the pinned WSL package into `directory`, resuming a partial file, and
|
|
368
|
+
* check its size and SHA-256. A file that fails the check is deleted.
|
|
369
|
+
*/
|
|
370
|
+
export type DownloadWslPackage = (options: {
|
|
371
|
+
readonly pinned: PinnedWslPackage
|
|
372
|
+
readonly directory: string
|
|
373
|
+
readonly fetch?: typeof fetch
|
|
374
|
+
readonly onProgress?: (progress: WslcSetupProgress) => void
|
|
375
|
+
readonly signal?: AbortSignal
|
|
376
|
+
}) => Promise<
|
|
377
|
+
| { readonly outcome: 'ok'; readonly path: string }
|
|
378
|
+
| {
|
|
379
|
+
readonly outcome: 'failed'
|
|
380
|
+
readonly code: 'download-failed' | 'download-corrupt'
|
|
381
|
+
readonly detail?: string
|
|
382
|
+
}
|
|
383
|
+
>
|
|
384
|
+
|
|
385
|
+
/** The restart marker in `<stateDirectory>/restart.json`. */
|
|
386
|
+
export type ReadRestartMarker = (host: WslcSetupHost) => Promise<WslcRestartMarker | undefined>
|
|
387
|
+
export type WriteRestartMarker = (host: WslcSetupHost, marker: WslcRestartMarker) => Promise<void>
|
|
388
|
+
export type ClearRestartMarker = (host: WslcSetupHost) => Promise<void>
|
|
389
|
+
|
|
390
|
+
/** Ask Windows to run `command` once at the next sign-in, and take that back. */
|
|
391
|
+
export type RegisterResumeAtSignIn = (host: WslcSetupHost, command: string) => Promise<void>
|
|
392
|
+
export type ClearResumeAtSignIn = (host: WslcSetupHost) => Promise<void>
|
|
393
|
+
|
|
394
|
+
/** The real Windows host, with `overrides` applied. */
|
|
395
|
+
export type CreateWslcSetupHost = (overrides?: Partial<WslcSetupHost>) => WslcSetupHost
|