[CI-SKIP] Upload current
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -10,52 +10,42 @@ import (
|
||||
"git.nevets.tech/Steven/certman/common"
|
||||
)
|
||||
|
||||
func WriteCommitHash(hash, dataRoot string) error {
|
||||
//TODO: unfuck this logic, maybe use a domain struct with a flag for non-standard data root?
|
||||
//var dataRoot string
|
||||
//if domainConfig.Certificates.DataRoot == "" {
|
||||
// dataRoot = filepath.Join(config.Certificates.DataRoot, "certificates", domainConfig.Domain.DomainName)
|
||||
//} else {
|
||||
// dataRoot = domainConfig.Certificates.DataRoot
|
||||
//}
|
||||
// hashFile is the filename inside a domain's local data root that records
|
||||
// the last remote commit SHA the client successfully synced from. The daemon
|
||||
// compares it against the remote HEAD to decide whether a sync is needed.
|
||||
const hashFile = "hash"
|
||||
|
||||
err := os.WriteFile(filepath.Join(dataRoot, "hash"), []byte(hash), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// defaultBranch is the branch the client tracks on the remote repo.
|
||||
const defaultBranch = "master"
|
||||
|
||||
return nil
|
||||
// WriteCommitHash persists hash to <certsDir>/hash. Call it after a
|
||||
// successful sync so the next tick can skip a no-op.
|
||||
func WriteCommitHash(certsDir, hash string) error {
|
||||
return os.WriteFile(filepath.Join(certsDir, hashFile), []byte(hash), 0o644)
|
||||
}
|
||||
|
||||
func LocalCommitHash(domain string, certsDir string) (string, error) {
|
||||
data, err := os.ReadFile(filepath.Join(certsDir, "hash"))
|
||||
// LocalCommitHash returns the commit SHA recorded at <certsDir>/hash. A
|
||||
// missing file is not an error: it returns "" so a fresh client falls
|
||||
// through to the full sync path.
|
||||
func LocalCommitHash(certsDir string) (string, error) {
|
||||
data, err := os.ReadFile(filepath.Join(certsDir, hashFile))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Printf("Error reading file for domain %s: %v\n", domain, err)
|
||||
return "", err
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return "", nil
|
||||
}
|
||||
return "", fmt.Errorf("read hash file: %w", 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)
|
||||
// RemoteCommitHash returns the current HEAD commit SHA of the domain's repo
|
||||
// on the configured git host. It returns common.ErrRepoNotFound if the repo
|
||||
// does not exist yet, letting the daemon handle the "not provisioned" case
|
||||
// without string-matching errors.
|
||||
func RemoteCommitHash(config *common.AppConfig, domainConfig *common.DomainConfig, domain string) (string, error) {
|
||||
provider, err := common.ProviderFor(config)
|
||||
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
|
||||
return provider.HeadCommit(domain, defaultBranch, domainConfig)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user