security audit
This commit is contained in:
1 parent
19e2db7fd4
commit
f16d848e42
7 files changed
+115
-116
No files matched your search
@@ -1,14 +1,14 @@
|
||||
"""Push system stats (CPU, RAM, disk, uptime) to Firestore on a loop."""
|
||||
"""Push system stats (CPU, RAM, disk, uptime) to Firestore via Admin SDK."""
|
||||
######################################################################
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
|
||||
import aiohttp
|
||||
import psutil
|
||||
|
||||
from .config import DOC_SYSINFO, SYSINFO_INTERVAL
|
||||
from . import get_db
|
||||
from .config import SYSINFO_INTERVAL
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -27,19 +27,16 @@ class SysInfoSync:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._task: asyncio.Task | None = None
|
||||
self._tick = 0
|
||||
|
||||
# ── public API ──────────────────────────────────────────────────────────
|
||||
|
||||
def start(self) -> None:
|
||||
"""Launch the background sync loop."""
|
||||
if self._task is not None:
|
||||
return
|
||||
self._task = asyncio.create_task(self._run())
|
||||
log.info("SysInfo → Firestore sync started (every %ds)", SYSINFO_INTERVAL)
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Cancel the background sync loop."""
|
||||
if self._task is None:
|
||||
return
|
||||
self._task.cancel()
|
||||
@@ -49,69 +46,57 @@ class SysInfoSync:
|
||||
# ── internals ───────────────────────────────────────────────────────────
|
||||
|
||||
async def _run(self) -> None:
|
||||
"""Main loop — collect stats, push, sleep, repeat."""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
while True:
|
||||
try:
|
||||
await self._push(session)
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
except Exception:
|
||||
log.debug("SysInfo Firestore push failed", exc_info=True)
|
||||
await asyncio.sleep(SYSINFO_INTERVAL)
|
||||
while True:
|
||||
try:
|
||||
await self._push()
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
except Exception:
|
||||
log.debug("SysInfo Firestore push failed", exc_info=True)
|
||||
await asyncio.sleep(SYSINFO_INTERVAL)
|
||||
|
||||
async def _push(self, session: aiohttp.ClientSession) -> None:
|
||||
"""Collect current stats and PATCH the Firestore document."""
|
||||
async def _push(self) -> None:
|
||||
cpu = psutil.cpu_percent(interval=1)
|
||||
mem = psutil.virtual_memory()
|
||||
disk = psutil.disk_usage("/")
|
||||
uptime_seconds = int(time.time() - psutil.boot_time())
|
||||
|
||||
# uptime → "3d 12h 5m" or "5h 23m"
|
||||
days, r = divmod(uptime_seconds, 86400)
|
||||
hours, r = divmod(r, 3600)
|
||||
minutes, _ = divmod(r, 60)
|
||||
if days > 0:
|
||||
uptime_str = f"{days}d {hours}h {minutes}m"
|
||||
else:
|
||||
uptime_str = f"{hours}h {minutes}m"
|
||||
uptime_str = f"{days}d {hours}h {minutes}m" if days > 0 else f"{hours}h {minutes}m"
|
||||
|
||||
load1, load5, load15 = psutil.getloadavg()
|
||||
net = psutil.net_io_counters()
|
||||
swap = psutil.swap_memory()
|
||||
|
||||
payload = {
|
||||
"fields": {
|
||||
"online": {"booleanValue": True},
|
||||
"cpu": {"doubleValue": round(cpu, 1)},
|
||||
"ram": {"doubleValue": round(mem.percent, 1)},
|
||||
"ramUsed": {"stringValue": f"{mem.used // (1024 ** 2)}MB"},
|
||||
"ramTotal": {"stringValue": f"{mem.total // (1024 ** 2)}MB"},
|
||||
"disk": {"doubleValue": round(disk.percent, 1)},
|
||||
"diskUsed": {"stringValue": f"{disk.used // (1024 ** 3)}GB"},
|
||||
"diskTotal": {"stringValue": f"{disk.total // (1024 ** 3)}GB"},
|
||||
"swap": {"doubleValue": round(swap.percent, 1)},
|
||||
"swapUsed": {"stringValue": f"{swap.used // (1024 ** 2)}MB"},
|
||||
"swapTotal": {"stringValue": f"{swap.total // (1024 ** 2)}MB"},
|
||||
"uptime": {"stringValue": uptime_str},
|
||||
"load1": {"doubleValue": round(load1, 1)},
|
||||
"load5": {"doubleValue": round(load5, 1)},
|
||||
"load15": {"doubleValue": round(load15, 1)},
|
||||
"netSent": {"stringValue": _fmt_bytes(net.bytes_sent)},
|
||||
"netRecv": {"stringValue": _fmt_bytes(net.bytes_recv)},
|
||||
"processes": {"integerValue": len(psutil.pids())},
|
||||
"fetch": {"stringValue": await self._get_fastfetch()},
|
||||
}
|
||||
"online": True,
|
||||
"cpu": round(cpu, 1),
|
||||
"ram": round(mem.percent, 1),
|
||||
"ramUsed": f"{mem.used // (1024 ** 2)}MB",
|
||||
"ramTotal": f"{mem.total // (1024 ** 2)}MB",
|
||||
"disk": round(disk.percent, 1),
|
||||
"diskUsed": f"{disk.used // (1024 ** 3)}GB",
|
||||
"diskTotal": f"{disk.total // (1024 ** 3)}GB",
|
||||
"swap": round(swap.percent, 1),
|
||||
"swapUsed": f"{swap.used // (1024 ** 2)}MB",
|
||||
"swapTotal": f"{swap.total // (1024 ** 2)}MB",
|
||||
"uptime": uptime_str,
|
||||
"load1": round(load1, 1),
|
||||
"load5": round(load5, 1),
|
||||
"load15": round(load15, 1),
|
||||
"netSent": _fmt_bytes(net.bytes_sent),
|
||||
"netRecv": _fmt_bytes(net.bytes_recv),
|
||||
"processes": len(psutil.pids()),
|
||||
"fetch": await self._get_fastfetch(),
|
||||
}
|
||||
|
||||
async with session.patch(DOC_SYSINFO, json=payload) as resp:
|
||||
if resp.status not in (200, 201):
|
||||
body = await resp.text()
|
||||
log.debug("Firestore PATCH %d: %s", resp.status, body[:200])
|
||||
db = get_db()
|
||||
await db.collection("sysinfo").document("server").set(payload, merge=True)
|
||||
|
||||
@staticmethod
|
||||
async def _get_fastfetch() -> str:
|
||||
"""Run fastfetch --pipe --logo none, strip ANSI, return first 15 lines."""
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"fastfetch", "--pipe", "--logo", "none",
|
||||
@@ -120,7 +105,7 @@ class SysInfoSync:
|
||||
)
|
||||
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5)
|
||||
clean = re.sub(r"\x1b\[[0-9;]*[a-zA-Z]|\[[0-9]+[A-Z]", "", stdout.decode())
|
||||
lines = [l for l in clean.strip().splitlines() if l.strip()]
|
||||
lines = [line for line in clean.strip().splitlines() if line.strip()]
|
||||
return "\n".join(lines[:15])
|
||||
except Exception:
|
||||
return ""
|
||||
Reference in new issue
Block a user