import fs, { createReadStream, writeFileSync } from "node:fs";import path from "node:path";import { createInterface } from "node:readline";import { paths } from "@dokploy/server/constants";import type { Domain } from "@dokploy/server/services/domain";import { parse, stringify } from "yaml";import { encodeBase64 } from "../docker/utils";import { execAsync, execAsyncRemote } from "../process/execAsync";import type { FileConfig, HttpLoadBalancerService } from "./file-types"; export const createTraefikConfig = (appName: string) => { const defaultPort = 3000; const serviceURLDefault = `http://${appName}:${defaultPort}`; const domainDefault = `Host(\`${appName}.docker.localhost\`)`; const config: FileConfig = { http: { routers: { ...(process.env.NODE_ENV === "production" ? {} : { [`${appName}-router-1`]: { rule: domainDefault, service: `${appName}-service-1`, entryPoints: ["web"], }, }), }, services: { ...(process.env.NODE_ENV === "production" ? {} : { [`${appName}-service-1`]: { loadBalancer: { servers: [{ url: serviceURLDefault }], passHostHeader: true, }, }, }), }, }, }; const yamlStr = stringify(config); const { DYNAMIC_TRAEFIK_PATH } = paths(); fs.mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true }); writeFileSync( path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`), yamlStr, "utf8", );}; export const removeTraefikConfig = async ( appName: string, serverId?: string | null,) => { try { const { DYNAMIC_TRAEFIK_PATH } = paths(!!serverId); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); const command = `rm -f ${configPath}`; if (serverId) { await execAsyncRemote(serverId, command); } else { await execAsync(command); } } catch (error) { console.error(`Error removing traefik config for ${appName}:`, error); }}; export const removeTraefikConfigRemote = async ( appName: string, serverId: string,) => { try { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); await execAsyncRemote(serverId, `rm -f ${configPath}`); } catch (error) { console.error( `Error removing remote traefik config for ${appName}:`, error, ); }}; export const loadOrCreateConfig = (appName: string): FileConfig => { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); if (fs.existsSync(configPath)) { const yamlStr = fs.readFileSync(configPath, "utf8"); const parsedConfig = (parse(yamlStr) as FileConfig) || { http: { routers: {}, services: {} }, }; return parsedConfig; } return { http: { routers: {}, services: {} } };}; export const loadOrCreateConfigRemote = async ( serverId: string, appName: string,) => { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const fileConfig: FileConfig = { http: { routers: {}, services: {} } }; const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); try { const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); if (!stdout) return fileConfig; const parsedConfig = (parse(stdout) as FileConfig) || { http: { routers: {}, services: {} }, }; return parsedConfig; } catch { return fileConfig; }}; export const readConfig = (appName: string) => { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); if (fs.existsSync(configPath)) { const yamlStr = fs.readFileSync(configPath, "utf8"); return yamlStr; } return null;}; export const readRemoteConfig = async (serverId: string, appName: string) => { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); try { const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); if (!stdout) return null; return stdout; } catch { return null; }}; export const readMonitoringConfig = async (readAll = false) => { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, "access.log"); if (fs.existsSync(configPath)) { if (!readAll) { // Read first 500 lines using streams let content = ""; let validCount = 0; const fileStream = createReadStream(configPath, { encoding: "utf8" }); const readline = createInterface({ input: fileStream, crlfDelay: Number.POSITIVE_INFINITY, }); for await (const line of readline) { try { const trimmed = line.trim(); if ( trimmed !== "" && trimmed.startsWith("{") && trimmed.endsWith("}") ) { const log = JSON.parse(trimmed); // Exclude Dokploy service app and Dashboard requests if (log.ServiceName !== "dokploy-service-app@file") { content += `${line}\n`; validCount++; if (validCount >= 500) { break; } } } } catch { // Ignore invalid JSON } } return content; } return fs.readFileSync(configPath, "utf8"); } return null;}; export const readConfigInPath = async (pathFile: string, serverId?: string) => { const configPath = path.join(pathFile); if (serverId) { const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); if (!stdout) return null; return stdout; } if (fs.existsSync(configPath)) { const yamlStr = fs.readFileSync(configPath, "utf8"); return yamlStr; } return null;}; export const writeConfig = (appName: string, traefikConfig: string) => { try { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); fs.writeFileSync(configPath, traefikConfig, "utf8"); } catch (e) { console.error("Error saving the YAML config file:", e); }}; export const writeConfigRemote = async ( serverId: string, appName: string, traefikConfig: string,) => { try { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); const encoded = encodeBase64(traefikConfig); await execAsyncRemote( serverId, `echo "${encoded}" | base64 -d > "${configPath}"`, ); } catch (e) { console.error("Error saving the YAML config file:", e); }}; export const writeTraefikConfigInPath = async ( pathFile: string, traefikConfig: string, serverId?: string,) => { try { const configPath = path.join(pathFile); if (serverId) { const encoded = encodeBase64(traefikConfig); await execAsyncRemote( serverId, `echo "${encoded}" | base64 -d > "${configPath}"`, ); } else { fs.writeFileSync(configPath, traefikConfig, "utf8"); } } catch (e) { console.error("Error saving the YAML config file:", e); }}; export const writeTraefikConfig = ( traefikConfig: FileConfig, appName: string,) => { try { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); const yamlStr = stringify(traefikConfig); fs.writeFileSync(configPath, yamlStr, "utf8"); } catch (e) { console.error("Error saving the YAML config file:", e); }}; export const writeTraefikConfigRemote = async ( traefikConfig: FileConfig, appName: string, serverId: string,) => { try { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); const yamlStr = stringify(traefikConfig); await execAsyncRemote(serverId, `echo '${yamlStr}' > ${configPath}`); } catch (e) { console.error("Error saving the YAML config file:", e); }}; export const createServiceConfig = ( appName: string, domain: Domain,): { loadBalancer: HttpLoadBalancerService;} => ({ loadBalancer: { servers: [{ url: `http://${appName}:${domain.port || 80}` }], passHostHeader: true, },});
import fs, { createReadStream, writeFileSync } from "node:fs";
import path from "node:path";
import { createInterface } from "node:readline";
import { paths } from "@dokploy/server/constants";
import type { Domain } from "@dokploy/server/services/domain";
import { parse, stringify } from "yaml";
import { encodeBase64 } from "../docker/utils";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import type { FileConfig, HttpLoadBalancerService } from "./file-types";
export const createTraefikConfig = (appName: string) => {
const defaultPort = 3000;
const serviceURLDefault = `http://${appName}:${defaultPort}`;
const domainDefault = `Host(\`${appName}.docker.localhost\`)`;
const config: FileConfig = {
http: {
routers: {
...(process.env.NODE_ENV === "production"
? {}
: {
[`${appName}-router-1`]: {
rule: domainDefault,
service: `${appName}-service-1`,
entryPoints: ["web"],
},
}),
},
services: {
...(process.env.NODE_ENV === "production"
? {}
: {
[`${appName}-service-1`]: {
loadBalancer: {
servers: [{ url: serviceURLDefault }],
passHostHeader: true,
},
},
}),
},
},
};
const yamlStr = stringify(config);
const { DYNAMIC_TRAEFIK_PATH } = paths();
fs.mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
writeFileSync(
path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`),
yamlStr,
"utf8",
);
};
export const removeTraefikConfig = async (
appName: string,
serverId?: string | null,
) => {
try {
const { DYNAMIC_TRAEFIK_PATH } = paths(!!serverId);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
const command = `rm -f ${configPath}`;
if (serverId) {
await execAsyncRemote(serverId, command);
} else {
await execAsync(command);
}
} catch (error) {
console.error(`Error removing traefik config for ${appName}:`, error);
}
};
export const removeTraefikConfigRemote = async (
appName: string,
serverId: string,
) => {
try {
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
await execAsyncRemote(serverId, `rm -f ${configPath}`);
} catch (error) {
console.error(
`Error removing remote traefik config for ${appName}:`,
error,
);
}
};
export const loadOrCreateConfig = (appName: string): FileConfig => {
const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
if (fs.existsSync(configPath)) {
const yamlStr = fs.readFileSync(configPath, "utf8");
const parsedConfig = (parse(yamlStr) as FileConfig) || {
http: { routers: {}, services: {} },
};
return parsedConfig;
}
return { http: { routers: {}, services: {} } };
};
export const loadOrCreateConfigRemote = async (
serverId: string,
appName: string,
) => {
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
const fileConfig: FileConfig = { http: { routers: {}, services: {} } };
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
try {
const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
if (!stdout) return fileConfig;
const parsedConfig = (parse(stdout) as FileConfig) || {
http: { routers: {}, services: {} },
};
return parsedConfig;
} catch {
return fileConfig;
}
};
export const readConfig = (appName: string) => {
const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
if (fs.existsSync(configPath)) {
const yamlStr = fs.readFileSync(configPath, "utf8");
return yamlStr;
}
return null;
};
export const readRemoteConfig = async (serverId: string, appName: string) => {
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
try {
const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
if (!stdout) return null;
return stdout;
} catch {
return null;
}
};
export const readMonitoringConfig = async (readAll = false) => {
const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, "access.log");
if (fs.existsSync(configPath)) {
if (!readAll) {
// Read first 500 lines using streams
let content = "";
let validCount = 0;
const fileStream = createReadStream(configPath, { encoding: "utf8" });
const readline = createInterface({
input: fileStream,
crlfDelay: Number.POSITIVE_INFINITY,
});
for await (const line of readline) {
try {
const trimmed = line.trim();
if (
trimmed !== "" &&
trimmed.startsWith("{") &&
trimmed.endsWith("}")
) {
const log = JSON.parse(trimmed);
// Exclude Dokploy service app and Dashboard requests
if (log.ServiceName !== "dokploy-service-app@file") {
content += `${line}\n`;
validCount++;
if (validCount >= 500) {
break;
}
}
}
} catch {
// Ignore invalid JSON
}
}
return content;
}
return fs.readFileSync(configPath, "utf8");
}
return null;
};
export const readConfigInPath = async (pathFile: string, serverId?: string) => {
const configPath = path.join(pathFile);
if (serverId) {
const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
if (!stdout) return null;
return stdout;
}
if (fs.existsSync(configPath)) {
const yamlStr = fs.readFileSync(configPath, "utf8");
return yamlStr;
}
return null;
};
export const writeConfig = (appName: string, traefikConfig: string) => {
try {
const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
fs.writeFileSync(configPath, traefikConfig, "utf8");
} catch (e) {
console.error("Error saving the YAML config file:", e);
}
};
export const writeConfigRemote = async (
serverId: string,
appName: string,
traefikConfig: string,
) => {
try {
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
const encoded = encodeBase64(traefikConfig);
await execAsyncRemote(
serverId,
`echo "${encoded}" | base64 -d > "${configPath}"`,
);
} catch (e) {
console.error("Error saving the YAML config file:", e);
}
};
export const writeTraefikConfigInPath = async (
pathFile: string,
traefikConfig: string,
serverId?: string,
) => {
try {
const configPath = path.join(pathFile);
if (serverId) {
const encoded = encodeBase64(traefikConfig);
await execAsyncRemote(
serverId,
`echo "${encoded}" | base64 -d > "${configPath}"`,
);
} else {
fs.writeFileSync(configPath, traefikConfig, "utf8");
}
} catch (e) {
console.error("Error saving the YAML config file:", e);
}
};
export const writeTraefikConfig = (
traefikConfig: FileConfig,
appName: string,
) => {
try {
const { DYNAMIC_TRAEFIK_PATH } = paths();
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
const yamlStr = stringify(traefikConfig);
fs.writeFileSync(configPath, yamlStr, "utf8");
} catch (e) {
console.error("Error saving the YAML config file:", e);
}
};
export const writeTraefikConfigRemote = async (
traefikConfig: FileConfig,
appName: string,
serverId: string,
) => {
try {
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
const yamlStr = stringify(traefikConfig);
await execAsyncRemote(serverId, `echo '${yamlStr}' > ${configPath}`);
} catch (e) {
console.error("Error saving the YAML config file:", e);
}
};
export const createServiceConfig = (
appName: string,
domain: Domain,
): {
loadBalancer: HttpLoadBalancerService;
} => ({
loadBalancer: {
servers: [{ url: `http://${appName}:${domain.port || 80}` }],
passHostHeader: true,
},
});