diff --git a/internal/certapply/applicators/sp_arvancloud.go b/internal/certapply/applicators/sp_arvancloud.go
new file mode 100644
index 000000000..c82f2f05b
--- /dev/null
+++ b/internal/certapply/applicators/sp_arvancloud.go
@@ -0,0 +1,29 @@
+package applicators
+
+import (
+ "fmt"
+
+ "github.com/go-acme/lego/v4/challenge"
+
+ "github.com/certimate-go/certimate/internal/domain"
+ "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/arvancloud"
+ xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
+)
+
+func init() {
+ if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeArvanCloud, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
+ credentials := domain.AccessConfigForArvanCloud{}
+ if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ provider, err := arvancloud.NewChallengeProvider(&arvancloud.ChallengeProviderConfig{
+ ApiKey: credentials.ApiKey,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return provider, err
+ }); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index 59e28e6db..0a3cb9985 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -60,6 +60,10 @@ type AccessConfigForAPISIX struct {
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
+type AccessConfigForArvanCloud struct {
+ ApiKey string `json:"apiKey"`
+}
+
type AccessConfigForAWS struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 4980be0c8..c8e215ab5 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -17,6 +17,7 @@ const (
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留)
AccessProviderTypeAliyun = AccessProviderType("aliyun")
AccessProviderTypeAPISIX = AccessProviderType("apisix")
+ AccessProviderTypeArvanCloud = AccessProviderType("arvancloud")
AccessProviderTypeAWS = AccessProviderType("aws")
AccessProviderTypeAzure = AccessProviderType("azure")
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
@@ -40,8 +41,10 @@ const (
AccessProviderTypeDingTalkBot = AccessProviderType("dingtalkbot")
AccessProviderTypeDiscordBot = AccessProviderType("discordbot")
AccessProviderTypeDNSLA = AccessProviderType("dnsla")
+ AccessProviderTypeDNSMadeEasy = AccessProviderType("dnsmadeeasy") // DNS Made Easy(预留)
AccessProviderTypeDogeCloud = AccessProviderType("dogecloud")
AccessProviderTypeDuckDNS = AccessProviderType("duckdns")
+ AccessProviderTypeDynu = AccessProviderType("dynu") // Dynu(预留)
AccessProviderTypeDynv6 = AccessProviderType("dynv6")
AccessProviderTypeEmail = AccessProviderType("email")
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留)
@@ -57,6 +60,7 @@ const (
AccessProviderTypeHostingde = AccessProviderType("hostingde")
AccessProviderTypeHostinger = AccessProviderType("hostinger")
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
+ AccessProviderTypeInfomaniak = AccessProviderType("infomaniak") // Infomaniak(预留)
AccessProviderTypeIONOS = AccessProviderType("ionos")
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
AccessProviderTypeKong = AccessProviderType("kong")
@@ -75,6 +79,7 @@ const (
AccessProviderTypeNetcup = AccessProviderType("netcup")
AccessProviderTypeNetlify = AccessProviderType("netlify")
AccessProviderTypeNS1 = AccessProviderType("ns1")
+ AccessProviderTypeOVHcloud = AccessProviderType("ovhcloud") // OVHcloud(预留)
AccessProviderTypePorkbun = AccessProviderType("porkbun")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
AccessProviderTypeProxmoxVE = AccessProviderType("proxmoxve")
@@ -141,6 +146,7 @@ const (
ACMEDns01ProviderTypeAliyun = ACMEDns01ProviderType(AccessProviderTypeAliyun) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAliyunDNS]
ACMEDns01ProviderTypeAliyunDNS = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-dns")
ACMEDns01ProviderTypeAliyunESA = ACMEDns01ProviderType(AccessProviderTypeAliyun + "-esa")
+ ACMEDns01ProviderTypeArvanCloud = ACMEDns01ProviderType(AccessProviderTypeArvanCloud)
ACMEDns01ProviderTypeAWS = ACMEDns01ProviderType(AccessProviderTypeAWS) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAWSRoute53]
ACMEDns01ProviderTypeAWSRoute53 = ACMEDns01ProviderType(AccessProviderTypeAWS + "-route53")
ACMEDns01ProviderTypeAzure = ACMEDns01ProviderType(AccessProviderTypeAzure) // 兼容旧值,等同于 [ACMEDns01ProviderTypeAzure]
@@ -159,7 +165,9 @@ const (
ACMEDns01ProviderTypeDeSEC = ACMEDns01ProviderType(AccessProviderTypeDeSEC)
ACMEDns01ProviderTypeDigitalOcean = ACMEDns01ProviderType(AccessProviderTypeDigitalOcean)
ACMEDns01ProviderTypeDNSLA = ACMEDns01ProviderType(AccessProviderTypeDNSLA)
+ ACMEDns01ProviderTypeDNSMadeEasy = ACMEDns01ProviderType(AccessProviderTypeDNSMadeEasy)
ACMEDns01ProviderTypeDuckDNS = ACMEDns01ProviderType(AccessProviderTypeDuckDNS)
+ ACMEDns01ProviderTypeDynu = ACMEDns01ProviderType(AccessProviderTypeDynu)
ACMEDns01ProviderTypeDynv6 = ACMEDns01ProviderType(AccessProviderTypeDynv6)
ACMEDns01ProviderTypeGandinet = ACMEDns01ProviderType(AccessProviderTypeGandinet)
ACMEDns01ProviderTypeGcore = ACMEDns01ProviderType(AccessProviderTypeGcore)
@@ -170,6 +178,7 @@ const (
ACMEDns01ProviderTypeHostinger = ACMEDns01ProviderType(AccessProviderTypeHostinger)
ACMEDns01ProviderTypeHuaweiCloud = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeHuaweiCloudDNS]
ACMEDns01ProviderTypeHuaweiCloudDNS = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud + "-dns")
+ ACMEDns01ProviderTypeInfomaniak = ACMEDns01ProviderType(AccessProviderTypeInfomaniak)
ACMEDns01ProviderTypeIONOS = ACMEDns01ProviderType(AccessProviderTypeIONOS)
ACMEDns01ProviderTypeJDCloud = ACMEDns01ProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeJDCloudDNS]
ACMEDns01ProviderTypeJDCloudDNS = ACMEDns01ProviderType(AccessProviderTypeJDCloud + "-dns")
@@ -180,6 +189,7 @@ const (
ACMEDns01ProviderTypeNetcup = ACMEDns01ProviderType(AccessProviderTypeNetcup)
ACMEDns01ProviderTypeNetlify = ACMEDns01ProviderType(AccessProviderTypeNetlify)
ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1)
+ ACMEDns01ProviderTypeOVHcloud = ACMEDns01ProviderType(AccessProviderTypeOVHcloud)
ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun)
ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS)
ACMEDns01ProviderTypeRainYun = ACMEDns01ProviderType(AccessProviderTypeRainYun)
diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/arvancloud/arvancloud.go b/pkg/core/ssl-applicator/acme-dns01/providers/arvancloud/arvancloud.go
new file mode 100644
index 000000000..60658885b
--- /dev/null
+++ b/pkg/core/ssl-applicator/acme-dns01/providers/arvancloud/arvancloud.go
@@ -0,0 +1,38 @@
+package arvancloud
+
+import (
+ "errors"
+ "time"
+
+ "github.com/go-acme/lego/v4/providers/dns/arvancloud"
+
+ "github.com/certimate-go/certimate/pkg/core"
+)
+
+type ChallengeProviderConfig struct {
+ ApiKey string `json:"apiKey"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) {
+ if config == nil {
+ return nil, errors.New("the configuration of the acme challenge provider is nil")
+ }
+
+ providerConfig := arvancloud.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 := arvancloud.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/ui/public/imgs/providers/arvancloud.svg b/ui/public/imgs/providers/arvancloud.svg
new file mode 100644
index 000000000..709bea279
--- /dev/null
+++ b/ui/public/imgs/providers/arvancloud.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
index fa5b18009..7afcb7fb1 100644
--- a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
+++ b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
@@ -9,6 +9,7 @@ import AccessConfigFieldsProviderACMEHttpReq from "./AccessConfigFieldsProviderA
import AccessConfigFieldsProviderActalisSSL from "./AccessConfigFieldsProviderActalisSSL";
import AccessConfigFieldsProviderAliyun from "./AccessConfigFieldsProviderAliyun";
import AccessConfigFieldsProviderAPISIX from "./AccessConfigFieldsProviderAPISIX";
+import AccessConfigFieldsProviderArvanCloud from "./AccessConfigFieldsProviderArvanCloud";
import AccessConfigFieldsProviderAWS from "./AccessConfigFieldsProviderAWS";
import AccessConfigFieldsProviderAzure from "./AccessConfigFieldsProviderAzure";
import AccessConfigFieldsProviderBaiduCloud from "./AccessConfigFieldsProviderBaiduCloud";
@@ -101,6 +102,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 {
+ apiKey: "",
+ };
+};
+
+const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => {
+ const { t } = i18n;
+
+ return z.object({
+ apiKey: z.string().nonempty(t("access.form.arvancloud_api_key.placeholder")),
+ });
+};
+
+const _default = Object.assign(AccessConfigFormFieldsProviderArvanCloud, {
+ getInitialValues,
+ getSchema,
+});
+
+export default _default;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 1e10f03ec..5a3a38623 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -22,6 +22,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
ACTALISSSL: "actalisssl",
ALIYUN: "aliyun",
APISIX: "apisix",
+ ARVANCLOUD: "arvancloud",
AWS: "aws",
AZURE: "azure",
BAIDUCLOUD: "baiducloud",
@@ -44,8 +45,10 @@ export const ACCESS_PROVIDERS = Object.freeze({
DINGTALKBOT: "dingtalkbot",
DISCORDBOT: "discordbot",
DNSLA: "dnsla",
+ DNSMADEEASY: "dnsmadeeasy",
DOGECLOUD: "dogecloud",
DUCKDNS: "duckdns",
+ DYNU: "dynu",
DYNV6: "dynv6",
EMAIL: "email",
FLEXCDN: "flexcdn",
@@ -60,6 +63,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
HOSTINGDE: "hostingDE",
HOSTINGER: "hostinger",
HUAWEICLOUD: "huaweicloud",
+ INFOMANIAK: "infomaniak",
IONOS: "ionos",
JDCLOUD: "jdcloud",
KONG: "kong",
@@ -77,6 +81,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
NETCUP: "netcup",
NETLIFY: "netlify",
NS1: "ns1",
+ OVHCLOUD: "ovhcloud",
PORKBUN: "porkbun",
POWERDNS: "powerdns",
PROXMOXVE: "proxmoxve",
@@ -170,6 +175,7 @@ export const accessProvidersMap: Maphttps://apisix.apache.org/docs/apisix/admin-api/",
+ "access.form.arvancloud_api_key.label": "ArvanCloud API key",
+ "access.form.arvancloud_api_key.placeholder": "Please enter ArvanCloud API key",
+ "access.form.arvancloud_api_key.tooltip": "For more information, see https://docs.arvancloud.ir/en/developer-tools/api/api-key",
"access.form.aws_access_key_id.label": "AWS AccessKeyID",
"access.form.aws_access_key_id.placeholder": "Please enter AWS AccessKeyID",
"access.form.aws_access_key_id.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/id_credentials_access-keys.html",
diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json
index 06d0270ec..9207ee4dc 100644
--- a/ui/src/i18n/locales/en/nls.provider.json
+++ b/ui/src/i18n/locales/en/nls.provider.json
@@ -27,6 +27,7 @@
"provider.akamai": "Akamai",
"provider.akamai.cdn": "Akamai - CDN (Content Delivery Network)",
"provider.apisix": "Apache APISIX",
+ "provider.arvancloud": "ArvanCloud",
"provider.aws": "AWS",
"provider.aws.acm": "AWS - ACM (Amazon Certificate Manager)",
"provider.aws.cloudfront": "AWS - CloudFront",
@@ -78,9 +79,11 @@
"provider.dingtalkbot": "DingTalk Bot",
"provider.discordbot": "Discord Bot",
"provider.dnsla": "DNS.LA",
- "provider.dogecloud": "Doge Cloud",
+ "provider.dnsmadeeasy": "DNS Made Easy",
"provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)",
+ "provider.dogecloud": "Doge Cloud",
"provider.duckdns": "Duck DNS",
+ "provider.dynu": "Dynu",
"provider.dynv6": "dynv6",
"provider.email": "Email (SMTP)",
"provider.fastly": "Fastly",
@@ -103,6 +106,7 @@
"provider.huaweicloud.obs": "Huawei Cloud - OBS (Object Storage Service)",
"provider.huaweicloud.scm_upload": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)",
"provider.huaweicloud.waf": "Huawei Cloud - WAF (Web Application Firewall)",
+ "provider.infomaniak": "Infomaniak",
"provider.ionos": "IONOS",
"provider.jdcloud": "JD Cloud",
"provider.jdcloud.alb": "JD Cloud - ALB (Application Load Balancer)",
@@ -126,6 +130,7 @@
"provider.netcup": "netcup",
"provider.netlify": "Netlify",
"provider.netlify.site": "Netlify - Site",
+ "provider.ovhcloud": "OVHcloud",
"provider.ns1": "NS1 (IBM NS1 Connect)",
"provider.porkbun": "Porkbun",
"provider.powerdns": "PowerDNS",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index 38bef4dae..ca95e968b 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -97,6 +97,9 @@
"access.form.apisix_api_key.label": "APISIX Admin API Key",
"access.form.apisix_api_key.placeholder": "请输入 APISIX Admin API Key",
"access.form.apisix_api_key.tooltip": "这是什么?请参阅 https://apisix.apache.org/zh/docs/apisix/admin-api/",
+ "access.form.arvancloud_api_key.label": "ArvanCloud API 密钥",
+ "access.form.arvancloud_api_key.placeholder": "请输入 ArvanCloud API 密钥",
+ "access.form.arvancloud_api_key.tooltip": "这是什么?请参阅 https://docs.arvancloud.ir/en/developer-tools/api/api-key",
"access.form.aws_access_key_id.label": "AWS AccessKeyID",
"access.form.aws_access_key_id.placeholder": "请输入 AWS AccessKeyID",
"access.form.aws_access_key_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_credentials_access-keys.html",
diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json
index 55d5af7b6..cafb7a867 100644
--- a/ui/src/i18n/locales/zh/nls.provider.json
+++ b/ui/src/i18n/locales/zh/nls.provider.json
@@ -27,6 +27,7 @@
"provider.akamai": "Akamai",
"provider.akamai.cdn": "Akamai - 内容分发网络 CDN",
"provider.apisix": "Apache APISIX",
+ "provider.arvancloud": "ArvanCloud",
"provider.aws": "AWS",
"provider.aws.acm": "AWS - ACM (Amazon Certificate Manager)",
"provider.aws.cloudfront": "AWS - CloudFront",
@@ -78,9 +79,11 @@
"provider.dingtalkbot": "钉钉群机器人",
"provider.discordbot": "Discord 机器人",
"provider.dnsla": "DNS.LA",
+ "provider.dnsmadeeasy": "DNS Made Easy",
"provider.dogecloud": "多吉云",
"provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN",
"provider.duckdns": "Duck DNS",
+ "provider.dynu": "Dynu",
"provider.dynv6": "dynv6",
"provider.email": "邮件(SMTP)",
"provider.fastly": "Fastly",
@@ -103,6 +106,7 @@
"provider.huaweicloud.obs": "华为云 - 对象储存服务 OBS",
"provider.huaweicloud.scm_upload": "华为云 - 上传到云证书管理服务 SCM",
"provider.huaweicloud.waf": "华为云 - Web 应用防火墙 WAF",
+ "provider.infomaniak": "Infomaniak",
"provider.ionos": "IONOS",
"provider.jdcloud": "京东云",
"provider.jdcloud.alb": "京东云 - 应用负载均衡 ALB",
@@ -127,6 +131,7 @@
"provider.netlify": "Netlify",
"provider.netlify.site": "Netlify - Site",
"provider.ns1": "NS1 (IBM NS1 Connect)",
+ "provider.ovhcloud": "OVHcloud",
"provider.porkbun": "Porkbun",
"provider.powerdns": "PowerDNS",
"provider.proxmoxve": "Proxmox VE",