diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index e6a04bcd..f1200094 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -26,13 +26,14 @@ import ( ) type ApplyResult struct { + CSR string FullChainCertificate string IssuerCertificate string PrivateKey string ACMEAccountUrl string ACMECertUrl string ACMECertStableUrl string - CSR string + ARIReplaced bool } type Applicant interface { @@ -109,7 +110,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err certRepo := repository.NewCertificateRepository() lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), config.Node.Id) - if lastCertificate != nil { + if lastCertificate != nil && !lastCertificate.ACMERenewed { newCertSan := slices.Clone(options.Domains) oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";") slices.Sort(newCertSan) @@ -119,8 +120,8 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate)) if lastCertX509 != nil { replacedARICertId, _ := certificate.MakeARICertID(lastCertX509) - options.ReplacedARIAcct = lastCertificate.ACMEAccountUrl - options.ReplacedARICert = replacedARICertId + options.ARIReplaceAcct = lastCertificate.ACMEAccountUrl + options.ARIReplaceCert = replacedARICertId } } } @@ -235,22 +236,24 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt Domains: options.Domains, Bundle: true, } - if options.ReplacedARIAcct == user.Registration.URI { - certRequest.ReplacesCertID = options.ReplacedARICert + if options.ARIReplaceAcct == user.Registration.URI { + certRequest.ReplacesCertID = options.ARIReplaceCert } + certResource, err := client.Certificate.Obtain(certRequest) if err != nil { return nil, err } return &ApplyResult{ + CSR: strings.TrimSpace(string(certResource.CSR)), FullChainCertificate: strings.TrimSpace(string(certResource.Certificate)), IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)), PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)), ACMEAccountUrl: user.Registration.URI, ACMECertUrl: certResource.CertURL, ACMECertStableUrl: certResource.CertStableURL, - CSR: strings.TrimSpace(string(certResource.CSR)), + ARIReplaced: certRequest.ReplacesCertID != "", }, nil } diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index de47ae18..175531ae 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -57,8 +57,8 @@ type applicantProviderOptions struct { DnsPropagationTimeout int32 DnsTTL int32 DisableFollowCNAME bool - ReplacedARIAcct string - ReplacedARICert string + ARIReplaceAcct string + ARIReplaceCert string } func createApplicantProvider(options *applicantProviderOptions) (challenge.Provider, error) { diff --git a/internal/domain/certificate.go b/internal/domain/certificate.go index 710ce268..b2b48fcd 100644 --- a/internal/domain/certificate.go +++ b/internal/domain/certificate.go @@ -28,6 +28,7 @@ type Certificate struct { ACMEAccountUrl string `json:"acmeAccountUrl" db:"acmeAccountUrl"` ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"` ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"` + ACMERenewed bool `json:"acmeRenewed" db:"acmeRenewed"` WorkflowId string `json:"workflowId" db:"workflowId"` WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"` WorkflowRunId string `json:"workflowRunId" db:"workflowRunId"` diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go index 690e5242..3a92bb7c 100644 --- a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go +++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go @@ -55,9 +55,10 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { } uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ - ApiUrl: config.ApiUrl, - ApiVersion: config.ApiVersion, - ApiKey: config.ApiKey, + ApiUrl: config.ApiUrl, + ApiVersion: config.ApiVersion, + ApiKey: config.ApiKey, + AllowInsecureConnections: config.AllowInsecureConnections, }) if err != nil { return nil, fmt.Errorf("failed to create ssl uploader: %w", 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 index 63900125..2a28c82c 100644 --- a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go +++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go @@ -2,6 +2,7 @@ package onepanelssl import ( "context" + "crypto/tls" "errors" "fmt" "log/slog" @@ -20,6 +21,8 @@ type UploaderConfig struct { ApiVersion string `json:"apiVersion"` // 1Panel 接口密钥。 ApiKey string `json:"apiKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } type UploaderProvider struct { @@ -35,7 +38,7 @@ func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { panic("config is nil") } - client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiKey) + client, err := createSdkClient(config.ApiUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections) if err != nil { return nil, fmt.Errorf("failed to create sdk client: %w", err) } @@ -132,7 +135,7 @@ func (u *UploaderProvider) getCertIfExists(ctx context.Context, certPEM string, return nil, nil } -func createSdkClient(apiUrl, apiVersion, apiKey string) (*onepanelsdk.Client, error) { +func createSdkClient(apiUrl, apiVersion, apiKey string, skipTlsVerify bool) (*onepanelsdk.Client, error) { if _, err := url.Parse(apiUrl); err != nil { return nil, errors.New("invalid 1panel api url") } @@ -146,5 +149,9 @@ func createSdkClient(apiUrl, apiVersion, apiKey string) (*onepanelsdk.Client, er } client := onepanelsdk.NewClient(apiUrl, apiVersion, apiKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + return client, nil } diff --git a/internal/repository/certificate.go b/internal/repository/certificate.go index 95bfd713..290d5f9f 100644 --- a/internal/repository/certificate.go +++ b/internal/repository/certificate.go @@ -77,6 +77,25 @@ func (r *CertificateRepository) GetByWorkflowNodeId(ctx context.Context, workflo return r.castRecordToModel(records[0]) } +func (r *CertificateRepository) GetByWorkflowRunId(ctx context.Context, workflowRunId string) (*domain.Certificate, error) { + records, err := app.GetApp().FindRecordsByFilter( + domain.CollectionNameCertificate, + "workflowRunId={:workflowRunId} && deleted=null", + "-created", + 1, 0, + dbx.Params{"workflowRunId": workflowRunId}, + ) + if err != nil { + return nil, err + } + + if len(records) == 0 { + return nil, domain.ErrRecordNotFound + } + + return r.castRecordToModel(records[0]) +} + func (r *CertificateRepository) Save(ctx context.Context, certificate *domain.Certificate) (*domain.Certificate, error) { collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameCertificate) if err != nil { @@ -109,6 +128,7 @@ func (r *CertificateRepository) Save(ctx context.Context, certificate *domain.Ce record.Set("acmeAccountUrl", certificate.ACMEAccountUrl) record.Set("acmeCertUrl", certificate.ACMECertUrl) record.Set("acmeCertStableUrl", certificate.ACMECertStableUrl) + record.Set("acmeRenewed", certificate.ACMERenewed) record.Set("workflowId", certificate.WorkflowId) record.Set("workflowRunId", certificate.WorkflowRunId) record.Set("workflowNodeId", certificate.WorkflowNodeId) @@ -170,6 +190,7 @@ func (r *CertificateRepository) castRecordToModel(record *core.Record) (*domain. ACMEAccountUrl: record.GetString("acmeAccountUrl"), ACMECertUrl: record.GetString("acmeCertUrl"), ACMECertStableUrl: record.GetString("acmeCertStableUrl"), + ACMERenewed: record.GetBool("acmeRenewed"), WorkflowId: record.GetString("workflowId"), WorkflowRunId: record.GetString("workflowRunId"), WorkflowNodeId: record.GetString("workflowNodeId"), diff --git a/internal/workflow/node-processor/apply_node.go b/internal/workflow/node-processor/apply_node.go index ff8c573d..d38ece89 100644 --- a/internal/workflow/node-processor/apply_node.go +++ b/internal/workflow/node-processor/apply_node.go @@ -96,6 +96,15 @@ func (n *applyNode) Process(ctx context.Context) error { return err } + // 保存 ARI 记录 + if applyResult.ARIReplaced { + lastCertificate, _ := n.certRepo.GetByWorkflowRunId(ctx, lastOutput.RunId) + if lastCertificate != nil { + lastCertificate.ACMERenewed = true + n.certRepo.Save(ctx, lastCertificate) + } + } + n.logger.Info("apply completed") return nil @@ -134,7 +143,7 @@ func (n *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo return false, "the configuration item 'KeyAlgorithm' changed" } - lastCertificate, _ := n.certRepo.GetByWorkflowNodeId(ctx, n.node.Id) + lastCertificate, _ := n.certRepo.GetByWorkflowRunId(ctx, lastOutput.RunId) if lastCertificate != nil { renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24 expirationTime := time.Until(lastCertificate.ExpireAt) diff --git a/internal/workflow/node-processor/processor.go b/internal/workflow/node-processor/processor.go index 4523b13a..f98aebae 100644 --- a/internal/workflow/node-processor/processor.go +++ b/internal/workflow/node-processor/processor.go @@ -34,6 +34,8 @@ func (n *nodeProcessor) SetLogger(logger *slog.Logger) { type certificateRepository interface { GetByWorkflowNodeId(ctx context.Context, workflowNodeId string) (*domain.Certificate, error) + GetByWorkflowRunId(ctx context.Context, workflowRunId string) (*domain.Certificate, error) + Save(ctx context.Context, certificate *domain.Certificate) (*domain.Certificate, error) } type workflowOutputRepository interface { diff --git a/internal/workflow/node-processor/upload_node.go b/internal/workflow/node-processor/upload_node.go index 2da19eed..a1878a41 100644 --- a/internal/workflow/node-processor/upload_node.go +++ b/internal/workflow/node-processor/upload_node.go @@ -83,7 +83,7 @@ func (n *uploadNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workfl return false, "the configuration item 'PrivateKey' changed" } - lastCertificate, _ := n.certRepo.GetByWorkflowNodeId(ctx, n.node.Id) + lastCertificate, _ := n.certRepo.GetByWorkflowRunId(ctx, lastOutput.RunId) if lastCertificate != nil { return true, "the certificate has already been uploaded" } diff --git a/migrations/1748228400_upgrade.go b/migrations/1748228400_upgrade.go new file mode 100644 index 00000000..b6f954d2 --- /dev/null +++ b/migrations/1748228400_upgrade.go @@ -0,0 +1,39 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + // update collection `certificate` + { + collection, err := app.FindCollectionByNameOrId("4szxr9x43tpj6np") + if err != nil { + return err + } + + // add field + if err := collection.Fields.AddMarshaledJSONAt(14, []byte(`{ + "hidden": false, + "id": "bool810050391", + "name": "acmeRenewed", + "presentable": false, + "required": false, + "system": false, + "type": "bool" + }`)); err != nil { + return err + } + + if err := app.Save(collection); err != nil { + return err + } + } + + return nil + }, func(app core.App) error { + return nil + }) +} diff --git a/ui/src/components/access/AccessEditModal.tsx b/ui/src/components/access/AccessEditModal.tsx index 8d2d5204..a20d50b9 100644 --- a/ui/src/components/access/AccessEditModal.tsx +++ b/ui/src/components/access/AccessEditModal.tsx @@ -100,6 +100,7 @@ const AccessEditModal = ({ data, loading, trigger, scene, usage, afterSubmit, .. }} afterClose={() => setOpen(false)} cancelButtonProps={{ disabled: formPending }} + cancelText={t("common.button.cancel")} closable confirmLoading={formPending} destroyOnHidden diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 80237287..e0602370 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -109,7 +109,7 @@ "workflow_node.deploy.form.certificate.placeholder": "Please select certificate", "workflow_node.deploy.form.certificate.tooltip": "The certificate to be deployed comes from the previous nodes of application or upload.", "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_console_auto_restart.label": "Auto restart 1Panel after deployment", "workflow_node.deploy.form.1panel_site_resource_type.label": "Resource type", "workflow_node.deploy.form.1panel_site_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.1panel_site_resource_type.option.website.label": "Website", @@ -331,7 +331,7 @@ "workflow_node.deploy.form.baishan_cdn_certificate_id.label": "Baishan Cloud CDN certificate ID (Optional)", "workflow_node.deploy.form.baishan_cdn_certificate_id.placeholder": "Please enter Baishan Cloud CDN certificate ID", "workflow_node.deploy.form.baishan_cdn_certificate_id.tooltip": "For more information, see https://cdnx.console.baishan.com/#/cdn/cert", - "workflow_node.deploy.form.baotapanel_console_auto_restart.label": "Auto restart after deployment", + "workflow_node.deploy.form.baotapanel_console_auto_restart.label": "Auto restart aaPanel after deployment", "workflow_node.deploy.form.baotapanel_site_type.label": "aaPanel site type", "workflow_node.deploy.form.baotapanel_site_type.placeholder": "Please select aaPanel site type", "workflow_node.deploy.form.baotapanel_site_type.option.php.label": "PHP sites", @@ -530,7 +530,7 @@ "workflow_node.deploy.form.netlify_site_id.tooltip": "For more information, see https://docs.netlify.com/api/get-started/#get-site", "workflow_node.deploy.form.proxmoxve_node_name.label": "Proxmox VE cluster node name", "workflow_node.deploy.form.proxmoxve_node_name.placeholder": "Please enter Proxmox VE cluster node name", - "workflow_node.deploy.form.proxmoxve_auto_restart.label": "Auto restart after deployment", + "workflow_node.deploy.form.proxmoxve_auto_restart.label": "Auto restart Proxmox VE after deployment", "workflow_node.deploy.form.qiniu_cdn_domain.label": "Qiniu CDN domain", "workflow_node.deploy.form.qiniu_cdn_domain.placeholder": "Please enter Qiniu CDN domain name", "workflow_node.deploy.form.qiniu_cdn_domain.tooltip": "For more information, see https://portal.qiniu.com/cdn", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index faf40816..2dbcb3ca 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -108,7 +108,7 @@ "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_console_auto_restart.label": "部署后自动重启 1Panel 服务", "workflow_node.deploy.form.1panel_site_resource_type.label": "证书部署方式", "workflow_node.deploy.form.1panel_site_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.1panel_site_resource_type.option.website.label": "替换指定网站的证书", @@ -330,7 +330,7 @@ "workflow_node.deploy.form.baishan_cdn_certificate_id.label": "白山云 CDN 原证书 ID(可选)", "workflow_node.deploy.form.baishan_cdn_certificate_id.placeholder": "请输入白山云 CDN 原证书 ID", "workflow_node.deploy.form.baishan_cdn_certificate_id.tooltip": "这是什么?请参阅 https://cdnx.console.baishan.com/#/cdn/cert

不填写时,将上传新证书;否则,将替换原证书。", - "workflow_node.deploy.form.baotapanel_console_auto_restart.label": "部署后自动重启 1Panel 服务", + "workflow_node.deploy.form.baotapanel_console_auto_restart.label": "部署后自动重启宝塔面板服务", "workflow_node.deploy.form.baotapanel_site_type.label": "宝塔面板网站类型", "workflow_node.deploy.form.baotapanel_site_type.placeholder": "请选择宝塔面板网站类型", "workflow_node.deploy.form.baotapanel_site_type.option.php.label": "PHP",