→ 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).
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.
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'
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.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.
<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.
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.
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.
lyx.lock mit gepinntem SHA256 muss auch in einem Jahr noch stimmen.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.
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.