mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #1637 from Dokploy/1601-duplicate-domain-bug
feat(settings): add HTTPS support and update user schema
This commit is contained in:
commit
48ec0a74ad
@ -14,6 +14,7 @@ import {
|
|||||||
import { beforeEach, expect, test, vi } from "vitest";
|
import { beforeEach, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
const baseAdmin: User = {
|
const baseAdmin: User = {
|
||||||
|
https: false,
|
||||||
enablePaidFeatures: false,
|
enablePaidFeatures: false,
|
||||||
metricsConfig: {
|
metricsConfig: {
|
||||||
containers: {
|
containers: {
|
||||||
@ -73,7 +74,6 @@ beforeEach(() => {
|
|||||||
|
|
||||||
test("Should read the configuration file", () => {
|
test("Should read the configuration file", () => {
|
||||||
const config: FileConfig = loadOrCreateConfig("dokploy");
|
const config: FileConfig = loadOrCreateConfig("dokploy");
|
||||||
|
|
||||||
expect(config.http?.routers?.["dokploy-router-app"]?.service).toBe(
|
expect(config.http?.routers?.["dokploy-router-app"]?.service).toBe(
|
||||||
"dokploy-service-app",
|
"dokploy-service-app",
|
||||||
);
|
);
|
||||||
@ -83,6 +83,7 @@ test("Should apply redirect-to-https", () => {
|
|||||||
updateServerTraefik(
|
updateServerTraefik(
|
||||||
{
|
{
|
||||||
...baseAdmin,
|
...baseAdmin,
|
||||||
|
https: true,
|
||||||
certificateType: "letsencrypt",
|
certificateType: "letsencrypt",
|
||||||
},
|
},
|
||||||
"example.com",
|
"example.com",
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@ -22,6 +23,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { GlobeIcon } from "lucide-react";
|
import { GlobeIcon } from "lucide-react";
|
||||||
@ -33,11 +35,19 @@ import { z } from "zod";
|
|||||||
|
|
||||||
const addServerDomain = z
|
const addServerDomain = z
|
||||||
.object({
|
.object({
|
||||||
domain: z.string().min(1, { message: "URL is required" }),
|
domain: z.string(),
|
||||||
letsEncryptEmail: z.string(),
|
letsEncryptEmail: z.string(),
|
||||||
|
https: z.boolean().optional(),
|
||||||
certificateType: z.enum(["letsencrypt", "none", "custom"]),
|
certificateType: z.enum(["letsencrypt", "none", "custom"]),
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
|
if (data.https && !data.certificateType) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["certificateType"],
|
||||||
|
message: "Required",
|
||||||
|
});
|
||||||
|
}
|
||||||
if (data.certificateType === "letsencrypt" && !data.letsEncryptEmail) {
|
if (data.certificateType === "letsencrypt" && !data.letsEncryptEmail) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
@ -61,15 +71,18 @@ export const WebDomain = () => {
|
|||||||
domain: "",
|
domain: "",
|
||||||
certificateType: "none",
|
certificateType: "none",
|
||||||
letsEncryptEmail: "",
|
letsEncryptEmail: "",
|
||||||
|
https: false,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addServerDomain),
|
resolver: zodResolver(addServerDomain),
|
||||||
});
|
});
|
||||||
|
const https = form.watch("https");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
form.reset({
|
form.reset({
|
||||||
domain: data?.user?.host || "",
|
domain: data?.user?.host || "",
|
||||||
certificateType: data?.user?.certificateType,
|
certificateType: data?.user?.certificateType,
|
||||||
letsEncryptEmail: data?.user?.letsEncryptEmail || "",
|
letsEncryptEmail: data?.user?.letsEncryptEmail || "",
|
||||||
|
https: data?.user?.https || false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [form, form.reset, data]);
|
}, [form, form.reset, data]);
|
||||||
@ -79,6 +92,7 @@ export const WebDomain = () => {
|
|||||||
host: data.domain,
|
host: data.domain,
|
||||||
letsEncryptEmail: data.letsEncryptEmail,
|
letsEncryptEmail: data.letsEncryptEmail,
|
||||||
certificateType: data.certificateType,
|
certificateType: data.certificateType,
|
||||||
|
https: data.https,
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await refetch();
|
await refetch();
|
||||||
@ -153,6 +167,28 @@ export const WebDomain = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="https"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm w-full col-span-2">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>HTTPS</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Automatically provision SSL Certificate.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{https && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="certificateType"
|
name="certificateType"
|
||||||
@ -193,6 +229,7 @@ export const WebDomain = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex w-full justify-end col-span-2">
|
<div className="flex w-full justify-end col-span-2">
|
||||||
<Button isLoading={isLoading} type="submit">
|
<Button isLoading={isLoading} type="submit">
|
||||||
|
1
apps/dokploy/drizzle/0084_thin_iron_lad.sql
Normal file
1
apps/dokploy/drizzle/0084_thin_iron_lad.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "user_temp" ADD COLUMN "https" boolean DEFAULT false NOT NULL;
|
5369
apps/dokploy/drizzle/meta/0084_snapshot.json
Normal file
5369
apps/dokploy/drizzle/meta/0084_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -589,6 +589,13 @@
|
|||||||
"when": 1743288371413,
|
"when": 1743288371413,
|
||||||
"tag": "0083_parallel_stranger",
|
"tag": "0083_parallel_stranger",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 84,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1743923992280,
|
||||||
|
"tag": "0084_thin_iron_lad",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -184,6 +184,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
letsEncryptEmail: input.letsEncryptEmail,
|
letsEncryptEmail: input.letsEncryptEmail,
|
||||||
}),
|
}),
|
||||||
certificateType: input.certificateType,
|
certificateType: input.certificateType,
|
||||||
|
https: input.https,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
@ -50,6 +50,7 @@ export const users_temp = pgTable("user_temp", {
|
|||||||
// Admin
|
// Admin
|
||||||
serverIp: text("serverIp"),
|
serverIp: text("serverIp"),
|
||||||
certificateType: certificateType("certificateType").notNull().default("none"),
|
certificateType: certificateType("certificateType").notNull().default("none"),
|
||||||
|
https: boolean("https").notNull().default(false),
|
||||||
host: text("host"),
|
host: text("host"),
|
||||||
letsEncryptEmail: text("letsEncryptEmail"),
|
letsEncryptEmail: text("letsEncryptEmail"),
|
||||||
sshPrivateKey: text("sshPrivateKey"),
|
sshPrivateKey: text("sshPrivateKey"),
|
||||||
@ -202,10 +203,12 @@ export const apiAssignDomain = createSchema
|
|||||||
host: true,
|
host: true,
|
||||||
certificateType: true,
|
certificateType: true,
|
||||||
letsEncryptEmail: true,
|
letsEncryptEmail: true,
|
||||||
|
https: true,
|
||||||
})
|
})
|
||||||
.required()
|
.required()
|
||||||
.partial({
|
.partial({
|
||||||
letsEncryptEmail: true,
|
letsEncryptEmail: true,
|
||||||
|
https: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiUpdateDockerCleanup = createSchema
|
export const apiUpdateDockerCleanup = createSchema
|
||||||
|
@ -3,7 +3,11 @@ import { join } from "node:path";
|
|||||||
import { paths } from "@dokploy/server/constants";
|
import { paths } from "@dokploy/server/constants";
|
||||||
import type { User } from "@dokploy/server/services/user";
|
import type { User } from "@dokploy/server/services/user";
|
||||||
import { dump, load } from "js-yaml";
|
import { dump, load } from "js-yaml";
|
||||||
import { loadOrCreateConfig, writeTraefikConfig } from "./application";
|
import {
|
||||||
|
loadOrCreateConfig,
|
||||||
|
removeTraefikConfig,
|
||||||
|
writeTraefikConfig,
|
||||||
|
} from "./application";
|
||||||
import type { FileConfig } from "./file-types";
|
import type { FileConfig } from "./file-types";
|
||||||
import type { MainTraefikConfig } from "./types";
|
import type { MainTraefikConfig } from "./types";
|
||||||
|
|
||||||
@ -11,32 +15,62 @@ export const updateServerTraefik = (
|
|||||||
user: User | null,
|
user: User | null,
|
||||||
newHost: string | null,
|
newHost: string | null,
|
||||||
) => {
|
) => {
|
||||||
|
const { https, certificateType } = user || {};
|
||||||
const appName = "dokploy";
|
const appName = "dokploy";
|
||||||
const config: FileConfig = loadOrCreateConfig(appName);
|
const config: FileConfig = loadOrCreateConfig(appName);
|
||||||
|
|
||||||
config.http = config.http || { routers: {}, services: {} };
|
config.http = config.http || { routers: {}, services: {} };
|
||||||
config.http.routers = config.http.routers || {};
|
config.http.routers = config.http.routers || {};
|
||||||
|
config.http.services = config.http.services || {};
|
||||||
|
|
||||||
const currentRouterConfig = config.http.routers[`${appName}-router-app`];
|
const currentRouterConfig = config.http.routers[`${appName}-router-app`] || {
|
||||||
|
rule: `Host(\`${newHost}\`)`,
|
||||||
|
service: `${appName}-service-app`,
|
||||||
|
entryPoints: ["web"],
|
||||||
|
};
|
||||||
|
config.http.routers[`${appName}-router-app`] = currentRouterConfig;
|
||||||
|
|
||||||
if (currentRouterConfig && newHost) {
|
config.http.services = {
|
||||||
currentRouterConfig.rule = `Host(\`${newHost}\`)`;
|
...config.http.services,
|
||||||
|
[`${appName}-service-app`]: {
|
||||||
|
loadBalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `http://dokploy:${process.env.PORT || 3000}`,
|
||||||
|
passHostHeader: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (user?.certificateType === "letsencrypt") {
|
if (https) {
|
||||||
|
currentRouterConfig.middlewares = ["redirect-to-https"];
|
||||||
|
|
||||||
|
if (certificateType === "letsencrypt") {
|
||||||
config.http.routers[`${appName}-router-app-secure`] = {
|
config.http.routers[`${appName}-router-app-secure`] = {
|
||||||
...currentRouterConfig,
|
rule: `Host(\`${newHost}\`)`,
|
||||||
|
service: `${appName}-service-app`,
|
||||||
entryPoints: ["websecure"],
|
entryPoints: ["websecure"],
|
||||||
tls: { certResolver: "letsencrypt" },
|
tls: { certResolver: "letsencrypt" },
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
currentRouterConfig.middlewares = ["redirect-to-https"];
|
config.http.routers[`${appName}-router-app-secure`] = {
|
||||||
|
rule: `Host(\`${newHost}\`)`,
|
||||||
|
service: `${appName}-service-app`,
|
||||||
|
entryPoints: ["websecure"],
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
delete config.http.routers[`${appName}-router-app-secure`];
|
delete config.http.routers[`${appName}-router-app-secure`];
|
||||||
currentRouterConfig.middlewares = [];
|
currentRouterConfig.middlewares = [];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
if (newHost) {
|
||||||
writeTraefikConfig(config, appName);
|
writeTraefikConfig(config, appName);
|
||||||
|
} else {
|
||||||
|
removeTraefikConfig(appName);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateLetsEncryptEmail = (newEmail: string | null) => {
|
export const updateLetsEncryptEmail = (newEmail: string | null) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user