# MrBlake NetBox Plugin Store Dieses Repository besteht aus drei getrennten Komponenten: | Pfad | Aufgabe | | --- | --- | | `store/` | Eigenständiger PHP-Store, direkt durch Apache ausgeliefert. Er synchronisiert Forgejo-/GitHub-Repositories, rendert README-Dateien und stellt den kuratierten API-Katalog bereit. | | `netbox_plugin/` | NetBox-Oberfläche für Katalog, Installation und Plugin-Lebenszyklus. Unterstützt NetBox 4.6.5 bis 4.6.8. | | `host_agent/` | Kleine privilegierte Linux-Komponente, die freigegebene Artefakte prüft, Source-Archive lokal als Wheel baut und die Änderungen am NetBox-Host ausführt. | Der Store verwendet standardmäßig eine lokale JSON-Datei. MariaDB ist optional. Die konfigurierte Standardquelle gilt als vom Betreiber freigegeben; weitere Quellen werden im Admin-Bereich zunächst ausstehend angelegt. Automatisch erkannte Plugins und Releases erscheinen erst nach einer ausdrücklichen Freigabe im öffentlichen Katalog. ## Native Installation mit Apache und JSON Docker ist nicht erforderlich. Der primäre Betriebsweg ist eine normale PHP-Anwendung unter Apache. Benötigt werden: - PHP 8.3 oder neuer mit CLI und Apache-Modul/FPM (getestet mit PHP 8.4); - die PHP-Erweiterungen cURL, DOM/XML, intl, JSON, mbstring und PDO; für MariaDB zusätzlich `pdo_mysql`; - Composer 2; - Apache 2.4 mit `mod_rewrite`; `mod_headers` wird für die zusätzlichen Header aus `.htaccess` empfohlen. Unter Debian/Ubuntu sind die wesentlichen Pakete beispielsweise: ```text sudo apt install apache2 composer libapache2-mod-php8.3 php8.3-cli \ php8.3-curl php8.3-intl php8.3-mbstring php8.3-mysql php8.3-xml sudo a2enmod rewrite headers expires ``` Die Paketnamen anderer Distributionen unterscheiden sich. Bei PHP-FPM muss Apache stattdessen mit dem passenden FPM-Handler konfiguriert werden. ### Anwendung und Konfiguration Dieses Beispiel verwendet `/var/www/netbox-plugin-store` als Repository-Pfad. Nur `store/public` darf durch Apache veröffentlicht werden. Das Repository kann zunächst unter einem normalen Deployment-Benutzer ausgecheckt und danach an diesen Pfad verschoben werden: ```text git clone https://git.mrblake.cc/MrBlake/Netbox-Store.git netbox-plugin-store sudo mv netbox-plugin-store /var/www/netbox-plugin-store sudo chown -R "$(id -un):$(id -gn)" /var/www/netbox-plugin-store cd /var/www/netbox-plugin-store/store composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader cp .env.example .env ``` Zuerst werden ein eigenes Admin-Passwort und ein unabhängiges Session-Geheimnis erzeugt: ```text php bin/console hash-password 'EIN-LANGES-ZUFÄLLIGES-PASSWORT' php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;' ``` Das Klartextpasswort sollte in der Praxis aus einer temporären, nicht protokollierten Shell-Variablen übergeben werden. In `store/.env` werden mindestens die folgenden Werte ersetzt. Der Argon2id-Hash steht in einfachen Anführungszeichen, damit seine `$`-Zeichen unverändert bleiben: ```dotenv APP_ENV=production STORE_PUBLIC_URL=https://plugins.example.internal STORE_COOKIE_SECURE=true STORE_ADMIN_USERNAME=mein-admin STORE_ADMIN_PASSWORD_HASH='$argon2id$...' STORE_SESSION_SECRET=... STORE_DB_DRIVER=json STORE_JSON_PATH=/var/www/netbox-plugin-store/store/data/store.json ``` Bei einem reinen HTTP-Testsystem müssen URL und `STORE_COOKIE_SECURE=false` zusammenpassen. Ein extern erreichbarer Produktivbetrieb sollte TLS direkt in Apache terminieren. Der Anwendungscode bleibt root-owned; nur `.env` ist für die Apache-Gruppe lesbar und nur `store/data` ist für den Web-/Scheduler-Benutzer schreibbar: ```text sudo chown -R root:root /var/www/netbox-plugin-store sudo chown root:www-data /var/www/netbox-plugin-store/store/.env sudo chmod 0640 /var/www/netbox-plugin-store/store/.env sudo install -d -o www-data -g www-data -m 0750 \ /var/www/netbox-plugin-store/store/data sudo chown -R www-data:www-data /var/www/netbox-plugin-store/store/data ``` ### Apache VirtualHost Der VirtualHost zeigt direkt auf `store/public`. `AllowOverride All` ist erforderlich, damit die mitgelieferte `.htaccess` das Front-Controller-Rewrite aktivieren kann: ```apache ServerName plugins.example.internal DocumentRoot /var/www/netbox-plugin-store/store/public Options -Indexes AllowOverride All Require all granted ErrorLog ${APACHE_LOG_DIR}/netbox-plugin-store-error.log CustomLog ${APACHE_LOG_DIR}/netbox-plugin-store-access.log combined ``` Die Konfiguration wird zum Beispiel als `/etc/apache2/sites-available/netbox-plugin-store.conf` gespeichert und aktiviert: ```text sudo a2ensite netbox-plugin-store.conf sudo apache2ctl configtest sudo systemctl reload apache2 ``` Für Produktion wird derselbe DocumentRoot in einem TLS-VirtualHost verwendet. Weder das Repository-Wurzelverzeichnis noch `store/` selbst dürfen als DocumentRoot dienen, da dort Konfiguration und Laufzeitdaten liegen. ### Initialisierung und erster Import CLI und Apache müssen den JSON-Zustand mit derselben UID verwalten. Deshalb laufen Bootstrap und Sync als Apache-Benutzer: ```text cd /var/www/netbox-plugin-store/store sudo -u www-data php bin/console bootstrap sudo -u www-data php bin/console sync ``` Danach sind Website und `/healthz` erreichbar. Die Admin-Anmeldung liegt unter `/admin`. Importierte Kandidaten bleiben bis zur manuellen Prüfung und Freigabe unsichtbar für den öffentlichen API-Katalog. ### Regelmäßige Synchronisierung Bevorzugt läuft der Watch-Modus als eigener systemd-Dienst. Beispiel für `/etc/systemd/system/netbox-plugin-store-sync.service`: ```ini [Unit] Description=NetBox Plugin Store repository sync After=network-online.target apache2.service Wants=network-online.target [Service] Type=simple User=www-data Group=www-data WorkingDirectory=/var/www/netbox-plugin-store/store ExecStart=/usr/bin/php /var/www/netbox-plugin-store/store/bin/console sync --watch --interval=900 Restart=on-failure RestartSec=10 UMask=0077 NoNewPrivileges=true PrivateTmp=true ProtectHome=true ProtectSystem=strict ReadWritePaths=/var/www/netbox-plugin-store/store/data [Install] WantedBy=multi-user.target ``` ```text sudo systemctl daemon-reload sudo systemctl enable --now netbox-plugin-store-sync.service sudo systemctl status netbox-plugin-store-sync.service ``` Alternativ kann `www-data` den einmaligen Befehl per Cron aufrufen. Dazu `sudo crontab -u www-data -e` öffnen und die folgende Zeile eintragen. `flock` verhindert überlappende Läufe: ```cron */15 * * * * /usr/bin/flock -n /var/www/netbox-plugin-store/store/data/sync-cron.lock /bin/sh -c 'cd /var/www/netbox-plugin-store/store && /usr/bin/php bin/console sync' 2>&1 | /usr/bin/logger -t netbox-plugin-store-sync ``` Der Store selbst weist zusätzlich jeden parallelen Sync-Lauf ab. Pro Lauf gelten standardmäßig 900 Sekunden, 2.500 HTTP-Requests, 1 GiB Downloadvolumen, 2.000 Repositories und 1.000 Releases als gemeinsame Obergrenzen. Diese Werte lassen sich mit `STORE_SYNC_MAX_SECONDS`, `STORE_SYNC_MAX_REQUESTS`, `STORE_SYNC_MAX_BYTES`, `STORE_SYNC_MAX_REPOSITORIES` und `STORE_SYNC_MAX_RELEASES` in `store/.env` an die eigene Instanz anpassen. ### Native MariaDB-Option JSON und MariaDB sind alternative Zustands-Speicher. Ein Wechsel des Treibers migriert bestehende Freigaben nicht automatisch. Nach dem Anlegen einer eigenen Datenbank und eines eingeschränkten Datenbankbenutzers werden diese Werte in `store/.env` gesetzt: ```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=EIN-EIGENES-ZUFÄLLIGES-PASSWORT ``` Die erforderliche Tabelle wird bei Bootstrap, Sync oder dem ersten Web-Aufruf angelegt. Das Datenbankkonto benötigt dafür Rechte auf genau diese Datenbank. `STORE_JSON_PATH` wird im MariaDB-Modus nicht verwendet. ## Optional: Docker Compose Alternativ kann derselbe Apache/PHP-Store in einem Container laufen. Der Standard-Stack bindet seinen Port absichtlich nur an localhost und verwendet ein benanntes JSON-Volume: ```text cp .env.example .env docker compose build store ``` Unter Windows kann statt `cp` der Befehl `copy .env.example .env` verwendet werden. Vor dem Start werden in der Root-`.env` eigene Admin-Zugangsdaten gesetzt. Ohne vollständige Admin-Konfiguration bleibt die Anmeldung deaktiviert; es gibt kein Standardpasswort. Hash und Session-Geheimnis lassen sich mit dem gebauten Image erzeugen: ```text docker compose run --rm --no-deps -e STORE_SCHEDULER_ENABLED=false store php bin/console hash-password 'EIN-LANGES-ZUFÄLLIGES-PASSWORT' docker compose run --rm --no-deps -e STORE_SCHEDULER_ENABLED=false store php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;' ``` Danach: ```text docker compose up -d --build ``` Der im Container aktivierte Scheduler führt den ersten Import unmittelbar aus. Mit `docker compose logs -f store` lässt sich der Lauf verfolgen. Für einen ausschließlich manuellen Sync wird vorher `STORE_SCHEDULER_ENABLED=false` gesetzt und anschließend `docker compose exec store php bin/console sync` aufgerufen. Der Store ist standardmäßig auf `http://127.0.0.1:8080` erreichbar. Für MariaDB werden in `.env` mindestens `MARIADB_PASSWORD` und `MARIADB_ROOT_PASSWORD` auf unterschiedliche, zufällige Werte gesetzt und das Overlay zugeschaltet: ```text docker compose -f compose.yaml -f compose.mariadb.yaml up -d --build ``` MariaDB erhält keinen Host-Port und ist nur im internen Compose-Netz erreichbar. Der Store wartet auf den Datenbank-Healthcheck. ## Synchronisierung und Freigabe Der voreingestellte Forgejo-Import liest Repositories von `https://git.mrblake.cc`. GitHub ist bereits als zweiter Provider implementiert und kann im Admin-Bereich über dieselbe Quellenverwaltung hinzugefügt werden. Mit einem Token lassen sich auch Metadaten und README privater GitHub-Repositories synchronisieren. Private Artefakte bleiben in API v1 bewusst nicht installierbar, weil der Host-Agent keine Provider-Zugangsdaten erhält. Öffentliche Projekte benötigen kein Release: Fehlt ein Wheel-Release, erzeugt der Store einen separat freizugebenden Source-Kandidaten für den exakten aktuellen Commit. Der normale Ablauf ist: 1. Der Scheduler oder `php bin/console sync` liest Repository-Metadaten, Manifest/Packaging-Konfiguration, Releases und die README eines festen Commit-SHA ein. 2. Neue oder in installrelevanten Feldern geänderte Datensätze erhalten den Status `pending`. 3. Ein Administrator prüft Quelle, Paket-/Importname, NetBox-Kompatibilität, Artifact-URL, Größe und SHA-256. 4. Quelle, Plugin und Release werden getrennt freigegeben. Eine Freigabe ist keine automatische Folge der Synchronisierung. 5. Nur aktive, vollständige und freigegebene Datensätze erscheinen unter `/api/v1/plugins/`. Die Detailseite wird aus der gespeicherten README erzeugt. Relative Links und Bilder werden auf den synchronisierten Commit aufgelöst; HTML wird vor der Ausgabe bereinigt. Verändert sich ein Artifact oder ein installrelevantes Metadatum, muss das Release erneut geprüft werden. ## NetBox-Plugin installieren Das Plugin unter `netbox_plugin/` ist auf NetBox 4.6.5–4.6.8 begrenzt. Zuerst wird ein Wheel gebaut und in die NetBox-Virtualenv installiert: ```text cd netbox_plugin python -m build /opt/netbox/venv/bin/pip install dist/netbox_plugin_store-0.1.0-py3-none-any.whl ``` Das Paket muss außerdem in `/opt/netbox/local_requirements.txt` festgehalten werden. In `configuration.py` wird es zunächst sicher im Dry-run-Modus eingerichtet: ```python PLUGINS = [ "netbox_plugin_store", ] PLUGINS_CONFIG = { "netbox_plugin_store": { "store_url": "https://plugins.example.internal", "allowed_store_urls": ["https://plugins.example.internal"], "allowed_artifact_urls": [ "https://plugins.example.internal", "https://git.mrblake.cc", ], "execution_mode": "dry_run", }, } ``` Danach wird der übliche NetBox-Upgrade-Ablauf mit Migration, `collectstatic` und Neustart von Web- und RQ-Dienst ausgeführt. Details und alle Einstellungen stehen in [`netbox_plugin/README.md`](netbox_plugin/README.md). ## Produktion: Host-Agent Für echte Installationen ist `execution_mode = "agent"` die vorgesehene Trennung: Der NetBox-Webprozess bleibt unprivilegiert und sendet einen kleinen Auftrag über den Unix-Socket `/run/netbox-store-agent/agent.sock`. Der Agent ruft Plugin und Release erneut aus dem Store ab, vergleicht die Freigabemarkierung und prüft Wheel, Dateigröße und SHA-256 vor jeder Änderung. Kurzablauf auf dem Linux-NetBox-Host: 1. Das Paket aus `host_agent/` in eine administrative Python-Umgebung installieren. 2. `host_agent/examples/agent.toml` nach `/etc/netbox-store-agent/agent.toml` kopieren, alle Hosts, Pfade, UIDs/GIDs und die NetBox-Version anpassen und die Datei root-owned mit Modus `0600` schützen. 3. Die beiden Dateien aus `host_agent/systemd/` nach `/etc/systemd/system/` kopieren. Insbesondere `SocketGroup` muss zur NetBox-Servicegruppe passen. 4. Die vom Agent verwaltete Plugin-Liste einmalig in `configuration.py` einbinden: ```python from store_plugins import STORE_PLUGINS PLUGINS += STORE_PLUGINS ``` 5. Socket aktivieren und zunächst im voreingestellten Dry-run-Modus testen: ```text systemctl daemon-reload systemctl enable --now netbox-store-agent.socket netbox-store-agent capabilities ``` 6. Erst nach erfolgreichen Katalog- und Lifecycle-Tests `dry_run = false` in der root-geschützten Agent-Konfiguration setzen und den Dienst neu starten. Die NetBox-Konfiguration wird dann ergänzt: ```python PLUGINS_CONFIG["netbox_plugin_store"].update({ "execution_mode": "agent", "agent_socket_path": "/run/netbox-store-agent/agent.sock", "agent_timeout": 30, }) ``` Der Agent installiert ausschließlich freigegebene, unveränderliche Artefakte. Source-Archive werden commitgebunden geprüft, offline lokal in ein Wheel gebaut und als gehashte Datei unter `/opt/netbox/.netbox-store-wheels` persistiert. Abhängigkeiten und Build-Backends müssen bereits durch den Betreiber bereitgestellt sein. Vollständige Sicherheits- und Recovery-Hinweise stehen in [`host_agent/README.md`](host_agent/README.md). ## API v1 Der öffentliche, nur freigegebene Katalog stellt diese Endpunkte bereit: ```text GET /api/v1/plugins/ GET /api/v1/plugins/{slug}/ GET /api/v1/plugins/{slug}/releases/{version}/ ``` Ein Plugin enthält unter anderem `package_name`, `import_name`, `min_netbox_version`, `max_netbox_version` und `releases`. Ein installierbares Release enthält `download_url`, Artifact-`sha256`, `artifact_size`, `immutable` und die opake Freigabemarkierung `approved_payload_sha256`. Der genaue Datenfluss und die Vertrauensgrenzen sind in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) beschrieben. ## Lokale Prüfungen Die Komponenten bringen getrennte Prüfungen mit; sie können ohne produktive NetBox- oder Host-Änderungen ausgeführt werden: ```text cd store && composer install && composer test cd host_agent && python -m pip install -e ".[dev]" && pytest cd netbox_plugin && python -m unittest discover -s tests -v ```