You can use this one for reference (save as “vitals.py”):
#!/usr/bin/env python3
"""
Start9 Server Vitals
--------------------
Fetches server status, uptime, and service list from a Start9 (StartOS) server
via its JSON-RPC 2.0 API at POST /rpc/v1.
Usage:
python vitals.py [--host HOST] [--json]
The server password can be supplied via:
- Environment variable: START9_PASSWORD=yourpassword python vitals.py
- Interactive prompt: (default — typed securely, never echoed)
Options:
--host HOST Hostname or IP of the Start9 server
(default: adjective-noun.local)
--json Output raw JSON instead of pretty-printed table
--no-verify Skip SSL certificate verification (default: True, since
.local servers use self-signed certs)
"""
import argparse
import getpass
import json
import os
import sys
from datetime import timedelta
try:
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except ImportError:
print("ERROR: 'requests' is not installed. Run: pip install requests")
sys.exit(1)
# ---------------------------------------------------------------------------
# RPC helpers
# ---------------------------------------------------------------------------
_rpc_id = 0
def _next_id() -> int:
global _rpc_id
_rpc_id += 1
return _rpc_id
def rpc_call(session: requests.Session, base_url: str, method: str, params: dict) -> dict:
"""
Send a single JSON-RPC 2.0 request.
Returns the 'result' field on success, raises RuntimeError on RPC error.
"""
payload = {
"jsonrpc": "2.0",
"id": _next_id(),
"method": method,
"params": params,
}
resp = session.post(f"{base_url}/rpc/v1", json=payload, verify=False, timeout=30)
resp.raise_for_status()
body = resp.json()
if "error" in body:
err = body["error"]
raise RuntimeError(f"RPC error [{err.get('code')}]: {err.get('message')} — {err.get('data', {}).get('details', '')}")
return body["result"]
# ---------------------------------------------------------------------------
# Data-fetching functions
# ---------------------------------------------------------------------------
def login(session: requests.Session, base_url: str, password: str) -> None:
"""Authenticate and store the session cookie."""
rpc_call(session, base_url, "auth.login", {
"password": password,
"metadata": {"platforms": ["cli"]},
})
def logout(session: requests.Session, base_url: str) -> None:
"""Cleanly end the session."""
try:
rpc_call(session, base_url, "auth.logout", {})
except Exception:
pass # best-effort
def get_uptime(session: requests.Session, base_url: str) -> dict:
"""
Returns server time info.
Result shape: { "now": "<ISO8601>", "uptime": <seconds: float> }
"""
return rpc_call(session, base_url, "server.time", {})
def get_metrics(session: requests.Session, base_url: str) -> dict:
"""
Returns server hardware metrics (CPU, RAM, storage, etc.).
"""
return rpc_call(session, base_url, "server.metrics", {})
def get_services(session: requests.Session, base_url: str) -> list:
"""
Returns a list of installed services.
Each item: { "id": str, "status": str, "version": str }
"""
result = rpc_call(session, base_url, "package.list", {})
# The API can return a list or a dict keyed by package id
if isinstance(result, dict):
services = []
for pkg_id, pkg_data in result.items():
entry = {"id": pkg_id}
entry.update(pkg_data if isinstance(pkg_data, dict) else {})
services.append(entry)
return services
return result if result else []
# ---------------------------------------------------------------------------
# Output formatting
# ---------------------------------------------------------------------------
def format_uptime(seconds: float) -> str:
td = timedelta(seconds=int(seconds))
days = td.days
hours, remainder = divmod(td.seconds, 3600)
minutes, secs = divmod(remainder, 60)
parts = []
if days:
parts.append(f"{days}d")
if hours:
parts.append(f"{hours}h")
if minutes:
parts.append(f"{minutes}m")
parts.append(f"{secs}s")
return " ".join(parts)
def _metric_value(metric_obj) -> str:
"""Extract a display value from a metric object like { value: X, unit: Y }."""
if isinstance(metric_obj, dict):
val = metric_obj.get("value", "")
unit = metric_obj.get("unit", "")
return f"{val} {unit}".strip() if unit else str(val)
return str(metric_obj)
def print_vitals(host: str, time_data: dict, metrics: dict, services: list) -> None:
uptime_secs = time_data.get("uptime", 0)
server_time = time_data.get("now", "unknown")
print()
print("=" * 55)
print(f" Start9 Server Vitals — {host}")
print("=" * 55)
# --- Server status & uptime ---
print(f" Status : Running ✓")
print(f" Uptime : {format_uptime(uptime_secs)}")
print(f" Time : {server_time}")
# --- Metrics ---
if metrics:
print()
print(" [ Hardware Metrics ]")
# Flatten common metric paths the API returns
def show(label: str, *keys):
obj = metrics
for k in keys:
if isinstance(obj, dict):
obj = obj.get(k)
else:
obj = None
break
if obj is not None:
print(f" {label:<12}: {_metric_value(obj)}")
# CPU — nested as metrics.cpu.percentage or metrics["cpu-load"]
show("CPU Load", "cpu", "percentage")
show("CPU Load", "cpu-load")
# RAM
show("RAM Used", "memory", "percentage-used")
show("RAM Used", "ram", "percentage-used")
# Storage
show("Disk Used", "disk", "used")
show("Storage", "storage","percentage-used")
# If above keys didn't match, dump all top-level metric keys
matched_any = any(
isinstance(metrics.get(k), dict)
for k in ("cpu", "memory", "disk", "ram", "storage", "cpu-load")
if k in metrics
)
if not matched_any:
for key, val in metrics.items():
if isinstance(val, (dict, str, int, float)):
print(f" {key:<12}: {_metric_value(val)}")
# --- Services ---
print()
print(" [ Installed Services ]")
if not services:
print(" (none)")
else:
col_id = max(len(s.get("id", "")) for s in services)
col_id = max(col_id, 4)
col_ver = max(len(str(s.get("version", ""))) for s in services)
col_ver = max(col_ver, 7)
header = f" {'ID':<{col_id}} {'VERSION':<{col_ver}} STATUS"
print(header)
print(" " + "-" * (len(header) - 2))
for svc in sorted(services, key=lambda s: s.get("id", "")):
svc_id = svc.get("id", "?")
version = str(svc.get("version", ""))
status = svc.get("status", "unknown")
# Add a simple visual indicator
indicator = "●" if status == "installed" else "◌"
print(f" {svc_id:<{col_id}} {version:<{col_ver}} {indicator} {status}")
print("=" * 55)
print()
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Fetch vitals from a Start9 (StartOS) server.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"--host",
default="adjective-noun.local",
help="Hostname or IP of the Start9 server (default: adjective-noun.local)",
)
parser.add_argument(
"--json",
action="store_true",
help="Output raw JSON",
)
parser.add_argument(
"--no-verify",
action="store_true",
default=True,
help="Skip SSL certificate verification (default: enabled for .local servers)",
)
args = parser.parse_args()
base_url = f"https://{args.host}"
# Get password
password = os.environ.get("START9_PASSWORD")
if not password:
try:
password = getpass.getpass(f"Password for {args.host}: ")
except (KeyboardInterrupt, EOFError):
print("\nAborted.")
sys.exit(1)
session = requests.Session()
session.headers.update({"Content-Type": "application/json"})
try:
# Authenticate
print(f"Connecting to {base_url} …", end="", flush=True)
login(session, base_url, password)
print(" OK")
# Fetch data
time_data = get_uptime(session, base_url)
metrics = get_metrics(session, base_url)
services = get_services(session, base_url)
if args.json:
output = {
"server": {
"status": "running",
"uptime_seconds": time_data.get("uptime"),
"time": time_data.get("now"),
},
"metrics": metrics,
"services": services,
}
print(json.dumps(output, indent=2))
else:
print_vitals(args.host, time_data, metrics, services)
except requests.exceptions.ConnectionError as e:
print(f"\nERROR: Could not connect to {base_url}")
print(f" Make sure the server is reachable and you're on the same network.")
print(f" Details: {e}")
sys.exit(1)
except RuntimeError as e:
print(f"\nERROR: {e}")
sys.exit(1)
finally:
logout(session, base_url)
if __name__ == "__main__":
main()
Example Usage:
python vitals.py --host adjective-noun.local --json
For full usage help:
python vitals.py --help