mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: initial commit
This commit is contained in:
132
server/utils/traefik/application.ts
Normal file
132
server/utils/traefik/application.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import fs, { writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { Domain } from "@/server/api/services/domain";
|
||||
import { DYNAMIC_TRAEFIK_PATH } from "@/server/constants";
|
||||
import { dump, load } from "js-yaml";
|
||||
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 = dump(config);
|
||||
fs.mkdirSync(DYNAMIC_TRAEFIK_PATH, { recursive: true });
|
||||
writeFileSync(
|
||||
path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`),
|
||||
yamlStr,
|
||||
"utf8",
|
||||
);
|
||||
};
|
||||
|
||||
export const removeTraefikConfig = async (appName: string) => {
|
||||
try {
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
if (fs.existsSync(configPath)) {
|
||||
await fs.promises.unlink(configPath);
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
export const loadOrCreateConfig = (appName: string): FileConfig => {
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
if (fs.existsSync(configPath)) {
|
||||
const yamlStr = fs.readFileSync(configPath, "utf8");
|
||||
const parsedConfig = (load(yamlStr) as FileConfig) || {
|
||||
http: { routers: {}, services: {} },
|
||||
};
|
||||
return parsedConfig;
|
||||
}
|
||||
return { http: { routers: {}, services: {} } };
|
||||
};
|
||||
|
||||
export const readConfig = (appName: string) => {
|
||||
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 readConfigInPath = (pathFile: string) => {
|
||||
const configPath = path.join(pathFile);
|
||||
if (fs.existsSync(configPath)) {
|
||||
const yamlStr = fs.readFileSync(configPath, "utf8");
|
||||
return yamlStr;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const writeConfig = (appName: string, traefikConfig: string) => {
|
||||
try {
|
||||
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 writeTraefikConfigInPath = (
|
||||
pathFile: string,
|
||||
traefikConfig: string,
|
||||
) => {
|
||||
try {
|
||||
const configPath = path.join(pathFile);
|
||||
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 configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
|
||||
const yamlStr = dump(traefikConfig);
|
||||
fs.writeFileSync(configPath, yamlStr, "utf8");
|
||||
} 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,
|
||||
},
|
||||
});
|
||||
82
server/utils/traefik/domain.ts
Normal file
82
server/utils/traefik/domain.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import {
|
||||
createServiceConfig,
|
||||
loadOrCreateConfig,
|
||||
writeTraefikConfig,
|
||||
} from "./application";
|
||||
import type { ApplicationNested } from "../builders";
|
||||
import type { Domain } from "@/server/api/services/domain";
|
||||
import type { FileConfig, HttpRouter } from "./file-types";
|
||||
|
||||
export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
|
||||
const { appName } = app;
|
||||
const config: FileConfig = loadOrCreateConfig(appName);
|
||||
const serviceName = `${appName}-service-${domain.uniqueConfigKey}`;
|
||||
const routerName = `${appName}-router-${domain.uniqueConfigKey}`;
|
||||
|
||||
config.http = config.http || { routers: {}, services: {} };
|
||||
config.http.routers = config.http.routers || {};
|
||||
config.http.services = config.http.services || {};
|
||||
|
||||
config.http.routers[routerName] = await createRouterConfig(app, domain);
|
||||
|
||||
config.http.services[serviceName] = createServiceConfig(appName, domain);
|
||||
writeTraefikConfig(config, appName);
|
||||
};
|
||||
|
||||
export const removeDomain = (appName: string, uniqueKey: number) => {
|
||||
const config: FileConfig = loadOrCreateConfig(appName);
|
||||
|
||||
const routerKey = `${appName}-router-${uniqueKey}`;
|
||||
const serviceKey = `${appName}-service-${uniqueKey}`;
|
||||
if (config.http?.routers?.[routerKey]) {
|
||||
delete config.http.routers[routerKey];
|
||||
}
|
||||
if (config.http?.services?.[serviceKey]) {
|
||||
delete config.http.services[serviceKey];
|
||||
}
|
||||
|
||||
writeTraefikConfig(config, appName);
|
||||
};
|
||||
|
||||
export const createRouterConfig = async (
|
||||
app: ApplicationNested,
|
||||
domain: Domain,
|
||||
) => {
|
||||
const { appName, redirects, security } = app;
|
||||
const { certificateType } = domain;
|
||||
|
||||
const { host, path, https, uniqueConfigKey } = domain;
|
||||
const routerConfig: HttpRouter = {
|
||||
rule: `Host(\`${host}\`)${path ? ` && PathPrefix(\`${path}\`)` : ""}`,
|
||||
service: `${appName}-service-${uniqueConfigKey}`,
|
||||
middlewares: [],
|
||||
entryPoints: https
|
||||
? ["web", ...(process.env.NODE_ENV === "production" ? ["websecure"] : [])]
|
||||
: ["web"],
|
||||
tls: {},
|
||||
};
|
||||
|
||||
if (https) {
|
||||
routerConfig.middlewares = ["redirect-to-https"];
|
||||
}
|
||||
|
||||
// redirects
|
||||
for (const redirect of redirects) {
|
||||
const middlewareName = `redirect-${appName}-${redirect.uniqueConfigKey}`;
|
||||
routerConfig.middlewares?.push(middlewareName);
|
||||
}
|
||||
|
||||
// security
|
||||
if (security.length > 0) {
|
||||
const middlewareName = `auth-${appName}`;
|
||||
routerConfig.middlewares?.push(middlewareName);
|
||||
}
|
||||
|
||||
if (certificateType === "letsencrypt") {
|
||||
routerConfig.tls = { certResolver: "letsencrypt" };
|
||||
} else if (certificateType === "none") {
|
||||
routerConfig.tls = undefined;
|
||||
}
|
||||
|
||||
return routerConfig;
|
||||
};
|
||||
1274
server/utils/traefik/file-types.ts
Normal file
1274
server/utils/traefik/file-types.ts
Normal file
File diff suppressed because it is too large
Load Diff
79
server/utils/traefik/middleware.ts
Normal file
79
server/utils/traefik/middleware.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { dump, load } from "js-yaml";
|
||||
import { join } from "node:path";
|
||||
import { DYNAMIC_TRAEFIK_PATH } from "@/server/constants";
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import type { ApplicationNested } from "../builders";
|
||||
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 = (application: ApplicationNested) => {
|
||||
const config = loadMiddlewares<FileConfig>();
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
writeMiddleware(config);
|
||||
};
|
||||
|
||||
export const loadMiddlewares = <T>() => {
|
||||
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 = load(yamlStr) as T;
|
||||
return config;
|
||||
};
|
||||
|
||||
export const writeMiddleware = <T>(config: T) => {
|
||||
const configPath = join(DYNAMIC_TRAEFIK_PATH, "middlewares.yml");
|
||||
const newYamlContent = dump(config);
|
||||
writeFileSync(configPath, newYamlContent, "utf8");
|
||||
};
|
||||
69
server/utils/traefik/redirect.ts
Normal file
69
server/utils/traefik/redirect.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { Redirect } from "@/server/api/services/redirect";
|
||||
import { loadOrCreateConfig, writeTraefikConfig } from "./application";
|
||||
import {
|
||||
addMiddleware,
|
||||
deleteMiddleware,
|
||||
loadMiddlewares,
|
||||
writeMiddleware,
|
||||
} from "./middleware";
|
||||
import type { FileConfig } from "./file-types";
|
||||
|
||||
export const updateRedirectMiddleware = (appName: string, data: Redirect) => {
|
||||
const config = loadMiddlewares<FileConfig>();
|
||||
|
||||
if (config?.http?.middlewares?.[appName]) {
|
||||
const middlewareName = `${appName}-${data.uniqueConfigKey}`;
|
||||
|
||||
config.http.middlewares[middlewareName] = {
|
||||
redirectRegex: {
|
||||
regex: data.regex,
|
||||
replacement: data.replacement,
|
||||
permanent: data.permanent,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
writeMiddleware(config);
|
||||
};
|
||||
export const createRedirectMiddleware = (appName: string, data: Redirect) => {
|
||||
const config = loadMiddlewares<FileConfig>();
|
||||
const middlewareName = `redirect-${appName}-${data.uniqueConfigKey}`;
|
||||
const newMiddleware = {
|
||||
[middlewareName]: {
|
||||
redirectRegex: {
|
||||
regex: data.regex,
|
||||
replacement: data.replacement,
|
||||
permanent: data.permanent,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (config?.http) {
|
||||
config.http.middlewares = {
|
||||
...config.http.middlewares,
|
||||
...newMiddleware,
|
||||
};
|
||||
}
|
||||
|
||||
const appConfig = loadOrCreateConfig(appName);
|
||||
|
||||
addMiddleware(appConfig, middlewareName);
|
||||
|
||||
writeTraefikConfig(appConfig, appName);
|
||||
writeMiddleware(config);
|
||||
};
|
||||
|
||||
export const removeRedirectMiddleware = (appName: string, data: Redirect) => {
|
||||
const config = loadMiddlewares<FileConfig>();
|
||||
const middlewareName = `redirect-${appName}-${data.uniqueConfigKey}`;
|
||||
|
||||
if (config?.http?.middlewares?.[middlewareName]) {
|
||||
delete config.http.middlewares[middlewareName];
|
||||
}
|
||||
|
||||
const appConfig = loadOrCreateConfig(appName);
|
||||
|
||||
deleteMiddleware(appConfig, middlewareName);
|
||||
writeTraefikConfig(appConfig, appName);
|
||||
writeMiddleware(config);
|
||||
};
|
||||
82
server/utils/traefik/security.ts
Normal file
82
server/utils/traefik/security.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { loadOrCreateConfig, writeTraefikConfig } from "./application";
|
||||
import type { Security } from "@/server/api/services/security";
|
||||
import * as bcrypt from "bcrypt";
|
||||
import {
|
||||
addMiddleware,
|
||||
deleteMiddleware,
|
||||
loadMiddlewares,
|
||||
writeMiddleware,
|
||||
} from "./middleware";
|
||||
import type {
|
||||
BasicAuthMiddleware,
|
||||
FileConfig,
|
||||
HttpMiddleware,
|
||||
} from "./file-types";
|
||||
|
||||
export const createSecurityMiddleware = async (
|
||||
appName: string,
|
||||
data: Security,
|
||||
) => {
|
||||
const config = loadMiddlewares<FileConfig>();
|
||||
const middlewareName = `auth-${appName}`;
|
||||
|
||||
const user = `${data.username}:${await bcrypt.hash(data.password, 10)}`;
|
||||
|
||||
if (config.http?.middlewares) {
|
||||
const currentMiddleware = config.http.middlewares[middlewareName];
|
||||
if (isBasicAuthMiddleware(currentMiddleware)) {
|
||||
currentMiddleware.basicAuth.users = [
|
||||
...(currentMiddleware.basicAuth.users || []),
|
||||
user,
|
||||
];
|
||||
} else {
|
||||
config.http.middlewares[middlewareName] = {
|
||||
basicAuth: {
|
||||
removeHeader: true,
|
||||
users: [user],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const appConfig = loadOrCreateConfig(appName);
|
||||
|
||||
addMiddleware(appConfig, middlewareName);
|
||||
writeTraefikConfig(appConfig, appName);
|
||||
writeMiddleware(config);
|
||||
};
|
||||
|
||||
export const removeSecurityMiddleware = (appName: string, data: Security) => {
|
||||
const config = loadMiddlewares<FileConfig>();
|
||||
const appConfig = loadOrCreateConfig(appName);
|
||||
const middlewareName = `auth-${appName}`;
|
||||
|
||||
if (config.http?.middlewares) {
|
||||
const currentMiddleware = config.http.middlewares[middlewareName];
|
||||
if (isBasicAuthMiddleware(currentMiddleware)) {
|
||||
const users = currentMiddleware.basicAuth.users;
|
||||
const filteredUsers =
|
||||
users?.filter((user) => {
|
||||
const [username] = user.split(":");
|
||||
return username !== data.username;
|
||||
}) || [];
|
||||
currentMiddleware.basicAuth.users = filteredUsers;
|
||||
|
||||
if (filteredUsers.length === 0) {
|
||||
if (config?.http?.middlewares?.[middlewareName]) {
|
||||
delete config.http.middlewares[middlewareName];
|
||||
}
|
||||
deleteMiddleware(appConfig, middlewareName);
|
||||
writeTraefikConfig(appConfig, appName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeMiddleware(config);
|
||||
};
|
||||
|
||||
const isBasicAuthMiddleware = (
|
||||
middleware: HttpMiddleware | undefined,
|
||||
): middleware is { basicAuth: BasicAuthMiddleware } => {
|
||||
return !!middleware && "basicAuth" in middleware;
|
||||
};
|
||||
574
server/utils/traefik/types.ts
Normal file
574
server/utils/traefik/types.ts
Normal file
@@ -0,0 +1,574 @@
|
||||
/* eslint-disable */
|
||||
|
||||
export interface MainTraefikConfig {
|
||||
accessLog?: {
|
||||
filePath?: string;
|
||||
format?: string;
|
||||
filters?: {
|
||||
statusCodes?: string[];
|
||||
retryAttempts?: boolean;
|
||||
minDuration?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
fields?: {
|
||||
defaultMode?: string;
|
||||
names?: {
|
||||
/**
|
||||
* This interface was referenced by `undefined`'s JSON-Schema definition
|
||||
* via the `patternProperty` "[a-zA-Z0-9-_]+".
|
||||
*/
|
||||
[k: string]: string;
|
||||
};
|
||||
headers?: {
|
||||
defaultMode?: string;
|
||||
names?: {
|
||||
/**
|
||||
* This interface was referenced by `undefined`'s JSON-Schema definition
|
||||
* via the `patternProperty` "[a-zA-Z0-9-_]+".
|
||||
*/
|
||||
[k: string]: string;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
bufferingSize?: number;
|
||||
};
|
||||
api?: {
|
||||
insecure?: boolean;
|
||||
dashboard?: boolean;
|
||||
debug?: boolean;
|
||||
};
|
||||
certificatesResolvers?: {
|
||||
/**
|
||||
* This interface was referenced by `undefined`'s JSON-Schema definition
|
||||
* via the `patternProperty` "[a-zA-Z0-9-_]+".
|
||||
*/
|
||||
[k: string]: {
|
||||
acme?: {
|
||||
email?: string;
|
||||
caServer?: string;
|
||||
certificatesDuration?: number;
|
||||
preferredChain?: string;
|
||||
storage?: string;
|
||||
keyType?: string;
|
||||
eab?: {
|
||||
kid?: string;
|
||||
hmacEncoded?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
dnsChallenge?: {
|
||||
provider?: string;
|
||||
delayBeforeCheck?: string;
|
||||
resolvers?: string[];
|
||||
disablePropagationCheck?: boolean;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
httpChallenge?: {
|
||||
entryPoint?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
tlsChallenge?: {
|
||||
[k: string]: unknown;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
entryPoints?: {
|
||||
/**
|
||||
* This interface was referenced by `undefined`'s JSON-Schema definition
|
||||
* via the `patternProperty` "[a-zA-Z0-9-_]+".
|
||||
*/
|
||||
[k: string]: {
|
||||
address?: string;
|
||||
transport?: {
|
||||
lifeCycle?: {
|
||||
requestAcceptGraceTimeout?: string;
|
||||
graceTimeOut?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
respondingTimeouts?: {
|
||||
readTimeout?: string;
|
||||
writeTimeout?: string;
|
||||
idleTimeout?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
proxyProtocol?: {
|
||||
insecure?: boolean;
|
||||
trustedIPs?: string[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
forwardedHeaders?: {
|
||||
insecure?: boolean;
|
||||
trustedIPs?: string[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
http?: {
|
||||
redirections?: {
|
||||
entryPoint?: {
|
||||
to?: string;
|
||||
scheme?: string;
|
||||
permanent?: boolean;
|
||||
priority?: number;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
middlewares?: string[];
|
||||
tls?: {
|
||||
options?: string;
|
||||
certResolver?: string;
|
||||
domains?: {
|
||||
main?: string;
|
||||
sans?: string[];
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
[k: string]: unknown;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
http2?: {
|
||||
maxConcurrentStreams?: number;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
http3?: {
|
||||
advertisedPort?: number;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
udp?: {
|
||||
timeout?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
experimental?: {
|
||||
kubernetesGateway?: boolean;
|
||||
http3?: boolean;
|
||||
hub?: boolean;
|
||||
plugins?: {
|
||||
/**
|
||||
* This interface was referenced by `undefined`'s JSON-Schema definition
|
||||
* via the `patternProperty` "[a-zA-Z0-9-_]+".
|
||||
*/
|
||||
[k: string]: {
|
||||
moduleName?: string;
|
||||
version?: string;
|
||||
};
|
||||
};
|
||||
localPlugins?: {
|
||||
/**
|
||||
* This interface was referenced by `undefined`'s JSON-Schema definition
|
||||
* via the `patternProperty` "[a-zA-Z0-9-_]+".
|
||||
*/
|
||||
[k: string]: {
|
||||
moduleName?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
global?: {
|
||||
checkNewVersion?: boolean;
|
||||
sendAnonymousUsage?: boolean;
|
||||
};
|
||||
hostResolver?: {
|
||||
cnameFlattening?: boolean;
|
||||
resolvConfig?: string;
|
||||
resolvDepth?: number;
|
||||
};
|
||||
hub?: {
|
||||
tls?: {
|
||||
insecure?: boolean;
|
||||
ca?: string;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
[k: string]: unknown;
|
||||
};
|
||||
};
|
||||
log?: {
|
||||
level?: string;
|
||||
filePath?: string;
|
||||
format?: string;
|
||||
};
|
||||
metrics?: {
|
||||
prometheus?: {
|
||||
buckets?: number[];
|
||||
addEntryPointsLabels?: boolean;
|
||||
addRoutersLabels?: boolean;
|
||||
addServicesLabels?: boolean;
|
||||
entryPoint?: string;
|
||||
manualRouting?: boolean;
|
||||
};
|
||||
datadog?: {
|
||||
address?: string;
|
||||
pushInterval?: string;
|
||||
addEntryPointsLabels?: boolean;
|
||||
addRoutersLabels?: boolean;
|
||||
addServicesLabels?: boolean;
|
||||
prefix?: string;
|
||||
};
|
||||
statsD?: {
|
||||
address?: string;
|
||||
pushInterval?: string;
|
||||
addEntryPointsLabels?: boolean;
|
||||
addRoutersLabels?: boolean;
|
||||
addServicesLabels?: boolean;
|
||||
prefix?: string;
|
||||
};
|
||||
influxDB?: {
|
||||
address?: string;
|
||||
protocol?: string;
|
||||
pushInterval?: string;
|
||||
database?: string;
|
||||
retentionPolicy?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
addEntryPointsLabels?: boolean;
|
||||
addRoutersLabels?: boolean;
|
||||
addServicesLabels?: boolean;
|
||||
additionalLabels?: {
|
||||
[k: string]: unknown;
|
||||
};
|
||||
};
|
||||
influxDB2?: {
|
||||
address?: string;
|
||||
token?: string;
|
||||
pushInterval?: string;
|
||||
org?: string;
|
||||
bucket?: string;
|
||||
addEntryPointsLabels?: boolean;
|
||||
addRoutersLabels?: boolean;
|
||||
addServicesLabels?: boolean;
|
||||
additionalLabels?: {
|
||||
[k: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
pilot?: {
|
||||
token?: string;
|
||||
dashboard?: boolean;
|
||||
};
|
||||
ping?: {
|
||||
entryPoint?: string;
|
||||
manualRouting?: boolean;
|
||||
terminatingStatusCode?: number;
|
||||
};
|
||||
providers?: {
|
||||
providersThrottleDuration?: string;
|
||||
docker?: {
|
||||
allowEmptyServices?: boolean;
|
||||
constraints?: string;
|
||||
defaultRule?: string;
|
||||
endpoint?: string;
|
||||
exposedByDefault?: boolean;
|
||||
httpClientTimeout?: number;
|
||||
network?: string;
|
||||
swarmMode?: boolean;
|
||||
swarmModeRefreshSeconds?: string;
|
||||
tls?: {
|
||||
ca?: string;
|
||||
caOptional?: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
insecureSkipVerify?: boolean;
|
||||
};
|
||||
useBindPortIP?: boolean;
|
||||
watch?: boolean;
|
||||
};
|
||||
file?: {
|
||||
directory?: string;
|
||||
watch?: boolean;
|
||||
filename?: string;
|
||||
debugLogGeneratedTemplate?: boolean;
|
||||
};
|
||||
marathon?: {
|
||||
constraints?: string;
|
||||
trace?: boolean;
|
||||
watch?: boolean;
|
||||
endpoint?: string;
|
||||
defaultRule?: string;
|
||||
exposedByDefault?: boolean;
|
||||
dcosToken?: string;
|
||||
tls?: {
|
||||
ca?: string;
|
||||
caOptional?: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
insecureSkipVerify?: boolean;
|
||||
};
|
||||
dialerTimeout?: string;
|
||||
responseHeaderTimeout?: string;
|
||||
tlsHandshakeTimeout?: string;
|
||||
keepAlive?: string;
|
||||
forceTaskHostname?: boolean;
|
||||
basic?: {
|
||||
httpBasicAuthUser?: string;
|
||||
httpBasicPassword?: string;
|
||||
};
|
||||
respectReadinessChecks?: boolean;
|
||||
};
|
||||
kubernetesIngress?: {
|
||||
endpoint?: string;
|
||||
token?: string;
|
||||
certAuthFilePath?: string;
|
||||
namespaces?: string[];
|
||||
labelSelector?: string;
|
||||
ingressClass?: string;
|
||||
throttleDuration?: string;
|
||||
allowEmptyServices?: boolean;
|
||||
allowExternalNameServices?: boolean;
|
||||
ingressEndpoint?: {
|
||||
ip?: string;
|
||||
hostname?: string;
|
||||
publishedService?: string;
|
||||
};
|
||||
};
|
||||
kubernetesCRD?: {
|
||||
endpoint?: string;
|
||||
token?: string;
|
||||
certAuthFilePath?: string;
|
||||
namespaces?: string[];
|
||||
allowCrossNamespace?: boolean;
|
||||
allowExternalNameServices?: boolean;
|
||||
labelSelector?: string;
|
||||
ingressClass?: string;
|
||||
throttleDuration?: string;
|
||||
allowEmptyServices?: boolean;
|
||||
};
|
||||
kubernetesGateway?: {
|
||||
endpoint?: string;
|
||||
token?: string;
|
||||
certAuthFilePath?: string;
|
||||
namespaces?: string[];
|
||||
labelSelector?: string;
|
||||
throttleDuration?: string;
|
||||
};
|
||||
rest?: {
|
||||
insecure?: boolean;
|
||||
};
|
||||
rancher?: {
|
||||
constraints?: string;
|
||||
watch?: boolean;
|
||||
defaultRule?: string;
|
||||
exposedByDefault?: boolean;
|
||||
enableServiceHealthFilter?: boolean;
|
||||
refreshSeconds?: number;
|
||||
intervalPoll?: boolean;
|
||||
prefix?: string;
|
||||
};
|
||||
consulCatalog?: {
|
||||
constraints?: string;
|
||||
prefix?: string;
|
||||
refreshInterval?: string;
|
||||
requireConsistent?: boolean;
|
||||
stale?: boolean;
|
||||
cache?: boolean;
|
||||
exposedByDefault?: boolean;
|
||||
defaultRule?: string;
|
||||
connectAware?: boolean;
|
||||
connectByDefault?: boolean;
|
||||
serviceName?: string;
|
||||
namespace?: string;
|
||||
namespaces?: string[];
|
||||
watch?: boolean;
|
||||
endpoint?: {
|
||||
address?: string;
|
||||
scheme?: string;
|
||||
datacenter?: string;
|
||||
token?: string;
|
||||
endpointWaitTime?: string;
|
||||
tls?: {
|
||||
ca?: string;
|
||||
caOptional?: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
insecureSkipVerify?: boolean;
|
||||
};
|
||||
httpAuth?: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
nomad?: {
|
||||
constraints?: string;
|
||||
prefix?: string;
|
||||
refreshInterval?: string;
|
||||
stale?: boolean;
|
||||
exposedByDefault?: boolean;
|
||||
defaultRule?: string;
|
||||
namespace?: string;
|
||||
endpoint?: {
|
||||
address?: string;
|
||||
region?: string;
|
||||
token?: string;
|
||||
endpointWaitTime?: string;
|
||||
tls?: {
|
||||
ca?: string;
|
||||
caOptional?: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
insecureSkipVerify?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
ecs?: {
|
||||
constraints?: string;
|
||||
exposedByDefault?: boolean;
|
||||
ecsAnywhere?: boolean;
|
||||
refreshSeconds?: number;
|
||||
defaultRule?: string;
|
||||
clusters?: string[];
|
||||
autoDiscoverClusters?: boolean;
|
||||
region?: string;
|
||||
accessKeyID?: string;
|
||||
secretAccessKey?: string;
|
||||
};
|
||||
consul?: {
|
||||
rootKey?: string;
|
||||
endpoints?: string[];
|
||||
token?: string;
|
||||
namespace?: string;
|
||||
namespaces?: string[];
|
||||
tls?: {
|
||||
ca?: string;
|
||||
caOptional?: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
insecureSkipVerify?: boolean;
|
||||
};
|
||||
};
|
||||
etcd?: {
|
||||
rootKey?: string;
|
||||
endpoints?: string[];
|
||||
username?: string;
|
||||
password?: string;
|
||||
tls?: {
|
||||
ca?: string;
|
||||
caOptional?: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
insecureSkipVerify?: boolean;
|
||||
};
|
||||
};
|
||||
zooKeeper?: {
|
||||
rootKey?: string;
|
||||
endpoints?: string[];
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
redis?: {
|
||||
rootKey?: string;
|
||||
endpoints?: string[];
|
||||
username?: string;
|
||||
password?: string;
|
||||
db?: number;
|
||||
tls?: {
|
||||
ca?: string;
|
||||
caOptional?: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
insecureSkipVerify?: boolean;
|
||||
};
|
||||
};
|
||||
http?: {
|
||||
endpoint?: string;
|
||||
pollInterval?: string;
|
||||
pollTimeout?: string;
|
||||
tls?: {
|
||||
ca?: string;
|
||||
caOptional?: boolean;
|
||||
cert?: string;
|
||||
key?: string;
|
||||
insecureSkipVerify?: boolean;
|
||||
};
|
||||
};
|
||||
plugin?: {
|
||||
/**
|
||||
* This interface was referenced by `undefined`'s JSON-Schema definition
|
||||
* via the `patternProperty` "[a-zA-Z0-9-_]+".
|
||||
*/
|
||||
[k: string]: {
|
||||
[k: string]: unknown;
|
||||
};
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
serversTransport?: {
|
||||
insecureSkipVerify?: boolean;
|
||||
rootCAs?: string[];
|
||||
maxIdleConnsPerHost?: number;
|
||||
forwardingTimeouts?: {
|
||||
dialTimeout?: string;
|
||||
responseHeaderTimeout?: string;
|
||||
idleConnTimeout?: string;
|
||||
};
|
||||
};
|
||||
tracing?: {
|
||||
serviceName?: string;
|
||||
spanNameLimit?: number;
|
||||
jaeger?: {
|
||||
samplingServerURL?: string;
|
||||
samplingType?: string;
|
||||
samplingParam?: number;
|
||||
localAgentHostPort?: string;
|
||||
gen128Bit?: boolean;
|
||||
propagation?: string;
|
||||
traceContextHeaderName?: string;
|
||||
disableAttemptReconnecting?: boolean;
|
||||
collector?: {
|
||||
endpoint?: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
};
|
||||
};
|
||||
zipkin?: {
|
||||
httpEndpoint?: string;
|
||||
sameSpan?: boolean;
|
||||
id128Bit?: boolean;
|
||||
sampleRate?: number;
|
||||
};
|
||||
datadog?: {
|
||||
localAgentHostPort?: string;
|
||||
globalTag?: string;
|
||||
/**
|
||||
* Sets a list of key:value tags on all spans.
|
||||
*/
|
||||
globalTags?: {
|
||||
/**
|
||||
* This interface was referenced by `undefined`'s JSON-Schema definition
|
||||
* via the `patternProperty` "[a-zA-Z0-9-_]+".
|
||||
*/
|
||||
[k: string]: string;
|
||||
};
|
||||
debug?: boolean;
|
||||
prioritySampling?: boolean;
|
||||
traceIDHeaderName?: string;
|
||||
parentIDHeaderName?: string;
|
||||
samplingPriorityHeaderName?: string;
|
||||
bagagePrefixHeaderName?: string;
|
||||
};
|
||||
instana?: {
|
||||
localAgentHost?: string;
|
||||
localAgentPort?: number;
|
||||
logLevel?: string;
|
||||
enableAutoProfile?: boolean;
|
||||
};
|
||||
haystack?: {
|
||||
localAgentHost?: string;
|
||||
localAgentPort?: number;
|
||||
globalTag?: string;
|
||||
traceIDHeaderName?: string;
|
||||
parentIDHeaderName?: string;
|
||||
spanIDHeaderName?: string;
|
||||
baggagePrefixHeaderName?: string;
|
||||
};
|
||||
elastic?: {
|
||||
serverURL?: string;
|
||||
secretToken?: string;
|
||||
serviceEnvironment?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
70
server/utils/traefik/web-server.ts
Normal file
70
server/utils/traefik/web-server.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { join } from "node:path";
|
||||
import type { MainTraefikConfig } from "./types";
|
||||
import { loadOrCreateConfig, writeTraefikConfig } from "./application";
|
||||
import { MAIN_TRAEFIK_PATH } from "@/server/constants";
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dump, load } from "js-yaml";
|
||||
import type { FileConfig } from "./file-types";
|
||||
import type { Admin } from "@/server/api/services/admin";
|
||||
|
||||
export const updateServerTraefik = (
|
||||
admin: Admin | null,
|
||||
newHost: string | null,
|
||||
) => {
|
||||
const appName = "dokploy";
|
||||
const config: FileConfig = loadOrCreateConfig(appName);
|
||||
|
||||
config.http = config.http || { routers: {}, services: {} };
|
||||
config.http.routers = config.http.routers || {};
|
||||
|
||||
const currentRouterConfig = config.http.routers[`${appName}-router-app`];
|
||||
|
||||
if (currentRouterConfig) {
|
||||
if (newHost) {
|
||||
currentRouterConfig.rule = `Host(\`${newHost}\`)`;
|
||||
}
|
||||
if (admin?.certificateType === "letsencrypt") {
|
||||
currentRouterConfig.tls = { certResolver: "letsencrypt" };
|
||||
} else if (admin?.certificateType === "none") {
|
||||
currentRouterConfig.tls = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
writeTraefikConfig(config, appName);
|
||||
};
|
||||
|
||||
export const updateLetsEncryptEmail = (newEmail: string | null) => {
|
||||
try {
|
||||
if (!newEmail) return;
|
||||
const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml");
|
||||
const configContent = readFileSync(configPath, "utf8");
|
||||
const config = load(configContent) as MainTraefikConfig;
|
||||
if (config?.certificatesResolvers?.letsencrypt?.acme) {
|
||||
config.certificatesResolvers.letsencrypt.acme.email = newEmail;
|
||||
} else {
|
||||
throw new Error("Invalid Let's Encrypt configuration structure.");
|
||||
}
|
||||
const newYamlContent = dump(config);
|
||||
writeFileSync(configPath, newYamlContent, "utf8");
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const readMainConfig = () => {
|
||||
const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml");
|
||||
if (existsSync(configPath)) {
|
||||
const yamlStr = readFileSync(configPath, "utf8");
|
||||
return yamlStr;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const writeMainConfig = (traefikConfig: string) => {
|
||||
try {
|
||||
const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml");
|
||||
writeFileSync(configPath, traefikConfig, "utf8");
|
||||
} catch (e) {
|
||||
console.error("Error saving the YAML config file:", e);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user