@milaboratories/pl-deployments 1.1.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/README.md +1 -0
- package/dist/common/os_and_arch.d.ts +9 -0
- package/dist/common/os_and_arch.d.ts.map +1 -0
- package/dist/common/pl_binary.d.ts +14 -0
- package/dist/common/pl_binary.d.ts.map +1 -0
- package/dist/common/pl_binary_download.d.ts +30 -0
- package/dist/common/pl_binary_download.d.ts.map +1 -0
- package/dist/common/pl_version.d.ts +2 -0
- package/dist/common/pl_version.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1028 -0
- package/dist/index.mjs.map +1 -0
- package/dist/local/options.d.ts +31 -0
- package/dist/local/options.d.ts.map +1 -0
- package/dist/local/pid.d.ts +4 -0
- package/dist/local/pid.d.ts.map +1 -0
- package/dist/local/pl.d.ts +66 -0
- package/dist/local/pl.d.ts.map +1 -0
- package/dist/local/process.d.ts +12 -0
- package/dist/local/process.d.ts.map +1 -0
- package/dist/local/trace.d.ts +10 -0
- package/dist/local/trace.d.ts.map +1 -0
- package/dist/ssh/__tests__/common-utils.d.ts +18 -0
- package/dist/ssh/__tests__/common-utils.d.ts.map +1 -0
- package/dist/ssh/pl.d.ts +101 -0
- package/dist/ssh/pl.d.ts.map +1 -0
- package/dist/ssh/pl_paths.d.ts +17 -0
- package/dist/ssh/pl_paths.d.ts.map +1 -0
- package/dist/ssh/ssh.d.ts +128 -0
- package/dist/ssh/ssh.d.ts.map +1 -0
- package/dist/ssh/supervisord.d.ts +8 -0
- package/dist/ssh/supervisord.d.ts.map +1 -0
- package/package.json +64 -0
- package/src/common/os_and_arch.ts +44 -0
- package/src/common/pl_binary.ts +39 -0
- package/src/common/pl_binary_download.ts +258 -0
- package/src/common/pl_version.ts +5 -0
- package/src/index.ts +4 -0
- package/src/local/config.test.yaml +60 -0
- package/src/local/options.ts +34 -0
- package/src/local/pid.ts +21 -0
- package/src/local/pl.test.ts +129 -0
- package/src/local/pl.ts +220 -0
- package/src/local/process.ts +44 -0
- package/src/local/trace.ts +30 -0
- package/src/ssh/Dockerfile +29 -0
- package/src/ssh/__tests__/common-utils.ts +131 -0
- package/src/ssh/__tests__/pl-docker.test.ts +192 -0
- package/src/ssh/__tests__/ssh-docker.test.ts +175 -0
- package/src/ssh/pl.ts +451 -0
- package/src/ssh/pl_paths.ts +57 -0
- package/src/ssh/ssh.ts +512 -0
- package/src/ssh/supervisord.ts +137 -0
- package/src/ssh/test-assets/simple-server.js +10 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1028 @@
|
|
|
1
|
+
var K = Object.defineProperty;
|
|
2
|
+
var q = (s, r, t) => r in s ? K(s, r, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[r] = t;
|
|
3
|
+
var d = (s, r, t) => q(s, typeof r != "symbol" ? r + "" : r, t);
|
|
4
|
+
import { spawn as G } from "node:child_process";
|
|
5
|
+
import { sleep as S, fileExists as f, assertNever as N, notEmpty as p } from "@milaboratories/ts-helpers";
|
|
6
|
+
import E from "node:fs";
|
|
7
|
+
import w, { readFile as Z } from "node:fs/promises";
|
|
8
|
+
import h from "upath";
|
|
9
|
+
import { request as V } from "undici";
|
|
10
|
+
import { Readable as Y, Writable as Q } from "node:stream";
|
|
11
|
+
import { text as X } from "node:stream/consumers";
|
|
12
|
+
import * as tt from "tar";
|
|
13
|
+
import rt from "decompress";
|
|
14
|
+
import x from "node:os";
|
|
15
|
+
import et, { Client as v } from "ssh2";
|
|
16
|
+
import T from "node:net";
|
|
17
|
+
import ot from "node:dns";
|
|
18
|
+
import { generateSshPlConfigs as it, getFreePort as y } from "@milaboratories/pl-config";
|
|
19
|
+
import { randomBytes as nt } from "node:crypto";
|
|
20
|
+
function st(s, r) {
|
|
21
|
+
return s.info(`Running:
|
|
22
|
+
cmd: ${JSON.stringify([r.cmd, ...r.args])}
|
|
23
|
+
wd: ${r.opts.cwd}`), s.info(" spawning child process"), G(r.cmd, r.args, r.opts);
|
|
24
|
+
}
|
|
25
|
+
async function C(s) {
|
|
26
|
+
try {
|
|
27
|
+
return process.kill(s, 0), !0;
|
|
28
|
+
} catch {
|
|
29
|
+
return !1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function U(s) {
|
|
33
|
+
return process.kill(s, "SIGINT");
|
|
34
|
+
}
|
|
35
|
+
async function B(s, r) {
|
|
36
|
+
let e = 0;
|
|
37
|
+
for (; await C(s); )
|
|
38
|
+
if (await S(100), e += 100, e > r)
|
|
39
|
+
throw new Error(`The process did not stopped after ${r} ms.`);
|
|
40
|
+
}
|
|
41
|
+
const at = ["linux", "macos", "windows"];
|
|
42
|
+
function _(s) {
|
|
43
|
+
switch (s.toLowerCase()) {
|
|
44
|
+
case "darwin":
|
|
45
|
+
return "macos";
|
|
46
|
+
case "linux":
|
|
47
|
+
return "linux";
|
|
48
|
+
case "win32":
|
|
49
|
+
return "windows";
|
|
50
|
+
default:
|
|
51
|
+
throw new Error(
|
|
52
|
+
`operating system '${s}' is not currently supported by Platforma ecosystem. The list of OSes supported: ` + JSON.stringify(at)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const ct = ["amd64", "arm64"];
|
|
57
|
+
function g(s) {
|
|
58
|
+
switch (s) {
|
|
59
|
+
case "aarch64":
|
|
60
|
+
case "aarch64_be":
|
|
61
|
+
case "arm64":
|
|
62
|
+
return "arm64";
|
|
63
|
+
case "x86_64":
|
|
64
|
+
case "x64":
|
|
65
|
+
return "amd64";
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(
|
|
68
|
+
`processor architecture '${s}' is not currently supported by Platforma ecosystem. The list of architectures supported: ` + JSON.stringify(ct)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function lt(s, r, t, e, o, i) {
|
|
73
|
+
const n = ut(t, e, r, g(o), _(i)), { archiveUrl: a, archivePath: c } = n;
|
|
74
|
+
return await H(s, a, c), n;
|
|
75
|
+
}
|
|
76
|
+
async function ht(s, r, t, e, o) {
|
|
77
|
+
const i = dt(t, r, g(e), _(o)), { archiveUrl: n, archivePath: a, archiveType: c, targetFolder: l, binaryPath: u } = i;
|
|
78
|
+
return await H(s, n, a), await pt(s, a, c, l), i;
|
|
79
|
+
}
|
|
80
|
+
function ut(s, r, t, e, o) {
|
|
81
|
+
const i = `${r}-${e}`, n = j[o], a = `${i}.${n}`, c = `https://cdn.platforma.bio/software/${s}/${o}/${a}`, l = h.join(t, a), u = h.join(t, i);
|
|
82
|
+
return {
|
|
83
|
+
archiveUrl: c,
|
|
84
|
+
archivePath: l,
|
|
85
|
+
archiveType: n,
|
|
86
|
+
targetFolder: u,
|
|
87
|
+
baseName: i
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function dt(s, r, t, e) {
|
|
91
|
+
const o = `pl-${s}-${t}`, i = j[e], n = `${o}.${i}`, a = `https://cdn.platforma.bio/software/pl/${e}/${n}`, c = h.join(r, n), l = h.join(r, o), u = h.join(o, "binaries", ft[e]);
|
|
92
|
+
return {
|
|
93
|
+
archiveUrl: a,
|
|
94
|
+
archivePath: c,
|
|
95
|
+
archiveType: i,
|
|
96
|
+
targetFolder: l,
|
|
97
|
+
binaryPath: u,
|
|
98
|
+
baseName: o
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
async function H(s, r, t) {
|
|
102
|
+
const e = {};
|
|
103
|
+
e.dstArchive = t;
|
|
104
|
+
try {
|
|
105
|
+
if (e.fileExisted = await f(t), e.fileExisted)
|
|
106
|
+
return s.info(`Platforma Backend archive download skipped: '${t}' already exists`), e;
|
|
107
|
+
await w.mkdir(h.dirname(t), { recursive: !0 }), e.dirnameCreated = !0, s.info(`Downloading archive:
|
|
108
|
+
URL: ${r}
|
|
109
|
+
Save to: ${t}`);
|
|
110
|
+
const { body: o, statusCode: i } = await V(r);
|
|
111
|
+
if (e.statusCode = i, i != 200) {
|
|
112
|
+
const n = await X(o);
|
|
113
|
+
throw e.errorMsg = `failed to download archive: ${i}, response: ${n.slice(0, 1e3)}`, s.error(e.errorMsg), new Error(e.errorMsg);
|
|
114
|
+
}
|
|
115
|
+
return e.tmpPath = t + ".tmp", await Y.toWeb(o).pipeTo(Q.toWeb(E.createWriteStream(e.tmpPath))), e.wroteTmp = !0, e.tmpExisted = await f(e.tmpPath), await w.rename(e.tmpPath, t), e.renamed = !0, e.newExisted = await f(t), e;
|
|
116
|
+
} catch (o) {
|
|
117
|
+
const i = `downloadArchive: error ${JSON.stringify(o)} occurred, state: ${JSON.stringify(e)}`;
|
|
118
|
+
throw s.error(i), new Error(i);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const wt = ".ok";
|
|
122
|
+
async function pt(s, r, t, e) {
|
|
123
|
+
if (s.info("extracting archive..."), s.info(` archive path: '${r}'`), s.info(` target dir: '${e}'`), !await f(r)) {
|
|
124
|
+
const i = `Platforma Backend binary archive not found at '${r}'`;
|
|
125
|
+
throw s.error(i), new Error(i);
|
|
126
|
+
}
|
|
127
|
+
const o = h.join(e, wt);
|
|
128
|
+
if (await f(o)) {
|
|
129
|
+
s.info(`Platforma Backend binaries unpack skipped: '${e}' exists`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
switch (await f(e) && (s.info(`Removing previous incompletely unpacked folder: '${e}'`), await w.rm(e, { recursive: !0 })), s.info(` creating target dir '${e}'`), await w.mkdir(e, { recursive: !0 }), s.info(
|
|
133
|
+
`Unpacking Platforma Backend archive:
|
|
134
|
+
Archive: ${r}
|
|
135
|
+
Target dir: ${e}`
|
|
136
|
+
), t) {
|
|
137
|
+
case "tgz":
|
|
138
|
+
await tt.x({
|
|
139
|
+
file: r,
|
|
140
|
+
cwd: e,
|
|
141
|
+
gzip: !0
|
|
142
|
+
});
|
|
143
|
+
break;
|
|
144
|
+
case "zip":
|
|
145
|
+
await rt(r, e);
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
N(t);
|
|
149
|
+
}
|
|
150
|
+
await w.writeFile(o, "ok"), s.info(" ... unpack done.");
|
|
151
|
+
}
|
|
152
|
+
const j = {
|
|
153
|
+
linux: "tgz",
|
|
154
|
+
macos: "tgz",
|
|
155
|
+
windows: "zip"
|
|
156
|
+
}, ft = {
|
|
157
|
+
linux: "platforma",
|
|
158
|
+
macos: "platforma",
|
|
159
|
+
windows: "platforma.exe"
|
|
160
|
+
};
|
|
161
|
+
function b() {
|
|
162
|
+
return "1.18.3";
|
|
163
|
+
}
|
|
164
|
+
function mt() {
|
|
165
|
+
return { type: "Download", version: b() };
|
|
166
|
+
}
|
|
167
|
+
async function yt(s, r, t) {
|
|
168
|
+
switch (t.type) {
|
|
169
|
+
case "Download":
|
|
170
|
+
return (await ht(s, r, t.version, x.arch(), x.platform())).binaryPath;
|
|
171
|
+
case "Local":
|
|
172
|
+
return t.path;
|
|
173
|
+
default:
|
|
174
|
+
N(t);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function I(s) {
|
|
178
|
+
return h.join(s, "pl_pid");
|
|
179
|
+
}
|
|
180
|
+
async function gt(s) {
|
|
181
|
+
if (!await f(s))
|
|
182
|
+
return;
|
|
183
|
+
const r = await w.readFile(s);
|
|
184
|
+
return Number(r.toString());
|
|
185
|
+
}
|
|
186
|
+
async function $t(s, r) {
|
|
187
|
+
await w.writeFile(s, JSON.stringify(r));
|
|
188
|
+
}
|
|
189
|
+
function Pt() {
|
|
190
|
+
return {};
|
|
191
|
+
}
|
|
192
|
+
function vt(s, r, t) {
|
|
193
|
+
return s[r] = t, t;
|
|
194
|
+
}
|
|
195
|
+
async function A(s, r) {
|
|
196
|
+
const t = Pt();
|
|
197
|
+
try {
|
|
198
|
+
return await r((o, i) => vt(t, o, i), t);
|
|
199
|
+
} catch (e) {
|
|
200
|
+
throw s.error(`error ${e} while doing traced operation, state: ${JSON.stringify(t)}`), e;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const St = "config-local.yaml";
|
|
204
|
+
class Et {
|
|
205
|
+
constructor(r, t, e, o, i, n, a, c) {
|
|
206
|
+
d(this, "instance");
|
|
207
|
+
d(this, "pid");
|
|
208
|
+
d(this, "nRuns", 0);
|
|
209
|
+
d(this, "lastRunHistory", {});
|
|
210
|
+
d(this, "wasStopped", !1);
|
|
211
|
+
this.logger = r, this.workingDir = t, this.startOptions = e, this.initialStartHistory = o, this.onClose = i, this.onError = n, this.onCloseAndError = a, this.onCloseAndErrorNoStop = c;
|
|
212
|
+
}
|
|
213
|
+
async start() {
|
|
214
|
+
await A(this.logger, async (r, t) => {
|
|
215
|
+
this.wasStopped = !1;
|
|
216
|
+
const e = st(this.logger, this.startOptions);
|
|
217
|
+
e.on("error", (i) => {
|
|
218
|
+
this.logger.error(
|
|
219
|
+
`error '${i}', while running platforma, started opts: ${JSON.stringify(this.debugInfo())}`
|
|
220
|
+
), this.onError !== void 0 && this.onError(this), this.onCloseAndError !== void 0 && this.onCloseAndError(this), this.onCloseAndErrorNoStop !== void 0 && !this.wasStopped && this.onCloseAndErrorNoStop(this);
|
|
221
|
+
}), e.on("close", () => {
|
|
222
|
+
this.logger.warn(`platforma was closed, started opts: ${JSON.stringify(this.debugInfo())}`), this.onClose !== void 0 && this.onClose(this), this.onCloseAndError !== void 0 && this.onCloseAndError(this), this.onCloseAndErrorNoStop !== void 0 && !this.wasStopped && this.onCloseAndErrorNoStop(this);
|
|
223
|
+
}), r("started", !0);
|
|
224
|
+
const o = r("pidFile", I(this.workingDir));
|
|
225
|
+
r("pid", p(e.pid)), r("pidWritten", await $t(o, p(e.pid))), this.nRuns++, this.instance = e, this.pid = e.pid, this.lastRunHistory = t;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
stop() {
|
|
229
|
+
this.wasStopped = !0, U(p(this.pid));
|
|
230
|
+
}
|
|
231
|
+
async waitStopped() {
|
|
232
|
+
await B(p(this.pid), 15e3);
|
|
233
|
+
}
|
|
234
|
+
stopped() {
|
|
235
|
+
return this.wasStopped;
|
|
236
|
+
}
|
|
237
|
+
async isAlive() {
|
|
238
|
+
return await C(p(this.pid));
|
|
239
|
+
}
|
|
240
|
+
debugInfo() {
|
|
241
|
+
return {
|
|
242
|
+
lastRunHistory: this.lastRunHistory,
|
|
243
|
+
nRuns: this.nRuns,
|
|
244
|
+
pid: this.pid,
|
|
245
|
+
workingDir: this.workingDir,
|
|
246
|
+
initialStartHistory: this.initialStartHistory,
|
|
247
|
+
wasStopped: this.wasStopped
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function Qt(s, r) {
|
|
252
|
+
const t = {
|
|
253
|
+
plBinary: mt(),
|
|
254
|
+
spawnOptions: {},
|
|
255
|
+
closeOld: !0,
|
|
256
|
+
...r
|
|
257
|
+
};
|
|
258
|
+
return await A(s, async (e, o) => {
|
|
259
|
+
e("startOptions", { ...t, config: "too wordy" });
|
|
260
|
+
const i = h.resolve(t.workingDir);
|
|
261
|
+
t.closeOld && e("closeOld", await Ct(s, i));
|
|
262
|
+
const n = h.join(i, St);
|
|
263
|
+
s.info(`writing configuration '${n}'...`), await w.writeFile(n, t.config);
|
|
264
|
+
const a = await yt(s, h.join(i, "binaries"), t.plBinary), l = {
|
|
265
|
+
cmd: e("binaryPath", h.join("binaries", a)),
|
|
266
|
+
args: ["-config", n],
|
|
267
|
+
opts: {
|
|
268
|
+
env: { ...process.env },
|
|
269
|
+
cwd: i,
|
|
270
|
+
stdio: ["pipe", "ignore", "inherit"],
|
|
271
|
+
windowsHide: !0,
|
|
272
|
+
// hide a terminal on Windows
|
|
273
|
+
...t.spawnOptions
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
e("processOpts", {
|
|
277
|
+
cmd: l.cmd,
|
|
278
|
+
args: l.args,
|
|
279
|
+
cwd: l.opts.cwd
|
|
280
|
+
});
|
|
281
|
+
const u = new Et(
|
|
282
|
+
s,
|
|
283
|
+
t.workingDir,
|
|
284
|
+
l,
|
|
285
|
+
o,
|
|
286
|
+
t.onClose,
|
|
287
|
+
t.onError,
|
|
288
|
+
t.onCloseAndError,
|
|
289
|
+
t.onCloseAndErrorNoStop
|
|
290
|
+
);
|
|
291
|
+
return await u.start(), u;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async function Ct(s, r) {
|
|
295
|
+
return await A(s, async (t, e) => {
|
|
296
|
+
const o = t("pidFilePath", I(r)), i = t("pid", await gt(o)), n = t("wasAlive", await C(i));
|
|
297
|
+
return i !== void 0 && n && (t("stopped", U(i)), t("waitStopped", await B(i, 1e4))), e;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
class F {
|
|
301
|
+
constructor(r, t) {
|
|
302
|
+
d(this, "config");
|
|
303
|
+
d(this, "homeDir");
|
|
304
|
+
this.logger = r, this.client = t;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Initializes the SshClient and establishes a connection using the provided configuration.
|
|
308
|
+
* @param config - The connection configuration object for the SSH client.
|
|
309
|
+
* @returns A new instance of SshClient with an active connection.
|
|
310
|
+
*/
|
|
311
|
+
static async init(r, t) {
|
|
312
|
+
const e = new F(r, new v());
|
|
313
|
+
return await e.connect(t), e;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Connects to the SSH server using the specified configuration.
|
|
317
|
+
* @param config - The connection configuration object for the SSH client.
|
|
318
|
+
* @returns A promise that resolves when the connection is established or rejects on error.
|
|
319
|
+
*/
|
|
320
|
+
async connect(r) {
|
|
321
|
+
return this.config = r, new Promise((t, e) => {
|
|
322
|
+
this.client.on("ready", () => {
|
|
323
|
+
t(void 0);
|
|
324
|
+
}).on("error", (o) => {
|
|
325
|
+
e(new Error(`ssh.connect: error occurred: ${o}`));
|
|
326
|
+
}).on("timeout", () => {
|
|
327
|
+
e(new Error("timeout was occurred while waiting for SSH connection."));
|
|
328
|
+
}).connect(r);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Executes a command on the SSH server.
|
|
333
|
+
* @param command - The command to execute on the remote server.
|
|
334
|
+
* @returns A promise resolving with the command's stdout and stderr outputs.
|
|
335
|
+
*/
|
|
336
|
+
async exec(r) {
|
|
337
|
+
return new Promise((t, e) => {
|
|
338
|
+
this.client.exec(r, (o, i) => {
|
|
339
|
+
if (o)
|
|
340
|
+
return e(`ssh.exec: ${r}, error occurred: ${o}`);
|
|
341
|
+
let n = "", a = "";
|
|
342
|
+
i.on("close", (c) => {
|
|
343
|
+
c === 0 ? t({ stdout: n, stderr: a }) : e(new Error(`Command ${r} exited with code ${c}`));
|
|
344
|
+
}).on("data", (c) => {
|
|
345
|
+
n += c.toString();
|
|
346
|
+
}).stderr.on("data", (c) => {
|
|
347
|
+
a += c.toString();
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Retrieves the supported authentication methods for a given host and port.
|
|
354
|
+
* @param host - The hostname or IP address of the server.
|
|
355
|
+
* @param port - The port number to connect to on the server.
|
|
356
|
+
* @returns 'publickey' | 'password'[] A promise resolving with a list of supported authentication methods.
|
|
357
|
+
*/
|
|
358
|
+
static async getAuthTypes(r, t) {
|
|
359
|
+
return new Promise((e) => {
|
|
360
|
+
let o = "";
|
|
361
|
+
const i = new v();
|
|
362
|
+
i.on("ready", () => {
|
|
363
|
+
i.end();
|
|
364
|
+
const n = this.extractAuthMethods(o);
|
|
365
|
+
e(n.length === 0 ? ["publickey", "password"] : n);
|
|
366
|
+
}), i.on("error", () => {
|
|
367
|
+
i.end(), e(["publickey", "password"]);
|
|
368
|
+
}), i.connect({
|
|
369
|
+
host: r,
|
|
370
|
+
port: t,
|
|
371
|
+
username: (/* @__PURE__ */ new Date()).getTime().toString(),
|
|
372
|
+
debug: (n) => {
|
|
373
|
+
o += `${n}
|
|
374
|
+
`;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Extracts authentication methods from debug logs.
|
|
381
|
+
* @param log - The debug log output containing authentication information.
|
|
382
|
+
* @returns An array of extracted authentication methods.
|
|
383
|
+
*/
|
|
384
|
+
static extractAuthMethods(r) {
|
|
385
|
+
const t = r.match(/Inbound: Received USERAUTH_FAILURE \((.+)\)/);
|
|
386
|
+
return t && t[1] ? t[1].split(",").map((e) => e.trim()) : [];
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Sets up port forwarding between a remote port on the SSH server and a local port.
|
|
390
|
+
* A new connection is used for this operation instead of an existing one.
|
|
391
|
+
* @param ports - An object specifying the remote and local port configuration.
|
|
392
|
+
* @param config - Optional connection configuration for the SSH client.
|
|
393
|
+
* @returns { server: net.Server } A promise resolving with the created server instance.
|
|
394
|
+
*/
|
|
395
|
+
async forwardPort(r, t) {
|
|
396
|
+
return t = t ?? this.config, new Promise((e, o) => {
|
|
397
|
+
if (!t) {
|
|
398
|
+
o("No config defined");
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const i = new v();
|
|
402
|
+
let n;
|
|
403
|
+
i.on("ready", () => {
|
|
404
|
+
this.logger.info(`[SSH] Connection to ${t.host}. Remote port ${r.remotePort} will be available locally on the ${r.localPort}`), n = T.createServer({ pauseOnConnect: !0 }, (a) => {
|
|
405
|
+
i.forwardOut(
|
|
406
|
+
"127.0.0.1",
|
|
407
|
+
0,
|
|
408
|
+
"127.0.0.1",
|
|
409
|
+
r.remotePort,
|
|
410
|
+
(c, l) => {
|
|
411
|
+
if (c) {
|
|
412
|
+
console.error("Error opening SSH channel:", c.message), a.end();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
a.pipe(l), l.pipe(a), a.resume();
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
}), n.listen(r.localPort, "127.0.0.1", () => {
|
|
419
|
+
this.logger.info(`[+] Port local ${r.localPort} available locally for remote port → :${r.remotePort}`), e({ server: n });
|
|
420
|
+
}), n.on("error", (a) => {
|
|
421
|
+
i.end(), n.close(), o(new Error(`ssh.forwardPort: server error: ${a}`));
|
|
422
|
+
}), n.on("close", () => {
|
|
423
|
+
this.logger.info(`Server closed ${JSON.stringify(r)}`), i && (this.logger.info("End SSH connection"), i.end());
|
|
424
|
+
});
|
|
425
|
+
}), i.on("error", (a) => {
|
|
426
|
+
console.error("[SSH] SSH connection error", "ports", r, a.message), n == null || n.close(), o(`ssh.forwardPort: conn.err: ${a}`);
|
|
427
|
+
}), i.on("close", () => {
|
|
428
|
+
this.logger.info(`[SSH] Connection closed, ports: ${r}`);
|
|
429
|
+
}), i.connect(t);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Checks if a specified host is available by performing a DNS lookup.
|
|
434
|
+
* @param hostname - The hostname or IP address to check.
|
|
435
|
+
* @returns A promise resolving with `true` if the host is reachable, otherwise `false`.
|
|
436
|
+
*/
|
|
437
|
+
static async checkHostAvailability(r) {
|
|
438
|
+
return new Promise((t) => {
|
|
439
|
+
ot.lookup(r, (e) => {
|
|
440
|
+
t(!e);
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Determines whether a private key requires a passphrase for use.
|
|
446
|
+
* @param privateKey - The private key content to check.
|
|
447
|
+
* @returns A promise resolving with `true` if a passphrase is required, otherwise `false`.
|
|
448
|
+
*/
|
|
449
|
+
static async isPassphraseRequiredForKey(r) {
|
|
450
|
+
return new Promise((t, e) => {
|
|
451
|
+
try {
|
|
452
|
+
return et.utils.parseKey(r) instanceof Error && t(!0), t(!1);
|
|
453
|
+
} catch (o) {
|
|
454
|
+
console.log("Error parsing privateKey"), e(new Error(`ssh.isPassphraseRequiredForKey: err ${o}`));
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Uploads a local file to a remote server via SFTP.
|
|
460
|
+
* This function creates new SFTP connection
|
|
461
|
+
* @param localPath - The local file path.
|
|
462
|
+
* @param remotePath - The remote file path on the server.
|
|
463
|
+
* @returns A promise resolving with `true` if the file was successfully uploaded.
|
|
464
|
+
*/
|
|
465
|
+
async uploadFile(r, t) {
|
|
466
|
+
return await this.withSftp(async (e) => new Promise((o, i) => {
|
|
467
|
+
e.fastPut(r, t, (n) => {
|
|
468
|
+
if (n) {
|
|
469
|
+
const a = new Error(
|
|
470
|
+
`ssh.uploadFile: err: ${n}, localPath: ${r}, remotePath: ${t}`
|
|
471
|
+
);
|
|
472
|
+
return i(a);
|
|
473
|
+
}
|
|
474
|
+
o(!0);
|
|
475
|
+
});
|
|
476
|
+
}));
|
|
477
|
+
}
|
|
478
|
+
delay(r) {
|
|
479
|
+
return new Promise((t, e) => {
|
|
480
|
+
setTimeout(() => t(), r);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
async withSftp(r) {
|
|
484
|
+
return new Promise((t, e) => {
|
|
485
|
+
this.client.sftp((o, i) => {
|
|
486
|
+
if (o)
|
|
487
|
+
return e(new Error(`ssh.withSftp: sftp err: ${o}`));
|
|
488
|
+
r(i).then(t).catch((n) => {
|
|
489
|
+
e(new Error(`ssh.withSftp.callback: err ${n}`));
|
|
490
|
+
}).finally(() => {
|
|
491
|
+
i == null || i.end();
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
async writeFileOnTheServer(r, t, e = 432) {
|
|
497
|
+
return this.withSftp(async (o) => this.writeFile(o, r, t, e));
|
|
498
|
+
}
|
|
499
|
+
async readFile(r) {
|
|
500
|
+
return this.withSftp(async (t) => new Promise((e, o) => {
|
|
501
|
+
t.readFile(r, (i, n) => {
|
|
502
|
+
if (i)
|
|
503
|
+
return o(new Error(`ssh.readFile: err occurred ${i}`));
|
|
504
|
+
e(n.toString());
|
|
505
|
+
});
|
|
506
|
+
}));
|
|
507
|
+
}
|
|
508
|
+
async chmod(r, t) {
|
|
509
|
+
return this.withSftp(async (e) => new Promise((o, i) => {
|
|
510
|
+
e.chmod(r, t, (n) => n ? i(new Error(`ssh.chmod: ${n}, path: ${r}, mode: ${t}`)) : o(void 0));
|
|
511
|
+
}));
|
|
512
|
+
}
|
|
513
|
+
async checkFileExists(r) {
|
|
514
|
+
return this.withSftp(async (t) => new Promise((e, o) => {
|
|
515
|
+
t.stat(r, (i, n) => {
|
|
516
|
+
if (i)
|
|
517
|
+
return i.code === 2 ? e(!1) : o(new Error(`ssh.checkFileExists: err ${i}`));
|
|
518
|
+
e(n.isFile());
|
|
519
|
+
});
|
|
520
|
+
}));
|
|
521
|
+
}
|
|
522
|
+
async checkPathExists(r) {
|
|
523
|
+
return this.withSftp(async (t) => new Promise((e, o) => {
|
|
524
|
+
t.stat(r, (i, n) => {
|
|
525
|
+
if (i)
|
|
526
|
+
return i.code === 2 ? e({ exists: !1, isFile: !1, isDirectory: !1 }) : o(new Error(`ssh.checkPathExists: ${i}`));
|
|
527
|
+
e({
|
|
528
|
+
exists: !0,
|
|
529
|
+
isFile: n.isFile(),
|
|
530
|
+
isDirectory: n.isDirectory()
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
async writeFile(r, t, e, o = 432) {
|
|
536
|
+
return new Promise((i, n) => {
|
|
537
|
+
r.writeFile(t, e, { mode: o }, (a) => {
|
|
538
|
+
if (a)
|
|
539
|
+
return n(new Error(`ssh.writeFile: err ${a}, remotePath: ${t}`));
|
|
540
|
+
i(!0);
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
uploadFileUsingExistingSftp(r, t, e, o = 432) {
|
|
545
|
+
return new Promise((i, n) => {
|
|
546
|
+
Z(t).then(async (a) => {
|
|
547
|
+
this.writeFile(r, e, a, o).then(() => {
|
|
548
|
+
i(void 0);
|
|
549
|
+
}).catch((c) => {
|
|
550
|
+
const l = `uploadFileUsingExistingSftp: error ${c} occurred`;
|
|
551
|
+
this.logger.error(l), n(new Error(l));
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
async __uploadDirectory(r, t, e, o = 432) {
|
|
557
|
+
return new Promise((i, n) => {
|
|
558
|
+
E.readdir(t, async (a, c) => {
|
|
559
|
+
if (a)
|
|
560
|
+
return n(new Error(`ssh.__uploadDir: err ${a}, localDir: ${t}, remoteDir: ${e}`));
|
|
561
|
+
try {
|
|
562
|
+
await this.__createRemoteDirectory(r, e);
|
|
563
|
+
for (const l of c) {
|
|
564
|
+
const u = h.join(t, l), m = `${e}/${l}`;
|
|
565
|
+
E.lstatSync(u).isDirectory() ? await this.__uploadDirectory(r, u, m, o) : await this.uploadFileUsingExistingSftp(r, u, m, o);
|
|
566
|
+
}
|
|
567
|
+
i();
|
|
568
|
+
} catch (l) {
|
|
569
|
+
const u = `ssh.__uploadDir: catched err ${l}`;
|
|
570
|
+
this.logger.error(u), n(new Error(u));
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Uploads a local directory and its contents (including subdirectories) to the remote server via SFTP.
|
|
577
|
+
* @param localDir - The path to the local directory to upload.
|
|
578
|
+
* @param remoteDir - The path to the remote directory on the server.
|
|
579
|
+
* @returns A promise that resolves when the directory and its contents are uploaded.
|
|
580
|
+
*/
|
|
581
|
+
async uploadDirectory(r, t, e = 432) {
|
|
582
|
+
return new Promise((o, i) => {
|
|
583
|
+
this.withSftp(async (n) => {
|
|
584
|
+
await this.__uploadDirectory(n, r, t, e), o();
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Ensures that a remote directory and all its parent directories exist.
|
|
590
|
+
* @param sftp - The SFTP wrapper.
|
|
591
|
+
* @param remotePath - The path to the remote directory.
|
|
592
|
+
* @returns A promise that resolves when the directory is created.
|
|
593
|
+
*/
|
|
594
|
+
__createRemoteDirectory(r, t) {
|
|
595
|
+
return new Promise((e, o) => {
|
|
596
|
+
const i = t.split("/");
|
|
597
|
+
let n = "";
|
|
598
|
+
const a = (c) => {
|
|
599
|
+
if (c >= i.length)
|
|
600
|
+
return e();
|
|
601
|
+
n += `${i[c]}/`, r.stat(n, (l) => {
|
|
602
|
+
l ? r.mkdir(n, (u) => {
|
|
603
|
+
if (u)
|
|
604
|
+
return o(new Error(`ssh.__createRemDir: err ${u}, remotePath: ${t}`));
|
|
605
|
+
a(c + 1);
|
|
606
|
+
}) : a(c + 1);
|
|
607
|
+
});
|
|
608
|
+
};
|
|
609
|
+
a(0);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Ensures that a remote directory and all its parent directories exist.
|
|
614
|
+
* @param sftp - The SFTP wrapper.
|
|
615
|
+
* @param remotePath - The path to the remote directory.
|
|
616
|
+
* @returns A promise that resolves when the directory is created.
|
|
617
|
+
*/
|
|
618
|
+
createRemoteDirectory(r, t = 493) {
|
|
619
|
+
return this.withSftp(async (e) => {
|
|
620
|
+
const o = r.split("/");
|
|
621
|
+
let i = "";
|
|
622
|
+
for (const n of o) {
|
|
623
|
+
i += `${n}/`;
|
|
624
|
+
try {
|
|
625
|
+
await new Promise((a, c) => {
|
|
626
|
+
e.stat(i, (l) => {
|
|
627
|
+
if (!l) return a();
|
|
628
|
+
e.mkdir(i, { mode: t }, (u) => {
|
|
629
|
+
if (u)
|
|
630
|
+
return c(new Error(`ssh.createRemoteDir: err ${u}, remotePath: ${r}`));
|
|
631
|
+
a();
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
} catch (a) {
|
|
636
|
+
throw console.error(`Failed to create directory: ${i}`, a), a;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Downloads a file from the remote server to a local path via SFTP.
|
|
643
|
+
* @param remotePath - The remote file path on the server.
|
|
644
|
+
* @param localPath - The local file path to save the file.
|
|
645
|
+
* @returns A promise resolving with `true` if the file was successfully downloaded.
|
|
646
|
+
*/
|
|
647
|
+
async downloadFile(r, t) {
|
|
648
|
+
return this.withSftp(async (e) => new Promise((o, i) => {
|
|
649
|
+
e.fastGet(r, t, (n) => {
|
|
650
|
+
if (n)
|
|
651
|
+
return i(new Error(`ssh.downloadFile: err ${n}, remotePath: ${r}, localPath: ${t}`));
|
|
652
|
+
o(!0);
|
|
653
|
+
});
|
|
654
|
+
}));
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Closes the SSH client connection.
|
|
658
|
+
*/
|
|
659
|
+
close() {
|
|
660
|
+
this.client.end();
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const bt = "minio-2024-12-18T13-15-44Z", At = "supervisord-0.7.3", Ft = "supervisord_0.7.3_Linux_64-bit";
|
|
664
|
+
function $(s) {
|
|
665
|
+
return h.join(s, "platforma_ssh");
|
|
666
|
+
}
|
|
667
|
+
function P(s) {
|
|
668
|
+
return h.join(s, "platforma_ssh", "binaries");
|
|
669
|
+
}
|
|
670
|
+
function Dt(s, r) {
|
|
671
|
+
return h.join(P(s), `pl-${b()}-${g(r)}`);
|
|
672
|
+
}
|
|
673
|
+
function M(s, r) {
|
|
674
|
+
return h.join(Dt(s, r), "binaries");
|
|
675
|
+
}
|
|
676
|
+
function O(s, r) {
|
|
677
|
+
return h.join(M(s, r), "platforma");
|
|
678
|
+
}
|
|
679
|
+
function xt(s, r) {
|
|
680
|
+
return h.join(M(s, r), "free-port");
|
|
681
|
+
}
|
|
682
|
+
function J(s, r) {
|
|
683
|
+
return h.join(P(s), `minio-2024-12-18T13-15-44Z-${g(r)}`);
|
|
684
|
+
}
|
|
685
|
+
function Ot(s, r) {
|
|
686
|
+
return h.join(J(s, r), "minio");
|
|
687
|
+
}
|
|
688
|
+
function kt(s, r) {
|
|
689
|
+
return h.join(P(s), `supervisord-0.7.3-${g(r)}`, Ft);
|
|
690
|
+
}
|
|
691
|
+
function L(s, r) {
|
|
692
|
+
return h.join(kt(s, r), "supervisord");
|
|
693
|
+
}
|
|
694
|
+
function z(s) {
|
|
695
|
+
return h.join($(s), "supervisor.conf");
|
|
696
|
+
}
|
|
697
|
+
function k(s) {
|
|
698
|
+
return h.join($(s), "connection.txt");
|
|
699
|
+
}
|
|
700
|
+
async function Rt(s, r, t) {
|
|
701
|
+
const e = await D(s, r, t, "--daemon");
|
|
702
|
+
if (e.stderr)
|
|
703
|
+
throw new Error(`Can not run ssh Platforma ${e.stderr}`);
|
|
704
|
+
}
|
|
705
|
+
async function Nt(s, r, t) {
|
|
706
|
+
const e = await D(s, r, t, "ctl shutdown");
|
|
707
|
+
if (e.stderr)
|
|
708
|
+
throw new Error(`Can not stop ssh Platforma ${e.stderr}`);
|
|
709
|
+
}
|
|
710
|
+
async function Tt(s, r, t, e) {
|
|
711
|
+
const o = await D(r, t, e, "ctl status");
|
|
712
|
+
if (o.stderr)
|
|
713
|
+
return s.info(`supervisord ctl status: stderr occurred: ${o.stderr}, stdout: ${o.stdout}`), !1;
|
|
714
|
+
const i = {
|
|
715
|
+
platforma: R(o.stdout, "platforma"),
|
|
716
|
+
minio: R(o.stdout, "minio")
|
|
717
|
+
};
|
|
718
|
+
return i.platforma && i.minio ? !0 : (i.minio || s.warn("Minio is not running on the server"), i.platforma || s.warn("Platforma is not running on the server"), !1);
|
|
719
|
+
}
|
|
720
|
+
function Ut(s, r, t, e, o, i, n) {
|
|
721
|
+
const a = Object.entries(r).map(([u, m]) => `${u}="${m}"`).join(","), c = nt(16).toString("hex"), l = t;
|
|
722
|
+
return `
|
|
723
|
+
[supervisord]
|
|
724
|
+
logfile=${e}/supervisord.log
|
|
725
|
+
loglevel=info
|
|
726
|
+
pidfile=${e}/supervisord.pid
|
|
727
|
+
|
|
728
|
+
[inet_http_server]
|
|
729
|
+
port=127.0.0.1:${l}
|
|
730
|
+
username=default-user
|
|
731
|
+
password=${c}
|
|
732
|
+
|
|
733
|
+
[supervisorctl]
|
|
734
|
+
serverurl=http://127.0.0.1:${l}
|
|
735
|
+
username=default-user
|
|
736
|
+
password=${c}
|
|
737
|
+
|
|
738
|
+
[program:platforma]
|
|
739
|
+
autostart=true
|
|
740
|
+
depends_on=minio
|
|
741
|
+
command=${n} --config ${o}
|
|
742
|
+
directory=${e}
|
|
743
|
+
autorestart=true
|
|
744
|
+
|
|
745
|
+
[program:minio]
|
|
746
|
+
autostart=true
|
|
747
|
+
environment=${a}
|
|
748
|
+
command=${i} server ${s}
|
|
749
|
+
directory=${e}
|
|
750
|
+
autorestart=true
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
async function D(s, r, t, e) {
|
|
754
|
+
const o = L(r, t), i = z(r), n = `${o} --configuration ${i} ${e}`;
|
|
755
|
+
return await s.exec(n);
|
|
756
|
+
}
|
|
757
|
+
function R(s, r) {
|
|
758
|
+
return ((o) => o.replace(/\x1B\[[0-9;]*m/g, ""))(s).split(`
|
|
759
|
+
`).some((o) => {
|
|
760
|
+
const [i, n] = o.trim().split(/\s{2,}/);
|
|
761
|
+
return i === r && n === "Running";
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
class W {
|
|
765
|
+
constructor(r, t, e) {
|
|
766
|
+
d(this, "initState", {});
|
|
767
|
+
this.logger = r, this.sshClient = t, this.username = e;
|
|
768
|
+
}
|
|
769
|
+
info() {
|
|
770
|
+
return {
|
|
771
|
+
username: this.username,
|
|
772
|
+
initState: this.initState
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
static async init(r, t) {
|
|
776
|
+
try {
|
|
777
|
+
const e = await F.init(r, t);
|
|
778
|
+
return new W(r, e, p(t.username));
|
|
779
|
+
} catch (e) {
|
|
780
|
+
throw r.error(`Connection error in SshClient.init: ${e}`), e;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
async isAlive() {
|
|
784
|
+
const r = await this.getArch(), t = await this.getUserHomeDirectory();
|
|
785
|
+
try {
|
|
786
|
+
return await Tt(this.logger, this.sshClient, t, r.arch);
|
|
787
|
+
} catch {
|
|
788
|
+
return !1;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async start() {
|
|
792
|
+
const r = await this.getArch(), t = await this.getUserHomeDirectory();
|
|
793
|
+
try {
|
|
794
|
+
return await Rt(this.sshClient, t, r.arch), await this.checkIsAliveWithInterval();
|
|
795
|
+
} catch (e) {
|
|
796
|
+
const o = `ssh.start: error occurred ${e}`;
|
|
797
|
+
throw this.logger.error(o), new Error(o);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
async stop() {
|
|
801
|
+
const r = await this.getArch(), t = await this.getUserHomeDirectory();
|
|
802
|
+
try {
|
|
803
|
+
return await Nt(this.sshClient, t, r.arch), await this.checkIsAliveWithInterval(void 0, void 0, !1);
|
|
804
|
+
} catch (e) {
|
|
805
|
+
const o = `ssh.stop: error occurred ${e}`;
|
|
806
|
+
throw this.logger.error(o), new Error(o);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
async platformaInit(r) {
|
|
810
|
+
const t = { localWorkdir: r };
|
|
811
|
+
try {
|
|
812
|
+
if (t.arch = await this.getArch(), t.remoteHome = await this.getUserHomeDirectory(), t.isAlive = await this.isAlive(), t.isAlive) {
|
|
813
|
+
if (t.userCredentials = await this.getUserCredentials(t.remoteHome), !t.userCredentials)
|
|
814
|
+
throw new Error("SshPl.platformaInit: platforma is alive but userCredentials are not found");
|
|
815
|
+
return t.userCredentials;
|
|
816
|
+
}
|
|
817
|
+
const e = await this.downloadBinariesAndUploadToTheServer(
|
|
818
|
+
r,
|
|
819
|
+
t.remoteHome,
|
|
820
|
+
t.arch
|
|
821
|
+
);
|
|
822
|
+
if (t.binPaths = { ...e, history: void 0 }, t.downloadedBinaries = e.history, t.ports = await this.fetchPorts(t.remoteHome, t.arch), !t.ports.debug.remote || !t.ports.grpc.remote || !t.ports.minioPort.remote || !t.ports.minioConsolePort.remote || !t.ports.monitoring.remote)
|
|
823
|
+
throw new Error("SshPl.platformaInit: remote ports are not defined");
|
|
824
|
+
const o = await it({
|
|
825
|
+
logger: this.logger,
|
|
826
|
+
workingDir: $(t.remoteHome),
|
|
827
|
+
portsMode: {
|
|
828
|
+
type: "customWithMinio",
|
|
829
|
+
ports: {
|
|
830
|
+
debug: t.ports.debug.remote,
|
|
831
|
+
grpc: t.ports.grpc.remote,
|
|
832
|
+
minio: t.ports.minioPort.remote,
|
|
833
|
+
minioConsole: t.ports.minioConsolePort.remote,
|
|
834
|
+
monitoring: t.ports.monitoring.remote,
|
|
835
|
+
grpcLocal: t.ports.grpc.local,
|
|
836
|
+
minioLocal: t.ports.minioPort.local
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
licenseMode: {
|
|
840
|
+
type: "env"
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
t.generatedConfig = { ...o, filesToCreate: { skipped: "it is too wordy" } };
|
|
844
|
+
for (const [a, c] of Object.entries(o.filesToCreate))
|
|
845
|
+
await this.sshClient.writeFileOnTheServer(a, c), this.logger.info(`Created file ${a}`);
|
|
846
|
+
for (const a of o.dirsToCreate)
|
|
847
|
+
await this.sshClient.createRemoteDirectory(a), this.logger.info(`Created directory ${a}`);
|
|
848
|
+
const i = Ut(
|
|
849
|
+
o.minioConfig.storageDir,
|
|
850
|
+
o.minioConfig.envs,
|
|
851
|
+
await this.getFreePortForPlatformaOnServer(t.remoteHome, t.arch),
|
|
852
|
+
o.workingDir,
|
|
853
|
+
o.plConfig.configPath,
|
|
854
|
+
t.binPaths.minioRelPath,
|
|
855
|
+
t.binPaths.downloadedPl
|
|
856
|
+
);
|
|
857
|
+
if (!await this.sshClient.writeFileOnTheServer(z(t.remoteHome), i))
|
|
858
|
+
throw new Error(`Can not write supervisord config on the server ${$(t.remoteHome)}`);
|
|
859
|
+
return t.connectionInfo = {
|
|
860
|
+
plUser: o.plUser,
|
|
861
|
+
plPassword: o.plPassword,
|
|
862
|
+
ports: t.ports
|
|
863
|
+
}, await this.sshClient.writeFileOnTheServer(
|
|
864
|
+
k(t.remoteHome),
|
|
865
|
+
JSON.stringify(t.connectionInfo, void 0, 2)
|
|
866
|
+
), await this.start(), t.started = !0, this.initState = t, {
|
|
867
|
+
plUser: o.plUser,
|
|
868
|
+
plPassword: o.plPassword,
|
|
869
|
+
ports: t.ports
|
|
870
|
+
};
|
|
871
|
+
} catch (e) {
|
|
872
|
+
const o = `SshPl.platformaInit: error occurred: ${e}, state: ${JSON.stringify(t)}`;
|
|
873
|
+
throw this.logger.error(o), new Error(o);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
async downloadBinariesAndUploadToTheServer(r, t, e) {
|
|
877
|
+
const o = [];
|
|
878
|
+
try {
|
|
879
|
+
const i = await this.downloadAndUntar(
|
|
880
|
+
r,
|
|
881
|
+
t,
|
|
882
|
+
e,
|
|
883
|
+
"pl",
|
|
884
|
+
`pl-${b()}`
|
|
885
|
+
);
|
|
886
|
+
o.push(i);
|
|
887
|
+
const n = await this.downloadAndUntar(
|
|
888
|
+
r,
|
|
889
|
+
t,
|
|
890
|
+
e,
|
|
891
|
+
"supervisord",
|
|
892
|
+
At
|
|
893
|
+
);
|
|
894
|
+
o.push(n);
|
|
895
|
+
const a = Ot(t, e.arch), c = await this.downloadAndUntar(
|
|
896
|
+
r,
|
|
897
|
+
t,
|
|
898
|
+
e,
|
|
899
|
+
"minio",
|
|
900
|
+
bt
|
|
901
|
+
);
|
|
902
|
+
return o.push(c), await this.sshClient.chmod(a, 488), {
|
|
903
|
+
history: o,
|
|
904
|
+
minioRelPath: a,
|
|
905
|
+
downloadedPl: O(t, e.arch)
|
|
906
|
+
};
|
|
907
|
+
} catch (i) {
|
|
908
|
+
const n = `SshPl.downloadBinariesAndUploadToServer: error ${i} occurred, state: ${JSON.stringify(o)}`;
|
|
909
|
+
throw this.logger.error(n), i;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/** We have to extract pl in the remote server,
|
|
913
|
+
* because Windows doesn't support symlinks
|
|
914
|
+
* that are found in linux pl binaries tgz archive.
|
|
915
|
+
* For this reason, we extract all to the remote server. */
|
|
916
|
+
async downloadAndUntar(r, t, e, o, i) {
|
|
917
|
+
const n = {};
|
|
918
|
+
n.binBasePath = P(t), await this.sshClient.createRemoteDirectory(n.binBasePath), n.binBasePathCreated = !0;
|
|
919
|
+
let a = null;
|
|
920
|
+
const c = 5;
|
|
921
|
+
for (let u = 1; u <= c; u++)
|
|
922
|
+
try {
|
|
923
|
+
a = await lt(
|
|
924
|
+
this.logger,
|
|
925
|
+
r,
|
|
926
|
+
o,
|
|
927
|
+
i,
|
|
928
|
+
e.arch,
|
|
929
|
+
e.platform
|
|
930
|
+
);
|
|
931
|
+
break;
|
|
932
|
+
} catch (m) {
|
|
933
|
+
if (await S(300), u == c)
|
|
934
|
+
throw new Error(`downloadAndUntar: ${c} attempts, last error: ${m}`);
|
|
935
|
+
}
|
|
936
|
+
n.downloadResult = p(a), n.localArchivePath = h.resolve(n.downloadResult.archivePath), n.remoteDir = h.join(n.binBasePath, n.downloadResult.baseName), n.remoteArchivePath = n.remoteDir + ".tgz", await this.sshClient.createRemoteDirectory(n.remoteDir), await this.sshClient.uploadFile(n.localArchivePath, n.remoteArchivePath);
|
|
937
|
+
const l = await this.sshClient.exec(
|
|
938
|
+
`tar xvf ${n.remoteArchivePath} --directory=${n.remoteDir}`
|
|
939
|
+
);
|
|
940
|
+
if (l.stderr)
|
|
941
|
+
throw new Error(`downloadAndUntar: untar: stderr occurred: ${l.stderr}, stdout: ${l.stdout}`);
|
|
942
|
+
return n.plUntarDone = !0, n;
|
|
943
|
+
}
|
|
944
|
+
async needDownload(r, t) {
|
|
945
|
+
const e = L(r, t.arch), o = J(r, t.arch), i = O(r, t.arch);
|
|
946
|
+
return !await this.sshClient.checkFileExists(i) || !await this.sshClient.checkFileExists(o) || !await this.sshClient.checkFileExists(e);
|
|
947
|
+
}
|
|
948
|
+
async checkIsAliveWithInterval(r = 1e3, t = 15, e = !0) {
|
|
949
|
+
const o = t * r;
|
|
950
|
+
let i = 0, n = await this.isAlive();
|
|
951
|
+
for (; e ? !n : n; ) {
|
|
952
|
+
if (await S(r), i += r, i > o)
|
|
953
|
+
throw new Error(`isAliveWithInterval: The process did not ${e ? "started" : "stopped"} after ${o} ms.`);
|
|
954
|
+
n = await this.isAlive();
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
async getUserCredentials(r) {
|
|
958
|
+
const t = await this.sshClient.readFile(k(r));
|
|
959
|
+
return JSON.parse(t);
|
|
960
|
+
}
|
|
961
|
+
async fetchPorts(r, t) {
|
|
962
|
+
return {
|
|
963
|
+
grpc: {
|
|
964
|
+
local: await y(),
|
|
965
|
+
remote: await this.getFreePortForPlatformaOnServer(r, t)
|
|
966
|
+
},
|
|
967
|
+
monitoring: {
|
|
968
|
+
local: await y(),
|
|
969
|
+
remote: await this.getFreePortForPlatformaOnServer(r, t)
|
|
970
|
+
},
|
|
971
|
+
debug: {
|
|
972
|
+
local: await y(),
|
|
973
|
+
remote: await this.getFreePortForPlatformaOnServer(r, t)
|
|
974
|
+
},
|
|
975
|
+
minioPort: {
|
|
976
|
+
local: await y(),
|
|
977
|
+
remote: await this.getFreePortForPlatformaOnServer(r, t)
|
|
978
|
+
},
|
|
979
|
+
minioConsolePort: {
|
|
980
|
+
local: await y(),
|
|
981
|
+
remote: await this.getFreePortForPlatformaOnServer(r, t)
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
async getLocalFreePort() {
|
|
986
|
+
return new Promise((r) => {
|
|
987
|
+
const t = T.createServer();
|
|
988
|
+
t.listen(0, () => {
|
|
989
|
+
const e = t.address().port;
|
|
990
|
+
t.close((o) => r(e));
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
async getFreePortForPlatformaOnServer(r, t) {
|
|
995
|
+
const e = xt(r, t.arch), { stdout: o, stderr: i } = await this.sshClient.exec(`${e}`);
|
|
996
|
+
if (i)
|
|
997
|
+
throw new Error(`getFreePortForPlatformaOnServer: stderr is not empty: ${i}, stdout: ${o}`);
|
|
998
|
+
return +o;
|
|
999
|
+
}
|
|
1000
|
+
async getArch() {
|
|
1001
|
+
const { stdout: r, stderr: t } = await this.sshClient.exec("uname -s && uname -m");
|
|
1002
|
+
if (t)
|
|
1003
|
+
throw new Error(`getArch: stderr is not empty: ${t}, stdout: ${r}`);
|
|
1004
|
+
const e = r.split(`
|
|
1005
|
+
`);
|
|
1006
|
+
return {
|
|
1007
|
+
platform: e[0],
|
|
1008
|
+
arch: e[1]
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
async getUserHomeDirectory() {
|
|
1012
|
+
const { stdout: r, stderr: t } = await this.sshClient.exec("echo $HOME");
|
|
1013
|
+
if (t) {
|
|
1014
|
+
const e = `/home/${this.username}`;
|
|
1015
|
+
return console.warn(`getUserHomeDirectory: stderr is not empty: ${t}, stdout: ${r}, will get a default home: ${e}`), e;
|
|
1016
|
+
}
|
|
1017
|
+
return r.trim();
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
export {
|
|
1021
|
+
St as LocalConfigYaml,
|
|
1022
|
+
Et as LocalPl,
|
|
1023
|
+
F as SshClient,
|
|
1024
|
+
W as SshPl,
|
|
1025
|
+
b as getDefaultPlVersion,
|
|
1026
|
+
Qt as localPlatformaInit
|
|
1027
|
+
};
|
|
1028
|
+
//# sourceMappingURL=index.mjs.map
|