diff --git a/internal/certacme/certifiers/sp_rucenter.go b/internal/certacme/certifiers/sp_rucenter.go
new file mode 100644
index 000000000..89d01e0bf
--- /dev/null
+++ b/internal/certacme/certifiers/sp_rucenter.go
@@ -0,0 +1,29 @@
+package certifiers
+
+import (
+ "fmt"
+
+ "github.com/certimate-go/certimate/internal/domain"
+ "github.com/certimate-go/certimate/pkg/core"
+ "github.com/certimate-go/certimate/pkg/core/certifier/challengers/dns01/rucenter"
+ xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
+)
+
+func init() {
+ ACMEDns01Registries.MustRegister(domain.ACMEDns01ProviderTypeRuCenter, func(options *ProviderFactoryOptions) (core.ACMEChallenger, error) {
+ credentials := domain.AccessConfigForRuCenter{}
+ if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ provider, err := rucenter.NewChallenger(&rucenter.ChallengerConfig{
+ Username: credentials.Username,
+ Password: credentials.Password,
+ ApplicationId: credentials.ApplicationId,
+ ApplicationToken: credentials.ApplicationToken,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return provider, err
+ })
+}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index 8e3e895ab..0c6e1f4f2 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -500,6 +500,13 @@ type AccessConfigForRFC2136 struct {
TsigSecret string `json:"tsigSecret,omitempty"`
}
+type AccessConfigForRuCenter struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ ApplicationId string `json:"applicationId"`
+ ApplicationToken string `json:"applicationToken"`
+}
+
type AccessConfigForS3 struct {
Endpoint string `json:"endpoint"`
AccessKey string `json:"accessKey"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 7a5f63269..d66ddc25b 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -106,6 +106,7 @@ const (
AccessProviderTypeRatPanel = AccessProviderType("ratpanel")
AccessProviderTypeRegru = AccessProviderType("regru")
AccessProviderTypeRFC2136 = AccessProviderType("rfc2136")
+ AccessProviderTypeRuCenter = AccessProviderType("rucenter")
AccessProviderTypeS3 = AccessProviderType("s3")
AccessProviderTypeSafeLine = AccessProviderType("safeline")
AccessProviderTypeSamWAF = AccessProviderType("samwaf")
@@ -246,6 +247,7 @@ const (
ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun)
ACMEDns01ProviderTypeRegru = ACMEDns01ProviderType(AccessProviderTypeRegru)
ACMEDns01ProviderTypeRFC2136 = ACMEDns01ProviderType(AccessProviderTypeRFC2136)
+ ACMEDns01ProviderTypeRuCenter = ACMEDns01ProviderType(AccessProviderTypeRuCenter)
ACMEDns01ProviderTypeSpaceship = ACMEDns01ProviderType(AccessProviderTypeSpaceship)
ACMEDns01ProviderTypeTechnitiumDNS = ACMEDns01ProviderType(AccessProviderTypeTechnitiumDNS)
ACMEDns01ProviderTypeTencentCloud = ACMEDns01ProviderType(AccessProviderTypeTencentCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeTencentCloudDNS]
diff --git a/pkg/core/certifier/challengers/dns01/rucenter/rucenter.go b/pkg/core/certifier/challengers/dns01/rucenter/rucenter.go
new file mode 100644
index 000000000..442bc3391
--- /dev/null
+++ b/pkg/core/certifier/challengers/dns01/rucenter/rucenter.go
@@ -0,0 +1,44 @@
+package rucenter
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/go-acme/lego/v5/providers/dns/nicru"
+
+ "github.com/certimate-go/certimate/pkg/core/certifier"
+)
+
+type ChallengerConfig struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ ApplicationId string `json:"applicationId"`
+ ApplicationToken string `json:"applicationToken"`
+ DnsPropagationTimeout int `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int `json:"dnsTTL,omitempty"`
+}
+
+func NewChallenger(config *ChallengerConfig) (certifier.ACMEChallenger, error) {
+ if config == nil {
+ return nil, fmt.Errorf("the configuration of the acme challenge provider is nil")
+ }
+
+ providerConfig := nicru.NewDefaultConfig()
+ providerConfig.Username = config.Username
+ providerConfig.Password = config.Password
+ providerConfig.ServiceID = config.ApplicationId
+ providerConfig.Secret = config.ApplicationToken
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = config.DnsTTL
+ }
+
+ provider, err := nicru.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/ui/public/imgs/providers/ricru.svg b/ui/public/imgs/providers/ricru.svg
new file mode 100644
index 000000000..76b841c3d
--- /dev/null
+++ b/ui/public/imgs/providers/ricru.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
index a6080f9a1..9da1b8a0c 100644
--- a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
+++ b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
@@ -90,6 +90,7 @@ import AccessConfigFieldsProviderRainYun from "./AccessConfigFieldsProviderRainY
import AccessConfigFieldsProviderRatPanel from "./AccessConfigFieldsProviderRatPanel";
import AccessConfigFieldsProviderRegru from "./AccessConfigFieldsProviderRegru";
import AccessConfigFieldsProviderRFC2136 from "./AccessConfigFieldsProviderRFC2136";
+import AccessConfigFieldsProviderRuCenter from "./AccessConfigFieldsProviderRuCenter";
import AccessConfigFieldsProviderS3 from "./AccessConfigFieldsProviderS3";
import AccessConfigFieldsProviderSafeLine from "./AccessConfigFieldsProviderSafeLine";
import AccessConfigFieldsProviderSamWAF from "./AccessConfigFieldsProviderSamWAF";
@@ -210,6 +211,7 @@ const providerComponentMap: Partial {
+ const { i18n, t } = useTranslation();
+
+ const { parentNamePath } = useFormNestedFieldsContext();
+ const formSchema = z.object({
+ [parentNamePath]: getSchema({ i18n }),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+ const initialValues = getInitialValues();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+ }
+ >
+
+
+ >
+ );
+};
+
+const getInitialValues = (): Nullish>> => {
+ return {
+ username: "",
+ password: "",
+ applicationId: "",
+ applicationToken: "",
+ };
+};
+
+const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => {
+ const { t: _ } = i18n;
+
+ return z.object({
+ username: z.union([z.string().endsWith("/NIC-D"), z.string().endsWith("/NIC-REG")]),
+ password: z.string().nonempty(),
+ applicationId: z.string().length(32),
+ applicationToken: z.string().length(91),
+ });
+};
+
+const _default = Object.assign(AccessConfigFormFieldsProviderRuCenter, {
+ getInitialValues,
+ getSchema,
+});
+
+export default _default;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 8d63d05b8..34b0d863d 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -106,6 +106,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
RATPANEL: "ratpanel",
REGRU: "regru",
RFC2136: "rfc2136",
+ RUCENTER: "rucenter",
S3: "s3",
SAFELINE: "safeline",
SAMWAF: "samwaf",
@@ -242,6 +243,7 @@ export const accessProvidersMap: Maphttps://ratpanel.github.io/advanced/api.html"
},
"regru_username": {
- "label": "Рег.ру username",
- "placeholder": "Please enter Рег.ру username"
+ "label": "Reg.ru username",
+ "placeholder": "Please enter Reg.ru username"
},
"regru_password": {
- "label": "Рег.ру password",
- "placeholder": "Please enter Рег.ру password or API password"
+ "label": "Reg.ru password",
+ "placeholder": "Please enter Reg.ru password or API password"
},
"regru_mtls_certificate": {
- "label": "Рег.ру mTLS certificate (Optional)",
- "placeholder": "Please enter Рег.ру mTLS certificate",
+ "label": "Reg.ru mTLS certificate (Optional)",
+ "placeholder": "Please enter Reg.ru mTLS certificate",
"errmsg": {
"invalid": "Invalid certificate"
},
"tooltip": "For more information, see https://www.reg.ru/user/account/#/settings/api/api/"
},
"regru_mtls_private_key": {
- "label": "Рег.ру mTLS private key (Optional)",
- "placeholder": "Please enter Рег.ру mTLS private key",
+ "label": "Reg.ru mTLS private key (Optional)",
+ "placeholder": "Please enter Reg.ru mTLS private key",
"errmsg": {
"invalid": "Invalid private key"
},
@@ -1118,6 +1118,24 @@
"label": "TSIG authentication secret (Optional)",
"placeholder": "Please enter TSIG authentication secret"
},
+ "rucenter_username": {
+ "label": "Ru-Center username",
+ "placeholder": "Please enter Ru-Center username"
+ },
+ "rucenter_password": {
+ "label": "Ru-Center password",
+ "placeholder": "Please enter Ru-Center password"
+ },
+ "rucenter_application_id": {
+ "label": "Ru-Center application ID",
+ "placeholder": "Please enter Ru-Center application ID",
+ "tooltip": "For more information, see https://www.nic.ru/manager/oauth.cgi?step=oauth.app_list"
+ },
+ "rucenter_application_token": {
+ "label": "Ru-Center application token",
+ "placeholder": "Please enter Ru-Center application token",
+ "tooltip": "For more information, see https://www.nic.ru/manager/oauth.cgi?step=oauth.app_list"
+ },
"s3_endpoint": {
"label": "Endpoint",
"placeholder": "Please enter endpoint",
diff --git a/ui/src/i18n/resources/en/nls.provider.json b/ui/src/i18n/resources/en/nls.provider.json
index a8a1341d0..c21ff6a1b 100644
--- a/ui/src/i18n/resources/en/nls.provider.json
+++ b/ui/src/i18n/resources/en/nls.provider.json
@@ -200,6 +200,7 @@
"ratpanel_console": "AcePanel - Console itself",
"regru": "Рег.ру (Reg.ru)",
"rfc2136": "Dynamic DNS Updates (RFC 2136)",
+ "rucenter": "Руцентр (Ru-Center)",
"s3": "Object storage (S3-compatible)",
"s3_upload": "Upload to S3-compatible object storage",
"safeline": "SafeLine",
diff --git a/ui/src/i18n/resources/zh/nls.access.json b/ui/src/i18n/resources/zh/nls.access.json
index 7ad0b7258..b6b5ae90d 100644
--- a/ui/src/i18n/resources/zh/nls.access.json
+++ b/ui/src/i18n/resources/zh/nls.access.json
@@ -1073,24 +1073,24 @@
"tooltip": "这是什么?请参阅 https://ratpanel.github.io/advanced/api.html"
},
"regru_username": {
- "label": "Рег.ру 用户名",
- "placeholder": "请输入 Рег.ру 用户名"
+ "label": "Reg.ru 用户名",
+ "placeholder": "请输入 Reg.ru 用户名"
},
"regru_password": {
- "label": "Рег.ру 密码",
- "placeholder": "请输入 Рег.ру 密码或 API 密码"
+ "label": "Reg.ru 密码",
+ "placeholder": "请输入 Reg.ru 密码或 API 密码"
},
"regru_mtls_certificate": {
- "label": "Рег.ру SSL 双向认证证书(可选)",
- "placeholder": "请输入 Рег.ру SSL 双向认证证书",
+ "label": "Reg.ru SSL 双向认证证书(可选)",
+ "placeholder": "请输入 Reg.ru SSL 双向认证证书",
"errmsg": {
"invalid": "无效的证书"
},
"tooltip": "这是什么?请参阅 https://www.reg.ru/user/account/#/settings/api/api/"
},
"regru_mtls_private_key": {
- "label": "Рег.ру SSL 双向认证私钥(可选)",
- "placeholder": "请输入 Рег.ру SSL 双向认证私钥",
+ "label": "Reg.ru SSL 双向认证私钥(可选)",
+ "placeholder": "请输入 Reg.ru SSL 双向认证私钥",
"errmsg": {
"invalid": "无效的私钥"
},
@@ -1116,6 +1116,24 @@
"label": "TSIG 认证密钥 Secret(可选)",
"placeholder": "请输入 TSIG 认证密钥 Secret"
},
+ "rucenter_username": {
+ "label": "Ru-Center 用户名",
+ "placeholder": "请输入 Ru-Center 用户名"
+ },
+ "rucenter_password": {
+ "label": "Ru-Center 密码",
+ "placeholder": "请输入 Ru-Center 密码"
+ },
+ "rucenter_application_id": {
+ "label": "Ru-Center 应用服务 ID",
+ "placeholder": "请输入 Ru-Center 应用服务 ID",
+ "tooltip": "这是什么?请参阅 https://www.nic.ru/manager/oauth.cgi?step=oauth.app_list"
+ },
+ "rucenter_application_token": {
+ "label": "Ru-Center 应用服务 Token",
+ "placeholder": "请输入 Ru-Center 应用服务 Token",
+ "tooltip": "这是什么?请参阅 https://www.nic.ru/manager/oauth.cgi?step=oauth.app_list"
+ },
"s3_endpoint": {
"label": "终端节点",
"placeholder": "请输入终端节点",
diff --git a/ui/src/i18n/resources/zh/nls.provider.json b/ui/src/i18n/resources/zh/nls.provider.json
index 14ee11774..221bfd80f 100644
--- a/ui/src/i18n/resources/zh/nls.provider.json
+++ b/ui/src/i18n/resources/zh/nls.provider.json
@@ -200,6 +200,7 @@
"ratpanel_console": "耗子面板 - 面板自身",
"regru": "Рег.ру(REG.ru)",
"rfc2136": "Dynamic DNS Updates (RFC 2136)",
+ "rucenter": "Руцентр(Ru-Center)",
"s3": "对象存储(S3 兼容)",
"s3_upload": "上传到 S3 兼容的对象存储",
"safeline": "雷池",