"""YakPanel - System services (systemctl)""" from fastapi import APIRouter, Depends, HTTPException from app.core.utils import exec_shell_sync from app.api.auth import get_current_user from app.models.user import User router = APIRouter(prefix="/service", tags=["service"]) # Common services (name -> systemd unit) SERVICES = [ {"id": "nginx", "name": "Nginx", "unit": "nginx"}, {"id": "mysql", "name": "MySQL", "unit": "mysql"}, {"id": "mariadb", "name": "MariaDB", "unit": "mariadb"}, {"id": "php-fpm", "name": "PHP-FPM", "unit": "php*-fpm"}, {"id": "redis", "name": "Redis", "unit": "redis-server"}, {"id": "pure-ftpd", "name": "Pure-FTPd", "unit": "pure-ftpd"}, ] def _get_unit(unit_pattern: str) -> str: """Resolve unit pattern (e.g. php*-fpm) to actual unit name.""" if "*" not in unit_pattern: return unit_pattern out, _ = exec_shell_sync("systemctl list-unit-files --type=service --no-legend 2>/dev/null | grep -E 'php[0-9.-]+-fpm' | head -1", timeout=5) if out: return out.split()[0] return "php8.2-fpm" # fallback def _service_status(unit: str) -> str: """Get service status: active, inactive, failed, not-found.""" resolved = _get_unit(unit) out, err = exec_shell_sync(f"systemctl is-active {resolved} 2>/dev/null", timeout=5) status = out.strip() if out else "inactive" if err or status not in ("active", "inactive", "failed", "activating"): return "inactive" if status else "not-found" return status @router.get("/list") async def service_list(current_user: User = Depends(get_current_user)): """List services with status""" result = [] for s in SERVICES: unit = _get_unit(s["unit"]) status = _service_status(s["unit"]) result.append({ **s, "unit": unit, "status": status, }) return {"services": result} @router.post("/{service_id}/start") async def service_start( service_id: str, current_user: User = Depends(get_current_user), ): """Start service""" s = next((x for x in SERVICES if x["id"] == service_id), None) if not s: raise HTTPException(status_code=404, detail="Service not found") unit = _get_unit(s["unit"]) out, err = exec_shell_sync(f"systemctl start {unit}", timeout=30) if err and "Failed" in err: raise HTTPException(status_code=500, detail=err.strip() or out.strip()) return {"status": True, "msg": "Started"} @router.post("/{service_id}/stop") async def service_stop( service_id: str, current_user: User = Depends(get_current_user), ): """Stop service""" s = next((x for x in SERVICES if x["id"] == service_id), None) if not s: raise HTTPException(status_code=404, detail="Service not found") unit = _get_unit(s["unit"]) out, err = exec_shell_sync(f"systemctl stop {unit}", timeout=30) if err and "Failed" in err: raise HTTPException(status_code=500, detail=err.strip() or out.strip()) return {"status": True, "msg": "Stopped"} @router.post("/{service_id}/restart") async def service_restart( service_id: str, current_user: User = Depends(get_current_user), ): """Restart service""" s = next((x for x in SERVICES if x["id"] == service_id), None) if not s: raise HTTPException(status_code=404, detail="Service not found") unit = _get_unit(s["unit"]) out, err = exec_shell_sync(f"systemctl restart {unit}", timeout=30) if err and "Failed" in err: raise HTTPException(status_code=500, detail=err.strip() or out.strip()) return {"status": True, "msg": "Restarted"}