package handlers import ( "crypto/sha256" "encoding/hex" "fmt" "net/http" "os" "os/exec" "path/filepath" "strconv" "time" "github.com/gin-gonic/gin" ) // SetupHandler handles server configuration type SetupHandler struct { configPath string } func NewSetupHandler(configPath string) *SetupHandler { return &SetupHandler{ configPath: configPath, } } // ShowSetupPage displays the web setup interface func (h *SetupHandler) ShowSetupPage(c *gin.Context) { html := ` RedFlag - Server Configuration

🚀 RedFlag Server Setup

Configure your update management server

🔐 Admin Account

💾 Database Configuration

🌐 Server Configuration

` c.Data(200, "text/html; charset=utf-8", []byte(html)) } // ConfigureServer handles the configuration submission func (h *SetupHandler) ConfigureServer(c *gin.Context) { var req struct { AdminUser string `json:"adminUser"` AdminPass string `json:"adminPassword"` DBHost string `json:"dbHost"` DBPort string `json:"dbPort"` DBName string `json:"dbName"` DBUser string `json:"dbUser"` DBPassword string `json:"dbPassword"` ServerHost string `json:"serverHost"` ServerPort string `json:"serverPort"` MaxSeats string `json:"maxSeats"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"}) return } // Validate inputs if req.AdminUser == "" || req.AdminPass == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Admin username and password are required"}) return } if req.DBHost == "" || req.DBPort == "" || req.DBName == "" || req.DBUser == "" || req.DBPassword == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "All database fields are required"}) return } // Parse numeric values dbPort, err := strconv.Atoi(req.DBPort) if err != nil || dbPort <= 0 || dbPort > 65535 { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid database port"}) return } serverPort, err := strconv.Atoi(req.ServerPort) if err != nil || serverPort <= 0 || serverPort > 65535 { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid server port"}) return } maxSeats, err := strconv.Atoi(req.MaxSeats) if err != nil || maxSeats <= 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid maximum agent seats"}) return } // Create configuration content envContent := fmt.Sprintf(`# RedFlag Server Configuration # Generated by web setup # Server Configuration REDFLAG_SERVER_HOST=%s REDFLAG_SERVER_PORT=%d REDFLAG_TLS_ENABLED=false # REDFLAG_TLS_CERT_FILE= # REDFLAG_TLS_KEY_FILE= # Database Configuration REDFLAG_DB_HOST=%s REDFLAG_DB_PORT=%d REDFLAG_DB_NAME=%s REDFLAG_DB_USER=%s REDFLAG_DB_PASSWORD=%s # Admin Configuration REDFLAG_ADMIN_USER=%s REDFLAG_ADMIN_PASSWORD=%s REDFLAG_JWT_SECRET=%s # Agent Registration REDFLAG_TOKEN_EXPIRY=24h REDFLAG_MAX_TOKENS=100 REDFLAG_MAX_SEATS=%d # Legacy Configuration (for backwards compatibility) SERVER_PORT=%d DATABASE_URL=postgres://%s:%s@%s:%d/%s?sslmode=disable JWT_SECRET=%s CHECK_IN_INTERVAL=300 OFFLINE_THRESHOLD=600 TIMEZONE=UTC LATEST_AGENT_VERSION=0.1.16`, req.ServerHost, serverPort, req.DBHost, dbPort, req.DBName, req.DBUser, req.DBPassword, req.AdminUser, req.AdminPass, deriveJWTSecret(req.AdminUser, req.AdminPass), maxSeats, serverPort, req.DBUser, req.DBPassword, req.DBHost, dbPort, req.DBName, deriveJWTSecret(req.AdminUser, req.AdminPass)) // Write configuration to persistent location configDir := "/app/config" if err := os.MkdirAll(configDir, 0755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create config directory"}) return } envPath := filepath.Join(configDir, ".env") if err := os.WriteFile(envPath, []byte(envContent), 0600); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save configuration"}) return } // Trigger graceful server restart after configuration go func() { time.Sleep(2 * time.Second) // Give response time to reach client // Get the current executable path execPath, err := os.Executable() if err != nil { fmt.Printf("Failed to get executable path: %v\n", err) return } // Restart the server with the same executable cmd := exec.Command(execPath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin // Start the new process if err := cmd.Start(); err != nil { fmt.Printf("Failed to start new server process: %v\n", err) return } // Exit the current process gracefully fmt.Printf("Server restarting... PID: %d\n", cmd.Process.Pid) os.Exit(0) }() c.JSON(http.StatusOK, gin.H{ "message": "Configuration saved successfully! Server will restart automatically.", "configPath": envPath, "restart": true, }) } // deriveJWTSecret generates a JWT secret from admin credentials func deriveJWTSecret(username, password string) string { hash := sha256.Sum256([]byte(username + password + "redflag-jwt-2024")) return hex.EncodeToString(hash[:]) }