panrouter 5.1.1 → 5.2.0
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/package.json +1 -1
- package/pool-worker.mjs +60 -32
package/package.json
CHANGED
package/pool-worker.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Pan Router Pool Worker —
|
|
4
|
+
* Pan Router Pool Worker — 懒汉心跳版
|
|
5
5
|
*
|
|
6
6
|
* 以 Sidecar 模式运行,守护 cloudflared 隧道进程。
|
|
7
|
-
*
|
|
7
|
+
* - 没注册上:死缠烂打,每 10 秒重试直到主控应答
|
|
8
|
+
* - 注册上了:绝对静默,既不轮询也不发心跳
|
|
9
|
+
* - 每 5 分钟发一次全量对齐(防止主控重启丢了节点)
|
|
10
|
+
* - 隧道崩了自动复活,关机优雅告别
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -23,33 +26,20 @@ const AUTH_SECRET = "jiuling-super-secret-2026";
|
|
|
23
26
|
const NODE_ID = os.hostname() + "-worker-" + Math.floor(Math.random() * 1000);
|
|
24
27
|
const SERVER_PORT = 50816;
|
|
25
28
|
const PID_FILE = path.join(os.tmpdir(), "panrouter-pool-worker.pid");
|
|
29
|
+
const ALIGN_INTERVAL = 5 * 60 * 1000; // 5 分钟全量对齐
|
|
26
30
|
|
|
27
31
|
let cfProcess = null;
|
|
28
32
|
let isShuttingDown = false;
|
|
33
|
+
let isConfirmed = false; // 主控是否已确认注册
|
|
29
34
|
let currentUrl = "";
|
|
35
|
+
let alignTimer = null;
|
|
30
36
|
|
|
31
37
|
function log(msg, type = "INFO") {
|
|
32
38
|
const icons = { INFO: "▪", OK: "✅", ERR: "❌", HART: "💓", WARN: "⚠️", OFF: "🔻", ON: "🟢" };
|
|
33
39
|
console.log(`${icons[type] || "▪"} [节点] ${msg}`);
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
// ───
|
|
37
|
-
function notifyHub(status, url = "") {
|
|
38
|
-
const payload = JSON.stringify({ nodeId: NODE_ID, status, url });
|
|
39
|
-
const req = https.request(MAIN_HUB_URL, {
|
|
40
|
-
method: "POST",
|
|
41
|
-
headers: {
|
|
42
|
-
"Content-Type": "application/json",
|
|
43
|
-
"Content-Length": Buffer.byteLength(payload),
|
|
44
|
-
"x-secret-token": AUTH_SECRET,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
req.on("error", () => {});
|
|
48
|
-
req.write(payload);
|
|
49
|
-
req.end();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 可等待的 notify — 用于首次上线确认
|
|
42
|
+
// ─── 可等待的通知(返回 true=200 应答) ─────────────────────────────────────
|
|
53
43
|
function notifyHubWait(status, url = "") {
|
|
54
44
|
return new Promise((resolve) => {
|
|
55
45
|
const payload = JSON.stringify({ nodeId: NODE_ID, status, url });
|
|
@@ -68,19 +58,54 @@ function notifyHubWait(status, url = "") {
|
|
|
68
58
|
});
|
|
69
59
|
}
|
|
70
60
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
61
|
+
// ─── 发后即忘的通知(离线遗言用,不等回执) ────────────────────────────────
|
|
62
|
+
function notifyHub(status, url = "") {
|
|
63
|
+
try {
|
|
64
|
+
const payload = JSON.stringify({ nodeId: NODE_ID, status, url });
|
|
65
|
+
const req = https.request(MAIN_HUB_URL, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
70
|
+
"x-secret-token": AUTH_SECRET,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
req.on("error", () => {});
|
|
74
|
+
req.write(payload);
|
|
75
|
+
req.end();
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── 懒汉注册:死缠烂打直到主控应答 ─────────────────────────────────────────
|
|
80
|
+
async function aggressiveRegister(url) {
|
|
81
|
+
log("正在向主控注册...");
|
|
82
|
+
while (!isShuttingDown) {
|
|
83
|
+
const ok = await notifyHubWait("online", url);
|
|
84
|
+
if (ok) {
|
|
85
|
+
log("主控已确认节点注册", "OK");
|
|
86
|
+
isConfirmed = true;
|
|
87
|
+
startAlignHeartbeat(); // 注册成功 → 开启长间隔对齐
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
log(`注册失败,10 秒后重试...`, "WARN");
|
|
91
|
+
await new Promise((r) => setTimeout(r, 10000));
|
|
82
92
|
}
|
|
83
|
-
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── 5 分钟全量对齐心跳(主控重启后自动恢复) ──────────────────────────────
|
|
96
|
+
function startAlignHeartbeat() {
|
|
97
|
+
if (alignTimer) clearInterval(alignTimer);
|
|
98
|
+
alignTimer = setInterval(async () => {
|
|
99
|
+
if (isShuttingDown || !currentUrl) return;
|
|
100
|
+
const ok = await notifyHubWait("online", currentUrl);
|
|
101
|
+
if (ok) {
|
|
102
|
+
log("全量对齐完成", "HART");
|
|
103
|
+
} else {
|
|
104
|
+
log("全量对齐失败,主控可能已重启,重新注册...", "WARN");
|
|
105
|
+
isConfirmed = false;
|
|
106
|
+
aggressiveRegister(currentUrl); // 异步重入注册流程
|
|
107
|
+
}
|
|
108
|
+
}, ALIGN_INTERVAL);
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
// ─── 检查端口 ────────────────────────────────────────────────────────────────
|
|
@@ -171,8 +196,9 @@ function startTunnel() {
|
|
|
171
196
|
const match = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
|
|
172
197
|
if (match && match[0] !== currentUrl) {
|
|
173
198
|
currentUrl = match[0];
|
|
199
|
+
isConfirmed = false; // 新 URL,重置确认状态
|
|
174
200
|
log(`成功获取公网入口: ${currentUrl}`, "OK");
|
|
175
|
-
|
|
201
|
+
aggressiveRegister(currentUrl); // 死缠烂打直到主控应答
|
|
176
202
|
}
|
|
177
203
|
|
|
178
204
|
// 网络波动提示
|
|
@@ -184,6 +210,8 @@ function startTunnel() {
|
|
|
184
210
|
cfProcess.on("close", (code) => {
|
|
185
211
|
log(`隧道进程断开退出 (code: ${code})`, "ERR");
|
|
186
212
|
cfProcess = null;
|
|
213
|
+
isConfirmed = false;
|
|
214
|
+
if (alignTimer) { clearInterval(alignTimer); alignTimer = null; }
|
|
187
215
|
|
|
188
216
|
// 通知主控剔除
|
|
189
217
|
if (currentUrl) {
|