diff --git a/.env.example b/.env.example deleted file mode 100644 index 1faebfc..0000000 --- a/.env.example +++ /dev/null @@ -1,35 +0,0 @@ -# ============================================================================= -# n8n Stack — Konfigurationsvorlage -# Kopieren nach .env und alle Werte ausfüllen. -# ============================================================================= - -# --- Domain & URL --- -N8N_HOST=n8n.linix.de -N8N_PROTOCOL=https -WEBHOOK_URL=https://n8n.linix.de/ - -# --- Datenbank --- -POSTGRES_DB=n8n -POSTGRES_USER=n8n -POSTGRES_PASSWORD=HIER_STARKES_PASSWORT_EINTRAGEN - -# --- Redis --- -REDIS_PASSWORD=HIER_STARKES_PASSWORT_EINTRAGEN - -# --- n8n Encryption Key --- -# KRITISCH: Einmalig generieren (z.B. openssl rand -base64 32) und nie mehr ändern! -# Änderung macht alle gespeicherten Credentials unbrauchbar. -N8N_ENCRYPTION_KEY=HIER_ZUFALLSKEY_EINTRAGEN - -# --- SMTP --- -SMTP_HOST=linix.de -SMTP_PORT=587 -SMTP_USER=nutzer@linix.de -SMTP_PASSWORD=SMTP_PASSWORT -SMTP_SENDER=n8n@linix.de - -# --- Worker --- -N8N_WORKER_REPLICAS=1 - -# --- Zeitzone --- -TZ=Europe/Berlin diff --git a/README.md b/README.md index 827d5a6..edeeb01 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,3 @@ -# n8n Stack +# n8n_stack -Produktionsnaher n8n-Stack auf Docker Compose mit PostgreSQL, Redis und Queue-Mode. - -## Architektur - -``` -Internet → n8n.linix.de:443 - → YunoHost (192.168.179.10) — TLS-Terminierung (Let's Encrypt) - → nginx lokal :8088 (192.168.179.124) - → n8n Docker :5678 - ↔ PostgreSQL (n8n-stack-postgres-1) - ↔ Redis (n8n-stack-redis-1) - ← n8n-worker (n8n-stack-n8n-worker-1) -``` - -## Verzeichnisstruktur - -``` -n8n_stack/ -├── compose/docker-compose.yml # Stack-Definition -├── .env # Secrets (600, nie committen!) -├── .env.example # Vorlage -├── scripts/ # Hilfsskripte (700) -├── data/ # Docker-Volumes (gitignoriert) -│ ├── n8n/ -│ ├── postgres/ -│ └── redis/ -├── backups/ # Backups, max. 2 (gitignoriert) -├── imports/workflows/ # Workflow-JSONs zum Importieren -├── imports/credentials/ # Credentials-Exporte (unverschlüsselt!) -├── local-files/ # Dateien für n8n-Container (/files) -└── docs/ # nginx-Configs, Runbooks -``` - -## Start / Stop / Status - -```bash -# Start -docker compose -f compose/docker-compose.yml up -d - -# Stop -docker compose -f compose/docker-compose.yml down - -# Status -docker compose -f compose/docker-compose.yml ps - -# Logs -docker compose -f compose/docker-compose.yml logs -f n8n - -# Worker skalieren (z.B. auf 2) -docker compose -f compose/docker-compose.yml up -d --scale n8n-worker=2 -``` - -## Update - -```bash -bash scripts/update-n8n.sh -``` - -Führt automatisch ein Backup aus, zieht neue Images und startet den Stack neu. - -## Backup / Restore - -```bash -# Backup erstellen (behält immer die letzten 2) -bash scripts/backup-n8n.sh - -# Restore aus Backup-Verzeichnis -bash scripts/restore-n8n.sh backups/backup_20240101_120000 -``` - -## Import / Export von Workflows - -```bash -# Einzelne Workflow-JSON importieren -bash scripts/import-workflow.sh imports/workflows/mein-workflow.json - -# Ganzes Verzeichnis importieren -bash scripts/import-workflow.sh imports/workflows/ - -# Alle Workflows exportieren -bash scripts/export-workflows.sh -``` - -## Import / Export von Credentials - -```bash -# Credentials importieren -bash scripts/import-credentials.sh imports/credentials/creds.json - -# Credentials exportieren (UNVERSCHLÜSSELT — sicher aufbewahren!) -bash scripts/export-credentials.sh -``` - -**Wichtig:** Credential-Exporte enthalten alle Secrets im Klartext. Export-Dateien nach Gebrauch löschen. - -## Übernahme fremder n8n-Projekte - -1. Workflow-JSON-Dateien nach `imports/workflows/` kopieren -2. `bash scripts/import-workflow.sh imports/workflows/.json` -3. Falls Credentials mitgeliefert: `bash scripts/import-credentials.sh ` -4. In der n8n-UI: Credentials der importierten Workflows prüfen und neu verknüpfen - -Bei Voll-Exporten (`.zip` mit mehreren JSONs): entpacken, dann Verzeichnis übergeben. - -## Deployment auf neuem Host - -1. Repo klonen -2. `.env.example` → `.env` kopieren, alle Werte ausfüllen -3. Neuen `N8N_ENCRYPTION_KEY` generieren: `openssl rand -base64 32` -4. `N8N_HOST` und `WEBHOOK_URL` auf die neue Domain anpassen -5. nginx-Config aus `docs/nginx-local-n8n.conf` anpassen und aktivieren -6. YunoHost-Config aus `docs/yunohost-nginx-n8n.conf` anpassen und installieren -7. `docker compose -f compose/docker-compose.yml up -d` - -## Kritische Hinweise - -- **`N8N_ENCRYPTION_KEY`** darf nach dem ersten Start **nie geändert** werden — eine Änderung macht alle gespeicherten Credentials unbrauchbar. Key steht in `.env`. -- **`.env`** nie committen (steht in `.gitignore`). -- Backup vor jedem Update läuft automatisch via `update-n8n.sh`. - -## URLs und Zugangspfade - -| Was | Wert | -|---|---| -| n8n Web-UI | https://n8n.linix.de | -| Erster Login | Beim ersten Aufruf Owner-Account anlegen | -| Secrets-Datei | `./.env` (Rechte 600) | -| Backup-Verzeichnis | `./backups/` | -| nginx lokal | `/etc/nginx/sites-available/n8n` (Port 8088) | -| YunoHost nginx | `/etc/nginx/conf.d/n8n.linix.de.conf` | - -## To-do (manuell auf YunoHost) - -- [ ] DNS: `n8n.linix.de` → `92.50.108.218` (A-Record beim DNS-Provider) -- [ ] YunoHost: Domain hinzufügen: `sudo yunohost domain add n8n.linix.de` -- [ ] YunoHost: Let's Encrypt: `sudo yunohost domain cert install n8n.linix.de` -- [ ] YunoHost: `docs/yunohost-nginx-n8n.conf` → `/etc/nginx/conf.d/n8n.linix.de.conf` kopieren, dann `sudo nginx -t && sudo systemctl reload nginx` -- [ ] SMTP-Credentials in `.env` eintragen (`SMTP_USER`, `SMTP_PASSWORD`) -- [ ] Ersten Owner-Account in n8n anlegen (https://n8n.linix.de beim ersten Aufruf) +Das Projekt installiert und konfiguriert einen vollständigen, produktionsnahen n8n-Stack auf einem Ubuntu-Linux-System. \ No newline at end of file diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml deleted file mode 100644 index 0e36e4c..0000000 --- a/compose/docker-compose.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: n8n-stack - -services: - - postgres: - image: postgres:16-alpine - restart: unless-stopped - environment: - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - ../data/postgres:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - n8n-internal - - redis: - image: redis:7-alpine - restart: unless-stopped - command: redis-server --requirepass ${REDIS_PASSWORD} - volumes: - - ../data/redis:/data - healthcheck: - test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - n8n-internal - - n8n: - image: n8nio/n8n:latest - restart: unless-stopped - ports: - - "127.0.0.1:5678:5678" - environment: - # URL & Protokoll - N8N_HOST: ${N8N_HOST} - N8N_PORT: 5678 - N8N_PROTOCOL: ${N8N_PROTOCOL} - WEBHOOK_URL: ${WEBHOOK_URL} - # Datenbank - DB_TYPE: postgresdb - DB_POSTGRESDB_HOST: postgres - DB_POSTGRESDB_PORT: 5432 - DB_POSTGRESDB_DATABASE: ${POSTGRES_DB} - DB_POSTGRESDB_USER: ${POSTGRES_USER} - DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD} - # Encryption - N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY} - # Queue / Redis - EXECUTIONS_MODE: queue - QUEUE_BULL_REDIS_HOST: redis - QUEUE_BULL_REDIS_PORT: 6379 - QUEUE_BULL_REDIS_PASSWORD: ${REDIS_PASSWORD} - # SMTP - N8N_EMAIL_MODE: smtp - N8N_SMTP_HOST: ${SMTP_HOST} - N8N_SMTP_PORT: ${SMTP_PORT} - N8N_SMTP_USER: ${SMTP_USER} - N8N_SMTP_PASS: ${SMTP_PASSWORD} - N8N_SMTP_SENDER: ${SMTP_SENDER} - N8N_SMTP_SSL: "false" - # User Management - N8N_USER_MANAGEMENT_DISABLED: "false" - # Zeitzone - GENERIC_TIMEZONE: ${TZ} - TZ: ${TZ} - volumes: - - ../data/n8n:/home/node/.n8n - - ../local-files:/files - - ../imports:/imports - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_healthy - networks: - - n8n-internal - - n8n-worker: - image: n8nio/n8n:latest - restart: unless-stopped - command: worker - environment: - # Datenbank - DB_TYPE: postgresdb - DB_POSTGRESDB_HOST: postgres - DB_POSTGRESDB_PORT: 5432 - DB_POSTGRESDB_DATABASE: ${POSTGRES_DB} - DB_POSTGRESDB_USER: ${POSTGRES_USER} - DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD} - # Encryption - N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY} - # Queue / Redis - EXECUTIONS_MODE: queue - QUEUE_BULL_REDIS_HOST: redis - QUEUE_BULL_REDIS_PORT: 6379 - QUEUE_BULL_REDIS_PASSWORD: ${REDIS_PASSWORD} - # Zeitzone - GENERIC_TIMEZONE: ${TZ} - TZ: ${TZ} - volumes: - - ../data/n8n:/home/node/.n8n - - ../local-files:/files - depends_on: - - n8n - networks: - - n8n-internal - -networks: - n8n-internal: - driver: bridge diff --git a/docs/nginx-local-n8n.conf b/docs/nginx-local-n8n.conf deleted file mode 100644 index 2171cc3..0000000 --- a/docs/nginx-local-n8n.conf +++ /dev/null @@ -1,28 +0,0 @@ -# Lokale nginx-Konfiguration auf 192.168.179.124 -# Ablegen als: /etc/nginx/sites-available/n8n -# Aktivieren mit: sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/n8n -# Danach: sudo nginx -t && sudo systemctl reload nginx - -server { - listen 8088; - server_name n8n.linix.de; - - # n8n-Proxy - location / { - proxy_pass http://127.0.0.1:5678; - proxy_http_version 1.1; - - # WebSocket-Support (n8n Editor + Push-Benachrichtigungen) - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; - - proxy_read_timeout 300s; - proxy_send_timeout 300s; - client_max_body_size 50m; - } -} diff --git a/docs/yunohost-nginx-n8n.conf b/docs/yunohost-nginx-n8n.conf deleted file mode 100644 index d2a19df..0000000 --- a/docs/yunohost-nginx-n8n.conf +++ /dev/null @@ -1,38 +0,0 @@ -# YunoHost nginx-Konfiguration (auf dem YunoHost-Server 192.168.179.10) -# -# Voraussetzungen: -# 1. Domain n8n.linix.de in YunoHost hinzufügen: -# sudo yunohost domain add n8n.linix.de -# 2. Let's Encrypt Zertifikat ausstellen: -# sudo yunohost domain cert install n8n.linix.de -# 3. my_webapp als Shim installieren (registriert SSOwat-Permission + nginx-Config): -# sudo yunohost app install my_webapp -a "domain=n8n.linix.de&path=/&is_public=1" -# 4. Den erzeugten location/-Block in der Datei unten durch den Proxy-Block ersetzen: -# /etc/nginx/conf.d/n8n.linix.de.d/my_webapp.conf -# 5. nginx neu laden: -# sudo nginx -t && sudo systemctl reload nginx -# -# ACHTUNG: Bei "yunohost app upgrade my_webapp" wird my_webapp.conf überschrieben -# → Schritt 4 muss danach wiederholt werden. - -# Inhalt von /etc/nginx/conf.d/n8n.linix.de.d/my_webapp.conf -# (ersetzt den von my_webapp generierten location/-Block): - -#sub_path_only rewrite ^/$ / permanent; -location / { - proxy_pass http://192.168.179.124:8088; - proxy_http_version 1.1; - - # WebSocket-Support - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto https; - - proxy_read_timeout 300s; - proxy_send_timeout 300s; - client_max_body_size 50m; -} diff --git a/scripts/backup-n8n.sh b/scripts/backup-n8n.sh deleted file mode 100755 index 432fb94..0000000 --- a/scripts/backup-n8n.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(dirname "$SCRIPT_DIR")" -COMPOSE="$ROOT/compose/docker-compose.yml" -ENV="$ROOT/.env" -BACKUP_DIR="$ROOT/backups" -MAX_BACKUPS=2 - -# shellcheck source=/dev/null -source "$ENV" - -TIMESTAMP=$(date +"%Y%m%d_%H%M%S") -TARGET="$BACKUP_DIR/backup_$TIMESTAMP" -mkdir -p "$TARGET" - -echo "[backup] Starte Backup → $TARGET" - -# PostgreSQL dump -echo "[backup] Datenbank-Dump..." -docker compose -f "$COMPOSE" exec -T postgres \ - pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB" \ - > "$TARGET/postgres.sql" - -# n8n data -echo "[backup] n8n Datendirectory..." -tar -czf "$TARGET/n8n-data.tar.gz" -C "$ROOT/data" n8n - -# Compose-Konfiguration -echo "[backup] Compose + .env..." -cp "$COMPOSE" "$TARGET/docker-compose.yml" -cp "$ENV" "$TARGET/.env" - -echo "[backup] Backup abgeschlossen: $TARGET" - -# Retention: nur die letzten MAX_BACKUPS behalten -EXISTING=$(ls -dt "$BACKUP_DIR"/backup_* 2>/dev/null) -COUNT=$(echo "$EXISTING" | wc -l) -if [ "$COUNT" -gt "$MAX_BACKUPS" ]; then - TO_DELETE=$(echo "$EXISTING" | tail -n +"$((MAX_BACKUPS + 1))") - echo "[backup] Lösche alte Backups:" - echo "$TO_DELETE" - echo "$TO_DELETE" | xargs rm -rf -fi - -echo "[backup] Fertig. Aktuelle Backups:" -ls -lht "$BACKUP_DIR" diff --git a/scripts/export-credentials.sh b/scripts/export-credentials.sh deleted file mode 100755 index 9d3b5eb..0000000 --- a/scripts/export-credentials.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(dirname "$SCRIPT_DIR")" -COMPOSE="$ROOT/compose/docker-compose.yml" - -TIMESTAMP=$(date +"%Y%m%d_%H%M%S") -OUTPUT_DIR="$ROOT/imports/credentials/export_$TIMESTAMP" -mkdir -p "$OUTPUT_DIR" - -echo "[export-credentials] WARNUNG: Export enthält unverschlüsselte Credentials!" -echo "[export-credentials] Exportiere → $OUTPUT_DIR" -docker compose -f "$COMPOSE" exec n8n \ - n8n export:credentials --all --output="/imports/credentials/export_$TIMESTAMP/" - -echo "[export-credentials] Fertig." -echo "[export-credentials] WICHTIG: Export-Verzeichnis sichern und nach Gebrauch löschen!" -ls -lh "$OUTPUT_DIR" diff --git a/scripts/export-workflows.sh b/scripts/export-workflows.sh deleted file mode 100755 index 4169ee7..0000000 --- a/scripts/export-workflows.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(dirname "$SCRIPT_DIR")" -COMPOSE="$ROOT/compose/docker-compose.yml" - -TIMESTAMP=$(date +"%Y%m%d_%H%M%S") -OUTPUT_DIR="$ROOT/imports/workflows/export_$TIMESTAMP" -mkdir -p "$OUTPUT_DIR" - -echo "[export-workflows] Exportiere alle Workflows → $OUTPUT_DIR" -docker compose -f "$COMPOSE" exec n8n \ - n8n export:workflow --all --output="/imports/workflows/export_$TIMESTAMP/" - -echo "[export-workflows] Fertig." -ls -lh "$OUTPUT_DIR" diff --git a/scripts/import-credentials.sh b/scripts/import-credentials.sh deleted file mode 100755 index dd8a354..0000000 --- a/scripts/import-credentials.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(dirname "$SCRIPT_DIR")" -COMPOSE="$ROOT/compose/docker-compose.yml" - -if [ -z "${1:-}" ]; then - echo "Verwendung: $0 " - echo "WARNUNG: Credentials-Exporte sind unverschlüsselt. Datei nach Import löschen!" - exit 1 -fi - -SOURCE="$1" -DEST="$ROOT/imports/credentials/$(basename "$SOURCE")" -cp "$SOURCE" "$DEST" - -echo "[import-credentials] Importiere: $(basename "$SOURCE")" -echo "[import-credentials] WARNUNG: Diese Datei enthält unverschlüsselte Credentials!" -docker compose -f "$COMPOSE" exec n8n \ - n8n import:credentials --input="/imports/credentials/$(basename "$SOURCE")" - -echo "[import-credentials] Fertig." -echo "[import-credentials] Empfehlung: Quelldatei jetzt löschen: rm '$SOURCE'" diff --git a/scripts/import-workflow.sh b/scripts/import-workflow.sh deleted file mode 100755 index 20f6c0d..0000000 --- a/scripts/import-workflow.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(dirname "$SCRIPT_DIR")" -COMPOSE="$ROOT/compose/docker-compose.yml" - -if [ -z "${1:-}" ]; then - echo "Verwendung: $0 " - exit 1 -fi - -SOURCE="$1" - -if [ -d "$SOURCE" ]; then - # Verzeichnis: alle JSON-Dateien importieren - echo "[import-workflow] Importiere alle Workflows aus: $SOURCE" - for f in "$SOURCE"/*.json; do - [ -f "$f" ] || continue - DEST="$ROOT/imports/workflows/$(basename "$f")" - cp "$f" "$DEST" - echo "[import-workflow] → $(basename "$f")" - docker compose -f "$COMPOSE" exec n8n \ - n8n import:workflow --input="/imports/workflows/$(basename "$f")" - done -else - # Einzelne Datei - DEST="$ROOT/imports/workflows/$(basename "$SOURCE")" - cp "$SOURCE" "$DEST" - echo "[import-workflow] Importiere: $(basename "$SOURCE")" - docker compose -f "$COMPOSE" exec n8n \ - n8n import:workflow --input="/imports/workflows/$(basename "$SOURCE")" -fi - -echo "[import-workflow] Fertig." diff --git a/scripts/restore-n8n.sh b/scripts/restore-n8n.sh deleted file mode 100755 index 89443ec..0000000 --- a/scripts/restore-n8n.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(dirname "$SCRIPT_DIR")" -COMPOSE="$ROOT/compose/docker-compose.yml" -ENV="$ROOT/.env" - -if [ -z "${1:-}" ]; then - echo "Verwendung: $0 " - echo "Verfügbare Backups:" - ls -lht "$ROOT/backups/" - exit 1 -fi - -BACKUP="$1" -if [ ! -d "$BACKUP" ]; then - echo "Fehler: Verzeichnis '$BACKUP' nicht gefunden." - exit 1 -fi - -# shellcheck source=/dev/null -source "$ENV" - -echo "[restore] Stelle wieder her aus: $BACKUP" -echo "[restore] Stack wird gestoppt..." -docker compose -f "$COMPOSE" down - -# n8n data -echo "[restore] n8n Daten..." -rm -rf "$ROOT/data/n8n" -mkdir -p "$ROOT/data/n8n" -tar -xzf "$BACKUP/n8n-data.tar.gz" -C "$ROOT/data" - -# Postgres -echo "[restore] Datenbank wird gestartet..." -docker compose -f "$COMPOSE" up -d postgres -echo "[restore] Warte auf PostgreSQL..." -sleep 10 - -echo "[restore] Datenbank-Restore..." -docker compose -f "$COMPOSE" exec -T postgres \ - psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" \ - < "$BACKUP/postgres.sql" - -echo "[restore] Stack wird neu gestartet..." -docker compose -f "$COMPOSE" up -d - -echo "[restore] Fertig. Stack-Status:" -docker compose -f "$COMPOSE" ps diff --git a/scripts/update-n8n.sh b/scripts/update-n8n.sh deleted file mode 100755 index 919360d..0000000 --- a/scripts/update-n8n.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(dirname "$SCRIPT_DIR")" -COMPOSE="$ROOT/compose/docker-compose.yml" - -echo "[update] Starte Pre-Update-Backup..." -bash "$SCRIPT_DIR/backup-n8n.sh" - -echo "[update] Neue Images ziehen..." -docker compose -f "$COMPOSE" pull - -echo "[update] Stack neu starten..." -docker compose -f "$COMPOSE" up -d - -echo "[update] Warte 15s auf Start..." -sleep 15 - -echo "[update] Stack-Status nach Update:" -docker compose -f "$COMPOSE" ps - -echo "[update] Update abgeschlossen."