commit 364e058b8c44e413d774a53c5100cf294205ad14 Author: Martin Eckardt Date: Sun Dec 28 17:12:43 2025 +0100 Proxmox Infrastruktur - Vollstaendige Konfiguration Enthaelt: - Docker Compose mit allen Services (Nextcloud, Vaultwarden, n8n, etc.) - nginx Reverse Proxy Konfiguration mit Rate Limiting - WireGuard VPN Template - Backup und Health-Check Scripts - Deployment Script - Ausfuehrliche Dokumentation und Troubleshooting Guide Services: - Isolierte Netzwerke pro Service - Resource Limits (CPU/Memory) - Health Checks - Logging Konfiguration Sicherheit: - .env Template ohne Secrets - Rate Limiting auf nginx - TLS 1.2+ only - Security Headers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 diff --git a/README.md b/README.md new file mode 100644 index 0000000..844cbb5 --- /dev/null +++ b/README.md @@ -0,0 +1,187 @@ +# Proxmox Infrastruktur + +Self-Hosted Services auf Proxmox VE mit Docker. + +## Architektur + +``` +Internet + | + v +[Windows VPS: 217.154.65.205] + - nginx Reverse Proxy + - SSL (Let's Encrypt via win-acme) + - WireGuard Server + | + | WireGuard Tunnel (10.0.0.0/24) + v +[Proxmox: 192.168.178.111 / 10.0.0.2] + - Docker Host + - Alle Services als Container +``` + +## Services + +| Service | Port | URL (Extern) | Beschreibung | +|---------|------|--------------|--------------| +| Nextcloud | 8081 | eckardt-cloud.duckdns.org | Cloud Storage | +| Vaultwarden | 8083 | eckardt-vault.duckdns.org/vault/ | Passwort Manager | +| n8n | 5678 | eckardt-vault.duckdns.org/n8n/ | Workflow Automation | +| Gitea | 3000 | eckardt-git.duckdns.org | Git Repository | +| Websites | 8082 | eckardt-vault.duckdns.org | Statische Websites | +| API | 8000 | eckardt-vault.duckdns.org/api/ | FastAPI Backend | +| Audiobookshelf | 13378 | (intern) | Audiobook Server | + +## Quick Start + +### Voraussetzungen + +- Proxmox VE oder Debian/Ubuntu mit Docker +- WireGuard fuer externen Zugriff +- Min. 4GB RAM, 50GB Speicher + +### Installation + +```bash +# Repository klonen +git clone https://eckardt-git.duckdns.org/Martin/proxmox-infrastruktur.git +cd proxmox-infrastruktur + +# Environment-Variablen konfigurieren +cp docker/.env.template docker/.env +nano docker/.env # Passwoerter anpassen! + +# Deployment starten +sudo ./scripts/deploy.sh +``` + +## Verzeichnisstruktur + +``` +proxmox-infrastruktur/ +├── docker/ +│ ├── docker-compose.yml # Haupt-Konfiguration +│ ├── .env.template # Environment Template +│ └── .gitignore # Secrets ausschliessen +├── configs/ +│ ├── nginx/ +│ │ └── nginx.conf # VPS Reverse Proxy +│ └── wireguard/ +│ └── wg0.conf.template # WireGuard Template +├── scripts/ +│ ├── deploy.sh # Installations-Script +│ ├── backup.sh # Backup-Script +│ └── health-check.sh # Health-Check Script +├── docs/ +│ ├── INSTALL.md # Detaillierte Installation +│ └── TROUBLESHOOTING.md # Problemloesungen +└── README.md # Diese Datei +``` + +## Wartung + +### Health Check + +```bash +# Alle Services pruefen +/opt/scripts/health-check.sh + +# Einzelnen Container pruefen +docker inspect --format='{{.State.Health.Status}}' nextcloud +``` + +### Backup + +```bash +# Vollstaendiges Backup +/opt/scripts/backup.sh all + +# Einzelner Service +/opt/scripts/backup.sh nextcloud + +# Automatisches Backup (crontab -e) +0 3 * * * /opt/scripts/backup.sh all >> /var/log/backup.log 2>&1 +``` + +### Updates + +```bash +cd /opt/docker + +# Alle Container aktualisieren +docker compose pull +docker compose up -d + +# Einzelnen Container aktualisieren +docker compose pull nextcloud +docker compose up -d nextcloud +``` + +### Logs + +```bash +# Alle Container +docker compose logs -f + +# Einzelner Container +docker logs -f nextcloud + +# Letzte 100 Zeilen +docker logs --tail 100 nextcloud +``` + +## Sicherheit + +### Implementierte Massnahmen + +- **Isolierte Netzwerke**: Jeder Service hat sein eigenes Docker-Netzwerk +- **Resource Limits**: CPU und Memory Limits pro Container +- **Health Checks**: Automatische Ueberwachung aller Services +- **Rate Limiting**: nginx begrenzt Anfragen pro IP +- **TLS 1.2+**: Nur sichere Verschluesselung +- **Security Headers**: X-Frame-Options, X-Content-Type-Options, etc. + +### Zu beachten + +- `.env` Datei NIEMALS committen +- Admin-Tokens regelmaessig rotieren +- Backups extern speichern +- Updates zeitnah einspielen + +## Netzwerk + +### WireGuard Tunnel + +``` +VPS (Server) Proxmox (Client) +10.0.0.1 <--> 10.0.0.2 +217.154.65.205 192.168.178.111 +``` + +### Ports + +| Port | Service | Zugriff | +|------|---------|---------| +| 51820/UDP | WireGuard | VPS extern | +| 80 | nginx HTTP | VPS extern | +| 443 | nginx HTTPS | VPS extern | +| 3000 | Gitea Web | Proxmox intern | +| 2222 | Gitea SSH | Proxmox intern | + +## Troubleshooting + +Siehe [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) fuer: + +- Container startet nicht +- Permission denied Fehler +- Netzwerk-Probleme +- SSL-Zertifikat Fehler +- Backup-Probleme + +## Changelog + +### 2024-12-28 +- Initial Setup mit allen Services +- Gitea auf eigener Subdomain +- Security Hardening (Registration disabled, Rate Limiting) +- Health Checks und Resource Limits diff --git a/configs/nginx/nginx.conf b/configs/nginx/nginx.conf new file mode 100644 index 0000000..4c3c0da --- /dev/null +++ b/configs/nginx/nginx.conf @@ -0,0 +1,177 @@ +# nginx Reverse Proxy Konfiguration +# Pfad auf VPS: C:\nginx\conf\nginx.conf +# Server: Windows VPS 217.154.65.205 + +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + keepalive_timeout 65; + client_max_body_size 10G; + server_names_hash_bucket_size 64; + + # Security Headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Rate Limiting + limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; + + # ============================================ + # HTTP -> HTTPS Redirects + # ============================================ + server { + listen 80; + server_name eckardt-vault.duckdns.org; + location /.well-known/acme-challenge/ { + alias C:/nginx/html/.well-known/acme-challenge/; + } + location / { + return 301 https://eckardt-vault.duckdns.org$request_uri; + } + } + + server { + listen 80; + server_name eckardt-cloud.duckdns.org; + location /.well-known/acme-challenge/ { + alias C:/nginx/html/.well-known/acme-challenge/; + } + location / { + return 301 https://eckardt-cloud.duckdns.org$request_uri; + } + } + + server { + listen 80; + server_name eckardt-git.duckdns.org; + location /.well-known/acme-challenge/ { + alias C:/nginx/html/.well-known/acme-challenge/; + } + location / { + return 301 https://eckardt-git.duckdns.org$request_uri; + } + } + + # ============================================ + # eckardt-vault.duckdns.org - Main Services + # ============================================ + server { + listen 443 ssl http2; + server_name eckardt-vault.duckdns.org; + + ssl_certificate C:/nginx/ssl/eckardt-vault.duckdns.org-chain.pem; + ssl_certificate_key C:/nginx/ssl/eckardt-vault.duckdns.org-key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; + ssl_prefer_server_ciphers off; + + # Vaultwarden - Password Manager + location /vault/ { + limit_req zone=login burst=5 nodelay; + proxy_pass http://10.0.0.2:8083/; + proxy_ssl_verify off; + 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 $scheme; + } + + # n8n - Workflow Automation + location /n8n/ { + limit_req zone=general burst=20 nodelay; + proxy_pass http://10.0.0.2:5678/; + proxy_http_version 1.1; + 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 $scheme; + } + + # API - FastAPI Backend + location /api/ { + limit_req zone=general burst=50 nodelay; + proxy_pass http://10.0.0.2:8000/; + 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 $scheme; + } + + # Websites - Static Content (Default) + location / { + limit_req zone=general burst=20 nodelay; + proxy_pass http://10.0.0.2:8082/; + 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 $scheme; + } + } + + # ============================================ + # eckardt-cloud.duckdns.org - Nextcloud + # ============================================ + server { + listen 443 ssl http2; + server_name eckardt-cloud.duckdns.org; + + ssl_certificate C:/nginx/ssl/eckardt-cloud.duckdns.org-chain.pem; + ssl_certificate_key C:/nginx/ssl/eckardt-cloud.duckdns.org-key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; + ssl_prefer_server_ciphers off; + + location / { + limit_req zone=general burst=50 nodelay; + proxy_pass http://10.0.0.2:8081/; + 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 $scheme; + + # Nextcloud specific + proxy_max_temp_file_size 10240m; + proxy_connect_timeout 300; + proxy_send_timeout 300; + proxy_read_timeout 300; + } + } + + # ============================================ + # eckardt-git.duckdns.org - Gitea + # ============================================ + server { + listen 443 ssl http2; + server_name eckardt-git.duckdns.org; + + ssl_certificate C:/nginx/ssl/eckardt-git.duckdns.org-chain.pem; + ssl_certificate_key C:/nginx/ssl/eckardt-git.duckdns.org-key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; + ssl_prefer_server_ciphers off; + + location / { + limit_req zone=general burst=30 nodelay; + proxy_pass http://10.0.0.2:3000/; + proxy_http_version 1.1; + 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 $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + client_max_body_size 1G; + } + } +} diff --git a/configs/wireguard/wg0.conf.template b/configs/wireguard/wg0.conf.template new file mode 100644 index 0000000..c4365e5 --- /dev/null +++ b/configs/wireguard/wg0.conf.template @@ -0,0 +1,32 @@ +# WireGuard Konfiguration - Proxmox Client +# Pfad auf Proxmox: /etc/wireguard/wg0.conf +# +# WICHTIG: PrivateKey und PublicKey muessen fuer jede Installation +# neu generiert werden! +# +# Keys generieren: +# wg genkey | tee privatekey | wg pubkey > publickey +# +# Aktivieren: +# systemctl enable wg-quick@wg0 +# systemctl start wg-quick@wg0 + +[Interface] +# Eigener Private Key (GEHEIM!) +PrivateKey = + +# IP im WireGuard Tunnel-Netzwerk +Address = 10.0.0.2/24 + +[Peer] +# Public Key des VPS Servers +PublicKey = + +# VPS Server IP und Port +Endpoint = 217.154.65.205:51820 + +# Erlaubte IPs (nur Tunnel-Netzwerk) +AllowedIPs = 10.0.0.0/24 + +# Keepalive fuer NAT-Traversal +PersistentKeepalive = 25 diff --git a/docker/.env.template b/docker/.env.template new file mode 100644 index 0000000..840bf62 --- /dev/null +++ b/docker/.env.template @@ -0,0 +1,21 @@ +# Proxmox Infrastruktur - Environment Variables +# WICHTIG: Diese Datei kopieren nach .env und Werte anpassen! +# Die .env Datei NIEMALS committen! + +# ============================================ +# NEXTCLOUD +# ============================================ +NEXTCLOUD_DB_PASSWORD= +NEXTCLOUD_DB_ROOT_PASSWORD= + +# ============================================ +# VAULTWARDEN +# ============================================ +# Admin Token generieren: openssl rand -base64 48 +VAULTWARDEN_ADMIN_TOKEN= + +# ============================================ +# N8N +# ============================================ +N8N_USER=admin +N8N_PASSWORD= diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 0000000..4cf4143 --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1,11 @@ +# Niemals Secrets committen! +.env +*.env +!.env.template + +# Backup-Dateien +*.bak +*.backup + +# Logs +*.log diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..5fc328e --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,270 @@ +services: + # ============================================ + # NEXTCLOUD - Cloud Storage + # ============================================ + nextcloud: + image: nextcloud:latest + container_name: nextcloud + restart: unless-stopped + security_opt: + - apparmor=unconfined + - seccomp=unconfined + ports: + - "8081:80" + volumes: + - /opt/docker/nextcloud/data:/var/www/html + environment: + - MYSQL_HOST=nextcloud-db + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=${NEXTCLOUD_DB_PASSWORD} + depends_on: + nextcloud-db: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/status.php"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + deploy: + resources: + limits: + cpus: '2.0' + memory: 2G + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - nextcloud-net + + nextcloud-db: + image: mariadb:10 + container_name: nextcloud-db + restart: unless-stopped + security_opt: + - apparmor=unconfined + - seccomp=unconfined + volumes: + - /opt/docker/nextcloud/db:/var/lib/mysql + environment: + - MYSQL_ROOT_PASSWORD=${NEXTCLOUD_DB_ROOT_PASSWORD} + - MYSQL_DATABASE=nextcloud + - MYSQL_USER=nextcloud + - MYSQL_PASSWORD=${NEXTCLOUD_DB_PASSWORD} + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + deploy: + resources: + limits: + cpus: '1.0' + memory: 1G + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - nextcloud-net + + # ============================================ + # VAULTWARDEN - Password Manager + # ============================================ + vaultwarden: + image: vaultwarden/server:latest + container_name: vaultwarden + restart: unless-stopped + security_opt: + - apparmor=unconfined + - seccomp=unconfined + ports: + - "8083:80" + volumes: + - /opt/docker/vaultwarden:/data + environment: + - ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN} + - SIGNUPS_ALLOWED=false + - INVITATIONS_ALLOWED=true + - SHOW_PASSWORD_HINT=false + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/alive"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - vaultwarden-net + + # ============================================ + # N8N - Workflow Automation + # ============================================ + n8n: + image: n8nio/n8n:latest + container_name: n8n + restart: unless-stopped + security_opt: + - apparmor=unconfined + - seccomp=unconfined + ports: + - "5678:5678" + volumes: + - /opt/docker/n8n:/home/node/.n8n + environment: + - N8N_BASIC_AUTH_ACTIVE=true + - N8N_BASIC_AUTH_USER=${N8N_USER} + - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD} + - N8N_PATH=/n8n/ + - N8N_EDITOR_BASE_URL=https://eckardt-vault.duckdns.org/n8n/ + - WEBHOOK_URL=https://eckardt-vault.duckdns.org/n8n/ + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:5678/healthz"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + cpus: '1.0' + memory: 1G + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - n8n-net + + # ============================================ + # AUDIOBOOKSHELF - Audiobook Server + # ============================================ + audiobookshelf: + image: ghcr.io/advplyr/audiobookshelf:latest + container_name: audiobookshelf + restart: unless-stopped + security_opt: + - apparmor=unconfined + - seccomp=unconfined + ports: + - "13378:80" + volumes: + - /opt/docker/audiobookshelf/audiobooks:/audiobooks + - /opt/docker/audiobookshelf/podcasts:/podcasts + - /opt/docker/audiobookshelf/config:/config + - /opt/docker/audiobookshelf/metadata:/metadata + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:80/healthcheck"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - audiobookshelf-net + + # ============================================ + # WEBSITES - Static Website Hosting + # ============================================ + websites: + image: nginx:alpine + container_name: websites + restart: unless-stopped + security_opt: + - apparmor=unconfined + - seccomp=unconfined + ports: + - "8082:80" + volumes: + - /opt/docker/websites/html:/usr/share/nginx/html:ro + - /opt/docker/websites/conf:/etc/nginx/conf.d:ro + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost/"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + cpus: '0.5' + memory: 128M + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - websites-net + + # ============================================ + # API - FastAPI Backend + # ============================================ + api: + image: python:3.11-slim + container_name: api-server + restart: unless-stopped + security_opt: + - apparmor=unconfined + - seccomp=unconfined + working_dir: /app + ports: + - "8000:8000" + volumes: + - /opt/docker/api:/app + command: sh -c "pip install --quiet fastapi uvicorn httpx && python -m uvicorn main:app --host 0.0.0.0 --port 8000" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + networks: + - api-net + +# ============================================ +# NETWORKS - Isolierte Netzwerke pro Service +# ============================================ +networks: + nextcloud-net: + driver: bridge + vaultwarden-net: + driver: bridge + n8n-net: + driver: bridge + audiobookshelf-net: + driver: bridge + websites-net: + driver: bridge + api-net: + driver: bridge diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 0000000..90ec781 --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,275 @@ +# Installationsanleitung + +Detaillierte Anleitung zur Erstinstallation der Proxmox Infrastruktur. + +## Voraussetzungen + +### Hardware + +- **CPU:** Min. 2 Cores (4 empfohlen) +- **RAM:** Min. 4GB (8GB empfohlen) +- **Speicher:** Min. 50GB SSD + +### Software + +- Proxmox VE 7.x/8.x oder Debian 11/12 / Ubuntu 22.04+ +- Docker CE (wird automatisch installiert) +- WireGuard (fuer externen Zugriff) + +### Netzwerk + +- Statische IP oder DHCP-Reservierung +- Zugriff auf Port 51820/UDP (WireGuard) +- Domain/Subdomain (z.B. via DuckDNS) + +--- + +## Teil 1: Proxmox Server vorbereiten + +### 1.1 Docker installieren + +```bash +# System aktualisieren +apt update && apt upgrade -y + +# Docker installieren +curl -fsSL https://get.docker.com | sh + +# Docker beim Boot starten +systemctl enable docker +systemctl start docker + +# Pruefen +docker --version +docker compose version +``` + +### 1.2 WireGuard installieren + +```bash +# WireGuard installieren +apt install wireguard -y + +# Keys generieren +wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey + +# Berechtigungen setzen +chmod 600 /etc/wireguard/privatekey + +# Keys anzeigen +cat /etc/wireguard/privatekey +cat /etc/wireguard/publickey +``` + +### 1.3 WireGuard konfigurieren + +```bash +cat > /etc/wireguard/wg0.conf << 'EOF' +[Interface] +PrivateKey = +Address = 10.0.0.2/24 + +[Peer] +PublicKey = +Endpoint = :51820 +AllowedIPs = 10.0.0.0/24 +PersistentKeepalive = 25 +EOF + +# Aktivieren +systemctl enable wg-quick@wg0 +systemctl start wg-quick@wg0 + +# Testen +ping 10.0.0.1 +``` + +--- + +## Teil 2: Repository klonen und konfigurieren + +### 2.1 Repository klonen + +```bash +cd /opt +git clone https://eckardt-git.duckdns.org/Martin/proxmox-infrastruktur.git +cd proxmox-infrastruktur +``` + +### 2.2 Environment-Variablen konfigurieren + +```bash +# Template kopieren +cp docker/.env.template docker/.env + +# Sichere Passwoerter generieren +echo "NEXTCLOUD_DB_PASSWORD=$(openssl rand -hex 16)" +echo "NEXTCLOUD_DB_ROOT_PASSWORD=$(openssl rand -hex 16)" +echo "VAULTWARDEN_ADMIN_TOKEN=$(openssl rand -base64 48)" +echo "N8N_PASSWORD=$(openssl rand -hex 16)" + +# Datei bearbeiten und Passwoerter eintragen +nano docker/.env +``` + +### 2.3 Verzeichnisse erstellen + +```bash +# Alle Datenverzeichnisse erstellen +mkdir -p /opt/docker/nextcloud/{data,db} +mkdir -p /opt/docker/vaultwarden +mkdir -p /opt/docker/n8n +mkdir -p /opt/docker/audiobookshelf/{audiobooks,podcasts,config,metadata} +mkdir -p /opt/docker/websites/{html,conf} +mkdir -p /opt/docker/api +mkdir -p /opt/docker/gitea +mkdir -p /opt/backups +mkdir -p /opt/scripts + +# Berechtigungen setzen +chown -R 1000:1000 /opt/docker/* +``` + +--- + +## Teil 3: Services deployen + +### 3.1 Docker Compose kopieren + +```bash +cp docker/docker-compose.yml /opt/docker/ +cp docker/.env /opt/docker/ +``` + +### 3.2 Container starten + +```bash +cd /opt/docker +docker compose pull +docker compose up -d +``` + +### 3.3 Status pruefen + +```bash +docker compose ps +docker compose logs -f +``` + +--- + +## Teil 4: Services einrichten + +### 4.1 Nextcloud + +1. Browser oeffnen: `http://:8081` +2. Admin-Account erstellen +3. Datenbank: MySQL/MariaDB + - Host: `nextcloud-db` + - Datenbank: `nextcloud` + - User: `nextcloud` + - Passwort: aus .env +4. Installation abschliessen + +### 4.2 Vaultwarden + +1. Browser oeffnen: `http://:8083` +2. Account erstellen +3. Admin-Panel: `http://:8083/admin` + - Token aus .env eingeben +4. Einstellungen anpassen: + - Registrierung deaktivieren + - Invite only aktivieren + +### 4.3 Gitea + +Separates Repository: [proxmox-gitea](https://eckardt-git.duckdns.org/Martin/proxmox-gitea) + +```bash +cd /opt/docker/gitea +# docker-compose.yml aus proxmox-gitea Repository +docker compose up -d +``` + +1. Browser oeffnen: `http://:3000` +2. Datenbank: SQLite3 +3. Admin-Account erstellen + +### 4.4 n8n + +1. Browser oeffnen: `http://:5678` +2. Login mit Credentials aus .env + +--- + +## Teil 5: VPS Reverse Proxy (Optional) + +Fuer externen Zugriff ueber das Internet. + +### 5.1 WireGuard auf VPS + +```bash +# Auf Windows VPS mit WireGuard installiert +# Config in C:\Program Files\WireGuard\wg0.conf + +[Interface] +PrivateKey = +Address = 10.0.0.1/24 +ListenPort = 51820 + +[Peer] +PublicKey = +AllowedIPs = 10.0.0.2/32 +``` + +### 5.2 nginx auf VPS + +```bash +# nginx.conf aus configs/nginx/nginx.conf verwenden +# Pfad auf Windows: C:\nginx\conf\nginx.conf +``` + +### 5.3 SSL-Zertifikate + +```cmd +# win-acme fuer Let's Encrypt +C:\winacme\wacs.exe --target manual --host eckardt-vault.duckdns.org --validation filesystem --webroot C:\nginx\html --store pemfiles --pemfilespath C:\nginx\ssl +``` + +--- + +## Teil 6: Automatisierung + +### 6.1 Scripts installieren + +```bash +cp scripts/*.sh /opt/scripts/ +chmod +x /opt/scripts/*.sh +``` + +### 6.2 Backup Cronjob + +```bash +# crontab -e +0 3 * * * /opt/scripts/backup.sh all >> /var/log/backup.log 2>&1 +``` + +### 6.3 Health-Check Cronjob + +```bash +# crontab -e +*/5 * * * * /opt/scripts/health-check.sh >> /var/log/health-check.log 2>&1 +``` + +--- + +## Fertig! + +Nach der Installation sollten alle Services laufen: + +```bash +# Pruefen +/opt/scripts/health-check.sh +``` + +Bei Problemen: [TROUBLESHOOTING.md](TROUBLESHOOTING.md) diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..7847c44 --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,351 @@ +# Troubleshooting Guide + +## Inhaltsverzeichnis + +1. [Container Probleme](#1-container-probleme) +2. [Netzwerk Probleme](#2-netzwerk-probleme) +3. [SSL/Zertifikat Probleme](#3-sslzertifikat-probleme) +4. [Service-spezifische Probleme](#4-service-spezifische-probleme) +5. [Backup/Restore Probleme](#5-backuprestore-probleme) +6. [Performance Probleme](#6-performance-probleme) + +--- + +## 1. Container Probleme + +### Container startet nicht + +**Symptom:** `docker compose up -d` laeuft, aber Container ist nicht aktiv + +```bash +# Status pruefen +docker compose ps + +# Logs anzeigen +docker logs + +# Detaillierte Infos +docker inspect +``` + +**Haeufige Ursachen:** + +1. **Port bereits belegt** + ```bash + # Port pruefen + netstat -tulpn | grep + + # Prozess beenden oder Port aendern + ``` + +2. **Volume-Berechtigungen** + ```bash + # Berechtigungen korrigieren + chown -R 1000:1000 /opt/docker/ + chmod -R 755 /opt/docker/ + ``` + +3. **Fehlende .env Datei** + ```bash + # Pruefen ob .env existiert + ls -la /opt/docker/.env + + # Aus Template erstellen + cp docker/.env.template docker/.env + ``` + +### "Permission denied" auf Proxmox + +**Symptom:** `socketpair() failed (13: Permission denied)` + +**Loesung:** security_opt in docker-compose.yml: +```yaml +security_opt: + - apparmor=unconfined + - seccomp=unconfined +``` + +### Container restarts staendig + +**Symptom:** Container Status zeigt "Restarting" + +```bash +# Exit-Code pruefen +docker inspect --format='{{.State.ExitCode}}' + +# Letzten Fehler anzeigen +docker logs --tail 50 + +# Health-Check deaktivieren zum Debuggen +docker compose up -d --no-healthcheck +``` + +--- + +## 2. Netzwerk Probleme + +### WireGuard Tunnel nicht verbunden + +**Symptom:** Keine Verbindung zu 10.0.0.x Adressen + +```bash +# WireGuard Status +wg show wg0 + +# Interface pruefen +ip addr show wg0 + +# Tunnel neu starten +systemctl restart wg-quick@wg0 + +# Logs pruefen +journalctl -u wg-quick@wg0 -n 50 +``` + +**Checkliste:** +- [ ] PrivateKey/PublicKey korrekt? +- [ ] Endpoint IP:Port erreichbar? +- [ ] Firewall-Regeln auf VPS? +- [ ] PersistentKeepalive gesetzt? + +### Service nicht extern erreichbar + +```bash +# 1. Container laeuft? +docker ps | grep + +# 2. Port offen auf Proxmox? +curl http://localhost: + +# 3. WireGuard Tunnel aktiv? +ping 10.0.0.1 # VPS von Proxmox + +# 4. nginx Config auf VPS testen +cd C:\nginx && nginx.exe -t + +# 5. nginx neu laden +net stop nginx && net start nginx +``` + +### DNS-Probleme + +```bash +# DuckDNS IP pruefen +nslookup eckardt-vault.duckdns.org + +# Eigene externe IP pruefen +curl ifconfig.me + +# DuckDNS manuell aktualisieren +curl "https://www.duckdns.org/update?domains=eckardt-vault&token=&ip=" +``` + +--- + +## 3. SSL/Zertifikat Probleme + +### Zertifikat abgelaufen + +**Auf Windows VPS:** +```cmd +# Zertifikat erneuern +cd C:\winacme +wacs.exe --renew --force + +# nginx neu laden +net stop nginx && net start nginx +``` + +### Let's Encrypt Rate Limit + +**Symptom:** "too many certificates already issued" + +**Loesung:** +- 5 Zertifikate pro Domain pro Woche +- Warten oder Subdomain aendern +- Staging-Umgebung zum Testen nutzen + +### Mixed Content Warnung + +**Symptom:** Browser zeigt "unsichere Inhalte" + +**Loesung:** Alle Services muessen HTTPS nutzen +```nginx +# In nginx.conf +proxy_set_header X-Forwarded-Proto $scheme; +``` + +--- + +## 4. Service-spezifische Probleme + +### Nextcloud + +**"Maintenance mode is enabled"** +```bash +docker exec nextcloud php occ maintenance:mode --off +``` + +**Datei-Upload schlaegt fehl** +```bash +# PHP Limits anpassen +docker exec nextcloud bash -c 'echo "upload_max_filesize=10G" >> /usr/local/etc/php/conf.d/uploads.ini' +docker exec nextcloud bash -c 'echo "post_max_size=10G" >> /usr/local/etc/php/conf.d/uploads.ini' +docker restart nextcloud +``` + +**"Trusted Domain" Fehler** +```bash +docker exec nextcloud php occ config:system:set trusted_domains 1 --value=eckardt-cloud.duckdns.org +``` + +### Vaultwarden + +**Admin-Seite nicht erreichbar** +```bash +# Admin-Token pruefen +docker logs vaultwarden | grep -i admin + +# URL: /admin mit Token aus .env +``` + +**Sync-Fehler in Clients** +```bash +# Verbindung testen +curl -v https://eckardt-vault.duckdns.org/vault/api/alive +``` + +### Gitea + +**SSH Clone funktioniert nicht** +```bash +# SSH-Verbindung testen +ssh -T -p 2222 git@192.168.178.111 + +# Authorized Keys pruefen +docker exec gitea cat /data/git/.ssh/authorized_keys +``` + +**"Unable to find user" nach Restart** +```bash +# Gitea User pruefen +docker exec gitea gitea admin user list +``` + +### n8n + +**Webhooks funktionieren nicht** +```bash +# Webhook-URL pruefen +# Muss WEBHOOK_URL in .env auf externe URL zeigen +docker logs n8n | grep -i webhook +``` + +--- + +## 5. Backup/Restore Probleme + +### Backup schlaegt fehl + +```bash +# Berechtigungen pruefen +ls -la /opt/backups/ + +# Speicherplatz pruefen +df -h /opt/backups/ + +# Manuell testen +/opt/scripts/backup.sh nextcloud +``` + +### Restore durchfuehren + +```bash +# Container stoppen +docker compose stop + +# Altes Volume loeschen +rm -rf /opt/docker//* + +# Backup entpacken +tar -xzf /opt/backups/_YYYYMMDD.tar.gz -C /opt/docker// + +# Container starten +docker compose up -d +``` + +--- + +## 6. Performance Probleme + +### Hohe CPU-Last + +```bash +# Top Prozesse +docker stats --no-stream + +# Ressourcen-Limits pruefen +docker inspect --format='{{.HostConfig.NanoCpus}}' +``` + +### Speicher voll + +```bash +# Docker Cleanup +docker system prune -a --volumes + +# Alte Logs loeschen +truncate -s 0 /var/lib/docker/containers/*/*-json.log + +# Alte Backups loeschen +find /opt/backups -mtime +30 -delete +``` + +### Langsame Antwortzeiten + +```bash +# Netzwerk-Latenz testen +ping -c 10 10.0.0.2 + +# Container-Ressourcen +docker stats + +# Disk I/O +iostat -x 1 5 +``` + +--- + +## Diagnose-Befehle Uebersicht + +```bash +# Alle Container Status +docker compose ps + +# Alle Logs (live) +docker compose logs -f + +# Ressourcen-Nutzung +docker stats + +# Netzwerke anzeigen +docker network ls + +# Volumes anzeigen +docker volume ls + +# System-Info +docker system df + +# Health-Check ausfuehren +/opt/scripts/health-check.sh +``` + +--- + +## Kontakt / Hilfe + +- **Gitea Issues:** https://eckardt-git.duckdns.org/Martin/proxmox-infrastruktur/issues +- **Docker Docs:** https://docs.docker.com/ +- **Nextcloud Docs:** https://docs.nextcloud.com/ +- **Vaultwarden Wiki:** https://github.com/dani-garcia/vaultwarden/wiki diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100644 index 0000000..c339dbc --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,144 @@ +#!/bin/bash +# ============================================ +# Proxmox Infrastruktur Backup Script +# ============================================ +# Ausfuehrung: ./backup.sh [service|all] +# Cronjob: 0 3 * * * /opt/scripts/backup.sh all >> /var/log/backup.log 2>&1 + +set -euo pipefail + +BACKUP_DIR="/opt/backups" +DATE=$(date +%Y%m%d_%H%M%S) +RETENTION_DAYS=7 + +# Farben fuer Output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1"; } + +# Backup-Verzeichnis erstellen +mkdir -p "$BACKUP_DIR" + +backup_nextcloud() { + log_info "Starte Nextcloud Backup..." + local backup_file="$BACKUP_DIR/nextcloud_$DATE.tar.gz" + + # Maintenance Mode aktivieren + docker exec nextcloud php occ maintenance:mode --on || true + + # Daten sichern + tar -czf "$backup_file" \ + -C /opt/docker/nextcloud \ + data db \ + 2>/dev/null + + # Maintenance Mode deaktivieren + docker exec nextcloud php occ maintenance:mode --off || true + + log_info "Nextcloud Backup erstellt: $backup_file ($(du -h "$backup_file" | cut -f1))" +} + +backup_vaultwarden() { + log_info "Starte Vaultwarden Backup..." + local backup_file="$BACKUP_DIR/vaultwarden_$DATE.tar.gz" + + tar -czf "$backup_file" \ + -C /opt/docker \ + vaultwarden \ + 2>/dev/null + + log_info "Vaultwarden Backup erstellt: $backup_file ($(du -h "$backup_file" | cut -f1))" +} + +backup_gitea() { + log_info "Starte Gitea Backup..." + local backup_file="$BACKUP_DIR/gitea_$DATE.tar.gz" + + # Gitea internen Backup-Befehl nutzen + docker exec -u git gitea gitea dump -c /data/gitea/conf/app.ini -f /data/gitea-dump.zip || true + + # Dump und Daten sichern + tar -czf "$backup_file" \ + -C /opt/docker/gitea \ + . \ + 2>/dev/null + + log_info "Gitea Backup erstellt: $backup_file ($(du -h "$backup_file" | cut -f1))" +} + +backup_n8n() { + log_info "Starte n8n Backup..." + local backup_file="$BACKUP_DIR/n8n_$DATE.tar.gz" + + tar -czf "$backup_file" \ + -C /opt/docker \ + n8n \ + 2>/dev/null + + log_info "n8n Backup erstellt: $backup_file ($(du -h "$backup_file" | cut -f1))" +} + +backup_audiobookshelf() { + log_info "Starte Audiobookshelf Backup..." + local backup_file="$BACKUP_DIR/audiobookshelf_$DATE.tar.gz" + + # Nur Config und Metadata, nicht die Audiobooks selbst + tar -czf "$backup_file" \ + -C /opt/docker/audiobookshelf \ + config metadata \ + 2>/dev/null + + log_info "Audiobookshelf Backup erstellt: $backup_file ($(du -h "$backup_file" | cut -f1))" +} + +backup_configs() { + log_info "Starte Config Backup..." + local backup_file="$BACKUP_DIR/configs_$DATE.tar.gz" + + tar -czf "$backup_file" \ + /opt/docker/docker-compose.yml \ + /opt/docker/gitea/docker-compose.yml \ + /etc/wireguard/wg0.conf \ + 2>/dev/null || true + + log_info "Config Backup erstellt: $backup_file" +} + +cleanup_old_backups() { + log_info "Loesche Backups aelter als $RETENTION_DAYS Tage..." + find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete + log_info "Cleanup abgeschlossen" +} + +backup_all() { + log_info "========== VOLLSTAENDIGES BACKUP GESTARTET ==========" + backup_nextcloud + backup_vaultwarden + backup_gitea + backup_n8n + backup_audiobookshelf + backup_configs + cleanup_old_backups + log_info "========== BACKUP ABGESCHLOSSEN ==========" + log_info "Backup-Groesse gesamt: $(du -sh "$BACKUP_DIR" | cut -f1)" +} + +# Hauptprogramm +case "${1:-all}" in + nextcloud) backup_nextcloud ;; + vaultwarden) backup_vaultwarden ;; + gitea) backup_gitea ;; + n8n) backup_n8n ;; + audiobookshelf) backup_audiobookshelf ;; + configs) backup_configs ;; + all) backup_all ;; + *) + echo "Verwendung: $0 [nextcloud|vaultwarden|gitea|n8n|audiobookshelf|configs|all]" + exit 1 + ;; +esac diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..1b36c1f --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# ============================================ +# Proxmox Infrastruktur Deployment Script +# ============================================ +# Erstinstallation oder Update der kompletten Infrastruktur + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# Root-Check +if [ "$EUID" -ne 0 ]; then + log_error "Bitte als root ausfuehren" + exit 1 +fi + +log_info "==========================================" +log_info " Proxmox Infrastruktur Deployment" +log_info "==========================================" + +# Verzeichnisse erstellen +log_info "Erstelle Verzeichnisse..." +mkdir -p /opt/docker/{nextcloud/{data,db},vaultwarden,n8n,audiobookshelf/{audiobooks,podcasts,config,metadata},websites/{html,conf},api,gitea} +mkdir -p /opt/backups +mkdir -p /opt/scripts + +# .env pruefen +if [ ! -f /opt/docker/.env ]; then + log_warn ".env Datei nicht gefunden!" + log_info "Erstelle Template..." + cat > /opt/docker/.env << 'EOF' +# Proxmox Infrastruktur - Environment Variables +NEXTCLOUD_DB_PASSWORD=CHANGE_ME_$(openssl rand -hex 8) +NEXTCLOUD_DB_ROOT_PASSWORD=CHANGE_ME_$(openssl rand -hex 8) +VAULTWARDEN_ADMIN_TOKEN=$(openssl rand -base64 48) +N8N_USER=admin +N8N_PASSWORD=CHANGE_ME_$(openssl rand -hex 8) +EOF + log_warn "WICHTIG: Bearbeite /opt/docker/.env und ersetze die Passwoerter!" + log_warn "Dann erneut ausfuehren." + exit 1 +fi + +# Docker installieren (falls nicht vorhanden) +if ! command -v docker &> /dev/null; then + log_info "Installiere Docker..." + curl -fsSL https://get.docker.com | sh + systemctl enable docker + systemctl start docker +fi + +# Docker Compose pruefen +if ! docker compose version &> /dev/null; then + log_error "Docker Compose nicht gefunden!" + exit 1 +fi + +# Scripts kopieren +log_info "Kopiere Scripts..." +cp -f scripts/*.sh /opt/scripts/ 2>/dev/null || true +chmod +x /opt/scripts/*.sh 2>/dev/null || true + +# Docker Compose kopieren +log_info "Kopiere Docker Compose..." +cp -f docker/docker-compose.yml /opt/docker/ + +# Container starten +log_info "Starte Container..." +cd /opt/docker +docker compose pull +docker compose up -d + +# Status pruefen +log_info "Warte auf Container-Start..." +sleep 10 + +docker compose ps + +log_info "==========================================" +log_info " Deployment abgeschlossen!" +log_info "==========================================" +log_info "" +log_info "Naechste Schritte:" +log_info " 1. Nextcloud einrichten: http://$(hostname -I | awk '{print $1}'):8081" +log_info " 2. Vaultwarden Admin: http://$(hostname -I | awk '{print $1}'):8083/admin" +log_info " 3. Gitea einrichten: http://$(hostname -I | awk '{print $1}'):3000" +log_info "" +log_info "Health Check: /opt/scripts/health-check.sh" +log_info "Backup: /opt/scripts/backup.sh all" diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100644 index 0000000..75eccc4 --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,122 @@ +#!/bin/bash +# ============================================ +# Proxmox Infrastruktur Health Check Script +# ============================================ +# Ausfuehrung: ./health-check.sh +# Cronjob: */5 * * * * /opt/scripts/health-check.sh >> /var/log/health-check.log 2>&1 + +set -uo pipefail + +# Farben fuer Output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +FAILED=0 + +check_service() { + local name=$1 + local url=$2 + local expected=${3:-200} + + response=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 "$url" 2>/dev/null || echo "000") + + if [ "$response" = "$expected" ]; then + echo -e "${GREEN}[OK]${NC} $name - HTTP $response" + else + echo -e "${RED}[FAIL]${NC} $name - HTTP $response (erwartet: $expected)" + FAILED=$((FAILED + 1)) + fi +} + +check_container() { + local name=$1 + + if docker ps --format '{{.Names}}' | grep -q "^${name}$"; then + status=$(docker inspect --format='{{.State.Health.Status}}' "$name" 2>/dev/null || echo "no-healthcheck") + if [ "$status" = "healthy" ] || [ "$status" = "no-healthcheck" ]; then + echo -e "${GREEN}[OK]${NC} Container $name - Running ($status)" + else + echo -e "${YELLOW}[WARN]${NC} Container $name - $status" + fi + else + echo -e "${RED}[FAIL]${NC} Container $name - Not running" + FAILED=$((FAILED + 1)) + fi +} + +check_disk() { + local path=$1 + local threshold=${2:-90} + + usage=$(df "$path" | tail -1 | awk '{print $5}' | tr -d '%') + + if [ "$usage" -lt "$threshold" ]; then + echo -e "${GREEN}[OK]${NC} Disk $path - ${usage}% verwendet" + else + echo -e "${RED}[FAIL]${NC} Disk $path - ${usage}% verwendet (Grenze: ${threshold}%)" + FAILED=$((FAILED + 1)) + fi +} + +check_wireguard() { + if wg show wg0 &>/dev/null; then + latest_handshake=$(wg show wg0 latest-handshakes | awk '{print $2}') + current_time=$(date +%s) + diff=$((current_time - latest_handshake)) + + if [ "$diff" -lt 180 ]; then + echo -e "${GREEN}[OK]${NC} WireGuard - Verbunden (Handshake vor ${diff}s)" + else + echo -e "${YELLOW}[WARN]${NC} WireGuard - Letzter Handshake vor ${diff}s" + fi + else + echo -e "${RED}[FAIL]${NC} WireGuard - Nicht aktiv" + FAILED=$((FAILED + 1)) + fi +} + +echo "==========================================" +echo " Proxmox Infrastruktur Health Check" +echo " $(date '+%Y-%m-%d %H:%M:%S')" +echo "==========================================" +echo "" + +echo "--- Container Status ---" +check_container "nextcloud" +check_container "nextcloud-db" +check_container "vaultwarden" +check_container "n8n" +check_container "audiobookshelf" +check_container "websites" +check_container "api-server" +check_container "gitea" +echo "" + +echo "--- Service Endpoints (Intern) ---" +check_service "Nextcloud" "http://localhost:8081/status.php" +check_service "Vaultwarden" "http://localhost:8083/alive" +check_service "n8n" "http://localhost:5678/healthz" +check_service "Websites" "http://localhost:8082/" +check_service "API" "http://localhost:8000/health" +check_service "Gitea" "http://localhost:3000/api/healthz" +echo "" + +echo "--- Netzwerk ---" +check_wireguard +echo "" + +echo "--- Disk Usage ---" +check_disk "/" 85 +check_disk "/opt/docker" 90 +echo "" + +echo "==========================================" +if [ $FAILED -eq 0 ]; then + echo -e "${GREEN}Alle Checks bestanden!${NC}" + exit 0 +else + echo -e "${RED}$FAILED Check(s) fehlgeschlagen!${NC}" + exit 1 +fi