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")}
+
@@ -293,11 +299,11 @@ const Edit = () => {
name="access"
render={({ field }) => (
-
+
{t("domain.application.form.access.label")}
+
@@ -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 跟随 */}
+ (
+
+
+
+
+
+
+ {
+ 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;
+