317 lines
12 KiB
Markdown
317 lines
12 KiB
Markdown
# ENV Controller
|
|
|
|
Eigenständige Webanwendung zum Vergleichen, Bearbeiten, Sichern und Wiederherstellen von `.env`-Dateien. Nach einer Änderung können ausschließlich freigegebene Docker-Container kontrolliert neu gestartet oder über Docker Compose neu erstellt werden.
|
|
|
|
## Kernfunktionen
|
|
|
|
- liest eine bestehende `.env` und die zugehörige `.env.example`
|
|
- zeigt fehlende und zusätzliche Schlüssel an
|
|
- importiert neue Schlüssel aus `.env.example`, ohne bestehende Werte zu überschreiben
|
|
- erhält Kommentare, Reihenfolge und zusätzliche lokale Schlüssel weitgehend unverändert
|
|
- erstellt **vor jedem Import, Speichern und Restore** automatisch ein Backup
|
|
- legt Backup-Dateien und Metadaten mit Dateimodus `0600` ab
|
|
- ermöglicht Download und Wiederherstellung früherer Stände
|
|
- lädt Secret-Werte erst beim Fokus des Feldes vom Server
|
|
- erlaubt Docker-Aktionen nur für eine feste Allowlist exakter `container_name`-Werte
|
|
- unterstützt `restart` und Compose-basiertes `recreate`
|
|
- kann abhängige Dritt-Container aus anderen Compose-Projekten mit derselben Änderung neu erstellen
|
|
- kann fehlende Schlüssel optional beim Controller-Start automatisch importieren
|
|
|
|
## Plattformkompatibilität
|
|
|
|
Der Controller ist für den produktiven Betrieb als Linux-Container vorgesehen. Der Quellcode lässt sich zusätzlich unter Windows entwickeln und kompilieren. Unix-Dateibesitzrechte (UID/GID) werden nur auf Unix-Systemen übernommen; unter Windows wird dieser nicht verfügbare Schritt übersprungen.
|
|
|
|
|
|
## Direkter Start unter Windows
|
|
|
|
Beim direkten Start der EXE wird die Konfiguration in dieser Reihenfolge gesucht:
|
|
|
|
1. Pfad aus `-config`
|
|
2. Pfad aus `ENV_CONTROLLER_CONFIG`
|
|
3. `controller.json` im aktuellen Arbeitsverzeichnis
|
|
4. `controller.json` neben der EXE
|
|
5. Container-Standard `/config/controller.json`
|
|
|
|
Erstelle zunächst eine lokale Konfiguration:
|
|
|
|
```powershell
|
|
Copy-Item .\controller.windows.example.json .\controller.json
|
|
```
|
|
|
|
Die mitgelieferte `controller.windows.example.json` verwendet bereits eine Passwort-Umgebungsvariable. Bei einer Übernahme aus `controller.example.json` müssen diese Felder so gesetzt werden:
|
|
|
|
```json
|
|
{
|
|
"password_env": "ENV_CONTROLLER_PASSWORD",
|
|
"password_file": ""
|
|
}
|
|
```
|
|
|
|
Setze anschließend das Passwort und starte die Anwendung:
|
|
|
|
```powershell
|
|
$env:ENV_CONTROLLER_PASSWORD = "ein-langes-zufaelliges-passwort"
|
|
.\dist\env-controller-windows-amd64.exe -config .\controller.json
|
|
```
|
|
|
|
Alternativ kann der Pfad dauerhaft über die Umgebung gesetzt werden:
|
|
|
|
```powershell
|
|
$env:ENV_CONTROLLER_CONFIG = "E:\GoProjects\glpi-ai-agent-controller\controller.json"
|
|
.\dist\env-controller-windows-amd64.exe
|
|
```
|
|
|
|
Die Projektpfade in `controller.json` müssen unter Windows als absolute Pfade angegeben werden, beispielsweise `E:/GoProjects/glpi-ai-agent/.env`. Der produktive Docker-Betrieb verwendet weiterhin `/config/controller.json` und das Compose-Secret.
|
|
|
|
## Architektur
|
|
|
|
```text
|
|
Browser
|
|
│ Basic Auth + CSRF
|
|
▼
|
|
ENV Controller
|
|
├── liest/schreibt /opt/.../.env atomar
|
|
├── vergleicht /opt/.../.env.example
|
|
├── sichert nach /backups/<projekt>
|
|
└── Docker CLI
|
|
├── docker restart <allowlisted-name>
|
|
└── docker compose ... up -d --no-deps --force-recreate <service>
|
|
```
|
|
|
|
Das Schreiben der `.env` verändert die bereits gestartete Prozessumgebung eines Containers nicht. Erst ein Compose-Recreate übernimmt die neue Konfiguration. Ein normaler Restart ist nur für Anwendungen geeignet, die ihre Konfiguration selbst aus einer gemounteten Datei neu einlesen.
|
|
|
|
## Schnellstart
|
|
|
|
```bash
|
|
cp controller.password.example controller.password
|
|
cp controller.example.json controller.json
|
|
mkdir -p backups
|
|
```
|
|
|
|
Danach anpassen:
|
|
|
|
1. den zufälligen Inhalt von `controller.password`
|
|
2. Host-Pfade in `compose.yml`
|
|
3. Projekte und erlaubte Container in `controller.json`
|
|
4. stabile Container-Namen über ein Compose-Override festlegen
|
|
|
|
Start:
|
|
|
|
```bash
|
|
docker compose -f compose.yml up -d --build
|
|
```
|
|
|
|
Aufruf standardmäßig:
|
|
|
|
```text
|
|
http://127.0.0.1:8090
|
|
```
|
|
|
|
Benutzername ist im Beispiel `admin`; das Passwort wird als Compose-Secret aus `controller.password` eingebunden. Alternativ unterstützt die Anwendung weiterhin `password_env` in `controller.json`.
|
|
|
|
## Stabile Container-Namen
|
|
|
|
Der Controller arbeitet absichtlich mit exakten Namen. Im verwalteten Projekt kann dafür ein Override wie `examples/compose.controller-names.yml` verwendet werden:
|
|
|
|
```yaml
|
|
services:
|
|
agent:
|
|
container_name: glpi-ai-agent
|
|
ollama:
|
|
container_name: glpi-ai-ollama
|
|
```
|
|
|
|
Das Projekt muss anschließend mit beiden Dateien erstellt werden:
|
|
|
|
```bash
|
|
docker compose \
|
|
-f docker-compose.yml \
|
|
-f compose.controller-names.yml \
|
|
up -d
|
|
```
|
|
|
|
Dieselben Compose-Dateien müssen beim Ziel in `controller.json` angegeben sein, damit ein Recreate die identische Definition verwendet.
|
|
|
|
## Projektkonfiguration
|
|
|
|
Ein Projekt verwaltet genau eine `.env` samt `.env.example` und Backup-Verzeichnis:
|
|
|
|
```json
|
|
{
|
|
"id": "glpi-ai-stack",
|
|
"title": "GLPI AI Stack",
|
|
"env_file": "/opt/glpi-ai-stack/.env",
|
|
"example_file": "/opt/glpi-ai-stack/.env.example",
|
|
"backup_dir": "/backups/glpi-ai-stack",
|
|
"targets": []
|
|
}
|
|
```
|
|
|
|
Alle drei Pfade müssen im Container absolut sein. Die `.env` und das Backup-Verzeichnis benötigen Schreibzugriff; `.env.example` darf read-only gemountet werden, sofern sie separat eingebunden wird.
|
|
|
|
### Automatischer Import
|
|
|
|
```json
|
|
"auto_import_missing": true
|
|
```
|
|
|
|
Beim Start werden fehlende Schlüssel automatisch importiert. Auch dabei wird vorher ein Backup erstellt. Standardmäßig ist die Funktion deaktiviert, damit neue Vorlagenwerte zunächst geprüft werden können.
|
|
|
|
## Container-Ziele
|
|
|
|
### Compose-Recreate
|
|
|
|
```json
|
|
{
|
|
"container_name": "glpi-ai-agent",
|
|
"display_name": "GLPI AI Agent",
|
|
"allowed_actions": ["restart", "recreate"],
|
|
"default_action": "recreate",
|
|
"apply_by_default": true,
|
|
"project_dir": "/opt/glpi-ai-stack",
|
|
"compose_files": [
|
|
"/opt/glpi-ai-stack/docker-compose.yml",
|
|
"/opt/glpi-ai-stack/compose.controller-names.yml"
|
|
],
|
|
"compose_service": "agent",
|
|
"compose_project": "glpi-ai-stack",
|
|
"env_file": "/opt/glpi-ai-stack/.env"
|
|
}
|
|
```
|
|
|
|
Ausgeführt wird sinngemäß:
|
|
|
|
```bash
|
|
docker compose \
|
|
--env-file /opt/glpi-ai-stack/.env \
|
|
--project-directory /opt/glpi-ai-stack \
|
|
-p glpi-ai-stack \
|
|
-f /opt/glpi-ai-stack/docker-compose.yml \
|
|
-f /opt/glpi-ai-stack/compose.controller-names.yml \
|
|
up -d --no-deps --force-recreate agent
|
|
```
|
|
|
|
`--no-deps` verhindert, dass Compose eigenständig weitere Dienste verändert. Gewünschte abhängige Dienste werden als separate, explizite Ziele eingetragen und in der Oberfläche ausgewählt.
|
|
|
|
### Einfacher Restart
|
|
|
|
Für einen nicht von Compose verwalteten Container oder eine Anwendung, die eine gemountete Datei bei Restart selbst liest:
|
|
|
|
```json
|
|
{
|
|
"container_name": "legacy-service",
|
|
"display_name": "Legacy Service",
|
|
"allowed_actions": ["restart"],
|
|
"default_action": "restart",
|
|
"apply_by_default": false
|
|
}
|
|
```
|
|
|
|
### Dritt-Container aus einem anderen Compose-Projekt
|
|
|
|
Ein Ziel kann ein anderes `project_dir`, andere Compose-Dateien und einen anderen Service besitzen. Mit `env_file` kann dennoch die gerade verwaltete gemeinsame `.env` an Compose übergeben werden:
|
|
|
|
```json
|
|
{
|
|
"container_name": "glpi-kb-search",
|
|
"allowed_actions": ["recreate"],
|
|
"default_action": "recreate",
|
|
"apply_by_default": true,
|
|
"project_dir": "/opt/glpi-kb-stack",
|
|
"compose_files": ["/opt/glpi-kb-stack/compose.yml"],
|
|
"compose_service": "kb-search",
|
|
"compose_project": "glpi-kb-stack",
|
|
"env_file": "/opt/glpi-ai-stack/.env"
|
|
}
|
|
```
|
|
|
|
Damit lassen sich mehrere von gemeinsamen Einstellungen abhängige Anwendungen gezielt nacheinander aktualisieren.
|
|
|
|
## Automatische Compose-Erkennung
|
|
|
|
Fehlen die expliziten Felder `project_dir`, `compose_files` oder `compose_service`, versucht der Controller diese Werte aus folgenden Compose-Labels des laufenden Containers zu lesen:
|
|
|
|
- `com.docker.compose.project.working_dir`
|
|
- `com.docker.compose.project.config_files`
|
|
- `com.docker.compose.project`
|
|
- `com.docker.compose.service`
|
|
|
|
Explizite Angaben sind robuster. Bei automatischer Erkennung müssen die im Label genannten Host-Pfade unter exakt demselben Pfad in den Controller gemountet sein.
|
|
|
|
## Backup-Verhalten
|
|
|
|
Vor jeder Mutation wird der aktuell gültige Stand gesichert:
|
|
|
|
- `edit` vor dem Speichern
|
|
- `import` vor dem Import neuer Schlüssel
|
|
- `pre_restore` vor einer Wiederherstellung
|
|
- `import` beim automatischen Startimport
|
|
|
|
Beispiel:
|
|
|
|
```text
|
|
20260801T211500.123456789Z_edit_f2c9148c7a11.env
|
|
20260801T211500.123456789Z_edit_f2c9148c7a11.env.json
|
|
```
|
|
|
|
Die JSON-Metadaten enthalten Zeitpunkt, Grund, Benutzer, Größe und SHA-256. Zusätzlich wird `audit.jsonl` fortgeschrieben. `max_backups` begrenzt die Anzahl der aufbewahrten `.env`-Revisionen je Projekt.
|
|
|
|
Das Wiederherstellen erzeugt zuerst ein neues `pre_restore`-Backup des aktuellen Standes. Dadurch kann auch ein versehentlicher Restore unmittelbar rückgängig gemacht werden.
|
|
|
|
## Secret-Felder
|
|
|
|
Ein Schlüssel gilt als geheim, wenn sein Name beispielsweise eines dieser Muster enthält:
|
|
|
|
- `PASSWORD`
|
|
- `PASSWD`
|
|
- `SECRET`
|
|
- `TOKEN`
|
|
- `API_KEY`
|
|
- `PRIVATE_KEY`
|
|
- `CREDENTIAL`
|
|
|
|
Der Klartext wird nicht in die initiale HTML-Seite eingebettet. Beim Fokus sendet der Browser einen authentifizierten, CSRF-geschützten Request. Beim Blur wird das Feld wieder als Passwortfeld dargestellt. Der Klartext befindet sich nach dem Öffnen technisch im Browserprozess; die Weboberfläche darf deshalb ausschließlich über ein vertrauenswürdiges Netz und vorzugsweise TLS erreichbar sein.
|
|
|
|
## Sicherheitsmodell
|
|
|
|
Der Docker-Socket verleiht dem Controller sehr weitreichende Rechte auf dem Docker-Host. Die Anwendung reduziert die Angriffsfläche, indem sie:
|
|
|
|
- keine frei eingegebenen Container-Namen akzeptiert
|
|
- keine frei eingegebenen Shell-Befehle ausführt
|
|
- Aktionen und Namen ausschließlich aus `controller.json` übernimmt
|
|
- nur `restart` und einen fest aufgebauten Compose-Recreate unterstützt
|
|
- Basic Auth und CSRF-Schutz verwendet
|
|
- Sicherheitsheader setzt
|
|
- Schreibvorgänge atomar ausführt
|
|
- als read-only Container mit `cap_drop: ALL` und `no-new-privileges` läuft
|
|
|
|
Der Socket bleibt trotzdem eine hochprivilegierte Schnittstelle. Die Oberfläche sollte nur an `127.0.0.1` oder ein internes Verwaltungsnetz gebunden und über einen TLS-Reverse-Proxy mit zusätzlicher Zugriffskontrolle veröffentlicht werden.
|
|
|
|
## Dateibesitz und Rechte
|
|
|
|
Beim atomaren Ersetzen versucht der Controller Modus, UID und GID der ursprünglichen `.env` beizubehalten. Backups werden mit `0600` angelegt. Der Controller benötigt Schreibrechte auf:
|
|
|
|
- das Verzeichnis der `.env`
|
|
- alle `backup_dir`-Verzeichnisse
|
|
|
|
Für den Docker-Socket läuft das Beispiel als root im Container. Dies bedeutet nicht, dass der Socket dadurch weniger privilegiert wäre; der Zugriff auf den Daemon selbst ist bereits die entscheidende Berechtigung.
|
|
|
|
## Verhalten bei Fehlern
|
|
|
|
- Schlägt das Backup fehl, wird die `.env` nicht verändert.
|
|
- Schlägt das atomare Schreiben fehl, bleibt die vorherige Datei bestehen.
|
|
- Schlägt ein Recreate nach erfolgreichem Speichern fehl, bleibt die neue `.env` gespeichert und die Oberfläche zeigt den Fehler je Ziel an. Das vorherige Backup kann wiederhergestellt werden.
|
|
- Nicht allowlistete Namen oder Aktionen werden abgelehnt.
|
|
- Ein Recreate ohne vollständige Compose-Metadaten wird abgelehnt, statt einen Container improvisiert aus `docker inspect` nachzubauen.
|
|
|
|
## Entwicklung
|
|
|
|
```bash
|
|
make test
|
|
make vet
|
|
make race
|
|
make build
|
|
```
|
|
|
|
Das Projekt verwendet ausschließlich die Go-Standardbibliothek. Für Container-Aktionen wird die Docker CLI mit Compose-Plugin im Image verwendet.
|