v0.01: 修复死锁Bug + DNS-01默认 + 超时调整
This commit is contained in:
+15
-10
@@ -43,9 +43,9 @@ func Load() *Config {
|
||||
|
||||
// CertStore is the in-memory store for certificates with file persistence
|
||||
type CertStore struct {
|
||||
mu sync.RWMutex
|
||||
data map[string]*Certificate // key: domain
|
||||
path string
|
||||
mu sync.RWMutex
|
||||
data map[string]*Certificate // key: domain
|
||||
path string
|
||||
nextID uint
|
||||
}
|
||||
|
||||
@@ -99,11 +99,9 @@ func (s *CertStore) Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save writes the current in-memory certificates to JSON file
|
||||
func (s *CertStore) Save() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
// save writes the current in-memory certificates to JSON file
|
||||
// IMPORTANT: caller must hold the lock (read or write) before calling this
|
||||
func (s *CertStore) save() error {
|
||||
certs := make([]*Certificate, 0, len(s.data))
|
||||
for _, c := range s.data {
|
||||
certs = append(certs, c)
|
||||
@@ -117,6 +115,13 @@ func (s *CertStore) Save() error {
|
||||
return os.WriteFile(s.path, data, 0600)
|
||||
}
|
||||
|
||||
// Save writes the current in-memory certificates to JSON file (public, acquires own lock)
|
||||
func (s *CertStore) Save() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// GetAll returns all certificates sorted by ID descending
|
||||
func (s *CertStore) GetAll() []*Certificate {
|
||||
s.mu.RLock()
|
||||
@@ -170,7 +175,7 @@ func (s *CertStore) Upsert(cert *Certificate) error {
|
||||
cert.UpdatedAt = time.Now()
|
||||
|
||||
s.data[cert.Domain] = cert
|
||||
return s.Save()
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// Delete removes a certificate by ID
|
||||
@@ -181,7 +186,7 @@ func (s *CertStore) Delete(id uint) error {
|
||||
for domain, c := range s.data {
|
||||
if c.ID == id {
|
||||
delete(s.data, domain)
|
||||
return s.Save()
|
||||
return s.save()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"auto-ssl/config"
|
||||
"auto-ssl/services"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -246,13 +244,13 @@ func (h *CertHandler) GetCertFiles(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
fullchain, privkey, chain := services.GetCertFilesPaths(cert.Domain, h.Cfg)
|
||||
fullchain, privkey, chain := services.GetCertFileContents(cert.Domain, h.Cfg)
|
||||
|
||||
result := gin.H{
|
||||
"domain": cert.Domain,
|
||||
"fullchain": readFileSafe(fullchain),
|
||||
"privkey": readFileSafe(privkey),
|
||||
"chain": readFileSafe(chain),
|
||||
"fullchain": fullchain,
|
||||
"privkey": privkey,
|
||||
"chain": chain,
|
||||
}
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
@@ -310,10 +308,4 @@ func (h *CertHandler) Stats(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func readFileSafe(path string) string {
|
||||
data, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
+12
-16
@@ -34,10 +34,8 @@ func main() {
|
||||
// Serve static files for frontend
|
||||
r.Static("/assets", "./dist/assets")
|
||||
r.StaticFile("/favicon.ico", "./dist/favicon.ico")
|
||||
r.StaticFile("/favicon.svg", "./dist/favicon.svg")
|
||||
r.StaticFile("/", "./dist/index.html")
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.File("./dist/index.html")
|
||||
})
|
||||
|
||||
// API routes
|
||||
api := r.Group("/api")
|
||||
@@ -58,6 +56,10 @@ func main() {
|
||||
api.GET("/stats", certHandler.Stats)
|
||||
}
|
||||
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.File("./dist/index.html")
|
||||
})
|
||||
|
||||
// Setup cron for auto-renewal (runs daily at 3:00 AM)
|
||||
c := cron.New()
|
||||
c.AddFunc("0 3 * * *", func() {
|
||||
@@ -80,20 +82,14 @@ func main() {
|
||||
})
|
||||
c.Start()
|
||||
|
||||
// Setup HTTP server for ACME HTTP-01 challenges (port 80)
|
||||
httpPort := os.Getenv("HTTP_PORT")
|
||||
if httpPort == "" {
|
||||
httpPort = "80"
|
||||
// Set ACME HTTP-01 challenge port from env (default 8082)
|
||||
// Nginx on port 80 should proxy .well-known/acme-challenge/ to this port
|
||||
acmePort := os.Getenv("ACME_PORT")
|
||||
if acmePort == "" {
|
||||
acmePort = "8082"
|
||||
}
|
||||
go func() {
|
||||
acme := gin.New()
|
||||
acme.Use(gin.Recovery())
|
||||
// HTTP-01 challenge handler from lego
|
||||
log.Printf("ACME HTTP challenge server listening on :%s", httpPort)
|
||||
if err := acme.Run(":" + httpPort); err != nil {
|
||||
log.Printf("ACME HTTP server (port %s) exited: %v", httpPort, err)
|
||||
}
|
||||
}()
|
||||
services.SetHTTP01Port(acmePort)
|
||||
log.Printf("ACME HTTP-01 challenge port: %s (nginx should proxy .well-known/acme-challenge/ to this port)", acmePort)
|
||||
|
||||
log.Printf("AutoSSL server starting on :%s", cfg.Port)
|
||||
if err := r.Run(":" + cfg.Port); err != nil {
|
||||
|
||||
@@ -28,6 +28,14 @@ import (
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
// http01Port is the port for HTTP-01 challenge server
|
||||
// Nginx on port 80 should proxy .well-known/acme-challenge/ requests to this port
|
||||
var http01Port = "8082"
|
||||
|
||||
func SetHTTP01Port(port string) {
|
||||
http01Port = port
|
||||
}
|
||||
|
||||
type ACMEAccount struct {
|
||||
Email string
|
||||
PrivateKey crypto.PrivateKey
|
||||
@@ -79,7 +87,9 @@ func GetACMECertificate(cert *config.Certificate, cfg *config.Config) error {
|
||||
return fmt.Errorf("failed to set DNS-01 provider: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80")); err != nil {
|
||||
// Use HTTP-01 challenge with the configured port
|
||||
// Nginx on port 80 proxies .well-known/acme-challenge/ to this port
|
||||
if err := client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", http01Port)); err != nil {
|
||||
return fmt.Errorf("failed to set HTTP-01 provider: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -145,22 +155,33 @@ func RenewCertificate(cert *config.Certificate, cfg *config.Config) error {
|
||||
return fmt.Errorf("failed to set DNS-01 provider: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "80")); err != nil {
|
||||
// Use HTTP-01 challenge with the configured port
|
||||
if err := client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", http01Port)); err != nil {
|
||||
return fmt.Errorf("failed to set HTTP-01 provider: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load existing certificate files for renewal
|
||||
certDir := filepath.Join(cfg.CertDir, sanitizeDomain(cert.Domain))
|
||||
existingCert, err := os.ReadFile(filepath.Join(certDir, "fullchain.pem"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read existing certificate for renewal: %v", err)
|
||||
}
|
||||
existingKey, err := os.ReadFile(filepath.Join(certDir, "privkey.pem"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read existing private key for renewal: %v", err)
|
||||
}
|
||||
|
||||
certRes, err := client.Certificate.Renew(certificate.Resource{
|
||||
Domain: cert.Domain,
|
||||
CertURL: cert.CertURL,
|
||||
PrivateKey: nil,
|
||||
Certificate: nil,
|
||||
PrivateKey: existingKey,
|
||||
Certificate: existingCert,
|
||||
}, true, false, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to renew certificate: %v", err)
|
||||
}
|
||||
|
||||
certDir := filepath.Join(cfg.CertDir, sanitizeDomain(cert.Domain))
|
||||
os.MkdirAll(certDir, 0700)
|
||||
os.WriteFile(filepath.Join(certDir, "fullchain.pem"), certRes.Certificate, 0644)
|
||||
os.WriteFile(filepath.Join(certDir, "privkey.pem"), certRes.PrivateKey, 0600)
|
||||
@@ -186,6 +207,34 @@ func GetCertFilesPaths(domain string, cfg *config.Config) (fullchain, privkey, c
|
||||
filepath.Join(dir, "chain.pem")
|
||||
}
|
||||
|
||||
// GetCertFileContents reads and returns certificate file contents
|
||||
func GetCertFileContents(domain string, cfg *config.Config) (fullchain, privkey, chain string) {
|
||||
fc, pk, ch := GetCertFilesPaths(domain, cfg)
|
||||
|
||||
data, err := os.ReadFile(fc)
|
||||
if err != nil {
|
||||
fullchain = ""
|
||||
} else {
|
||||
fullchain = string(data)
|
||||
}
|
||||
|
||||
data, err = os.ReadFile(pk)
|
||||
if err != nil {
|
||||
privkey = ""
|
||||
} else {
|
||||
privkey = string(data)
|
||||
}
|
||||
|
||||
data, err = os.ReadFile(ch)
|
||||
if err != nil {
|
||||
chain = ""
|
||||
} else {
|
||||
chain = string(data)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getOrCreateAccount(email, provider, dir string) (*ACMEAccount, error) {
|
||||
keyFile := filepath.Join(dir, "account.key")
|
||||
regFile := filepath.Join(dir, "registration.json")
|
||||
@@ -293,7 +342,7 @@ func getDNSProvider(cert *config.Certificate) (challenge.Provider, error) {
|
||||
return provider, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported DNS provider: %s", dnsCfg.Provider)
|
||||
return nil, fmt.Errorf("unsupported DNS provider: %s", cert.DNSProvider)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +367,6 @@ func sanitizeDomain(domain string) string {
|
||||
}
|
||||
|
||||
// lego User interface implementation
|
||||
func (a *ACMEAccount) GetEmail() string { return a.Email }
|
||||
func (a *ACMEAccount) GetEmail() string { return a.Email }
|
||||
func (a *ACMEAccount) GetRegistration() *registration.Resource { return a.Registration }
|
||||
func (a *ACMEAccount) GetPrivateKey() crypto.PrivateKey { return a.PrivateKey }
|
||||
func (a *ACMEAccount) GetPrivateKey() crypto.PrivateKey { return a.PrivateKey }
|
||||
|
||||
Reference in New Issue
Block a user