====== LPM — eine Registry betreiben ======
→ [[lyx_-_programmiersprache:tools:lpm|Zurück zu LPM]] · [[lyx_-_programmiersprache:tools:lpm:registry-protokoll|Registry-Protokoll]]
Diese Seite richtet sich an **Betreiber** einer Registry. Wer nur Pakete benutzen oder veröffentlichen will, braucht sie nicht.
Die Registry ist statisch: ''lpm install'' liest ausschließlich Dateien über ''GET''. Serverseitige Logik gibt es an genau einer Stelle — dem Publish-Endpunkt unter ''/api/''. Beschrieben ist der Stand auf ''lpm.seolizer.de'' (Debian 12, nginx 1.22.1, php8.2-fpm, Cloudflare davor).
----
===== 1. Verzeichnisse =====
sudo install -d -o www-data -g www-data /var/www/lpm
sudo install -d -o www-data -g www-data /var/www/lpm/packages
sudo install -d -o www-data -g www-data /var/www/lpm/keys
sudo install -d -o www-data -g www-data /var/www/lpm/api/v1
sudo install -d -o www-data -g www-data /var/www/lpm/api/lib
Der Webroot muss ''www-data'' gehören — der Publish-Endpunkt legt dort Archive und Indizes an.
===== 2. API ausrollen =====
scp deploy/api/lib/registry.php root@:/var/www/lpm/api/lib/
scp deploy/api/v1/publish.php root@:/var/www/lpm/api/v1/
scp deploy/lpm-token root@:/usr/local/sbin/
ssh root@ 'chown -R www-data:www-data /var/www/lpm/api \
&& chmod 644 /var/www/lpm/api/*/*.php \
&& chmod 755 /usr/local/sbin/lpm-token'
===== 3. vhost =====
''deploy/nginx-lpm.seolizer.de.conf'' nach ''/etc/nginx/sites-available/lpm.seolizer.de'', verlinken, prüfen, neu laden:
sudo ln -sf /etc/nginx/sites-available/lpm.seolizer.de /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Drei Regeln, auf die es ankommt:
**PHP läuft nur unter ''/api/''.** Außerhalb gibt ''location ~ \.php$'' ein 404 zurück. Ohne diese Regel würde eine ''.php''-Datei in einem hochgeladenen Paket zu **Remote Code Execution** — der Server liefert schließlich Archive aus, die Fremde eingereicht haben.
* **''index.json'' wird nicht gecacht** (''Cache-Control: no-cache''), sonst liefert Cloudflare nach einem ''publish'' weiter den alten Index aus.
* **''.lxpkg'' ist unveränderlich** (''immutable'', ein Jahr) und wird nicht gzip-komprimiert — zstd ist schon drin.
----
===== 4. Token anlegen =====
Für ''lpm.seolizer.de'' laufen Anfragen über ein Issue unter https://github.com/SEOLizer/Lyx-Package-Manager/issues, Titel mit ''Token:'' beginnend. Der Anfragende nennt dort Namensraum, Publisher-Name, Zweck und einen Kontaktweg — der ausgestellte Token geht über diesen Kontaktweg zurück und **niemals ins Issue**, das öffentlich bleibt. Die Anwenderseite ist unter [[lyx_-_programmiersprache:tools:lpm|LPM, Kapitel 4]] beschrieben.
Dafür gibt es ''lpm-token''. Es legt ''/etc/lpm/tokens.json'' bei Bedarf an, setzt die Rechte und schreibt **nur SHA256-Hashes** hinein, nie den Token selbst. Wer die Datei liest, kann damit nichts hochladen.
# lpm-token add anna net/ crypto/
Token für 'anna' (net/, crypto/):
3f9a… (64 Hexziffern)
Dieser Token wird NICHT gespeichert und lässt sich nicht erneut anzeigen.
Die Namensräume sind **erlaubte Präfixe**. Ein Token mit ''net/'' kann ''net/http'' veröffentlichen, ''crypto/tls'' aber nicht — so lässt sich die Übernahme fremder Namensräume verhindern, ohne für jeden Publisher einen eigenen Server zu brauchen. Ohne Angabe (''lpm-token add anna'') gilt ''*'', also alles.
Die Grenze wird tatsächlich durchgesetzt:
{"ok": false, "error": "Dieser Token darf unter \"net/boese\" nicht veroeffentlichen."}
# lpm-token list
PUBLISHER NAMENSRÄUME
roene *
anna net/, crypto/
# lpm-token remove anna
Ein entzogener Zugang lässt bereits veröffentlichte Pakete unberührt — **sie gehören zur Registry, nicht zum Token**. Ein verlorener Token lässt sich nicht wiederherstellen: entziehen und neu ausstellen.
Von Hand ginge es auch; das Format ist ''{"<sha256 des tokens>": {"publisher": "...", "namespaces": ["..."]}}'', Eigentümer ''root:www-data'', Modus ''640''.
----
===== 5. Prüfen =====
curl -s https://lpm.seolizer.de/index.json | head
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://lpm.seolizer.de/api/v1/publish
**''401'' ist hier das gute Ergebnis:** der Endpunkt lebt und verlangt einen Token.
----
===== Der Publish-Endpunkt =====
''POST /api/v1/publish''
^ Kopfzeile ^ Pflicht ^ Inhalt ^
| ''Authorization: Bearer <token>'' | ja | |
| ''X-Lpm-Name'' | ja | ''net/http'' |
| ''X-Lpm-Version'' | ja | ''1.2.0'' |
| ''X-Lpm-Sha256'' | nein | wird gegen den Rumpf geprüft, nicht übernommen |
| ''X-Lpm-Deps'' | nein | JSON-Objekt, etwa ''{"std/io":">=1.0.0"}'' |
| ''X-Lpm-Desc'' | nein | eine Zeile, landet im Gesamtindex |
Der Rumpf ist das rohe ''.lxpkg'' — kein ''multipart''. Die Antwort ist JSON.
^ Status ^ Bedeutung ^
| 201 | aufgenommen; die Antwort enthält ''sha256'' und ''source'' |
| 400 | Name, Version, Deps oder Archiv unbrauchbar |
| 401 | Token fehlt oder ist unbekannt |
| 403 | Token darf unter diesem Namen nicht veröffentlichen |
| 409 | Version existiert bereits — Archive sind unveränderlich |
| 413 | größer als 32 MiB |
Nach jedem erfolgreichen Upload wird ''/packages/<name>/index.json'' ergänzt und ''/index.json'' vollständig neu gebaut. Beides über ''rename()'', damit ein gleichzeitiger ''GET'' nie eine halb geschriebene Datei sieht.
==== Was der Endpunkt bewusst nicht tut ====
* **Er packt das Archiv nicht aus.** Ein Server, der fremde Archive entpackt, ist ein Server, der fremde Pfade anlegt. Geprüft werden Größe und die zstd-Magic-Bytes; der Inhalt interessiert erst den Client.
* **Er überschreibt nie eine Version.** Wer sich vertan hat, veröffentlicht eine neue. Ein ''lyx.lock'' mit gepinntem SHA256 muss auch in einem Jahr noch stimmen.
* **Er nimmt keine Signaturen entgegen.** Ed25519 mit Trust-on-first-use ist WP-PM-10; bis dahin ist der Token die einzige Zugangskontrolle.
----
===== Von Hand veröffentlichen =====
Ohne Client geht es auch:
curl -X POST https://lpm.seolizer.de/api/v1/publish \
-H "Authorization: Bearer $TOKEN" \
-H "X-Lpm-Name: net/http" \
-H "X-Lpm-Version: 0.2.0" \
-H 'X-Lpm-Deps: {"std/io":">=1.0.0"}' \
-H "X-Lpm-Desc: HTTP client for Lyx" \
--data-binary @net_http-0.2.0.lxpkg
Mit Client ist es ''lpm login --token=<token>'' einmalig, danach ''lpm publish''.
----
===== Ein Paket zurückziehen =====
Dafür gibt es noch keinen Endpunkt. Von Hand:
# yanked auf true setzen ...
sudo -u www-data nano /var/www/lpm/packages/net/http/index.json
# ... und den Gesamtindex neu bauen lassen
sudo -u www-data php -r 'require "/var/www/lpm/api/lib/registry.php"; lpm_rebuild_root_index();'
Das Archiv bleibt liegen. ''yanked'' heißt „nicht mehr für neue Auflösungen wählbar", nicht „gelöscht" — bestehende ''lyx.lock'' müssen reproduzierbar bleiben.