Files
B0rbor4d cc4c3fcecb Hub-and-Spoke Umbau: Multi-Tenant Zentrale + Satellite-Agent
- Backend: Customer/Satellite Models, customer_id auf Server/Job/Audit
- Satellite-API: heartbeat, poll (atomares Claiming), logs, result,
  scan-result, health-report - Auth via X-Api-Key (SHA-256 gehasht)
- Job-Queue: pending/claimed/running/success/failed + Stale-Janitor
- Batch-Trigger: ein Job pro Server, Satellite arbeitet sequenziell ab
- Credentials bleiben lokal: nur symbolische credential_ref zentral
- Neues Paket satellite/: Pull-Loop, WinRM/SSH/CAU/Scanner, PyInstaller-tauglich
- Frontend: Kunden-Switcher, Satelliten-View, Polling statt WebSocket
- Entfernt: WebSocket/Socket.io, Redis, zentrale Credentials, JobRunner
- Docs: README/AGENTS/PROMPT auf neue Architektur aktualisiert
2026-08-07 03:42:06 +00:00

126 lines
5.4 KiB
Markdown

# Insight Updater - Agent Orientation
## Project Purpose
Zentrale, mandantenfaehige Update-Orchestrierung (Hub-and-Spoke). Zentrale (Docker) +
Satelliten beim Kunden (Binary, kein Docker). Satelliten pollen Jobs, fuehren sie lokal
im Kundennetz aus (WinRM/SSH/CAU/Scan) und melden Ergebnisse zurueck.
## Quick Start
```bash
# Zentrale lokal
cd ~/projects/insight-updater
docker compose up -d --build
# Backend only
cd backend && pip install -e . && uvicorn app.main:app --reload
# Frontend only
cd frontend && npm install && npm run dev
# Satellite (Dev, gegen lokale Zentrale)
cd satellite && pip install -e .
cp config.example.yaml config.yaml # api_key eintragen
insight-satellite
```
## Architecture Overview
```
Kundennetz Zentrale
┌────────────┐ outbound HTTPS ┌──────────────────┐
│ Satellite │ ──────────────────▶ │ FastAPI Backend │
│ (pollt │ X-Api-Key Auth │ /api/satellite │
│ alle 30s) │ ◀────────────────── │ /api (Dashboard │
└────────────┘ Jobs (claimed) │ JWT) + DB │
│ WinRM/SSH/CAU lokal └──────────────────┘
▼ ▲
Server im Kundennetz Vue 3 Frontend (JWT, Kunden-Switcher)
```
## Key Directories
| Pfad | Zweck |
|------|-------|
| `backend/app/api/routes/satellite_api.py` | Agent-API: heartbeat, poll, logs, result, scan-result, health-report |
| `backend/app/api/routes/` | Dashboard-REST: auth, customers, satellites, servers, updates, audit |
| `backend/app/models/` | Customer, Satellite, Server, UpdateJob, UpdateLog, AuditLog, User |
| `backend/app/services/janitor.py` | Markiert stale Jobs (Satellite meldet nicht mehr) als failed |
| `satellite/satellite/runner.py` | Main-Loop: heartbeat, poll, execute, report |
| `satellite/satellite/winrm_exec.py` | Windows Update via pywinrm |
| `satellite/satellite/ssh_exec.py` | Linux Update via asyncssh |
| `satellite/satellite/cau_exec.py` | Invoke-CauRun via WinRM |
| `satellite/satellite/scanner.py` | Ping-Sweep + Port-Probe (5985/22) |
| `frontend/src/stores/` | Pinia: auth, customers, satellites, servers, updates |
## Core Models
| Model | Beschreibung |
|-------|--------------|
| `Customer` | Tenant: name, slug |
| `Satellite` | Agent beim Kunden: api_key_hash, last_seen, version, hostname |
| `Server` | Inventar pro Kunde: hostname, type, credential_ref (symbolisch!) |
| `UpdateJob` | customer_id, server_id (null bei Scan), satellite_id, status, params (JSON) |
| `UpdateLog` | Log-Zeilen pro Job (Batch-Upload vom Satellite) |
| `AuditLog` | Wer, wann, was - mit customer_id |
| `User` | Dashboard-User (lokal oder LDAP-Stub) |
## Job Lifecycle (Pull-Modell)
```
pending -> claimed (beim Poll) -> running (erster Log-Push) -> success | failed
| cancelled (nur aus pending)
```
- Claiming passiert atomar im Poll (`with_for_update`), zwei Satelliten eines Kunden
bekommen nie denselben Job
- Janitor (`services/janitor.py`, alle 60s): claimed/running ohne Report seit
`JOB_STALE_TIMEOUT` (default 3600s) -> failed
- Cancel nur moeglich solange pending
## API Endpoints (Dashboard, JWT)
| Method | Path | Beschreibung |
|--------|------|--------------|
| POST | `/api/auth/login` | Login |
| GET/POST/PATCH/DELETE | `/api/customers[/{id}]` | Kunden CRUD |
| GET/POST/DELETE | `/api/satellites[/{id}]` | Satelliten CRUD |
| POST | `/api/satellites/{id}/rotate-key` | Neuer API-Key |
| GET/POST/PATCH/DELETE | `/api/servers[/{id}]` | Inventar (customer_id scoped) |
| POST | `/api/updates/trigger` | Einzelner Job |
| POST | `/api/updates/trigger-batch` | Ein Job pro Server |
| GET | `/api/updates/{id}/logs` | Job-Logs |
| GET | `/api/audit` | Audit (filterbar per customer_id) |
## API Endpoints (Satellite, X-Api-Key)
| Method | Path | Beschreibung |
|--------|------|--------------|
| POST | `/api/satellite/heartbeat` | Lebenszeichen + Version |
| GET | `/api/satellite/poll` | Pending Jobs claimen + abholen |
| POST | `/api/satellite/logs` | Log-Batch + Progress |
| POST | `/api/satellite/result` | Abschluss success/failed |
| POST | `/api/satellite/scan-result` | Gefundene Hosts (legt Server an) |
| POST | `/api/satellite/health-report` | Health-Ergebnis pro Server |
## Satellite-Deployment beim Kunden
- Binary via PyInstaller: `pyinstaller --onefile satellite/runner.py`
- `config.yaml`: central_url, api_key, poll_interval
- `credentials.yaml`: WinRM/SSH Zugangsdaten (bleiben lokal!)
- Start: Scheduled Task (Windows) oder systemd (Linux), siehe `satellite/README.md`
- 1-2 Stueck pro Kunde reichen - steuern das ganze Netz
## Conventions
- **Sprache**: Deutsch fuer User-facing Text, Englisch fuer Code/Kommentare
- **Keine Umlaute in .ps1/Python-Dateien** (ae, oe, ue, ss)
- **Keine Credentials zentral** - nur `credential_ref` Strings
- **Async**: Backend vollstaendig async; Satellite async mit to_thread fuer pywinrm
- **Types**: Strict mypy, Pydantic v2, SQLAlchemy 2.0
## Deployment
**Target Zentrale**: `monitoring` (10.0.2.105)
**Reverse Proxy**: Traefik (Docker labels)
**Git Remote**: `ssh://git@gitea.insight-it.de:2222/b0rbor4d/insight-updater.git`
**Wichtig**: Zentrale muss von Kundenstandorten aus per HTTPS (443) erreichbar sein.