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/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 + }) +}