[CI-SKIP] Upload current

This commit is contained in:
2026-04-24 10:37:46 -04:00
parent 6aacbfbb71
commit fb1abd6211
12 changed files with 519 additions and 579 deletions

View File

@@ -1,6 +1,7 @@
package client
import (
"errors"
"fmt"
"io"
"os"
@@ -10,100 +11,118 @@ import (
"git.nevets.tech/Steven/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)
// DecryptAndWriteCertificates walks the workspace's root directory, decrypts
// every *.crpt file using the domain's crypto key, and writes the cleartext
// output into certsDir.
//
// On a fully successful pass it records the current HEAD commit SHA via
// WriteCommitHash, so the next tick can short-circuit when nothing changed.
// Per-file failures are collected and returned together; the commit-hash
// marker is only written when every file decrypted cleanly, so a partial
// sync never masquerades as up-to-date on the next tick.
func DecryptAndWriteCertificates(certsDir string, domainConfig *common.DomainConfig, ws *common.GitWorkspace) error {
entries, err := ws.FS.ReadDir("/")
if err != nil {
return fmt.Errorf("Error cloning domain repo %s: %v\n", gitWorkspace.Domain, err)
return fmt.Errorf("read workspace root: %w", 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
}
var errs []error
var wrote int
for _, entry := range entries {
name := entry.Name()
if !strings.HasSuffix(name, ".crpt") {
continue
}
plainName, _ := strings.CutSuffix(name, ".crpt")
data, err := readWorkspaceFile(ws, name)
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", name, err))
continue
}
if err := common.DecryptFileFromBytes(domainConfig.Certificates.CryptoKey, data, filepath.Join(certsDir, plainName), nil); err != nil {
errs = append(errs, fmt.Errorf("%s: decrypt: %w", name, err))
continue
}
wrote++
}
return nil
if len(errs) > 0 {
return errors.Join(errs...)
}
if wrote == 0 {
return nil
}
head, err := ws.Repo.Head()
if err != nil {
return fmt.Errorf("get repo head: %w", err)
}
return WriteCommitHash(certsDir, head.Hash().String())
}
// DecryptCertificates is a standalone utility that decrypts every *.crpt
// file in an on-disk directory. It is used by the `cert decrypt` CLI command
// and does not touch git state.
func DecryptCertificates(certPath, cryptoKey string) error {
// Get files in repo
fileInfos, err := os.ReadDir(certPath)
entries, err := os.ReadDir(certPath)
if err != nil {
return fmt.Errorf("error reading directory: %v", err)
return fmt.Errorf("read %s: %w", certPath, 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
}
var errs []error
for _, entry := range entries {
name := entry.Name()
if !strings.HasSuffix(name, ".crpt") {
continue
}
plainName, _ := strings.CutSuffix(name, ".crpt")
err = common.DecryptFileFromBytes(cryptoKey, fileBytes, filepath.Join(certPath, filename), nil)
if err != nil {
fmt.Printf("Error decrypting file %s: %v\n", filename, err)
continue
}
data, err := os.ReadFile(filepath.Join(certPath, name))
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", name, err))
continue
}
if err := common.DecryptFileFromBytes(cryptoKey, data, filepath.Join(certPath, plainName), nil); err != nil {
errs = append(errs, fmt.Errorf("%s: decrypt: %w", name, err))
continue
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
// UpdateSymlinks refreshes every configured cert and key symlink so it
// points at the domain's current cert/key files under certsDir. It reports
// all link failures together rather than stopping at the first one.
func UpdateSymlinks(domain string, domainConfig *common.DomainConfig, certsDir string) error {
var errs []error
for _, link := range domainConfig.Certificates.CertSymlinks {
if err := common.LinkFile(filepath.Join(certsDir, domain+".crt"), link, domain, ".crt"); err != nil {
errs = append(errs, fmt.Errorf("cert link %s: %w", link, err))
}
}
for _, link := range domainConfig.Certificates.KeySymlinks {
if err := common.LinkFile(filepath.Join(certsDir, domain+".key"), link, domain, ".key"); err != nil {
errs = append(errs, fmt.Errorf("key link %s: %w", link, err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
func readWorkspaceFile(ws *common.GitWorkspace, name string) ([]byte, error) {
f, err := ws.FS.Open(name)
if err != nil {
return nil, fmt.Errorf("open: %w", err)
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("read: %w", err)
}
return data, nil
}