From 7e9c4840dd61e319d824bf7745b8f393dfc209c6 Mon Sep 17 00:00:00 2001 From: mahesh bhatiya Date: Fri, 13 Jun 2025 07:03:03 +0530 Subject: [PATCH] enhance logger output --- cmd/root.go | 152 ++++++++++++++++++++++++-------------- config/apache/apache.go | 4 - config/caddy/caddy.go | 1 - config/config.go | 2 - config/nginx/nginx.go | 1 - internal/logger/logger.go | 45 +++++++++++ internal/utils.go | 2 - 7 files changed, 143 insertions(+), 64 deletions(-) create mode 100644 internal/logger/logger.go diff --git a/cmd/root.go b/cmd/root.go index f3f55f3..79b92c4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "stackroost/config" "stackroost/internal" + "stackroost/internal/logger" ) var rootCmd = &cobra.Command{ @@ -23,6 +24,8 @@ var createDomainCmd = &cobra.Command{ Use: "create-domain", Short: "Create a web server configuration for a domain", Run: func(cmd *cobra.Command, args []string) { + logger.Info("Starting create-domain command execution") + domain, _ := cmd.Flags().GetString("name") port, _ := cmd.Flags().GetString("port") serverType, _ := cmd.Flags().GetString("server") @@ -31,14 +34,18 @@ var createDomainCmd = &cobra.Command{ createDir, _ := cmd.Flags().GetBool("useridr") if internal.IsNilOrEmpty(domain) { - fmt.Println("Error: --name flag is required and cannot be empty") + logger.Error("Domain name is required and cannot be empty") os.Exit(1) } + if internal.IsNilOrEmpty(port) { + logger.Info("No port specified, defaulting to 80") port = "80" } + username := strings.Split(domain, ".")[0] - + logger.Debug(fmt.Sprintf("Extracted username: %s from domain: %s", username, domain)) + ext := ".conf" var configPath string @@ -49,142 +56,138 @@ var createDomainCmd = &cobra.Command{ configPath = filepath.Join("/etc/caddy/sites-available", domain+ext) case "apache": configPath = filepath.Join("/etc/apache2/sites-available", domain+ext) - default: - fmt.Println("Error: Unsupported server type. Supported types are: apache, nginx, caddy") + default: + logger.Error(fmt.Sprintf("Unsupported server type: %s. Supported types are: apache, nginx, caddy", serverType)) + os.Exit(1) } if _, err := os.Stat(configPath); err == nil { - fmt.Printf(" Configuration for '%s' already exists at %s\n", domain, configPath) - fmt.Println(" Aborting to prevent overwriting existing configuration.") + logger.Error(fmt.Sprintf("Configuration for '%s' already exists at %s", domain, configPath)) os.Exit(1) } - fmt.Println(" Starting setup for domain:", domain) + logger.Info(fmt.Sprintf("Initiating setup for domain: %s with server type: %s", domain, serverType)) - // Shell user creation if shellUser { if internal.IsNilOrEmpty(password) { - fmt.Println(" Error: --pass is required when --shelluser is true") + logger.Error("Password is required when shelluser is enabled") os.Exit(1) } - fmt.Println("๐Ÿ”ง Creating system user:", username) + logger.Info(fmt.Sprintf("Creating system user: %s", username)) userAddCmd := fmt.Sprintf("id -u %s || useradd -m -s /bin/bash %s", username, username) setPassCmd := fmt.Sprintf("echo '%s:%s' | chpasswd", username, password) if err := internal.RunCommand("sudo", "bash", "-c", userAddCmd); err != nil { - fmt.Printf(" Failed to create user: %v\n", err) + logger.Error(fmt.Sprintf("Failed to create user %s: %v", username, err)) os.Exit(1) } if err := internal.RunCommand("sudo", "bash", "-c", setPassCmd); err != nil { - fmt.Printf(" Failed to set password: %v\n", err) + logger.Error(fmt.Sprintf("Failed to set password for user %s: %v", username, err)) os.Exit(1) } - fmt.Printf(" User '%s' created with shell access\n", username) + logger.Success(fmt.Sprintf("User '%s' created with shell access", username)) } - // Create user directory if createDir { - fmt.Println(" Creating public_html directory for user...") + logger.Info("Creating public_html directory for user") publicHtmlPath := fmt.Sprintf("/home/%s/public_html", username) if err := os.MkdirAll(publicHtmlPath, 0755); err != nil { - fmt.Printf(" Failed to create directory: %v\n", err) + logger.Error(fmt.Sprintf("Failed to create directory %s: %v", publicHtmlPath, err)) os.Exit(1) } if err := internal.RunCommand("sudo", "chown", "-R", fmt.Sprintf("%s:%s", username, username), fmt.Sprintf("/home/%s", username)); err != nil { - fmt.Printf(" Failed to assign ownership: %v\n", err) + logger.Error(fmt.Sprintf("Failed to assign ownership for %s: %v", username, err)) os.Exit(1) } - fmt.Printf(" Directory '%s' created and owned by '%s'\n", publicHtmlPath, username) + logger.Success(fmt.Sprintf("Directory '%s' created and owned by '%s'", publicHtmlPath, username)) } - fmt.Println(" Generating Apache configuration...") + logger.Info(fmt.Sprintf("Generating %s configuration", serverType)) configGen, err := config.NewWebServerConfig(serverType) if err != nil { - fmt.Printf(" Error: %v\n", err) + logger.Error(fmt.Sprintf("Failed to create config generator: %v", err)) os.Exit(1) } - // Use extracted username in DocumentRoot configContent, err := configGen.Generate(domain, port, username) if err != nil { - fmt.Printf(" Error generating config: %v\n", err) + logger.Error(fmt.Sprintf("Failed to generate config for %s: %v", domain, err)) os.Exit(1) } if err := writeConfigFile(domain, configContent, configGen.GetFileExtension()); err != nil { - fmt.Printf(" Error writing config file: %v\n", err) + logger.Error(fmt.Sprintf("Failed to write config file: %v", err)) os.Exit(1) } - fmt.Println(" Configuration file created.") + logger.Success("Configuration file created") filename := fmt.Sprintf("%s%s", domain, configGen.GetFileExtension()) switch serverType { case "apache": - fmt.Println(" Enabling site with a2ensite...") + logger.Info("Enabling site with a2ensite") if err := internal.RunCommand("sudo", "a2ensite", filename); err != nil { - fmt.Printf(" Failed to enable site: %v\n", err) + logger.Error(fmt.Sprintf("Failed to enable site %s: %v", filename, err)) os.Exit(1) } - fmt.Println("Reloading Apache server...") + logger.Info("Reloading Apache server") if err := internal.RunCommand("sudo", "systemctl", "reload", "apache2"); err != nil { - fmt.Printf(" Failed to reload apache: %v\n", err) + logger.Error(fmt.Sprintf("Failed to reload Apache: %v", err)) os.Exit(1) } case "nginx": sitePath := filepath.Join("/etc/nginx/sites-available", filename) linkPath := filepath.Join("/etc/nginx/sites-enabled", filename) - fmt.Println(" Enabling Nginx site...") + logger.Info("Enabling Nginx site") if _, err := os.Stat(linkPath); os.IsNotExist(err) { if err := internal.RunCommand("sudo", "ln", "-s", sitePath, linkPath); err != nil { - fmt.Printf(" Failed to enable nginx site: %v\n", err) + logger.Error(fmt.Sprintf("Failed to enable Nginx site %s: %v", filename, err)) os.Exit(1) } } - fmt.Println("Reloading Nginx server...") + logger.Info("Reloading Nginx server") if err := internal.RunCommand("sudo", "systemctl", "reload", "nginx"); err != nil { - fmt.Printf(" Failed to reload nginx: %v\n", err) + logger.Error(fmt.Sprintf("Failed to reload Nginx: %v", err)) os.Exit(1) } case "caddy": sitePath := filepath.Join("/etc/caddy/sites-available", filename) linkPath := filepath.Join("/etc/caddy/sites-enabled", filename) - fmt.Println(" Enabling Caddy site...") - + logger.Info("Enabling Caddy site") if _, err := os.Stat(linkPath); os.IsNotExist(err) { if err := internal.RunCommand("sudo", "ln", "-s", sitePath, linkPath); err != nil { - fmt.Printf(" Failed to enable caddy site: %v\n", err) + logger.Error(fmt.Sprintf("Failed to enable Caddy site %s: %v", filename, err)) os.Exit(1) } } - } - fmt.Println("Reloading Caddy server...") - if err := internal.RunCommand("sudo", "systemctl", "reload", "caddy"); err != nil { - fmt.Printf(" Failed to reload caddy: %v\n", err) - os.Exit(1) + logger.Info("Reloading Caddy server") + if err := internal.RunCommand("sudo", "systemctl", "reload", "caddy"); err != nil { + logger.Error(fmt.Sprintf("Failed to reload Caddy: %v", err)) + os.Exit(1) + } } - fmt.Printf("๐ŸŽ‰ %s configuration created and enabled for %s on port %s\n", serverType, domain, port) + logger.Success(fmt.Sprintf("%s configuration created and enabled for %s on port %s", serverType, domain, port)) }, } func init() { rootCmd.AddCommand(createDomainCmd) - createDomainCmd.Flags().StringP("name", "n", "", "Domain name for the configuration (e.g., mahesh.spark.dev)") + createDomainCmd.Flags().StringP("name", "n", "", "Domain name for the configuration (e.g., example.com)") createDomainCmd.Flags().Bool("shelluser", false, "Create a shell user for the domain") createDomainCmd.Flags().String("pass", "", "Password for the shell user") createDomainCmd.Flags().Bool("useridr", false, "Create user directory /home//public_html") @@ -195,42 +198,83 @@ func init() { func Execute() { if err := rootCmd.Execute(); err != nil { + logger.Error(fmt.Sprintf("Command execution failed: %v", err)) fmt.Println("Error:", err) os.Exit(1) } } func printWelcome() { - fmt.Println("Welcome to StackRoost CLI!") - fmt.Println("Your terminal assistant for managing Linux servers.") + reset := "\033[0m" + bold := "\033[1m" + gray := "\033[38;2;180;180;180m" + + title := "WELCOME TO STACKROOST CLI" + subTitle := "Smart Linux Server Manager" + + startR, startG, startB := 135, 206, 235 + endR, endG, endB := 255, 0, 0 + + length := len(title) + + fmt.Println() + fmt.Print(bold) + + for i, ch := range title { + t := float64(i) / float64(length-1) + r := int(float64(startR)*(1-t) + float64(endR)*t) + g := int(float64(startG)*(1-t) + float64(endG)*t) + b := int(float64(startB)*(1-t) + float64(endB)*t) + fmt.Printf("\033[38;2;%d;%d;%dm%c", r, g, b, ch) + } + + fmt.Println(reset) + fmt.Println() + + fmt.Printf("%s\033[38;2;135;206;235m%s%s\n\n", bold, subTitle, reset) + + fmt.Println(gray + "Welcome to StackRoost โ€” your powerful CLI for managing Linux domains," + reset) + fmt.Println(gray + "creating shell users, and configuring Apache ยท Nginx ยท Caddy effortlessly." + reset) + fmt.Println() + + fmt.Printf("%s\033[38;2;200;200;200mtry: %sstackroost --help%s\n\n", bold, reset, reset) } func writeConfigFile(domain, content, extension string) error { - var outputDir string + logger.Info(fmt.Sprintf("Writing configuration file for %s", domain)) + + var configPath string if extension == ".conf" { if strings.HasPrefix(content, "server") { - outputDir = "/etc/nginx/sites-available" + configPath = "/etc/nginx/sites-available" + logger.Debug("Detected nginx configuration") } else if strings.Contains(content, "php_fastcgi") { - outputDir = "/etc/caddy/sites-available" + configPath = "/etc/caddy/sites-available" + logger.Debug("Detected caddy configuration") } else { - outputDir = "/etc/apache2/sites-available" + configPath = "/etc/apache2/sites-available" + logger.Debug("Detected apache configuration") } } - if err := os.MkdirAll(outputDir, 0755); err != nil { - return fmt.Errorf("failed to create output directory: %v", err) + if err := os.MkdirAll(configPath, 0755); err != nil { + logger.Error(fmt.Sprintf("Failed to create output directory %s: %v", configPath, err)) + return err } filename := fmt.Sprintf("%s%s", domain, extension) - outputPath := filepath.Join(outputDir, filename) + outputPath := filepath.Join(configPath, filename) if _, err := os.Stat(outputPath); err == nil { - return fmt.Errorf("configuration for '%s' already exists at %s", domain, outputPath) + logger.Error(fmt.Sprintf("Configuration already exists at %s for domain %s", outputPath, domain)) + return fmt.Errorf("configuration exists") } if err := os.WriteFile(outputPath, []byte(content), 0644); err != nil { - return fmt.Errorf("failed to write config file: %v", err) + logger.Error(fmt.Sprintf("Failed to write config file %s: %v", outputPath, err)) + return err } + logger.Success(fmt.Sprintf("Configuration file written to %s", outputPath)) return nil -} +} \ No newline at end of file diff --git a/config/apache/apache.go b/config/apache/apache.go index 2f9748b..7968714 100644 --- a/config/apache/apache.go +++ b/config/apache/apache.go @@ -2,10 +2,8 @@ package apache import "fmt" -// ApacheConfig implements the WebServerConfig interface for Apache type ApacheConfig struct{} -// Generate creates an Apache virtual host configuration func (a *ApacheConfig) Generate(domain, port, username string) (string, error) { vhostTemplate := ` ServerName %s @@ -23,8 +21,6 @@ func (a *ApacheConfig) Generate(domain, port, username string) (string, error) { return fmt.Sprintf(vhostTemplate, port, domain, domain, username, domain, domain, username), nil } - -// GetFileExtension returns the file extension for Apache config files func (a *ApacheConfig) GetFileExtension() string { return ".conf" } \ No newline at end of file diff --git a/config/caddy/caddy.go b/config/caddy/caddy.go index dc3eb00..22b3d0b 100644 --- a/config/caddy/caddy.go +++ b/config/caddy/caddy.go @@ -2,7 +2,6 @@ package caddy import "fmt" -// CaddyConfig implements WebServerConfig for Caddy type CaddyConfig struct{} func (c *CaddyConfig) Generate(domain, port, username string) (string, error) { diff --git a/config/config.go b/config/config.go index 8e9ae9d..a6b5f0c 100644 --- a/config/config.go +++ b/config/config.go @@ -7,13 +7,11 @@ import ( "stackroost/config/caddy" ) -// WebServerConfig defines the interface for generating web server configurations type WebServerConfig interface { Generate(domain, port, username string) (string, error) GetFileExtension() string } -// NewWebServerConfig creates a new configuration generator based on the server type func NewWebServerConfig(serverType string) (WebServerConfig, error) { switch serverType { case "apache": diff --git a/config/nginx/nginx.go b/config/nginx/nginx.go index 63648c7..5a4a63d 100644 --- a/config/nginx/nginx.go +++ b/config/nginx/nginx.go @@ -2,7 +2,6 @@ package nginx import "fmt" -// NginxConfig implements WebServerConfig for Nginx type NginxConfig struct{} func (n *NginxConfig) Generate(domain, port, username string) (string, error) { diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..1ca6533 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,45 @@ +package logger + +import ( + "fmt" + "time" +) + +const ( + Reset = "\033[0m" + Gray = "\033[38;2;160;160;160m" + Green = "\033[38;2;0;220;100m" + Yellow = "\033[38;2;255;204;0m" + Red = "\033[38;2;255;80;80m" + Cyan = "\033[38;2;135;206;235m" + Blue = "\033[38;2;100;180;255m" + Bold = "\033[1m" +) + +func timeStamp() string { + return fmt.Sprintf("%s[%s]%s", Gray, time.Now().Format("15:04:05"), Reset) +} + +func log(label string, labelColor string, message string) { + fmt.Printf("%s %s%-8s%s %s\n", timeStamp(), labelColor, "["+label+"]", Reset, message) +} + +func Info(msg string) { + log("INFO", Cyan, msg) +} + +func Success(msg string) { + log("SUCCESS", Green, msg) +} + +func Warn(msg string) { + log("WARN", Yellow, msg) +} + +func Error(msg string) { + log("ERROR", Red, msg) +} + +func Debug(msg string) { + log("DEBUG", Blue, msg) +} \ No newline at end of file diff --git a/internal/utils.go b/internal/utils.go index 8710d43..40db50e 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -6,7 +6,6 @@ import ( "os" ) -// RunCommand runs a shell command with args and returns error if any func RunCommand(name string, args ...string) error { cmd := exec.Command(name, args...) cmd.Stdout = os.Stdout @@ -17,7 +16,6 @@ func RunCommand(name string, args ...string) error { return nil } -// IsNilOrEmpty returns true if the string is empty or "" func IsNilOrEmpty(s string) bool { return s == "" || s == "" }