[CI-SKIP] Upload current
This commit is contained in:
182
server/git.go
182
server/git.go
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user