package common import ( "errors" "fmt" "io" "os" "path/filepath" "strings" "time" "code.gitea.io/sdk/gitea" "github.com/go-git/go-billy/v5" "github.com/go-git/go-git/v5" gitconf "github.com/go-git/go-git/v5/config" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/storage/memory" ) type CertManMode int const ( Server CertManMode = iota Client ) type GitWorkspace struct { Domain string Repo *git.Repository Storage *memory.Storage FS billy.Filesystem WorkTree *git.Worktree } type GitSource int const ( Github GitSource = iota Gitlab Gitea Gogs Bitbucket CodeCommit ) var GitSourceName = map[GitSource]string{ Github: "github", Gitlab: "gitlab", Gitea: "gitea", Gogs: "gogs", Bitbucket: "bitbucket", CodeCommit: "code-commit", } func StrToGitSource(s string) (GitSource, error) { for k, v := range GitSourceName { if v == s { return k, nil } } return GitSource(0), errors.New("invalid gitsource name") } //func createGithubClient() *github.Client { // return github.NewClient(nil).WithAuthToken(config.GetString("Git.api_token")) //} func CreateGiteaClient(config *AppConfig) *gitea.Client { client, err := gitea.NewClient(config.Git.Server, gitea.SetToken(config.Git.APIToken)) if err != nil { fmt.Printf("Error connecting to gitea instance: %v\n", err) return nil } return client } //func createGithubRepo(domain *Domain, Client *github.Client) string { // name := domain.name // owner := domain.config.GetString("Repo.owner") // description := domain.description // private := true // includeAllBranches := false // // ctx := context.Background() // template := &github.TemplateRepoRequest{ // Name: name, // Owner: &owner, // Description: description, // Private: &private, // IncludeAllBranches: &includeAllBranches, // } // repo, _, err := Client.Repositories.CreateFromTemplate(ctx, config.GetString("Git.org_name"), config.GetString("Git.template_name"), template) // if err != nil { // fmt.Println("Error creating repository from template,", err) // return "" // } // return *repo.CloneURL //} func CreateGiteaRepo(domain string, giteaClient *gitea.Client, config *AppConfig, domainConfig *DomainConfig) string { options := gitea.CreateRepoOption{ Name: domain + domainConfig.Repo.RepoSuffix, Description: "Certificate storage for " + domain, Private: true, IssueLabels: "", AutoInit: false, Template: false, Gitignores: "", License: "", Readme: "", DefaultBranch: "master", TrustModel: gitea.TrustModelDefault, } giteaRepo, _, err := giteaClient.CreateOrgRepo(config.Git.OrgName, options) if err != nil { fmt.Printf("Error creating repo: %v\n", err) return "" } return giteaRepo.CloneURL } func InitRepo(url string, ws *GitWorkspace) error { var err error ws.Repo, err = git.Init(ws.Storage, ws.FS) if err != nil { fmt.Printf("Error initializing local repo: %v\n", err) return err } _, err = ws.Repo.CreateRemote(&gitconf.RemoteConfig{ Name: "origin", URLs: []string{url}, }) if err != nil && !errors.Is(err, git.ErrRemoteExists) { fmt.Printf("Error creating remote origin repo: %v\n", err) return err } ws.WorkTree, err = ws.Repo.Worktree() if err != nil { fmt.Printf("Error getting worktree from local repo: %v\n", err) return err } return nil } func CloneRepo(url string, ws *GitWorkspace, certmanMode CertManMode, config *AppConfig) error { creds := &http.BasicAuth{ Username: config.Git.Username, Password: config.Git.APIToken, } var err error ws.Repo, err = git.Clone(ws.Storage, ws.FS, &git.CloneOptions{URL: url, Auth: creds}) if err != nil { fmt.Printf("Error cloning repo: %v\n", err) } ws.WorkTree, err = ws.Repo.Worktree() if err != nil { fmt.Printf("Error getting worktree from cloned repo: %v\n", err) return err } if certmanMode == Server { serverIdFile, err := ws.FS.OpenFile("/SERVER_ID", os.O_RDWR, 0640) if err != nil { if os.IsNotExist(err) { fmt.Printf("server ID file not found for %s, adopting domain\n", url) return nil } return err } serverIdBytes, err := io.ReadAll(serverIdFile) if err != nil { return err } serverId := strings.TrimSpace(string(serverIdBytes)) if serverId != config.App.UUID { return fmt.Errorf("domain is already managed by server with uuid %s", serverId) } } return nil } func AddAndPushCerts(domain string, ws *GitWorkspace, config *AppConfig, domainConfig *DomainConfig) error { var dataRoot string if domainConfig.Certificates.DataRoot == "" { dataRoot = config.Certificates.DataRoot } else { dataRoot = domainConfig.Certificates.DataRoot } certFiles, err := os.ReadDir(dataRoot) if err != nil { fmt.Printf("Error reading from directory: %v\n", 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) } } file, err := ws.FS.Create("/SERVER_ID") if err != nil { fmt.Printf("Error creating file in memfs: %v\n", err) 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) } } status, err := ws.WorkTree.Status() if err != nil { fmt.Printf("Error getting repo status: %v\n", err) return 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{ Name: "Cert Manager", Email: config.Certificates.Email, When: time.Now(), } _, err = ws.WorkTree.Commit("Update "+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, } err = ws.Repo.Push(&git.PushOptions{ Auth: creds, Force: true, RemoteName: "origin", RefSpecs: []gitconf.RefSpec{ "refs/heads/master:refs/heads/master", }, }) if err != nil { fmt.Printf("Error pushing to origin: %v\n", err) return err } fmt.Println("Successfully uploaded to " + config.Git.Server + "/" + config.Git.OrgName + "/" + domain + domainConfig.Repo.RepoSuffix + ".git") return nil } func WriteCommitHash(hash string, config *AppConfig, domainConfig *DomainConfig) error { var dataRoot string if domainConfig.Certificates.DataRoot == "" { dataRoot = config.Certificates.DataRoot } else { dataRoot = domainConfig.Certificates.DataRoot } err := os.WriteFile(filepath.Join(dataRoot, "hash"), []byte(hash), 0644) if err != nil { return err } return nil }