@wendongfly/myhi 1.0.92 → 1.0.93

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 ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * myhi 守护进程
4
+ * 负责 fork/管理 server 子进程,处理升级和重启
5
+ * daemon 本身不会被升级影响,保证服务连续性
6
+ */
7
+ import { fork, execSync } from 'child_process';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname, join } from 'path';
10
+ import { writeFileSync, readFileSync, mkdirSync, unlinkSync } from 'fs';
11
+ import { homedir } from 'os';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const configDir = join(homedir(), '.myhi');
16
+ const pidFile = join(configDir, 'daemon.pid');
17
+ const serverEntry = join(__dirname, '../dist/index.js');
18
+
19
+ mkdirSync(configDir, { recursive: true });
20
+ writeFileSync(pidFile, String(process.pid));
21
+
22
+ let child = null;
23
+ let restarting = false;
24
+ let crashCount = 0;
25
+ let lastCrash = 0;
26
+ const startTime = Date.now();
27
+
28
+ function log(msg) {
29
+ const ts = new Date().toISOString().slice(11, 19);
30
+ process.stderr.write(`[daemon ${ts}] ${msg}\n`);
31
+ }
32
+
33
+ function startServer() {
34
+ log('启动 server 子进程...');
35
+ child = fork(serverEntry, process.argv.slice(2), {
36
+ stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
37
+ env: { ...process.env, MYHI_DAEMON: '1' },
38
+ });
39
+
40
+ child.on('message', (msg) => {
41
+ if (msg?.type === 'upgrade') doUpgrade();
42
+ else if (msg?.type === 'restart') doRestart();
43
+ });
44
+
45
+ child.on('exit', (code, signal) => {
46
+ child = null;
47
+ if (restarting) return; // 主动重启,不自动恢复
48
+
49
+ // 防止崩溃循环:10秒内连续崩溃超过5次则退出
50
+ const now = Date.now();
51
+ if (now - lastCrash < 10000) crashCount++;
52
+ else crashCount = 1;
53
+ lastCrash = now;
54
+
55
+ if (crashCount >= 5) {
56
+ log(`server 连续崩溃 ${crashCount} 次,daemon 退出`);
57
+ cleanup();
58
+ process.exit(1);
59
+ }
60
+
61
+ log(`server 退出 (code=${code}, signal=${signal}),${1}秒后重启...`);
62
+ setTimeout(startServer, 1000);
63
+ });
64
+ }
65
+
66
+ function doRestart() {
67
+ log('收到重启请求');
68
+ restarting = true;
69
+ if (child) {
70
+ child.kill('SIGTERM');
71
+ // 等子进程退出后重启
72
+ child.on('exit', () => {
73
+ restarting = false;
74
+ setTimeout(startServer, 500);
75
+ });
76
+ // 5秒后强制杀
77
+ setTimeout(() => {
78
+ if (child) { try { child.kill('SIGKILL'); } catch {} }
79
+ }, 5000);
80
+ } else {
81
+ restarting = false;
82
+ startServer();
83
+ }
84
+ }
85
+
86
+ async function doUpgrade() {
87
+ log('收到升级请求,执行 npm install...');
88
+ try {
89
+ const cmd = process.platform === 'win32'
90
+ ? 'npm install -g @wendongfly/myhi@latest'
91
+ : 'sudo npm install -g @wendongfly/myhi@latest 2>&1 || npm install -g @wendongfly/myhi@latest 2>&1';
92
+ const output = execSync(cmd, { timeout: 120000, encoding: 'utf8' });
93
+ log('升级完成: ' + output.trim().split('\n').pop());
94
+ } catch (e) {
95
+ log('升级失败: ' + e.message);
96
+ // 即使升级失败也重启,用现有代码继续运行
97
+ }
98
+ doRestart();
99
+ }
100
+
101
+ function cleanup() {
102
+ try {
103
+ const savedPid = parseInt(readFileSync(pidFile, 'utf8').trim(), 10);
104
+ if (savedPid === process.pid) unlinkSync(pidFile);
105
+ } catch {}
106
+ }
107
+
108
+ // 信号处理
109
+ process.on('SIGTERM', () => {
110
+ log('收到 SIGTERM,停止...');
111
+ if (child) child.kill('SIGTERM');
112
+ cleanup();
113
+ setTimeout(() => process.exit(0), 1000);
114
+ });
115
+
116
+ process.on('SIGINT', () => {
117
+ log('收到 SIGINT,停止...');
118
+ if (child) child.kill('SIGTERM');
119
+ cleanup();
120
+ setTimeout(() => process.exit(0), 1000);
121
+ });
122
+
123
+ // Windows: 通过 stdin 消息(无 SIGUSR2 支持)
124
+ if (process.platform === 'win32') {
125
+ process.on('message', (msg) => {
126
+ if (msg === 'upgrade') doUpgrade();
127
+ else if (msg === 'restart') doRestart();
128
+ });
129
+ }
130
+
131
+ log(`daemon 启动 (PID ${process.pid})`);
132
+ startServer();
package/bin/myhi.js CHANGED
@@ -57,17 +57,18 @@ function stopDaemon() {
57
57
  function startDaemon() {
58
58
  mkdirSync(configDir, { recursive: true });
59
59
  const out = openSync(logFile, 'a');
60
- const child = spawn(process.execPath, [join(__dirname, '..', 'dist', 'index.js')], {
60
+ // 启动守护进程(daemon.js),由它管理 server 子进程
61
+ const child = spawn(process.execPath, [join(__dirname, 'daemon.js')], {
61
62
  detached: true,
62
63
  stdio: ['ignore', out, out],
63
64
  cwd: process.cwd(),
64
65
  env: { ...process.env },
65
66
  });
66
- writeFileSync(pidFile, String(child.pid));
67
67
  child.unref();
68
68
  console.log(`[myhi] 已在后台启动 (PID ${child.pid})`);
69
69
  console.log(`[myhi] 日志: ${logFile}`);
70
70
  console.log(`[myhi] 停止: myhi stop`);
71
+ console.log(`[myhi] 管理: http://localhost:${process.env.PORT || 12300}/admin`);
71
72
  }
72
73
 
73
74
  // ── 命令分发 ──────────────────────────────────────────────
package/dist/index.html CHANGED
@@ -429,10 +429,7 @@
429
429
  </div>
430
430
  </div>
431
431
 
432
- <div id="update-banner">
433
- <span class="update-text">新版本 <span class="update-ver" id="update-ver"></span> 可用</span>
434
- <button id="update-btn" onclick="doUpdate()">更新</button>
435
- </div>
432
+ <!-- 升级管理已移至 /admin -->
436
433
 
437
434
  <div id="list">
438
435
  <div class="empty-state">
@@ -974,39 +971,10 @@
974
971
  const resp = await fetch('/api/version');
975
972
  if (!resp.ok) return;
976
973
  const data = await resp.json();
977
- // 显示当前版本
978
974
  const verEl = document.getElementById('current-version');
979
975
  if (verEl) verEl.textContent = 'v' + data.current;
980
- if (data.updateAvailable) {
981
- document.getElementById('update-ver').textContent = 'v' + data.latest;
982
- document.getElementById('update-banner').classList.add('show');
983
- }
984
976
  } catch {}
985
977
  }
986
- window.doUpdate = async function() {
987
- const btn = document.getElementById('update-btn');
988
- btn.disabled = true;
989
- btn.textContent = '更新中...';
990
- try {
991
- const resp = await fetch('/api/update', { method: 'POST' });
992
- const data = await resp.json();
993
- if (data.ok) {
994
- btn.textContent = '重启中...';
995
- // 等待服务器重启后自动刷新
996
- setTimeout(() => waitAndReload(), 3000);
997
- } else {
998
- btn.textContent = '失败';
999
- setTimeout(() => { btn.textContent = '更新'; btn.disabled = false; }, 3000);
1000
- }
1001
- } catch {
1002
- btn.textContent = '重启中...';
1003
- setTimeout(() => waitAndReload(), 3000);
1004
- }
1005
- };
1006
- function waitAndReload() {
1007
- const check = () => fetch('/api/version').then(r => { if (r.ok) location.reload(); else throw 0; }).catch(() => setTimeout(check, 2000));
1008
- check();
1009
- }
1010
978
  checkUpdate();
1011
979
  setInterval(checkUpdate, 10 * 60 * 1000);
1012
980
  </script>