From cd93a2d72c8c18ba4a393291216bdaffaf85c044 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 15 May 2025 21:48:30 +0800 Subject: [PATCH] feat: new acme dns-01 provider: netcup --- internal/applicant/providers.go | 18 ++++ internal/domain/access.go | 6 ++ internal/domain/provider.go | 2 + .../lego-providers/netcup/netcup.go | 40 +++++++++ ui/public/imgs/providers/netcup.png | Bin 0 -> 4093 bytes ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormNetcupConfig.tsx | 79 ++++++++++++++++++ .../node/DeployNodeConfigFormSSHConfig.tsx | 1 - ui/src/domain/access.ts | 7 ++ ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 9 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 9 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 2 +- 15 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 internal/pkg/core/applicant/acme-dns-01/lego-providers/netcup/netcup.go create mode 100644 ui/public/imgs/providers/netcup.png create mode 100644 ui/src/components/access/AccessFormNetcupConfig.tsx diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 454f9376..d4d630c4 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -27,6 +27,7 @@ import ( pNamecheap "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap" pNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom" pNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo" + pNetcup "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/netcup" pNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1" pPorkbun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/porkbun" pPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns" @@ -402,6 +403,23 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi return applicant, err } + case domain.ACMEDns01ProviderTypeNetcup: + { + access := domain.AccessConfigForNetcup{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + applicant, err := pNetcup.NewChallengeProvider(&pNetcup.ChallengeProviderConfig{ + CustomerNumber: access.CustomerNumber, + ApiKey: access.ApiKey, + ApiPassword: access.ApiPassword, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return applicant, err + } + case domain.ACMEDns01ProviderTypeNS1: { access := domain.AccessConfigForNS1{} diff --git a/internal/domain/access.go b/internal/domain/access.go index b2ff5e94..c18f846e 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -199,6 +199,12 @@ type AccessConfigForNameSilo struct { ApiKey string `json:"apiKey"` } +type AccessConfigForNetcup struct { + CustomerNumber string `json:"customerNumber"` + ApiKey string `json:"apiKey"` + ApiPassword string `json:"apiPassword"` +} + type AccessConfigForNS1 struct { ApiKey string `json:"apiKey"` } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 728f89b6..4de70cd3 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -52,6 +52,7 @@ const ( AccessProviderTypeNamecheap = AccessProviderType("namecheap") AccessProviderTypeNameDotCom = AccessProviderType("namedotcom") AccessProviderTypeNameSilo = AccessProviderType("namesilo") + AccessProviderTypeNetcup = AccessProviderType("netcup") AccessProviderTypeNS1 = AccessProviderType("ns1") AccessProviderTypePorkbun = AccessProviderType("porkbun") AccessProviderTypePowerDNS = AccessProviderType("powerdns") @@ -130,6 +131,7 @@ const ( ACMEDns01ProviderTypeNamecheap = ACMEDns01ProviderType(AccessProviderTypeNamecheap) ACMEDns01ProviderTypeNameDotCom = ACMEDns01ProviderType(AccessProviderTypeNameDotCom) ACMEDns01ProviderTypeNameSilo = ACMEDns01ProviderType(AccessProviderTypeNameSilo) + ACMEDns01ProviderTypeNetcup = ACMEDns01ProviderType(AccessProviderTypeNetcup) ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1) ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun) ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS) diff --git a/internal/pkg/core/applicant/acme-dns-01/lego-providers/netcup/netcup.go b/internal/pkg/core/applicant/acme-dns-01/lego-providers/netcup/netcup.go new file mode 100644 index 00000000..43d7a694 --- /dev/null +++ b/internal/pkg/core/applicant/acme-dns-01/lego-providers/netcup/netcup.go @@ -0,0 +1,40 @@ +package netcup + +import ( + "time" + + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/providers/dns/netcup" +) + +type ChallengeProviderConfig struct { + CustomerNumber string `json:"customerNumber"` + ApiKey string `json:"apiKey"` + ApiPassword string `json:"apiPassword"` + 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 := netcup.NewDefaultConfig() + providerConfig.Customer = config.CustomerNumber + providerConfig.Key = config.ApiKey + providerConfig.Password = config.ApiPassword + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := netcup.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/netcup.png b/ui/public/imgs/providers/netcup.png new file mode 100644 index 0000000000000000000000000000000000000000..2f56ce118c3868be85c1eba20bf2592c673f04a0 GIT binary patch literal 4093 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 className, return ; case ACCESS_PROVIDERS.NAMESILO: return ; + case ACCESS_PROVIDERS.NETCUP: + return ; case ACCESS_PROVIDERS.NS1: return ; case ACCESS_PROVIDERS.PORKBUN: diff --git a/ui/src/components/access/AccessFormNetcupConfig.tsx b/ui/src/components/access/AccessFormNetcupConfig.tsx new file mode 100644 index 00000000..c5d4bfc6 --- /dev/null +++ b/ui/src/components/access/AccessFormNetcupConfig.tsx @@ -0,0 +1,79 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForNetcup } from "@/domain/access"; + +type AccessFormNetcupConfigFieldValues = Nullish; + +export type AccessFormNetcupConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormNetcupConfigFieldValues; + onValuesChange?: (values: AccessFormNetcupConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormNetcupConfigFieldValues => { + return { + customerNumber: "", + apiKey: "", + apiPassword: "", + }; +}; + +const AccessFormNetcupConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormNetcupConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + customerNumber: z.string().nonempty(t("access.form.netcup_customer_number.placeholder")).trim(), + apiKey: z.string().nonempty(t("access.form.netcup_api_key.placeholder")).trim(), + apiPassword: z.string().nonempty(t("access.form.netcup_api_password.placeholder")).trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormNetcupConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx index 0f3f3082..e99a2431 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx @@ -131,7 +131,6 @@ info "Completed" return `# *** 需要 root 权限 *** # 脚本参考 https://github.com/lfgyx/fnos_certificate_update/blob/main/src/update_cert.sh - # 请将以下变量替换为实际值 # 飞牛证书实际存放路径请在 \`/usr/trim/etc/network_cert_all.conf\` 中查看,注意不要修改文件名 $tmpFullchainPath = "${params?.certPath || ""}" # 证书文件路径(与表单中保持一致) diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index b3ba29ca..e0cce59d 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -41,6 +41,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForNamecheap | AccessConfigForNameDotCom | AccessConfigForNameSilo + | AccessConfigForNetcup | AccessConfigForPorkbun | AccessConfigForPowerDNS | AccessConfigForProxmoxVE @@ -249,6 +250,12 @@ export type AccessConfigForNameSilo = { apiKey: string; }; +export type AccessConfigForNetcup = { + customerNumber: string; + apiKey: string; + apiPassword: string; +}; + export type AccessConfigForNS1 = { apiKey: string; }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 6fc29ad4..5cc50534 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -43,6 +43,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ NAMECHEAP: "namecheap", NAMEDOTCOM: "namedotcom", NAMESILO: "namesilo", + NETCUP: "netcup", NS1: "ns1", PORKBUN: "porkbun", POWERDNS: "powerdns", @@ -132,6 +133,7 @@ export const accessProvidersMap: Maphttps://www.namesilo.com/support/v2/articles/account-options/api-manager", + "access.form.netcup_customer_number.label": "netcup customer number", + "access.form.netcup_customer_number.placeholder": "Please enter netcup customer number", + "access.form.netcup_customer_number.tooltip": "For more information, see https://helpcenter.netcup.com/en/wiki/general/ccp-login/", + "access.form.netcup_api_key.label": "netcup API key", + "access.form.netcup_api_key.placeholder": "Please enter netcup API key", + "access.form.netcup_api_key.tooltip": "For more information, see https://helpcenter.netcup.com/en/wiki/general/our-api/", + "access.form.netcup_api_password.label": "netcup API password", + "access.form.netcup_api_password.placeholder": "Please enter netcup API password", + "access.form.netcup_api_password.tooltip": "For more information, see https://helpcenter.netcup.com/en/wiki/general/our-api/", "access.form.ns1_api_key.label": "NS1 API key", "access.form.ns1_api_key.placeholder": "Please enter NS1 API key", "access.form.ns1_api_key.tooltip": "For more information, see https://www.ibm.com/docs/en/ns1-connect?topic=introduction-using-api", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 0c140684..1f7e5589 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -90,6 +90,7 @@ "provider.namecheap": "Namecheap", "provider.namedotcom": "Name.com", "provider.namesilo": "NameSilo", + "provider.netcup": "netcup", "provider.ns1": "NS1 (IBM NS1 Connect)", "provider.porkbun": "Porkbun", "provider.powerdns": "PowerDNS", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 4839d6ad..fd993178 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -256,6 +256,15 @@ "access.form.namesilo_api_key.label": "NameSilo API Key", "access.form.namesilo_api_key.placeholder": "请输入 NameSilo API Key", "access.form.namesilo_api_key.tooltip": "这是什么?请参阅 https://www.namesilo.com/support/v2/articles/account-options/api-manager", + "access.form.netcup_customer_number.label": "netcup 客户编号", + "access.form.netcup_customer_number.placeholder": "请输入 netcup 客户编号", + "access.form.netcup_customer_number.tooltip": "这是什么?请参阅 https://helpcenter.netcup.com/en/wiki/general/ccp-login/", + "access.form.netcup_api_key.label": "netcup API Key", + "access.form.netcup_api_key.placeholder": "请输入 netcup API Key", + "access.form.netcup_api_key.tooltip": "这是什么?请参阅 https://helpcenter.netcup.com/en/wiki/general/our-api/", + "access.form.netcup_api_password.label": "netcup API Key 密码", + "access.form.netcup_api_password.placeholder": "请输入 netcup API Key 密码", + "access.form.netcup_api_password.tooltip": "这是什么?请参阅 https://helpcenter.netcup.com/en/wiki/general/our-api/", "access.form.ns1_api_key.label": "NS1 API Key", "access.form.ns1_api_key.placeholder": "请输入 NS1 API Key", "access.form.ns1_api_key.tooltip": "这是什么?请参阅 https://www.ibm.com/docs/zh/ns1-connect?topic=introduction-using-api", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 5626426e..739fcebf 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -90,6 +90,7 @@ "provider.namecheap": "Namecheap", "provider.namedotcom": "Name.com", "provider.namesilo": "NameSilo", + "provider.netcup": "netcup", "provider.ns1": "NS1 (IBM NS1 Connect)", "provider.porkbun": "Porkbun", "provider.powerdns": "PowerDNS", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 378cff37..d1829698 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -549,7 +549,7 @@ "workflow_node.deploy.form.ssh_preset_scripts.option.ps_backup_files.label": "PowerShell - 备份原证书文件", "workflow_node.deploy.form.ssh_preset_scripts.option.sh_reload_nginx.label": "POSIX Bash - 重启 nginx 进程", "workflow_node.deploy.form.ssh_preset_scripts.option.sh_replace_synologydsm_ssl.label": "POSIX Bash - 替换群晖 DSM 证书", - "workflow_node.deploy.form.ssh_preset_scripts.option.sh_replace_fnos_ssl.label": "POSIX Bash - 替换飞牛 OS 证书", + "workflow_node.deploy.form.ssh_preset_scripts.option.sh_replace_fnos_ssl.label": "POSIX Bash - 替换飞牛 fnOS 证书", "workflow_node.deploy.form.ssh_preset_scripts.option.ps_binding_iis.label": "PowerShell - 导入并绑定到 IIS", "workflow_node.deploy.form.ssh_preset_scripts.option.ps_binding_netsh.label": "PowerShell - 导入并绑定到 netsh", "workflow_node.deploy.form.ssh_preset_scripts.option.ps_binding_rdp.label": "PowerShell - 导入并绑定到 RDP",