Compare commits

...

2 Commits

Author SHA1 Message Date
Hitonabi 00cf14c6e3 docs: roadmap update for feature 1 2026-06-20 21:34:43 +02:00
Hitonabi 04bac7e13e feat: server management (Feature 1) 2026-06-20 21:34:14 +02:00
3 changed files with 85 additions and 20 deletions
+10 -12
View File
@@ -15,29 +15,27 @@ Modell-Listen, Aktivitäts-Stream) angelehnt an `docs/mission-control-overview.p
**✅ Schritt 2 erledigt & live** — *Feature 3: Live-Auslastung*. `system.py` Router mit `psutil` und sysfs für CPU/RAM/Disk/GPU/Temp. **✅ Schritt 2 erledigt & live** — *Feature 3: Live-Auslastung*. `system.py` Router mit `psutil` und sysfs für CPU/RAM/Disk/GPU/Temp.
**Reihenfolge (abgestimmt):** Design/Architektur zuerst (✅), dann Quick Wins, Security-Brocken zuletzt: **Reihenfolge (abgestimmt):** Design/Architektur zuerst (✅), dann Quick Wins, Security-Brocken zuletzt:
`1 (✅) Fundament → 2 (✅) Feature 3 Live-Auslastung → 3 (✅) Feature 6 Mehr LLM-Metriken → [4] Feature 1 Server-Management ← NÄCHSTES → Feature 4 → Feature 7 → Feature 2`. `1 (✅) Fundament → 2 (✅) Feature 3 Live-Auslastung → 3 (✅) Feature 6 Mehr LLM-Metriken → 4 (✅) Feature 1 Server-Management → [5] Feature 4 Cookbook ← NÄCHSTES → Feature 7 → Feature 2`.
**Arbeitsweise je Schritt:** neuer `routers/<x>.py` + `js/panels/<x>.js` + Nav-Eintrag, sauber degradierend. **Arbeitsweise je Schritt:** neuer `routers/<x>.py` + `js/panels/<x>.js` + Nav-Eintrag, sauber degradierend.
Bauen + Smoke-Test auf Windows, dann push→pull→rsync→restart auf den Bosgame (CLAUDE.md „Entwickeln & Deployen"). Bauen + Smoke-Test auf Windows, dann push→pull→rsync→restart auf den Bosgame (CLAUDE.md „Entwickeln & Deployen").
Ein Commit je Schritt. **Ich (KI) habe key-basierten SSH-Zugang zum Bosgame und kann selbst deployen+restarten.** Ein Commit je Schritt. **Ich (KI) habe key-basierten SSH-Zugang zum Bosgame und kann selbst deployen+restarten.**
**→ Nächster Schritt konkret = Feature 1 (Server-Management).** **→ Nächster Schritt konkret = Feature 4 (Cookbook + "Modell holen" verschmelzen).**
- Aktuell gibt es nur "Container aktualisieren" + "Alles aus dem Speicher". Ziel: den kompletten Server aus der UI verwalten. - Bisher: Textfelder für HuggingFace-Repo + Pfad unter "Modelle". Das ist super für Custom-Zeug.
- OS-/Core-Updates (`apt update/upgrade`) per Knopf, mit Live-Output - Neu: Ein Klick-Cookbook (Sidebar-Tab "Cookbook") mit kuratierter Liste (z.B. Qwen2.5-Coder 32B, Llama3 Vision, etc.).
- Dienste steuern (`llama-swap`, `mission-control`: Status, Restart) - Klick auf Modellkarte im Cookbook triggert den Download via `/api/download`.
- Reboot / Health-Übersicht
- Scope-/Security-Sprung: macht MC zum Server-Admin-Panel. Rechte minimal halten.
--- ---
## Features ## Features
### 1. Server-Management ("Update-Panel 2.0") ← **NÄCHSTER SCHRITT** ### 1. Server-Management ("Update-Panel 2.0") (✅ Erledigt)
Aktuell gibt es nur "Container aktualisieren" + "Alles aus dem Speicher". Ziel: den kompletten Server aus der UI verwalten. Aktuell gibt es nur "Container aktualisieren" + "Alles aus dem Speicher". Ziel: den kompletten Server aus der UI verwalten.
- [ ] OS-/Core-Updates (`apt update/upgrade`) per Knopf, mit Live-Output - [x] OS-/Core-Updates (`apt update/upgrade`) per Knopf, mit Live-Output
- [ ] Dienste steuern (`llama-swap`, `mission-control`: Status, Restart) - [x] Dienste steuern (`llama-swap`, `mission-control`: Status, Restart)
- [ ] Reboot / Health-Übersicht - [x] Reboot / Health-Übersicht
- [ ] Referenz: altes `ai-control`-Skript als Funktionsvorlage - [x] Referenz: altes `ai-control`-Skript als Funktionsvorlage
- ⚠️ **Scope-/Security-Sprung**: macht MC zum Server-Admin-Panel. Rechte minimal halten (sudoers-Whitelist für genau die erlaubten Befehle, statt Vollzugriff). - ⚠️ **Scope-/Security-Sprung**: macht MC zum Server-Admin-Panel. Rechte minimal halten (sudoers-Whitelist für genau die erlaubten Befehle, statt Vollzugriff).
### 2. Live-Terminal / Log via SSH ### 2. Live-Terminal / Log via SSH
+23
View File
@@ -7,8 +7,10 @@ Server-Wartung hinein (siehe Roadmap: Server-Management).
""" """
import shlex import shlex
import subprocess
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from auth import auth from auth import auth
from config import UPDATE_CMD from config import UPDATE_CMD
@@ -16,6 +18,8 @@ from jobengine import start_job
router = APIRouter(prefix="/api", dependencies=[Depends(auth)]) router = APIRouter(prefix="/api", dependencies=[Depends(auth)])
class PwdReq(BaseModel):
password: str
@router.post("/update") @router.post("/update")
def update(): def update():
@@ -23,3 +27,22 @@ def update():
raise HTTPException(400, "Kein Update-Befehl gesetzt (MC_UPDATE_CMD).") raise HTTPException(400, "Kein Update-Befehl gesetzt (MC_UPDATE_CMD).")
job_id = start_job(shlex.split(UPDATE_CMD), "update containers") job_id = start_job(shlex.split(UPDATE_CMD), "update containers")
return {"job_id": job_id} return {"job_id": job_id}
@router.post("/os-update")
def os_update(req: PwdReq):
cmd = f"echo {shlex.quote(req.password)} | sudo -S bash -c 'apt-get update && DEBIAN_FRONTEND=noninteractive apt-get upgrade -y'"
job_id = start_job(["bash", "-c", cmd], "os update")
return {"job_id": job_id}
@router.post("/service/{name}/restart")
def service_restart(name: str):
if name not in ("llama-swap", "mission-control"):
raise HTTPException(400, "Dienst nicht erlaubt.")
subprocess.Popen(["sudo", "systemctl", "restart", name])
return {"ok": True, "note": f"Restart {name} getriggert."}
@router.post("/reboot")
def reboot(req: PwdReq):
cmd = f"echo {shlex.quote(req.password)} | sudo -S reboot"
subprocess.Popen(["bash", "-c", cmd])
return {"ok": True, "note": "Reboot getriggert."}
+52 -8
View File
@@ -1,24 +1,68 @@
// maintenance.js — Wartungs-Karte: Container aktualisieren + alle Modelle entladen.
// Spaeter waechst hier das Server-Management an (Roadmap Feature 1).
import { api } from "../core/api.js"; import { api } from "../core/api.js";
import { $, toast } from "../core/ui.js"; import { $, toast } from "../core/ui.js";
import { track } from "./jobs.js"; import { track } from "./jobs.js";
function refreshSoon() { document.dispatchEvent(new Event("mc:refresh")); }
function mount() { function mount() {
$("#wartung").innerHTML = ` $("#wartung").innerHTML = `
<div class="card-h"><h3>Wartung</h3></div> <div class="card-h"><h3>Dienste & Applikation</h3></div>
<div class="btn-row" style="margin-bottom:20px">
<button id="w-restart-swap">llama-swap neustarten</button>
<button id="w-restart-mc">Mission Control neustarten</button>
</div>
<div class="btn-row"> <div class="btn-row">
<button id="w-update">Container aktualisieren</button> <button id="w-update">Container aktualisieren</button>
<button id="w-unload" class="danger">Alles aus dem Speicher</button> <button id="w-unload" class="ghost">Modelle entladen</button>
</div>
<div class="hint" style="margin-top:12px; margin-bottom:32px">
Dienste starten via passwortlosem Sudo neu.
</div>
<div class="card-h"><h3>Betriebssystem (Bosgame)</h3></div>
<div class="btn-row">
<button id="w-os-update">OS-Updates installieren (apt update)</button>
<button id="w-reboot" class="danger">Server Reboot</button>
</div> </div>
<div class="hint" style="margin-top:12px"> <div class="hint" style="margin-top:12px">
Update-Befehl wird per <span class="mono-sm">MC_UPDATE_CMD</span> gesetzt. Für tiefe Eingriffe fragt das Dashboard einmalig das sudo-Passwort ab.
Server-Steuerung (Dienste, OS-Updates, Reboot) folgt als eigener Bereich.
</div>`; </div>`;
$("#w-restart-swap").addEventListener("click", () => restartService("llama-swap"));
$("#w-restart-mc").addEventListener("click", () => restartService("mission-control"));
$("#w-update").addEventListener("click", update); $("#w-update").addEventListener("click", update);
$("#w-unload").addEventListener("click", unloadAll); $("#w-unload").addEventListener("click", unloadAll);
$("#w-os-update").addEventListener("click", osUpdate);
$("#w-reboot").addEventListener("click", rebootServer);
}
async function restartService(name) {
try {
await api("/api/service/" + name + "/restart", { method: "POST" });
toast("Neustart ausgelöst: " + name);
setTimeout(refreshSoon, 2000);
} catch (e) { toast(e.message, true); }
}
async function osUpdate() {
const pwd = window.prompt("Bitte sudo Passwort eingeben (für apt-get update & upgrade):");
if (!pwd) return;
try {
const r = await api("/api/os-update", { method: "POST", body: JSON.stringify({ password: pwd }) });
toast("OS-Update gestartet.");
track(r.job_id);
} catch (e) { toast(e.message, true); }
}
async function rebootServer() {
if (!window.confirm("ACHTUNG: Server wird komplett neu gestartet. Fortfahren?")) return;
const pwd = window.prompt("Bitte sudo Passwort eingeben (für reboot):");
if (!pwd) return;
try {
await api("/api/reboot", { method: "POST", body: JSON.stringify({ password: pwd }) });
toast("Reboot ausgelöst. UI ist gleich offline.");
} catch (e) { toast(e.message, true); }
} }
async function update() { async function update() {
@@ -33,7 +77,7 @@ async function unloadAll() {
try { try {
await api("/api/unload", { method: "POST" }); await api("/api/unload", { method: "POST" });
toast("Alle Modelle entladen."); toast("Alle Modelle entladen.");
setTimeout(() => document.dispatchEvent(new Event("mc:refresh")), 600); setTimeout(refreshSoon, 600);
} catch (e) { toast(e.message, true); } } catch (e) { toast(e.message, true); }
} }