This commit is contained in:
@@ -33,3 +33,7 @@ NetMapper è una web app self-hosted per la mappatura dinamica di un homelab su
|
|||||||
- Database: PostgreSQL
|
- Database: PostgreSQL
|
||||||
- Cache/queue: Redis
|
- Cache/queue: Redis
|
||||||
- Deploy: Docker Compose
|
- Deploy: Docker Compose
|
||||||
|
|
||||||
|
### Agent
|
||||||
|
|
||||||
|
- `agent/` - control agent per gestire i servizi systemd (start/stop/status).
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
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"}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
fastapi==0.115.0
|
||||||
|
uvicorn[standard]==0.30.6
|
||||||
|
dbus-python==1.3.2
|
||||||
|
pydantic==2.9.2
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=NetMapper Control Agent
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/opt/NetMapper2026/agent
|
||||||
|
ExecStart=/opt/NetMapper2026/agent/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8001
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=NetMapper Backend
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/opt/NetMapper2026/backend
|
||||||
|
ExecStart=/opt/NetMapper2026/backend/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=NetMapper Frontend
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/opt/NetMapper2026/frontend
|
||||||
|
ExecStart=/usr/bin/npm run dev -- --host 0.0.0.0 --port 5173
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=NetMapper Worker
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/opt/NetMapper2026/worker
|
||||||
|
ExecStart=/opt/NetMapper2026/worker/.venv/bin/python app/main.py
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user