Whoops
This commit is contained in:
parent
87f8a54656
commit
e1b5f73c31
286
index.mjs
286
index.mjs
|
@ -1,29 +1,48 @@
|
|||
import cluster from 'cluster';
|
||||
import compression from 'compression';
|
||||
import express from 'express';
|
||||
import fs from 'fs';
|
||||
import { createServer } from 'http';
|
||||
import { spawnSync } from 'child_process';
|
||||
import net from 'net';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import WebSocket from 'ws';
|
||||
import { LRUCache } from 'lru-cache';
|
||||
import wisp from 'wisp-server-node';
|
||||
import cluster from "cluster";
|
||||
import os from "os";
|
||||
import net from "net";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { spawnSync } from "child_process";
|
||||
import express from "express";
|
||||
import { createServer } from "http";
|
||||
import compression from "compression";
|
||||
import WebSocket from "ws";
|
||||
import { LRUCache } from "lru-cache";
|
||||
import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
|
||||
import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
|
||||
import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
|
||||
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
|
||||
import wisp from "wisp-server-node";
|
||||
|
||||
import { baremuxPath } from '@mercuryworkshop/bare-mux/node';
|
||||
import { epoxyPath } from '@mercuryworkshop/epoxy-transport';
|
||||
import { libcurlPath } from '@mercuryworkshop/libcurl-transport';
|
||||
import { uvPath } from '@titaniumnetwork-dev/ultraviolet';
|
||||
|
||||
import './others/scaler.mjs';
|
||||
import './others/warmup.mjs';
|
||||
|
||||
const surgeConfigPath = path.resolve('surge.config.json');
|
||||
const isSurgedRun = process.argv.includes('--surged');
|
||||
const PORT = parseInt(process.env.PORT || '3000', 10);
|
||||
const surgeConfigPath = path.resolve("surge.config.json");
|
||||
const isSurgedRun = process.argv.includes("--surged");
|
||||
let startTime = Date.now();
|
||||
|
||||
function applySurgeAndRestartIfNeeded() {
|
||||
if (isSurgedRun) {
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(surgeConfigPath, "utf-8"));
|
||||
process.env.UV_THREADPOOL_SIZE = String(config.uvThreadpoolSize);
|
||||
} catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
const result = spawnSync("node", ["./others/surge.mjs"], { stdio: "inherit" });
|
||||
if (result.error) process.exit(1);
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(surgeConfigPath, "utf-8"));
|
||||
const nodeArgs = config.nodeFlags.concat([path.resolve("index.mjs"), "--surged"]);
|
||||
const env = {
|
||||
...process.env,
|
||||
UV_THREADPOOL_SIZE: String(config.uvThreadpoolSize),
|
||||
ALREADY_SURGED: "true"
|
||||
};
|
||||
|
||||
const relaunch = spawnSync(process.execPath, nodeArgs, { stdio: "inherit", env });
|
||||
process.exit(relaunch.status || 0);
|
||||
}
|
||||
|
||||
applySurgeAndRestartIfNeeded();
|
||||
|
||||
if (global.gc) {
|
||||
|
@ -33,111 +52,111 @@ if (global.gc) {
|
|||
}, 60000);
|
||||
}
|
||||
|
||||
function logInfo(msg) { console.info(`[~] ${msg}`); }
|
||||
function logSuccess(msg) { console.info(`[+] ${msg}`); }
|
||||
function logError(err) { console.error(`[!] ${err instanceof Error ? err.message : err}`); }
|
||||
import "./others/scaler.mjs";
|
||||
import "./others/warmup.mjs";
|
||||
|
||||
process.on('uncaughtException', err => logError(`Unhandled Exception: ${err}`));
|
||||
process.on('unhandledRejection', reason => logError(`Unhandled Rejection: ${reason}`));
|
||||
const cache = new LRUCache({
|
||||
maxSize: 1000,
|
||||
ttl: 345600000,
|
||||
allowStale: false,
|
||||
sizeCalculation: (value, key) => Buffer.byteLength(value) + Buffer.byteLength(key)
|
||||
});
|
||||
|
||||
function applySurgeAndRestartIfNeeded() {
|
||||
if (isSurgedRun) {
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(surgeConfigPath, 'utf-8'));
|
||||
process.env.UV_THREADPOOL_SIZE = String(config.uvThreadpoolSize);
|
||||
} catch {}
|
||||
return;
|
||||
const port = parseInt(process.env.PORT || "3000", 10);
|
||||
|
||||
function logInfo(msg) {
|
||||
console.info(`[~] ${msg}`);
|
||||
}
|
||||
|
||||
const prep = spawnSync(process.execPath, ['./others/surge.mjs'], { stdio: 'inherit' });
|
||||
if (prep.error) process.exit(1);
|
||||
|
||||
const { nodeFlags, uvThreadpoolSize } = JSON.parse(fs.readFileSync(surgeConfigPath, 'utf-8'));
|
||||
const args = [...nodeFlags, path.resolve('index.mjs'), '--surged'];
|
||||
const env = { ...process.env, UV_THREADPOOL_SIZE: String(uvThreadpoolSize), ALREADY_SURGED: 'true' };
|
||||
|
||||
const relaunch = spawnSync(process.execPath, args, { stdio: 'inherit', env });
|
||||
process.exit(relaunch.status || 0);
|
||||
function logSuccess(msg) {
|
||||
console.info(`[+] ${msg}`);
|
||||
}
|
||||
|
||||
function logError(err) {
|
||||
console.error(`[!] ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
process.on("uncaughtException", err => logError(`Unhandled Exception: ${err}`));
|
||||
process.on("unhandledRejection", reason => logError(`Unhandled Rejection: ${reason}`));
|
||||
|
||||
if (cluster.isPrimary) {
|
||||
const cpuCount = Math.max(1, os.cpus().length - 1);
|
||||
logInfo(`Master: forking ${cpuCount} workers`);
|
||||
const cpus = os.cpus().length;
|
||||
const workers = Math.max(1, cpus - 1);
|
||||
|
||||
for (let i = 0; i < cpuCount; i++) {
|
||||
logInfo(`Master: forking ${workers} workers`);
|
||||
|
||||
for (let i = 0; i < workers; i++) {
|
||||
cluster.fork();
|
||||
}
|
||||
|
||||
cluster.on('exit', worker => {
|
||||
cluster.on("exit", worker => {
|
||||
logError(`Worker ${worker.process.pid} exited. Restarting...`);
|
||||
cluster.fork();
|
||||
});
|
||||
|
||||
const balancer = net.createServer({ pauseOnConnect: true }, conn => {
|
||||
const workers = Object.values(cluster.workers);
|
||||
if (workers.length === 0) return conn.destroy();
|
||||
const worker = workers[(balancer.current = (balancer.current + 1 || 0)) % workers.length];
|
||||
worker.send('sticky-session:connection', conn);
|
||||
let current = 0;
|
||||
const server = net.createServer({ pauseOnConnect: true }, conn => {
|
||||
const workersArr = Object.values(cluster.workers);
|
||||
if (!workersArr.length) return conn.destroy();
|
||||
const worker = workersArr[current++ % workersArr.length];
|
||||
worker.send("sticky-session:connection", conn);
|
||||
});
|
||||
|
||||
balancer.on('error', err => logError(`Balancer error: ${err}`));
|
||||
balancer.listen(PORT, () => logSuccess(`Balancer listening on ${PORT}`));
|
||||
server.on("error", err => logError(`Server error: ${err}`));
|
||||
server.listen(port, () => logSuccess(`Server listening on ${port}`));
|
||||
} else {
|
||||
const __dirname = process.cwd();
|
||||
const publicPath = path.join(__dirname, "public");
|
||||
const app = express();
|
||||
const publicPath = path.join(process.cwd(), 'public');
|
||||
const cache = new LRUCache({
|
||||
maxSize: 1000,
|
||||
ttl: 345600000,
|
||||
sizeCalculation: (value, key) => Buffer.byteLength(value) + Buffer.byteLength(key)
|
||||
});
|
||||
const latencySamples = [];
|
||||
let latencySamples = [];
|
||||
|
||||
app.use(compression({ level: 4, memLevel: 4, threshold: 1024 }));
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const key = req.originalUrl;
|
||||
const cached = cache.get(key);
|
||||
if (cached) {
|
||||
res.setHeader('X-Cache', 'HIT');
|
||||
return res.send(cached);
|
||||
const val = cache.get(key);
|
||||
if (val) {
|
||||
res.setHeader("X-Cache", "HIT");
|
||||
return res.send(val);
|
||||
}
|
||||
|
||||
res.sendResponse = res.send.bind(res);
|
||||
res.send = body => {
|
||||
cache.set(key, body);
|
||||
res.setHeader('X-Cache', 'MISS');
|
||||
res.setHeader("X-Cache", "MISS");
|
||||
res.sendResponse(body);
|
||||
};
|
||||
next();
|
||||
});
|
||||
|
||||
const staticOpts = { maxAge: '7d', immutable: true };
|
||||
app.use('/baremux', express.static(baremuxPath, staticOpts));
|
||||
app.use('/epoxy', express.static(epoxyPath, staticOpts));
|
||||
app.use('/libcurl', express.static(libcurlPath, staticOpts));
|
||||
app.use('/wah', express.static(uvPath, staticOpts));
|
||||
const staticOpts = { maxAge: "7d", immutable: true };
|
||||
app.use("/baremux/", express.static(baremuxPath, staticOpts));
|
||||
app.use("/epoxy/", express.static(epoxyPath, staticOpts));
|
||||
app.use("/libcurl/", express.static(libcurlPath, staticOpts));
|
||||
app.use(express.static(publicPath, staticOpts));
|
||||
app.use("/wah/", express.static(uvPath, staticOpts));
|
||||
app.use(express.json());
|
||||
|
||||
const sendHtml = file => (_req, res) => res.sendFile(path.join(publicPath, file));
|
||||
|
||||
app.get('/', sendHtml('$.html'));
|
||||
app.get('/g', sendHtml('!.html'));
|
||||
app.get('/a', sendHtml('!!.html'));
|
||||
app.get('/resent', (_req, res) => res.sendFile(path.join(publicPath, 'resent/index.html')));
|
||||
app.get('/resent', (_req, res) => res.sendFile(path.join(publicPath, 'resent', 'index.html')));
|
||||
|
||||
app.get('/api/info', (_req, res) => {
|
||||
try {
|
||||
const avg = latencySamples.length
|
||||
? latencySamples.reduce((sum, v) => sum + v, 0) / latencySamples.length
|
||||
const average = latencySamples.length > 0
|
||||
? latencySamples.reduce((a, b) => a + b, 0) / latencySamples.length
|
||||
: 0;
|
||||
let speed = 'Medium';
|
||||
if (avg < 200) speed = 'Fast';
|
||||
else if (avg > 500) speed = 'Slow';
|
||||
|
||||
if (average < 200) speed = 'Fast';
|
||||
else if (average > 500) speed = 'Slow';
|
||||
const cpus = os.cpus();
|
||||
const totalMem = os.totalmem() / 1024 / 1024 / 1024;
|
||||
res.json({
|
||||
speed,
|
||||
averageLatency: avg.toFixed(2),
|
||||
specs: `${os.cpus()[0].model} + ${os.cpus().length} cores + ${(os.totalmem() / 1e9).toFixed(1)}GB RAM`,
|
||||
averageLatency: average.toFixed(2),
|
||||
specs: `${cpus[0].model} + ${cpus.length} CPU Cores + ${totalMem.toFixed(1)}GB of RAM`,
|
||||
startTime,
|
||||
samples: latencySamples.length,
|
||||
timestamp: Date.now()
|
||||
|
@ -149,40 +168,34 @@ if (cluster.isPrimary) {
|
|||
|
||||
app.get('/api/github-updates', async (_req, res) => {
|
||||
try {
|
||||
const ghRes = await fetch(
|
||||
'https://api.github.com/repos/xojw/waves/commits?per_page=1',
|
||||
{
|
||||
const ghRes = await fetch('https://api.github.com/repos/xojw/waves/commits?per_page=1', {
|
||||
headers: {
|
||||
'User-Agent': 'waves-app',
|
||||
'Accept': 'application/vnd.github.v3+json'
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!ghRes.ok) {
|
||||
return res.status(ghRes.status).json({ error: 'GitHub API error' });
|
||||
}
|
||||
|
||||
const [latest] = await ghRes.json();
|
||||
const then = new Date(latest.commit.author.date).getTime();
|
||||
const diff = Date.now() - then;
|
||||
const mins = Math.round(diff / 60000);
|
||||
const hrs = Math.round(diff / 3600000);
|
||||
const ago = hrs >= 1
|
||||
? `${hrs} hour${hrs > 1 ? 's' : ''} ago`
|
||||
: `${mins} minute${mins !== 1 ? 's' : ''} ago`;
|
||||
|
||||
res.json({
|
||||
repo: 'xojw/waves',
|
||||
update: {
|
||||
sha: latest.sha.slice(0, 7),
|
||||
author: latest.commit.author.name,
|
||||
message: latest.commit.message.split('\n')[0],
|
||||
date: latest.commit.author.date,
|
||||
ago
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logError(err);
|
||||
if (!ghRes.ok) return res.status(ghRes.status).json({ error: 'GitHub API error' });
|
||||
|
||||
const commits = await ghRes.json();
|
||||
const now = Date.now();
|
||||
|
||||
const updates = commits.map(c => {
|
||||
const dateMs = new Date(c.commit.author.date).getTime();
|
||||
const diffMs = now - dateMs;
|
||||
const diffMins = Math.round(diffMs / 60000);
|
||||
const diffHours = Math.round(diffMs / 3600000);
|
||||
const ago = diffHours > 1 ? `${diffHours} hours ago` : `${diffMins} minutes ago`;
|
||||
return {
|
||||
sha: c.sha.slice(0, 7),
|
||||
message: c.commit.message.split('\n')[0],
|
||||
author: c.commit.author.name,
|
||||
date: c.commit.author.date,
|
||||
ago
|
||||
};
|
||||
});
|
||||
|
||||
res.json({ repo: 'xojw/waves', updates });
|
||||
} catch {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
@ -193,56 +206,55 @@ if (cluster.isPrimary) {
|
|||
server.keepAliveTimeout = 0;
|
||||
server.headersTimeout = 0;
|
||||
|
||||
const pingWSS = new WebSocket.Server({
|
||||
noServer: true,
|
||||
maxPayload: 4194304,
|
||||
perMessageDeflate: false
|
||||
});
|
||||
const pingWSS = new WebSocket.Server({ noServer: true, maxPayload: 4194304, perMessageDeflate: false });
|
||||
|
||||
pingWSS.on('connection', (ws, req) => {
|
||||
pingWSS.on("connection", (ws, req) => {
|
||||
const remote = req.socket.remoteAddress || 'unknown';
|
||||
let lat = [];
|
||||
|
||||
const ticker = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));
|
||||
}
|
||||
const interval = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));
|
||||
}, 1000);
|
||||
|
||||
ws.on('message', msg => {
|
||||
try {
|
||||
const { type, timestamp } = JSON.parse(msg);
|
||||
if (type === 'pong' && timestamp) {
|
||||
const d = Date.now() - timestamp;
|
||||
const data = JSON.parse(msg);
|
||||
if (data.type === 'pong' && data.timestamp) {
|
||||
const d = Date.now() - data.timestamp;
|
||||
lat.push(d);
|
||||
latencySamples.push(d);
|
||||
if (lat.length > 5) lat.shift();
|
||||
latencySamples.push(d);
|
||||
if (latencySamples.length > 100) latencySamples.shift();
|
||||
ws.send(JSON.stringify({ type: 'latency', latency: d }));
|
||||
}
|
||||
} catch(e) {
|
||||
logError(`Ping error: ${e}`);
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
clearInterval(ticker);
|
||||
clearInterval(interval);
|
||||
const avg = lat.length ? (lat.reduce((a,b)=>a+b)/lat.length).toFixed(2) : 0;
|
||||
logInfo(`WS ${remote} closed. Avg: ${avg}ms`);
|
||||
});
|
||||
});
|
||||
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
if (req.url === '/ws/ping') {
|
||||
pingWSS.handleUpgrade(req, socket, head, ws => {
|
||||
pingWSS.emit('connection', ws, req);
|
||||
});
|
||||
server.on('upgrade', (req, sock, head) => {
|
||||
if (req.url === '/w/ping') {
|
||||
pingWSS.handleUpgrade(req, sock, head, ws => pingWSS.emit('connection', ws, req));
|
||||
} else if (req.url.startsWith('/w/')) {
|
||||
wisp.routeRequest(req, sock, head);
|
||||
} else {
|
||||
socket.destroy();
|
||||
sock.end();
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(0, () => {
|
||||
logSuccess(`Worker ${process.pid} ready`);
|
||||
});
|
||||
server.on('error', err => logError(`Worker error: ${err}`));
|
||||
server.listen(0, () => logSuccess(`Worker ${process.pid} ready`));
|
||||
|
||||
process.on('message', (msg, conn) => {
|
||||
if (msg !== 'sticky-session:connection') return;
|
||||
if (msg === 'sticky-session:connection' && conn) {
|
||||
server.emit('connection', conn);
|
||||
conn.resume();
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user