Deploy to Raspberry Pi / deploy (push) Successful in 52s
- Renamed the control process to `jflow-control` and updated the script path to `control.js`. - Introduced a new `jflow-web` process for the production UI, serving on port 8500. - Enhanced environment variable handling for both processes, ensuring proper port assignments. - Updated README to reflect new process architecture and usage instructions for starting the application.
117 lines
2.9 KiB
JavaScript
117 lines
2.9 KiB
JavaScript
/**
|
|
* Production UI server (:8500).
|
|
* Serves packages/web/dist and proxies /api, /ops, /admin, /u like Vite in dev.
|
|
* Always-on — survives Ops stop of jflow-http.
|
|
*/
|
|
import fs from "fs";
|
|
import fastify from "fastify";
|
|
import fastifyStatic from "@fastify/static";
|
|
import { log } from "./logger.js";
|
|
import { WEB_DIST } from "./paths.js";
|
|
import {
|
|
controlOrigin,
|
|
httpOrigin,
|
|
proxyToOrigin,
|
|
} from "./ops-proxy.js";
|
|
|
|
if (!fs.existsSync(WEB_DIST)) {
|
|
log.error(
|
|
{ WEB_DIST },
|
|
"web dist missing — run `pnpm build` before starting the UI server",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const port = Number(process.env.JFLOW_UI_PORT ?? 8500);
|
|
const control = controlOrigin();
|
|
const http = httpOrigin();
|
|
|
|
const server = fastify({ loggerInstance: log });
|
|
|
|
/**
|
|
* @param {import("fastify").FastifyRequest} req
|
|
* @param {import("fastify").FastifyReply} reply
|
|
*/
|
|
async function proxyApi(req, reply) {
|
|
const url = req.raw.url ?? "";
|
|
// Match Vite: /api/auth → control (login works when HTTP is stopped).
|
|
if (url === "/api/auth" || url.startsWith("/api/auth/") || url.startsWith("/api/auth?")) {
|
|
return proxyToOrigin(req, reply, control, {
|
|
unreachableMessage: "control plane unreachable",
|
|
});
|
|
}
|
|
return proxyToOrigin(req, reply, http, {
|
|
unreachableMessage: "HTTP API unreachable",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {import("fastify").FastifyRequest} req
|
|
* @param {import("fastify").FastifyReply} reply
|
|
*/
|
|
async function proxyOps(req, reply) {
|
|
return proxyToOrigin(req, reply, control, {
|
|
unreachableMessage: "control plane unreachable",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {import("fastify").FastifyRequest} req
|
|
* @param {import("fastify").FastifyReply} reply
|
|
*/
|
|
async function proxyHttp(req, reply) {
|
|
return proxyToOrigin(req, reply, http, {
|
|
unreachableMessage: "HTTP API unreachable",
|
|
});
|
|
}
|
|
|
|
server.all("/api", proxyApi);
|
|
server.all("/api/*", proxyApi);
|
|
server.all("/ops", proxyOps);
|
|
server.all("/ops/*", proxyOps);
|
|
server.all("/admin", proxyHttp);
|
|
server.all("/admin/*", proxyHttp);
|
|
server.all("/u", proxyHttp);
|
|
server.all("/u/*", proxyHttp);
|
|
|
|
await server.register(fastifyStatic, {
|
|
root: WEB_DIST,
|
|
wildcard: false,
|
|
});
|
|
|
|
server.setNotFoundHandler((req, reply) => {
|
|
const url = req.raw.url ?? "";
|
|
if (
|
|
url.startsWith("/api") ||
|
|
url.startsWith("/u/") ||
|
|
url.startsWith("/admin") ||
|
|
url.startsWith("/ops")
|
|
) {
|
|
return reply.code(404).send({ error: "not found" });
|
|
}
|
|
return reply.sendFile("index.html");
|
|
});
|
|
|
|
async function shutdown() {
|
|
try {
|
|
await server.close();
|
|
} catch (err) {
|
|
log.error({ err }, "web-server shutdown error");
|
|
}
|
|
process.exit(0);
|
|
}
|
|
|
|
process.on("SIGINT", shutdown);
|
|
process.on("SIGTERM", shutdown);
|
|
|
|
try {
|
|
await server.listen({ host: "0.0.0.0", port });
|
|
log.info(
|
|
{ port, WEB_DIST, control, http },
|
|
"UI server listening (static + proxy)",
|
|
);
|
|
} catch (err) {
|
|
log.error({ err }, "failed to start UI server");
|
|
process.exit(1);
|
|
}
|