diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go
index 81cf8156..f967aef9 100644
--- a/internal/applicant/providers.go
+++ b/internal/applicant/providers.go
@@ -22,6 +22,7 @@ import (
pGcore "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gcore"
pGname "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/gname"
pGoDaddy "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/godaddy"
+ pHetzner "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/hetzner"
pHuaweiCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/huaweicloud"
pJDCloud "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/jdcloud"
pNamecheap "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap"
@@ -324,6 +325,21 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
return applicant, err
}
+ case domain.ACMEDns01ProviderTypeHetzner:
+ {
+ access := domain.AccessConfigForHetzner{}
+ if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ applicant, err := pHetzner.NewChallengeProvider(&pHetzner.ChallengeProviderConfig{
+ ApiToken: access.ApiToken,
+ DnsPropagationTimeout: options.DnsPropagationTimeout,
+ DnsTTL: options.DnsTTL,
+ })
+ return applicant, err
+ }
+
case domain.ACMEDns01ProviderTypeHuaweiCloud, domain.ACMEDns01ProviderTypeHuaweiCloudDNS:
{
access := domain.AccessConfigForHuaweiCloud{}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index 7e1549f1..30088bdf 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -181,6 +181,10 @@ type AccessConfigForGoogleTrustServices struct {
EabHmacKey string `json:"eabHmacKey"`
}
+type AccessConfigForHetzner 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 db0de59d..4552553d 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -44,6 +44,7 @@ const (
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
AccessProviderTypeGoEdge = AccessProviderType("goedge")
AccessProviderTypeGoogleTrustServices = AccessProviderType("googletrustservices")
+ AccessProviderTypeHetzner = AccessProviderType("hetzner")
AccessProviderTypeHuaweiCloud = AccessProviderType("huaweicloud")
AccessProviderTypeJDCloud = AccessProviderType("jdcloud")
AccessProviderTypeKubernetes = AccessProviderType("k8s")
@@ -131,6 +132,7 @@ const (
ACMEDns01ProviderTypeGcore = ACMEDns01ProviderType(AccessProviderTypeGcore)
ACMEDns01ProviderTypeGname = ACMEDns01ProviderType(AccessProviderTypeGname)
ACMEDns01ProviderTypeGoDaddy = ACMEDns01ProviderType(AccessProviderTypeGoDaddy)
+ ACMEDns01ProviderTypeHetzner = ACMEDns01ProviderType(AccessProviderTypeHetzner)
ACMEDns01ProviderTypeHuaweiCloud = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeHuaweiCloudDNS]
ACMEDns01ProviderTypeHuaweiCloudDNS = ACMEDns01ProviderType(AccessProviderTypeHuaweiCloud + "-dns")
ACMEDns01ProviderTypeJDCloud = ACMEDns01ProviderType(AccessProviderTypeJDCloud) // 兼容旧值,等同于 [ACMEDns01ProviderTypeJDCloudDNS]
diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/hetzner/hetzner.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/hetzner/hetzner.go
new file mode 100644
index 00000000..c202cc78
--- /dev/null
+++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/hetzner/hetzner.go
@@ -0,0 +1,36 @@
+package namedotcom
+
+import (
+ "time"
+
+ "github.com/go-acme/lego/v4/challenge"
+ "github.com/go-acme/lego/v4/providers/dns/hetzner"
+)
+
+type ChallengeProviderConfig struct {
+ ApiToken string `json:"apiToken"`
+ DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
+ DnsTTL int32 `json:"dnsTTL,omitempty"`
+}
+
+func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
+ if config == nil {
+ panic("config is nil")
+ }
+
+ providerConfig := hetzner.NewDefaultConfig()
+ providerConfig.APIKey = 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 := hetzner.NewDNSProviderConfig(providerConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return provider, nil
+}
diff --git a/ui/public/imgs/providers/hetzner.svg b/ui/public/imgs/providers/hetzner.svg
new file mode 100644
index 00000000..670cdda8
--- /dev/null
+++ b/ui/public/imgs/providers/hetzner.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index fdf4f93f..6f4d5086 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -41,6 +41,7 @@ import AccessFormGnameConfig from "./AccessFormGnameConfig";
import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig";
import AccessFormGoEdgeConfig from "./AccessFormGoEdgeConfig";
import AccessFormGoogleTrustServicesConfig from "./AccessFormGoogleTrustServicesConfig";
+import AccessFormHetznerConfig from "./AccessFormHetznerConfig";
import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig";
import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig";
import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig";
@@ -239,6 +240,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.GOOGLETRUSTSERVICES:
return ;
+ case ACCESS_PROVIDERS.HETZNER:
+ return ;
case ACCESS_PROVIDERS.HUAWEICLOUD:
return ;
case ACCESS_PROVIDERS.JDCLOUD:
diff --git a/ui/src/components/access/AccessFormHetznerConfig.tsx b/ui/src/components/access/AccessFormHetznerConfig.tsx
new file mode 100644
index 00000000..12bf21b2
--- /dev/null
+++ b/ui/src/components/access/AccessFormHetznerConfig.tsx
@@ -0,0 +1,57 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type AccessConfigForHetzner } from "@/domain/access";
+
+type AccessFormHetznerConfigFieldValues = Nullish;
+
+export type AccessFormHetznerConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormHetznerConfigFieldValues;
+ onValuesChange?: (values: AccessFormHetznerConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormHetznerConfigFieldValues => {
+ return {
+ apiToken: "",
+ };
+};
+
+const AccessFormHetznerConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormHetznerConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ apiToken: z.string().nonempty(t("access.form.hetzner_api_token.placeholder")).trim(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessFormHetznerConfig;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index 3765310a..1576fd67 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -36,6 +36,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForGoDaddy
| AccessConfigForGoEdge
| AccessConfigForGoogleTrustServices
+ | AccessConfigForHetzner
| AccessConfigForHuaweiCloud
| AccessConfigForJDCloud
| AccessConfigForKubernetes
@@ -238,6 +239,10 @@ export type AccessConfigForGoogleTrustServices = {
eabHmacKey: string;
};
+export type AccessConfigForHetzner = {
+ apiToken: string;
+};
+
export type AccessConfigForHuaweiCloud = {
accessKeyId: string;
secretAccessKey: string;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 15542455..712530d7 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -35,6 +35,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
GODADDY: "godaddy",
GOEDGE: "goedge",
GOOGLETRUSTSERVICES: "googletrustservices",
+ HETZNER: "hetzner",
HUAWEICLOUD: "huaweicloud",
JDCLOUD: "jdcloud",
KUBERNETES: "k8s",
@@ -142,6 +143,7 @@ export const accessProvidersMap: Maphttps://cloud.google.com/certificate-manager/docs/public-ca-tutorial",
+ "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 https://docs.hetzner.com/cloud/api/getting-started/generating-api-token",
"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 a3bbfecc..e9122149 100644
--- a/ui/src/i18n/locales/en/nls.provider.json
+++ b/ui/src/i18n/locales/en/nls.provider.json
@@ -74,6 +74,7 @@
"provider.godaddy": "GoDaddy",
"provider.goedge": "GoEdge",
"provider.googletrustservices": "Google Trust Services",
+ "provider.hetzner": "Hetzner",
"provider.huaweicloud": "Huawei Cloud",
"provider.huaweicloud.cdn": "Huawei Cloud - CDN (Content Delivery Network)",
"provider.huaweicloud.dns": "Huawei Cloud - DNS (Domain Name Service)",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index fdae1705..e2cacc1f 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -231,6 +231,9 @@
"access.form.googletrustservices_eab_hmac_key.label": "ACME EAB HMAC Key",
"access.form.googletrustservices_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
"access.form.googletrustservices_eab_hmac_key.tooltip": "这是什么?请参阅 https://cloud.google.com/certificate-manager/docs/public-ca-tutorial",
+ "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.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 b9fb8777..a6b94779 100644
--- a/ui/src/i18n/locales/zh/nls.provider.json
+++ b/ui/src/i18n/locales/zh/nls.provider.json
@@ -74,6 +74,7 @@
"provider.godaddy": "GoDaddy",
"provider.goedge": "GoEdge",
"provider.googletrustservices": "Google Trust Services",
+ "provider.hetzner": "Hetzner",
"provider.huaweicloud": "华为云",
"provider.huaweicloud.cdn": "华为云 - 内容分发网络 CDN",
"provider.huaweicloud.dns": "华为云 - 云解析 DNS",