diff --git a/internal/certapply/applicators/sp_hetzner.go b/internal/certapply/applicators/sp_hetzner.go index 89ec83e60..19457def2 100644 --- a/internal/certapply/applicators/sp_hetzner.go +++ b/internal/certapply/applicators/sp_hetzner.go @@ -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" ) diff --git a/internal/certapply/applicators/sp_hostinger.go b/internal/certapply/applicators/sp_hostinger.go new file mode 100644 index 000000000..80c65b767 --- /dev/null +++ b/internal/certapply/applicators/sp_hostinger.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/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) + } +} diff --git a/internal/domain/access.go b/internal/domain/access.go index dde8954cf..405691a71 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -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"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index bb970cd26..1389ca7e4 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -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] diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/hostinger/hostinger.go b/pkg/core/ssl-applicator/acme-dns01/providers/hostinger/hostinger.go new file mode 100644 index 000000000..e8988764c --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/hostinger/hostinger.go @@ -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 +} diff --git a/ui/public/imgs/providers/hostinger.svg b/ui/public/imgs/providers/hostinger.svg new file mode 100644 index 000000000..e0f4829eb --- /dev/null +++ b/ui/public/imgs/providers/hostinger.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index e0eab008b..90ad24ebf 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -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 ; } + case ACCESS_PROVIDERS.HOSTINGER: { + return ; + } case ACCESS_PROVIDERS.HUAWEICLOUD: { return ; } diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderHostinger.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderHostinger.tsx new file mode 100644 index 000000000..9b32e0281 --- /dev/null +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderHostinger.tsx @@ -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 ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + apiToken: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + 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; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index e9a777fb9..af32e9f64 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -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: Maphttps://docs.hetzner.com/cloud/api/getting-started/generating-api-token", + "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 https://developers.hostinger.com/#description/authentication", "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 https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 1f6c86b64..c43f2dd52 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -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", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index a6666061e..a7751327e 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -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": "这是什么?请参阅 https://docs.hetzner.com/cloud/api/getting-started/generating-api-token", + "access.form.hostinger_api_token.label": "Hostinger API Token", + "access.form.hostinger_api_token.placeholder": "请输入 Hostinger API Token", + "access.form.hostinger_api_token.tooltip": "这是什么?请参阅 https://developers.hostinger.com/#description/authentication", "access.form.huaweicloud_access_key_id.label": "华为云 AccessKeyId", "access.form.huaweicloud_access_key_id.placeholder": "请输入华为云 AccessKeyId", "access.form.huaweicloud_access_key_id.tooltip": "这是什么?请参阅 https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index dc743940b..60c310401 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -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",