diff --git a/Dockerfile b/Dockerfile index 173dca23..ff0b4b44 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ RUN \ npm install && \ npm run build + FROM golang:1.22-alpine AS builder WORKDIR /app @@ -16,14 +17,17 @@ WORKDIR /app COPY ../. /app/ RUN rm -rf /app/ui/dist + COPY --from=front-builder /app/ui/dist /app/ui/dist RUN go build -o certimate + + FROM alpine:latest WORKDIR /app COPY --from=builder /app/certimate . -ENTRYPOINT ["./certimate", "serve", "--http", "0.0.0.0:8090"] +ENTRYPOINT ["./certimate", "serve", "--http", "0.0.0.0:8090"] \ No newline at end of file diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index b70f216e..cdaab89e 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -7,6 +7,8 @@ import ( "crypto/rand" "errors" "fmt" + "os" + "strconv" "strings" "github.com/usual2970/certimate/internal/domain" @@ -63,12 +65,13 @@ type Certificate struct { } type ApplyOption struct { - Email string `json:"email"` - Domain string `json:"domain"` - Access string `json:"access"` - KeyAlgorithm string `json:"keyAlgorithm"` - Nameservers string `json:"nameservers"` - Timeout int64 `json:"timeout"` + Email string `json:"email"` + Domain string `json:"domain"` + Access string `json:"access"` + KeyAlgorithm string `json:"keyAlgorithm"` + Nameservers string `json:"nameservers"` + Timeout int64 `json:"timeout"` + DisableFollowCNAME bool `json:"disableFollowCNAME"` } type ApplyUser struct { @@ -115,12 +118,13 @@ func Get(record *models.Record) (Applicant, error) { } option := &ApplyOption{ - Email: applyConfig.Email, - Domain: record.GetString("domain"), - Access: access.GetString("config"), - KeyAlgorithm: applyConfig.KeyAlgorithm, - Nameservers: applyConfig.Nameservers, - Timeout: applyConfig.Timeout, + Email: applyConfig.Email, + Domain: record.GetString("domain"), + Access: access.GetString("config"), + KeyAlgorithm: applyConfig.KeyAlgorithm, + Nameservers: applyConfig.Nameservers, + Timeout: applyConfig.Timeout, + DisableFollowCNAME: applyConfig.DisableFollowCNAME, } switch access.GetString("configType") { @@ -177,6 +181,10 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro return nil, err } + // Some unified lego environment variables are configured here. + // link: https://github.com/go-acme/lego/issues/1867 + os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(option.DisableFollowCNAME)) + myUser := ApplyUser{ Email: option.Email, key: privateKey, diff --git a/internal/deployer/aliyun_esa.go b/internal/deployer/aliyun_esa.go index 79bc54ea..012ca887 100644 --- a/internal/deployer/aliyun_esa.go +++ b/internal/deployer/aliyun_esa.go @@ -1,7 +1,7 @@ /* * @Author: Bin * @Date: 2024-09-17 - * @FilePath: /github.com/usual2970/certimate/internal/deployer/aliyun_esa.go + * @FilePath: /certimate/internal/deployer/aliyun_esa.go */ package deployer @@ -9,6 +9,7 @@ import ( "context" "encoding/json" "fmt" + "strings" openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" dcdn20180115 "github.com/alibabacloud-go/dcdn-20180115/v3/client" @@ -55,8 +56,15 @@ func (d *AliyunESADeployer) GetInfo() []string { func (d *AliyunESADeployer) Deploy(ctx context.Context) error { certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6)) + + // 支持泛解析域名,在 Aliyun DCND 中泛解析域名表示为 .example.com + domain := getDeployString(d.option.DeployConfig, "domain") + if strings.HasPrefix(domain, "*") { + domain = strings.TrimPrefix(domain, "*") + } + setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{ - DomainName: tea.String(getDeployString(d.option.DeployConfig, "domain")), + DomainName: tea.String(domain), CertName: tea.String(certName), CertType: tea.String("upload"), SSLProtocol: tea.String("on"), diff --git a/internal/domain/domains.go b/internal/domain/domains.go index 0ddc4df6..bb481ae3 100644 --- a/internal/domain/domains.go +++ b/internal/domain/domains.go @@ -1,11 +1,12 @@ package domain type ApplyConfig struct { - Email string `json:"email"` - Access string `json:"access"` - KeyAlgorithm string `json:"keyAlgorithm"` - Nameservers string `json:"nameservers"` - Timeout int64 `json:"timeout"` + Email string `json:"email"` + Access string `json:"access"` + KeyAlgorithm string `json:"keyAlgorithm"` + Nameservers string `json:"nameservers"` + Timeout int64 `json:"timeout"` + DisableFollowCNAME bool `json:"disableFollowCNAME"` } type DeployConfig struct { diff --git a/ui/src/components/certimate/Version.tsx b/ui/src/components/certimate/Version.tsx index f8023ec2..626e8809 100644 --- a/ui/src/components/certimate/Version.tsx +++ b/ui/src/components/certimate/Version.tsx @@ -3,13 +3,17 @@ import { BookOpen } from "lucide-react"; import { Separator } from "@/components/ui/separator"; import { version } from "@/domain/version"; +import { cn } from "@/lib/utils"; -const Version = () => { +type VersionProps = { + className?: string; +}; + +const Version = ({ className }: VersionProps) => { const { t } = useTranslation(); return ( -
-
+
@@ -25,3 +29,4 @@ const Version = () => { }; export default Version; + diff --git a/ui/src/components/ui/tooltip.tsx b/ui/src/components/ui/tooltip.tsx index ed4fa100..54f6e5bb 100644 --- a/ui/src/components/ui/tooltip.tsx +++ b/ui/src/components/ui/tooltip.tsx @@ -24,4 +24,31 @@ const TooltipContent = React.forwardRef & { + contentView?: JSX.Element; + }; + +const TooltipLink = React.forwardRef((props: React.PropsWithChildren, forwardedRef: React.ForwardedRef) => ( + + {props.children} + +)); + +function TooltipFast({ children, contentView, open, defaultOpen, onOpenChange, ...props }: TooltipFastProps) { + return ( + + + + {children} + + + {contentView} + + + + ); +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipFast }; diff --git a/ui/src/domain/domain.ts b/ui/src/domain/domain.ts index f7a0a58f..6d31ee63 100644 --- a/ui/src/domain/domain.ts +++ b/ui/src/domain/domain.ts @@ -53,6 +53,7 @@ export type ApplyConfig = { keyAlgorithm?: string; nameservers?: string; timeout?: number; + disableFollowCNAME?: boolean; }; export type Statistic = { diff --git a/ui/src/domain/version.ts b/ui/src/domain/version.ts index 817537fa..9585a43e 100644 --- a/ui/src/domain/version.ts +++ b/ui/src/domain/version.ts @@ -1 +1 @@ -export const version = "Certimate v0.2.4"; +export const version = "Certimate v0.2.5"; diff --git a/ui/src/i18n/locales/en/nls.domain.json b/ui/src/i18n/locales/en/nls.domain.json index f0c9c9a9..74e5ee57 100644 --- a/ui/src/i18n/locales/en/nls.domain.json +++ b/ui/src/i18n/locales/en/nls.domain.json @@ -41,6 +41,9 @@ "domain.application.form.key_algorithm.placeholder": "Please select certificate key algorithm", "domain.application.form.timeout.label": "DNS Propagation Timeout (Seconds)", "domain.application.form.timeoue.placeholder": "Please enter maximum waiting time for DNS propagation", + "domain.application.form.disable_follow_cname.label": "Disable DNS CNAME following", + "domain.application.form.disable_follow_cname.tips": "This option will disable Acme DNS authentication CNAME follow. If you don't understand this option, just keep it by default. ", + "domain.application.form.disable_follow_cname.tips_link": "Learn more", "domain.application.unsaved.message": "Please save applyment configuration first", "domain.deployment.tab": "Deploy Settings", diff --git a/ui/src/i18n/locales/zh/nls.domain.json b/ui/src/i18n/locales/zh/nls.domain.json index 78b27332..197200a8 100644 --- a/ui/src/i18n/locales/zh/nls.domain.json +++ b/ui/src/i18n/locales/zh/nls.domain.json @@ -41,6 +41,9 @@ "domain.application.form.key_algorithm.placeholder": "请选择数字证书算法", "domain.application.form.timeout.label": "DNS 传播检查超时时间(单位:秒)", "domain.application.form.timeoue.placeholder": "请输入 DNS 传播检查超时时间", + "domain.application.form.disable_follow_cname.label": "禁用 DNS CNAME 跟随", + "domain.application.form.disable_follow_cname.tips": "该选项将禁用 Acme DNS 认证 CNAME 跟随,如果你不了解此选项保持默认即可,", + "domain.application.form.disable_follow_cname.tips_link": "了解更多", "domain.application.unsaved.message": "请先保存申请配置", "domain.deployment.tab": "部署配置", diff --git a/ui/src/lib/time.ts b/ui/src/lib/time.ts index fd77b7ba..27bcb552 100644 --- a/ui/src/lib/time.ts +++ b/ui/src/lib/time.ts @@ -21,6 +21,16 @@ export const getDate = (zuluTime: string) => { return time.split(" ")[0]; }; +export const getLeftDays = (zuluTime: string) => { + const time = convertZulu2Beijing(zuluTime); + const date = time.split(" ")[0]; + const now = new Date(); + const target = new Date(date); + const diff = target.getTime() - now.getTime(); + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + return days; +}; + export function getTimeBefore(days: number): string { // 获取当前时间 const currentDate = new Date(); @@ -66,3 +76,4 @@ export function getTimeAfter(days: number): string { return formattedDate; } + diff --git a/ui/src/pages/DashboardLayout.tsx b/ui/src/pages/DashboardLayout.tsx index 597587a4..b769d8f3 100644 --- a/ui/src/pages/DashboardLayout.tsx +++ b/ui/src/pages/DashboardLayout.tsx @@ -72,6 +72,10 @@ export default function Dashboard() {
+ +
+ +
@@ -117,6 +121,9 @@ export default function Dashboard() { {t("history.page.title")} +
+ +
@@ -137,8 +144,6 @@ export default function Dashboard() {
- -
@@ -146,3 +151,4 @@ export default function Dashboard() { ); } + diff --git a/ui/src/pages/LoginLayout.tsx b/ui/src/pages/LoginLayout.tsx index 5564c40e..9c738610 100644 --- a/ui/src/pages/LoginLayout.tsx +++ b/ui/src/pages/LoginLayout.tsx @@ -12,9 +12,10 @@ const LoginLayout = () => {
- +
); }; export default LoginLayout; + diff --git a/ui/src/pages/domains/Edit.tsx b/ui/src/pages/domains/Edit.tsx index d0a50c35..d882b5cc 100644 --- a/ui/src/pages/domains/Edit.tsx +++ b/ui/src/pages/domains/Edit.tsx @@ -4,7 +4,7 @@ import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import z from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import { ChevronsUpDown, Plus } from "lucide-react"; +import { ChevronsUpDown, Plus, CircleHelp } from "lucide-react"; import { ClientResponseError } from "pocketbase"; import { Button } from "@/components/ui/button"; @@ -26,6 +26,8 @@ import { EmailsSetting } from "@/domain/settings"; import { DeployConfig, Domain } from "@/domain/domain"; import { save, get } from "@/repository/domains"; import { useConfig } from "@/providers/config"; +import { Switch } from "@/components/ui/switch"; +import { TooltipFast } from "@/components/ui/tooltip"; const Edit = () => { const { @@ -64,6 +66,7 @@ const Edit = () => { keyAlgorithm: z.string().optional(), nameservers: z.string().optional(), timeout: z.number().optional(), + disableFollowCNAME: z.boolean().optional(), }); const form = useForm>({ @@ -76,6 +79,7 @@ const Edit = () => { keyAlgorithm: "RSA2048", nameservers: "", timeout: 60, + disableFollowCNAME: true, }, }); @@ -89,6 +93,7 @@ const Edit = () => { keyAlgorithm: domain.applyConfig?.keyAlgorithm, nameservers: domain.applyConfig?.nameservers, timeout: domain.applyConfig?.timeout, + disableFollowCNAME: domain.applyConfig?.disableFollowCNAME, }); } }, [domain, form]); @@ -108,6 +113,7 @@ const Edit = () => { keyAlgorithm: data.keyAlgorithm, nameservers: data.nameservers, timeout: data.timeout, + disableFollowCNAME: data.disableFollowCNAME, }, }; @@ -176,7 +182,7 @@ const Edit = () => { <>
-
+
@@ -190,7 +196,7 @@ const Edit = () => {
-
+
{ name="email" render={({ field }) => ( - +
{t("domain.application.form.email.label") + " " + t("domain.application.form.email.tips")}
+
{t("common.add")}
@@ -293,11 +299,11 @@ const Edit = () => { name="access" render={({ field }) => ( - +
{t("domain.application.form.access.label")}
+
{t("common.add")}
@@ -344,8 +350,8 @@ const Edit = () => {
- {t("domain.application.form.advanced_settings.label")} - + {t("domain.application.form.advanced_settings.label")} +
@@ -424,6 +430,49 @@ const Edit = () => {
)} /> + + {/* 禁用 CNAME 跟随 */} + ( + + +
+ {t("domain.application.form.disable_follow_cname.label")} + + {t("domain.application.form.disable_follow_cname.tips")} + + {t("domain.application.form.disable_follow_cname.tips_link")} + +

+ } + > + +
+
+
+ +
+ { + form.setValue(field.name, value); + }} + /> +
+
+ +
+ )} + />
diff --git a/ui/src/pages/domains/Home.tsx b/ui/src/pages/domains/Home.tsx index f59ed7b9..4e66714c 100644 --- a/ui/src/pages/domains/Home.tsx +++ b/ui/src/pages/domains/Home.tsx @@ -26,7 +26,7 @@ import { Toaster } from "@/components/ui/toaster"; import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip"; import { useToast } from "@/components/ui/use-toast"; import { CustomFile, saveFiles2ZIP } from "@/lib/file"; -import { convertZulu2Beijing, getDate } from "@/lib/time"; +import { convertZulu2Beijing, getDate, getLeftDays } from "@/lib/time"; import { Domain } from "@/domain/domain"; import { list, remove, save, subscribeId, unsubscribeId } from "@/repository/domains"; @@ -213,7 +213,7 @@ const Home = () => {
{domain.expiredAt ? ( <> -
{t("domain.props.expiry.date1", { date: 90 })}
+
{t("domain.props.expiry.date1", { date: `${getLeftDays(domain.expiredAt)}/90` })}
{t("domain.props.expiry.date2", { date: getDate(domain.expiredAt), @@ -340,3 +340,4 @@ const Home = () => { }; export default Home; +