// front.ts: one public front door on port 8000 for the vals that ask for it. // A val with PUBLIC=1 and a PORT in its .env is served at // https://.exe.xyz//... (the prefix is removed), and at // https:///... (custom domains, comma-separated). // Vals without PUBLIC=1 are never reachable here, only on their own, // private port. WebSockets are not passed through. const VALS = `${Deno.env.get("HOME")}/vals`; type Route = { val: string; port: number; hosts: string[] }; let routes: Route[] = [], loaded = 0; async function load() { if (Date.now() - loaded < 5000) return routes; const next: Route[] = []; for await (const d of Deno.readDir(VALS)) { if (!d.isDirectory || d.name.startsWith(".")) continue; let env = ""; try { env = await Deno.readTextFile(`${VALS}/${d.name}/.env`); } catch { continue; } const get = (k: string) => env.match(new RegExp(`^${k}=(.*)$`, "m"))?.[1].trim(); if (get("PUBLIC") !== "1" || !get("PORT")) continue; const hosts = (get("HOSTS") ?? "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean); next.push({ val: d.name, port: Number(get("PORT")), hosts }); } routes = next; loaded = Date.now(); return routes; } Deno.serve({ port: Number(Deno.env.get("PORT") ?? 8000) }, async (req) => { const url = new URL(req.url); const host = (req.headers.get("x-forwarded-host") ?? req.headers.get("host") ?? "") .replace(/:\d+$/, "").toLowerCase(); const all = await load(); let route = all.find((r) => r.hosts.includes(host)); let path = url.pathname, prefix = ""; if (!route) { const first = path.split("/")[1]; route = all.find((r) => r.val === first); if (!route) return new Response("Not found\n", { status: 404 }); prefix = `/${first}`; path = path.slice(prefix.length) || "/"; } const headers = new Headers(req.headers); headers.set("accept-encoding", "identity"); if (prefix) headers.set("x-forwarded-prefix", prefix); try { return await fetch(`http://127.0.0.1:${route.port}${path}${url.search}`, { method: req.method, headers, body: req.body, redirect: "manual", }); } catch { return new Response(`${route.val} is not running\n`, { status: 502 }); } });