dokploy/apps/vite/server/server.ts
Mauricio Siu a7adc3c36b refactor: update Vite application structure and dependencies
- Removed the `index.html` file as part of the transition to a new routing system.
- Updated `package.json` scripts to streamline development commands using `concurrently`.
- Enhanced dependency management by adding new packages and updating existing ones in `package.json`.
- Introduced a new server function for branding and session management, improving API interactions.
- Refactored routing to utilize TanStack Start, ensuring better integration with the existing Dokploy architecture.
- Improved static file handling and server response management in the custom Node.js server.
- Updated README to reflect changes in architecture and development processes.
2026-08-11 01:15:43 -06:00

233 lines
7.3 KiB
TypeScript

import fs from "node:fs";
import http from "node:http";
import path from "node:path";
import { pathToFileURL } from "node:url";
import {
auth,
createDefaultMiddlewares,
createDefaultServerTraefikConfig,
createDefaultTraefikConfig,
getPublicWhitelabelingConfig,
IS_CLOUD,
initCancelDeployments,
initCronJobs,
initEnterpriseBackupCronJobs,
initializeNetwork,
initSchedules,
initVolumeBackupsCronJobs,
sendDokployRestartNotifications,
setupDirectories,
} from "@dokploy/server";
import { toNodeHandler } from "better-auth/node";
import { config } from "dotenv";
import openApiHandler from "@/pages/api/[...trpc]";
import deployRefreshTokenHandler from "@/pages/api/deploy/[refreshToken]";
import deployComposeHandler from "@/pages/api/deploy/compose/[refreshToken]";
import deployGithubHandler from "@/pages/api/deploy/github";
import healthHandler from "@/pages/api/health";
import giteaAuthorizeHandler from "@/pages/api/providers/gitea/authorize";
import giteaCallbackHandler from "@/pages/api/providers/gitea/callback";
import githubSetupHandler from "@/pages/api/providers/github/setup";
import githubWebhookHandler from "@/pages/api/providers/github/webhook";
import gitlabCallbackHandler from "@/pages/api/providers/gitlab/callback";
import stripeWebhookHandler from "@/pages/api/stripe/webhook";
import trpcHandler from "@/pages/api/trpc/[trpc]";
import { setupDockerContainerLogsWebSocketServer } from "@/server/wss/docker-container-logs";
import { setupDockerContainerTerminalWebSocketServer } from "@/server/wss/docker-container-terminal";
import { setupDockerStatsMonitoringSocketServer } from "@/server/wss/docker-stats";
import { setupDrawerLogsWebSocketServer } from "@/server/wss/drawer-logs";
import { setupDeploymentLogsWebSocketServer } from "@/server/wss/listen-deployment";
import { setupTerminalWebSocketServer } from "@/server/wss/terminal";
import packageInfo from "../package.json";
import { runNextApiHandler } from "./next-compat";
import { createStartBridge } from "./start-bridge";
import { createStaticHandler } from "./static";
config({ path: ".env" });
config({ path: "../dokploy/.env" });
const PORT = Number.parseInt(process.env.PORT || "3000", 10);
const HOST = process.env.HOST || "0.0.0.0";
const isProd = process.env.NODE_ENV === "production";
if (isProd && !IS_CLOUD) {
setupDirectories();
createDefaultTraefikConfig();
createDefaultServerTraefikConfig();
console.log("✅ initialization complete");
}
const authHandler = toNodeHandler(auth.handler);
const handleApi = async (
req: http.IncomingMessage,
res: http.ServerResponse,
pathname: string,
) => {
if (pathname === "/api/health") {
return runNextApiHandler(healthHandler, req, res);
}
if (pathname === "/api/branding") {
let config = null;
try {
config = await getPublicWhitelabelingConfig();
} catch {
config = null;
}
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
res.end(JSON.stringify(config));
return;
}
if (pathname.startsWith("/api/auth/")) {
return authHandler(req, res);
}
if (pathname.startsWith("/api/trpc/")) {
const trpc = decodeURIComponent(pathname.slice("/api/trpc/".length));
return runNextApiHandler(trpcHandler, req, res, {
params: { trpc },
parseBody: false,
});
}
if (pathname === "/api/deploy/github") {
return runNextApiHandler(deployGithubHandler, req, res);
}
if (pathname === "/api/stripe/webhook") {
return runNextApiHandler(stripeWebhookHandler, req, res, {
parseBody: false,
});
}
if (pathname === "/api/providers/github/setup") {
return runNextApiHandler(githubSetupHandler, req, res);
}
if (pathname === "/api/providers/github/webhook") {
return runNextApiHandler(githubWebhookHandler, req, res);
}
if (pathname === "/api/providers/gitlab/callback") {
return runNextApiHandler(gitlabCallbackHandler, req, res);
}
if (pathname === "/api/providers/gitea/authorize") {
return runNextApiHandler(giteaAuthorizeHandler, req, res);
}
if (pathname === "/api/providers/gitea/callback") {
return runNextApiHandler(giteaCallbackHandler, req, res);
}
const composeMatch = pathname.match(/^\/api\/deploy\/compose\/([^/]+)$/);
if (composeMatch?.[1]) {
return runNextApiHandler(deployComposeHandler, req, res, {
params: { refreshToken: composeMatch[1] },
});
}
const deployMatch = pathname.match(/^\/api\/deploy\/([^/]+)$/);
if (deployMatch?.[1]) {
return runNextApiHandler(deployRefreshTokenHandler, req, res, {
params: { refreshToken: deployMatch[1] },
});
}
const segments = pathname
.slice("/api/".length)
.split("/")
.filter(Boolean)
.map(decodeURIComponent);
return runNextApiHandler(openApiHandler, req, res, {
params: { trpc: segments },
});
};
const startServer = async () => {
console.log("Running DokployVersion: ", packageInfo.version);
let handleUi: (
req: http.IncomingMessage,
res: http.ServerResponse,
url: URL,
) => void | Promise<void> = (_req, res) => {
res.writeHead(503, { "content-type": "text/plain" });
res.end("UI not ready");
};
const server = http.createServer(async (req, res) => {
try {
const url = new URL(req.url ?? "/", "http://localhost");
if (url.pathname.startsWith("/api")) {
await handleApi(req, res, url.pathname);
return;
}
await handleUi(req, res, url);
} catch (error) {
console.error("Request error", error);
if (!res.headersSent) {
res.writeHead(500, { "content-type": "text/plain" });
}
res.end("Internal Server Error");
}
});
if (isProd) {
const clientDir = path.resolve(import.meta.dirname, "../dist/client");
const serverEntry = path.resolve(
import.meta.dirname,
"../dist/server/server.js",
);
const staticHandler = createStaticHandler(clientDir);
const entry = await import(pathToFileURL(serverEntry).href);
const bridge = createStartBridge(entry.default);
handleUi = async (req, res, url) => {
const pathname = decodeURIComponent(url.pathname);
const filePath = path.join(clientDir, pathname);
if (
pathname !== "/" &&
filePath.startsWith(clientDir) &&
fs.existsSync(filePath) &&
fs.statSync(filePath).isFile()
) {
staticHandler(req, res, url);
return;
}
await bridge(req, res);
};
console.log("TanStack Start SSR bridge enabled");
} else {
handleUi = (_req, res) => {
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
res.end(
"API-only mode: the UI dev server runs separately (pnpm --filter dokploy-vite dev → http://localhost:5173).",
);
};
}
setupDrawerLogsWebSocketServer(server);
setupDeploymentLogsWebSocketServer(server);
setupDockerContainerLogsWebSocketServer(server);
setupDockerContainerTerminalWebSocketServer(server);
setupTerminalWebSocketServer(server);
if (!IS_CLOUD) {
setupDockerStatsMonitoringSocketServer(server);
}
server.listen(PORT, HOST);
console.log(`Standalone Server Started on: http://${HOST}:${PORT}`);
if (isProd && !IS_CLOUD) {
createDefaultMiddlewares();
await initializeNetwork();
await initCronJobs();
await initSchedules();
await initCancelDeployments();
await initVolumeBackupsCronJobs();
await sendDokployRestartNotifications();
}
await initEnterpriseBackupCronJobs();
if (!IS_CLOUD) {
console.log("Starting Deployment Worker");
const { startDeploymentWorker } = await import(
"@/server/queues/queueSetup"
);
await startDeploymentWorker();
}
};
startServer().catch((error) => {
console.error("Main Server Error", error);
});