import os import systemd.dbus import dbus from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from typing import List app = FastAPI(title="NetMapper Agent", version="0.1.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], # da limitare in produzione allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) ALLOWED_SERVICES = [ "netmapper-frontend.service", "netmapper-backend.service", "netmapper-worker.service", ] class ServiceInfo(BaseModel): name: str active_state: str sub_state: str loaded: str pid: int | None = None class ActionRequest(BaseModel): mode: str = "sync" def get_unit(name: str): bus = dbus.SystemBus() systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager') unit_path = manager.GetUnit(name) unit = bus.get_object('org.freedesktop.systemd1', str(unit_path)) props = dbus.Interface(unit, 'org.freedesktop.DBus.Properties') return props.GetAll('org.freedesktop.systemd1.Unit') def refresh_dbus(): bus = dbus.SystemBus() systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager') return manager @app.get("/health") def health(): return {"status": "ok"} @app.get("/services", response_model=List[ServiceInfo]) def list_services(): bus = dbus.SystemBus() systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager') units = manager.ListUnits() result = [] for u in units: name = u[0] if name not in ALLOWED_SERVICES: continue result.append(ServiceInfo( name=name, active_state=u[3], sub_state=u[4], loaded=u[2], pid=None, )) return result @app.get("/services/{name}") def get_service(name: str): if name not in ALLOWED_SERVICES: raise HTTPException(status_code=403, detail="Service not allowed") props = get_unit(name) pid = None if "MainPID" in props: pid = int(props["MainPID"]) or None return ServiceInfo( name=name, active_state=str(props.get("ActiveState", "unknown")), sub_state=str(props.get("SubState", "unknown")), loaded=str(props.get("LoadState", "unknown")), pid=pid, ) @app.post("/services/{name}/start") def start_service(name: str): if name not in ALLOWED_SERVICES: raise HTTPException(status_code=403, detail="Service not allowed") manager = refresh_dbus() job = manager.StartUnit(name, "replace") return {"job": str(job), "service": name, "action": "start"} @app.post("/services/{name}/stop") def stop_service(name: str): if name not in ALLOWED_SERVICES: raise HTTPException(status_code=403, detail="Service not allowed") manager = refresh_dbus() job = manager.StopUnit(name, "replace") return {"job": str(job), "service": name, "action": "stop"} @app.post("/services/{name}/restart") def restart_service(name: str): if name not in ALLOWED_SERVICES: raise HTTPException(status_code=403, detail="Service not allowed") manager = refresh_dbus() job = manager.RestartUnit(name, "replace") return {"job": str(job), "service": name, "action": "restart"}