modelstat 0.10.1 → 0.10.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/cli.mjs +44 -24
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +57 -35
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* skip-mode just see the download then, instead of now.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import { existsSync } from "node:fs";
|
|
28
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
29
29
|
import { homedir } from "node:os";
|
|
30
30
|
import { dirname, join, resolve } from "node:path";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
@@ -144,13 +144,24 @@ async function rebootServiceIfInstalled() {
|
|
|
144
144
|
"user",
|
|
145
145
|
"modelstat.service",
|
|
146
146
|
);
|
|
147
|
+
// Refresh if there's an installed service OR a daemon currently RUNNING. The
|
|
148
|
+
// latter catches a daemon that was hand-spawned (`modelstat start --force`) or
|
|
149
|
+
// whose plist was removed: it has a live pid in the lock file but no plist. We
|
|
150
|
+
// (re)install the MANAGED service for it too, so it ends up always-on instead
|
|
151
|
+
// of an unmanaged process that vanishes on the next reboot.
|
|
147
152
|
const hasService = existsSync(launchdPlist) || existsSync(systemdUnit);
|
|
148
|
-
|
|
153
|
+
const runningPid = liveDaemonPid(stateDir);
|
|
154
|
+
if (!hasService && !runningPid) {
|
|
149
155
|
console.log(
|
|
150
156
|
"[modelstat] no background service installed — run `modelstat connect` to set one up",
|
|
151
157
|
);
|
|
152
158
|
return;
|
|
153
159
|
}
|
|
160
|
+
if (!hasService && runningPid) {
|
|
161
|
+
console.log(
|
|
162
|
+
`[modelstat] found a running but UNMANAGED daemon (pid ${runningPid}, no service file) — converting it to a managed always-on service`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
154
165
|
|
|
155
166
|
// Locate the freshly-installed bundle in this package. From
|
|
156
167
|
// scripts/postinstall.mjs → ../dist/cli.mjs.
|
|
@@ -185,47 +196,58 @@ async function rebootServiceIfInstalled() {
|
|
|
185
196
|
// first, escalate to SIGKILL if it's still around 2 s later.
|
|
186
197
|
await killStaleDaemon(stateDir);
|
|
187
198
|
|
|
188
|
-
// Re
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
// ~/.modelstat/bin/
|
|
193
|
-
//
|
|
194
|
-
// ~/.modelstat/bin/node_modules. freshBundle runs from the just-
|
|
195
|
-
// unpacked npm tree, so its installNativeRuntime() pins the version.
|
|
199
|
+
// (Re)install the MANAGED service from the fresh bundle in ONE canonical step.
|
|
200
|
+
// `_install-service` → installService() (see service.ts) stages the bundle +
|
|
201
|
+
// native runtime (copies dist/cli.mjs to ~/.modelstat/bin and npm-installs the
|
|
202
|
+
// node-llama-cpp + @huggingface/transformers closures into
|
|
203
|
+
// ~/.modelstat/bin/node_modules) AND (re)writes + loads the launchd plist /
|
|
204
|
+
// systemd unit, then kickstarts it.
|
|
196
205
|
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
206
|
+
// Why install, NOT a detached `start`: a hand-spawned `start` leaves an
|
|
207
|
+
// UNMANAGED daemon — it dies on reboot and has no crash-restart. Installing
|
|
208
|
+
// the service makes the daemon ALWAYS-ON (RunAtLoad + KeepAlive on macOS,
|
|
209
|
+
// Restart=always on Linux) and CONVERTS a previously hand-spawned daemon
|
|
210
|
+
// (running, no plist) into a managed one. launchd/systemd — not this script —
|
|
211
|
+
// owns the process, so it survives the npm install exiting. The `stop` +
|
|
212
|
+
// killStaleDaemon above already evicted the old daemon and bootout cleared
|
|
213
|
+
// KeepAlive, so nothing races this restage.
|
|
214
|
+
console.log("[modelstat] installing managed background service (always-on)…");
|
|
215
|
+
const inst = spawnSync(process.execPath, [freshBundle, "_install-service"], {
|
|
202
216
|
stdio: "inherit",
|
|
203
217
|
timeout: 300_000,
|
|
204
218
|
});
|
|
205
|
-
if (
|
|
219
|
+
if (inst.status !== 0) {
|
|
206
220
|
console.warn(
|
|
207
|
-
`[modelstat] couldn't
|
|
221
|
+
`[modelstat] couldn't (re)install the managed service (exit ${inst.status ?? "?"}); the daemon may be on the previous build or unmanaged — run \`modelstat connect\` to fix`,
|
|
222
|
+
);
|
|
223
|
+
} else {
|
|
224
|
+
console.log(
|
|
225
|
+
"[modelstat] ✓ managed background service installed + running the new build",
|
|
208
226
|
);
|
|
209
227
|
}
|
|
228
|
+
}
|
|
210
229
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
230
|
+
/**
|
|
231
|
+
* The pid of a currently-running daemon per ~/.modelstat/daemon.lock, or null if
|
|
232
|
+
* the lock is missing/stale/dead. Lets the refresh detect a daemon that's
|
|
233
|
+
* RUNNING but UNMANAGED (hand-spawned, or its plist was removed) so the upgrade
|
|
234
|
+
* can convert it into a managed always-on service. `kill(pid, 0)` probes
|
|
235
|
+
* liveness without signalling; EPERM (another user's process) still counts.
|
|
236
|
+
*/
|
|
237
|
+
function liveDaemonPid(stateDir) {
|
|
238
|
+
try {
|
|
239
|
+
const payload = JSON.parse(readFileSync(join(stateDir, "daemon.lock"), "utf8"));
|
|
240
|
+
const pid = Number(payload?.pid);
|
|
241
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
242
|
+
try {
|
|
243
|
+
process.kill(pid, 0);
|
|
244
|
+
return pid;
|
|
245
|
+
} catch (e) {
|
|
246
|
+
return e && e.code === "EPERM" ? pid : null;
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
229
251
|
}
|
|
230
252
|
|
|
231
253
|
/**
|