diff --git a/go.mod b/go.mod index d10a26f3..123c69ae 100644 --- a/go.mod +++ b/go.mod @@ -66,6 +66,10 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect + github.com/alibabacloud-go/fc-20230330/v4 v4.1.7 // indirect + github.com/alibabacloud-go/fc-open-20210406 v1.1.14 // indirect + github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 // indirect github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect @@ -105,6 +109,7 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1115 // indirect github.com/x448/float16 v0.8.4 // indirect go.mongodb.org/mongo-driver v1.17.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/go.sum b/go.sum index d8160a1e..04555b1e 100644 --- a/go.sum +++ b/go.sum @@ -110,6 +110,9 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= github.com/alibabacloud-go/alb-20200616/v2 v2.2.8 h1:/6+1AqIiENG3u6RmEYWEQ/YZv3YgdFZkE6Xd9RZM6n0= github.com/alibabacloud-go/alb-20200616/v2 v2.2.8/go.mod h1:jU/K+GVb5b0vjiDpkf6E0dH77tsi1jTLGWm4ouCiRxk= +github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.6/go.mod h1:H0RPHXHP/ICfEQrKzQcCqXI15jcV4zaDPCOAmh3U9O8= +github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 h1:RDatRb9RG39HjkevgzTeiVoDDaamoB+12GHNairp3Ag= +github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7/go.mod h1:H0RPHXHP/ICfEQrKzQcCqXI15jcV4zaDPCOAmh3U9O8= github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= @@ -126,6 +129,7 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.1/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= @@ -146,6 +150,12 @@ github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9 github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= github.com/alibabacloud-go/esa-20240910/v2 v2.22.1 h1:Wmvb90nS6IxJjVe+fUWv7jtXFrJttSh/WkFQ2HIsQmQ= github.com/alibabacloud-go/esa-20240910/v2 v2.22.1/go.mod h1:P1w/+i7dE2xSXVHJznEOVImlLtqqrzUJQQk2AsyBJ6o= +github.com/alibabacloud-go/fc-20230330/v4 v4.1.7 h1:rQvPfzPaouL/WGNgMDMCplA4wDscmVFff7aLCUkjv4g= +github.com/alibabacloud-go/fc-20230330/v4 v4.1.7/go.mod h1:ssEfKO6MskPtq7QaQnyiOHGWLXOZcl7a8YIf8u56DGc= +github.com/alibabacloud-go/fc-open-20210406 v1.1.14 h1:avhpcxvdwexER2S1buxgnYoEq+/rWX3iMCgP3EZZz+E= +github.com/alibabacloud-go/fc-open-20210406 v1.1.14/go.mod h1:M3vmom/tsiVbnIBBZshm8JBXcniX9Ryek3NFX2Q95dg= +github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 h1:A3D8Mp6qf8DfR6Dt5MpS8aDVaWfS4N85T5CvGUvgrjM= +github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12/go.mod h1:F5c0E5UB3k8v6neTtw3FBcJ1YCNFzVoL1JPRHTe33u4= github.com/alibabacloud-go/live-20161101 v1.1.1 h1:rUGfA8RHmCMtQ5M3yMSyRde+yRXWqVecmiXBU3XrGJ8= github.com/alibabacloud-go/live-20161101 v1.1.1/go.mod h1:g84w6qeAodT0/IHdc0tEed2a8PyhQhYl7TAj3jGl4A4= github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko= @@ -847,6 +857,8 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084 h1:kwc github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1084/go.mod h1:qE67ApiBzeRvzeDsV+GxyIDbVIDemsKpHXllQATz/Vw= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1115 h1:qy05kto0yI5AG6u0+BHwfUK9jJUJ92081ee+wvSkpk8= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/live v1.0.1115/go.mod h1:BSeUvwz3WO7BbTan1OKC0+NDeiULVZVovrd93qCtWJM= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1115 h1:Qi7VWmJ0AQxEMlwKpbWfnsLA5QdNxekdcLJTBVdO85U= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf v1.0.1115/go.mod h1:P16nIMvmpSY+arTc2m2HyJmrYQP6CFnr48glz0+abyw= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1115 h1:xAMIp4en0Wm4FAS4zo5ZXeYT4FMXms68Fc2COP4J/TM= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl v1.0.1115/go.mod h1:3zCzHke2XQMBm6T2PIdnfTCXGxykV4uTTdRStpUdS0g= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/teo v1.0.1115 h1:3Xz/q/m9gl++KtPkrgaXvFCXjM1y9QmAbpG8qfLUm9M= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 6a8a0b6c..1efd9c96 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -6,12 +6,15 @@ import ( "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/deployer" + p1PanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console" + p1PanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site" pAliyunALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" pAliyunCASDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas-deploy" pAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" pAliyunCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" pAliyunDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn" pAliyunESA "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-esa" + pAliyunFC "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-fc" pAliyunLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-live" pAliyunNLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" pAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" @@ -47,6 +50,7 @@ import ( pTencentCloudCSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-css" pTencentCloudECDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn" pTencentCloudEO "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-eo" + pTencentCloudSCF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-scf" pTencentCloudSSLDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy" pTencentCloudVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-vod" pTencentCloudWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-waf" @@ -69,7 +73,38 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { NOTICE: If you add new constant, please keep ASCII order. */ switch options.Provider { - case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF: + case domain.DeployProviderType1PanelConsole, domain.DeployProviderType1PanelSite: + { + access := domain.AccessConfigFor1Panel{} + if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + switch options.Provider { + case domain.DeployProviderType1PanelConsole: + deployer, err := p1PanelConsole.NewDeployer(&p1PanelConsole.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + AutoRestart: maps.GetValueAsBool(options.ProviderDeployConfig, "autoRestart"), + }) + return deployer, err + + case domain.DeployProviderType1PanelSite: + deployer, err := p1PanelSite.NewDeployer(&p1PanelSite.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + WebsiteId: maps.GetValueAsInt64(options.ProviderDeployConfig, "websiteId"), + }) + return deployer, err + + default: + break + } + } + + case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunFC, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF: { access := domain.AccessConfigForAliyun{} if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -136,6 +171,16 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { }) return deployer, err + case domain.DeployProviderTypeAliyunFC: + deployer, err := pAliyunFC.NewDeployer(&pAliyunFC.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + ServiceVersion: maps.GetValueAsString(options.ProviderDeployConfig, "serviceVersion"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), + }) + return deployer, err + case domain.DeployProviderTypeAliyunLive: deployer, err := pAliyunLive.NewDeployer(&pAliyunLive.DeployerConfig{ AccessKeyId: access.AccessKeyId, @@ -262,19 +307,21 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { switch options.Provider { case domain.DeployProviderTypeBaotaPanelConsole: deployer, err := pBaotaPanelConsole.NewDeployer(&pBaotaPanelConsole.DeployerConfig{ - ApiUrl: access.ApiUrl, - ApiKey: access.ApiKey, - AutoRestart: maps.GetValueAsBool(options.ProviderDeployConfig, "autoRestart"), + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + AutoRestart: maps.GetValueAsBool(options.ProviderDeployConfig, "autoRestart"), }) return deployer, err case domain.DeployProviderTypeBaotaPanelSite: deployer, err := pBaotaPanelSite.NewDeployer(&pBaotaPanelSite.DeployerConfig{ - ApiUrl: access.ApiUrl, - ApiKey: access.ApiKey, - SiteType: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "siteType", "other"), - SiteName: maps.GetValueAsString(options.ProviderDeployConfig, "siteName"), - SiteNames: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + AllowInsecureConnections: access.AllowInsecureConnections, + SiteType: maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "siteType", "other"), + SiteName: maps.GetValueAsString(options.ProviderDeployConfig, "siteName"), + SiteNames: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "siteNames"), ";"), func(s string) bool { return s != "" }), }) return deployer, err @@ -551,10 +598,11 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } deployer, err := pSafeLine.NewDeployer(&pSafeLine.DeployerConfig{ - ApiUrl: access.ApiUrl, - ApiToken: access.ApiToken, - ResourceType: pSafeLine.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), - CertificateId: maps.GetValueAsInt32(options.ProviderDeployConfig, "certificateId"), + ApiUrl: access.ApiUrl, + ApiToken: access.ApiToken, + AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: pSafeLine.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")), + CertificateId: maps.GetValueAsInt32(options.ProviderDeployConfig, "certificateId"), }) return deployer, err } @@ -587,7 +635,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { return deployer, err } - case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO, domain.DeployProviderTypeTencentCloudSSLDeploy, domain.DeployProviderTypeTencentCloudVOD, domain.DeployProviderTypeTencentCloudWAF: + case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO, domain.DeployProviderTypeTencentCloudSCF, domain.DeployProviderTypeTencentCloudSSLDeploy, domain.DeployProviderTypeTencentCloudVOD, domain.DeployProviderTypeTencentCloudWAF: { access := domain.AccessConfigForTencentCloud{} if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -650,6 +698,15 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { }) return deployer, err + case domain.DeployProviderTypeTencentCloudSCF: + deployer, err := pTencentCloudSCF.NewDeployer(&pTencentCloudSCF.DeployerConfig{ + SecretId: access.SecretId, + SecretKey: access.SecretKey, + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + Domain: maps.GetValueAsString(options.ProviderDeployConfig, "domain"), + }) + return deployer, err + case domain.DeployProviderTypeTencentCloudSSLDeploy: deployer, err := pTencentCloudSSLDeploy.NewDeployer(&pTencentCloudSSLDeploy.DeployerConfig{ SecretId: access.SecretId, @@ -792,8 +849,9 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } deployer, err := pWebhook.NewDeployer(&pWebhook.DeployerConfig{ - WebhookUrl: access.Url, - WebhookData: maps.GetValueAsString(options.ProviderDeployConfig, "webhookData"), + WebhookUrl: access.Url, + WebhookData: maps.GetValueAsString(options.ProviderDeployConfig, "webhookData"), + AllowInsecureConnections: access.AllowInsecureConnections, }) return deployer, err } diff --git a/internal/domain/access.go b/internal/domain/access.go index 25df3210..fc6a7eb1 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -24,6 +24,12 @@ func (a *Access) UnmarshalConfigToMap() (map[string]any, error) { return config, nil } +type AccessConfigFor1Panel struct { + ApiUrl string `json:"apiUrl"` + ApiKey string `json:"apiKey"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForACMEHttpReq struct { Endpoint string `json:"endpoint"` Mode string `json:"mode,omitempty"` @@ -58,8 +64,9 @@ type AccessConfigForBaishan struct { } type AccessConfigForBaotaPanel struct { - ApiUrl string `json:"apiUrl"` - ApiKey string `json:"apiKey"` + ApiUrl string `json:"apiUrl"` + ApiKey string `json:"apiKey"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } type AccessConfigForBytePlus struct { @@ -169,8 +176,9 @@ type AccessConfigForRainYun struct { } type AccessConfigForSafeLine struct { - ApiUrl string `json:"apiUrl"` - ApiToken string `json:"apiToken"` + ApiUrl string `json:"apiUrl"` + ApiToken string `json:"apiToken"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } type AccessConfigForSSH struct { @@ -199,7 +207,8 @@ type AccessConfigForVolcEngine struct { } type AccessConfigForWebhook struct { - Url string `json:"url"` + Url string `json:"url"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } type AccessConfigForWestcn struct { diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 950abc08..78c79d4a 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -9,7 +9,7 @@ type AccessProviderType string NOTICE: If you add new constant, please keep ASCII order. */ const ( - AccessProviderType1Panel = AccessProviderType("1panel") // 1Panel(预留) + AccessProviderType1Panel = AccessProviderType("1panel") AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq") AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留) AccessProviderTypeAliyun = AccessProviderType("aliyun") @@ -108,12 +108,15 @@ type DeployProviderType string NOTICE: If you add new constant, please keep ASCII order. */ const ( + DeployProviderType1PanelConsole = DeployProviderType("1panel-console") + DeployProviderType1PanelSite = DeployProviderType("1panel-site") DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy") DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb") DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn") DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa") + DeployProviderTypeAliyunFC = DeployProviderType("aliyun-fc") DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live") DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb") DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") @@ -149,6 +152,7 @@ const ( DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css") DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn") DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo") + DeployProviderTypeTencentCloudSCF = DeployProviderType("tencentcloud-scf") DeployProviderTypeTencentCloudSSLDeploy = DeployProviderType("tencentcloud-ssldeploy") DeployProviderTypeTencentCloudVOD = DeployProviderType("tencentcloud-vod") DeployProviderTypeTencentCloudWAF = DeployProviderType("tencentcloud-waf") diff --git a/internal/notify/providers.go b/internal/notify/providers.go index 6e18a84c..9cd27439 100644 --- a/internal/notify/providers.go +++ b/internal/notify/providers.go @@ -63,7 +63,8 @@ func createNotifier(channel domain.NotifyChannelType, channelConfig map[string]a case domain.NotifyChannelTypeWebhook: return pWebhook.NewNotifier(&pWebhook.NotifierConfig{ - Url: maps.GetValueAsString(channelConfig, "url"), + Url: maps.GetValueAsString(channelConfig, "url"), + AllowInsecureConnections: maps.GetValueAsBool(channelConfig, "allowInsecureConnections"), }) case domain.NotifyChannelTypeWeCom: diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go new file mode 100644 index 00000000..512b5296 --- /dev/null +++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go @@ -0,0 +1,95 @@ +package onepanelconsole + +import ( + "context" + "crypto/tls" + "errors" + "net/url" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + opsdk "github.com/usual2970/certimate/internal/pkg/vendors/1panel-sdk" +) + +type DeployerConfig struct { + // 1Panel 地址。 + ApiUrl string `json:"apiUrl"` + // 1Panel 接口密钥。 + ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 是否自动重启。 + AutoRestart bool `json:"autoRestart"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger logger.Logger + sdkClient *opsdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &DeployerProvider{ + config: config, + logger: logger.NewNilLogger(), + sdkClient: client, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger logger.Logger) *DeployerProvider { + d.logger = logger + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 设置面板 SSL 证书 + updateSystemSSLReq := &opsdk.UpdateSystemSSLRequest{ + Cert: certPem, + Key: privkeyPem, + SSL: "enable", + SSLType: "import-paste", + } + if d.config.AutoRestart { + updateSystemSSLReq.AutoRestart = "true" + } else { + updateSystemSSLReq.AutoRestart = "false" + } + updateSystemSSLResp, err := d.sdkClient.UpdateSystemSSL(updateSystemSSLReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateSystemSSL'") + } else { + d.logger.Logt("已设置面板 SSL 证书", updateSystemSSLResp) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*opsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid 1panel api url") + } + + if apiKey == "" { + return nil, errors.New("invalid 1panel api key") + } + + client := opsdk.NewClient(apiUrl, apiKey) + if allowInsecure { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go new file mode 100644 index 00000000..d4b7cfa9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go @@ -0,0 +1,72 @@ +package onepanelconsole_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiKey string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_1PANELCONSOLE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") +} + +/* +Shell command to run this test: + + go test -v ./1panel_console_test.go -args \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIKEY: %v", fApiKey), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + AutoRestart: true, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go new file mode 100644 index 00000000..cdad354a --- /dev/null +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go @@ -0,0 +1,127 @@ +package onepanelsite + +import ( + "context" + "crypto/tls" + "errors" + "net/url" + "strconv" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/1panel-ssl" + opsdk "github.com/usual2970/certimate/internal/pkg/vendors/1panel-sdk" +) + +type DeployerConfig struct { + // 1Panel 地址。 + ApiUrl string `json:"apiUrl"` + // 1Panel 接口密钥。 + ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 网站 ID。 + WebsiteId int64 `json:"websiteId"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger logger.Logger + sdkClient *opsdk.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + ApiUrl: config.ApiUrl, + ApiKey: config.ApiKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DeployerProvider{ + config: config, + logger: logger.NewNilLogger(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger logger.Logger) *DeployerProvider { + d.logger = logger + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 获取网站 HTTPS 配置 + getHttpsConfReq := &opsdk.GetHttpsConfRequest{ + WebsiteID: d.config.WebsiteId, + } + getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'") + } else { + d.logger.Logt("已获取网站 HTTPS 配置", getHttpsConfResp) + } + + // 上传证书到面板 + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Logt("certificate file uploaded", upres) + } + + // 修改网站 HTTPS 配置 + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + updateHttpsConfReq := &opsdk.UpdateHttpsConfRequest{ + WebsiteID: d.config.WebsiteId, + Type: "existed", + WebsiteSSLID: certId, + Enable: getHttpsConfResp.Data.Enable, + HttpConfig: getHttpsConfResp.Data.HttpConfig, + SSLProtocol: getHttpsConfResp.Data.SSLProtocol, + Algorithm: getHttpsConfResp.Data.Algorithm, + Hsts: getHttpsConfResp.Data.Hsts, + } + updateHttpsConfResp, err := d.sdkClient.UpdateHttpsConf(updateHttpsConfReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'") + } else { + d.logger.Logt("已获取网站 HTTPS 配置", updateHttpsConfResp) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*opsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid 1panel api url") + } + + if apiKey == "" { + return nil, errors.New("invalid 1panel api key") + } + + client := opsdk.NewClient(apiUrl, apiKey) + if allowInsecure { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go new file mode 100644 index 00000000..1be2444d --- /dev/null +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go @@ -0,0 +1,76 @@ +package onepanelsite_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiKey string + fWebsiteId int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_1PANELCONSOLE_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") + flag.Int64Var(&fWebsiteId, argsPrefix+"WEBSITEID", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./1panel_console_test.go -args \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key" \ + --CERTIMATE_DEPLOYER_1PANELCONSOLE_WEBSITEID="your-website-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIKEY: %v", fApiKey), + fmt.Sprintf("WEBSITEID: %v", fWebsiteId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiKey: fApiKey, + WebsiteId: fWebsiteId, + AllowInsecureConnections: true, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go index 39600c7b..4388fbf0 100644 --- a/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go +++ b/internal/pkg/core/deployer/providers/aliyun-alb/aliyun_alb.go @@ -386,7 +386,7 @@ func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudL } func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients, error) { - // 接入点一览 https://www.alibabacloud.com/help/zh/slb/application-load-balancer/developer-reference/api-alb-2020-06-16-albEndpoint + // 接入点一览 https://api.aliyun.com/product/Alb var albEndpoint string switch region { case "cn-hangzhou-finance": @@ -405,7 +405,7 @@ func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients return nil, err } - // 接入点一览 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints + // 接入点一览 https://api.aliyun.com/product/cas var casEndpoint string if !strings.HasPrefix(region, "cn-") { casEndpoint = "cas.ap-southeast-1.aliyuncs.com" diff --git a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go index 1cc36c6f..fa045521 100644 --- a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go +++ b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go @@ -151,7 +151,7 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州 } - // 接入点一览 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints + // 接入点一览 https://api.aliyun.com/product/cas var endpoint string switch region { case "cn-hangzhou": diff --git a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go index 21a1e471..304a7131 100644 --- a/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go +++ b/internal/pkg/core/deployer/providers/aliyun-clb/aliyun_clb.go @@ -274,7 +274,7 @@ func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudL } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) { - // 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint + // 接入点一览 https://api.aliyun.com/product/Slb var endpoint string switch region { case diff --git a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go index 1ccbdae5..5134d115 100644 --- a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go +++ b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go @@ -98,7 +98,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunEsa.Client, error) { - // 接入点一览 https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-endpoint + // 接入点一览 https://api.aliyun.com/product/ESA config := &aliyunOpen.Config{ AccessKeyId: tea.String(accessKeyId), AccessKeySecret: tea.String(accessKeySecret), diff --git a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go index c2d81aa3..9a2c4ca0 100644 --- a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNESA_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_ALIYUNESA_ACCESSKEYID="your-access-key-id" \ --CERTIMATE_DEPLOYER_ALIYUNESA_ACCESSKEYSECRET="your-access-key-secret" \ - --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNESA_REGION="cn-hangzhou" \ --CERTIMATE_DEPLOYER_ALIYUNESA_SITEID="your-esa-site-id" */ func TestDeploy(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go new file mode 100644 index 00000000..e70931f8 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc.go @@ -0,0 +1,186 @@ +package aliyunfc + +import ( + "context" + "fmt" + "time" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliyunFc3 "github.com/alibabacloud-go/fc-20230330/v4/client" + aliyunFc2 "github.com/alibabacloud-go/fc-open-20210406/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" +) + +type DeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 服务版本。 + // 零值时默认为 "3.0"。 + ServiceVersion string `json:"serviceVersion"` + // 自定义域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger logger.Logger + sdkClients *wSdkClients +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +type wSdkClients struct { + fc2 *aliyunFc2.Client + fc3 *aliyunFc3.Client +} + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + clients, err := createSdkClients(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk clients") + } + + return &DeployerProvider{ + config: config, + logger: logger.NewNilLogger(), + sdkClients: clients, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger logger.Logger) *DeployerProvider { + d.logger = logger + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + switch d.config.ServiceVersion { + case "", "3.0": + if err := d.deployToFC3(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + case "2.0": + if err := d.deployToFC2(ctx, certPem, privkeyPem); err != nil { + return nil, err + } + + default: + return nil, xerrors.Errorf("unsupported service version: %s", d.config.ServiceVersion) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToFC3(ctx context.Context, certPem string, privkeyPem string) error { + // 获取自定义域名 + // REF: https://help.aliyun.com/zh/functioncompute/fc-3-0/developer-reference/api-fc-2023-03-30-getcustomdomain + getCustomDomainResp, err := d.sdkClients.fc3.GetCustomDomain(tea.String(d.config.Domain)) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'fc.GetCustomDomain'") + } else { + d.logger.Logt("已获取自定义域名", getCustomDomainResp) + } + + // 更新自定义域名 + // REF: https://help.aliyun.com/zh/functioncompute/fc-3-0/developer-reference/api-fc-2023-03-30-updatecustomdomain + updateCustomDomainReq := &aliyunFc3.UpdateCustomDomainRequest{ + Body: &aliyunFc3.UpdateCustomDomainInput{ + CertConfig: &aliyunFc3.CertConfig{ + CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), + Certificate: tea.String(certPem), + PrivateKey: tea.String(privkeyPem), + }, + Protocol: getCustomDomainResp.Body.Protocol, + TlsConfig: getCustomDomainResp.Body.TlsConfig, + }, + } + updateCustomDomainResp, err := d.sdkClients.fc3.UpdateCustomDomain(tea.String(d.config.Domain), updateCustomDomainReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'fc.UpdateCustomDomain'") + } else { + d.logger.Logt("已更新自定义域名", updateCustomDomainResp) + } + + return nil +} + +func (d *DeployerProvider) deployToFC2(ctx context.Context, certPem string, privkeyPem string) error { + // 获取自定义域名 + // REF: https://help.aliyun.com/zh/functioncompute/fc-2-0/developer-reference/api-fc-open-2021-04-06-getcustomdomain + getCustomDomainResp, err := d.sdkClients.fc2.GetCustomDomain(tea.String(d.config.Domain)) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'fc.GetCustomDomain'") + } else { + d.logger.Logt("已获取自定义域名", getCustomDomainResp) + } + + // 更新自定义域名 + // REF: https://help.aliyun.com/zh/functioncompute/fc-2-0/developer-reference/api-fc-open-2021-04-06-updatecustomdomain + updateCustomDomainReq := &aliyunFc2.UpdateCustomDomainRequest{ + CertConfig: &aliyunFc2.CertConfig{ + CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), + Certificate: tea.String(certPem), + PrivateKey: tea.String(privkeyPem), + }, + Protocol: getCustomDomainResp.Body.Protocol, + TlsConfig: getCustomDomainResp.Body.TlsConfig, + } + updateCustomDomainResp, err := d.sdkClients.fc2.UpdateCustomDomain(tea.String(d.config.Domain), updateCustomDomainReq) + if err != nil { + return xerrors.Wrap(err, "failed to execute sdk request 'fc.UpdateCustomDomain'") + } else { + d.logger.Logt("已更新自定义域名", updateCustomDomainResp) + } + + return nil +} + +func createSdkClients(accessKeyId, accessKeySecret, region string) (*wSdkClients, error) { + // 接入点一览 https://api.aliyun.com/product/FC-Open + var fc2Endpoint string + switch region { + case "cn-hangzhou-finance": + fc2Endpoint = fmt.Sprintf("%s.fc.aliyuncs.com", region) + default: + fc2Endpoint = fmt.Sprintf("fc.%s.aliyuncs.com", region) + } + + fc2Config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(fc2Endpoint), + } + fc2Client, err := aliyunFc2.NewClient(fc2Config) + if err != nil { + return nil, err + } + + // 接入点一览 https://api.aliyun.com/product/FC-Open + fc3Endpoint := fmt.Sprintf("fcv3.%s.aliyuncs.com", region) + fc3Config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(fc3Endpoint), + } + fc3Client, err := aliyunFc3.NewClient(fc3Config) + if err != nil { + return nil, err + } + + return &wSdkClients{ + fc2: fc2Client, + fc3: fc3Client, + }, nil +} diff --git a/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc_test.go b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc_test.go new file mode 100644 index 00000000..a8780285 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-fc/aliyun_fc_test.go @@ -0,0 +1,80 @@ +package aliyunfc_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-fc" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fSiteId int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNFC_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.Int64Var(&fSiteId, argsPrefix+"SITEID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./aliyun_fc_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNFC_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNFC_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNFC_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNFC_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNFC_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNFC_SITEID="your-fc-site-id" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("SITEID: %v", fSiteId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + SiteId: fSiteId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/aliyun-live/aliyun_live.go b/internal/pkg/core/deployer/providers/aliyun-live/aliyun_live.go index 5735da79..99b06aca 100644 --- a/internal/pkg/core/deployer/providers/aliyun-live/aliyun_live.go +++ b/internal/pkg/core/deployer/providers/aliyun-live/aliyun_live.go @@ -81,7 +81,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunLive.Client, error) { - // 接入点一览 https://help.aliyun.com/zh/live/developer-reference/api-live-2016-11-01-endpoint + // 接入点一览 https://api.aliyun.com/product/live var endpoint string switch region { case diff --git a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go index 0f1f1bca..8dc1b2e3 100644 --- a/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go +++ b/internal/pkg/core/deployer/providers/aliyun-nlb/aliyun_nlb.go @@ -211,7 +211,7 @@ func (d *DeployerProvider) updateListenerCertificate(ctx context.Context, cloudL } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunNlb.Client, error) { - // 接入点一览 https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-endpoint + // 接入点一览 https://api.aliyun.com/product/Nlb var endpoint string switch region { default: diff --git a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go index 88adbf0b..3eba5c55 100644 --- a/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go +++ b/internal/pkg/core/deployer/providers/aliyun-oss/aliyun_oss.go @@ -81,7 +81,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe } func createSdkClient(accessKeyId, accessKeySecret, region string) (*oss.Client, error) { - // 接入点一览 https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints + // 接入点一览 https://api.aliyun.com/product/Oss var endpoint string switch region { case "": diff --git a/internal/pkg/core/deployer/providers/aliyun-vod/aliyun_vod.go b/internal/pkg/core/deployer/providers/aliyun-vod/aliyun_vod.go index 740fbb56..77c2ebf0 100644 --- a/internal/pkg/core/deployer/providers/aliyun-vod/aliyun_vod.go +++ b/internal/pkg/core/deployer/providers/aliyun-vod/aliyun_vod.go @@ -77,7 +77,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunVod.Client, error) { - // 接入点一览 https://help.aliyun.com/zh/vod/developer-reference/api-vod-2017-03-21-endpoint + // 接入点一览 https://api.aliyun.com/product/vod endpoint := fmt.Sprintf("vod.%s.aliyuncs.com", region) config := &aliyunOpen.Config{ diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go index 5747d23e..ccb9adc6 100644 --- a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go +++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go @@ -111,7 +111,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunWaf.Client, error) { - // 接入点一览:https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint + // 接入点一览:https://api.aliyun.com/product/waf-openapi config := &aliyunOpen.Config{ AccessKeyId: tea.String(accessKeyId), AccessKeySecret: tea.String(accessKeySecret), diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go index 06a76c63..2668db47 100644 --- a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go +++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf_test.go @@ -39,7 +39,7 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_ALIYUNWAF_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_ALIYUNWAF_ACCESSKEYID="your-access-key-id" \ --CERTIMATE_DEPLOYER_ALIYUNWAF_ACCESSKEYSECRET="your-access-key-secret" \ - --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNWAF_REGION="cn-hangzhou" \ --CERTIMATE_DEPLOYER_ALIYUNWAF_INSTANCEID="your-waf-instance-id" */ func TestDeploy(t *testing.T) { diff --git a/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console.go b/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console.go index 6343acf7..ff09dc26 100644 --- a/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console.go +++ b/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console.go @@ -2,6 +2,7 @@ import ( "context" + "crypto/tls" "errors" "net/url" @@ -17,6 +18,8 @@ type DeployerConfig struct { ApiUrl string `json:"apiUrl"` // 宝塔面板接口密钥。 ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` // 是否自动重启。 AutoRestart bool `json:"autoRestart"` } @@ -34,7 +37,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiKey) + client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) if err != nil { return nil, xerrors.Wrap(err, "failed to create sdk client") } @@ -65,21 +68,18 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe } if d.config.AutoRestart { - // 重启面板 + // 重启面板(无需关心响应,因为宝塔重启时会断开连接产生 error) systemServiceAdminReq := &btsdk.SystemServiceAdminRequest{ Name: "nginx", Type: "restart", } - _, err := d.sdkClient.SystemServiceAdmin(systemServiceAdminReq) - if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request 'bt.SystemServiceAdmin'") - } + d.sdkClient.SystemServiceAdmin(systemServiceAdminReq) } return &deployer.DeployResult{}, nil } -func createSdkClient(apiUrl, apiKey string) (*btsdk.Client, error) { +func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*btsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid baota api url") } @@ -89,5 +89,9 @@ func createSdkClient(apiUrl, apiKey string) (*btsdk.Client, error) { } client := btsdk.NewClient(apiUrl, apiKey) + if allowInsecure { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + return client, nil } diff --git a/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console_test.go b/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console_test.go index 06a2f096..a10afb37 100644 --- a/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console_test.go +++ b/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console_test.go @@ -49,8 +49,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ - ApiUrl: fApiUrl, - ApiKey: fApiKey, + ApiUrl: fApiUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + AutoRestart: true, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel_site.go b/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel_site.go index 62cef9de..c6bf4966 100644 --- a/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel_site.go +++ b/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel_site.go @@ -2,6 +2,7 @@ import ( "context" + "crypto/tls" "errors" "fmt" "net/url" @@ -19,6 +20,8 @@ type DeployerConfig struct { ApiUrl string `json:"apiUrl"` // 宝塔面板接口密钥。 ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` // 站点类型。 SiteType string `json:"siteType"` // 站点名称(单个)。 @@ -40,7 +43,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiKey) + client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections) if err != nil { return nil, xerrors.Wrap(err, "failed to create sdk client") } @@ -122,7 +125,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe return &deployer.DeployResult{}, nil } -func createSdkClient(apiUrl, apiKey string) (*btsdk.Client, error) { +func createSdkClient(apiUrl, apiKey string, allowInsecure bool) (*btsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid baota api url") } @@ -132,5 +135,9 @@ func createSdkClient(apiUrl, apiKey string) (*btsdk.Client, error) { } client := btsdk.NewClient(apiUrl, apiKey) + if allowInsecure { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + return client, nil } diff --git a/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel_site_test.go b/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel_site_test.go index 4c31b021..f36605fe 100644 --- a/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel_site_test.go +++ b/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel_site_test.go @@ -57,11 +57,12 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ - ApiUrl: fApiUrl, - ApiKey: fApiKey, - SiteType: fSiteType, - SiteName: fSiteName, - SiteNames: []string{fSiteName}, + ApiUrl: fApiUrl, + ApiKey: fApiKey, + AllowInsecureConnections: true, + SiteType: fSiteType, + SiteName: fSiteName, + SiteNames: []string{fSiteName}, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/safeline/safeline.go b/internal/pkg/core/deployer/providers/safeline/safeline.go index 3b4006c4..d0186100 100644 --- a/internal/pkg/core/deployer/providers/safeline/safeline.go +++ b/internal/pkg/core/deployer/providers/safeline/safeline.go @@ -2,6 +2,7 @@ import ( "context" + "crypto/tls" "errors" "fmt" "net/url" @@ -18,6 +19,8 @@ type DeployerConfig struct { ApiUrl string `json:"apiUrl"` // 雷池 API Token。 ApiToken string `json:"apiToken"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` // 部署资源类型。 ResourceType ResourceType `json:"resourceType"` // 证书 ID。 @@ -38,9 +41,9 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiToken) + client, err := createSdkClient(config.ApiUrl, config.ApiToken, config.AllowInsecureConnections) if err != nil { - return nil, xerrors.Wrap(err, "failed to create sdk clients") + return nil, xerrors.Wrap(err, "failed to create sdk client") } return &DeployerProvider{ @@ -94,7 +97,7 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPem stri return nil } -func createSdkClient(apiUrl, apiToken string) (*safelinesdk.Client, error) { +func createSdkClient(apiUrl, apiToken string, allowInsecure bool) (*safelinesdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid safeline api url") } @@ -104,5 +107,9 @@ func createSdkClient(apiUrl, apiToken string) (*safelinesdk.Client, error) { } client := safelinesdk.NewClient(apiUrl, apiToken) + if allowInsecure { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + return client, nil } diff --git a/internal/pkg/core/deployer/providers/safeline/safeline_test.go b/internal/pkg/core/deployer/providers/safeline/safeline_test.go index 0d7f2223..42c6313f 100644 --- a/internal/pkg/core/deployer/providers/safeline/safeline_test.go +++ b/internal/pkg/core/deployer/providers/safeline/safeline_test.go @@ -53,10 +53,11 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ - ApiUrl: fApiUrl, - ApiToken: fApiToken, - ResourceType: provider.ResourceType("certificate"), - CertificateId: fCertificateId, + ApiUrl: fApiUrl, + ApiToken: fApiToken, + AllowInsecureConnections: true, + ResourceType: provider.ResourceType("certificate"), + CertificateId: int32(fCertificateId), }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go index d3b3f881..becceab6 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-cos/tencentcloud_cos.go @@ -45,7 +45,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { client, err := createSdkClient(config.SecretId, config.SecretKey, config.Region) if err != nil { - return nil, xerrors.Wrap(err, "failed to create sdk clients") + return nil, xerrors.Wrap(err, "failed to create sdk client") } uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ diff --git a/internal/pkg/core/deployer/providers/tencentcloud-scf/tencentcloud_scf.go b/internal/pkg/core/deployer/providers/tencentcloud-scf/tencentcloud_scf.go new file mode 100644 index 00000000..3a3aeb82 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-scf/tencentcloud_scf.go @@ -0,0 +1,114 @@ +package tencentcloudscf + +import ( + "context" + + xerrors "github.com/pkg/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tcScf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/scf/v20180416" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" +) + +type DeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 腾讯云地域。 + Region string `json:"region"` + // 自定义域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger logger.Logger + sdkClient *tcScf.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.SecretId, config.SecretKey, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DeployerProvider{ + config: config, + logger: logger.NewNilLogger(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger logger.Logger) *DeployerProvider { + d.logger = logger + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 查看云函数自定义域名详情 + // REF: https://cloud.tencent.com/document/product/583/111924 + getCustomDomainReq := tcScf.NewGetCustomDomainRequest() + getCustomDomainReq.Domain = common.StringPtr(d.config.Domain) + getCustomDomainResp, err := d.sdkClient.GetCustomDomain(getCustomDomainReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'scf.GetCustomDomain'") + } else { + d.logger.Logt("已查看云函数自定义域名详情", getCustomDomainResp.Response) + } + + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Logt("certificate file uploaded", upres) + } + + // 更新云函数自定义域名 + // REF: https://cloud.tencent.com/document/product/583/111922 + updateCustomDomainReq := tcScf.NewUpdateCustomDomainRequest() + updateCustomDomainReq.Domain = common.StringPtr(d.config.Domain) + updateCustomDomainReq.CertConfig = &tcScf.CertConf{ + CertificateId: common.StringPtr(upres.CertId), + } + updateCustomDomainReq.Protocol = getCustomDomainResp.Response.Protocol + updateCustomDomainResp, err := d.sdkClient.UpdateCustomDomain(updateCustomDomainReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'scf.UpdateCustomDomain'") + } else { + d.logger.Logt("已设置点播域名 HTTPS 证书", updateCustomDomainResp.Response) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(secretId, secretKey, region string) (*tcScf.Client, error) { + credential := common.NewCredential(secretId, secretKey) + client, err := tcScf.NewClient(credential, region, profile.NewClientProfile()) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go b/internal/pkg/core/deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go new file mode 100644 index 00000000..84a1ad79 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-scf/tencentcloud_scf_test.go @@ -0,0 +1,80 @@ +package tencentcloudscf_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-scf" +) + +var ( + fInputCertPath string + fInputKeyPath string + fSecretId string + fSecretKey string + fRegion string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_TENCENTCLOUDSCF_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "") + flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./tencentcloud_scf_test.go -args \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDSCF_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDSCF_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDSCF_SECRETID="your-secret-id" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDSCF_SECRETKEY="your-secret-key" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDSCF_REGION="ap-guangzhou" \ + --CERTIMATE_DEPLOYER_TENCENTCLOUDSCF_DOMAIN="example.com" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("SECRETID: %v", fSecretId), + fmt.Sprintf("SECRETKEY: %v", fSecretKey), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + SecretId: fSecretId, + SecretKey: fSecretKey, + Region: fRegion, + Domain: fDomain, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/deployer/providers/tencentcloud-vod/tencentcloud_vod.go b/internal/pkg/core/deployer/providers/tencentcloud-vod/tencentcloud_vod.go index fbce8d6f..77177469 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-vod/tencentcloud_vod.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-vod/tencentcloud_vod.go @@ -41,7 +41,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { client, err := createSdkClient(config.SecretId, config.SecretKey) if err != nil { - return nil, xerrors.Wrap(err, "failed to create sdk clients") + return nil, xerrors.Wrap(err, "failed to create sdk client") } uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ diff --git a/internal/pkg/core/deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go b/internal/pkg/core/deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go index 60871a31..ffd085cd 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-vod/tencentcloud_vod_test.go @@ -16,7 +16,6 @@ var ( fInputKeyPath string fSecretId string fSecretKey string - fRegion string fDomain string fSubAppId int64 fInstanceId string @@ -29,7 +28,6 @@ func init() { flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") flag.StringVar(&fSecretId, argsPrefix+"SECRETID", "", "") flag.StringVar(&fSecretKey, argsPrefix+"SECRETKEY", "", "") - flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") flag.Int64Var(&fSubAppId, argsPrefix+"SUBAPPID", 0, "") flag.StringVar(&fInstanceId, argsPrefix+"INSTANCEID", "", "") @@ -43,7 +41,6 @@ Shell command to run this test: --CERTIMATE_DEPLOYER_TENCENTCLOUDVOD_INPUTKEYPATH="/path/to/your-input-key.pem" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDVOD_SECRETID="your-secret-id" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDVOD_SECRETKEY="your-secret-key" \ - --CERTIMATE_DEPLOYER_TENCENTCLOUDVOD_REGION="ap-guangzhou" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDVOD_SUBAPPID="your-app-id" \ --CERTIMATE_DEPLOYER_TENCENTCLOUDVOD_DOMAIN="example.com" */ @@ -57,7 +54,6 @@ func TestDeploy(t *testing.T) { fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), fmt.Sprintf("SECRETID: %v", fSecretId), fmt.Sprintf("SECRETKEY: %v", fSecretKey), - fmt.Sprintf("REGION: %v", fRegion), fmt.Sprintf("DOMAIN: %v", fDomain), fmt.Sprintf("INSTANCEID: %v", fInstanceId), }, "\n")) @@ -65,7 +61,6 @@ func TestDeploy(t *testing.T) { deployer, err := provider.NewDeployer(&provider.DeployerConfig{ SecretId: fSecretId, SecretKey: fSecretKey, - Region: fRegion, SubAppId: fSubAppId, Domain: fDomain, }) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-waf/tencentcloud_waf.go b/internal/pkg/core/deployer/providers/tencentcloud-waf/tencentcloud_waf.go index 7919516c..c84067ce 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-waf/tencentcloud_waf.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-waf/tencentcloud_waf.go @@ -46,7 +46,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { client, err := createSdkClient(config.SecretId, config.SecretKey, config.Region) if err != nil { - return nil, xerrors.Wrap(err, "failed to create sdk clients") + return nil, xerrors.Wrap(err, "failed to create sdk client") } uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 7a9edfda..6a1e8a21 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -2,6 +2,7 @@ package webhook import ( "context" + "crypto/tls" "encoding/json" "strings" "time" @@ -19,6 +20,8 @@ type DeployerConfig struct { WebhookUrl string `json:"webhookUrl"` // Webhook 回调数据(JSON 格式)。 WebhookData string `json:"webhookData,omitempty"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } type DeployerProvider struct { @@ -38,6 +41,9 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { SetTimeout(30 * time.Second). SetRetryCount(3). SetRetryWaitTime(5 * time.Second) + if config.AllowInsecureConnections { + client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + } return &DeployerProvider{ config: config, diff --git a/internal/pkg/core/deployer/providers/webhook/webhook_test.go b/internal/pkg/core/deployer/providers/webhook/webhook_test.go index 7dd6f24a..a31ef913 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook_test.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook_test.go @@ -49,8 +49,9 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewDeployer(&provider.DeployerConfig{ - WebhookUrl: fWebhookUrl, - WebhookData: fWebhookData, + WebhookUrl: fWebhookUrl, + WebhookData: fWebhookData, + AllowInsecureConnections: true, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/notifier/providers/webhook/webhook.go b/internal/pkg/core/notifier/providers/webhook/webhook.go index 55c0e668..f753645a 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook.go @@ -2,8 +2,10 @@ import ( "context" + "crypto/tls" + "net/http" - "github.com/nikoksr/notify/service/http" + webhook "github.com/nikoksr/notify/service/http" "github.com/usual2970/certimate/internal/pkg/core/notifier" ) @@ -11,6 +13,8 @@ import ( type NotifierConfig struct { // Webhook URL。 Url string `json:"url"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } type NotifierProvider struct { @@ -30,10 +34,16 @@ func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { } func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { - srv := http.New() - + srv := webhook.New() srv.AddReceiversURLs(n.config.Url) + if n.config.AllowInsecureConnections { + tlsConfig := &tls.Config{InsecureSkipVerify: true} + transport := &http.Transport{TLSClientConfig: tlsConfig} + client := &http.Client{Transport: transport} + srv.WithClient(client) + } + err = srv.Send(ctx, subject, message) if err != nil { return nil, err diff --git a/internal/pkg/core/notifier/providers/webhook/webhook_test.go b/internal/pkg/core/notifier/providers/webhook/webhook_test.go index 7afe6be4..8210358b 100644 --- a/internal/pkg/core/notifier/providers/webhook/webhook_test.go +++ b/internal/pkg/core/notifier/providers/webhook/webhook_test.go @@ -39,7 +39,8 @@ func TestNotify(t *testing.T) { }, "\n")) notifier, err := provider.NewNotifier(&provider.NotifierConfig{ - Url: fUrl, + Url: fUrl, + AllowInsecureConnections: true, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go new file mode 100644 index 00000000..bb612bec --- /dev/null +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go @@ -0,0 +1,125 @@ +package onepanelssl + +import ( + "context" + "errors" + "fmt" + "net/url" + "strings" + "time" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + opsdk "github.com/usual2970/certimate/internal/pkg/vendors/1panel-sdk" +) + +type UploaderConfig struct { + // 1Panel 地址。 + ApiUrl string `json:"apiUrl"` + // 1Panel 接口密钥。 + ApiKey string `json:"apiKey"` +} + +type UploaderProvider struct { + config *UploaderConfig + sdkClient *opsdk.Client +} + +var _ uploader.Uploader = (*UploaderProvider)(nil) + +func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &UploaderProvider{ + config: config, + sdkClient: client, + }, nil +} + +func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { + // 遍历证书列表,避免重复上传 + if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil { + return nil, err + } else if res != nil { + return res, nil + } + + // 生成新证书名(需符合 1Panel 命名规则) + certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + + // 上传证书 + uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{ + Type: "paste", + Description: certName, + Certificate: certPem, + PrivateKey: privkeyPem, + } + uploadWebsiteSSLResp, err := u.sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UploadWebsiteSSL'") + } + + // 遍历证书列表,获取刚刚上传证书 ID + if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil { + return nil, err + } else if res == nil { + return nil, fmt.Errorf("no ssl certificate found, may be upload failed (code: %d, message: %s)", uploadWebsiteSSLResp.GetCode(), uploadWebsiteSSLResp.GetMessage()) + } else { + return res, nil + } +} + +func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) { + searchWebsiteSSLPageNumber := int32(1) + searchWebsiteSSLPageSize := int32(100) + for { + searchWebsiteSSLReq := &opsdk.SearchWebsiteSSLRequest{ + Page: searchWebsiteSSLPageNumber, + PageSize: searchWebsiteSSLPageSize, + } + searchWebsiteSSLResp, err := u.sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.SearchWebsiteSSL'") + } + + for _, sslItem := range searchWebsiteSSLResp.Data.Items { + if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPem) && + strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPem) { + // 如果已存在相同证书,直接返回已有的证书信息 + return &uploader.UploadResult{ + CertId: fmt.Sprintf("%d", sslItem.ID), + CertName: sslItem.Description, + }, nil + } + } + + if len(searchWebsiteSSLResp.Data.Items) < int(searchWebsiteSSLPageSize) { + break + } else { + searchWebsiteSSLPageNumber++ + } + } + + return nil, nil +} + +func createSdkClient(apiUrl, apiKey string) (*opsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid 1panel api url") + } + + if apiKey == "" { + return nil, errors.New("invalid 1panel api key") + } + + client := opsdk.NewClient(apiUrl, apiKey) + return client, nil +} diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go new file mode 100644 index 00000000..5f146dd1 --- /dev/null +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go @@ -0,0 +1,72 @@ +package onepanelssl_test + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/1panel-ssl" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiKey string +) + +func init() { + argsPrefix := "CERTIMATE_UPLOADER_1PANELSSL_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "") +} + +/* +Shell command to run this test: + + go test -v ./1panel_ssl_test.go -args \ + --CERTIMATE_UPLOADER_1PANELSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_UPLOADER_1PANELSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_UPLOADER_1PANELSSL_APIURL="http://127.0.0.1:20410" \ + --CERTIMATE_UPLOADER_1PANELSSL_APIKEY="your-api-key" +*/ +func TestDeploy(t *testing.T) { + flag.Parse() + + t.Run("Deploy", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath), + fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath), + fmt.Sprintf("APIURL: %v", fApiUrl), + fmt.Sprintf("APIKEY: %v", fApiKey), + }, "\n")) + + uploader, err := provider.NewUploader(&provider.UploaderConfig{ + ApiUrl: fApiUrl, + ApiKey: fApiKey, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + fInputCertData, _ := os.ReadFile(fInputCertPath) + fInputKeyData, _ := os.ReadFile(fInputKeyPath) + res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData)) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + sres, _ := json.Marshal(res) + t.Logf("ok: %s", string(sres)) + }) +} diff --git a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go index 202339c4..2b582409 100644 --- a/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go +++ b/internal/pkg/core/uploader/providers/aliyun-cas/aliyun_cas.go @@ -143,7 +143,7 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Cl region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州 } - // 接入点一览 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints + // 接入点一览 https://api.aliyun.com/product/cas var endpoint string switch region { case "cn-hangzhou": diff --git a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go index c53eced2..d687822a 100644 --- a/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go +++ b/internal/pkg/core/uploader/providers/aliyun-slb/aliyun_slb.go @@ -119,7 +119,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunSlb.Client, error) { - // 接入点一览 https://help.aliyun.com/zh/slb/classic-load-balancer/developer-reference/api-slb-2014-05-15-endpoint + // 接入点一览 https://api.aliyun.com/product/Slb var endpoint string switch region { case diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go index e5d2fc1c..67506bf4 100644 --- a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go +++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go @@ -76,7 +76,13 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe uploadNormalCertificateResp, err := u.sdkClient.UploadNormalCertificate(uploadNormalCertificateReq) if err != nil { if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 { - return u.getExistCert(ctx, certPem) + if res, err := u.getExistCert(ctx, certPem); err != nil { + return nil, err + } else if res == nil { + return nil, errors.New("no certificate found") + } else { + return res, nil + } } return nil, xerrors.Wrap(err, "failed to execute sdk request 'ussl.UploadNormalCertificate'") @@ -205,7 +211,7 @@ func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string) (re } } - return nil, errors.New("no certificate found") + return nil, nil } func createSdkClient(privateKey, publicKey string) (*usdkSsl.USSLClient, error) { diff --git a/internal/pkg/vendors/1panel-sdk/api.go b/internal/pkg/vendors/1panel-sdk/api.go new file mode 100644 index 00000000..a6390408 --- /dev/null +++ b/internal/pkg/vendors/1panel-sdk/api.go @@ -0,0 +1,51 @@ +package onepanelsdk + +import ( + "fmt" + "net/http" +) + +func (c *Client) UpdateSystemSSL(req *UpdateSystemSSLRequest) (*UpdateSystemSSLResponse, error) { + resp := &UpdateSystemSSLResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/settings/ssl/update", req, resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) SearchWebsiteSSL(req *SearchWebsiteSSLRequest) (*SearchWebsiteSSLResponse, error) { + resp := &SearchWebsiteSSLResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/search", req, resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) UploadWebsiteSSL(req *UploadWebsiteSSLRequest) (*UploadWebsiteSSLResponse, error) { + resp := &UploadWebsiteSSLResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/upload", req, resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) GetHttpsConf(req *GetHttpsConfRequest) (*GetHttpsConfResponse, error) { + resp := &GetHttpsConfResponse{} + err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/websites/%d/https", req.WebsiteID), req, resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) UpdateHttpsConf(req *UpdateHttpsConfRequest) (*UpdateHttpsConfResponse, error) { + resp := &UpdateHttpsConfResponse{} + err := c.sendRequestWithResult(http.MethodPost, fmt.Sprintf("/websites/%d/https", req.WebsiteID), req, resp) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/internal/pkg/vendors/1panel-sdk/client.go b/internal/pkg/vendors/1panel-sdk/client.go new file mode 100644 index 00000000..9c424111 --- /dev/null +++ b/internal/pkg/vendors/1panel-sdk/client.go @@ -0,0 +1,101 @@ +package onepanelsdk + +import ( + "crypto/md5" + "crypto/tls" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + apiHost string + apiKey string + + client *resty.Client +} + +func NewClient(apiHost, apiKey string) *Client { + client := resty.New() + + return &Client{ + apiHost: strings.TrimRight(apiHost, "/"), + apiKey: apiKey, + client: client, + } +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.client.SetTimeout(timeout) + return c +} + +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + +func (c *Client) generateToken(timestamp string) string { + tokenMd5 := md5.Sum([]byte("1panel" + c.apiKey + timestamp)) + tokenMd5Hex := hex.EncodeToString(tokenMd5[:]) + return tokenMd5Hex +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R() + req.Method = method + req.URL = c.apiHost + "/api/v1" + path + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req.SetQueryParams(qs) + } else { + req = req. + SetHeader("Content-Type", "application/json"). + SetBody(params) + } + + timestamp := fmt.Sprintf("%d", time.Now().Unix()) + token := c.generateToken(timestamp) + req.SetHeader("1Panel-Timestamp", timestamp) + req.SetHeader("1Panel-Token", token) + + resp, err := req.Send() + if err != nil { + return nil, fmt.Errorf("1panel api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("1panel api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("1panel api error: failed to parse response: %w", err) + } else if errcode := result.GetCode(); errcode/100 != 2 { + return fmt.Errorf("1panel api error: %d - %s", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/vendors/1panel-sdk/models.go b/internal/pkg/vendors/1panel-sdk/models.go new file mode 100644 index 00000000..8510aefe --- /dev/null +++ b/internal/pkg/vendors/1panel-sdk/models.go @@ -0,0 +1,103 @@ +package onepanelsdk + +type BaseResponse interface { + GetCode() int32 + GetMessage() string +} + +type baseResponse struct { + Code int32 `json:"code"` + Message string `json:"message"` +} + +func (r *baseResponse) GetCode() int32 { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type UpdateSystemSSLRequest struct { + Cert string `json:"cert"` + Key string `json:"key"` + SSLType string `json:"sslType"` + SSL string `json:"ssl"` + SSLID int64 `json:"sslID"` + AutoRestart string `json:"autoRestart"` +} + +type UpdateSystemSSLResponse struct { + baseResponse +} + +type SearchWebsiteSSLRequest struct { + Page int32 `json:"page"` + PageSize int32 `json:"pageSize"` +} + +type SearchWebsiteSSLResponse struct { + baseResponse + Data struct { + Items []*struct { + ID int64 `json:"id"` + PEM string `json:"pem"` + PrivateKey string `json:"privateKey"` + Domains string `json:"domains"` + Description string `json:"description"` + Status string `json:"status"` + UpdatedAt string `json:"updatedAt"` + CreatedAt string `json:"createdAt"` + } `json:"items"` + Total int32 `json:"total"` + } `json:"data"` +} + +type UploadWebsiteSSLRequest struct { + Type string `json:"type"` + SSLID int64 `json:"sslID"` + Certificate string `json:"certificate"` + CertificatePath string `json:"certificatePath"` + PrivateKey string `json:"privateKey"` + PrivateKeyPath string `json:"privateKeyPath"` + Description string `json:"description"` +} + +type UploadWebsiteSSLResponse struct { + baseResponse +} + +type GetHttpsConfRequest struct { + WebsiteID int64 `json:"-"` +} + +type GetHttpsConfResponse struct { + baseResponse + Data struct { + Enable bool `json:"enable"` + HttpConfig string `json:"httpConfig"` + SSLProtocol []string `json:"SSLProtocol"` + Algorithm string `json:"algorithm"` + Hsts bool `json:"hsts"` + } `json:"data"` +} + +type UpdateHttpsConfRequest struct { + WebsiteID int64 `json:"websiteId"` + Enable bool `json:"enable"` + Type string `json:"type"` + WebsiteSSLID int64 `json:"websiteSSLId"` + PrivateKey string `json:"privateKey"` + Certificate string `json:"certificate"` + PrivateKeyPath string `json:"privateKeyPath"` + CertificatePath string `json:"certificatePath"` + ImportType string `json:"importType"` + HttpConfig string `json:"httpConfig"` + SSLProtocol []string `json:"SSLProtocol"` + Algorithm string `json:"algorithm"` + Hsts bool `json:"hsts"` +} + +type UpdateHttpsConfResponse struct { + baseResponse +} diff --git a/internal/pkg/vendors/btpanel-sdk/client.go b/internal/pkg/vendors/btpanel-sdk/client.go index 54564f0e..847a4387 100644 --- a/internal/pkg/vendors/btpanel-sdk/client.go +++ b/internal/pkg/vendors/btpanel-sdk/client.go @@ -2,6 +2,7 @@ package btpanelsdk import ( "crypto/md5" + "crypto/tls" "encoding/hex" "encoding/json" "fmt" @@ -34,6 +35,11 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { return c } +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + func (c *Client) generateSignature(timestamp string) string { keyMd5 := md5.Sum([]byte(c.apiKey)) keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:])) diff --git a/internal/pkg/vendors/safeline-sdk/client.go b/internal/pkg/vendors/safeline-sdk/client.go index c6e6caf1..0d47c028 100644 --- a/internal/pkg/vendors/safeline-sdk/client.go +++ b/internal/pkg/vendors/safeline-sdk/client.go @@ -1,6 +1,7 @@ package safelinesdk import ( + "crypto/tls" "encoding/json" "fmt" "strings" @@ -31,6 +32,11 @@ func (c *Client) WithTimeout(timeout time.Duration) *Client { return c } +func (c *Client) WithTLSConfig(config *tls.Config) *Client { + c.client.SetTLSClientConfig(config) + return c +} + func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, error) { url := c.apiHost + path req := c.client.R(). diff --git a/ui/public/imgs/providers/1panel.svg b/ui/public/imgs/providers/1panel.svg new file mode 100644 index 00000000..561199ce --- /dev/null +++ b/ui/public/imgs/providers/1panel.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 47c6d8c6..7f2143ac 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -9,6 +9,7 @@ import { type AccessModel } from "@/domain/access"; import { ACCESS_PROVIDERS } from "@/domain/provider"; import { useAntdForm, useAntdFormName } from "@/hooks"; +import AccessForm1PanelConfig from "./AccessForm1PanelConfig"; import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig"; import AccessFormAliyunConfig from "./AccessFormAliyunConfig"; import AccessFormAWSConfig from "./AccessFormAWSConfig"; @@ -99,6 +100,8 @@ const AccessForm = forwardRef(({ className, NOTICE: If you add new child component, please keep ASCII order. */ switch (fieldProvider) { + case ACCESS_PROVIDERS["1PANEL"]: + return ; case ACCESS_PROVIDERS.ACMEHTTPREQ: return ; case ACCESS_PROVIDERS.ALIYUN: diff --git a/ui/src/components/access/AccessForm1PanelConfig.tsx b/ui/src/components/access/AccessForm1PanelConfig.tsx new file mode 100644 index 00000000..1dde96b5 --- /dev/null +++ b/ui/src/components/access/AccessForm1PanelConfig.tsx @@ -0,0 +1,85 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigFor1Panel } from "@/domain/access"; + +type AccessForm1PanelConfigFieldValues = Nullish; + +export type AccessForm1PanelConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessForm1PanelConfigFieldValues; + onValuesChange?: (values: AccessForm1PanelConfigFieldValues) => void; +}; + +const initFormModel = (): AccessForm1PanelConfigFieldValues => { + return { + apiUrl: "http://:20410/", + apiKey: "", + }; +}; + +const AccessForm1PanelConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessForm1PanelConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + apiKey: z + .string() + .min(1, t("access.form.1panel_api_key.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + + } + > + + +
+ ); +}; + +export default AccessForm1PanelConfig; diff --git a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx index accd80d0..fa9c4723 100644 --- a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx +++ b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input } from "antd"; +import { Form, type FormInstance, Input, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -17,7 +17,7 @@ export type AccessFormBaotaPanelConfigProps = { const initFormModel = (): AccessFormBaotaPanelConfigFieldValues => { return { - apiUrl: "http://:8888/", + apiUrl: "http://:8888/", apiKey: "", }; }; @@ -32,6 +32,7 @@ const AccessFormBaotaPanelConfig = ({ form: formInst, formName, disabled, initia .min(1, t("access.form.baotapanel_api_key.placeholder")) .max(64, t("common.errmsg.string_max", { max: 64 })) .trim(), + allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -65,6 +66,18 @@ const AccessFormBaotaPanelConfig = ({ form: formInst, formName, disabled, initia > + + } + > + + ); }; diff --git a/ui/src/components/access/AccessFormCdnflyConfig.tsx b/ui/src/components/access/AccessFormCdnflyConfig.tsx index a9dd1139..ec0afaa2 100644 --- a/ui/src/components/access/AccessFormCdnflyConfig.tsx +++ b/ui/src/components/access/AccessFormCdnflyConfig.tsx @@ -17,7 +17,7 @@ export type AccessFormCdnflyConfigProps = { const initFormModel = (): AccessFormCdnflyConfigFieldValues => { return { - apiUrl: "http://:88/", + apiUrl: "http://:88/", apiKey: "", apiSecret: "", }; diff --git a/ui/src/components/access/AccessFormPowerDNSConfig.tsx b/ui/src/components/access/AccessFormPowerDNSConfig.tsx index e93980c7..cada5b61 100644 --- a/ui/src/components/access/AccessFormPowerDNSConfig.tsx +++ b/ui/src/components/access/AccessFormPowerDNSConfig.tsx @@ -17,7 +17,7 @@ export type AccessFormPowerDNSConfigProps = { const initFormModel = (): AccessFormPowerDNSConfigFieldValues => { return { - apiUrl: "http://:8082/", + apiUrl: "http://:8082/", apiKey: "", }; }; diff --git a/ui/src/components/access/AccessFormSafeLineConfig.tsx b/ui/src/components/access/AccessFormSafeLineConfig.tsx index b9de115c..5b16c508 100644 --- a/ui/src/components/access/AccessFormSafeLineConfig.tsx +++ b/ui/src/components/access/AccessFormSafeLineConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input } from "antd"; +import { Form, type FormInstance, Input, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -17,7 +17,7 @@ export type AccessFormSafeLineConfigProps = { const initFormModel = (): AccessFormSafeLineConfigFieldValues => { return { - apiUrl: "http://:9443/", + apiUrl: "http://:9443/", apiToken: "", }; }; @@ -32,6 +32,7 @@ const AccessFormSafeLineConfig = ({ form: formInst, formName, disabled, initialV .min(1, t("access.form.safeline_api_token.placeholder")) .max(64, t("common.errmsg.string_max", { max: 64 })) .trim(), + allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -65,6 +66,18 @@ const AccessFormSafeLineConfig = ({ form: formInst, formName, disabled, initialV > + + } + > + + ); }; diff --git a/ui/src/components/access/AccessFormWebhookConfig.tsx b/ui/src/components/access/AccessFormWebhookConfig.tsx index 60f55fe6..89280d79 100644 --- a/ui/src/components/access/AccessFormWebhookConfig.tsx +++ b/ui/src/components/access/AccessFormWebhookConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input } from "antd"; +import { Form, type FormInstance, Input, Switch } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -26,6 +26,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa const formSchema = z.object({ url: z.string({ message: t("access.form.webhook_url.placeholder") }).url(t("common.errmsg.url_invalid")), + allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); @@ -45,6 +46,18 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa + + } + > + + ); }; diff --git a/ui/src/components/notification/NotifyChannels.tsx b/ui/src/components/notification/NotifyChannels.tsx index 8dacecb9..50bc881d 100644 --- a/ui/src/components/notification/NotifyChannels.tsx +++ b/ui/src/components/notification/NotifyChannels.tsx @@ -98,8 +98,8 @@ const NotifyChannels = ({ className, classNames, style, styles }: NotifyChannels handleSwitchChange(channel.type, checked)} /> diff --git a/ui/src/components/provider/DeployProviderPicker.tsx b/ui/src/components/provider/DeployProviderPicker.tsx index 57ba39a6..cc34ef16 100644 --- a/ui/src/components/provider/DeployProviderPicker.tsx +++ b/ui/src/components/provider/DeployProviderPicker.tsx @@ -64,6 +64,7 @@ const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSele DEPLOY_CATEGORIES.LOADBALANCE, DEPLOY_CATEGORIES.FIREWALL, DEPLOY_CATEGORIES.AV, + DEPLOY_CATEGORIES.SERVERLESS, DEPLOY_CATEGORIES.WEBSITE, DEPLOY_CATEGORIES.OTHER, ].map((key) => ({ diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index b9f65b08..4f05c1c8 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -15,12 +15,15 @@ import { type WorkflowNode, type WorkflowNodeConfigForDeploy } from "@/domain/wo import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks"; import { useWorkflowStore } from "@/stores/workflow"; +import DeployNodeConfigForm1PanelConsoleConfig from "./DeployNodeConfigForm1PanelConsoleConfig"; +import DeployNodeConfigForm1PanelSiteConfig from "./DeployNodeConfigForm1PanelSiteConfig"; import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALBConfig"; import DeployNodeConfigFormAliyunCASDeployConfig from "./DeployNodeConfigFormAliyunCASDeployConfig"; import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig"; import DeployNodeConfigFormAliyunCLBConfig from "./DeployNodeConfigFormAliyunCLBConfig"; import DeployNodeConfigFormAliyunDCDNConfig from "./DeployNodeConfigFormAliyunDCDNConfig"; import DeployNodeConfigFormAliyunESAConfig from "./DeployNodeConfigFormAliyunESAConfig"; +import DeployNodeConfigFormAliyunFCConfig from "./DeployNodeConfigFormAliyunFCConfig"; import DeployNodeConfigFormAliyunLiveConfig from "./DeployNodeConfigFormAliyunLiveConfig"; import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLBConfig"; import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig"; @@ -55,6 +58,7 @@ import DeployNodeConfigFormTencentCloudCOSConfig from "./DeployNodeConfigFormTen import DeployNodeConfigFormTencentCloudCSSConfig from "./DeployNodeConfigFormTencentCloudCSSConfig.tsx"; import DeployNodeConfigFormTencentCloudECDNConfig from "./DeployNodeConfigFormTencentCloudECDNConfig.tsx"; import DeployNodeConfigFormTencentCloudEOConfig from "./DeployNodeConfigFormTencentCloudEOConfig.tsx"; +import DeployNodeConfigFormTencentCloudSCFConfig from "./DeployNodeConfigFormTencentCloudSCFConfig"; import DeployNodeConfigFormTencentCloudSSLDeployConfig from "./DeployNodeConfigFormTencentCloudSSLDeployConfig"; import DeployNodeConfigFormTencentCloudVODConfig from "./DeployNodeConfigFormTencentCloudVODConfig"; import DeployNodeConfigFormTencentCloudWAFConfig from "./DeployNodeConfigFormTencentCloudWAFConfig"; @@ -138,6 +142,10 @@ const DeployNodeConfigForm = forwardRef; + case DEPLOY_PROVIDERS["1PANEL_SITE"]: + return ; case DEPLOY_PROVIDERS.ALIYUN_ALB: return ; case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY: @@ -150,6 +158,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.ALIYUN_ESA: return ; + case DEPLOY_PROVIDERS.ALIYUN_FC: + return ; case DEPLOY_PROVIDERS.ALIYUN_LIVE: return ; case DEPLOY_PROVIDERS.ALIYUN_NLB: @@ -218,6 +228,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.TENCENTCLOUD_EO: return ; + case DEPLOY_PROVIDERS.TENCENTCLOUD_SCF: + return ; case DEPLOY_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY: return ; case DEPLOY_PROVIDERS.TENCENTCLOUD_VOD: @@ -427,8 +439,8 @@ const DeployNodeConfigForm = forwardRef{t("workflow_node.deploy.form.skip_on_last_succeeded.prefix")}
{t("workflow_node.deploy.form.skip_on_last_succeeded.suffix")}
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm1PanelConsoleConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelConsoleConfig.tsx new file mode 100644 index 00000000..349b516e --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelConsoleConfig.tsx @@ -0,0 +1,56 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigForm1PanelConsoleConfigFieldValues = Nullish<{ + autoRestart?: boolean; +}>; + +export type DeployNodeConfigForm1PanelConsoleConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigForm1PanelConsoleConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigForm1PanelConsoleConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigForm1PanelConsoleConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigForm1PanelConsoleConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigForm1PanelConsoleConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + autoRestart: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + +
+ ); +}; + +export default DeployNodeConfigForm1PanelConsoleConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx new file mode 100644 index 00000000..f5a26450 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx @@ -0,0 +1,63 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigForm1PanelSiteConfigFieldValues = Nullish<{ + websiteId: string | number; +}>; + +export type DeployNodeConfigForm1PanelSiteConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigForm1PanelSiteConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigForm1PanelSiteConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigForm1PanelSiteConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigForm1PanelSiteConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigForm1PanelSiteConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + websiteId: z.union([z.string(), z.number()]).refine((v) => { + return /^\d+$/.test(v + "") && +v > 0; + }, t("workflow_node.deploy.form.1panel_site_website_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigForm1PanelSiteConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunFCConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunFCConfig.tsx new file mode 100644 index 00000000..87212953 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunFCConfig.tsx @@ -0,0 +1,90 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormAliyunFCConfigFieldValues = Nullish<{ + region: string; + serviceVersion: string; + domain: string; +}>; + +export type DeployNodeConfigFormAliyunFCConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAliyunFCConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAliyunFCConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormAliyunFCConfigFieldValues => { + return { + serviceVersion: "3.0", + }; +}; + +const DeployNodeConfigFormAliyunFCConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormAliyunFCConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + serviceVersion: z.union([z.literal("2.0"), z.literal("3.0")], { + message: t("workflow_node.deploy.form.aliyun_fc_service_version.placeholder"), + }), + region: z + .string({ message: t("workflow_node.deploy.form.aliyun_fc_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aliyun_fc_region.placeholder")) + .trim(), + domain: z + .string({ message: t("workflow_node.deploy.form.aliyun_fc_domain.placeholder") }) + .refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + } + > + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAliyunFCConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSCFConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSCFConfig.tsx new file mode 100644 index 00000000..6758ddbd --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSCFConfig.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 { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormTencentCloudSCFConfigFieldValues = Nullish<{ + region: string; + domain: string; +}>; + +export type DeployNodeConfigFormTencentCloudSCFConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormTencentCloudSCFConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormTencentCloudSCFConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormTencentCloudSCFConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormTencentCloudSCFConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormTencentCloudSCFConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.tencentcloud_scf_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.tencentcloud_scf_region.placeholder")) + .trim(), + domain: z + .string({ message: t("workflow_node.deploy.form.tencentcloud_scf_domain.placeholder") }) + .refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormTencentCloudSCFConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index d32960ca..1b5adf45 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -6,6 +6,7 @@ export interface AccessModel extends BaseModel { NOTICE: If you add new type, please keep ASCII order. */ Record & ( + | AccessConfigFor1Panel | AccessConfigForACMEHttpReq | AccessConfigForAliyun | AccessConfigForAWS @@ -46,6 +47,12 @@ export interface AccessModel extends BaseModel { } // #region AccessConfig +export type AccessConfigFor1Panel = { + apiUrl: string; + apiKey: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForACMEHttpReq = { endpoint: string; mode?: string; @@ -82,6 +89,7 @@ export type AccessConfigForBaishan = { export type AccessConfigForBaotaPanel = { apiUrl: string; apiKey: string; + allowInsecureConnections?: boolean; }; export type AccessConfigForBytePlus = { @@ -193,6 +201,7 @@ export type AccessConfigForRainYun = { export type AccessConfigForSafeLine = { apiUrl: string; apiToken: string; + allowInsecureConnections?: boolean; }; export type AccessConfigForSSH = { @@ -222,6 +231,7 @@ export type AccessConfigForVolcEngine = { export type AccessConfigForWebhook = { url: string; + allowInsecureConnections?: boolean; }; export type AccessConfigForWestcn = { diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 0841f39d..f3d6deb3 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -4,6 +4,7 @@ NOTICE: If you add new constant, please keep ASCII order. */ export const ACCESS_PROVIDERS = Object.freeze({ + ["1PANEL"]: "1panel", ACMEHTTPREQ: "acmehttpreq", ALIYUN: "aliyun", AWS: "aws", @@ -84,6 +85,7 @@ export const accessProvidersMap: MapHost provider: The provider that hosts your servers or cloud services for deploying certificates.

Cannot be edited after saving.", + "access.form.1panel_api_url.label": "1Panel URL", + "access.form.1panel_api_url.placeholder": "Please enter 1Panel URL", + "access.form.1panel_api_url.tooltip": "For more information, see https://docs.1panel.pro/dev_manual/api_manual/", + "access.form.1panel_api_key.label": "1Panel API key", + "access.form.1panel_api_key.placeholder": "Please enter 1Panel API key", + "access.form.1panel_api_key.tooltip": "For more information, see https://docs.1panel.pro/dev_manual/api_manual/", + "access.form.1panel_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.1panel_allow_insecure_conns.tooltip": "Allowing insecure connections may lead to data leak or tampering. Use this option only when under trusted networks.", + "access.form.1panel_allow_insecure_conns.switch.on": "Allow", + "access.form.1panel_allow_insecure_conns.switch.off": "Disallow", "access.form.acmehttpreq_endpoint.label": "Endpoint", "access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint", "access.form.acmehttpreq_endpoint.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", @@ -73,6 +83,10 @@ "access.form.baotapanel_api_key.label": "aaPanel API key", "access.form.baotapanel_api_key.placeholder": "Please enter aaPanel API key", "access.form.baotapanel_api_key.tooltip": "For more information, see https://www.bt.cn/bbs/thread-20376-1-1.html", + "access.form.baotapanel_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.baotapanel_allow_insecure_conns.tooltip": "Allowing insecure connections may lead to data leak or tampering. Use this option only when under trusted networks.", + "access.form.baotapanel_allow_insecure_conns.switch.on": "Allow", + "access.form.baotapanel_allow_insecure_conns.switch.off": "Disallow", "access.form.byteplus_access_key.label": "BytePlus AccessKey", "access.form.byteplus_access_key.placeholder": "Please enter BytePlus AccessKey", "access.form.byteplus_access_key.tooltip": "For more information, see https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys", @@ -194,6 +208,10 @@ "access.form.safeline_api_token.label": "SafeLine API token", "access.form.safeline_api_token.placeholder": "Please enter SafeLine API token", "access.form.safeline_api_token.tooltip": "For more information, see https://docs.waf.chaitin.com/en/reference/articles/openapi", + "access.form.safeline_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.safeline_allow_insecure_conns.tooltip": "Allowing insecure connections may lead to data leak or tampering. Use this option only when under trusted networks.", + "access.form.safeline_allow_insecure_conns.switch.on": "Allow", + "access.form.safeline_allow_insecure_conns.switch.off": "Disallow", "access.form.ssh_host.label": "Server host", "access.form.ssh_host.placeholder": "Please enter server host", "access.form.ssh_port.label": "Server port", @@ -233,6 +251,10 @@ "access.form.volcengine_secret_access_key.tooltip": "For more information, see https://www.volcengine.com/docs/6291/216571", "access.form.webhook_url.label": "Webhook URL", "access.form.webhook_url.placeholder": "Please enter Webhook URL", + "access.form.webhook_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.webhook_allow_insecure_conns.tooltip": "Allowing insecure connections may lead to data leak or tampering. Use this option only when under trusted networks.", + "access.form.webhook_allow_insecure_conns.switch.on": "Allow", + "access.form.webhook_allow_insecure_conns.switch.off": "Disallow", "access.form.westcn_username.label": "West.cn username", "access.form.westcn_username.placeholder": "Please enter West.cn username", "access.form.westcn_username.tooltip": "For more information, see https://www.west.cn/CustomerCenter/doc/apiv2.html", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 9ce0bc5c..4c6091e1 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -1,5 +1,7 @@ { "provider.1panel": "1Panel", + "provider.1panel.console": "1Panel - Console", + "provider.1panel.site": "1Panel - Website", "provider.acmehttpreq": "Http Request (ACME Proxy)", "provider.aliyun": "Alibaba Cloud", "provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", @@ -9,6 +11,7 @@ "provider.aliyun.dcdn": "Alibaba Cloud - DCDN (Dynamic Route for Content Delivery Network)", "provider.aliyun.dns": "Alibaba Cloud - DNS (Domain Name Service)", "provider.aliyun.esa": "Alibaba Cloud - ESA (Edge Security Acceleration)", + "provider.aliyun.fc": "Alibaba Cloud - FC (Function Compute)", "provider.aliyun.live": "Alibaba Cloud - ApsaraVideo Live", "provider.aliyun.nlb": "Alibaba Cloud - NLB (Network Load Balancer)", "provider.aliyun.oss": "Alibaba Cloud - OSS (Object Storage Service)", @@ -28,7 +31,7 @@ "provider.baishan.cdn": "Baishan - CDN (Content Delivery Network)", "provider.baotapanel": "aaPanel (aka BaoTaPanel)", "provider.baotapanel.console": "aaPanel (aka BaoTaPanel) - Console", - "provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Site", + "provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Website", "provider.byteplus": "BytePlus", "provider.byteplus.cdn": "BytePlus - CDN (Content Delivery Network)", "provider.cachefly": "CacheFly", @@ -83,6 +86,7 @@ "provider.tencentcloud.dns": "Tencent Cloud - DNS (Domain Name Service)", "provider.tencentcloud.ecdn": "Tencent Cloud - ECDN (Enterprise Content Delivery Network)", "provider.tencentcloud.eo": "Tencent Cloud - EdgeOne", + "provider.tencentcloud.scf": "Tencent Cloud - SCF (Serverless Cloud Function)", "provider.tencentcloud.ssl_deploy": "Tencent Cloud - via SSL Certificate Service Deployment Job", "provider.tencentcloud.vod": "Tencent Cloud - VOD (Video on Demand)", "provider.tencentcloud.waf": "Tencent Cloud - WAF (Web Application Firewall)", @@ -106,6 +110,7 @@ "provider.category.loadbalance": "Loadbalance", "provider.category.firewall": "Firewall", "provider.category.av": "Audio/Video", + "provider.category.serverless": "Serverless", "provider.category.website": "Website", "provider.category.other": "Other" } diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index 0bb6ba40..f4b3c85f 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -16,7 +16,7 @@ "settings.password.form.password.errmsg.not_matched": "Passwords do not match", "settings.notification.tab": "Notification", - "settings.notification.template.card.title": "Template", + "settings.notification.template.card.title": "Certificate expiration notification template", "settings.notification.template.form.subject.label": "Subject", "settings.notification.template.form.subject.placeholder": "Please enter notification subject", "settings.notification.template.form.subject.extra": "Supported variables (${COUNT}: number of expiring soon)", @@ -24,8 +24,8 @@ "settings.notification.template.form.message.placeholder": "Please enter notification message", "settings.notification.template.form.message.extra": "Supported variables (${COUNT}: number of expiring soon. ${DOMAINS}: Domain list)", "settings.notification.channels.card.title": "Channels", - "settings.notification.channel.enabled.on": "On", - "settings.notification.channel.enabled.off": "Off", + "settings.notification.channel.switch.on": "On", + "settings.notification.channel.switch.off": "Off", "settings.notification.push_test.button": "Send test notification", "settings.notification.push_test.pushed": "Sent", "settings.notification.channel.form.bark_server_url.label": "Server URL", @@ -44,7 +44,7 @@ "settings.notification.channel.form.email_smtp_host.placeholder": "Please enter SMTP host", "settings.notification.channel.form.email_smtp_port.label": "SMTP port", "settings.notification.channel.form.email_smtp_port.placeholder": "Please enter SMTP port", - "settings.notification.channel.form.email_smtp_tls.label": "Use TLS/SSL", + "settings.notification.channel.form.email_smtp_tls.label": "Use SSL/TLS", "settings.notification.channel.form.email_username.label": "Username", "settings.notification.channel.form.email_username.placeholder": "please enter username", "settings.notification.channel.form.email_password.label": "Password", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 66f43308..4035755c 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -90,6 +90,10 @@ "workflow_node.deploy.form.certificate.placeholder": "Please select certificate", "workflow_node.deploy.form.certificate.tooltip": "The certificate to be deployed comes from the previous application stage node.", "workflow_node.deploy.form.params_config.label": "Parameter settings", + "workflow_node.deploy.form.1panel_console_auto_restart.label": "Auto restart after deployment", + "workflow_node.deploy.form.1panel_site_website_id.label": "1Panel website ID", + "workflow_node.deploy.form.1panel_site_website_id.placeholder": "Please enter 1Panel website ID", + "workflow_node.deploy.form.1panel_site_website_id.tooltip": "You can find it on 1Panel WebUI.", "workflow_node.deploy.form.aliyun_alb_resource_type.label": "Resource type", "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "ALB load balancer", @@ -150,6 +154,14 @@ "workflow_node.deploy.form.aliyun_esa_site_id.label": "Alibaba Cloud ESA site ID", "workflow_node.deploy.form.aliyun_esa_site_id.placeholder": "Please enter Alibaba Cloud ESA site ID", "workflow_node.deploy.form.aliyun_esa_site_id.tooltip": "For more information, see https://esa.console.aliyun.com/siteManage/list", + "workflow_node.deploy.form.aliyun_fc_region.label": "Alibaba Cloud FC region", + "workflow_node.deploy.form.aliyun_fc_region.placeholder": "Please enter Alibaba Cloud FC region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_fc_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/functioncompute/fc-3-0/product-overview/supported-regions", + "workflow_node.deploy.form.aliyun_fc_service_version.label": "Alibaba Cloud FC version", + "workflow_node.deploy.form.aliyun_fc_service_version.placeholder": "Please select Alibaba Cloud FC version", + "workflow_node.deploy.form.aliyun_fc_domain.label": "Alibaba Cloud FC domain", + "workflow_node.deploy.form.aliyun_fc_domain.placeholder": "Please enter Alibaba Cloud FC domain name", + "workflow_node.deploy.form.aliyun_fc_domain.tooltip": "For more information, see https://fcnext.console.aliyun.com", "workflow_node.deploy.form.aliyun_live_region.label": "Alibaba Cloud Live region", "workflow_node.deploy.form.aliyun_live_region.placeholder": "Please enter Alibaba Cloud Live region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/live/product-overview/supported-regions", @@ -441,6 +453,12 @@ "workflow_node.deploy.form.tencentcloud_eo_domain.label": "Tencent Cloud EdgeOne domain", "workflow_node.deploy.form.tencentcloud_eo_domain.placeholder": "Please enter Tencent Cloud EdgeOne domain name", "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "For more information, see https://console.tencentcloud.com/edgeone", + "workflow_node.deploy.form.tencentcloud_scf_region.label": "Tencent Cloud SCF region", + "workflow_node.deploy.form.tencentcloud_scf_region.placeholder": "Please enter Tencent Cloud SCF region (e.g. ap-guangzhou)", + "workflow_node.deploy.form.tencentcloud_scf_region.tooltip": "For more information, see https://www.tencentcloud.com/document/product/583/17299", + "workflow_node.deploy.form.tencentcloud_scf_domain.label": "Tencent Cloud SCF domain", + "workflow_node.deploy.form.tencentcloud_scf_domain.placeholder": "Please enter Tencent Cloud SCF domain name", + "workflow_node.deploy.form.tencentcloud_scf_domain.tooltip": "For more information, see https://console.tencentcloud.com/scf", "workflow_node.deploy.form.tencentcloud_ssl_deploy.guide": "TIPS: You need to go to the Tencent Cloud console to check the actual deployment results by yourself, because Tencent Cloud deployment tasks are running asynchronously.", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.label": "Tencent Cloud service region", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.placeholder": "Please enter Tencent Cloud service region (e.g. ap-guangzhou)", @@ -529,8 +547,8 @@ "workflow_node.deploy.form.skip_on_last_succeeded.label": "Repeated deployment", "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "If the last deployment was successful, ", "workflow_node.deploy.form.skip_on_last_succeeded.suffix": " to re-deploy.", - "workflow_node.deploy.form.skip_on_last_succeeded.enabled.on": "skip", - "workflow_node.deploy.form.skip_on_last_succeeded.enabled.off": "not skip", + "workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "skip", + "workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "not skip", "workflow_node.notify.label": "Notification", "workflow_node.notify.form.subject.label": "Subject", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 64b034d5..d72cd259 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -23,6 +23,16 @@ "access.form.provider.label": "提供商", "access.form.provider.placeholder": "请选择提供商", "access.form.provider.tooltip": "提供商分为两种类型:
【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。
【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。

该字段保存后不可修改。", + "access.form.1panel_api_url.label": "1Panel URL", + "access.form.1panel_api_url.placeholder": "请输入 1Panel URL", + "access.form.1panel_api_url.tooltip": "这是什么?请参阅 https://1panel.cn/docs/dev_manual/api_manual/", + "access.form.1panel_api_key.label": "1Panel 接口密钥", + "access.form.1panel_api_key.placeholder": "请输入 1Panel 接口密钥", + "access.form.1panel_api_key.tooltip": "这是什么?请参阅 https://1panel.cn/docs/dev_manual/api_manual/", + "access.form.1panel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.1panel_allow_insecure_conns.tooltip": "忽略 SSL/TLS 证书错误可能导致数据泄露或被篡改。建议仅在可信网络下启用。", + "access.form.1panel_allow_insecure_conns.switch.on": "允许", + "access.form.1panel_allow_insecure_conns.switch.off": "不允许", "access.form.acmehttpreq_endpoint.label": "服务端点", "access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点", "access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", @@ -73,6 +83,10 @@ "access.form.baotapanel_api_key.label": "宝塔面板接口密钥", "access.form.baotapanel_api_key.placeholder": "请输入宝塔面板接口密钥", "access.form.baotapanel_api_key.tooltip": "这是什么?请参阅 https://www.bt.cn/bbs/thread-113890-1-1.html", + "access.form.baotapanel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.baotapanel_allow_insecure_conns.tooltip": "忽略 SSL/TLS 证书错误可能导致数据泄露或被篡改。建议仅在可信网络下启用。", + "access.form.baotapanel_allow_insecure_conns.switch.on": "允许", + "access.form.baotapanel_allow_insecure_conns.switch.off": "不允许", "access.form.byteplus_access_key.label": "BytePlus AccessKey", "access.form.byteplus_access_key.placeholder": "请输入 BytePlus AccessKey", "access.form.byteplus_access_key.tooltip": "这是什么?请参阅 https://docs.byteplus.com/zh-CN/docs/byteplus-platform/docs-managing-keys", @@ -194,6 +208,10 @@ "access.form.safeline_api_token.label": "雷池 API Token", "access.form.safeline_api_token.placeholder": "请输入雷池 API Token", "access.form.safeline_api_token.tooltip": "这是什么?请参阅 https://docs.waf-ce.chaitin.cn/zh/更多技术文档/OPENAPI", + "access.form.safeline_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.safeline_allow_insecure_conns.tooltip": "忽略 SSL/TLS 证书错误可能导致数据泄露或被篡改。建议仅在可信网络下启用。", + "access.form.safeline_allow_insecure_conns.switch.on": "允许", + "access.form.safeline_allow_insecure_conns.switch.off": "不允许", "access.form.ssh_host.label": "服务器地址", "access.form.ssh_host.placeholder": "请输入服务器地址", "access.form.ssh_port.label": "服务器端口", @@ -233,6 +251,10 @@ "access.form.volcengine_secret_access_key.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6291/216571", "access.form.webhook_url.label": "Webhook 回调地址", "access.form.webhook_url.placeholder": "请输入 Webhook 回调地址", + "access.form.webhook_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.webhook_allow_insecure_conns.tooltip": "忽略 SSL/TLS 证书错误可能导致数据泄露或被篡改。建议仅在可信网络下启用。", + "access.form.webhook_allow_insecure_conns.switch.on": "允许", + "access.form.webhook_allow_insecure_conns.switch.off": "不允许", "access.form.westcn_username.label": "西部数码用户名", "access.form.westcn_username.placeholder": "请输入西部数码用户名", "access.form.westcn_username.tooltip": "这是什么?请参阅 https://www.west.cn/CustomerCenter/doc/apiv2.html", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index f8d36830..e8580e41 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -1,5 +1,7 @@ { "provider.1panel": "1Panel", + "provider.1panel.console": "1Panel - 面板", + "provider.1panel.site": "1Panel - 网站", "provider.acmehttpreq": "Http Request (ACME Proxy)", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", @@ -8,6 +10,7 @@ "provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB", "provider.aliyun.dcdn": "阿里云 - 全站加速 DCDN", "provider.aliyun.esa": "阿里云 - 边缘安全加速 ESA", + "provider.aliyun.fc": "阿里云 - 函数计算 FC", "provider.aliyun.dns": "阿里云 - 云解析 DNS", "provider.aliyun.live": "阿里云 - 视频直播 Live", "provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB", @@ -83,6 +86,7 @@ "provider.tencentcloud.dns": "腾讯云 - 云解析 DNS", "provider.tencentcloud.ecdn": "腾讯云 - 全站加速网络 ECDN", "provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne", + "provider.tencentcloud.scf": "腾讯云 - 云函数 SCF", "provider.tencentcloud.ssl_deploy": "腾讯云 - 通过 SSL 证书服务创建部署任务", "provider.tencentcloud.vod": "腾讯云 - 云点播 VOD", "provider.tencentcloud.waf": "腾讯云 - Web 应用防火墙 WAF", @@ -106,6 +110,7 @@ "provider.category.loadbalance": "负载均衡", "provider.category.firewall": "防火墙", "provider.category.av": "音视频", - "provider.category.website": "网站", + "provider.category.serverless": "Serverless", + "provider.category.website": "网站托管", "provider.category.other": "其他" } diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index c2204d4d..1fcec35d 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -16,16 +16,16 @@ "settings.password.form.password.errmsg.not_matched": "两次密码不一致", "settings.notification.tab": "消息推送", - "settings.notification.template.card.title": "通知模板", + "settings.notification.template.card.title": "证书过期通知模板(全局)", "settings.notification.template.form.subject.label": "通知主题", "settings.notification.template.form.subject.placeholder": "请输入通知主题", "settings.notification.template.form.subject.extra": "支持的变量(${COUNT}: 即将过期张数)", "settings.notification.template.form.message.label": "通知内容", "settings.notification.template.form.message.placeholder": "请输入通知内容", - "settings.notification.template.form.message.extra": "支持的变量(${COUNT}: 即将过期张数;${DOMAINS}: 域名列表)", + "settings.notification.template.form.message.extra": "过期前 20 天发送通知。支持的变量(${COUNT}: 即将过期张数;${DOMAINS}: 域名列表)", "settings.notification.channels.card.title": "通知渠道", - "settings.notification.channel.enabled.on": "启用", - "settings.notification.channel.enabled.off": "停用", + "settings.notification.channel.switch.on": "启用", + "settings.notification.channel.switch.off": "停用", "settings.notification.push_test.button": "推送测试消息", "settings.notification.push_test.pushed": "已推送", "settings.notification.channel.form.bark_server_url.label": "服务器地址", @@ -44,7 +44,7 @@ "settings.notification.channel.form.email_smtp_host.placeholder": "请输入 SMTP 服务器地址", "settings.notification.channel.form.email_smtp_port.label": "SMTP 服务器端口", "settings.notification.channel.form.email_smtp_port.placeholder": "请输入 SMTP 服务器端口", - "settings.notification.channel.form.email_smtp_tls.label": "TLS/SSL 连接", + "settings.notification.channel.form.email_smtp_tls.label": "SSL/TLS 连接", "settings.notification.channel.form.email_username.label": "用户名", "settings.notification.channel.form.email_username.placeholder": "请输入用户名", "settings.notification.channel.form.email_password.label": "密码", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 8778b6b5..2ba9e329 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -90,6 +90,10 @@ "workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书", "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请阶段。如果选项为空请先确保前序节点配置正确。", "workflow_node.deploy.form.params_config.label": "参数设置", + "workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启面板服务", + "workflow_node.deploy.form.1panel_site_website_id.label": "1Panel 网站 ID", + "workflow_node.deploy.form.1panel_site_website_id.placeholder": "请输入 1Panel 网站 ID", + "workflow_node.deploy.form.1panel_site_website_id.tooltip": "请在 1Panel 管理面板查看。", "workflow_node.deploy.form.aliyun_alb_resource_type.label": "证书替换方式", "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", @@ -150,6 +154,14 @@ "workflow_node.deploy.form.aliyun_esa_site_id.label": "阿里云 ESA 站点 ID", "workflow_node.deploy.form.aliyun_esa_site_id.placeholder": "请输入阿里云 ESA 站点 ID", "workflow_node.deploy.form.aliyun_esa_site_id.tooltip": "这是什么?请参阅 https://esa.console.aliyun.com/siteManage/list", + "workflow_node.deploy.form.aliyun_fc_region.label": "阿里云 FC 服务地域", + "workflow_node.deploy.form.aliyun_fc_region.placeholder": "请输入阿里云 FC 服务地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_fc_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/functioncompute/fc-3-0/product-overview/supported-regions", + "workflow_node.deploy.form.aliyun_fc_service_version.label": "阿里云 FC 服务版本", + "workflow_node.deploy.form.aliyun_fc_service_version.placeholder": "请选择阿里云 FC 服务版本", + "workflow_node.deploy.form.aliyun_fc_domain.label": "阿里云 FC 自定义域名", + "workflow_node.deploy.form.aliyun_fc_domain.placeholder": "请输入阿里云 FC 自定义域名", + "workflow_node.deploy.form.aliyun_fc_domain.tooltip": "这是什么?请参阅 see https://fcnext.console.aliyun.com/", "workflow_node.deploy.form.aliyun_live_region.label": "阿里云视频直播服务地域", "workflow_node.deploy.form.aliyun_live_region.placeholder": "请输入阿里云视频直播服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/live/product-overview/supported-regions", @@ -441,6 +453,12 @@ "workflow_node.deploy.form.tencentcloud_eo_domain.label": "腾讯云 EdgeOne 加速域名", "workflow_node.deploy.form.tencentcloud_eo_domain.placeholder": "请输入腾讯云 EdgeOne 加速域名", "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", + "workflow_node.deploy.form.tencentcloud_scf_region.label": "腾讯云 SCF 产品地域", + "workflow_node.deploy.form.tencentcloud_scf_region.placeholder": "输入腾讯云 SCF 产品地域(例如:ap-guangzhou)", + "workflow_node.deploy.form.tencentcloud_scf_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/583/17299", + "workflow_node.deploy.form.tencentcloud_scf_domain.label": "腾讯云 SCF 自定义域名", + "workflow_node.deploy.form.tencentcloud_scf_domain.placeholder": "输入腾讯云 SCF 自定义域名", + "workflow_node.deploy.form.tencentcloud_scf_domain.tooltip": "这是什么?请参阅 https://console.tencentcloud.com/scf", "workflow_node.deploy.form.tencentcloud_ssl_deploy.guide": "小贴士:由于腾讯云证书部署任务是异步的,此节点若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往腾讯云控制台查询。", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.label": "腾讯云云产品地域", "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.placeholder": "请输入腾讯云云产品地域(例如:ap-guangzhou)", @@ -529,8 +547,8 @@ "workflow_node.deploy.form.skip_on_last_succeeded.label": "重复部署", "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署已成功时", "workflow_node.deploy.form.skip_on_last_succeeded.suffix": "重新部署。", - "workflow_node.deploy.form.skip_on_last_succeeded.enabled.on": "跳过", - "workflow_node.deploy.form.skip_on_last_succeeded.enabled.off": "不跳过", + "workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "跳过", + "workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "不跳过", "workflow_node.notify.label": "通知", "workflow_node.notify.form.subject.label": "通知主题",