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