@wendongfly/zihi 1.1.12 → 1.1.14
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/bin/daemon.js +3 -19
- package/bin/zihi.js +147 -22
- package/dist/781.index.js +154 -0
- package/dist/attach.js +1 -1
- package/dist/client-dist/socket.io.esm.min.js +1 -1
- package/dist/client-dist/socket.io.esm.min.js.map +1 -0
- package/dist/client-dist/socket.io.js +1 -1
- package/dist/client-dist/socket.io.js.map +1 -0
- package/dist/client-dist/socket.io.min.js +1 -1
- package/dist/client-dist/socket.io.min.js.map +1 -0
- package/dist/client-dist/socket.io.msgpack.min.js +1 -1
- package/dist/client-dist/socket.io.msgpack.min.js.map +1 -0
- package/dist/index.html +2 -2
- package/dist/index.js +214 -286
- package/dist/index.min.js +393 -0
- package/dist/package.json +1 -1
- package/package.json +2 -1
package/bin/daemon.js
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* zihi
|
|
3
|
+
* zihi 守护进程:标记为后台模式并启动 server。
|
|
4
|
+
* PID 文件的写入/清理由 server 自己负责(写的是完整 JSON 元信息)。
|
|
4
5
|
*/
|
|
5
|
-
|
|
6
|
-
import { join, dirname } from 'path';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
import { homedir } from 'os';
|
|
9
|
-
|
|
10
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const pidFile = join(homedir(), '.zihi', 'daemon.pid');
|
|
12
|
-
|
|
13
|
-
// 写入自身 PID
|
|
14
|
-
writeFileSync(pidFile, String(process.pid));
|
|
15
|
-
|
|
16
|
-
// 退出时清理 PID 文件
|
|
17
|
-
function cleanup() { try { unlinkSync(pidFile); } catch {} }
|
|
18
|
-
process.on('exit', cleanup);
|
|
19
|
-
process.on('SIGTERM', () => process.exit(0));
|
|
20
|
-
process.on('SIGINT', () => process.exit(0));
|
|
21
|
-
|
|
22
|
-
// 启动 server
|
|
6
|
+
process.env.ZIHI_DAEMON = '1';
|
|
23
7
|
await import('../dist/index.js');
|
package/bin/zihi.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from 'child_process';
|
|
2
|
+
import { spawn, execSync } from 'child_process';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname, join } from 'path';
|
|
5
5
|
import { mkdirSync, openSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
@@ -30,31 +30,142 @@ const logFile = join(configDir, 'daemon.log');
|
|
|
30
30
|
|
|
31
31
|
// ── 工具函数 ──────────────────────────────────────────────
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
/** 读取 pidfile,兼容 JSON(新格式)和纯 PID 数字(旧格式)。 */
|
|
34
|
+
function readPidInfo() {
|
|
35
|
+
try {
|
|
36
|
+
const raw = readFileSync(pidFile, 'utf8').trim();
|
|
37
|
+
if (!raw) return null;
|
|
38
|
+
if (raw.startsWith('{')) return JSON.parse(raw);
|
|
39
|
+
const pid = parseInt(raw, 10);
|
|
40
|
+
return pid ? { pid } : null;
|
|
41
|
+
} catch { return null; }
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
function
|
|
44
|
+
function isPidAlive(pid) {
|
|
39
45
|
try { process.kill(pid, 0); return true; }
|
|
40
46
|
catch { return false; }
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
/** 查进程命令行是否含 zihi 标识,防 PID 复用导致误判成别的 node 进程。 */
|
|
50
|
+
function pidCmdlineLooksLikeZihi(pid) {
|
|
51
|
+
try {
|
|
52
|
+
if (process.platform === 'win32') {
|
|
53
|
+
const out = execSync(
|
|
54
|
+
`wmic process where "ProcessId=${pid}" get CommandLine /format:list`,
|
|
55
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000 }
|
|
56
|
+
);
|
|
57
|
+
return /zihi/i.test(out) && /(daemon\.js|dist[\\/]index\.js)/i.test(out);
|
|
58
|
+
} else {
|
|
59
|
+
const out = readFileSync(`/proc/${pid}/cmdline`, 'utf8');
|
|
60
|
+
return /zihi/i.test(out) && /(daemon\.js|dist\/index\.js)/i.test(out);
|
|
61
|
+
}
|
|
62
|
+
} catch { return false; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 向运行中的实例发健康探针:确认签名为 zihi 且 pid 匹配。 */
|
|
66
|
+
async function probeZihiPing(port, timeoutMs = 1500) {
|
|
67
|
+
if (!port) return null;
|
|
68
|
+
try {
|
|
69
|
+
const ctrl = new AbortController();
|
|
70
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
71
|
+
const resp = await fetch(`http://127.0.0.1:${port}/api/zihi-ping`, { signal: ctrl.signal });
|
|
72
|
+
clearTimeout(t);
|
|
73
|
+
if (!resp.ok) return null;
|
|
74
|
+
const data = await resp.json();
|
|
75
|
+
return data && data.zihi === true ? data : null;
|
|
76
|
+
} catch { return null; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 校验当前 pidfile 指向的是否确实是一个活着的 zihi 实例。
|
|
81
|
+
* 两层校验:HTTP 探针 为主,cmdline 检查 为辅。
|
|
82
|
+
*/
|
|
83
|
+
async function verifyRunning() {
|
|
84
|
+
const info = readPidInfo();
|
|
85
|
+
if (!info || !info.pid) return null;
|
|
86
|
+
if (!isPidAlive(info.pid)) return null;
|
|
87
|
+
|
|
88
|
+
// 主校验:健康探针
|
|
89
|
+
const ping = await probeZihiPing(info.port);
|
|
90
|
+
if (ping && ping.pid === info.pid) {
|
|
91
|
+
return { ...info, ...ping, verifiedBy: 'http' };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 辅校验:命令行关键字
|
|
95
|
+
if (pidCmdlineLooksLikeZihi(info.pid)) {
|
|
96
|
+
return { ...info, verifiedBy: 'cmdline' };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function formatUptime(startedAt) {
|
|
103
|
+
if (!startedAt) return '未知';
|
|
104
|
+
const ms = Date.now() - startedAt;
|
|
105
|
+
const s = Math.floor(ms / 1000);
|
|
106
|
+
if (s < 60) return `${s}s`;
|
|
107
|
+
const m = Math.floor(s / 60);
|
|
108
|
+
if (m < 60) return `${m}m${s % 60}s`;
|
|
109
|
+
const h = Math.floor(m / 60);
|
|
110
|
+
return `${h}h${m % 60}m`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function printRunningInfo(info, opts = {}) {
|
|
114
|
+
const started = info.startedAt ? new Date(info.startedAt).toLocaleString() : '未知';
|
|
115
|
+
console.log(`[zihi] 服务已在运行,跳过启动`);
|
|
116
|
+
console.log(` PID: ${info.pid}`);
|
|
117
|
+
if (info.port) console.log(` 端口: ${info.port}`);
|
|
118
|
+
if (info.version) console.log(` 版本: v${info.version}`);
|
|
119
|
+
if (info.cwd) console.log(` 工作目录: ${info.cwd}`);
|
|
120
|
+
if (info.execPath) console.log(` Node: ${info.execPath}`);
|
|
121
|
+
console.log(` 启动时间: ${started} (运行 ${formatUptime(info.startedAt)})`);
|
|
122
|
+
console.log(` 校验方式: ${info.verifiedBy === 'http' ? 'HTTP 探针' : '命令行匹配'}`);
|
|
123
|
+
if (info.port) console.log(` 管理界面: http://localhost:${info.port}/admin`);
|
|
124
|
+
|
|
125
|
+
const n = opts.logLines ?? 20;
|
|
126
|
+
if (n > 0) {
|
|
127
|
+
try {
|
|
128
|
+
const content = readFileSync(logFile, 'utf8');
|
|
129
|
+
const lines = content.split('\n');
|
|
130
|
+
console.log(`\n ── 最近 ${n} 行日志 ──`);
|
|
131
|
+
console.log(lines.slice(-n).join('\n'));
|
|
132
|
+
} catch {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function stopDaemon() {
|
|
137
|
+
const info = readPidInfo();
|
|
138
|
+
if (!info || !info.pid) { console.log('[zihi] 没有正在运行的后台进程'); return false; }
|
|
139
|
+
if (!isPidAlive(info.pid)) {
|
|
47
140
|
try { unlinkSync(pidFile); } catch {}
|
|
48
141
|
console.log('[zihi] 进程已不存在,已清理 PID 文件');
|
|
49
142
|
return false;
|
|
50
143
|
}
|
|
51
|
-
|
|
144
|
+
// 额外校验:确认 PID 确实是 zihi,避免 PID 复用后误杀别人
|
|
145
|
+
const verified = await verifyRunning();
|
|
146
|
+
if (!verified) {
|
|
147
|
+
try { unlinkSync(pidFile); } catch {}
|
|
148
|
+
console.log(`[zihi] PID ${info.pid} 存在但不是 zihi 进程,已清理陈旧 PID 文件`);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
process.kill(info.pid, 'SIGTERM');
|
|
52
152
|
try { unlinkSync(pidFile); } catch {}
|
|
53
|
-
console.log(`[zihi] 已停止后台进程 (PID ${pid})`);
|
|
153
|
+
console.log(`[zihi] 已停止后台进程 (PID ${info.pid})`);
|
|
54
154
|
return true;
|
|
55
155
|
}
|
|
56
156
|
|
|
57
|
-
function startDaemon() {
|
|
157
|
+
async function startDaemon() {
|
|
158
|
+
// 启动前唯一性校验
|
|
159
|
+
const running = await verifyRunning();
|
|
160
|
+
if (running) {
|
|
161
|
+
printRunningInfo(running);
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
// pidfile 存在但校验失败 → 清理陈旧文件
|
|
165
|
+
if (readPidInfo()) {
|
|
166
|
+
try { unlinkSync(pidFile); } catch {}
|
|
167
|
+
}
|
|
168
|
+
|
|
58
169
|
mkdirSync(configDir, { recursive: true });
|
|
59
170
|
const out = openSync(logFile, 'a');
|
|
60
171
|
// 启动守护进程(daemon.js),由它管理 server 子进程
|
|
@@ -103,21 +214,28 @@ if (cmd === 'attach') {
|
|
|
103
214
|
await import('../dist/attach.js');
|
|
104
215
|
|
|
105
216
|
} else if (cmd === 'stop') {
|
|
106
|
-
stopDaemon();
|
|
217
|
+
await stopDaemon();
|
|
107
218
|
|
|
108
219
|
} else if (cmd === 'restart') {
|
|
109
|
-
stopDaemon();
|
|
220
|
+
await stopDaemon();
|
|
110
221
|
await new Promise(r => setTimeout(r, 500));
|
|
111
|
-
startDaemon();
|
|
222
|
+
await startDaemon();
|
|
112
223
|
process.exit(0);
|
|
113
224
|
|
|
114
225
|
} else if (cmd === 'status') {
|
|
115
226
|
const pkgVersion = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '../package.json'), 'utf8')).version;
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
console.log(`[zihi] v${pkgVersion}
|
|
227
|
+
const running = await verifyRunning();
|
|
228
|
+
if (running) {
|
|
229
|
+
console.log(`[zihi] v${pkgVersion} 运行中`);
|
|
230
|
+
printRunningInfo(running, { logLines: 0 });
|
|
119
231
|
} else {
|
|
120
|
-
|
|
232
|
+
const stale = readPidInfo();
|
|
233
|
+
if (stale) {
|
|
234
|
+
try { unlinkSync(pidFile); } catch {}
|
|
235
|
+
console.log(`[zihi] v${pkgVersion} 未运行(已清理陈旧 PID 文件)`);
|
|
236
|
+
} else {
|
|
237
|
+
console.log(`[zihi] v${pkgVersion} 未运行`);
|
|
238
|
+
}
|
|
121
239
|
}
|
|
122
240
|
|
|
123
241
|
} else if (cmd === 'log' || cmd === 'logs') {
|
|
@@ -594,10 +712,17 @@ SSH 开发:
|
|
|
594
712
|
`);
|
|
595
713
|
|
|
596
714
|
} else if (cmd === '-d' || cmd === '--daemon' || cmd === 'daemon') {
|
|
597
|
-
startDaemon();
|
|
598
|
-
|
|
715
|
+
await startDaemon();
|
|
716
|
+
// startDaemon 内部会在检测到已运行时 exit(0);
|
|
717
|
+
// 正常启动路径由 poll 定时器结束进程。
|
|
599
718
|
|
|
600
719
|
} else {
|
|
601
|
-
// 默认 / 'start'
|
|
720
|
+
// 默认 / 'start':前台运行前也要做唯一性校验
|
|
721
|
+
const running = await verifyRunning();
|
|
722
|
+
if (running) {
|
|
723
|
+
printRunningInfo(running);
|
|
724
|
+
process.exit(0);
|
|
725
|
+
}
|
|
726
|
+
if (readPidInfo()) { try { unlinkSync(pidFile); } catch {} }
|
|
602
727
|
await import('../dist/index.js');
|
|
603
728
|
}
|