import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import type { Domain } from "@dokploy/server/services/domain"; import { parse, stringify } from "yaml"; import type { ApplicationNested } from "../builders"; import { execAsyncRemote } from "../process/execAsync"; import { writeTraefikConfigRemote } from "./application"; import type { FileConfig } from "./file-types"; export const addMiddleware = (config: FileConfig, middlewareName: string) => { if (config.http?.routers) { const values = Object.keys(config.http.routers); for (const routerName of values) { const router = config.http.routers[routerName]; if (router) { if (!router.middlewares) { router.middlewares = []; } if (!router.middlewares.includes(middlewareName)) { router.middlewares.push(middlewareName); } } } } }; export const deleteMiddleware = ( config: FileConfig, middlewareName: string, ) => { if (config.http?.routers) { const values = Object.keys(config?.http?.routers); for (const routerName of values) { const router = config.http.routers[routerName]; if (router?.middlewares) { router.middlewares = router.middlewares.filter( (m) => m !== middlewareName, ); } } } }; export const deleteAllMiddlewares = async (application: ApplicationNested) => { const config = loadMiddlewares(); const { security, appName, redirects } = application; if (config.http?.middlewares) { if (security.length > 0) { const middlewareName = `auth-${appName}`; delete config.http.middlewares[middlewareName]; } for (const redirect of redirects) { const middlewareName = `redirect-${appName}-${redirect.uniqueConfigKey}`; delete config.http.middlewares[middlewareName]; } } if (application.serverId) { await writeTraefikConfigRemote(config, "middlewares", application.serverId); } else { writeMiddleware(config); } }; export const loadMiddlewares = () => { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml"); if (!existsSync(configPath)) { throw new Error(`File not found: ${configPath}`); } const yamlStr = readFileSync(configPath, "utf8"); const config = parse(yamlStr) as T; return config; }; export const loadRemoteMiddlewares = async (serverId: string) => { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml"); try { const { stdout, stderr } = await execAsyncRemote( serverId, `cat ${configPath}`, ); if (stderr) { console.error(`Error: ${stderr}`); throw new Error(`File not found: ${configPath}`); } const config = parse(stdout) as FileConfig; return config; } catch (_) { throw new Error(`File not found: ${configPath}`); } }; export const writeMiddleware = (config: FileConfig) => { const { DYNAMIC_TRAEFIK_PATH } = paths(); const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml"); const newYamlContent = stringify(config); writeFileSync(configPath, newYamlContent, "utf8"); }; export const createPathMiddlewares = async ( app: ApplicationNested, domain: Domain, ) => { const { appName } = app; const { uniqueConfigKey, internalPath, stripPath, path } = domain; // Early return if there's no path middleware to create const needsInternalPathMiddleware = internalPath && internalPath !== "/" && internalPath !== path; const needsStripPathMiddleware = stripPath && path && path !== "/"; if (!needsInternalPathMiddleware && !needsStripPathMiddleware) { return; } let config: FileConfig; if (app.serverId) { try { config = await loadRemoteMiddlewares(app.serverId); } catch { config = { http: { middlewares: {} } }; } } else { try { config = loadMiddlewares(); } catch { config = { http: { middlewares: {} } }; } } if (!config) { config = { http: { middlewares: {} } }; } else if (!config.http) { config.http = { middlewares: {} }; } if (!config.http?.middlewares) { config.http!.middlewares = {}; } // Add internal path prefix middleware if (internalPath && internalPath !== "/" && internalPath !== path) { const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`; config.http!.middlewares[middlewareName] = { addPrefix: { prefix: internalPath, }, }; } // Strip external path middleware if needed if (stripPath && path && path !== "/") { const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`; config.http!.middlewares[middlewareName] = { stripPrefix: { prefixes: [path], }, }; } if (app.serverId) { await writeTraefikConfigRemote(config, "middlewares", app.serverId); } else { writeMiddleware(config); } }; export const removePathMiddlewares = async ( app: ApplicationNested, uniqueConfigKey: number, ) => { let config: FileConfig; if (app.serverId) { try { config = await loadRemoteMiddlewares(app.serverId); } catch { return; } } else { try { config = loadMiddlewares(); } catch { return; } } if (!config) { return; } const { appName } = app; if (config.http?.middlewares) { const addPrefixMiddleware = `addprefix-${appName}-${uniqueConfigKey}`; const stripPrefixMiddleware = `stripprefix-${appName}-${uniqueConfigKey}`; delete config.http.middlewares[addPrefixMiddleware]; delete config.http.middlewares[stripPrefixMiddleware]; } if ( config?.http?.middlewares && Object.keys(config.http.middlewares).length === 0 ) { // if there aren't any middlewares, remove the whole section delete config.http.middlewares; } // // If http section is empty, remove it completely if (config?.http && Object.keys(config?.http).length === 0) { delete config.http; } if (config && Object.keys(config || {}).length === 0) { config = {}; } if (app.serverId) { await writeTraefikConfigRemote(config, "middlewares", app.serverId); } else { writeMiddleware(config); } };