Files
nsrb be8122f9e5
Deploy to Raspberry Pi / deploy (push) Successful in 52s
feat(ecosystem): update PM2 configuration for control plane and web server
- 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.
2026-08-22 22:32:04 +07:00

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);
}