mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-07 04:22:16 +00:00
feat: add support for custom entry point
This commit is contained in:
@@ -7,6 +7,7 @@ describe("createDomainLabels", () => {
|
|||||||
const baseDomain: Domain = {
|
const baseDomain: Domain = {
|
||||||
host: "example.com",
|
host: "example.com",
|
||||||
port: 8080,
|
port: 8080,
|
||||||
|
customEntrypoint: null,
|
||||||
https: false,
|
https: false,
|
||||||
uniqueConfigKey: 1,
|
uniqueConfigKey: 1,
|
||||||
customCertResolver: null,
|
customCertResolver: null,
|
||||||
@@ -240,4 +241,38 @@ describe("createDomainLabels", () => {
|
|||||||
"traefik.http.routers.test-app-1-websecure.middlewares=stripprefix-test-app-1,addprefix-test-app-1",
|
"traefik.http.routers.test-app-1-websecure.middlewares=stripprefix-test-app-1,addprefix-test-app-1",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should create basic labels for custom entrypoint", async () => {
|
||||||
|
const labels = await createDomainLabels(
|
||||||
|
appName,
|
||||||
|
{ ...baseDomain, customEntrypoint: "custom" },
|
||||||
|
"custom",
|
||||||
|
);
|
||||||
|
expect(labels).toEqual([
|
||||||
|
"traefik.http.routers.test-app-1-custom.rule=Host(`example.com`)",
|
||||||
|
"traefik.http.routers.test-app-1-custom.entrypoints=custom",
|
||||||
|
"traefik.http.services.test-app-1-custom.loadbalancer.server.port=8080",
|
||||||
|
"traefik.http.routers.test-app-1-custom.service=test-app-1-custom",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create https labels for custom entrypoint", async () => {
|
||||||
|
const labels = await createDomainLabels(
|
||||||
|
appName,
|
||||||
|
{
|
||||||
|
...baseDomain,
|
||||||
|
https: true,
|
||||||
|
customEntrypoint: "custom",
|
||||||
|
certificateType: "letsencrypt",
|
||||||
|
},
|
||||||
|
"custom",
|
||||||
|
);
|
||||||
|
expect(labels).toEqual([
|
||||||
|
"traefik.http.routers.test-app-1-custom.rule=Host(`example.com`)",
|
||||||
|
"traefik.http.routers.test-app-1-custom.entrypoints=custom",
|
||||||
|
"traefik.http.services.test-app-1-custom.loadbalancer.server.port=8080",
|
||||||
|
"traefik.http.routers.test-app-1-custom.service=test-app-1-custom",
|
||||||
|
"traefik.http.routers.test-app-1-custom.tls.certresolver=letsencrypt",
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ const baseDomain: Domain = {
|
|||||||
https: false,
|
https: false,
|
||||||
path: null,
|
path: null,
|
||||||
port: null,
|
port: null,
|
||||||
|
customEntrypoint: null,
|
||||||
serviceName: "",
|
serviceName: "",
|
||||||
composeId: "",
|
composeId: "",
|
||||||
customCertResolver: null,
|
customCertResolver: null,
|
||||||
@@ -261,3 +262,32 @@ test("CertificateType on websecure entrypoint", async () => {
|
|||||||
|
|
||||||
expect(router.tls?.certResolver).toBe("letsencrypt");
|
expect(router.tls?.certResolver).toBe("letsencrypt");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Custom entrypoint on http domain", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{ ...baseDomain, https: false, customEntrypoint: "custom" },
|
||||||
|
"custom",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.entryPoints).toEqual(["custom"]);
|
||||||
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||||
|
expect(router.tls).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Custom entrypoint on https domain", async () => {
|
||||||
|
const router = await createRouterConfig(
|
||||||
|
baseApp,
|
||||||
|
{
|
||||||
|
...baseDomain,
|
||||||
|
https: true,
|
||||||
|
customEntrypoint: "custom",
|
||||||
|
certificateType: "letsencrypt",
|
||||||
|
},
|
||||||
|
"custom",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(router.entryPoints).toEqual(["custom"]);
|
||||||
|
expect(router.middlewares).not.toContain("redirect-to-https");
|
||||||
|
expect(router.tls?.certResolver).toBe("letsencrypt");
|
||||||
|
});
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ export const domain = z
|
|||||||
.min(1, { message: "Port must be at least 1" })
|
.min(1, { message: "Port must be at least 1" })
|
||||||
.max(65535, { message: "Port must be 65535 or below" })
|
.max(65535, { message: "Port must be 65535 or below" })
|
||||||
.optional(),
|
.optional(),
|
||||||
|
useCustomEntrypoint: z.boolean(),
|
||||||
|
customEntrypoint: z.string().optional(),
|
||||||
https: z.boolean().optional(),
|
https: z.boolean().optional(),
|
||||||
certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
||||||
customCertResolver: z.string().optional(),
|
customCertResolver: z.string().optional(),
|
||||||
@@ -108,6 +110,14 @@ export const domain = z
|
|||||||
message: "Internal path must start with '/'",
|
message: "Internal path must start with '/'",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.useCustomEntrypoint && !input.customEntrypoint) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["customEntrypoint"],
|
||||||
|
message: "Custom entry point must be specified",
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
type Domain = z.infer<typeof domain>;
|
type Domain = z.infer<typeof domain>;
|
||||||
@@ -190,6 +200,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
internalPath: undefined,
|
internalPath: undefined,
|
||||||
stripPath: false,
|
stripPath: false,
|
||||||
port: undefined,
|
port: undefined,
|
||||||
|
useCustomEntrypoint: false,
|
||||||
|
customEntrypoint: undefined,
|
||||||
https: false,
|
https: false,
|
||||||
certificateType: undefined,
|
certificateType: undefined,
|
||||||
customCertResolver: undefined,
|
customCertResolver: undefined,
|
||||||
@@ -200,6 +212,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const certificateType = form.watch("certificateType");
|
const certificateType = form.watch("certificateType");
|
||||||
|
const useCustomEntrypoint = form.watch("useCustomEntrypoint");
|
||||||
const https = form.watch("https");
|
const https = form.watch("https");
|
||||||
const domainType = form.watch("domainType");
|
const domainType = form.watch("domainType");
|
||||||
|
|
||||||
@@ -212,6 +225,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
internalPath: data?.internalPath || undefined,
|
internalPath: data?.internalPath || undefined,
|
||||||
stripPath: data?.stripPath || false,
|
stripPath: data?.stripPath || false,
|
||||||
port: data?.port || undefined,
|
port: data?.port || undefined,
|
||||||
|
useCustomEntrypoint: !!data.customEntrypoint,
|
||||||
|
customEntrypoint: data.customEntrypoint || undefined,
|
||||||
certificateType: data?.certificateType || undefined,
|
certificateType: data?.certificateType || undefined,
|
||||||
customCertResolver: data?.customCertResolver || undefined,
|
customCertResolver: data?.customCertResolver || undefined,
|
||||||
serviceName: data?.serviceName || undefined,
|
serviceName: data?.serviceName || undefined,
|
||||||
@@ -226,6 +241,8 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
internalPath: undefined,
|
internalPath: undefined,
|
||||||
stripPath: false,
|
stripPath: false,
|
||||||
port: undefined,
|
port: undefined,
|
||||||
|
useCustomEntrypoint: false,
|
||||||
|
customEntrypoint: undefined,
|
||||||
https: false,
|
https: false,
|
||||||
certificateType: undefined,
|
certificateType: undefined,
|
||||||
customCertResolver: undefined,
|
customCertResolver: undefined,
|
||||||
@@ -613,6 +630,50 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="useCustomEntrypoint"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Custom Entrypoint</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Use custom entrypoint for domina
|
||||||
|
<br />
|
||||||
|
"web" and/or "websecure" is used by default.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{useCustomEntrypoint && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="customEntrypoint"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="w-full">
|
||||||
|
<FormLabel>Entrypoint Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Enter entrypoint name manually"
|
||||||
|
{...field}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="https"
|
name="https"
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "domain" ADD COLUMN "customEntrypoint" text;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -820,6 +820,13 @@
|
|||||||
"when": 1759645163834,
|
"when": 1759645163834,
|
||||||
"tag": "0116_amusing_firedrake",
|
"tag": "0116_amusing_firedrake",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 117,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1760873947240,
|
||||||
|
"tag": "0117_needy_moira_mactaggert",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,7 @@ export const domains = pgTable("domain", {
|
|||||||
host: text("host").notNull(),
|
host: text("host").notNull(),
|
||||||
https: boolean("https").notNull().default(false),
|
https: boolean("https").notNull().default(false),
|
||||||
port: integer("port").default(3000),
|
port: integer("port").default(3000),
|
||||||
|
customEntrypoint: text("customEntrypoint"),
|
||||||
path: text("path").default("/"),
|
path: text("path").default("/"),
|
||||||
serviceName: text("serviceName"),
|
serviceName: text("serviceName"),
|
||||||
domainType: domainType("domainType").default("application"),
|
domainType: domainType("domainType").default("application"),
|
||||||
@@ -76,6 +77,7 @@ export const apiCreateDomain = createSchema.pick({
|
|||||||
host: true,
|
host: true,
|
||||||
path: true,
|
path: true,
|
||||||
port: true,
|
port: true,
|
||||||
|
customEntrypoint: true,
|
||||||
https: true,
|
https: true,
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
certificateType: true,
|
certificateType: true,
|
||||||
@@ -111,6 +113,7 @@ export const apiUpdateDomain = createSchema
|
|||||||
host: true,
|
host: true,
|
||||||
path: true,
|
path: true,
|
||||||
port: true,
|
port: true,
|
||||||
|
customEntrypoint: true,
|
||||||
https: true,
|
https: true,
|
||||||
certificateType: true,
|
certificateType: true,
|
||||||
customCertResolver: true,
|
customCertResolver: true,
|
||||||
|
|||||||
@@ -220,8 +220,8 @@ export const addDomainToCompose = async (
|
|||||||
throw new Error(`The service ${serviceName} not found in the compose`);
|
throw new Error(`The service ${serviceName} not found in the compose`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const httpLabels = createDomainLabels(appName, domain, "web");
|
const httpLabels = createDomainLabels(appName, domain, domain.customEntrypoint || "web");
|
||||||
if (https) {
|
if (domain.customEntrypoint == null && https) {
|
||||||
const httpsLabels = createDomainLabels(appName, domain, "websecure");
|
const httpsLabels = createDomainLabels(appName, domain, "websecure");
|
||||||
httpLabels.push(...httpsLabels);
|
httpLabels.push(...httpsLabels);
|
||||||
}
|
}
|
||||||
@@ -299,11 +299,12 @@ export const writeComposeFile = async (
|
|||||||
export const createDomainLabels = (
|
export const createDomainLabels = (
|
||||||
appName: string,
|
appName: string,
|
||||||
domain: Domain,
|
domain: Domain,
|
||||||
entrypoint: "web" | "websecure",
|
entrypoint: string,
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
customEntrypoint,
|
||||||
https,
|
https,
|
||||||
uniqueConfigKey,
|
uniqueConfigKey,
|
||||||
certificateType,
|
certificateType,
|
||||||
@@ -360,7 +361,7 @@ export const createDomainLabels = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add TLS configuration for websecure
|
// Add TLS configuration for websecure
|
||||||
if (entrypoint === "websecure") {
|
if (entrypoint === "websecure" || (customEntrypoint != null && https)) {
|
||||||
if (certificateType === "letsencrypt") {
|
if (certificateType === "letsencrypt") {
|
||||||
labels.push(
|
labels.push(
|
||||||
`traefik.http.routers.${routerName}.tls.certresolver=letsencrypt`,
|
`traefik.http.routers.${routerName}.tls.certresolver=letsencrypt`,
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ export const manageDomain = async (app: ApplicationNested, domain: Domain) => {
|
|||||||
config.http.routers[routerName] = await createRouterConfig(
|
config.http.routers[routerName] = await createRouterConfig(
|
||||||
app,
|
app,
|
||||||
domain,
|
domain,
|
||||||
"web",
|
domain.customEntrypoint || "web",
|
||||||
);
|
);
|
||||||
|
|
||||||
if (domain.https) {
|
if (domain.customEntrypoint == null && domain.https) {
|
||||||
config.http.routers[routerNameSecure] = await createRouterConfig(
|
config.http.routers[routerNameSecure] = await createRouterConfig(
|
||||||
app,
|
app,
|
||||||
domain,
|
domain,
|
||||||
@@ -107,12 +107,12 @@ export const removeDomain = async (
|
|||||||
export const createRouterConfig = async (
|
export const createRouterConfig = async (
|
||||||
app: ApplicationNested,
|
app: ApplicationNested,
|
||||||
domain: Domain,
|
domain: Domain,
|
||||||
entryPoint: "web" | "websecure",
|
entryPoint: string,
|
||||||
) => {
|
) => {
|
||||||
const { appName, redirects, security } = app;
|
const { appName, redirects, security } = app;
|
||||||
const { certificateType } = domain;
|
const { certificateType } = domain;
|
||||||
|
|
||||||
const { host, path, https, uniqueConfigKey, internalPath, stripPath } =
|
const { host, path, https, uniqueConfigKey, internalPath, stripPath, customEntrypoint } =
|
||||||
domain;
|
domain;
|
||||||
const routerConfig: HttpRouter = {
|
const routerConfig: HttpRouter = {
|
||||||
rule: `Host(\`${host}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`,
|
rule: `Host(\`${host}\`)${path !== null && path !== "/" ? ` && PathPrefix(\`${path}\`)` : ""}`,
|
||||||
@@ -162,7 +162,7 @@ export const createRouterConfig = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entryPoint === "websecure") {
|
if (entryPoint === "websecure" || (customEntrypoint != null && https)) {
|
||||||
if (certificateType === "letsencrypt") {
|
if (certificateType === "letsencrypt") {
|
||||||
routerConfig.tls = { certResolver: "letsencrypt" };
|
routerConfig.tls = { certResolver: "letsencrypt" };
|
||||||
} else if (certificateType === "custom" && domain.customCertResolver) {
|
} else if (certificateType === "custom" && domain.customCertResolver) {
|
||||||
|
|||||||
Reference in New Issue
Block a user