badmfck-api-server 4.1.57 → 4.1.59

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.
@@ -122,6 +122,7 @@ export declare class DeployerService extends BaseService {
122
122
  private static _extractPort;
123
123
  private _renderNginx;
124
124
  runPM2(found: IConfig, projectState?: IProjectState | null): Promise<IError | null>;
125
+ private _verifyAutorestart;
125
126
  private static readonly SKIP_DIRS;
126
127
  private static readonly TEMPLATE_EXTENSIONS;
127
128
  applyConfig(dir: string, config: Record<string, string>): Promise<void>;
@@ -496,9 +496,12 @@ class DeployerService extends BaseService_1.BaseService {
496
496
  return new Promise((resolve) => {
497
497
  try {
498
498
  (0, child_process_1.execFileSync)("pm2", ["--version"]);
499
- const extra = Array.isArray(pm2Attrs)
499
+ const rawExtra = Array.isArray(pm2Attrs)
500
500
  ? pm2Attrs.filter(s => typeof s === "string" && s.length > 0)
501
501
  : [];
502
+ const extra = rawExtra.filter(a => !/^--no-autorestart(=.*)?$/.test(a));
503
+ if (extra.length !== rawExtra.length)
504
+ (0, LogService_1.logWarn)(`Ignored "--no-autorestart" in pm2 attributes for "${found.name}" — autorestart is force-kept ON so the slot self-recovers.`);
502
505
  const ecoJs = path_1.default.resolve(found.destination, "ecosystem.config.js");
503
506
  const ecoCjs = path_1.default.resolve(found.destination, "ecosystem.config.cjs");
504
507
  const ecosystem = fs_1.default.existsSync(ecoJs) ? ecoJs
@@ -506,6 +509,7 @@ class DeployerService extends BaseService_1.BaseService {
506
509
  : null;
507
510
  if (ecosystem) {
508
511
  (0, child_process_1.execFileSync)("pm2", ["startOrReload", ecosystem, "--update-env", ...extra]);
512
+ this._verifyAutorestart(found.name);
509
513
  resolve(null);
510
514
  return;
511
515
  }
@@ -530,6 +534,7 @@ class DeployerService extends BaseService_1.BaseService {
530
534
  catch { }
531
535
  }
532
536
  (0, child_process_1.execFileSync)("pm2", ["start", found.destination + main, "--name", found.name, ...extra]);
537
+ this._verifyAutorestart(found.name);
533
538
  resolve(null);
534
539
  }
535
540
  catch (e) {
@@ -537,6 +542,18 @@ class DeployerService extends BaseService_1.BaseService {
537
542
  }
538
543
  });
539
544
  }
545
+ _verifyAutorestart(name) {
546
+ try {
547
+ const list = JSON.parse((0, child_process_1.execFileSync)("pm2", ["jlist"]).toString());
548
+ const proc = Array.isArray(list) ? list.find((p) => p?.name === name) : null;
549
+ if (proc?.pm2_env && proc.pm2_env.autorestart === false) {
550
+ (0, LogService_1.logWarn)(`pm2 autorestart is DISABLED for "${name}" — the slot will NOT respawn after a restart/exit. Check the project's ecosystem.config (autorestart:false).`);
551
+ }
552
+ }
553
+ catch (e) {
554
+ (0, LogService_1.logWarn)(`Could not verify pm2 autorestart for "${name}": ${e?.message ?? e}`);
555
+ }
556
+ }
540
557
  static SKIP_DIRS = new Set(["node_modules", ".git", "__tmp"]);
541
558
  static TEMPLATE_EXTENSIONS = [".js", ".ts", ".json", ".env"];
542
559
  async applyConfig(dir, config) {
@@ -401,6 +401,20 @@ class Validator {
401
401
  else if (Array.isArray(tmpl) && Array.isArray(item)) {
402
402
  out[i] = this.syncStructure(tmpl, item);
403
403
  }
404
+ else if (!this.isPlainObject(tmpl) && !Array.isArray(tmpl)) {
405
+ const wantType = typeof tmpl;
406
+ if (typeof item === wantType) {
407
+ out[i] = item;
408
+ }
409
+ else if (wantType === "string" || wantType === "number" || wantType === "boolean") {
410
+ const converted = this.convertToType(item, wantType);
411
+ const bad = converted === null || (wantType === "number" && Number.isNaN(converted));
412
+ out[i] = bad ? this.clone(tmpl) : converted;
413
+ }
414
+ else {
415
+ out[i] = item;
416
+ }
417
+ }
404
418
  else {
405
419
  out[i] = this.syncStructure(tmpl, this.clone(tmpl));
406
420
  }
@@ -6,6 +6,12 @@ export interface IUserAction {
6
6
  action: string;
7
7
  }
8
8
  export declare const S_MONITOR_REGISTRATE_ACTION: Signal<IUserAction>;
9
+ export interface IRestartRequest {
10
+ by: string;
11
+ name: string | null;
12
+ pmId: string | null;
13
+ }
14
+ export declare const S_MONITOR_RESTART_REQUESTED: Signal<IRestartRequest>;
9
15
  export declare class Monitor extends BaseEndpoint {
10
16
  ignoreHttpLogging: boolean;
11
17
  ignoreInDocumentation: boolean;
@@ -24,10 +30,11 @@ export declare class Monitor extends BaseEndpoint {
24
30
  sysinfo(req: HTTPRequestVO): Promise<TransferPacketVO>;
25
31
  procinfo(req: HTTPRequestVO): Promise<TransferPacketVO>;
26
32
  fdreport(req: HTTPRequestVO): Promise<TransferPacketVO>;
33
+ restart(req: HTTPRequestVO): Promise<TransferPacketVO>;
27
34
  httpstats(req: HTTPRequestVO): Promise<TransferPacketVO>;
28
35
  inventory(req: HTTPRequestVO): Promise<TransferPacketVO>;
29
36
  metrics(req: HTTPRequestVO): Promise<TransferPacketVO>;
30
37
  connections(req: HTTPRequestVO): Promise<TransferPacketVO>;
31
38
  db(req: HTTPRequestVO): Promise<TransferPacketVO>;
32
- checkAuthentication(req: HTTPRequestVO): Promise<void>;
39
+ checkAuthentication(req: HTTPRequestVO): Promise<string>;
33
40
  }
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Monitor = exports.S_MONITOR_REGISTRATE_ACTION = void 0;
6
+ exports.Monitor = exports.S_MONITOR_RESTART_REQUESTED = exports.S_MONITOR_REGISTRATE_ACTION = void 0;
7
7
  const badmfck_signal_1 = require("badmfck-signal");
8
8
  const APIService_1 = require("../APIService");
9
9
  const BaseEndpoint_1 = require("../BaseEndpoint");
@@ -19,6 +19,7 @@ const DBService_1 = require("../DBService");
19
19
  const SystemInfo_1 = require("./SystemInfo");
20
20
  const ProcessMonitor_1 = require("./ProcessMonitor");
21
21
  exports.S_MONITOR_REGISTRATE_ACTION = new badmfck_signal_1.Signal();
22
+ exports.S_MONITOR_RESTART_REQUESTED = new badmfck_signal_1.Signal();
22
23
  const logMap = {
23
24
  ALL: 10,
24
25
  API: 19,
@@ -46,6 +47,9 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
46
47
  { ignoreInterceptor: true, endpoint: "sysinfo", handler: this.sysinfo },
47
48
  { ignoreInterceptor: true, endpoint: "procinfo", handler: this.procinfo },
48
49
  { ignoreInterceptor: true, endpoint: "fdreport", handler: this.fdreport },
50
+ { ignoreInterceptor: true, endpoint: "restart", handler: this.restart, validationModel: {
51
+ confirm: ""
52
+ } },
49
53
  { ignoreInterceptor: true, endpoint: "inventory", handler: this.inventory },
50
54
  { ignoreInterceptor: true, endpoint: "httpstats", handler: this.httpstats },
51
55
  { ignoreInterceptor: true, endpoint: "metrics", handler: this.metrics, validationModel: {
@@ -145,6 +149,23 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
145
149
  await this.checkAuthentication(req);
146
150
  return { data: await ProcessMonitor_1.ProcessMonitor.getFdReport() };
147
151
  }
152
+ async restart(req) {
153
+ const login = await this.checkAuthentication(req);
154
+ const id = ProcessMonitor_1.ProcessMonitor.getPm2Identity();
155
+ if (!id.managed)
156
+ throw { ...DefaultErrors_1.default.BAD_REQUEST, details: "Process is not managed by pm2 (no pm_id in env) — refusing to exit, nothing would restart it." };
157
+ if (req.data.confirm !== id.name)
158
+ throw { ...DefaultErrors_1.default.BAD_REQUEST, details: `Confirmation mismatch. To restart send { "confirm": "${id.name}" }.` };
159
+ exports.S_MONITOR_RESTART_REQUESTED.invoke({ by: login, name: id.name, pmId: id.pmId });
160
+ exports.S_MONITOR_REGISTRATE_ACTION.invoke({ action: `restart requested by "${login}" for pm2 "${id.name}" (id ${id.pmId})` });
161
+ (0, LogService_1.logInfo)(`Restart requested via monitor by "${login}" for pm2 process "${id.name}" (id ${id.pmId})`, "Monitor");
162
+ const graceMs = 750;
163
+ setTimeout(() => {
164
+ (0, LogService_1.logInfo)(`Exiting now for pm2 restart (requested by "${login}")`, "Monitor");
165
+ process.exit(0);
166
+ }, graceMs);
167
+ return { data: { restarting: true, name: id.name, pmId: id.pmId, by: login, graceMs } };
168
+ }
148
169
  async httpstats(req) {
149
170
  await this.checkAuthentication(req);
150
171
  return { data: Http_1.Http.getHttpStats() };
@@ -201,7 +222,7 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
201
222
  for (let i of users) {
202
223
  const hash = crypto_1.default.createHash("sha256").update(i.login + ":" + i.password).digest("hex");
203
224
  if (hash === header) {
204
- return;
225
+ return i.login;
205
226
  }
206
227
  }
207
228
  throw { ...DefaultErrors_1.default.UNAUTHORIZED, details: "Invalid user" };
@@ -37,6 +37,14 @@ export declare class ProcessMonitor {
37
37
  startedAtUnix: number;
38
38
  uptimeSec: number;
39
39
  availableParallelism: number;
40
+ pm2: {
41
+ managed: boolean;
42
+ name: string | null;
43
+ pmId: string | null;
44
+ instanceId: string | null;
45
+ execPath: string | null;
46
+ cwd: string | null;
47
+ };
40
48
  };
41
49
  memory: {
42
50
  rss: number;
@@ -117,6 +125,14 @@ export declare class ProcessMonitor {
117
125
  unit: string;
118
126
  }> | null;
119
127
  }>;
128
+ static getPm2Identity(): {
129
+ managed: boolean;
130
+ name: string | null;
131
+ pmId: string | null;
132
+ instanceId: string | null;
133
+ execPath: string | null;
134
+ cwd: string | null;
135
+ };
120
136
  static getFdReport(): Promise<{
121
137
  now: {
122
138
  ts: number;
@@ -198,6 +198,7 @@ class ProcessMonitor {
198
198
  availableParallelism: typeof os_1.default.availableParallelism === "function"
199
199
  ? os_1.default.availableParallelism()
200
200
  : os_1.default.cpus().length,
201
+ pm2: this.getPm2Identity(),
201
202
  },
202
203
  memory: {
203
204
  rss: mem.rss,
@@ -243,6 +244,18 @@ class ProcessMonitor {
243
244
  limits: limitsParsed,
244
245
  };
245
246
  }
247
+ static getPm2Identity() {
248
+ const env = process.env;
249
+ const pmId = env.pm_id ?? null;
250
+ return {
251
+ managed: pmId !== null && pmId !== "",
252
+ name: env.name ?? null,
253
+ pmId,
254
+ instanceId: env.NODE_APP_INSTANCE ?? null,
255
+ execPath: env.pm_exec_path ?? null,
256
+ cwd: env.pm_cwd ?? null,
257
+ };
258
+ }
246
259
  static async getFdReport() {
247
260
  const [sockets, limits, fdOpen] = await Promise.all([
248
261
  this._readSocketBreakdown(),