320 lines
15 KiB
Markdown
320 lines
15 KiB
Markdown
# 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.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
|
||
```
|