querysub 0.654.0 ā 0.656.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 +2 -2
- package/src/-g-core-values/NodeCapabilities.ts +10 -1
- package/src/deployManager/components/MachineDetailPage.tsx +2 -2
- package/src/deployManager/components/MachinePicker.tsx +2 -2
- package/src/deployManager/components/MachinesListPage.tsx +2 -2
- package/src/deployManager/components/ServiceDetailPage.tsx +4 -4
- package/src/deployManager/components/ServicesListPage.tsx +2 -2
- package/src/deployManager/machineSchema.ts +18 -1
- package/src/deployManager/setupMachineMain.ts +30 -0
- package/src/diagnostics/MachineThreadInfo.tsx +3 -2
- package/src/diagnostics/NodeViewer.tsx +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.656.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,7 +79,7 @@
|
|
|
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.
|
|
82
|
+
"sliftutils": "^1.7.128",
|
|
83
83
|
"socket-function": "^1.2.34",
|
|
84
84
|
"terser": "^5.31.0",
|
|
85
85
|
"typenode": "^6.6.1",
|
|
@@ -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[]) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { MACHINE_RESYNC_INTERVAL, MachineServiceController, ServiceConfig, getMachineIdList } from "../machineSchema";
|
|
3
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceController, ServiceConfig, getMachineIdList, getMachineInfoLive } from "../machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { currentViewParam, selectedMachineIdParam, selectedServiceIdParam } from "../urlParams";
|
|
6
6
|
import { formatNumber, formatVeryNiceDateTime } from "socket-function/src/formatting/format";
|
|
@@ -19,7 +19,7 @@ export class MachineDetailPage extends qreact.Component {
|
|
|
19
19
|
if (!selectedMachineId) return <div>No machine selected</div>;
|
|
20
20
|
|
|
21
21
|
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
22
|
-
let machineInfo =
|
|
22
|
+
let machineInfo = getMachineInfoLive(selectedMachineId);
|
|
23
23
|
let serviceList = controller.getServiceList();
|
|
24
24
|
let machineConfig = controller.getMachineConfig(selectedMachineId);
|
|
25
25
|
|
|
@@ -6,7 +6,7 @@ import { isDefined } from "../../misc";
|
|
|
6
6
|
import { formatTime } from "socket-function/src/formatting/format";
|
|
7
7
|
import { css } from "typesafecss";
|
|
8
8
|
import { Querysub } from "../../4-querysub/Querysub";
|
|
9
|
-
import { MachineServiceController, getLiveServiceParameters, getMachineTargets, applyCommandTemplate } from "../machineSchema";
|
|
9
|
+
import { MachineServiceController, getLiveServiceParameters, getMachineTargets, applyCommandTemplate, getMachineInfoLive } from "../machineSchema";
|
|
10
10
|
import { FUNCTION_RUNNER_COMMAND_PREFIX } from "../serviceCategories";
|
|
11
11
|
|
|
12
12
|
module.hotreload = true;
|
|
@@ -53,7 +53,7 @@ export class MachinePicker extends qreact.Component<{
|
|
|
53
53
|
}> {
|
|
54
54
|
render() {
|
|
55
55
|
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
56
|
-
let machines = (controller.getMachineList() || []).map(x =>
|
|
56
|
+
let machines = (controller.getMachineList() || []).map(x => getMachineInfoLive(x)).filter(isDefined);
|
|
57
57
|
sort(machines, x => -x.heartbeat);
|
|
58
58
|
|
|
59
59
|
let networksPerMachine = getMachineNetworks(controller);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { MACHINE_RESYNC_INTERVAL, MachineServiceController } from "../machineSchema";
|
|
3
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceController, getMachineInfoLive } from "../machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { t } from "../../2-proxy/schema2";
|
|
6
6
|
import { Querysub } from "../../4-querysub/Querysub";
|
|
@@ -26,7 +26,7 @@ export class MachinesListPage extends qreact.Component {
|
|
|
26
26
|
|
|
27
27
|
if (!machineList) return <div>Loading machines...</div>;
|
|
28
28
|
|
|
29
|
-
let machines = machineList.map(machineId => [machineId,
|
|
29
|
+
let machines = machineList.map(machineId => [machineId, getMachineInfoLive(machineId)] as const).filter(x => x[1]);
|
|
30
30
|
sort(machines, x => -(x[1]?.heartbeat || 0));
|
|
31
31
|
|
|
32
32
|
let machineConfigs = controller.getMachineConfigList();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { DEFAULT_OVERLAP_TIME, MACHINE_RESYNC_INTERVAL, MachineServiceController, ServiceConfig, ServiceParameters, applyCommandTemplate, getCommandTemplateVariables, getLiveServiceParameters, getMachineTargets, setMachineTargets, stripReleaseState } from "../machineSchema";
|
|
3
|
+
import { DEFAULT_OVERLAP_TIME, MACHINE_RESYNC_INTERVAL, MachineServiceController, ServiceConfig, ServiceParameters, applyCommandTemplate, getCommandTemplateVariables, getLiveServiceParameters, getMachineInfoLive, getMachineTargets, setMachineTargets, stripReleaseState } from "../machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { t } from "../../2-proxy/schema2";
|
|
6
6
|
import { Querysub } from "../../4-querysub/Querysub";
|
|
@@ -121,7 +121,7 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
121
121
|
|
|
122
122
|
return <div className={css.vbox(8).fillWidth}>
|
|
123
123
|
{[...machineGroups.entries()].map(([machineId, group]) => {
|
|
124
|
-
let ip =
|
|
124
|
+
let ip = getMachineInfoLive(machineId)?.info["getExternalIP"];
|
|
125
125
|
if (typeof ip !== "string") {
|
|
126
126
|
ip = "";
|
|
127
127
|
}
|
|
@@ -226,7 +226,7 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
226
226
|
let machineStatuses = machineTargets.map(({ machineId, variables }) => {
|
|
227
227
|
let index = nextIndexes.get(machineId) || 0;
|
|
228
228
|
nextIndexes.set(machineId, index + 1);
|
|
229
|
-
let machineInfo =
|
|
229
|
+
let machineInfo = getMachineInfoLive(machineId);
|
|
230
230
|
let machineConfig = controller.getMachineConfig(machineId);
|
|
231
231
|
const serviceInfo = machineInfo?.services[selectedServiceId || ""];
|
|
232
232
|
const isMachineDead = machineInfo ? Date.now() - machineInfo.heartbeat > (MACHINE_RESYNC_INTERVAL * 4) : true;
|
|
@@ -248,7 +248,7 @@ export class ServiceDetailPage extends qreact.Component {
|
|
|
248
248
|
sort(machineStatuses, x => (x.isDisabled ? 3 : x.hasError ? 0 : x.isMachineDead ? 1 : 2) * 1000000 + x.heartbeat);
|
|
249
249
|
let now = Querysub.nowDelayed(timeInSecond);
|
|
250
250
|
|
|
251
|
-
let machines = (controller.getMachineList() || []).map(x =>
|
|
251
|
+
let machines = (controller.getMachineList() || []).map(x => getMachineInfoLive(x)).filter(isDefined);
|
|
252
252
|
sort(machines, x => -x.heartbeat);
|
|
253
253
|
|
|
254
254
|
return <div className={css.vbox(16).fillWidth}>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
2
|
import { qreact } from "../../4-dom/qreact";
|
|
3
|
-
import { DEFAULT_OVERLAP_TIME, MachineServiceController, ServiceConfig, getLiveServiceParameters, getMachineIdList } from "../machineSchema";
|
|
3
|
+
import { DEFAULT_OVERLAP_TIME, MachineServiceController, ServiceConfig, getLiveServiceParameters, getMachineIdList, getMachineInfoLive } from "../machineSchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
5
|
import { t } from "../../2-proxy/schema2";
|
|
6
6
|
import { Querysub } from "../../4-querysub/Querysub";
|
|
@@ -62,7 +62,7 @@ export class ServicesListPage extends qreact.Component {
|
|
|
62
62
|
let services = serviceList.map(serviceId => [serviceId, controller.getServiceConfig(serviceId)] as const);
|
|
63
63
|
sort(services, x => -(x[1]?.info.lastUpdatedTime || Date.now()) - (x[1]?.parameters.deploy && (Number.MAX_SAFE_INTEGER / 2) || 0));
|
|
64
64
|
|
|
65
|
-
let getMachineInfo = cache((machineId: string) =>
|
|
65
|
+
let getMachineInfo = cache((machineId: string) => getMachineInfoLive(machineId));
|
|
66
66
|
let getMachineConfig = cache((machineId: string) => controller.getMachineConfig(machineId));
|
|
67
67
|
|
|
68
68
|
let keyCounts = new Map<string, number>();
|
|
@@ -9,7 +9,7 @@ import { getSyncedController } from "../library-components/SyncedController";
|
|
|
9
9
|
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
10
10
|
import { extractType } from "../misc/extractType";
|
|
11
11
|
import { getOwnMachineId } from "sliftutils/misc/https/certs";
|
|
12
|
-
import { delay, runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
12
|
+
import { delay, runInSerial, runInfinitePoll, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
13
13
|
import { lazy } from "socket-function/src/caching";
|
|
14
14
|
import { errorToUndefinedSilent } from "../errors";
|
|
15
15
|
import { commitAndPush, getGitDiff, getGitRefInfo, getGitRefLive, getGitURLLive, getGitUncommitted, getLatestRefOnUpstreamBranch, setGitRef } from "../4-deploy/git";
|
|
@@ -704,3 +704,20 @@ export const MachineServiceController = getSyncedController(
|
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
706
|
);
|
|
707
|
+
|
|
708
|
+
const MACHINE_INFO_REFRESH_INTERVAL = timeInMinute * 5;
|
|
709
|
+
let accessedMachineInfoIds = new Set<string>();
|
|
710
|
+
const startMachineInfoRefreshLoop = lazy(() => {
|
|
711
|
+
runInfinitePoll(MACHINE_INFO_REFRESH_INTERVAL, () => {
|
|
712
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
713
|
+
for (let id of accessedMachineInfoIds) {
|
|
714
|
+
controller.getMachineInfo.refresh(id);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
/** Machine infos only change via heartbeats, which nothing on the client invalidates, so a page left open shows an ever-older heartbeat and eventually flags a healthy machine as likely dead. All UI reads go through here: the first access starts a loop that keeps refreshing every machine that has ever been accessed (refreshes are cheap, and this way nothing polls unless the data was actually used). */
|
|
719
|
+
export function getMachineInfoLive(machineId: string): MachineInfo | undefined {
|
|
720
|
+
accessedMachineInfoIds.add(machineId);
|
|
721
|
+
startMachineInfoRefreshLoop();
|
|
722
|
+
return MachineServiceController(SocketFunction.browserNodeId()).getMachineInfo(machineId);
|
|
723
|
+
}
|
|
@@ -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";
|
|
@@ -575,8 +576,37 @@ sudo systemctl start ${SERVICE_UNIT_NAME}
|
|
|
575
576
|
console.log(` Screen: ssh ${sshRemote} -t "tmux attach -t ${SERVICE_NAME}"`);
|
|
576
577
|
console.log(` Status: ssh ${sshRemote} "systemctl status ${SERVICE_UNIT_NAME}"`);
|
|
577
578
|
|
|
579
|
+
await runMachineDeployHook(sshRemote);
|
|
580
|
+
|
|
578
581
|
console.log("\nš Machine setup complete!");
|
|
579
582
|
}
|
|
580
583
|
|
|
584
|
+
async function runMachineDeployHook(sshRemote: string) {
|
|
585
|
+
let hookPathBase = path.resolve("./machineDeploy");
|
|
586
|
+
let hookExists = false;
|
|
587
|
+
for (let extension of [".ts", ".tsx", ".js"]) {
|
|
588
|
+
if (await fsExistsAsync(hookPathBase + extension)) {
|
|
589
|
+
hookExists = true;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (!hookExists) {
|
|
594
|
+
console.log(`No machineDeploy file at ${hookPathBase}, skipping the application deploy hook`);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
console.log(`Running the machineDeploy hook at ${hookPathBase}`);
|
|
598
|
+
try {
|
|
599
|
+
let hookModule = await import(hookPathBase) as { machineDeploy?: (config: { sshRemote: string }) => Promise<void> };
|
|
600
|
+
if (!hookModule.machineDeploy) {
|
|
601
|
+
throw new Error(`Expected ${hookPathBase} to export machineDeploy, exports are: ${Object.keys(hookModule).join(", ") || "(none)"}`);
|
|
602
|
+
}
|
|
603
|
+
await hookModule.machineDeploy({ sshRemote });
|
|
604
|
+
console.log("ā
machineDeploy hook finished");
|
|
605
|
+
} catch (e) {
|
|
606
|
+
console.error((e as Error).stack || String(e));
|
|
607
|
+
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.`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
581
611
|
|
|
582
612
|
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
|
-
|
|
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
|
|