mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-06 22:45:24 +02:00
Merge branch 'canary' into 394-ability-to-backup-named-volume-to-s3
This commit is contained in:
@@ -301,6 +301,8 @@ export const createDomainLabels = (
|
||||
certificateType,
|
||||
path,
|
||||
customCertResolver,
|
||||
stripPath,
|
||||
internalPath,
|
||||
} = domain;
|
||||
const routerName = `${appName}-${uniqueConfigKey}-${entrypoint}`;
|
||||
const labels = [
|
||||
@@ -310,6 +312,34 @@ export const createDomainLabels = (
|
||||
`traefik.http.routers.${routerName}.service=${routerName}`,
|
||||
];
|
||||
|
||||
// Validate stripPath - it should only be used when path is defined and not "/"
|
||||
if (stripPath) {
|
||||
if (!path || path === "/") {
|
||||
console.warn(
|
||||
`stripPath is enabled but path is not defined or is "/" for domain ${host}`,
|
||||
);
|
||||
} else {
|
||||
const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`;
|
||||
labels.push(
|
||||
`traefik.http.middlewares.${middlewareName}.stripprefix.prefixes=${path}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate internalPath - ensure it's a valid path format
|
||||
if (internalPath && internalPath !== "/") {
|
||||
if (!internalPath.startsWith("/")) {
|
||||
console.warn(
|
||||
`internalPath "${internalPath}" should start with "/" and not be empty for domain ${host}`,
|
||||
);
|
||||
} else {
|
||||
const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`;
|
||||
labels.push(
|
||||
`traefik.http.middlewares.${middlewareName}.addprefix.prefix=${internalPath}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (entrypoint === "web" && https) {
|
||||
labels.push(
|
||||
`traefik.http.routers.${routerName}.middlewares=redirect-to-https@file`,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import fs, { writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { createReadStream } from "node:fs";
|
||||
import { createInterface } from "node:readline";
|
||||
import { paths } from "@dokploy/server/constants";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { dump, load } from "js-yaml";
|
||||
@@ -137,39 +139,40 @@ export const readRemoteConfig = async (serverId: string, appName: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const readMonitoringConfig = (readAll = false) => {
|
||||
export const readMonitoringConfig = async (readAll = false) => {
|
||||
const { DYNAMIC_TRAEFIK_PATH } = paths();
|
||||
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, "access.log");
|
||||
if (fs.existsSync(configPath)) {
|
||||
if (!readAll) {
|
||||
// Read first 500 lines
|
||||
// Read first 500 lines using streams
|
||||
let content = "";
|
||||
let chunk = "";
|
||||
let validCount = 0;
|
||||
|
||||
for (const char of fs.readFileSync(configPath, "utf8")) {
|
||||
chunk += char;
|
||||
if (char === "\n") {
|
||||
try {
|
||||
const trimmed = chunk.trim();
|
||||
if (
|
||||
trimmed !== "" &&
|
||||
trimmed.startsWith("{") &&
|
||||
trimmed.endsWith("}")
|
||||
) {
|
||||
const log = JSON.parse(trimmed);
|
||||
if (log.ServiceName !== "dokploy-service-app@file") {
|
||||
content += chunk;
|
||||
validCount++;
|
||||
if (validCount >= 500) {
|
||||
break;
|
||||
}
|
||||
const fileStream = createReadStream(configPath, { encoding: "utf8" });
|
||||
const readline = createInterface({
|
||||
input: fileStream,
|
||||
crlfDelay: Number.POSITIVE_INFINITY,
|
||||
});
|
||||
|
||||
for await (const line of readline) {
|
||||
try {
|
||||
const trimmed = line.trim();
|
||||
if (
|
||||
trimmed !== "" &&
|
||||
trimmed.startsWith("{") &&
|
||||
trimmed.endsWith("}")
|
||||
) {
|
||||
const log = JSON.parse(trimmed);
|
||||
if (log.ServiceName !== "dokploy-service-app@file") {
|
||||
content += `${line}\n`;
|
||||
validCount++;
|
||||
if (validCount >= 500) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore invalid JSON
|
||||
}
|
||||
chunk = "";
|
||||
} catch {
|
||||
// Ignore invalid JSON
|
||||
}
|
||||
}
|
||||
return content;
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
writeTraefikConfigRemote,
|
||||
} from "./application";
|
||||
import type { FileConfig, HttpRouter } from "./file-types";
|
||||
import { createPathMiddlewares, removePathMiddlewares } from "./middleware";
|
||||
|
||||
export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
|
||||
const { appName } = app;
|
||||
@@ -46,6 +47,8 @@ export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
|
||||
|
||||
config.http.services[serviceName] = createServiceConfig(appName, domain);
|
||||
|
||||
await createPathMiddlewares(app, domain);
|
||||
|
||||
if (app.serverId) {
|
||||
await writeTraefikConfigRemote(config, appName, app.serverId);
|
||||
} else {
|
||||
@@ -80,6 +83,8 @@ export const removeDomain = async (
|
||||
delete config.http.services[serviceKey];
|
||||
}
|
||||
|
||||
await removePathMiddlewares(application, uniqueKey);
|
||||
|
||||
// verify if is the last router if so we delete the router
|
||||
if (
|
||||
config?.http?.routers &&
|
||||
@@ -107,7 +112,8 @@ export const createRouterConfig = async (
|
||||
const { appName, redirects, security } = app;
|
||||
const { certificateType } = domain;
|
||||
|
||||
const { host, path, https, uniqueConfigKey } = domain;
|
||||
const { host, path, https, uniqueConfigKey, internalPath, stripPath } =
|
||||
domain;
|
||||
const routerConfig: HttpRouter = {
|
||||
rule: `Host(\`${host}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`,
|
||||
service: `${appName}-service-${uniqueConfigKey}`,
|
||||
@@ -115,6 +121,17 @@ export const createRouterConfig = async (
|
||||
entryPoints: [entryPoint],
|
||||
};
|
||||
|
||||
// Add path rewriting middleware if needed
|
||||
if (internalPath && internalPath !== "/" && internalPath !== path) {
|
||||
const pathMiddleware = `addprefix-${appName}-${uniqueConfigKey}`;
|
||||
routerConfig.middlewares?.push(pathMiddleware);
|
||||
}
|
||||
|
||||
if (stripPath && path && path !== "/") {
|
||||
const stripMiddleware = `stripprefix-${appName}-${uniqueConfigKey}`;
|
||||
routerConfig.middlewares?.push(stripMiddleware);
|
||||
}
|
||||
|
||||
if (entryPoint === "web" && https) {
|
||||
routerConfig.middlewares = ["redirect-to-https"];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ApplicationNested } from "../builders";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import { writeTraefikConfigRemote } from "./application";
|
||||
import type { FileConfig } from "./file-types";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
|
||||
export const addMiddleware = (config: FileConfig, middlewareName: string) => {
|
||||
if (config.http?.routers) {
|
||||
@@ -105,3 +106,97 @@ export const writeMiddleware = <T>(config: T) => {
|
||||
const newYamlContent = dump(config);
|
||||
writeFileSync(configPath, newYamlContent, "utf8");
|
||||
};
|
||||
|
||||
export const createPathMiddlewares = async (
|
||||
app: ApplicationNested,
|
||||
domain: Domain,
|
||||
) => {
|
||||
let config: FileConfig;
|
||||
|
||||
if (app.serverId) {
|
||||
try {
|
||||
config = await loadRemoteMiddlewares(app.serverId);
|
||||
} catch {
|
||||
config = { http: { middlewares: {} } };
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
config = loadMiddlewares<FileConfig>();
|
||||
} catch {
|
||||
config = { http: { middlewares: {} } };
|
||||
}
|
||||
}
|
||||
|
||||
const { appName } = app;
|
||||
const { uniqueConfigKey, internalPath, stripPath, path } = domain;
|
||||
|
||||
if (!config.http) {
|
||||
config.http = { middlewares: {} };
|
||||
}
|
||||
if (!config.http.middlewares) {
|
||||
config.http.middlewares = {};
|
||||
}
|
||||
|
||||
// Add internal path prefix middleware
|
||||
if (internalPath && internalPath !== "/" && internalPath !== path) {
|
||||
const middlewareName = `addprefix-${appName}-${uniqueConfigKey}`;
|
||||
config.http.middlewares[middlewareName] = {
|
||||
addPrefix: {
|
||||
prefix: internalPath,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Strip external path middleware if needed
|
||||
if (stripPath && path && path !== "/") {
|
||||
const middlewareName = `stripprefix-${appName}-${uniqueConfigKey}`;
|
||||
config.http.middlewares[middlewareName] = {
|
||||
stripPrefix: {
|
||||
prefixes: [path],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (app.serverId) {
|
||||
await writeTraefikConfigRemote(config, "middlewares", app.serverId);
|
||||
} else {
|
||||
writeMiddleware(config);
|
||||
}
|
||||
};
|
||||
|
||||
export const removePathMiddlewares = async (
|
||||
app: ApplicationNested,
|
||||
uniqueConfigKey: number,
|
||||
) => {
|
||||
let config: FileConfig;
|
||||
|
||||
if (app.serverId) {
|
||||
try {
|
||||
config = await loadRemoteMiddlewares(app.serverId);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
config = loadMiddlewares<FileConfig>();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const { appName } = app;
|
||||
|
||||
if (config.http?.middlewares) {
|
||||
const addPrefixMiddleware = `addprefix-${appName}-${uniqueConfigKey}`;
|
||||
const stripPrefixMiddleware = `stripprefix-${appName}-${uniqueConfigKey}`;
|
||||
|
||||
delete config.http.middlewares[addPrefixMiddleware];
|
||||
delete config.http.middlewares[stripPrefixMiddleware];
|
||||
}
|
||||
|
||||
if (app.serverId) {
|
||||
await writeTraefikConfigRemote(config, "middlewares", app.serverId);
|
||||
} else {
|
||||
writeMiddleware(config);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user