@automagik/omni 2.260508.1 → 2.260508.3
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/dist/commands/doctor.d.ts +25 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/index.js +180 -26
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
* 5. orphaned-data-dirs — `.pgserve-data/` directories under cwd
|
|
16
16
|
* 6. version-match — CLI version vs. /api/v2/health `version` field
|
|
17
17
|
* 7. pm2-status — omni-api and omni-nats both `online` in pm2
|
|
18
|
-
* 8. pm2-max-restarts — omni-api
|
|
18
|
+
* 8. pm2-max-restarts — omni-api + omni-nats have bounded restarts
|
|
19
19
|
* 9. pm2-logrotate-installed — pm2-logrotate module configured correctly
|
|
20
|
+
* 10. port-canonical-owner — canonical ports owned by pm2-managed PIDs
|
|
20
21
|
*
|
|
21
22
|
* Each check returns OK / WARN / FAIL with a one-line detail. `--fix`
|
|
22
23
|
* attempts repair for checks with a known repair path. The fix flow
|
|
@@ -35,6 +36,12 @@
|
|
|
35
36
|
* so the hardened `--max-restarts` flag takes effect.
|
|
36
37
|
* - pm2-logrotate-installed: Re-run `pm2 install pm2-logrotate` + the four
|
|
37
38
|
* `pm2 set pm2-logrotate:*` commands.
|
|
39
|
+
* - port-canonical-owner: SIGTERM (then SIGKILL) the non-pm2 squatter
|
|
40
|
+
* holding 4222 / api port, then `pm2 restart`
|
|
41
|
+
* the managed entry. Refuses to act when the
|
|
42
|
+
* squatter PID matches another pm2-managed entry.
|
|
43
|
+
* - pm2-status: Reconcile port ownership (above) then `pm2
|
|
44
|
+
* restart` any non-online managed process.
|
|
38
45
|
*/
|
|
39
46
|
import { Command } from 'commander';
|
|
40
47
|
import { type Config, type ServerConfig } from '../config.js';
|
|
@@ -42,7 +49,7 @@ import { type EmbeddedDumpResult } from '../lib/canonical-pgserve.js';
|
|
|
42
49
|
/** Severity levels reported by each check. */
|
|
43
50
|
export type CheckLevel = 'OK' | 'WARN' | 'FAIL';
|
|
44
51
|
/** Identifier used in tests and --json output. */
|
|
45
|
-
export type CheckId = 'pm2-env-drift' | 'cli-key-valid' | 'pgserve-reachable' | 'omni-db-exists' | 'orphaned-data-dirs' | 'version-match' | 'pm2-status' | 'pm2-max-restarts' | 'pm2-logrotate-installed' | 'cli-signing-key-for-locked-instances' | 'pgserve-canonical';
|
|
52
|
+
export type CheckId = 'pm2-env-drift' | 'cli-key-valid' | 'pgserve-reachable' | 'omni-db-exists' | 'orphaned-data-dirs' | 'version-match' | 'pm2-status' | 'pm2-max-restarts' | 'pm2-logrotate-installed' | 'cli-signing-key-for-locked-instances' | 'pgserve-canonical' | 'port-canonical-owner';
|
|
46
53
|
export interface CheckResult {
|
|
47
54
|
id: CheckId;
|
|
48
55
|
level: CheckLevel;
|
|
@@ -77,6 +84,8 @@ export interface DoctorOptions {
|
|
|
77
84
|
interface Pm2Entry {
|
|
78
85
|
name?: string;
|
|
79
86
|
pm_id?: number;
|
|
87
|
+
/** Top-level OS PID of the spawned child. Used by port-canonical-owner. */
|
|
88
|
+
pid?: number;
|
|
80
89
|
pm2_env?: {
|
|
81
90
|
status?: string;
|
|
82
91
|
env?: Record<string, string | undefined>;
|
|
@@ -174,6 +183,20 @@ export interface DoctorDeps {
|
|
|
174
183
|
* to ~/.omni/config.json.
|
|
175
184
|
*/
|
|
176
185
|
saveServerConfig: (partial: Partial<ServerConfig>) => void;
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the OS PID currently bound to a TCP port (LISTEN). Returns
|
|
188
|
+
* null when nothing is listening, when the lookup fails, or when the
|
|
189
|
+
* platform tool (`ss`) is unavailable. Used by the port-canonical-owner
|
|
190
|
+
* check + fixer to detect non-pm2 squatters on canonical ports.
|
|
191
|
+
*/
|
|
192
|
+
findPortOwner: (port: number) => Promise<number | null>;
|
|
193
|
+
/**
|
|
194
|
+
* Send a signal to a PID. Returns true when the signal was delivered,
|
|
195
|
+
* false on EPERM/ESRCH/etc. We never throw — callers treat falsy as
|
|
196
|
+
* "could not signal, fall through to next strategy". Tests stub this
|
|
197
|
+
* so they don't actually kill anything.
|
|
198
|
+
*/
|
|
199
|
+
processKill: (pid: number, signal: 'SIGTERM' | 'SIGKILL') => boolean;
|
|
177
200
|
}
|
|
178
201
|
/**
|
|
179
202
|
* Run all checks and optionally apply fixes. Returns a structured
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAMH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,KAAK,MAAM,EACX,KAAK,YAAY,EAKlB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,6BAA6B,CAAC;AAarC,8CAA8C;AAC9C,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhD,kDAAkD;AAClD,MAAM,MAAM,OAAO,GACf,eAAe,GACf,eAAe,GACf,mBAAmB,GACnB,gBAAgB,GAChB,oBAAoB,GACpB,eAAe,GACf,YAAY,GACZ,kBAAkB,GAClB,yBAAyB,GACzB,sCAAsC,GACtC,mBAAmB,GACnB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;GAGG;AACH,UAAU,QAAQ;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;QACzC,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7B;AAoCD,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,eAAe,EAAE,MAAM,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IAClD,+DAA+D;IAC/D,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,uEAAuE;IACvE,YAAY,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACrC,oEAAoE;IACpE,oBAAoB,EAAE,MAAM,MAAM,EAAE,CAAC;IACrC,qDAAqD;IACrD,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChE,8DAA8D;IAC9D,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,6CAA6C;IAC7C,SAAS,EAAE,MAAM;QAAE,YAAY,EAAE,YAAY,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE;;;OAGG;IACH,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1E,oEAAoE;IACpE,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,2DAA2D;IAC3D,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,cAAc,EAAE,MAAM,MAAM,CAAC;IAC7B,mEAAmE;IACnE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,iFAAiF;IACjF,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C;;;;;OAKG;IACH,mBAAmB,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IACnE;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC;;;;OAIG;IACH,qBAAqB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpD;;;;;OAKG;IACH,cAAc,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC5E;;;;OAIG;IACH,0BAA0B,EAAE,CAC1B,IAAI,EAAE,kBAAkB,EACxB,oBAAoB,EAAE,MAAM,KACzB,OAAO,CAAC;QAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,6EAA6E;IAC7E,0BAA0B,EAAE,MAAM,MAAM,CAAC;IACzC;;;;OAIG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IAC3D;;;;;OAKG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD;;;;;OAKG;IACH,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,KAAK,OAAO,CAAC;CACtE;AAi+BD;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,YAAY,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAiBxG;AA2BD,wBAAgB,mBAAmB,IAAI,OAAO,CAiD7C"}
|
package/dist/index.js
CHANGED
|
@@ -123990,7 +123990,7 @@ import { fileURLToPath } from "url";
|
|
|
123990
123990
|
// package.json
|
|
123991
123991
|
var package_default = {
|
|
123992
123992
|
name: "@automagik/omni",
|
|
123993
|
-
version: "2.260508.
|
|
123993
|
+
version: "2.260508.3",
|
|
123994
123994
|
description: "LLM-optimized CLI for Omni",
|
|
123995
123995
|
type: "module",
|
|
123996
123996
|
bin: {
|
|
@@ -129027,7 +129027,31 @@ function productionDeps() {
|
|
|
129027
129027
|
dumpEmbeddedDb,
|
|
129028
129028
|
restoreSnapshotToCanonical,
|
|
129029
129029
|
getCanonicalPgserveDataDir,
|
|
129030
|
-
saveServerConfig
|
|
129030
|
+
saveServerConfig,
|
|
129031
|
+
findPortOwner: async (port) => {
|
|
129032
|
+
try {
|
|
129033
|
+
const proc = Bun.spawn(["ss", "-tlnpH", `sport = :${port}`], {
|
|
129034
|
+
stdout: "pipe",
|
|
129035
|
+
stderr: "ignore"
|
|
129036
|
+
});
|
|
129037
|
+
const stdout = await new Response(proc.stdout).text();
|
|
129038
|
+
const exitCode = await proc.exited;
|
|
129039
|
+
if (exitCode !== 0)
|
|
129040
|
+
return null;
|
|
129041
|
+
const match = stdout.match(/pid=(\d+)/);
|
|
129042
|
+
return match ? Number.parseInt(match[1], 10) : null;
|
|
129043
|
+
} catch {
|
|
129044
|
+
return null;
|
|
129045
|
+
}
|
|
129046
|
+
},
|
|
129047
|
+
processKill: (pid, signal) => {
|
|
129048
|
+
try {
|
|
129049
|
+
process.kill(pid, signal);
|
|
129050
|
+
return true;
|
|
129051
|
+
} catch {
|
|
129052
|
+
return false;
|
|
129053
|
+
}
|
|
129054
|
+
}
|
|
129031
129055
|
};
|
|
129032
129056
|
}
|
|
129033
129057
|
function scanForOrphans(dir, acc, depth, maxDepth = 4) {
|
|
@@ -129162,36 +129186,41 @@ async function checkPm2MaxRestarts(deps) {
|
|
|
129162
129186
|
if (!processes) {
|
|
129163
129187
|
return { id: "pm2-max-restarts", level: "WARN", detail: "pm2 not reachable" };
|
|
129164
129188
|
}
|
|
129165
|
-
const
|
|
129166
|
-
|
|
129167
|
-
|
|
129168
|
-
|
|
129169
|
-
|
|
129170
|
-
|
|
129171
|
-
|
|
129172
|
-
|
|
129173
|
-
|
|
129174
|
-
|
|
129175
|
-
|
|
129176
|
-
|
|
129177
|
-
|
|
129178
|
-
|
|
129179
|
-
|
|
129180
|
-
|
|
129181
|
-
|
|
129182
|
-
|
|
129189
|
+
const issues = [];
|
|
129190
|
+
let hasFail = false;
|
|
129191
|
+
const targets = [PM2_PROCESSES.api, PM2_PROCESSES.nats];
|
|
129192
|
+
for (const name of targets) {
|
|
129193
|
+
const entry = processes.find((p) => p.name === name);
|
|
129194
|
+
if (!entry) {
|
|
129195
|
+
issues.push(`${name} not found in pm2`);
|
|
129196
|
+
continue;
|
|
129197
|
+
}
|
|
129198
|
+
const v = entry.pm2_env?.max_restarts;
|
|
129199
|
+
if (typeof v !== "number") {
|
|
129200
|
+
hasFail = true;
|
|
129201
|
+
issues.push(`${name} has no max_restarts set \u2014 crash loops are unbounded`);
|
|
129202
|
+
continue;
|
|
129203
|
+
}
|
|
129204
|
+
if (v === 0 || v >= 1000) {
|
|
129205
|
+
hasFail = true;
|
|
129206
|
+
issues.push(`${name} max_restarts=${v} \u2014 crash loops are effectively unbounded`);
|
|
129207
|
+
continue;
|
|
129208
|
+
}
|
|
129209
|
+
if (!(v >= 5 && v <= 50)) {
|
|
129210
|
+
issues.push(`${name} max_restarts=${v} \u2014 expected 5..50`);
|
|
129211
|
+
}
|
|
129183
129212
|
}
|
|
129184
|
-
if (
|
|
129213
|
+
if (issues.length === 0) {
|
|
129185
129214
|
return {
|
|
129186
129215
|
id: "pm2-max-restarts",
|
|
129187
129216
|
level: "OK",
|
|
129188
|
-
detail: `${PM2_PROCESSES.api}
|
|
129217
|
+
detail: `${PM2_PROCESSES.api} and ${PM2_PROCESSES.nats} max_restarts in hardened range`
|
|
129189
129218
|
};
|
|
129190
129219
|
}
|
|
129191
129220
|
return {
|
|
129192
129221
|
id: "pm2-max-restarts",
|
|
129193
|
-
level: "WARN",
|
|
129194
|
-
detail:
|
|
129222
|
+
level: hasFail ? "FAIL" : "WARN",
|
|
129223
|
+
detail: issues.join("; ")
|
|
129195
129224
|
};
|
|
129196
129225
|
}
|
|
129197
129226
|
async function checkPm2LogrotateInstalled(deps) {
|
|
@@ -129226,6 +129255,48 @@ async function checkPm2LogrotateInstalled(deps) {
|
|
|
129226
129255
|
detail: "pm2-logrotate installed with expected settings"
|
|
129227
129256
|
};
|
|
129228
129257
|
}
|
|
129258
|
+
function canonicalPortTargets(serverConfig) {
|
|
129259
|
+
return [
|
|
129260
|
+
{ name: PM2_PROCESSES.api, port: serverConfig.port },
|
|
129261
|
+
{ name: PM2_PROCESSES.nats, port: 4222 }
|
|
129262
|
+
];
|
|
129263
|
+
}
|
|
129264
|
+
async function checkPortCanonicalOwner(deps) {
|
|
129265
|
+
const processes = await deps.getPm2Processes();
|
|
129266
|
+
if (!processes) {
|
|
129267
|
+
return { id: "port-canonical-owner", level: "WARN", detail: "pm2 not reachable" };
|
|
129268
|
+
}
|
|
129269
|
+
const { serverConfig } = deps.loadState();
|
|
129270
|
+
const targets = canonicalPortTargets(serverConfig);
|
|
129271
|
+
const issues = [];
|
|
129272
|
+
for (const { name, port } of targets) {
|
|
129273
|
+
const pm2Entry = processes.find((p) => p.name === name);
|
|
129274
|
+
const pm2Pid = pm2Entry?.pid;
|
|
129275
|
+
const ownerPid = await deps.findPortOwner(port);
|
|
129276
|
+
if (ownerPid === null) {
|
|
129277
|
+
continue;
|
|
129278
|
+
}
|
|
129279
|
+
if (typeof pm2Pid !== "number" || pm2Pid <= 0) {
|
|
129280
|
+
issues.push(`${name}:${port} owned by pid=${ownerPid} but pm2 has no PID for ${name}`);
|
|
129281
|
+
continue;
|
|
129282
|
+
}
|
|
129283
|
+
if (ownerPid !== pm2Pid) {
|
|
129284
|
+
issues.push(`${name}:${port} owned by non-pm2 pid=${ownerPid} (pm2 child pid=${pm2Pid})`);
|
|
129285
|
+
}
|
|
129286
|
+
}
|
|
129287
|
+
if (issues.length === 0) {
|
|
129288
|
+
return {
|
|
129289
|
+
id: "port-canonical-owner",
|
|
129290
|
+
level: "OK",
|
|
129291
|
+
detail: "all canonical ports owned by pm2-managed processes"
|
|
129292
|
+
};
|
|
129293
|
+
}
|
|
129294
|
+
return {
|
|
129295
|
+
id: "port-canonical-owner",
|
|
129296
|
+
level: "FAIL",
|
|
129297
|
+
detail: issues.join("; ")
|
|
129298
|
+
};
|
|
129299
|
+
}
|
|
129229
129300
|
async function fixPm2EnvDrift(deps) {
|
|
129230
129301
|
const { serverConfig, cliConfig } = deps.loadState();
|
|
129231
129302
|
const env2 = buildRuntimeEnv(serverConfig, cliConfig);
|
|
@@ -129335,6 +129406,83 @@ async function fixPgserveCanonical(deps) {
|
|
|
129335
129406
|
const dataNote = dumpResult.status === "dumped" && restoreOutcome.status === "restored" ? `restored ${dumpResult.snapshotPath} into ${canonicalDir} (omni-api \u2192 ${url})` : dumpResult.status === "no-embedded-data" ? `no embedded data to migrate; canonical started empty at ${canonicalDir} (omni-api \u2192 ${url})` : `embedded data dir invalid; canonical started empty at ${canonicalDir} (omni-api \u2192 ${url})`;
|
|
129336
129407
|
return `migrated to canonical pgserve@^2.1.0; ${dataNote}`;
|
|
129337
129408
|
}
|
|
129409
|
+
async function waitForPortRelease(deps, pid, port) {
|
|
129410
|
+
for (let i = 0;i < 10; i++) {
|
|
129411
|
+
await deps.sleepMs(500);
|
|
129412
|
+
const stillOwner = await deps.findPortOwner(port);
|
|
129413
|
+
if (stillOwner !== pid)
|
|
129414
|
+
return true;
|
|
129415
|
+
}
|
|
129416
|
+
return false;
|
|
129417
|
+
}
|
|
129418
|
+
async function reclaimPortFromSquatter(deps, target, pm2Pid, managedPids) {
|
|
129419
|
+
const { name, port } = target;
|
|
129420
|
+
const ownerPid = await deps.findPortOwner(port);
|
|
129421
|
+
if (ownerPid === null)
|
|
129422
|
+
return null;
|
|
129423
|
+
if (typeof pm2Pid === "number" && ownerPid === pm2Pid)
|
|
129424
|
+
return null;
|
|
129425
|
+
if (managedPids.has(ownerPid)) {
|
|
129426
|
+
return `SKIPPED ${name}:${port} squatter pid=${ownerPid} is itself pm2-managed`;
|
|
129427
|
+
}
|
|
129428
|
+
if (!deps.processKill(ownerPid, "SIGTERM")) {
|
|
129429
|
+
return `FAILED ${name}:${port} could not signal pid=${ownerPid} (EPERM/ESRCH)`;
|
|
129430
|
+
}
|
|
129431
|
+
const released = await waitForPortRelease(deps, ownerPid, port);
|
|
129432
|
+
if (!released) {
|
|
129433
|
+
deps.processKill(ownerPid, "SIGKILL");
|
|
129434
|
+
await deps.sleepMs(500);
|
|
129435
|
+
}
|
|
129436
|
+
const code = await deps.runPm2(["restart", name]);
|
|
129437
|
+
if (code !== 0) {
|
|
129438
|
+
return `FAILED ${name}:${port} pm2 restart exited ${code} after killing pid=${ownerPid}`;
|
|
129439
|
+
}
|
|
129440
|
+
return `reclaimed ${name}:${port} from non-pm2 pid=${ownerPid}`;
|
|
129441
|
+
}
|
|
129442
|
+
async function fixPortCanonicalOwner(deps) {
|
|
129443
|
+
const processes = await deps.getPm2Processes();
|
|
129444
|
+
if (!processes) {
|
|
129445
|
+
throw new Error("pm2 not reachable \u2014 cannot reconcile port ownership");
|
|
129446
|
+
}
|
|
129447
|
+
const { serverConfig } = deps.loadState();
|
|
129448
|
+
const targets = canonicalPortTargets(serverConfig);
|
|
129449
|
+
const managedPids = new Set(processes.map((p) => p.pid).filter((p) => typeof p === "number" && p > 0));
|
|
129450
|
+
const repairs = [];
|
|
129451
|
+
for (const target of targets) {
|
|
129452
|
+
const pm2Entry = processes.find((p) => p.name === target.name);
|
|
129453
|
+
const outcome = await reclaimPortFromSquatter(deps, target, pm2Entry?.pid, managedPids);
|
|
129454
|
+
if (outcome !== null)
|
|
129455
|
+
repairs.push(outcome);
|
|
129456
|
+
}
|
|
129457
|
+
if (repairs.length === 0)
|
|
129458
|
+
return "no port-ownership conflicts to reconcile";
|
|
129459
|
+
return repairs.join("; ");
|
|
129460
|
+
}
|
|
129461
|
+
async function restartNonOnlineProcesses(deps) {
|
|
129462
|
+
const processes = await deps.getPm2Processes();
|
|
129463
|
+
if (!processes)
|
|
129464
|
+
return [];
|
|
129465
|
+
const restarted = [];
|
|
129466
|
+
for (const name of [PM2_PROCESSES.api, PM2_PROCESSES.nats]) {
|
|
129467
|
+
const entry = processes.find((p) => p.name === name);
|
|
129468
|
+
if (entry?.pm2_env?.status !== "online") {
|
|
129469
|
+
const code = await deps.runPm2(["restart", name]);
|
|
129470
|
+
if (code === 0)
|
|
129471
|
+
restarted.push(name);
|
|
129472
|
+
}
|
|
129473
|
+
}
|
|
129474
|
+
return restarted;
|
|
129475
|
+
}
|
|
129476
|
+
async function fixPm2Status(deps) {
|
|
129477
|
+
const portRepairs = await fixPortCanonicalOwner(deps);
|
|
129478
|
+
const restarted = await restartNonOnlineProcesses(deps);
|
|
129479
|
+
const parts = [];
|
|
129480
|
+
if (portRepairs !== "no port-ownership conflicts to reconcile")
|
|
129481
|
+
parts.push(portRepairs);
|
|
129482
|
+
if (restarted.length > 0)
|
|
129483
|
+
parts.push(`restarted ${restarted.join(", ")}`);
|
|
129484
|
+
return parts.length > 0 ? parts.join("; ") : "no pm2 status remediation needed";
|
|
129485
|
+
}
|
|
129338
129486
|
function fixOrphanedDataDirs(deps) {
|
|
129339
129487
|
const found = deps.findOrphanedDataDirs();
|
|
129340
129488
|
if (found.length === 0) {
|
|
@@ -129400,7 +129548,8 @@ async function runAllChecks(deps) {
|
|
|
129400
129548
|
await checkPm2MaxRestarts(deps),
|
|
129401
129549
|
await checkPm2LogrotateInstalled(deps),
|
|
129402
129550
|
await checkSigningKeyForLockedInstances(deps),
|
|
129403
|
-
checkPgserveCanonical(deps)
|
|
129551
|
+
checkPgserveCanonical(deps),
|
|
129552
|
+
await checkPortCanonicalOwner(deps)
|
|
129404
129553
|
];
|
|
129405
129554
|
}
|
|
129406
129555
|
async function applyFix(deps, check) {
|
|
@@ -129417,6 +129566,10 @@ async function applyFix(deps, check) {
|
|
|
129417
129566
|
return await fixPm2LogrotateInstalled(deps);
|
|
129418
129567
|
if (check.id === "pgserve-canonical")
|
|
129419
129568
|
return await fixPgserveCanonical(deps);
|
|
129569
|
+
if (check.id === "port-canonical-owner")
|
|
129570
|
+
return await fixPortCanonicalOwner(deps);
|
|
129571
|
+
if (check.id === "pm2-status")
|
|
129572
|
+
return await fixPm2Status(deps);
|
|
129420
129573
|
return null;
|
|
129421
129574
|
} catch (err) {
|
|
129422
129575
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -129508,9 +129661,10 @@ Checks:
|
|
|
129508
129661
|
orphaned-data-dirs .pgserve-data directories outside ~/.omni
|
|
129509
129662
|
version-match CLI version vs. /api/v2/health version field
|
|
129510
129663
|
pm2-status omni-api and omni-nats both online in pm2
|
|
129511
|
-
pm2-max-restarts omni-api max_restarts
|
|
129664
|
+
pm2-max-restarts omni-api + omni-nats max_restarts in hardened range
|
|
129512
129665
|
pm2-logrotate-installed pm2-logrotate module installed with expected settings
|
|
129513
129666
|
pgserve-canonical using canonical pgserve@^2.1.0 (shared backbone) vs. embedded
|
|
129667
|
+
port-canonical-owner canonical ports (NATS, API) owned by pm2-managed PIDs
|
|
129514
129668
|
|
|
129515
129669
|
Safety:
|
|
129516
129670
|
--fix NEVER touches ~/.omni/data/pgserve \u2014 it only operates on the pm2
|
package/dist/server/index.js
CHANGED
|
@@ -230060,7 +230060,7 @@ var init_sentry_scrub = __esm(() => {
|
|
|
230060
230060
|
var require_package8 = __commonJS((exports, module) => {
|
|
230061
230061
|
module.exports = {
|
|
230062
230062
|
name: "@omni/api",
|
|
230063
|
-
version: "2.260508.
|
|
230063
|
+
version: "2.260508.3",
|
|
230064
230064
|
type: "module",
|
|
230065
230065
|
exports: {
|
|
230066
230066
|
".": {
|