Major Refactoring, Client can now be used as a library
Some checks failed
Build (artifact) / build (push) Failing after 1m3s

This commit is contained in:
2026-03-16 21:48:32 +01:00
parent e6a2ba2f8b
commit e0f68788c0
45 changed files with 1359 additions and 1245 deletions

109
client/certificates.go Normal file
View File

@@ -0,0 +1,109 @@
package client
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"git.nevets.tech/Keys/certman/common"
)
func PullCerts(config *common.AppConfig, domainConfig *common.DomainConfig, gitWorkspace *common.GitWorkspace) error {
// Ex: https://git.example.com/Org/Repo-suffix.git
// Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?)
repoUrl := config.Git.Server + "/" + config.Git.OrgName + "/" + gitWorkspace.Domain + domainConfig.Repo.RepoSuffix + ".git"
err := common.CloneRepo(repoUrl, gitWorkspace, common.Client, config)
if err != nil {
return fmt.Errorf("Error cloning domain repo %s: %v\n", gitWorkspace.Domain, err)
}
return nil
}
func DecryptAndWriteCertificates(certsDir string, config *common.AppConfig, domainConfig *common.DomainConfig, gitWorkspace *common.GitWorkspace) error {
// 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", gitWorkspace.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", gitWorkspace.Domain, err)
continue
}
fileBytes, err := io.ReadAll(file)
if err != nil {
fmt.Printf("Error reading file in memFS on domain %s: %v\n", gitWorkspace.Domain, err)
file.Close()
continue
}
err = file.Close()
if err != nil {
fmt.Printf("Error closing file on domain %s: %v\n", gitWorkspace.Domain, 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, gitWorkspace.Domain, err)
continue
}
headRef, err := gitWorkspace.Repo.Head()
if err != nil {
fmt.Printf("Error getting head reference for domain %s: %v\n", gitWorkspace.Domain, err)
continue
}
err = common.WriteCommitHash(headRef.Hash().String(), config, domainConfig)
if err != nil {
fmt.Printf("Error writing commit hash: %v\n", err)
continue
}
}
}
return nil
}
func DecryptCertificates(certPath, cryptoKey string) error {
// Get files in repo
fileInfos, err := os.ReadDir(certPath)
if err != nil {
return fmt.Errorf("error reading directory: %v", 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 := os.OpenFile(fileInfo.Name(), os.O_RDONLY, 0640)
if err != nil {
fmt.Printf("Error opening file: %v\n", err)
continue
}
fileBytes, err := io.ReadAll(file)
if err != nil {
fmt.Printf("Error reading file: %v\n", err)
file.Close()
continue
}
err = file.Close()
if err != nil {
fmt.Printf("Error closing file: %v\n", err)
continue
}
err = common.DecryptFileFromBytes(cryptoKey, fileBytes, filepath.Join(certPath, filename), nil)
if err != nil {
fmt.Printf("Error decrypting file %s: %v\n", filename, err)
continue
}
}
}
return nil
}

View File

@@ -1,161 +0,0 @@
package client
import (
"fmt"
"io"
"log"
"path/filepath"
"strings"
"git.nevets.tech/Keys/CertManager/internal"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5/storage/memory"
)
func Init() {
err := internal.LoadDomainConfigs()
if err != nil {
log.Fatalf("Error loading domain configs: %v", err)
}
Tick()
}
func Tick() {
fmt.Println("Tick!")
// Get local copy of configs
config := internal.Config()
localDomainConfigs := internal.DomainStore().Snapshot()
// Loop over all domain configs (domains)
for domainStr, domainConfig := range localDomainConfigs {
// Skip non-enabled domains
if !domainConfig.GetBool("Domain.enabled") {
continue
}
// Skip domains with up-to-date commit hashes
// If the repo doesn't exist, we can't check for a remote commit, so stop the rest of the check
repoExists := domainConfig.GetBool("Internal.repo_exists")
if repoExists {
localHash, err := internal.LocalCommitHash(domainStr)
if err != nil {
fmt.Printf("No local commit hash found for domain %s\n", domainStr)
}
gitSource, err := internal.StrToGitSource(internal.Config().GetString("Git.host"))
if err != nil {
fmt.Printf("Error getting git source for domain %s: %v\n", domainStr, err)
continue
}
remoteHash, err := internal.RemoteCommitHash(domainStr, gitSource)
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 := &internal.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 := internal.Config().GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domainStr + domainConfig.GetString("Repo.repo_suffix") + ".git"
err := internal.CloneRepo(repoUrl, gitWorkspace, internal.Client)
if err != nil {
fmt.Printf("Error cloning domain repo %s: %v\n", domainStr, err)
continue
}
certsDir, err := internal.DomainCertsDirWConf(domainStr, domainConfig)
if err != nil {
fmt.Printf("Error getting certificates dir for domain %s: %v\n", domainStr, err)
continue
}
// 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 = internal.DecryptFileFromBytes(domainConfig.GetString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil)
if err != nil {
fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domainStr, err)
continue
}
headRef, err := gitWorkspace.Repo.Head()
if err != nil {
fmt.Printf("Error getting head reference for domain %s: %v\n", domainStr, err)
continue
}
err = internal.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 {
err = internal.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 = internal.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 Reload() {
fmt.Println("Reloading configs...")
err := internal.LoadDomainConfigs()
if err != nil {
fmt.Printf("Error loading domain configs: %v\n", err)
return
}
}
func Stop() {
fmt.Println("Shutting down client")
}

44
client/git.go Normal file
View File

@@ -0,0 +1,44 @@
package client
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"git.nevets.tech/Keys/certman/common"
)
func LocalCommitHash(domain string, certsDir string) (string, error) {
data, err := os.ReadFile(filepath.Join(certsDir, "hash"))
if err != nil {
if !os.IsNotExist(err) {
fmt.Printf("Error reading file for domain %s: %v\n", domain, err)
return "", err
}
}
return strings.TrimSpace(string(data)), nil
}
func RemoteCommitHash(domain string, gitSource common.GitSource, config *common.AppConfig, domainConfig *common.DomainConfig) (string, error) {
switch gitSource {
case common.Gitea:
return getRemoteCommitHashGitea(config.Git.OrgName, domain+domainConfig.Repo.RepoSuffix, "master", config)
default:
fmt.Printf("Unimplemented git source %v\n", gitSource)
return "", errors.New("unimplemented git source")
}
}
func getRemoteCommitHashGitea(org, repo, branchName string, config *common.AppConfig) (string, error) {
giteaClient := common.CreateGiteaClient(config)
branch, _, err := giteaClient.GetRepoBranch(org, repo, branchName)
if err != nil {
fmt.Printf("Error getting repo branch: %v\n", err)
return "", err
}
//TODO catch repo not found as ErrRepoNotInit
return branch.Commit.ID, nil
}

View File

@@ -1,49 +0,0 @@
package client
import (
"context"
"fmt"
"log"
"time"
"git.nevets.tech/Keys/CertManager/internal"
pb "git.nevets.tech/Keys/CertManager/proto/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func SendHook(domain string) {
conn, err := grpc.NewClient(
"unix:///run/certman.sock",
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
log.Fatalf("fail to dial: %v", err)
}
defer conn.Close()
client := pb.NewHookServiceClient(conn)
hooks, err := internal.PostPullHooks(domain)
if err != nil {
fmt.Printf("Error getting hooks: %v\n", err)
return
}
for _, hook := range hooks {
sendHook(client, hook)
}
}
func sendHook(client pb.HookServiceClient, hook *pb.Hook) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
res, err := client.ExecuteHook(ctx, &pb.ExecuteHookRequest{Hook: hook})
if err != nil {
fmt.Printf("Error executing hook: %v\n", err)
return
}
if res.GetError() != "" {
fmt.Printf("Error executing hook: %s\n", res.GetError())
}
}