agent-dev-env 0.1.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/LICENSE +21 -0
- package/dist/assets/bridge/bridge.js +202 -0
- package/dist/assets/guest/guest-agent-mac.js +586 -0
- package/dist/assets/guest/guest-agent-ubuntu.js +481 -0
- package/dist/assets/guest/guest-agent-windows.js +466 -0
- package/dist/assets/images/mac/CHANGELOG.md +265 -0
- package/dist/assets/images/mac/README.md +63 -0
- package/dist/assets/images/mac/sandbox.pkr.hcl +468 -0
- package/dist/assets/images/mac/vars/sandbox-macos-tahoe.pkrvars.hcl +39 -0
- package/dist/assets/images/ubuntu-arm64-vmware/CHANGELOG.md +239 -0
- package/dist/assets/images/ubuntu-arm64-vmware/README.md +177 -0
- package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/meta-data +2 -0
- package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/user-data +34 -0
- package/dist/assets/images/ubuntu-arm64-vmware/sandbox.pkr.hcl +776 -0
- package/dist/assets/images/ubuntu-arm64-vmware/vars/sandbox-ubuntu-24-04-arm64-vmware.pkrvars.hcl +71 -0
- package/dist/assets/images/windows-arm64-qemu/CHANGELOG.md +209 -0
- package/dist/assets/images/windows-arm64-qemu/README.md +142 -0
- package/dist/assets/images/windows-arm64-qemu/autounattend.xml +325 -0
- package/dist/assets/images/windows-arm64-qemu/qemu-with-tpm.sh +170 -0
- package/dist/assets/images/windows-arm64-qemu/sandbox.pkr.hcl +1012 -0
- package/dist/assets/images/windows-arm64-qemu/vars/sandbox-windows-11-arm64-qemu.pkrvars.hcl +83 -0
- package/dist/assets/images/windows-arm64-vmware/CHANGELOG.md +270 -0
- package/dist/assets/images/windows-arm64-vmware/README.md +171 -0
- package/dist/assets/images/windows-arm64-vmware/autounattend.xml +311 -0
- package/dist/assets/images/windows-arm64-vmware/sandbox.pkr.hcl +1036 -0
- package/dist/assets/images/windows-arm64-vmware/vars/sandbox-windows-11-arm64-vmware.pkrvars.hcl +83 -0
- package/dist/assets/rules/agent-rules-linux.md +68 -0
- package/dist/assets/rules/agent-rules.md +71 -0
- package/dist/assets/watchdog/watch-build-ocr.swift +40 -0
- package/dist/assets/watchdog/watch-build.py +275 -0
- package/dist/cli.js +52 -0
- package/dist/commands/delete.js +179 -0
- package/dist/commands/doctor.js +165 -0
- package/dist/commands/list.js +38 -0
- package/dist/commands/not-yet.js +15 -0
- package/dist/commands/register.js +150 -0
- package/dist/commands/run.js +65 -0
- package/dist/commands/status.js +142 -0
- package/dist/commands/stop.js +186 -0
- package/dist/commands/sync.js +98 -0
- package/dist/lib/exec.js +250 -0
- package/dist/lib/ghcr.js +82 -0
- package/dist/lib/git.js +100 -0
- package/dist/lib/logger.js +87 -0
- package/dist/lib/network.js +63 -0
- package/dist/lib/paths.js +116 -0
- package/dist/lib/platform.js +87 -0
- package/dist/lib/prompt.js +66 -0
- package/dist/lib/qemu.js +373 -0
- package/dist/lib/regex.js +9 -0
- package/dist/lib/ssh.js +230 -0
- package/dist/lib/tart.js +210 -0
- package/dist/lib/template.js +35 -0
- package/dist/lib/vars.js +99 -0
- package/dist/lib/vmrun.js +334 -0
- package/dist/lifecycle/build-macos.js +23 -0
- package/dist/lifecycle/build-qemu.js +204 -0
- package/dist/lifecycle/build-shared.js +355 -0
- package/dist/lifecycle/build-ubuntu.js +184 -0
- package/dist/lifecycle/build-watchdog.js +146 -0
- package/dist/lifecycle/build-windows-vmware.js +112 -0
- package/dist/lifecycle/build.js +55 -0
- package/dist/lifecycle/catalog.js +165 -0
- package/dist/lifecycle/deploy.js +161 -0
- package/dist/lifecycle/tag.js +77 -0
- package/dist/lifecycle/watch-build.js +64 -0
- package/dist/runners/bridges.js +153 -0
- package/dist/runners/framework.js +66 -0
- package/dist/runners/macos-bridges.js +143 -0
- package/dist/runners/macos-guest.js +76 -0
- package/dist/runners/macos-rules.js +77 -0
- package/dist/runners/macos-summary.js +145 -0
- package/dist/runners/macos.js +258 -0
- package/dist/runners/openchamber.js +60 -0
- package/dist/runners/options.js +56 -0
- package/dist/runners/qemu-image.js +183 -0
- package/dist/runners/rules.js +49 -0
- package/dist/runners/ubuntu-bridges.js +154 -0
- package/dist/runners/ubuntu-guest.js +136 -0
- package/dist/runners/ubuntu-image.js +26 -0
- package/dist/runners/ubuntu-rules.js +86 -0
- package/dist/runners/ubuntu-shared.js +65 -0
- package/dist/runners/ubuntu-summary.js +141 -0
- package/dist/runners/ubuntu.js +117 -0
- package/dist/runners/vmware-common.js +158 -0
- package/dist/runners/vmware-image.js +242 -0
- package/dist/runners/windows-autologon.js +119 -0
- package/dist/runners/windows-bridges.js +136 -0
- package/dist/runners/windows-guest.js +213 -0
- package/dist/runners/windows-image.js +23 -0
- package/dist/runners/windows-qemu-summary.js +85 -0
- package/dist/runners/windows-qemu.js +189 -0
- package/dist/runners/windows-shared.js +68 -0
- package/dist/runners/windows-summary.js +104 -0
- package/dist/runners/windows.js +96 -0
- package/dist/settings/common.js +107 -0
- package/dist/settings/macos-copy.js +223 -0
- package/dist/settings/macos.js +41 -0
- package/dist/settings/ubuntu-copy.js +223 -0
- package/dist/settings/ubuntu.js +68 -0
- package/package.json +52 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// packages/guest-agent-windows/src/index.ts
|
|
4
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
5
|
+
import { mkdirSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
// packages/bridge-core/src/index.ts
|
|
11
|
+
import { rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
|
|
13
|
+
// packages/bridge-core/src/endpoints.ts
|
|
14
|
+
var PIPE_PREFIX = "\\\\.\\pipe\\";
|
|
15
|
+
function parseEndpoint(spec) {
|
|
16
|
+
const sep = spec.indexOf(":");
|
|
17
|
+
if (sep <= 0) {
|
|
18
|
+
throw new Error(`invalid endpoint '${spec}' (expected unix:|pipe:|tcp:)`);
|
|
19
|
+
}
|
|
20
|
+
const scheme = spec.slice(0, sep);
|
|
21
|
+
const rest = spec.slice(sep + 1);
|
|
22
|
+
switch (scheme) {
|
|
23
|
+
case "unix": {
|
|
24
|
+
if (!rest) {
|
|
25
|
+
throw new Error(`invalid unix endpoint '${spec}': missing path`);
|
|
26
|
+
}
|
|
27
|
+
return { kind: "unix", path: rest };
|
|
28
|
+
}
|
|
29
|
+
case "pipe": {
|
|
30
|
+
if (!rest) {
|
|
31
|
+
throw new Error(`invalid pipe endpoint '${spec}': missing name`);
|
|
32
|
+
}
|
|
33
|
+
const name = rest.startsWith(PIPE_PREFIX) ? rest : `${PIPE_PREFIX}${rest}`;
|
|
34
|
+
return { kind: "pipe", name };
|
|
35
|
+
}
|
|
36
|
+
case "tcp": {
|
|
37
|
+
const m = /^([^:]+):(\d+)$/.exec(rest);
|
|
38
|
+
const port = m ? Number(m[2]) : NaN;
|
|
39
|
+
if (!m || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
40
|
+
throw new Error(`invalid tcp endpoint '${spec}' (expected tcp:host:port, port 1-65535)`);
|
|
41
|
+
}
|
|
42
|
+
return { kind: "tcp", host: m[1], port };
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
throw new Error(
|
|
46
|
+
`unknown endpoint scheme '${scheme}' in '${spec}' (expected unix:|pipe:|tcp:)`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function formatEndpoint(endpoint) {
|
|
51
|
+
switch (endpoint.kind) {
|
|
52
|
+
case "unix":
|
|
53
|
+
return `unix:${endpoint.path}`;
|
|
54
|
+
case "pipe":
|
|
55
|
+
return `pipe:${endpoint.name}`;
|
|
56
|
+
case "tcp":
|
|
57
|
+
return `tcp:${endpoint.host}:${endpoint.port}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// packages/bridge-core/src/forwarder.ts
|
|
62
|
+
import net from "node:net";
|
|
63
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
64
|
+
function startForwarder(options) {
|
|
65
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
66
|
+
const server = net.createServer((client) => {
|
|
67
|
+
sockets.add(client);
|
|
68
|
+
client.on("close", () => sockets.delete(client));
|
|
69
|
+
forwardConnection(client, options.forward, sockets);
|
|
70
|
+
});
|
|
71
|
+
return bind(server, options.listen).then(() => {
|
|
72
|
+
options.log?.(
|
|
73
|
+
`listening on ${formatEndpoint(options.listen)} \u2192 ${formatEndpoint(options.forward)}`
|
|
74
|
+
);
|
|
75
|
+
return {
|
|
76
|
+
close: () => closeForwarder(server, sockets, options.listen, options.log)
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function forwardConnection(client, target, sockets) {
|
|
81
|
+
const upstream = connectTo(target);
|
|
82
|
+
sockets.add(upstream);
|
|
83
|
+
upstream.on("close", () => sockets.delete(upstream));
|
|
84
|
+
upstream.once("connect", () => {
|
|
85
|
+
client.pipe(upstream);
|
|
86
|
+
upstream.pipe(client);
|
|
87
|
+
});
|
|
88
|
+
const tearDown = () => {
|
|
89
|
+
client.destroy();
|
|
90
|
+
upstream.destroy();
|
|
91
|
+
};
|
|
92
|
+
upstream.once("error", tearDown);
|
|
93
|
+
client.once("error", () => {
|
|
94
|
+
upstream.destroy();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function bind(server, endpoint) {
|
|
98
|
+
switch (endpoint.kind) {
|
|
99
|
+
case "unix":
|
|
100
|
+
if (existsSync(endpoint.path)) {
|
|
101
|
+
unlinkSync(endpoint.path);
|
|
102
|
+
}
|
|
103
|
+
await listen(server, endpoint.path);
|
|
104
|
+
break;
|
|
105
|
+
case "pipe":
|
|
106
|
+
await listen(server, endpoint.name);
|
|
107
|
+
break;
|
|
108
|
+
case "tcp":
|
|
109
|
+
await listen(server, { port: endpoint.port, host: endpoint.host });
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function listen(server, spec) {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
server.once("error", reject);
|
|
116
|
+
server.listen(spec, () => {
|
|
117
|
+
server.removeListener("error", reject);
|
|
118
|
+
resolve();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
function connectTo(endpoint) {
|
|
123
|
+
switch (endpoint.kind) {
|
|
124
|
+
case "unix":
|
|
125
|
+
return net.connect({ path: endpoint.path });
|
|
126
|
+
case "pipe":
|
|
127
|
+
return net.connect({ path: endpoint.name });
|
|
128
|
+
case "tcp":
|
|
129
|
+
return net.connect({ port: endpoint.port, host: endpoint.host });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function closeForwarder(server, sockets, listen2, log) {
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
for (const socket of sockets) {
|
|
135
|
+
socket.destroy();
|
|
136
|
+
}
|
|
137
|
+
server.close(() => {
|
|
138
|
+
if (listen2.kind === "unix" && existsSync(listen2.path)) {
|
|
139
|
+
unlinkSync(listen2.path);
|
|
140
|
+
}
|
|
141
|
+
log?.("closed");
|
|
142
|
+
resolve();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// packages/bridge-core/src/index.ts
|
|
148
|
+
async function runBridge(options) {
|
|
149
|
+
const running = await startForwarder(options);
|
|
150
|
+
if (options.pidfile) {
|
|
151
|
+
writeFileSync(options.pidfile, `${process.pid}
|
|
152
|
+
`);
|
|
153
|
+
}
|
|
154
|
+
await new Promise((resolve) => {
|
|
155
|
+
const stop = () => {
|
|
156
|
+
void running.close().then(() => {
|
|
157
|
+
if (options.pidfile) {
|
|
158
|
+
rmSync(options.pidfile, { force: true });
|
|
159
|
+
}
|
|
160
|
+
resolve();
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
process.on("SIGINT", stop);
|
|
164
|
+
process.on("SIGTERM", stop);
|
|
165
|
+
});
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// packages/bridge-core/src/probe.ts
|
|
170
|
+
import net2 from "node:net";
|
|
171
|
+
function canConnect(endpoint, timeoutMs = 500) {
|
|
172
|
+
return new Promise((resolve) => {
|
|
173
|
+
const socket = endpoint.kind === "tcp" ? net2.connect({ port: endpoint.port, host: endpoint.host }) : net2.connect({ path: endpoint.kind === "unix" ? endpoint.path : endpoint.name });
|
|
174
|
+
const done = (ok) => {
|
|
175
|
+
socket.destroy();
|
|
176
|
+
resolve(ok);
|
|
177
|
+
};
|
|
178
|
+
socket.once("connect", () => done(true));
|
|
179
|
+
socket.once("error", () => done(false));
|
|
180
|
+
setTimeout(() => done(false), timeoutMs);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// packages/guest-agent-windows/src/schtasks.ts
|
|
185
|
+
var WINDOWS_PIPES = {
|
|
186
|
+
"ssh-agent": "\\\\.\\pipe\\openssh-ssh-agent",
|
|
187
|
+
docker: "\\\\.\\pipe\\docker_engine"
|
|
188
|
+
};
|
|
189
|
+
var WINDOWS_TASK_NAMES = {
|
|
190
|
+
"ssh-agent": "agent-sandbox-ssh-agent",
|
|
191
|
+
docker: "agent-sandbox-docker",
|
|
192
|
+
/** the SYSTEM ONCE task that starts both relays right after install. */
|
|
193
|
+
relays: "agent-sandbox-relays"
|
|
194
|
+
};
|
|
195
|
+
var DOCKER_CONTEXT = "host";
|
|
196
|
+
function schtasksXml(task) {
|
|
197
|
+
const args = [
|
|
198
|
+
"bridge",
|
|
199
|
+
task.role,
|
|
200
|
+
"--port",
|
|
201
|
+
String(task.port),
|
|
202
|
+
"--host-alias",
|
|
203
|
+
task.hostAlias,
|
|
204
|
+
"--listen",
|
|
205
|
+
`pipe:${task.pipe}`,
|
|
206
|
+
"--forward",
|
|
207
|
+
`tcp:${task.hostAlias}:${task.port}`
|
|
208
|
+
].join(" ");
|
|
209
|
+
return [
|
|
210
|
+
'<?xml version="1.0" encoding="UTF-16"?>',
|
|
211
|
+
'<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">',
|
|
212
|
+
" <RegistrationInfo>",
|
|
213
|
+
` <Description>${escapeXml(task.description)}</Description>`,
|
|
214
|
+
" <Author>agent-dev-env</Author>",
|
|
215
|
+
" </RegistrationInfo>",
|
|
216
|
+
" <Triggers>",
|
|
217
|
+
" <LogonTrigger>",
|
|
218
|
+
" <Enabled>true</Enabled>",
|
|
219
|
+
" </LogonTrigger>",
|
|
220
|
+
" </Triggers>",
|
|
221
|
+
" <Principals>",
|
|
222
|
+
' <Principal id="Author">',
|
|
223
|
+
" <LogonType>InteractiveToken</LogonType>",
|
|
224
|
+
" <RunLevel>LeastPrivilege</RunLevel>",
|
|
225
|
+
" </Principal>",
|
|
226
|
+
" </Principals>",
|
|
227
|
+
" <Settings>",
|
|
228
|
+
" <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>",
|
|
229
|
+
" <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>",
|
|
230
|
+
" <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>",
|
|
231
|
+
" <StartWhenAvailable>true</StartWhenAvailable>",
|
|
232
|
+
" <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>",
|
|
233
|
+
" <Enabled>true</Enabled>",
|
|
234
|
+
" </Settings>",
|
|
235
|
+
' <Actions Context="Author">',
|
|
236
|
+
" <Exec>",
|
|
237
|
+
` <Command>${escapeXml(task.nodePath)}</Command>`,
|
|
238
|
+
` <Arguments>${escapeXml(`"${task.agentPath}" ${args}`)}</Arguments>`,
|
|
239
|
+
" </Exec>",
|
|
240
|
+
" </Actions>",
|
|
241
|
+
"</Task>",
|
|
242
|
+
""
|
|
243
|
+
].join("\n");
|
|
244
|
+
}
|
|
245
|
+
function userEnvCommand(name, value) {
|
|
246
|
+
return `[Environment]::SetEnvironmentVariable('${name}','${value}','User')`;
|
|
247
|
+
}
|
|
248
|
+
function dockerContextCommands() {
|
|
249
|
+
const endpoint = `npipe:////./pipe/docker_engine`;
|
|
250
|
+
return [
|
|
251
|
+
`docker context create ${DOCKER_CONTEXT} --docker "host=${endpoint}"`,
|
|
252
|
+
`docker context update ${DOCKER_CONTEXT} --docker "host=${endpoint}"`,
|
|
253
|
+
`docker context use ${DOCKER_CONTEXT}`
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
function relayStartCommand(nodePath, agentPath, tasks) {
|
|
257
|
+
const lines = ["@echo off"];
|
|
258
|
+
for (const task of tasks) {
|
|
259
|
+
lines.push(
|
|
260
|
+
`start "" /b "${nodePath}" "${agentPath}" bridge ${task.role} --port ${task.port} --host-alias ${task.hostAlias} --listen pipe:${task.pipe} --forward tcp:${task.hostAlias}:${task.port}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return lines.join("\r\n") + "\r\n";
|
|
264
|
+
}
|
|
265
|
+
function escapeXml(text) {
|
|
266
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// packages/guest-agent-windows/src/index.ts
|
|
270
|
+
var AGENT_PATH = fileURLToPath(import.meta.url);
|
|
271
|
+
var NODE_PATH = process.execPath;
|
|
272
|
+
var DEFAULTS = {
|
|
273
|
+
agentPort: 4300,
|
|
274
|
+
dockerPort: 4301
|
|
275
|
+
};
|
|
276
|
+
async function main(argv) {
|
|
277
|
+
const [cmd, ...rest] = argv;
|
|
278
|
+
try {
|
|
279
|
+
switch (cmd) {
|
|
280
|
+
case "install":
|
|
281
|
+
return install(rest);
|
|
282
|
+
case "bridge":
|
|
283
|
+
return bridgeCommand(rest);
|
|
284
|
+
case "status":
|
|
285
|
+
return status();
|
|
286
|
+
case "uninstall":
|
|
287
|
+
return uninstall();
|
|
288
|
+
default:
|
|
289
|
+
process.stderr.write(usage());
|
|
290
|
+
return 1;
|
|
291
|
+
}
|
|
292
|
+
} catch (err) {
|
|
293
|
+
process.stderr.write(`guest-agent-windows: ${err.message}
|
|
294
|
+
`);
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function usage() {
|
|
299
|
+
return [
|
|
300
|
+
"usage: guest-agent-windows <install|bridge|status|uninstall> [options]",
|
|
301
|
+
" install [--agent-port N] [--docker-port N] --host-alias A",
|
|
302
|
+
" bridge <ssh-agent|docker> [--listen EP] [--forward EP] [--port N] [--host-alias A]",
|
|
303
|
+
""
|
|
304
|
+
].join("\n");
|
|
305
|
+
}
|
|
306
|
+
function argValue(argv, flag) {
|
|
307
|
+
const index = argv.indexOf(flag);
|
|
308
|
+
return index === -1 ? void 0 : argv[index + 1];
|
|
309
|
+
}
|
|
310
|
+
function taskFor(role, port, hostAlias) {
|
|
311
|
+
return {
|
|
312
|
+
taskName: WINDOWS_TASK_NAMES[role],
|
|
313
|
+
description: role === "ssh-agent" ? "Agent Sandbox SSH agent bridge" : "Agent Sandbox Docker bridge",
|
|
314
|
+
nodePath: NODE_PATH,
|
|
315
|
+
agentPath: AGENT_PATH,
|
|
316
|
+
role,
|
|
317
|
+
pipe: WINDOWS_PIPES[role],
|
|
318
|
+
port,
|
|
319
|
+
hostAlias
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function install(argv) {
|
|
323
|
+
const hostAlias = argValue(argv, "--host-alias");
|
|
324
|
+
if (!hostAlias) {
|
|
325
|
+
throw new Error("--host-alias is required (10.0.2.2 for QEMU, the NAT router otherwise)");
|
|
326
|
+
}
|
|
327
|
+
const agentPort = Number(argValue(argv, "--agent-port") ?? DEFAULTS.agentPort);
|
|
328
|
+
const dockerPort = Number(argValue(argv, "--docker-port") ?? DEFAULTS.dockerPort);
|
|
329
|
+
const tasks = [];
|
|
330
|
+
for (const [role, port] of [
|
|
331
|
+
["ssh-agent", agentPort],
|
|
332
|
+
["docker", dockerPort]
|
|
333
|
+
]) {
|
|
334
|
+
const task = taskFor(role, port, hostAlias);
|
|
335
|
+
tasks.push(task);
|
|
336
|
+
const xmlPath = join(tmpdir(), `${task.taskName}.xml`);
|
|
337
|
+
writeFileSync2(xmlPath, schtasksXml(task), "utf16le");
|
|
338
|
+
try {
|
|
339
|
+
execFileSync("schtasks", ["/Create", "/TN", task.taskName, "/XML", xmlPath, "/F"], {
|
|
340
|
+
stdio: "ignore"
|
|
341
|
+
});
|
|
342
|
+
process.stdout.write(`installed:${role} (task ${task.taskName}, port ${port})
|
|
343
|
+
`);
|
|
344
|
+
} finally {
|
|
345
|
+
rmSync2(xmlPath, { force: true });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
startRelays(tasks);
|
|
349
|
+
setUserEnv("SSH_AUTH_SOCK", WINDOWS_PIPES["ssh-agent"]);
|
|
350
|
+
setupDockerContext();
|
|
351
|
+
return 0;
|
|
352
|
+
}
|
|
353
|
+
function startRelays(tasks) {
|
|
354
|
+
const dir = dirname(AGENT_PATH);
|
|
355
|
+
const cmdPath = join(dir, "start-relays.cmd");
|
|
356
|
+
try {
|
|
357
|
+
mkdirSync(dir, { recursive: true });
|
|
358
|
+
writeFileSync2(cmdPath, relayStartCommand(NODE_PATH, AGENT_PATH, tasks));
|
|
359
|
+
execFileSync(
|
|
360
|
+
"schtasks",
|
|
361
|
+
[
|
|
362
|
+
"/Create",
|
|
363
|
+
"/TN",
|
|
364
|
+
WINDOWS_TASK_NAMES.relays,
|
|
365
|
+
"/SC",
|
|
366
|
+
"ONCE",
|
|
367
|
+
"/ST",
|
|
368
|
+
"00:00",
|
|
369
|
+
"/RU",
|
|
370
|
+
"SYSTEM",
|
|
371
|
+
"/RL",
|
|
372
|
+
"HIGHEST",
|
|
373
|
+
"/F",
|
|
374
|
+
"/TR",
|
|
375
|
+
cmdPath
|
|
376
|
+
],
|
|
377
|
+
{ stdio: "ignore" }
|
|
378
|
+
);
|
|
379
|
+
execFileSync("schtasks", ["/Run", "/TN", WINDOWS_TASK_NAMES.relays], { stdio: "ignore" });
|
|
380
|
+
process.stdout.write("installed:relays (started via a system task)\n");
|
|
381
|
+
} catch (err) {
|
|
382
|
+
process.stderr.write(
|
|
383
|
+
`warning: could not start the relays right away (${err.message}) \u2014 they start at the next logon
|
|
384
|
+
`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function setUserEnv(name, value) {
|
|
389
|
+
const cmd = userEnvCommand(name, value);
|
|
390
|
+
execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", cmd], {
|
|
391
|
+
stdio: "ignore"
|
|
392
|
+
});
|
|
393
|
+
process.stdout.write(`installed:env ${name}
|
|
394
|
+
`);
|
|
395
|
+
}
|
|
396
|
+
function setupDockerContext() {
|
|
397
|
+
const docker = spawnSync("where.exe", ["docker"], { encoding: "utf8" });
|
|
398
|
+
if (docker.status !== 0) {
|
|
399
|
+
process.stderr.write("warning: docker CLI not found \u2014 skipping the docker context\n");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
for (const command of dockerContextCommands()) {
|
|
403
|
+
spawnSync("cmd.exe", ["/c", command], { stdio: "ignore" });
|
|
404
|
+
}
|
|
405
|
+
process.stdout.write("installed:docker context host\n");
|
|
406
|
+
}
|
|
407
|
+
function bridgeCommand(argv) {
|
|
408
|
+
const role = argv[0];
|
|
409
|
+
if (role !== "ssh-agent" && role !== "docker") {
|
|
410
|
+
throw new Error(`unknown bridge role '${role}' (ssh-agent|docker)`);
|
|
411
|
+
}
|
|
412
|
+
const port = Number(
|
|
413
|
+
argValue(argv, "--port") ?? DEFAULTS[role === "ssh-agent" ? "agentPort" : "dockerPort"]
|
|
414
|
+
);
|
|
415
|
+
const hostAlias = argValue(argv, "--host-alias");
|
|
416
|
+
if (!hostAlias) {
|
|
417
|
+
throw new Error("--host-alias is required");
|
|
418
|
+
}
|
|
419
|
+
const listen2 = argValue(argv, "--listen") ?? `pipe:${WINDOWS_PIPES[role]}`;
|
|
420
|
+
const forward = argValue(argv, "--forward") ?? `tcp:${hostAlias}:${port}`;
|
|
421
|
+
return runBridge({
|
|
422
|
+
listen: parseEndpoint(listen2),
|
|
423
|
+
forward: parseEndpoint(forward),
|
|
424
|
+
log: (message) => process.stderr.write(`${message}
|
|
425
|
+
`)
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
async function status() {
|
|
429
|
+
const agentUp = await canConnect(parseEndpoint(`pipe:${WINDOWS_PIPES["ssh-agent"]}`));
|
|
430
|
+
const dockerUp = await canConnect(parseEndpoint(`pipe:${WINDOWS_PIPES.docker}`));
|
|
431
|
+
process.stdout.write(`bridge-status:ssh-agent=${agentUp ? "up" : "down"}
|
|
432
|
+
`);
|
|
433
|
+
process.stdout.write(`bridge-status:docker=${dockerUp ? "up" : "down"}
|
|
434
|
+
`);
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
function uninstall() {
|
|
438
|
+
for (const role of ["ssh-agent", "docker"]) {
|
|
439
|
+
const taskName = WINDOWS_TASK_NAMES[role];
|
|
440
|
+
try {
|
|
441
|
+
execFileSync("schtasks", ["/Delete", "/TN", taskName, "/F"], { stdio: "ignore" });
|
|
442
|
+
} catch {
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
execFileSync("schtasks", ["/Delete", "/TN", WINDOWS_TASK_NAMES.relays, "/F"], {
|
|
447
|
+
stdio: "ignore"
|
|
448
|
+
});
|
|
449
|
+
} catch {
|
|
450
|
+
}
|
|
451
|
+
rmSync2(join(dirname(AGENT_PATH), "start-relays.cmd"), { force: true });
|
|
452
|
+
const cmd = userEnvCommand("SSH_AUTH_SOCK", "");
|
|
453
|
+
try {
|
|
454
|
+
execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", cmd], {
|
|
455
|
+
stdio: "ignore"
|
|
456
|
+
});
|
|
457
|
+
} catch {
|
|
458
|
+
}
|
|
459
|
+
process.stdout.write("uninstalled:ssh-agent,docker\n");
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
462
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
463
|
+
void main(process.argv.slice(2)).then((code) => {
|
|
464
|
+
process.exit(code);
|
|
465
|
+
});
|
|
466
|
+
}
|