feat(provider): new acme dns-01 provider: hostinger

This commit is contained in:
Fu Diwei
2025-10-22 22:25:16 +08:00
parent f4ea0a071f
commit 715dc20c73
13 changed files with 143 additions and 2 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"github.com/go-acme/lego/v4/challenge"
"github.com/certimate-go/certimate/internal/domain"
hetzner "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/hetzner"
"github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/hetzner"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
@@ -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/hostinger"
xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
)
func init() {
if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeHostinger, func(options *ProviderFactoryOptions) (challenge.Provider, error) {
credentials := domain.AccessConfigForHostinger{}
if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
provider, err := hostinger.NewChallengeProvider(&hostinger.ChallengeProviderConfig{
ApiToken: credentials.ApiToken,
DnsPropagationTimeout: options.DnsPropagationTimeout,
DnsTTL: options.DnsTTL,
})
return provider, err
}); err != nil {
panic(err)
}
}
+4
View File
@@ -242,6 +242,10 @@ type AccessConfigForHetzner struct {
ApiToken string `json:"apiToken"`
}
type AccessConfigForHostinger struct {
ApiToken string `json:"apiToken"`
}
type AccessConfigForHuaweiCloud struct {
AccessKeyId string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
+2 -1
View File
@@ -53,7 +53,7 @@ const (
AccessProviderTypeGlobalSignAtlas = AccessProviderType("globalsignatlas")
AccessProviderTypeGoogleTrustServices = AccessProviderType("googletrustservices")
AccessProviderTypeHetzner = AccessProviderType("hetzner")
AccessProviderTypeHostinger = AccessProviderType("hostinger") // Hostinger(预留)
AccessProviderTypeHostinger = AccessProviderType("hostinger")
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
AccessProviderTypeKong = AccessProviderType("kong")
@@ -162,6 +162,7 @@ const (
ACMEDns01ProviderTypeGname = ACMEDns01ProviderType(AccessProviderTypeGname)
ACMEDns01ProviderTypeGoDaddy = ACMEDns01ProviderType(AccessProviderTypeGoDaddy)
ACMEDns01ProviderTypeHetzner = ACMEDns01ProviderType(AccessProviderTypeHetzner)
ACMEDns01ProviderTypeHostinger = ACMEDns01ProviderType(AccessProviderTypeHostinger)
ACMEDns01ProviderTypeHuaweiCloud = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeHuaweiCloudDNS]
ACMEDns01ProviderTypeHuaweiCloudDNS = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud + "-dns")
ACMEDns01ProviderTypeJDCloud = ACMEDns01ProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeJDCloudDNS]
@@ -0,0 +1,38 @@
package hostinger
import (
"errors"
"time"
"github.com/go-acme/lego/v4/providers/dns/hostinger"
"github.com/certimate-go/certimate/pkg/core"
)
type ChallengeProviderConfig struct {
ApiToken string `json:"apiToken"`
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 := hostinger.NewDefaultConfig()
providerConfig.APIToken = config.ApiToken
if config.DnsPropagationTimeout != 0 {
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
}
if config.DnsTTL != 0 {
providerConfig.TTL = int(config.DnsTTL)
}
provider, err := hostinger.NewDNSProviderConfig(providerConfig)
if err != nil {
return nil, err
}
return provider, nil
}
+1
View File
@@ -0,0 +1 @@
<svg clip-rule="evenodd" fill-rule="evenodd" height="200" stroke-linejoin="round" stroke-miterlimit="2" viewBox="7.002 8.287 148.203 175.426" width="200" xmlns="http://www.w3.org/2000/svg"><path d="m7.002 8.287 39.319 21.172v39.32h57.467l36.295 21.172h-133.081zm148.203 75.615v-54.443l-42.344-21.172v51.418zm0 99.811-39.319-21.172v-39.32h-57.467l-36.295-21.172h133.081zm-148.203-75.615v54.443l42.343 21.172v-51.418z" fill="#6747c7"/></svg>

After

Width:  |  Height:  |  Size: 440 B

+4
View File
@@ -52,6 +52,7 @@ import AccessConfigFieldsProviderGoDaddy from "./forms/AccessConfigFieldsProvide
import AccessConfigFieldsProviderGoEdge from "./forms/AccessConfigFieldsProviderGoEdge";
import AccessConfigFieldsProviderGoogleTrustServices from "./forms/AccessConfigFieldsProviderGoogleTrustServices";
import AccessConfigFieldsProviderHetzner from "./forms/AccessConfigFieldsProviderHetzner";
import AccessConfigFieldsProviderHostinger from "./forms/AccessConfigFieldsProviderHostinger";
import AccessConfigFieldsProviderHuaweiCloud from "./forms/AccessConfigFieldsProviderHuaweiCloud";
import AccessConfigFieldsProviderJDCloud from "./forms/AccessConfigFieldsProviderJDCloud";
import AccessConfigFieldsProviderKong from "./forms/AccessConfigFieldsProviderKong";
@@ -259,6 +260,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, on
case ACCESS_PROVIDERS.HETZNER: {
return <AccessConfigFieldsProviderHetzner />;
}
case ACCESS_PROVIDERS.HOSTINGER: {
return <AccessConfigFieldsProviderHostinger />;
}
case ACCESS_PROVIDERS.HUAWEICLOUD: {
return <AccessConfigFieldsProviderHuaweiCloud />;
}
@@ -0,0 +1,52 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { useFormNestedFieldsContext } from "./_context";
const AccessConfigFormFieldsProviderHostinger = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "apiToken"]}
initialValue={initialValues.apiToken}
label={t("access.form.hostinger_api_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.hostinger_api_token.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.hostinger_api_token.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
apiToken: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
apiToken: z.string().nonempty(t("access.form.hostinger_api_token.placeholder")),
});
};
const _default = Object.assign(AccessConfigFormFieldsProviderHostinger, {
getInitialValues,
getSchema,
});
export default _default;
+4
View File
@@ -56,6 +56,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
GOEDGE: "goedge",
GOOGLETRUSTSERVICES: "googletrustservices",
HETZNER: "hetzner",
HOSTINGER: "hostinger",
HUAWEICLOUD: "huaweicloud",
JDCLOUD: "jdcloud",
KONG: "kong",
@@ -177,6 +178,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.GNAME, "provider.gname", "/imgs/providers/gname.png", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.GODADDY, "provider.godaddy", "/imgs/providers/godaddy.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.HETZNER, "provider.hetzner", "/imgs/providers/hetzner.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.HOSTINGER, "provider.hostinger", "/imgs/providers/hostinger.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.NAMECHEAP, "provider.namecheap", "/imgs/providers/namecheap.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.NAMEDOTCOM, "provider.namedotcom", "/imgs/providers/namedotcom.svg", [ACCESS_USAGES.DNS]],
[ACCESS_PROVIDERS.NAMESILO, "provider.namesilo", "/imgs/providers/namesilo.svg", [ACCESS_USAGES.DNS]],
@@ -312,6 +314,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
GNAME: `${ACCESS_PROVIDERS.GNAME}`,
GODADDY: `${ACCESS_PROVIDERS.GODADDY}`,
HETZNER: `${ACCESS_PROVIDERS.HETZNER}`,
HOSTINGER: `${ACCESS_PROVIDERS.HOSTINGER}`,
HUAWEICLOUD: `${ACCESS_PROVIDERS.HUAWEICLOUD}`, // 兼容旧值,等同于 `HUAWEICLOUD_DNS`
HUAWEICLOUD_DNS: `${ACCESS_PROVIDERS.HUAWEICLOUD}-dns`,
JDCLOUD: `${ACCESS_PROVIDERS.JDCLOUD}`, // 兼容旧值,等同于 `JDCLOUD_DNS`
@@ -374,6 +377,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
[ACME_DNS01_PROVIDERS.GNAME, "provider.gname"],
[ACME_DNS01_PROVIDERS.GODADDY, "provider.godaddy"],
[ACME_DNS01_PROVIDERS.HETZNER, "provider.hetzner"],
[ACME_DNS01_PROVIDERS.HOSTINGER, "provider.hostinger"],
[ACME_DNS01_PROVIDERS.NAMECHEAP, "provider.namecheap"],
[ACME_DNS01_PROVIDERS.NAMEDOTCOM, "provider.namedotcom"],
[ACME_DNS01_PROVIDERS.NAMESILO, "provider.namesilo"],
+3
View File
@@ -293,6 +293,9 @@
"access.form.hetzner_api_token.label": "Hetzner API token",
"access.form.hetzner_api_token.placeholder": "Please enter Hetzner API token",
"access.form.hetzner_api_token.tooltip": "For more information, see <a href=\"https://docs.hetzner.com/cloud/api/getting-started/generating-api-token\" target=\"_blank\">https://docs.hetzner.com/cloud/api/getting-started/generating-api-token</a>",
"access.form.hostinger_api_token.label": "Hostinger API token",
"access.form.hostinger_api_token.placeholder": "Please enter Hostinger API token",
"access.form.hostinger_api_token.tooltip": "For more information, see <a href=\"https://developers.hostinger.com/#description/authentication\" target=\"_blank\">https://developers.hostinger.com/#description/authentication</a>",
"access.form.huaweicloud_access_key_id.label": "Huawei Cloud AccessKeyId",
"access.form.huaweicloud_access_key_id.placeholder": "Please enter Huawei Cloud AccessKeyId",
"access.form.huaweicloud_access_key_id.tooltip": "For more information, see <a href=\"https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html\" target=\"_blank\">https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html</a>",
+1
View File
@@ -92,6 +92,7 @@
"provider.goedge": "GoEdge",
"provider.googletrustservices": "Google Trust Services",
"provider.hetzner": "Hetzner",
"provider.hostinger": "Hostinger",
"provider.huaweicloud": "Huawei Cloud",
"provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)",
"provider.huaweicloud.dns": "Huawei Cloud - DNS",
+3
View File
@@ -292,6 +292,9 @@
"access.form.hetzner_api_token.label": "Hetzner API Token",
"access.form.hetzner_api_token.placeholder": "请输入 Hetzner API Token",
"access.form.hetzner_api_token.tooltip": "这是什么?请参阅 <a href=\"https://docs.hetzner.com/cloud/api/getting-started/generating-api-token\" target=\"_blank\">https://docs.hetzner.com/cloud/api/getting-started/generating-api-token</a>",
"access.form.hostinger_api_token.label": "Hostinger API Token",
"access.form.hostinger_api_token.placeholder": "请输入 Hostinger API Token",
"access.form.hostinger_api_token.tooltip": "这是什么?请参阅 <a href=\"https://developers.hostinger.com/#description/authentication\" target=\"_blank\">https://developers.hostinger.com/#description/authentication</a>",
"access.form.huaweicloud_access_key_id.label": "华为云 AccessKeyId",
"access.form.huaweicloud_access_key_id.placeholder": "请输入华为云 AccessKeyId",
"access.form.huaweicloud_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html\" target=\"_blank\">https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html</a>",
+1
View File
@@ -92,6 +92,7 @@
"provider.goedge": "GoEdge",
"provider.googletrustservices": "Google Trust Services",
"provider.hetzner": "Hetzner",
"provider.hostinger": "Hostinger",
"provider.huaweicloud": "华为云",
"provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
"provider.huaweicloud.dns": "华为云 - 云解析 DNS",