panrouter 5.1.0 → 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 +78 -16
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,30 +26,86 @@ 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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
// ─── 可等待的通知(返回 true=200 应答) ─────────────────────────────────────
|
|
43
|
+
function notifyHubWait(status, url = "") {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const payload = JSON.stringify({ nodeId: NODE_ID, status, url });
|
|
46
|
+
const req = https.request(MAIN_HUB_URL, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
51
|
+
"x-secret-token": AUTH_SECRET,
|
|
52
|
+
},
|
|
53
|
+
}, (res) => resolve(res.statusCode === 200));
|
|
54
|
+
req.on("error", () => resolve(false));
|
|
55
|
+
req.setTimeout(5000, () => { req.destroy(); resolve(false); });
|
|
56
|
+
req.write(payload);
|
|
57
|
+
req.end();
|
|
46
58
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
}
|
|
60
|
+
|
|
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));
|
|
92
|
+
}
|
|
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);
|
|
50
109
|
}
|
|
51
110
|
|
|
52
111
|
// ─── 检查端口 ────────────────────────────────────────────────────────────────
|
|
@@ -137,8 +196,9 @@ function startTunnel() {
|
|
|
137
196
|
const match = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
|
|
138
197
|
if (match && match[0] !== currentUrl) {
|
|
139
198
|
currentUrl = match[0];
|
|
199
|
+
isConfirmed = false; // 新 URL,重置确认状态
|
|
140
200
|
log(`成功获取公网入口: ${currentUrl}`, "OK");
|
|
141
|
-
|
|
201
|
+
aggressiveRegister(currentUrl); // 死缠烂打直到主控应答
|
|
142
202
|
}
|
|
143
203
|
|
|
144
204
|
// 网络波动提示
|
|
@@ -150,6 +210,8 @@ function startTunnel() {
|
|
|
150
210
|
cfProcess.on("close", (code) => {
|
|
151
211
|
log(`隧道进程断开退出 (code: ${code})`, "ERR");
|
|
152
212
|
cfProcess = null;
|
|
213
|
+
isConfirmed = false;
|
|
214
|
+
if (alignTimer) { clearInterval(alignTimer); alignTimer = null; }
|
|
153
215
|
|
|
154
216
|
// 通知主控剔除
|
|
155
217
|
if (currentUrl) {
|