Major refactoring, updated config structure
This commit is contained in:
7
Makefile
7
Makefile
@@ -1,12 +1,13 @@
|
||||
VERSION := 1.0.0
|
||||
VERSION := 1.0.0-beta
|
||||
BUILD := $(shell git rev-parse --short HEAD)
|
||||
|
||||
GO := go
|
||||
|
||||
BUILD_FLAGS := -buildmode=pie -trimpath
|
||||
LDFLAGS := -linkmode=external -extldflags="-Wl,-z,relro,-z,now"
|
||||
LDFLAGS := -linkmode=external -extldflags="-Wl,-z,relro,-z,now" -X git.nevets.tech/Keys/CertManager/internal.Version=$(VERSION) -X git.nevets.tech/Keys/CertManager/internal.Build=$(BUILD)
|
||||
|
||||
build:
|
||||
$(GO) build $(BUILD_FLAGS) -ldflags='$(LDFLAGS)' -o ./certman .
|
||||
$(GO) build $(BUILD_FLAGS) -ldflags="$(LDFLAGS)" -o ./certman .
|
||||
@cp ./certman ./certman-$(VERSION)-amd64
|
||||
|
||||
stage: build
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -7,24 +7,26 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.nevets.tech/Keys/CertManager/internal"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
)
|
||||
|
||||
func initClient() {
|
||||
err := LoadDomainConfigs()
|
||||
func Init() {
|
||||
err := internal.LoadDomainConfigs()
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading domain configs: %v", err)
|
||||
}
|
||||
|
||||
clientTick()
|
||||
Tick()
|
||||
}
|
||||
|
||||
func clientTick() {
|
||||
func Tick() {
|
||||
fmt.Println("Tick!")
|
||||
|
||||
// Get local copy of domain configs
|
||||
localDomainConfigs := domainStore.Snapshot()
|
||||
// Get local copy of configs
|
||||
config := internal.Config()
|
||||
localDomainConfigs := internal.DomainStore().Snapshot()
|
||||
|
||||
// Loop over all domain configs (domains)
|
||||
for domainStr, domainConfig := range localDomainConfigs {
|
||||
@@ -37,16 +39,16 @@ func clientTick() {
|
||||
// If the repo doesn't exist, we can't check for a remote commit, so stop the rest of the check
|
||||
repoExists := domainConfig.GetBool("Internal.repo_exists")
|
||||
if repoExists {
|
||||
localHash, err := getLocalCommitHash(domainStr)
|
||||
localHash, err := internal.LocalCommitHash(domainStr)
|
||||
if err != nil {
|
||||
fmt.Printf("No local commit hash found for domain %s\n", domainStr)
|
||||
}
|
||||
gitSource, err := strToGitSource(config.GetString("Git.host"))
|
||||
gitSource, err := internal.StrToGitSource(internal.Config().GetString("Git.host"))
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting git source for domain %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
remoteHash, err := getRemoteCommitHash(domainStr, gitSource)
|
||||
remoteHash, err := internal.RemoteCommitHash(domainStr, gitSource)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting remote commit hash for domain %s: %v\n", domainStr, err)
|
||||
}
|
||||
@@ -58,20 +60,20 @@ func clientTick() {
|
||||
}
|
||||
}
|
||||
|
||||
gitWorkspace := &GitWorkspace{
|
||||
gitWorkspace := &internal.GitWorkspace{
|
||||
Storage: memory.NewStorage(),
|
||||
FS: memfs.New(),
|
||||
}
|
||||
// Ex: https://git.example.com/Org/Repo-suffix.git
|
||||
// Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?)
|
||||
repoUrl := config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domainStr + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
||||
err := cloneRepo(repoUrl, gitWorkspace)
|
||||
repoUrl := internal.Config().GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domainStr + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
||||
err := internal.CloneRepo(repoUrl, gitWorkspace, internal.Client)
|
||||
if err != nil {
|
||||
fmt.Printf("Error cloning domain repo %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
certsDir, err := getDomainCertsDirWConf(domainStr, domainConfig)
|
||||
certsDir, err := internal.DomainCertsDirWConf(domainStr, domainConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting certificates dir for domain %s: %v\n", domainStr, err)
|
||||
continue
|
||||
@@ -104,7 +106,7 @@ func clientTick() {
|
||||
continue
|
||||
}
|
||||
|
||||
err = DecryptFileFromBytes(domainConfig.GetString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil)
|
||||
err = internal.DecryptFileFromBytes(domainConfig.GetString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domainStr, err)
|
||||
continue
|
||||
@@ -116,7 +118,7 @@ func clientTick() {
|
||||
continue
|
||||
}
|
||||
|
||||
err = writeCommitHash(headRef.Hash().String(), domainConfig)
|
||||
err = internal.WriteCommitHash(headRef.Hash().String(), domainConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error writing commit hash: %v\n", err)
|
||||
continue
|
||||
@@ -124,7 +126,7 @@ func clientTick() {
|
||||
|
||||
certLinks := domainConfig.GetStringSlice("Certificates.cert_symlinks")
|
||||
for _, certLink := range certLinks {
|
||||
err = linkFile(filepath.Join(certsDir, domainStr+".crt"), certLink, domainStr, ".crt")
|
||||
err = internal.LinkFile(filepath.Join(certsDir, domainStr+".crt"), certLink, domainStr, ".crt")
|
||||
if err != nil {
|
||||
fmt.Printf("Error linking cert %s to %s: %v\n", certLink, domainStr, err)
|
||||
continue
|
||||
@@ -133,7 +135,7 @@ func clientTick() {
|
||||
|
||||
keyLinks := domainConfig.GetStringSlice("Certificates.key_symlinks")
|
||||
for _, keyLink := range keyLinks {
|
||||
err = linkFile(filepath.Join(certsDir, domainStr+".crt"), keyLink, domainStr, ".key")
|
||||
err = internal.LinkFile(filepath.Join(certsDir, domainStr+".crt"), keyLink, domainStr, ".key")
|
||||
if err != nil {
|
||||
fmt.Printf("Error linking cert %s to %s: %v\n", keyLink, domainStr, err)
|
||||
continue
|
||||
@@ -144,16 +146,16 @@ func clientTick() {
|
||||
}
|
||||
}
|
||||
|
||||
func reloadClient() {
|
||||
func Reload() {
|
||||
fmt.Println("Reloading configs...")
|
||||
|
||||
err := LoadDomainConfigs()
|
||||
err := internal.LoadDomainConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading domain configs: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func stopClient() {
|
||||
func Stop() {
|
||||
fmt.Println("Shutting down client")
|
||||
}
|
||||
494
commands.go
494
commands.go
@@ -1,494 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func devFunc(cmd *cobra.Command, args []string) {
|
||||
testDomain := "lunamc.org"
|
||||
//config, err = ezconf.LoadConfiguration("/etc/certman/certman.conf")
|
||||
err := LoadConfig("/etc/certman/certman.conf")
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading configuration: %v\n", err)
|
||||
}
|
||||
err = LoadDomainConfigs()
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading configs: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Println(testDomain)
|
||||
}
|
||||
|
||||
func versionResponse(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("CertManager (certman) - Steven Tracey\nVersion: " + version + " build-" + build)
|
||||
}
|
||||
|
||||
func newKey(cmd *cobra.Command, args []string) {
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
fmt.Printf(key)
|
||||
}
|
||||
|
||||
func newDomain(domain, domainDir string, dirOverridden bool) error {
|
||||
//TODO add config option for "overriden dir"
|
||||
fmt.Printf("Creating new domain %s\n", domain)
|
||||
err := createNewDomainConfig(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createNewDomainCertsDir(domain, domainDir, dirOverridden)
|
||||
|
||||
certmanUser, err := user.Lookup("certman")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting user certman: %v", err)
|
||||
}
|
||||
uid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Uid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Gid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ChownRecursive("/etc/certman/domains", uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ChownRecursive("/var/local/certman", uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Successfully created domain entry for " + domain + "\nUpdate config file as needed in /etc/certman/domains/" + domain + ".conf\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func install(isThin bool, mode string) error {
|
||||
if !isThin {
|
||||
if os.Geteuid() != 0 {
|
||||
return fmt.Errorf("installation must be run as root")
|
||||
}
|
||||
|
||||
makeDirs()
|
||||
createNewConfig(mode)
|
||||
|
||||
f, err := os.OpenFile("/var/run/certman.pid", os.O_RDONLY|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating pid file: %v", err)
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error closing pid file: %v", err)
|
||||
}
|
||||
|
||||
newUserCmd := exec.Command("useradd", "-d", "/var/local/certman", "-U", "-r", "-s", "/sbin/nologin", "certman")
|
||||
if output, err := newUserCmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("error creating user: %v: output %s", err, output)
|
||||
}
|
||||
certmanUser, err := user.Lookup("certman")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting user certman: %v", err)
|
||||
}
|
||||
uid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Uid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Gid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ChownRecursive("/etc/certman", uid, gid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing uid/gid: %v", err)
|
||||
}
|
||||
err = ChownRecursive("/var/local/certman", uid, gid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing uid/gid: %v", err)
|
||||
}
|
||||
err = os.Chown("/var/run/certman.pid", uid, gid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing uid/gid: %v", err)
|
||||
}
|
||||
} else {
|
||||
createNewConfig(mode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renewCertFunc(domain string, noPush bool) error {
|
||||
err := LoadConfig("/etc/certman/certman.conf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = LoadDomainConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch config.GetString("App.mode") {
|
||||
case "server":
|
||||
mgr, err = NewACMEManager()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = renewCerts(domain, noPush)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return reloadDaemon()
|
||||
case "client":
|
||||
return pullCerts(domain)
|
||||
default:
|
||||
return fmt.Errorf("invalid operating mode %s", config.GetString("App.mode"))
|
||||
}
|
||||
}
|
||||
|
||||
func renewCerts(domain string, noPush bool) error {
|
||||
_, err := mgr.RenewForDomain(domain)
|
||||
if err != nil {
|
||||
// if no existing cert, obtain instead
|
||||
_, err = mgr.ObtainForDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error obtaining domain certificates for domain %s: %v", domain, err)
|
||||
}
|
||||
}
|
||||
|
||||
domainConfig, exists := domainStore.Get(domain)
|
||||
if !exists {
|
||||
return fmt.Errorf("domain %s does not exist", domain)
|
||||
}
|
||||
|
||||
domainConfig.Set("Internal.last_issued", time.Now().UTC().Unix())
|
||||
err = WriteDomainConfig(domainConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving domain config %s: %v", domain, err)
|
||||
}
|
||||
|
||||
err = EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domain, domain+".crt"), filepath.Join(mgr.certsRoot, domain, domain+".crt.crpt"), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encrypting domain cert for domain %s: %v", domain, err)
|
||||
}
|
||||
err = EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domain, domain+".key"), filepath.Join(mgr.certsRoot, domain, domain+".key.crpt"), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encrypting domain key for domain %s: %v", domain, err)
|
||||
}
|
||||
|
||||
if !noPush {
|
||||
giteaClient := createGiteaClient()
|
||||
if giteaClient == nil {
|
||||
return fmt.Errorf("error creating gitea client for domain %s: %v", domain, err)
|
||||
}
|
||||
gitWorkspace := &GitWorkspace{
|
||||
Storage: memory.NewStorage(),
|
||||
FS: memfs.New(),
|
||||
}
|
||||
|
||||
var repoUrl string
|
||||
if !domainConfig.GetBool("Internal.repo_exists") {
|
||||
repoUrl = createGiteaRepo(domain, giteaClient)
|
||||
if repoUrl == "" {
|
||||
return fmt.Errorf("error creating Gitea repo for domain %s", domain)
|
||||
}
|
||||
domainConfig.Set("Internal.repo_exists", true)
|
||||
err = WriteDomainConfig(domainConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving domain config %s: %v", domain, err)
|
||||
}
|
||||
|
||||
err = initRepo(repoUrl, gitWorkspace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing repo for domain %s: %v", domain, err)
|
||||
}
|
||||
} else {
|
||||
repoUrl = config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
||||
err = cloneRepo(repoUrl, gitWorkspace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error cloning repo for domain %s: %v", domain, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = addAndPushCerts(domain, gitWorkspace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error pushing certificates for domain %s: %v", domain, err)
|
||||
}
|
||||
fmt.Printf("Successfully pushed certificates for domain %s\n", domain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pullCerts(domain string) error {
|
||||
gitWorkspace := &GitWorkspace{
|
||||
Storage: memory.NewStorage(),
|
||||
FS: memfs.New(),
|
||||
}
|
||||
|
||||
domainConfig, exists := domainStore.Get(domain)
|
||||
if !exists {
|
||||
return fmt.Errorf("domain %s does not exist", domain)
|
||||
}
|
||||
|
||||
// Ex: https://git.example.com/Org/Repo-suffix.git
|
||||
// Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?)
|
||||
repoUrl := config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
||||
err := cloneRepo(repoUrl, gitWorkspace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error cloning domain repo %s: %v\n", domain, err)
|
||||
}
|
||||
|
||||
certsDir, err := getDomainCertsDirWConf(domain, domainConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting certificates dir for domain %s: %v\n", domain, err)
|
||||
}
|
||||
|
||||
// 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", 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", domain, err)
|
||||
continue
|
||||
}
|
||||
fileBytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file in memFS on domain %s: %v\n", domain, err)
|
||||
file.Close()
|
||||
continue
|
||||
}
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("Error closing file on domain %s: %v\n", domain, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = DecryptFileFromBytes(domainConfig.GetString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domain, err)
|
||||
continue
|
||||
}
|
||||
|
||||
headRef, err := gitWorkspace.Repo.Head()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting head reference for domain %s: %v\n", domain, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = writeCommitHash(headRef.Hash().String(), domainConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error writing commit hash: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
certLinks := domainConfig.GetStringSlice("Certificates.cert_symlinks")
|
||||
for _, certLink := range certLinks {
|
||||
if certLink == "" {
|
||||
continue
|
||||
}
|
||||
linkInfo, err := os.Stat(certLink)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Printf("Error stating cert link %s: %v\n", certLink, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if linkInfo.IsDir() {
|
||||
certLink = filepath.Join(certLink, domain+".crt")
|
||||
}
|
||||
|
||||
err = os.Link(filepath.Join(certsDir, domain+".crt"), certLink)
|
||||
if err != nil {
|
||||
fmt.Printf("Error linking cert %s to %s: %v\n", certLink, domain, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
keyLinks := domainConfig.GetStringSlice("Certificates.key_symlinks")
|
||||
for _, keyLink := range keyLinks {
|
||||
if keyLink == "" {
|
||||
continue
|
||||
}
|
||||
linkInfo, err := os.Stat(keyLink)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Printf("Error stating key link %s: %v\n", keyLink, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if linkInfo.IsDir() {
|
||||
keyLink = filepath.Join(keyLink, domain+".crt")
|
||||
}
|
||||
|
||||
err = os.Link(filepath.Join(certsDir, domain+".crt"), keyLink)
|
||||
if err != nil {
|
||||
fmt.Printf("Error linking cert %s to %s: %v\n", keyLink, domain, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDaemon() error {
|
||||
err := createOrUpdatePIDFile("/var/run/certman.pid")
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrorPIDInUse) {
|
||||
return fmt.Errorf("daemon process is already running")
|
||||
}
|
||||
return fmt.Errorf("error creating pidfile: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
|
||||
// Check if main config exists
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
return fmt.Errorf("main config file not found, please run 'certman --install', then properly configure /etc/certman/certman.conf")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error opening %s: %v", configFile, err)
|
||||
}
|
||||
err = LoadConfig(configFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading configuration: %v", err)
|
||||
}
|
||||
|
||||
// Setup SIGINT and SIGTERM listeners
|
||||
sigChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer signal.Stop(sigChannel)
|
||||
|
||||
reloadSigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(reloadSigChan, syscall.SIGHUP)
|
||||
defer signal.Stop(reloadSigChan)
|
||||
|
||||
tickSigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(tickSigChan, syscall.SIGUSR1)
|
||||
defer signal.Stop(tickSigChan)
|
||||
|
||||
tickRate := config.GetInt("App.tick_rate")
|
||||
ticker := time.NewTicker(time.Duration(tickRate) * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
wg.Add(1)
|
||||
if config.GetString("App.mode") == "server" {
|
||||
fmt.Println("Starting CertManager in server mode...")
|
||||
// Server Task loop
|
||||
go func() {
|
||||
initServer()
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
stopServer()
|
||||
return
|
||||
case <-reloadSigChan:
|
||||
reloadServer()
|
||||
case <-ticker.C:
|
||||
serverTick()
|
||||
case <-tickSigChan:
|
||||
serverTick()
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else if config.GetString("App.mode") == "client" {
|
||||
fmt.Println("Starting CertManager in client mode...")
|
||||
// Client Task loop
|
||||
go func() {
|
||||
initClient()
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
stopClient()
|
||||
return
|
||||
case <-reloadSigChan:
|
||||
reloadClient()
|
||||
case <-ticker.C:
|
||||
clientTick()
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
return fmt.Errorf("invalid operating mode \"" + config.GetString("App.mode") + "\"")
|
||||
}
|
||||
|
||||
// Cleanup on stop
|
||||
sig := <-sigChannel
|
||||
fmt.Printf("Program terminated with %v\n", sig.String())
|
||||
|
||||
stop()
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func stop() {
|
||||
cancel()
|
||||
clearPIDFile()
|
||||
}
|
||||
|
||||
func stopDaemon() error {
|
||||
proc, err := getDaemonProcess()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting daemon process: %v", err)
|
||||
}
|
||||
|
||||
err = proc.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending SIGTERM to daemon PID: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reloadDaemon() error {
|
||||
proc, err := getDaemonProcess()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting daemon process: %v", err)
|
||||
}
|
||||
|
||||
err = proc.Signal(syscall.SIGHUP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending SIGHUP to daemon PID: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tickDaemon() error {
|
||||
proc, err := getDaemonProcess()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting daemon process: %v", err)
|
||||
}
|
||||
|
||||
err = proc.Signal(syscall.SIGUSR1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending SIGUSR1 to daemon PID: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func statusDaemon() error {
|
||||
fmt.Println("Not implemented :/")
|
||||
return nil
|
||||
}
|
||||
37
commands/basic.go
Normal file
37
commands/basic.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.nevets.tech/Keys/CertManager/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func DevCmd(cmd *cobra.Command, args []string) {
|
||||
testDomain := "lunamc.org"
|
||||
err := internal.LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading configuration: %v\n", err)
|
||||
}
|
||||
err = internal.LoadDomainConfigs()
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading configs: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Println(testDomain)
|
||||
}
|
||||
|
||||
func VersionCmd(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("CertManager (certman) - Steven Tracey\nVersion: %s build-%s\n",
|
||||
internal.Version, internal.Build,
|
||||
)
|
||||
}
|
||||
|
||||
func NewKeyCmd(cmd *cobra.Command, args []string) {
|
||||
key, err := internal.GenerateKey()
|
||||
if err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
fmt.Printf(key)
|
||||
}
|
||||
266
commands/certs.go
Normal file
266
commands/certs.go
Normal file
@@ -0,0 +1,266 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.nevets.tech/Keys/CertManager/internal"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
)
|
||||
|
||||
var mgr *internal.ACMEManager
|
||||
|
||||
func RenewCertCmd(domain string, noPush bool, certmanMode internal.CertManMode) error {
|
||||
err := internal.LoadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = internal.LoadDomainConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch internal.Config().GetString("App.mode") {
|
||||
case "server":
|
||||
mgr, err = internal.NewACMEManager()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = renewCerts(domain, noPush, certmanMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ReloadDaemonCmd()
|
||||
case "client":
|
||||
return pullCerts(domain, certmanMode)
|
||||
default:
|
||||
return fmt.Errorf("invalid operating mode %s", internal.Config().GetString("App.mode"))
|
||||
}
|
||||
}
|
||||
|
||||
func renewCerts(domain string, noPush bool, certmanMode internal.CertManMode) error {
|
||||
_, err := mgr.RenewForDomain(domain)
|
||||
if err != nil {
|
||||
// if no existing cert, obtain instead
|
||||
_, err = mgr.ObtainForDomain(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error obtaining domain certificates for domain %s: %v", domain, err)
|
||||
}
|
||||
}
|
||||
|
||||
domainConfig, exists := internal.DomainStore().Get(domain)
|
||||
if !exists {
|
||||
return fmt.Errorf("domain %s does not exist", domain)
|
||||
}
|
||||
|
||||
domainConfig.Set("Internal.last_issued", time.Now().UTC().Unix())
|
||||
err = internal.WriteDomainConfig(domainConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving domain config %s: %v", domain, err)
|
||||
}
|
||||
|
||||
err = internal.EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.CertsRoot, domain, domain+".crt"), filepath.Join(mgr.CertsRoot, domain, domain+".crt.crpt"), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encrypting domain cert for domain %s: %v", domain, err)
|
||||
}
|
||||
err = internal.EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.CertsRoot, domain, domain+".key"), filepath.Join(mgr.CertsRoot, domain, domain+".key.crpt"), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encrypting domain key for domain %s: %v", domain, err)
|
||||
}
|
||||
|
||||
if !noPush {
|
||||
giteaClient := internal.CreateGiteaClient()
|
||||
if giteaClient == nil {
|
||||
return fmt.Errorf("error creating gitea client for domain %s: %v", domain, err)
|
||||
}
|
||||
gitWorkspace := &internal.GitWorkspace{
|
||||
Storage: memory.NewStorage(),
|
||||
FS: memfs.New(),
|
||||
}
|
||||
|
||||
var repoUrl string
|
||||
if !domainConfig.GetBool("Internal.repo_exists") {
|
||||
repoUrl = internal.CreateGiteaRepo(domain, giteaClient)
|
||||
if repoUrl == "" {
|
||||
return fmt.Errorf("error creating Gitea repo for domain %s", domain)
|
||||
}
|
||||
domainConfig.Set("Internal.repo_exists", true)
|
||||
err = internal.WriteDomainConfig(domainConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving domain config %s: %v", domain, err)
|
||||
}
|
||||
|
||||
err = internal.InitRepo(repoUrl, gitWorkspace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error initializing repo for domain %s: %v", domain, err)
|
||||
}
|
||||
} else {
|
||||
repoUrl = internal.Config().GetString("Git.server") + "/" + internal.Config().GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
||||
err = internal.CloneRepo(repoUrl, gitWorkspace, certmanMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error cloning repo for domain %s: %v", domain, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = internal.AddAndPushCerts(domain, gitWorkspace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error pushing certificates for domain %s: %v", domain, err)
|
||||
}
|
||||
fmt.Printf("Successfully pushed certificates for domain %s\n", domain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pullCerts(domain string, certmanMode internal.CertManMode) error {
|
||||
gitWorkspace := &internal.GitWorkspace{
|
||||
Storage: memory.NewStorage(),
|
||||
FS: memfs.New(),
|
||||
}
|
||||
|
||||
domainConfig, exists := internal.DomainStore().Get(domain)
|
||||
if !exists {
|
||||
return fmt.Errorf("domain %s does not exist", domain)
|
||||
}
|
||||
|
||||
// Ex: https://git.example.com/Org/Repo-suffix.git
|
||||
// Clones repo and stores in gitWorkspace, skip if clone fails (doesn't exist?)
|
||||
repoUrl := internal.Config().GetString("Git.server") + "/" + internal.Config().GetString("Git.org_name") + "/" + domain + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
||||
err := internal.CloneRepo(repoUrl, gitWorkspace, certmanMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error cloning domain repo %s: %v\n", domain, err)
|
||||
}
|
||||
|
||||
certsDir, err := internal.DomainCertsDirWConf(domain, domainConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting certificates dir for domain %s: %v\n", domain, err)
|
||||
}
|
||||
|
||||
// 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", 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", domain, err)
|
||||
continue
|
||||
}
|
||||
fileBytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file in memFS on domain %s: %v\n", domain, err)
|
||||
file.Close()
|
||||
continue
|
||||
}
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
fmt.Printf("Error closing file on domain %s: %v\n", domain, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = internal.DecryptFileFromBytes(domainConfig.GetString("Certificates.crypto_key"), fileBytes, filepath.Join(certsDir, filename), nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error decrypting file %s in domain %s: %v\n", filename, domain, err)
|
||||
continue
|
||||
}
|
||||
|
||||
headRef, err := gitWorkspace.Repo.Head()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting head reference for domain %s: %v\n", domain, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = internal.WriteCommitHash(headRef.Hash().String(), domainConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error writing commit hash: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
certLinks := domainConfig.GetStringSlice("Certificates.cert_symlinks")
|
||||
for _, certLink := range certLinks {
|
||||
if certLink == "" {
|
||||
continue
|
||||
}
|
||||
linkInfo, err := os.Stat(certLink)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Printf("Error stating cert link %s: %v\n", certLink, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if linkInfo.IsDir() {
|
||||
certLink = filepath.Join(certLink, domain+".crt")
|
||||
}
|
||||
|
||||
err = os.Link(filepath.Join(certsDir, domain+".crt"), certLink)
|
||||
if err != nil {
|
||||
fmt.Printf("Error linking cert %s to %s: %v\n", certLink, domain, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
keyLinks := domainConfig.GetStringSlice("Certificates.key_symlinks")
|
||||
for _, keyLink := range keyLinks {
|
||||
if keyLink == "" {
|
||||
continue
|
||||
}
|
||||
linkInfo, err := os.Stat(keyLink)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Printf("Error stating key link %s: %v\n", keyLink, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if linkInfo.IsDir() {
|
||||
keyLink = filepath.Join(keyLink, domain+".crt")
|
||||
}
|
||||
|
||||
err = os.Link(filepath.Join(certsDir, domain+".crt"), keyLink)
|
||||
if err != nil {
|
||||
fmt.Printf("Error linking cert %s to %s: %v\n", keyLink, domain, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateLinksCmd(domain string) error {
|
||||
domainConfig, exists := internal.DomainStore().Get(domain)
|
||||
if !exists {
|
||||
return fmt.Errorf("domain %s does not exist", domain)
|
||||
}
|
||||
|
||||
certsDir, err := internal.DomainCertsDirWConf(domain, domainConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting certificates dir for domain %s: %v", domain, err)
|
||||
}
|
||||
|
||||
certLinks := domainConfig.GetStringSlice("Certificates.cert_symlinks")
|
||||
for _, certLink := range certLinks {
|
||||
err = internal.LinkFile(filepath.Join(certsDir, domain+".crt"), certLink, domain, ".crt")
|
||||
if err != nil {
|
||||
fmt.Printf("Error linking cert %s to %s: %v", certLink, domain, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
keyLinks := domainConfig.GetStringSlice("Certificates.key_symlinks")
|
||||
for _, keyLink := range keyLinks {
|
||||
err = internal.LinkFile(filepath.Join(certsDir, domain+".crt"), keyLink, domain, ".key")
|
||||
if err != nil {
|
||||
fmt.Printf("Error linking cert %s to %s: %v", keyLink, domain, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
164
commands/daemon.go
Normal file
164
commands/daemon.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.nevets.tech/Keys/CertManager/client"
|
||||
"git.nevets.tech/Keys/CertManager/internal"
|
||||
"git.nevets.tech/Keys/CertManager/server"
|
||||
)
|
||||
|
||||
var (
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
func RunDaemonCmd() error {
|
||||
err := internal.CreateOrUpdatePIDFile("/var/run/certman.pid")
|
||||
if err != nil {
|
||||
if errors.Is(err, internal.ErrorPIDInUse) {
|
||||
return fmt.Errorf("daemon process is already running")
|
||||
}
|
||||
return fmt.Errorf("error creating pidfile: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
|
||||
// Check if main config exists
|
||||
if _, err := os.Stat("/etc/certman/certman.conf"); os.IsNotExist(err) {
|
||||
return fmt.Errorf("main config file not found, please run 'certman --install', then properly configure /etc/certman/certman.conf")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error opening /etc/certman/certman.conf: %v", err)
|
||||
}
|
||||
err = internal.LoadConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading configuration: %v", err)
|
||||
}
|
||||
|
||||
// Setup SIGINT and SIGTERM listeners
|
||||
sigChannel := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer signal.Stop(sigChannel)
|
||||
|
||||
reloadSigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(reloadSigChan, syscall.SIGHUP)
|
||||
defer signal.Stop(reloadSigChan)
|
||||
|
||||
tickSigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(tickSigChan, syscall.SIGUSR1)
|
||||
defer signal.Stop(tickSigChan)
|
||||
|
||||
tickRate := internal.Config().GetInt("App.tick_rate")
|
||||
ticker := time.NewTicker(time.Duration(tickRate) * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
wg.Add(1)
|
||||
if internal.Config().GetString("App.mode") == "server" {
|
||||
fmt.Println("Starting CertManager in server mode...")
|
||||
// Server Task loop
|
||||
go func() {
|
||||
server.Init()
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
server.Stop()
|
||||
return
|
||||
case <-reloadSigChan:
|
||||
server.Reload()
|
||||
case <-ticker.C:
|
||||
server.Tick()
|
||||
case <-tickSigChan:
|
||||
server.Tick()
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else if internal.Config().GetString("App.mode") == "client" {
|
||||
fmt.Println("Starting CertManager in client mode...")
|
||||
// Client Task loop
|
||||
go func() {
|
||||
client.Init()
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
client.Stop()
|
||||
return
|
||||
case <-reloadSigChan:
|
||||
client.Reload()
|
||||
case <-ticker.C:
|
||||
client.Tick()
|
||||
case <-tickSigChan:
|
||||
client.Tick()
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
return fmt.Errorf("invalid operating mode \"" + internal.Config().GetString("App.mode") + "\"")
|
||||
}
|
||||
|
||||
// Cleanup on stop
|
||||
sig := <-sigChannel
|
||||
fmt.Printf("Program terminated with %v\n", sig.String())
|
||||
|
||||
stop()
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func stop() {
|
||||
cancel()
|
||||
internal.ClearPIDFile()
|
||||
}
|
||||
|
||||
func StopDaemonCmd() error {
|
||||
proc, err := internal.DaemonProcess()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting daemon process: %v", err)
|
||||
}
|
||||
|
||||
err = proc.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending SIGTERM to daemon PID: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReloadDaemonCmd() error {
|
||||
proc, err := internal.DaemonProcess()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting daemon process: %v", err)
|
||||
}
|
||||
|
||||
err = proc.Signal(syscall.SIGHUP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending SIGHUP to daemon PID: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TickDaemonCmd() error {
|
||||
proc, err := internal.DaemonProcess()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting daemon process: %v", err)
|
||||
}
|
||||
|
||||
err = proc.Signal(syscall.SIGUSR1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error sending SIGUSR1 to daemon PID: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DaemonStatusCmd() error {
|
||||
fmt.Println("Not implemented :/")
|
||||
return nil
|
||||
}
|
||||
98
commands/install.go
Normal file
98
commands/install.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.nevets.tech/Keys/CertManager/internal"
|
||||
)
|
||||
|
||||
func NewDomainCmd(domain, domainDir string, dirOverridden bool) error {
|
||||
//TODO add config option for "overriden dir"
|
||||
fmt.Printf("Creating new domain %s\n", domain)
|
||||
err := internal.CreateDomainConfig(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
internal.CreateDomainCertsDir(domain, domainDir, dirOverridden)
|
||||
|
||||
certmanUser, err := user.Lookup("certman")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting user certman: %v", err)
|
||||
}
|
||||
uid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Uid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Gid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = internal.ChownRecursive("/etc/certman/domains", uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = internal.ChownRecursive("/var/local/certman", uid, gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Successfully created domain entry for " + domain + "\nUpdate config file as needed in /etc/certman/domains/" + domain + ".conf\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
func InstallCmd(isThin bool, mode string) error {
|
||||
if !isThin {
|
||||
if os.Geteuid() != 0 {
|
||||
return fmt.Errorf("installation must be run as root")
|
||||
}
|
||||
|
||||
internal.MakeDirs()
|
||||
internal.CreateConfig(mode)
|
||||
|
||||
f, err := os.OpenFile("/var/run/certman.pid", os.O_RDONLY|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating pid file: %v", err)
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error closing pid file: %v", err)
|
||||
}
|
||||
|
||||
newUserCmd := exec.Command("useradd", "-d", "/var/local/certman", "-U", "-r", "-s", "/sbin/nologin", "certman")
|
||||
if output, err := newUserCmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("error creating user: %v: output %s", err, output)
|
||||
}
|
||||
certmanUser, err := user.Lookup("certman")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting user certman: %v", err)
|
||||
}
|
||||
uid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Uid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid, err := strconv.Atoi(strings.TrimSpace(certmanUser.Gid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = internal.ChownRecursive("/etc/certman", uid, gid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing uid/gid: %v", err)
|
||||
}
|
||||
err = internal.ChownRecursive("/var/local/certman", uid, gid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing uid/gid: %v", err)
|
||||
}
|
||||
err = os.Chown("/var/run/certman.pid", uid, gid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error changing uid/gid: %v", err)
|
||||
}
|
||||
} else {
|
||||
internal.CreateConfig(mode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
[App]
|
||||
mode = {mode}
|
||||
|
||||
[Git]
|
||||
host = gitea
|
||||
server = https://gitea.instance.com
|
||||
username = user
|
||||
org_name = org
|
||||
template_name = template
|
||||
|
||||
[Certificates]
|
||||
email = user@example.com
|
||||
data_root = /var/local/certman
|
||||
request_method = dns
|
||||
|
||||
[Cloudflare]
|
||||
cf_email = email@example.com
|
||||
@@ -1,19 +0,0 @@
|
||||
[Domain]
|
||||
domain_name = {domain}
|
||||
; default (use system dns) or IPv4 Address (1.1.1.1)
|
||||
dns_server = default
|
||||
; optionally use /path/to/directory
|
||||
file_location = default
|
||||
|
||||
[Certificates]
|
||||
subdomains =
|
||||
expiry = 90
|
||||
cert_symlink =
|
||||
key_symlink =
|
||||
|
||||
[Repo]
|
||||
repo_suffix = -certificates
|
||||
|
||||
; Don't change setting below here unless you know what you're doing!
|
||||
[Internal]
|
||||
last_issued = never
|
||||
4
go.mod
4
go.mod
@@ -1,4 +1,4 @@
|
||||
module main
|
||||
module git.nevets.tech/Keys/CertManager
|
||||
|
||||
go 1.25.0
|
||||
|
||||
@@ -7,7 +7,6 @@ require (
|
||||
github.com/go-acme/lego/v4 v4.32.0
|
||||
github.com/go-git/go-billy/v5 v5.8.0
|
||||
github.com/go-git/go-git/v5 v5.17.0
|
||||
github.com/google/go-github/v55 v55.0.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
@@ -30,7 +29,6 @@ require (
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// acme_manager.go
|
||||
package main
|
||||
package internal
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
@@ -28,14 +27,14 @@ import (
|
||||
// Thread safety for your domain config map
|
||||
// (assumes you already have these globals elsewhere)
|
||||
// ---------------------------------------------
|
||||
// var mu sync.RWMutex
|
||||
// var MU sync.RWMutex
|
||||
// var domainConfigs map[string]*ezconf.Configuration
|
||||
// var config *ezconf.Configuration
|
||||
//
|
||||
// func getDomainConfig(domain string) (*ezconf.Configuration, bool) { ... }
|
||||
|
||||
// ---------------------------------------------
|
||||
// ACME account user (file-backed)
|
||||
// ACME account User (file-backed)
|
||||
// ---------------------------------------------
|
||||
|
||||
type fileUser struct {
|
||||
@@ -54,14 +53,14 @@ func (u *fileUser) GetPrivateKey() crypto.PrivateKey { return u.privateKe
|
||||
// ---------------------------------------------
|
||||
|
||||
type ACMEManager struct {
|
||||
mu sync.Mutex // serializes lego client ops + account writes
|
||||
client *lego.Client
|
||||
user *fileUser
|
||||
MU sync.Mutex // serializes lego Client ops + account writes
|
||||
Client *lego.Client
|
||||
User *fileUser
|
||||
|
||||
// root dirs
|
||||
dataRoot string // e.g. /var/local/certman
|
||||
accountRoot string // e.g. /var/local/certman/accounts
|
||||
certsRoot string // e.g. /var/local/certman/certificates
|
||||
CertsRoot string // e.g. /var/local/certman/certificates
|
||||
}
|
||||
|
||||
// DomainRuntimeConfig has domain-specific runtime settings derived from main+domain config.
|
||||
@@ -89,7 +88,7 @@ type StoredCertMeta struct {
|
||||
// Public API
|
||||
// ---------------------------------------------
|
||||
|
||||
// NewACMEManager initializes a long-lived lego client using:
|
||||
// NewACMEManager initializes a long-lived lego Client using:
|
||||
// - file-backed account
|
||||
// - persistent ECDSA P-256 account key
|
||||
// - Let’s Encrypt production by default (from config fallback)
|
||||
@@ -104,20 +103,20 @@ func NewACMEManager() (*ACMEManager, error) {
|
||||
mgr := &ACMEManager{
|
||||
dataRoot: dataRoot,
|
||||
accountRoot: filepath.Join(dataRoot, "accounts"),
|
||||
certsRoot: filepath.Join(dataRoot, "certificates"),
|
||||
CertsRoot: filepath.Join(dataRoot, "certificates"),
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(mgr.accountRoot, 0o700); err != nil {
|
||||
return nil, fmt.Errorf("create account root: %w", err)
|
||||
}
|
||||
if err := os.MkdirAll(mgr.certsRoot, 0o700); err != nil {
|
||||
if err := os.MkdirAll(mgr.CertsRoot, 0o700); err != nil {
|
||||
return nil, fmt.Errorf("create certs root: %w", err)
|
||||
}
|
||||
|
||||
// Create/load file-backed account user
|
||||
// Create/load file-backed account User
|
||||
user, err := loadOrCreateACMEUser(mgr.accountRoot, email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load/create acme user: %w", err)
|
||||
return nil, fmt.Errorf("load/create acme User: %w", err)
|
||||
}
|
||||
|
||||
// Cloudflare provider (DNS-01 only).
|
||||
@@ -147,20 +146,20 @@ func NewACMEManager() (*ACMEManager, error) {
|
||||
return nil, fmt.Errorf("set dns-01 provider: %w", err)
|
||||
}
|
||||
|
||||
mgr.client = client
|
||||
mgr.user = user
|
||||
mgr.Client = client
|
||||
mgr.User = user
|
||||
|
||||
// Register account only on first run
|
||||
if mgr.user.Registration == nil {
|
||||
reg, err := mgr.client.Registration.Register(registration.RegisterOptions{
|
||||
if mgr.User.Registration == nil {
|
||||
reg, err := mgr.Client.Registration.Register(registration.RegisterOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme registration: %w", err)
|
||||
}
|
||||
mgr.user.Registration = reg
|
||||
if err := saveACMEUser(mgr.accountRoot, mgr.user); err != nil {
|
||||
return nil, fmt.Errorf("save acme user registration: %w", err)
|
||||
mgr.User.Registration = reg
|
||||
if err := saveACMEUser(mgr.accountRoot, mgr.User); err != nil {
|
||||
return nil, fmt.Errorf("save acme User registration: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,10 +183,10 @@ func (m *ACMEManager) ObtainForDomain(domainKey string) (*certificate.Resource,
|
||||
Bundle: true,
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.MU.Lock()
|
||||
defer m.MU.Unlock()
|
||||
|
||||
res, err := m.client.Certificate.Obtain(req)
|
||||
res, err := m.Client.Certificate.Obtain(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("obtain %q: %w", domainKey, err)
|
||||
}
|
||||
@@ -201,8 +200,8 @@ func (m *ACMEManager) ObtainForDomain(domainKey string) (*certificate.Resource,
|
||||
|
||||
// RenewForDomain renews an existing stored cert for a domain key.
|
||||
func (m *ACMEManager) RenewForDomain(domainKey string) (*certificate.Resource, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.MU.Lock()
|
||||
defer m.MU.Unlock()
|
||||
|
||||
existing, err := m.loadStoredResource(domainKey)
|
||||
if err != nil {
|
||||
@@ -210,7 +209,7 @@ func (m *ACMEManager) RenewForDomain(domainKey string) (*certificate.Resource, e
|
||||
}
|
||||
|
||||
// RenewWithOptions is preferred in newer lego versions.
|
||||
renewed, err := m.client.Certificate.RenewWithOptions(*existing, &certificate.RenewOptions{
|
||||
renewed, err := m.Client.Certificate.RenewWithOptions(*existing, &certificate.RenewOptions{
|
||||
Bundle: true,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -226,8 +225,8 @@ func (m *ACMEManager) RenewForDomain(domainKey string) (*certificate.Resource, e
|
||||
|
||||
// GetCertPaths returns disk paths for the domain's cert material.
|
||||
func (m *ACMEManager) GetCertPaths(domainKey string) (certPEM, keyPEM string) {
|
||||
base := sanitizeDomainKey(domainKey)
|
||||
dir := filepath.Join(m.certsRoot, base)
|
||||
base := SanitizeDomainKey(domainKey)
|
||||
dir := filepath.Join(m.CertsRoot, base)
|
||||
return filepath.Join(dir, base+".crt"),
|
||||
filepath.Join(dir, base+".key")
|
||||
}
|
||||
@@ -450,7 +449,7 @@ func loadACMEUser(accountRoot string) (*fileUser, error) {
|
||||
|
||||
func saveACMEUser(accountRoot string, u *fileUser) error {
|
||||
if u == nil {
|
||||
return errors.New("nil user")
|
||||
return errors.New("nil User")
|
||||
}
|
||||
if err := os.MkdirAll(accountRoot, 0o700); err != nil {
|
||||
return err
|
||||
@@ -525,8 +524,8 @@ func (m *ACMEManager) saveCertFiles(domainKey string, res *certificate.Resource,
|
||||
return errors.New("nil certificate resource")
|
||||
}
|
||||
|
||||
base := sanitizeDomainKey(domainKey)
|
||||
dir := filepath.Join(m.certsRoot, base)
|
||||
base := SanitizeDomainKey(domainKey)
|
||||
dir := filepath.Join(m.CertsRoot, base)
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -599,8 +598,8 @@ func (m *ACMEManager) saveCertFiles(domainKey string, res *certificate.Resource,
|
||||
}
|
||||
|
||||
func (m *ACMEManager) loadStoredResource(domainKey string) (*certificate.Resource, error) {
|
||||
base := sanitizeDomainKey(domainKey)
|
||||
dir := filepath.Join(m.certsRoot, base)
|
||||
base := SanitizeDomainKey(domainKey)
|
||||
dir := filepath.Join(m.CertsRoot, base)
|
||||
raw, err := os.ReadFile(filepath.Join(dir, base+".json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -623,8 +622,8 @@ func (m *ACMEManager) loadStoredResource(domainKey string) (*certificate.Resourc
|
||||
}
|
||||
|
||||
func (m *ACMEManager) loadMeta(domainKey string) (*StoredCertMeta, error) {
|
||||
base := sanitizeDomainKey(domainKey)
|
||||
dir := filepath.Join(m.certsRoot, base)
|
||||
base := SanitizeDomainKey(domainKey)
|
||||
dir := filepath.Join(m.CertsRoot, base)
|
||||
raw, err := os.ReadFile(filepath.Join(dir, base+".meta.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
6
internal/buildinfo.go
Normal file
6
internal/buildinfo.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package internal
|
||||
|
||||
var (
|
||||
Version = "dev"
|
||||
Build = "local"
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -67,19 +67,48 @@ func (s *DomainConfigStore) Snapshot() map[string]*viper.Viper {
|
||||
|
||||
var (
|
||||
config *viper.Viper
|
||||
configMu sync.RWMutex
|
||||
domainStore = NewDomainConfigStore()
|
||||
)
|
||||
|
||||
func Config() *viper.Viper {
|
||||
configMu.RLock()
|
||||
defer configMu.RUnlock()
|
||||
return config
|
||||
}
|
||||
|
||||
func DomainStore() *DomainConfigStore {
|
||||
domainStore.mu.RLock()
|
||||
defer domainStore.mu.RUnlock()
|
||||
return domainStore
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Loading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// LoadConfig reads the main certman.conf into config.
|
||||
func LoadConfig(path string) error {
|
||||
func LoadConfig() error {
|
||||
config = viper.New()
|
||||
config.SetConfigFile(path)
|
||||
config.SetConfigFile("/etc/certman/certman.conf")
|
||||
config.SetConfigType("toml")
|
||||
return config.ReadInConfig()
|
||||
err := config.ReadInConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch config.GetString("App.mode") {
|
||||
case "server":
|
||||
config.SetConfigType("toml")
|
||||
config.SetConfigFile("server.conf")
|
||||
return config.MergeInConfig()
|
||||
case "Client":
|
||||
config.SetConfigType("toml")
|
||||
config.SetConfigFile("Client.conf")
|
||||
return config.MergeInConfig()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadDomainConfigs reads every .conf file in the domains directory.
|
||||
@@ -223,7 +252,7 @@ func EffectiveBool(domainCfg *viper.Viper, key string) bool {
|
||||
// Directory bootstrapping
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func makeDirs() {
|
||||
func MakeDirs() {
|
||||
dirs := []struct {
|
||||
path string
|
||||
perm os.FileMode
|
||||
@@ -240,31 +269,49 @@ func makeDirs() {
|
||||
}
|
||||
}
|
||||
|
||||
func createNewConfig(mode string) {
|
||||
func CreateConfig(mode string) {
|
||||
content := strings.NewReplacer(
|
||||
"{mode}", mode,
|
||||
"{uuid}", uuid.New().String(),
|
||||
).Replace(defaultConfig)
|
||||
createFile("/etc/certman/certman.conf", 0640, []byte(content))
|
||||
|
||||
switch mode {
|
||||
case "server":
|
||||
content = strings.NewReplacer(
|
||||
"{uuid}", uuid.New().String(),
|
||||
).Replace(defaultServerConfig)
|
||||
createFile("/etc/certman/server.conf", 640, []byte(content))
|
||||
}
|
||||
}
|
||||
|
||||
func createNewDomainConfig(domain string) error {
|
||||
func CreateDomainConfig(domain string) error {
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate key: %v", err)
|
||||
}
|
||||
|
||||
content := strings.NewReplacer(
|
||||
"{domain}", domain,
|
||||
"{key}", key,
|
||||
).Replace(defaultDomainConfig)
|
||||
var content string
|
||||
switch Config().GetString("App.mode") {
|
||||
case "server":
|
||||
content = strings.NewReplacer(
|
||||
"{domain}", domain,
|
||||
"{key}", key,
|
||||
).Replace(defaultServerDomainConfig)
|
||||
case "Client":
|
||||
content = strings.NewReplacer(
|
||||
"{domain}", domain,
|
||||
"{key}", key,
|
||||
).Replace(defaultClientDomainConfig)
|
||||
default:
|
||||
return fmt.Errorf("unknown certman mode: %v", Config().GetString("App.mode"))
|
||||
}
|
||||
|
||||
path := filepath.Join("/etc/certman/domains", domain+".conf")
|
||||
createFile(path, 0640, []byte(content))
|
||||
return nil
|
||||
}
|
||||
|
||||
func createNewDomainCertsDir(domain string, dir string, dirOverride bool) {
|
||||
func CreateDomainCertsDir(domain string, dir string, dirOverride bool) {
|
||||
var target string
|
||||
if dirOverride {
|
||||
target = filepath.Join(dir, domain)
|
||||
@@ -286,49 +333,68 @@ func createNewDomainCertsDir(domain string, dir string, dirOverride bool) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const defaultConfig = `[App]
|
||||
mode = "{mode}"
|
||||
mode = '{mode}'
|
||||
tick_rate = 2
|
||||
uuid = "{uuid}"
|
||||
|
||||
[Git]
|
||||
host = "gitea"
|
||||
server = "https://gitea.instance.com"
|
||||
username = "user"
|
||||
api_token = "xxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
org_name = "org"
|
||||
host = 'gitea'
|
||||
server = 'https://gitea.instance.com'
|
||||
username = 'User'
|
||||
api_token = 'xxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
org_name = 'org'
|
||||
|
||||
[Certificates]
|
||||
email = "user@example.com"
|
||||
data_root = "/var/local/certman"
|
||||
ca_dir_url = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
|
||||
[Cloudflare]
|
||||
cf_email = "email@example.com"
|
||||
cf_api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
data_root = '/var/local/certman'
|
||||
`
|
||||
|
||||
const defaultDomainConfig = `[Domain]
|
||||
domain_name = "{domain}"
|
||||
enabled = true
|
||||
dns_server = "default"
|
||||
const defaultServerConfig = `[App]
|
||||
uuid = '{uuid}'
|
||||
|
||||
[Certificates]
|
||||
data_root = ""
|
||||
email = 'User@example.com'
|
||||
data_root = '/var/local/certman'
|
||||
ca_dir_url = 'https://acme-v02.api.letsencrypt.org/directory'
|
||||
|
||||
[Cloudflare]
|
||||
cf_email = 'email@example.com'
|
||||
cf_api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'`
|
||||
|
||||
const defaultClientConfig = ``
|
||||
|
||||
const defaultServerDomainConfig = `[Domain]
|
||||
domain_name = '{domain}'
|
||||
enabled = true
|
||||
dns_server = 'default'
|
||||
|
||||
[Certificates]
|
||||
data_root = ''
|
||||
expiry = 90
|
||||
request_method = "dns-01"
|
||||
request_method = 'dns-01'
|
||||
renew_period = 30
|
||||
subdomains = []
|
||||
cert_symlinks = []
|
||||
key_symlinks = []
|
||||
crypto_key = "{key}"
|
||||
crypto_key = '{key}'
|
||||
|
||||
[Repo]
|
||||
repo_suffix = "-certificates"
|
||||
repo_suffix = '-certificates'
|
||||
|
||||
[Internal]
|
||||
last_issued = 0
|
||||
repo_exists = false
|
||||
status = "clean"
|
||||
status = 'clean'
|
||||
`
|
||||
|
||||
const defaultClientDomainConfig = `[Certificates]
|
||||
data_root = ''
|
||||
cert_symlinks = []
|
||||
key_symlinks = []
|
||||
crypto_key = '{key}'
|
||||
|
||||
[Domain]
|
||||
domain_name = '{domain}'
|
||||
enabled = true
|
||||
|
||||
[Repo]
|
||||
repo_suffix = '-certificates'
|
||||
`
|
||||
|
||||
const readme = ``
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package internal
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -17,10 +16,16 @@ import (
|
||||
"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"
|
||||
"github.com/google/go-github/v55/github"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type CertManMode int
|
||||
|
||||
const (
|
||||
Server CertManMode = iota
|
||||
Client
|
||||
)
|
||||
|
||||
type GitWorkspace struct {
|
||||
Repo *git.Repository
|
||||
Storage *memory.Storage
|
||||
@@ -48,7 +53,7 @@ var GitSourceName = map[GitSource]string{
|
||||
CodeCommit: "code-commit",
|
||||
}
|
||||
|
||||
func strToGitSource(s string) (GitSource, error) {
|
||||
func StrToGitSource(s string) (GitSource, error) {
|
||||
for k, v := range GitSourceName {
|
||||
if v == s {
|
||||
return k, nil
|
||||
@@ -57,11 +62,11 @@ func strToGitSource(s string) (GitSource, error) {
|
||||
return GitSource(0), errors.New("invalid gitsource name")
|
||||
}
|
||||
|
||||
func createGithubClient() *github.Client {
|
||||
return github.NewClient(nil).WithAuthToken(config.GetString("Git.api_token"))
|
||||
}
|
||||
//func createGithubClient() *github.Client {
|
||||
// return github.NewClient(nil).WithAuthToken(config.GetString("Git.api_token"))
|
||||
//}
|
||||
|
||||
func createGiteaClient() *gitea.Client {
|
||||
func CreateGiteaClient() *gitea.Client {
|
||||
client, err := gitea.NewClient(config.GetString("Git.server"), gitea.SetToken(config.GetString("Git.api_token")))
|
||||
if err != nil {
|
||||
fmt.Printf("Error connecting to gitea instance: %v\n", err)
|
||||
@@ -70,30 +75,30 @@ func createGiteaClient() *gitea.Client {
|
||||
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
|
||||
//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
|
||||
//}
|
||||
|
||||
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) string {
|
||||
func CreateGiteaRepo(domain string, giteaClient *gitea.Client) string {
|
||||
domainConfig, exists := domainStore.Get(domain)
|
||||
if !exists {
|
||||
fmt.Printf("Domain %s config does not exist\n", domain)
|
||||
@@ -121,7 +126,7 @@ func createGiteaRepo(domain string, giteaClient *gitea.Client) string {
|
||||
return giteaRepo.CloneURL
|
||||
}
|
||||
|
||||
func initRepo(url string, ws *GitWorkspace) error {
|
||||
func InitRepo(url string, ws *GitWorkspace) error {
|
||||
var err error
|
||||
ws.Repo, err = git.Init(ws.Storage, ws.FS)
|
||||
if err != nil {
|
||||
@@ -147,7 +152,7 @@ func initRepo(url string, ws *GitWorkspace) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cloneRepo(url string, ws *GitWorkspace) error {
|
||||
func CloneRepo(url string, ws *GitWorkspace, certmanMode CertManMode) error {
|
||||
creds := &http.BasicAuth{
|
||||
Username: config.GetString("Git.username"),
|
||||
Password: config.GetString("Git.api_token"),
|
||||
@@ -163,33 +168,35 @@ func cloneRepo(url string, ws *GitWorkspace) error {
|
||||
fmt.Printf("Error getting worktree from cloned repo: %v\n", err)
|
||||
return err
|
||||
}
|
||||
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
|
||||
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.GetString("App.uuid") {
|
||||
return fmt.Errorf("domain is already managed by server with uuid %s", serverId)
|
||||
}
|
||||
return err
|
||||
}
|
||||
serverIdBytes, err := io.ReadAll(serverIdFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serverId := strings.TrimSpace(string(serverIdBytes))
|
||||
if serverId != config.GetString("App.uuid") {
|
||||
return fmt.Errorf("domain is already managed by server with uuid %s", serverId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addAndPushCerts(domain string, ws *GitWorkspace) error {
|
||||
func AddAndPushCerts(domain string, ws *GitWorkspace) error {
|
||||
domainConfig, exists := domainStore.Get(domain)
|
||||
if !exists {
|
||||
fmt.Printf("Domain %s config does not exist\n", domain)
|
||||
return ErrConfigNotFound
|
||||
}
|
||||
|
||||
certsDir, err := getDomainCertsDirWConf(domain, domainConfig)
|
||||
certsDir, err := DomainCertsDirWConf(domain, domainConfig)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrConfigNotFound) {
|
||||
fmt.Printf("Domain %s config not found: %v\n", domain, err)
|
||||
@@ -301,8 +308,8 @@ func addAndPushCerts(domain string, ws *GitWorkspace) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeCommitHash(hash string, domainConfig *viper.Viper) error {
|
||||
certsDir, err := getDomainCertsDirWOnlyConf(domainConfig)
|
||||
func WriteCommitHash(hash string, domainConfig *viper.Viper) error {
|
||||
certsDir, err := DomainCertsDirWOnlyConf(domainConfig)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrConfigNotFound) {
|
||||
return err
|
||||
@@ -318,8 +325,8 @@ func writeCommitHash(hash string, domainConfig *viper.Viper) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLocalCommitHash(domain string) (string, error) {
|
||||
certsDir, err := getDomainCertsDir(domain)
|
||||
func LocalCommitHash(domain string) (string, error) {
|
||||
certsDir, err := DomainCertsDir(domain)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrConfigNotFound) {
|
||||
fmt.Printf("Domain %s config not found: %v\n", domain, err)
|
||||
@@ -330,15 +337,17 @@ func getLocalCommitHash(domain string) (string, error) {
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(certsDir, "hash"))
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading file for domain %s: %v\n", domain, err)
|
||||
return "", err
|
||||
if !os.IsNotExist(err) {
|
||||
fmt.Printf("Error reading file for domain %s: %v\n", domain, err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(data)), nil
|
||||
}
|
||||
|
||||
func getRemoteCommitHash(domain string, gitSource GitSource) (string, error) {
|
||||
domainConfig, exists := domainStore.Get(domain)
|
||||
func RemoteCommitHash(domain string, gitSource GitSource) (string, error) {
|
||||
domainConfig, exists := DomainStore().Get(domain)
|
||||
if !exists {
|
||||
fmt.Printf("Domain %s config does not exist\n", domain)
|
||||
return "", ErrConfigNotFound
|
||||
@@ -354,7 +363,7 @@ func getRemoteCommitHash(domain string, gitSource GitSource) (string, error) {
|
||||
}
|
||||
|
||||
func getRemoteCommitHashGitea(org, repo, branchName string) (string, error) {
|
||||
giteaClient := createGiteaClient()
|
||||
giteaClient := CreateGiteaClient()
|
||||
branch, _, err := giteaClient.GetRepoBranch(org, repo, branchName)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting repo branch: %v\n", err)
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"syscall"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/google/go-github/v55/github"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
@@ -25,7 +24,6 @@ type Domain struct {
|
||||
name *string
|
||||
config *viper.Viper
|
||||
description *string
|
||||
ghClient *github.Client
|
||||
gtClient *gitea.Client
|
||||
}
|
||||
|
||||
@@ -44,7 +42,7 @@ func createPIDFile() {
|
||||
}
|
||||
|
||||
// 0x02
|
||||
func clearPIDFile() {
|
||||
func ClearPIDFile() {
|
||||
file, err := os.OpenFile("/var/run/certman.pid", os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("0x02: Error opening PID file: %v\n", err)
|
||||
@@ -60,7 +58,7 @@ func clearPIDFile() {
|
||||
}
|
||||
|
||||
// 0x03
|
||||
func createOrUpdatePIDFile(filename string) error {
|
||||
func CreateOrUpdatePIDFile(filename string) error {
|
||||
pidBytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Printf("0x03: Error reading PID file: %v\n", err)
|
||||
@@ -142,7 +140,7 @@ func isProcessActive(pid int) (bool, error) {
|
||||
}
|
||||
|
||||
// 0x05
|
||||
func getDaemonProcess() (*os.Process, error) {
|
||||
func DaemonProcess() (*os.Process, error) {
|
||||
pidBytes, err := os.ReadFile("/var/run/certman.pid")
|
||||
if err != nil {
|
||||
fmt.Printf("0x05: Error getting PID from /var/run/certman.pid: %v\n", err)
|
||||
@@ -209,15 +207,20 @@ func createFile(fileName string, filePermission os.FileMode, data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func linkFile(source, target, domain, extension string) error {
|
||||
func LinkFile(source, target, domain, extension string) error {
|
||||
if target == "" {
|
||||
return ErrBlankCert
|
||||
}
|
||||
linkInfo, err := os.Stat(target)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
if os.IsNotExist(err) {
|
||||
err = os.Symlink(source, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if linkInfo.IsDir() {
|
||||
target = filepath.Join(target, domain+extension)
|
||||
@@ -252,24 +255,24 @@ func insert(a []string, index int, value string) []string {
|
||||
return a
|
||||
}
|
||||
|
||||
func sanitizeDomainKey(s string) string {
|
||||
func SanitizeDomainKey(s string) string {
|
||||
s = strings.TrimSpace(strings.ToLower(s))
|
||||
r := strings.NewReplacer("/", "_", "\\", "_", " ", "_", ":", "_")
|
||||
return r.Replace(s)
|
||||
}
|
||||
|
||||
// getDomainCertsDir Can return ErrBlankConfigEntry, ErrConfigNotFound, or other errors
|
||||
func getDomainCertsDir(domain string) (string, error) {
|
||||
// DomainCertsDir Can return ErrBlankConfigEntry, ErrConfigNotFound, or other errors
|
||||
func DomainCertsDir(domain string) (string, error) {
|
||||
domainConfig, exists := domainStore.Get(domain)
|
||||
if !exists {
|
||||
return "", ErrConfigNotFound
|
||||
}
|
||||
|
||||
return getDomainCertsDirWConf(domain, domainConfig)
|
||||
return DomainCertsDirWConf(domain, domainConfig)
|
||||
}
|
||||
|
||||
// getDomainCertsDir Can return ErrBlankConfigEntry or other errors
|
||||
func getDomainCertsDirWConf(domain string, domainConfig *viper.Viper) (string, error) {
|
||||
// DomainCertsDirWConf Can return ErrBlankConfigEntry or other errors
|
||||
func DomainCertsDirWConf(domain string, domainConfig *viper.Viper) (string, error) {
|
||||
effectiveDataRoot, err := EffectiveString(domainConfig, "Certificates.data_root")
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -278,9 +281,9 @@ func getDomainCertsDirWConf(domain string, domainConfig *viper.Viper) (string, e
|
||||
return filepath.Join(effectiveDataRoot, "certificates", domain), nil
|
||||
}
|
||||
|
||||
func getDomainCertsDirWOnlyConf(domainConfig *viper.Viper) (string, error) {
|
||||
func DomainCertsDirWOnlyConf(domainConfig *viper.Viper) (string, error) {
|
||||
domain := domainConfig.GetString("Domain.domain_name")
|
||||
return getDomainCertsDirWConf(domain, domainConfig)
|
||||
return DomainCertsDirWConf(domain, domainConfig)
|
||||
}
|
||||
|
||||
func ChownRecursive(path string, uid, gid int) error {
|
||||
46
main.go
46
main.go
@@ -1,24 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"git.nevets.tech/Keys/CertManager/commands"
|
||||
"git.nevets.tech/Keys/CertManager/internal"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var version = "1.0.0"
|
||||
var build = "1"
|
||||
|
||||
var (
|
||||
configFile string
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
var configFile string
|
||||
|
||||
var fqdnRegex = regexp.MustCompile(`^(?i:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$`)
|
||||
|
||||
@@ -36,9 +28,9 @@ func main() {
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "/etc/certman/certman.conf", "Configuration file")
|
||||
|
||||
rootCmd.AddCommand(basicCmd("version", "Show version", versionResponse))
|
||||
rootCmd.AddCommand(basicCmd("gen-key", "Generates encryption key", newKey))
|
||||
rootCmd.AddCommand(basicCmd("dev", "Dev Function", devFunc))
|
||||
rootCmd.AddCommand(basicCmd("version", "Show version", commands.VersionCmd))
|
||||
rootCmd.AddCommand(basicCmd("gen-key", "Generates encryption key", commands.NewKeyCmd))
|
||||
rootCmd.AddCommand(basicCmd("dev", "Dev Function", commands.DevCmd))
|
||||
|
||||
var domainCertDir string
|
||||
newDomainCmd := &cobra.Command{
|
||||
@@ -49,7 +41,7 @@ func main() {
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
dirOverridden := cmd.Flags().Changed("dir")
|
||||
return newDomain(args[0], domainCertDir, dirOverridden)
|
||||
return commands.NewDomainCmd(args[0], domainCertDir, dirOverridden)
|
||||
},
|
||||
}
|
||||
newDomainCmd.Flags().StringVar(&domainCertDir, "dir", "/var/local/certman/certificates/", "Alternate directory for certificates")
|
||||
@@ -65,7 +57,7 @@ func main() {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
switch modeFlag {
|
||||
case "server", "client":
|
||||
return install(thinInstallFlag, modeFlag)
|
||||
return commands.InstallCmd(thinInstallFlag, modeFlag)
|
||||
default:
|
||||
return fmt.Errorf("invalid --mode %q (must be server or client)", modeFlag)
|
||||
}
|
||||
@@ -89,12 +81,22 @@ func main() {
|
||||
Short: "Renews a domains certificate",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return renewCertFunc(args[0], noPush)
|
||||
return commands.RenewCertCmd(args[0], noPush, internal.Server)
|
||||
},
|
||||
}
|
||||
renewCertCmd.Flags().BoolVar(&noPush, "no-push", false, "Don't push certs to repo, renew locally only [server mode only]")
|
||||
certCmd.AddCommand(renewCertCmd)
|
||||
|
||||
updateCertLinkCmd := &cobra.Command{
|
||||
Use: "update-link",
|
||||
Short: "Update linked certificates",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return commands.UpdateLinksCmd(args[0])
|
||||
},
|
||||
}
|
||||
certCmd.AddCommand(updateCertLinkCmd)
|
||||
|
||||
rootCmd.AddCommand(certCmd)
|
||||
|
||||
daemonCmd := &cobra.Command{
|
||||
@@ -110,7 +112,7 @@ func main() {
|
||||
Short: "Start the daemon",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDaemon()
|
||||
return commands.RunDaemonCmd()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -119,7 +121,7 @@ func main() {
|
||||
Short: "Stop the daemon",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return stopDaemon()
|
||||
return commands.StopDaemonCmd()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -128,7 +130,7 @@ func main() {
|
||||
Short: "Reload daemon configs",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return reloadDaemon()
|
||||
return commands.ReloadDaemonCmd()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -137,7 +139,7 @@ func main() {
|
||||
Short: "Manually triggers daemon tick",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return tickDaemon()
|
||||
return commands.TickDaemonCmd()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -146,7 +148,7 @@ func main() {
|
||||
Short: "Show daemon status",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return statusDaemon()
|
||||
return commands.DaemonStatusCmd()
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -7,23 +7,24 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.nevets.tech/Keys/CertManager/internal"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
)
|
||||
|
||||
var (
|
||||
tickMu sync.Mutex
|
||||
mgr *ACMEManager
|
||||
mgr *internal.ACMEManager
|
||||
mgrMu sync.Mutex
|
||||
)
|
||||
|
||||
func getACMEManager() (*ACMEManager, error) {
|
||||
func getACMEManager() (*internal.ACMEManager, error) {
|
||||
mgrMu.Lock()
|
||||
defer mgrMu.Unlock()
|
||||
|
||||
if mgr == nil {
|
||||
var err error
|
||||
mgr, err = NewACMEManager()
|
||||
mgr, err = internal.NewACMEManager()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -31,21 +32,22 @@ func getACMEManager() (*ACMEManager, error) {
|
||||
return mgr, nil
|
||||
}
|
||||
|
||||
func initServer() {
|
||||
err := LoadDomainConfigs()
|
||||
func Init() {
|
||||
err := internal.LoadDomainConfigs()
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading domain configs: %v", err)
|
||||
}
|
||||
|
||||
serverTick()
|
||||
Tick()
|
||||
}
|
||||
|
||||
func serverTick() {
|
||||
func Tick() {
|
||||
tickMu.Lock()
|
||||
defer tickMu.Unlock()
|
||||
fmt.Println("Tick!")
|
||||
|
||||
mgr, err := getACMEManager()
|
||||
var err error
|
||||
mgr, err = getACMEManager()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting acme manager: %v\n", err)
|
||||
return
|
||||
@@ -53,7 +55,7 @@ func serverTick() {
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
localDomainConfigs := domainStore.Snapshot()
|
||||
localDomainConfigs := internal.DomainStore().Snapshot()
|
||||
|
||||
for domainStr, domainConfig := range localDomainConfigs {
|
||||
if !domainConfig.GetBool("Domain.enabled") {
|
||||
@@ -75,62 +77,62 @@ func serverTick() {
|
||||
}
|
||||
|
||||
domainConfig.Set("Internal.last_issued", time.Now().UTC().Unix())
|
||||
err = WriteDomainConfig(domainConfig)
|
||||
err = internal.WriteDomainConfig(domainConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".crt"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".crt.crpt"), nil)
|
||||
err = internal.EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.CertsRoot, domainStr, domainStr+".crt"), filepath.Join(mgr.CertsRoot, domainStr, domainStr+".crt.crpt"), nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error encrypting domain cert for domain %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
err = EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".key"), filepath.Join(mgr.certsRoot, domainStr, domainStr+".key.crpt"), nil)
|
||||
err = internal.EncryptFileXChaCha(domainConfig.GetString("Certificates.crypto_key"), filepath.Join(mgr.CertsRoot, domainStr, domainStr+".key"), filepath.Join(mgr.CertsRoot, domainStr, domainStr+".key.crpt"), nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Error encrypting domain key for domain %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
giteaClient := createGiteaClient()
|
||||
giteaClient := internal.CreateGiteaClient()
|
||||
if giteaClient == nil {
|
||||
fmt.Printf("Error creating gitea client for domain %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
gitWorkspace := &GitWorkspace{
|
||||
gitWorkspace := &internal.GitWorkspace{
|
||||
Storage: memory.NewStorage(),
|
||||
FS: memfs.New(),
|
||||
}
|
||||
|
||||
var repoUrl string
|
||||
if !domainConfig.GetBool("Internal.repo_exists") {
|
||||
repoUrl = createGiteaRepo(domainStr, giteaClient)
|
||||
repoUrl = internal.CreateGiteaRepo(domainStr, giteaClient)
|
||||
if repoUrl == "" {
|
||||
fmt.Printf("Error creating Gitea repo for domain %s\n", domainStr)
|
||||
continue
|
||||
}
|
||||
domainConfig.Set("Internal.repo_exists", true)
|
||||
err = WriteDomainConfig(domainConfig)
|
||||
err = internal.WriteDomainConfig(domainConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving domain config %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = initRepo(repoUrl, gitWorkspace)
|
||||
err = internal.InitRepo(repoUrl, gitWorkspace)
|
||||
if err != nil {
|
||||
fmt.Printf("Error initializing repo for domain %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
repoUrl = config.GetString("Git.server") + "/" + config.GetString("Git.org_name") + "/" + domainStr + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
||||
err = cloneRepo(repoUrl, gitWorkspace)
|
||||
repoUrl = internal.Config().GetString("Git.server") + "/" + internal.Config().GetString("Git.org_name") + "/" + domainStr + domainConfig.GetString("Repo.repo_suffix") + ".git"
|
||||
err = internal.CloneRepo(repoUrl, gitWorkspace, internal.Server)
|
||||
if err != nil {
|
||||
fmt.Printf("Error cloning repo for domain %s: %v\n", domainStr, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
err = addAndPushCerts(domainStr, gitWorkspace)
|
||||
err = internal.AddAndPushCerts(domainStr, gitWorkspace)
|
||||
if err != nil {
|
||||
fmt.Printf("Error pushing certificates for domain %s: %v\n", domainStr, err)
|
||||
continue
|
||||
@@ -138,15 +140,15 @@ func serverTick() {
|
||||
fmt.Printf("Successfully pushed certificates for domain %s\n", domainStr)
|
||||
}
|
||||
}
|
||||
err = SaveDomainConfigs()
|
||||
err = internal.SaveDomainConfigs()
|
||||
if err != nil {
|
||||
fmt.Printf("Error saving domain configs: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func reloadServer() {
|
||||
func Reload() {
|
||||
fmt.Println("Reloading configs...")
|
||||
err := LoadDomainConfigs()
|
||||
err := internal.LoadDomainConfigs()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error loading domain configs: %v\n", err)
|
||||
@@ -160,6 +162,6 @@ func reloadServer() {
|
||||
fmt.Println("Successfully reloaded configs")
|
||||
}
|
||||
|
||||
func stopServer() {
|
||||
func Stop() {
|
||||
fmt.Println("Shutting down server")
|
||||
}
|
||||
Reference in New Issue
Block a user