Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 154 additions & 86 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -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/<user>/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
}
10 changes: 5 additions & 5 deletions config/apache/apache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 := `<VirtualHost *:%s>
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
<Directory /var/www/%s>
<Directory /home/%s/public_html>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>`

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"
Expand Down
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Loading