# BZZZ Built-in Web Configuration UI Development Plan ## Clear Separation of Responsibilities ### chorus.services (Installer Host) - ✅ **Already Complete**: Hosts `install-chorus-enhanced.sh` - ✅ **Single Purpose**: One-line installation script download - ✅ **Working**: `curl -fsSL https://chorus.services/install.sh | sh` ### BZZZ (This Plan) - 🔄 **To Implement**: Built-in web UI for cluster configuration - 🎯 **Purpose**: Post-installation setup wizard when no config exists - 🌐 **Location**: Served by BZZZ itself at `:8080/setup` ## Architecture Overview The BZZZ binary will serve its own web UI for configuration when: 1. No configuration file exists (first run) 2. User explicitly accesses `:8080/setup` 3. Configuration is incomplete or invalid ## Implementation Strategy ### Phase 1: Integrate Web UI into BZZZ Binary #### 1.1 Embed Web UI in Go Binary **File**: `main.go` and new `pkg/webui/` ```go //go:embed web-ui/dist/* var webUIFiles embed.FS func main() { // Check if configuration exists if !configExists() { log.Println("🌐 No configuration found, starting setup wizard...") startWebUI() } // Normal BZZZ startup startBZZZServices() } func startWebUI() { // Serve embedded web UI // Start configuration wizard // Block until configuration is complete } ``` #### 1.2 Extend Existing HTTP Server **File**: `api/http_server.go` Add setup routes to existing server: ```go func (h *HTTPServer) setupRoutes(router *mux.Router) { // Existing API routes api := router.PathPrefix("/api").Subrouter() // ... existing routes ... // Setup wizard routes (only if no config exists) if !h.hasValidConfig() { h.setupWizardRoutes(router) } } func (h *HTTPServer) setupWizardRoutes(router *mux.Router) { // Serve static web UI files router.PathPrefix("/setup").Handler(http.StripPrefix("/setup", http.FileServer(http.FS(webUIFiles)))) // Setup API endpoints setup := router.PathPrefix("/api/setup").Subrouter() setup.HandleFunc("/system", h.handleSystemDetection).Methods("GET") setup.HandleFunc("/repository", h.handleRepositoryConfig).Methods("GET", "POST") setup.HandleFunc("/cluster", h.handleClusterConfig).Methods("GET", "POST") setup.HandleFunc("/validate", h.handleValidateConfig).Methods("POST") setup.HandleFunc("/save", h.handleSaveConfig).Methods("POST") } ``` ### Phase 2: Configuration Management Integration #### 2.1 Leverage Existing Config System **File**: `pkg/config/setup.go` (new) ```go type SetupManager struct { configPath string systemInfo *SystemInfo repoFactory *repository.DefaultProviderFactory taskCoordinator *coordinator.TaskCoordinator } func (s *SetupManager) DetectSystem() (*SystemInfo, error) { // Hardware detection (GPU, CPU, memory) // Network interface discovery // Software prerequisites check return systemInfo, nil } func (s *SetupManager) ValidateRepositoryConfig(config RepositoryConfig) error { // Use existing repository factory provider, err := s.repoFactory.CreateProvider(ctx, &repository.Config{ Provider: config.Provider, BaseURL: config.BaseURL, AccessToken: config.Token, // ... }) // Test connectivity return provider.ListAvailableTasks() } func (s *SetupManager) SaveConfiguration(config *SetupConfig) error { // Convert to existing config.Config format bzzzConfig := &config.Config{ Node: config.NodeConfig, Repository: config.RepositoryConfig, Agent: config.AgentConfig, // ... } // Save to YAML file return bzzzConfig.SaveToFile(s.configPath) } ``` #### 2.2 Repository Integration **File**: `api/setup_repository.go` (new) ```go func (h *HTTPServer) handleRepositoryConfig(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { // Return current repository configuration config := h.setupManager.GetRepositoryConfig() json.NewEncoder(w).Encode(config) return } // POST: Validate and save repository configuration var repoConfig RepositoryConfig if err := json.NewDecoder(r.Body).Decode(&repoConfig); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } // Validate using existing repository factory if err := h.setupManager.ValidateRepositoryConfig(repoConfig); err != nil { http.Error(w, fmt.Sprintf("Repository validation failed: %v", err), http.StatusBadRequest) return } // Save configuration h.setupManager.SaveRepositoryConfig(repoConfig) json.NewEncoder(w).Encode(map[string]string{"status": "success"}) } ``` ### Phase 3: Enhanced Web UI Components #### 3.1 Complete Existing Components The config-ui framework exists but needs implementation: **SystemDetection.tsx**: ```typescript const SystemDetection = ({ onComplete, onBack }) => { const [systemInfo, setSystemInfo] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { fetch('/api/setup/system') .then(res => res.json()) .then(data => { setSystemInfo(data); setLoading(false); }); }, []); return (

System Detection Results

{systemInfo && (
OS: {systemInfo.os}
Architecture: {systemInfo.arch}
GPUs: {systemInfo.gpus?.length || 0}
Memory: {systemInfo.memory}
{systemInfo.gpus?.length > 1 && (
💡 Multi-GPU detected! Consider Parallama for optimal performance.
)}
)}
); }; ``` **RepositoryConfiguration.tsx**: ```typescript const RepositoryConfiguration = ({ onComplete, onBack, configData }) => { const [provider, setProvider] = useState('gitea'); const [config, setConfig] = useState({ provider: 'gitea', baseURL: 'http://ironwood:3000', owner: '', repository: '', token: '' }); const [validating, setValidating] = useState(false); const validateConfig = async () => { setValidating(true); try { const response = await fetch('/api/setup/repository', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); if (response.ok) { onComplete(config); } else { const error = await response.text(); alert(`Validation failed: ${error}`); } } catch (error) { alert(`Connection failed: ${error.message}`); } setValidating(false); }; return (

Repository Configuration

{ e.preventDefault(); validateConfig(); }}>
{provider === 'gitea' && (
setConfig({...config, baseURL: e.target.value})} placeholder="http://ironwood:3000" />
)}
setConfig({...config, owner: e.target.value})} placeholder="username or organization" />
setConfig({...config, repository: e.target.value})} placeholder="repository name" />
setConfig({...config, token: e.target.value})} placeholder="access token" />
); }; ``` ### Phase 4: Build Process Integration #### 4.1 Web UI Build Integration **File**: `Makefile` or build script ```bash build-web-ui: cd install/config-ui && npm install && npm run build mkdir -p web-ui/dist cp -r install/config-ui/.next/static web-ui/dist/ cp -r install/config-ui/out/* web-ui/dist/ 2>/dev/null || true build-bzzz: build-web-ui go build -o bzzz main.go ``` #### 4.2 Embed Files in Binary **File**: `pkg/webui/embed.go` ```go package webui import ( "embed" "io/fs" "net/http" ) //go:embed dist/* var webUIFiles embed.FS func GetWebUIHandler() http.Handler { // Get the web-ui subdirectory webUIFS, err := fs.Sub(webUIFiles, "dist") if err != nil { panic(err) } return http.FileServer(http.FS(webUIFS)) } ``` ### Phase 5: Configuration Flow Integration #### 5.1 Startup Logic **File**: `main.go` ```go func main() { configPath := getConfigPath() // Check if configuration exists and is valid config, err := loadConfig(configPath) if err != nil || !isValidConfig(config) { fmt.Println("🌐 Starting BZZZ setup wizard...") fmt.Printf("📱 Open your browser to: http://localhost:8080/setup\n") // Start HTTP server in setup mode startSetupMode(configPath) // Wait for configuration to be saved waitForConfiguration(configPath) // Reload configuration config, err = loadConfig(configPath) if err != nil { log.Fatal("Failed to load configuration after setup:", err) } } // Normal BZZZ startup with configuration fmt.Println("🚀 Starting BZZZ with configuration...") startNormalMode(config) } func startSetupMode(configPath string) { // Start HTTP server in setup mode setupManager := NewSetupManager(configPath) httpServer := api.NewHTTPServer(8080, nil, nil) httpServer.SetSetupManager(setupManager) go func() { if err := httpServer.Start(); err != nil { log.Fatal("Failed to start setup server:", err) } }() } func waitForConfiguration(configPath string) { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: if fileExists(configPath) { if config, err := loadConfig(configPath); err == nil && isValidConfig(config) { fmt.Println("✅ Configuration saved successfully!") return } } case <-time.After(30 * time.Minute): log.Fatal("Setup timeout - no configuration provided within 30 minutes") } } } ``` #### 5.2 Configuration Validation **File**: `pkg/config/validation.go` ```go func isValidConfig(config *Config) bool { // Check required fields if config.Node.ID == "" || config.Node.Role == "" { return false } // Check repository configuration if config.Repository.Provider == "" || config.Repository.Config.Owner == "" { return false } // Check agent configuration if config.Agent.ID == "" || len(config.Agent.Expertise) == 0 { return false } return true } ``` ## Integration with Existing Systems ### ✅ **Leverage Without Changes** 1. **HTTP Server** (`api/http_server.go`) - Extend with setup routes 2. **Configuration System** (`pkg/config/`) - Use existing YAML structure 3. **Repository Factory** (`repository/factory.go`) - Validate credentials 4. **Task Coordinator** (`coordinator/task_coordinator.go`) - Agent management ### 🔧 **Minimal Extensions** 1. **Setup Manager** - New component using existing systems 2. **Web UI Routes** - Add to existing HTTP server 3. **Embedded Files** - Add web UI to binary 4. **Startup Logic** - Add configuration check to main.go ## File Structure ``` /home/tony/chorus/project-queues/active/BZZZ/ ├── main.go # Enhanced startup logic ├── api/ │ ├── http_server.go # Extended with setup routes │ ├── setup_handlers.go # New setup API handlers │ └── setup_repository.go # Repository configuration API ├── pkg/ │ ├── config/ │ │ ├── config.go # Existing │ │ ├── setup.go # New setup manager │ │ └── validation.go # New validation logic │ └── webui/ │ └── embed.go # New embedded web UI ├── install/config-ui/ # Existing React app │ ├── app/setup/page.tsx # Existing wizard framework │ ├── app/setup/components/ # Complete component implementations │ └── package.json # Existing └── web-ui/ # Build output (embedded) └── dist/ # Generated by build process ``` ## Implementation Timeline ### Week 1: Core Integration - **Day 1**: Setup manager and configuration validation - **Day 2**: Extend HTTP server with setup routes - **Day 3**: Repository configuration API integration - **Day 4**: System detection and hardware profiling - **Day 5**: Build process and file embedding ### Week 2: Web UI Implementation - **Day 1-2**: Complete SystemDetection and RepositoryConfiguration components - **Day 3-4**: Cluster formation and validation components - **Day 5**: UI polish and error handling ### Week 3: Integration and Testing - **Day 1-2**: End-to-end configuration flow testing - **Day 3-4**: Multi-node cluster testing - **Day 5**: Documentation and deployment ## Success Criteria ### Technical - [ ] BZZZ binary serves web UI when no config exists - [ ] Complete 8-step setup wizard functional - [ ] Repository integration with GITEA/GitHub validation - [ ] Configuration saves to existing YAML format - [ ] Seamless transition from setup to normal operation ### User Experience - [ ] Zero-config startup launches web wizard - [ ] Intuitive setup flow with validation - [ ] Professional UI matching BZZZ branding - [ ] Clear error messages and recovery options - [ ] Mobile-responsive design This plan keeps the clear separation between the chorus.services installer (already working) and BZZZ's built-in configuration system (to be implemented).