Merge pull request #394 from RangerCD/feat-name-dot-com

feat(provider): add name.com
This commit is contained in:
Yoan.liu 2024-12-25 15:35:20 +08:00 committed by GitHub
commit 1f602c00be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 262 additions and 0 deletions

View File

@ -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 的域名 |

View File

@ -87,6 +87,7 @@ password1234567890
| 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 |

1
go.mod
View File

@ -64,6 +64,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

2
go.sum
View File

@ -620,6 +620,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=

View File

@ -39,6 +39,7 @@ const (
configTypeCloudflare = "cloudflare"
configTypeGoDaddy = "godaddy"
configTypeHuaweiCloud = "huaweicloud"
configTypeNameDotCom = "namedotcom"
configTypeNameSilo = "namesilo"
configTypePowerDNS = "powerdns"
configTypeTencentCloud = "tencentcloud"
@ -219,6 +220,8 @@ func GetWithTypeOption(t string, option *ApplyOption) (Applicant, error) {
return NewAws(option), nil
case configTypeCloudflare:
return NewCloudflare(option), nil
case configTypeNameDotCom:
return NewNameDotCom(option), nil
case configTypeNameSilo:
return NewNamesilo(option), nil
case configTypeGoDaddy:

View File

@ -0,0 +1,36 @@
package applicant
import (
"encoding/json"
"time"
"github.com/go-acme/lego/v4/providers/dns/namedotcom"
"github.com/usual2970/certimate/internal/domain"
)
type nameDotCom struct {
option *ApplyOption
}
func NewNameDotCom(option *ApplyOption) Applicant {
return &nameDotCom{
option: option,
}
}
func (n *nameDotCom) Apply() (*Certificate, error) {
access := &domain.NameDotComAccess{}
json.Unmarshal([]byte(n.option.Access), access)
config := namedotcom.NewDefaultConfig()
config.Username = access.Username
config.APIToken = access.ApiToken
config.PropagationTimeout = time.Duration(n.option.Timeout) * time.Second
dnsProvider, err := namedotcom.NewDNSProviderConfig(config)
if err != nil {
return nil, err
}
return apply(n.option, dnsProvider)
}

View File

@ -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"`

View File

@ -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)
})
}

View File

@ -0,0 +1 @@
<svg enable-background="new 0 0 180 180" viewBox="0 0 180 180" width="200" height="200" xmlns="http://www.w3.org/2000/svg"><path d="m90.1 0c49.7 0 89.9 40.2 89.9 89.9s-40.2 89.9-89.9 89.9-90-40.1-90-89.9c0-49.6 40.3-89.9 90-89.9z" fill="#282828"/><path d="m39.1 121.1v-61.4l18-4.3-2.8 19.8h1.9c1.1-4.6 2.9-8.5 5.2-11.6 2.5-3.2 5.5-5.6 9.2-7.2 3.8-1.7 8.2-2.6 13.2-2.6 6.5 0 12.1 1.4 16.8 4.4 4.7 2.9 8.3 7 10.8 12.6 2.6 5.4 3.9 11.9 3.9 19.5v30.8h-17.7v-26.3c0-5.1-.8-9.4-2.3-13-1.6-3.6-3.9-6.3-6.9-8.2-3.1-1.9-6.6-2.9-10.9-2.9-6.3 0-11.3 2-14.9 6.2-3.6 4.1-5.3 10-5.3 17.7v26.4h-18.2z" fill="#fff"/><circle cx="137.3" cy="110.4" fill="#6eda78" r="12.4"/></svg>

After

Width:  |  Height:  |  Size: 663 B

View File

@ -18,6 +18,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";
@ -101,6 +102,8 @@ const AccessEditForm = forwardRef<AccessEditFormInstance, AccessEditFormProps>((
return <AccessEditFormKubernetesConfig {...configFormProps} />;
case ACCESS_PROVIDER_TYPES.LOCAL:
return <AccessEditFormLocalConfig {...configFormProps} />;
case ACCESS_PROVIDER_TYPES.NAMEDOTCOM:
return <AccessEditFormNameDotComConfig {...configFormProps} />;
case ACCESS_PROVIDER_TYPES.NAMESILO:
return <AccessEditFormNameSiloConfig {...configFormProps} />;
case ACCESS_PROVIDER_TYPES.POWERDNS:

View File

@ -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<NameDotComAccessConfig>;
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<Partial<z.infer<typeof formSchema>>>(model ?? initModel());
useDeepCompareEffect(() => {
setInitialValues(model ?? initModel());
}, [model]);
const handleFormChange = (_: unknown, fields: AccessEditFormNameDotComConfigModelType) => {
onModelChange?.(fields);
};
return (
<Form form={form} disabled={loading || disabled} initialValues={initialValues} layout="vertical" name={formName} onValuesChange={handleFormChange}>
<Form.Item
name="username"
label={t("access.form.namedotcom_username.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.namedotcom_username.tooltip") }}></span>}
>
<Input autoComplete="new-password" placeholder={t("access.form.namedotcom_username.placeholder")} />
</Form.Item>
<Form.Item
name="apiToken"
label={t("access.form.namedotcom_api_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.namedotcom_api_token.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.namedotcom_api_token.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessEditFormNameDotComConfig;

View File

@ -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<unknown>;
export type NameDotComAccessConfig = {
username: string;
apiToken: string;
};
export type NameSiloAccessConfig = {
apiKey: string;
};
@ -204,6 +212,7 @@ export const accessProvidersMap: Map<AccessProvider["type"], AccessProvider> = 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"],

View File

@ -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 <a href=\"https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>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 <a href=\"https://www.name.com/account/settings/api\" target=\"_blank\">https://www.name.com/account/settings/api</a>",
"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 <a href=\"https://www.name.com/account/settings/api\" target=\"_blank\">https://www.name.com/account/settings/api</a>",
"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 <a href=\"https://www.namesilo.com/support/v2/articles/account-options/api-manager\" target=\"_blank\">https://www.namesilo.com/support/v2/articles/account-options/api-manager</a>",

View File

@ -68,6 +68,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",

View File

@ -91,6 +91,12 @@
"access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件",
"access.form.k8s_kubeconfig.upload": "选择文件",
"access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 <a href=\"https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>为空时,将使用 Pod 的 ServiceAccount 作为凭证。",
"access.form.namedotcom_username.label": "Name.com Username",
"access.form.namedotcom_username.placeholder": "请输入 Name.com Username",
"access.form.namedotcom_username.tooltip": "这是什么?请参阅 <a href=\"https://www.name.com/account/settings/api\" target=\"_blank\">https://www.name.com/account/settings/api</a>",
"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": "这是什么?请参阅 <a href=\"https://www.name.com/account/settings/api\" target=\"_blank\">https://www.name.com/account/settings/api</a>",
"access.form.namesilo_api_key.label": "NameSilo API Key",
"access.form.namesilo_api_key.placeholder": "请输入 NameSilo API Key",
"access.form.namesilo_api_key.tooltip": "这是什么?请参阅 <a href=\"https://www.namesilo.com/support/v2/articles/account-options/api-manager\" target=\"_blank\">https://www.namesilo.com/support/v2/articles/account-options/api-manager</a>",

View File

@ -68,6 +68,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": "七牛云",