diff --git a/internal/certacme/certifiers/sp_regru.go b/internal/certacme/certifiers/sp_regru.go
new file mode 100644
index 000000000..95488f15a
--- /dev/null
+++ b/internal/certacme/certifiers/sp_regru.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/regru"
+ xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
+)
+
+func init() {
+ ACMEDns01Registries.MustRegister(domain.ACMEDns01ProviderTypeRegru, func(options *ProviderFactoryOptions) (core.ACMEChallenger, error) {
+ credentials := domain.AccessConfigForRegru{}
+ if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ provider, err := regru.NewChallenger(®ru.ChallengerConfig{
+ Username: credentials.Username,
+ Password: credentials.Password,
+ MtlsCertificate: credentials.MtlsCertificate,
+ MtlsPrivateKey: credentials.MtlsPrivateKey,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return provider, err
+ })
+}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index c6491bcd4..8e3e895ab 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -485,6 +485,13 @@ type AccessConfigForRatPanel struct {
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
+type AccessConfigForRegru struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ MtlsCertificate string `json:"mtlsCertificate,omitempty"`
+ MtlsPrivateKey string `json:"mtlsPrivateKey,omitempty"`
+}
+
type AccessConfigForRFC2136 struct {
Host string `json:"host"`
Port int32 `json:"port"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 581d10fca..7a5f63269 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -104,6 +104,7 @@ const (
AccessProviderTypeQingCloud = AccessProviderType("qingcloud")
AccessProviderTypeRainYun = AccessProviderType("rainyun")
AccessProviderTypeRatPanel = AccessProviderType("ratpanel")
+ AccessProviderTypeRegru = AccessProviderType("regru")
AccessProviderTypeRFC2136 = AccessProviderType("rfc2136")
AccessProviderTypeS3 = AccessProviderType("s3")
AccessProviderTypeSafeLine = AccessProviderType("safeline")
@@ -243,6 +244,7 @@ const (
ACMEDns01ProviderTypeQingCloud = ACMEDns01ProviderType(AccessProviderTypeQingCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeQingCloudDNS]
ACMEDns01ProviderTypeQingCloudDNS = ACMEDns01ProviderType(AccessProviderTypeQingCloud + "-dns")
ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun)
+ ACMEDns01ProviderTypeRegru = ACMEDns01ProviderType(AccessProviderTypeRegru)
ACMEDns01ProviderTypeRFC2136 = ACMEDns01ProviderType(AccessProviderTypeRFC2136)
ACMEDns01ProviderTypeSpaceship = ACMEDns01ProviderType(AccessProviderTypeSpaceship)
ACMEDns01ProviderTypeTechnitiumDNS = ACMEDns01ProviderType(AccessProviderTypeTechnitiumDNS)
diff --git a/pkg/core/certifier/challengers/dns01/regru/regru.go b/pkg/core/certifier/challengers/dns01/regru/regru.go
new file mode 100644
index 000000000..07ab413ef
--- /dev/null
+++ b/pkg/core/certifier/challengers/dns01/regru/regru.go
@@ -0,0 +1,44 @@
+package regru
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/go-acme/lego/v5/providers/dns/regru"
+
+ "github.com/certimate-go/certimate/pkg/core/certifier"
+)
+
+type ChallengerConfig struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ MtlsCertificate string `json:"mtlsCertificate,omitempty"`
+ MtlsPrivateKey string `json:"mtlsPrivateKey,omitempty"`
+ 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 := regru.NewDefaultConfig()
+ providerConfig.Username = config.Username
+ providerConfig.Password = config.Password
+ providerConfig.TLSCert = config.MtlsCertificate
+ providerConfig.TLSKey = config.MtlsPrivateKey
+ if config.DnsPropagationTimeout != 0 {
+ providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
+ }
+ if config.DnsTTL != 0 {
+ providerConfig.TTL = config.DnsTTL
+ }
+
+ provider, err := regru.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/ui/public/imgs/providers/regru.svg b/ui/public/imgs/providers/regru.svg
new file mode 100644
index 000000000..38888884e
--- /dev/null
+++ b/ui/public/imgs/providers/regru.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
index 04d4e316e..a6080f9a1 100644
--- a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
+++ b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
@@ -88,6 +88,7 @@ import AccessConfigFieldsProviderQingCloud from "./AccessConfigFieldsProviderQin
import AccessConfigFieldsProviderQiniu from "./AccessConfigFieldsProviderQiniu";
import AccessConfigFieldsProviderRainYun from "./AccessConfigFieldsProviderRainYun";
import AccessConfigFieldsProviderRatPanel from "./AccessConfigFieldsProviderRatPanel";
+import AccessConfigFieldsProviderRegru from "./AccessConfigFieldsProviderRegru";
import AccessConfigFieldsProviderRFC2136 from "./AccessConfigFieldsProviderRFC2136";
import AccessConfigFieldsProviderS3 from "./AccessConfigFieldsProviderS3";
import AccessConfigFieldsProviderSafeLine from "./AccessConfigFieldsProviderSafeLine";
@@ -207,6 +208,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: "",
+ mtlsCertificate: "",
+ mtlsPrivateKey: "",
+ };
+};
+
+const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => {
+ const { t } = i18n;
+
+ return z
+ .object({
+ username: z.string().nonempty(),
+ password: z.string().nonempty(),
+ mtlsCertificate: z.string().nullish(),
+ mtlsPrivateKey: z.string().nullish(),
+ })
+ .superRefine((values, ctx) => {
+ const scMtlsCertificate = z.string().nonempty();
+ const spMtlsCertificate = scMtlsCertificate.safeParse(values.mtlsCertificate);
+ const scMtlsPrivateKey = z.string().nonempty();
+ const spMtlsPrivateKey = scMtlsPrivateKey.safeParse(values.mtlsPrivateKey);
+ if (!spMtlsCertificate.success && spMtlsPrivateKey.success) {
+ ctx.addIssue({
+ code: "custom",
+ message: t("access.form.regru_mtls_certificate.errmsg.invalid"),
+ path: ["mtlsCertificate"],
+ });
+ } else if (spMtlsCertificate.success && !spMtlsPrivateKey.success) {
+ ctx.addIssue({
+ code: "custom",
+ message: t("access.form.regru_mtls_private_key.errmsg.invalid"),
+ path: ["mtlsPrivateKey"],
+ });
+ }
+ });
+};
+
+const _default = Object.assign(AccessConfigFormFieldsProviderRegru, {
+ getInitialValues,
+ getSchema,
+});
+
+export default _default;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index fea37e264..8d63d05b8 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -104,6 +104,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
QINIU: "qiniu",
RAINYUN: "rainyun",
RATPANEL: "ratpanel",
+ REGRU: "regru",
RFC2136: "rfc2136",
S3: "s3",
SAFELINE: "safeline",
@@ -240,6 +241,7 @@ export const accessProvidersMap: Maphttps://ratpanel.github.io/advanced/api.html"
},
+ "regru_username": {
+ "label": "Рег.ру username",
+ "placeholder": "Please enter Рег.ру username"
+ },
+ "regru_password": {
+ "label": "Рег.ру password",
+ "placeholder": "Please enter Рег.ру password or API password"
+ },
+ "regru_mtls_certificate": {
+ "label": "Рег.ру mTLS certificate (Optional)",
+ "placeholder": "Please enter Рег.ру 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",
+ "errmsg": {
+ "invalid": "Invalid private key"
+ },
+ "tooltip": "For more information, see https://www.reg.ru/user/account/#/settings/api/api/"
+ },
"rfc2136_host": {
"label": "DNS server host",
"placeholder": "Please enter DNS server host"
diff --git a/ui/src/i18n/resources/en/nls.provider.json b/ui/src/i18n/resources/en/nls.provider.json
index 81f28df36..a8a1341d0 100644
--- a/ui/src/i18n/resources/en/nls.provider.json
+++ b/ui/src/i18n/resources/en/nls.provider.json
@@ -198,6 +198,7 @@
"ratpanel": "AcePanel (aka RatPanel)",
"ratpanel_common": "AcePanel",
"ratpanel_console": "AcePanel - Console itself",
+ "regru": "Рег.ру (Reg.ru)",
"rfc2136": "Dynamic DNS Updates (RFC 2136)",
"s3": "Object storage (S3-compatible)",
"s3_upload": "Upload to S3-compatible object storage",
diff --git a/ui/src/i18n/resources/zh/nls.access.json b/ui/src/i18n/resources/zh/nls.access.json
index c62fea365..7ad0b7258 100644
--- a/ui/src/i18n/resources/zh/nls.access.json
+++ b/ui/src/i18n/resources/zh/nls.access.json
@@ -1072,6 +1072,30 @@
"placeholder": "请输入耗子面板 AccessToken",
"tooltip": "这是什么?请参阅 https://ratpanel.github.io/advanced/api.html"
},
+ "regru_username": {
+ "label": "Рег.ру 用户名",
+ "placeholder": "请输入 Рег.ру 用户名"
+ },
+ "regru_password": {
+ "label": "Рег.ру 密码",
+ "placeholder": "请输入 Рег.ру 密码或 API 密码"
+ },
+ "regru_mtls_certificate": {
+ "label": "Рег.ру SSL 双向认证证书(可选)",
+ "placeholder": "请输入 Рег.ру SSL 双向认证证书",
+ "errmsg": {
+ "invalid": "无效的证书"
+ },
+ "tooltip": "这是什么?请参阅 https://www.reg.ru/user/account/#/settings/api/api/"
+ },
+ "regru_mtls_private_key": {
+ "label": "Рег.ру SSL 双向认证私钥(可选)",
+ "placeholder": "请输入 Рег.ру SSL 双向认证私钥",
+ "errmsg": {
+ "invalid": "无效的私钥"
+ },
+ "tooltip": "这是什么?请参阅 https://www.reg.ru/user/account/#/settings/api/api/"
+ },
"rfc2136_host": {
"label": "DNS 服务器地址",
"placeholder": "请输入 DNS 服务器地址"
diff --git a/ui/src/i18n/resources/zh/nls.provider.json b/ui/src/i18n/resources/zh/nls.provider.json
index 6015de294..14ee11774 100644
--- a/ui/src/i18n/resources/zh/nls.provider.json
+++ b/ui/src/i18n/resources/zh/nls.provider.json
@@ -198,6 +198,7 @@
"ratpanel": "耗子面板(又名:AcePanel)",
"ratpanel_common": "耗子面板",
"ratpanel_console": "耗子面板 - 面板自身",
+ "regru": "Рег.ру(REG.ru)",
"rfc2136": "Dynamic DNS Updates (RFC 2136)",
"s3": "对象存储(S3 兼容)",
"s3_upload": "上传到 S3 兼容的对象存储",