154 lines
5.1 KiB
Python
154 lines
5.1 KiB
Python
|
from fastapi import FastAPI, HTTPException
|
||
|
from fastapi.responses import HTMLResponse
|
||
|
import requests
|
||
|
import os
|
||
|
import subprocess
|
||
|
import sqlite3
|
||
|
from wakeonlan import send_magic_packet
|
||
|
from pydantic import BaseModel
|
||
|
from fastapi.openapi.utils import get_openapi
|
||
|
|
||
|
# Database setup
|
||
|
def init_db():
|
||
|
conn = sqlite3.connect("servers.db")
|
||
|
cursor = conn.cursor()
|
||
|
cursor.execute('''CREATE TABLE IF NOT EXISTS servers (
|
||
|
name TEXT PRIMARY KEY,
|
||
|
ip TEXT NOT NULL,
|
||
|
mac TEXT NOT NULL)''')
|
||
|
conn.commit()
|
||
|
conn.close()
|
||
|
|
||
|
init_db()
|
||
|
|
||
|
def get_server_info(server_name):
|
||
|
conn = sqlite3.connect("servers.db")
|
||
|
cursor = conn.cursor()
|
||
|
cursor.execute("SELECT ip, mac FROM servers WHERE name = ?", (server_name,))
|
||
|
server = cursor.fetchone()
|
||
|
conn.close()
|
||
|
if server:
|
||
|
return {"ip": server[0], "mac": server[1]}
|
||
|
return None
|
||
|
|
||
|
def list_servers():
|
||
|
conn = sqlite3.connect("servers.db")
|
||
|
cursor = conn.cursor()
|
||
|
cursor.execute("SELECT name, ip, mac FROM servers")
|
||
|
servers = cursor.fetchall()
|
||
|
conn.close()
|
||
|
return [{"name": s[0], "ip": s[1], "mac": s[2]} for s in servers]
|
||
|
|
||
|
def add_or_update_server(name: str, ip: str, mac: str):
|
||
|
conn = sqlite3.connect("servers.db")
|
||
|
cursor = conn.cursor()
|
||
|
cursor.execute("REPLACE INTO servers (name, ip, mac) VALUES (?, ?, ?)", (name, ip, mac))
|
||
|
conn.commit()
|
||
|
conn.close()
|
||
|
|
||
|
app = FastAPI()
|
||
|
|
||
|
class ServerModel(BaseModel):
|
||
|
name: str
|
||
|
ip: str
|
||
|
mac: str
|
||
|
|
||
|
def get_proxmox_status(server_ip):
|
||
|
"""Fetch Proxmox server status."""
|
||
|
try:
|
||
|
response = requests.get(f"https://{server_ip}:8006/api2/json/nodes", verify=False) # Assuming no authentication
|
||
|
response.raise_for_status()
|
||
|
data = response.json()
|
||
|
return {"status": data["data"][0]["status"]} # Extract node status
|
||
|
except requests.RequestException as e:
|
||
|
return {"status": "unknown", "error": str(e)}
|
||
|
|
||
|
@app.get("/openapi.json")
|
||
|
def get_openapi_spec():
|
||
|
"""Returns the OpenAPI specification."""
|
||
|
return get_openapi(title=app.title, version="1.0.0", routes=app.routes)
|
||
|
|
||
|
@app.get("/apidoc", response_class=HTMLResponse)
|
||
|
def api_docs():
|
||
|
"""Returns the API documentation using RapiDoc."""
|
||
|
return HTMLResponse(content="""
|
||
|
<!DOCTYPE html>
|
||
|
<html>
|
||
|
<head>
|
||
|
<title>API Documentation</title>
|
||
|
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
|
||
|
</head>
|
||
|
<body>
|
||
|
<rapi-doc spec-url="/openapi.json" theme="light"></rapi-doc>
|
||
|
</body>
|
||
|
</html>
|
||
|
""", status_code=200)
|
||
|
|
||
|
@app.get("/statuses/{server_name}")
|
||
|
def status(server_name: str):
|
||
|
"""Returns the Proxmox server status for a specific server."""
|
||
|
server = get_server_info(server_name)
|
||
|
if not server:
|
||
|
raise HTTPException(status_code=404, detail="Server not found")
|
||
|
return get_proxmox_status(server["ip"])
|
||
|
|
||
|
@app.get("/statuses")
|
||
|
def list_all_statuses():
|
||
|
"""Returns the statuses of all servers."""
|
||
|
servers = list_servers()
|
||
|
return {server["name"]: get_proxmox_status(server["ip"]) for server in servers}
|
||
|
|
||
|
def check_power_state(server_ip):
|
||
|
"""Check if the server is online by pinging it."""
|
||
|
response = subprocess.run(["ping", "-c", "1", server_ip], stdout=subprocess.DEVNULL)
|
||
|
return "on" if response.returncode == 0 else "off"
|
||
|
|
||
|
@app.get("/states/{server_name}")
|
||
|
def get_power_status(server_name: str):
|
||
|
"""Returns the power status of a specific server."""
|
||
|
server = get_server_info(server_name)
|
||
|
if not server:
|
||
|
raise HTTPException(status_code=404, detail="Server not found")
|
||
|
return {"power": check_power_state(server["ip"])}
|
||
|
|
||
|
@app.get("/states")
|
||
|
def list_all_states():
|
||
|
"""Returns the power states of all servers."""
|
||
|
servers = list_servers()
|
||
|
return {server["name"]: check_power_state(server["ip"]) for server in servers}
|
||
|
|
||
|
@app.put("/states/{server_name}")
|
||
|
@app.patch("/states/{server_name}")
|
||
|
def control_power(server_name: str, state: str):
|
||
|
"""Controls the server power state (wake or shutdown)."""
|
||
|
server = get_server_info(server_name)
|
||
|
if not server:
|
||
|
raise HTTPException(status_code=404, detail="Server not found")
|
||
|
|
||
|
if state == "on":
|
||
|
if check_power_state(server["ip"]) == "off":
|
||
|
send_magic_packet(server["mac"])
|
||
|
return {"message": "Wake-on-LAN signal sent"}
|
||
|
return {"message": "Server is already on"}
|
||
|
elif state == "off":
|
||
|
try:
|
||
|
requests.post(f"https://{server["ip"]}:8006/api2/json/nodes/shutdown", verify=False)
|
||
|
return {"message": "Shutdown command sent"}
|
||
|
except requests.RequestException as e:
|
||
|
raise HTTPException(status_code=500, detail=str(e))
|
||
|
else:
|
||
|
raise HTTPException(status_code=400, detail="Invalid state. Use 'on' or 'off'")
|
||
|
|
||
|
@app.get("/servers")
|
||
|
def get_servers():
|
||
|
"""Returns the list of registered servers."""
|
||
|
return list_servers()
|
||
|
|
||
|
@app.post("/servers")
|
||
|
def add_server(server: ServerModel):
|
||
|
"""Adds or updates a server."""
|
||
|
add_or_update_server(server.name, server.ip, server.mac)
|
||
|
return {"message": "Server added/updated successfully"}
|
||
|
|
||
|
# Run the server with: uvicorn script_name:app --host 0.0.0.0 --port 8000
|