diff --git a/README.md b/README.md
index 762af031..ebfc8cce 100644
--- a/README.md
+++ b/README.md
@@ -99,6 +99,7 @@ make local.run
| [Name.com](https://www.name.com/) | |
| [NameSilo](https://www.namesilo.com/) | |
| [IBM NS1 Connect](https://www.ibm.com/cn-zh/products/ns1-connect/) | |
+| [雨云](https://www.rainyun.com/) | |
| [西部数码](https://www.west.cn/) | |
| [PowerDNS](https://www.powerdns.com/) | |
| ACME 代理 HTTP 请求 | 可申请允许通过 HTTP 请求修改 DNS 的域名 |
diff --git a/README_EN.md b/README_EN.md
index fbb97125..502062c6 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -98,6 +98,7 @@ The following DNS providers are supported:
| [Name.com](https://www.name.com/) | |
| [NameSilo](https://www.namesilo.com/) | |
| [IBM NS1 Connect](https://www.ibm.com/products/ns1-connect/) | |
+| [Rain Yun](https://www.rainyun.com/) | |
| [West.cn](https://www.west.cn/) | |
| [PowerDNS](https://www.powerdns.com/) | |
| ACME Proxy HTTP Request | Supports managing DNS by HTTP request |
diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go
index 41541f01..75d9d3ce 100644
--- a/internal/applicant/providers.go
+++ b/internal/applicant/providers.go
@@ -17,6 +17,7 @@ import (
providerNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo"
providerNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1"
providerPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns"
+ providerRainYun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun"
providerTencentCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/tencentcloud"
providerVolcEngine "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/volcengine"
providerWestcn "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/westcn"
@@ -208,6 +209,21 @@ func createApplicant(options *applicantOptions) (challenge.Provider, error) {
return applicant, err
}
+ case domain.ApplyDNSProviderTypeRainYun:
+ {
+ access := domain.AccessConfigForRainYun{}
+ if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to decode provider access config: %w", err)
+ }
+
+ applicant, err := providerRainYun.NewChallengeProvider(&providerRainYun.RainYunApplicantConfig{
+ ApiKey: access.ApiKey,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
+
case domain.ApplyDNSProviderTypeTencentCloud, domain.ApplyDNSProviderTypeTencentCloudDNS:
{
access := domain.AccessConfigForTencentCloud{}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index d045853a..7f49c897 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -112,6 +112,10 @@ type AccessConfigForQiniu struct {
SecretKey string `json:"secretKey"`
}
+type AccessConfigForRainYun struct {
+ ApiKey string `json:"apiKey"`
+}
+
type AccessConfigForSSH struct {
Host string `json:"host"`
Port int32 `json:"port"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 32835e6c..c780b934 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -27,6 +27,7 @@ const (
AccessProviderTypeNS1 = AccessProviderType("ns1")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
AccessProviderTypeQiniu = AccessProviderType("qiniu")
+ AccessProviderTypeRainYun = AccessProviderType("rainyun")
AccessProviderTypeSSH = AccessProviderType("ssh")
AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
AccessProviderTypeUCloud = AccessProviderType("ucloud")
@@ -59,6 +60,7 @@ const (
ApplyDNSProviderTypeNameSilo = ApplyDNSProviderType("namesilo")
ApplyDNSProviderTypeNS1 = ApplyDNSProviderType("ns1")
ApplyDNSProviderTypePowerDNS = ApplyDNSProviderType("powerdns")
+ ApplyDNSProviderTypeRainYun = ApplyDNSProviderType("rainyun")
ApplyDNSProviderTypeTencentCloud = ApplyDNSProviderType("tencentcloud") // 兼容旧值,等同于 [ApplyDNSProviderTypeTencentCloudDNS]
ApplyDNSProviderTypeTencentCloudDNS = ApplyDNSProviderType("tencentcloud-dns")
ApplyDNSProviderTypeVolcEngine = ApplyDNSProviderType("volcengine") // 兼容旧值,等同于 [ApplyDNSProviderTypeVolcEngineDNS]
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go
new file mode 100644
index 00000000..d250a279
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/rainyun/rainyun.go
@@ -0,0 +1,37 @@
+package rainyun
+
+import (
+ "errors"
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/providers/dns/rainyun"
+)
+
+type RainYunApplicantConfig struct {
+ ApiKey string `json:"apiKey"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *RainYunApplicantConfig) (challenge.Provider, error) {
+ if config == nil {
+ return nil, errors.New("config is nil")
+ }
+
+ providerConfig := rainyun.NewDefaultConfig()
+ 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 := rainyun.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/ui/public/imgs/providers/rainyun.svg b/ui/public/imgs/providers/rainyun.svg
new file mode 100644
index 00000000..18413e16
--- /dev/null
+++ b/ui/public/imgs/providers/rainyun.svg
@@ -0,0 +1,10 @@
+
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index 296eec43..9671ffa2 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -27,6 +27,7 @@ import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig";
import AccessFormQiniuConfig from "./AccessFormQiniuConfig";
+import AccessFormRainYunConfig from "./AccessFormRainYunConfig";
import AccessFormSSHConfig from "./AccessFormSSHConfig";
import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig";
import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
@@ -122,6 +123,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.QINIU:
return ;
+ case ACCESS_PROVIDERS.RAINYUN:
+ return ;
case ACCESS_PROVIDERS.SSH:
return ;
case ACCESS_PROVIDERS.TENCENTCLOUD:
diff --git a/ui/src/components/access/AccessFormRainYunConfig.tsx b/ui/src/components/access/AccessFormRainYunConfig.tsx
new file mode 100644
index 00000000..a34cf683
--- /dev/null
+++ b/ui/src/components/access/AccessFormRainYunConfig.tsx
@@ -0,0 +1,61 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type AccessConfigForRainYun } from "@/domain/access";
+
+type AccessFormRainYunConfigFieldValues = Nullish;
+
+export type AccessFormRainYunConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormRainYunConfigFieldValues;
+ onValuesChange?: (values: AccessFormRainYunConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormRainYunConfigFieldValues => {
+ return {
+ apiKey: "",
+ };
+};
+
+const AccessFormRainYunConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormRainYunConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ apiKey: z
+ .string()
+ .min(1, t("access.form.rainyun_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 AccessFormRainYunConfig;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index 19167ddc..8efe8b27 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -25,6 +25,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForNameSilo
| AccessConfigForPowerDNS
| AccessConfigForQiniu
+ | AccessConfigForRainYun
| AccessConfigForSSH
| AccessConfigForTencentCloud
| AccessConfigForUCloud
@@ -123,6 +124,10 @@ export type AccessConfigForQiniu = {
secretKey: string;
};
+export type AccessConfigForRainYun = {
+ apiKey: string;
+};
+
export type AccessConfigForSSH = {
host: string;
port: number;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index cb8acfb1..a13011be 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -22,6 +22,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
NS1: "ns1",
POWERDNS: "powerdns",
QINIU: "qiniu",
+ RAINYUN: "rainyun",
SSH: "ssh",
TENCENTCLOUD: "tencentcloud",
UCLOUD: "ucloud",
@@ -60,10 +61,10 @@ export const accessProvidersMap: Maphttps://portal.qiniu.com/",
+ "access.form.rainyun_api_key.label": "Rain Yun API key",
+ "access.form.rainyun_api_key.placeholder": "Please enter Rain Yun API key",
+ "access.form.rainyun_api_key.tooltip": "For more information, see https://www.rainyun.com/docs/account/racc/setting",
"access.form.ssh_host.label": "Server host",
"access.form.ssh_host.placeholder": "Please enter server host",
"access.form.ssh_port.label": "Server port",
diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json
index 3bed9469..7de04394 100644
--- a/ui/src/i18n/locales/en/nls.common.json
+++ b/ui/src/i18n/locales/en/nls.common.json
@@ -73,6 +73,7 @@
"common.provider.qiniu": "Qiniu",
"common.provider.qiniu.cdn": "Qiniu - Content Delivery Network (CDN)",
"common.provider.qiniu.pili": "Qiniu - Pili",
+ "common.provider.rainyun": "Rain Yun",
"common.provider.ssh": "SSH deployment",
"common.provider.tencentcloud": "Tencent Cloud",
"common.provider.tencentcloud.cdn": "Tencent Cloud - Content Delivery Network (CDN)",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index d3baaf67..8be0c1cc 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -124,6 +124,9 @@
"access.form.qiniu_secret_key.label": "七牛云 SecretKey",
"access.form.qiniu_secret_key.placeholder": "请输入七牛云 SecretKey",
"access.form.qiniu_secret_key.tooltip": "这是什么?请参阅 https://portal.qiniu.com/",
+ "access.form.rainyun_api_key.label": "雨云 API 密钥",
+ "access.form.rainyun_api_key.placeholder": "请输入雨云 API 密钥",
+ "access.form.rainyun_api_key.tooltip": "这是什么?请参阅 https://www.rainyun.com/docs/account/racc/setting",
"access.form.ssh_host.label": "服务器地址",
"access.form.ssh_host.placeholder": "请输入服务器地址",
"access.form.ssh_port.label": "服务器端口",
diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json
index d555d473..48fa3bbc 100644
--- a/ui/src/i18n/locales/zh/nls.common.json
+++ b/ui/src/i18n/locales/zh/nls.common.json
@@ -73,6 +73,7 @@
"common.provider.qiniu": "七牛云",
"common.provider.qiniu.cdn": "七牛云 - 内容分发网络 CDN",
"common.provider.qiniu.pili": "七牛云 - 视频直播 Pili",
+ "common.provider.rainyun": "雨云",
"common.provider.ssh": "SSH 部署",
"common.provider.tencentcloud": "腾讯云",
"common.provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN",