@mnemonik/scanner 5.119.5 → 5.131.2
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/doctor.d.ts +1 -0
- package/dist/doctor.js +201 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fileLog.d.ts +17 -0
- package/dist/fileLog.js +70 -0
- package/dist/fileLog.js.map +1 -0
- package/dist/index.js +34 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/doctor.ts +207 -0
- package/src/fileLog.ts +70 -0
- package/src/index.ts +39 -16
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDoctor(): Promise<void>;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
/**
|
|
7
|
+
* `mnemonik-scanner doctor` — the guard that turns the prose upgrade procedure
|
|
8
|
+
* into an executable check. It asserts the invariants that every past botched
|
|
9
|
+
* reinstall violated, and prints the one blessed remediation. Any single ✗
|
|
10
|
+
* exits non-zero so a wrapper (or a human) can react.
|
|
11
|
+
*
|
|
12
|
+
* Invariants:
|
|
13
|
+
* 1. Exactly one `mnemonik-scanner` on PATH, inside the user npm prefix
|
|
14
|
+
* (a second copy under /usr is the classic PATH-shadow version skew).
|
|
15
|
+
* 2. If a systemd user unit exists, it is active and its ExecStart points at
|
|
16
|
+
* that same binary.
|
|
17
|
+
* 3. Exactly one live daemon, and under systemd its PID == the unit MainPID
|
|
18
|
+
* (a stray `node …/scanner/dist/index.js` is a duplicate local-build daemon).
|
|
19
|
+
* 4. No leftover legacy per-project daemons.
|
|
20
|
+
* 5. Installed version is not behind npm latest (warning, not failure).
|
|
21
|
+
*/
|
|
22
|
+
const HOME = homedir();
|
|
23
|
+
const MNEMONIK_DIR = join(HOME, '.mnemonik');
|
|
24
|
+
const PID_FILE = join(MNEMONIK_DIR, 'daemon.pid');
|
|
25
|
+
const LEGACY_DAEMONS_DIR = join(MNEMONIK_DIR, 'daemons');
|
|
26
|
+
const UNIT_FILE = join(HOME, '.config/systemd/user/mnemonik-scanner.service');
|
|
27
|
+
function sh(cmd, args, timeoutMs = 5000) {
|
|
28
|
+
try {
|
|
29
|
+
return execFileSync(cmd, args, {
|
|
30
|
+
encoding: 'utf-8',
|
|
31
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
32
|
+
timeout: timeoutMs,
|
|
33
|
+
}).trim();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function pidAlive(pid) {
|
|
40
|
+
try {
|
|
41
|
+
process.kill(pid, 0);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function installedVersion() {
|
|
49
|
+
try {
|
|
50
|
+
const here = fileURLToPath(new URL('.', import.meta.url));
|
|
51
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf-8'));
|
|
52
|
+
return pkg.version ?? null;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function runDoctor() {
|
|
59
|
+
const checks = [];
|
|
60
|
+
// 1. Binary uniqueness + location.
|
|
61
|
+
const prefix = sh('npm', ['config', 'get', 'prefix']);
|
|
62
|
+
const whichOut = sh('which', ['-a', 'mnemonik-scanner']);
|
|
63
|
+
const binPaths = whichOut ? whichOut.split('\n').map((s) => s.trim()).filter(Boolean) : [];
|
|
64
|
+
if (binPaths.length === 0) {
|
|
65
|
+
checks.push({ level: 'fail', label: 'binary on PATH', detail: 'mnemonik-scanner not found on PATH' });
|
|
66
|
+
}
|
|
67
|
+
else if (binPaths.length > 1) {
|
|
68
|
+
checks.push({
|
|
69
|
+
level: 'fail',
|
|
70
|
+
label: 'single binary on PATH',
|
|
71
|
+
detail: `found ${binPaths.length} copies (PATH shadow → version skew):\n ${binPaths.join('\n ')}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else if (prefix && !binPaths[0].startsWith(prefix)) {
|
|
75
|
+
checks.push({
|
|
76
|
+
level: 'warn',
|
|
77
|
+
label: 'binary in user npm prefix',
|
|
78
|
+
detail: `${binPaths[0]} is not under npm prefix ${prefix}`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
checks.push({ level: 'ok', label: 'single binary on PATH', detail: binPaths[0] });
|
|
83
|
+
}
|
|
84
|
+
const binPath = binPaths.length === 1 ? binPaths[0] : null;
|
|
85
|
+
// 2. systemd unit health + ExecStart match.
|
|
86
|
+
let unitMainPid = null;
|
|
87
|
+
const hasUnit = existsSync(UNIT_FILE);
|
|
88
|
+
if (hasUnit) {
|
|
89
|
+
const show = sh('systemctl', [
|
|
90
|
+
'--user',
|
|
91
|
+
'show',
|
|
92
|
+
'mnemonik-scanner',
|
|
93
|
+
'-p',
|
|
94
|
+
'ActiveState',
|
|
95
|
+
'-p',
|
|
96
|
+
'MainPID',
|
|
97
|
+
'-p',
|
|
98
|
+
'ExecStart',
|
|
99
|
+
]);
|
|
100
|
+
const active = /ActiveState=active/.test(show ?? '');
|
|
101
|
+
const mainPidMatch = /MainPID=(\d+)/.exec(show ?? '');
|
|
102
|
+
unitMainPid = mainPidMatch ? parseInt(mainPidMatch[1], 10) : null;
|
|
103
|
+
const execMatch = /path=([^\s;]+)/.exec(show ?? '');
|
|
104
|
+
const execPath = execMatch ? execMatch[1] : null;
|
|
105
|
+
if (!active) {
|
|
106
|
+
checks.push({ level: 'fail', label: 'systemd unit active', detail: 'unit is not active' });
|
|
107
|
+
}
|
|
108
|
+
else if (binPath && execPath && execPath !== binPath) {
|
|
109
|
+
checks.push({
|
|
110
|
+
level: 'fail',
|
|
111
|
+
label: 'ExecStart matches binary',
|
|
112
|
+
detail: `unit runs ${execPath} but PATH resolves ${binPath}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
checks.push({ level: 'ok', label: 'systemd unit active', detail: `MainPID ${unitMainPid ?? '?'}` });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
checks.push({ level: 'warn', label: 'systemd unit', detail: 'no user unit — daemon must be supervised another way' });
|
|
121
|
+
}
|
|
122
|
+
// 3. Single live daemon; under systemd it must equal MainPID.
|
|
123
|
+
let filePid = null;
|
|
124
|
+
try {
|
|
125
|
+
filePid = parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
126
|
+
if (Number.isNaN(filePid))
|
|
127
|
+
filePid = null;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
filePid = null;
|
|
131
|
+
}
|
|
132
|
+
const psOut = sh('ps', ['-eo', 'pid,cmd']);
|
|
133
|
+
const daemonPids = (psOut ?? '')
|
|
134
|
+
.split('\n')
|
|
135
|
+
// Only the long-running `start` daemon counts — never a `doctor`/`status`/
|
|
136
|
+
// `log`/`stop` invocation (which also runs …/scanner/dist/index.js), and
|
|
137
|
+
// never this doctor process itself.
|
|
138
|
+
.filter((l) => /(mnemonik-scanner|scanner\/dist\/index\.js)/.test(l) && /\bstart\b/.test(l))
|
|
139
|
+
.map((l) => parseInt(l.trim().split(/\s+/)[0], 10))
|
|
140
|
+
.filter((n) => !Number.isNaN(n) && n !== process.pid);
|
|
141
|
+
if (daemonPids.length === 0) {
|
|
142
|
+
checks.push({ level: 'fail', label: 'daemon running', detail: 'no daemon process found' });
|
|
143
|
+
}
|
|
144
|
+
else if (daemonPids.length > 1) {
|
|
145
|
+
checks.push({
|
|
146
|
+
level: 'fail',
|
|
147
|
+
label: 'single daemon',
|
|
148
|
+
detail: `${daemonPids.length} daemon processes (duplicate) — PIDs ${daemonPids.join(', ')}`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else if (hasUnit && unitMainPid && daemonPids[0] !== unitMainPid) {
|
|
152
|
+
checks.push({
|
|
153
|
+
level: 'fail',
|
|
154
|
+
label: 'daemon is the systemd one',
|
|
155
|
+
detail: `running PID ${daemonPids[0]} != unit MainPID ${unitMainPid} (stray local-build daemon)`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else if (filePid && !pidAlive(filePid)) {
|
|
159
|
+
checks.push({ level: 'warn', label: 'PID file fresh', detail: `stale ${PID_FILE} (PID ${filePid} dead)` });
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
checks.push({ level: 'ok', label: 'single daemon', detail: `PID ${daemonPids[0]}` });
|
|
163
|
+
}
|
|
164
|
+
// 4. Legacy per-project daemons.
|
|
165
|
+
try {
|
|
166
|
+
const legacy = readdirSync(LEGACY_DAEMONS_DIR)
|
|
167
|
+
.filter((f) => f.endsWith('.pid'))
|
|
168
|
+
.map((f) => parseInt(readFileSync(join(LEGACY_DAEMONS_DIR, f), 'utf-8').trim(), 10))
|
|
169
|
+
.filter((n) => !Number.isNaN(n) && pidAlive(n));
|
|
170
|
+
if (legacy.length > 0) {
|
|
171
|
+
checks.push({ level: 'fail', label: 'no legacy daemons', detail: `legacy per-project daemons alive: ${legacy.join(', ')}` });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// dir absent — nothing to check
|
|
176
|
+
}
|
|
177
|
+
// 5. Version vs npm latest (informational).
|
|
178
|
+
const local = installedVersion();
|
|
179
|
+
const latest = sh('npm', ['view', '@mnemonik/scanner', 'version'], 15000);
|
|
180
|
+
if (local && latest && local !== latest) {
|
|
181
|
+
checks.push({ level: 'warn', label: 'up to date', detail: `installed ${local}, npm latest ${latest} — run: make scanner-deploy` });
|
|
182
|
+
}
|
|
183
|
+
else if (local) {
|
|
184
|
+
checks.push({ level: 'ok', label: 'up to date', detail: `v${local}` });
|
|
185
|
+
}
|
|
186
|
+
// Report.
|
|
187
|
+
const icon = { ok: '✓', warn: '⚠', fail: '✗' };
|
|
188
|
+
console.log('mnemonik-scanner doctor\n');
|
|
189
|
+
for (const c of checks) {
|
|
190
|
+
console.log(` ${icon[c.level]} ${c.label}${c.detail ? `: ${c.detail}` : ''}`);
|
|
191
|
+
}
|
|
192
|
+
const failed = checks.filter((c) => c.level === 'fail');
|
|
193
|
+
if (failed.length > 0) {
|
|
194
|
+
console.log(`\n${failed.length} problem(s). Canonical fix: \`make scanner-deploy\` (build → publish shared-first → npm i -g → systemctl restart → re-verify).\n` +
|
|
195
|
+
'Do NOT hand-copy dist/ or run `make daemon-*` on a systemd host — those are what cause this.');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
console.log('\nAll invariants hold.');
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only sink that mirrors the daemon's console output to a file the
|
|
3
|
+
* `mnemonik-scanner log` command can tail. Rotates to `<file>.old` once the
|
|
4
|
+
* file would pass `maxSize`, so a daemon that runs for weeks can't grow it
|
|
5
|
+
* without bound. Every operation is best-effort: a logging failure must never
|
|
6
|
+
* crash the daemon, so all filesystem errors are swallowed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createRotatingFileSink(logFile: string, maxSize: number): (chunk: string) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Tee `process.stdout`/`process.stderr` into `logFile` on top of their normal
|
|
11
|
+
* destination. This is what makes `mnemonik-scanner log` reflect live activity
|
|
12
|
+
* regardless of how the daemon is supervised: under systemd stdout is wired to
|
|
13
|
+
* the journal (a socket), under a bare shell to the terminal — either way the
|
|
14
|
+
* file now receives the same lines. Call once at daemon start, before the
|
|
15
|
+
* daemon writes anything worth capturing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function installFileLogging(logFile: string, maxSize: number): void;
|
package/dist/fileLog.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { appendFileSync, renameSync, statSync } from 'fs';
|
|
2
|
+
/**
|
|
3
|
+
* Append-only sink that mirrors the daemon's console output to a file the
|
|
4
|
+
* `mnemonik-scanner log` command can tail. Rotates to `<file>.old` once the
|
|
5
|
+
* file would pass `maxSize`, so a daemon that runs for weeks can't grow it
|
|
6
|
+
* without bound. Every operation is best-effort: a logging failure must never
|
|
7
|
+
* crash the daemon, so all filesystem errors are swallowed.
|
|
8
|
+
*/
|
|
9
|
+
export function createRotatingFileSink(logFile, maxSize) {
|
|
10
|
+
let size = 0;
|
|
11
|
+
try {
|
|
12
|
+
size = statSync(logFile).size;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
size = 0;
|
|
16
|
+
}
|
|
17
|
+
return (chunk) => {
|
|
18
|
+
if (!chunk)
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
const bytes = Buffer.byteLength(chunk);
|
|
22
|
+
if (size > 0 && size + bytes > maxSize) {
|
|
23
|
+
try {
|
|
24
|
+
renameSync(logFile, logFile + '.old');
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Rotation failed (e.g. cross-device) — keep appending to the
|
|
28
|
+
// current file rather than losing the line.
|
|
29
|
+
}
|
|
30
|
+
size = 0;
|
|
31
|
+
}
|
|
32
|
+
appendFileSync(logFile, chunk);
|
|
33
|
+
size += bytes;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Never let logging break the daemon.
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Tee `process.stdout`/`process.stderr` into `logFile` on top of their normal
|
|
42
|
+
* destination. This is what makes `mnemonik-scanner log` reflect live activity
|
|
43
|
+
* regardless of how the daemon is supervised: under systemd stdout is wired to
|
|
44
|
+
* the journal (a socket), under a bare shell to the terminal — either way the
|
|
45
|
+
* file now receives the same lines. Call once at daemon start, before the
|
|
46
|
+
* daemon writes anything worth capturing.
|
|
47
|
+
*/
|
|
48
|
+
export function installFileLogging(logFile, maxSize) {
|
|
49
|
+
const sink = createRotatingFileSink(logFile, maxSize);
|
|
50
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
51
|
+
const original = stream.write.bind(stream);
|
|
52
|
+
stream.write = ((chunk, encoding, cb) => {
|
|
53
|
+
try {
|
|
54
|
+
if (typeof chunk === 'string') {
|
|
55
|
+
sink(chunk);
|
|
56
|
+
}
|
|
57
|
+
else if (Buffer.isBuffer(chunk)) {
|
|
58
|
+
const enc = typeof encoding === 'string' ? encoding : 'utf8';
|
|
59
|
+
sink(chunk.toString(enc));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ignore — mirroring is best-effort
|
|
64
|
+
}
|
|
65
|
+
// Preserve the real stream's overloads (chunk, cb) / (chunk, enc, cb).
|
|
66
|
+
return original(chunk, encoding, cb);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=fileLog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileLog.js","sourceRoot":"","sources":["../src/fileLog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAE1D;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAe,EACf,OAAe;IAEf,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC;QACH,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,KAAa,EAAQ,EAAE;QAC7B,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACvC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,GAAG,OAAO,EAAE,CAAC;gBACvC,IAAI,CAAC;oBACH,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;gBACxC,CAAC;gBAAC,MAAM,CAAC;oBACP,8DAA8D;oBAC9D,4CAA4C;gBAC9C,CAAC;gBACD,IAAI,GAAG,CAAC,CAAC;YACX,CAAC;YACD,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC/B,IAAI,IAAI,KAAK,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,OAAe;IACjE,MAAM,IAAI,GAAG,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAEtD,KAAK,MAAM,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAoC,CAAC;QAC9E,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,KAAc,EAAE,QAAkB,EAAE,EAAY,EAAW,EAAE;YAC5E,IAAI,CAAC;gBACH,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC9B,IAAI,CAAC,KAAK,CAAC,CAAC;gBACd,CAAC;qBAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBAClC,MAAM,GAAG,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAE,QAA2B,CAAC,CAAC,CAAC,MAAM,CAAC;oBACjF,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC5B,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,oCAAoC;YACtC,CAAC;YACD,uEAAuE;YACvE,OAAO,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QACvC,CAAC,CAAwB,CAAC;IAC5B,CAAC;AACH,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile, writeFile, unlink, mkdir, stat, chmod } from 'fs/promises';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
3
4
|
import { join } from 'path';
|
|
4
5
|
import { homedir } from 'os';
|
|
5
6
|
import { ScannerDaemon } from './daemon.js';
|
|
7
|
+
import { installFileLogging } from './fileLog.js';
|
|
8
|
+
import { runDoctor } from './doctor.js';
|
|
6
9
|
const DEFAULT_SERVER = 'https://api.mnemonik.dev';
|
|
7
10
|
const MNEMONIK_DIR = join(homedir(), '.mnemonik');
|
|
8
11
|
const PID_FILE = join(MNEMONIK_DIR, 'daemon.pid');
|
|
@@ -12,7 +15,7 @@ const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
|
|
|
12
15
|
function parseCliArgs() {
|
|
13
16
|
const args = process.argv.slice(2);
|
|
14
17
|
const command = (args[0] ?? 'help');
|
|
15
|
-
if (!['start', 'stop', 'status', 'log', 'help'].includes(command)) {
|
|
18
|
+
if (!['start', 'stop', 'status', 'log', 'doctor', 'help'].includes(command)) {
|
|
16
19
|
return { command: 'help' };
|
|
17
20
|
}
|
|
18
21
|
let key;
|
|
@@ -57,18 +60,6 @@ async function checkConfigPermissions() {
|
|
|
57
60
|
// Config file doesn't exist yet
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
|
-
async function rotateLogIfNeeded() {
|
|
61
|
-
try {
|
|
62
|
-
const { rename } = await import('fs/promises');
|
|
63
|
-
const s = await stat(LOG_FILE);
|
|
64
|
-
if (s.size > MAX_LOG_SIZE) {
|
|
65
|
-
await rename(LOG_FILE, LOG_FILE + '.old');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
// Log file doesn't exist yet
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
63
|
async function acquireLock(retried = false) {
|
|
73
64
|
try {
|
|
74
65
|
const { open: fsOpen } = await import('fs/promises');
|
|
@@ -129,6 +120,7 @@ Usage:
|
|
|
129
120
|
mnemonik-scanner stop Stop the running daemon
|
|
130
121
|
mnemonik-scanner status Show daemon status
|
|
131
122
|
mnemonik-scanner log Tail the scanner log file
|
|
123
|
+
mnemonik-scanner doctor Check install health (drift detection)
|
|
132
124
|
mnemonik-scanner help Show this help
|
|
133
125
|
|
|
134
126
|
Options (for start):
|
|
@@ -149,6 +141,26 @@ Environment variables:
|
|
|
149
141
|
`);
|
|
150
142
|
}
|
|
151
143
|
async function handleStart(cli) {
|
|
144
|
+
// Guardrail — this daemon is meant to run under the systemd user service
|
|
145
|
+
// (`systemctl --user start mnemonik-scanner`; see BUILD_AND_DEPLOY.md
|
|
146
|
+
// "Scanner daemon"). systemd sets INVOCATION_ID on processes it spawns. A
|
|
147
|
+
// manual foreground `start` attaches the daemon to the invoking shell: it
|
|
148
|
+
// dies when the shell exits and can spawn duplicate/stale daemons — the exact
|
|
149
|
+
// failure mode behind version skew and apparent "restart loops". If a unit
|
|
150
|
+
// exists but we were NOT launched by systemd, refuse (overridable).
|
|
151
|
+
if (!process.env.INVOCATION_ID && !process.env.MNEMONIK_SCANNER_FOREGROUND) {
|
|
152
|
+
const unitPath = join(homedir(), '.config/systemd/user/mnemonik-scanner.service');
|
|
153
|
+
if (existsSync(unitPath)) {
|
|
154
|
+
console.error('[mnemonik] A systemd unit already manages this scanner.\n' +
|
|
155
|
+
' Start/restart it with:\n' +
|
|
156
|
+
' systemctl --user restart mnemonik-scanner\n' +
|
|
157
|
+
' Running `mnemonik-scanner start` by hand attaches the daemon to your shell — it\n' +
|
|
158
|
+
' dies when the shell exits and can leave duplicate/stale daemons running (version\n' +
|
|
159
|
+
' skew, apparent "restart loops"). To force a foreground run, set\n' +
|
|
160
|
+
' MNEMONIK_SCANNER_FOREGROUND=1.');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
152
164
|
const config = await readConfig();
|
|
153
165
|
// Resolve API key: env var > CLI > config
|
|
154
166
|
const apiKey = process.env.MNEMONIK_API_KEY || cli.key || config?.apiKey;
|
|
@@ -167,6 +179,12 @@ async function handleStart(cli) {
|
|
|
167
179
|
const server = cli.server || config?.server || DEFAULT_SERVER;
|
|
168
180
|
// Ensure directories exist
|
|
169
181
|
await mkdir(MNEMONIK_DIR, { recursive: true });
|
|
182
|
+
// Mirror all console output into scanner.log so `mnemonik-scanner log`
|
|
183
|
+
// reflects live activity regardless of supervisor. Under systemd, stdout is
|
|
184
|
+
// wired to the journal socket, not this file — without the tee the on-disk
|
|
185
|
+
// log goes stale and `log` shows nothing current. Install before any daemon
|
|
186
|
+
// output so the whole session is captured.
|
|
187
|
+
installFileLogging(LOG_FILE, MAX_LOG_SIZE);
|
|
170
188
|
// Save config for future runs (never save env var key to file)
|
|
171
189
|
const configToSave = {
|
|
172
190
|
roots,
|
|
@@ -182,7 +200,6 @@ async function handleStart(cli) {
|
|
|
182
200
|
}
|
|
183
201
|
await writeConfig(configToSave);
|
|
184
202
|
await checkConfigPermissions();
|
|
185
|
-
await rotateLogIfNeeded();
|
|
186
203
|
const locked = await acquireLock();
|
|
187
204
|
if (!locked) {
|
|
188
205
|
const pid = await readPid();
|
|
@@ -320,6 +337,9 @@ async function main() {
|
|
|
320
337
|
case 'log':
|
|
321
338
|
await handleLog();
|
|
322
339
|
break;
|
|
340
|
+
case 'doctor':
|
|
341
|
+
await runDoctor();
|
|
342
|
+
break;
|
|
323
343
|
case 'help':
|
|
324
344
|
default:
|
|
325
345
|
printHelp();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/doctor.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `mnemonik-scanner doctor` — the guard that turns the prose upgrade procedure
|
|
9
|
+
* into an executable check. It asserts the invariants that every past botched
|
|
10
|
+
* reinstall violated, and prints the one blessed remediation. Any single ✗
|
|
11
|
+
* exits non-zero so a wrapper (or a human) can react.
|
|
12
|
+
*
|
|
13
|
+
* Invariants:
|
|
14
|
+
* 1. Exactly one `mnemonik-scanner` on PATH, inside the user npm prefix
|
|
15
|
+
* (a second copy under /usr is the classic PATH-shadow version skew).
|
|
16
|
+
* 2. If a systemd user unit exists, it is active and its ExecStart points at
|
|
17
|
+
* that same binary.
|
|
18
|
+
* 3. Exactly one live daemon, and under systemd its PID == the unit MainPID
|
|
19
|
+
* (a stray `node …/scanner/dist/index.js` is a duplicate local-build daemon).
|
|
20
|
+
* 4. No leftover legacy per-project daemons.
|
|
21
|
+
* 5. Installed version is not behind npm latest (warning, not failure).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const HOME = homedir();
|
|
25
|
+
const MNEMONIK_DIR = join(HOME, '.mnemonik');
|
|
26
|
+
const PID_FILE = join(MNEMONIK_DIR, 'daemon.pid');
|
|
27
|
+
const LEGACY_DAEMONS_DIR = join(MNEMONIK_DIR, 'daemons');
|
|
28
|
+
const UNIT_FILE = join(HOME, '.config/systemd/user/mnemonik-scanner.service');
|
|
29
|
+
|
|
30
|
+
type Level = 'ok' | 'warn' | 'fail';
|
|
31
|
+
interface Check {
|
|
32
|
+
level: Level;
|
|
33
|
+
label: string;
|
|
34
|
+
detail?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sh(cmd: string, args: string[], timeoutMs = 5000): string | null {
|
|
38
|
+
try {
|
|
39
|
+
return execFileSync(cmd, args, {
|
|
40
|
+
encoding: 'utf-8',
|
|
41
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
42
|
+
timeout: timeoutMs,
|
|
43
|
+
}).trim();
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function pidAlive(pid: number): boolean {
|
|
50
|
+
try {
|
|
51
|
+
process.kill(pid, 0);
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function installedVersion(): string | null {
|
|
59
|
+
try {
|
|
60
|
+
const here = fileURLToPath(new URL('.', import.meta.url));
|
|
61
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf-8')) as {
|
|
62
|
+
version?: string;
|
|
63
|
+
};
|
|
64
|
+
return pkg.version ?? null;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function runDoctor(): Promise<void> {
|
|
71
|
+
const checks: Check[] = [];
|
|
72
|
+
|
|
73
|
+
// 1. Binary uniqueness + location.
|
|
74
|
+
const prefix = sh('npm', ['config', 'get', 'prefix']);
|
|
75
|
+
const whichOut = sh('which', ['-a', 'mnemonik-scanner']);
|
|
76
|
+
const binPaths = whichOut ? whichOut.split('\n').map((s) => s.trim()).filter(Boolean) : [];
|
|
77
|
+
if (binPaths.length === 0) {
|
|
78
|
+
checks.push({ level: 'fail', label: 'binary on PATH', detail: 'mnemonik-scanner not found on PATH' });
|
|
79
|
+
} else if (binPaths.length > 1) {
|
|
80
|
+
checks.push({
|
|
81
|
+
level: 'fail',
|
|
82
|
+
label: 'single binary on PATH',
|
|
83
|
+
detail: `found ${binPaths.length} copies (PATH shadow → version skew):\n ${binPaths.join('\n ')}`,
|
|
84
|
+
});
|
|
85
|
+
} else if (prefix && !binPaths[0]!.startsWith(prefix)) {
|
|
86
|
+
checks.push({
|
|
87
|
+
level: 'warn',
|
|
88
|
+
label: 'binary in user npm prefix',
|
|
89
|
+
detail: `${binPaths[0]} is not under npm prefix ${prefix}`,
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
checks.push({ level: 'ok', label: 'single binary on PATH', detail: binPaths[0] });
|
|
93
|
+
}
|
|
94
|
+
const binPath = binPaths.length === 1 ? binPaths[0]! : null;
|
|
95
|
+
|
|
96
|
+
// 2. systemd unit health + ExecStart match.
|
|
97
|
+
let unitMainPid: number | null = null;
|
|
98
|
+
const hasUnit = existsSync(UNIT_FILE);
|
|
99
|
+
if (hasUnit) {
|
|
100
|
+
const show = sh('systemctl', [
|
|
101
|
+
'--user',
|
|
102
|
+
'show',
|
|
103
|
+
'mnemonik-scanner',
|
|
104
|
+
'-p',
|
|
105
|
+
'ActiveState',
|
|
106
|
+
'-p',
|
|
107
|
+
'MainPID',
|
|
108
|
+
'-p',
|
|
109
|
+
'ExecStart',
|
|
110
|
+
]);
|
|
111
|
+
const active = /ActiveState=active/.test(show ?? '');
|
|
112
|
+
const mainPidMatch = /MainPID=(\d+)/.exec(show ?? '');
|
|
113
|
+
unitMainPid = mainPidMatch ? parseInt(mainPidMatch[1]!, 10) : null;
|
|
114
|
+
const execMatch = /path=([^\s;]+)/.exec(show ?? '');
|
|
115
|
+
const execPath = execMatch ? execMatch[1]! : null;
|
|
116
|
+
|
|
117
|
+
if (!active) {
|
|
118
|
+
checks.push({ level: 'fail', label: 'systemd unit active', detail: 'unit is not active' });
|
|
119
|
+
} else if (binPath && execPath && execPath !== binPath) {
|
|
120
|
+
checks.push({
|
|
121
|
+
level: 'fail',
|
|
122
|
+
label: 'ExecStart matches binary',
|
|
123
|
+
detail: `unit runs ${execPath} but PATH resolves ${binPath}`,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
checks.push({ level: 'ok', label: 'systemd unit active', detail: `MainPID ${unitMainPid ?? '?'}` });
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
checks.push({ level: 'warn', label: 'systemd unit', detail: 'no user unit — daemon must be supervised another way' });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 3. Single live daemon; under systemd it must equal MainPID.
|
|
133
|
+
let filePid: number | null = null;
|
|
134
|
+
try {
|
|
135
|
+
filePid = parseInt(readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
136
|
+
if (Number.isNaN(filePid)) filePid = null;
|
|
137
|
+
} catch {
|
|
138
|
+
filePid = null;
|
|
139
|
+
}
|
|
140
|
+
const psOut = sh('ps', ['-eo', 'pid,cmd']);
|
|
141
|
+
const daemonPids = (psOut ?? '')
|
|
142
|
+
.split('\n')
|
|
143
|
+
// Only the long-running `start` daemon counts — never a `doctor`/`status`/
|
|
144
|
+
// `log`/`stop` invocation (which also runs …/scanner/dist/index.js), and
|
|
145
|
+
// never this doctor process itself.
|
|
146
|
+
.filter((l) => /(mnemonik-scanner|scanner\/dist\/index\.js)/.test(l) && /\bstart\b/.test(l))
|
|
147
|
+
.map((l) => parseInt(l.trim().split(/\s+/)[0]!, 10))
|
|
148
|
+
.filter((n) => !Number.isNaN(n) && n !== process.pid);
|
|
149
|
+
if (daemonPids.length === 0) {
|
|
150
|
+
checks.push({ level: 'fail', label: 'daemon running', detail: 'no daemon process found' });
|
|
151
|
+
} else if (daemonPids.length > 1) {
|
|
152
|
+
checks.push({
|
|
153
|
+
level: 'fail',
|
|
154
|
+
label: 'single daemon',
|
|
155
|
+
detail: `${daemonPids.length} daemon processes (duplicate) — PIDs ${daemonPids.join(', ')}`,
|
|
156
|
+
});
|
|
157
|
+
} else if (hasUnit && unitMainPid && daemonPids[0] !== unitMainPid) {
|
|
158
|
+
checks.push({
|
|
159
|
+
level: 'fail',
|
|
160
|
+
label: 'daemon is the systemd one',
|
|
161
|
+
detail: `running PID ${daemonPids[0]} != unit MainPID ${unitMainPid} (stray local-build daemon)`,
|
|
162
|
+
});
|
|
163
|
+
} else if (filePid && !pidAlive(filePid)) {
|
|
164
|
+
checks.push({ level: 'warn', label: 'PID file fresh', detail: `stale ${PID_FILE} (PID ${filePid} dead)` });
|
|
165
|
+
} else {
|
|
166
|
+
checks.push({ level: 'ok', label: 'single daemon', detail: `PID ${daemonPids[0]}` });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 4. Legacy per-project daemons.
|
|
170
|
+
try {
|
|
171
|
+
const legacy = readdirSync(LEGACY_DAEMONS_DIR)
|
|
172
|
+
.filter((f) => f.endsWith('.pid'))
|
|
173
|
+
.map((f) => parseInt(readFileSync(join(LEGACY_DAEMONS_DIR, f), 'utf-8').trim(), 10))
|
|
174
|
+
.filter((n) => !Number.isNaN(n) && pidAlive(n));
|
|
175
|
+
if (legacy.length > 0) {
|
|
176
|
+
checks.push({ level: 'fail', label: 'no legacy daemons', detail: `legacy per-project daemons alive: ${legacy.join(', ')}` });
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// dir absent — nothing to check
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 5. Version vs npm latest (informational).
|
|
183
|
+
const local = installedVersion();
|
|
184
|
+
const latest = sh('npm', ['view', '@mnemonik/scanner', 'version'], 15000);
|
|
185
|
+
if (local && latest && local !== latest) {
|
|
186
|
+
checks.push({ level: 'warn', label: 'up to date', detail: `installed ${local}, npm latest ${latest} — run: make scanner-deploy` });
|
|
187
|
+
} else if (local) {
|
|
188
|
+
checks.push({ level: 'ok', label: 'up to date', detail: `v${local}` });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Report.
|
|
192
|
+
const icon = { ok: '✓', warn: '⚠', fail: '✗' } as const;
|
|
193
|
+
console.log('mnemonik-scanner doctor\n');
|
|
194
|
+
for (const c of checks) {
|
|
195
|
+
console.log(` ${icon[c.level]} ${c.label}${c.detail ? `: ${c.detail}` : ''}`);
|
|
196
|
+
}
|
|
197
|
+
const failed = checks.filter((c) => c.level === 'fail');
|
|
198
|
+
if (failed.length > 0) {
|
|
199
|
+
console.log(
|
|
200
|
+
`\n${failed.length} problem(s). Canonical fix: \`make scanner-deploy\` (build → publish shared-first → npm i -g → systemctl restart → re-verify).\n` +
|
|
201
|
+
'Do NOT hand-copy dist/ or run `make daemon-*` on a systemd host — those are what cause this.'
|
|
202
|
+
);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
console.log('\nAll invariants hold.');
|
|
206
|
+
process.exit(0);
|
|
207
|
+
}
|
package/src/fileLog.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { appendFileSync, renameSync, statSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Append-only sink that mirrors the daemon's console output to a file the
|
|
5
|
+
* `mnemonik-scanner log` command can tail. Rotates to `<file>.old` once the
|
|
6
|
+
* file would pass `maxSize`, so a daemon that runs for weeks can't grow it
|
|
7
|
+
* without bound. Every operation is best-effort: a logging failure must never
|
|
8
|
+
* crash the daemon, so all filesystem errors are swallowed.
|
|
9
|
+
*/
|
|
10
|
+
export function createRotatingFileSink(
|
|
11
|
+
logFile: string,
|
|
12
|
+
maxSize: number
|
|
13
|
+
): (chunk: string) => void {
|
|
14
|
+
let size = 0;
|
|
15
|
+
try {
|
|
16
|
+
size = statSync(logFile).size;
|
|
17
|
+
} catch {
|
|
18
|
+
size = 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (chunk: string): void => {
|
|
22
|
+
if (!chunk) return;
|
|
23
|
+
try {
|
|
24
|
+
const bytes = Buffer.byteLength(chunk);
|
|
25
|
+
if (size > 0 && size + bytes > maxSize) {
|
|
26
|
+
try {
|
|
27
|
+
renameSync(logFile, logFile + '.old');
|
|
28
|
+
} catch {
|
|
29
|
+
// Rotation failed (e.g. cross-device) — keep appending to the
|
|
30
|
+
// current file rather than losing the line.
|
|
31
|
+
}
|
|
32
|
+
size = 0;
|
|
33
|
+
}
|
|
34
|
+
appendFileSync(logFile, chunk);
|
|
35
|
+
size += bytes;
|
|
36
|
+
} catch {
|
|
37
|
+
// Never let logging break the daemon.
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Tee `process.stdout`/`process.stderr` into `logFile` on top of their normal
|
|
44
|
+
* destination. This is what makes `mnemonik-scanner log` reflect live activity
|
|
45
|
+
* regardless of how the daemon is supervised: under systemd stdout is wired to
|
|
46
|
+
* the journal (a socket), under a bare shell to the terminal — either way the
|
|
47
|
+
* file now receives the same lines. Call once at daemon start, before the
|
|
48
|
+
* daemon writes anything worth capturing.
|
|
49
|
+
*/
|
|
50
|
+
export function installFileLogging(logFile: string, maxSize: number): void {
|
|
51
|
+
const sink = createRotatingFileSink(logFile, maxSize);
|
|
52
|
+
|
|
53
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
54
|
+
const original = stream.write.bind(stream) as (...args: unknown[]) => boolean;
|
|
55
|
+
stream.write = ((chunk: unknown, encoding?: unknown, cb?: unknown): boolean => {
|
|
56
|
+
try {
|
|
57
|
+
if (typeof chunk === 'string') {
|
|
58
|
+
sink(chunk);
|
|
59
|
+
} else if (Buffer.isBuffer(chunk)) {
|
|
60
|
+
const enc = typeof encoding === 'string' ? (encoding as BufferEncoding) : 'utf8';
|
|
61
|
+
sink(chunk.toString(enc));
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// ignore — mirroring is best-effort
|
|
65
|
+
}
|
|
66
|
+
// Preserve the real stream's overloads (chunk, cb) / (chunk, enc, cb).
|
|
67
|
+
return original(chunk, encoding, cb);
|
|
68
|
+
}) as typeof stream.write;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { readFile, writeFile, unlink, mkdir, stat, chmod } from 'fs/promises';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
4
5
|
import { join } from 'path';
|
|
5
6
|
import { homedir } from 'os';
|
|
6
7
|
import { ScannerDaemon } from './daemon.js';
|
|
8
|
+
import { installFileLogging } from './fileLog.js';
|
|
9
|
+
import { runDoctor } from './doctor.js';
|
|
7
10
|
|
|
8
11
|
const DEFAULT_SERVER = 'https://api.mnemonik.dev';
|
|
9
12
|
const MNEMONIK_DIR = join(homedir(), '.mnemonik');
|
|
@@ -20,7 +23,7 @@ interface ScannerConfig {
|
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
interface CliArgs {
|
|
23
|
-
command: 'start' | 'stop' | 'status' | 'log' | 'help';
|
|
26
|
+
command: 'start' | 'stop' | 'status' | 'log' | 'doctor' | 'help';
|
|
24
27
|
key?: string;
|
|
25
28
|
server?: string;
|
|
26
29
|
roots?: string[];
|
|
@@ -30,7 +33,7 @@ function parseCliArgs(): CliArgs {
|
|
|
30
33
|
const args = process.argv.slice(2);
|
|
31
34
|
const command = (args[0] ?? 'help') as CliArgs['command'];
|
|
32
35
|
|
|
33
|
-
if (!['start', 'stop', 'status', 'log', 'help'].includes(command)) {
|
|
36
|
+
if (!['start', 'stop', 'status', 'log', 'doctor', 'help'].includes(command)) {
|
|
34
37
|
return { command: 'help' };
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -80,18 +83,6 @@ async function checkConfigPermissions(): Promise<void> {
|
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
async function rotateLogIfNeeded(): Promise<void> {
|
|
84
|
-
try {
|
|
85
|
-
const { rename } = await import('fs/promises');
|
|
86
|
-
const s = await stat(LOG_FILE);
|
|
87
|
-
if (s.size > MAX_LOG_SIZE) {
|
|
88
|
-
await rename(LOG_FILE, LOG_FILE + '.old');
|
|
89
|
-
}
|
|
90
|
-
} catch {
|
|
91
|
-
// Log file doesn't exist yet
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
86
|
async function acquireLock(retried = false): Promise<boolean> {
|
|
96
87
|
try {
|
|
97
88
|
const { open: fsOpen } = await import('fs/promises');
|
|
@@ -153,6 +144,7 @@ Usage:
|
|
|
153
144
|
mnemonik-scanner stop Stop the running daemon
|
|
154
145
|
mnemonik-scanner status Show daemon status
|
|
155
146
|
mnemonik-scanner log Tail the scanner log file
|
|
147
|
+
mnemonik-scanner doctor Check install health (drift detection)
|
|
156
148
|
mnemonik-scanner help Show this help
|
|
157
149
|
|
|
158
150
|
Options (for start):
|
|
@@ -174,6 +166,29 @@ Environment variables:
|
|
|
174
166
|
}
|
|
175
167
|
|
|
176
168
|
async function handleStart(cli: CliArgs): Promise<void> {
|
|
169
|
+
// Guardrail — this daemon is meant to run under the systemd user service
|
|
170
|
+
// (`systemctl --user start mnemonik-scanner`; see BUILD_AND_DEPLOY.md
|
|
171
|
+
// "Scanner daemon"). systemd sets INVOCATION_ID on processes it spawns. A
|
|
172
|
+
// manual foreground `start` attaches the daemon to the invoking shell: it
|
|
173
|
+
// dies when the shell exits and can spawn duplicate/stale daemons — the exact
|
|
174
|
+
// failure mode behind version skew and apparent "restart loops". If a unit
|
|
175
|
+
// exists but we were NOT launched by systemd, refuse (overridable).
|
|
176
|
+
if (!process.env.INVOCATION_ID && !process.env.MNEMONIK_SCANNER_FOREGROUND) {
|
|
177
|
+
const unitPath = join(homedir(), '.config/systemd/user/mnemonik-scanner.service');
|
|
178
|
+
if (existsSync(unitPath)) {
|
|
179
|
+
console.error(
|
|
180
|
+
'[mnemonik] A systemd unit already manages this scanner.\n' +
|
|
181
|
+
' Start/restart it with:\n' +
|
|
182
|
+
' systemctl --user restart mnemonik-scanner\n' +
|
|
183
|
+
' Running `mnemonik-scanner start` by hand attaches the daemon to your shell — it\n' +
|
|
184
|
+
' dies when the shell exits and can leave duplicate/stale daemons running (version\n' +
|
|
185
|
+
' skew, apparent "restart loops"). To force a foreground run, set\n' +
|
|
186
|
+
' MNEMONIK_SCANNER_FOREGROUND=1.'
|
|
187
|
+
);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
177
192
|
const config = await readConfig();
|
|
178
193
|
|
|
179
194
|
// Resolve API key: env var > CLI > config
|
|
@@ -201,6 +216,13 @@ async function handleStart(cli: CliArgs): Promise<void> {
|
|
|
201
216
|
// Ensure directories exist
|
|
202
217
|
await mkdir(MNEMONIK_DIR, { recursive: true });
|
|
203
218
|
|
|
219
|
+
// Mirror all console output into scanner.log so `mnemonik-scanner log`
|
|
220
|
+
// reflects live activity regardless of supervisor. Under systemd, stdout is
|
|
221
|
+
// wired to the journal socket, not this file — without the tee the on-disk
|
|
222
|
+
// log goes stale and `log` shows nothing current. Install before any daemon
|
|
223
|
+
// output so the whole session is captured.
|
|
224
|
+
installFileLogging(LOG_FILE, MAX_LOG_SIZE);
|
|
225
|
+
|
|
204
226
|
// Save config for future runs (never save env var key to file)
|
|
205
227
|
const configToSave: ScannerConfig = {
|
|
206
228
|
roots,
|
|
@@ -216,8 +238,6 @@ async function handleStart(cli: CliArgs): Promise<void> {
|
|
|
216
238
|
await writeConfig(configToSave);
|
|
217
239
|
await checkConfigPermissions();
|
|
218
240
|
|
|
219
|
-
await rotateLogIfNeeded();
|
|
220
|
-
|
|
221
241
|
const locked = await acquireLock();
|
|
222
242
|
if (!locked) {
|
|
223
243
|
const pid = await readPid();
|
|
@@ -364,6 +384,9 @@ async function main(): Promise<void> {
|
|
|
364
384
|
case 'log':
|
|
365
385
|
await handleLog();
|
|
366
386
|
break;
|
|
387
|
+
case 'doctor':
|
|
388
|
+
await runDoctor();
|
|
389
|
+
break;
|
|
367
390
|
case 'help':
|
|
368
391
|
default:
|
|
369
392
|
printHelp();
|