querysub 0.655.0 → 0.657.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.655.0",
3
+ "version": "0.657.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -79,8 +79,8 @@
79
79
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
80
80
  "pako": "^2.1.0",
81
81
  "peggy": "^5.0.6",
82
- "sliftutils": "^1.7.127",
83
- "socket-function": "^1.2.34",
82
+ "sliftutils": "^1.7.130",
83
+ "socket-function": "^1.2.35",
84
84
  "terser": "^5.31.0",
85
85
  "typenode": "^6.6.1",
86
86
  "typesafecss": "^0.32.0",
@@ -14,7 +14,7 @@ import { formatTime } from "socket-function/src/formatting/format";
14
14
  import type { DebugFunctionShardInfo } from "../3-path-functions/PathFunctionRunner";
15
15
  import type { AuthoritySpec } from "../0-path-value-core/PathRouter";
16
16
  import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
17
- import { isNoNetwork } from "../config";
17
+ import { isNoNetwork, isPublic } from "../config";
18
18
  import { getDomain } from "../config";
19
19
  import { getDebuggerUrl } from "../diagnostics/listenOnDebugger";
20
20
  import { hackDevtoolsWebsocketForward } from "./oneTimeForward";
@@ -176,10 +176,12 @@ class NodeCapabilitiesControllerBase {
176
176
  }
177
177
 
178
178
  public async getInspectURL() {
179
+ assertAssistedDebugConnectionAllowed();
179
180
  return await getDebuggerUrl();
180
181
  }
181
182
 
182
183
  public async exposeExternalDebugPortOnce(forExternalIP: string) {
184
+ assertAssistedDebugConnectionAllowed();
183
185
  // https://notdevtools.com/devtools/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:62448/22895aed-f8da-4dfb-8d50-e432a9c2d827
184
186
  let debugURL = await this.getInspectURL();
185
187
 
@@ -203,6 +205,13 @@ class NodeCapabilitiesControllerBase {
203
205
  }
204
206
  let lastExposed: (() => void) | undefined;
205
207
 
208
+ /** The assisted debug connection (opening the inspector, forwarding the port externally, TLS proxying) only exists because public servers can't be reached directly - on a non-public server the debugger can be attached directly, and the forwarding machinery would only add attack surface. */
209
+ export function assertAssistedDebugConnectionAllowed() {
210
+ if (!isPublic()) {
211
+ throw new Error(`Non-public server, connect directly. Don't use this assisted connection code.`);
212
+ }
213
+ }
214
+
206
215
 
207
216
  let getFunctionRunnerShards = () => [] as DebugFunctionShardInfo[];
208
217
  export function set_debug_getFunctionRunnerShards(fnc: () => DebugFunctionShardInfo[]) {
@@ -4,6 +4,7 @@ import { Querysub } from "../4-querysub/Querysub";
4
4
  import { runPromise } from "../functional/runCommand";
5
5
  import fs from "fs";
6
6
  import os from "os";
7
+ import path from "path";
7
8
  import readline from "readline";
8
9
  import open from "open";
9
10
  import { fsExistsAsync } from "../fs";
@@ -113,6 +114,12 @@ async function writeRemoteFile(sshRemote: string, remotePath: string, content: s
113
114
  await runPromise(`ssh ${sshRemote} "echo ${encoded} | base64 -d | ${write}"`);
114
115
  }
115
116
 
117
+ /** Reads one value off the remote: the last line of the command's output, since banners and connection chatter come before it. */
118
+ async function sshValue(sshRemote: string, command: string, config?: { nothrow?: boolean }): Promise<string> {
119
+ let output = await runPromise(`ssh ${sshRemote} "${command}"`, { nothrow: config?.nothrow });
120
+ return (output.trim().split("\n").at(-1) || "").trim();
121
+ }
122
+
116
123
  function getGitHubKeyCachePath(repoUrl: string): string {
117
124
  let repoOwner = "";
118
125
  let repoName = "";
@@ -490,7 +497,7 @@ async function main() {
490
497
  }
491
498
 
492
499
  // Get the public key from remote machine
493
- const sshPublicKey = await runPromise(`ssh ${sshRemote} "cat ~/.ssh/id_rsa.pub"`);
500
+ const sshPublicKey = await sshValue(sshRemote, "cat ~/.ssh/id_rsa.pub");
494
501
  const keyTitle = `Machine Setup - ${sshRemote} - ${new Date().toISOString()}`;
495
502
 
496
503
  // Add the deploy key to GitHub repository
@@ -512,8 +519,8 @@ async function main() {
512
519
  console.log("āœ… Dependencies installed");
513
520
 
514
521
  // 6. Install the daemon that keeps the machine process running
515
- let remoteUser = (await runPromise(`ssh ${sshRemote} "whoami"`)).trim();
516
- let remoteHome = (await runPromise(`ssh ${sshRemote} "echo $HOME"`)).trim();
522
+ let remoteUser = await sshValue(sshRemote, "whoami");
523
+ let remoteHome = await sshValue(sshRemote, "echo $HOME");
517
524
  console.log(`Installing the ${SERVICE_UNIT_NAME} daemon (user ${remoteUser}, home ${remoteHome})...`);
518
525
  await writeRemoteFile(sshRemote, `~/${DAEMON_SCRIPT_NAME}`, getDaemonScript(remoteHome), false);
519
526
  await runPromise(`ssh ${sshRemote} "chmod +x ~/${DAEMON_SCRIPT_NAME}"`);
@@ -559,7 +566,7 @@ sudo systemctl start ${SERVICE_UNIT_NAME}
559
566
  let screenDeadline = Date.now() + SCREEN_VERIFY_TIMEOUT;
560
567
  let screenUp = false;
561
568
  while (Date.now() < screenDeadline) {
562
- let panePid = (await runPromise(`ssh ${sshRemote} "tmux list-panes -t ${SERVICE_NAME} -F '#{pane_pid}' 2>/dev/null || true"`, { nothrow: true })).trim();
569
+ let panePid = await sshValue(sshRemote, `tmux list-panes -t ${SERVICE_NAME} -F '#{pane_pid}' 2>/dev/null || true`, { nothrow: true });
563
570
  if (panePid) {
564
571
  screenUp = true;
565
572
  break;
@@ -575,8 +582,37 @@ sudo systemctl start ${SERVICE_UNIT_NAME}
575
582
  console.log(` Screen: ssh ${sshRemote} -t "tmux attach -t ${SERVICE_NAME}"`);
576
583
  console.log(` Status: ssh ${sshRemote} "systemctl status ${SERVICE_UNIT_NAME}"`);
577
584
 
585
+ await runMachineDeployHook(sshRemote);
586
+
578
587
  console.log("\nšŸŽ‰ Machine setup complete!");
579
588
  }
580
589
 
590
+ async function runMachineDeployHook(sshRemote: string) {
591
+ let hookPathBase = path.resolve("./machineDeploy");
592
+ let hookExists = false;
593
+ for (let extension of [".ts", ".tsx", ".js"]) {
594
+ if (await fsExistsAsync(hookPathBase + extension)) {
595
+ hookExists = true;
596
+ break;
597
+ }
598
+ }
599
+ if (!hookExists) {
600
+ console.log(`No machineDeploy file at ${hookPathBase}, skipping the application deploy hook`);
601
+ return;
602
+ }
603
+ console.log(`Running the machineDeploy hook at ${hookPathBase}`);
604
+ try {
605
+ let hookModule = await import(hookPathBase) as { machineDeploy?: (config: { sshRemote: string }) => Promise<void> };
606
+ if (!hookModule.machineDeploy) {
607
+ throw new Error(`Expected ${hookPathBase} to export machineDeploy, exports are: ${Object.keys(hookModule).join(", ") || "(none)"}`);
608
+ }
609
+ await hookModule.machineDeploy({ sshRemote });
610
+ console.log("āœ… machineDeploy hook finished");
611
+ } catch (e) {
612
+ console.error((e as Error).stack || String(e));
613
+ console.error(`āš ļø The machineDeploy hook failed (see the error above). This is fine if the synchronization database hasn't been set up yet, or isn't running.`);
614
+ }
615
+ }
616
+
581
617
 
582
618
  main().catch(console.error).finally(() => process.exit(0));
@@ -10,7 +10,7 @@ import { assertIsNetworkTrusted } from "../-d-trust/NetworkTrust2";
10
10
  import { runPromise } from "../functional/runCommand";
11
11
  import { errorToUndefinedSilent } from "../errors";
12
12
  import { decodeNodeId } from "sliftutils/misc/https/certs";
13
- import { getDomain } from "../config";
13
+ import { getDomain, isPublic } from "../config";
14
14
  import { keyBy, keyByArray, timeInSecond, timeoutToUndefined, timeoutToUndefinedSilent } from "socket-function/src/misc";
15
15
  import dns from "dns";
16
16
  import { isDefined } from "../misc";
@@ -239,7 +239,8 @@ class MachineThreadInfoBase {
239
239
  hostname: hostname?.trim() || "",
240
240
  entrypoint: process.argv[1] || "",
241
241
  aliveTime: aliveTime,
242
- inspectURL: await getDebuggerUrl(),
242
+ // Only public servers hand out an inspect URL - getDebuggerUrl opens the inspector port as a side effect, and on a non-public server the debugger should be attached directly instead
243
+ inspectURL: isPublic() && await getDebuggerUrl() || "",
243
244
  };
244
245
  }
245
246
 
@@ -5,7 +5,7 @@ import { getAllNodeIds, getBrowserUrlNode, syncNodesNow } from "../../src/-f-nod
5
5
  import { errorToUndefined, errorToUndefinedSilent, logErrors, timeoutToError } from "../../src/errors";
6
6
  import { QuerysubController } from "../../src/4-querysub/QuerysubController";
7
7
  import { Querysub } from "../../src/4-querysub/Querysub";
8
- import { NodeCapabilitiesController, getControllerNodeIdList } from "../../src/-g-core-values/NodeCapabilities";
8
+ import { NodeCapabilitiesController, assertAssistedDebugConnectionAllowed, getControllerNodeIdList } from "../../src/-g-core-values/NodeCapabilities";
9
9
  import { lazy } from "socket-function/src/caching";
10
10
  import { atomicObjectWrite } from "../../src/2-proxy/PathValueProxyWatcher";
11
11
  import { Button } from "../../src/library-components/Button";
@@ -355,6 +355,7 @@ class NodeViewerControllerBase {
355
355
  }
356
356
 
357
357
  public async getExternalInspectURL(nodeId: string) {
358
+ assertAssistedDebugConnectionAllowed();
358
359
  // TODO: Replace this with a an HTTPS => HTTP proxy, adding a secret time based key
359
360
  // - Needs to run in another process
360
361
  // - Key will use machine hash(secret key + time mod interval). Will last for 2X interval, so at
@@ -482,6 +483,7 @@ class NodeViewerControllerBase {
482
483
  }
483
484
 
484
485
  public async getInspectURL(nodeId: string) {
486
+ assertAssistedDebugConnectionAllowed();
485
487
  return await NodeCapabilitiesController.nodes[nodeId].getInspectURL();
486
488
  }
487
489