@milaboratories/pl-deployments 2.15.7 → 2.15.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/README.md +18 -5
- package/dist/common/os_and_arch.cjs +19 -19
- package/dist/common/os_and_arch.cjs.map +1 -1
- package/dist/common/os_and_arch.js +19 -19
- package/dist/common/os_and_arch.js.map +1 -1
- package/dist/common/pl_binary.cjs +9 -9
- package/dist/common/pl_binary.cjs.map +1 -1
- package/dist/common/pl_binary.d.ts +6 -6
- package/dist/common/pl_binary.d.ts.map +1 -1
- package/dist/common/pl_binary.js +9 -9
- package/dist/common/pl_binary.js.map +1 -1
- package/dist/common/pl_binary_download.cjs +16 -16
- package/dist/common/pl_binary_download.cjs.map +1 -1
- package/dist/common/pl_binary_download.d.ts +6 -6
- package/dist/common/pl_binary_download.d.ts.map +1 -1
- package/dist/common/pl_binary_download.js +16 -16
- package/dist/common/pl_binary_download.js.map +1 -1
- package/dist/common/pl_version.cjs +1 -1
- package/dist/common/pl_version.cjs.map +1 -1
- package/dist/common/pl_version.js +1 -1
- package/dist/common/pl_version.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/local/options.d.ts +4 -4
- package/dist/local/pid.cjs +1 -1
- package/dist/local/pid.cjs.map +1 -1
- package/dist/local/pid.js +1 -1
- package/dist/local/pid.js.map +1 -1
- package/dist/local/pl.cjs +26 -26
- package/dist/local/pl.cjs.map +1 -1
- package/dist/local/pl.d.ts +8 -8
- package/dist/local/pl.d.ts.map +1 -1
- package/dist/local/pl.js +26 -26
- package/dist/local/pl.js.map +1 -1
- package/dist/local/process.cjs +16 -16
- package/dist/local/process.cjs.map +1 -1
- package/dist/local/process.d.ts +2 -2
- package/dist/local/process.js +16 -16
- package/dist/local/process.js.map +1 -1
- package/dist/local/trace.cjs.map +1 -1
- package/dist/local/trace.d.ts +1 -1
- package/dist/local/trace.js.map +1 -1
- package/dist/package.json.cjs +2 -1
- package/dist/package.json.cjs.map +1 -1
- package/dist/package.json.js +2 -1
- package/dist/package.json.js.map +1 -1
- package/dist/ssh/__tests__/common-utils.d.ts +2 -2
- package/dist/ssh/__tests__/common-utils.d.ts.map +1 -1
- package/dist/ssh/connection_info.cjs +13 -7
- package/dist/ssh/connection_info.cjs.map +1 -1
- package/dist/ssh/connection_info.d.ts +1 -1
- package/dist/ssh/connection_info.d.ts.map +1 -1
- package/dist/ssh/connection_info.js +13 -7
- package/dist/ssh/connection_info.js.map +1 -1
- package/dist/ssh/pl.cjs +79 -76
- package/dist/ssh/pl.cjs.map +1 -1
- package/dist/ssh/pl.d.ts +18 -18
- package/dist/ssh/pl.d.ts.map +1 -1
- package/dist/ssh/pl.js +79 -76
- package/dist/ssh/pl.js.map +1 -1
- package/dist/ssh/pl_paths.cjs +13 -13
- package/dist/ssh/pl_paths.cjs.map +1 -1
- package/dist/ssh/pl_paths.d.ts.map +1 -1
- package/dist/ssh/pl_paths.js +13 -13
- package/dist/ssh/pl_paths.js.map +1 -1
- package/dist/ssh/ssh.cjs +43 -40
- package/dist/ssh/ssh.cjs.map +1 -1
- package/dist/ssh/ssh.d.ts +9 -9
- package/dist/ssh/ssh.d.ts.map +1 -1
- package/dist/ssh/ssh.js +43 -40
- package/dist/ssh/ssh.js.map +1 -1
- package/dist/ssh/ssh_errors.cjs +7 -5
- package/dist/ssh/ssh_errors.cjs.map +1 -1
- package/dist/ssh/ssh_errors.d.ts.map +1 -1
- package/dist/ssh/ssh_errors.js +7 -5
- package/dist/ssh/ssh_errors.js.map +1 -1
- package/dist/ssh/supervisord.cjs +15 -13
- package/dist/ssh/supervisord.cjs.map +1 -1
- package/dist/ssh/supervisord.d.ts +2 -2
- package/dist/ssh/supervisord.d.ts.map +1 -1
- package/dist/ssh/supervisord.js +15 -13
- package/dist/ssh/supervisord.js.map +1 -1
- package/package.json +38 -37
- package/src/common/os_and_arch.ts +19 -19
- package/src/common/pl_binary.ts +30 -27
- package/src/common/pl_binary_download.ts +80 -59
- package/src/common/pl_version.ts +2 -2
- package/src/index.ts +5 -5
- package/src/local/config.test.yaml +19 -19
- package/src/local/options.ts +4 -4
- package/src/local/pid.ts +4 -4
- package/src/local/pl.test.ts +245 -253
- package/src/local/pl.ts +45 -50
- package/src/local/process.ts +21 -21
- package/src/local/trace.ts +1 -1
- package/src/ssh/__tests__/common-utils.ts +21 -19
- package/src/ssh/__tests__/pl-docker.test.ts +68 -59
- package/src/ssh/__tests__/ssh-docker.test.ts +152 -90
- package/src/ssh/__tests__/ssh-upload.test.ts +42 -31
- package/src/ssh/connection_info.ts +33 -27
- package/src/ssh/pl.test.ts +15 -13
- package/src/ssh/pl.ts +228 -143
- package/src/ssh/pl_paths.ts +22 -18
- package/src/ssh/ssh.ts +151 -74
- package/src/ssh/ssh_errors.test.ts +39 -39
- package/src/ssh/ssh_errors.ts +8 -6
- package/src/ssh/supervisord.ts +28 -28
package/src/ssh/pl.ts
CHANGED
|
@@ -1,34 +1,46 @@
|
|
|
1
|
-
import type * as ssh from
|
|
2
|
-
import { SshClient } from
|
|
3
|
-
import type { MiLogger } from
|
|
4
|
-
import { sleep, notEmpty } from
|
|
5
|
-
import type { DownloadBinaryResult } from
|
|
6
|
-
import { downloadBinaryNoExtract } from
|
|
7
|
-
import upath from
|
|
8
|
-
import * as plpath from
|
|
9
|
-
import { getDefaultPlVersion } from
|
|
10
|
-
import type { ProxySettings } from
|
|
11
|
-
import { defaultHttpDispatcher } from
|
|
12
|
-
import type { Dispatcher } from
|
|
13
|
-
|
|
14
|
-
import net from
|
|
15
|
-
import type {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
import type {
|
|
1
|
+
import type * as ssh from "ssh2";
|
|
2
|
+
import { SshClient } from "./ssh";
|
|
3
|
+
import type { MiLogger } from "@milaboratories/ts-helpers";
|
|
4
|
+
import { sleep, notEmpty } from "@milaboratories/ts-helpers";
|
|
5
|
+
import type { DownloadBinaryResult } from "../common/pl_binary_download";
|
|
6
|
+
import { downloadBinaryNoExtract } from "../common/pl_binary_download";
|
|
7
|
+
import upath from "upath";
|
|
8
|
+
import * as plpath from "./pl_paths";
|
|
9
|
+
import { getDefaultPlVersion } from "../common/pl_version";
|
|
10
|
+
import type { ProxySettings } from "@milaboratories/pl-http";
|
|
11
|
+
import { defaultHttpDispatcher } from "@milaboratories/pl-http";
|
|
12
|
+
import type { Dispatcher } from "undici";
|
|
13
|
+
|
|
14
|
+
import net from "node:net";
|
|
15
|
+
import type {
|
|
16
|
+
PlConfig,
|
|
17
|
+
PlLicenseMode,
|
|
18
|
+
SshPlConfigGenerationResult,
|
|
19
|
+
} from "@milaboratories/pl-config";
|
|
20
|
+
import { getFreePort, generateSshPlConfigs } from "@milaboratories/pl-config";
|
|
21
|
+
import type { SupervisorStatus } from "./supervisord";
|
|
22
|
+
import {
|
|
23
|
+
supervisorStatus,
|
|
24
|
+
supervisorStop as supervisorCtlShutdown,
|
|
25
|
+
generateSupervisordConfigWithMinio,
|
|
26
|
+
supervisorCtlStart,
|
|
27
|
+
isSupervisordRunning,
|
|
28
|
+
generateSupervisordConfig,
|
|
29
|
+
isAllAlive,
|
|
30
|
+
} from "./supervisord";
|
|
31
|
+
import type { ConnectionInfo, SshPlPorts } from "./connection_info";
|
|
32
|
+
import { newConnectionInfo, parseConnectionInfo, stringifyConnectionInfo } from "./connection_info";
|
|
33
|
+
import type { PlBinarySourceDownload } from "../common/pl_binary";
|
|
22
34
|
|
|
23
35
|
const minRequiredGlibcVersion = 2.28;
|
|
24
36
|
|
|
25
37
|
export class SshPl {
|
|
26
|
-
private initState: PlatformaInitState = { step:
|
|
38
|
+
private initState: PlatformaInitState = { step: "init" };
|
|
27
39
|
constructor(
|
|
28
40
|
public readonly logger: MiLogger,
|
|
29
41
|
public readonly sshClient: SshClient,
|
|
30
42
|
private readonly username: string,
|
|
31
|
-
) {
|
|
43
|
+
) {}
|
|
32
44
|
|
|
33
45
|
public info() {
|
|
34
46
|
return {
|
|
@@ -59,7 +71,7 @@ export class SshPl {
|
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
/** Starts all the services on the server.
|
|
62
|
-
|
|
74
|
+
* Idempotent semantic: we could call it several times. */
|
|
63
75
|
public async start(shouldUseMinio: boolean) {
|
|
64
76
|
const arch = await this.getArch();
|
|
65
77
|
const remoteHome = await this.getUserHomeDirectory();
|
|
@@ -74,7 +86,7 @@ export class SshPl {
|
|
|
74
86
|
} catch (e: unknown) {
|
|
75
87
|
let msg = `SshPl.start: ${e}`;
|
|
76
88
|
|
|
77
|
-
let logs =
|
|
89
|
+
let logs = "";
|
|
78
90
|
try {
|
|
79
91
|
logs = await this.sshClient.readFile(plpath.platformaCliLogs(remoteHome));
|
|
80
92
|
msg += `, platforma cli logs: ${logs}`;
|
|
@@ -88,7 +100,7 @@ export class SshPl {
|
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
/** Stops all the services on the server.
|
|
91
|
-
|
|
103
|
+
* Idempotent semantic: we could call it several times. */
|
|
92
104
|
public async stop() {
|
|
93
105
|
const arch = await this.getArch();
|
|
94
106
|
const remoteHome = await this.getUserHomeDirectory();
|
|
@@ -122,7 +134,9 @@ export class SshPl {
|
|
|
122
134
|
this.logger.info(`pl.reset: Stop Platforma on the server`);
|
|
123
135
|
await this.stop();
|
|
124
136
|
|
|
125
|
-
this.logger.info(
|
|
137
|
+
this.logger.info(
|
|
138
|
+
`pl.reset: Deleting Platforma workDir ${plpath.workDir(remoteHome)} on the server`,
|
|
139
|
+
);
|
|
126
140
|
await this.sshClient.deleteFolder(plpath.workDir(remoteHome));
|
|
127
141
|
}
|
|
128
142
|
|
|
@@ -130,7 +144,7 @@ export class SshPl {
|
|
|
130
144
|
* generates all the configs, creates necessary dirs,
|
|
131
145
|
* and finally starts all the services. */
|
|
132
146
|
public async platformaInit(options: SshPlConfig): Promise<ConnectionInfo> {
|
|
133
|
-
const state: PlatformaInitState = { localWorkdir: options.localWorkdir, step:
|
|
147
|
+
const state: PlatformaInitState = { localWorkdir: options.localWorkdir, step: "init" };
|
|
134
148
|
|
|
135
149
|
const { onProgress } = options;
|
|
136
150
|
|
|
@@ -147,13 +161,13 @@ export class SshPl {
|
|
|
147
161
|
|
|
148
162
|
const needRestartPlatforma = await this.doStepReadExistedConfig(state, ops, onProgress);
|
|
149
163
|
if (!needRestartPlatforma) {
|
|
150
|
-
await onProgress?.(
|
|
164
|
+
await onProgress?.("Platforma is already running. Skipping initialization.");
|
|
151
165
|
return state.existedSettings!;
|
|
152
166
|
}
|
|
153
167
|
await this.doStepStopExistedPlatforma(state, onProgress);
|
|
154
168
|
await this.doStepCheckDbLock(state, onProgress);
|
|
155
169
|
|
|
156
|
-
await onProgress?.(
|
|
170
|
+
await onProgress?.("Installation platforma...");
|
|
157
171
|
|
|
158
172
|
await this.doStepDownloadBinaries(state, onProgress, ops);
|
|
159
173
|
await this.doStepFetchPorts(state);
|
|
@@ -172,36 +186,49 @@ export class SshPl {
|
|
|
172
186
|
}
|
|
173
187
|
}
|
|
174
188
|
|
|
175
|
-
private async doStepStopExistedPlatforma(
|
|
176
|
-
state
|
|
189
|
+
private async doStepStopExistedPlatforma(
|
|
190
|
+
state: PlatformaInitState,
|
|
191
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
192
|
+
) {
|
|
193
|
+
state.step = "stopExistedPlatforma";
|
|
177
194
|
if (!isAllAlive(state.alive!, state.shouldUseMinio ?? false)) {
|
|
178
195
|
return;
|
|
179
196
|
}
|
|
180
197
|
|
|
181
|
-
await onProgress?.(
|
|
198
|
+
await onProgress?.("Stopping services...");
|
|
182
199
|
await this.stop();
|
|
183
200
|
}
|
|
184
201
|
|
|
185
202
|
private removeSensitiveData(state: PlatformaInitState): PlatformaInitState {
|
|
186
203
|
const stateCopy = { ...state };
|
|
187
|
-
stateCopy.generatedConfig = {
|
|
204
|
+
stateCopy.generatedConfig = {
|
|
205
|
+
...stateCopy.generatedConfig,
|
|
206
|
+
filesToCreate: { skipped: "sanitized" },
|
|
207
|
+
} as SshPlConfigGenerationResult;
|
|
188
208
|
return stateCopy;
|
|
189
209
|
}
|
|
190
210
|
|
|
191
|
-
private async doStepStartPlatforma(
|
|
192
|
-
state
|
|
193
|
-
|
|
211
|
+
private async doStepStartPlatforma(
|
|
212
|
+
state: PlatformaInitState,
|
|
213
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
214
|
+
) {
|
|
215
|
+
state.step = "startPlatforma";
|
|
216
|
+
await onProgress?.("Starting Platforma on the server...");
|
|
194
217
|
await this.start(state.shouldUseMinio ?? false);
|
|
195
218
|
state.started = true;
|
|
196
219
|
this.initState = state;
|
|
197
220
|
|
|
198
|
-
await onProgress?.(
|
|
221
|
+
await onProgress?.("Platforma has been started successfully.");
|
|
199
222
|
}
|
|
200
223
|
|
|
201
|
-
private async doStepSaveNewConnectionInfo(
|
|
202
|
-
state
|
|
224
|
+
private async doStepSaveNewConnectionInfo(
|
|
225
|
+
state: PlatformaInitState,
|
|
226
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
227
|
+
ops: SshPlConfig,
|
|
228
|
+
) {
|
|
229
|
+
state.step = "saveNewConnectionInfo";
|
|
203
230
|
const config = state.generatedConfig!;
|
|
204
|
-
await onProgress?.(
|
|
231
|
+
await onProgress?.("Saving connection information...");
|
|
205
232
|
state.connectionInfo = newConnectionInfo(
|
|
206
233
|
config.plUser,
|
|
207
234
|
config.plPassword,
|
|
@@ -214,7 +241,7 @@ export class SshPl {
|
|
|
214
241
|
plpath.connectionInfo(state.remoteHome!),
|
|
215
242
|
stringifyConnectionInfo(state.connectionInfo),
|
|
216
243
|
);
|
|
217
|
-
await onProgress?.(
|
|
244
|
+
await onProgress?.("Connection information saved.");
|
|
218
245
|
}
|
|
219
246
|
|
|
220
247
|
private async doStepCheckDbLock(
|
|
@@ -232,14 +259,14 @@ export class SshPl {
|
|
|
232
259
|
}
|
|
233
260
|
};
|
|
234
261
|
|
|
235
|
-
state.step =
|
|
236
|
-
await onProgress?.(
|
|
262
|
+
state.step = "checkDbLock";
|
|
263
|
+
await onProgress?.("Checking for DB lock...");
|
|
237
264
|
|
|
238
265
|
const lockFilePath = plpath.platformaDbLock(state.remoteHome!);
|
|
239
266
|
const lockFileExists = await this.sshClient.checkFileExists(lockFilePath);
|
|
240
267
|
|
|
241
268
|
if (!lockFileExists) {
|
|
242
|
-
await onProgress?.(
|
|
269
|
+
await onProgress?.("No DB lock found. Proceeding...");
|
|
243
270
|
return;
|
|
244
271
|
}
|
|
245
272
|
|
|
@@ -247,7 +274,9 @@ export class SshPl {
|
|
|
247
274
|
const lockProcessInfo = await this.findLockHolder(lockFilePath);
|
|
248
275
|
|
|
249
276
|
if (!lockProcessInfo) {
|
|
250
|
-
this.logger.warn(
|
|
277
|
+
this.logger.warn(
|
|
278
|
+
"Lock file exists but no process is holding it. Removing stale lock file...",
|
|
279
|
+
);
|
|
251
280
|
await removeLockFile(lockFilePath);
|
|
252
281
|
return;
|
|
253
282
|
}
|
|
@@ -257,17 +286,19 @@ export class SshPl {
|
|
|
257
286
|
);
|
|
258
287
|
|
|
259
288
|
if (lockProcessInfo.user !== this.username) {
|
|
260
|
-
const msg
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
289
|
+
const msg =
|
|
290
|
+
`DB lock is held by process ${lockProcessInfo.pid} ` +
|
|
291
|
+
`owned by user '${lockProcessInfo.user}', but current user is '${this.username}'. ` +
|
|
292
|
+
"Cannot kill process owned by different user.";
|
|
264
293
|
this.logger.error(msg);
|
|
265
294
|
throw new Error(msg);
|
|
266
295
|
}
|
|
267
296
|
|
|
268
|
-
this.logger.info(
|
|
297
|
+
this.logger.info(
|
|
298
|
+
`Process ${lockProcessInfo.pid} belongs to current user ${this.username}. Killing it...`,
|
|
299
|
+
);
|
|
269
300
|
await this.killRemoteProcess(lockProcessInfo.pid);
|
|
270
|
-
this.logger.info(
|
|
301
|
+
this.logger.info("Process holding DB lock has been terminated.");
|
|
271
302
|
|
|
272
303
|
// Verify lock file is gone or can be removed
|
|
273
304
|
const lockStillExists = await this.sshClient.checkFileExists(lockFilePath);
|
|
@@ -276,9 +307,12 @@ export class SshPl {
|
|
|
276
307
|
}
|
|
277
308
|
}
|
|
278
309
|
|
|
279
|
-
private async doStepConfigureSupervisord(
|
|
280
|
-
|
|
281
|
-
|
|
310
|
+
private async doStepConfigureSupervisord(
|
|
311
|
+
state: PlatformaInitState,
|
|
312
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
313
|
+
) {
|
|
314
|
+
await onProgress?.("Writing supervisord configuration...");
|
|
315
|
+
state.step = "configureSupervisord";
|
|
282
316
|
|
|
283
317
|
const config = state.generatedConfig!;
|
|
284
318
|
|
|
@@ -302,17 +336,25 @@ export class SshPl {
|
|
|
302
336
|
);
|
|
303
337
|
}
|
|
304
338
|
|
|
305
|
-
const writeResult = await this.sshClient.writeFileOnTheServer(
|
|
339
|
+
const writeResult = await this.sshClient.writeFileOnTheServer(
|
|
340
|
+
plpath.supervisorConf(state.remoteHome!),
|
|
341
|
+
supervisorConfig,
|
|
342
|
+
);
|
|
306
343
|
if (!writeResult) {
|
|
307
|
-
throw new Error(
|
|
344
|
+
throw new Error(
|
|
345
|
+
`Can not write supervisord config on the server ${plpath.workDir(state.remoteHome!)}`,
|
|
346
|
+
);
|
|
308
347
|
}
|
|
309
|
-
await onProgress?.(
|
|
348
|
+
await onProgress?.("Supervisord configuration written.");
|
|
310
349
|
}
|
|
311
350
|
|
|
312
|
-
private async doStepCreateFoldersAndSaveFiles(
|
|
313
|
-
state
|
|
351
|
+
private async doStepCreateFoldersAndSaveFiles(
|
|
352
|
+
state: PlatformaInitState,
|
|
353
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
354
|
+
) {
|
|
355
|
+
state.step = "createFoldersAndSaveFiles";
|
|
314
356
|
const config = state.generatedConfig!;
|
|
315
|
-
await onProgress?.(
|
|
357
|
+
await onProgress?.("Generating folder structure...");
|
|
316
358
|
for (const [filePath, content] of Object.entries(config.filesToCreate)) {
|
|
317
359
|
await this.sshClient.writeFileOnTheServer(filePath, content);
|
|
318
360
|
this.logger.info(`Created file ${filePath}`);
|
|
@@ -322,18 +364,22 @@ export class SshPl {
|
|
|
322
364
|
await this.sshClient.ensureRemoteDirCreated(dir);
|
|
323
365
|
this.logger.info(`Created directory ${dir}`);
|
|
324
366
|
}
|
|
325
|
-
await onProgress?.(
|
|
367
|
+
await onProgress?.("Folder structure created.");
|
|
326
368
|
}
|
|
327
369
|
|
|
328
|
-
private async doStepGenerateNewConfig(
|
|
329
|
-
state
|
|
370
|
+
private async doStepGenerateNewConfig(
|
|
371
|
+
state: PlatformaInitState,
|
|
372
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
373
|
+
ops: SshPlConfig,
|
|
374
|
+
) {
|
|
375
|
+
state.step = "generateNewConfig";
|
|
330
376
|
|
|
331
|
-
await onProgress?.(
|
|
377
|
+
await onProgress?.("Generating new config...");
|
|
332
378
|
const config = await generateSshPlConfigs({
|
|
333
379
|
logger: this.logger,
|
|
334
380
|
workingDir: plpath.workDir(state.remoteHome!),
|
|
335
381
|
portsMode: {
|
|
336
|
-
type:
|
|
382
|
+
type: "customWithMinio",
|
|
337
383
|
ports: {
|
|
338
384
|
debug: state.ports!.debug.remote,
|
|
339
385
|
grpc: state.ports!.grpc.remote,
|
|
@@ -353,53 +399,71 @@ export class SshPl {
|
|
|
353
399
|
useMinio: state.shouldUseMinio ?? false,
|
|
354
400
|
});
|
|
355
401
|
state.generatedConfig = { ...config };
|
|
356
|
-
await onProgress?.(
|
|
402
|
+
await onProgress?.("New config generated");
|
|
357
403
|
}
|
|
358
404
|
|
|
359
405
|
private async doStepFetchPorts(state: PlatformaInitState) {
|
|
360
|
-
state.step =
|
|
406
|
+
state.step = "fetchPorts";
|
|
361
407
|
state.ports = await this.fetchPorts(state.remoteHome!, state.arch!);
|
|
362
408
|
|
|
363
|
-
if (
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
409
|
+
if (
|
|
410
|
+
!state.ports.debug.remote ||
|
|
411
|
+
!state.ports.grpc.remote ||
|
|
412
|
+
!state.ports.minioPort.remote ||
|
|
413
|
+
!state.ports.minioConsolePort.remote ||
|
|
414
|
+
!state.ports.monitoring.remote ||
|
|
415
|
+
!state.ports.http?.remote
|
|
416
|
+
) {
|
|
369
417
|
throw new Error(`SshPl.platformaInit: remote ports are not defined`);
|
|
370
418
|
}
|
|
371
419
|
}
|
|
372
420
|
|
|
373
|
-
private async doStepDownloadBinaries(
|
|
374
|
-
state
|
|
375
|
-
|
|
421
|
+
private async doStepDownloadBinaries(
|
|
422
|
+
state: PlatformaInitState,
|
|
423
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
424
|
+
ops: SshPlConfig,
|
|
425
|
+
) {
|
|
426
|
+
state.step = "downloadBinaries";
|
|
427
|
+
await onProgress?.("Downloading and uploading required binaries...");
|
|
376
428
|
|
|
377
429
|
const glibcVersion = await getGlibcVersion(this.logger, this.sshClient);
|
|
378
430
|
if (glibcVersion < minRequiredGlibcVersion)
|
|
379
|
-
throw new Error(
|
|
431
|
+
throw new Error(
|
|
432
|
+
`glibc version ${glibcVersion} is too old. Version ${minRequiredGlibcVersion} or higher is required for Platforma.`,
|
|
433
|
+
);
|
|
380
434
|
|
|
381
435
|
const downloadRes = await this.downloadBinariesAndUploadToTheServer(
|
|
382
|
-
ops.localWorkdir,
|
|
436
|
+
ops.localWorkdir,
|
|
437
|
+
ops.plBinary!,
|
|
438
|
+
state.remoteHome!,
|
|
439
|
+
state.arch!,
|
|
440
|
+
state.shouldUseMinio ?? false,
|
|
383
441
|
ops.proxy,
|
|
384
442
|
);
|
|
385
|
-
await onProgress?.(
|
|
443
|
+
await onProgress?.("All required binaries have been downloaded and uploaded.");
|
|
386
444
|
|
|
387
445
|
state.binPaths = { ...downloadRes, history: undefined };
|
|
388
446
|
state.downloadedBinaries = downloadRes.history;
|
|
389
447
|
}
|
|
390
448
|
|
|
391
|
-
private async doStepDetectArch(
|
|
392
|
-
state
|
|
393
|
-
|
|
449
|
+
private async doStepDetectArch(
|
|
450
|
+
state: PlatformaInitState,
|
|
451
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
452
|
+
) {
|
|
453
|
+
state.step = "detectArch";
|
|
454
|
+
await onProgress?.("Detecting server architecture...");
|
|
394
455
|
state.arch = await this.getArch();
|
|
395
|
-
await onProgress?.(
|
|
456
|
+
await onProgress?.("Server architecture detected.");
|
|
396
457
|
}
|
|
397
458
|
|
|
398
|
-
private async doStepDetectHome(
|
|
399
|
-
state
|
|
400
|
-
|
|
459
|
+
private async doStepDetectHome(
|
|
460
|
+
state: PlatformaInitState,
|
|
461
|
+
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
462
|
+
) {
|
|
463
|
+
state.step = "detectHome";
|
|
464
|
+
await onProgress?.("Fetching user home directory...");
|
|
401
465
|
state.remoteHome = await this.getUserHomeDirectory();
|
|
402
|
-
await onProgress?.(
|
|
466
|
+
await onProgress?.("User home directory retrieved.");
|
|
403
467
|
}
|
|
404
468
|
|
|
405
469
|
private async doStepReadExistedConfig(
|
|
@@ -407,15 +471,15 @@ export class SshPl {
|
|
|
407
471
|
ops: SshPlConfig,
|
|
408
472
|
onProgress: ((...args: any) => Promise<any>) | undefined,
|
|
409
473
|
): Promise<boolean> {
|
|
410
|
-
state.step =
|
|
411
|
-
await onProgress?.(
|
|
474
|
+
state.step = "checkAlive";
|
|
475
|
+
await onProgress?.("Checking platform status...");
|
|
412
476
|
state.alive = await this.isAlive();
|
|
413
477
|
|
|
414
478
|
if (!state.alive?.platforma) {
|
|
415
479
|
return true;
|
|
416
480
|
}
|
|
417
481
|
|
|
418
|
-
await onProgress?.(
|
|
482
|
+
await onProgress?.("All required services are running.");
|
|
419
483
|
|
|
420
484
|
state.existedSettings = await this.readExistedConfig(state.remoteHome!);
|
|
421
485
|
if (!state.existedSettings) {
|
|
@@ -435,11 +499,11 @@ export class SshPl {
|
|
|
435
499
|
}
|
|
436
500
|
|
|
437
501
|
if (!state.needRestart) {
|
|
438
|
-
await onProgress?.(
|
|
502
|
+
await onProgress?.("Server setup completed.");
|
|
439
503
|
return false;
|
|
440
504
|
}
|
|
441
505
|
|
|
442
|
-
await onProgress?.(
|
|
506
|
+
await onProgress?.("Stopping services...");
|
|
443
507
|
await this.stop();
|
|
444
508
|
|
|
445
509
|
return true;
|
|
@@ -457,15 +521,21 @@ export class SshPl {
|
|
|
457
521
|
const dispatcher = defaultHttpDispatcher(proxy);
|
|
458
522
|
try {
|
|
459
523
|
const pl = await this.downloadAndUntar(
|
|
460
|
-
localWorkdir,
|
|
461
|
-
|
|
524
|
+
localWorkdir,
|
|
525
|
+
remoteHome,
|
|
526
|
+
arch,
|
|
527
|
+
"pl",
|
|
528
|
+
`pl-${plBinary.version}`,
|
|
462
529
|
dispatcher,
|
|
463
530
|
);
|
|
464
531
|
state.push(pl);
|
|
465
532
|
|
|
466
533
|
const supervisor = await this.downloadAndUntar(
|
|
467
|
-
localWorkdir,
|
|
468
|
-
|
|
534
|
+
localWorkdir,
|
|
535
|
+
remoteHome,
|
|
536
|
+
arch,
|
|
537
|
+
"supervisord",
|
|
538
|
+
plpath.supervisordDirName,
|
|
469
539
|
dispatcher,
|
|
470
540
|
);
|
|
471
541
|
state.push(supervisor);
|
|
@@ -473,8 +543,11 @@ export class SshPl {
|
|
|
473
543
|
const minioPath = plpath.minioBin(remoteHome, arch.arch);
|
|
474
544
|
if (shouldUseMinio) {
|
|
475
545
|
const minio = await this.downloadAndUntar(
|
|
476
|
-
localWorkdir,
|
|
477
|
-
|
|
546
|
+
localWorkdir,
|
|
547
|
+
remoteHome,
|
|
548
|
+
arch,
|
|
549
|
+
"minio",
|
|
550
|
+
plpath.minioDirName,
|
|
478
551
|
dispatcher,
|
|
479
552
|
);
|
|
480
553
|
state.push(minio);
|
|
@@ -506,7 +579,7 @@ export class SshPl {
|
|
|
506
579
|
// Example:
|
|
507
580
|
// COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
|
508
581
|
// platforma 11628 rfiskov 10u REG 1,16 0 66670038 ./LOCK
|
|
509
|
-
const lines = output.split(
|
|
582
|
+
const lines = output.split("\n");
|
|
510
583
|
if (lines.length <= 1) {
|
|
511
584
|
return null;
|
|
512
585
|
}
|
|
@@ -593,11 +666,11 @@ export class SshPl {
|
|
|
593
666
|
}
|
|
594
667
|
|
|
595
668
|
/** We have to extract pl in the remote server,
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
669
|
+
* because Windows doesn't support symlinks
|
|
670
|
+
* that are found in Linux pl binaries tgz archive.
|
|
671
|
+
* For this reason, we extract all to the remote server.
|
|
672
|
+
* It requires `tar` to be installed on the server
|
|
673
|
+
* (it's not installed for Rocky Linux for example). */
|
|
601
674
|
public async downloadAndUntar(
|
|
602
675
|
localWorkdir: string,
|
|
603
676
|
remoteHome: string,
|
|
@@ -636,16 +709,18 @@ export class SshPl {
|
|
|
636
709
|
|
|
637
710
|
state.localArchivePath = upath.resolve(state.downloadResult.archivePath);
|
|
638
711
|
state.remoteDir = upath.join(state.binBasePath, state.downloadResult.baseName);
|
|
639
|
-
state.remoteArchivePath = state.remoteDir +
|
|
712
|
+
state.remoteArchivePath = state.remoteDir + ".tgz";
|
|
640
713
|
|
|
641
714
|
await this.sshClient.ensureRemoteDirCreated(state.remoteDir);
|
|
642
715
|
await this.sshClient.uploadFile(state.localArchivePath, state.remoteArchivePath);
|
|
643
716
|
state.uploadDone = true;
|
|
644
717
|
|
|
645
718
|
try {
|
|
646
|
-
await this.sshClient.exec(
|
|
647
|
-
} catch
|
|
648
|
-
throw new Error(
|
|
719
|
+
await this.sshClient.exec("hash tar");
|
|
720
|
+
} catch {
|
|
721
|
+
throw new Error(
|
|
722
|
+
`tar is not installed on the server. Please install it before running Platforma.`,
|
|
723
|
+
);
|
|
649
724
|
}
|
|
650
725
|
|
|
651
726
|
// TODO: Create a proper archive to avoid xattr warnings
|
|
@@ -654,7 +729,9 @@ export class SshPl {
|
|
|
654
729
|
);
|
|
655
730
|
|
|
656
731
|
if (untarResult.stderr)
|
|
657
|
-
throw new Error(
|
|
732
|
+
throw new Error(
|
|
733
|
+
`downloadAndUntar: untar: stderr occurred: ${untarResult.stderr}, stdout: ${untarResult.stdout}`,
|
|
734
|
+
);
|
|
658
735
|
|
|
659
736
|
state.untarDone = true;
|
|
660
737
|
|
|
@@ -666,16 +743,23 @@ export class SshPl {
|
|
|
666
743
|
const checkPathMinio = plpath.minioDir(remoteHome, arch.arch);
|
|
667
744
|
const checkPathPlatforma = plpath.platformaBin(remoteHome, arch.arch);
|
|
668
745
|
|
|
669
|
-
if (
|
|
670
|
-
|
|
671
|
-
|
|
746
|
+
if (
|
|
747
|
+
!(await this.sshClient.checkFileExists(checkPathPlatforma)) ||
|
|
748
|
+
!(await this.sshClient.checkFileExists(checkPathMinio)) ||
|
|
749
|
+
!(await this.sshClient.checkFileExists(checkPathSupervisor))
|
|
750
|
+
) {
|
|
672
751
|
return true;
|
|
673
752
|
}
|
|
674
753
|
|
|
675
754
|
return false;
|
|
676
755
|
}
|
|
677
756
|
|
|
678
|
-
public async checkIsAliveWithInterval(
|
|
757
|
+
public async checkIsAliveWithInterval(
|
|
758
|
+
shouldUseMinio: boolean,
|
|
759
|
+
interval: number = 1000,
|
|
760
|
+
count = 15,
|
|
761
|
+
shouldStart = true,
|
|
762
|
+
): Promise<void> {
|
|
679
763
|
const maxMs = count * interval;
|
|
680
764
|
|
|
681
765
|
let total = 0;
|
|
@@ -684,7 +768,9 @@ export class SshPl {
|
|
|
684
768
|
await sleep(interval);
|
|
685
769
|
total += interval;
|
|
686
770
|
if (total > maxMs) {
|
|
687
|
-
throw new Error(
|
|
771
|
+
throw new Error(
|
|
772
|
+
`isAliveWithInterval: The process did not ${shouldStart ? "started" : "stopped"} after ${maxMs} ms. Live status: ${JSON.stringify(alive)}`,
|
|
773
|
+
);
|
|
688
774
|
}
|
|
689
775
|
alive = await this.isAlive();
|
|
690
776
|
}
|
|
@@ -741,18 +827,19 @@ export class SshPl {
|
|
|
741
827
|
|
|
742
828
|
const { stdout, stderr } = await this.sshClient.exec(`${freePortBin}`);
|
|
743
829
|
if (stderr) {
|
|
744
|
-
throw new Error(
|
|
830
|
+
throw new Error(
|
|
831
|
+
`getFreePortForPlatformaOnServer: stderr is not empty: ${stderr}, stdout: ${stdout}`,
|
|
832
|
+
);
|
|
745
833
|
}
|
|
746
834
|
|
|
747
835
|
return +stdout;
|
|
748
836
|
}
|
|
749
837
|
|
|
750
838
|
public async getArch(): Promise<Arch> {
|
|
751
|
-
const { stdout, stderr } = await this.sshClient.exec(
|
|
752
|
-
if (stderr)
|
|
753
|
-
throw new Error(`getArch: stderr is not empty: ${stderr}, stdout: ${stdout}`);
|
|
839
|
+
const { stdout, stderr } = await this.sshClient.exec("uname -s && uname -m");
|
|
840
|
+
if (stderr) throw new Error(`getArch: stderr is not empty: ${stderr}, stdout: ${stdout}`);
|
|
754
841
|
|
|
755
|
-
const arr = stdout.split(
|
|
842
|
+
const arr = stdout.split("\n");
|
|
756
843
|
|
|
757
844
|
return {
|
|
758
845
|
platform: arr[0],
|
|
@@ -761,11 +848,13 @@ export class SshPl {
|
|
|
761
848
|
}
|
|
762
849
|
|
|
763
850
|
public async getUserHomeDirectory() {
|
|
764
|
-
const { stdout, stderr } = await this.sshClient.exec(
|
|
851
|
+
const { stdout, stderr } = await this.sshClient.exec("echo $HOME");
|
|
765
852
|
|
|
766
853
|
if (stderr) {
|
|
767
854
|
const home = `/home/${this.username}`;
|
|
768
|
-
console.warn(
|
|
855
|
+
console.warn(
|
|
856
|
+
`getUserHomeDirectory: stderr is not empty: ${stderr}, stdout: ${stdout}, will get a default home: ${home}`,
|
|
857
|
+
);
|
|
769
858
|
|
|
770
859
|
return home;
|
|
771
860
|
}
|
|
@@ -789,14 +878,10 @@ export type SshPlConfig = {
|
|
|
789
878
|
|
|
790
879
|
export type LockProcessInfo = { pid: number; user: string };
|
|
791
880
|
|
|
792
|
-
const defaultSshPlConfig: Pick<
|
|
793
|
-
SshPlConfig,
|
|
794
|
-
| 'useGlobalAccess'
|
|
795
|
-
| 'plBinary'
|
|
796
|
-
> = {
|
|
881
|
+
const defaultSshPlConfig: Pick<SshPlConfig, "useGlobalAccess" | "plBinary"> = {
|
|
797
882
|
useGlobalAccess: false,
|
|
798
883
|
plBinary: {
|
|
799
|
-
type:
|
|
884
|
+
type: "Download",
|
|
800
885
|
version: getDefaultPlVersion(),
|
|
801
886
|
},
|
|
802
887
|
};
|
|
@@ -821,19 +906,19 @@ type DownloadAndUntarState = {
|
|
|
821
906
|
};
|
|
822
907
|
|
|
823
908
|
type PlatformaInitStep =
|
|
824
|
-
|
|
825
|
-
|
|
|
826
|
-
|
|
|
827
|
-
|
|
|
828
|
-
|
|
|
829
|
-
|
|
|
830
|
-
|
|
|
831
|
-
|
|
|
832
|
-
|
|
|
833
|
-
|
|
|
834
|
-
|
|
|
835
|
-
|
|
|
836
|
-
|
|
|
909
|
+
| "init"
|
|
910
|
+
| "detectArch"
|
|
911
|
+
| "detectHome"
|
|
912
|
+
| "checkAlive"
|
|
913
|
+
| "stopExistedPlatforma"
|
|
914
|
+
| "checkDbLock"
|
|
915
|
+
| "downloadBinaries"
|
|
916
|
+
| "fetchPorts"
|
|
917
|
+
| "generateNewConfig"
|
|
918
|
+
| "createFoldersAndSaveFiles"
|
|
919
|
+
| "configureSupervisord"
|
|
920
|
+
| "saveNewConnectionInfo"
|
|
921
|
+
| "startPlatforma";
|
|
837
922
|
|
|
838
923
|
type PlatformaInitState = {
|
|
839
924
|
step: PlatformaInitStep;
|
|
@@ -858,9 +943,9 @@ type PlatformaInitState = {
|
|
|
858
943
|
* @returns The glibc version as a number
|
|
859
944
|
* @throws Error if version cannot be determined
|
|
860
945
|
*/
|
|
861
|
-
async function getGlibcVersion(logger: MiLogger, sshClient: SshClient): Promise
|
|
946
|
+
async function getGlibcVersion(logger: MiLogger, sshClient: SshClient): Promise<number> {
|
|
862
947
|
try {
|
|
863
|
-
const { stdout, stderr } = await sshClient.exec(
|
|
948
|
+
const { stdout, stderr } = await sshClient.exec("ldd --version | head -n 1");
|
|
864
949
|
if (stderr) {
|
|
865
950
|
throw new Error(`Failed to check glibc version: ${stderr}`);
|
|
866
951
|
}
|