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.
Files changed (102) hide show
  1. package/dist/server.js +329 -23
  2. package/dist/web/assets/MonacoEditor-Bpu4vhKo.js +918 -0
  3. package/dist/web/assets/MonacoEditor-CJZrZ56I.css +1 -0
  4. package/dist/web/assets/abap-DLDM7-KI.js +1 -0
  5. package/dist/web/assets/apex-DNDY2TF8.js +1 -0
  6. package/dist/web/assets/azcli-Y6nb8tq_.js +1 -0
  7. package/dist/web/assets/bat-BwHxbl9M.js +1 -0
  8. package/dist/web/assets/bicep-CFznDFnq.js +2 -0
  9. package/dist/web/assets/cameligo-Bf6VGUru.js +1 -0
  10. package/dist/web/assets/clojure-Dnu-v4kV.js +1 -0
  11. package/dist/web/assets/codicon-ngg6Pgfi.ttf +0 -0
  12. package/dist/web/assets/coffee-Bd8akH9Z.js +1 -0
  13. package/dist/web/assets/cpp-BbWJElDN.js +1 -0
  14. package/dist/web/assets/csharp-Co3qMtFm.js +1 -0
  15. package/dist/web/assets/csp-D-4FJmMZ.js +1 -0
  16. package/dist/web/assets/css-DdJfP1eB.js +3 -0
  17. package/dist/web/assets/css.worker-BvV5MPou.js +93 -0
  18. package/dist/web/assets/cssMode-BT_k4Q42.js +1 -0
  19. package/dist/web/assets/cypher-cTPe9QuQ.js +1 -0
  20. package/dist/web/assets/dart-BOtBlQCF.js +1 -0
  21. package/dist/web/assets/dockerfile-BG73LgW2.js +1 -0
  22. package/dist/web/assets/ecl-BEgZUVRK.js +1 -0
  23. package/dist/web/assets/editor.worker-CKy7Pnvo.js +26 -0
  24. package/dist/web/assets/elixir-BkW5O-1t.js +1 -0
  25. package/dist/web/assets/flow9-BeJ5waoc.js +1 -0
  26. package/dist/web/assets/freemarker2-BdkTw1xb.js +3 -0
  27. package/dist/web/assets/fsharp-PahG7c26.js +1 -0
  28. package/dist/web/assets/go-acbASCJo.js +1 -0
  29. package/dist/web/assets/graphql-BxJiqAUM.js +1 -0
  30. package/dist/web/assets/handlebars-DXoyCFgZ.js +1 -0
  31. package/dist/web/assets/hcl-DtV1sZF8.js +1 -0
  32. package/dist/web/assets/html-D7HVWL2_.js +1 -0
  33. package/dist/web/assets/html.worker-BLJhxQJQ.js +470 -0
  34. package/dist/web/assets/htmlMode-Bfg9aePq.js +1 -0
  35. package/dist/web/assets/index-Cf64AFEI.css +32 -0
  36. package/dist/web/assets/index-DAZrr4ue.js +664 -0
  37. package/dist/web/assets/ini-Kd9XrMLS.js +1 -0
  38. package/dist/web/assets/java-CXBNlu9o.js +1 -0
  39. package/dist/web/assets/javascript-BJ-HXNJj.js +1 -0
  40. package/dist/web/assets/json.worker-usMZ-FED.js +58 -0
  41. package/dist/web/assets/jsonMode-C1lebKm2.js +7 -0
  42. package/dist/web/assets/julia-cl7-CwDS.js +1 -0
  43. package/dist/web/assets/kotlin-s7OhZKlX.js +1 -0
  44. package/dist/web/assets/less-9HpZscsL.js +2 -0
  45. package/dist/web/assets/lexon-OrD6JF1K.js +1 -0
  46. package/dist/web/assets/liquid-DGCf7J5D.js +1 -0
  47. package/dist/web/assets/lspLanguageFeatures-C5UYdOKC.js +4 -0
  48. package/dist/web/assets/lua-Cyyb5UIc.js +1 -0
  49. package/dist/web/assets/m3-B8OfTtLu.js +1 -0
  50. package/dist/web/assets/markdown-BFxVWTOG.js +1 -0
  51. package/dist/web/assets/mdx-BHDjeYzF.js +1 -0
  52. package/dist/web/assets/mips-CiqrrVzr.js +1 -0
  53. package/dist/web/assets/msdax-DmeGPVcC.js +1 -0
  54. package/dist/web/assets/mysql-C_tMU-Nz.js +1 -0
  55. package/dist/web/assets/objective-c-BDtDVThU.js +1 -0
  56. package/dist/web/assets/pascal-vHIfCaH5.js +1 -0
  57. package/dist/web/assets/pascaligo-DtZ0uQbO.js +1 -0
  58. package/dist/web/assets/pdf.worker.min-CrMmvqMo.mjs +29 -0
  59. package/dist/web/assets/perl-Ub6l9XKa.js +1 -0
  60. package/dist/web/assets/pgsql-BlNEE0v7.js +1 -0
  61. package/dist/web/assets/php-BBUBE1dy.js +1 -0
  62. package/dist/web/assets/pla-DSh2-awV.js +1 -0
  63. package/dist/web/assets/postiats-CocnycG-.js +1 -0
  64. package/dist/web/assets/powerquery-tScXyioY.js +1 -0
  65. package/dist/web/assets/powershell-COWaemsV.js +1 -0
  66. package/dist/web/assets/protobuf-Brw8urJB.js +2 -0
  67. package/dist/web/assets/pug-8SOpv6rk.js +1 -0
  68. package/dist/web/assets/python-DX69zUMQ.js +1 -0
  69. package/dist/web/assets/qsharp-Bw9ernYp.js +1 -0
  70. package/dist/web/assets/r-j7ic8hl3.js +1 -0
  71. package/dist/web/assets/razor-DYOcA8DH.js +1 -0
  72. package/dist/web/assets/redis-Bu5POkcn.js +1 -0
  73. package/dist/web/assets/redshift-Bs9aos_-.js +1 -0
  74. package/dist/web/assets/restructuredtext-CqXO7rUv.js +1 -0
  75. package/dist/web/assets/ruby-zBfavPgS.js +1 -0
  76. package/dist/web/assets/rust-BzKRNQWT.js +1 -0
  77. package/dist/web/assets/sb-BBc9UKZt.js +1 -0
  78. package/dist/web/assets/scala-D9hQfWCl.js +1 -0
  79. package/dist/web/assets/scheme-BPhDTwHR.js +1 -0
  80. package/dist/web/assets/scss-CBJaRo0y.js +3 -0
  81. package/dist/web/assets/shell-DiJ1NA_G.js +1 -0
  82. package/dist/web/assets/solidity-Db0IVjzk.js +1 -0
  83. package/dist/web/assets/sophia-CnS9iZB_.js +1 -0
  84. package/dist/web/assets/sparql-CJmd_6j2.js +1 -0
  85. package/dist/web/assets/sql-ClhHkBeG.js +1 -0
  86. package/dist/web/assets/st-CHwy0fLd.js +1 -0
  87. package/dist/web/assets/swift-Bqt4WxQ4.js +3 -0
  88. package/dist/web/assets/systemverilog-Bs9z6M-B.js +1 -0
  89. package/dist/web/assets/tcl-Dm6ycUr_.js +1 -0
  90. package/dist/web/assets/ts.worker-DGHjMaqB.js +67731 -0
  91. package/dist/web/assets/tsMode-D7VZcKkg.js +11 -0
  92. package/dist/web/assets/twig-Csy3S7wG.js +1 -0
  93. package/dist/web/assets/typescript-C5srmkoK.js +1 -0
  94. package/dist/web/assets/typespec-Btyra-wh.js +1 -0
  95. package/dist/web/assets/vb-Db0cS2oM.js +1 -0
  96. package/dist/web/assets/wgsl-BTesnYfV.js +298 -0
  97. package/dist/web/assets/xml-Cn2AcXda.js +1 -0
  98. package/dist/web/assets/yaml-DtI773Kq.js +1 -0
  99. package/dist/web/index.html +2 -2
  100. package/package.json +1 -1
  101. package/dist/web/assets/index-CzkdUSiL.js +0 -278
  102. 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
- async exec(command) {
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
- async writeFile(path, contents) {
420
+ writeFile(path, contents) {
306
421
  const b64 = Buffer.from(contents).toString("base64");
307
- const cmd = `printf %s ${quote(b64)} | base64 -d > ${quote(path)}`;
308
- const { stderr, exitCode } = await this.exec.exec(cmd);
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 { kind: "failed", raw: stderr, exitCode, reason: stderr.trim() || "write failed" };
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 Promise.resolve(unsupported("listProcesses is a post-v1 capability"));
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
- exec(command) {
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
- /** Open a PTY as a transport-agnostic {@link PtySession} (no ssh2 types leak). */
444
- async openPty(cols = 80, rows = 24) {
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 result = await entry.adapter.writeFile(asString(body, "path"), new Uint8Array(bytes));
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) {