package config import ( "encoding/json" "log" "os" "sync" "time" ) type Config struct { Port string CertDir string AccountsDir string DataDir string } func Load() *Config { port := os.Getenv("PORT") if port == "" { port = "8080" } certDir := os.Getenv("CERT_DIR") if certDir == "" { certDir = "./data/certs" } accountsDir := os.Getenv("ACCOUNTS_DIR") if accountsDir == "" { accountsDir = "./data/accounts" } dataDir := os.Getenv("DATA_DIR") if dataDir == "" { dataDir = "./data" } return &Config{ Port: port, CertDir: certDir, AccountsDir: accountsDir, DataDir: dataDir, } } // 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{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) } } Store = &CertStore{ data: make(map[string]*Certificate), path: cfg.DataDir + "/certs.json", } if err := Store.Load(); err != nil { log.Printf("No existing cert store or failed to load: %v, starting fresh", err) } 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 }