deskssh 0.1.2 → 0.1.9
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/dist/server.js +329 -23
- package/dist/web/assets/MonacoEditor-Bpu4vhKo.js +918 -0
- package/dist/web/assets/MonacoEditor-CJZrZ56I.css +1 -0
- package/dist/web/assets/abap-DLDM7-KI.js +1 -0
- package/dist/web/assets/apex-DNDY2TF8.js +1 -0
- package/dist/web/assets/azcli-Y6nb8tq_.js +1 -0
- package/dist/web/assets/bat-BwHxbl9M.js +1 -0
- package/dist/web/assets/bicep-CFznDFnq.js +2 -0
- package/dist/web/assets/cameligo-Bf6VGUru.js +1 -0
- package/dist/web/assets/clojure-Dnu-v4kV.js +1 -0
- package/dist/web/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/web/assets/coffee-Bd8akH9Z.js +1 -0
- package/dist/web/assets/cpp-BbWJElDN.js +1 -0
- package/dist/web/assets/csharp-Co3qMtFm.js +1 -0
- package/dist/web/assets/csp-D-4FJmMZ.js +1 -0
- package/dist/web/assets/css-DdJfP1eB.js +3 -0
- package/dist/web/assets/css.worker-BvV5MPou.js +93 -0
- package/dist/web/assets/cssMode-BT_k4Q42.js +1 -0
- package/dist/web/assets/cypher-cTPe9QuQ.js +1 -0
- package/dist/web/assets/dart-BOtBlQCF.js +1 -0
- package/dist/web/assets/dockerfile-BG73LgW2.js +1 -0
- package/dist/web/assets/ecl-BEgZUVRK.js +1 -0
- package/dist/web/assets/editor.worker-CKy7Pnvo.js +26 -0
- package/dist/web/assets/elixir-BkW5O-1t.js +1 -0
- package/dist/web/assets/flow9-BeJ5waoc.js +1 -0
- package/dist/web/assets/freemarker2-BdkTw1xb.js +3 -0
- package/dist/web/assets/fsharp-PahG7c26.js +1 -0
- package/dist/web/assets/go-acbASCJo.js +1 -0
- package/dist/web/assets/graphql-BxJiqAUM.js +1 -0
- package/dist/web/assets/handlebars-DXoyCFgZ.js +1 -0
- package/dist/web/assets/hcl-DtV1sZF8.js +1 -0
- package/dist/web/assets/html-D7HVWL2_.js +1 -0
- package/dist/web/assets/html.worker-BLJhxQJQ.js +470 -0
- package/dist/web/assets/htmlMode-Bfg9aePq.js +1 -0
- package/dist/web/assets/index-Cf64AFEI.css +32 -0
- package/dist/web/assets/index-DAZrr4ue.js +664 -0
- package/dist/web/assets/ini-Kd9XrMLS.js +1 -0
- package/dist/web/assets/java-CXBNlu9o.js +1 -0
- package/dist/web/assets/javascript-BJ-HXNJj.js +1 -0
- package/dist/web/assets/json.worker-usMZ-FED.js +58 -0
- package/dist/web/assets/jsonMode-C1lebKm2.js +7 -0
- package/dist/web/assets/julia-cl7-CwDS.js +1 -0
- package/dist/web/assets/kotlin-s7OhZKlX.js +1 -0
- package/dist/web/assets/less-9HpZscsL.js +2 -0
- package/dist/web/assets/lexon-OrD6JF1K.js +1 -0
- package/dist/web/assets/liquid-DGCf7J5D.js +1 -0
- package/dist/web/assets/lspLanguageFeatures-C5UYdOKC.js +4 -0
- package/dist/web/assets/lua-Cyyb5UIc.js +1 -0
- package/dist/web/assets/m3-B8OfTtLu.js +1 -0
- package/dist/web/assets/markdown-BFxVWTOG.js +1 -0
- package/dist/web/assets/mdx-BHDjeYzF.js +1 -0
- package/dist/web/assets/mips-CiqrrVzr.js +1 -0
- package/dist/web/assets/msdax-DmeGPVcC.js +1 -0
- package/dist/web/assets/mysql-C_tMU-Nz.js +1 -0
- package/dist/web/assets/objective-c-BDtDVThU.js +1 -0
- package/dist/web/assets/pascal-vHIfCaH5.js +1 -0
- package/dist/web/assets/pascaligo-DtZ0uQbO.js +1 -0
- package/dist/web/assets/pdf.worker.min-CrMmvqMo.mjs +29 -0
- package/dist/web/assets/perl-Ub6l9XKa.js +1 -0
- package/dist/web/assets/pgsql-BlNEE0v7.js +1 -0
- package/dist/web/assets/php-BBUBE1dy.js +1 -0
- package/dist/web/assets/pla-DSh2-awV.js +1 -0
- package/dist/web/assets/postiats-CocnycG-.js +1 -0
- package/dist/web/assets/powerquery-tScXyioY.js +1 -0
- package/dist/web/assets/powershell-COWaemsV.js +1 -0
- package/dist/web/assets/protobuf-Brw8urJB.js +2 -0
- package/dist/web/assets/pug-8SOpv6rk.js +1 -0
- package/dist/web/assets/python-DX69zUMQ.js +1 -0
- package/dist/web/assets/qsharp-Bw9ernYp.js +1 -0
- package/dist/web/assets/r-j7ic8hl3.js +1 -0
- package/dist/web/assets/razor-DYOcA8DH.js +1 -0
- package/dist/web/assets/redis-Bu5POkcn.js +1 -0
- package/dist/web/assets/redshift-Bs9aos_-.js +1 -0
- package/dist/web/assets/restructuredtext-CqXO7rUv.js +1 -0
- package/dist/web/assets/ruby-zBfavPgS.js +1 -0
- package/dist/web/assets/rust-BzKRNQWT.js +1 -0
- package/dist/web/assets/sb-BBc9UKZt.js +1 -0
- package/dist/web/assets/scala-D9hQfWCl.js +1 -0
- package/dist/web/assets/scheme-BPhDTwHR.js +1 -0
- package/dist/web/assets/scss-CBJaRo0y.js +3 -0
- package/dist/web/assets/shell-DiJ1NA_G.js +1 -0
- package/dist/web/assets/solidity-Db0IVjzk.js +1 -0
- package/dist/web/assets/sophia-CnS9iZB_.js +1 -0
- package/dist/web/assets/sparql-CJmd_6j2.js +1 -0
- package/dist/web/assets/sql-ClhHkBeG.js +1 -0
- package/dist/web/assets/st-CHwy0fLd.js +1 -0
- package/dist/web/assets/swift-Bqt4WxQ4.js +3 -0
- package/dist/web/assets/systemverilog-Bs9z6M-B.js +1 -0
- package/dist/web/assets/tcl-Dm6ycUr_.js +1 -0
- package/dist/web/assets/ts.worker-DGHjMaqB.js +67731 -0
- package/dist/web/assets/tsMode-D7VZcKkg.js +11 -0
- package/dist/web/assets/twig-Csy3S7wG.js +1 -0
- package/dist/web/assets/typescript-C5srmkoK.js +1 -0
- package/dist/web/assets/typespec-Btyra-wh.js +1 -0
- package/dist/web/assets/vb-Db0cS2oM.js +1 -0
- package/dist/web/assets/wgsl-BTesnYfV.js +298 -0
- package/dist/web/assets/xml-Cn2AcXda.js +1 -0
- package/dist/web/assets/yaml-DtI773Kq.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-CzkdUSiL.js +0 -278
- package/dist/web/assets/index-u2N9bzsG.css +0 -32
package/dist/server.js
CHANGED
|
@@ -73,10 +73,11 @@ var TransparencyLog = class {
|
|
|
73
73
|
};
|
|
74
74
|
function withTransparency(executor, log, host) {
|
|
75
75
|
return {
|
|
76
|
-
|
|
76
|
+
// `input` (e.g. a sudo password) is forwarded to the host but never logged.
|
|
77
|
+
async exec(command, input) {
|
|
77
78
|
const startedAt = Date.now();
|
|
78
79
|
try {
|
|
79
|
-
const result = await executor.exec(command);
|
|
80
|
+
const result = await executor.exec(command, input);
|
|
80
81
|
log.record({
|
|
81
82
|
command,
|
|
82
83
|
host,
|
|
@@ -123,6 +124,35 @@ async function runParsed(executor, command, parser) {
|
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
// ../core/dist/adapters/shell.js
|
|
128
|
+
function quote(arg) {
|
|
129
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ../core/dist/privilege/elevation.js
|
|
133
|
+
function withElevation(executor, password) {
|
|
134
|
+
return {
|
|
135
|
+
exec(command) {
|
|
136
|
+
return executor.exec(`sudo -S -p '' sh -c ${quote(command)}`, `${password}
|
|
137
|
+
`);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
var SUDO_GROUPS = /* @__PURE__ */ new Set(["sudo", "wheel", "admin", "root"]);
|
|
142
|
+
async function detectPrivilege(executor) {
|
|
143
|
+
const probe = 'echo "UID=$(id -u)"; echo "GROUPS=$(id -nG 2>/dev/null)"; command -v sudo >/dev/null 2>&1 && echo HAVE_SUDO; command -v su >/dev/null 2>&1 && echo HAVE_SU';
|
|
144
|
+
const { stdout } = await executor.exec(probe);
|
|
145
|
+
const isRoot = /UID=0\b/.test(stdout);
|
|
146
|
+
const groups = (/GROUPS=(.*)/.exec(stdout)?.[1] ?? "").trim().split(/\s+/).filter(Boolean);
|
|
147
|
+
const haveSudo = /\bHAVE_SUDO\b/.test(stdout);
|
|
148
|
+
const haveSu = /\bHAVE_SU\b/.test(stdout);
|
|
149
|
+
return {
|
|
150
|
+
isRoot,
|
|
151
|
+
canSudo: isRoot || haveSudo && groups.some((g) => SUDO_GROUPS.has(g)),
|
|
152
|
+
escalationAvailable: haveSudo || haveSu
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
126
156
|
// ../core/dist/adapters/os.js
|
|
127
157
|
function parseOsRelease(raw) {
|
|
128
158
|
const out = {};
|
|
@@ -172,11 +202,6 @@ async function detectOs(executor) {
|
|
|
172
202
|
};
|
|
173
203
|
}
|
|
174
204
|
|
|
175
|
-
// ../core/dist/adapters/shell.js
|
|
176
|
-
function quote(arg) {
|
|
177
|
-
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
205
|
// ../core/dist/adapters/debian.js
|
|
181
206
|
var FIND_PRINTF = String.raw`%y\t%s\t%m\t%u\t%g\t%T@\t%f\n`;
|
|
182
207
|
var STAT_PRINTF = String.raw`%F\t%s\t%a\t%U\t%G\t%Y\t%n`;
|
|
@@ -287,6 +312,96 @@ function parseSystemMetrics(stdout) {
|
|
|
287
312
|
};
|
|
288
313
|
}
|
|
289
314
|
var METRICS_COMMAND = "echo ===UPTIME===; cat /proc/uptime; echo ===LOAD===; cat /proc/loadavg; echo ===MEM===; cat /proc/meminfo";
|
|
315
|
+
var PS_COMMAND = "ps -eo pid=,user=,pcpu=,pmem=,args=";
|
|
316
|
+
function parseProcessLine(line) {
|
|
317
|
+
const m = /^\s*(\d+)\s+(\S+)\s+([\d.]+)\s+([\d.]+)\s+(.*)$/.exec(line);
|
|
318
|
+
if (!m)
|
|
319
|
+
throw new Error(`Unexpected ps output: ${line}`);
|
|
320
|
+
return {
|
|
321
|
+
pid: Number.parseInt(m[1] ?? "", 10) || 0,
|
|
322
|
+
user: m[2] ?? "",
|
|
323
|
+
cpu: Number.parseFloat(m[3] ?? "0") || 0,
|
|
324
|
+
mem: Number.parseFloat(m[4] ?? "0") || 0,
|
|
325
|
+
command: m[5] ?? ""
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function parseProcesses(stdout) {
|
|
329
|
+
return stdout.split("\n").filter((line) => line.trim().length > 0).map(parseProcessLine);
|
|
330
|
+
}
|
|
331
|
+
var SYSINFO_COMMAND = [
|
|
332
|
+
"echo ===HOST===; hostname",
|
|
333
|
+
'echo ===OS===; . /etc/os-release 2>/dev/null; echo "$PRETTY_NAME"',
|
|
334
|
+
"echo ===KERNEL===; uname -r",
|
|
335
|
+
"echo ===UPTIME===; cat /proc/uptime",
|
|
336
|
+
'echo ===PKGS===; dpkg --list 2>/dev/null | grep -c "^ii"',
|
|
337
|
+
'echo ===SHELL===; echo "${SHELL##*/}"',
|
|
338
|
+
'echo ===CPU===; grep -m1 "model name" /proc/cpuinfo | cut -d: -f2; grep -c "^processor" /proc/cpuinfo',
|
|
339
|
+
'echo ===MEM===; grep -E "MemTotal|MemAvailable" /proc/meminfo',
|
|
340
|
+
"echo ===DISK===; df -P / | tail -1",
|
|
341
|
+
"echo ===IP===; hostname -I 2>/dev/null"
|
|
342
|
+
].join("; ");
|
|
343
|
+
function sectionsOf(stdout) {
|
|
344
|
+
const out = /* @__PURE__ */ new Map();
|
|
345
|
+
let current = "";
|
|
346
|
+
for (const line of stdout.split("\n")) {
|
|
347
|
+
const marker = /^===(\w+)===$/.exec(line);
|
|
348
|
+
if (marker?.[1]) {
|
|
349
|
+
current = marker[1];
|
|
350
|
+
out.set(current, []);
|
|
351
|
+
} else if (current) {
|
|
352
|
+
out.get(current)?.push(line);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
function parseSystemInfo(stdout) {
|
|
358
|
+
const s = sectionsOf(stdout);
|
|
359
|
+
const line = (k, i = 0) => (s.get(k)?.[i] ?? "").trim();
|
|
360
|
+
const mem = /* @__PURE__ */ new Map();
|
|
361
|
+
for (const l of s.get("MEM") ?? []) {
|
|
362
|
+
const m = /^(\w+):\s+(\d+)\s*kB$/.exec(l.trim());
|
|
363
|
+
if (m?.[1] && m[2])
|
|
364
|
+
mem.set(m[1], Number.parseInt(m[2], 10) * 1024);
|
|
365
|
+
}
|
|
366
|
+
const memTotal = mem.get("MemTotal") ?? 0;
|
|
367
|
+
const memAvail = mem.get("MemAvailable") ?? 0;
|
|
368
|
+
const disk = line("DISK").split(/\s+/);
|
|
369
|
+
const ip = line("IP").split(/\s+/)[0] ?? "";
|
|
370
|
+
if (!line("HOST") && memTotal === 0)
|
|
371
|
+
throw new Error("Empty system-info output");
|
|
372
|
+
return {
|
|
373
|
+
hostname: line("HOST"),
|
|
374
|
+
prettyName: line("OS"),
|
|
375
|
+
kernel: line("KERNEL"),
|
|
376
|
+
uptimeSeconds: Math.round(Number.parseFloat(line("UPTIME").split(/\s+/)[0] ?? "0") || 0),
|
|
377
|
+
packages: Number.parseInt(line("PKGS"), 10) || 0,
|
|
378
|
+
shell: line("SHELL"),
|
|
379
|
+
cpuModel: line("CPU"),
|
|
380
|
+
cpuCount: Number.parseInt(line("CPU", 1), 10) || 1,
|
|
381
|
+
memTotalBytes: memTotal,
|
|
382
|
+
memUsedBytes: Math.max(0, memTotal - memAvail),
|
|
383
|
+
diskTotalBytes: (Number.parseInt(disk[1] ?? "0", 10) || 0) * 1024,
|
|
384
|
+
diskUsedBytes: (Number.parseInt(disk[2] ?? "0", 10) || 0) * 1024,
|
|
385
|
+
localIp: ip
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function parseServiceState(name, stdout) {
|
|
389
|
+
const kv = /* @__PURE__ */ new Map();
|
|
390
|
+
for (const line of stdout.split("\n")) {
|
|
391
|
+
const eq = line.indexOf("=");
|
|
392
|
+
if (eq > 0)
|
|
393
|
+
kv.set(line.slice(0, eq), line.slice(eq + 1).trim());
|
|
394
|
+
}
|
|
395
|
+
const active = kv.get("ActiveState");
|
|
396
|
+
if (active === void 0)
|
|
397
|
+
throw new Error("Incomplete systemctl show output");
|
|
398
|
+
return {
|
|
399
|
+
name,
|
|
400
|
+
active: active === "active",
|
|
401
|
+
enabled: kv.get("UnitFileState") === "enabled",
|
|
402
|
+
status: kv.get("SubState") || active
|
|
403
|
+
};
|
|
404
|
+
}
|
|
290
405
|
var DebianAdapter = class {
|
|
291
406
|
exec;
|
|
292
407
|
constructor(exec) {
|
|
@@ -302,20 +417,62 @@ var DebianAdapter = class {
|
|
|
302
417
|
async readFile(path) {
|
|
303
418
|
return runParsed(this.exec, `base64 -w0 ${quote(path)}`, (b64) => Uint8Array.from(Buffer.from(b64.trim(), "base64")));
|
|
304
419
|
}
|
|
305
|
-
|
|
420
|
+
writeFile(path, contents) {
|
|
306
421
|
const b64 = Buffer.from(contents).toString("base64");
|
|
307
|
-
|
|
308
|
-
|
|
422
|
+
return this.runVoid(`printf %s ${quote(b64)} | base64 -d > ${quote(path)}`);
|
|
423
|
+
}
|
|
424
|
+
makeDir(path) {
|
|
425
|
+
return this.runVoid(`mkdir -p ${quote(path)}`);
|
|
426
|
+
}
|
|
427
|
+
createFile(path) {
|
|
428
|
+
return this.runVoid(`touch ${quote(path)}`);
|
|
429
|
+
}
|
|
430
|
+
move(from, to) {
|
|
431
|
+
return this.runVoid(`mv -n ${quote(from)} ${quote(to)}`);
|
|
432
|
+
}
|
|
433
|
+
copy(from, to) {
|
|
434
|
+
return this.runVoid(`cp -a -n ${quote(from)} ${quote(to)}`);
|
|
435
|
+
}
|
|
436
|
+
remove(path) {
|
|
437
|
+
return this.runVoid(`rm -rf ${quote(path)}`);
|
|
438
|
+
}
|
|
439
|
+
/** Run a command whose only outcome is success/failure (no parsed value). */
|
|
440
|
+
async runVoid(command) {
|
|
441
|
+
const { stdout, stderr, exitCode } = await this.exec.exec(command);
|
|
309
442
|
if (exitCode !== 0) {
|
|
310
|
-
return {
|
|
443
|
+
return {
|
|
444
|
+
kind: "failed",
|
|
445
|
+
raw: stderr || stdout,
|
|
446
|
+
exitCode,
|
|
447
|
+
reason: stderr.trim() || "command failed"
|
|
448
|
+
};
|
|
311
449
|
}
|
|
312
|
-
return ok(void 0,
|
|
450
|
+
return ok(void 0, stdout);
|
|
313
451
|
}
|
|
314
452
|
systemMetrics() {
|
|
315
453
|
return runParsed(this.exec, METRICS_COMMAND, parseSystemMetrics);
|
|
316
454
|
}
|
|
455
|
+
systemInfo() {
|
|
456
|
+
return runParsed(this.exec, SYSINFO_COMMAND, parseSystemInfo);
|
|
457
|
+
}
|
|
317
458
|
listProcesses() {
|
|
318
|
-
return
|
|
459
|
+
return runParsed(this.exec, PS_COMMAND, parseProcesses);
|
|
460
|
+
}
|
|
461
|
+
signalProcess(pid, signal) {
|
|
462
|
+
return this.runVoid(`kill -s ${signal} ${Math.trunc(pid)}`);
|
|
463
|
+
}
|
|
464
|
+
async serviceAction(name, action) {
|
|
465
|
+
const act = await this.exec.exec(`systemctl ${action} ${quote(name)}`);
|
|
466
|
+
if (act.exitCode !== 0) {
|
|
467
|
+
return {
|
|
468
|
+
kind: "failed",
|
|
469
|
+
raw: act.stderr || act.stdout,
|
|
470
|
+
exitCode: act.exitCode,
|
|
471
|
+
reason: act.stderr.trim() || `service ${action} failed`
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const show = `systemctl show ${quote(name)} --property=ActiveState,UnitFileState,SubState`;
|
|
475
|
+
return runParsed(this.exec, show, (out) => parseServiceState(name, out));
|
|
319
476
|
}
|
|
320
477
|
listServices() {
|
|
321
478
|
return Promise.resolve(unsupported("listServices is a post-v1 capability"));
|
|
@@ -330,8 +487,16 @@ function createUnsupportedAdapter(reason) {
|
|
|
330
487
|
stat: fail,
|
|
331
488
|
readFile: fail,
|
|
332
489
|
writeFile: fail,
|
|
490
|
+
makeDir: fail,
|
|
491
|
+
createFile: fail,
|
|
492
|
+
move: fail,
|
|
493
|
+
copy: fail,
|
|
494
|
+
remove: fail,
|
|
333
495
|
systemMetrics: fail,
|
|
496
|
+
systemInfo: fail,
|
|
334
497
|
listProcesses: fail,
|
|
498
|
+
signalProcess: fail,
|
|
499
|
+
serviceAction: fail,
|
|
335
500
|
listServices: fail
|
|
336
501
|
};
|
|
337
502
|
}
|
|
@@ -409,8 +574,10 @@ var SshSession = class _SshSession {
|
|
|
409
574
|
}).connect(config);
|
|
410
575
|
});
|
|
411
576
|
}
|
|
412
|
-
/** Run a command, capturing stdout/stderr and the exit code (FR-030 building block).
|
|
413
|
-
|
|
577
|
+
/** Run a command, capturing stdout/stderr and the exit code (FR-030 building block).
|
|
578
|
+
* `input`, when given, is written to stdin and stdin is closed (e.g. a password
|
|
579
|
+
* for `sudo -S`); it is never part of the command string. */
|
|
580
|
+
exec(command, input) {
|
|
414
581
|
return new Promise((resolve, reject) => {
|
|
415
582
|
this.client.exec(command, (err, stream) => {
|
|
416
583
|
if (err)
|
|
@@ -427,6 +594,8 @@ var SshSession = class _SshSession {
|
|
|
427
594
|
stream.stderr.on("data", (chunk) => {
|
|
428
595
|
stderr += chunk.toString("utf8");
|
|
429
596
|
});
|
|
597
|
+
if (input !== void 0)
|
|
598
|
+
stream.end(input);
|
|
430
599
|
});
|
|
431
600
|
});
|
|
432
601
|
}
|
|
@@ -440,9 +609,17 @@ var SshSession = class _SshSession {
|
|
|
440
609
|
});
|
|
441
610
|
});
|
|
442
611
|
}
|
|
443
|
-
/**
|
|
444
|
-
|
|
612
|
+
/**
|
|
613
|
+
* Open a PTY as a transport-agnostic {@link PtySession} (no ssh2 types leak).
|
|
614
|
+
* When `cwd` is given the shell starts in that directory (e.g. "Open in
|
|
615
|
+
* terminal" from the file manager); the cd is issued as the first input and
|
|
616
|
+
* the screen cleared so the user sees a clean prompt.
|
|
617
|
+
*/
|
|
618
|
+
async openPty(cols = 80, rows = 24, cwd) {
|
|
445
619
|
const stream = await this.shell({ cols, rows, term: "xterm-256color" });
|
|
620
|
+
if (cwd)
|
|
621
|
+
stream.write(`cd ${quote(cwd)} 2>/dev/null; clear
|
|
622
|
+
`);
|
|
446
623
|
return {
|
|
447
624
|
onData: (listener) => {
|
|
448
625
|
stream.on("data", (chunk) => listener(chunk.toString("utf8")));
|
|
@@ -538,8 +715,10 @@ function createSshOpener(store) {
|
|
|
538
715
|
home,
|
|
539
716
|
os,
|
|
540
717
|
adapter,
|
|
718
|
+
executor,
|
|
719
|
+
endpoint: { host: req.host, port: req.port ?? 22 },
|
|
541
720
|
log,
|
|
542
|
-
openPty: (cols, rows) => session.openPty(cols, rows),
|
|
721
|
+
openPty: (cols, rows, cwd) => session.openPty(cols, rows, cwd),
|
|
543
722
|
close: () => session.close()
|
|
544
723
|
};
|
|
545
724
|
};
|
|
@@ -597,17 +776,18 @@ function attachTerminal(server, manager) {
|
|
|
597
776
|
if (url.pathname !== TERMINAL_PATH)
|
|
598
777
|
return;
|
|
599
778
|
const sessionId = url.searchParams.get("sessionId") ?? "";
|
|
779
|
+
const cwd = url.searchParams.get("cwd") ?? void 0;
|
|
600
780
|
const entry = manager.get(sessionId);
|
|
601
781
|
if (!entry?.openPty) {
|
|
602
782
|
socket.destroy();
|
|
603
783
|
return;
|
|
604
784
|
}
|
|
605
|
-
wss.handleUpgrade(req, socket, head, (ws) => bridge(ws, entry.openPty));
|
|
785
|
+
wss.handleUpgrade(req, socket, head, (ws) => bridge(ws, entry.openPty, cwd));
|
|
606
786
|
});
|
|
607
787
|
return wss;
|
|
608
788
|
}
|
|
609
|
-
function bridge(ws, openPty) {
|
|
610
|
-
void openPty(80, 24).then((pty) => {
|
|
789
|
+
function bridge(ws, openPty, cwd) {
|
|
790
|
+
void openPty(80, 24, cwd).then((pty) => {
|
|
611
791
|
pty.onData((chunk) => {
|
|
612
792
|
if (ws.readyState === ws.OPEN)
|
|
613
793
|
ws.send(JSON.stringify({ type: "output", data: chunk }));
|
|
@@ -660,6 +840,27 @@ function serveStatic(dir, urlPath, res) {
|
|
|
660
840
|
return true;
|
|
661
841
|
}
|
|
662
842
|
|
|
843
|
+
// ../server/dist/elevate.js
|
|
844
|
+
async function makeElevated(entry, elevate) {
|
|
845
|
+
if (elevate.kind === "current") {
|
|
846
|
+
return {
|
|
847
|
+
adapter: selectAdapter(entry.os, withElevation(entry.executor, elevate.password)),
|
|
848
|
+
cleanup: () => {
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
const session = await SshSession.connect({
|
|
853
|
+
host: entry.endpoint.host,
|
|
854
|
+
port: entry.endpoint.port,
|
|
855
|
+
username: elevate.user,
|
|
856
|
+
auth: { kind: "password", password: elevate.password },
|
|
857
|
+
verifyHostKey: () => true
|
|
858
|
+
});
|
|
859
|
+
const exec = withTransparency(session, entry.log, `${elevate.user}@${entry.endpoint.host}`);
|
|
860
|
+
const executor = elevate.user === "root" ? exec : withElevation(exec, elevate.password);
|
|
861
|
+
return { adapter: selectAdapter(entry.os, executor), cleanup: () => session.close() };
|
|
862
|
+
}
|
|
863
|
+
|
|
663
864
|
// ../server/dist/gateway.js
|
|
664
865
|
var MAX_BODY_BYTES = 1e6;
|
|
665
866
|
var HttpError = class extends Error {
|
|
@@ -710,6 +911,38 @@ function asString(obj, key) {
|
|
|
710
911
|
}
|
|
711
912
|
return value;
|
|
712
913
|
}
|
|
914
|
+
function asOneOf(obj, key, allowed) {
|
|
915
|
+
const value = obj[key];
|
|
916
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
917
|
+
throw new HttpError(400, `Invalid "${key}": expected one of ${allowed.join(", ")}`);
|
|
918
|
+
}
|
|
919
|
+
return value;
|
|
920
|
+
}
|
|
921
|
+
function parseElevate(value) {
|
|
922
|
+
if (value === void 0 || value === null)
|
|
923
|
+
return void 0;
|
|
924
|
+
if (typeof value !== "object")
|
|
925
|
+
throw new HttpError(400, 'Invalid "elevate"');
|
|
926
|
+
const o = value;
|
|
927
|
+
if (o["kind"] === "current" && typeof o["password"] === "string") {
|
|
928
|
+
return { kind: "current", password: o["password"] };
|
|
929
|
+
}
|
|
930
|
+
if (o["kind"] === "user" && typeof o["user"] === "string" && typeof o["password"] === "string") {
|
|
931
|
+
return { kind: "user", user: o["user"], password: o["password"] };
|
|
932
|
+
}
|
|
933
|
+
throw new HttpError(400, 'Invalid "elevate": expected { kind: "current"|"user", \u2026 }');
|
|
934
|
+
}
|
|
935
|
+
async function runCap(entry, body, call) {
|
|
936
|
+
const elevate = parseElevate(body["elevate"]);
|
|
937
|
+
if (!elevate)
|
|
938
|
+
return call(entry.adapter);
|
|
939
|
+
const { adapter, cleanup } = await makeElevated(entry, elevate);
|
|
940
|
+
try {
|
|
941
|
+
return await call(adapter);
|
|
942
|
+
} finally {
|
|
943
|
+
cleanup();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
713
946
|
function createGateway(deps = {}) {
|
|
714
947
|
const manager = deps.manager ?? new SessionManager();
|
|
715
948
|
const opener = deps.opener ?? createSshOpener(new FileKnownHosts());
|
|
@@ -794,10 +1027,83 @@ async function handle(req, res, manager, opener, staticDir) {
|
|
|
794
1027
|
if (route === "POST /api/writefile") {
|
|
795
1028
|
const body = await readJsonBody(req);
|
|
796
1029
|
const entry = requireSession(manager, body);
|
|
797
|
-
const bytes = Buffer.from(asString(body, "base64"), "base64");
|
|
798
|
-
const
|
|
1030
|
+
const bytes = new Uint8Array(Buffer.from(asString(body, "base64"), "base64"));
|
|
1031
|
+
const path = asString(body, "path");
|
|
1032
|
+
const result = await runCap(entry, body, (a) => a.writeFile(path, bytes));
|
|
799
1033
|
return sendJson(res, 200, { result });
|
|
800
1034
|
}
|
|
1035
|
+
if (route === "POST /api/mkdir") {
|
|
1036
|
+
const body = await readJsonBody(req);
|
|
1037
|
+
const entry = requireSession(manager, body);
|
|
1038
|
+
const path = asString(body, "path");
|
|
1039
|
+
return sendJson(res, 200, { result: await runCap(entry, body, (a) => a.makeDir(path)) });
|
|
1040
|
+
}
|
|
1041
|
+
if (route === "POST /api/createfile") {
|
|
1042
|
+
const body = await readJsonBody(req);
|
|
1043
|
+
const entry = requireSession(manager, body);
|
|
1044
|
+
const path = asString(body, "path");
|
|
1045
|
+
return sendJson(res, 200, { result: await runCap(entry, body, (a) => a.createFile(path)) });
|
|
1046
|
+
}
|
|
1047
|
+
if (route === "POST /api/move") {
|
|
1048
|
+
const body = await readJsonBody(req);
|
|
1049
|
+
const entry = requireSession(manager, body);
|
|
1050
|
+
const from = asString(body, "from");
|
|
1051
|
+
const to = asString(body, "to");
|
|
1052
|
+
return sendJson(res, 200, { result: await runCap(entry, body, (a) => a.move(from, to)) });
|
|
1053
|
+
}
|
|
1054
|
+
if (route === "POST /api/copy") {
|
|
1055
|
+
const body = await readJsonBody(req);
|
|
1056
|
+
const entry = requireSession(manager, body);
|
|
1057
|
+
const from = asString(body, "from");
|
|
1058
|
+
const to = asString(body, "to");
|
|
1059
|
+
return sendJson(res, 200, { result: await runCap(entry, body, (a) => a.copy(from, to)) });
|
|
1060
|
+
}
|
|
1061
|
+
if (route === "POST /api/remove") {
|
|
1062
|
+
const body = await readJsonBody(req);
|
|
1063
|
+
const entry = requireSession(manager, body);
|
|
1064
|
+
const path = asString(body, "path");
|
|
1065
|
+
return sendJson(res, 200, { result: await runCap(entry, body, (a) => a.remove(path)) });
|
|
1066
|
+
}
|
|
1067
|
+
if (route === "POST /api/processes") {
|
|
1068
|
+
const body = await readJsonBody(req);
|
|
1069
|
+
const entry = requireSession(manager, body);
|
|
1070
|
+
return sendJson(res, 200, { result: await entry.adapter.listProcesses() });
|
|
1071
|
+
}
|
|
1072
|
+
if (route === "POST /api/signal") {
|
|
1073
|
+
const body = await readJsonBody(req);
|
|
1074
|
+
const entry = requireSession(manager, body);
|
|
1075
|
+
const pid = Number(body["pid"]);
|
|
1076
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
1077
|
+
throw new HttpError(400, 'Invalid "pid"');
|
|
1078
|
+
const signal = asOneOf(body, "signal", ["TERM", "KILL", "HUP"]);
|
|
1079
|
+
return sendJson(res, 200, {
|
|
1080
|
+
result: await runCap(entry, body, (a) => a.signalProcess(pid, signal))
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
if (route === "POST /api/service") {
|
|
1084
|
+
const body = await readJsonBody(req);
|
|
1085
|
+
const entry = requireSession(manager, body);
|
|
1086
|
+
const name = asString(body, "name");
|
|
1087
|
+
const action = asOneOf(body, "action", ["start", "stop", "restart"]);
|
|
1088
|
+
return sendJson(res, 200, {
|
|
1089
|
+
result: await runCap(entry, body, (a) => a.serviceAction(name, action))
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
if (route === "POST /api/privilege") {
|
|
1093
|
+
const body = await readJsonBody(req);
|
|
1094
|
+
const entry = requireSession(manager, body);
|
|
1095
|
+
return sendJson(res, 200, { privilege: await detectPrivilege(entry.executor) });
|
|
1096
|
+
}
|
|
1097
|
+
if (route === "POST /api/systeminfo") {
|
|
1098
|
+
const body = await readJsonBody(req);
|
|
1099
|
+
const entry = requireSession(manager, body);
|
|
1100
|
+
return sendJson(res, 200, { result: await entry.adapter.systemInfo() });
|
|
1101
|
+
}
|
|
1102
|
+
if (route === "POST /api/transparency") {
|
|
1103
|
+
const body = await readJsonBody(req);
|
|
1104
|
+
const entry = requireSession(manager, body);
|
|
1105
|
+
return sendJson(res, 200, { transparency: entry.log.list() });
|
|
1106
|
+
}
|
|
801
1107
|
sendJson(res, 404, { error: `No route for ${route}` });
|
|
802
1108
|
}
|
|
803
1109
|
function requireSession(manager, body) {
|