diff --git a/cmd/root.go b/cmd/root.go index 86e5d0d..6281238 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,111 +1,179 @@ package cmd import ( - "fmt" - "os" - "path/filepath" - "stackroost/config" - "stackroost/internal" - "github.com/spf13/cobra" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "stackroost/config" + "stackroost/internal" ) -// 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() - }, + 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) - }, + 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") + shellUser, _ := cmd.Flags().GetBool("shelluser") + password, _ := cmd.Flags().GetString("pass") + createDir, _ := cmd.Flags().GetBool("useridr") + + if internal.IsNilOrEmpty(domain) { + fmt.Println("Error: --name flag is required and cannot be empty") + os.Exit(1) + } + if internal.IsNilOrEmpty(port) { + port = "80" + } + + // Extract username from domain + username := strings.Split(domain, ".")[0] + + // Check config existence first + ext := ".conf" + configPath := filepath.Join("/etc/apache2/sites-available", domain+ext) + 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.") + os.Exit(1) + } + + fmt.Println(" Starting setup for domain:", domain) + + // Shell user creation + if shellUser { + if internal.IsNilOrEmpty(password) { + fmt.Println(" Error: --pass is required when --shelluser is true") + os.Exit(1) + } + + fmt.Println("🔧 Creating system user:", 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) + os.Exit(1) + } + + if err := internal.RunCommand("sudo", "bash", "-c", setPassCmd); err != nil { + fmt.Printf(" Failed to set password: %v\n", err) + os.Exit(1) + } + + fmt.Printf(" User '%s' created with shell access\n", username) + } + + // Create user directory + if createDir { + fmt.Println(" 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) + 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) + os.Exit(1) + } + + fmt.Printf(" Directory '%s' created and owned by '%s'\n", publicHtmlPath, username) + } + + fmt.Println(" Generating Apache configuration...") + + configGen, err := config.NewWebServerConfig(serverType) + if err != nil { + fmt.Printf(" Error: %v\n", 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) + os.Exit(1) + } + + if err := writeConfigFile(domain, configContent, configGen.GetFileExtension()); err != nil { + fmt.Printf(" Error writing config file: %v\n", err) + os.Exit(1) + } + + fmt.Println(" Configuration file created.") + + filename := fmt.Sprintf("%s%s", domain, configGen.GetFileExtension()) + + fmt.Println(" Enabling site with a2ensite...") + if err := internal.RunCommand("sudo", "a2ensite", filename); err != nil { + fmt.Printf(" Failed to enable site: %v\n", err) + os.Exit(1) + } + + fmt.Println("Reloading Apache server...") + 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") + rootCmd.AddCommand(createDomainCmd) + createDomainCmd.Flags().StringP("name", "n", "", "Domain name for the configuration (e.g., mahesh.spark.dev)") + 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") + 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) - } + 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.") + 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) - } + 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) + 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) - } + if _, err := os.Stat(outputPath); err == nil { + return fmt.Errorf("configuration for '%s' already exists at %s", domain, outputPath) + } - return nil + 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 index 69e9d4f..2f9748b 100644 --- a/config/apache/apache.go +++ b/config/apache/apache.go @@ -6,24 +6,24 @@ import "fmt" type ApacheConfig struct{} // Generate creates an Apache virtual host configuration -func (a *ApacheConfig) Generate(domain, port string) (string, error) { +func (a *ApacheConfig) Generate(domain, port, username string) (string, error) { vhostTemplate := ` ServerName %s ServerAlias www.%s - DocumentRoot /var/www/%s + DocumentRoot /home/%s/public_html 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 + 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" diff --git a/config/config.go b/config/config.go index f819735..6c97d31 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,7 @@ import ( // WebServerConfig defines the interface for generating web server configurations type WebServerConfig interface { - Generate(domain, port string) (string, error) + Generate(domain, port, username string) (string, error) GetFileExtension() string }