Inhaltsverzeichnis

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>


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 {„&lt;sha256 des tokens&gt;“: {„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 &lt;token&gt; 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/&lt;name&gt;/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


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=&lt;token&gt; 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.