Migrated to cobra for command handling and viper for config handling
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -128,3 +128,4 @@ $RECYCLE.BIN/
|
|||||||
|
|
||||||
config.ini
|
config.ini
|
||||||
certman
|
certman
|
||||||
|
certman-*-amd64
|
||||||
10
Makefile
10
Makefile
@@ -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:
|
build:
|
||||||
@go build -o ./certman .
|
$(GO) build $(BUILD_FLAGS) -ldflags='$(LDFLAGS)' -o ./certman .
|
||||||
|
@cp ./certman ./certman-$(VERSION)-amd64
|
||||||
|
|
||||||
stage: build
|
stage: build
|
||||||
@sudo cp ./certman /srv/vm-passthru/certman
|
@sudo cp ./certman /srv/vm-passthru/certman
|
||||||
|
|||||||
@@ -96,18 +96,9 @@ type StoredCertMeta struct {
|
|||||||
// - Cloudflare DNS-01 only
|
// - Cloudflare DNS-01 only
|
||||||
func NewACMEManager() (*ACMEManager, error) {
|
func NewACMEManager() (*ACMEManager, error) {
|
||||||
// Pull effective (main-only) certificate settings.
|
// Pull effective (main-only) certificate settings.
|
||||||
email, err := config.GetAsStringErr("Certificates.email")
|
email := config.GetString("Certificates.email")
|
||||||
if err != nil {
|
dataRoot := config.GetString("Certificates.data_root")
|
||||||
return nil, err
|
caDirURL := config.GetString("Certificates.ca_dir_url")
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build manager paths
|
// Build manager paths
|
||||||
mgr := &ACMEManager{
|
mgr := &ACMEManager{
|
||||||
@@ -246,51 +237,31 @@ func (m *ACMEManager) GetCertPaths(domainKey string) (certPEM, keyPEM string) {
|
|||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
|
|
||||||
func buildDomainRuntimeConfig(domainKey string) (*DomainRuntimeConfig, error) {
|
func buildDomainRuntimeConfig(domainKey string) (*DomainRuntimeConfig, error) {
|
||||||
domainCfg, exists := getDomainConfig(domainKey)
|
domainCfg, exists := domainStore.Get(domainKey)
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("domain config not found for %q", domainKey)
|
return nil, fmt.Errorf("domain config not found for %q", domainKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
domainName, err := domainCfg.GetAsStringErr("Domain.domain_name")
|
domainName := domainCfg.GetString("Domain.domain_name")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
email, err := config.GetAsStringErr("Certificates.email")
|
email := config.GetString("Certificates.email")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// domain override data_root can be blank -> main fallback
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
caDirURL, err := config.GetAsStringErr("Certificates.ca_dir_url")
|
caDirURL := config.GetString("Certificates.ca_dir_url")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
expiry, err := domainCfg.GetAsIntErr("Certificates.expiry")
|
expiry := domainCfg.GetInt("Certificates.expiry")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
renewPeriod, err := domainCfg.GetAsIntErr("Certificates.renew_period")
|
renewPeriod := domainCfg.GetInt("Certificates.renew_period")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
requestMethod := ""
|
requestMethod := domainCfg.GetString("Certificates.request_method")
|
||||||
if k, err := domainCfg.GetKey("Certificates.request_method"); err == nil && k != nil {
|
|
||||||
requestMethod = strings.TrimSpace(k.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
subdomains := []string{}
|
subdomains := domainCfg.GetString("Certificates.subdomains")
|
||||||
if k, err := domainCfg.GetKey("Certificates.subdomains"); err == nil && k != nil {
|
subdomainArray := parseCSVLines(subdomains)
|
||||||
subdomains = parseCSVLines(k.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DomainRuntimeConfig{
|
return &DomainRuntimeConfig{
|
||||||
DomainName: domainName,
|
DomainName: domainName,
|
||||||
@@ -300,7 +271,7 @@ func buildDomainRuntimeConfig(domainKey string) (*DomainRuntimeConfig, error) {
|
|||||||
ExpiryDays: expiry,
|
ExpiryDays: expiry,
|
||||||
RenewPeriod: renewPeriod,
|
RenewPeriod: renewPeriod,
|
||||||
RequestMethod: requestMethod,
|
RequestMethod: requestMethod,
|
||||||
Subdomains: subdomains,
|
Subdomains: subdomainArray,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,14 +348,8 @@ func buildDomainList(baseDomain string, subs []string) []string {
|
|||||||
func setCloudflareEnvFromMainConfig() (restore func(), err error) {
|
func setCloudflareEnvFromMainConfig() (restore func(), err error) {
|
||||||
// Your current defaults show legacy email + API key fields.
|
// 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}
|
// 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")
|
cfEmail := config.GetString("Cloudflare.cf_email")
|
||||||
if err != nil {
|
cfAPIKey := config.GetString("Cloudflare.cf_api_key")
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cfAPIKey, err := config.GetAsStringErr("Cloudflare.cf_api_key")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save prior env values so we can restore them after provider creation.
|
// Save prior env values so we can restore them after provider creation.
|
||||||
prevEmail, hadEmail := os.LookupEnv("CLOUDFLARE_EMAIL")
|
prevEmail, hadEmail := os.LookupEnv("CLOUDFLARE_EMAIL")
|
||||||
|
|||||||
24
client.go
24
client.go
@@ -7,45 +7,41 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.nevets.tech/Steven/ezconf"
|
|
||||||
"github.com/go-git/go-billy/v5/memfs"
|
"github.com/go-git/go-billy/v5/memfs"
|
||||||
"github.com/go-git/go-git/v5/storage/memory"
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initClient() {
|
func initClient() {
|
||||||
err := loadDomainConfigs()
|
err := LoadDomainConfigs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error loading domain configs: %v", err)
|
log.Fatalf("Error loading domain configs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientTick()
|
||||||
}
|
}
|
||||||
|
|
||||||
func clientTick() {
|
func clientTick() {
|
||||||
fmt.Println("Tick!")
|
fmt.Println("Tick!")
|
||||||
|
|
||||||
// Get local copy of domain configs
|
// Get local copy of domain configs
|
||||||
mu.RLock()
|
localDomainConfigs := domainStore.Snapshot()
|
||||||
localDomainConfigs := make(map[string]*ezconf.Configuration, len(domainConfigs))
|
|
||||||
for k, v := range domainConfigs {
|
|
||||||
localDomainConfigs[k] = v
|
|
||||||
}
|
|
||||||
mu.RUnlock()
|
|
||||||
|
|
||||||
// Loop over all domain configs (domains)
|
// Loop over all domain configs (domains)
|
||||||
for domainStr, domainConfig := range localDomainConfigs {
|
for domainStr, domainConfig := range localDomainConfigs {
|
||||||
// Skip non-enabled domains
|
// Skip non-enabled domains
|
||||||
if !domainConfig.GetAsBoolean("Domain.enabled") {
|
if !domainConfig.GetBool("Domain.enabled") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip domains with up-to-date commit hashes
|
// 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
|
// 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 {
|
if repoExists {
|
||||||
localHash, err := getLocalCommitHash(domainStr)
|
localHash, err := getLocalCommitHash(domainStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("No local commit hash found for domain %s\n", domainStr)
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("Error getting git source for domain %s: %v\n", domainStr, err)
|
fmt.Printf("Error getting git source for domain %s: %v\n", domainStr, err)
|
||||||
continue
|
continue
|
||||||
@@ -68,7 +64,7 @@ func clientTick() {
|
|||||||
}
|
}
|
||||||
// Ex: https://git.example.com/Org/Repo-suffix.git
|
// Ex: https://git.example.com/Org/Repo-suffix.git
|
||||||
// Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?)
|
// 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)
|
err := cloneRepo(repoUrl, gitWorkspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error cloning domain repo %s: %v\n", domainStr, err)
|
fmt.Printf("Error cloning domain repo %s: %v\n", domainStr, err)
|
||||||
@@ -108,7 +104,7 @@ func clientTick() {
|
|||||||
continue
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domainStr, err)
|
fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domainStr, err)
|
||||||
continue
|
continue
|
||||||
@@ -133,7 +129,7 @@ func clientTick() {
|
|||||||
func reloadClient() {
|
func reloadClient() {
|
||||||
fmt.Println("Reloading configs...")
|
fmt.Println("Reloading configs...")
|
||||||
|
|
||||||
err := loadDomainConfigs()
|
err := LoadDomainConfigs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error loading domain configs: %v\n", err)
|
fmt.Printf("Error loading domain configs: %v\n", err)
|
||||||
return
|
return
|
||||||
|
|||||||
183
commands.go
Normal file
183
commands.go
Normal 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
|
||||||
|
}
|
||||||
329
config.go
329
config.go
@@ -9,173 +9,258 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.nevets.tech/Steven/ezconf"
|
"github.com/spf13/viper"
|
||||||
"gopkg.in/ini.v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var domainConfigs map[string]*ezconf.Configuration
|
|
||||||
var mu sync.RWMutex
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
BlankConfigEntry = errors.New("blank config entry")
|
ErrBlankConfigEntry = errors.New("blank config entry")
|
||||||
ConfigNotFound = errors.New("config file not found")
|
ErrConfigNotFound = errors.New("config file not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeDirs() {
|
type DomainConfigStore struct {
|
||||||
err := os.MkdirAll("/etc/certman", 0644)
|
mu sync.RWMutex
|
||||||
if err != nil {
|
configs map[string]*viper.Viper
|
||||||
if !os.IsExist(err) {
|
}
|
||||||
fmt.Println("Unable to create config directory")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Mkdir("/etc/certman/domains", 0644)
|
func NewDomainConfigStore() *DomainConfigStore {
|
||||||
if err != nil {
|
return &DomainConfigStore{
|
||||||
if !os.IsExist(err) {
|
configs: make(map[string]*viper.Viper),
|
||||||
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) {
|
func (s *DomainConfigStore) Get(domain string) (*viper.Viper, bool) {
|
||||||
key, err := GenerateKey()
|
s.mu.RLock()
|
||||||
if err != nil {
|
defer s.mu.RUnlock()
|
||||||
log.Fatalf("Unable to generate key: %v\n", err)
|
v, ok := s.configs[domain]
|
||||||
}
|
return v, ok
|
||||||
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) {
|
func (s *DomainConfigStore) Set(domain string, v *viper.Viper) {
|
||||||
var err error
|
s.mu.Lock()
|
||||||
if dir == "/opt/certs/example.com" {
|
defer s.mu.Unlock()
|
||||||
err = os.MkdirAll("/var/local/certman/certificates/"+domain, 0640)
|
s.configs[domain] = v
|
||||||
} 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 {
|
// Swap atomically replaces the entire config map (used during reload).
|
||||||
tempDomainConfigs := make(map[string]*ezconf.Configuration)
|
func (s *DomainConfigStore) Swap(newConfigs map[string]*viper.Viper) {
|
||||||
entries, err := os.ReadDir("/etc/certman/domains/")
|
s.mu.Lock()
|
||||||
if err != nil {
|
defer s.mu.Unlock()
|
||||||
return err
|
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 {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() || filepath.Ext(entry.Name()) != ".conf" {
|
if entry.IsDir() || filepath.Ext(entry.Name()) != ".conf" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
domainConf, err := ezconf.LoadConfiguration("/etc/certman/domains/" + entry.Name())
|
path := filepath.Join(dir, entry.Name())
|
||||||
if err != nil {
|
v := viper.New()
|
||||||
return err
|
v.SetConfigFile(path)
|
||||||
}
|
v.SetConfigType("ini")
|
||||||
domain, err := domainConf.GetAsStringErr("Domain.domain_name")
|
|
||||||
if err != nil {
|
if err := v.ReadInConfig(); err != nil {
|
||||||
return err
|
return fmt.Errorf("loading %s: %w", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := tempDomainConfigs[domain]; exists {
|
domain := v.GetString("domain.domain_name")
|
||||||
fmt.Printf("Duplicate domain found in %s, skipping...\n", "/etc/certman/domains/"+entry.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
|
continue
|
||||||
}
|
}
|
||||||
tempDomainConfigs[domain] = domainConf
|
temp[domain] = v
|
||||||
}
|
}
|
||||||
mu.Lock()
|
|
||||||
domainConfigs = tempDomainConfigs
|
domainStore.Swap(temp)
|
||||||
mu.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveDomainConfigs() {
|
// ---------------------------------------------------------------------------
|
||||||
mu.RLock()
|
// Saving
|
||||||
localDomainConfigs := make(map[string]*ezconf.Configuration, len(domainConfigs))
|
// ---------------------------------------------------------------------------
|
||||||
for k, v := range domainConfigs {
|
|
||||||
localDomainConfigs[k] = v
|
|
||||||
}
|
|
||||||
mu.RUnlock()
|
|
||||||
|
|
||||||
for domainStr, domainConfig := range localDomainConfigs {
|
// SaveDomainConfigs writes every loaded domain config back to disk.
|
||||||
err := domainConfig.Save()
|
func SaveDomainConfigs() {
|
||||||
if err != nil {
|
for domain, v := range domainStore.Snapshot() {
|
||||||
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
|
if err := v.WriteConfig(); err != nil {
|
||||||
continue
|
fmt.Printf("Error saving domain config %s: %v\n", domain, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDomainConfig(domain string) (*ezconf.Configuration, bool) {
|
// ---------------------------------------------------------------------------
|
||||||
mu.RLock()
|
// Effective lookups (domain → global fallback)
|
||||||
defer mu.RUnlock()
|
// ---------------------------------------------------------------------------
|
||||||
funcDomainConfig, exists := domainConfigs[domain]
|
|
||||||
return funcDomainConfig, exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEffectiveKey(domainConfig *ezconf.Configuration, path string) *ini.Key {
|
// EffectiveString looks up a key in the domain config first, falling back to
|
||||||
key, err := getEffectiveKeyErr(domainConfig, path)
|
// the global config. Keys use dot notation matching INI sections, e.g.
|
||||||
if err != nil {
|
// "certificates.data_root".
|
||||||
if errors.Is(err, BlankConfigEntry) {
|
func EffectiveString(domainCfg *viper.Viper, key string) (string, error) {
|
||||||
return &ini.Key{}
|
if domainCfg != nil {
|
||||||
}
|
val := strings.TrimSpace(domainCfg.GetString(key))
|
||||||
fmt.Printf("Error getting value for %s: %v\n", path, err)
|
if val != "" {
|
||||||
return nil
|
return val, 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 config == nil {
|
||||||
if err != nil {
|
return "", ErrConfigNotFound
|
||||||
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
|
val := strings.TrimSpace(config.GetString(key))
|
||||||
|
if val == "" {
|
||||||
|
return "", ErrBlankConfigEntry
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEffectiveString(domainCfg *ezconf.Configuration, path string) (string, error) {
|
// MustEffectiveString is like EffectiveString but logs a fatal error on failure.
|
||||||
k, err := getEffectiveKeyErr(domainCfg, path)
|
func MustEffectiveString(domainCfg *viper.Viper, key string) string {
|
||||||
|
val, err := EffectiveString(domainCfg, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
log.Fatalf("Config key %q: %v", key, err)
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(k.String()), nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(target, 0750); err != nil {
|
||||||
|
if os.IsExist(err) {
|
||||||
|
fmt.Println("Directory already exists...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Fatalf("Error creating certificate directory for %s: %v", domain, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Default config templates
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const defaultConfig = `[App]
|
const defaultConfig = `[App]
|
||||||
mode = {mode}
|
mode = {mode}
|
||||||
|
tick_rate = 2
|
||||||
|
|
||||||
[Git]
|
[Git]
|
||||||
host = gitea
|
host = gitea
|
||||||
@@ -197,7 +282,6 @@ cf_api_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|||||||
const defaultDomainConfig = `[Domain]
|
const defaultDomainConfig = `[Domain]
|
||||||
domain_name = {domain}
|
domain_name = {domain}
|
||||||
enabled = true
|
enabled = true
|
||||||
; default (use system dns) or IPv4 Address (1.1.1.1)
|
|
||||||
dns_server = default
|
dns_server = default
|
||||||
|
|
||||||
|
|
||||||
@@ -217,9 +301,10 @@ crypto_key = {key}
|
|||||||
repo_suffix = -certificates
|
repo_suffix = -certificates
|
||||||
|
|
||||||
|
|
||||||
; Don't change setting below here unless you know what you're doing!
|
|
||||||
[Internal]
|
[Internal]
|
||||||
last_issued = 0
|
last_issued = 0
|
||||||
repo_exists = false
|
repo_exists = false
|
||||||
status = clean
|
status = clean
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const readme = ``
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
_ "filippo.io/age"
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
47
git.go
47
git.go
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"git.nevets.tech/Steven/ezconf"
|
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
gitconf "github.com/go-git/go-git/v5/config"
|
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/plumbing/transport/http"
|
||||||
"github.com/go-git/go-git/v5/storage/memory"
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
"github.com/google/go-github/v55/github"
|
"github.com/google/go-github/v55/github"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GitWorkspace struct {
|
type GitWorkspace struct {
|
||||||
@@ -57,11 +57,11 @@ func strToGitSource(s string) (GitSource, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createGithubClient() *github.Client {
|
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 {
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("Error connecting to gitea instance: %v\n", err)
|
fmt.Printf("Error connecting to gitea instance: %v\n", err)
|
||||||
return nil
|
return nil
|
||||||
@@ -71,7 +71,7 @@ func createGiteaClient() *gitea.Client {
|
|||||||
|
|
||||||
func createGithubRepo(domain *Domain, client *github.Client) string {
|
func createGithubRepo(domain *Domain, client *github.Client) string {
|
||||||
name := domain.name
|
name := domain.name
|
||||||
owner := domain.config.GetAsString("Repo.owner")
|
owner := domain.config.GetString("Repo.owner")
|
||||||
description := domain.description
|
description := domain.description
|
||||||
private := true
|
private := true
|
||||||
includeAllBranches := false
|
includeAllBranches := false
|
||||||
@@ -84,7 +84,7 @@ func createGithubRepo(domain *Domain, client *github.Client) string {
|
|||||||
Private: &private,
|
Private: &private,
|
||||||
IncludeAllBranches: &includeAllBranches,
|
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 {
|
if err != nil {
|
||||||
fmt.Println("Error creating repository from template,", err)
|
fmt.Println("Error creating repository from template,", err)
|
||||||
return ""
|
return ""
|
||||||
@@ -93,7 +93,7 @@ func createGithubRepo(domain *Domain, client *github.Client) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createGiteaRepo(domain string, giteaClient *gitea.Client) string {
|
func createGiteaRepo(domain string, giteaClient *gitea.Client) string {
|
||||||
domainConfig, exists := getDomainConfig(domain)
|
domainConfig, exists := domainStore.Get(domain)
|
||||||
if !exists {
|
if !exists {
|
||||||
fmt.Printf("Domain %s config does not exist\n", domain)
|
fmt.Printf("Domain %s config does not exist\n", domain)
|
||||||
return ""
|
return ""
|
||||||
@@ -111,7 +111,7 @@ func createGiteaRepo(domain string, giteaClient *gitea.Client) string {
|
|||||||
// Webhooks: true,
|
// Webhooks: true,
|
||||||
//}
|
//}
|
||||||
options := gitea.CreateRepoOption{
|
options := gitea.CreateRepoOption{
|
||||||
Name: domain + domainConfig.GetAsString("Repo.repo_suffix"),
|
Name: domain + domainConfig.GetString("Repo.repo_suffix"),
|
||||||
Description: "Certificate storage for " + domain,
|
Description: "Certificate storage for " + domain,
|
||||||
Private: true,
|
Private: true,
|
||||||
IssueLabels: "",
|
IssueLabels: "",
|
||||||
@@ -124,8 +124,7 @@ func createGiteaRepo(domain string, giteaClient *gitea.Client) string {
|
|||||||
TrustModel: gitea.TrustModelDefault,
|
TrustModel: gitea.TrustModelDefault,
|
||||||
}
|
}
|
||||||
|
|
||||||
giteaRepo, _, err := giteaClient.CreateOrgRepo(config.GetAsString("Git.org_name"), options)
|
giteaRepo, _, err := giteaClient.CreateOrgRepo(config.GetString("Git.org_name"), options)
|
||||||
//giteaRepo, _, err := giteaClient.CreateRepoFromTemplate(config.GetAsString("Git.org_name"), config.GetAsString("Git.template_name"), options)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error creating repo: %v\n", err)
|
fmt.Printf("Error creating repo: %v\n", err)
|
||||||
return ""
|
return ""
|
||||||
@@ -161,8 +160,8 @@ func initRepo(url string, ws *GitWorkspace) error {
|
|||||||
|
|
||||||
func cloneRepo(url string, ws *GitWorkspace) error {
|
func cloneRepo(url string, ws *GitWorkspace) error {
|
||||||
creds := &http.BasicAuth{
|
creds := &http.BasicAuth{
|
||||||
Username: config.GetAsString("Git.username"),
|
Username: config.GetString("Git.username"),
|
||||||
Password: config.GetAsString("Git.api_token"),
|
Password: config.GetString("Git.api_token"),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
ws.Repo, err = git.Clone(ws.Storage, ws.FS, &git.CloneOptions{URL: url, Auth: creds})
|
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 {
|
func addAndPushCerts(domain string, ws *GitWorkspace) error {
|
||||||
domainConfig, exists := getDomainConfig(domain)
|
domainConfig, exists := domainStore.Get(domain)
|
||||||
if !exists {
|
if !exists {
|
||||||
fmt.Printf("Domain %s config does not exist\n", domain)
|
fmt.Printf("Domain %s config does not exist\n", domain)
|
||||||
return ConfigNotFound
|
return ErrConfigNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
certsDir, err := getDomainCertsDirWConf(domain, domainConfig)
|
certsDir, err := getDomainCertsDirWConf(domain, domainConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ConfigNotFound) {
|
if errors.Is(err, ErrConfigNotFound) {
|
||||||
fmt.Printf("Domain %s config not found: %v\n", domain, err)
|
fmt.Printf("Domain %s config not found: %v\n", domain, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -244,7 +243,7 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error {
|
|||||||
fmt.Println("Work Tree Status:\n" + status.String())
|
fmt.Println("Work Tree Status:\n" + status.String())
|
||||||
signature := &object.Signature{
|
signature := &object.Signature{
|
||||||
Name: "Cert Manager",
|
Name: "Cert Manager",
|
||||||
Email: config.GetAsString("Certificates.email"),
|
Email: config.GetString("Certificates.email"),
|
||||||
When: time.Now(),
|
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})
|
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
|
return err
|
||||||
}
|
}
|
||||||
creds := &http.BasicAuth{
|
creds := &http.BasicAuth{
|
||||||
Username: config.GetAsString("Git.username"),
|
Username: config.GetString("Git.username"),
|
||||||
Password: config.GetAsString("Git.api_token"),
|
Password: config.GetString("Git.api_token"),
|
||||||
}
|
}
|
||||||
err = ws.Repo.Push(&git.PushOptions{
|
err = ws.Repo.Push(&git.PushOptions{
|
||||||
Auth: creds,
|
Auth: creds,
|
||||||
@@ -269,7 +268,7 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error {
|
|||||||
return err
|
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)
|
err = writeCommitHash(commitHash.String(), domainConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -280,10 +279,10 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCommitHash(hash string, domainConfig *ezconf.Configuration) error {
|
func writeCommitHash(hash string, domainConfig *viper.Viper) error {
|
||||||
certsDir, err := getDomainCertsDirWOnlyConf(domainConfig)
|
certsDir, err := getDomainCertsDirWOnlyConf(domainConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ConfigNotFound) {
|
if errors.Is(err, ErrConfigNotFound) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -300,7 +299,7 @@ func writeCommitHash(hash string, domainConfig *ezconf.Configuration) error {
|
|||||||
func getLocalCommitHash(domain string) (string, error) {
|
func getLocalCommitHash(domain string) (string, error) {
|
||||||
certsDir, err := getDomainCertsDir(domain)
|
certsDir, err := getDomainCertsDir(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ConfigNotFound) {
|
if errors.Is(err, ErrConfigNotFound) {
|
||||||
fmt.Printf("Domain %s config not found: %v\n", domain, err)
|
fmt.Printf("Domain %s config not found: %v\n", domain, err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -317,15 +316,15 @@ func getLocalCommitHash(domain string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getRemoteCommitHash(domain string, gitSource GitSource) (string, error) {
|
func getRemoteCommitHash(domain string, gitSource GitSource) (string, error) {
|
||||||
domainConfig, exists := getDomainConfig(domain)
|
domainConfig, exists := domainStore.Get(domain)
|
||||||
if !exists {
|
if !exists {
|
||||||
fmt.Printf("Domain %s config does not exist\n", domain)
|
fmt.Printf("Domain %s config does not exist\n", domain)
|
||||||
return "", ConfigNotFound
|
return "", ErrConfigNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
switch gitSource {
|
switch gitSource {
|
||||||
case Gitea:
|
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:
|
default:
|
||||||
fmt.Printf("Unimplemented git source %v\n", gitSource)
|
fmt.Printf("Unimplemented git source %v\n", gitSource)
|
||||||
return "", errors.New("unimplemented git source")
|
return "", errors.New("unimplemented git source")
|
||||||
|
|||||||
22
go.mod
22
go.mod
@@ -6,13 +6,12 @@ toolchain go1.24.7
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/sdk/gitea v0.15.1
|
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-acme/lego/v4 v4.26.0
|
||||||
github.com/go-git/go-billy/v5 v5.4.1
|
github.com/go-git/go-billy/v5 v5.4.1
|
||||||
github.com/go-git/go-git/v5 v5.7.0
|
github.com/go-git/go-git/v5 v5.7.0
|
||||||
github.com/google/go-github/v55 v55.0.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
|
golang.org/x/crypto v0.42.0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,19 +22,35 @@ require (
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cloudflare/circl v1.3.3 // indirect
|
github.com/cloudflare/circl v1.3.3 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // 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-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.7.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/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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // 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/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/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/sergi/go-diff v1.3.1 // indirect
|
||||||
github.com/skeema/knownhosts v1.1.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
|
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/mod v0.27.0 // indirect
|
||||||
golang.org/x/net v0.44.0 // indirect
|
golang.org/x/net v0.44.0 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
@@ -44,4 +59,5 @@ require (
|
|||||||
golang.org/x/tools v0.36.0 // indirect
|
golang.org/x/tools v0.36.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
64
go.sum
64
go.sum
@@ -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/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 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M=
|
||||||
code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA=
|
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 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
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=
|
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.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/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/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 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
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 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||||
github.com/go-acme/lego/v4 v4.26.0 h1:521aEQxNstXvPQcFDDPrJiFfixcCQuvAvm35R4GbyYA=
|
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.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 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
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 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
|
||||||
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
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 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
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 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
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.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.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/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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/makifdb/pidfile v0.0.0-20231129022650-50ec86392313/go.mod h1:nm72+BE0Z1PcotR9soX+NnGyEs1iQ0b1Ot0IhL2Nwwk=
|
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 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
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 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
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/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 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
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.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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
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/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 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE=
|
||||||
github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
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 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
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.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
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/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.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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
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/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.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
410
main.go
410
main.go
@@ -2,303 +2,136 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.nevets.tech/Steven/ezconf"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.0.0"
|
var version = "1.0.0"
|
||||||
var build = "1"
|
var build = "2"
|
||||||
|
|
||||||
var config *ezconf.Configuration
|
var (
|
||||||
|
configFile string
|
||||||
var ctx context.Context
|
ctx context.Context
|
||||||
var cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
var wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
|
||||||
//TODO create logic for gh vs gt repos
|
//TODO create logic for gh vs gt repos
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
rootCmd := &cobra.Command{
|
||||||
devFlag := flag.Bool("dev", false, "Developer Mode")
|
Use: "certman",
|
||||||
|
Short: "CertMan",
|
||||||
versionFlag := flag.Bool("version", false, "Show version")
|
Long: "Certificate Manager",
|
||||||
helpFlag := flag.Bool("help", false, "Show help")
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return cmd.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(testDomain)
|
|
||||||
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *versionFlag {
|
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "/etc/certman/certman.conf", "Configuration file")
|
||||||
fmt.Println("CertManager (certman) - Steven Tracey\nVersion: " + version + " build-" + build)
|
|
||||||
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)
|
||||||
|
|
||||||
if *helpFlag {
|
var (
|
||||||
fmt.Printf(`CertManager (certman) - Steven Tracey
|
modeFlag string
|
||||||
Version: %s build-%s
|
thinInstallFlag bool
|
||||||
|
)
|
||||||
Subcommands: certman -subcommand
|
installCmd := &cobra.Command{
|
||||||
- version Shows the current version and build
|
Use: "install",
|
||||||
- help Displays this help message
|
Short: "Create certman files and directories",
|
||||||
- newkey Creates a new random 256 bit base64 key
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
switch modeFlag {
|
||||||
Daemon Controls: certman -command
|
case "server", "client":
|
||||||
- d Start in daemon mode
|
return install(thinInstallFlag, modeFlag)
|
||||||
- reload Reload configs
|
default:
|
||||||
- stop Stop Daemon
|
return fmt.Errorf("invalid --mode %q (must be server or client)", modeFlag)
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
},
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
ctx, cancel = context.WithCancel(context.Background())
|
daemonCmd := &cobra.Command{
|
||||||
|
Use: "daemon",
|
||||||
|
Short: "Daemon management",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// Check if main config exists
|
daemonCmd.AddCommand(&cobra.Command{
|
||||||
if _, err := os.Stat(*configFile); os.IsNotExist(err) {
|
Use: "start",
|
||||||
log.Fatalf("Main config file not found, please run 'certman --install', then properly configure /etc/certman/certman.conf.")
|
Short: "Start the daemon",
|
||||||
} else if err != nil {
|
Args: cobra.NoArgs,
|
||||||
fmt.Printf("Error opening %s: %v\n", *configFile, err)
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
}
|
return runDaemonCmd()
|
||||||
config, err = ezconf.LoadConfiguration(*configFile)
|
},
|
||||||
if err != nil {
|
})
|
||||||
log.Fatalf("Error loading configuration: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup SIGINT and SIGTERM listeners
|
daemonCmd.AddCommand(&cobra.Command{
|
||||||
sigChannel := make(chan os.Signal, 1)
|
Use: "stop",
|
||||||
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM)
|
Short: "Stop the daemon",
|
||||||
defer signal.Stop(sigChannel)
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return stopDaemonCmd()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
reloadSigChan := make(chan os.Signal, 1)
|
daemonCmd.AddCommand(&cobra.Command{
|
||||||
signal.Notify(reloadSigChan, syscall.SIGHUP)
|
Use: "reload",
|
||||||
defer signal.Stop(reloadSigChan)
|
Short: "Reload daemon configs",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return reloadDaemonCmd()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
ticker := time.NewTicker(30 * time.Second)
|
daemonCmd.AddCommand(&cobra.Command{
|
||||||
defer ticker.Stop()
|
Use: "status",
|
||||||
|
Short: "Show daemon status",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return statusDaemonCmd()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
wg.Add(1)
|
rootCmd.AddCommand(daemonCmd)
|
||||||
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
|
if err := rootCmd.Execute(); err != nil {
|
||||||
sig := <-sigChannel
|
fmt.Fprintln(os.Stderr, err)
|
||||||
fmt.Printf("Program terminated with %v\n", sig)
|
os.Exit(1)
|
||||||
|
|
||||||
stop()
|
|
||||||
wg.Wait()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() {
|
func basicCmd(use, short string, commandFunc func(cmd *cobra.Command, args []string)) *cobra.Command {
|
||||||
cancel()
|
return &cobra.Command{
|
||||||
clearPIDFile()
|
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":
|
// case "gen":
|
||||||
// {
|
// {
|
||||||
// url := createGiteaRepo(domain)
|
// url := createGiteaRepo(domain)
|
||||||
@@ -343,44 +176,3 @@ func stop() {
|
|||||||
// }
|
// }
|
||||||
// os.Exit(0)
|
// 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
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|||||||
48
server.go
48
server.go
@@ -4,11 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.nevets.tech/Steven/ezconf"
|
|
||||||
"github.com/go-git/go-billy/v5/memfs"
|
"github.com/go-git/go-billy/v5/memfs"
|
||||||
"github.com/go-git/go-git/v5/storage/memory"
|
"github.com/go-git/go-git/v5/storage/memory"
|
||||||
)
|
)
|
||||||
@@ -34,10 +32,12 @@ func getACMEManager() (*ACMEManager, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initServer() {
|
func initServer() {
|
||||||
err := loadDomainConfigs()
|
err := LoadDomainConfigs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error loading domain configs: %v", err)
|
log.Fatalf("Error loading domain configs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverTick()
|
||||||
}
|
}
|
||||||
|
|
||||||
func serverTick() {
|
func serverTick() {
|
||||||
@@ -53,18 +53,14 @@ func serverTick() {
|
|||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
mu.RLock()
|
localDomainConfigs := domainStore.Snapshot()
|
||||||
localDomainConfigs := make(map[string]*ezconf.Configuration, len(domainConfigs))
|
|
||||||
for k, v := range domainConfigs {
|
|
||||||
localDomainConfigs[k] = v
|
|
||||||
}
|
|
||||||
mu.RUnlock()
|
|
||||||
for domainStr, domainConfig := range localDomainConfigs {
|
for domainStr, domainConfig := range localDomainConfigs {
|
||||||
if !domainConfig.GetAsBoolean("Domain.enabled") {
|
if !domainConfig.GetBool("Domain.enabled") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
renewPeriod := domainConfig.GetAsInt("Certificates.renew_period")
|
renewPeriod := domainConfig.GetInt("Certificates.renew_period")
|
||||||
lastIssued := time.Unix(domainConfig.GetAsInt64("Internal.last_issued"), 0).UTC()
|
lastIssued := time.Unix(domainConfig.GetInt64("Internal.last_issued"), 0).UTC()
|
||||||
renewalDue := lastIssued.AddDate(0, 0, renewPeriod)
|
renewalDue := lastIssued.AddDate(0, 0, renewPeriod)
|
||||||
if now.After(renewalDue) {
|
if now.After(renewalDue) {
|
||||||
_, err = mgr.RenewForDomain(domainStr)
|
_, err = mgr.RenewForDomain(domainStr)
|
||||||
@@ -77,23 +73,19 @@ func serverTick() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = domainConfig.SetValueErr("Internal.last_issued", strconv.FormatInt(time.Now().UTC().Unix(), 10))
|
domainConfig.Set("Internal.last_issued", time.Now().UTC().Unix())
|
||||||
if err != nil {
|
err = domainConfig.WriteConfig()
|
||||||
fmt.Printf("Error updating last_issued config for domain %s: %v\n", domainStr, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = domainConfig.Save()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
|
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
|
||||||
continue
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("Error encrypting domain cert for domain %s: %v\n", domainStr, err)
|
fmt.Printf("Error encrypting domain cert for domain %s: %v\n", domainStr, err)
|
||||||
continue
|
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 {
|
if err != nil {
|
||||||
fmt.Printf("Error encrypting domain key for domain %s: %v\n", domainStr, err)
|
fmt.Printf("Error encrypting domain key for domain %s: %v\n", domainStr, err)
|
||||||
continue
|
continue
|
||||||
@@ -110,18 +102,14 @@ func serverTick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var repoUrl string
|
var repoUrl string
|
||||||
if !domainConfig.GetAsBoolean("Internal.repo_exists") {
|
if !domainConfig.GetBool("Internal.repo_exists") {
|
||||||
repoUrl = createGiteaRepo(domainStr, giteaClient)
|
repoUrl = createGiteaRepo(domainStr, giteaClient)
|
||||||
if repoUrl == "" {
|
if repoUrl == "" {
|
||||||
fmt.Printf("Error creating Gitea repo for domain %s\n", domainStr)
|
fmt.Printf("Error creating Gitea repo for domain %s\n", domainStr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = domainConfig.SetValueErr("Internal.repo_exists", "true")
|
domainConfig.Set("Internal.repo_exists", true)
|
||||||
if err != nil {
|
err = domainConfig.WriteConfig()
|
||||||
fmt.Printf("Error updating repo_exists config for domain %s: %v\n", domainStr, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = domainConfig.Save()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
|
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
|
||||||
continue
|
continue
|
||||||
@@ -133,7 +121,7 @@ func serverTick() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
err = cloneRepo(repoUrl, gitWorkspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error cloning repo for domain %s: %v\n", domainStr, err)
|
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)
|
fmt.Printf("Successfully pushed certificates for domain %s\n", domainStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveDomainConfigs()
|
SaveDomainConfigs()
|
||||||
}
|
}
|
||||||
|
|
||||||
func reloadServer() {
|
func reloadServer() {
|
||||||
fmt.Println("Reloading configs...")
|
fmt.Println("Reloading configs...")
|
||||||
err := loadDomainConfigs()
|
err := LoadDomainConfigs()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error loading domain configs: %v\n", err)
|
fmt.Printf("Error loading domain configs: %v\n", err)
|
||||||
|
|||||||
20
util.go
20
util.go
@@ -10,8 +10,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"git.nevets.tech/Steven/ezconf"
|
|
||||||
"github.com/google/go-github/v55/github"
|
"github.com/google/go-github/v55/github"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -22,7 +22,7 @@ var (
|
|||||||
|
|
||||||
type Domain struct {
|
type Domain struct {
|
||||||
name *string
|
name *string
|
||||||
config *ezconf.Configuration
|
config *viper.Viper
|
||||||
description *string
|
description *string
|
||||||
ghClient *github.Client
|
ghClient *github.Client
|
||||||
gtClient *gitea.Client
|
gtClient *gitea.Client
|
||||||
@@ -245,19 +245,19 @@ func sanitizeDomainKey(s string) string {
|
|||||||
return r.Replace(s)
|
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) {
|
func getDomainCertsDir(domain string) (string, error) {
|
||||||
domainConfig, exists := getDomainConfig(domain)
|
domainConfig, exists := domainStore.Get(domain)
|
||||||
if !exists {
|
if !exists {
|
||||||
return "", ConfigNotFound
|
return "", ErrConfigNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return getDomainCertsDirWConf(domain, domainConfig)
|
return getDomainCertsDirWConf(domain, domainConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDomainCertsDir Can return BlankConfigEntry or other errors
|
// getDomainCertsDir Can return ErrBlankConfigEntry or other errors
|
||||||
func getDomainCertsDirWConf(domain string, domainConfig *ezconf.Configuration) (string, error) {
|
func getDomainCertsDirWConf(domain string, domainConfig *viper.Viper) (string, error) {
|
||||||
effectiveDataRoot, err := getEffectiveString(domainConfig, "Certificates.data_root")
|
effectiveDataRoot, err := EffectiveString(domainConfig, "Certificates.data_root")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -265,7 +265,7 @@ func getDomainCertsDirWConf(domain string, domainConfig *ezconf.Configuration) (
|
|||||||
return filepath.Join(effectiveDataRoot, "certificates", domain), nil
|
return filepath.Join(effectiveDataRoot, "certificates", domain), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDomainCertsDirWOnlyConf(domainConfig *ezconf.Configuration) (string, error) {
|
func getDomainCertsDirWOnlyConf(domainConfig *viper.Viper) (string, error) {
|
||||||
domain := domainConfig.GetAsString("Domain.domain_name")
|
domain := domainConfig.GetString("Domain.domain_name")
|
||||||
return getDomainCertsDirWConf(domain, domainConfig)
|
return getDomainCertsDirWConf(domain, domainConfig)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user