363 lines
18 KiB
Markdown
363 lines
18 KiB
Markdown
# NetBox Plugin Store
|
||
|
||
Der Store ist eine eigenständige PHP-Anwendung für einen kuratierten NetBox-Plugin-Katalog. Er liest Repositories von Forgejo/Gitea (standardmäßig `git.mrblake.cc`) ein, erkennt Plugin-Metadaten, rendert die README der exakt synchronisierten Commit-Revision und veröffentlicht erst nach einer getrennten Admin-Freigabe einen API-v1-Katalog.
|
||
|
||
Die native Installation mit PHP und Apache ist der primäre Betriebsweg. Docker ist optional.
|
||
|
||
## Funktionsumfang
|
||
|
||
- responsive deutschsprachige Store-, Such-, Filter- und Plugin-Detailseiten;
|
||
- vollständige Forgejo-/Gitea- und GitHub-Discovery für Benutzer und Organisationen;
|
||
- Erkennung über Topic, `netbox-plugin.json|yaml`, `pyproject.toml`, `setup.py` und `PluginConfig`;
|
||
- commitgenaue Reads für Manifest, Projektmetadaten, Tree, PluginConfig und README;
|
||
- CommonMark-Rendering ohne eingebettetes HTML; relative Links und Bilder werden auf die Raw-URL des Commit-SHA umgeschrieben;
|
||
- eigener Admin-Login mit Argon2id, CSRF-Schutz, Session-Härtung und persistentem Login-Rate-Limit;
|
||
- Freigabe/Ablehnung von Quellen, Plugins und jedem Release-Artefakt; Admin-Metadatenkorrekturen und Audit-Log;
|
||
- tatsächlicher Download jedes öffentlichen Wheel-Artefakts mit SHA-256 und Größe; sicherheitsrelevante Änderungen setzen die Freigabe zurück;
|
||
- öffentlicher, freigegebener JSON-Katalog unter `/api/v1/`;
|
||
- atomar geschriebener JSON-Datastore als Zero-Setup-Standard oder optional MariaDB über PDO;
|
||
- einmaliger Sync sowie signalverträglicher Watch-Modus ohne Cron/Celery;
|
||
- pro Source eine prozessübergreifende exklusive Sync-Lease sowie aggregierte Limits für Laufzeit, Requests, Bytes, Repositories und Releases.
|
||
|
||
Wichtig: Ein eingelesenes und freigegebenes Repository ist noch nicht automatisch installierbar. API v1 veröffentlicht ausschließlich separat freigegebene `.whl`-Artefakte. Fehlt ein Wheel, bleibt das Plugin als Katalogeintrag sichtbar und `latest_version` ist `null`; die UI zeigt „Kein installierbares Release“.
|
||
|
||
## 1. Native Installation (empfohlen)
|
||
|
||
### Voraussetzungen
|
||
|
||
- PHP 8.3 oder neuer (getestet mit PHP 8.4);
|
||
- Apache 2.4 mit `mod_rewrite` und `mod_headers`;
|
||
- Composer 2;
|
||
- PHP-Erweiterungen: `curl`, `dom`, `iconv`, `intl`, `json`, `mbstring`, `pdo` und für MariaDB zusätzlich `pdo_mysql`; für einen graceful beendbaren dauerhaften `--watch`-Prozess wird `pcntl` empfohlen;
|
||
- Schreibzugriff des Apache-/Sync-Benutzers auf `store/data`.
|
||
|
||
Beispiel für Debian/Ubuntu (Paketnamen können je Distribution/PHP-Repository abweichen):
|
||
|
||
```bash
|
||
sudo apt update
|
||
sudo apt install apache2 libapache2-mod-php php-cli php-curl php-xml php-intl php-mbstring php-mysql composer
|
||
sudo a2enmod rewrite headers
|
||
```
|
||
|
||
### Anwendung installieren
|
||
|
||
```bash
|
||
sudo mkdir -p /opt/netbox-plugin-store
|
||
sudo chown "$USER":"$USER" /opt/netbox-plugin-store
|
||
git clone <REPOSITORY-URL> /opt/netbox-plugin-store
|
||
cd /opt/netbox-plugin-store/store
|
||
composer install --no-dev --no-interaction --prefer-dist --classmap-authoritative
|
||
cp .env.example .env
|
||
mkdir -p data
|
||
sudo chown -R www-data:www-data data
|
||
sudo chmod 750 data
|
||
```
|
||
|
||
Der JSON-Pfad in `.env.example` ist relativ zum `store/`-Verzeichnis und funktioniert nativ ohne Änderung:
|
||
|
||
```dotenv
|
||
STORE_DB_DRIVER=json
|
||
STORE_JSON_PATH=./data/store.json
|
||
```
|
||
|
||
Der Datastore verwendet einen prozessübergreifenden `flock`, schreibt in eine temporäre Datei, synchronisiert sie und ersetzt anschließend die alte Datei atomar. Eine separate nicht-blockierende Lease-Datei hält zusätzlich jeden Source-Sync für seine gesamte Laufzeit exklusiv; ein Prozessabbruch gibt die Kernel-Sperre frei. Verwende ein lokales Dateisystem; geteilte/NFS-Dateisysteme bieten nicht immer die benötigten Lock-/Rename-Garantien. Für mehrere Web-Hosts ist MariaDB die bessere Wahl; dort übernimmt `GET_LOCK` die Sync-Lease.
|
||
|
||
### Admin-Zugang sicher konfigurieren
|
||
|
||
Es gibt absichtlich keine Standard-Zugangsdaten. Erzeuge einen langen Zufallswert als Session-Secret und einen Argon2id-Hash:
|
||
|
||
```bash
|
||
php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'
|
||
php bin/console hash-password 'ein-langes-zufaelliges-passwort'
|
||
```
|
||
|
||
Trage Benutzername, ausgegebenen Hash und Secret vollständig in `.env` ein:
|
||
|
||
```dotenv
|
||
STORE_ADMIN_USERNAME=admin
|
||
STORE_ADMIN_PASSWORD_HASH=$argon2id$...
|
||
STORE_SESSION_SECRET=<mindestens-32-zeichen>
|
||
```
|
||
|
||
Sind alle drei Werte leer, bleibt `/admin` deaktiviert. Ist nur ein Teil gesetzt, bricht die Anwendung fail-closed mit einer Konfigurationsmeldung ab. Vermeide Passwörter in der Shell-History; alternativ liest `hash-password` den Wert aus `STORE_ADMIN_PASSWORD_TO_HASH`.
|
||
|
||
Setze außerdem die öffentliche URL. Sichere Cookies richten sich standardmäßig nach ihrem Protokoll:
|
||
|
||
```dotenv
|
||
APP_ENV=production
|
||
STORE_PUBLIC_URL=https://plugins.example.com
|
||
# Optionaler expliziter Override:
|
||
STORE_COOKIE_SECURE=true
|
||
```
|
||
|
||
Ein Production-Quickstart über `http://localhost` funktioniert mit `STORE_PUBLIC_URL=http://localhost:3000` und `STORE_COOKIE_SECURE=false`; für einen öffentlichen Betrieb ist HTTPS erforderlich.
|
||
|
||
### Apache konfigurieren
|
||
|
||
Nur `store/public` darf DocumentRoot sein. Passe Pfad und Servernamen in [deploy/apache-vhost.conf.example](deploy/apache-vhost.conf.example) an:
|
||
|
||
```bash
|
||
sudo cp deploy/apache-vhost.conf.example /etc/apache2/sites-available/netbox-plugin-store.conf
|
||
sudo editor /etc/apache2/sites-available/netbox-plugin-store.conf
|
||
sudo a2ensite netbox-plugin-store
|
||
sudo apache2ctl configtest
|
||
sudo systemctl reload apache2
|
||
```
|
||
|
||
Die Beispielkonfiguration erlaubt `.htaccess` ausschließlich im Store-DocumentRoot. Alternativ können Rewrite- und Header-Regeln aus `public/.htaccess` direkt in den vHost übernommen und `AllowOverride None` gesetzt werden. Für Produktion HTTPS direkt mit Apache `mod_ssl`/ACME konfigurieren und `STORE_PUBLIC_URL` auf `https://…` setzen; ein Reverse Proxy ist nicht erforderlich.
|
||
|
||
Übernimm für den produktiven PHP-SAPI außerdem die sicherheitsrelevanten Werte aus `deploy/php-production.ini` (insbesondere `display_errors=Off`, `log_errors=On` und `expose_php=Off`) in deine PHP-Konfiguration und lade Apache neu. Der Front Controller deaktiviert die Fehlerausgabe zusätzlich selbst.
|
||
|
||
### Bootstrap und erster Sync
|
||
|
||
```bash
|
||
cd /opt/netbox-plugin-store/store
|
||
sudo -u www-data php bin/console bootstrap
|
||
sudo -u www-data php bin/console sync
|
||
```
|
||
|
||
Wenn noch keine Quelle existiert, legt `bootstrap` (und standardmäßig auch `sync`) aus den `STORE_DEFAULT_*`-Werten eine aktive, freigegebene Forgejo-Quelle an. Standardwerte:
|
||
|
||
- Provider `forgejo`;
|
||
- Basis `https://git.mrblake.cc`;
|
||
- API `https://git.mrblake.cc/api/v1`;
|
||
- Benutzer `MrBlake` – entsprechend `/api/v1/users/MrBlake/repos`.
|
||
|
||
Mit `--no-bootstrap` wird die automatische Anlage unterdrückt. Einzelne Quellen lassen sich mit `--source=slug` synchronisieren; `--fail-fast` beendet den Lauf beim ersten Repositoryfehler.
|
||
|
||
Jeder Source-Lauf ist mit konservativen, konfigurierbaren Gesamtbudgets begrenzt. Eine Überschreitung beendet ihn fail-closed als `failed`; neue Freigaben entstehen dabei nicht. Die Defaults stehen auch in `.env.example`:
|
||
|
||
```dotenv
|
||
STORE_SYNC_MAX_SECONDS=900
|
||
STORE_SYNC_MAX_REQUESTS=2500
|
||
STORE_SYNC_MAX_BYTES=1073741824
|
||
STORE_SYNC_MAX_REPOSITORIES=2000
|
||
STORE_SYNC_MAX_RELEASES=1000
|
||
```
|
||
|
||
Die Limits zählen Redirects und Retries als weitere Requests sowie Metadaten und Artefakte gemeinsam gegen das Byte-Budget. Repository- und Release-Zähler werden nach jeder validierten Provider-API-Seite vor deren Übernahme in den Akkumulator geprüft; dadurch kann höchstens die gerade empfangene Seite zusätzlich im Speicher liegen. `STORE_SYNC_MAX_RELEASES` ist zugleich auf die API-v1-Grenze von 1.000 begrenzt. Ein zweiter Lauf derselben Source wird unabhängig vom Alter eines sichtbaren Run-Eintrags abgewiesen; nach einem Prozessabbruch wird der verwaiste Eintrag erst nach erfolgreichem Erwerb der exklusiven Lease geschlossen.
|
||
|
||
### Regelmäßiger Sync mit systemd
|
||
|
||
Die mitgelieferte Unit ist ein `oneshot`-Dienst mit einem 15-Minuten-Timer. Passe gegebenenfalls `/opt/netbox-plugin-store` und `www-data` an:
|
||
|
||
```bash
|
||
sudo cp deploy/netbox-plugin-store-sync.service /etc/systemd/system/
|
||
sudo cp deploy/netbox-plugin-store-sync.timer /etc/systemd/system/
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now netbox-plugin-store-sync.timer
|
||
systemctl list-timers netbox-plugin-store-sync.timer
|
||
journalctl -u netbox-plugin-store-sync.service
|
||
```
|
||
|
||
Alternativ kann ein dauerhafter Prozess verwendet werden:
|
||
|
||
```bash
|
||
sudo -u www-data php bin/console sync --watch --interval=900
|
||
```
|
||
|
||
Oder per Cron (keine überlappenden Läufe; der Store blockiert parallele Syncs zusätzlich):
|
||
|
||
```cron
|
||
*/15 * * * * cd /opt/netbox-plugin-store/store && /usr/bin/php bin/console sync >>/var/log/netbox-plugin-store-sync.log 2>&1
|
||
```
|
||
|
||
## 2. MariaDB statt JSON (optional)
|
||
|
||
Der MariaDB-Adapter nutzt dieselbe Repository-/State-Schnittstelle. Er legt beim ersten Start automatisch eine einzelne, per InnoDB-Transaktion und `SELECT … FOR UPDATE` geschützte State-Zeile an. Das äquivalente Schema liegt unter `config/schema.sql`.
|
||
|
||
```dotenv
|
||
STORE_DB_DRIVER=mariadb
|
||
STORE_MARIADB_DSN=mysql:host=127.0.0.1;port=3306;dbname=netbox_store;charset=utf8mb4
|
||
STORE_MARIADB_USER=netbox_store
|
||
STORE_MARIADB_PASSWORD=<starkes-passwort>
|
||
```
|
||
|
||
Der Datenbankbenutzer benötigt `CREATE`, `SELECT`, `INSERT` und `UPDATE` auf der Store-Datenbank. Sichere die Datenbank wie andere Produktionsdaten regelmäßig.
|
||
|
||
## 3. Quellen und automatische Erkennung
|
||
|
||
Neue Quellen werden in `/admin` zunächst als `pending` angelegt und müssen per POST/CSRF freigegeben werden. Verfügbare Provider:
|
||
|
||
- `forgejo` für Forgejo und Gitea;
|
||
- `github` für GitHub-User oder -Organisationen.
|
||
|
||
Tokens werden nur aus dem in der Source hinterlegten Namen einer Umgebungsvariable gelesen, beispielsweise `GITEA_TOKEN` oder `GITHUB_TOKEN`; Tokenwerte werden nie im Datastore gespeichert. Ein Authorization-Header wird nur gesendet, wenn die Ziel-Origin exakt der `apiUrl`-Origin entspricht, und nach jedem Redirect neu bewertet.
|
||
|
||
Private GitHub-Repositories dürfen mit einem entsprechend berechtigten Token für Metadaten und README eingelesen werden. Ihre Release-Assets werden in v1 bewusst fail-closed übersprungen: Der Store betreibt keinen Artefakt-Cache, und der Host-Agent besitzt keine GitHub-Credentials für einen späteren Download. Der Sync zeigt dafür einen Moderationshinweis und zieht eventuell ältere installierbare Releases dieses Repositorys zurück. Installierbar sind von GitHub ausschließlich öffentliche `.whl`-Assets über deren `browser_download_url`; Provider-Tokens werden niemals an Asset-Hosts weitergereicht.
|
||
|
||
Die zuverlässigste Discovery gelingt mit einem Manifest im Repository-Root:
|
||
|
||
```json
|
||
{
|
||
"name": "Mein NetBox Plugin",
|
||
"summary": "Kurze Beschreibung",
|
||
"package_name": "netbox-mein-plugin",
|
||
"import_name": "netbox_mein_plugin",
|
||
"version": "1.2.3",
|
||
"min_netbox_version": "4.6.5",
|
||
"max_netbox_version": "4.6.8"
|
||
}
|
||
```
|
||
|
||
Alternativ werden PEP-621/Poetry/Setuptools-Metadaten, Entry Points, `setup.py` und `PluginConfig` ausgewertet. Dynamische Setuptools-Versionen über `tool.setuptools.dynamic.version.attr` werden aus dem am gleichen Commit gepinnten Modul gelesen. Bleiben Paket, Top-Level-Import oder Kompatibilitätsgrenzen unbekannt, blockiert die Admin-Seite die Freigabe mit einer klaren Meldung. Dort können die Felder korrigiert werden; diese Overrides bleiben bei späteren Syncs erhalten.
|
||
|
||
## 4. Ein Wheel veröffentlichen und freigeben
|
||
|
||
Die aktuell eingelesenen MrBlake-Repositories besitzen überwiegend keine Forgejo-Release-Wheels. Sie können nach Plugin-Freigabe im Store erscheinen, sind aber nicht automatisch installierbar.
|
||
|
||
Empfohlener Release-Ablauf im Plugin-Repository:
|
||
|
||
```bash
|
||
python -m pip install --upgrade build
|
||
python -m build
|
||
```
|
||
|
||
1. Einen unveränderlichen Git-Tag setzen.
|
||
2. In Forgejo einen Release für diesen Tag erstellen.
|
||
3. Das erzeugte `dist/*.whl` als Release-Asset hochladen.
|
||
4. Den Store synchronisieren.
|
||
5. Im Admin zunächst fehlende Plugin-Metadaten korrigieren/freigeben und anschließend genau das neue Release-Artefakt freigeben.
|
||
|
||
Beim Sync lädt der Store das Wheel tatsächlich, berechnet SHA-256 und `artifact_size` und speichert den Commit-SHA. Neue Releases erben niemals die Plugin-Freigabe. Ändern sich URL, Hash, Größe, Commit, Paket-/Importname oder Kompatibilitätsgrenzen, werden Plugin bzw. Release wieder `pending`. Entfernte Upstream-Releases werden als zurückgezogen markiert und verschwinden sofort aus dem öffentlichen Katalog.
|
||
|
||
Ein Git-Tag oder Quellcode-Archiv allein erzeugt kein installierbares Release. Der Store erzeugt keine Ersatzartefakte; NetBox-Client und Host-Agent installieren ausschließlich separat veröffentlichte und freigegebene Wheels.
|
||
|
||
## 5. Moderationsablauf
|
||
|
||
Unter `/admin` stehen ausschließlich POST-Aktionen mit CSRF-Schutz bereit:
|
||
|
||
1. Source prüfen und freigeben;
|
||
2. Source synchronisieren;
|
||
3. Plugin-Metadaten prüfen/korrigieren und Plugin freigeben;
|
||
4. Wheel-Hash, Größe, Version und Kompatibilität prüfen und Release separat freigeben.
|
||
|
||
Der Store erzeugt beim Release-Approval einen kanonischen `approved_payload_sha256`. Die öffentliche API liefert ein Artefakt nur, wenn der aktuelle Payload weiterhin exakt zu diesem Approval-Hash passt. Das ist unabhängig von den sichtbaren Statusfeldern eine zusätzliche Defense-in-Depth-Prüfung.
|
||
|
||
## 6. API v1
|
||
|
||
Alle Routen tolerieren einen abschließenden Slash:
|
||
|
||
```text
|
||
GET /api/v1/plugins/
|
||
GET /api/v1/plugins/:slug/
|
||
GET /api/v1/plugins/:slug/releases/:version/
|
||
```
|
||
|
||
Die Liste ist paginiert (`page`, `page_size`) und unterstützt `q`, `source` und `netbox_version`. Sie enthält ausschließlich aktive, vollständig validierte und freigegebene Source-/Plugin-Ketten.
|
||
|
||
Plugin-Detail (gekürzt auf ein Release):
|
||
|
||
```json
|
||
{
|
||
"api_version": "v1",
|
||
"slug": "demo-plugin",
|
||
"name": "Demo Plugin",
|
||
"summary": "Kurzbeschreibung",
|
||
"description": "Beschreibung",
|
||
"repository_url": "https://git.example.com/team/demo",
|
||
"latest_version": "1.2.3",
|
||
"package_name": "netbox-demo",
|
||
"import_name": "netbox_demo",
|
||
"min_netbox_version": "4.6.5",
|
||
"max_netbox_version": "4.6.8",
|
||
"approved": true,
|
||
"status": "approved",
|
||
"releases": [
|
||
{
|
||
"version": "1.2.3",
|
||
"download_url": "https://git.example.com/assets/netbox_demo-1.2.3-py3-none-any.whl",
|
||
"sha256": "<64-hex>",
|
||
"artifact_size": 12345,
|
||
"commit_sha": "<git-sha>",
|
||
"min_netbox_version": "4.6.5",
|
||
"max_netbox_version": "4.6.8",
|
||
"published_at": "2026-08-20T10:00:00+00:00",
|
||
"approved": true,
|
||
"status": "approved",
|
||
"immutable": true,
|
||
"approved_payload_sha256": "<64-hex>"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Die Release-Detailroute liefert exakt das flache Release-Objekt aus `releases[]`, ohne interne Admin-ID. Ohne freigegebenes Wheel ist `releases` leer und `latest_version` `null`. Ein Plugin-Detail enthält höchstens 1.000 Releases. `commit_sha` ist entweder leer oder exakt 40 kleingeschriebene Hex-Zeichen. Vor der Freigabe validiert der Store außerdem den Wheel-Dateinamen und verlangt, dass dessen normalisierte Distribution und Version exakt zu `package_name` und `version` passen; damit scheitert ein Artefakt nicht erst später im Host-Agent.
|
||
|
||
## 7. Netzwerk- und SSRF-Schutz
|
||
|
||
Ausgehende Ziele müssen credential-freies HTTPS verwenden und in `STORE_ALLOWED_SOURCE_HOSTS` stehen. Jeder DNS-A/AAAA-Wert wird geprüft; Loopback, private, Link-Local- und reservierte Adressen sind standardmäßig gesperrt. cURL wird an die geprüfte Adresse gepinnt, Redirects werden einzeln erneut aufgelöst/geprüft, und Provider-Tokens bleiben auf der exakten API-Origin.
|
||
|
||
```dotenv
|
||
STORE_ALLOWED_SOURCE_HOSTS=git.mrblake.cc,github.com,api.github.com,*.github.com,*.githubusercontent.com
|
||
STORE_ALLOW_PRIVATE_NETWORKS=false
|
||
```
|
||
|
||
Für ein bewusst internes Forgejo muss dessen Host explizit allowgelistet und `STORE_ALLOW_PRIVATE_NETWORKS=true` gesetzt werden. Diese Ausnahme erweitert den SSRF-Radius und sollte nur in einem kontrollierten Netz verwendet werden.
|
||
|
||
`STORE_TRUST_PROXY=true` darf nur genutzt werden, wenn direkter Zugriff auf Apache per Firewall ausgeschlossen ist. Zusätzlich werden Forwarded-IPs nur von exakt gelisteten Peers akzeptiert:
|
||
|
||
```dotenv
|
||
STORE_TRUST_PROXY=true
|
||
STORE_TRUSTED_PROXY_IPS=10.20.0.10,2001:db8::10
|
||
```
|
||
|
||
## 8. Optionale Docker-Installation
|
||
|
||
Das Image enthält Apache und PHP 8.4; ein Proxy ist nicht erforderlich. Der Scheduler läuft im Container als `www-data`, damit Webprozess und Watcher dieselben geschützten JSON-Dateien lesen können.
|
||
|
||
```bash
|
||
cd store
|
||
docker build -t netbox-plugin-store .
|
||
docker volume create netbox-plugin-store-data
|
||
docker run -d --name netbox-plugin-store \
|
||
-p 3000:80 \
|
||
--env-file .env \
|
||
-e STORE_PUBLIC_URL=http://localhost:3000 \
|
||
-e STORE_COOKIE_SECURE=false \
|
||
-e STORE_JSON_PATH=/var/www/html/data/store.json \
|
||
-e STORE_SCHEDULER_ENABLED=true \
|
||
-v netbox-plugin-store-data:/var/www/html/data \
|
||
netbox-plugin-store
|
||
```
|
||
|
||
Der Healthcheck ist `GET /healthz`. Für Produktion HTTPS konfigurieren und `STORE_PUBLIC_URL`/Cookie-Einstellung entsprechend setzen.
|
||
|
||
## 9. Update, Backup und Diagnose
|
||
|
||
Native Aktualisierung:
|
||
|
||
```bash
|
||
cd /opt/netbox-plugin-store
|
||
git pull --ff-only
|
||
cd store
|
||
composer install --no-dev --no-interaction --prefer-dist --classmap-authoritative
|
||
sudo systemctl reload apache2
|
||
sudo -u www-data php bin/console sync
|
||
```
|
||
|
||
Vor Updates den JSON-Datastore oder MariaDB sichern. Der JSON-Store darf nur kopiert werden, während kein Schreibvorgang läuft; am einfachsten Apache/Timer kurz stoppen oder ein konsistentes dateisystemseitiges Backup-Verfahren verwenden.
|
||
|
||
Nützliche Prüfungen:
|
||
|
||
```bash
|
||
php -l public/index.php
|
||
composer validate --strict
|
||
composer test
|
||
curl -fsS http://127.0.0.1/healthz
|
||
curl -fsS http://127.0.0.1/api/v1/plugins/
|
||
```
|
||
|
||
Sync-Ergebnisse stehen im Admin-Dashboard und werden als JSON vom CLI ausgegeben. Einzelne Repository-Fehler führen zu einem `partial`-Lauf und archivieren den letzten bekannten Eintrag nicht; eine erfolgreich festgestellte Nicht-Kandidatur oder ein erfolgreich festgestellter Release-Rückzug wird dagegen fail-closed aus dem öffentlichen Katalog entfernt.
|
||
|
||
## Tests
|
||
|
||
Der zero-dependency Runner nutzt PHPs Laufzeit direkt; die Anwendungsabhängigkeiten müssen per Composer installiert sein:
|
||
|
||
```bash
|
||
composer install
|
||
composer test
|
||
```
|
||
|
||
Abgedeckt sind unter anderem API-Feldvertrag und 1.000er-Grenze, Approval-Payload, Wheel-Distribution/-Version/-Dateiname, 40-stellige Commit-SHAs, strikte Versions-/Metadatenvalidierung, README-Sanitizing und Commit-Pinning, Origin-Token-Isolation, private GitHub-Assets, private-IP-SSRF, atomare JSON-Grenzen und exklusive Leases, aggregierte Sync-Budgets, Trusted-Proxy-Auswertung, sticky Admin-Overrides, Rehash bei ersetzten Assets, Withdrawal und Nicht-Kandidaten-Archivierung.
|