Files
Netbox-Store/README.md
T
MrBlake 26aea40e6a
CI / php-store (push) Waiting to run
CI / python-components (push) Waiting to run
feat: install plugins from approved source commits
2026-08-24 21:37:17 +02:00

320 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
<VirtualHost *:80>
ServerName plugins.example.internal
DocumentRoot /var/www/netbox-plugin-store/store/public
<Directory /var/www/netbox-plugin-store/store/public>
Options -Indexes
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/netbox-plugin-store-error.log
CustomLog ${APACHE_LOG_DIR}/netbox-plugin-store-access.log combined
</VirtualHost>
```
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.54.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
```