Files
Netbox-Store/store
MrBlake eb6be0e5fa
CI / php-store (push) Waiting to run
CI / python-components (push) Waiting to run
Fix host agent socket activation
2026-08-24 23:13:10 +02:00
..
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 23:13:10 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00
2026-08-24 20:51:25 +02:00

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):

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

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:

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:

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:

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:

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 an:

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

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:

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:

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:

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):

*/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.

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:

{
  "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:

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:

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):

{
  "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.

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:

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.

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:

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:

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:

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.