diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx index 13a7703b3..c24d87812 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/show-volumes.tsx @@ -7,7 +7,7 @@ import { CardTitle, } from "@/components/ui/card"; import { api } from "@/utils/api"; -import { AlertTriangle, Package } from "lucide-react"; +import { Package } from "lucide-react"; import React from "react"; import { AddVolumes } from "./add-volumes"; import { DeleteVolume } from "./delete-volume"; diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 1c645139b..67e670995 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -23,11 +23,7 @@ import { type DeploymentJob, cleanQueuesByApplication, } from "@/server/queues/deployments-queue"; -import { - enqueueDeploymentJob, - myQueue, - redisConfig, -} from "@/server/queues/queueSetup"; +import { enqueueDeploymentJob, myQueue } from "@/server/queues/queueSetup"; import { removeService, startService, @@ -39,7 +35,7 @@ import { } from "@/server/utils/filesystem/directory"; import { readConfig, - readConfigInServer, + readRemoteConfig, removeTraefikConfig, writeConfig, } from "@/server/utils/traefik/application"; @@ -338,7 +334,7 @@ export const applicationRouter = createTRPCRouter({ const application = await findApplicationById(input.applicationId); let traefikConfig = null; if (application.serverId) { - traefikConfig = await readConfigInServer( + traefikConfig = await readRemoteConfig( application.serverId, application.appName, ); diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts index f3d308810..1487bd79c 100644 --- a/apps/dokploy/server/api/routers/domain.ts +++ b/apps/dokploy/server/api/routers/domain.ts @@ -71,8 +71,10 @@ export const domainRouter = createTRPCRouter({ .mutation(async ({ input }) => { const domain = await findDomainById(input.domainId); const result = await removeDomainById(input.domainId); - if (domain.application) { - await removeDomain(domain.application.appName, domain.uniqueConfigKey); + + if (domain.applicationId) { + const application = await findApplicationById(domain.applicationId); + await removeDomain(application, domain.uniqueConfigKey); } return result; diff --git a/apps/dokploy/server/api/services/docker.ts b/apps/dokploy/server/api/services/docker.ts index 7263ed67a..1554846a2 100644 --- a/apps/dokploy/server/api/services/docker.ts +++ b/apps/dokploy/server/api/services/docker.ts @@ -1,5 +1,5 @@ import { readSSHKey } from "@/server/utils/filesystem/ssh"; -import { execAsync } from "@/server/utils/process/execAsync"; +import { execAsync, execAsyncRemote } from "@/server/utils/process/execAsync"; import { tail } from "lodash"; import { stderr, stdout } from "node:process"; import { Client } from "ssh2"; @@ -86,40 +86,14 @@ export const getContainersByAppNameMatch = async ( ? `${cmd} --filter='label=com.docker.compose.project=${appName}'` : `${cmd} | grep ${appName}`; if (serverId) { - const server = await findServerById(serverId); + const { stdout, stderr } = await execAsyncRemote(serverId, command); - if (!server.sshKeyId) return; - const keys = await readSSHKey(server.sshKeyId); - const client = new Client(); - result = await new Promise((resolve, reject) => { - let output = ""; - client - .on("ready", () => { - client.exec(command, (err, stream) => { - if (err) { - console.error("Execution error:", err); - reject(err); - return; - } - stream - .on("close", () => { - client.end(); - resolve(output.trim().split("\n")); - }) - .on("data", (data: string) => { - output += data.toString(); - }) - .stderr.on("data", (data) => {}); - }); - }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: keys.privateKey, - timeout: 99999, - }); - }); + if (stderr) { + return []; + } + + if (!stdout) return []; + result = stdout.trim().split("\n"); } else { const { stdout, stderr } = await execAsync(command); diff --git a/apps/dokploy/server/api/services/mount.ts b/apps/dokploy/server/api/services/mount.ts index d8270498d..083121faa 100644 --- a/apps/dokploy/server/api/services/mount.ts +++ b/apps/dokploy/server/api/services/mount.ts @@ -1,5 +1,4 @@ -import { rmdir, stat, unlink } from "node:fs/promises"; -import path, { join } from "node:path"; +import path from "node:path"; import { APPLICATIONS_PATH, COMPOSE_PATH } from "@/server/constants"; import { db } from "@/server/db"; import { @@ -7,10 +6,11 @@ import { type apiCreateMount, mounts, } from "@/server/db/schema"; -import { createFile } from "@/server/utils/docker/utils"; +import { createFile, getCreateFileCommand } from "@/server/utils/docker/utils"; import { removeFileOrDirectory } from "@/server/utils/filesystem/directory"; import { TRPCError } from "@trpc/server"; import { type SQL, eq, sql } from "drizzle-orm"; +import { execAsyncRemote } from "@/server/utils/process/execAsync"; export type Mount = typeof mounts.$inferSelect; @@ -71,7 +71,19 @@ export const createFileMount = async (mountId: string) => { try { const mount = await findMountById(mountId); const baseFilePath = await getBaseFilesPath(mountId); - await createFile(baseFilePath, mount.filePath || "", mount.content || ""); + + const serverId = await getServerId(mount); + + if (serverId) { + const command = getCreateFileCommand( + baseFilePath, + mount.filePath || "", + mount.content || "", + ); + await execAsyncRemote(serverId, command); + } else { + await createFile(baseFilePath, mount.filePath || "", mount.content || ""); + } } catch (error) { console.log(`Error to create the file mount: ${error}`); throw new TRPCError({ @@ -186,9 +198,16 @@ export const deleteFileMount = async (mountId: string) => { const mount = await findMountById(mountId); if (!mount.filePath) return; const basePath = await getBaseFilesPath(mountId); + const fullPath = path.join(basePath, mount.filePath); try { - await removeFileOrDirectory(fullPath); + const serverId = await getServerId(mount); + if (serverId) { + const command = `rm -rf ${fullPath}`; + await execAsyncRemote(serverId, command); + } else { + await removeFileOrDirectory(fullPath); + } } catch (error) {} }; @@ -219,3 +238,30 @@ export const getBaseFilesPath = async (mountId: string) => { return directoryPath; }; + +type MountNested = Awaited>; +export const getServerId = async (mount: MountNested) => { + if (mount.serviceType === "application" && mount?.application?.serverId) { + return mount.application.serverId; + } + if (mount.serviceType === "postgres" && mount?.postgres?.serverId) { + return mount.postgres.serverId; + } + if (mount.serviceType === "mariadb" && mount?.mariadb?.serverId) { + return mount.mariadb.serverId; + } + if (mount.serviceType === "mongo" && mount?.mongo?.serverId) { + return mount.mongo.serverId; + } + if (mount.serviceType === "mysql" && mount?.mysql?.serverId) { + return mount.mysql.serverId; + } + if (mount.serviceType === "redis" && mount?.redis?.serverId) { + return mount.redis.serverId; + } + if (mount.serviceType === "compose" && mount?.compose?.serverId) { + return mount.compose.serverId; + } + + return null; +}; diff --git a/apps/dokploy/server/api/services/redirect.ts b/apps/dokploy/server/api/services/redirect.ts index 476023a7d..972603f2e 100644 --- a/apps/dokploy/server/api/services/redirect.ts +++ b/apps/dokploy/server/api/services/redirect.ts @@ -46,7 +46,7 @@ export const createRedirect = async ( const application = await findApplicationById(redirect.applicationId); - createRedirectMiddleware(application.appName, redirect); + createRedirectMiddleware(application, redirect); }); return true; @@ -77,7 +77,7 @@ export const removeRedirectById = async (redirectId: string) => { const application = await findApplicationById(response.applicationId); - removeRedirectMiddleware(application.appName, response); + await removeRedirectMiddleware(application, response); return response; } catch (error) { @@ -111,7 +111,7 @@ export const updateRedirectById = async ( } const application = await findApplicationById(redirect.applicationId); - updateRedirectMiddleware(application.appName, redirect); + await updateRedirectMiddleware(application, redirect); return redirect; } catch (error) { diff --git a/apps/dokploy/server/api/services/security.ts b/apps/dokploy/server/api/services/security.ts index ed7803a0d..2da78e266 100644 --- a/apps/dokploy/server/api/services/security.ts +++ b/apps/dokploy/server/api/services/security.ts @@ -44,7 +44,7 @@ export const createSecurity = async ( message: "Error to create the security", }); } - await createSecurityMiddleware(application.appName, securityResponse); + await createSecurityMiddleware(application, securityResponse); return true; }); } catch (error) { @@ -74,7 +74,7 @@ export const deleteSecurityById = async (securityId: string) => { const application = await findApplicationById(result.applicationId); - removeSecurityMiddleware(application.appName, result); + await removeSecurityMiddleware(application, result); return result; } catch (error) { throw new TRPCError({ diff --git a/apps/dokploy/server/utils/docker/utils.ts b/apps/dokploy/server/utils/docker/utils.ts index 2ca135727..20a34859f 100644 --- a/apps/dokploy/server/utils/docker/utils.ts +++ b/apps/dokploy/server/utils/docker/utils.ts @@ -393,6 +393,24 @@ export const createFile = async ( } }; +export const getCreateFileCommand = ( + outputPath: string, + filePath: string, + content: string, +) => { + const fullPath = path.join(outputPath, filePath); + if (fullPath.endsWith(path.sep) || filePath.endsWith("/")) { + return `mkdir -p ${fullPath};`; + } + + const directory = path.dirname(fullPath); + + return ` + mkdir -p ${directory}; + echo "${content}" > ${fullPath}; + `; +}; + export const getServiceContainer = async (appName: string) => { try { const filter = { diff --git a/apps/dokploy/server/utils/process/execAsync.ts b/apps/dokploy/server/utils/process/execAsync.ts index 35b903e7b..eb9dfa293 100644 --- a/apps/dokploy/server/utils/process/execAsync.ts +++ b/apps/dokploy/server/utils/process/execAsync.ts @@ -1,3 +1,39 @@ import { exec } from "node:child_process"; import util from "node:util"; +import { connectSSH } from "../servers/connection"; export const execAsync = util.promisify(exec); + +export const execAsyncRemote = async ( + serverId: string, + command: string, +): Promise<{ stdout: string; stderr: string }> => { + const client = await connectSSH(serverId); + + return new Promise((resolve, reject) => { + client.exec(command, (err, stream) => { + if (err) { + client.end(); + return reject(err); + } + + let stdout = ""; + let stderr = ""; + + stream + .on("data", (data: string) => { + stdout += data.toString(); + }) + .on("close", (code, signal) => { + client.end(); + if (code === 0) { + resolve({ stdout, stderr }); + } else { + reject(new Error(`Command exited with code ${code}`)); + } + }) + .stderr.on("data", (data) => { + stderr += data.toString(); + }); + }); + }); +}; diff --git a/apps/dokploy/server/utils/servers/command.ts b/apps/dokploy/server/utils/servers/command.ts index 163e081c1..c89434be6 100644 --- a/apps/dokploy/server/utils/servers/command.ts +++ b/apps/dokploy/server/utils/servers/command.ts @@ -1,41 +1,10 @@ -import { findServerById } from "@/server/api/services/server"; -import { readSSHKey } from "../filesystem/ssh"; -import { Client } from "ssh2"; +import { execAsyncRemote } from "../process/execAsync"; export const executeCommand = async (serverId: string, command: string) => { - const server = await findServerById(serverId); - - if (!server.sshKeyId) return; - const keys = await readSSHKey(server.sshKeyId); - const client = new Client(); - return new Promise((resolve, reject) => { - client - .on("ready", () => { - client.exec(command, (err, stream) => { - if (err) { - console.error("Execution error:", err); - reject(err); - return; - } - stream - .on("close", (code, signal) => { - client.end(); - if (code === 0) { - resolve(); - } else { - reject(new Error(`Command exited with code ${code}`)); - } - }) - .on("data", (data: string) => {}) - .stderr.on("data", (data) => {}); - }); - }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: keys.privateKey, - timeout: 99999, - }); - }); + try { + await execAsyncRemote(serverId, command); + } catch (err) { + console.error("Execution error:", err); + throw err; + } }; diff --git a/apps/dokploy/server/utils/servers/connection.ts b/apps/dokploy/server/utils/servers/connection.ts new file mode 100644 index 000000000..1829ad645 --- /dev/null +++ b/apps/dokploy/server/utils/servers/connection.ts @@ -0,0 +1,24 @@ +import { findServerById } from "@/server/api/services/server"; +import { Client } from "ssh2"; +import { readSSHKey } from "../filesystem/ssh"; + +export const connectSSH = async (serverId: string) => { + const server = await findServerById(serverId); + if (!server.sshKeyId) throw new Error("No SSH key available for this server"); + + const keys = await readSSHKey(server.sshKeyId); + const client = new Client(); + + return new Promise((resolve, reject) => { + client + .on("ready", () => resolve(client)) + .on("error", reject) + .connect({ + host: server.ipAddress, + port: server.port, + username: server.username, + privateKey: keys.privateKey, + timeout: 99999, + }); + }); +}; diff --git a/apps/dokploy/server/utils/traefik/application.ts b/apps/dokploy/server/utils/traefik/application.ts index cabbf5c2d..5173be870 100644 --- a/apps/dokploy/server/utils/traefik/application.ts +++ b/apps/dokploy/server/utils/traefik/application.ts @@ -1,12 +1,10 @@ import fs, { writeFileSync } from "node:fs"; import path from "node:path"; import type { Domain } from "@/server/api/services/domain"; -import { DYNAMIC_TRAEFIK_PATH, MAIN_TRAEFIK_PATH } from "@/server/constants"; +import { DYNAMIC_TRAEFIK_PATH } from "@/server/constants"; import { dump, load } from "js-yaml"; import type { FileConfig, HttpLoadBalancerService } from "./file-types"; -import { findServerById } from "@/server/api/services/server"; -import { Client } from "ssh2"; -import { readSSHKey } from "../filesystem/ssh"; +import { execAsyncRemote } from "../process/execAsync"; export const createTraefikConfig = (appName: string) => { const defaultPort = 3000; @@ -58,6 +56,16 @@ export const removeTraefikConfig = async (appName: string) => { } catch (error) {} }; +export const removeTraefikConfigRemote = async ( + appName: string, + serverId: string, +) => { + try { + const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); + await execAsyncRemote(serverId, `rm ${configPath}`); + } catch (error) {} +}; + export const loadOrCreateConfig = (appName: string): FileConfig => { const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); if (fs.existsSync(configPath)) { @@ -74,56 +82,20 @@ export const loadOrCreateConfigRemote = async ( serverId: string, appName: string, ) => { - const server = await findServerById(serverId); - if (!server.sshKeyId) return { http: { routers: {}, services: {} } }; - - const keys = await readSSHKey(server.sshKeyId); - const client = new Client(); - let fileConfig: FileConfig; + const fileConfig: FileConfig = { http: { routers: {}, services: {} } }; const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); - return new Promise((resolve, reject) => { - client - .on("ready", () => { - client.exec(`cat ${configPath}`, (err, stream) => { - if (err) { - console.error("Execution error:", err); - return { http: { routers: {}, services: {} } }; - } - stream - .on("close", (code, signal) => { - client.end(); - if (code === 0) { - if (!fileConfig) { - fileConfig = { http: { routers: {}, services: {} } }; - } - resolve( - (load(fileConfig) as FileConfig) || { - http: { routers: {}, services: {} }, - }, - ); - } else { - console.log(fileConfig); + try { + const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); - resolve({ http: { routers: {}, services: {} } }); + if (!stdout) return fileConfig; - // reject(new Error(`Command exited with code ${code}`)); - } - }) - .on("data", (data: string) => { - console.log(data.toString()); - fileConfig = data.toString() as unknown as FileConfig; - }) - .stderr.on("data", (data) => {}); - }); - }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: keys.privateKey, - timeout: 99999, - }); - }); + const parsedConfig = (load(stdout) as FileConfig) || { + http: { routers: {}, services: {} }, + }; + return parsedConfig; + } catch (err) { + return fileConfig; + } }; export const readConfig = (appName: string) => { @@ -135,51 +107,15 @@ export const readConfig = (appName: string) => { return null; }; -export const readConfigInServer = async (serverId: string, appName: string) => { +export const readRemoteConfig = async (serverId: string, appName: string) => { const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); - let content = ""; - // if (fs.existsSync(configPath)) { - // const yamlStr = fs.readFileSync(configPath, "utf8"); - // return yamlStr; - // } - - const client = new Client(); - const server = await findServerById(serverId); - if (!server.sshKeyId) return; - const keys = await readSSHKey(server.sshKeyId); - return new Promise((resolve, reject) => { - client - .on("ready", () => { - const bashCommand = ` - cat ${configPath} - `; - - client.exec(bashCommand, (err, stream) => { - if (err) { - reject(err); - return; - } - stream - .on("close", () => { - client.end(); - resolve(content); - }) - .on("data", (data: string) => { - content = data.toString(); - }) - .stderr.on("data", (data) => { - reject(new Error(`stderr: ${data.toString()}`)); - }); - }); - }) - .connect({ - host: server.ipAddress, - port: server.port, - username: server.username, - privateKey: keys.privateKey, - timeout: 99999, - }); - }); + try { + const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); + if (!stdout) return null; + return stdout; + } catch (err) { + return null; + } }; export const readMonitoringConfig = () => { @@ -228,13 +164,26 @@ export const writeTraefikConfig = ( try { const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); const yamlStr = dump(traefikConfig); - console.log(yamlStr); 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 configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); + const yamlStr = dump(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, diff --git a/apps/dokploy/server/utils/traefik/domain.ts b/apps/dokploy/server/utils/traefik/domain.ts index c4ef77c91..684586fca 100644 --- a/apps/dokploy/server/utils/traefik/domain.ts +++ b/apps/dokploy/server/utils/traefik/domain.ts @@ -5,13 +5,11 @@ import { loadOrCreateConfig, loadOrCreateConfigRemote, removeTraefikConfig, + removeTraefikConfigRemote, writeTraefikConfig, + writeTraefikConfigRemote, } from "./application"; import type { FileConfig, HttpRouter } from "./file-types"; -import { DYNAMIC_TRAEFIK_PATH } from "@/server/constants"; -import path from "node:path"; -import { dump } from "js-yaml"; -import { executeCommand } from "../servers/command"; export const manageDomain = async (app: ApplicationNested, domain: Domain) => { const { appName } = app; @@ -49,23 +47,24 @@ export const manageDomain = async (app: ApplicationNested, domain: Domain) => { config.http.services[serviceName] = createServiceConfig(appName, domain); if (app.serverId) { - const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); - const yamlStr = dump(config); - - console.log(yamlStr); - - const command = ` - echo '${yamlStr}' > ${configPath} - `; - - await executeCommand(app.serverId, command); + await writeTraefikConfigRemote(config, appName, app.serverId); } else { writeTraefikConfig(config, appName); } }; -export const removeDomain = async (appName: string, uniqueKey: number) => { - const config: FileConfig = loadOrCreateConfig(appName); +export const removeDomain = async ( + application: ApplicationNested, + uniqueKey: number, +) => { + const { appName, serverId } = application; + let config: FileConfig; + + if (serverId) { + config = await loadOrCreateConfigRemote(serverId, appName); + } else { + config = loadOrCreateConfig(appName); + } const routerKey = `${appName}-router-${uniqueKey}`; const routerSecureKey = `${appName}-router-websecure-${uniqueKey}`; @@ -86,9 +85,17 @@ export const removeDomain = async (appName: string, uniqueKey: number) => { config?.http?.routers && Object.keys(config?.http?.routers).length === 0 ) { - await removeTraefikConfig(appName); + if (serverId) { + await removeTraefikConfigRemote(appName, serverId); + } else { + await removeTraefikConfig(appName); + } } else { - writeTraefikConfig(config, appName); + if (serverId) { + await writeTraefikConfigRemote(config, appName, serverId); + } else { + writeTraefikConfig(config, appName); + } } }; diff --git a/apps/dokploy/server/utils/traefik/middleware.ts b/apps/dokploy/server/utils/traefik/middleware.ts index 09c78a193..18c63931d 100644 --- a/apps/dokploy/server/utils/traefik/middleware.ts +++ b/apps/dokploy/server/utils/traefik/middleware.ts @@ -4,6 +4,7 @@ import { DYNAMIC_TRAEFIK_PATH } from "@/server/constants"; import { dump, load } from "js-yaml"; import type { ApplicationNested } from "../builders"; import type { FileConfig } from "./file-types"; +import { execAsyncRemote } from "../process/execAsync"; export const addMiddleware = (config: FileConfig, middlewareName: string) => { if (config.http?.routers) { @@ -72,6 +73,25 @@ export const loadMiddlewares = () => { return config; }; +export const loadRemoteMiddlewares = async (serverId: string) => { + 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 = load(stdout) as FileConfig; + return config; + } catch (error) { + throw new Error(`File not found: ${configPath}`); + } +}; export const writeMiddleware = (config: T) => { const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml"); const newYamlContent = dump(config); diff --git a/apps/dokploy/server/utils/traefik/redirect.ts b/apps/dokploy/server/utils/traefik/redirect.ts index 6e0922a61..7b5f2ca1c 100644 --- a/apps/dokploy/server/utils/traefik/redirect.ts +++ b/apps/dokploy/server/utils/traefik/redirect.ts @@ -1,15 +1,32 @@ import type { Redirect } from "@/server/api/services/redirect"; -import { loadOrCreateConfig, writeTraefikConfig } from "./application"; +import { + loadOrCreateConfig, + loadOrCreateConfigRemote, + writeTraefikConfig, + writeTraefikConfigRemote, +} from "./application"; import type { FileConfig } from "./file-types"; import { addMiddleware, deleteMiddleware, loadMiddlewares, + loadRemoteMiddlewares, writeMiddleware, } from "./middleware"; +import type { ApplicationNested } from "../builders"; -export const updateRedirectMiddleware = (appName: string, data: Redirect) => { - const config = loadMiddlewares(); +export const updateRedirectMiddleware = async ( + application: ApplicationNested, + data: Redirect, +) => { + const { appName, serverId } = application; + let config: FileConfig; + + if (serverId) { + config = await loadRemoteMiddlewares(serverId); + } else { + config = loadMiddlewares(); + } const middlewareName = `redirect-${appName}-${data.uniqueConfigKey}`; if (config?.http?.middlewares?.[middlewareName]) { @@ -22,10 +39,26 @@ export const updateRedirectMiddleware = (appName: string, data: Redirect) => { }; } - writeMiddleware(config); + if (serverId) { + await writeTraefikConfigRemote(config, "middlewares", serverId); + } else { + writeMiddleware(config); + } }; -export const createRedirectMiddleware = (appName: string, data: Redirect) => { - const config = loadMiddlewares(); +export const createRedirectMiddleware = async ( + application: ApplicationNested, + data: Redirect, +) => { + const { appName, serverId } = application; + + let config: FileConfig; + + if (serverId) { + config = await loadRemoteMiddlewares(serverId); + } else { + config = loadMiddlewares(); + } + const middlewareName = `redirect-${appName}-${data.uniqueConfigKey}`; const newMiddleware = { [middlewareName]: { @@ -44,25 +77,56 @@ export const createRedirectMiddleware = (appName: string, data: Redirect) => { }; } - const appConfig = loadOrCreateConfig(appName); + let appConfig: FileConfig; + + if (serverId) { + appConfig = await loadOrCreateConfigRemote(serverId, appName); + } else { + appConfig = loadOrCreateConfig(appName); + } addMiddleware(appConfig, middlewareName); - writeTraefikConfig(appConfig, appName); - writeMiddleware(config); + if (serverId) { + await writeTraefikConfigRemote(config, "middlewares", serverId); + await writeTraefikConfigRemote(appConfig, appName, serverId); + } else { + writeMiddleware(config); + writeTraefikConfig(appConfig, appName); + } }; -export const removeRedirectMiddleware = (appName: string, data: Redirect) => { - const config = loadMiddlewares(); +export const removeRedirectMiddleware = async ( + application: ApplicationNested, + data: Redirect, +) => { + const { appName, serverId } = application; + let config: FileConfig; + + if (serverId) { + config = await loadRemoteMiddlewares(serverId); + } else { + config = loadMiddlewares(); + } const middlewareName = `redirect-${appName}-${data.uniqueConfigKey}`; if (config?.http?.middlewares?.[middlewareName]) { delete config.http.middlewares[middlewareName]; } - - const appConfig = loadOrCreateConfig(appName); + let appConfig: FileConfig; + if (serverId) { + appConfig = await loadOrCreateConfigRemote(serverId, appName); + } else { + appConfig = loadOrCreateConfig(appName); + } deleteMiddleware(appConfig, middlewareName); - writeTraefikConfig(appConfig, appName); - writeMiddleware(config); + + if (serverId) { + await writeTraefikConfigRemote(config, "middlewares", serverId); + await writeTraefikConfigRemote(appConfig, appName, serverId); + } else { + writeTraefikConfig(appConfig, appName); + writeMiddleware(config); + } }; diff --git a/apps/dokploy/server/utils/traefik/security.ts b/apps/dokploy/server/utils/traefik/security.ts index 484151bcb..1d834694c 100644 --- a/apps/dokploy/server/utils/traefik/security.ts +++ b/apps/dokploy/server/utils/traefik/security.ts @@ -1,6 +1,11 @@ import type { Security } from "@/server/api/services/security"; import * as bcrypt from "bcrypt"; -import { loadOrCreateConfig, writeTraefikConfig } from "./application"; +import { + loadOrCreateConfig, + loadOrCreateConfigRemote, + writeTraefikConfig, + writeTraefikConfigRemote, +} from "./application"; import type { BasicAuthMiddleware, FileConfig, @@ -10,14 +15,23 @@ import { addMiddleware, deleteMiddleware, loadMiddlewares, + loadRemoteMiddlewares, writeMiddleware, } from "./middleware"; +import type { ApplicationNested } from "../builders"; export const createSecurityMiddleware = async ( - appName: string, + application: ApplicationNested, data: Security, ) => { - const config = loadMiddlewares(); + const { appName, serverId } = application; + let config: FileConfig; + + if (serverId) { + config = await loadRemoteMiddlewares(serverId); + } else { + config = loadMiddlewares(); + } const middlewareName = `auth-${appName}`; const user = `${data.username}:${await bcrypt.hash(data.password, 10)}`; @@ -38,17 +52,42 @@ export const createSecurityMiddleware = async ( }; } } + let appConfig: FileConfig; - const appConfig = loadOrCreateConfig(appName); - + if (serverId) { + appConfig = await loadOrCreateConfigRemote(serverId, appName); + } else { + appConfig = loadOrCreateConfig(appName); + } addMiddleware(appConfig, middlewareName); - writeTraefikConfig(appConfig, appName); - writeMiddleware(config); + if (serverId) { + await writeTraefikConfigRemote(config, "middlewares", serverId); + await writeTraefikConfigRemote(appConfig, appName, serverId); + } else { + writeTraefikConfig(appConfig, appName); + writeMiddleware(config); + } }; -export const removeSecurityMiddleware = (appName: string, data: Security) => { - const config = loadMiddlewares(); - const appConfig = loadOrCreateConfig(appName); +export const removeSecurityMiddleware = async ( + application: ApplicationNested, + data: Security, +) => { + const { appName, serverId } = application; + let config: FileConfig; + + if (serverId) { + config = await loadRemoteMiddlewares(serverId); + } else { + config = loadMiddlewares(); + } + let appConfig: FileConfig; + + if (serverId) { + appConfig = await loadOrCreateConfigRemote(serverId, appName); + } else { + appConfig = loadOrCreateConfig(appName); + } const middlewareName = `auth-${appName}`; if (config.http?.middlewares) { @@ -67,12 +106,20 @@ export const removeSecurityMiddleware = (appName: string, data: Security) => { delete config.http.middlewares[middlewareName]; } deleteMiddleware(appConfig, middlewareName); - writeTraefikConfig(appConfig, appName); + if (serverId) { + await writeTraefikConfigRemote(appConfig, appName, serverId); + } else { + writeTraefikConfig(appConfig, appName); + } } } } - writeMiddleware(config); + if (serverId) { + await writeTraefikConfigRemote(config, "middlewares", serverId); + } else { + writeMiddleware(config); + } }; const isBasicAuthMiddleware = (