diff --git a/config/supervisord.conf b/config/supervisord.conf index 67dc92b..355e073 100644 --- a/config/supervisord.conf +++ b/config/supervisord.conf @@ -25,7 +25,7 @@ stderr_logfile=/app/data/logs/mediamtx.log [program:nextjs] directory=/app -command=node server.js +command=node /opt/server.mjs environment=PORT=3000,HOSTNAME=0.0.0.0,DATA_DIR=/app/data autorestart=true priority=2 diff --git a/docker/Dockerfile b/docker/Dockerfile index c4c8abe..fc627cb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -66,6 +66,7 @@ COPY --from=builder /build/public/ /app/public/ COPY config/supervisord.conf /etc/supervisor/supervisord.conf COPY config/mediamtx.yml /etc/mediamtx.yml COPY scripts/ /opt/scripts/ +COPY docker/server.mjs /opt/server.mjs COPY docker/entrypoint.sh /entrypoint.sh RUN chmod +x /opt/scripts/*.sh /entrypoint.sh diff --git a/docker/server.mjs b/docker/server.mjs new file mode 100644 index 0000000..2085a97 --- /dev/null +++ b/docker/server.mjs @@ -0,0 +1,38 @@ +// Custom entry point: patches http.createServer before Next.js starts, +// so WebSocket upgrade requests to /websockify are proxied to localhost:6080 +// (noVNC/websockify) without exposing that port publicly. +import http from "node:http" +import net from "node:net" + +const _createServer = http.createServer.bind(http) + +function attachWebSocketProxy(server) { + server.on("upgrade", (req, socket, head) => { + const upstream = net.connect({ host: "127.0.0.1", port: 6080 }) + + upstream.once("connect", () => { + let raw = `${req.method} ${req.url} HTTP/1.1\r\n` + for (const [k, v] of Object.entries(req.headers)) { + raw += `${k}: ${Array.isArray(v) ? v.join(", ") : v}\r\n` + } + raw += "\r\n" + upstream.write(raw) + if (head?.length) upstream.write(head) + socket.pipe(upstream) + upstream.pipe(socket) + }) + + upstream.on("error", () => { try { socket.destroy() } catch {} }) + socket.on("error", () => { try { upstream.destroy() } catch {} }) + socket.on("close", () => { try { upstream.destroy() } catch {} }) + upstream.on("close", () => { try { socket.destroy() } catch {} }) + }) +} + +http.createServer = function (...args) { + const server = _createServer(...args) + attachWebSocketProxy(server) + return server +} + +await import("/app/server.js") diff --git a/src/app/api/novnc/[...path]/route.ts b/src/app/api/novnc/[...path]/route.ts new file mode 100644 index 0000000..957e79b --- /dev/null +++ b/src/app/api/novnc/[...path]/route.ts @@ -0,0 +1,27 @@ +import { type NextRequest, NextResponse } from "next/server" + +type Ctx = { params: Promise<{ path: string[] }> } + +export async function GET(req: NextRequest, { params }: Ctx) { + const { path } = await params + const searchParams = req.nextUrl.searchParams.toString() + const query = searchParams ? `?${searchParams}` : "" + const upstream = `http://localhost:6080/${path.join("/")}${query}` + + try { + const res = await fetch(upstream, { + headers: { Accept: req.headers.get("Accept") ?? "*/*" }, + }) + + if (!res.ok) return new NextResponse(null, { status: res.status }) + + const headers = new Headers() + const ct = res.headers.get("content-type") + if (ct) headers.set("content-type", ct) + headers.set("cache-control", "no-cache") + + return new NextResponse(res.body, { status: 200, headers }) + } catch { + return new NextResponse(null, { status: 502 }) + } +} diff --git a/src/app/vnc/[id]/page.tsx b/src/app/vnc/[id]/page.tsx index 19789f0..4e20d75 100644 --- a/src/app/vnc/[id]/page.tsx +++ b/src/app/vnc/[id]/page.tsx @@ -38,14 +38,17 @@ function BackButton({ onClick }: { onClick: () => void }) { function VncInner() { const { id } = useParams<{ id: string }>() const router = useRouter() - const host = typeof window !== "undefined" ? window.location.hostname : "localhost" - const token = encodeURIComponent(`token=${id}`) - const vncUrl = `http://${host}:6080/vnc.html?autoconnect=true&path=websockify%3F${token}` + const [vncUrl, setVncUrl] = useState(null) + + useEffect(() => { + const token = encodeURIComponent(`token=${id}`) + setVncUrl(`/api/novnc/vnc.html?autoconnect=true&path=websockify%3F${token}`) + }, [id]) return (
router.push("/")} /> -