From 161d278cdeff96b313f8f5a416de30c569e2cccc Mon Sep 17 00:00:00 2001 From: mahesh bhatiya Date: Sat, 7 Jun 2025 23:43:05 +0530 Subject: [PATCH] feat(cmd): add create-domain command to generate and enable Apache config - Implemented `create-domain` CLI command to generate Apache virtual host config - Writes config file to /etc/apache2/sites-available - Enables site using a2ensite and reloads Apache service - Added flag validation and error handling --- cmd/root.go | 111 ++++++++++++++++++++++++++++++++++++++++ config/apache/apache.go | 30 +++++++++++ config/config.go | 22 ++++++++ internal/utils.go | 21 ++++++++ 4 files changed, 184 insertions(+) create mode 100644 cmd/root.go create mode 100644 config/apache/apache.go create mode 100644 config/config.go create mode 100644 internal/utils.go diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..86e5d0d --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,111 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "stackroost/config" + "stackroost/internal" + "github.com/spf13/cobra" +) + +// rootCmd is the base command +var rootCmd = &cobra.Command{ + Use: "stackroost", + Short: "StackRoost CLI - manage your Linux servers with ease", + Run: func(cmd *cobra.Command, args []string) { + printWelcome() + }, +} + +// createDomainCmd is the command to create a web server configuration +var createDomainCmd = &cobra.Command{ + Use: "create-domain", + Short: "Create a web server configuration for a domain", + Run: func(cmd *cobra.Command, args []string) { + domain, _ := cmd.Flags().GetString("name") + port, _ := cmd.Flags().GetString("port") + serverType, _ := cmd.Flags().GetString("server") + + if internal.IsNilOrEmpty(domain) { + fmt.Println("Error: --name flag is required and cannot be empty") + os.Exit(1) + } + if internal.IsNilOrEmpty(port) { + port = "80" // Default port + } + + // Create web server configuration generator + configGen, err := config.NewWebServerConfig(serverType) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + // Generate configuration + configContent, err := configGen.Generate(domain, port) + if err != nil { + fmt.Printf("Error generating config: %v\n", err) + os.Exit(1) + } + + // Write configuration to file + if err := writeConfigFile(domain, configContent, configGen.GetFileExtension()); err != nil { + fmt.Printf("Error writing config file: %v\n", err) + os.Exit(1) + } + + filename := fmt.Sprintf("%s%s", domain, configGen.GetFileExtension()) + + // Enable site using a2ensite + if err := internal.RunCommand("sudo", "a2ensite", filename); err != nil { + fmt.Printf("Failed to enable site: %v\n", err) + os.Exit(1) + } + + // Reload apache to apply changes + if err := internal.RunCommand("sudo", "systemctl", "reload", "apache2"); err != nil { + fmt.Printf("Failed to reload apache: %v\n", err) + os.Exit(1) + } + + fmt.Printf("%s configuration created and enabled for %s on port %s\n", 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("port", "p", "80", "Port for the configuration (default: 80)") + createDomainCmd.Flags().StringP("server", "s", "apache", "Web server type (e.g., apache, nginx, caddy)") + createDomainCmd.MarkFlagRequired("name") +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } +} + +func printWelcome() { + fmt.Println("Welcome to StackRoost CLI!") + fmt.Println("Your terminal assistant for managing Linux servers.") +} + +// writeConfigFile writes the configuration to a file +func writeConfigFile(domain, content, extension string) error { + outputDir := "/etc/apache2/sites-available" + if err := os.MkdirAll(outputDir, 0755); err != nil { + return fmt.Errorf("failed to create output directory: %v", err) + } + + filename := fmt.Sprintf("%s%s", domain, extension) + outputPath := filepath.Join(outputDir, filename) + + if err := os.WriteFile(outputPath, []byte(content), 0644); err != nil { + return fmt.Errorf("failed to write config file: %v", err) + } + + return nil +} diff --git a/config/apache/apache.go b/config/apache/apache.go new file mode 100644 index 0000000..69e9d4f --- /dev/null +++ b/config/apache/apache.go @@ -0,0 +1,30 @@ +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 string) (string, error) { + vhostTemplate := ` + ServerName %s + ServerAlias www.%s + DocumentRoot /var/www/%s + ErrorLog ${APACHE_LOG_DIR}/%s-error.log + CustomLog ${APACHE_LOG_DIR}/%s-access.log combined + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + +` + + config := fmt.Sprintf(vhostTemplate, port, domain, domain, domain, domain, domain, domain) + return config, 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/config.go b/config/config.go new file mode 100644 index 0000000..f819735 --- /dev/null +++ b/config/config.go @@ -0,0 +1,22 @@ +package config + +import ( + "fmt" + "stackroost/config/apache" +) + +// WebServerConfig defines the interface for generating web server configurations +type WebServerConfig interface { + Generate(domain, port 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": + return &apache.ApacheConfig{}, nil + default: + return nil, fmt.Errorf("unsupported web server type: %s", serverType) + } +} \ No newline at end of file diff --git a/internal/utils.go b/internal/utils.go new file mode 100644 index 0000000..1f30429 --- /dev/null +++ b/internal/utils.go @@ -0,0 +1,21 @@ +package internal + +import ( + "fmt" + "os/exec" +) + +// 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 = nil + cmd.Stderr = nil + if err := cmd.Run(); err != nil { + return fmt.Errorf("command %s %v failed: %w", name, args, err) + } + return nil +} + +func IsNilOrEmpty(s string) bool { + return len(s) == 0 +} \ No newline at end of file