Dokumentation durchblättern
Module entwickeln
Modul-Signierung & Verteilung
Wie Module als signierte Pakete über einen Katalog-Server verteilt, geprüft und installiert werden — Trust-Kette, Sync-Prozess und Hinweise für Modul-Herausgeber.
Module werden nicht offen aus einem Repository gezogen, sondern als signierte Pakete über einen Katalog-Server verteilt. Schneespur prüft jedes Paket gegen eine Vertrauensliste, bevor es installiert wird — so lässt sich nachvollziehen, dass ein Modul tatsächlich von einem berechtigten Schlüssel stammt und unterwegs nicht verändert wurde.
Wie Module verteilt werden
Die Verteilung läuft über einen Katalog-Server. Das Update-System der Anwendung übernimmt das Abrufen, Prüfen, Installieren und Aktualisieren der Module — Sie stoßen den Vorgang an, die Schritte selbst laufen automatisch ab.
Den Katalog-Server konfigurieren Sie in config/schneespur_modules.php:
'server_url' => 'https://jenni.noschmarrn.dev',
'collection_slug' => 'schneespur-module',
'catalog_endpoint' => '/api/modules/{slug}',
Der Sync-Prozess
Der Befehl schneespur:modules-sync gleicht Ihre Installation mit dem Katalog ab. Er
durchläuft dabei diese Schritte:
- Katalog abrufen — HTTP-GET auf den
catalog_endpoint, mit ETag-Caching - Signaturen prüfen — der Katalog ist mit libsodium signiert
- Versionen vergleichen — installierte gegen im Katalog angebotene Version
- Updates herunterladen — ZIP-Dateien für neue oder aktualisierte Module
- ZIP-Integrität prüfen — Signaturprüfung der heruntergeladenen Datei
- Installieren/aktualisieren — Entpacken ins Verzeichnis
modules/ - Migrationen ausführen —
artisan migrate --path=... - Verwaiste Module erkennen — lokal vorhanden, aber aus dem Katalog entfernt
Der Grund für diese Reihenfolge: Signaturen werden vor dem Entpacken geprüft. Ein Paket, das die Prüfung nicht besteht, wird gar nicht erst auf die Platte geschrieben.
Mit der Option --dry-run zeigt der Befehl nur an, was geschehen würde, ohne etwas zu
verändern — nützlich, um vor einem echten Sync zu sehen, welche Module aktualisiert werden.
php artisan schneespur:modules-sync --dry-run
Signaturprüfung
Damit ein Paket installiert wird, muss seine Signatur zu einem Schlüssel passen, dem die Installation vertraut. Dieses Vertrauen ist nicht fest verdrahtet, sondern als Kette aufgebaut — vom einen Wurzelschlüssel in Ihrer Konfiguration bis zum einzelnen Modul-ZIP.
Die Trust-Kette
Root public key (in config)
→ fetches trust list from server (/api/signing/trust)
→ trust list contains valid signing keys + revoked keys
→ module ZIPs/catalogs are signed with a valid key
Der Wurzelschlüssel ist der einzige Vertrauensanker, der lokal liegt. Alles Weitere — welche Signaturschlüssel aktuell gültig und welche zurückgezogen sind — kommt über die signierte Vertrauensliste vom Server. So lassen sich Schlüssel sperren, ohne dass Sie Ihre Konfiguration anfassen müssen.
Der ModuleSignatureVerifier
Der Dienst ModuleSignatureVerifier führt die Prüfung in dieser Reihenfolge durch:
- Vertrauensliste vom Server abrufen
- Signatur der Vertrauensliste gegen den
root_pubkeyprüfen - auf zurückgezogene Schlüssel prüfen
- Modul-Manifest- und ZIP-Signaturen gegen die gültigen Schlüssel prüfen
- Vertrauensstufe melden:
unsigned,signed,verifiedoderfailed
Die gemeldete Stufe macht den Zustand eines Pakets nachvollziehbar: verified bedeutet eine
gültige Signatur eines vertrauenswürdigen Schlüssels, failed eine fehlgeschlagene Prüfung —
ein solches Paket wird nicht installiert.
Der Root Public Key
Der Wurzelschlüssel liegt als base64-codierter libsodium-Public-Key in der Konfiguration:
'root_pubkey_b64' => 'bbYkDrjwTapdcONvnhB3tfcwe0aA+lAcgnd0dLMlkmg=',
Es ist derselbe Schlüssel, den auch das Update-System der Anwendung verwendet — Module und Anwendungs-Updates teilen sich denselben Vertrauensanker.
Modul-Installation
Der SchneespurModuleInstaller
Das Entpacken übernimmt der SchneespurModuleInstaller. Er behandelt ein ZIP-Archiv nicht
als vertrauenswürdig, sondern wendet mehrere Schutzmaßnahmen an:
- Path-Traversal-Schutz — verwirft Einträge mit
..oder führendem/ - macOS-Metadaten filtern — überspringt
__MACOSX/-Verzeichnisse - Auto-Prefix-Erkennung — entfernt ein gemeinsames Wurzelverzeichnis (typisches Artefakt von ZIP-Exporten)
- Atomare Updates — sichert die aktuelle Version vor dem Entpacken und rollt bei einem Fehler zurück
Der Path-Traversal-Schutz ist hier kein Detail: Er verhindert, dass ein Archiv-Eintrag außerhalb des Modulverzeichnisses schreibt. Deshalb wird jeder Eintrag vor dem Schreiben auf Pfadsicherheit geprüft.
Ablauf der Installation
install(zipPath, slug)
1. Extract ZIP to modules/{slug}/
2. Detect and strip common prefix directory
3. Validate all entries for path safety
4. Write files to disk
update(zipPath, slug)
1. Backup current module to modules/{slug}.bak/
2. Extract new version
3. On failure: rollback from .bak
remove(slug)
1. Delete modules/{slug}/
Der Backup-Schritt beim Update ist der Grund, warum ein fehlgeschlagenes Update Ihre
laufende Installation nicht beschädigt: Schlägt das Entpacken fehl, stellt der Installer die
gesicherte Version aus .bak wieder her.
Die Modul-Zustandsdatei
Welche Module installiert sind und welcher Trust-Stand gilt, hält Schneespur in
storage/app/schneespur_modules_state.json fest:
{
"catalog_etag": "\"abc123\"",
"catalog_cache": [...],
"synced_at": "2026-05-26T10:00:00Z",
"installed": ["telegram", "documents"],
"orphans": [],
"trust_version": 1,
"valid_keys": ["key1_b64", "key2_b64"],
"revoked_keys": [],
"trust_expires_at": "2026-12-31T23:59:59Z"
}
Diese Datei wird atomar geschrieben (temporäre Datei plus Umbenennen), damit ein Abbruch mitten im Schreiben sie nicht beschädigt. Das ETag erspart beim nächsten Sync einen vollständigen erneuten Download, wenn sich am Katalog nichts geändert hat.
Module entfernen
Ein Modul entfernen Sie mit schneespur:modules-remove:
php artisan schneespur:modules-remove telegram
php artisan schneespur:modules-remove telegram --force
Der Befehl arbeitet diese Schritte ab:
- Modul deaktivieren (prüft umgekehrte Abhängigkeiten)
- Modul-Migrationen zurückrollen
- Einstellungen aufräumen (
Setting::where('key', 'like', '{slug}.%')->delete()) - Modulverzeichnis löschen
- öffentlichen Symlink entfernen
Die Prüfung der umgekehrten Abhängigkeiten verhindert, dass Sie ein Modul entfernen, auf das
ein anderes noch angewiesen ist. Mit --force überspringen Sie diese Prüfung — das sollten
Sie nur tun, wenn Sie die Folgen kennen.
Für Modul-Herausgeber
Wenn Sie ein Modul für den Katalog herausgeben, muss Ihr Paket einem festen Aufbau folgen, damit der Installer und die Signaturprüfung greifen.
Aufbau des ZIP-Archivs
my-module/
module.json
src/
MyModuleServiceProvider.php
resources/views/
database/migrations/
lang/
dist/
Anforderungen
- Gültige
module.jsonmit allen Pflichtfeldern - ServiceProvider erweitert
Illuminate\Support\ServiceProvider - Der Namespace stimmt mit der Angabe in
module.jsonüberein - Tabellennamen tragen das Präfix
mod_{slug}_ - Keine Änderungen an Core-Dateien — Erweiterungen ausschließlich über die Registries
- Übersetzungen für
deundenwerden empfohlen
Die Forderung „keine Änderungen an Core-Dateien” ist der Kern des Modulsystems: Module erweitern den Core über die Registries, statt ihn umzuschreiben. Dadurch bleiben Updates der Anwendung konfliktfrei. Wie diese Erweiterungspunkte funktionieren, steht unter ServiceProvider und Registries.
Versionsnummern
Folgen Sie der semantischen Versionierung MAJOR.MINOR.PATCH:
- MAJOR — brechende Änderungen (umbenannte Einstellungen, entfernte Funktionen)
- MINOR — neue Funktionen, abwärtskompatibel
- PATCH — Fehlerbehebungen, keine neuen Funktionen
Saubere Versionsnummern sind kein Selbstzweck: Der Sync-Prozess entscheidet anhand des Versionsvergleichs, ob ein Update angeboten wird. Wer eine brechende Änderung als PATCH ausliefert, überrascht damit jede Installation, die automatisch aktualisiert.
Lokale Entwicklung ohne Katalog-Server
Während der Entwicklung brauchen Sie weder Signatur noch Katalog. Legen Sie Ihr Modul
einfach direkt in modules/ ab:
modules/my-module/
module.json
src/
...
Aktivieren Sie es danach über die Modul-Verwaltung in der Oberfläche oder setzen Sie
default_enabled: true in der module.json. Den Einstieg in den Aufbau eines Moduls finden
Sie im Schnelleinstieg; ein vollständiges Beispiel liegt der
Anwendung als Referenzmodul bei.