Refactored config system again
All checks were successful
Build (artifact) / build (push) Successful in 27s

This commit is contained in:
2026-06-30 16:48:05 -04:00
parent c01195643a
commit 0a78b70821
18 changed files with 478 additions and 175 deletions

View File

@@ -19,6 +19,7 @@ import (
"git.nevets.tech/Steven/certman/common"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/registration"
@@ -58,6 +59,11 @@ type ACMEManager struct {
Client *lego.Client
User *fileUser
// cached DNS-01 provider so we can re-bind it with per-domain recursive nameservers
cfProvider *cloudflare.DNSProvider
// snapshot of /etc/resolv.conf nameservers taken at manager init
defaultNameservers []string
// root dirs
dataRoot string // e.g. /var/local/certman
accountRoot string // e.g. /var/local/certman/accounts
@@ -143,7 +149,10 @@ func NewACMEManager(config *common.AppConfig) (*ACMEManager, error) {
return nil, fmt.Errorf("cloudflare dns provider: %w", err)
}
if err = client.Challenge.SetDNS01Provider(cfProvider); err != nil {
mgr.cfProvider = cfProvider
mgr.defaultNameservers = dns01.ParseNameservers(readResolvConfNameservers("/etc/resolv.conf"))
if err = client.Challenge.SetDNS01Provider(cfProvider, dns01.AddRecursiveNameservers(mgr.defaultNameservers)); err != nil {
return nil, fmt.Errorf("set dns-01 provider: %w", err)
}
@@ -168,7 +177,9 @@ func NewACMEManager(config *common.AppConfig) (*ACMEManager, error) {
}
// ObtainForDomain obtains a new cert for a configured domain and saves it to disk.
func (m *ACMEManager) ObtainForDomain(domainKey string, config *common.AppConfig, domainConfig *common.DomainConfig) (*certificate.Resource, error) {
func (m *ACMEManager) ObtainForDomain(config *common.AppConfig, domainConfig *common.ServerDomainConfig) (*certificate.Resource, error) {
domainKey := domainConfig.Domain.DomainName
rcfg, err := buildDomainRuntimeConfig(config, domainConfig)
if err != nil {
return nil, err
@@ -187,6 +198,10 @@ func (m *ACMEManager) ObtainForDomain(domainKey string, config *common.AppConfig
m.MU.Lock()
defer m.MU.Unlock()
if err := m.applyDNS01ForDomain(domainConfig); err != nil {
return nil, fmt.Errorf("apply dns-01 nameservers for %q: %w", domainKey, err)
}
res, err := m.Client.Certificate.Obtain(req)
if err != nil {
return nil, fmt.Errorf("obtain %q: %w", domainKey, err)
@@ -200,7 +215,9 @@ func (m *ACMEManager) ObtainForDomain(domainKey string, config *common.AppConfig
}
// RenewForDomain renews an existing stored cert for a domain key.
func (m *ACMEManager) RenewForDomain(domainKey string) (*certificate.Resource, error) {
func (m *ACMEManager) RenewForDomain(domainConfig *common.ServerDomainConfig) (*certificate.Resource, error) {
domainKey := domainConfig.Domain.DomainName
m.MU.Lock()
defer m.MU.Unlock()
@@ -209,6 +226,10 @@ func (m *ACMEManager) RenewForDomain(domainKey string) (*certificate.Resource, e
return nil, fmt.Errorf("load stored resource for %q: %w", domainKey, err)
}
if err := m.applyDNS01ForDomain(domainConfig); err != nil {
return nil, fmt.Errorf("apply dns-01 nameservers for %q: %w", domainKey, err)
}
// RenewWithOptions is preferred in newer lego versions.
renewed, err := m.Client.Certificate.RenewWithOptions(*existing, &certificate.RenewOptions{
Bundle: true,
@@ -232,17 +253,55 @@ func (m *ACMEManager) GetCertPaths(domainKey string) (certPEM, keyPEM string) {
filepath.Join(dir, base+".key")
}
// applyDNS01ForDomain re-binds the lego DNS-01 provider with recursive nameservers
// appropriate for this domain. When ServerDomainCfg.DNSServer is empty or "default", the
// resolv.conf snapshot taken at NewACMEManager time is used; otherwise the configured
// server is used. Caller must hold m.MU.
func (m *ACMEManager) applyDNS01ForDomain(domainConfig *common.ServerDomainConfig) error {
ns := m.defaultNameservers
cfgDNS := strings.TrimSpace(domainConfig.Domain.DNSServer)
if cfgDNS != "" && !strings.EqualFold(cfgDNS, "default") {
ns = dns01.ParseNameservers([]string{cfgDNS})
}
return m.Client.Challenge.SetDNS01Provider(m.cfProvider, dns01.AddRecursiveNameservers(ns))
}
// readResolvConfNameservers parses `nameserver` lines from a resolv.conf-style file.
// Returns nil on any error so the caller falls through to lego's own defaults.
func readResolvConfNameservers(path string) []string {
data, err := os.ReadFile(path)
if err != nil {
return nil
}
var servers []string
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
continue
}
rest, ok := strings.CutPrefix(line, "nameserver")
if !ok {
continue
}
ns := strings.TrimSpace(rest)
if ns != "" {
servers = append(servers, ns)
}
}
return servers
}
// ---------------------------------------------
// Domain runtime config assembly
// ---------------------------------------------
func buildDomainRuntimeConfig(config *common.AppConfig, domainConfig *common.DomainConfig) (*DomainRuntimeConfig, error) {
func buildDomainRuntimeConfig(config *common.AppConfig, domainConfig *common.ServerDomainConfig) (*DomainRuntimeConfig, error) {
domainName := domainConfig.Domain.DomainName
email := config.Certificates.Email
// domain override data_root can be blank -> main fallback
dataRoot := common.EffectiveDataRoot(config, domainConfig)
dataRoot := common.EffectiveDataRoot(config, domainConfig.Certificates.DataRoot)
caDirURL := config.Certificates.CADirURL