diff --git a/README.md b/README.md
index dbfcd1ee..e295adbb 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,7 @@ make local.run
| AWS | √ | | 可签发在 AWS Route53 托管的域名 |
| CloudFlare | √ | | 可签发在 CloudFlare 注册的域名;CloudFlare 服务自带 SSL 证书 |
| GoDaddy | √ | | 可签发在 GoDaddy 注册的域名 |
+| Name.com | √ | | 可签发在 Name.com 注册的域名 |
| NameSilo | √ | | 可签发在 NameSilo 注册的域名 |
| PowerDNS | √ | | 可签发在 PowerDNS 托管的域名 |
| HTTP 请求 | √ | | 可签发允许通过 HTTP 请求修改 DNS 的域名 |
diff --git a/README_EN.md b/README_EN.md
index 88e03baf..181f5190 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -87,6 +87,7 @@ password:1234567890
| AWS | √ | | Supports domains managed on AWS Route53 |
| CloudFlare | √ | | Supports domains registered on CloudFlare; CloudFlare services come with SSL certificates |
| GoDaddy | √ | | Supports domains registered on GoDaddy |
+| Name.com | √ | | Supports domains registered on Name.com |
| NameSilo | √ | | Supports domains registered on NameSilo |
| PowerDNS | √ | | Supports domains managed on PowerDNS |
| HTTP Request | √ | | Supports domains which allow managing DNS by HTTP request |
diff --git a/go.mod b/go.mod
index 9433acba..8fa0f6a6 100644
--- a/go.mod
+++ b/go.mod
@@ -63,6 +63,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
diff --git a/go.sum b/go.sum
index 4549f32b..abf63404 100644
--- a/go.sum
+++ b/go.sum
@@ -640,6 +640,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
+github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY=
github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g=
diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go
index c4804559..d2056863 100644
--- a/internal/applicant/applicant.go
+++ b/internal/applicant/applicant.go
@@ -39,6 +39,7 @@ const (
configTypeCloudflare = "cloudflare"
configTypeGoDaddy = "godaddy"
configTypeHuaweiCloud = "huaweicloud"
+ configTypeNameDotCom = "namedotcom"
configTypeNameSilo = "namesilo"
configTypePowerDNS = "powerdns"
configTypeTencentCloud = "tencentcloud"
@@ -225,6 +226,8 @@ func GetWithTypeOption(t string, option *ApplyOption) (Applicant, error) {
return NewGoDaddyApplicant(option), nil
case configTypeHuaweiCloud:
return NewHuaweiCloudApplicant(option), nil
+ case configTypeNameDotCom:
+ return NewNameDotComApplicant(option), nil
case configTypeNameSilo:
return NewNamesiloApplicant(option), nil
case configTypePowerDNS:
diff --git a/internal/applicant/namedotcom.go b/internal/applicant/namedotcom.go
new file mode 100644
index 00000000..5bcc39f2
--- /dev/null
+++ b/internal/applicant/namedotcom.go
@@ -0,0 +1,38 @@
+package applicant
+
+import (
+ "encoding/json"
+ "time"
+
+ "github.com/go-acme/lego/v4/providers/dns/namedotcom"
+ "github.com/usual2970/certimate/internal/domain"
+)
+
+type nameDotComApplicant struct {
+ option *ApplyOption
+}
+
+func NewNameDotComApplicant(option *ApplyOption) Applicant {
+ return &nameDotComApplicant{
+ option: option,
+ }
+}
+
+func (a *nameDotComApplicant) Apply() (*Certificate, error) {
+ access := &domain.NameDotComAccess{}
+ json.Unmarshal([]byte(a.option.Access), access)
+
+ config := namedotcom.NewDefaultConfig()
+ config.Username = access.Username
+ config.APIToken = access.ApiToken
+ if a.option.Timeout != 0 {
+ config.PropagationTimeout = time.Duration(a.option.Timeout) * time.Second
+ }
+
+ provider, err := namedotcom.NewDNSProviderConfig(config)
+ if err != nil {
+ return nil, err
+ }
+
+ return apply(a.option, provider)
+}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index f040c5ca..348e6c46 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -77,6 +77,11 @@ type GodaddyAccess struct {
ApiSecret string `json:"apiSecret"`
}
+type NameDotComAccess struct {
+ Username string `json:"username"`
+ ApiToken string `json:"apiToken"`
+}
+
type PdnsAccess struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
diff --git a/migrations/1735032595_add_namedotcom.go b/migrations/1735032595_add_namedotcom.go
new file mode 100644
index 00000000..b464a93c
--- /dev/null
+++ b/migrations/1735032595_add_namedotcom.go
@@ -0,0 +1,109 @@
+package migrations
+
+import (
+ "encoding/json"
+
+ "github.com/pocketbase/dbx"
+ "github.com/pocketbase/pocketbase/daos"
+ m "github.com/pocketbase/pocketbase/migrations"
+ "github.com/pocketbase/pocketbase/models/schema"
+)
+
+func init() {
+ m.Register(func(db dbx.Builder) error {
+ dao := daos.New(db)
+
+ collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
+ if err != nil {
+ return err
+ }
+
+ // update
+ edit_configType := &schema.SchemaField{}
+ if err := json.Unmarshal([]byte(`{
+ "system": false,
+ "id": "hwy7m03o",
+ "name": "configType",
+ "type": "select",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "maxSelect": 1,
+ "values": [
+ "aliyun",
+ "tencent",
+ "huaweicloud",
+ "qiniu",
+ "aws",
+ "cloudflare",
+ "namesilo",
+ "godaddy",
+ "pdns",
+ "httpreq",
+ "local",
+ "ssh",
+ "webhook",
+ "k8s",
+ "baiducloud",
+ "dogecloud",
+ "volcengine",
+ "byteplus",
+ "namedotcom"
+ ]
+ }
+ }`), edit_configType); err != nil {
+ return err
+ }
+ collection.Schema.AddField(edit_configType)
+
+ return dao.SaveCollection(collection)
+ }, func(db dbx.Builder) error {
+ dao := daos.New(db)
+
+ collection, err := dao.FindCollectionByNameOrId("4yzbv8urny5ja1e")
+ if err != nil {
+ return err
+ }
+
+ // update
+ edit_configType := &schema.SchemaField{}
+ if err := json.Unmarshal([]byte(`{
+ "system": false,
+ "id": "hwy7m03o",
+ "name": "configType",
+ "type": "select",
+ "required": false,
+ "presentable": false,
+ "unique": false,
+ "options": {
+ "maxSelect": 1,
+ "values": [
+ "aliyun",
+ "tencent",
+ "huaweicloud",
+ "qiniu",
+ "aws",
+ "cloudflare",
+ "namesilo",
+ "godaddy",
+ "pdns",
+ "httpreq",
+ "local",
+ "ssh",
+ "webhook",
+ "k8s",
+ "baiducloud",
+ "dogecloud",
+ "volcengine",
+ "byteplus"
+ ]
+ }
+ }`), edit_configType); err != nil {
+ return err
+ }
+ collection.Schema.AddField(edit_configType)
+
+ return dao.SaveCollection(collection)
+ })
+}
diff --git a/ui/public/imgs/providers/namedotcom.svg b/ui/public/imgs/providers/namedotcom.svg
new file mode 100644
index 00000000..2b7c7131
--- /dev/null
+++ b/ui/public/imgs/providers/namedotcom.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/AccessEditForm.tsx b/ui/src/components/access/AccessEditForm.tsx
index 01f182d1..511c28f4 100644
--- a/ui/src/components/access/AccessEditForm.tsx
+++ b/ui/src/components/access/AccessEditForm.tsx
@@ -19,6 +19,7 @@ import AccessEditFormGoDaddyConfig from "./AccessEditFormGoDaddyConfig";
import AccessEditFormHuaweiCloudConfig from "./AccessEditFormHuaweiCloudConfig";
import AccessEditFormKubernetesConfig from "./AccessEditFormKubernetesConfig";
import AccessEditFormLocalConfig from "./AccessEditFormLocalConfig";
+import AccessEditFormNameDotComConfig from "./AccessEditFormNameDotComConfig";
import AccessEditFormNameSiloConfig from "./AccessEditFormNameSiloConfig";
import AccessEditFormPowerDNSConfig from "./AccessEditFormPowerDNSConfig";
import AccessEditFormQiniuConfig from "./AccessEditFormQiniuConfig";
@@ -98,6 +99,8 @@ const AccessEditForm = forwardRef((
return ;
case ACCESS_PROVIDER_TYPES.LOCAL:
return ;
+ case ACCESS_PROVIDER_TYPES.NAMEDOTCOM:
+ return ;
case ACCESS_PROVIDER_TYPES.NAMESILO:
return ;
case ACCESS_PROVIDER_TYPES.POWERDNS:
diff --git a/ui/src/components/access/AccessEditFormNameDotComConfig.tsx b/ui/src/components/access/AccessEditFormNameDotComConfig.tsx
new file mode 100644
index 00000000..5b810ce8
--- /dev/null
+++ b/ui/src/components/access/AccessEditFormNameDotComConfig.tsx
@@ -0,0 +1,77 @@
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useDeepCompareEffect } from "ahooks";
+import { Form, Input, type FormInstance } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type NameDotComAccessConfig } from "@/domain/access";
+
+type AccessEditFormNameDotComConfigModelType = Partial;
+
+export type AccessEditFormNameDotComConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ loading?: boolean;
+ model?: AccessEditFormNameDotComConfigModelType;
+ onModelChange?: (model: AccessEditFormNameDotComConfigModelType) => void;
+};
+
+const initModel = () => {
+ return {
+ username: "",
+ apiToken: "",
+ } as AccessEditFormNameDotComConfigModelType;
+};
+
+const AccessEditFormNameDotComConfig = ({ form, formName, disabled, loading, model, onModelChange }: AccessEditFormNameDotComConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ username: z
+ .string()
+ .trim()
+ .min(1, t("access.form.namedotcom_username.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 })),
+ apiToken: z
+ .string()
+ .trim()
+ .min(1, t("access.form.namedotcom_api_token.placeholder"))
+ .max(64, t("common.errmsg.string_max", { max: 64 })),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const [initialValues, setInitialValues] = useState>>(model ?? initModel());
+ useDeepCompareEffect(() => {
+ setInitialValues(model ?? initModel());
+ }, [model]);
+
+ const handleFormChange = (_: unknown, fields: AccessEditFormNameDotComConfigModelType) => {
+ onModelChange?.(fields);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessEditFormNameDotComConfig;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index 9e08fa1e..23adc541 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -13,6 +13,7 @@ export const ACCESS_PROVIDER_TYPE_GODADDY = "godaddy" as const;
export const ACCESS_PROVIDER_TYPE_HUAWEICLOUD = "huaweicloud" as const;
export const ACCESS_PROVIDER_TYPE_KUBERNETES = "k8s" as const;
export const ACCESS_PROVIDER_TYPE_LOCAL = "local" as const;
+export const ACCESS_PROVIDER_TYPE_NAMEDOTCOM = "namedotcom" as const;
export const ACCESS_PROVIDER_TYPE_NAMESILO = "namesilo" as const;
export const ACCESS_PROVIDER_TYPE_POWERDNS = "powerdns" as const;
export const ACCESS_PROVIDER_TYPE_QINIU = "qiniu" as const;
@@ -32,6 +33,7 @@ export const ACCESS_PROVIDER_TYPES = Object.freeze({
HUAWEICLOUD: ACCESS_PROVIDER_TYPE_HUAWEICLOUD,
KUBERNETES: ACCESS_PROVIDER_TYPE_KUBERNETES,
LOCAL: ACCESS_PROVIDER_TYPE_LOCAL,
+ NAMEDOTCOM: ACCESS_PROVIDER_TYPE_NAMEDOTCOM,
NAMESILO: ACCESS_PROVIDER_TYPE_NAMESILO,
POWERDNS: ACCESS_PROVIDER_TYPE_POWERDNS,
QINIU: ACCESS_PROVIDER_TYPE_QINIU,
@@ -75,6 +77,7 @@ export interface AccessModel extends BaseModel {
| HuaweiCloudAccessConfig
| KubernetesAccessConfig
| LocalAccessConfig
+ | NameDotComAccessConfig
| NameSiloAccessConfig
| PowerDNSAccessConfig
| QiniuAccessConfig
@@ -142,6 +145,11 @@ export type KubernetesAccessConfig = {
export type LocalAccessConfig = NonNullable;
+export type NameDotComAccessConfig = {
+ username: string;
+ apiToken: string;
+};
+
export type NameSiloAccessConfig = {
apiKey: string;
};
@@ -194,6 +202,9 @@ export const accessProvidersMap: Map = n
NOTICE: The following order determines the order displayed at the frontend.
*/
[
+ [ACCESS_PROVIDER_TYPE_LOCAL, "common.provider.local", "/imgs/providers/local.svg", "deploy"],
+ [ACCESS_PROVIDER_TYPE_SSH, "common.provider.ssh", "/imgs/providers/ssh.svg", "deploy"],
+ [ACCESS_PROVIDER_TYPE_WEBHOOK, "common.provider.webhook", "/imgs/providers/webhook.svg", "deploy"],
[ACCESS_PROVIDER_TYPE_ALIYUN, "common.provider.aliyun", "/imgs/providers/aliyun.svg", "all"],
[ACCESS_PROVIDER_TYPE_TENCENTCLOUD, "common.provider.tencentcloud", "/imgs/providers/tencentcloud.svg", "all"],
[ACCESS_PROVIDER_TYPE_HUAWEICLOUD, "common.provider.huaweicloud", "/imgs/providers/huaweicloud.svg", "all"],
@@ -204,12 +215,10 @@ export const accessProvidersMap: Map = n
[ACCESS_PROVIDER_TYPE_BYTEPLUS, "common.provider.byteplus", "/imgs/providers/byteplus.svg", "all"],
[ACCESS_PROVIDER_TYPE_AWS, "common.provider.aws", "/imgs/providers/aws.svg", "apply"],
[ACCESS_PROVIDER_TYPE_CLOUDFLARE, "common.provider.cloudflare", "/imgs/providers/cloudflare.svg", "apply"],
+ [ACCESS_PROVIDER_TYPE_NAMEDOTCOM, "common.provider.namedotcom", "/imgs/providers/namedotcom.svg", "apply"],
[ACCESS_PROVIDER_TYPE_NAMESILO, "common.provider.namesilo", "/imgs/providers/namesilo.svg", "apply"],
[ACCESS_PROVIDER_TYPE_GODADDY, "common.provider.godaddy", "/imgs/providers/godaddy.svg", "apply"],
[ACCESS_PROVIDER_TYPE_POWERDNS, "common.provider.powerdns", "/imgs/providers/powerdns.svg", "apply"],
- [ACCESS_PROVIDER_TYPE_LOCAL, "common.provider.local", "/imgs/providers/local.svg", "deploy"],
- [ACCESS_PROVIDER_TYPE_SSH, "common.provider.ssh", "/imgs/providers/ssh.svg", "deploy"],
- [ACCESS_PROVIDER_TYPE_WEBHOOK, "common.provider.webhook", "/imgs/providers/webhook.svg", "deploy"],
[ACCESS_PROVIDER_TYPE_KUBERNETES, "common.provider.kubernetes", "/imgs/providers/kubernetes.svg", "deploy"],
[ACCESS_PROVIDER_TYPE_ACMEHTTPREQ, "common.provider.acmehttpreq", "/imgs/providers/acmehttpreq.svg", "apply"],
].map(([type, name, icon, usage]) => [type, { type, name, icon, usage: usage as AccessProviderUsages }])
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json
index e24657d1..0d00395a 100644
--- a/ui/src/i18n/locales/en/nls.access.json
+++ b/ui/src/i18n/locales/en/nls.access.json
@@ -91,6 +91,12 @@
"access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file",
"access.form.k8s_kubeconfig.upload": "Choose File ...",
"access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
Leave blank to use the Pod's ServiceAccount.",
+ "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",
+ "access.form.namedotcom_api_token.label": "Name.com API Token",
+ "access.form.namedotcom_api_token.placeholder": "Please enter Name.com API Token",
+ "access.form.namedotcom_api_token.tooltip": "For more information, see https://www.name.com/support/articles/31142639244819-how-to-manage-your-api-tokens",
"access.form.namesilo_api_key.label": "NameSilo API Key",
"access.form.namesilo_api_key.placeholder": "Please enter NameSilo API Key",
"access.form.namesilo_api_key.tooltip": "For more information, see https://www.namesilo.com/support/v2/articles/account-options/api-manager",
diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json
index 3cc8d1ba..3356ff58 100644
--- a/ui/src/i18n/locales/en/nls.common.json
+++ b/ui/src/i18n/locales/en/nls.common.json
@@ -71,6 +71,7 @@
"common.provider.kubernetes": "Kubernetes",
"common.provider.kubernetes.secret": "Kubernetes - Secret",
"common.provider.local": "Local Deployment",
+ "common.provider.namedotcom": "Name.com",
"common.provider.namesilo": "NameSilo",
"common.provider.powerdns": "PowerDNS",
"common.provider.qiniu": "Qiniu",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index ea4ecafb..0de78b3c 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -91,6 +91,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.namedotcom_username.label": "Name.com 用户名",
+ "access.form.namedotcom_username.placeholder": "请输入 Name.com 用户名",
+ "access.form.namedotcom_username.tooltip": "这是什么?请参阅 https://www.name.com/account/settings/api",
+ "access.form.namedotcom_api_token.label": "Name.com API Token",
+ "access.form.namedotcom_api_token.placeholder": "请输入 Name.com API Token",
+ "access.form.namedotcom_api_token.tooltip": "这是什么?请参阅 https://www.name.com/support/articles/31142639244819-how-to-manage-your-api-tokens",
"access.form.namesilo_api_key.label": "NameSilo API Key",
"access.form.namesilo_api_key.placeholder": "请输入 NameSilo API Key",
"access.form.namesilo_api_key.tooltip": "这是什么?请参阅 https://www.namesilo.com/support/v2/articles/account-options/api-manager",
diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json
index fd707ff2..30140399 100644
--- a/ui/src/i18n/locales/zh/nls.common.json
+++ b/ui/src/i18n/locales/zh/nls.common.json
@@ -71,6 +71,7 @@
"common.provider.kubernetes": "Kubernetes",
"common.provider.kubernetes.secret": "Kubernetes - Secret",
"common.provider.local": "本地部署",
+ "common.provider.namedotcom": "Name.com",
"common.provider.namesilo": "NameSilo",
"common.provider.powerdns": "PowerDNS",
"common.provider.qiniu": "七牛云",