Migrated to cobra for command handling and viper for config handling

This commit is contained in:
2026-02-25 21:17:23 +01:00
parent 61b65bf81c
commit 9eeb7a6ec0
13 changed files with 646 additions and 580 deletions

1
.gitignore vendored
View File

@@ -128,3 +128,4 @@ $RECYCLE.BIN/
config.ini
certman
certman-*-amd64

View File

@@ -1,5 +1,13 @@
VERSION := 1.0.0
GO := go
BUILD_FLAGS := -buildmode=pie -trimpath
LDFLAGS := -linkmode=external -extldflags="-Wl,-z,relro,-z,now"
build:
@go build -o ./certman .
$(GO) build $(BUILD_FLAGS) -ldflags='$(LDFLAGS)' -o ./certman .
@cp ./certman ./certman-$(VERSION)-amd64
stage: build
@sudo cp ./certman /srv/vm-passthru/certman

View File

@@ -96,18 +96,9 @@ type StoredCertMeta struct {
// - Cloudflare DNS-01 only
func NewACMEManager() (*ACMEManager, error) {
// Pull effective (main-only) certificate settings.
email, err := config.GetAsStringErr("Certificates.email")
if err != nil {
return nil, err
}
dataRoot, err := config.GetAsStringErr("Certificates.data_root")
if err != nil {
return nil, err
}
caDirURL, err := config.GetAsStringErr("Certificates.ca_dir_url")
if err != nil {
return nil, err
}
email := config.GetString("Certificates.email")
dataRoot := config.GetString("Certificates.data_root")
caDirURL := config.GetString("Certificates.ca_dir_url")
// Build manager paths
mgr := &ACMEManager{
@@ -246,51 +237,31 @@ func (m *ACMEManager) GetCertPaths(domainKey string) (certPEM, keyPEM string) {
// ---------------------------------------------
func buildDomainRuntimeConfig(domainKey string) (*DomainRuntimeConfig, error) {
domainCfg, exists := getDomainConfig(domainKey)
domainCfg, exists := domainStore.Get(domainKey)
if !exists {
return nil, fmt.Errorf("domain config not found for %q", domainKey)
}
domainName, err := domainCfg.GetAsStringErr("Domain.domain_name")
if err != nil {
return nil, err
}
domainName := domainCfg.GetString("Domain.domain_name")
email, err := config.GetAsStringErr("Certificates.email")
if err != nil {
return nil, err
}
email := config.GetString("Certificates.email")
// domain override data_root can be blank -> main fallback
dataRoot, err := getEffectiveString(domainCfg, "Certificates.data_root")
dataRoot, err := EffectiveString(domainCfg, "Certificates.data_root")
if err != nil {
return nil, err
}
caDirURL, err := config.GetAsStringErr("Certificates.ca_dir_url")
if err != nil {
return nil, err
}
caDirURL := config.GetString("Certificates.ca_dir_url")
expiry, err := domainCfg.GetAsIntErr("Certificates.expiry")
if err != nil {
return nil, err
}
expiry := domainCfg.GetInt("Certificates.expiry")
renewPeriod, err := domainCfg.GetAsIntErr("Certificates.renew_period")
if err != nil {
return nil, err
}
renewPeriod := domainCfg.GetInt("Certificates.renew_period")
requestMethod := ""
if k, err := domainCfg.GetKey("Certificates.request_method"); err == nil && k != nil {
requestMethod = strings.TrimSpace(k.String())
}
requestMethod := domainCfg.GetString("Certificates.request_method")
subdomains := []string{}
if k, err := domainCfg.GetKey("Certificates.subdomains"); err == nil && k != nil {
subdomains = parseCSVLines(k.String())
}
subdomains := domainCfg.GetString("Certificates.subdomains")
subdomainArray := parseCSVLines(subdomains)
return &DomainRuntimeConfig{
DomainName: domainName,
@@ -300,7 +271,7 @@ func buildDomainRuntimeConfig(domainKey string) (*DomainRuntimeConfig, error) {
ExpiryDays: expiry,
RenewPeriod: renewPeriod,
RequestMethod: requestMethod,
Subdomains: subdomains,
Subdomains: subdomainArray,
}, nil
}
@@ -377,14 +348,8 @@ func buildDomainList(baseDomain string, subs []string) []string {
func setCloudflareEnvFromMainConfig() (restore func(), err error) {
// Your current defaults show legacy email + API key fields.
// Prefer API tokens in the future if you add them. Cloudflare provider supports both styles. :contentReference[oaicite:3]{index=3}
cfEmail, err := config.GetAsStringErr("Cloudflare.cf_email")
if err != nil {
return nil, err
}
cfAPIKey, err := config.GetAsStringErr("Cloudflare.cf_api_key")
if err != nil {
return nil, err
}
cfEmail := config.GetString("Cloudflare.cf_email")
cfAPIKey := config.GetString("Cloudflare.cf_api_key")
// Save prior env values so we can restore them after provider creation.
prevEmail, hadEmail := os.LookupEnv("CLOUDFLARE_EMAIL")

View File

@@ -7,45 +7,41 @@ import (
"path/filepath"
"strings"
"git.nevets.tech/Steven/ezconf"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5/storage/memory"
)
func initClient() {
err := loadDomainConfigs()
err := LoadDomainConfigs()
if err != nil {
log.Fatalf("Error loading domain configs: %v", err)
}
clientTick()
}
func clientTick() {
fmt.Println("Tick!")
// Get local copy of domain configs
mu.RLock()
localDomainConfigs := make(map[string]*ezconf.Configuration, len(domainConfigs))
for k, v := range domainConfigs {
localDomainConfigs[k] = v
}
mu.RUnlock()
localDomainConfigs := domainStore.Snapshot()
// Loop over all domain configs (domains)
for domainStr, domainConfig := range localDomainConfigs {
// Skip non-enabled domains
if !domainConfig.GetAsBoolean("Domain.enabled") {
if !domainConfig.GetBool("Domain.enabled") {
continue
}
// Skip domains with up-to-date commit hashes
// If the repo doesn't exist, we can't check for a remote commit, so stop the rest of the check
repoExists := domainConfig.GetAsBoolean("Internal.repo_exists")
repoExists := domainConfig.GetBool("Internal.repo_exists")
if repoExists {
localHash, err := getLocalCommitHash(domainStr)
if err != nil {
fmt.Printf("No local commit hash found for domain %s\n", domainStr)
}
gitSource, err := strToGitSource(config.GetAsString("Git.host"))
gitSource, err := strToGitSource(config.GetString("Git.host"))
if err != nil {
fmt.Printf("Error getting git source for domain %s: %v\n", domainStr, err)
continue
@@ -68,7 +64,7 @@ func clientTick() {
}
// Ex: https://git.example.com/Org/Repo-suffix.git
// Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?)
repoUrl := config.GetAsString("Git.server") + "/" + config.GetAsString("Git.org_name") + "/" + domainStr + domainConfig.GetAsString("Repo.repo_suffix") + ".git"
repoUrl := config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domainStr + domainConfig.GetString("Repo.repo_suffix") + ".git"
err := cloneRepo(repoUrl, gitWorkspace)
if err != nil {
fmt.Printf("Error cloning domain repo %s: %v\n", domainStr, err)
@@ -108,7 +104,7 @@ func clientTick() {
continue
}
err = DecryptFileFromBytes(domainConfig.GetAsString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil)
err = DecryptFileFromBytes(domainConfig.GetString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil)
if err != nil {
fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domainStr, err)
continue
@@ -133,7 +129,7 @@ func clientTick() {
func reloadClient() {
fmt.Println("Reloading configs...")
err := loadDomainConfigs()
err := LoadDomainConfigs()
if err != nil {
fmt.Printf("Error loading domain configs: %v\n", err)
return

183
commands.go Normal file
View File

@@ -0,0 +1,183 @@
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/spf13/cobra"
)
func devCmd(cmd *cobra.Command, args []string) {
testDomain := "lunamc.org"
//config, err = ezconf.LoadConfiguration("/etc/certman/certman.conf")
err := LoadConfig("/etc/certman/certman.conf")
if err != nil {
log.Fatalf("Error loading configuration: %v\n", err)
}
err = LoadDomainConfigs()
if err != nil {
log.Fatalf("Error loading configs: %v\n", err)
}
fmt.Println(testDomain)
}
func versionCmd(cmd *cobra.Command, args []string) {
fmt.Println("CertManager (certman) - Steven Tracey\nVersion: " + version + " build-" + build)
}
func newKeyCmd(cmd *cobra.Command, args []string) {
key, err := GenerateKey()
if err != nil {
log.Fatalf("%v", err)
}
fmt.Printf(key)
}
func newDomain(domain, domainDir string, dirOverridden bool) error {
//TODO add config option for "overriden dir"
fmt.Printf("Creating new domain %s\n", domain)
err := createNewDomainConfig(domain)
if err != nil {
return err
}
createNewDomainCertsDir(domain, domainDir, dirOverridden)
fmt.Println("Successfully created domain entry for " + domain + "\nUpdate config file as needed in /etc/certman/domains/" + domainDir + ".conf")
return nil
}
func install(isThin bool, mode string) error {
if !isThin {
makeDirs()
}
//config, err = ezconf.NewConfiguration(configFile, strings.ReplaceAll(defaultConfig, "{mode}", mode))
createNewConfig(mode)
return nil
}
func runDaemonCmd() error {
err := createOrUpdatePIDFile("/var/run/certman.pid")
if err != nil {
if errors.Is(err, ErrorPIDInUse) {
return fmt.Errorf("daemon process is already running")
}
return fmt.Errorf("error creating pidfile: %v", err)
}
ctx, cancel = context.WithCancel(context.Background())
// Check if main config exists
if _, err := os.Stat(configFile); os.IsNotExist(err) {
return fmt.Errorf("main config file not found, please run 'certman --install', then properly configure /etc/certman/certman.conf")
} else if err != nil {
return fmt.Errorf("error opening %s: %v", configFile, err)
}
err = LoadConfig(configFile)
if err != nil {
return fmt.Errorf("error loading configuration: %v", err)
}
// Setup SIGINT and SIGTERM listeners
sigChannel := make(chan os.Signal, 1)
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigChannel)
reloadSigChan := make(chan os.Signal, 1)
signal.Notify(reloadSigChan, syscall.SIGHUP)
defer signal.Stop(reloadSigChan)
tickRate := config.GetInt("App.tick_rate")
ticker := time.NewTicker(time.Duration(tickRate) * time.Hour)
defer ticker.Stop()
wg.Add(1)
if config.GetString("App.mode") == "server" {
fmt.Println("Starting CertManager in server mode...")
// Server Task loop
go func() {
initServer()
defer wg.Done()
for {
select {
case <-ctx.Done():
stopServer()
return
case <-reloadSigChan:
reloadServer()
case <-ticker.C:
serverTick()
}
}
}()
} else if config.GetString("App.mode") == "client" {
fmt.Println("Starting CertManager in client mode...")
// Client Task loop
go func() {
initClient()
defer wg.Done()
for {
select {
case <-ctx.Done():
stopClient()
return
case <-reloadSigChan:
reloadClient()
case <-ticker.C:
clientTick()
}
}
}()
} else {
return fmt.Errorf("invalid operating mode \"" + config.GetString("App.mode") + "\"")
}
// Cleanup on stop
sig := <-sigChannel
fmt.Printf("Program terminated with %v\n", sig.String())
stop()
wg.Wait()
return nil
}
func stop() {
cancel()
clearPIDFile()
}
func stopDaemonCmd() error {
proc, err := getDaemonProcess()
if err != nil {
return fmt.Errorf("error getting daemon process: %v", err)
}
err = proc.Signal(syscall.SIGTERM)
if err != nil {
return fmt.Errorf("error sending SIGTERM to daemon PID: %v", err)
}
return nil
}
func reloadDaemonCmd() error {
proc, err := getDaemonProcess()
if err != nil {
return fmt.Errorf("error getting daemon process: %v", err)
}
err = proc.Signal(syscall.SIGHUP)
if err != nil {
return fmt.Errorf("error sending SIGHUP to daemon PID: %v", err)
}
return nil
}
func statusDaemonCmd() error {
fmt.Println("Not implemented :/")
return nil
}

325
config.go
View File

@@ -9,173 +9,258 @@ import (
"strings"
"sync"
"git.nevets.tech/Steven/ezconf"
"gopkg.in/ini.v1"
"github.com/spf13/viper"
)
var domainConfigs map[string]*ezconf.Configuration
var mu sync.RWMutex
var (
BlankConfigEntry = errors.New("blank config entry")
ConfigNotFound = errors.New("config file not found")
ErrBlankConfigEntry = errors.New("blank config entry")
ErrConfigNotFound = 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)
}
}
type DomainConfigStore struct {
mu sync.RWMutex
configs map[string]*viper.Viper
}
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 NewDomainConfigStore() *DomainConfigStore {
return &DomainConfigStore{
configs: make(map[string]*viper.Viper),
}
}
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 (s *DomainConfigStore) Get(domain string) (*viper.Viper, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
v, ok := s.configs[domain]
return v, ok
}
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 (s *DomainConfigStore) Set(domain string, v *viper.Viper) {
s.mu.Lock()
defer s.mu.Unlock()
s.configs[domain] = v
}
func loadDomainConfigs() error {
tempDomainConfigs := make(map[string]*ezconf.Configuration)
entries, err := os.ReadDir("/etc/certman/domains/")
if err != nil {
return err
// Swap atomically replaces the entire config map (used during reload).
func (s *DomainConfigStore) Swap(newConfigs map[string]*viper.Viper) {
s.mu.Lock()
defer s.mu.Unlock()
s.configs = newConfigs
}
// Snapshot returns a shallow copy safe to iterate without holding the lock.
func (s *DomainConfigStore) Snapshot() map[string]*viper.Viper {
s.mu.RLock()
defer s.mu.RUnlock()
snap := make(map[string]*viper.Viper, len(s.configs))
for k, v := range s.configs {
snap[k] = v
}
return snap
}
// ---------------------------------------------------------------------------
// Global state
// ---------------------------------------------------------------------------
var (
config *viper.Viper
domainStore = NewDomainConfigStore()
)
// ---------------------------------------------------------------------------
// Loading
// ---------------------------------------------------------------------------
// LoadConfig reads the main certman.conf into config.
func LoadConfig(path string) error {
config = viper.New()
config.SetConfigFile(path)
config.SetConfigType("ini")
return config.ReadInConfig()
}
// LoadDomainConfigs reads every .conf file in the domains directory.
func LoadDomainConfigs() error {
dir := "/etc/certman/domains/"
entries, err := os.ReadDir(dir)
if err != nil {
return fmt.Errorf("reading domain config dir: %w", err)
}
temp := make(map[string]*viper.Viper)
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
path := filepath.Join(dir, entry.Name())
v := viper.New()
v.SetConfigFile(path)
v.SetConfigType("ini")
if err := v.ReadInConfig(); err != nil {
return fmt.Errorf("loading %s: %w", path, err)
}
if _, exists := tempDomainConfigs[domain]; exists {
fmt.Printf("Duplicate domain found in %s, skipping...\n", "/etc/certman/domains/"+entry.Name())
domain := v.GetString("domain.domain_name")
if domain == "" {
return fmt.Errorf("%s: missing domain.domain_name", path)
}
if _, exists := temp[domain]; exists {
fmt.Printf("Duplicate domain in %s, skipping...\n", path)
continue
}
tempDomainConfigs[domain] = domainConf
temp[domain] = v
}
mu.Lock()
domainConfigs = tempDomainConfigs
mu.Unlock()
domainStore.Swap(temp)
return nil
}
func saveDomainConfigs() {
mu.RLock()
localDomainConfigs := make(map[string]*ezconf.Configuration, len(domainConfigs))
for k, v := range domainConfigs {
localDomainConfigs[k] = v
}
mu.RUnlock()
// ---------------------------------------------------------------------------
// Saving
// ---------------------------------------------------------------------------
for domainStr, domainConfig := range localDomainConfigs {
err := domainConfig.Save()
if err != nil {
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
continue
// SaveDomainConfigs writes every loaded domain config back to disk.
func SaveDomainConfigs() {
for domain, v := range domainStore.Snapshot() {
if err := v.WriteConfig(); err != nil {
fmt.Printf("Error saving domain config %s: %v\n", domain, err)
}
}
}
func getDomainConfig(domain string) (*ezconf.Configuration, bool) {
mu.RLock()
defer mu.RUnlock()
funcDomainConfig, exists := domainConfigs[domain]
return funcDomainConfig, exists
// ---------------------------------------------------------------------------
// Effective lookups (domain → global fallback)
// ---------------------------------------------------------------------------
// EffectiveString looks up a key in the domain config first, falling back to
// the global config. Keys use dot notation matching INI sections, e.g.
// "certificates.data_root".
func EffectiveString(domainCfg *viper.Viper, key string) (string, error) {
if domainCfg != nil {
val := strings.TrimSpace(domainCfg.GetString(key))
if val != "" {
return val, nil
}
}
if config == nil {
return "", ErrConfigNotFound
}
val := strings.TrimSpace(config.GetString(key))
if val == "" {
return "", ErrBlankConfigEntry
}
return val, nil
}
func getEffectiveKey(domainConfig *ezconf.Configuration, path string) *ini.Key {
key, err := getEffectiveKeyErr(domainConfig, path)
// MustEffectiveString is like EffectiveString but logs a fatal error on failure.
func MustEffectiveString(domainCfg *viper.Viper, key string) string {
val, err := EffectiveString(domainCfg, key)
if err != nil {
if errors.Is(err, BlankConfigEntry) {
return &ini.Key{}
log.Fatalf("Config key %q: %v", key, err)
}
fmt.Printf("Error getting value for %s: %v\n", path, err)
return val
}
// EffectiveInt returns an int with domain → global fallback. Returns the
// fallback value if the key is missing or zero in both configs.
func EffectiveInt(domainCfg *viper.Viper, key string, fallback int) int {
if domainCfg != nil {
if val := domainCfg.GetInt(key); val != 0 {
return val
}
}
if config != nil {
if val := config.GetInt(key); val != 0 {
return val
}
}
return fallback
}
// EffectiveBool returns a bool with domain → global fallback.
func EffectiveBool(domainCfg *viper.Viper, key string) bool {
if domainCfg != nil && domainCfg.IsSet(key) {
return domainCfg.GetBool(key)
}
if config != nil {
return config.GetBool(key)
}
return false
}
// ---------------------------------------------------------------------------
// Directory bootstrapping
// ---------------------------------------------------------------------------
func makeDirs() {
dirs := []struct {
path string
perm os.FileMode
}{
{"/etc/certman", 0755},
{"/etc/certman/domains", 0755},
{"/var/local/certman", 0750},
}
for _, d := range dirs {
if err := os.MkdirAll(d.path, d.perm); err != nil {
log.Fatalf("Unable to create directory %s: %v", d.path, err)
}
}
}
func createNewConfig(mode string) {
content := strings.NewReplacer("{mode}", mode).Replace(defaultConfig)
createFile("/etc/certman/certman.conf", 640, []byte(content))
}
func createNewDomainConfig(domain string) error {
key, err := GenerateKey()
if err != nil {
return fmt.Errorf("unable to generate key: %v", err)
}
content := strings.NewReplacer(
"{domain}", domain,
"{key}", key,
).Replace(defaultDomainConfig)
path := filepath.Join("/etc/certman/domains", domain+".conf")
createFile(path, 0640, []byte(content))
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
}
}
func createNewDomainCertsDir(domain string, dir string, dirOverride bool) {
var target string
if dirOverride {
target = filepath.Join(dir, domain)
} else {
target = filepath.Join("/var/local/certman/certificates", domain)
}
key, err := config.GetKey(path)
if err != nil {
fmt.Printf("Error getting key for %s: %v\n", path, err)
return nil, err
if err := os.MkdirAll(target, 0750); err != nil {
if os.IsExist(err) {
fmt.Println("Directory already exists...")
return
}
if strings.TrimSpace(key.String()) == "" {
return nil, BlankConfigEntry
log.Fatalf("Error creating certificate directory for %s: %v", domain, err)
}
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
}
// ---------------------------------------------------------------------------
// Default config templates
// ---------------------------------------------------------------------------
const defaultConfig = `[App]
mode = {mode}
tick_rate = 2
[Git]
host = gitea
@@ -197,7 +282,6 @@ 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
@@ -217,9 +301,10 @@ crypto_key = {key}
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
`
const readme = ``

View File

@@ -8,7 +8,6 @@ import (
"io"
"os"
_ "filippo.io/age"
"golang.org/x/crypto/chacha20poly1305"
)

47
git.go
View File

@@ -10,7 +10,6 @@ import (
"time"
"code.gitea.io/sdk/gitea"
"git.nevets.tech/Steven/ezconf"
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-git/v5"
gitconf "github.com/go-git/go-git/v5/config"
@@ -18,6 +17,7 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/google/go-github/v55/github"
"github.com/spf13/viper"
)
type GitWorkspace struct {
@@ -57,11 +57,11 @@ func strToGitSource(s string) (GitSource, error) {
}
func createGithubClient() *github.Client {
return github.NewClient(nil).WithAuthToken(config.GetAsString("Git.api_token"))
return github.NewClient(nil).WithAuthToken(config.GetString("Git.api_token"))
}
func createGiteaClient() *gitea.Client {
client, err := gitea.NewClient(config.GetAsString("Git.server"), gitea.SetToken(config.GetAsString("Git.api_token")))
client, err := gitea.NewClient(config.GetString("Git.server"), gitea.SetToken(config.GetString("Git.api_token")))
if err != nil {
fmt.Printf("Error connecting to gitea instance: %v\n", err)
return nil
@@ -71,7 +71,7 @@ func createGiteaClient() *gitea.Client {
func createGithubRepo(domain *Domain, client *github.Client) string {
name := domain.name
owner := domain.config.GetAsString("Repo.owner")
owner := domain.config.GetString("Repo.owner")
description := domain.description
private := true
includeAllBranches := false
@@ -84,7 +84,7 @@ func createGithubRepo(domain *Domain, client *github.Client) string {
Private: &private,
IncludeAllBranches: &includeAllBranches,
}
repo, _, err := client.Repositories.CreateFromTemplate(ctx, config.GetAsString("Git.org_name"), config.GetAsString("Git.template_name"), template)
repo, _, err := client.Repositories.CreateFromTemplate(ctx, config.GetString("Git.org_name"), config.GetString("Git.template_name"), template)
if err != nil {
fmt.Println("Error creating repository from template,", err)
return ""
@@ -93,7 +93,7 @@ func createGithubRepo(domain *Domain, client *github.Client) string {
}
func createGiteaRepo(domain string, giteaClient *gitea.Client) string {
domainConfig, exists := getDomainConfig(domain)
domainConfig, exists := domainStore.Get(domain)
if !exists {
fmt.Printf("Domain %s config does not exist\n", domain)
return ""
@@ -111,7 +111,7 @@ func createGiteaRepo(domain string, giteaClient *gitea.Client) string {
// Webhooks: true,
//}
options := gitea.CreateRepoOption{
Name: domain + domainConfig.GetAsString("Repo.repo_suffix"),
Name: domain + domainConfig.GetString("Repo.repo_suffix"),
Description: "Certificate storage for " + domain,
Private: true,
IssueLabels: "",
@@ -124,8 +124,7 @@ func createGiteaRepo(domain string, giteaClient *gitea.Client) string {
TrustModel: gitea.TrustModelDefault,
}
giteaRepo, _, err := giteaClient.CreateOrgRepo(config.GetAsString("Git.org_name"), options)
//giteaRepo, _, err := giteaClient.CreateRepoFromTemplate(config.GetAsString("Git.org_name"), config.GetAsString("Git.template_name"), options)
giteaRepo, _, err := giteaClient.CreateOrgRepo(config.GetString("Git.org_name"), options)
if err != nil {
fmt.Printf("Error creating repo: %v\n", err)
return ""
@@ -161,8 +160,8 @@ func initRepo(url string, ws *GitWorkspace) error {
func cloneRepo(url string, ws *GitWorkspace) error {
creds := &http.BasicAuth{
Username: config.GetAsString("Git.username"),
Password: config.GetAsString("Git.api_token"),
Username: config.GetString("Git.username"),
Password: config.GetString("Git.api_token"),
}
var err error
ws.Repo, err = git.Clone(ws.Storage, ws.FS, &git.CloneOptions{URL: url, Auth: creds})
@@ -179,15 +178,15 @@ func cloneRepo(url string, ws *GitWorkspace) error {
}
func addAndPushCerts(domain string, ws *GitWorkspace) error {
domainConfig, exists := getDomainConfig(domain)
domainConfig, exists := domainStore.Get(domain)
if !exists {
fmt.Printf("Domain %s config does not exist\n", domain)
return ConfigNotFound
return ErrConfigNotFound
}
certsDir, err := getDomainCertsDirWConf(domain, domainConfig)
if err != nil {
if errors.Is(err, ConfigNotFound) {
if errors.Is(err, ErrConfigNotFound) {
fmt.Printf("Domain %s config not found: %v\n", domain, err)
return err
}
@@ -244,7 +243,7 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error {
fmt.Println("Work Tree Status:\n" + status.String())
signature := &object.Signature{
Name: "Cert Manager",
Email: config.GetAsString("Certificates.email"),
Email: config.GetString("Certificates.email"),
When: time.Now(),
}
commitHash, err := ws.WorkTree.Commit("Update "+domain+" @ "+time.Now().Format("Mon Jan _2 2006 15:04:05 MST"), &git.CommitOptions{Author: signature, Committer: signature})
@@ -253,8 +252,8 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error {
return err
}
creds := &http.BasicAuth{
Username: config.GetAsString("Git.username"),
Password: config.GetAsString("Git.api_token"),
Username: config.GetString("Git.username"),
Password: config.GetString("Git.api_token"),
}
err = ws.Repo.Push(&git.PushOptions{
Auth: creds,
@@ -269,7 +268,7 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error {
return err
}
fmt.Println("Successfully uploaded to " + config.GetAsString("Git.server") + "/" + config.GetAsString("Git.org_name") + "/" + domain + domainConfig.GetAsString("Repo.repo_suffix") + ".git")
fmt.Println("Successfully uploaded to " + config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git")
err = writeCommitHash(commitHash.String(), domainConfig)
if err != nil {
@@ -280,10 +279,10 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error {
return nil
}
func writeCommitHash(hash string, domainConfig *ezconf.Configuration) error {
func writeCommitHash(hash string, domainConfig *viper.Viper) error {
certsDir, err := getDomainCertsDirWOnlyConf(domainConfig)
if err != nil {
if errors.Is(err, ConfigNotFound) {
if errors.Is(err, ErrConfigNotFound) {
return err
}
return err
@@ -300,7 +299,7 @@ func writeCommitHash(hash string, domainConfig *ezconf.Configuration) error {
func getLocalCommitHash(domain string) (string, error) {
certsDir, err := getDomainCertsDir(domain)
if err != nil {
if errors.Is(err, ConfigNotFound) {
if errors.Is(err, ErrConfigNotFound) {
fmt.Printf("Domain %s config not found: %v\n", domain, err)
return "", err
}
@@ -317,15 +316,15 @@ func getLocalCommitHash(domain string) (string, error) {
}
func getRemoteCommitHash(domain string, gitSource GitSource) (string, error) {
domainConfig, exists := getDomainConfig(domain)
domainConfig, exists := domainStore.Get(domain)
if !exists {
fmt.Printf("Domain %s config does not exist\n", domain)
return "", ConfigNotFound
return "", ErrConfigNotFound
}
switch gitSource {
case Gitea:
return getRemoteCommitHashGitea(config.GetAsString("Git.org_name"), domain+domainConfig.GetAsString("Repo.repo_suffix"), "master")
return getRemoteCommitHashGitea(config.GetString("Git.org_name"), domain+domainConfig.GetString("Repo.repo_suffix"), "master")
default:
fmt.Printf("Unimplemented git source %v\n", gitSource)
return "", errors.New("unimplemented git source")

22
go.mod
View File

@@ -6,13 +6,12 @@ toolchain go1.24.7
require (
code.gitea.io/sdk/gitea v0.15.1
filippo.io/age v1.2.1
git.nevets.tech/Steven/ezconf v0.1.4
github.com/go-acme/lego/v4 v4.26.0
github.com/go-git/go-billy/v5 v5.4.1
github.com/go-git/go-git/v5 v5.7.0
github.com/google/go-github/v55 v55.0.0
github.com/makifdb/pidfile v0.0.0-20231129022650-50ec86392313
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.18.2
golang.org/x/crypto v0.42.0
)
@@ -23,19 +22,35 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/miekg/dns v1.1.68 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.1.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/sync v0.17.0 // indirect
@@ -44,4 +59,5 @@ require (
golang.org/x/tools v0.36.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

64
go.sum
View File

@@ -1,18 +1,6 @@
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
git.nevets.tech/Steven/ezconf v0.1.1 h1:dEqV9Q0zVKX9UkPg5UTchGLd0J0WhiuV4dVg0o3blnY=
git.nevets.tech/Steven/ezconf v0.1.1/go.mod h1:O8svyJLWVPYdxPeZeiTkfmwz77BM0Wq2ZhDrHtdRhvI=
git.nevets.tech/Steven/ezconf v0.1.2 h1:KD47Av0swRPHKLxmDtJwahZd+x0k902CgNqBVQcxf2w=
git.nevets.tech/Steven/ezconf v0.1.2/go.mod h1:O8svyJLWVPYdxPeZeiTkfmwz77BM0Wq2ZhDrHtdRhvI=
git.nevets.tech/Steven/ezconf v0.1.3 h1:l9yG5SwYx/Jg4HzkikOsJ5FTPS9BTLGDBxTPgVOovLI=
git.nevets.tech/Steven/ezconf v0.1.3/go.mod h1:O8svyJLWVPYdxPeZeiTkfmwz77BM0Wq2ZhDrHtdRhvI=
git.nevets.tech/Steven/ezconf v0.1.4 h1:W9AHcnWQfmkc1PAlrRj54u3zPq1BXeX3u37X/+Y746g=
git.nevets.tech/Steven/ezconf v0.1.4/go.mod h1:O8svyJLWVPYdxPeZeiTkfmwz77BM0Wq2ZhDrHtdRhvI=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek=
@@ -29,6 +17,7 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -38,6 +27,10 @@ github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3O
github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-acme/lego/v4 v4.26.0 h1:521aEQxNstXvPQcFDDPrJiFfixcCQuvAvm35R4GbyYA=
@@ -64,26 +57,35 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/makifdb/pidfile v0.0.0-20231129022650-50ec86392313 h1:5/CjuZQWnRALu4hkEDRg4fA5lWDSfjlKg+koRDRuotQ=
github.com/makifdb/pidfile v0.0.0-20231129022650-50ec86392313/go.mod h1:nm72+BE0Z1PcotR9soX+NnGyEs1iQ0b1Ot0IhL2Nwwk=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -91,20 +93,49 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE=
github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -112,6 +143,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -183,5 +216,6 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

414
main.go
View File

@@ -2,303 +2,136 @@ package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
"git.nevets.tech/Steven/ezconf"
"github.com/spf13/cobra"
)
var version = "1.0.0"
var build = "1"
var build = "2"
var config *ezconf.Configuration
var ctx context.Context
var cancel context.CancelFunc
var wg sync.WaitGroup
var (
configFile string
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
)
//TODO create logic for gh vs gt repos
func main() {
devFlag := flag.Bool("dev", false, "Developer Mode")
versionFlag := flag.Bool("version", false, "Show version")
helpFlag := flag.Bool("help", false, "Show help")
configFile := flag.String("config", "/etc/certman/certman.conf", "Configuration file")
newDomainFlag := flag.String("new-domain", "example.com", "Domain to create new configs and directories for")
newDomainDirFlag := flag.String("new-domain-dir", "/opt/certs/example.com", "Directory that certs will be stored in")
installFlag := flag.Bool("install", false, "Install Certman")
modeFlag := flag.String("mode", "client", "CertManager Mode [server, client]")
thinInstallFlag := flag.Bool("t", false, "Thin Install (skip creating dirs)")
newKeyFlag := flag.Bool("newkey", false, "Generate new encryption key")
reloadFlag := flag.Bool("reload", false, "Reload configs")
stopFlag := flag.Bool("stop", false, "Stop certman")
daemonFlag := flag.Bool("d", false, "Daemon Mode")
flag.Parse()
if *devFlag {
testDomain := "lunamc.org"
var err error
config, err = ezconf.LoadConfiguration("/etc/certman/certman.conf")
if err != nil {
log.Fatalf("Error loading configuration: %v\n", err)
}
err = loadDomainConfigs()
if err != nil {
log.Fatalf("Error loading configs: %v\n", err)
rootCmd := &cobra.Command{
Use: "certman",
Short: "CertMan",
Long: "Certificate Manager",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
fmt.Println(testDomain)
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "/etc/certman/certman.conf", "Configuration file")
os.Exit(0)
rootCmd.AddCommand(basicCmd("version", "Show version", versionCmd))
rootCmd.AddCommand(basicCmd("gen-key", "Generates encryption key", newKeyCmd))
rootCmd.AddCommand(basicCmd("dev", "Dev Function", devCmd))
var domainCertDir string
newDomainCmd := &cobra.Command{
Use: "new-domain",
Short: "Create config and directories for new domain",
Args: cobra.ExactArgs(1),
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
dirOverridden := cmd.Flags().Changed("dir")
return newDomain(args[0], domainCertDir, dirOverridden)
},
}
newDomainCmd.Flags().StringVar(&domainCertDir, "dir", "/var/local/certman/certificates/", "Alternate directory for certificates")
rootCmd.AddCommand(newDomainCmd)
var (
modeFlag string
thinInstallFlag bool
)
installCmd := &cobra.Command{
Use: "install",
Short: "Create certman files and directories",
RunE: func(cmd *cobra.Command, args []string) error {
switch modeFlag {
case "server", "client":
return install(thinInstallFlag, modeFlag)
default:
return fmt.Errorf("invalid --mode %q (must be server or client)", modeFlag)
}
},
}
installCmd.Flags().StringVar(&modeFlag, "mode", "client", "CertManager mode [server, client]")
installCmd.Flags().BoolVarP(&thinInstallFlag, "thin", "t", false, "Thin install (skip creating dirs)")
rootCmd.AddCommand(installCmd)
daemonCmd := &cobra.Command{
Use: "daemon",
Short: "Daemon management",
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
}
if *versionFlag {
fmt.Println("CertManager (certman) - Steven Tracey\nVersion: " + version + " build-" + build)
os.Exit(0)
}
daemonCmd.AddCommand(&cobra.Command{
Use: "start",
Short: "Start the daemon",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runDaemonCmd()
},
})
if *helpFlag {
fmt.Printf(`CertManager (certman) - Steven Tracey
Version: %s build-%s
daemonCmd.AddCommand(&cobra.Command{
Use: "stop",
Short: "Stop the daemon",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return stopDaemonCmd()
},
})
Subcommands: certman -subcommand
- version Shows the current version and build
- help Displays this help message
- newkey Creates a new random 256 bit base64 key
daemonCmd.AddCommand(&cobra.Command{
Use: "reload",
Short: "Reload daemon configs",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return reloadDaemonCmd()
},
})
Daemon Controls: certman -command
- d Start in daemon mode
- reload Reload configs
- stop Stop Daemon
daemonCmd.AddCommand(&cobra.Command{
Use: "status",
Short: "Show daemon status",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return statusDaemonCmd()
},
})
Installation: certman -install -mode (mode) [-t] [-config /path/to/file]
- install
- mode [mode] Uses the specified config file [server, client]
- t Thin install (skip creating directories)
- config /path/to/file Create config file at the specified path
rootCmd.AddCommand(daemonCmd)
New Domain Options: certman -new-domain example.com [-new-domain-dir /path/to/certs]
- new-domain Creates a new domain config
- new-domain-dir Specifies directory for new domain certificates to be stored
`, version, build)
os.Exit(0)
}
if *newDomainFlag != "example.com" {
fmt.Printf("Creating new domain %s\n", *newDomainFlag)
createNewDomainConfig(*newDomainFlag)
createNewDomainCertsDir(*newDomainFlag, *newDomainDirFlag)
fmt.Println("Successfully created domain entry for " + *newDomainFlag + "\nUpdate config file as needed in /etc/certman/domains/" + *newDomainFlag + ".conf")
os.Exit(0)
}
if *installFlag {
if !*thinInstallFlag {
makeDirs()
}
var err error
config, err = ezconf.NewConfiguration(*configFile, strings.ReplaceAll(defaultConfig, "{mode}", *modeFlag))
if err != nil {
log.Fatalf("Error creating config: %s\n", err)
}
os.Exit(0)
}
if *newKeyFlag {
key, err := GenerateKey()
if err != nil {
log.Fatalf("%v", err)
}
fmt.Printf(key)
os.Exit(0)
}
if *reloadFlag {
proc, err := getDaemonProcess()
if err != nil {
log.Fatalf("Error getting daemon process: %v", err)
}
err = proc.Signal(syscall.SIGHUP)
if err != nil {
log.Fatalf("Error sending SIGHUP to daemon PID: %v\n", err)
}
os.Exit(0)
}
if *stopFlag {
proc, err := getDaemonProcess()
if err != nil {
log.Fatalf("Error getting daemon process: %v", err)
}
err = proc.Signal(syscall.SIGTERM)
if err != nil {
log.Fatalf("Error sending SIGTERM to daemon PID: %v\n", err)
}
os.Exit(0)
}
if *daemonFlag {
err := createOrUpdatePIDFile("/var/run/certman.pid")
if err != nil {
if errors.Is(err, ErrorPIDInUse) {
log.Fatalf("Deemon process is already running\n")
}
log.Fatalf("Error creating pidfile: %v\n", err)
}
ctx, cancel = context.WithCancel(context.Background())
// Check if main config exists
if _, err := os.Stat(*configFile); os.IsNotExist(err) {
log.Fatalf("Main config file not found, please run 'certman --install', then properly configure /etc/certman/certman.conf.")
} else if err != nil {
fmt.Printf("Error opening %s: %v\n", *configFile, err)
}
config, err = ezconf.LoadConfiguration(*configFile)
if err != nil {
log.Fatalf("Error loading configuration: %v\n", err)
}
// Setup SIGINT and SIGTERM listeners
sigChannel := make(chan os.Signal, 1)
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(sigChannel)
reloadSigChan := make(chan os.Signal, 1)
signal.Notify(reloadSigChan, syscall.SIGHUP)
defer signal.Stop(reloadSigChan)
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
wg.Add(1)
if config.GetAsString("App.mode") == "server" {
fmt.Println("Starting CertManager in server mode...")
// Server Task loop
go func() {
initServer()
defer wg.Done()
for {
select {
case <-ctx.Done():
stopServer()
return
case <-reloadSigChan:
reloadServer()
case <-ticker.C:
serverTick()
}
}
}()
} else if config.GetAsString("App.mode") == "client" {
fmt.Println("Starting CertManager in client mode...")
// Client Task loop
go func() {
initClient()
defer wg.Done()
for {
select {
case <-ctx.Done():
stopClient()
return
case <-reloadSigChan:
reloadClient()
case <-ticker.C:
clientTick()
}
}
}()
} else {
fmt.Println("Invalid operating mode \"" + config.GetAsString("App.mode") + "\"")
}
// Cleanup on stop
sig := <-sigChannel
fmt.Printf("Program terminated with %v\n", sig)
stop()
wg.Wait()
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func stop() {
cancel()
clearPIDFile()
func basicCmd(use, short string, commandFunc func(cmd *cobra.Command, args []string)) *cobra.Command {
return &cobra.Command{
Use: use,
Short: short,
Run: commandFunc,
}
}
//var legoBaseArgs []string
//
//func maindis() {
// config, err := ezconf.NewConfiguration("/etc/certman/certman.conf", "")
// var domain string
// if err != nil {
// log.Fatalf("Error loading configuration: %v\n", err)
// }
//
// args := os.Args
//
// // -d
// hasDomain, domainIndex := contains(args, "-d")
// if hasDomain {
// domain = args[domainIndex+1]
// } else {
// log.Fatalf("Error, no domain passed. Please add '-d domain.tld' to the command\n")
// }
//
// hasDns, dnsIndex := contains(args, "--dns")
//
// legoBaseArgs = []string{
// "-a",
// "--dns",
// "cloudflare",
// "--email=" + config.GetAsString("Cloudflare.cf_email"),
// "--domains=" + domain,
// "--domains=*." + domain,
// "--path=" + config.GetAsString("Certificates.certs_path"),
// }
// legoNewSiteArgs := append(legoBaseArgs, "run")
// legoRenewSiteArgs := append(legoBaseArgs, "renew", "--days", "90")
//
// subdomains := config.GetAsStrings("Certificates.subdomains")
// if subdomains != nil {
// for i, subdomain := range subdomains {
// legoBaseArgs = insert(legoBaseArgs, 5+i, "--domains=*."+subdomain+"."+domain)
// }
// }
//
// if hasDns {
// legoBaseArgs = insert(legoBaseArgs, 3, "--dns.resolvers="+args[dnsIndex+1])
// }
//
// giteaClient = createGiteaClient()
// gitWorkspace := &GitWorkspace{
// Storage: memory.NewStorage(),
// FS: memfs.New(),
// }
//
// var cmd *exec.Cmd
// switch args[len(args)-1] {
// case "gen":
// {
// url := createGiteaRepo(domain)
@@ -343,44 +176,3 @@ func stop() {
// }
// os.Exit(0)
// }
// default:
// {
// fmt.Println("Missing arguments: conclude command with 'gen' or 'renew'")
// os.Exit(1)
// }
// }
// cmd.Env = append(cmd.Environ(),
// "CLOUDFLARE_DNS_API_TOKEN="+config.GetAsString("Cloudflare.cf_api_token"),
// "CLOUDFLARE_ZONE_API_TOKEN"+config.GetAsString("Cloudflare.cf_api_token"),
// "CLOUDFLARE_EMAIL="+config.GetAsString("Cloudflare.cf_email"),
// )
// stdout, err := cmd.StdoutPipe()
// if err != nil {
// fmt.Printf("Error getting stdout from lego process: %v\n", err)
// os.Exit(1)
// }
// err = cmd.Start()
// if err != nil {
// fmt.Printf("Error creating certs with lego: %v\n", err)
// os.Exit(1)
// }
// scanner := bufio.NewScanner(stdout)
// go func() {
// for scanner.Scan() {
// fmt.Println(scanner.Text())
// }
// if err := scanner.Err(); err != nil {
// fmt.Fprintln(os.Stderr, "reading standard input:", err)
// }
// }()
// err = cmd.Wait()
// if err != nil {
// fmt.Printf("Error waiting for lego command to finish: %v\n", err)
// os.Exit(1)
// }
// err = addAndPushCerts(domain, gitWorkspace)
// if err != nil {
// fmt.Printf("Error adding and pushing certs: %v\n", err)
// return
// }
//}

View File

@@ -4,11 +4,9 @@ import (
"fmt"
"log"
"path/filepath"
"strconv"
"sync"
"time"
"git.nevets.tech/Steven/ezconf"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5/storage/memory"
)
@@ -34,10 +32,12 @@ func getACMEManager() (*ACMEManager, error) {
}
func initServer() {
err := loadDomainConfigs()
err := LoadDomainConfigs()
if err != nil {
log.Fatalf("Error loading domain configs: %v", err)
}
serverTick()
}
func serverTick() {
@@ -53,18 +53,14 @@ func serverTick() {
now := time.Now().UTC()
mu.RLock()
localDomainConfigs := make(map[string]*ezconf.Configuration, len(domainConfigs))
for k, v := range domainConfigs {
localDomainConfigs[k] = v
}
mu.RUnlock()
localDomainConfigs := domainStore.Snapshot()
for domainStr, domainConfig := range localDomainConfigs {
if !domainConfig.GetAsBoolean("Domain.enabled") {
if !domainConfig.GetBool("Domain.enabled") {
continue
}
renewPeriod := domainConfig.GetAsInt("Certificates.renew_period")
lastIssued := time.Unix(domainConfig.GetAsInt64("Internal.last_issued"), 0).UTC()
renewPeriod := domainConfig.GetInt("Certificates.renew_period")
lastIssued := time.Unix(domainConfig.GetInt64("Internal.last_issued"), 0).UTC()
renewalDue := lastIssued.AddDate(0, 0, renewPeriod)
if now.After(renewalDue) {
_, err = mgr.RenewForDomain(domainStr)
@@ -77,23 +73,19 @@ func serverTick() {
}
}
err = domainConfig.SetValueErr("Internal.last_issued", strconv.FormatInt(time.Now().UTC().Unix(), 10))
if err != nil {
fmt.Printf("Error updating last_issued config for domain %s: %v\n", domainStr, err)
continue
}
err = domainConfig.Save()
domainConfig.Set("Internal.last_issued", time.Now().UTC().Unix())
err = domainConfig.WriteConfig()
if err != nil {
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
continue
}
err = EncryptFileXChaCha(domainConfig.GetAsString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".crt"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".crt.crpt"), nil)
err = EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".crt"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".crt.crpt"), nil)
if err != nil {
fmt.Printf("Error encrypting domain cert for domain %s: %v\n", domainStr, err)
continue
}
err = EncryptFileXChaCha(domainConfig.GetAsString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".key.crpt"), nil)
err = EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".key.crpt"), nil)
if err != nil {
fmt.Printf("Error encrypting domain key for domain %s: %v\n", domainStr, err)
continue
@@ -110,18 +102,14 @@ func serverTick() {
}
var repoUrl string
if !domainConfig.GetAsBoolean("Internal.repo_exists") {
if !domainConfig.GetBool("Internal.repo_exists") {
repoUrl = createGiteaRepo(domainStr, giteaClient)
if repoUrl == "" {
fmt.Printf("Error creating Gitea repo for domain %s\n", domainStr)
continue
}
err = domainConfig.SetValueErr("Internal.repo_exists", "true")
if err != nil {
fmt.Printf("Error updating repo_exists config for domain %s: %v\n", domainStr, err)
continue
}
err = domainConfig.Save()
domainConfig.Set("Internal.repo_exists", true)
err = domainConfig.WriteConfig()
if err != nil {
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
continue
@@ -133,7 +121,7 @@ func serverTick() {
continue
}
} else {
repoUrl = config.GetAsString("Git.server") + "/" + config.GetAsString("Git.org_name") + "/" + domainStr + domainConfig.GetAsString("Repo.repo_suffix") + ".git"
repoUrl = config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domainStr + domainConfig.GetString("Repo.repo_suffix") + ".git"
err = cloneRepo(repoUrl, gitWorkspace)
if err != nil {
fmt.Printf("Error cloning repo for domain %s: %v\n", domainStr, err)
@@ -149,12 +137,12 @@ func serverTick() {
fmt.Printf("Successfully pushed certificates for domain %s\n", domainStr)
}
}
saveDomainConfigs()
SaveDomainConfigs()
}
func reloadServer() {
fmt.Println("Reloading configs...")
err := loadDomainConfigs()
err := LoadDomainConfigs()
if err != nil {
fmt.Printf("Error loading domain configs: %v\n", err)

20
util.go
View File

@@ -10,8 +10,8 @@ import (
"syscall"
"code.gitea.io/sdk/gitea"
"git.nevets.tech/Steven/ezconf"
"github.com/google/go-github/v55/github"
"github.com/spf13/viper"
)
var (
@@ -22,7 +22,7 @@ var (
type Domain struct {
name *string
config *ezconf.Configuration
config *viper.Viper
description *string
ghClient *github.Client
gtClient *gitea.Client
@@ -245,19 +245,19 @@ func sanitizeDomainKey(s string) string {
return r.Replace(s)
}
// getDomainCertsDir Can return BlankConfigEntry, ConfigNotFound, or other errors
// getDomainCertsDir Can return ErrBlankConfigEntry, ErrConfigNotFound, or other errors
func getDomainCertsDir(domain string) (string, error) {
domainConfig, exists := getDomainConfig(domain)
domainConfig, exists := domainStore.Get(domain)
if !exists {
return "", ConfigNotFound
return "", ErrConfigNotFound
}
return getDomainCertsDirWConf(domain, domainConfig)
}
// getDomainCertsDir Can return BlankConfigEntry or other errors
func getDomainCertsDirWConf(domain string, domainConfig *ezconf.Configuration) (string, error) {
effectiveDataRoot, err := getEffectiveString(domainConfig, "Certificates.data_root")
// getDomainCertsDir Can return ErrBlankConfigEntry or other errors
func getDomainCertsDirWConf(domain string, domainConfig *viper.Viper) (string, error) {
effectiveDataRoot, err := EffectiveString(domainConfig, "Certificates.data_root")
if err != nil {
return "", err
}
@@ -265,7 +265,7 @@ func getDomainCertsDirWConf(domain string, domainConfig *ezconf.Configuration) (
return filepath.Join(effectiveDataRoot, "certificates", domain), nil
}
func getDomainCertsDirWOnlyConf(domainConfig *ezconf.Configuration) (string, error) {
domain := domainConfig.GetAsString("Domain.domain_name")
func getDomainCertsDirWOnlyConf(domainConfig *viper.Viper) (string, error) {
domain := domainConfig.GetString("Domain.domain_name")
return getDomainCertsDirWConf(domain, domainConfig)
}