diff --git a/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx index b232591e4b..113e7db15a 100644 --- a/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx +++ b/apps/dokploy/components/dashboard/application/domains/handle-domain.tsx @@ -183,6 +183,16 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => { serverId: application?.serverId || "", }); + const { data: certificateResolvers } = + api.domain.certificateResolvers.useQuery( + { + serverId: application?.serverId || undefined, + }, + { + enabled: isOpen, + }, + ); + const { data: services, isFetching: isLoadingServices, @@ -221,12 +231,23 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => { }); const certificateType = form.watch("certificateType"); + const customCertResolver = form.watch("customCertResolver"); const useCustomEntrypoint = form.watch("useCustomEntrypoint"); const https = form.watch("https"); const domainType = form.watch("domainType"); const host = form.watch("host"); const isTraefikMeDomain = host?.includes("sslip.io") || false; + // Synthetic value for the certificate provider Select: detected resolvers + // from traefik.yml are stored as certificateType="custom" + + // customCertResolver=, but displayed as their own option. + const certSelectValue = + certificateType === "custom" && + customCertResolver && + certificateResolvers?.includes(customCertResolver) + ? `resolver:${customCertResolver}` + : (certificateType ?? ""); + useEffect(() => { if (data) { form.reset({ @@ -740,15 +761,29 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => { Certificate Provider @@ -769,7 +816,7 @@ export const AddDomain = ({ id, type, domainId = "", children }: Props) => { }} /> - {certificateType === "custom" && ( + {certSelectValue === "custom" && ( { + if (input.serverId) { + const server = await findServerById(input.serverId); + if (server.organizationId !== ctx.session.activeOrganizationId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this server", + }); + } + return getCertificateResolvers(input.serverId); + } + if (IS_CLOUD) { + return []; + } + return getCertificateResolvers(); + }), + update: protectedProcedure .input(apiUpdateDomain) .mutation(async ({ input, ctx }) => { diff --git a/packages/server/src/utils/traefik/web-server.ts b/packages/server/src/utils/traefik/web-server.ts index e5315dab44..943f56cc76 100644 --- a/packages/server/src/utils/traefik/web-server.ts +++ b/packages/server/src/utils/traefik/web-server.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { paths } from "@dokploy/server/constants"; import type { webServerSettings } from "@dokploy/server/db/schema/web-server-settings"; import { parse, stringify } from "yaml"; +import { execAsyncRemote } from "../process/execAsync"; import { loadOrCreateConfig, removeTraefikConfig, @@ -109,6 +110,33 @@ export const readMainConfig = () => { return null; }; +export const getCertificateResolvers = async ( + serverId?: string | null, +): Promise => { + try { + let yamlStr: string | null = null; + if (serverId) { + const { MAIN_TRAEFIK_PATH } = paths(true); + const configPath = join(MAIN_TRAEFIK_PATH, "traefik.yml"); + const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); + yamlStr = stdout || null; + } else { + yamlStr = readMainConfig(); + } + if (!yamlStr) return []; + const config = parse(yamlStr) as MainTraefikConfig; + if ( + !config?.certificatesResolvers || + typeof config.certificatesResolvers !== "object" + ) { + return []; + } + return Object.keys(config.certificatesResolvers); + } catch { + return []; + } +}; + export const writeMainConfig = (traefikConfig: string) => { try { const { MAIN_TRAEFIK_PATH } = paths();