package client import ( "fmt" "io" "log" "path/filepath" "strings" appShared "git.nevets.tech/Keys/certman/app/shared" "git.nevets.tech/Keys/certman/client" "git.nevets.tech/Keys/certman/common" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-git/v5/storage/memory" ) type Daemon struct{} func (d *Daemon) Init() { fmt.Println("Starting CertManager in client mode...") err := appShared.LoadDomainConfigs() if err != nil { log.Fatalf("Error loading domain configs: %v", err) } d.Tick() } func (d *Daemon) Tick() { fmt.Println("tick!") // Get local copy of configs config := appShared.Config() localDomainConfigs := appShared.DomainStore().Snapshot() // Loop over all domain configs (domains) for domainStr, domainConfig := range localDomainConfigs { // Skip non-enabled domains if !domainConfig.Domain.Enabled { continue } // Skip domains with up-to-date commit hashes // If the repo doesn't exist, we can't check for a remote commit, so stop the rest of the check repoExists := domainConfig.Internal.RepoExists if repoExists { var dataRoot string if domainConfig.Certificates.DataRoot == "" { config.Certificates.DataRoot = domainStr } else { dataRoot = domainConfig.Certificates.DataRoot } localHash, err := client.LocalCommitHash(domainStr, dataRoot) if err != nil { fmt.Printf("No local commit hash found for domain %s\n", domainStr) } gitSource, err := common.StrToGitSource(appShared.Config().Git.Host) if err != nil { fmt.Printf("Error getting git source for domain %s: %v\n", domainStr, err) continue } remoteHash, err := client.RemoteCommitHash(domainStr, gitSource, config, domainConfig) if err != nil { fmt.Printf("Error getting remote commit hash for domain %s: %v\n", domainStr, err) } // If both hashes are blank (errored), break // If localHash equals remoteHash (local is up-to-date), skip if !(localHash == "" && remoteHash == "") && localHash == remoteHash { fmt.Printf("Domain %s is up to date. Skipping...\n", domainStr) continue } } gitWorkspace := &common.GitWorkspace{ Storage: memory.NewStorage(), FS: memfs.New(), } // Ex: https://git.example.com/Org/Repo-suffix.git // Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?) repoUrl := appShared.Config().Git.Server + "/" + config.Git.OrgName + "/" + domainStr + domainConfig.Repo.RepoSuffix + ".git" err := common.CloneRepo(repoUrl, gitWorkspace, common.Client, config) if err != nil { fmt.Printf("Error cloning domain repo %s: %v\n", domainStr, err) continue } certsDir := appShared.DomainCertsDirWConf(domainStr, domainConfig) // Get files in repo fileInfos, err := gitWorkspace.FS.ReadDir("/") if err != nil { fmt.Printf("Error reading directory in memFS on domain %s: %v\n", domainStr, err) continue } // 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", domainStr, err) continue } fileBytes, err := io.ReadAll(file) if err != nil { fmt.Printf("Error reading file in memFS on domain %s: %v\n", domainStr, err) file.Close() continue } err = file.Close() if err != nil { fmt.Printf("Error closing file on domain %s: %v\n", domainStr, err) continue } err = common.DecryptFileFromBytes(domainConfig.Certificates.CryptoKey, fileBytes, filepath.Join(certsDir, filename), nil) if err != nil { fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domainStr, err) continue } headRef, err := gitWorkspace.Repo.Head() if err != nil { fmt.Printf("Error getting head reference for domain %s: %v\n", domainStr, err) continue } err = common.WriteCommitHash(headRef.Hash().String(), config, domainConfig) if err != nil { fmt.Printf("Error writing commit hash: %v\n", err) continue } certLinks := domainConfig.Certificates.CertSymlinks for _, certLink := range certLinks { err = common.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.Certificates.KeySymlinks for _, keyLink := range keyLinks { err = common.LinkFile(filepath.Join(certsDir, domainStr+".key"), keyLink, domainStr, ".key") if err != nil { fmt.Printf("Error linking cert %s to %s: %v\n", keyLink, domainStr, err) continue } } } } } } func (d *Daemon) Reload() { fmt.Println("Reloading configs...") err := appShared.LoadDomainConfigs() if err != nil { fmt.Printf("Error loading domain configs: %v\n", err) return } } func (d *Daemon) Stop() { fmt.Println("Shutting down client") }