102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
"""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"}
|