mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 09:21:56 +08:00
feat: new acme dns-01 provider: hetzner
This commit is contained in:
parent
b139139f50
commit
4c13a3e86a
@ -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{}
|
||||
|
@ -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"`
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
}
|
1
ui/public/imgs/providers/hetzner.svg
Normal file
1
ui/public/imgs/providers/hetzner.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3000" height="380" viewBox="0 0 182 23" version="1.1"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(-1163.000000, -1368.000000)" fill="#D50C2D" fill-rule="nonzero"><g transform="translate(256.000000, 1363.000000)"><g transform="translate(907.000000, 5.000000)"><path d="M174.05,14.12 C175.70112,13.8347617 177.256782,13.1479352 178.58,12.12 L178.58,12.12 C179.800803,10.8420724 180.41283,9.10071075 180.26,7.34 C180.264681,5.90702289 179.869913,4.5010961 179.12,3.28 C178.012832,1.39206624 176.026262,0.191846827 173.84,0.09 L172.75,0.09 L170.2,0 L158.66,0 C157.96,0 157.66,0.29 157.66,1 L157.66,21.22 C157.66,21.92 157.95,22.22 158.66,22.22 L161.66,22.22 C162.36,22.22 162.66,21.93 162.66,21.22 L162.66,14.52 L166.33,14.52 C167.137054,14.5621636 167.904273,14.8839006 168.5,15.43 L174.32,21.28 C174.850563,21.8225831 175.56276,22.1501937 176.32,22.2 L180.79,22.2 C181.49,22.2 181.66,21.79 181.17,21.29 L174.05,14.12 Z M173.29,9.82 L162.64,9.82 L162.64,4.72 L173.29,4.72 C174.371509,4.85285283 175.178361,5.7805161 175.16,6.87 L175.16,7.66 C175.178907,8.75159761 174.373058,9.68241814 173.29,9.82 L173.29,9.82 Z"/><path d="M153,17.52 L136.47,17.52 L136.47,13.35 L149.66,13.35 C150.36,13.35 150.66,13.06 150.66,12.35 L150.66,9.92 C150.66,9.22 150.37,8.92 149.66,8.92 L136.46,8.92 L136.46,4.76 L153,4.76 C153.7,4.76 154,4.47 154,3.76 L154,1 C154,0.3 153.71,0 153,0 L132.38,0 C131.68,0 131.38,0.29 131.38,1 L131.38,21.24 C131.38,21.94 131.67,22.24 132.38,22.24 L153,22.24 C153.7,22.24 154,21.95 154,21.24 L154,18.51 C154,17.81 153.67,17.52 153,17.52 Z"/><path d="M127.73,7.3 C127.75181,5.88495547 127.358997,4.49446656 126.6,3.3 C125.485363,1.36532022 123.469349,0.124117692 121.24,0 L106.08,0 C105.37,0 105.08,0.29 105.08,1 L105.08,21.22 C105.08,21.92 105.37,22.22 106.08,22.22 L109.34,22.22 C110.04,22.22 110.34,21.94 110.34,21.22 L110.34,4.73 L119.12,4.73 C120.99,4.73 122.81,5.97 122.81,7.84 L122.81,21.24 C122.81,21.94 123.1,22.24 123.81,22.24 L126.76,22.24 C127.47,22.24 127.76,21.95 127.76,21.24 L127.73,7.3 Z"/><path d="M100.47,17.39 L86.22,17.39 L100.5,4.84 C101.079439,4.39644884 101.443005,3.72748773 101.5,3 L101.5,1 C101.5,0.3 101.2,0 100.5,0 L79.83,0 C79.13,0 78.83,0.29 78.83,1 L78.83,3.77 C78.83,4.47 79.12,4.77 79.83,4.77 L93.08,4.77 L79.79,17.24 C79.2153814,17.6876568 78.8530625,18.3543236 78.79,19.08 L78.79,21.25 C78.79,21.95 79.08,22.25 79.79,22.25 L100.44,22.25 C101.14,22.25 101.44,21.96 101.44,21.25 L101.44,18.38 C101.46,17.68 101.17,17.39 100.47,17.39 Z"/><path d="M74.19,0 L53.55,0 C52.84,0 52.55,0.28 52.55,1 L52.55,3.76 C52.55,4.46 52.83,4.76 53.55,4.76 L61.33,4.76 L61.33,21.24 C61.33,21.94 61.62,22.24 62.33,22.24 L65.63,22.24 C66.33,22.24 66.63,21.95 66.63,21.24 L66.63,4.75 L74.2,4.75 C74.9,4.75 75.2,4.46 75.2,3.75 L75.2,1 C75.18,0.32 74.89,0 74.19,0 Z"/><path d="M47.91,17.52 L31.41,17.52 L31.41,13.35 L44.6,13.35 C45.3,13.35 45.6,13.06 45.6,12.35 L45.6,9.92 C45.6,9.22 45.32,8.92 44.6,8.92 L31.41,8.92 L31.41,4.76 L47.91,4.76 C48.61,4.76 48.91,4.47 48.91,3.76 L48.91,1 C48.91,0.3 48.62,0 47.91,0 L27.33,0 C26.63,0 26.33,0.29 26.33,1 L26.33,21.24 C26.33,21.94 26.62,22.24 27.33,22.24 L47.91,22.24 C48.61,22.24 48.91,21.95 48.91,21.24 L48.91,18.51 C48.9,17.81 48.61,17.52 47.91,17.52 Z"/><path d="M21.63,0 L18.52,0 C17.82,0 17.52,0.29 17.52,1 L17.52,8.87 L5.13,8.87 L5.13,1 C5.13,0.3 4.84,0 4.13,0 L1,0 C0.29,0 0,0.29 0,1 L0,21.25 C0,21.96 0.29,22.25 1,22.25 L4.13,22.25 C4.83,22.25 5.13,21.97 5.13,21.25 L5.13,13.25 L17.53,13.25 L17.53,21.25 C17.53,21.95 17.82,22.25 18.53,22.25 L21.64,22.25 C22.34,22.25 22.64,21.96 22.64,21.25 L22.64,1 C22.62,0.32 22.33,0 21.63,0 Z"/></g></g></g></g></svg>
|
After Width: | Height: | Size: 3.7 KiB |
@ -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<AccessFormInstance, AccessFormProps>(({ className,
|
||||
return <AccessFormGoEdgeConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.GOOGLETRUSTSERVICES:
|
||||
return <AccessFormGoogleTrustServicesConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.HETZNER:
|
||||
return <AccessFormHetznerConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.HUAWEICLOUD:
|
||||
return <AccessFormHuaweiCloudConfig {...nestedFormProps} />;
|
||||
case ACCESS_PROVIDERS.JDCLOUD:
|
||||
|
57
ui/src/components/access/AccessFormHetznerConfig.tsx
Normal file
57
ui/src/components/access/AccessFormHetznerConfig.tsx
Normal file
@ -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<AccessConfigForHetzner>;
|
||||
|
||||
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<typeof formSchema>) => {
|
||||
onValuesChange?.(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={formInst}
|
||||
disabled={disabled}
|
||||
initialValues={initialValues ?? initFormModel()}
|
||||
layout="vertical"
|
||||
name={formName}
|
||||
onValuesChange={handleFormChange}
|
||||
>
|
||||
<Form.Item
|
||||
name="apiToken"
|
||||
label={t("access.form.hetzner_api_token.label")}
|
||||
rules={[formRule]}
|
||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.hetzner_api_token.tooltip") }}></span>}
|
||||
>
|
||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.hetzner_api_token.placeholder")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessFormHetznerConfig;
|
@ -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;
|
||||
|
@ -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: Map<AccessProvider["type"] | string, AccessProv
|
||||
[ACCESS_PROVIDERS.DYNV6, "provider.dynv6", "/imgs/providers/dynv6.svg", [ACCESS_USAGES.DNS]],
|
||||
[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.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]],
|
||||
@ -258,6 +260,7 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
|
||||
GCORE: `${ACCESS_PROVIDERS.GCORE}`,
|
||||
GNAME: `${ACCESS_PROVIDERS.GNAME}`,
|
||||
GODADDY: `${ACCESS_PROVIDERS.GODADDY}`,
|
||||
HETZNER: `${ACCESS_PROVIDERS.HETZNER}`,
|
||||
HUAWEICLOUD: `${ACCESS_PROVIDERS.HUAWEICLOUD}`, // 兼容旧值,等同于 `HUAWEICLOUD_DNS`
|
||||
HUAWEICLOUD_DNS: `${ACCESS_PROVIDERS.HUAWEICLOUD}-dns`,
|
||||
JDCLOUD: `${ACCESS_PROVIDERS.JDCLOUD}`, // 兼容旧值,等同于 `JDCLOUD_DNS`
|
||||
@ -314,6 +317,7 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
|
||||
[ACME_DNS01_PROVIDERS.GCORE, "provider.gcore"],
|
||||
[ACME_DNS01_PROVIDERS.GNAME, "provider.gname"],
|
||||
[ACME_DNS01_PROVIDERS.GODADDY, "provider.godaddy"],
|
||||
[ACME_DNS01_PROVIDERS.HETZNER, "provider.hetzner"],
|
||||
[ACME_DNS01_PROVIDERS.NAMECHEAP, "provider.namecheap"],
|
||||
[ACME_DNS01_PROVIDERS.NAMEDOTCOM, "provider.namedotcom"],
|
||||
[ACME_DNS01_PROVIDERS.NAMESILO, "provider.namesilo"],
|
||||
|
@ -237,6 +237,9 @@
|
||||
"access.form.googletrustservices_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||
"access.form.googletrustservices_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||
"access.form.googletrustservices_eab_hmac_key.tooltip": "For more information, see <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"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.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>",
|
||||
|
@ -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)",
|
||||
|
@ -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": "这是什么?请参阅 <a href=\"https://cloud.google.com/certificate-manager/docs/public-ca-tutorial\" target=\"_blank\">https://cloud.google.com/certificate-manager/docs/public-ca-tutorial</a>",
|
||||
"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.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>",
|
||||
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user