diff --git a/cmd/root.go b/cmd/root.go index ceb4b5d..86e5d0d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,26 +1,111 @@ package cmd import ( - "fmt" - "github.com/spf13/cobra" + "fmt" + "os" + "path/filepath" + "stackroost/config" + "stackroost/internal" + "github.com/spf13/cobra" ) // rootCmd is the base command var rootCmd = &cobra.Command{ - Use: "stackrooy", - Short: "StackRooy CLI - manage your Linux servers with ease", - Run: func(cmd *cobra.Command, args []string) { - printWelcome() - }, + 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) - } + if err := rootCmd.Execute(); err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } } func printWelcome() { - fmt.Println("Welcome to StackRoot CLI!") - fmt.Println("Your terminal assistant for managing Linux servers.") + 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 index 5de376f..bfcf731 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -1,5 +1,22 @@ 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 = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("command %s %v failed: %w", name, args, err) + } + return nil +} + +// IsNilOrEmpty returns true if the string is empty or "" func IsNilOrEmpty(s string) bool { - return s == "" || s == "" -} \ No newline at end of file + return s == "" || s == "" +}