package httpapp import ( "log" "net/http" "os/exec" "strconv" "strings" ) // ManagedService represents a service with its status type ManagedService struct { Name string `json:"name"` DisplayName string `json:"display_name"` Status string `json:"status"` Output string `json:"output,omitempty"` } // getServiceName extracts service name from query parameter or defaults to atlas-api func getServiceName(r *http.Request) string { serviceName := r.URL.Query().Get("service") if serviceName == "" { return "atlas-api" } return serviceName } // getAllServices returns list of all managed services func getAllServices() []ManagedService { services := []ManagedService{ {Name: "atlas-api", DisplayName: "AtlasOS API"}, {Name: "smbd", DisplayName: "SMB/CIFS (Samba)"}, {Name: "nfs-server", DisplayName: "NFS Server"}, {Name: "target", DisplayName: "iSCSI Target"}, } return services } // getServiceStatus returns the status of a specific service func getServiceStatus(serviceName string) (string, string, error) { cmd := exec.Command("systemctl", "status", serviceName, "--no-pager", "-l") output, err := cmd.CombinedOutput() outputStr := string(output) if err != nil { // systemctl status returns non-zero exit code even when service is running // Check if output contains "active (running)" to determine actual status if strings.Contains(outputStr, "active (running)") { return "running", outputStr, nil } if strings.Contains(outputStr, "inactive (dead)") { return "stopped", outputStr, nil } if strings.Contains(outputStr, "failed") { return "failed", outputStr, nil } // Service might not exist if strings.Contains(outputStr, "could not be found") || strings.Contains(outputStr, "not found") { return "not-found", outputStr, nil } return "unknown", outputStr, err } status := "unknown" if strings.Contains(outputStr, "active (running)") { status = "running" } else if strings.Contains(outputStr, "inactive (dead)") { status = "stopped" } else if strings.Contains(outputStr, "failed") { status = "failed" } return status, outputStr, nil } // handleListServices returns the status of all services func (a *App) handleListServices(w http.ResponseWriter, r *http.Request) { allServices := getAllServices() servicesStatus := make([]ManagedService, 0, len(allServices)) for _, svc := range allServices { status, output, err := getServiceStatus(svc.Name) if err != nil { log.Printf("error getting status for %s: %v", svc.Name, err) status = "error" } servicesStatus = append(servicesStatus, ManagedService{ Name: svc.Name, DisplayName: svc.DisplayName, Status: status, Output: output, }) } writeJSON(w, http.StatusOK, map[string]interface{}{ "services": servicesStatus, }) } // handleServiceStatus returns the status of a specific service func (a *App) handleServiceStatus(w http.ResponseWriter, r *http.Request) { serviceName := getServiceName(r) status, output, err := getServiceStatus(serviceName) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{ "error": "failed to get service status", "details": output, }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "service": serviceName, "status": status, "output": output, }) } // handleServiceStart starts a service func (a *App) handleServiceStart(w http.ResponseWriter, r *http.Request) { serviceName := getServiceName(r) var cmd *exec.Cmd // Special handling for SMB - use smbcontrol for reload, but systemctl for start/stop if serviceName == "smbd" { cmd = exec.Command("systemctl", "start", "smbd") } else { cmd = exec.Command("systemctl", "start", serviceName) } output, err := cmd.CombinedOutput() if err != nil { log.Printf("service start error for %s: %v", serviceName, err) writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "error": "failed to start service", "service": serviceName, "details": string(output), }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "message": "service started successfully", "service": serviceName, }) } // handleServiceStop stops a service func (a *App) handleServiceStop(w http.ResponseWriter, r *http.Request) { serviceName := getServiceName(r) cmd := exec.Command("systemctl", "stop", serviceName) output, err := cmd.CombinedOutput() if err != nil { log.Printf("service stop error for %s: %v", serviceName, err) writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "error": "failed to stop service", "service": serviceName, "details": string(output), }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "message": "service stopped successfully", "service": serviceName, }) } // handleServiceRestart restarts a service func (a *App) handleServiceRestart(w http.ResponseWriter, r *http.Request) { serviceName := getServiceName(r) cmd := exec.Command("systemctl", "restart", serviceName) output, err := cmd.CombinedOutput() if err != nil { log.Printf("service restart error for %s: %v", serviceName, err) writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "error": "failed to restart service", "service": serviceName, "details": string(output), }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "message": "service restarted successfully", "service": serviceName, }) } // handleServiceReload reloads a service func (a *App) handleServiceReload(w http.ResponseWriter, r *http.Request) { serviceName := getServiceName(r) var cmd *exec.Cmd // Special handling for SMB - use smbcontrol for reload if serviceName == "smbd" { cmd = exec.Command("smbcontrol", "all", "reload-config") } else { cmd = exec.Command("systemctl", "reload", serviceName) } output, err := cmd.CombinedOutput() if err != nil { log.Printf("service reload error for %s: %v", serviceName, err) writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "error": "failed to reload service", "service": serviceName, "details": string(output), }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "message": "service reloaded successfully", "service": serviceName, }) } // handleServiceLogs returns the logs of a service func (a *App) handleServiceLogs(w http.ResponseWriter, r *http.Request) { serviceName := getServiceName(r) // Get number of lines from query parameter (default: 50) linesStr := r.URL.Query().Get("lines") lines := "50" if linesStr != "" { if n, err := strconv.Atoi(linesStr); err == nil && n > 0 && n <= 1000 { lines = linesStr } } cmd := exec.Command("journalctl", "-u", serviceName, "-n", lines, "--no-pager") output, err := cmd.CombinedOutput() if err != nil { log.Printf("service logs error for %s: %v", serviceName, err) writeJSON(w, http.StatusInternalServerError, map[string]interface{}{ "error": "failed to get service logs", "service": serviceName, "details": string(output), }) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "service": serviceName, "logs": string(output), }) }