querysub 0.252.0 → 0.253.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/3-path-functions/PathFunctionRunner.ts +1 -0
- package/src/4-deploy/git.ts +34 -1
- package/src/deployManager/MachinesPage.tsx +7 -10
- package/src/deployManager/components/MachineDetailPage.tsx +13 -21
- package/src/deployManager/components/MachinesListPage.tsx +59 -48
- package/src/deployManager/components/ServiceDetailPage.tsx +7 -9
- package/src/deployManager/components/ServicesListPage.tsx +41 -31
- package/src/deployManager/components/deployButtons.tsx +212 -0
- package/src/deployManager/machineApplyMainCode.ts +10 -4
- package/src/deployManager/machineController.ts +31 -4
- package/src/deployManager/machineSchema.ts +134 -45
- package/src/deployManager/spec.txt +3 -62
- package/src/deployManager/urlParams.ts +0 -1
- package/src/diagnostics/errorLogs/ErrorLogController.ts +1 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +6 -0
- package/src/library-components/SyncedController.ts +91 -31
- package/src/misc/formatJSX.tsx +6 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
+
import { qreact } from "../../4-dom/qreact";
|
|
3
|
+
import { MachineServiceController, MachineInfo, ServiceConfig } from "../machineSchema";
|
|
4
|
+
import { showFullscreenModal } from "../../5-diagnostics/FullscreenModal";
|
|
5
|
+
import { InputLabel } from "../../library-components/InputLabel";
|
|
6
|
+
import { css } from "../../4-dom/css";
|
|
7
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
8
|
+
import { formatTimeJSX } from "../../misc/formatJSX";
|
|
9
|
+
import { MachineController } from "../machineController";
|
|
10
|
+
|
|
11
|
+
module.hotreload = true;
|
|
12
|
+
|
|
13
|
+
function bigEmoji(emoji: string, topOffset = 0) {
|
|
14
|
+
return <span className={css.fontSize(26).marginTop(topOffset)}>{emoji}</span>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class RenderGitRefInfo extends qreact.Component<{
|
|
18
|
+
gitRef: string;
|
|
19
|
+
isQuerysub?: boolean;
|
|
20
|
+
}> {
|
|
21
|
+
render() {
|
|
22
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
23
|
+
let gitRefInfo = controller.getGitRefInfo({
|
|
24
|
+
ref: this.props.gitRef,
|
|
25
|
+
useQuerysub: this.props.isQuerysub,
|
|
26
|
+
});
|
|
27
|
+
if (!gitRefInfo) return undefined;
|
|
28
|
+
return <div className={css.fontWeight("normal")}>{formatTimeJSX(gitRefInfo.time)} AGO {gitRefInfo.description}</div>;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const buttonStyle = css.pad2(12, 8).button.bord2(0, 0, 20).fontWeight("bold").vbox(0).alignItems("center");
|
|
33
|
+
|
|
34
|
+
export class UpdateButtons extends qreact.Component<{
|
|
35
|
+
services: ServiceConfig[];
|
|
36
|
+
}> {
|
|
37
|
+
render() {
|
|
38
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
39
|
+
const gitInfo = controller.getGitInfo();
|
|
40
|
+
|
|
41
|
+
let outdatedServices = this.props.services.filter(service => {
|
|
42
|
+
let { gitRef, repoUrl } = service.parameters;
|
|
43
|
+
return repoUrl === gitInfo?.repoUrl && gitRef !== gitInfo?.latestRef;
|
|
44
|
+
});
|
|
45
|
+
let outdatedRefs = outdatedServices.map(service => service.parameters.gitRef);
|
|
46
|
+
|
|
47
|
+
return <>
|
|
48
|
+
{!!gitInfo?.querysubUncommitted.length && <button
|
|
49
|
+
className={buttonStyle.hsl(260, 60, 55)}
|
|
50
|
+
title={gitInfo?.querysubUncommitted.join("\n")}
|
|
51
|
+
onClick={() => {
|
|
52
|
+
showFullscreenModal({
|
|
53
|
+
content: <div>
|
|
54
|
+
<InputLabel
|
|
55
|
+
label="Commit message (enter to commit, escape to cancel)"
|
|
56
|
+
value={""}
|
|
57
|
+
textarea
|
|
58
|
+
fillWidth
|
|
59
|
+
onChangeValue={value => {
|
|
60
|
+
if (!value) return;
|
|
61
|
+
void controller.commitPushAndPublishQuerysub.promise(value);
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
<div className={css.vbox(5)}>
|
|
65
|
+
{gitInfo?.querysubUncommitted.map(change => {
|
|
66
|
+
return <div>
|
|
67
|
+
{change}
|
|
68
|
+
</div>;
|
|
69
|
+
})}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
});
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<div>
|
|
76
|
+
{bigEmoji("📚", -5)}
|
|
77
|
+
Publish & Commit & Push Querysub & Update Package.json ({gitInfo?.querysubUncommitted.length} Files Changed)
|
|
78
|
+
</div>
|
|
79
|
+
<RenderGitRefInfo gitRef={gitInfo.querysubRef} isQuerysub />
|
|
80
|
+
</button>}
|
|
81
|
+
{!!gitInfo?.uncommitted.length && <button
|
|
82
|
+
className={buttonStyle.hsl(210, 40, 50)}
|
|
83
|
+
title={gitInfo?.uncommitted.join("\n")}
|
|
84
|
+
onClick={() => {
|
|
85
|
+
showFullscreenModal({
|
|
86
|
+
content: <div>
|
|
87
|
+
<InputLabel
|
|
88
|
+
label="Commit message (enter to commit, escape to cancel)"
|
|
89
|
+
value={""}
|
|
90
|
+
textarea
|
|
91
|
+
fillWidth
|
|
92
|
+
onChangeValue={value => {
|
|
93
|
+
if (!value) return;
|
|
94
|
+
void controller.commitPushService.promise(value);
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
<div className={css.vbox(5)}>
|
|
98
|
+
{gitInfo?.uncommitted.map(change => {
|
|
99
|
+
return <div>
|
|
100
|
+
{change}
|
|
101
|
+
</div>;
|
|
102
|
+
})}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
});
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{bigEmoji("⬆️")} <span>Commit & Push ({gitInfo?.uncommitted.length} Files Changed)</span>
|
|
109
|
+
</button>}
|
|
110
|
+
|
|
111
|
+
{outdatedServices.length > 0 && gitInfo &&
|
|
112
|
+
<button
|
|
113
|
+
className={buttonStyle.hsl(120, 70, 50)}
|
|
114
|
+
onClick={async () => {
|
|
115
|
+
for (let service of outdatedServices) {
|
|
116
|
+
service.parameters.gitRef = gitInfo.latestRef;
|
|
117
|
+
}
|
|
118
|
+
await controller.setServiceConfigs.promise(outdatedServices);
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<div>
|
|
122
|
+
{bigEmoji("🎯")} <span>Deploy & Restart All ({outdatedServices.length}) Services</span>
|
|
123
|
+
</div>
|
|
124
|
+
<div className={css.hbox(5)}>
|
|
125
|
+
<b>New</b>
|
|
126
|
+
<RenderGitRefInfo gitRef={gitInfo.latestRef} />
|
|
127
|
+
</div>
|
|
128
|
+
{outdatedRefs.map(ref => <div className={css.hbox(5)}>
|
|
129
|
+
<b>Current</b>
|
|
130
|
+
<RenderGitRefInfo gitRef={ref} />
|
|
131
|
+
</div>)}
|
|
132
|
+
</button>
|
|
133
|
+
}
|
|
134
|
+
</>;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export class UpdateServiceButtons extends qreact.Component<{
|
|
139
|
+
service: ServiceConfig;
|
|
140
|
+
}> {
|
|
141
|
+
render() {
|
|
142
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
143
|
+
const gitInfo = controller.getGitInfo();
|
|
144
|
+
let gitRef = this.props.service.parameters.gitRef;
|
|
145
|
+
let repoUrl = this.props.service.parameters.repoUrl;
|
|
146
|
+
if (gitInfo?.repoUrl !== repoUrl) return undefined;
|
|
147
|
+
|
|
148
|
+
return <>
|
|
149
|
+
{gitRef !== gitInfo.latestRef && <button
|
|
150
|
+
className={buttonStyle.hsl(120, 70, 50).alignSelf("stretch")}
|
|
151
|
+
onClick={async () => {
|
|
152
|
+
this.props.service.parameters.gitRef = gitInfo.latestRef;
|
|
153
|
+
await controller.setServiceConfigs.promise([this.props.service]);
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
<div>
|
|
157
|
+
{bigEmoji("🎯", -4)} <span>Deploy & Restart</span>
|
|
158
|
+
</div>
|
|
159
|
+
<div className={css.hbox(5)}>
|
|
160
|
+
<b>New</b>
|
|
161
|
+
<RenderGitRefInfo gitRef={gitInfo.latestRef} />
|
|
162
|
+
</div>
|
|
163
|
+
<div className={css.hbox(5)}>
|
|
164
|
+
<b>Current</b>
|
|
165
|
+
<RenderGitRefInfo gitRef={gitRef} />
|
|
166
|
+
</div>
|
|
167
|
+
</button>}
|
|
168
|
+
</>;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export class DeployMachineButtons extends qreact.Component<{
|
|
173
|
+
machines: MachineInfo[];
|
|
174
|
+
}> {
|
|
175
|
+
render() {
|
|
176
|
+
let controller = MachineServiceController(SocketFunction.browserNodeId());
|
|
177
|
+
const gitInfo = controller.getGitInfo();
|
|
178
|
+
if (!gitInfo) return undefined;
|
|
179
|
+
|
|
180
|
+
let outdatedMachines = this.props.machines.filter(machine => {
|
|
181
|
+
return machine.repoUrl === gitInfo.repoUrl && machine.gitRef !== gitInfo.latestRef;
|
|
182
|
+
});
|
|
183
|
+
let oudatedRefs = outdatedMachines.map(machine => machine.gitRef);
|
|
184
|
+
|
|
185
|
+
return <>
|
|
186
|
+
{outdatedMachines.length > 0 && <div
|
|
187
|
+
className={buttonStyle.alignSelf("stretch").hsl(120, 70, 50)}
|
|
188
|
+
onClick={async () => {
|
|
189
|
+
let machineController = MachineController(SocketFunction.browserNodeId());
|
|
190
|
+
await Promise.all(outdatedMachines.map(machine => {
|
|
191
|
+
return machineController.deployMachineFromBrowser.promise({
|
|
192
|
+
machineNodeId: machine.applyNodeId,
|
|
193
|
+
gitRef: gitInfo.latestRef,
|
|
194
|
+
});
|
|
195
|
+
}));
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
<div>
|
|
199
|
+
{bigEmoji("🎯", -4)} <span>Deploy {outdatedMachines.length} Machines</span>
|
|
200
|
+
</div>
|
|
201
|
+
<div className={css.hbox(5)}>
|
|
202
|
+
<b>New</b>
|
|
203
|
+
<RenderGitRefInfo gitRef={gitInfo.latestRef} />
|
|
204
|
+
</div>
|
|
205
|
+
{oudatedRefs.map(ref => <div className={css.hbox(5)}>
|
|
206
|
+
<b>Current</b>
|
|
207
|
+
<RenderGitRefInfo gitRef={ref} />
|
|
208
|
+
</div>)}
|
|
209
|
+
</div>}
|
|
210
|
+
</>;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -4,7 +4,7 @@ import { measureWrap } from "socket-function/src/profiling/measure";
|
|
|
4
4
|
import { getOwnMachineId } from "../-a-auth/certs";
|
|
5
5
|
import { getOurNodeId, getOurNodeIdAssert } from "../-f-node-discovery/NodeDiscovery";
|
|
6
6
|
import { Querysub } from "../4-querysub/QuerysubController";
|
|
7
|
-
import { MACHINE_RESYNC_INTERVAL,
|
|
7
|
+
import { MACHINE_RESYNC_INTERVAL, MachineServiceControllerBase, MachineInfo, ServiceConfig, serviceConfigs, SERVICE_FOLDER, machineInfos } from "./machineSchema";
|
|
8
8
|
import { runPromise } from "../functional/runCommand";
|
|
9
9
|
import { getExternalIP } from "socket-function/src/networking";
|
|
10
10
|
import { errorToUndefined, errorToUndefinedSilent } from "../errors";
|
|
@@ -18,7 +18,7 @@ import os from "os";
|
|
|
18
18
|
import fs from "fs";
|
|
19
19
|
import { spawn, ChildProcess } from "child_process";
|
|
20
20
|
import { lazy } from "socket-function/src/caching";
|
|
21
|
-
import { getGitRefLive, setGitRef } from "../4-deploy/git";
|
|
21
|
+
import { getGitRefLive, getGitURLLive, setGitRef } from "../4-deploy/git";
|
|
22
22
|
import { blue, green, magenta, red } from "socket-function/src/formatting/logColors";
|
|
23
23
|
import { shutdown } from "../diagnostics/periodic";
|
|
24
24
|
import { onServiceConfigChange } from "./machineController";
|
|
@@ -34,6 +34,8 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
|
|
|
34
34
|
heartbeat: Date.now(),
|
|
35
35
|
info: {},
|
|
36
36
|
services: {},
|
|
37
|
+
repoUrl: "",
|
|
38
|
+
gitRef: "",
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
machineInfo.info.hostnamectl = await errorToUndefinedSilent(runPromise("hostnamectl")) || "";
|
|
@@ -41,6 +43,9 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
|
|
|
41
43
|
machineInfo.info.lscpu = await errorToUndefinedSilent(runPromise("lscpu")) || "";
|
|
42
44
|
machineInfo.info.id = await errorToUndefinedSilent(runPromise("id")) || await errorToUndefinedSilent(runPromise("whoami")) || "";
|
|
43
45
|
|
|
46
|
+
machineInfo.repoUrl = await getGitURLLive(".");
|
|
47
|
+
machineInfo.gitRef = await getGitRefLive(".");
|
|
48
|
+
|
|
44
49
|
return machineInfo;
|
|
45
50
|
});
|
|
46
51
|
|
|
@@ -78,7 +83,7 @@ export async function streamScreenOutput(config: {
|
|
|
78
83
|
try {
|
|
79
84
|
await serialOnData(data);
|
|
80
85
|
} catch (e: any) {
|
|
81
|
-
console.log(`Callback for stream output ${screenName} failed. It probably just disconnected, almost certainly not an error: ${e.
|
|
86
|
+
console.log(`Callback for stream output ${screenName} failed. It probably just disconnected, almost certainly not an error: ${e.message}`);
|
|
82
87
|
await stop();
|
|
83
88
|
} finally {
|
|
84
89
|
pendingDataCalls--;
|
|
@@ -425,7 +430,7 @@ async function quickIsOutdated() {
|
|
|
425
430
|
index: i,
|
|
426
431
|
});
|
|
427
432
|
let screen = screens.find(x => x.screenName === screenName);
|
|
428
|
-
if (!screen) return true;
|
|
433
|
+
if (!screen?.isProcessRunning) return true;
|
|
429
434
|
|
|
430
435
|
let folder = root + screenName + "/";
|
|
431
436
|
await fs.promises.mkdir(folder, { recursive: true });
|
|
@@ -433,6 +438,7 @@ async function quickIsOutdated() {
|
|
|
433
438
|
if (!fs.existsSync(parameterPath)) return true;
|
|
434
439
|
let prevParameters = await fs.promises.readFile(parameterPath, "utf8");
|
|
435
440
|
if (prevParameters !== newParametersString) return true;
|
|
441
|
+
|
|
436
442
|
}
|
|
437
443
|
}
|
|
438
444
|
return false;
|
|
@@ -8,6 +8,10 @@ import { isNode } from "typesafecss";
|
|
|
8
8
|
import { streamScreenOutput } from "./machineApplyMainCode";
|
|
9
9
|
import { Querysub } from "../4-querysub/QuerysubController";
|
|
10
10
|
import { getPathStr2 } from "../path";
|
|
11
|
+
import { getGitURLLive, setGitRef } from "../4-deploy/git";
|
|
12
|
+
import os from "os";
|
|
13
|
+
import { runPromise } from "../functional/runCommand";
|
|
14
|
+
import { getSyncedController } from "../library-components/SyncedController";
|
|
11
15
|
|
|
12
16
|
const POLL_INTERVAL = timeInMinute * 15;
|
|
13
17
|
|
|
@@ -78,27 +82,50 @@ class MachineControllerBase {
|
|
|
78
82
|
}) {
|
|
79
83
|
let caller = SocketFunction.getCaller();
|
|
80
84
|
forwardedCallbacks.set(config.callbackId, caller.nodeId);
|
|
81
|
-
await MachineController
|
|
85
|
+
await MachineController(config.nodeId).streamScreenOutput.promise({
|
|
82
86
|
key: config.key,
|
|
83
87
|
index: config.index,
|
|
84
88
|
callbackId: config.callbackId,
|
|
85
89
|
});
|
|
86
90
|
}
|
|
91
|
+
|
|
92
|
+
public async deployMachineFromBrowser(config: {
|
|
93
|
+
machineNodeId: string;
|
|
94
|
+
gitRef: string;
|
|
95
|
+
}) {
|
|
96
|
+
await MachineController(config.machineNodeId).deployService.promise({
|
|
97
|
+
gitRef: config.gitRef,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public async deployService(config: {
|
|
102
|
+
gitRef: string;
|
|
103
|
+
}) {
|
|
104
|
+
let gitFolder = os.homedir() + "/machine-alwaysup";
|
|
105
|
+
await setGitRef({
|
|
106
|
+
gitFolder,
|
|
107
|
+
gitRef: config.gitRef,
|
|
108
|
+
});
|
|
109
|
+
await runPromise("bash machine-startup.sh", { cwd: os.homedir() });
|
|
110
|
+
}
|
|
111
|
+
|
|
87
112
|
}
|
|
88
113
|
|
|
89
114
|
let forwardedCallbacks = new Map<string, string>();
|
|
90
115
|
|
|
91
|
-
const MachineController = SocketFunction.register(
|
|
116
|
+
export const MachineController = getSyncedController(SocketFunction.register(
|
|
92
117
|
"machine-controller-c3157d4a-580c-4e76-9dc9-072dd92e70af",
|
|
93
118
|
() => new MachineControllerBase(),
|
|
94
119
|
() => ({
|
|
95
120
|
streamScreenOutput: {},
|
|
96
121
|
watchOtherScreenOutput: {},
|
|
122
|
+
deployMachineFromBrowser: {},
|
|
123
|
+
deployService: {},
|
|
97
124
|
}),
|
|
98
125
|
() => ({
|
|
99
126
|
hooks: [assertIsManagementUser],
|
|
100
127
|
}),
|
|
101
|
-
);
|
|
128
|
+
));
|
|
102
129
|
|
|
103
130
|
let callbacks = new Map<string, (data: string) => Promise<void>>();
|
|
104
131
|
export async function watchScreenOutput(config: {
|
|
@@ -110,7 +137,7 @@ export async function watchScreenOutput(config: {
|
|
|
110
137
|
}) {
|
|
111
138
|
let callbackId = config.callbackId;
|
|
112
139
|
callbacks.set(callbackId, config.onData);
|
|
113
|
-
await MachineController
|
|
140
|
+
await MachineController(SocketFunction.browserNodeId()).watchOtherScreenOutput.promise({
|
|
114
141
|
nodeId: config.nodeId,
|
|
115
142
|
key: config.key,
|
|
116
143
|
index: config.index,
|
|
@@ -13,12 +13,42 @@ import { getOwnMachineId } from "../-a-auth/certs";
|
|
|
13
13
|
import { runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
14
14
|
import { lazy } from "socket-function/src/caching";
|
|
15
15
|
import { errorToUndefinedSilent } from "../errors";
|
|
16
|
-
import { getGitRefLive, getGitURLLive } from "../4-deploy/git";
|
|
16
|
+
import { commitAndPush, getGitRefInfo, getGitRefLive, getGitURLLive, getGitUncommitted, getLatestRefOnUpstreamBranch, setGitRef } from "../4-deploy/git";
|
|
17
17
|
import { OnServiceChange } from "./machineController";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
import { runPromise } from "../functional/runCommand";
|
|
18
21
|
|
|
19
22
|
export const SERVICE_FOLDER = "machine-services/";
|
|
20
23
|
export const MACHINE_RESYNC_INTERVAL = timeInMinute * 15;
|
|
21
24
|
|
|
25
|
+
export type MachineInfo = {
|
|
26
|
+
machineId: string;
|
|
27
|
+
|
|
28
|
+
// Used to tell the apply tool to update it's configs now
|
|
29
|
+
applyNodeId: string;
|
|
30
|
+
|
|
31
|
+
heartbeat: number;
|
|
32
|
+
/*
|
|
33
|
+
// TODO: ShowMore on each of the infos, so large ones are fine.
|
|
34
|
+
hostnamectl (fallback to hostname)
|
|
35
|
+
getExternalIP()
|
|
36
|
+
lscpu
|
|
37
|
+
id (fallback to whoami)
|
|
38
|
+
*/
|
|
39
|
+
info: Record<string, string>;
|
|
40
|
+
|
|
41
|
+
repoUrl: string;
|
|
42
|
+
gitRef: string;
|
|
43
|
+
|
|
44
|
+
services: Record<string, {
|
|
45
|
+
lastLaunchedTime: number;
|
|
46
|
+
errorFromLastRun: string;
|
|
47
|
+
// Only times launched for the current applyNodeId, but... still very useful.
|
|
48
|
+
totalTimesLaunched: number;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
|
|
22
52
|
|
|
23
53
|
export type ServiceConfig = {
|
|
24
54
|
/** Just a random id to manage the service */
|
|
@@ -50,38 +80,13 @@ export type ServiceConfig = {
|
|
|
50
80
|
lastUpdatedTime: number;
|
|
51
81
|
};
|
|
52
82
|
};
|
|
53
|
-
|
|
54
|
-
export type MachineInfo = {
|
|
55
|
-
machineId: string;
|
|
56
|
-
|
|
57
|
-
// Used to tell the apply tool to update it's configs now
|
|
58
|
-
applyNodeId: string;
|
|
59
|
-
|
|
60
|
-
heartbeat: number;
|
|
61
|
-
/*
|
|
62
|
-
// TODO: ShowMore on each of the infos, so large ones are fine.
|
|
63
|
-
hostnamectl (fallback to hostname)
|
|
64
|
-
getExternalIP()
|
|
65
|
-
lscpu
|
|
66
|
-
id (fallback to whoami)
|
|
67
|
-
*/
|
|
68
|
-
info: Record<string, string>;
|
|
69
|
-
|
|
70
|
-
services: Record<string, {
|
|
71
|
-
lastLaunchedTime: number;
|
|
72
|
-
errorFromLastRun: string;
|
|
73
|
-
// Only times launched for the current applyNodeId, but... still very useful.
|
|
74
|
-
totalTimesLaunched: number;
|
|
75
|
-
}>;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
export const serviceConfigs = archiveJSONT<ServiceConfig>(() => nestArchives("machines/service-configs/", getArchivesBackblaze(getDomain())));
|
|
79
83
|
export const machineInfos = archiveJSONT<MachineInfo>(() => nestArchives("machines/machine-heartbeats/", getArchivesBackblaze(getDomain())));
|
|
84
|
+
export const serviceConfigs = archiveJSONT<ServiceConfig>(() => nestArchives("machines/service-configs/", getArchivesBackblaze(getDomain())));
|
|
80
85
|
|
|
81
86
|
|
|
82
|
-
export class
|
|
87
|
+
export class MachineServiceControllerBase {
|
|
83
88
|
|
|
84
|
-
public async getMachineList()
|
|
89
|
+
public async getMachineList() {
|
|
85
90
|
return [...new Set([
|
|
86
91
|
...(await machineInfos.keys()),
|
|
87
92
|
])];
|
|
@@ -151,17 +156,28 @@ export class DeployControllerBase {
|
|
|
151
156
|
await this.notifyMachines(config.machineIds, []);
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
public async
|
|
155
|
-
|
|
156
|
-
let
|
|
159
|
+
public async setServiceConfigs(configs: ServiceConfig[]) {
|
|
160
|
+
let newMachines = new Set<string>();
|
|
161
|
+
let oldMachines = new Set<string>();
|
|
162
|
+
for (let config of configs) {
|
|
163
|
+
let serviceId = config.serviceId;
|
|
164
|
+
config.info.lastUpdatedTime = Date.now();
|
|
165
|
+
let serviceConfig = await serviceConfigs.get(serviceId);
|
|
157
166
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
if (!serviceConfig) {
|
|
168
|
+
throw new Error(`Service ${serviceId} does not exist`);
|
|
169
|
+
}
|
|
161
170
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
171
|
+
// Set the new service config
|
|
172
|
+
await serviceConfigs.set(serviceId, config);
|
|
173
|
+
for (let machineId of config.machineIds) {
|
|
174
|
+
newMachines.add(machineId);
|
|
175
|
+
}
|
|
176
|
+
for (let machineId of serviceConfig.machineIds) {
|
|
177
|
+
oldMachines.add(machineId);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
await this.notifyMachines([...newMachines], [...oldMachines]);
|
|
165
181
|
}
|
|
166
182
|
|
|
167
183
|
public async deleteServiceConfig(serviceId: string) {
|
|
@@ -180,19 +196,86 @@ export class DeployControllerBase {
|
|
|
180
196
|
}
|
|
181
197
|
public async getGitInfo() {
|
|
182
198
|
let repoUrl = await getGitURLLive();
|
|
183
|
-
let
|
|
199
|
+
let uncommitted = await getGitUncommitted(".");
|
|
200
|
+
let latestRef = await getLatestRefOnUpstreamBranch(".");
|
|
201
|
+
let querysubFolder = path.resolve("../querysub");
|
|
202
|
+
let querysubRef = "";
|
|
203
|
+
let querysubUncommitted: string[] = [];
|
|
204
|
+
if (fs.existsSync(querysubFolder)) {
|
|
205
|
+
querysubRef = await getGitRefLive(querysubFolder);
|
|
206
|
+
querysubUncommitted = await getGitUncommitted(querysubFolder);
|
|
207
|
+
}
|
|
184
208
|
return {
|
|
185
209
|
repoUrl,
|
|
186
|
-
|
|
210
|
+
latestRef,
|
|
211
|
+
uncommitted,
|
|
212
|
+
querysubRef,
|
|
213
|
+
querysubUncommitted,
|
|
187
214
|
};
|
|
188
215
|
}
|
|
216
|
+
|
|
217
|
+
public async getGitRefInfo(config: {
|
|
218
|
+
ref: string;
|
|
219
|
+
useQuerysub?: boolean;
|
|
220
|
+
}) {
|
|
221
|
+
if (!config.ref) return undefined;
|
|
222
|
+
try {
|
|
223
|
+
let gitDir = ".";
|
|
224
|
+
if (config.useQuerysub) {
|
|
225
|
+
gitDir = path.resolve("../querysub");
|
|
226
|
+
}
|
|
227
|
+
return await getGitRefInfo({
|
|
228
|
+
gitDir,
|
|
229
|
+
ref: config.ref,
|
|
230
|
+
});
|
|
231
|
+
} catch (error) {
|
|
232
|
+
console.error(error);
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public async commitPushService(commitMessage: string) {
|
|
238
|
+
await commitAndPush({
|
|
239
|
+
gitDir: ".",
|
|
240
|
+
message: commitMessage,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
public async commitPushAndPublishQuerysub(commitMessage: string) {
|
|
244
|
+
let querysubFolder = path.resolve("../querysub");
|
|
245
|
+
if (!fs.existsSync(querysubFolder)) {
|
|
246
|
+
throw new Error(`Querysub folder does not exist at ${querysubFolder}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Increment minor version and publish to npm with yarn
|
|
250
|
+
await runPromise("yarn version --minor --no-git-tag-version", { cwd: querysubFolder });
|
|
251
|
+
await runPromise("yarn publish --non-interactive", { cwd: querysubFolder });
|
|
252
|
+
|
|
253
|
+
// Update package.json reference in our actual repo (version only, no install)
|
|
254
|
+
let querysubPackageJson = JSON.parse(fs.readFileSync(path.join(querysubFolder, "package.json"), "utf8"));
|
|
255
|
+
let newVersion = querysubPackageJson.version;
|
|
256
|
+
|
|
257
|
+
let currentPackageJsonPath = path.join(".", "package.json");
|
|
258
|
+
let currentPackageJson = JSON.parse(fs.readFileSync(currentPackageJsonPath, "utf8"));
|
|
259
|
+
|
|
260
|
+
if (!currentPackageJson.dependencies?.querysub) {
|
|
261
|
+
throw new Error(`Querysub is not a dependency in package.json?`);
|
|
262
|
+
}
|
|
263
|
+
currentPackageJson.dependencies.querysub = `^${newVersion}`;
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(currentPackageJsonPath, JSON.stringify(currentPackageJson, null, 2));
|
|
266
|
+
|
|
267
|
+
await commitAndPush({
|
|
268
|
+
gitDir: querysubFolder,
|
|
269
|
+
message: commitMessage,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
189
272
|
}
|
|
190
273
|
|
|
191
274
|
|
|
192
|
-
export const
|
|
275
|
+
export const MachineServiceController = getSyncedController(
|
|
193
276
|
SocketFunction.register(
|
|
194
|
-
"
|
|
195
|
-
() => new
|
|
277
|
+
"machine-controller-eda94f05-5e4d-4f5a-b1c1-98613fba60b8",
|
|
278
|
+
() => new MachineServiceControllerBase(),
|
|
196
279
|
() => ({
|
|
197
280
|
getMachineList: {},
|
|
198
281
|
deleteMachineIds: {},
|
|
@@ -202,10 +285,13 @@ export const DeployController = getSyncedController(
|
|
|
202
285
|
getServiceList: {},
|
|
203
286
|
getServiceConfig: {},
|
|
204
287
|
addServiceConfig: {},
|
|
205
|
-
|
|
288
|
+
setServiceConfigs: {},
|
|
206
289
|
getServiceConfigType: {},
|
|
207
290
|
deleteServiceConfig: {},
|
|
208
291
|
getGitInfo: {},
|
|
292
|
+
getGitRefInfo: {},
|
|
293
|
+
commitPushService: {},
|
|
294
|
+
commitPushAndPublishQuerysub: {},
|
|
209
295
|
}),
|
|
210
296
|
() => ({
|
|
211
297
|
hooks: [assertIsManagementUser],
|
|
@@ -217,14 +303,17 @@ export const DeployController = getSyncedController(
|
|
|
217
303
|
addMachineInfo: ["MachineInfo", "MachineInfoList"],
|
|
218
304
|
setMachineInfo: ["MachineInfo"],
|
|
219
305
|
addServiceConfig: ["ServiceConfig", "ServiceConfigList"],
|
|
220
|
-
|
|
306
|
+
setServiceConfigs: ["ServiceConfig"],
|
|
221
307
|
deleteServiceConfig: ["ServiceConfig", "ServiceConfigList"],
|
|
308
|
+
commitPushService: ["gitInfo"],
|
|
309
|
+
commitPushAndPublishQuerysub: ["gitInfo"],
|
|
222
310
|
},
|
|
223
311
|
reads: {
|
|
224
312
|
getMachineList: ["MachineInfoList"],
|
|
225
313
|
getMachineInfo: ["MachineInfo"],
|
|
226
314
|
getServiceList: ["ServiceConfigList"],
|
|
227
315
|
getServiceConfig: ["ServiceConfig"],
|
|
316
|
+
getGitInfo: ["gitInfo"],
|
|
228
317
|
}
|
|
229
318
|
}
|
|
230
319
|
);
|
|
@@ -1,62 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
1) Our local http server can't talk to the remote PathValueServer server?
|
|
3
|
-
2) The remote http server isn't loading the modules?
|
|
4
|
-
- Which... come from permissions, and so the PathValueServer?
|
|
5
|
-
- Ah, and, it can't find the PathValueServer either?
|
|
6
|
-
|
|
7
|
-
OH! It is... that we keep picking the wrong server, thinking it is working, but it has an old config?
|
|
8
|
-
|
|
9
|
-
bd6cf9eb4accffba8
|
|
10
|
-
|
|
11
|
-
4) Add 2X redundancy to PathValueServer, and... FunctionRunner?
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
5) Setup on our regular digital ocean server
|
|
15
|
-
- Setup all the services in the new UI
|
|
16
|
-
- Copy from the previous startup.sh, running the same services
|
|
17
|
-
- Changing the UI if anything is extremely annoying, but... I don't see how it would be...
|
|
18
|
-
tmux send-keys -t server1 "cd ~/cyoa && yarn server-public" Enter
|
|
19
|
-
tmux send-keys -t server2 "cd ~/cyoa && yarn server-public" Enter
|
|
20
|
-
tmux send-keys -t fnc "cd ~/cyoa && yarn function-public --verbosecalls" Enter
|
|
21
|
-
|
|
22
|
-
tmux send-keys -t http "cd ~/cyoa && yarn cyoa-public --verbosecalls" Enter
|
|
23
|
-
tmux send-keys -t watch "cd ~/cyoa && yarn gc-watch-public" Enter
|
|
24
|
-
tmux send-keys -t join "cd ~/cyoa && yarn join-public" Enter
|
|
25
|
-
5) Verify the editor works
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
8) REIMPLEMENT yarn do-update functionality, with UI on the configuration page
|
|
31
|
-
- Components
|
|
32
|
-
<UpdateButtons />
|
|
33
|
-
- Commit all (add, commit, push)
|
|
34
|
-
- Only shows up if there are unsaved changes
|
|
35
|
-
- Commit querysub (publish, add, commit, push querysub, update package.json of app, add, commit, push)
|
|
36
|
-
- Only shows up if there are unsaved changes
|
|
37
|
-
- Deploy all (set hash to latest and save)
|
|
38
|
-
- Only shows up if (any) deployed hashes differ from latest for app
|
|
39
|
-
<UpdateServiceButtons service={serviceConfig} />
|
|
40
|
-
- Commit (add, commit, push)
|
|
41
|
-
- Only shows up if there are unsaved changes
|
|
42
|
-
- Deploy (set hash to latest and save)
|
|
43
|
-
- Only shows up if deployed hash differ from latest for app
|
|
44
|
-
- Endpoints
|
|
45
|
-
- anyQuerysubUnsaved
|
|
46
|
-
- anyAppUnsaved
|
|
47
|
-
- querysubLatestHash
|
|
48
|
-
- appLatestHash (already have this)
|
|
49
|
-
- publish, add, commit, and push querysub (and update app package.json reference)
|
|
50
|
-
- add, commit and push app
|
|
51
|
-
|
|
52
|
-
- On services list page, and individual service page
|
|
53
|
-
- So... maybe the button a component
|
|
54
|
-
- MULTIPLE buttons, to just update the main site, just querysub, or both
|
|
55
|
-
- Button to update on each service where the repoUrl === the repoUrl of the server
|
|
56
|
-
- Only from a non-public server
|
|
57
|
-
- ALSO, button to update all (that match repoUrl)
|
|
58
|
-
- Commit, push, update hash
|
|
59
|
-
- Also with an option to also update querysub (it'll tell us if querysub has changes), updating package.json to
|
|
1
|
+
OH, okay, so... we DO need machine update support, because... we will need to update them
|
|
60
2
|
|
|
61
3
|
|
|
62
4
|
7) Quick node removal on process crash OR removal
|
|
@@ -67,6 +9,8 @@ bd6cf9eb4accffba8
|
|
|
67
9
|
|
|
68
10
|
|
|
69
11
|
In general, hit our servers hard, to make sure we can launch them, they can talk to each other, etc. Even with entirely new folders (to somewhat emulate another machine), and they can all startup smoothly.
|
|
12
|
+
|
|
13
|
+
AH, I think I fixed that bug. I think it was an issue with using old hosts?
|
|
70
14
|
6) BUG! On startup, new nodes might take a poll interval to be fully ready? Hmm... we should restart our servers a lot again to make sure they come back up QUICKLY
|
|
71
15
|
- OH! It might be that the identity takes some time to propagate (the DNS), which causes us to require a second poll to be alive? Maybe... our local machine deleted it, because it couldn't see the DNS? No... none of these are reasonable...
|
|
72
16
|
- Try changing the key, so it syncs again, so we start fresh with the server
|
|
@@ -76,9 +20,6 @@ In general, hit our servers hard, to make sure we can launch them, they can talk
|
|
|
76
20
|
FORTUNATELY, looking at logs is now very simple, as well as updating nodes, so... this shouldn't be too hard to debug
|
|
77
21
|
|
|
78
22
|
|
|
79
|
-
6) Verify PathValueServer gracefully shutdowns, not losing any values (because it delays and flushes writes before shutting down, detecting the ctrl+c).
|
|
80
|
-
|
|
81
|
-
|
|
82
23
|
8) Fix deploy user notification issue, where the refresh button doesn't work?
|
|
83
24
|
|
|
84
25
|
9) Rolling service updates
|