mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add application and databases external servers
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string[]>((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);
|
||||
|
||||
|
||||
@@ -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<ReturnType<typeof findMountById>>;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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<void>((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;
|
||||
}
|
||||
};
|
||||
|
||||
24
apps/dokploy/server/utils/servers/connection.ts
Normal file
24
apps/dokploy/server/utils/servers/connection.ts
Normal file
@@ -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<Client>((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,
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -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<FileConfig>((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<string>((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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 = <T>() => {
|
||||
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 = <T>(config: T) => {
|
||||
const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
|
||||
const newYamlContent = dump(config);
|
||||
|
||||
@@ -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<FileConfig>();
|
||||
export const updateRedirectMiddleware = async (
|
||||
application: ApplicationNested,
|
||||
data: Redirect,
|
||||
) => {
|
||||
const { appName, serverId } = application;
|
||||
let config: FileConfig;
|
||||
|
||||
if (serverId) {
|
||||
config = await loadRemoteMiddlewares(serverId);
|
||||
} else {
|
||||
config = loadMiddlewares<FileConfig>();
|
||||
}
|
||||
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<FileConfig>();
|
||||
export const createRedirectMiddleware = async (
|
||||
application: ApplicationNested,
|
||||
data: Redirect,
|
||||
) => {
|
||||
const { appName, serverId } = application;
|
||||
|
||||
let config: FileConfig;
|
||||
|
||||
if (serverId) {
|
||||
config = await loadRemoteMiddlewares(serverId);
|
||||
} else {
|
||||
config = loadMiddlewares<FileConfig>();
|
||||
}
|
||||
|
||||
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<FileConfig>();
|
||||
export const removeRedirectMiddleware = async (
|
||||
application: ApplicationNested,
|
||||
data: Redirect,
|
||||
) => {
|
||||
const { appName, serverId } = application;
|
||||
let config: FileConfig;
|
||||
|
||||
if (serverId) {
|
||||
config = await loadRemoteMiddlewares(serverId);
|
||||
} else {
|
||||
config = loadMiddlewares<FileConfig>();
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<FileConfig>();
|
||||
const { appName, serverId } = application;
|
||||
let config: FileConfig;
|
||||
|
||||
if (serverId) {
|
||||
config = await loadRemoteMiddlewares(serverId);
|
||||
} else {
|
||||
config = loadMiddlewares<FileConfig>();
|
||||
}
|
||||
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<FileConfig>();
|
||||
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<FileConfig>();
|
||||
}
|
||||
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 = (
|
||||
|
||||
Reference in New Issue
Block a user