mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-18 08:24:40 +03:00
f28b6b1074
@BehroozAmoozad reported the docs reference a wrapper.ts file that doesn't exist in the repo — the README.md / README.fa.md "Your own VPS" row pointed at a wrapper.ts the user was supposed to write themselves. For non-JavaScript users, this was a hard blocker. Adds assets/exit_node/wrapper.ts that: - Imports the handler from exit_node.ts directly (so editing the PSK in exit_node.ts is the only setup step). - Auto-detects Deno, Bun, or Node 22+ at runtime and uses the matching HTTP server primitive (Deno.serve / Bun.serve / node:http). - Reads PORT, HOST, CERT_FILE, KEY_FILE from env. - Defaults to plain HTTP on port 8443 and recommends a reverse proxy (Caddy / nginx / Cloudflare Tunnel) for TLS termination — the path most VPS setups already have. - Optional standalone TLS via CERT_FILE + KEY_FILE for users without a reverse proxy. README.md + README.fa.md updated to link the file directly and adjust the deno run command to include the new --allow-env / --allow-read permissions the wrapper needs. No code changes — assets-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
120 lines
4.4 KiB
TypeScript
120 lines
4.4 KiB
TypeScript
// VPS wrapper for exit_node.ts — used when you run the exit node on your
|
|
// own server (any platform that can run Deno or Bun) instead of on a
|
|
// platform that auto-invokes the default export (Deno Deploy, Val.town,
|
|
// Cloudflare Workers, etc.).
|
|
//
|
|
// Pick ONE runtime + matching command:
|
|
//
|
|
// Deno (recommended, comes with HTTPS support out of the box):
|
|
// deno run --allow-net --allow-env wrapper.ts
|
|
//
|
|
// Bun (also works, slightly faster cold start):
|
|
// bun run wrapper.ts
|
|
//
|
|
// Node 22+ (no extra runtime; needs `--experimental-fetch` only on <22):
|
|
// node wrapper.ts # if your Node has fetch + Bun's
|
|
// # global Request/Response (22+)
|
|
//
|
|
// ENV VARS (all optional):
|
|
// PORT — TCP port to bind. Default 8443.
|
|
// HOST — bind address. Default 0.0.0.0 (all interfaces).
|
|
// CERT_FILE — path to TLS cert PEM. Omit for plain HTTP (use a reverse
|
|
// proxy like Caddy / nginx / Cloudflare Tunnel for TLS).
|
|
// KEY_FILE — path to TLS key PEM (must be set together with CERT_FILE).
|
|
//
|
|
// Behind a reverse proxy (Caddy, nginx, Cloudflare Tunnel) you typically
|
|
// run this on plain HTTP and let the proxy terminate TLS — that's the
|
|
// simpler setup if you already have a domain. Set PORT=8443 and point
|
|
// your reverse proxy at http://localhost:8443.
|
|
//
|
|
// Standalone TLS (rare but supported): set CERT_FILE + KEY_FILE to
|
|
// matching PEM-encoded files and Deno will terminate TLS itself. Use
|
|
// Let's Encrypt's certbot / acme.sh to fetch a real cert; self-signed
|
|
// will not work (Apps Script's UrlFetchApp validates the chain).
|
|
//
|
|
// EDIT exit_node.ts FIRST: replace the placeholder PSK with a strong
|
|
// secret. The wrapper imports the handler from exit_node.ts directly,
|
|
// so changing the constant in exit_node.ts is all you need.
|
|
|
|
import handler from "./exit_node.ts";
|
|
|
|
// Deno (preferred)
|
|
if (typeof (globalThis as any).Deno !== "undefined") {
|
|
const Deno = (globalThis as any).Deno;
|
|
const port = Number(Deno.env.get("PORT") ?? 8443);
|
|
const hostname = Deno.env.get("HOST") ?? "0.0.0.0";
|
|
const certFile = Deno.env.get("CERT_FILE");
|
|
const keyFile = Deno.env.get("KEY_FILE");
|
|
|
|
if (certFile && keyFile) {
|
|
Deno.serve(
|
|
{
|
|
port,
|
|
hostname,
|
|
cert: Deno.readTextFileSync(certFile),
|
|
key: Deno.readTextFileSync(keyFile),
|
|
},
|
|
handler,
|
|
);
|
|
console.log(`exit_node listening on https://${hostname}:${port}`);
|
|
} else {
|
|
Deno.serve({ port, hostname }, handler);
|
|
console.log(
|
|
`exit_node listening on http://${hostname}:${port} ` +
|
|
`(no TLS — terminate it with a reverse proxy like Caddy/nginx)`,
|
|
);
|
|
}
|
|
}
|
|
// Bun
|
|
else if (typeof (globalThis as any).Bun !== "undefined") {
|
|
const Bun = (globalThis as any).Bun;
|
|
const port = Number(process.env.PORT ?? 8443);
|
|
const hostname = process.env.HOST ?? "0.0.0.0";
|
|
|
|
Bun.serve({
|
|
port,
|
|
hostname,
|
|
fetch: handler,
|
|
tls: process.env.CERT_FILE && process.env.KEY_FILE
|
|
? {
|
|
cert: Bun.file(process.env.CERT_FILE),
|
|
key: Bun.file(process.env.KEY_FILE),
|
|
}
|
|
: undefined,
|
|
});
|
|
console.log(`exit_node listening on ${hostname}:${port}`);
|
|
}
|
|
// Node 22+ — uses the built-in `node:http` module + globalThis.Request/Response
|
|
else if (typeof (globalThis as any).process !== "undefined") {
|
|
const { createServer } = await import("node:http");
|
|
const port = Number(process.env.PORT ?? 8443);
|
|
const hostname = process.env.HOST ?? "0.0.0.0";
|
|
|
|
createServer(async (req, res) => {
|
|
// Build a web-standard Request from Node's IncomingMessage.
|
|
const chunks: Uint8Array[] = [];
|
|
for await (const c of req) chunks.push(c as Uint8Array);
|
|
const body = chunks.length ? Buffer.concat(chunks) : undefined;
|
|
|
|
const url = `http://${req.headers.host ?? hostname}${req.url ?? "/"}`;
|
|
const webReq = new Request(url, {
|
|
method: req.method,
|
|
headers: req.headers as Record<string, string>,
|
|
body,
|
|
});
|
|
|
|
const webRes = await handler(webReq);
|
|
|
|
res.statusCode = webRes.status;
|
|
webRes.headers.forEach((v: string, k: string) => res.setHeader(k, v));
|
|
const buf = new Uint8Array(await webRes.arrayBuffer());
|
|
res.end(buf);
|
|
}).listen(port, hostname, () => {
|
|
console.log(`exit_node listening on http://${hostname}:${port}`);
|
|
});
|
|
} else {
|
|
throw new Error(
|
|
"No supported runtime detected. Run this file with Deno, Bun, or Node 22+.",
|
|
);
|
|
}
|