ARCHITECTURE.md 6.1 KB

Architektur

Überblick

manage besteht aus zwei Hälften: dem Server in diesem Repository und dem Client-Paket, das in jedes betreute Projekt kopiert wird.

Eine Server-Installation betreut ein Produkt mit einer überschaubaren Zahl von Instanzen. Für ein weiteres Produkt wird manage erneut ausgerollt.

Relevante Verzeichnisse:

  • admin/ – Oberfläche
  • api/v1/ – Schnittstelle für Clients
  • includes/ – gemeinsame Bibliothek
  • storage/ – sämtlicher Zustand, nicht über das Web erreichbar
  • client-package/ – der weitergebbare Ordner für Projekte

Datenfluss

Projekt (Instanz)                        Manage-Server
─────────────────                        ─────────────
manage-client/
  bin/manage-client.php  ──── check ───►  api/v1/manifest.php  ──► storage/releases/manifest.json
  ui/panel.php           ──── update ──►  api/v1/package.php   ──► storage/releases/packages/
  lib/*.php              ──── backup ──►  api/v1/backup.php    ──► storage/backups/<instanz>/
                         ──── status ──►  api/v1/heartbeat.php ──► storage/instances.json
                                                                        │
                                                                        ▼
                                                            admin/  (Anmeldung mit Passwort)

Der Client zieht; der Server schiebt nie. Es gibt keine Verbindung vom Server zur Instanz, was den Betrieb hinter NAT und Firewalls unkompliziert macht.

Authentifizierung

Zwei getrennte Wege:

Weg Wer Mittel
admin/ Menschen ein Passwort, Sitzung, CSRF, Ratenbegrenzung
api/v1/ Instanzen Instanz-Kennung + Token in zwei Headern, zustandslos

Tokens werden als SHA-256-Hash gespeichert und in konstanter Zeit verglichen. Das Klartext-Token erscheint genau einmal, beim Anlegen oder Erneuern.

Details: client-package/docs/08_PROTOCOL.md.

Instanzregister

storage/instances.json ist das Bindeglied zwischen beiden Modulen. Es ersetzt zwei getrennte Mechanismen der Vorgängerlösung: die Namensliste des Backup-Servers und die vollständig fehlende Client-Identität des Update-Servers.

{
    "instances": [
        {
            "id": "example-prod",
            "label": "Stadt Freising Produktiv",
            "enabled": true,
            "token_hash": "…",
            "created_at": "…",
            "token_rotated_at": "…",
            "last_seen_at": "…",
            "last_ip": "…",
            "version": "v1.3.14",
            "php_version": "8.3.6",
            "disk_free": 12884901888,
            "pending_migrations": 0,
            "last_backup_at": "…",
            "backup_count": 3,
            "notes": ""
        }
    ]
}

Jede erfolgreich authentifizierte Anfrage aktualisiert last_seen_at und last_ip. Die Übersicht bleibt dadurch aktuell, auch ohne eigenen Heartbeat.

Eine gelöschte Instanz kann nichts mehr hochladen; ihre bereits gespeicherten Backups bleiben aber erhalten und in der Oberfläche sichtbar.

Releases

storage/releases/manifest.json ist die Release-Datenbank, die Pakete liegen daneben in packages/:

{
    "latest": "v1.3.0",
    "releases": {
        "v1.3.0": {
            "version": "v1.3.0",
            "package": "packages/example-orderform-v1.3.0.zip",
            "sha256": "…",
            "size": 2199,
            "published_at": "…"
        }
    }
}

Prüfsumme und Größe berechnet immer der Server nach dem Upload; sie werden nie vom Hochladenden übernommen. Ein Upload setzt das Release automatisch als latest.

Die Download-URL wird aus MANAGE_PUBLIC_URL gebildet, nicht aus dem Host-Header. Die Vorgängerlösung leitete sie aus HTTP_HOST ab, also aus einem vom Client kontrollierten Wert.

Backups

storage/backups/
  index.json                     Metadaten aller Backups
  <instanz>/backup-YYYYmmdd-HHMMSS[-N].zip

Der Server prüft nach dem Speichern die Prüfsumme erneut und löscht die Datei bei Abweichung. Ein vorhandener Dateiname wird nie überschrieben.

Zwei Aufbewahrungsstufen

Bei aktivem S3-Archiv arbeitet die lokale Platte als schneller Zwischenspeicher und der Bucket als vollständiges Archiv:

  • S3: behält die neuesten s3_retention Sicherungen je Instanz (Standard 365).
  • Lokal: behält die neuesten retention Sicherungen (Standard 30), löscht eine Datei aber nie, solange ihr S3-Upload noch aussteht.

Ist S3 nicht erreichbar, wachsen die lokalen Kopien also über die Aufbewahrung hinaus, statt die einzige Kopie zu verlieren. Fehlgeschlagene S3-Uploads werden beim nächsten Upload derselben Instanz oder über die Schaltfläche in der Oberfläche nachgeholt.

S3-Fehler lassen einen Client-Upload nie fehlschlagen: Die lokale Kopie liegt bereits vor. Protokolliert werden sie in storage/logs/s3.log.

Speicherung

Ausschließlich flache Dateien, kein Datenbankserver. Alle Schreibvorgänge laufen über manageWriteJsonFile(): erst in eine .tmp-Datei, dann rename(). Damit kann ein abgebrochener Request keinen halb geschriebenen Index hinterlassen.

Bekannte Grenze: Gleichzeitige Uploads derselben Instanz können sich beim Schreiben von index.json überschneiden. Bei einer Handvoll Instanzen mit nächtlichen Backups ist das praktisch ausgeschlossen; bei vielen gleichzeitigen Uploads wäre eine Sperre nötig.

Protokolle

Datei Inhalt
storage/logs/access.log JSONL: Anmeldungen, Releases, empfangene Backups, Downloads
storage/logs/error.log JSONL: fehlgeschlagene Anmeldungen, abgelehnte Uploads, interne Fehler
storage/logs/s3.log Klartext: S3-Diagnose mit Status, Umleitungen und Request-ID

Die JSONL-Protokolle rotieren nach MANAGE_LOG_MAX_BYTES und werden nach MANAGE_LOG_MAX_AGE_SECONDS entfernt.

Weiter