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 00000000..2f56ce11 Binary files /dev/null and b/ui/public/imgs/providers/netcup.png differ diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 02a71854..b7d374d3 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -46,6 +46,7 @@ import AccessFormMattermostConfig from "./AccessFormMattermostConfig"; import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig"; import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig"; import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig"; +import AccessFormNetcupConfig from "./AccessFormNetcupConfig"; import AccessFormNS1Config from "./AccessFormNS1Config"; import AccessFormPorkbunConfig from "./AccessFormPorkbunConfig"; import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig"; @@ -242,6 +243,8 @@ const AccessForm = forwardRef(({ 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",