package main import ( "context" "errors" "flag" "fmt" "log" "os" "os/signal" "strings" "sync" "syscall" "time" "git.nevets.tech/Steven/ezconf" ) var version = "1.1.0-beta" var build = "1" var config *ezconf.Configuration var ctx context.Context var cancel context.CancelFunc var wg sync.WaitGroup //TODO create logic for gh vs gt repos func main() { devFlag := flag.Bool("dev", false, "Developer Mode") versionFlag := flag.Bool("version", false, "Show version") helpFlag := flag.Bool("help", false, "Show help") configFile := flag.String("config", "/etc/certman/certman.conf", "Configuration file") newDomainFlag := flag.String("new-domain", "example.com", "Domain to create new configs and directories for") newDomainDirFlag := flag.String("new-domain-dir", "/opt/certs/example.com", "Directory that certs will be stored in") localOnlyFlag := flag.Bool("local-only", false, "Local only") installFlag := flag.Bool("install", false, "Install Certman") modeFlag := flag.String("mode", "client", "CertManager Mode [server, client]") thinInstallFlag := flag.Bool("t", false, "Thin Install (skip creating dirs)") newKeyFlag := flag.Bool("newkey", false, "Generate new encryption key") reloadFlag := flag.Bool("reload", false, "Reload configs") stopFlag := flag.Bool("stop", false, "Stop certman") daemonFlag := flag.Bool("d", false, "Daemon Mode") flag.Parse() if *devFlag { testDomain := "lunamc.org" var err error config, err = ezconf.LoadConfiguration("/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) os.Exit(0) } if *versionFlag { fmt.Println("CertManager (certman) - Steven Tracey\nVersion: " + version + " build-" + build) os.Exit(0) } if *helpFlag { fmt.Printf(`CertManager (certman) - Steven Tracey Version: %s build-%s Subcommands: certman -subcommand - version Shows the current version and build - help Displays this help message - newkey Creates a new random 256 bit base64 key Daemon Controls: certman -command - d Start in daemon mode - reload Reload configs - stop Stop Daemon Installation: certman -install -mode (mode) [-t] [-config /path/to/file] - install - mode [mode] Uses the specified config file [server, client] - t Thin install (skip creating directories) - config /path/to/file Create config file at the specified path New Domain Options: certman -new-domain example.com [-new-domain-dir /path/to/certs] - new-domain Creates a new domain config - new-domain-dir Specifies directory for new domain certificates to be stored - local-only Don't create git repo `, version, build) os.Exit(0) } if *newDomainFlag != "example.com" { fmt.Printf("Creating new domain %s\n", *newDomainFlag) createNewDomainConfig(*newDomainFlag) createNewDomainCertsDir(*newDomainFlag, *newDomainDirFlag) if !*localOnlyFlag { //TODO create git repo } fmt.Println("Successfully created domain entry for " + *newDomainFlag + "\nUpdate config file as needed in /etc/certman/domains/" + *newDomainFlag + ".conf") os.Exit(0) } if *installFlag { if !*thinInstallFlag { makeDirs() } var err error config, err = ezconf.NewConfiguration(*configFile, strings.ReplaceAll(defaultConfig, "{mode}", *modeFlag)) if err != nil { log.Fatalf("Error creating config: %s\n", err) } os.Exit(0) } if *newKeyFlag { key, err := GenerateKey() if err != nil { log.Fatalf("%v", err) } fmt.Printf(key) os.Exit(0) } if *reloadFlag { proc, err := getDaemonProcess() if err != nil { log.Fatalf("Error getting daemon process: %v", err) } err = proc.Signal(syscall.SIGHUP) if err != nil { log.Fatalf("Error sending SIGHUP to daemon PID: %v\n", err) } os.Exit(0) } if *stopFlag { proc, err := getDaemonProcess() if err != nil { log.Fatalf("Error getting daemon process: %v", err) } err = proc.Signal(syscall.SIGTERM) if err != nil { log.Fatalf("Error sending SIGTERM to daemon PID: %v\n", err) } os.Exit(0) } if *daemonFlag { err := createOrUpdatePIDFile("/var/run/certman.pid") if err != nil { if errors.Is(err, ErrorPIDInUse) { log.Fatalf("Deemon process is already running\n") } log.Fatalf("Error creating pidfile: %v\n", err) } ctx, cancel = context.WithCancel(context.Background()) // Check if main config exists if _, err := os.Stat(*configFile); os.IsNotExist(err) { log.Fatalf("Main config file not found, please run 'certman --install', then properly configure /etc/certman/certman.conf.") } else if err != nil { fmt.Printf("Error opening %s: %v\n", *configFile, err) } config, err = ezconf.LoadConfiguration(*configFile) if err != nil { log.Fatalf("Error loading configuration: %v\n", 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) ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() wg.Add(1) if config.GetAsString("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() } } }() } else if config.GetAsString("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 { fmt.Println("Invalid operating mode \"" + config.GetAsString("App.mode") + "\"") } // Cleanup on stop sig := <-sigChannel fmt.Printf("Program terminated with %v\n", sig) stop() wg.Wait() } } func stop() { cancel() clearPIDFile() } //var legoBaseArgs []string // //func maindis() { // config, err := ezconf.NewConfiguration("/etc/certman/certman.conf", "") // var domain string // if err != nil { // log.Fatalf("Error loading configuration: %v\n", err) // } // // args := os.Args // // // -d // hasDomain, domainIndex := contains(args, "-d") // if hasDomain { // domain = args[domainIndex+1] // } else { // log.Fatalf("Error, no domain passed. Please add '-d domain.tld' to the command\n") // } // // hasDns, dnsIndex := contains(args, "--dns") // // legoBaseArgs = []string{ // "-a", // "--dns", // "cloudflare", // "--email=" + config.GetAsString("Cloudflare.cf_email"), // "--domains=" + domain, // "--domains=*." + domain, // "--path=" + config.GetAsString("Certificates.certs_path"), // } // legoNewSiteArgs := append(legoBaseArgs, "run") // legoRenewSiteArgs := append(legoBaseArgs, "renew", "--days", "90") // // subdomains := config.GetAsStrings("Certificates.subdomains") // if subdomains != nil { // for i, subdomain := range subdomains { // legoBaseArgs = insert(legoBaseArgs, 5+i, "--domains=*."+subdomain+"."+domain) // } // } // // if hasDns { // legoBaseArgs = insert(legoBaseArgs, 3, "--dns.resolvers="+args[dnsIndex+1]) // } // // giteaClient = createGiteaClient() // gitWorkspace := &GitWorkspace{ // Storage: memory.NewStorage(), // FS: memfs.New(), // } // // var cmd *exec.Cmd // switch args[len(args)-1] { // case "gen": // { // url := createGiteaRepo(domain) // if url == "" { // return // } // gitWorkspace.Repo, gitWorkspace.WorkTree = cloneRepo(url, gitWorkspace) // if gitWorkspace.Repo == nil { // return // } // cmd = exec.Command("lego", legoNewSiteArgs...) // } // case "renew": // { // gitWorkspace.Repo, gitWorkspace.WorkTree = cloneRepo(config.GetAsString("Git.server")+"/"+config.GetAsString("Git.org_name")+"/"+domain+"-certificates.git", gitWorkspace) // if gitWorkspace.Repo == nil { // return // } // cmd = exec.Command("lego", legoRenewSiteArgs...) // } // case "gen-cert-only": // { // cmd = exec.Command("lego", legoNewSiteArgs...) // } // case "renew-cert-only": // { // cmd = exec.Command("lego", legoRenewSiteArgs...) // } // case "git": // { // url := createGiteaRepo(domain) // if url == "" { // return // } // gitWorkspace.Repo, gitWorkspace.WorkTree = cloneRepo(url, gitWorkspace) // if gitWorkspace.Repo == nil { // return // } // err := addAndPushCerts(domain, gitWorkspace) // if err != nil { // return // } // os.Exit(0) // } // default: // { // fmt.Println("Missing arguments: conclude command with 'gen' or 'renew'") // os.Exit(1) // } // } // cmd.Env = append(cmd.Environ(), // "CLOUDFLARE_DNS_API_TOKEN="+config.GetAsString("Cloudflare.cf_api_token"), // "CLOUDFLARE_ZONE_API_TOKEN"+config.GetAsString("Cloudflare.cf_api_token"), // "CLOUDFLARE_EMAIL="+config.GetAsString("Cloudflare.cf_email"), // ) // stdout, err := cmd.StdoutPipe() // if err != nil { // fmt.Printf("Error getting stdout from lego process: %v\n", err) // os.Exit(1) // } // err = cmd.Start() // if err != nil { // fmt.Printf("Error creating certs with lego: %v\n", err) // os.Exit(1) // } // scanner := bufio.NewScanner(stdout) // go func() { // for scanner.Scan() { // fmt.Println(scanner.Text()) // } // if err := scanner.Err(); err != nil { // fmt.Fprintln(os.Stderr, "reading standard input:", err) // } // }() // err = cmd.Wait() // if err != nil { // fmt.Printf("Error waiting for lego command to finish: %v\n", err) // os.Exit(1) // } // err = addAndPushCerts(domain, gitWorkspace) // if err != nil { // fmt.Printf("Error adding and pushing certs: %v\n", err) // return // } //}