# 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.