Files
atlas/internal/httpapp/service_handlers.go

250 lines
7.0 KiB
Go

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),
})
}