LPM — eine Registry betreiben
→ Zurück zu LPM · 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@<server>:/var/www/lpm/api/lib/
scp deploy/api/v1/publish.php root@<server>:/var/www/lpm/api/v1/
scp deploy/lpm-token root@<server>:/usr/local/sbin/
ssh root@<server> '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:
<WRAP alert>
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.
</WRAP>
index.jsonwird nicht gecacht (Cache-Control: no-cache), sonst liefert Cloudflare nach einempublishweiter den alten Index aus..lxpkgist unveränderlich (immutable, ein Jahr) und wird nicht gzip-komprimiert — zstd ist schon drin.
4. Token anlegen
<WRAP info>
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 LPM, Kapitel 4 beschrieben.
</WRAP>
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.lockmit 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.
