package main import ( "errors" "fmt" "log" "os" "path/filepath" "strings" "sync" "git.nevets.tech/Steven/ezconf" "gopkg.in/ini.v1" ) var domainConfigs map[string]*ezconf.Configuration var mu sync.RWMutex var ( BlankConfigEntry = errors.New("blank config entry") ConfigNotFound = errors.New("config file not found") ) func makeDirs() { err := os.MkdirAll("/etc/certman", 0644) if err != nil { if !os.IsExist(err) { fmt.Println("Unable to create config directory") os.Exit(1) } } err = os.Mkdir("/etc/certman/domains", 0644) if err != nil { if !os.IsExist(err) { fmt.Println("Unable to create config directory") os.Exit(1) } } err = os.Mkdir("/var/local/certman", 0640) if err != nil { if !os.IsExist(err) { fmt.Printf("Unable to create certman directory: %v\n", err) os.Exit(1) } } } func createNewDomainConfig(domain string) { key, err := GenerateKey() if err != nil { log.Fatalf("Unable to generate key: %v\n", err) } data := []byte(strings.ReplaceAll(strings.ReplaceAll(defaultDomainConfig, "{domain}", domain), "{key}", key)) createFile("/etc/certman/domains/"+domain+".conf", 0640, data) } func createNewDomainCertsDir(domain string, dir string) { var err error if dir == "/opt/certs/example.com" { err = os.MkdirAll("/var/local/certman/certificates/"+domain, 0640) } else { if strings.HasSuffix(dir, "/") { err = os.MkdirAll(dir+domain, 0640) } else { err = os.Mkdir(dir+"/"+domain, 0640) } } if err != nil { if os.IsExist(err) { fmt.Println("Directory already exists...") } else { log.Fatalf("Error creating certificate directory for %s: %v\n", domain, err) } } } func loadDomainConfigs() error { tempDomainConfigs := make(map[string]*ezconf.Configuration) entries, err := os.ReadDir("/etc/certman/domains/") if err != nil { return err } for _, entry := range entries { if entry.IsDir() || filepath.Ext(entry.Name()) != ".conf" { continue } domainConf, err := ezconf.LoadConfiguration("/etc/certman/domains/" + entry.Name()) if err != nil { return err } domain, err := domainConf.GetAsStringErr("Domain.domain_name") if err != nil { return err } if _, exists := tempDomainConfigs[domain]; exists { fmt.Printf("Duplicate domain found in %s, skipping...\n", "/etc/certman/domains/"+entry.Name()) continue } tempDomainConfigs[domain] = domainConf } mu.Lock() domainConfigs = tempDomainConfigs mu.Unlock() return nil } func saveDomainConfigs() { mu.RLock() localDomainConfigs := make(map[string]*ezconf.Configuration, len(domainConfigs)) for k, v := range domainConfigs { localDomainConfigs[k] = v } mu.RUnlock() for domainStr, domainConfig := range localDomainConfigs { err := domainConfig.Save() if err != nil { fmt.Printf("Error saving domain config %s: %v\n", domainStr, err) continue } } } func getDomainConfig(domain string) (*ezconf.Configuration, bool) { mu.RLock() defer mu.RUnlock() funcDomainConfig, exists := domainConfigs[domain] return funcDomainConfig, exists } func getEffectiveKey(domainConfig *ezconf.Configuration, path string) *ini.Key { key, err := getEffectiveKeyErr(domainConfig, path) if err != nil { if errors.Is(err, BlankConfigEntry) { return &ini.Key{} } fmt.Printf("Error getting value for %s: %v\n", path, err) return nil } return key } func getEffectiveKeyErr(domainConfig *ezconf.Configuration, path string) (*ini.Key, error) { if domainConfig != nil { if key, err := domainConfig.GetKey(path); err == nil && key != nil { if strings.TrimSpace(key.String()) != "" { return key, nil } } } key, err := config.GetKey(path) if err != nil { fmt.Printf("Error getting key for %s: %v\n", path, err) return nil, err } if strings.TrimSpace(key.String()) == "" { return nil, BlankConfigEntry } return key, nil } func getEffectiveString(domainCfg *ezconf.Configuration, path string) (string, error) { k, err := getEffectiveKeyErr(domainCfg, path) if err != nil { return "", err } return strings.TrimSpace(k.String()), nil } const defaultConfig = `[App] mode = {mode} [Git] host = gitea server = https://gitea.instance.com username = user api_token = xxxxxxxxxxxxxxxxxxxxxxxxx org_name = org [Certificates] email = user@example.com data_root = /var/local/certman ca_dir_url = https://acme-v02.api.letsencrypt.org/directory [Cloudflare] cf_email = email@example.com cf_api_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxx ` const defaultDomainConfig = `[Domain] domain_name = {domain} enabled = true ; default (use system dns) or IPv4 Address (1.1.1.1) dns_server = default [Certificates] data_root = expiry = 90 request_method = dns-01 renew_period = 30 subdomains = cert_symlink = key_symlink = crypto_key = {key} [Repo] repo_suffix = -certificates ; Don't change setting below here unless you know what you're doing! [Internal] last_issued = 0 repo_exists = false status = clean `