diff --git a/certman.service b/certman.service new file mode 100644 index 0000000..c35f36e --- /dev/null +++ b/certman.service @@ -0,0 +1,34 @@ +[Unit] +Description=CertMan Certificate Manager Daemon +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple + +User=certman +Group=certman + +WorkingDirectory=/var/local/certman + +ExecStart=/usr/local/bin/certman daemon start +ExecReload=/usr/local/bin/certman daemon reload +ExecStop=/usr/local/bin/certman daemon stop + +Restart=on-failure +RestartSec=2s + +NoNewPrivileges=true +PrivateTmp=true +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true + +StandardOutput=journal +StandardError=journal + +TimeoutStartSec=30 +TimeoutStopSec=30 + +[Install] +WantedBy=multi-user.target diff --git a/client.go b/client.go index 9124ab6..c186707 100644 --- a/client.go +++ b/client.go @@ -121,6 +121,24 @@ func clientTick() { fmt.Printf("Error writing commit hash: %v\n", err) continue } + + certLinks := domainConfig.GetStringSlice("Certificates.cert_symlinks") + for _, certLink := range certLinks { + err = linkFile(filepath.Join(certsDir, domainStr+".crt"), certLink, domainStr, ".crt") + if err != nil { + fmt.Printf("Error linking cert %s to %s: %v\n", certLink, domainStr, err) + continue + } + } + + keyLinks := domainConfig.GetStringSlice("Certificates.key_symlinks") + for _, keyLink := range keyLinks { + err = linkFile(filepath.Join(certsDir, domainStr+".crt"), keyLink, domainStr, ".key") + if err != nil { + fmt.Printf("Error linking cert %s to %s: %v\n", keyLink, domainStr, err) + continue + } + } } } } diff --git a/commands.go b/commands.go index 518199b..40f7edd 100644 --- a/commands.go +++ b/commands.go @@ -4,16 +4,24 @@ import ( "context" "errors" "fmt" + "io" "log" "os" + "os/exec" "os/signal" + "os/user" + "path/filepath" + "strconv" + "strings" "syscall" "time" + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5/storage/memory" "github.com/spf13/cobra" ) -func devCmd(cmd *cobra.Command, args []string) { +func devFunc(cmd *cobra.Command, args []string) { testDomain := "lunamc.org" //config, err = ezconf.LoadConfiguration("/etc/certman/certman.conf") err := LoadConfig("/etc/certman/certman.conf") @@ -28,11 +36,11 @@ func devCmd(cmd *cobra.Command, args []string) { fmt.Println(testDomain) } -func versionCmd(cmd *cobra.Command, args []string) { +func versionResponse(cmd *cobra.Command, args []string) { fmt.Println("CertManager (certman) - Steven Tracey\nVersion: " + version + " build-" + build) } -func newKeyCmd(cmd *cobra.Command, args []string) { +func newKey(cmd *cobra.Command, args []string) { key, err := GenerateKey() if err != nil { log.Fatalf("%v", err) @@ -48,20 +56,304 @@ func newDomain(domain, domainDir string, dirOverridden bool) error { return err } createNewDomainCertsDir(domain, domainDir, dirOverridden) + + certmanUser, err := user.Lookup("certman") + if err != nil { + return fmt.Errorf("error getting user certman: %v", err) + } + uid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Uid)) + if err != nil { + return err + } + gid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Gid)) + if err != nil { + return err + } + err = ChownRecursive("/etc/certman/domains", uid, gid) + if err != nil { + return err + } + err = ChownRecursive("/var/local/certman", uid, gid) + if err != nil { + return err + } + fmt.Println("Successfully created domain entry for " + domain + "\nUpdate config file as needed in /etc/certman/domains/" + domain + ".conf\n") return nil } func install(isThin bool, mode string) error { if !isThin { + if os.Geteuid() != 0 { + return fmt.Errorf("installation must be run as root") + } + makeDirs() + createNewConfig(mode) + + f, err := os.OpenFile("/var/run/certman.pid", os.O_RDONLY|os.O_CREATE, 0755) + if err != nil { + return fmt.Errorf("error creating pid file: %v", err) + } + err = f.Close() + if err != nil { + return fmt.Errorf("error closing pid file: %v", err) + } + + newUserCmd := exec.Command("useradd", "-d", "/var/local/certman", "-U", "-r", "-s", "/sbin/nologin", "certman") + if output, err := newUserCmd.CombinedOutput(); err != nil { + return fmt.Errorf("error creating user: %v: output %s", err, output) + } + certmanUser, err := user.Lookup("certman") + if err != nil { + return fmt.Errorf("error getting user certman: %v", err) + } + uid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Uid)) + if err != nil { + return err + } + gid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Gid)) + if err != nil { + return err + } + err = ChownRecursive("/etc/certman", uid, gid) + if err != nil { + return fmt.Errorf("error changing uid/gid: %v", err) + } + err = ChownRecursive("/var/local/certman", uid, gid) + if err != nil { + return fmt.Errorf("error changing uid/gid: %v", err) + } + err = os.Chown("/var/run/certman.pid", uid, gid) + if err != nil { + return fmt.Errorf("error changing uid/gid: %v", err) + } + } else { + createNewConfig(mode) } - //config, err = ezconf.NewConfiguration(configFile, strings.ReplaceAll(defaultConfig, "{mode}", mode)) - createNewConfig(mode) return nil } -func runDaemonCmd() error { +func renewCertFunc(domain string, noPush bool) error { + err := LoadConfig("/etc/certman/certman.conf") + if err != nil { + return err + } + err = LoadDomainConfigs() + if err != nil { + return err + } + switch config.GetString("App.mode") { + case "server": + mgr, err = NewACMEManager() + if err != nil { + return err + } + err = renewCerts(domain, noPush) + if err != nil { + return err + } + return reloadDaemon() + case "client": + return pullCerts(domain) + default: + return fmt.Errorf("invalid operating mode %s", config.GetString("App.mode")) + } +} + +func renewCerts(domain string, noPush bool) error { + _, err := mgr.RenewForDomain(domain) + if err != nil { + // if no existing cert, obtain instead + _, err = mgr.ObtainForDomain(domain) + if err != nil { + return fmt.Errorf("error obtaining domain certificates for domain %s: %v", domain, err) + } + } + + domainConfig, exists := domainStore.Get(domain) + if !exists { + return fmt.Errorf("domain %s does not exist", domain) + } + + domainConfig.Set("Internal.last_issued", time.Now().UTC().Unix()) + err = WriteDomainConfig(domainConfig) + if err != nil { + return fmt.Errorf("error saving domain config %s: %v", domain, err) + } + + err = EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domain, domain+".crt"), filepath.Join(mgr.certsRoot, domain, domain+".crt.crpt"), nil) + if err != nil { + return fmt.Errorf("error encrypting domain cert for domain %s: %v", domain, err) + } + err = EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domain, domain+".key"), filepath.Join(mgr.certsRoot, domain, domain+".key.crpt"), nil) + if err != nil { + return fmt.Errorf("error encrypting domain key for domain %s: %v", domain, err) + } + + if !noPush { + giteaClient := createGiteaClient() + if giteaClient == nil { + return fmt.Errorf("error creating gitea client for domain %s: %v", domain, err) + } + gitWorkspace := &GitWorkspace{ + Storage: memory.NewStorage(), + FS: memfs.New(), + } + + var repoUrl string + if !domainConfig.GetBool("Internal.repo_exists") { + repoUrl = createGiteaRepo(domain, giteaClient) + if repoUrl == "" { + return fmt.Errorf("error creating Gitea repo for domain %s", domain) + } + domainConfig.Set("Internal.repo_exists", true) + err = WriteDomainConfig(domainConfig) + if err != nil { + return fmt.Errorf("error saving domain config %s: %v", domain, err) + } + + err = initRepo(repoUrl, gitWorkspace) + if err != nil { + return fmt.Errorf("error initializing repo for domain %s: %v", domain, err) + } + } else { + repoUrl = config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git" + err = cloneRepo(repoUrl, gitWorkspace) + if err != nil { + return fmt.Errorf("error cloning repo for domain %s: %v", domain, err) + } + } + + err = addAndPushCerts(domain, gitWorkspace) + if err != nil { + return fmt.Errorf("error pushing certificates for domain %s: %v", domain, err) + } + fmt.Printf("Successfully pushed certificates for domain %s\n", domain) + } + + return nil +} + +func pullCerts(domain string) error { + gitWorkspace := &GitWorkspace{ + Storage: memory.NewStorage(), + FS: memfs.New(), + } + + domainConfig, exists := domainStore.Get(domain) + if !exists { + return fmt.Errorf("domain %s does not exist", domain) + } + + // Ex: https://git.example.com/Org/Repo-suffix.git + // Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?) + repoUrl := config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git" + err := cloneRepo(repoUrl, gitWorkspace) + if err != nil { + return fmt.Errorf("Error cloning domain repo %s: %v\n", domain, err) + } + + certsDir, err := getDomainCertsDirWConf(domain, domainConfig) + if err != nil { + return fmt.Errorf("Error getting certificates dir for domain %s: %v\n", domain, err) + } + + // Get files in repo + fileInfos, err := gitWorkspace.FS.ReadDir("/") + if err != nil { + return fmt.Errorf("Error reading directory in memFS on domain %s: %v\n", domain, err) + } + // Iterate over files, filtering by .crpt (encrypted) files in case other files were accidentally added + for _, fileInfo := range fileInfos { + if strings.HasSuffix(fileInfo.Name(), ".crpt") { + filename, _ := strings.CutSuffix(fileInfo.Name(), ".crpt") + file, err := gitWorkspace.FS.Open(fileInfo.Name()) + if err != nil { + fmt.Printf("Error opening file in memFS on domain %s: %v\n", domain, err) + continue + } + fileBytes, err := io.ReadAll(file) + if err != nil { + fmt.Printf("Error reading file in memFS on domain %s: %v\n", domain, err) + file.Close() + continue + } + err = file.Close() + if err != nil { + fmt.Printf("Error closing file on domain %s: %v\n", domain, err) + continue + } + + err = DecryptFileFromBytes(domainConfig.GetString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil) + if err != nil { + fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domain, err) + continue + } + + headRef, err := gitWorkspace.Repo.Head() + if err != nil { + fmt.Printf("Error getting head reference for domain %s: %v\n", domain, err) + continue + } + + err = writeCommitHash(headRef.Hash().String(), domainConfig) + if err != nil { + fmt.Printf("Error writing commit hash: %v\n", err) + continue + } + + certLinks := domainConfig.GetStringSlice("Certificates.cert_symlinks") + for _, certLink := range certLinks { + if certLink == "" { + continue + } + linkInfo, err := os.Stat(certLink) + if err != nil { + if !os.IsNotExist(err) { + fmt.Printf("Error stating cert link %s: %v\n", certLink, err) + continue + } + } + if linkInfo.IsDir() { + certLink = filepath.Join(certLink, domain+".crt") + } + + err = os.Link(filepath.Join(certsDir, domain+".crt"), certLink) + if err != nil { + fmt.Printf("Error linking cert %s to %s: %v\n", certLink, domain, err) + continue + } + } + + keyLinks := domainConfig.GetStringSlice("Certificates.key_symlinks") + for _, keyLink := range keyLinks { + if keyLink == "" { + continue + } + linkInfo, err := os.Stat(keyLink) + if err != nil { + if !os.IsNotExist(err) { + fmt.Printf("Error stating key link %s: %v\n", keyLink, err) + continue + } + } + if linkInfo.IsDir() { + keyLink = filepath.Join(keyLink, domain+".crt") + } + + err = os.Link(filepath.Join(certsDir, domain+".crt"), keyLink) + if err != nil { + fmt.Printf("Error linking cert %s to %s: %v\n", keyLink, domain, err) + continue + } + } + } + } + return nil +} + +func runDaemon() error { err := createOrUpdatePIDFile("/var/run/certman.pid") if err != nil { if errors.Is(err, ErrorPIDInUse) { @@ -92,6 +384,10 @@ func runDaemonCmd() error { signal.Notify(reloadSigChan, syscall.SIGHUP) defer signal.Stop(reloadSigChan) + tickSigChan := make(chan os.Signal, 1) + signal.Notify(tickSigChan, syscall.SIGUSR1) + defer signal.Stop(tickSigChan) + tickRate := config.GetInt("App.tick_rate") ticker := time.NewTicker(time.Duration(tickRate) * time.Hour) defer ticker.Stop() @@ -112,6 +408,8 @@ func runDaemonCmd() error { reloadServer() case <-ticker.C: serverTick() + case <-tickSigChan: + serverTick() } } }() @@ -151,7 +449,7 @@ func stop() { clearPIDFile() } -func stopDaemonCmd() error { +func stopDaemon() error { proc, err := getDaemonProcess() if err != nil { return fmt.Errorf("error getting daemon process: %v", err) @@ -164,7 +462,7 @@ func stopDaemonCmd() error { return nil } -func reloadDaemonCmd() error { +func reloadDaemon() error { proc, err := getDaemonProcess() if err != nil { return fmt.Errorf("error getting daemon process: %v", err) @@ -177,7 +475,20 @@ func reloadDaemonCmd() error { return nil } -func statusDaemonCmd() error { +func tickDaemon() error { + proc, err := getDaemonProcess() + if err != nil { + return fmt.Errorf("error getting daemon process: %v", err) + } + + err = proc.Signal(syscall.SIGUSR1) + if err != nil { + return fmt.Errorf("error sending SIGUSR1 to daemon PID: %v", err) + } + return nil +} + +func statusDaemon() error { fmt.Println("Not implemented :/") return nil } diff --git a/config.go b/config.go index b771596..2048c92 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "errors" "fmt" "log" @@ -9,6 +10,7 @@ import ( "strings" "sync" + "github.com/google/uuid" "github.com/spf13/viper" ) @@ -76,7 +78,7 @@ var ( func LoadConfig(path string) error { config = viper.New() config.SetConfigFile(path) - config.SetConfigType("ini") + config.SetConfigType("toml") return config.ReadInConfig() } @@ -98,7 +100,7 @@ func LoadDomainConfigs() error { path := filepath.Join(dir, entry.Name()) v := viper.New() v.SetConfigFile(path) - v.SetConfigType("ini") + v.SetConfigType("toml") if err := v.ReadInConfig(); err != nil { return fmt.Errorf("loading %s: %w", path, err) @@ -124,13 +126,35 @@ func LoadDomainConfigs() error { // Saving // --------------------------------------------------------------------------- +func WriteConfig(filePath string, config *viper.Viper) error { + var buf bytes.Buffer + if err := config.WriteConfigTo(&buf); err != nil { + return fmt.Errorf("marshal config: %w", err) + } + + if err := os.WriteFile(filePath, buf.Bytes(), 0640); err != nil { + return fmt.Errorf("write config file: %w", err) + } + return nil +} + +func WriteMainConfig() error { + return WriteConfig("/etc/certman/certman.conf", config) +} + +func WriteDomainConfig(config *viper.Viper) error { + return WriteConfig(config.GetString("Domain.domain_name"), config) +} + // SaveDomainConfigs writes every loaded domain config back to disk. -func SaveDomainConfigs() { +func SaveDomainConfigs() error { for domain, v := range domainStore.Snapshot() { - if err := v.WriteConfig(); err != nil { - fmt.Printf("Error saving domain config %s: %v\n", domain, err) + err := WriteConfig("/etc/certman/domains/"+domain+".conf", v) + if err != nil { + return err } } + return nil } // --------------------------------------------------------------------------- @@ -217,8 +241,11 @@ func makeDirs() { } func createNewConfig(mode string) { - content := strings.NewReplacer("{mode}", mode).Replace(defaultConfig) - createFile("/etc/certman/certman.conf", 640, []byte(content)) + content := strings.NewReplacer( + "{mode}", mode, + "{uuid}", uuid.New().String(), + ).Replace(defaultConfig) + createFile("/etc/certman/certman.conf", 0640, []byte(content)) } func createNewDomainConfig(domain string) error { @@ -259,52 +286,49 @@ func createNewDomainCertsDir(domain string, dir string, dirOverride bool) { // --------------------------------------------------------------------------- const defaultConfig = `[App] -mode = {mode} +mode = "{mode}" tick_rate = 2 +uuid = "{uuid}" [Git] -host = gitea -server = https://gitea.instance.com -username = user -api_token = xxxxxxxxxxxxxxxxxxxxxxxxx -org_name = org +host = "gitea" +server = "https://gitea.instance.com" +username = "user" +api_token = "xxxxxxxxxxxxxxxxxxxxxxxxx" +org_name = "org" [Certificates] -email = user@example.com -data_root = /var/local/certman -ca_dir_url = https://acme-v02.api.letsencrypt.org/directory +email = "user@example.com" +data_root = "/var/local/certman" +ca_dir_url = "https://acme-v02.api.letsencrypt.org/directory" [Cloudflare] -cf_email = email@example.com -cf_api_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxx +cf_email = "email@example.com" +cf_api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx" ` const defaultDomainConfig = `[Domain] -domain_name = {domain} +domain_name = "{domain}" enabled = true -dns_server = default - +dns_server = "default" [Certificates] -data_root = +data_root = "" expiry = 90 -request_method = dns-01 +request_method = "dns-01" renew_period = 30 - -subdomains = -cert_symlink = -key_symlink = -crypto_key = {key} - +subdomains = [] +cert_symlinks = [] +key_symlinks = [] +crypto_key = "{key}" [Repo] -repo_suffix = -certificates - +repo_suffix = "-certificates" [Internal] last_issued = 0 repo_exists = false -status = clean +status = "clean" ` const readme = `` diff --git a/git.go b/git.go index f16a2c5..30b1fb8 100644 --- a/git.go +++ b/git.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -98,18 +99,6 @@ func createGiteaRepo(domain string, giteaClient *gitea.Client) string { fmt.Printf("Domain %s config does not exist\n", domain) return "" } - //options := gitea.CreateRepoFromTemplateOption{ - // Avatar: true, - // Description: "Certificates storage for " + domain, - // GitContent: true, - // GitHooks: true, - // Labels: true, - // Name: domain + domainConfig.GetAsString("Repo.repo_suffix"), - // Owner: config.GetAsString("Git.org_name"), - // Private: true, - // Topics: true, - // Webhooks: true, - //} options := gitea.CreateRepoOption{ Name: domain + domainConfig.GetString("Repo.repo_suffix"), Description: "Certificate storage for " + domain, @@ -174,6 +163,22 @@ func cloneRepo(url string, ws *GitWorkspace) error { fmt.Printf("Error getting worktree from cloned repo: %v\n", err) return err } + serverIdFile, err := ws.FS.OpenFile("/SERVER_ID", os.O_RDWR, 0640) + if err != nil { + if os.IsNotExist(err) { + fmt.Printf("Server ID file not found for %s, adopting domain\n", url) + return nil + } + return err + } + serverIdBytes, err := io.ReadAll(serverIdFile) + if err != nil { + return err + } + serverId := strings.TrimSpace(string(serverIdBytes)) + if serverId != config.GetString("App.uuid") { + return fmt.Errorf("domain is already managed by server with uuid %s", serverId) + } return nil } @@ -205,7 +210,7 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error { fmt.Printf("Error copying file to memfs: %v\n", err) return err } - certFile, err := os.ReadFile(filepath.Join(certsDir, file.Name())) + certFile, err := os.ReadFile(filepath.Join(certsDir, entry.Name())) if err != nil { fmt.Printf("Error reading file to memfs: %v\n", err) file.Close() @@ -228,6 +233,29 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error { fmt.Printf("Error closing file: %v\n", err) } } + + file, err := ws.FS.Create("/SERVER_ID") + if err != nil { + fmt.Printf("Error creating file in memfs: %v\n", err) + return err + } + _, err = file.Write([]byte(config.GetString("App.uuid"))) + if err != nil { + fmt.Printf("Error writing to memfs: %v\n", err) + file.Close() + return err + } + _, err = ws.WorkTree.Add(file.Name()) + if err != nil { + fmt.Printf("Error adding file %v: %v\n", file.Name(), err) + file.Close() + return err + } + err = file.Close() + if err != nil { + fmt.Printf("Error closing file: %v\n", err) + } + } status, err := ws.WorkTree.Status() @@ -246,7 +274,7 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error { Email: config.GetString("Certificates.email"), When: time.Now(), } - commitHash, err := ws.WorkTree.Commit("Update "+domain+" @ "+time.Now().Format("Mon Jan _2 2006 15:04:05 MST"), &git.CommitOptions{Author: signature, Committer: signature}) + _, err = ws.WorkTree.Commit("Update "+domain+" @ "+time.Now().Format("Mon Jan _2 2006 15:04:05 MST"), &git.CommitOptions{Author: signature, Committer: signature}) if err != nil { fmt.Printf("Error committing certs: %v\n", err) return err @@ -270,12 +298,6 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error { fmt.Println("Successfully uploaded to " + config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git") - err = writeCommitHash(commitHash.String(), domainConfig) - if err != nil { - fmt.Printf("Error writing commit hash: %v\n", err) - return err - } - return nil } diff --git a/go.mod b/go.mod index d4de304..b802774 100644 --- a/go.mod +++ b/go.mod @@ -1,63 +1,58 @@ module main -go 1.24.0 - -toolchain go1.24.7 +go 1.25.0 require ( - code.gitea.io/sdk/gitea v0.15.1 - github.com/go-acme/lego/v4 v4.26.0 - github.com/go-git/go-billy/v5 v5.4.1 - github.com/go-git/go-git/v5 v5.7.0 + code.gitea.io/sdk/gitea v0.23.2 + github.com/go-acme/lego/v4 v4.32.0 + github.com/go-git/go-billy/v5 v5.8.0 + github.com/go-git/go-git/v5 v5.17.0 github.com/google/go-github/v55 v55.0.0 + github.com/google/uuid v1.6.0 github.com/spf13/cobra v1.10.2 - github.com/spf13/viper v1.18.2 - golang.org/x/crypto v0.42.0 + github.com/spf13/viper v1.21.0 + golang.org/x/crypto v0.48.0 ) require ( - github.com/Microsoft/go-winio v0.5.2 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect - github.com/acomagu/bufpipe v1.0.4 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/42wim/httpsig v1.2.3 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-jose/go-jose/v4 v4.1.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/go-querystring v1.1.0 // indirect - github.com/hashicorp/go-version v1.7.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/imdario/mergo v0.3.15 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/go-querystring v1.2.0 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/miekg/dns v1.1.68 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/pjbgf/sha1cd v0.3.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sergi/go-diff v1.3.1 // indirect - github.com/skeema/knownhosts v1.1.1 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/pflag v1.0.9 // indirect + github.com/kevinburke/ssh_config v1.6.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/miekg/dns v1.1.72 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/sergi/go-diff v1.4.0 // indirect + github.com/skeema/knownhosts v1.3.2 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.44.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/tools v0.36.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2fcbd67..829c7e4 100644 --- a/go.sum +++ b/go.sum @@ -1,171 +1,147 @@ -code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= -code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M= -code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA= -github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= +code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= +github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= 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/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= -github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= -github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= 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/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= 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.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= -github.com/go-acme/lego/v4 v4.26.0 h1:521aEQxNstXvPQcFDDPrJiFfixcCQuvAvm35R4GbyYA= -github.com/go-acme/lego/v4 v4.26.0/go.mod h1:BQVAWgcyzW4IT9eIKHY/RxYlVhoyKyOMXOkq7jK1eEQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-acme/lego/v4 v4.32.0 h1:z7Ss7aa1noabhKj+DBzhNCO2SM96xhE3b0ucVW3x8Tc= +github.com/go-acme/lego/v4 v4.32.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= -github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= -github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= -github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM= +github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= -github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= +github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= -github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= -github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= +github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= +github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= -github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= +github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= 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/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -173,49 +149,27 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 9843be7..4f1b7e9 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,14 @@ import ( "context" "fmt" "os" + "regexp" "sync" "github.com/spf13/cobra" ) var version = "1.0.0" -var build = "2" +var build = "1" var ( configFile string @@ -19,6 +20,8 @@ var ( wg sync.WaitGroup ) +var fqdnRegex = regexp.MustCompile(`^(?i:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$`) + //TODO create logic for gh vs gt repos func main() { @@ -33,9 +36,9 @@ func main() { rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "/etc/certman/certman.conf", "Configuration file") - rootCmd.AddCommand(basicCmd("version", "Show version", versionCmd)) - rootCmd.AddCommand(basicCmd("gen-key", "Generates encryption key", newKeyCmd)) - rootCmd.AddCommand(basicCmd("dev", "Dev Function", devCmd)) + rootCmd.AddCommand(basicCmd("version", "Show version", versionResponse)) + rootCmd.AddCommand(basicCmd("gen-key", "Generates encryption key", newKey)) + rootCmd.AddCommand(basicCmd("dev", "Dev Function", devFunc)) var domainCertDir string newDomainCmd := &cobra.Command{ @@ -72,6 +75,28 @@ func main() { installCmd.Flags().BoolVarP(&thinInstallFlag, "thin", "t", false, "Thin install (skip creating dirs)") rootCmd.AddCommand(installCmd) + certCmd := &cobra.Command{ + Use: "cert", + Short: "Certificate management", + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + + var noPush bool + renewCertCmd := &cobra.Command{ + Use: "renew", + Short: "Renews a domains certificate", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return renewCertFunc(args[0], noPush) + }, + } + renewCertCmd.Flags().BoolVar(&noPush, "no-push", false, "Don't push certs to repo, renew locally only [server mode only]") + certCmd.AddCommand(renewCertCmd) + + rootCmd.AddCommand(certCmd) + daemonCmd := &cobra.Command{ Use: "daemon", Short: "Daemon management", @@ -85,7 +110,7 @@ func main() { Short: "Start the daemon", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return runDaemonCmd() + return runDaemon() }, }) @@ -94,7 +119,7 @@ func main() { Short: "Stop the daemon", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return stopDaemonCmd() + return stopDaemon() }, }) @@ -103,7 +128,16 @@ func main() { Short: "Reload daemon configs", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return reloadDaemonCmd() + return reloadDaemon() + }, + }) + + daemonCmd.AddCommand(&cobra.Command{ + Use: "tick", + Short: "Manually triggers daemon tick", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + return tickDaemon() }, }) @@ -112,7 +146,7 @@ func main() { Short: "Show daemon status", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - return statusDaemonCmd() + return statusDaemon() }, }) @@ -132,47 +166,6 @@ func basicCmd(use, short string, commandFunc func(cmd *cobra.Command, args []str } } -// case "gen": -// { -// url := createGiteaRepo(domain) -// if url == "" { -// return -// } -// gitWorkspace.Repo, gitWorkspace.WorkTree = cloneRepo(url, gitWorkspace) -// if gitWorkspace.Repo == nil { -// return -// } -// cmd = exec.Command("lego", legoNewSiteArgs...) -// } -// case "renew": -// { -// gitWorkspace.Repo, gitWorkspace.WorkTree = cloneRepo(config.GetAsString("Git.server")+"/"+config.GetAsString("Git.org_name")+"/"+domain+"-certificates.git", gitWorkspace) -// if gitWorkspace.Repo == nil { -// return -// } -// cmd = exec.Command("lego", legoRenewSiteArgs...) -// } -// case "gen-cert-only": -// { -// cmd = exec.Command("lego", legoNewSiteArgs...) -// } -// case "renew-cert-only": -// { -// cmd = exec.Command("lego", legoRenewSiteArgs...) -// } -// case "git": -// { -// url := createGiteaRepo(domain) -// if url == "" { -// return -// } -// gitWorkspace.Repo, gitWorkspace.WorkTree = cloneRepo(url, gitWorkspace) -// if gitWorkspace.Repo == nil { -// return -// } -// err := addAndPushCerts(domain, gitWorkspace) -// if err != nil { -// return -// } -// os.Exit(0) -// } +func IsValidFQDN(domain string) bool { + return len(domain) <= 253 && fqdnRegex.MatchString(domain) +} diff --git a/server.go b/server.go index 7132056..e6353dc 100644 --- a/server.go +++ b/server.go @@ -63,6 +63,7 @@ func serverTick() { lastIssued := time.Unix(domainConfig.GetInt64("Internal.last_issued"), 0).UTC() renewalDue := lastIssued.AddDate(0, 0, renewPeriod) if now.After(renewalDue) { + //TODO extra check if certificate expiry (create cache?) _, err = mgr.RenewForDomain(domainStr) if err != nil { // if no existing cert, obtain instead @@ -74,7 +75,7 @@ func serverTick() { } domainConfig.Set("Internal.last_issued", time.Now().UTC().Unix()) - err = domainConfig.WriteConfig() + err = WriteDomainConfig(domainConfig) if err != nil { fmt.Printf("Error saving domain config %s: %v\n", domainStr, err) continue @@ -109,7 +110,7 @@ func serverTick() { continue } domainConfig.Set("Internal.repo_exists", true) - err = domainConfig.WriteConfig() + err = WriteDomainConfig(domainConfig) if err != nil { fmt.Printf("Error saving domain config %s: %v\n", domainStr, err) continue @@ -137,7 +138,10 @@ func serverTick() { fmt.Printf("Successfully pushed certificates for domain %s\n", domainStr) } } - SaveDomainConfigs() + err = SaveDomainConfigs() + if err != nil { + fmt.Printf("Error saving domain configs: %v\n", err) + } } func reloadServer() { diff --git a/util.go b/util.go index 8889959..b7d2b2c 100644 --- a/util.go +++ b/util.go @@ -3,6 +3,7 @@ package main import ( "errors" "fmt" + "io/fs" "os" "path/filepath" "strconv" @@ -15,9 +16,9 @@ import ( ) var ( - ErrorPIDInUse = errors.New("daemon is already running") - ErrLockFailed = errors.New("failed to acquire a lock on the PID file") - ErrRepoNotInit = errors.New("repo not initialized") + ErrorPIDInUse = errors.New("daemon is already running") + ErrLockFailed = errors.New("failed to acquire a lock on the PID file") + ErrBlankCert = errors.New("cert is blank") ) type Domain struct { @@ -208,10 +209,22 @@ func createFile(fileName string, filePermission os.FileMode, data []byte) { } } -func linkFile(source, target string) error { - err := os.Symlink(source, target) +func linkFile(source, target, domain, extension string) error { + if target == "" { + return ErrBlankCert + } + linkInfo, err := os.Stat(target) + if err != nil { + if !os.IsNotExist(err) { + return err + } + } + if linkInfo.IsDir() { + target = filepath.Join(target, domain+extension) + } + + err = os.Symlink(source, target) if err != nil { - fmt.Println("Error creating symlink:", err) return err } return nil @@ -269,3 +282,13 @@ func getDomainCertsDirWOnlyConf(domainConfig *viper.Viper) (string, error) { domain := domainConfig.GetString("Domain.domain_name") return getDomainCertsDirWConf(domain, domainConfig) } + +func ChownRecursive(path string, uid, gid int) error { + return filepath.WalkDir(path, func(name string, d fs.DirEntry, err error) error { + if err != nil { + return err // Stop if we encounter a permission error on a specific file + } + // Apply ownership change to the current item + return os.Chown(name, uid, gid) + }) +}