refactor: replace SQLite/GORM with JSON file storage
- Remove GORM and SQLite, use simple JSON file (data/certs.json) for persistence - CertStore with sync.RWMutex for thread-safe in-memory cache - All handlers updated to use config.Store instead of config.DB - Cron job and stats updated accordingly - go mod tidy removes unused gorm/sqlite dependencies
This commit is contained in:
@@ -1,33 +1,25 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
import "time"
|
||||
|
||||
type Certificate struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
ID uint `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
Domain string `json:"domain" gorm:"uniqueIndex;size:255"`
|
||||
Email string `json:"email" gorm:"size:255"`
|
||||
Provider string `json:"provider" gorm:"size:50;default:letsencrypt"` // letsencrypt, zerossl
|
||||
ChallengeType string `json:"challenge_type" gorm:"size:20;default:http"` // http, dns
|
||||
DNSProvider string `json:"dns_provider,omitempty" gorm:"size:50"` // alidns, cloudflare, etc.
|
||||
DNSConfig string `json:"dns_config,omitempty" gorm:"type:text"` // JSON config for DNS provider
|
||||
Domain string `json:"domain"`
|
||||
Email string `json:"email"`
|
||||
Provider string `json:"provider"` // letsencrypt, zerossl
|
||||
ChallengeType string `json:"challenge_type"` // http, dns
|
||||
DNSProvider string `json:"dns_provider,omitempty"` // alidns, cloudflare, etc.
|
||||
DNSConfig string `json:"dns_config,omitempty"` // JSON config for DNS provider
|
||||
|
||||
Status string `json:"status" gorm:"size:20;default:pending"` // pending, active, expired, error
|
||||
CertURL string `json:"cert_url,omitempty" gorm:"size:512"`
|
||||
Status string `json:"status"` // pending, active, expired, error
|
||||
CertURL string `json:"cert_url,omitempty"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
LastRenewedAt *time.Time `json:"last_renewed_at,omitempty"`
|
||||
ErrorMessage string `json:"error_message,omitempty" gorm:"type:text"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
|
||||
// Auto renew settings
|
||||
AutoRenew bool `json:"auto_renew" gorm:"default:true"`
|
||||
RenewDays int `json:"renew_days" gorm:"default:30"` // Renew when expires within this many days
|
||||
|
||||
// ACME account key
|
||||
AccountKeyID uint `json:"account_key_id,omitempty"`
|
||||
AutoRenew bool `json:"auto_renew"`
|
||||
RenewDays int `json:"renew_days"` // Renew when expires within this many days
|
||||
}
|
||||
|
||||
+156
-23
@@ -1,20 +1,18 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
type Config struct {
|
||||
Port string
|
||||
DBPath string
|
||||
CertDir string
|
||||
AccountsDir string
|
||||
DataDir string
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
@@ -22,10 +20,6 @@ func Load() *Config {
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
dbPath := os.Getenv("DB_PATH")
|
||||
if dbPath == "" {
|
||||
dbPath = "./data/autossl.db"
|
||||
}
|
||||
certDir := os.Getenv("CERT_DIR")
|
||||
if certDir == "" {
|
||||
certDir = "./data/certs"
|
||||
@@ -34,36 +28,175 @@ func Load() *Config {
|
||||
if accountsDir == "" {
|
||||
accountsDir = "./data/accounts"
|
||||
}
|
||||
dataDir := os.Getenv("DATA_DIR")
|
||||
if dataDir == "" {
|
||||
dataDir = "./data"
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Port: port,
|
||||
DBPath: dbPath,
|
||||
CertDir: certDir,
|
||||
AccountsDir: accountsDir,
|
||||
DataDir: dataDir,
|
||||
}
|
||||
}
|
||||
|
||||
func InitDB(cfg *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
|
||||
nextID uint
|
||||
}
|
||||
|
||||
var Store *CertStore
|
||||
|
||||
func InitStore(cfg *Config) {
|
||||
// Ensure data directories exist
|
||||
dirs := []string{"./data", cfg.CertDir, cfg.AccountsDir}
|
||||
dirs := []string{cfg.DataDir, cfg.CertDir, cfg.AccountsDir}
|
||||
for _, d := range dirs {
|
||||
if err := os.MkdirAll(d, 0700); err != nil {
|
||||
log.Fatalf("Failed to create directory %s: %v", d, err)
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
DB, err = gorm.Open(sqlite.Open(cfg.DBPath), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Warn),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect database: %v", err)
|
||||
Store = &CertStore{
|
||||
data: make(map[string]*Certificate),
|
||||
path: cfg.DataDir + "/certs.json",
|
||||
}
|
||||
|
||||
// Auto migrate
|
||||
if err := DB.AutoMigrate(&Certificate{}); err != nil {
|
||||
log.Fatalf("Failed to migrate database: %v", err)
|
||||
if err := Store.Load(); err != nil {
|
||||
log.Printf("No existing cert store or failed to load: %v, starting fresh", err)
|
||||
}
|
||||
|
||||
log.Println("Database initialized successfully")
|
||||
log.Println("Cert store initialized successfully")
|
||||
}
|
||||
|
||||
// Load reads certificates from JSON file into memory
|
||||
func (s *CertStore) Load() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
data, err := os.ReadFile(s.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var certs []*Certificate
|
||||
if err := json.Unmarshal(data, &certs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.data = make(map[string]*Certificate)
|
||||
s.nextID = 0
|
||||
for _, c := range certs {
|
||||
s.data[c.Domain] = c
|
||||
if c.ID >= s.nextID {
|
||||
s.nextID = c.ID + 1
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save writes the current in-memory certificates to JSON file
|
||||
func (s *CertStore) Save() error {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
certs := make([]*Certificate, 0, len(s.data))
|
||||
for _, c := range s.data {
|
||||
certs = append(certs, c)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(certs, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(s.path, data, 0600)
|
||||
}
|
||||
|
||||
// GetAll returns all certificates sorted by ID descending
|
||||
func (s *CertStore) GetAll() []*Certificate {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
certs := make([]*Certificate, 0, len(s.data))
|
||||
for _, c := range s.data {
|
||||
certs = append(certs, c)
|
||||
}
|
||||
|
||||
// Sort by ID descending (newest first)
|
||||
for i := 0; i < len(certs)-1; i++ {
|
||||
for j := i + 1; j < len(certs); j++ {
|
||||
if certs[i].ID < certs[j].ID {
|
||||
certs[i], certs[j] = certs[j], certs[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
// GetByDomain returns a certificate by domain
|
||||
func (s *CertStore) GetByDomain(domain string) *Certificate {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.data[domain]
|
||||
}
|
||||
|
||||
// GetByID returns a certificate by ID
|
||||
func (s *CertStore) GetByID(id uint) *Certificate {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
for _, c := range s.data {
|
||||
if c.ID == id {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert inserts or updates a certificate
|
||||
func (s *CertStore) Upsert(cert *Certificate) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if cert.ID == 0 {
|
||||
cert.ID = s.nextID
|
||||
s.nextID++
|
||||
cert.CreatedAt = time.Now()
|
||||
}
|
||||
cert.UpdatedAt = time.Now()
|
||||
|
||||
s.data[cert.Domain] = cert
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
// Delete removes a certificate by ID
|
||||
func (s *CertStore) Delete(id uint) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for domain, c := range s.data {
|
||||
if c.ID == id {
|
||||
delete(s.data, domain)
|
||||
return s.Save()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetActiveWithAutoRenew returns all active certs with auto-renewal enabled
|
||||
func (s *CertStore) GetActiveWithAutoRenew() []*Certificate {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
var result []*Certificate
|
||||
for _, c := range s.data {
|
||||
if c.AutoRenew && c.Status == "active" {
|
||||
result = append(result, c)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user