Files

102 lines
3.6 KiB
Python
Raw Permalink Normal View History

2026-04-07 02:04:22 +05:30
"""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"}