34 KiB
Install.sh - Umfassende Dokumentation
Datei: install.sh (695 Zeilen)
Zweck: Vollautomatische Installation und Konfiguration des Container Spawner Systems
Kompatibilität: Bash, BusyBox (Synology NAS), Docker ≥ 20.10, Docker Compose ≥ 2.0
Übersicht der Phasen
Das Script läuft 11 sequenzielle Phasen durch:
- Startup & Konfiguration - Logging, Farben, Mindestversionen definieren
- .env Prüfung - Konfigurationsdatei vorhanden?
- Voraussetzungen prüfen - Docker, Compose, Git Versionen validieren
- Update vs. Neuinstallation - Git Repository klonen oder aktualisieren
- Verzeichnisse & Rechte - data/, logs/ erstellen, Berechtigungen setzen
- Docker-Netzwerk - Traefik-Netzwerk erstellen/prüfen
- Traefik Prüfung - Ist Traefik-Container laufend?
- Docker Images bauen - User-Templates und Spawner-Images kompilieren
- Container starten - Docker Compose up, Health-Checks
- Fertig-Nachricht - URLs anzeigen, nützliche Befehle listen
Phase 1: Startup & Konfiguration (Zeilen 1-25)
Zweck
Initialisierung des Scripts, Definition globaler Variablen und Logging-Setup.
Zeilen 1-2: Shebang & Error Handling
#!/bin/bash
set -e
#!/bin/bash- Bash-Interpreter aufrufen (nicht sh/dash/ash)set -e- KRITISCH: Beende Script sofort bei erstem Fehler (exit code ≠ 0)- Verhindert, dass fehlerhafte Befehle ignoriert werden und weitere Schritte ausgeführt werden
Zeilen 4-13: Repositoriums- & Installationsvariablen
REPO_URL="https://gitea.iotxs.de/RainerWieland/spawner.git"
RAW_URL="https://gitea.iotxs.de/RainerWieland/spawner/raw/branch/main"
INSTALL_DIR="${PWD}"
VERSION="0.1.0"
LOG_FILE="${INSTALL_DIR}/spawner-install.log"
REPO_URL- Git Repository für klonen/pull (https, kein SSH)RAW_URL- Rohe Datei-Downloads (für .env.example)INSTALL_DIR="${PWD}"- WICHTIG: Installiert im aktuellen Verzeichnis (not hardcoded)VERSION- Script-Version (für Logs)LOG_FILE- Alle Build-Logs werden hier gesammelt
Voraussetzungen:
- Bash >= 4.0
- PWD muss beschreibbar sein (chmod 755 mindestens)
Zeilen 15-20: Farben für Terminal-Output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
- ANSI Escape Codes für farbige Konsolen-Ausgabe
- Hilft bei Fehler/Success-Anzeige
- Kompatibel mit BusyBox (einfache Codes)
Zeilen 22-24: Mindestversionen
MIN_DOCKER_VERSION="20.10"
MIN_COMPOSE_VERSION="2.0"
- Definieren Minimum-Versionen für Voraussetzungs-Check
- Docker < 20.10: Kein Docker Compose v2 Support
- Compose < 2.0: Alte
docker-composeCLI (veraltet)
Voraussetzungen:
- Docker 20.10+ installiert
- Docker Compose 2.0+ installiert (integriert in Docker Desktop oder separat)
Phase 2: Version-Vergleich Hilfsfunktion (Zeilen 30-72)
Zweck
BusyBox-kompatible Versionierung (Synology NAS nutzt BusyBox, kein sort -V vorhanden).
Zeilen 30-35: Funktions-Definition
version_gte() {
local ver1="$1"
local ver2="$2"
version_gte "20.10.21" "20.10"→ return 0 (true, >= erfüllt)version_gte "20.0" "20.10"→ return 1 (false, < Minimum)- Warum:
sort -Vexistiert in BusyBox nicht
Zeilen 40-50: Major/Minor/Patch Parsing
v1_major=$(echo "$ver1" | cut -d. -f1)
v1_minor=$(echo "$ver1" | cut -d. -f2)
v1_patch=$(echo "$ver1" | cut -d. -f3)
- Teile Version "20.10.21" auf:
- v1_major=20, v1_minor=10, v1_patch=21
- Verwende 0 als Default wenn Feld fehlt (
${v1_minor:-0})
Zeilen 52-71: Vergleich Logic
-
Major vergleichen (Zeile 53-57)
- Wenn v1_major > v2_major → return 0 (erfüllt)
- Wenn v1_major < v2_major → return 1 (nicht erfüllt)
- Wenn gleich → weiter zu Minor
-
Minor vergleichen (Zeile 60-63)
- Analog Major-Vergleich
-
Patch vergleichen (Zeile 67-70)
version_patch >= version_patch→ return 0
Beispiele:
version_gte "20.10.21" "20.10"→ 20==20, 10==10, 21>=0 → TRUEversion_gte "20.9.0" "20.10"→ 20==20, 9<10 → FALSEversion_gte "21.0" "20.10"→ 21>20 → TRUE
Voraussetzungen:
- cut, echo Commands vorhanden (in BusyBox)
Phase 3: .env Datei Prüfung (Zeilen 74-115)
Zweck
Prüfe ob .env existiert. Wenn nicht → Download .env.example und beende Script.
Zeilen 74-82: Willkommens-Banner & Log-Init
echo "============================================================"
echo " Container Spawner Installation v${VERSION}"
echo "============================================================"
- Zeige Script-Version an
- Initialisiere Log-Datei mit Timestamp
Zeilen 87-100: .env Existenz-Check
if [ ! -f "${INSTALL_DIR}/.env" ]; then
echo "Lade .env.example herunter..."
if command -v curl >/dev/null 2>&1; then
curl -sSL "${RAW_URL}/.env.example" -o "${INSTALL_DIR}/.env.example"
elif command -v wget >/dev/null 2>&1; then
wget -q "${RAW_URL}/.env.example" -O "${INSTALL_DIR}/.env.example"
Logik:
- Existiert
.envNICHT?- Versuche
.env.exampleherunterzuladen (über curl oder wget) - Speichere in
${INSTALL_DIR}/.env.example - Stoppe Script mit exit 0 → Zeige Anleitung zur manuellen Konfiguration
- Versuche
Warum .env nicht automatisch kopieren?
.enventhält Secrets (SECRET_KEY, SMTP_PASSWORD etc.)- Admin muss Werte bewusst setzen (BASE_DOMAIN, TRAEFIK_NETWORK etc.)
- Verhindert Sicherheitslücken durch Auto-Configuration
Zeilen 104-111: Konfigurationsanleitung
echo "Naechste Schritte:"
echo " 1. Kopiere die Vorlage: cp .env.example .env"
echo " 2. Passe die Werte an: nano .env"
echo " 3. Fuehre erneut aus: bash install.sh"
Voraussetzungen:
- curl ODER wget installiert
- Internet-Zugriff zu Gitea-Server
.env.exampleim Repository vorhanden
Phase 4: Voraussetzungen Prüfung (Zeilen 122-185)
Zweck
Validiere dass alle erforderlichen Tools verfügbar und aktuelle Versionen sind.
Zeilen 128-149: Docker Version Check
if ! command -v docker >/dev/null 2>&1; then
echo -e "${RED}Fehler: Docker nicht gefunden!${NC}"
exit 1
fi
DOCKER_VERSION=$(docker version --format '{{.Server.Version}}' 2>/dev/null || \
docker version 2>/dev/null | grep -i "version" | head -1 | sed 's/.*version[: ]*\([0-9.]*\).*/\1/')
Logic:
- Prüfe ob
dockercommand existiertcommand -vist POSIX-kompatibel (funktioniert auch in BusyBox/ash)
- Extrahiere Docker Server-Version:
- Versuche
docker version --format(moderne Syntax) - Fallback zu regex parsing (für ältere Docker Versionen)
- Versuche
- Vergleiche gegen MIN_DOCKER_VERSION="20.10" mit
version_gte()Funktion
Fehlerbehandlung:
if [ -z "$DOCKER_VERSION" ]; then
echo "Version unbekannt (OK, mit Warnung fortfahren)"
elif version_gte "$DOCKER_VERSION" "$MIN_DOCKER_VERSION"; then
echo "OK (v${DOCKER_VERSION})"
else
echo "FEHLER: Version ${DOCKER_VERSION} zu alt"
exit 1
fi
Voraussetzungen:
dockerexecutable vorhanden (PATH)- Docker Server läuft (für version --format)
Zeilen 151-178: Docker Compose Check
if docker compose version >/dev/null 2>&1; then
COMPOSE_CMD="docker compose" # Neue Syntax (v2)
elif command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD="docker-compose" # Alte Syntax (v1)
else
echo -e "${RED}Fehler: Docker Compose nicht gefunden!${NC}"
exit 1
fi
Logic:
- Prüfe ob
docker composevorhanden (Docker Desktop oder separate Installation) - Fallback zu
docker-compose(veraltet aber noch unterstützt) - Speichere in
COMPOSE_CMDVariable für später
Wichtig: ${COMPOSE_CMD} wird später überall verwendet (Zeile 371, 626 etc.)
Voraussetzungen:
- Docker Compose v2+ oder
docker-composecommand vorhanden
Zeilen 180-185: Git Check
if ! command -v git >/dev/null 2>&1; then
echo -e "${RED}Fehler: Git nicht gefunden!${NC}"
exit 1
fi
Benötigt für:
- Clone Repository bei Neuinstallation (Zeile 220)
- Pull Updates bei bestehender Installation (Zeile 209)
- Versionskontrolle
Voraussetzungen:
gitexecutable vorhanden (mindestens Git 2.0)
Phase 5: Update vs. Neuinstallation (Zeilen 188-241)
Zweck
Prüfe ob bereits Installation existiert. Wenn ja → update via git pull. Wenn nein → clone repository.
Zeilen 192-193: Git Safe Directory (Synology-Workaround)
git config --global --add safe.directory "${INSTALL_DIR}" 2>/dev/null || true
Warum?
- Synology NAS: Docker Container läuft oft als root, Host-Verzeichnis ist root-owned
- Git misstraut Verzeichnissen mit unterschiedlichem Owner
- Fehler:
fatal: detected dubious ownership in repository at ... - Lösung:
safe.directoryerlaubt Repository trotzdem
Syntax:
2>/dev/null || true- Ignoriere Fehler (akzeptabel wenn git zu alt)
Zeilen 195-213: Update-Branch (Existierende Installation)
if [ -d "${INSTALL_DIR}/.git" ]; then
echo "Update erkannt - hole neueste Aenderungen..."
cd "${INSTALL_DIR}"
# Sichere lokale Aenderungen
if git diff --quiet 2>/dev/null; then
: # Keine Aenderungen
else
git stash 2>/dev/null || true
fi
# Update durchfuehren
if git fetch origin && git pull origin main 2>/dev/null; then
echo "Repository aktualisiert"
else
echo "Git-Update fehlgeschlagen, fahre mit lokalen Dateien fort..."
fi
Logik:
- Existiert
.gitVerzeichnis? → Installation vorhanden - Prüfe auf lokale Änderungen mit
git diff --quiet - Falls Änderungen →
git stash(temporär speichern) git fetch+git pull origin main(Update durchführen)- Falls fehlgeschlagen → Warnung aber nicht abbrechen (fahre mit lokalen Dateien fort)
Warum stash?
- User hat möglicherweise Dateien lokal angepasst (z.B. api.py Bugfixes)
git pullwürde MERGE CONFLICT verursachengit stashspeichert lokale Änderungen sicher → Merge ist sauber
Voraussetzungen:
.gitVerzeichnis existiert (= Installation bereits vorhanden)originRemote existiert (Standard bei clone)
Zeilen 214-240: Clone-Branch (Neuinstallation)
else
echo "Neuinstallation - klone Repository..."
TEMP_DIR=$(mktemp -d)
if git clone "${REPO_URL}" "${TEMP_DIR}"; then
shopt -s dotglob 2>/dev/null || true
for item in "${TEMP_DIR}"/*; do
basename_item=$(basename "$item")
if [ "$basename_item" != ".env" ] && [ "$basename_item" != ".git" ]; then
cp -r "$item" "${INSTALL_DIR}/"
fi
done
cp -r "${TEMP_DIR}/.git" "${INSTALL_DIR}/"
rm -rf "${TEMP_DIR}"
Logik:
- Keine
.gitVerzeichnis → Neuinstallation - Clone in temporäres Verzeichnis (nicht direkt nach INSTALL_DIR)
- Kopiere Dateien (außer
.envund.git) - Kopiere
.gitVerzeichnis nachträglich (für zukünftige Updates) - Räume Temp-Verzeichnis auf
Warum nicht direkt klonen?
git clonewürde.envüberschreiben (wenn Admin bereits angepasst hat)- Temporärer Clone als Workaround
Voraussetzungen:
git clonefunktioniert (Internet, SSH-Key oder HTTPS)- mktemp Command vorhanden (POSIX-kompatibel)
Phase 6: Verzeichnisse & Berechtigungen (Zeilen 244-285)
Zweck
Erstelle erforderliche Verzeichnisse und setze Berechtigungen für Docker-Zugriff.
Zeilen 249-255: Verzeichnisse erstellen
mkdir -p "${INSTALL_DIR}/data"
mkdir -p "${INSTALL_DIR}/logs"
chmod 755 "${INSTALL_DIR}/data"
chmod 755 "${INSTALL_DIR}/logs"
Warum diese Verzeichnisse?
data/- Persistente Datenbank + Konfigurationlogs/- Installations- und Anwendungs-Logs
Berechtigungen (755 = rwxr-xr-x):
- Owner: read + write + execute
- Group: read + execute (nur)
- Others: read + execute (nur)
Kompatibilität:
- Docker Container als root: 755 ausreichend
- Docker Container als non-root: Könnten 777 brauchen (kommentiert Zeile 271)
Zeilen 262-274: Root-Check
if [ "$(id -u)" = "0" ]; then
# Als root: 755 reicht
echo "755 (root)"
else
# Als normaler User: 755 auch OK
echo "755"
fi
Warum Root-Check?
- Synology NAS: Installation oft als root
- Docker-Container: Läuft auch als root
- User-Scripts (z.B. cron): Könnten als normaler User laufen
Regel:
- Root + Docker-Root: 755 OK
- Normaler User + Docker-Root: 755 auch OK
- Normaler User + Docker-NonRoot: 777 nötig (siehe Kommentar)
Zeilen 277-279: .env schützen
if [ -f "${INSTALL_DIR}/.env" ]; then
chmod 600 "${INSTALL_DIR}/.env"
echo ".env: OK (600, nur Owner)"
Berechtigungen (600 = rw-------):
- Nur Owner: read + write
- Group/Others: kein Zugriff
WICHTIG: Secrets in .env:
SECRET_KEY- Flask Session SecretSMTP_PASSWORD- Email-Authentifizierung- Darf nicht für alle lesbar sein!
Zeilen 282-284: install.sh auführbar machen
if [ -f "${INSTALL_DIR}/install.sh" ]; then
chmod +x "${INSTALL_DIR}/install.sh"
Warum?
- Nach git pull/clone könnte execute-bit verloren gehen
- Stelle sicher dass
bash install.shimmer funktioniert
Voraussetzungen:
- chmod Command vorhanden (POSIX)
- Berechtigungen änderbar (nicht in read-only Filesystem)
Phase 7: Docker-Netzwerk Prüfung (Zeilen 288-309)
Zweck
Prüfe dass Traefik-Netzwerk existiert. Wenn nicht → Erstelle automatisch.
Zeilen 291-292: Netzwerk aus .env lesen
NETWORK="${TRAEFIK_NETWORK:-web}"
echo "Pruefe Docker-Netzwerk: ${NETWORK}"
- Lese
TRAEFIK_NETWORKVariable aus .env - Fallback zu "web" wenn nicht definiert
Zeilen 294-309: Netzwerk-Logik
if docker network inspect "${NETWORK}" >/dev/null 2>&1; then
echo "Netzwerk '${NETWORK}': existiert"
else
echo "Fehler: Docker-Netzwerk '${NETWORK}' existiert nicht!"
# Automatische Erstellung (vorher: interaktive Frage)
docker network create "${NETWORK}" 2>/dev/null || true
echo "Netzwerk '${NETWORK}': erstellt/vorhanden"
fi
Logik:
-
Prüfe ob Netzwerk existiert mit
docker network inspect- Erfolgreich → Netzwerk vorhanden, weitermachen
- Fehler → Netzwerk nicht vorhanden
-
Automatische Erstellung (Zeile 307)
- Erstelle Netzwerk:
docker network create "${NETWORK}" 2>/dev/null || true- Ignoriere Fehler (z.B. bereits vorhanden)- Dadurch war das Script nicht mehr blockiert (früher:
read -p "...?")
- Erstelle Netzwerk:
Warum dieses Netzwerk?
- Traefik + Spawner müssen im gleichen Netzwerk sein
- Nur so können Traefik die User-Container entdecken und routen
- Name definiert in
.env→TRAEFIK_NETWORK=web(oder custom)
Voraussetzungen:
- Docker Daemon läuft
- Benutzer hat docker Zugriff (sudo oder docker-Gruppe)
- Netzwerk nicht durch andere Tools belegt
Phase 8: Traefik Prüfung (Zeilen 312-361)
Zweck
Prüfe ob Traefik-Container läuft. Falls ja → Validiere dass es im richtigen Netzwerk ist.
Zeilen 318-333: Traefik-Container Suche (3 Versuche)
Versuch 1 (Zeile 318): Nach Name suchen
TRAEFIK_CONTAINER=$(docker ps --filter "name=traefik" --filter "status=running" \
--format "{{.Names}}" 2>/dev/null | head -1)
- Suche nach Container mit "traefik" im Namen
--filter "status=running"- Nur laufende Container--format "{{.Names}}"- Nur Container-Name ausgebenhead -1- Nimm ersten (falls mehrere)
Versuch 2 (Zeile 322): Nach Image suchen
if [ -z "$TRAEFIK_CONTAINER" ]; then
TRAEFIK_CONTAINER=$(docker ps --filter "ancestor=traefik" \
--filter "status=running" --format "{{.Names}}" 2>/dev/null | head -1)
fi
- Falls Name-Suche leer → Suche nach
ancestor=traefik(Image-Name) - Findungschance: Container könnte anders benannt sein, aber vom traefik Image stammen
Versuch 3 (Zeile 327-332): Nach Label suchen
if [ -z "$TRAEFIK_CONTAINER" ]; then
TRAEFIK_CONTAINER=$(docker ps ... | while read name; do
if docker inspect "$name" 2>/dev/null | grep -q '"traefik.http.routers\|com.docker.compose.service": "traefik"'; then
echo "$name"
break
fi
done)
fi
- Falls noch immer nicht gefunden → Prüfe Labels
- Traefik setzt typischerweise:
com.docker.compose.service=traefik
Zeilen 335-361: Traefik-Status Ausgabe
Falls gefunden (Zeile 335-350):
if [ -n "$TRAEFIK_CONTAINER" ]; then
TRAEFIK_VERSION=$(docker exec "$TRAEFIK_CONTAINER" traefik version 2>/dev/null | ...)
echo "Traefik: OK (Container: ${TRAEFIK_CONTAINER}, v${TRAEFIK_VERSION})"
# Prüfe ob im richtigen Netzwerk
TRAEFIK_NETWORKS=$(docker inspect "$TRAEFIK_CONTAINER" \
--format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}')
if echo "$TRAEFIK_NETWORKS" | grep -q "$NETWORK"; then
echo "Traefik-Netzwerk: OK (verbunden mit '${NETWORK}')"
else
echo "WARNUNG: Traefik nicht mit Netzwerk '${NETWORK}' verbunden"
echo "Traefik-Netzwerke: ${TRAEFIK_NETWORKS}"
fi
fi
Checks:
traefik version- Zeige Traefik-Version andocker inspect- Prüfe Netzwerk-Verbindungen- Regex-Match gegen
${NETWORK}- Sind Traefik und Spawner im gleichen Netzwerk?
Falls nicht gefunden (Zeile 352-361):
else
echo "WARNUNG: Kein laufender Traefik-Container gefunden!"
echo "Traefik wird fuer das Routing benoetigt, aber Script faehrt fort..."
- WARNUNG nicht FEHLER → Script bricht nicht ab
- Spawner funktioniert auch ohne Traefik:
- Nur lokal erreichbar (localhost:5000)
- Keine automatischen Subdomains
- Keine User-Container Routing
Voraussetzungen:
- Docker Daemon läuft und antwortet
- Traefik-Container läuft (optional, mit Warnung weitergemacht)
Phase 9: Docker Images Bauen (Zeilen 364-563)
Zweck
Baue alle erforderlichen Docker Images:
- User-Templates (template-01, template-02, template-next etc.)
- Spawner API (Flask Backend)
- Spawner Frontend (Next.js)
Zeilen 370-378: USER_TEMPLATE_IMAGES aus .env auslesen
USER_TEMPLATE_IMAGES=""
if [ -f "${INSTALL_DIR}/.env" ]; then
USER_TEMPLATE_IMAGES=$(grep "^USER_TEMPLATE_IMAGES=" "${INSTALL_DIR}/.env" | \
cut -d'=' -f2- | tr -d '"' | tr -d "'")
fi
Parse-Logic:
grep "^USER_TEMPLATE_IMAGES="- Finde genau diese Zeile (nicht commented)cut -d'=' -f2-- Alles nach dem=Zeichentr -d '"' | tr -d "'"- Entferne Quotes (USER_TEMPLATE_IMAGES="...;...;...")
Beispiel:
# In .env
USER_TEMPLATE_IMAGES="user-template-01:latest;user-template-02:latest;user-template-next:latest"
# Nach Parsing
USER_TEMPLATE_IMAGES="user-template-01:latest;user-template-02:latest;user-template-next:latest"
Zeilen 380-385: Fallback auf .env.example
if [ -z "$USER_TEMPLATE_IMAGES" ] && [ -f "${INSTALL_DIR}/.env.example" ]; then
echo "⚠️ USER_TEMPLATE_IMAGES nicht definiert"
echo " Nutze .env.example als Fallback..."
USER_TEMPLATE_IMAGES=$(grep "^USER_TEMPLATE_IMAGES=" "${INSTALL_DIR}/.env.example" | ...)
fi
- Wenn
.envUSER_TEMPLATE_IMAGES nicht hat → Versuche.env.example - Erlaubt Test-Installation ohne .env Anpassung
Zeilen 387-437: Fallback-Modus (Keine .env Konfiguration)
if [ -z "$USER_TEMPLATE_IMAGES" ]; then
echo "⚠️ USER_TEMPLATE_IMAGES nicht konfiguriert"
echo " Fallback: Baue alle user-template-* Verzeichnisse..."
for template_dir in "${INSTALL_DIR}"/user-template*; do
[ -d "$template_dir" ] || continue
template_name=$(basename "$template_dir")
image_name="${template_name}:latest"
docker build --no-cache -t "${image_name}" "${template_dir}/" >> "${BUILD_LOG}" 2>&1
Logik (Rückwärtskompatibilität):
- Wenn USER_TEMPLATE_IMAGES leer → Auto-Detection
- Suche alle
user-template-*Verzeichnisse - Baue jedes als Docker Image
Beispiel:
user-template-01/
user-template-02/
user-template-next/
→ Baue: user-template-01:latest, user-template-02:latest, user-template-next:latest
Warum Fallback?
- Alte Installationen ohne .env Konfiguration müssen auch funktionieren
- Neue Installationen verwenden .env-basiertes System
Zeilen 440-562: .env-basiertes Building (NEUE LOGIK)
else
echo " Baue Templates aus .env Konfiguration..."
# Split by Semicolon
IFS=';' read -ra TEMPLATE_IMAGES <<< "$USER_TEMPLATE_IMAGES"
TOTAL_TEMPLATES=${#TEMPLATE_IMAGES[@]}
for image_with_tag in "${TEMPLATE_IMAGES[@]}"; do
image_with_tag=$(echo "$image_with_tag" | xargs) # Trim whitespace
[ -z "$image_with_tag" ] && continue
# Extract directory name (vor dem :)
template_dir_name="${image_with_tag%%:*}"
# Extract tag (nach dem :)
template_tag="${image_with_tag##*:}"
[ -z "$template_tag" ] && template_tag="latest"
template_dir="${INSTALL_DIR}/${template_dir_name}"
Parse-Logik für "user-template-01:latest":
-
Split Array by
;"user-template-01:latest;user-template-02:latest"- →
["user-template-01:latest", "user-template-02:latest"]
-
Trim Whitespace mit
xargs" user-template-01:latest "→"user-template-01:latest"
-
Extrahiere Image-Name und Tag
${image%%:*}- Alles VOR dem ersten:(user-template-01)${image##*:}- Alles NACH dem letzten:(latest)
Validation (Zeilen 475-493):
# Prüfe ob Verzeichnis existiert
if [ ! -d "$template_dir" ]; then
echo "❌ Fehler: Template-Verzeichnis nicht gefunden"
echo " Definiert in .env: USER_TEMPLATE_IMAGES"
echo " Erwartetes Verzeichnis: ${template_dir}"
continue # Überspringe dieses Template
fi
# Dockerfile vorhanden?
if [ ! -f "${template_dir}/Dockerfile" ]; then
echo "❌ Fehler: Kein Dockerfile gefunden"
continue
fi
- Nicht-existente Verzeichnisse → WARNUNG + continue (nicht FEHLER)
- So können einzelne Templates fehlschlagen, andere funktionieren weiterhin
Build-Logik (Zeilen 507-527):
docker build --no-cache -t "${template_dir_name}:${template_tag}" "${template_dir}/" >> "${BUILD_LOG}" 2>&1
BUILD_EXIT=$?
# Zeige gefilterten Output
grep -E "(Step |#[0-9]+ |Successfully|ERROR|error:|COPY|RUN|FROM)" "${BUILD_LOG}" | sed 's/^/ /'
# Prüfe ob erfolgreich
if [ $BUILD_EXIT -eq 0 ] && docker image inspect "${template_dir_name}:${template_tag}" >/dev/null 2>&1; then
echo "✅ ${template_name}: OK"
BUILT_TEMPLATES=$((BUILT_TEMPLATES + 1))
else
echo "❌ ${template_name}: FEHLER"
tail -50 "${BUILD_LOG}"
exit 1 # Breche ab
fi
Build-Logik:
- Führe
docker buildaus, leite Output zu Log-Datei - Zeige gefilterte Build-Schritte (Step, FROM, RUN, COPY, ERROR)
- Prüfe ob erfolgreich:
- Exit-Code = 0?
docker image inspectfindet das Image?
- Beide Bedingungen erfüllt → OK
- Sonst → FEHLER, zeige last 50 Zeilen, beende Script
Spezieller Handling für Next.js (Zeile 497-498):
if [[ "$template_dir_name" == *"next"* ]]; then
echo "${BLUE}Dies kann 2-5 Minuten dauern (npm install + build)...${NC}"
fi
- Next.js Templates brauchen länger (npm install + npm run build)
- Warnung damit Admin nicht denkt Installation ist hänggeblieben
Ungekonfigurierte Templates Warnung (Zeilen 539-561):
echo " Prüfe auf ungekonfigurierte Template-Verzeichnisse..."
for template_dir in "${INSTALL_DIR}"/user-template*; do
template_name=$(basename "$template_dir")
# Ist dieses Template in USER_TEMPLATE_IMAGES definiert?
if [[ ! "$USER_TEMPLATE_IMAGES" =~ "$template_name" ]]; then
echo " ⚠️ ${template_name} (nicht in USER_TEMPLATE_IMAGES definiert)"
fi
done
- Hilft Admin zu sehen, welche Templates nicht gebaut wurden
- Z.B. Template existiert lokal, aber nicht in .env → wird ignoriert
Zeilen 565-616: Spawner API & Frontend bauen
Spawner API (Flask Backend) - Zeilen 565-587:
echo " Baue Spawner API (Flask Backend)..."
docker build --no-cache -t spawner:latest "${INSTALL_DIR}/" >> "${BUILD_LOG}" 2>&1
BUILD_EXIT=$?
if [ $BUILD_EXIT -eq 0 ] && docker image inspect spawner:latest >/dev/null 2>&1; then
echo " spawner-api: ${GREEN}OK${NC}"
else
echo " spawner-api: ${RED}FEHLER${NC}"
tail -50 "${BUILD_LOG}"
exit 1
fi
- Baut Image aus Dockerfile im root directory (nicht frontend/)
- Image-Name:
spawner:latest - Muss erfolgreich sein (exit 1 bei Fehler)
Spawner Frontend (Next.js) - Zeilen 590-616:
if [ -d "${INSTALL_DIR}/frontend" ]; then
echo " Baue Frontend (Next.js)..."
echo "${BLUE}Dies kann 2-5 Minuten dauern (npm install + build)...${NC}"
docker build --no-cache -t spawner-frontend:latest "${INSTALL_DIR}/frontend/" >> "${BUILD_LOG}" 2>&1
...
fi
- Optional Check: Falls
frontend/Verzeichnis nicht vorhanden → überspringe - Image-Name:
spawner-frontend:latest - Warnung dass npm build lange dauert
Voraussetzungen:
docker buildCommand funktioniert- Ausreichend Disk-Space für Images
- Internetzugriff (für npm install, pip install etc.)
- Dockerfile(s) syntaktisch korrekt
Phase 10: Container Starten (Zeilen 621-646)
Zweck
Starte Docker Compose Services (Spawner API, Frontend, optional Traefik) und prüfe Health.
Zeilen 625-626: Compose Up
echo ""
echo "Starte Container..."
${COMPOSE_CMD} up -d
${COMPOSE_CMD}ist entwederdocker composeoderdocker-compose(aus Phase 4)-dFlag: Detached Mode (laufen im Hintergrund)
Was wird gestartet? (Definiert in docker-compose.yml)
- spawner (API, Port 5000)
- spawner-frontend (Next.js, Port 3000)
- Optional: traefik, database etc. (je nach Konfiguration)
Zeilen 631-646: Health-Checks
echo "Warte auf Spawner-Start..."
sleep 5
SPAWNER_URL="http://localhost:${SPAWNER_PORT:-5000}/health"
if curl -sf "${SPAWNER_URL}" >/dev/null 2>&1; then
echo " API Health-Check: ${GREEN}OK${NC}"
else
echo " API Health-Check: ${YELLOW}Noch nicht bereit (normal beim ersten Start)${NC}"
fi
if curl -sf "http://localhost:3000/" >/dev/null 2>&1; then
echo " Frontend Health-Check: ${GREEN}OK${NC}"
else
echo " Frontend Health-Check: ${YELLOW}Noch nicht bereit${NC}"
fi
Logik:
- Warte 5 Sekunden (Container brauchen Zeit zu starten)
- Prüfe API:
curl -sf http://localhost:5000/health-sSilent mode-fFail silently auf HTTP-Error
- Prüfe Frontend:
curl -sf http://localhost:3000/
Status:
- Beide OK → ✅ Alles funktioniert
- Eine/beide nicht bereit → ⚠️ Normal beim ersten Start (DB wird initialisiert etc.)
Voraussetzungen:
- curl Command verfügbar
- Container haben Zeit zu starten
- Ports 5000 und 3000 lokal verfügbar
Phase 11: Fertig-Nachricht (Zeilen 648-695)
Zweck
Zeige Erfolgs-Nachricht und nützliche Informationen für Admin.
Zeilen 652-656: Success Banner
echo "============================================================"
echo -e " ${GREEN}Installation abgeschlossen!${NC}"
echo "============================================================"
echo ""
echo "Installations-Log: ${LOG_FILE}"
- Zeige Erfolgs-Banner
- Link zu Log-Datei (für Debugging)
Zeilen 659-668: URLs anzeigen
SCHEME="https"
if [ "${BASE_DOMAIN:-localhost}" = "localhost" ]; then
SCHEME="http"
fi
FULL_URL="${SCHEME}://${SPAWNER_SUBDOMAIN:-coder}.${BASE_DOMAIN:-localhost}"
echo "Zugriff:"
echo " Frontend: ${FULL_URL}"
echo " API: ${FULL_URL}/api"
echo " Health: ${FULL_URL}/health"
Logic:
- Lese
BASE_DOMAINundSPAWNER_SUBDOMAINaus .env - Falls localhost → nutze http:// (kein HTTPS)
- Sonst → nutze https:// (erwartet Let's Encrypt)
Beispiel .env:
BASE_DOMAIN=wieland.org
SPAWNER_SUBDOMAIN=coder
→ URLs:
- Frontend: https://coder.wieland.org
- API: https://coder.wieland.org/api
- Health: https://coder.wieland.org/health
Zeilen 670-678: Lokale URLs & Befehle
echo ""
echo "Lokaler Zugriff (ohne Traefik):"
echo " API: http://localhost:${SPAWNER_PORT:-5000}"
echo " Frontend: http://localhost:3000"
echo ""
echo "Nützliche Befehle:"
echo " Status: ${COMPOSE_CMD} ps"
echo " Logs API: ${COMPOSE_CMD} logs -f spawner"
echo " Logs FE: ${COMPOSE_CMD} logs -f frontend"
- Zeige wie man Services ohne Traefik zugreift
- Zeige häufige Wartungs-Befehle
Zeilen 681-695: Wichtige Informationen
echo "WICHTIG - Multi-Container MVP:"
echo " - Jeder User erhält 2 Container: Development und Production"
echo " - Dev Container: https://${SPAWNER_SUBDOMAIN}.${BASE_DOMAIN}/{slug}-dev"
echo " - Prod Container: https://${SPAWNER_SUBDOMAIN}.${BASE_DOMAIN}/{slug}-prod"
echo ""
echo "WICHTIG - Passwordless Auth:"
echo " Das System nutzt Magic Links (Email-basiert)!"
echo " - SMTP konfigurieren: .env Datei anpassen"
echo " - Datenbank wird automatisch mit allen Tabellen erstellt"
- Erkläre Multi-Container Feature
- Erkläre Passwordless Authentication
- Hinweis auf SMTP Konfiguration
Zusammenfassung der Abhängigkeiten
Externe Tools (müssen vorhanden sein)
| Tool | Min-Version | Zweck | Fallback |
|---|---|---|---|
| docker | 20.10 | Container management | Keine |
| docker compose | 2.0 | Service orchestration | docker-compose (v1) |
| git | 2.0+ | Repository management | Keine |
| curl/wget | - | Download .env.example | curl ODER wget |
| bash | 4.0+ | Script interpreter | Nur bash (nicht sh) |
| grep, cut, tr, sed | - | Text parsing | BusyBox kompatibel |
Umgebungsvariablen (.env)
| Variable | Beispiel | Zweck | Required |
|---|---|---|---|
SECRET_KEY |
abc123... |
Flask session secret | Ja |
BASE_DOMAIN |
wieland.org |
Hauptdomain | Ja |
SPAWNER_SUBDOMAIN |
coder |
Subdomain prefix | Ja |
TRAEFIK_NETWORK |
web |
Docker network für Traefik | Ja |
USER_TEMPLATE_IMAGES |
user-template-01:latest;... |
Templates zum bauen | Optional (Fallback) |
SPAWNER_PORT |
5000 |
Backend Port | Optional |
Dateisystem-Anforderungen
- Disk-Space: 3-5 GB (für Docker Images)
- Verzeichnisse:
.env- Konfiguration (chmod 600)data/- Datenbank (chmod 755)logs/- Installation/App Logs (chmod 755)frontend/- Frontend Code (optional)user-template-*/- Template Dockerfiles
Netzwerk-Anforderungen
- Internet: Zum downloaden .env.example, docker images, npm/pip packages
- Docker Daemon: Lokal erreichbar (Unix Socket oder TCP)
- Docker Network:
${TRAEFIK_NETWORK}darf nicht existieren (oder wird geteilt mit Traefik)
Sicherheits-Anforderungen
.envmuss before Installation vorhanden sein (mit SECRET_KEY gesetzt)- Docker Socket nur für vertrauenswürdige Benutzer zugänglich
- SMTP-Credentials (wenn konfiguriert) müssen sicher sein
Fehlerbehandlung & Logging
Log-Datei: spawner-install.log
Alle Build-Outputs werden geloggt:
=== Spawner Installation 2026-02-03 10:00:00 ===
=== Build: user-template-01 ===
Step 1/5 : FROM nginx:latest
---> ...
Step 2/5 : COPY index.html /usr/share/nginx/html/
...
Successfully built abc123
=== Build: spawner-api ===
...
Fehler-Handling mit set -e
Reihenfolge:
- Script stoppt sofort bei erstem Fehler (
set -e) - Zuletzt erfolgreich durchgeführte Zeile wird gezeigt
- Admin kann Log-Datei prüfen:
tail spawner-install.log
Beispiel:
# Fehler: Docker nicht installiert
$ bash install.sh
Fehler: Docker nicht gefunden!
→ exit 1 (Script stoppt)
→ install.sh log nicht weitergehe
Recovery-Strategien
Fehler: Zu alte Docker Version
→ Installiere Docker ≥ 20.10
→ Starte install.sh erneut
Fehler: .env nicht gefunden
→ cp .env.example .env
→ nano .env (setze SECRET_KEY, BASE_DOMAIN etc.)
→ bash install.sh
Fehler: Template-Image Build fehlt
→ Prüfe docker build Fehler in Log
→ Ursache meist: Dockerfile Syntax, Base Image nicht vorhanden
→ Korrigiere Dockerfile
→ Starte install.sh erneut (re-baut Imagen)
Fehler: Git Clone fehlgeschlagen
→ Prüfe Internet-Zugriff: curl https://gitea.iotxs.de
→ Prüfe Git SSH/HTTPS Keys (falls SSH)
→ Starte install.sh erneut
Tipps für Synology NAS
BusyBox-Kompatibilität
Install.sh läuft auf Synology mit BusyBox-Shell. Wichtige Unterschiede:
| Bash | BusyBox | Workaround in install.sh |
|---|---|---|
sort -V |
Nicht vorhanden | Eigene version_gte() Funktion |
grep -P (Perl) |
Nicht vorhanden | Nutze Basic grep |
shopt |
Teilweise | 2>/dev/null || true (ignore error) |
Docker auf Synology
# Docker läuft meist als root
id -u # → 0 (root)
# Berechtigungen daher 755 OK (nicht 777 nötig)
# Git safe.directory Workaround
git config --global --add safe.directory /volume1/docker/spawner
Disk-Space prüfen
# Vor Installation
df -h /volume1/docker/
# Braucht ~5 GB für alle Images
# Bei < 10 GB verfügbar → Warnung geben
Häufige Fragen
F: Kann ich install.sh mehrmals hintereinander ausführen?
A: Ja. Script erkennt bestehende Installation (.git Verzeichnis) und führt git pull aus statt zu klonen.
F: Was passiert bei Netzwerk-Fehler während Download?
A: Script stoppt mit Fehler (set -e). Nach Netzwerk-Reparatur kann man bash install.sh erneut ausführen.
F: Kann ich nur bestimmte Templates bauen?
A: Ja. Ändere .env: USER_TEMPLATE_IMAGES="user-template-01:latest" (nur template-01)
F: Wie debugge ich Build-Fehler?
A: Prüfe spawner-install.log: cat spawner-install.log | grep -A 20 "ERROR\|FEHLER"
F: Können User-Container nach Installation noch hinzugefügt werden?
A: Ja. Neue user-template-* Ordner + .env update + docker build manuell oder nächster install.sh run.
Stand: 2026-02-03 Version: install.sh v0.1.0 Kompatibilität: Bash 4.0+, BusyBox, Docker 20.10+, Docker Compose 2.0+