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ächeapi/v1/ – Schnittstelle für Clientsincludes/ – gemeinsame Bibliothekstorage/ – sämtlicher Zustand, nicht über das Web erreichbarclient-package/ – der weitergebbare Ordner für ProjekteProjekt (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.
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.
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.
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.
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.
Bei aktivem S3-Archiv arbeitet die lokale Platte als schneller Zwischenspeicher und der Bucket als vollständiges Archiv:
s3_retention Sicherungen je Instanz (Standard 365).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.
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.
| 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.