diff --git a/README.md b/README.md
index 10987477..1bcf2034 100644
--- a/README.md
+++ b/README.md
@@ -102,6 +102,7 @@ make local.run
| [GNAME](https://www.gname.com/) | |
| [GoDaddy](https://www.godaddy.com/) | |
| [Name.com](https://www.name.com/) | |
+| [Namecheap](https://www.namecheap.com/) | |
| [NameSilo](https://www.namesilo.com/) | |
| [IBM NS1 Connect](https://www.ibm.com/cn-zh/products/ns1-connect/) | |
| [移动云](https://ecloud.10086.cn/) | |
diff --git a/README_EN.md b/README_EN.md
index bd58f8bb..2e850330 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -101,6 +101,7 @@ The following DNS providers are supported:
| [GNAME](https://www.gname.com/) | |
| [GoDaddy](https://www.godaddy.com/) | |
| [Name.com](https://www.name.com/) | |
+| [Namecheap](https://www.namecheap.com/) | |
| [NameSilo](https://www.namesilo.com/) | |
| [IBM NS1 Connect](https://www.ibm.com/products/ns1-connect/) | |
| [CMCC Cloud](https://ecloud.10086.cn/) | |
diff --git a/go.mod b/go.mod
index 5694ec54..40efa5a4 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.10
github.com/baidubce/bce-sdk-go v0.9.217
github.com/byteplus-sdk/byteplus-sdk-golang v1.0.41
- github.com/go-acme/lego/v4 v4.21.0
+ github.com/go-acme/lego/v4 v4.22.2
github.com/go-resty/resty/v2 v2.16.5
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.136
@@ -215,4 +215,5 @@ require (
)
replace gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkcore@v1.0.0
+
replace gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 => ./internal/pkg/vendors/cmcc-sdk/ecloudsdkclouddns@v1.0.1
diff --git a/go.sum b/go.sum
index e85c4f8d..d15c396f 100644
--- a/go.sum
+++ b/go.sum
@@ -376,6 +376,8 @@ github.com/ganigeorgiev/fexpr v0.4.1/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego/v4 v4.21.0 h1:arEW+8o5p7VI8Bk1kr/PDlgD1DrxtTH1gJ4b7mehL8o=
github.com/go-acme/lego/v4 v4.21.0/go.mod h1:HrSWzm3Ckj45Ie3i+p1zKVobbQoMOaGu9m4up0dUeDI=
+github.com/go-acme/lego/v4 v4.22.2 h1:ck+HllWrV/rZGeYohsKQ5iKNnU/WAZxwOdiu6cxky+0=
+github.com/go-acme/lego/v4 v4.22.2/go.mod h1:E2FndyI3Ekv0usNJt46mFb9LVpV/XBYT+4E3tz02Tzo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go
index 9989da8f..ef013490 100644
--- a/internal/applicant/providers.go
+++ b/internal/applicant/providers.go
@@ -19,6 +19,7 @@ import (
pGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy"
pHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud"
pJDCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud"
+ pNamecheap "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap"
pNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom"
pNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo"
pNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1"
@@ -249,6 +250,22 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
+ case domain.ApplyDNSProviderTypeNamecheap:
+ {
+ access := domain.AccessConfigForNamecheap{}
+ if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ applicant, err := pNamecheap.NewChallengeProvider(&pNamecheap.ChallengeProviderConfig{
+ Username: access.Username,
+ ApiKey: access.ApiKey,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
+
case domain.ApplyDNSProviderTypeNameDotCom:
{
access := domain.AccessConfigForNameDotCom{}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index fd01af3b..4b693522 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -131,6 +131,11 @@ type AccessConfigForKubernetes struct {
type AccessConfigForLocal struct{}
+type AccessConfigForNamecheap struct {
+ Username string `json:"username"`
+ ApiKey string `json:"apiKey"`
+}
+
type AccessConfigForNameDotCom struct {
Username string `json:"username"`
ApiToken string `json:"apiToken"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 375176ce..f0949e03 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -26,6 +26,7 @@ const (
AccessProviderTypeCMCCCloud = AccessProviderType("cmcccloud")
AccessProviderTypeCTCCCloud = AccessProviderType("ctcccloud") // 联通云(预留)
AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留)
+ AccessProviderTypeDNSLA = AccessProviderType("dnsla") // DNS.LA(预留)
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
AccessProviderTypeEdgio = AccessProviderType("edgio")
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留)
@@ -37,6 +38,7 @@ const (
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
AccessProviderTypeKubernetes = AccessProviderType("k8s")
AccessProviderTypeLocal = AccessProviderType("local")
+ AccessProviderTypeNamecheap = AccessProviderType("namecheap")
AccessProviderTypeNameDotCom = AccessProviderType("namedotcom")
AccessProviderTypeNameSilo = AccessProviderType("namesilo")
AccessProviderTypeNS1 = AccessProviderType("ns1")
@@ -82,6 +84,7 @@ const (
ApplyDNSProviderTypeHuaweiCloudDNS = ApplyDNSProviderType("huaweicloud-dns")
ApplyDNSProviderTypeJDCloud = ApplyDNSProviderType("jdcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeJDCloudDNS]
ApplyDNSProviderTypeJDCloudDNS = ApplyDNSProviderType("jdcloud-dns")
+ ApplyDNSProviderTypeNamecheap = ApplyDNSProviderType("namecheap")
ApplyDNSProviderTypeNameDotCom = ApplyDNSProviderType("namedotcom")
ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo")
ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1")
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap/namecheap.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap/namecheap.go
new file mode 100644
index 00000000..9bf2f3c3
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap/namecheap.go
@@ -0,0 +1,38 @@
+package namedotcom
+
+import (
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/providers/dns/namecheap"
+)
+
+type ChallengeProviderConfig struct {
+ Username string `json:"username"`
+ ApiKey string `json:"apiKey"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
+ if config == nil {
+ panic("config is nil")
+ }
+
+ providerConfig := namecheap.NewDefaultConfig()
+ providerConfig.APIUser = config.Username
+ providerConfig.APIKey = config.ApiKey
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = int(config.DnsTTL)
+ }
+
+ provider, err := namecheap.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/migrations/1739462400_collections_snapshot.go b/migrations/1739462400_collections_snapshot.go
index 9c079405..67453954 100644
--- a/migrations/1739462400_collections_snapshot.go
+++ b/migrations/1739462400_collections_snapshot.go
@@ -65,6 +65,7 @@ func init() {
"cmcccloud",
"ctcccloud",
"cucccloud",
+ "dnsla",
"dogecloud",
"edgio",
"fastly",
@@ -76,6 +77,7 @@ func init() {
"jdcloud",
"k8s",
"local",
+ "namecheap",
"namedotcom",
"namesilo",
"ns1",
@@ -173,6 +175,7 @@ func init() {
"cmcccloud",
"ctcccloud",
"cucccloud",
+ "dnsla",
"dogecloud",
"edgio",
"fastly",
@@ -184,6 +187,7 @@ func init() {
"jdcloud",
"k8s",
"local",
+ "namecheap",
"namedotcom",
"namesilo",
"ns1",
diff --git a/ui/public/imgs/providers/namecheap.svg b/ui/public/imgs/providers/namecheap.svg
new file mode 100644
index 00000000..e90dbfe9
--- /dev/null
+++ b/ui/public/imgs/providers/namecheap.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index 90bb6ff5..681d80f1 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -31,6 +31,7 @@ import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig";
import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig";
import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig";
import AccessFormLocalConfig from "./AccessFormLocalConfig";
+import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig";
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
import AccessFormNS1Config from "./AccessFormNS1Config";
@@ -141,6 +142,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.LOCAL:
return ;
+ case ACCESS_PROVIDERS.NAMECHEAP:
+ return ;
case ACCESS_PROVIDERS.NAMEDOTCOM:
return ;
case ACCESS_PROVIDERS.NAMESILO:
diff --git a/ui/src/components/access/AccessFormNamecheapConfig.tsx b/ui/src/components/access/AccessFormNamecheapConfig.tsx
new file mode 100644
index 00000000..d6a79f2a
--- /dev/null
+++ b/ui/src/components/access/AccessFormNamecheapConfig.tsx
@@ -0,0 +1,76 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type AccessConfigForNamecheap } from "@/domain/access";
+
+type AccessFormNamecheapConfigFieldValues = Nullish;
+
+export type AccessFormNamecheapConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormNamecheapConfigFieldValues;
+ onValuesChange?: (values: AccessFormNamecheapConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormNamecheapConfigFieldValues => {
+ return {
+ username: "",
+ apiKey: "",
+ };
+};
+
+const AccessFormNamecheapConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormNamecheapConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ username: z
+ .string()
+ .min(1, t("access.form.namecheap_username.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ apiKey: z
+ .string()
+ .min(1, t("access.form.namecheap_api_key.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 }))
+ .trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormNamecheapConfig;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index da8739b7..a1e14d86 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -28,6 +28,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForJDCloud
| AccessConfigForKubernetes
| AccessConfigForLocal
+ | AccessConfigForNamecheap
| AccessConfigForNameDotCom
| AccessConfigForNameSilo
| AccessConfigForPowerDNS
@@ -151,6 +152,11 @@ export type AccessConfigForKubernetes = {
export type AccessConfigForLocal = NonNullable;
+export type AccessConfigForNamecheap = {
+ username: string;
+ apiKey: string;
+};
+
export type AccessConfigForNameDotCom = {
username: string;
apiToken: string;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index e99abb5c..3a806b88 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -26,6 +26,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
JDCLOUD: "jdcloud",
KUBERNETES: "k8s",
LOCAL: "local",
+ NAMECHEAP: "namecheap",
NAMEDOTCOM: "namedotcom",
NAMESILO: "namesilo",
NS1: "ns1",
@@ -92,6 +93,7 @@ export const accessProvidersMap: Maphttps://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
Leave it blank to use the Pod's ServiceAccount.",
+ "access.form.namecheap_username.label": "Namecheap username",
+ "access.form.namecheap_username.placeholder": "Please enter Namecheap username",
+ "access.form.namecheap_username.tooltip": "For more information, see https://www.namecheap.com/support/api/intro/",
+ "access.form.namecheap_api_key.label": "Namecheap API key",
+ "access.form.namecheap_api_key.placeholder": "Please enter Namecheap API key",
+ "access.form.namecheap_api_key.tooltip": "For more information, see https://www.namecheap.com/support/api/intro/",
"access.form.namedotcom_username.label": "Name.com username",
"access.form.namedotcom_username.placeholder": "Please enter Name.com username",
"access.form.namedotcom_username.tooltip": "For more information, see https://www.name.com/account/settings/api",
diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json
index f0b13069..e924b43a 100644
--- a/ui/src/i18n/locales/en/nls.provider.json
+++ b/ui/src/i18n/locales/en/nls.provider.json
@@ -38,6 +38,7 @@
"provider.cmcccloud": "China Mobile Cloud (ECloud)",
"provider.ctcccloud": "China Telecom Cloud (State Cloud)",
"provider.cucccloud": "China Unicom Cloud",
+ "provider.dnsla": "DNS.LA",
"provider.dogecloud": "Doge Cloud",
"provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)",
"provider.edgio": "Edgio",
@@ -63,6 +64,7 @@
"provider.kubernetes": "Kubernetes",
"provider.kubernetes.secret": "Kubernetes - Secret",
"provider.local": "Local deployment",
+ "provider.namecheap": "Namecheap",
"provider.namedotcom": "Name.com",
"provider.namesilo": "NameSilo",
"provider.ns1": "NS1 (IBM NS1 Connect)",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index f4648c4d..cb9befa4 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -149,6 +149,12 @@
"access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件",
"access.form.k8s_kubeconfig.upload": "选择文件",
"access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/
为空时,将使用 Pod 的 ServiceAccount 作为凭证。",
+ "access.form.namecheap_username.label": "Namecheap 用户名",
+ "access.form.namecheap_username.placeholder": "请输入 Namecheap 用户名",
+ "access.form.namecheap_username.tooltip": "这是什么?请参阅 https://www.namecheap.com/support/api/intro/",
+ "access.form.namecheap_api_key.label": "Namecheap API Key",
+ "access.form.namecheap_api_key.placeholder": "请输入 Namecheap API Key",
+ "access.form.namecheap_api_key.tooltip": "这是什么?请参阅 https://www.namecheap.com/support/api/intro/",
"access.form.namedotcom_username.label": "Name.com 用户名",
"access.form.namedotcom_username.placeholder": "请输入 Name.com 用户名",
"access.form.namedotcom_username.tooltip": "这是什么?请参阅 https://www.name.com/account/settings/api",
diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json
index 101b0553..f8d36830 100644
--- a/ui/src/i18n/locales/zh/nls.provider.json
+++ b/ui/src/i18n/locales/zh/nls.provider.json
@@ -38,6 +38,7 @@
"provider.cmcccloud": "移动云",
"provider.ctcccloud": "联通云",
"provider.cucccloud": "天翼云",
+ "provider.dnsla": "DNS.LA",
"provider.dogecloud": "多吉云",
"provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN",
"provider.edgio": "Edgio",
@@ -63,6 +64,7 @@
"provider.kubernetes": "Kubernetes",
"provider.kubernetes.secret": "Kubernetes - Secret",
"provider.local": "本地部署",
+ "provider.namecheap": "Namecheap",
"provider.namedotcom": "Name.com",
"provider.namesilo": "NameSilo",
"provider.ns1": "NS1(IBM NS1 Connect)",