Einstellungen, die etwas einstellen (services/einstellungen.py, /api/einstellungen): - Update-Zeitfenster: Wochentag und Uhrzeit des Update-Laufs als Drop-in mc2-autoupdate.timer.d/zeitfenster.conf. Gegen die Nachhol-Falle von Persistent=true (24.09.): Timer vor dem daemon-reload stoppen, Stempel auf jetzt, dann starten. Bei Fehler zurück auf das alte Drop-in. Während eines Update-Laufs abgelehnt. - Neustart nur im eingestellten Fenster (deploy/wartungsfenster.sh, drei Stunden ab der vollen Stunde des Beginns; Standard So 04:00-06:59). - Radar an/aus: disable --now bzw. enable + Stempel + start (kein Nachholen). deploy.sh respektiert den Schalter und startet den Radar-Timer nur mit Stempel; das Zeitfenster-Drop-in fasst er nie an. - Wächter: schläft der Timer eines Timer-Dienstes, ist ein alter Fehlschlag kein Befund mehr (der Dienst selbst ist static und schläft nie). - Telegram-Test je Instanz (/api/einstellungen/telegram-test und /api/homelab/einstellungen/telegram-test) über notify.sh -d; Ergebnis und Grund aus dem Melde-Log. notify.sh schreibt jetzt auch den Grund des gescheiterten Zweitwegs in den FALLBACK-Eintrag. - Einstellungsdatei mc2-einstellungen.json im Datenordner. Software-Stand: deploy.sh und homelab/ausrollen.sh schreiben nach dem Umschalten mc2-stand.json (deploy/stand-schreiben.py); /api/instanz liefert den Stand, die Einstellungen zeigen beide Instanzen und warnen bei Abweichung. Ohne Datei aus git, sonst unbekannt. Aufräumen: Altreste neben dem Betrieb aus einer festen, auf der Box geprüften Liste (Größe, Hinweis, nur was da und nicht in Gebrauch ist). Löschen nur per Kennung und Klick mit Rückfrage; Worktrees mit git worktree prune, Alt-Units mit daemon-reload, /opt/llamacpp notfalls mit sudo -n. Tests: pytest mit einem systemd-Abbild samt Nachhol-Regel, echtem notify.sh (Ersatz-Hermes, Tmp-Zugang, lokale Bot-API), Shell-Funktionen des Wartungsfensters und von deploy.sh; vitest für die Rückfragen der Schublade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
2.7 KiB
Python
56 lines
2.7 KiB
Python
"""Partner-Instanz: Lebenszeichen und Durchreiche (zwei Instanzen, eine Oberfläche, 24.09.2026).
|
|
|
|
Die Oberfläche fragt den Bereich der anderen Instanz über /api/partner/<pfad> ab; hier wird
|
|
daraus <partner>/api/<pfad>. Die Herkunftsprüfung (services/herkunft.py) greift wie bei jeder
|
|
anderen schreibenden Anfrage schon hier, bevor etwas weitergereicht wird.
|
|
"""
|
|
|
|
import httpx
|
|
from fastapi import APIRouter, HTTPException, Request, Response
|
|
from kern import partner
|
|
from kern.einstellungen import einstellungen
|
|
from services import software_stand
|
|
|
|
router = APIRouter(prefix="/api", tags=["partner"])
|
|
|
|
# Nicht durchreichen: den Partner-Weg des Partners (sonst reichen sich zwei Instanzen eine Anfrage
|
|
# endlos zu) und den Live-Strom (SSE hält die Verbindung offen; der Partner-Bereich fragt ab).
|
|
_GESPERRT = ("partner", "stream")
|
|
_ZEITLIMIT = httpx.Timeout(30.0, connect=5.0)
|
|
|
|
|
|
@router.get("/instanz")
|
|
def instanz() -> dict:
|
|
"""Wer liefert die Seite aus? Ohne Netzabfragen, damit die Oberfläche sofort weiß, welche Anfragen
|
|
sie selbst beantwortet und welche über /api/partner/… an die andere Instanz gehen. Seit 24.09.2026 mit dem
|
|
Software-Stand (welcher Commit seit wann live ist); den der anderen Instanz liefert /api/partner/instanz."""
|
|
e = einstellungen()
|
|
return {"rolle": e.rolle, "instanz": e.instanz,
|
|
"partner": {"eingerichtet": bool(e.partner_url), "name": e.partner_name},
|
|
"stand": software_stand.stand()}
|
|
|
|
|
|
@router.get("/partner")
|
|
def partner_status() -> dict:
|
|
return partner.status()
|
|
|
|
|
|
@router.api_route("/partner/{pfad:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"], include_in_schema=False)
|
|
async def weiterreichen(pfad: str, request: Request) -> Response:
|
|
e = einstellungen()
|
|
if not e.partner_url:
|
|
raise HTTPException(status_code=404, detail="Es ist keine zweite Instanz eingerichtet.")
|
|
if pfad.split("/", 1)[0] in _GESPERRT:
|
|
raise HTTPException(status_code=404, detail="Diese Schnittstelle wird nicht weitergereicht.")
|
|
kopf = {"X-MC-Von": e.instanz}
|
|
if request.headers.get("content-type"):
|
|
kopf["Content-Type"] = request.headers["content-type"]
|
|
try:
|
|
async with httpx.AsyncClient(timeout=_ZEITLIMIT) as c:
|
|
r = await c.request(request.method, f"{e.partner_url}/api/{pfad}",
|
|
params=request.query_params, content=await request.body(), headers=kopf)
|
|
except httpx.HTTPError:
|
|
raise HTTPException(status_code=502, detail=f"{e.partner_name} ist gerade nicht erreichbar.") from None
|
|
return Response(content=r.content, status_code=r.status_code,
|
|
media_type=r.headers.get("content-type", "application/json"))
|