From 881b94099c6b91d5209f7d2c81f8719e5d37d799 Mon Sep 17 00:00:00 2001 From: cclohmar Date: Tue, 7 Jul 2026 15:23:40 +0100 Subject: [PATCH] fix: handle SAN certs from lego, check both backup and lego cache --- tools/nextwks-tool/main.go | 51 +++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/tools/nextwks-tool/main.go b/tools/nextwks-tool/main.go index f90c165..3ff18f9 100644 --- a/tools/nextwks-tool/main.go +++ b/tools/nextwks-tool/main.go @@ -113,12 +113,11 @@ func runCert(args []string) { log.Fatalf("Failed to create backup dir: %v", err) } - // Check if all domains have valid LE certs in backup + // Check if all domains have valid LE certs in backup or lego cache needIssue := false for _, domain := range domains { - certFile := filepath.Join(*backupDir, domain, "fullchain.pem") - keyFile := filepath.Join(*backupDir, domain, "privkey.pem") - if !fileExists(certFile) || !fileExists(keyFile) { + certFile := findCertFile(domain, *backupDir, *legoDir) + if certFile == "" { needIssue = true break } @@ -186,20 +185,22 @@ func obtainCertsLego(domains []string, email, backupDir, legoDir, legoPath strin } // Copy certificates from lego output to backup + // Lego issues a SAN cert (single cert for all domains) named after the first domain certDir := filepath.Join(legoDir, "certificates") + firstDomain := domains[0] + crtSrc := filepath.Join(certDir, firstDomain+".crt") + keySrc := filepath.Join(certDir, firstDomain+".key") + + if !fileExists(crtSrc) || !fileExists(keySrc) { + return fmt.Errorf("lego did not produce expected cert files in %s", certDir) + } + for _, domain := range domains { - crtSrc := filepath.Join(certDir, domain+".crt") - keySrc := filepath.Join(certDir, domain+".key") domainDir := filepath.Join(backupDir, domain) os.MkdirAll(domainDir, 0755) - - if fileExists(crtSrc) && fileExists(keySrc) { - copyFile(crtSrc, filepath.Join(domainDir, "fullchain.pem")) - copyFile(keySrc, filepath.Join(domainDir, "privkey.pem")) - log.Printf("[OK] Certificate obtained for %s", domain) - } else { - log.Printf("[WARN] Certificate files not found for %s in %s", domain, certDir) - } + copyFile(crtSrc, filepath.Join(domainDir, "fullchain.pem")) + copyFile(keySrc, filepath.Join(domainDir, "privkey.pem")) + log.Printf("[OK] Certificate obtained for %s", domain) } return nil } @@ -245,6 +246,28 @@ func runDB(args []string) { // --- Helpers --- +func findCertFile(domain, backupDir, legoDir string) string { + // Check backup first + candidates := []string{ + filepath.Join(backupDir, domain, "fullchain.pem"), + filepath.Join(legoDir, "certificates", domain+".crt"), + } + // Lego issues SAN cert named after first domain — check in lego cache + entries, _ := os.ReadDir(filepath.Join(legoDir, "certificates")) + for _, e := range entries { + if strings.HasSuffix(e.Name(), ".crt") && !strings.Contains(e.Name(), ".issuer.") { + candidates = append(candidates, filepath.Join(legoDir, "certificates", e.Name())) + break + } + } + for _, c := range candidates { + if fileExists(c) && isCertFromLE(c) { + return c + } + } + return "" +} + func fileExists(path string) bool { _, err := os.Stat(path) return err == nil