[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

@@ -2,6 +2,7 @@ package server
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
@@ -14,107 +15,142 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport/http"
)
type GitWorkspace common.GitWorkspace
// serverIDFile is the filename inside the domain's git repo that records
// which server (by config.App.UUID) owns the repo. Only the owning server
// pushes to it; other servers must refuse.
const serverIDFile = "SERVER_ID"
func (ws *GitWorkspace) AddAndPushCerts(dataRoot, repoSuffix string, config *common.AppConfig) error {
certFiles, err := os.ReadDir(dataRoot)
// pushBranch is the branch server mode pushes to. The Gitea repo is created
// with this as its default branch; the client tracks the same name.
const pushBranch = "master"
// CreateRepo provisions the domain's remote repo via the configured provider
// and returns its clone URL.
func CreateRepo(config *common.AppConfig, domainConfig *common.DomainConfig, domain string) (string, error) {
provider, err := common.ProviderFor(config)
if err != nil {
fmt.Printf("Error reading from directory: %v\n", err)
return err
return "", err
}
for _, entry := range certFiles {
if strings.HasSuffix(entry.Name(), ".crpt") {
file, err := ws.FS.Create(entry.Name())
if err != nil {
fmt.Printf("Error copying file to memfs: %v\n", err)
return err
}
certFile, err := os.ReadFile(filepath.Join(dataRoot, entry.Name()))
if err != nil {
fmt.Printf("Error reading file to memfs: %v\n", err)
file.Close()
return err
}
_, err = file.Write(certFile)
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)
}
return provider.CreateRepo(domain, domainConfig)
}
// VerifyOwnership reads SERVER_ID from the cloned workspace and compares it
// against uuid. It returns:
//
// (true, nil) — SERVER_ID matches uuid (we own this repo).
// (false, nil) — SERVER_ID is missing (repo is unclaimed; safe to adopt).
// (false, err) — SERVER_ID names a different server (refuse to push).
//
// The caller decides what to do with an unclaimed repo; adoption must be an
// explicit decision, not a silent fall-through. AddAndPushCerts re-writes
// SERVER_ID on every push, so the first successful push after adoption
// claims the repo for this server.
func VerifyOwnership(ws *common.GitWorkspace, uuid string) (bool, error) {
f, err := ws.FS.Open(serverIDFile)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, fmt.Errorf("open SERVER_ID: %w", err)
}
defer f.Close()
file, err := ws.FS.Create("/SERVER_ID")
raw, err := io.ReadAll(f)
if err != nil {
fmt.Printf("Error creating file in memfs: %v\n", err)
return false, fmt.Errorf("read SERVER_ID: %w", err)
}
existing := strings.TrimSpace(string(raw))
if existing == uuid {
return true, nil
}
return false, fmt.Errorf("domain is owned by server %q", existing)
}
// AddAndPushCerts stages every *.crpt file from dataRoot into the workspace,
// (re-)writes SERVER_ID with config.App.UUID, commits any resulting change,
// and pushes to origin/<pushBranch>. If nothing changed the call is a no-op
// and returns nil without pushing.
func AddAndPushCerts(ws *common.GitWorkspace, dataRoot string, config *common.AppConfig) error {
if err := stageCerts(ws, dataRoot); err != nil {
return err
}
_, err = file.Write([]byte(config.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)
if err := stageFile(ws, serverIDFile, []byte(config.App.UUID)); err != nil {
return fmt.Errorf("stage SERVER_ID: %w", err)
}
status, err := ws.WorkTree.Status()
if err != nil {
fmt.Printf("Error getting repo status: %v\n", err)
return err
return fmt.Errorf("get worktree status: %w", err)
}
if status.IsClean() {
fmt.Printf("Repository is clean, skipping commit...\n")
return nil
}
fmt.Println("Work Tree Status:\n" + status.String())
signature := &object.Signature{
sig := &object.Signature{
Name: "Cert Manager",
Email: config.Certificates.Email,
When: time.Now(),
}
_, err = ws.WorkTree.Commit("Update "+ws.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
}
creds := &http.BasicAuth{
Username: config.Git.Username,
Password: config.Git.APIToken,
msg := fmt.Sprintf("Update %s @ %s", ws.Domain, time.Now().Format("Mon Jan _2 2006 15:04:05 MST"))
if _, err := ws.WorkTree.Commit(msg, &git.CommitOptions{Author: sig, Committer: sig}); err != nil {
return fmt.Errorf("commit: %w", err)
}
err = ws.Repo.Push(&git.PushOptions{
Auth: creds,
Auth: &http.BasicAuth{
Username: config.Git.Username,
Password: config.Git.APIToken,
},
Force: true,
RemoteName: "origin",
RefSpecs: []gitconf.RefSpec{
"refs/heads/master:refs/heads/master",
},
RefSpecs: []gitconf.RefSpec{gitconf.RefSpec("refs/heads/" + pushBranch + ":refs/heads/" + pushBranch)},
})
if err != nil {
fmt.Printf("Error pushing to origin: %v\n", err)
return err
return fmt.Errorf("push %s: %w", ws.URL, err)
}
return nil
}
// stageCerts copies every *.crpt file in dataRoot into the workspace
// filesystem and adds it to the work tree.
func stageCerts(ws *common.GitWorkspace, dataRoot string) error {
entries, err := os.ReadDir(dataRoot)
if err != nil {
return fmt.Errorf("read %s: %w", dataRoot, err)
}
for _, entry := range entries {
name := entry.Name()
if !strings.HasSuffix(name, ".crpt") {
continue
}
body, err := os.ReadFile(filepath.Join(dataRoot, name))
if err != nil {
return fmt.Errorf("read %s: %w", name, err)
}
if err := stageFile(ws, name, body); err != nil {
return fmt.Errorf("stage %s: %w", name, err)
}
}
return nil
}
// stageFile writes body to name in the workspace filesystem and adds it to
// the work tree. It is the single point where workspace-relative paths are
// constructed, so Create and Add always agree on the path.
func stageFile(ws *common.GitWorkspace, name string, body []byte) error {
f, err := ws.FS.Create(name)
if err != nil {
return fmt.Errorf("create: %w", err)
}
if _, err := f.Write(body); err != nil {
f.Close()
return fmt.Errorf("write: %w", err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("close: %w", err)
}
if _, err := ws.WorkTree.Add(f.Name()); err != nil {
return fmt.Errorf("git add: %w", err)
}
fmt.Println("Successfully uploaded to " + config.Git.Server + "/" + config.Git.OrgName + "/" + ws.Domain + repoSuffix + ".git")
return nil
}