- VERSION in install.sh wird jetzt aus Git-Tags gelesen - Format: git describe --tags --always - Beispiel: v0.2.0 → zeigt VERSION=0.2.0 im Script - Fallback auf 'dev' wenn keine Tags vorhanden Vorteil: Version wird automatisch aktualisiert, keine manuelle Änderung nötig
694 lines
25 KiB
Bash
694 lines
25 KiB
Bash
#!/bin/bash
|
|
set -e
|
|
|
|
# ============================================================
|
|
# Container Spawner - Installationsskript
|
|
# https://gitea.iotxs.de/RainerWieland/spawner
|
|
# ============================================================
|
|
|
|
REPO_URL="https://gitea.iotxs.de/RainerWieland/spawner.git"
|
|
RAW_URL="https://gitea.iotxs.de/RainerWieland/spawner/raw/branch/main"
|
|
INSTALL_DIR="${PWD}"
|
|
# VERSION automatisch aus Git-Tags bestimmen (z.B. v0.1.0, v0.2.1)
|
|
# Fallback auf "dev" wenn keine Tags vorhanden
|
|
VERSION=$(git describe --tags --always 2>/dev/null | sed 's/^v//' || echo "dev")
|
|
LOG_FILE="${INSTALL_DIR}/spawner-install.log"
|
|
|
|
# Farben fuer Output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Mindestversionen
|
|
MIN_DOCKER_VERSION="20.10"
|
|
MIN_COMPOSE_VERSION="2.0"
|
|
|
|
# ============================================================
|
|
# Hilfsfunktion: Versionen vergleichen (BusyBox/Synology kompatibel)
|
|
# Gibt 0 zurueck wenn $1 >= $2, sonst 1
|
|
# ============================================================
|
|
version_gte() {
|
|
# Vergleiche zwei Versionen (z.B. "20.10.21" >= "20.10")
|
|
# Kompatibel mit BusyBox (kein sort -V)
|
|
local ver1="$1"
|
|
local ver2="$2"
|
|
|
|
# Extrahiere Major.Minor.Patch (fuege .0 hinzu falls noetig)
|
|
local v1_major v1_minor v1_patch
|
|
local v2_major v2_minor v2_patch
|
|
|
|
v1_major=$(echo "$ver1" | cut -d. -f1)
|
|
v1_minor=$(echo "$ver1" | cut -d. -f2)
|
|
v1_patch=$(echo "$ver1" | cut -d. -f3)
|
|
v1_minor=${v1_minor:-0}
|
|
v1_patch=${v1_patch:-0}
|
|
|
|
v2_major=$(echo "$ver2" | cut -d. -f1)
|
|
v2_minor=$(echo "$ver2" | cut -d. -f2)
|
|
v2_patch=$(echo "$ver2" | cut -d. -f3)
|
|
v2_minor=${v2_minor:-0}
|
|
v2_patch=${v2_patch:-0}
|
|
|
|
# Vergleiche Major
|
|
if [ "$v1_major" -gt "$v2_major" ] 2>/dev/null; then
|
|
return 0
|
|
elif [ "$v1_major" -lt "$v2_major" ] 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
# Major gleich, vergleiche Minor
|
|
if [ "$v1_minor" -gt "$v2_minor" ] 2>/dev/null; then
|
|
return 0
|
|
elif [ "$v1_minor" -lt "$v2_minor" ] 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
# Minor gleich, vergleiche Patch
|
|
if [ "$v1_patch" -ge "$v2_patch" ] 2>/dev/null; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
echo ""
|
|
echo "============================================================"
|
|
echo " Container Spawner Installation v${VERSION}"
|
|
echo "============================================================"
|
|
echo ""
|
|
|
|
# Log-Datei initialisieren
|
|
echo "=== Spawner Installation $(date) ===" > "${LOG_FILE}"
|
|
echo "" >> "${LOG_FILE}"
|
|
|
|
# ============================================================
|
|
# 1. Pruefe .env
|
|
# ============================================================
|
|
if [ ! -f "${INSTALL_DIR}/.env" ]; then
|
|
echo -e "${YELLOW}HINWEIS: Keine .env-Datei gefunden!${NC}"
|
|
echo ""
|
|
|
|
# Erstelle .env.example aus Repository
|
|
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"
|
|
else
|
|
echo -e "${RED}Fehler: Weder curl noch wget gefunden!${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}Vorlage erstellt: .env.example${NC}"
|
|
echo ""
|
|
echo "Naechste Schritte:"
|
|
echo " 1. Kopiere die Vorlage: cp .env.example .env"
|
|
echo " 2. Passe die Werte an: nano .env"
|
|
echo " - SECRET_KEY generieren (siehe Kommentar in .env)"
|
|
echo " - BASE_DOMAIN setzen"
|
|
echo " - TRAEFIK_NETWORK pruefen"
|
|
echo " 3. Fuehre erneut aus: bash install.sh"
|
|
echo ""
|
|
exit 0
|
|
fi
|
|
|
|
echo -e "${GREEN}.env-Datei gefunden${NC}"
|
|
|
|
# Lade .env Variablen
|
|
set -a
|
|
source "${INSTALL_DIR}/.env"
|
|
set +a
|
|
|
|
# ============================================================
|
|
# 2. Pruefe Voraussetzungen
|
|
# ============================================================
|
|
echo ""
|
|
echo "Pruefe Voraussetzungen..."
|
|
|
|
# Docker - Existenz und Version pruefen
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo -e "${RED}Fehler: Docker nicht gefunden!${NC}"
|
|
echo "Installiere Docker: https://docs.docker.com/get-docker/"
|
|
exit 1
|
|
fi
|
|
|
|
# Docker Version extrahieren (z.B. "20.10.21" aus "Docker version 20.10.21, build baeda1f")
|
|
# BusyBox-kompatibel (kein grep -P)
|
|
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/')
|
|
if [ -z "$DOCKER_VERSION" ]; then
|
|
echo -e "${YELLOW}Warnung: Docker-Version konnte nicht ermittelt werden${NC}"
|
|
echo -e " Docker: ${YELLOW}OK${NC} (Version unbekannt, min. ${MIN_DOCKER_VERSION} empfohlen)"
|
|
elif version_gte "$DOCKER_VERSION" "$MIN_DOCKER_VERSION"; then
|
|
echo -e " Docker: ${GREEN}OK${NC} (v${DOCKER_VERSION})"
|
|
else
|
|
echo -e "${RED}Fehler: Docker-Version ${DOCKER_VERSION} ist zu alt!${NC}"
|
|
echo "Mindestversion: ${MIN_DOCKER_VERSION}"
|
|
echo "Aktualisiere Docker: https://docs.docker.com/get-docker/"
|
|
exit 1
|
|
fi
|
|
|
|
# Docker Compose - Existenz und Version pruefen
|
|
# BusyBox-kompatibel (kein grep -P)
|
|
COMPOSE_VERSION=""
|
|
if docker compose version >/dev/null 2>&1; then
|
|
COMPOSE_CMD="docker compose"
|
|
# Version extrahieren (z.B. "2.21.0" aus "Docker Compose version v2.21.0")
|
|
COMPOSE_VERSION=$(docker compose version --short 2>/dev/null || \
|
|
docker compose version 2>/dev/null | sed 's/[^0-9.]*\([0-9][0-9.]*\).*/\1/' | head -1)
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
COMPOSE_CMD="docker-compose"
|
|
COMPOSE_VERSION=$(docker-compose version --short 2>/dev/null || \
|
|
docker-compose version 2>/dev/null | sed 's/[^0-9.]*\([0-9][0-9.]*\).*/\1/' | head -1)
|
|
else
|
|
echo -e "${RED}Fehler: Docker Compose nicht gefunden!${NC}"
|
|
echo "Docker Compose ist Teil von Docker Desktop oder kann separat installiert werden."
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$COMPOSE_VERSION" ]; then
|
|
echo -e " Docker Compose: ${YELLOW}OK${NC} (${COMPOSE_CMD}, Version unbekannt)"
|
|
elif version_gte "$COMPOSE_VERSION" "$MIN_COMPOSE_VERSION"; then
|
|
echo -e " Docker Compose: ${GREEN}OK${NC} (v${COMPOSE_VERSION})"
|
|
else
|
|
echo -e "${RED}Fehler: Docker Compose Version ${COMPOSE_VERSION} ist zu alt!${NC}"
|
|
echo "Mindestversion: ${MIN_COMPOSE_VERSION}"
|
|
echo "Aktualisiere Docker Compose: https://docs.docker.com/compose/install/"
|
|
exit 1
|
|
fi
|
|
|
|
# Git
|
|
if ! command -v git >/dev/null 2>&1; then
|
|
echo -e "${RED}Fehler: Git nicht gefunden!${NC}"
|
|
exit 1
|
|
fi
|
|
echo -e " Git: ${GREEN}OK${NC}"
|
|
|
|
# ============================================================
|
|
# 3. Pruefe ob bereits installiert (Update vs. Neuinstallation)
|
|
# ============================================================
|
|
echo ""
|
|
|
|
# Git safe.directory setzen (fuer NAS/Container-Umgebungen)
|
|
git config --global --add safe.directory "${INSTALL_DIR}" 2>/dev/null || true
|
|
|
|
if [ -d "${INSTALL_DIR}/.git" ]; then
|
|
echo -e "${YELLOW}Update erkannt - hole neueste Aenderungen...${NC}"
|
|
|
|
cd "${INSTALL_DIR}"
|
|
|
|
# WICHTIG: Auf Synology gibt es Berechtigungsbits-Unterschiede (old mode 100644 vs new mode 100755)
|
|
# Diese sind KEINE echten Code-Aenderungen, daher einfach die Remote-Version nehmen
|
|
git fetch origin 2>/dev/null || true
|
|
git reset --hard origin/main 2>/dev/null || true
|
|
|
|
# Update durchfuehren (nach Reset sollte es funktionieren)
|
|
if git pull origin main 2>/dev/null; then
|
|
echo -e "${GREEN}Repository aktualisiert${NC}"
|
|
else
|
|
echo -e "${YELLOW}Git-Update fehlgeschlagen, fahre mit lokalen Dateien fort...${NC}"
|
|
fi
|
|
else
|
|
echo "Neuinstallation - klone Repository..."
|
|
|
|
# Temporaeres Verzeichnis fuer Clone
|
|
TEMP_DIR=$(mktemp -d)
|
|
|
|
if git clone "${REPO_URL}" "${TEMP_DIR}"; then
|
|
# Kopiere Dateien (ueberschreibt nicht .env)
|
|
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
|
|
|
|
# .git Verzeichnis kopieren fuer Updates
|
|
cp -r "${TEMP_DIR}/.git" "${INSTALL_DIR}/"
|
|
|
|
rm -rf "${TEMP_DIR}"
|
|
echo -e "${GREEN}Repository geklont${NC}"
|
|
else
|
|
echo -e "${RED}Fehler: Repository konnte nicht geklont werden!${NC}"
|
|
echo "URL: ${REPO_URL}"
|
|
rm -rf "${TEMP_DIR}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# ============================================================
|
|
# 4. Verzeichnisse und Rechte setzen
|
|
# ============================================================
|
|
echo ""
|
|
echo "Setze Verzeichnisse und Berechtigungen..."
|
|
|
|
# Verzeichnisse erstellen falls nicht vorhanden
|
|
mkdir -p "${INSTALL_DIR}/data"
|
|
mkdir -p "${INSTALL_DIR}/logs"
|
|
|
|
# Berechtigungen setzen (rwx fuer Owner, rx fuer Group/Other)
|
|
chmod 755 "${INSTALL_DIR}/data"
|
|
chmod 755 "${INSTALL_DIR}/logs"
|
|
|
|
# Fuer Docker: Verzeichnisse muessen vom Container beschreibbar sein
|
|
# Option 1: Wenn Container als root laeuft (Standard) - 755 reicht
|
|
# Option 2: Wenn Container als non-root laeuft - 777 oder chown noetig
|
|
|
|
# Pruefen ob wir als root laufen (fuer chown)
|
|
if [ "$(id -u)" = "0" ]; then
|
|
# Als root: Owner auf aktuellen User setzen (oder Docker-User)
|
|
# Standard: belassen wie es ist (root kann alles)
|
|
echo -e " data/: ${GREEN}OK${NC} (755, root)"
|
|
echo -e " logs/: ${GREEN}OK${NC} (755, root)"
|
|
else
|
|
# Als normaler User: Verzeichnisse muessen beschreibbar sein
|
|
# Docker-Container laeuft meist als root, daher 755 ausreichend
|
|
# Falls Container als non-root laeuft, auf 777 setzen:
|
|
# chmod 777 "${INSTALL_DIR}/data" "${INSTALL_DIR}/logs"
|
|
echo -e " data/: ${GREEN}OK${NC} (755)"
|
|
echo -e " logs/: ${GREEN}OK${NC} (755)"
|
|
fi
|
|
|
|
# .env Datei schuetzen (nur Owner kann lesen/schreiben)
|
|
if [ -f "${INSTALL_DIR}/.env" ]; then
|
|
chmod 600 "${INSTALL_DIR}/.env"
|
|
echo -e " .env: ${GREEN}OK${NC} (600, nur Owner)"
|
|
fi
|
|
|
|
# install.sh ausfuehrbar machen
|
|
if [ -f "${INSTALL_DIR}/install.sh" ]; then
|
|
chmod +x "${INSTALL_DIR}/install.sh"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 5. Docker-Netzwerk pruefen/erstellen
|
|
# ============================================================
|
|
echo ""
|
|
NETWORK="${TRAEFIK_NETWORK:-web}"
|
|
echo "Pruefe Docker-Netzwerk: ${NETWORK}"
|
|
|
|
if docker network inspect "${NETWORK}" >/dev/null 2>&1; then
|
|
echo -e " Netzwerk '${NETWORK}': ${GREEN}existiert${NC}"
|
|
else
|
|
echo -e "${RED}Fehler: Docker-Netzwerk '${NETWORK}' existiert nicht!${NC}"
|
|
echo ""
|
|
echo "Das Netzwerk muss von Traefik erstellt werden oder bereits existieren."
|
|
echo "Stelle sicher, dass Traefik laeuft und das Netzwerk '${NETWORK}' verwendet."
|
|
echo ""
|
|
echo "Optionen:"
|
|
echo " 1. Starte Traefik zuerst (empfohlen)"
|
|
echo " 2. Erstelle das Netzwerk manuell: docker network create ${NETWORK}"
|
|
echo ""
|
|
echo "Erstelle Netzwerk automatisch..."
|
|
docker network create "${NETWORK}" 2>/dev/null || true
|
|
echo -e " Netzwerk '${NETWORK}': ${GREEN}erstellt/vorhanden${NC}"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 6. Pruefe ob Traefik laeuft
|
|
# ============================================================
|
|
echo ""
|
|
echo "Pruefe Traefik..."
|
|
|
|
# Suche nach laufenden Traefik-Containern
|
|
TRAEFIK_CONTAINER=$(docker ps --filter "name=traefik" --filter "status=running" --format "{{.Names}}" 2>/dev/null | head -1)
|
|
|
|
# Falls nicht nach Name gefunden, suche nach Image
|
|
if [ -z "$TRAEFIK_CONTAINER" ]; then
|
|
TRAEFIK_CONTAINER=$(docker ps --filter "ancestor=traefik" --filter "status=running" --format "{{.Names}}" 2>/dev/null | head -1)
|
|
fi
|
|
|
|
# Falls immer noch nicht gefunden, suche nach Label (traefik.enable=true auf sich selbst)
|
|
if [ -z "$TRAEFIK_CONTAINER" ]; then
|
|
TRAEFIK_CONTAINER=$(docker ps --filter "status=running" --format "{{.Names}}" 2>/dev/null | 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
|
|
|
|
if [ -n "$TRAEFIK_CONTAINER" ]; then
|
|
# Pruefe Traefik-Version (BusyBox-kompatibel)
|
|
TRAEFIK_VERSION=$(docker exec "$TRAEFIK_CONTAINER" traefik version 2>/dev/null | \
|
|
grep -i "version" | head -1 | sed 's/.*Version[: ]*\([0-9.]*\).*/\1/' || echo "unbekannt")
|
|
[ -z "$TRAEFIK_VERSION" ] && TRAEFIK_VERSION="unbekannt"
|
|
echo -e " Traefik: ${GREEN}laeuft${NC} (Container: ${TRAEFIK_CONTAINER}, v${TRAEFIK_VERSION})"
|
|
|
|
# Pruefe ob Traefik am gleichen Netzwerk haengt
|
|
TRAEFIK_NETWORKS=$(docker inspect "$TRAEFIK_CONTAINER" --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}' 2>/dev/null)
|
|
if echo "$TRAEFIK_NETWORKS" | grep -q "$NETWORK"; then
|
|
echo -e " Traefik-Netzwerk: ${GREEN}OK${NC} (verbunden mit '${NETWORK}')"
|
|
else
|
|
echo -e " ${YELLOW}Warnung: Traefik ist nicht mit Netzwerk '${NETWORK}' verbunden${NC}"
|
|
echo " Traefik-Netzwerke: ${TRAEFIK_NETWORKS}"
|
|
echo " Stelle sicher, dass TRAEFIK_NETWORK in .env korrekt konfiguriert ist."
|
|
fi
|
|
else
|
|
echo -e " ${YELLOW}Warnung: Kein laufender Traefik-Container gefunden!${NC}"
|
|
echo ""
|
|
echo " Traefik wird fuer das Routing der User-Container benoetigt."
|
|
echo " Der Spawner kann ohne Traefik gestartet werden, aber:"
|
|
echo " - User-Container sind nur lokal erreichbar"
|
|
echo " - Kein automatisches HTTPS"
|
|
echo " - Kein Subdomain-Routing"
|
|
echo ""
|
|
echo -e " ${YELLOW}Fortfahren ohne Traefik...${NC}"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 7. Docker-Images bauen
|
|
# ============================================================
|
|
echo ""
|
|
echo "Baue Docker-Images (Dynamisches Template-System)..."
|
|
echo ""
|
|
|
|
# Stoppe laufende Container
|
|
${COMPOSE_CMD} down 2>/dev/null || true
|
|
|
|
# Lese USER_TEMPLATE_IMAGES aus .env
|
|
USER_TEMPLATE_IMAGES=""
|
|
if [ -f "${INSTALL_DIR}/.env" ]; then
|
|
# Extrahiere USER_TEMPLATE_IMAGES (mit oder ohne Quotes)
|
|
USER_TEMPLATE_IMAGES=$(grep "^USER_TEMPLATE_IMAGES=" "${INSTALL_DIR}/.env" | cut -d'=' -f2- | tr -d '"' | tr -d "'")
|
|
fi
|
|
|
|
# Fallback auf .env.example wenn .env nicht existiert
|
|
if [ -z "$USER_TEMPLATE_IMAGES" ] && [ -f "${INSTALL_DIR}/.env.example" ]; then
|
|
echo -e "${YELLOW}⚠️ .env nicht gefunden oder USER_TEMPLATE_IMAGES nicht definiert${NC}"
|
|
echo " Nutze .env.example als Fallback..."
|
|
USER_TEMPLATE_IMAGES=$(grep "^USER_TEMPLATE_IMAGES=" "${INSTALL_DIR}/.env.example" | cut -d'=' -f2- | tr -d '"' | tr -d "'")
|
|
fi
|
|
|
|
# Fallback: Wenn noch immer leer, baue alle Templates (rückwärtskompatibel)
|
|
if [ -z "$USER_TEMPLATE_IMAGES" ]; then
|
|
echo -e "${YELLOW}⚠️ USER_TEMPLATE_IMAGES nicht konfiguriert${NC}"
|
|
echo " Fallback: Baue alle user-template-* Verzeichnisse (alte Logik)..."
|
|
echo ""
|
|
|
|
BUILT_TEMPLATES=0
|
|
TEMPLATE_DIRS=$(find "${INSTALL_DIR}" -maxdepth 1 -type d -name "user-template*" 2>/dev/null | wc -l)
|
|
TOTAL_BUILDS=$((2 + TEMPLATE_DIRS))
|
|
BUILD_STEP=$((BUILD_STEP + 1))
|
|
|
|
for template_dir in "${INSTALL_DIR}"/user-template*; do
|
|
[ -d "$template_dir" ] || continue
|
|
template_name=$(basename "$template_dir")
|
|
image_name="${template_name}:latest"
|
|
|
|
echo " [$BUILD_STEP/$TOTAL_BUILDS] Baue ${template_name}..."
|
|
|
|
if [[ "$template_name" == *"next"* ]]; then
|
|
echo -e " ${BLUE}Dies kann 2-5 Minuten dauern (npm install + build)...${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
BUILD_LOG="${LOG_FILE}"
|
|
echo "" >> "${LOG_FILE}"
|
|
echo "=== Build: ${template_name} ===" >> "${LOG_FILE}"
|
|
|
|
docker build --no-cache -t "${image_name}" "${template_dir}/" >> "${BUILD_LOG}" 2>&1
|
|
BUILD_EXIT=$?
|
|
grep -E "(Step |#[0-9]+ |Successfully|ERROR|error:|COPY|RUN|FROM)" "${BUILD_LOG}" 2>/dev/null | sed 's/^/ /' || true
|
|
|
|
if [ $BUILD_EXIT -eq 0 ] && docker image inspect "${image_name}" >/dev/null 2>&1; then
|
|
echo ""
|
|
echo -e " ${template_name}: ${GREEN}OK${NC}"
|
|
BUILT_TEMPLATES=$((BUILT_TEMPLATES + 1))
|
|
else
|
|
echo ""
|
|
echo -e " ${template_name}: ${RED}FEHLER${NC}"
|
|
echo " Siehe Build-Log: ${LOG_FILE}"
|
|
tail -50 "${BUILD_LOG}"
|
|
exit 1
|
|
fi
|
|
|
|
BUILD_STEP=$((BUILD_STEP + 1))
|
|
done
|
|
|
|
if [ $BUILT_TEMPLATES -eq 0 ]; then
|
|
echo -e "${RED}FEHLER: Keine Template-Verzeichnisse gefunden!${NC}"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}Alle ${BUILT_TEMPLATES} Template(s) erfolgreich gebaut.${NC}"
|
|
else
|
|
# .env-basiertes Building (neue Logik)
|
|
echo " Baue Templates aus .env Konfiguration..."
|
|
echo ""
|
|
|
|
# Konvertiere zu Array (split by ;)
|
|
IFS=';' read -ra TEMPLATE_IMAGES <<< "$USER_TEMPLATE_IMAGES"
|
|
|
|
# Zähle Templates für Fortschrittsanzeige
|
|
TOTAL_TEMPLATES=${#TEMPLATE_IMAGES[@]}
|
|
TOTAL_BUILDS=$((2 + TOTAL_TEMPLATES))
|
|
BUILD_STEP=$((BUILD_STEP + 1))
|
|
|
|
BUILT_TEMPLATES=0
|
|
|
|
for image_with_tag in "${TEMPLATE_IMAGES[@]}"; do
|
|
# Entferne Whitespace
|
|
image_with_tag=$(echo "$image_with_tag" | xargs)
|
|
|
|
[ -z "$image_with_tag" ] && continue
|
|
|
|
# Extrahiere Verzeichnisnamen (vor dem :)
|
|
template_dir_name="${image_with_tag%%:*}"
|
|
|
|
# Extrahiere Tag (nach dem :)
|
|
template_tag="${image_with_tag##*:}"
|
|
|
|
# Fallback auf :latest wenn kein Tag
|
|
[ -z "$template_tag" ] && template_tag="latest"
|
|
|
|
# Vollständiger Pfad zum Template-Verzeichnis
|
|
template_dir="${INSTALL_DIR}/${template_dir_name}"
|
|
|
|
echo " [$BUILD_STEP/$TOTAL_BUILDS] Baue Template: ${template_dir_name}:${template_tag}"
|
|
|
|
# Prüfe ob Verzeichnis existiert
|
|
if [ ! -d "$template_dir" ]; then
|
|
echo -e " ${RED}❌ Fehler: Template-Verzeichnis nicht gefunden${NC}"
|
|
echo " Definiert in .env: USER_TEMPLATE_IMAGES"
|
|
echo " Erwartetes Verzeichnis: ${template_dir}"
|
|
echo " Überspringe dieses Template."
|
|
echo ""
|
|
BUILD_STEP=$((BUILD_STEP + 1))
|
|
continue
|
|
fi
|
|
|
|
# Dockerfile vorhanden?
|
|
if [ ! -f "${template_dir}/Dockerfile" ]; then
|
|
echo -e " ${RED}❌ Fehler: Kein Dockerfile gefunden${NC}"
|
|
echo " Pfad: ${template_dir}/Dockerfile"
|
|
echo " Überspringe dieses Template."
|
|
echo ""
|
|
BUILD_STEP=$((BUILD_STEP + 1))
|
|
continue
|
|
fi
|
|
|
|
# Special handling für Next.js Templates
|
|
if [[ "$template_dir_name" == *"next"* ]]; then
|
|
echo -e " ${BLUE}Dies kann 2-5 Minuten dauern (npm install + build)...${NC}"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
BUILD_LOG="${LOG_FILE}"
|
|
echo "" >> "${LOG_FILE}"
|
|
echo "=== Build: ${template_dir_name}:${template_tag} ===" >> "${LOG_FILE}"
|
|
|
|
docker build --no-cache -t "${template_dir_name}:${template_tag}" "${template_dir}/" >> "${BUILD_LOG}" 2>&1
|
|
BUILD_EXIT=$?
|
|
|
|
# Gefilterten Output anzeigen
|
|
grep -E "(Step |#[0-9]+ |Successfully|ERROR|error:|COPY|RUN|FROM)" "${BUILD_LOG}" 2>/dev/null | sed 's/^/ /' || true
|
|
|
|
# Prüfe ob Build erfolgreich UND Image existiert
|
|
if [ $BUILD_EXIT -eq 0 ] && docker image inspect "${template_dir_name}:${template_tag}" >/dev/null 2>&1; then
|
|
echo ""
|
|
echo -e " ${template_dir_name}: ${GREEN}OK${NC}"
|
|
BUILT_TEMPLATES=$((BUILT_TEMPLATES + 1))
|
|
else
|
|
echo ""
|
|
echo -e " ${template_dir_name}: ${RED}FEHLER${NC}"
|
|
echo " Siehe Build-Log: ${LOG_FILE}"
|
|
echo " Letzte 50 Zeilen:"
|
|
tail -50 "${BUILD_LOG}"
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
BUILD_STEP=$((BUILD_STEP + 1))
|
|
done
|
|
|
|
if [ $BUILT_TEMPLATES -eq 0 ]; then
|
|
echo -e "${RED}FEHLER: Keine Templates aus USER_TEMPLATE_IMAGES gebaut!${NC}"
|
|
echo "Prüfe .env: USER_TEMPLATE_IMAGES"
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}✅ ${BUILT_TEMPLATES}/${TOTAL_TEMPLATES} Template(s) erfolgreich gebaut.${NC}"
|
|
|
|
# Warne bei ungenutzten Template-Verzeichnissen
|
|
echo ""
|
|
echo " Prüfe auf ungekonfigurierte Template-Verzeichnisse..."
|
|
UNUSED_COUNT=0
|
|
|
|
for template_dir in "${INSTALL_DIR}"/user-template*; do
|
|
[ -d "$template_dir" ] || continue
|
|
template_name=$(basename "$template_dir")
|
|
|
|
# Ist dieses Template in USER_TEMPLATE_IMAGES definiert?
|
|
if [[ ! "$USER_TEMPLATE_IMAGES" =~ "$template_name" ]]; then
|
|
if [ $UNUSED_COUNT -eq 0 ]; then
|
|
echo -e " ${YELLOW}⚠️ Ungekonfigurierte Template-Verzeichnisse:${NC}"
|
|
fi
|
|
echo " - ${template_name} (nicht in USER_TEMPLATE_IMAGES definiert)"
|
|
UNUSED_COUNT=$((UNUSED_COUNT + 1))
|
|
fi
|
|
done
|
|
|
|
if [ $UNUSED_COUNT -gt 0 ]; then
|
|
echo " Hinweis: Diese Templates wurden NICHT gebaut."
|
|
echo " Um sie zu bauen, füge sie zu .env hinzu: USER_TEMPLATE_IMAGES=...;${template_name}:latest"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# Spawner Backend Image bauen
|
|
echo " [$BUILD_STEP/$TOTAL_BUILDS] Baue Spawner API (Flask Backend)..."
|
|
echo ""
|
|
|
|
BUILD_LOG="${LOG_FILE}"
|
|
echo "" >> "${LOG_FILE}"
|
|
echo "=== Build: spawner-api ===" >> "${LOG_FILE}"
|
|
docker build --no-cache -t spawner:latest "${INSTALL_DIR}/" >> "${BUILD_LOG}" 2>&1
|
|
BUILD_EXIT=$?
|
|
|
|
grep -E "(Step |#[0-9]+ |Successfully|ERROR|error:|COPY|RUN|FROM)" "${BUILD_LOG}" 2>/dev/null | sed 's/^/ /' || true
|
|
|
|
if [ $BUILD_EXIT -eq 0 ] && docker image inspect spawner:latest >/dev/null 2>&1; then
|
|
echo ""
|
|
echo -e " spawner-api: ${GREEN}OK${NC}"
|
|
else
|
|
echo ""
|
|
echo -e " spawner-api: ${RED}FEHLER${NC}"
|
|
echo " Siehe Build-Log: ${LOG_FILE}"
|
|
echo " Letzte 50 Zeilen:"
|
|
tail -50 "${BUILD_LOG}"
|
|
exit 1
|
|
fi
|
|
BUILD_STEP=$((BUILD_STEP + 1))
|
|
|
|
# Frontend Image bauen
|
|
if [ -d "${INSTALL_DIR}/frontend" ]; then
|
|
echo " [$BUILD_STEP/$TOTAL_BUILDS] Baue Frontend (Next.js)..."
|
|
echo -e " ${BLUE}Dies kann 2-5 Minuten dauern (npm install + build)...${NC}"
|
|
echo ""
|
|
|
|
BUILD_LOG="${LOG_FILE}"
|
|
echo "" >> "${LOG_FILE}"
|
|
echo "=== Build: spawner-frontend ===" >> "${LOG_FILE}"
|
|
docker build --no-cache -t spawner-frontend:latest "${INSTALL_DIR}/frontend/" >> "${BUILD_LOG}" 2>&1
|
|
BUILD_EXIT=$?
|
|
|
|
grep -E "(Step |#[0-9]+ |Successfully|ERROR|error:|COPY|RUN|FROM)" "${BUILD_LOG}" 2>/dev/null | sed 's/^/ /' || true
|
|
|
|
if [ $BUILD_EXIT -eq 0 ] && docker image inspect spawner-frontend:latest >/dev/null 2>&1; then
|
|
echo ""
|
|
echo -e " spawner-frontend: ${GREEN}OK${NC}"
|
|
else
|
|
echo ""
|
|
echo -e " spawner-frontend: ${RED}FEHLER${NC}"
|
|
echo " Siehe Build-Log: ${LOG_FILE}"
|
|
echo " Letzte 50 Zeilen:"
|
|
tail -50 "${BUILD_LOG}"
|
|
exit 1
|
|
fi
|
|
BUILD_STEP=$((BUILD_STEP + 1))
|
|
fi
|
|
|
|
echo ""
|
|
echo "Alle erforderlichen Images erfolgreich gebaut."
|
|
|
|
# ============================================================
|
|
# 8. Container starten
|
|
# ============================================================
|
|
echo ""
|
|
echo "Starte Container..."
|
|
${COMPOSE_CMD} up -d
|
|
|
|
# Warte auf Health-Check
|
|
echo ""
|
|
echo "Warte auf Spawner-Start..."
|
|
sleep 5
|
|
|
|
# Health-Check fuer API
|
|
SPAWNER_URL="http://localhost:${SPAWNER_PORT:-5000}/health"
|
|
if curl -sf "${SPAWNER_URL}" >/dev/null 2>&1; then
|
|
echo -e " API Health-Check: ${GREEN}OK${NC}"
|
|
else
|
|
echo -e " API Health-Check: ${YELLOW}Noch nicht bereit (normal beim ersten Start)${NC}"
|
|
fi
|
|
|
|
# Health-Check fuer Frontend
|
|
if curl -sf "http://localhost:3000/" >/dev/null 2>&1; then
|
|
echo -e " Frontend Health-Check: ${GREEN}OK${NC}"
|
|
else
|
|
echo -e " Frontend Health-Check: ${YELLOW}Noch nicht bereit (normal beim ersten Start)${NC}"
|
|
fi
|
|
|
|
# ============================================================
|
|
# 9. Fertig
|
|
# ============================================================
|
|
echo ""
|
|
echo "============================================================"
|
|
echo -e " ${GREEN}Installation abgeschlossen!${NC}"
|
|
echo "============================================================"
|
|
echo ""
|
|
echo "Installations-Log: ${LOG_FILE}"
|
|
|
|
# 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"
|
|
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"
|
|
echo " Neustart: ${COMPOSE_CMD} restart"
|
|
echo " Stoppen: ${COMPOSE_CMD} down"
|
|
echo ""
|
|
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"
|
|
echo ""
|
|
echo "Template Configuration (.env):"
|
|
echo " USER_TEMPLATE_IMAGE_DEV=user-service-template:latest"
|
|
echo " USER_TEMPLATE_IMAGE_PROD=user-template-next:latest"
|
|
echo ""
|