From fcff144ac4f88ab3c1dcf158df2762cd88dce8cb Mon Sep 17 00:00:00 2001 From: mahesh bhatiya Date: Fri, 27 Jun 2025 22:27:38 +0530 Subject: [PATCH] feat(cli): add renew-ssl command to manage SSL certificate renewal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added 'renew-ssl' CLI command with support for: --all → renew all SSL certificates via Certbot --domain → renew certificate for a specific domain --force → force renewal even if not due - Auto-detects the web server (apache/nginx/caddy) for the given domain by checking config paths. - Uses certbot with secure defaults and non-interactive mode. - Reuses internal.RunCommand and logger for consistent output. This command streamlines certificate renewal and automates detection of server configurations. --- cmd/renew_ssl.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 cmd/renew_ssl.go diff --git a/cmd/renew_ssl.go b/cmd/renew_ssl.go new file mode 100644 index 0000000..c5c13c9 --- /dev/null +++ b/cmd/renew_ssl.go @@ -0,0 +1,92 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "stackroost/internal" + "stackroost/internal/logger" +) + +var ( + renewAll bool + domainName string + forceFlag bool +) + +var renewSSLCmd = &cobra.Command{ + Use: "renew-ssl", + Short: "Renew SSL certificates for all or specific domains", + Run: func(cmd *cobra.Command, args []string) { + if renewAll { + logger.Info("Renewing SSL certificates for all domains") + err := internal.RunCommand("sudo", "certbot", "renew") + if err != nil { + logger.Error(fmt.Sprintf("SSL renewal failed: %v", err)) + os.Exit(1) + } + logger.Success("All certificates renewed successfully") + return + } + + if domainName == "" { + logger.Error("Either --all or --domain must be provided") + os.Exit(1) + } + + serverType := detectServerType(domainName) + if serverType == "" { + logger.Error(fmt.Sprintf("Could not detect server type for domain: %s", domainName)) + os.Exit(1) + } + + logger.Info(fmt.Sprintf("Renewing certificate for %s (%s)", domainName, serverType)) + + cmdArgs := []string{ + fmt.Sprintf("--%s", serverType), + "-d", domainName, + "-d", "www." + domainName, + "--non-interactive", + "--agree-tos", + "--register-unsafely-without-email", + } + + if forceFlag { + cmdArgs = append(cmdArgs, "--force-renewal") + } + + if err := internal.RunCommand("sudo", append([]string{"certbot"}, cmdArgs...)...); err != nil { + logger.Error(fmt.Sprintf("SSL renewal failed for %s: %v", domainName, err)) + os.Exit(1) + } + + logger.Success(fmt.Sprintf("Certificate renewed for %s", domainName)) + }, +} + +func init() { + rootCmd.AddCommand(renewSSLCmd) + renewSSLCmd.Flags().BoolVar(&renewAll, "all", false, "Renew all certificates") + renewSSLCmd.Flags().StringVar(&domainName, "domain", "", "Domain to renew certificate for") + renewSSLCmd.Flags().BoolVar(&forceFlag, "force", false, "Force renew the certificate") +} + +// detectServerType scans known config directories to guess server type +func detectServerType(domain string) string { + filename := domain + ".conf" + + paths := map[string]string{ + "apache": "/etc/apache2/sites-available", + "nginx": "/etc/nginx/sites-available", + "caddy": "/etc/caddy/sites-available", + } + + for server, dir := range paths { + if _, err := os.Stat(filepath.Join(dir, filename)); err == nil { + return server + } + } + return "" +}