From ec5526e2b20e3c2edd1718c3431e4e6591745997 Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Thu, 17 Jul 2025 16:38:55 +0200 Subject: [PATCH] chore: complete update --- .gitignore | 2 +- ansible/netcup-simple-deploy/.gitignore | 6 + ansible/netcup-simple-deploy/Makefile | 136 +++++++++ ansible/netcup-simple-deploy/QUICK-SETUP.md | 128 +++++++++ ansible/netcup-simple-deploy/README.md | 40 +++ ansible/netcup-simple-deploy/SETUP-COMPOSE.md | 161 +++++++++++ .../netcup-simple-deploy/SETUP-OHNE-GIT.md | 172 +++++++++++ ansible/netcup-simple-deploy/SETUP-PHP.md | 165 +++++++++++ ansible/netcup-simple-deploy/ansible.cfg | 8 + .../deploy-debian-fallback.yml | 105 +++++++ ansible/netcup-simple-deploy/deploy.sh | 119 ++++++++ ansible/netcup-simple-deploy/deploy.yml | 163 +++++++++++ .../inventory/group_vars.yml | 19 ++ .../netcup-simple-deploy/inventory/hosts.yml | 26 ++ ansible/netcup-simple-deploy/restart-app.yml | 91 ++++++ .../roles/webapp/defaults/main.yml | 24 ++ .../roles/webapp/tasks/main.yml | 272 ++++++++++++++++++ .../roles/webapp/templates/app.env.j2 | 12 + .../roles/webapp/templates/nginx-site.conf.j2 | 53 ++++ ansible/netcup-simple-deploy/upload-only.yml | 128 +++++++++ ansible/nginx-cdn-germany/.gitignore | 31 ++ ansible/nginx-cdn-germany/Makefile | 64 +++++ ansible/nginx-cdn-germany/README.md | 48 ++++ ansible/nginx-cdn-germany/SETUP.md | 115 ++++++++ ansible/nginx-cdn-germany/ansible.cfg | 15 + ansible/nginx-cdn-germany/init.sh | 19 ++ .../production/group_vars/all/main.yml | 26 ++ .../production/group_vars/cdn_nodes/main.yml | 22 ++ .../production/hosts-grouped-keys.yml.example | 47 +++ .../hosts-individual-keys.yml.example | 48 ++++ .../inventories/production/hosts.yml | 45 +++ .../playbooks/deploy-simple-cdn.yml | 43 +++ .../playbooks/manage-cdn.yml | 68 +++++ .../roles/nginx-cdn-config/handlers/main.yml | 10 + .../roles/nginx-cdn-config/tasks/main.yml | 64 +++++ .../templates/cdn-site.conf.j2 | 195 +++++++++++++ .../templates/gzip-settings.conf.j2 | 20 ++ .../nginx-cdn-config/templates/nginx.conf.j2 | 75 +++++ .../templates/rate-limiting.conf.j2 | 9 + .../templates/security-headers.conf.j2 | 10 + .../roles/simple-monitoring/tasks/main.yml | 29 ++ .../templates/simple-monitor.sh.j2 | 71 +++++ .../roles/ssl-certificates/tasks/main.yml | 30 ++ ansible/nginx-cdn-germany/scripts/deploy.sh | 44 +++ ansible/nginx-cdn-germany/scripts/ssh-keys.sh | 125 ++++++++ .../nginx-cdn-germany/scripts/warm-cache.sh | 37 +++ 46 files changed, 3139 insertions(+), 1 deletion(-) create mode 100644 ansible/netcup-simple-deploy/.gitignore create mode 100644 ansible/netcup-simple-deploy/Makefile create mode 100644 ansible/netcup-simple-deploy/QUICK-SETUP.md create mode 100644 ansible/netcup-simple-deploy/README.md create mode 100644 ansible/netcup-simple-deploy/SETUP-COMPOSE.md create mode 100644 ansible/netcup-simple-deploy/SETUP-OHNE-GIT.md create mode 100644 ansible/netcup-simple-deploy/SETUP-PHP.md create mode 100644 ansible/netcup-simple-deploy/ansible.cfg create mode 100644 ansible/netcup-simple-deploy/deploy-debian-fallback.yml create mode 100755 ansible/netcup-simple-deploy/deploy.sh create mode 100644 ansible/netcup-simple-deploy/deploy.yml create mode 100644 ansible/netcup-simple-deploy/inventory/group_vars.yml create mode 100644 ansible/netcup-simple-deploy/inventory/hosts.yml create mode 100644 ansible/netcup-simple-deploy/restart-app.yml create mode 100644 ansible/netcup-simple-deploy/roles/webapp/defaults/main.yml create mode 100644 ansible/netcup-simple-deploy/roles/webapp/tasks/main.yml create mode 100644 ansible/netcup-simple-deploy/roles/webapp/templates/app.env.j2 create mode 100644 ansible/netcup-simple-deploy/roles/webapp/templates/nginx-site.conf.j2 create mode 100644 ansible/netcup-simple-deploy/upload-only.yml create mode 100644 ansible/nginx-cdn-germany/.gitignore create mode 100644 ansible/nginx-cdn-germany/Makefile create mode 100644 ansible/nginx-cdn-germany/README.md create mode 100644 ansible/nginx-cdn-germany/SETUP.md create mode 100644 ansible/nginx-cdn-germany/ansible.cfg create mode 100644 ansible/nginx-cdn-germany/init.sh create mode 100644 ansible/nginx-cdn-germany/inventories/production/group_vars/all/main.yml create mode 100644 ansible/nginx-cdn-germany/inventories/production/group_vars/cdn_nodes/main.yml create mode 100644 ansible/nginx-cdn-germany/inventories/production/hosts-grouped-keys.yml.example create mode 100644 ansible/nginx-cdn-germany/inventories/production/hosts-individual-keys.yml.example create mode 100644 ansible/nginx-cdn-germany/inventories/production/hosts.yml create mode 100644 ansible/nginx-cdn-germany/playbooks/deploy-simple-cdn.yml create mode 100644 ansible/nginx-cdn-germany/playbooks/manage-cdn.yml create mode 100644 ansible/nginx-cdn-germany/roles/nginx-cdn-config/handlers/main.yml create mode 100644 ansible/nginx-cdn-germany/roles/nginx-cdn-config/tasks/main.yml create mode 100644 ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/cdn-site.conf.j2 create mode 100644 ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/gzip-settings.conf.j2 create mode 100644 ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/nginx.conf.j2 create mode 100644 ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/rate-limiting.conf.j2 create mode 100644 ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/security-headers.conf.j2 create mode 100644 ansible/nginx-cdn-germany/roles/simple-monitoring/tasks/main.yml create mode 100644 ansible/nginx-cdn-germany/roles/simple-monitoring/templates/simple-monitor.sh.j2 create mode 100644 ansible/nginx-cdn-germany/roles/ssl-certificates/tasks/main.yml create mode 100644 ansible/nginx-cdn-germany/scripts/deploy.sh create mode 100644 ansible/nginx-cdn-germany/scripts/ssh-keys.sh create mode 100644 ansible/nginx-cdn-germany/scripts/warm-cache.sh diff --git a/.gitignore b/.gitignore index a50d2a1f..47907ade 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,7 @@ ssl/*.pem node_modules storage/uploads/* storage/logs/.gitkeep -dist/ +!/dist/ .archive/ diff --git a/ansible/netcup-simple-deploy/.gitignore b/ansible/netcup-simple-deploy/.gitignore new file mode 100644 index 00000000..2631a645 --- /dev/null +++ b/ansible/netcup-simple-deploy/.gitignore @@ -0,0 +1,6 @@ +# .gitignore für Netcup Deployment +*.retry +.ansible/ +*.log +.env.local +secrets.yml diff --git a/ansible/netcup-simple-deploy/Makefile b/ansible/netcup-simple-deploy/Makefile new file mode 100644 index 00000000..9a1e3fd9 --- /dev/null +++ b/ansible/netcup-simple-deploy/Makefile @@ -0,0 +1,136 @@ +# Test Makefile für rsync debugging (Fixed) + +.PHONY: test-rsync debug-sync upload restart quick-deploy + +# Teste manuellen rsync +test-rsync: + @echo "🔍 Testing manual rsync..." + @SERVER_IP=$$(grep ansible_host inventory/hosts.yml | awk '{print $$2}'); \ + echo "Server IP: $$SERVER_IP"; \ + APP_PATH=$$(grep local_app_path inventory/hosts.yml | awk '{print $$2}' | tr -d '"'); \ + echo "Local path: $$APP_PATH"; \ + echo ""; \ + echo "=== Testing dry-run rsync ==="; \ + rsync -av --dry-run \ + --exclude='ansible' \ + --exclude='.git' \ + --exclude='vendor' \ + --exclude='node_modules' \ + --exclude='storage/logs' \ + --exclude='cache' \ + --exclude='logs' \ + --exclude='dist' \ + --exclude='.archive' \ + $$APP_PATH/ root@$$SERVER_IP:/opt/myapp/; \ + echo ""; \ + echo "If this shows files, then rsync should work" + +# Debug was in lokalen Dateien ist +debug-local: + @echo "📁 Local files debug:" + @APP_PATH=$$(grep local_app_path inventory/hosts.yml | awk '{print $$2}' | tr -d '"'); \ + echo "Path: $$APP_PATH"; \ + echo ""; \ + if [ -z "$$APP_PATH" ]; then \ + echo "❌ APP_PATH is empty!"; \ + echo "Raw line from hosts.yml:"; \ + grep local_app_path inventory/hosts.yml; \ + exit 1; \ + fi; \ + echo "=== Root files ==="; \ + ls -la "$$APP_PATH" | head -10; \ + echo ""; \ + echo "=== Public files ==="; \ + ls -la "$$APP_PATH/public" | head -10; \ + echo ""; \ + echo "=== Does index.php exist locally? ==="; \ + if [ -f "$$APP_PATH/public/index.php" ]; then \ + echo "✅ index.php exists locally"; \ + echo "Size: $$(wc -c < $$APP_PATH/public/index.php) bytes"; \ + echo "Content preview:"; \ + head -5 "$$APP_PATH/public/index.php"; \ + else \ + echo "❌ index.php NOT found locally!"; \ + echo "Checking if public folder exists:"; \ + if [ -d "$$APP_PATH/public" ]; then \ + echo "Public folder exists, contents:"; \ + ls -la "$$APP_PATH/public/"; \ + else \ + echo "Public folder does not exist!"; \ + fi; \ + fi + +# Test direkt mit absoluten Pfaden +debug-direct: + @echo "📁 Direct path test:" + @echo "=== Current directory ===" + pwd + @echo "" + @echo "=== Going to project root ===" + cd ../.. && pwd + @echo "" + @echo "=== Files in project root ===" + cd ../.. && ls -la | head -10 + @echo "" + @echo "=== Public folder ===" + cd ../.. && ls -la public/ | head -10 + @echo "" + @echo "=== Index.php check ===" + cd ../.. && if [ -f "public/index.php" ]; then \ + echo "✅ index.php found!"; \ + echo "Size: $$(wc -c < public/index.php) bytes"; \ + else \ + echo "❌ index.php not found"; \ + fi + +# Test Ansible synchronize mit debug +debug-sync: + @echo "🔍 Testing Ansible synchronize with debug..." + ansible-playbook -i inventory/hosts.yml debug-sync.yml -v + +# Upload files only (no infrastructure setup) +upload: + @echo "📤 Uploading files only..." + ansible-playbook -i inventory/hosts.yml upload-only.yml + +# Restart application after upload +restart: + @echo "🔄 Restarting application..." + ansible-playbook -i inventory/hosts.yml restart-app.yml + +# Quick upload and restart +quick-deploy: + @echo "⚡ Quick deploy: upload + restart..." + ansible-playbook -i inventory/hosts.yml upload-only.yml + ansible-playbook -i inventory/hosts.yml restart-app.yml + +# Alle Standard-Befehle +deploy: + @echo "🚀 Deploying project to Netcup..." + chmod +x deploy.sh + ./deploy.sh + +check: + @echo "🔍 Testing configuration..." + ansible all -m ping + +logs: + @echo "📋 Showing container logs..." + ansible all -m shell -a "cd /opt/myapp && (docker compose logs --tail 100 || docker-compose logs --tail 100)" + +help: + @echo "📖 Debug commands:" + @echo " make debug-local - Check local files" + @echo " make debug-direct - Check with direct paths" + @echo " make test-rsync - Test manual rsync" + @echo " make debug-sync - Test Ansible sync" + @echo "" + @echo "📖 Deploy commands:" + @echo " make deploy - Full deployment (infrastructure + app)" + @echo " make upload - Upload files only (no infrastructure)" + @echo " make restart - Restart application containers" + @echo " make quick-deploy - Upload files + restart (fastest)" + @echo "" + @echo "📖 Utility commands:" + @echo " make logs - Show container logs" + @echo " make check - Test connection" diff --git a/ansible/netcup-simple-deploy/QUICK-SETUP.md b/ansible/netcup-simple-deploy/QUICK-SETUP.md new file mode 100644 index 00000000..8c4fb84e --- /dev/null +++ b/ansible/netcup-simple-deploy/QUICK-SETUP.md @@ -0,0 +1,128 @@ +# Netcup Quick Setup Guide + +## 1. Server vorbereiten + +### Netcup VPS bestellen +- **Mindestens:** VPS 200 G8 (2 CPU, 4GB RAM) +- **OS:** Ubuntu 22.04 LTS +- **Netzwerk:** IPv4 + IPv6 + +### SSH-Key installieren +```bash +# SSH-Key generieren (falls noch nicht vorhanden) +ssh-keygen -t ed25519 -C "netcup-deploy" + +# Key zum Server kopieren +ssh-copy-id root@DEINE-SERVER-IP +``` + +## 2. Konfiguration + +### Basis-Einstellungen +```bash +# Server-Details eintragen +vim inventory/hosts.yml +``` + +**Wichtig ändern:** +- `ansible_host: 85.123.456.789` → deine Netcup IP +- `domain: "example.com"` → deine Domain +- `ssl_email: "admin@example.com"` → deine E-Mail +- `git_repo: "https://github.com/user/repo.git"` → dein Git Repository + +### DNS konfigurieren +Stelle sicher dass deine Domain zur Netcup IP zeigt: +```bash +# A-Record setzen +example.com. IN A DEINE-NETCUP-IP +``` + +## 3. App-Anforderungen + +Deine App braucht: +- **Dockerfile** im Repository-Root +- **Port 3000** (oder ändere `app_port` in hosts.yml) +- **Health-Check** Endpoint `/health` (oder ändere `health_check_url`) + +### Beispiel Dockerfile (Node.js) +```dockerfile +FROM node:18-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production +COPY . . +EXPOSE 3000 +CMD ["npm", "start"] +``` + +### Beispiel Health-Check (Express.js) +```javascript +app.get('/health', (req, res) => { + res.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); +``` + +## 4. Deployment + +```bash +# Einfach deployen +make deploy + +# Oder manuell +./deploy.sh +``` + +## 5. Troubleshooting + +### Server nicht erreichbar? +```bash +# Ping testen +ping DEINE-SERVER-IP + +# SSH testen +ssh root@DEINE-SERVER-IP + +# Firewall prüfen (auf dem Server) +ufw status +``` + +### SSL-Probleme? +```bash +# DNS prüfen +nslookup DEINE-DOMAIN + +# Certbot manuell +ssh root@DEINE-SERVER-IP +certbot certificates +``` + +### App startet nicht? +```bash +# Logs anschauen +make logs + +# Container status +ansible all -m shell -a "docker ps -a" + +# Ins Container einsteigen +ansible all -m shell -a "docker exec -it CONTAINER_NAME sh" +``` + +## 6. Nach dem Deployment + +- ✅ **App testen:** https://deine-domain.com +- ✅ **Health-Check:** https://deine-domain.com/health +- ✅ **SSL prüfen:** https://www.ssllabs.com/ssltest/ +- ✅ **Performance:** https://pagespeed.web.dev/ + +## 7. Updates + +```bash +# App updaten (git pull + rebuild) +make update + +# Logs nach Update prüfen +make logs +``` + +Das war's! Deine App läuft jetzt auf Netcup mit SSL! 🎉 diff --git a/ansible/netcup-simple-deploy/README.md b/ansible/netcup-simple-deploy/README.md new file mode 100644 index 00000000..c41607da --- /dev/null +++ b/ansible/netcup-simple-deploy/README.md @@ -0,0 +1,40 @@ +# Netcup Simple Deploy + +Ultra-einfaches Ansible-Setup für Netcup VPS Deployment. + +## Quick Start + +1. **Server-Info eintragen:** + ```bash + vim inventory/hosts.yml + # Deine Netcup-Server IP und Domain eintragen + ``` + +2. **App-Einstellungen:** + ```bash + vim inventory/group_vars.yml + # Domain, Repo, etc. anpassen + ``` + +3. **Deployen:** + ```bash + ansible-playbook deploy.yml + ``` + +## Was wird installiert + +✅ Docker & Docker Compose +✅ Nginx Reverse Proxy +✅ SSL mit Let's Encrypt +✅ Deine App aus Git +✅ Automatische Updates + +## Features + +- 🚀 **Ein Kommando** deployment +- 🔒 **Automatisches SSL** +- 🐳 **Docker-basiert** +- 📱 **Health Checks** +- 🔄 **Zero-Downtime Updates** + +Perfekt für einfache Web-Apps auf Netcup VPS! diff --git a/ansible/netcup-simple-deploy/SETUP-COMPOSE.md b/ansible/netcup-simple-deploy/SETUP-COMPOSE.md new file mode 100644 index 00000000..1d599d42 --- /dev/null +++ b/ansible/netcup-simple-deploy/SETUP-COMPOSE.md @@ -0,0 +1,161 @@ +# Projekt Setup für Netcup (nutzt deine docker-compose.yml) + +## Projektstruktur + +Das Deployment nutzt deine bestehende Docker-Konfiguration: + +``` +dein-projekt/ # Hauptordner +├── ansible/ # Hier sind wir jetzt +│ └── netcup-simple-deploy/ +├── docker-compose.yml # ← DEINE Compose-Datei (wird verwendet!) +├── docker/ # Docker-Konfiguration +│ ├── Dockerfile +│ └── docker-compose.yml # ← Alternative hier +├── src/ # PHP Framework/Library Dateien +├── public/ # Web-Root +└── ... +``` + +## Was das Deployment macht: + +✅ **Nutzt deine bestehende docker-compose.yml** +✅ **Startet ALLE deine Services** (DB, Redis, etc.) +✅ **Überträgt komplettes Projekt** +✅ **Nginx als Reverse Proxy** für SSL + +## Quick Setup + +### 1. Konfiguration +```bash +cd ansible/netcup-simple-deploy +vim inventory/hosts.yml +``` + +**Wichtig ändern:** +```yaml +ansible_host: DEINE-NETCUP-IP +domain: "deine-domain.com" +app_port: 8080 # Port deiner App aus docker-compose.yml +``` + +### 2. Port prüfen +Schaue in deine `docker-compose.yml` welchen Port deine App exponiertrt: +```yaml +services: + myapp: + ports: + - "8080:80" # ← Dann ist app_port: 8080 +``` + +### 3. Deployment +```bash +make deploy +``` + +## Beispiel docker-compose.yml Strukturen + +### Einfache PHP App +```yaml +version: '3.8' +services: + web: + build: . + ports: + - "8080:80" + volumes: + - ./src:/var/www/src + - ./public:/var/www/html +``` + +### Mit Datenbank +```yaml +version: '3.8' +services: + web: + build: . + ports: + - "8080:80" + depends_on: + - db + environment: + - DATABASE_URL=mysql://user:pass@db:3306/myapp + + db: + image: mysql:8.0 + environment: + - MYSQL_ROOT_PASSWORD=secret + - MYSQL_DATABASE=myapp + volumes: + - db_data:/var/lib/mysql + +volumes: + db_data: +``` + +### Mit Redis + Database +```yaml +version: '3.8' +services: + web: + build: . + ports: + - "8080:80" + depends_on: + - db + - redis + + db: + image: postgres:15 + environment: + - POSTGRES_DB=myapp + - POSTGRES_USER=user + - POSTGRES_PASSWORD=secret + volumes: + - postgres_data:/var/lib/postgresql/data + + redis: + image: redis:7-alpine + volumes: + - redis_data:/data + +volumes: + postgres_data: + redis_data: +``` + +## Nach dem Deployment + +**Alle Services verwalten:** +```bash +make services # Zeige alle Services +make logs-service # Logs für bestimmten Service +make status # Status aller Container +make shell # In Container einsteigen +``` + +**Updates:** +```bash +# Nach Änderungen an Code oder docker-compose.yml +make deploy + +# Nur Container neu bauen +make rebuild +``` + +**Monitoring:** +```bash +make logs # Alle Logs +make tail-logs # Live logs +make show-env # Environment variables +``` + +## Vorteile dieser Lösung + +✅ **Deine bestehende Konfiguration** wird verwendet +✅ **Alle Services** (DB, Redis, etc.) funktionieren +✅ **Keine Code-Änderungen** nötig +✅ **SSL-Termination** durch nginx +✅ **Einfache Updates** mit make deploy + +Das Deployment ist jetzt vollständig auf deine bestehende Docker-Infrastruktur ausgerichtet! 🎉 diff --git a/ansible/netcup-simple-deploy/SETUP-OHNE-GIT.md b/ansible/netcup-simple-deploy/SETUP-OHNE-GIT.md new file mode 100644 index 00000000..13f098d3 --- /dev/null +++ b/ansible/netcup-simple-deploy/SETUP-OHNE-GIT.md @@ -0,0 +1,172 @@ +# Netcup Setup ohne Git + +## 1. App-Struktur vorbereiten + +### Option A: Bestehende App +Falls du bereits eine App hast, stelle sicher dass sie diese Struktur hat: +``` +deine-app/ +├── package.json # Node.js Abhängigkeiten +├── server.js # Hauptdatei +├── Dockerfile # Docker-Konfiguration +└── ... weitere Dateien +``` + +### Option B: Neue App erstellen +```bash +# Erstelle App-Verzeichnis +mkdir -p ~/meine-app + +# Beispiel Node.js App +cd ~/meine-app + +# package.json +cat > package.json << 'EOF' +{ + "name": "meine-app", + "version": "1.0.0", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.0" + } +} +EOF + +# server.js +cat > server.js << 'EOF' +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +app.get('/', (req, res) => { + res.json({ + message: 'Hello World!', + timestamp: new Date().toISOString() + }); +}); + +app.get('/health', (req, res) => { + res.json({ status: 'ok' }); +}); + +app.listen(port, '0.0.0.0', () => { + console.log(`Server running on port ${port}`); +}); +EOF + +# Dockerfile +cat > Dockerfile << 'EOF' +FROM node:18-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +EXPOSE 3000 +CMD ["npm", "start"] +EOF +``` + +## 2. Ansible konfigurieren + +```bash +# Ins Deployment-Verzeichnis +cd ansible/netcup-simple-deploy + +# Konfiguration anpassen +vim inventory/hosts.yml +``` + +**Wichtige Änderungen:** +```yaml +ansible_host: DEINE-NETCUP-IP # ← Server IP +domain: "deine-domain.com" # ← Domain +ssl_email: "deine@email.com" # ← E-Mail +local_app_path: "~/meine-app" # ← Pfad zu deiner App +``` + +## 3. Deployment + +```bash +# SSH-Key zum Server (falls noch nicht gemacht) +ssh-copy-id root@DEINE-NETCUP-IP + +# App deployen +make deploy +``` + +## 4. App updaten + +Nach Änderungen an deiner App: +```bash +# Einfach erneut deployen +make deploy +``` + +Die Dateien werden automatisch zum Server übertragen und die App neu gebaut. + +## 5. Verschiedene App-Typen + +### PHP App +```dockerfile +FROM php:8.1-apache +COPY . /var/www/html/ +EXPOSE 80 +``` + +### Python Flask +```dockerfile +FROM python:3.9-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY . . +EXPOSE 3000 +CMD ["python", "app.py"] +``` + +### Static HTML +```dockerfile +FROM nginx:alpine +COPY . /usr/share/nginx/html +EXPOSE 80 +``` + +## 6. Ordnerstruktur + +``` +netcup-simple-deploy/ +├── inventory/ +│ └── hosts.yml # ← Hier konfigurieren +├── deploy.sh # ← Deployment starten +└── Makefile # ← Einfache Befehle + +~/meine-app/ # ← Deine App-Dateien +├── Dockerfile +├── package.json +└── server.js +``` + +## 7. Troubleshooting + +### App startet nicht? +```bash +# Logs anschauen +make logs + +# Container status prüfen +ansible all -m shell -a "docker ps -a" +``` + +### Dateien werden nicht übertragen? +```bash +# Pfad prüfen +ls -la ~/meine-app + +# Manuell testen +ansible all -m shell -a "ls -la /opt/myapp/src/" +``` + +Das war's! Keine Git-Kenntnisse nötig - einfach deine Dateien bearbeiten und deployen! 🎉 diff --git a/ansible/netcup-simple-deploy/SETUP-PHP.md b/ansible/netcup-simple-deploy/SETUP-PHP.md new file mode 100644 index 00000000..50994958 --- /dev/null +++ b/ansible/netcup-simple-deploy/SETUP-PHP.md @@ -0,0 +1,165 @@ +# PHP Projekt Setup für Netcup + +## Projektstruktur + +Das Deployment erwartet diese Struktur in deinem Hauptprojekt: + +``` +dein-projekt/ # Hauptordner +├── ansible/ # Hier sind wir jetzt +│ └── netcup-simple-deploy/ +├── docker/ # Docker-Konfiguration +│ ├── Dockerfile # (optional, wird sonst automatisch erstellt) +│ └── docker-compose.yml # (optional) +├── src/ # PHP Framework/Library Dateien +│ ├── classes/ +│ ├── includes/ +│ └── ... +├── public/ # Web-Root (öffentlich zugänglich) +│ ├── index.php # Haupteinstiegspunkt +│ ├── css/ +│ ├── js/ +│ ├── images/ +│ └── ... +├── storage/ # (optional) Logs, Cache, etc. +├── cache/ # (optional) Cache-Dateien +├── logs/ # (optional) Log-Dateien +└── .env # (optional) Umgebungsvariablen +``` + +## Quick Setup + +### 1. Konfiguration +```bash +cd ansible/netcup-simple-deploy +vim inventory/hosts.yml +``` + +**Ändere diese Werte:** +```yaml +ansible_host: DEINE-NETCUP-IP +domain: "deine-domain.com" +ssl_email: "deine@email.com" +local_app_path: "../.." # Zeigt auf dein Hauptprojekt +php_version: "8.2" # PHP Version +``` + +### 2. Deployment +```bash +make deploy +``` + +Das war's! Deine PHP-App läuft unter `https://deine-domain.com` + +## Was passiert beim Deployment? + +1. **Dateien übertragen:** `public/`, `src/`, `docker/` → Server +2. **Dockerfile erstellen:** Falls keins in `docker/` vorhanden +3. **Docker Container bauen:** PHP + Apache + deine App +4. **Nginx Proxy:** SSL-Termination und Weiterleitung +5. **SSL-Zertifikat:** Automatisch mit Let's Encrypt + +## Für verschiedene PHP-Setups + +### Eigenes Dockerfile verwenden +Lege dein Dockerfile in `docker/Dockerfile`: +```dockerfile +FROM php:8.2-apache + +# Deine spezifischen PHP Extensions +RUN docker-php-ext-install pdo pdo_mysql + +# Custom Apache Config +COPY docker/apache.conf /etc/apache2/sites-available/000-default.conf + +# App Dateien +COPY public/ /var/www/html/ +COPY src/ /var/www/src/ + +EXPOSE 80 +``` + +### Mit Composer Dependencies +```dockerfile +FROM php:8.2-apache + +# Composer installieren +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# Dependencies installieren +COPY composer.json composer.lock /var/www/ +WORKDIR /var/www +RUN composer install --no-dev --optimize-autoloader + +# App kopieren +COPY public/ /var/www/html/ +COPY src/ /var/www/src/ +``` + +### Mit Database +Erweitere `inventory/hosts.yml`: +```yaml +app_env: + APP_ENV: "production" + DATABASE_HOST: "your-db-host" + DATABASE_NAME: "your-db-name" + DATABASE_USER: "your-db-user" + DATABASE_PASS: "your-db-password" +``` + +## Nützliche Befehle + +```bash +# Logs anschauen +make logs +make error-logs + +# Cache löschen +make clear-cache + +# Permissions reparieren +make fix-permissions + +# Composer auf Server ausführen +make composer-install + +# Live logs verfolgen +make tail-logs + +# SSH auf Server +make ssh +``` + +## Troubleshooting + +### App lädt nicht? +```bash +# Apache Fehler-Logs prüfen +make error-logs + +# Allgemeine Logs +make logs + +# Container Status +make status +``` + +### Permissions-Probleme? +```bash +# Permissions reparieren +make fix-permissions +``` + +### Nach Code-Änderungen? +```bash +# Einfach neu deployen +make deploy +``` + +### Database-Verbindung? +```bash +# Umgebungsvariablen prüfen +ansible all -m shell -a "docker exec \$(docker ps -q | head -1) env | grep DATABASE" +``` + +Das Setup ist optimiert für deine bestehende Projektstruktur - keine Änderungen an deinem Code nötig! 🎉 diff --git a/ansible/netcup-simple-deploy/ansible.cfg b/ansible/netcup-simple-deploy/ansible.cfg new file mode 100644 index 00000000..59373cd4 --- /dev/null +++ b/ansible/netcup-simple-deploy/ansible.cfg @@ -0,0 +1,8 @@ +[defaults] +inventory = inventory/hosts.yml +host_key_checking = False +timeout = 30 + +[privilege_escalation] +become = True +become_method = sudo diff --git a/ansible/netcup-simple-deploy/deploy-debian-fallback.yml b/ansible/netcup-simple-deploy/deploy-debian-fallback.yml new file mode 100644 index 00000000..c469034c --- /dev/null +++ b/ansible/netcup-simple-deploy/deploy-debian-fallback.yml @@ -0,0 +1,105 @@ +--- +# Fallback Deployment für Debian (mit allen Variablen) + +- name: Deploy App to Netcup VPS (Debian Fallback) + hosts: all + become: yes + vars_files: + - inventory/group_vars.yml + + tasks: + - name: Update system + apt: + update_cache: yes + upgrade: dist + + - name: Install packages from Debian repos + apt: + name: + - nginx + - certbot + - python3-certbot-nginx + - git + - curl + - rsync + - docker.io + - docker-compose + state: present + + - name: Start and enable Docker + systemd: + name: docker + state: started + enabled: yes + + - name: Add user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + + - name: Deploy webapp + include_role: + name: webapp + + - name: Configure Nginx reverse proxy + template: + src: roles/webapp/templates/nginx-site.conf.j2 + dest: /etc/nginx/sites-available/{{ domain }} + backup: yes + notify: reload nginx + + - name: Enable site + file: + src: /etc/nginx/sites-available/{{ domain }} + dest: /etc/nginx/sites-enabled/{{ domain }} + state: link + notify: reload nginx + + - name: Remove default site + file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: reload nginx + + - name: Generate SSL certificate + command: > + certbot --nginx -d {{ domain }} + --non-interactive --agree-tos + --email {{ ssl_email }} + args: + creates: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem" + + - name: Setup SSL renewal + cron: + name: "Renew SSL" + minute: "0" + hour: "3" + job: "certbot renew --quiet" + + - name: Start nginx + systemd: + name: nginx + state: started + enabled: yes + + - name: Wait for app to be ready + wait_for: + port: 80 + delay: 10 + timeout: 60 + + - name: Health check + uri: + url: "https://{{ domain }}" + method: GET + status_code: [200, 301, 302] + retries: 5 + delay: 10 + ignore_errors: yes + + handlers: + - name: reload nginx + systemd: + name: nginx + state: reloaded diff --git a/ansible/netcup-simple-deploy/deploy.sh b/ansible/netcup-simple-deploy/deploy.sh new file mode 100755 index 00000000..f70e3175 --- /dev/null +++ b/ansible/netcup-simple-deploy/deploy.sh @@ -0,0 +1,119 @@ +#!/bin/bash +# PHP Projekt Deployment Script für Netcup (nutzt bestehende docker-compose.yml) + +set -e + +echo "🚀 Projekt Deployment zu Netcup (nutzt deine docker-compose.yml)" +echo "" + +# Prüfe ob Konfiguration angepasst wurde +if grep -q "85.123.456.789" inventory/hosts.yml; then + echo "❌ Bitte erst die Konfiguration anpassen!" + echo "" + echo "1. vim inventory/hosts.yml" + echo " - Server IP ändern" + echo " - Domain ändern" + echo " - app_port prüfen (Port deiner App)" + echo "" + echo "2. Dann nochmal: ./deploy.sh" + exit 1 +fi + +LOCAL_APP_PATH=$(grep "local_app_path:" inventory/hosts.yml | awk '{print $2}' | tr -d '"') + +# Prüfe Projektstruktur +echo "📁 Prüfe Projektstruktur..." +FULL_PATH="$LOCAL_APP_PATH" + +if [ ! -d "$FULL_PATH" ]; then + echo "❌ Projekt-Verzeichnis nicht gefunden: $FULL_PATH" + exit 1 +fi + +echo "✅ Projektstruktur OK:" +echo " 📂 Projekt: $FULL_PATH" + +# Prüfe docker-compose.yml +if [ -f "$FULL_PATH/docker-compose.yml" ]; then + echo " ✅ docker-compose.yml gefunden im Root" +elif [ -f "$FULL_PATH/docker/docker-compose.yml" ]; then + echo " ✅ docker-compose.yml gefunden in docker/" +else + echo " ℹ️ Keine docker-compose.yml gefunden - wird automatisch erstellt" +fi + +# Zeige docker-compose.yml Inhalt falls vorhanden +if [ -f "$FULL_PATH/docker-compose.yml" ]; then + echo "" + echo "📋 Deine docker-compose.yml (erste 10 Zeilen):" + head -10 "$FULL_PATH/docker-compose.yml" | sed 's/^/ /' +elif [ -f "$FULL_PATH/docker/docker-compose.yml" ]; then + echo "" + echo "📋 Deine docker-compose.yml aus docker/ (erste 10 Zeilen):" + head -10 "$FULL_PATH/docker/docker-compose.yml" | sed 's/^/ /' +fi + +# Ping test +echo "" +echo "🔍 Teste Verbindung zum Server..." +if ! ansible all -m ping; then + echo "❌ Server nicht erreichbar. Prüfe:" + echo " - IP-Adresse korrekt?" + echo " - SSH-Key installiert? (ssh-copy-id root@deine-ip)" + echo " - Server läuft?" + exit 1 +fi + +echo "✅ Server erreichbar!" +echo "" + +# Wähle Deployment-Methode +echo "🔧 Deployment-Optionen:" +echo "1. Standard: Saubere Docker-Installation (empfohlen)" +echo "2. Fallback: Debian Standard-Pakete (falls Probleme auftreten)" +echo "" +read -p "Wähle Option (1/2): " -n 1 -r +echo + +if [[ $REPLY == "2" ]]; then + PLAYBOOK="deploy-debian-fallback.yml" + echo "📦 Verwende Debian Standard-Pakete" +else + PLAYBOOK="deploy.yml" + echo "🐳 Verwende saubere Docker-Installation" +fi + +# Deployment confirmation +read -p "🚀 Projekt deployen? (y/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Deployment abgebrochen." + exit 0 +fi + +echo "🔧 Starte Deployment mit $PLAYBOOK..." +echo "💡 Das Deployment nutzt deine bestehende docker-compose.yml!" +echo "" + +ansible-playbook "$PLAYBOOK" + +echo "" +echo "🎉 Deployment abgeschlossen!" +echo "" + +# Zeige Ergebnisse +DOMAIN=$(grep "domain:" inventory/hosts.yml | awk '{print $2}' | tr -d '"') +echo "🌐 Dein Projekt ist verfügbar unter:" +echo " https://$DOMAIN" +echo "" +echo "📊 Status prüfen:" +echo " curl -I https://$DOMAIN" +echo "" +echo "🔧 Container-Status anschauen:" +echo " make status" +echo "" +echo "🔧 Logs anschauen:" +echo " make logs" +echo "" +echo "🔄 Nach Änderungen:" +echo " make deploy" diff --git a/ansible/netcup-simple-deploy/deploy.yml b/ansible/netcup-simple-deploy/deploy.yml new file mode 100644 index 00000000..017b932a --- /dev/null +++ b/ansible/netcup-simple-deploy/deploy.yml @@ -0,0 +1,163 @@ +--- +# Ultra-einfaches Netcup Deployment (Port-Konflikt behoben) + +- name: Deploy App to Netcup VPS (Debian Clean) + hosts: all + become: yes + vars_files: + - inventory/group_vars.yml + + tasks: + - name: Clean up any existing Docker repositories + file: + path: "{{ item }}" + state: absent + loop: + - /etc/apt/sources.list.d/docker.list + - /etc/apt/sources.list.d/download_docker_com_linux_debian.list + - /etc/apt/keyrings/docker.gpg + - /etc/apt/keyrings/docker.asc + ignore_errors: yes + + - name: Remove any Docker GPG keys from apt-key + shell: apt-key del 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 || true + ignore_errors: yes + + - name: Update apt cache after cleanup + apt: + update_cache: yes + + - name: Install basic packages first + apt: + name: + - nginx + - certbot + - python3-certbot-nginx + - git + - curl + - rsync + - ca-certificates + - gnupg + - lsb-release + state: present + + - name: Create keyrings directory + file: + path: /etc/apt/keyrings + state: directory + mode: '0755' + + - name: Add Docker GPG key (new method) + shell: | + curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + args: + creates: /etc/apt/keyrings/docker.gpg + + - name: Add Docker repository (new method) + shell: | + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + args: + creates: /etc/apt/sources.list.d/docker.list + + - name: Update apt cache + apt: + update_cache: yes + + - name: Install Docker + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + + - name: Start and enable Docker + systemd: + name: docker + state: started + enabled: yes + + - name: Add user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + + - name: Stop nginx temporarily (to avoid port conflicts) + systemd: + name: nginx + state: stopped + ignore_errors: yes + + - name: Deploy webapp + include_role: + name: webapp + + - name: Configure Nginx reverse proxy + template: + src: roles/webapp/templates/nginx-site.conf.j2 + dest: /etc/nginx/sites-available/{{ domain }} + backup: yes + notify: reload nginx + + - name: Enable site + file: + src: /etc/nginx/sites-available/{{ domain }} + dest: /etc/nginx/sites-enabled/{{ domain }} + state: link + notify: reload nginx + + - name: Remove default site + file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: reload nginx + + - name: Test nginx configuration + command: nginx -t + register: nginx_test + + - name: Start nginx + systemd: + name: nginx + state: started + enabled: yes + + - name: Generate SSL certificate + command: > + certbot --nginx -d {{ domain }} + --non-interactive --agree-tos + --email {{ ssl_email }} + args: + creates: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem" + + - name: Setup SSL renewal + cron: + name: "Renew SSL" + minute: "0" + hour: "3" + job: "certbot renew --quiet" + + - name: Wait for app to be ready + wait_for: + port: 80 + delay: 10 + timeout: 60 + + - name: Health check + uri: + url: "https://{{ domain }}" + method: GET + status_code: [200, 301, 302] + retries: 5 + delay: 10 + ignore_errors: yes + + handlers: + - name: reload nginx + systemd: + name: nginx + state: reloaded diff --git a/ansible/netcup-simple-deploy/inventory/group_vars.yml b/ansible/netcup-simple-deploy/inventory/group_vars.yml new file mode 100644 index 00000000..ea04148c --- /dev/null +++ b/ansible/netcup-simple-deploy/inventory/group_vars.yml @@ -0,0 +1,19 @@ +--- +# Globale Einstellungen + +# Docker-Einstellungen +docker_compose_version: "2.24.0" + +# Nginx-Einstellungen +nginx_client_max_body_size: "50M" +nginx_worker_connections: 1024 + +# SSL-Einstellungen +ssl_protocols: "TLSv1.2 TLSv1.3" + +# App-Verzeichnis auf dem Server +app_directory: "/opt/{{ app_name }}" + +# Health Check +health_check_url: "/health" +health_check_timeout: 30 diff --git a/ansible/netcup-simple-deploy/inventory/hosts.yml b/ansible/netcup-simple-deploy/inventory/hosts.yml new file mode 100644 index 00000000..38f35e30 --- /dev/null +++ b/ansible/netcup-simple-deploy/inventory/hosts.yml @@ -0,0 +1,26 @@ +--- +# Netcup Inventar für PHP-Projekt (Fixed paths) + +all: + hosts: + netcup-server: + ansible_host: 94.16.110.151 + ansible_user: deploy + ansible_ssh_private_key_file: /home/michael/.ssh/staging + + # Server-Details + domain: "test.michaelschiemer.de" + ssl_email: "kontakt@michaelschiemer.de" + + # App-Konfiguration + app_name: "michaelschiemer" + app_port: 8000 + + # Pfad zu deinem Projekt (ABSOLUT!) + local_app_path: "/home/michael/dev/michaelschiemer" # Absoluter Pfad zu deinem Hauptprojekt + + # Umgebungsvariablen für deine App (wird in .env geschrieben) + app_env: + APP_ENV: "production" + DATABASE_URL: "sqlite:///app/data/app.db" + # Füge hier weitere ENV-Variablen hinzu die deine App braucht diff --git a/ansible/netcup-simple-deploy/restart-app.yml b/ansible/netcup-simple-deploy/restart-app.yml new file mode 100644 index 00000000..c41c0e64 --- /dev/null +++ b/ansible/netcup-simple-deploy/restart-app.yml @@ -0,0 +1,91 @@ +--- +# Restart application containers after file upload + +- name: Restart Application Containers + hosts: all + become: yes + vars_files: + - inventory/group_vars.yml + + tasks: + - name: Check if app directory exists + stat: + path: "{{ app_directory }}" + register: app_dir_exists + + - name: Fail if app directory doesn't exist + fail: + msg: "App directory {{ app_directory }} not found. Please deploy first with deploy.yml" + when: not app_dir_exists.stat.exists + + - name: Check which docker compose command is available + shell: | + if docker compose version >/dev/null 2>&1; then + echo "docker compose" + elif docker-compose --version >/dev/null 2>&1; then + echo "docker-compose" + else + echo "none" + fi + register: docker_compose_cmd + changed_when: false + + - name: Fail if docker compose not available + fail: + msg: "Neither 'docker compose' nor 'docker-compose' is available" + when: docker_compose_cmd.stdout == "none" + + - name: Show current container status + shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} ps" + register: container_status_before + ignore_errors: yes + changed_when: false + + - name: Stop existing containers + shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} down" + register: stop_result + + - name: Start containers with updated files + shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} up -d --build" + register: start_result + + - name: Wait for application to start + wait_for: + port: "{{ app_port }}" + host: "127.0.0.1" + delay: 5 + timeout: 60 + + - name: Test if app is accessible + uri: + url: "http://127.0.0.1:{{ app_port }}/" + method: GET + status_code: [200, 301, 302] + register: app_test + ignore_errors: yes + + - name: Show final container status + shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} ps" + register: container_status_after + changed_when: false + + - name: Show restart result + debug: + msg: | + 🔄 Application restart completed! + + 📂 Directory: {{ app_directory }} + 🐳 Docker Compose: {{ docker_compose_cmd.stdout }} + + 🚀 Restart status: {{ 'Success' if start_result.rc == 0 else 'Failed' }} + + {% if app_test.status is defined and (app_test.status == 200 or app_test.status == 301 or app_test.status == 302) %} + ✅ App is responding (HTTP {{ app_test.status }}) + 🌐 Available at: https://{{ domain }} + {% else %} + ⚠️ App health check failed - please check logs + 🔍 Check logs with: cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} logs + {% endif %} + + 📊 Container Status: + {{ container_status_after.stdout }} diff --git a/ansible/netcup-simple-deploy/roles/webapp/defaults/main.yml b/ansible/netcup-simple-deploy/roles/webapp/defaults/main.yml new file mode 100644 index 00000000..28abeeea --- /dev/null +++ b/ansible/netcup-simple-deploy/roles/webapp/defaults/main.yml @@ -0,0 +1,24 @@ +--- +# Default variables for webapp role (Port-Konflikt behoben) + +# App directory on server +app_directory: "/opt/{{ app_name }}" + +# PHP settings +php_version: "8.4" + +# Health check +health_check_url: "/health" +health_check_timeout: 30 + +# Default app settings if not defined in inventory +app_name: "myapp" +app_port: 8000 # PHP läuft auf Port 8080, nginx auf Port 80 +domain: "test.michaelschiemer.de" +ssl_email: "kontakt@michaelschiemer.de" + +# Default environment variables +app_env: + APP_ENV: "production" + PHP_MEMORY_LIMIT: "256M" + PHP_UPLOAD_MAX_FILESIZE: "50M" diff --git a/ansible/netcup-simple-deploy/roles/webapp/tasks/main.yml b/ansible/netcup-simple-deploy/roles/webapp/tasks/main.yml new file mode 100644 index 00000000..e8e06b38 --- /dev/null +++ b/ansible/netcup-simple-deploy/roles/webapp/tasks/main.yml @@ -0,0 +1,272 @@ +--- +# PHP Webapp Deployment (Handle missing PHP config files) + +- name: Create app directory + file: + path: "{{ app_directory }}" + state: directory + mode: '0755' + +- name: Check if docker-compose.yml exists locally first + local_action: + module: stat + path: "{{ local_app_path }}/docker-compose.yml" + register: local_compose_exists + become: no + +- name: Show local docker-compose.yml status + debug: + msg: | + 🔍 Local docker-compose.yml check: + - Path: {{ local_app_path }}/docker-compose.yml + - Exists: {{ local_compose_exists.stat.exists }} + +- name: Fail if docker-compose.yml doesn't exist locally + fail: + msg: | + ❌ docker-compose.yml nicht im lokalen Projekt gefunden! + + Geprüft: {{ local_app_path }}/docker-compose.yml + + Bitte stelle sicher, dass eine docker-compose.yml in deinem Projekt-Root existiert. + when: not local_compose_exists.stat.exists + +- name: Upload project files with working synchronize + synchronize: + src: "{{ local_app_path }}/" + dest: "{{ app_directory }}/" + delete: no + archive: yes + checksum: yes + rsync_opts: + - "--exclude=ansible" + - "--exclude=.git" + - "--exclude=vendor" + - "--exclude=node_modules" + - "--exclude=storage/logs" + - "--exclude=cache" + - "--exclude=logs" + - "--exclude=dist" + - "--exclude=.archive" + - "--exclude=x_ansible" + - "--verbose" + register: sync_result + +- name: Check if required PHP config files exist + stat: + path: "{{ app_directory }}/docker/php/php.production.ini" + register: php_prod_config + +- name: Create missing PHP production config if needed + copy: + content: | + ; PHP Production Configuration + memory_limit = 256M + upload_max_filesize = 50M + post_max_size = 50M + max_execution_time = 300 + max_input_vars = 3000 + + ; Error reporting for production + display_errors = Off + log_errors = On + error_log = /var/log/php_errors.log + + ; Opcache settings + opcache.enable = 1 + opcache.memory_consumption = 128 + opcache.max_accelerated_files = 4000 + opcache.revalidate_freq = 2 + opcache.validate_timestamps = 0 + dest: "{{ app_directory }}/docker/php/php.production.ini" + mode: '0644' + when: not php_prod_config.stat.exists + +- name: Check if common PHP config exists + stat: + path: "{{ app_directory }}/docker/php/php.common.ini" + register: php_common_config + +- name: Create missing PHP common config if needed + copy: + content: | + ; PHP Common Configuration + date.timezone = Europe/Berlin + short_open_tag = Off + expose_php = Off + + ; Security + allow_url_fopen = On + allow_url_include = Off + dest: "{{ app_directory }}/docker/php/php.common.ini" + mode: '0644' + when: not php_common_config.stat.exists + +- name: Ensure PHP config directory exists + file: + path: "{{ app_directory }}/docker/php" + state: directory + mode: '0755' + +- name: Show what files were synced + debug: + msg: | + 📂 Sync Result: + - Changed: {{ sync_result.changed }} + +- name: Ensure proper permissions for directories + file: + path: "{{ item }}" + state: directory + mode: '0755' + recurse: yes + loop: + - "{{ app_directory }}/public" + - "{{ app_directory }}/src" + - "{{ app_directory }}/docker" + ignore_errors: yes + +- name: Create storage directories if they don't exist + file: + path: "{{ app_directory }}/{{ item }}" + state: directory + mode: '0777' + loop: + - "storage/logs" + - "storage/cache" + - "cache" + - "logs" + ignore_errors: yes + +- name: Check if docker-compose.yml exists in project root + stat: + path: "{{ app_directory }}/docker-compose.yml" + register: compose_exists + +- name: Check if docker-compose.yml exists in docker folder + stat: + path: "{{ app_directory }}/docker/docker-compose.yml" + register: compose_docker_exists + +- name: Use docker-compose.yml from docker folder if available + copy: + src: "{{ app_directory }}/docker/docker-compose.yml" + dest: "{{ app_directory }}/docker-compose.yml" + remote_src: yes + when: compose_docker_exists.stat.exists and not compose_exists.stat.exists + +- name: Manually copy docker-compose.yml if sync failed + copy: + src: "{{ local_app_path }}/docker-compose.yml" + dest: "{{ app_directory }}/docker-compose.yml" + mode: '0644' + when: local_compose_exists.stat.exists and not compose_exists.stat.exists + +- name: Show which docker-compose.yml we found + debug: + msg: | + 📋 Docker Compose Status: + - Root compose exists: {{ compose_exists.stat.exists }} + - Docker folder compose exists: {{ compose_docker_exists.stat.exists }} + +- name: Fail if no docker-compose.yml found + fail: + msg: | + ❌ Keine docker-compose.yml gefunden! + + Erwartet in: + - {{ app_directory }}/docker-compose.yml + - {{ app_directory }}/docker/docker-compose.yml + + Bitte erstelle eine docker-compose.yml in deinem Projekt. + when: not compose_exists.stat.exists and not compose_docker_exists.stat.exists + +- name: Check if public/index.php exists after sync + stat: + path: "{{ app_directory }}/public/index.php" + register: index_exists + +- name: Fail if index.php not found + fail: + msg: | + ❌ index.php nicht gefunden! + + Geprüft: {{ app_directory }}/public/index.php + + Die Dateien wurden nicht korrekt übertragen. + when: not index_exists.stat.exists + +- name: Create environment file + template: + src: app.env.j2 + dest: "{{ app_directory }}/.env" + register: env_result + +- name: Check which docker compose command is available + shell: | + if docker compose version >/dev/null 2>&1; then + echo "docker compose" + elif docker-compose --version >/dev/null 2>&1; then + echo "docker-compose" + else + echo "none" + fi + register: docker_compose_cmd + changed_when: false + +- name: Stop existing containers (if any) + shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} down" + when: docker_compose_cmd.stdout != "none" + ignore_errors: yes + +- name: Start containers with your docker-compose.yml + shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} up -d --build" + register: start_result + when: docker_compose_cmd.stdout != "none" + +- name: Wait for application to start + wait_for: + port: "{{ app_port }}" + host: "127.0.0.1" + delay: 5 + timeout: 60 + +- name: Test if app is accessible + uri: + url: "http://127.0.0.1:{{ app_port }}/" + method: GET + status_code: [200, 301, 302] + register: app_test + ignore_errors: yes + +- name: Show container status + shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} ps" + register: container_status + when: docker_compose_cmd.stdout != "none" + ignore_errors: yes + +- name: Show deployment result + debug: + msg: | + 🎉 Projekt {{ app_name }} erfolgreich deployed! + + 📂 Projektdateien synchronisiert von: {{ local_app_path }} + 🐳 Verwendete docker-compose.yml: + {% if compose_exists.stat.exists %} + ✅ Aus Projekt-Root: {{ app_directory }}/docker-compose.yml + {% elif compose_docker_exists.stat.exists %} + ✅ Aus docker/ Ordner: {{ app_directory }}/docker/docker-compose.yml + {% endif %} + 🌐 Erreichbar unter: https://{{ domain }} + ⚙️ Docker Compose: {{ docker_compose_cmd.stdout }} + + 📁 index.php Status: {{ 'Gefunden ✅' if index_exists.stat.exists else 'Nicht gefunden ❌' }} + + 🐳 Container Status: + {{ container_status.stdout if container_status.stdout is defined else 'Status konnte nicht abgerufen werden' }} + + {% if app_test.status is defined and (app_test.status == 200 or app_test.status == 301 or app_test.status == 302) %} + ✅ App reagiert erfolgreich (HTTP {{ app_test.status }}) + {% else %} + ⚠️ App-Test fehlgeschlagen - prüfe Logs mit 'make logs' + {% endif %} diff --git a/ansible/netcup-simple-deploy/roles/webapp/templates/app.env.j2 b/ansible/netcup-simple-deploy/roles/webapp/templates/app.env.j2 new file mode 100644 index 00000000..ab2f9900 --- /dev/null +++ b/ansible/netcup-simple-deploy/roles/webapp/templates/app.env.j2 @@ -0,0 +1,12 @@ +# Environment variables for {{ app_name }} + +{% for key, value in app_env.items() %} +{{ key }}={{ value }} +{% endfor %} + +# Deployment info +DEPLOYED_AT={{ ansible_date_time.iso8601 }} +DEPLOYED_BY=ansible +SERVER_NAME={{ inventory_hostname }} + +APP_PORT=8000 diff --git a/ansible/netcup-simple-deploy/roles/webapp/templates/nginx-site.conf.j2 b/ansible/netcup-simple-deploy/roles/webapp/templates/nginx-site.conf.j2 new file mode 100644 index 00000000..bbef8073 --- /dev/null +++ b/ansible/netcup-simple-deploy/roles/webapp/templates/nginx-site.conf.j2 @@ -0,0 +1,53 @@ +server { + listen 80; + server_name {{ domain }}; + + # HTTP to HTTPS redirect (wird von Certbot hinzugefügt) + + location / { + proxy_pass http://127.0.0.1:{{ app_port }}; # Weiterleitung zu PHP-Container + 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; + proxy_cache_bypass $http_upgrade; + + # PHP-spezifische Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + # File upload für PHP + client_max_body_size {{ nginx_client_max_body_size }}; + } + + # Assets (CSS, JS, Bilder) direkt servieren falls gewünscht + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + proxy_pass http://127.0.0.1:{{ app_port }}; + proxy_cache_valid 200 1d; + expires 1d; + add_header Cache-Control "public, immutable"; + } + + # PHP-Admin Tools (falls vorhanden) schützen + location ~ /(admin|phpmyadmin|adminer) { + proxy_pass http://127.0.0.1:{{ app_port }}; + + # Basis Auth hier hinzufügen falls gewünscht + # auth_basic "Admin Area"; + # auth_basic_user_file /etc/nginx/.htpasswd; + } + + # 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; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; +} diff --git a/ansible/netcup-simple-deploy/upload-only.yml b/ansible/netcup-simple-deploy/upload-only.yml new file mode 100644 index 00000000..ede7cabf --- /dev/null +++ b/ansible/netcup-simple-deploy/upload-only.yml @@ -0,0 +1,128 @@ +--- +# Nur Dateien-Upload ohne Infrastruktur-Setup + +- name: Upload Files Only to Netcup VPS + hosts: all + become: yes + vars_files: + - inventory/group_vars.yml + + tasks: + - name: Check if app directory exists + stat: + path: "{{ app_directory }}" + register: app_dir_exists + + - name: Create app directory if it doesn't exist + file: + path: "{{ app_directory }}" + state: directory + mode: '0755' + when: not app_dir_exists.stat.exists + + - name: Check if docker-compose.yml exists locally + local_action: + module: stat + path: "{{ local_app_path }}/docker-compose.yml" + register: local_compose_exists + become: no + + - name: Show upload information + debug: + msg: | + 📤 Uploading files... + - From: {{ local_app_path }} + - To: {{ app_directory }} + - Docker Compose available: {{ local_compose_exists.stat.exists }} + + - name: Upload project files + synchronize: + src: "{{ local_app_path }}/" + dest: "{{ app_directory }}/" + delete: no + archive: yes + checksum: yes + rsync_opts: + - "--exclude=ansible" + - "--exclude=.git" + - "--exclude=vendor" + - "--exclude=node_modules" + - "--exclude=storage/logs" + - "--exclude=cache" + - "--exclude=logs" + - "--exclude=dist" + - "--exclude=.archive" + - "--exclude=x_ansible" + - "--exclude=.env" + - "--verbose" + register: sync_result + + - name: Ensure proper permissions for directories + file: + path: "{{ item }}" + state: directory + mode: '0755' + recurse: yes + loop: + - "{{ app_directory }}/public" + - "{{ app_directory }}/src" + - "{{ app_directory }}/docker" + ignore_errors: yes + + - name: Create storage directories if they don't exist + file: + path: "{{ app_directory }}/{{ item }}" + state: directory + mode: '0777' + loop: + - "storage/logs" + - "storage/cache" + - "cache" + - "logs" + ignore_errors: yes + + - name: Check if .env exists on server + stat: + path: "{{ app_directory }}/.env" + register: server_env_exists + + - name: Create environment file if it doesn't exist + template: + src: roles/webapp/templates/app.env.j2 + dest: "{{ app_directory }}/.env" + when: not server_env_exists.stat.exists + register: env_created + + - name: Show what was uploaded + debug: + msg: | + 📂 Upload completed! + + 📁 Files synced: {{ 'Yes' if sync_result.changed else 'No changes detected' }} + 📄 Environment file: {{ 'Created' if env_created.changed else 'Already exists' }} + + 📍 Files are now at: {{ app_directory }} + + 🔄 To restart the application, run: + cd {{ app_directory }} + docker compose down && docker compose up -d --build + + or use: make restart (if Makefile is available) + + - name: Check if containers are running (optional restart) + shell: "cd {{ app_directory }} && docker compose ps --format json" + register: container_status + ignore_errors: yes + changed_when: false + + - name: Ask about restarting containers + debug: + msg: | + ℹ️ Current container status: + {{ container_status.stdout if container_status.stdout else 'No containers found or docker compose not available' }} + + 💡 Tip: If you want to restart the application with the new files, run: + ansible-playbook restart-app.yml + + or manually: + ssh {{ ansible_host }} "cd {{ app_directory }} && docker compose restart" diff --git a/ansible/nginx-cdn-germany/.gitignore b/ansible/nginx-cdn-germany/.gitignore new file mode 100644 index 00000000..214fc92e --- /dev/null +++ b/ansible/nginx-cdn-germany/.gitignore @@ -0,0 +1,31 @@ +# Cache +*.cache +.cache/ + +# Ansible +*.retry +.ansible/ + +# System +.DS_Store +Thumbs.db + +# Logs +*.log + +# Backups +*.backup +*.bak + +# SSL Keys (niemals committen!) +*.key +*.pem +*.crt + +# Secrets +vault.yml +secrets.yml + +# Temporäre Dateien +*.tmp +*.temp diff --git a/ansible/nginx-cdn-germany/Makefile b/ansible/nginx-cdn-germany/Makefile new file mode 100644 index 00000000..d15c62a4 --- /dev/null +++ b/ansible/nginx-cdn-germany/Makefile @@ -0,0 +1,64 @@ +# Einfache CDN-Verwaltung mit Make + +.PHONY: deploy check health purge-cache warm-cache status reload + +# Standard deployment +deploy: + @echo "🚀 Deploying Simple CDN..." + chmod +x scripts/deploy.sh + ./scripts/deploy.sh + +# Deployment mit Check-Modus (Dry-Run) +check: + @echo "🔍 Checking deployment (dry-run)..." + ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-simple-cdn.yml --check --diff + +# Health check aller Nodes +health: + @echo "🏥 Checking CDN health..." + ansible cdn_nodes -i inventories/production/hosts.yml -m uri -a "url=https://{{ inventory_hostname }}/health method=GET" + +# Cache leeren +purge-cache: + @echo "🧹 Purging cache on all nodes..." + ansible cdn_nodes -i inventories/production/hosts.yml -m shell -a "find /var/cache/nginx/ -type f -delete" + @echo "✅ Cache purged on all nodes" + +# Cache warming +warm-cache: + @echo "🔥 Warming cache..." + chmod +x scripts/warm-cache.sh + ./scripts/warm-cache.sh + +# Status-Report +status: + @echo "📊 CDN Status Report..." + ansible cdn_nodes -i inventories/production/hosts.yml -m shell -a "echo '=== {{ inventory_hostname }} ===' && /usr/local/bin/cdn-monitor && echo ''" + +# Nginx neuladen +reload: + @echo "⚙️ Reloading nginx configuration..." + ansible cdn_nodes -i inventories/production/hosts.yml -m systemd -a "name=nginx state=reloaded" + +# SSL-Zertifikate erneuern +renew-ssl: + @echo "🔐 Renewing SSL certificates..." + ansible cdn_nodes -i inventories/production/hosts.yml -m shell -a "certbot renew --quiet" + +# Interaktive Verwaltung +manage: + @echo "🔧 Starting interactive management..." + ansible-playbook -i inventories/production/hosts.yml playbooks/manage-cdn.yml + +# Hilfe +help: + @echo "📖 Available commands:" + @echo " make deploy - Deploy CDN" + @echo " make check - Test deployment (dry-run)" + @echo " make health - Check all nodes health" + @echo " make purge-cache - Clear all cache" + @echo " make warm-cache - Warm cache with popular URLs" + @echo " make status - Show detailed status" + @echo " make reload - Reload nginx config" + @echo " make renew-ssl - Renew SSL certificates" + @echo " make manage - Interactive management" diff --git a/ansible/nginx-cdn-germany/README.md b/ansible/nginx-cdn-germany/README.md new file mode 100644 index 00000000..1063fd20 --- /dev/null +++ b/ansible/nginx-cdn-germany/README.md @@ -0,0 +1,48 @@ +# Simple Nginx CDN für Deutschland + +Dieses Ansible-Projekt erstellt ein einfaches, aber effektives CDN nur mit Nginx für deutsche Server. + +## Schnellstart + +1. **Konfiguration anpassen:** + ```bash + # Server-IPs eintragen + vim inventories/production/hosts.yml + + # Domains anpassen + vim inventories/production/group_vars/all/main.yml + ``` + +2. **Deployment:** + ```bash + # Testen + ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-simple-cdn.yml --check + + # Deployen + ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-simple-cdn.yml + ``` + +3. **Verwalten:** + ```bash + # Cache leeren + make purge-cache + + # Status prüfen + make health + ``` + +## Struktur + +- `inventories/` - Server-Konfiguration +- `roles/` - Ansible-Rollen +- `playbooks/` - Deployment-Skripte +- `scripts/` - Hilfsskripte + +## Features + +✅ Nginx-basiertes CDN +✅ SSL mit Let's Encrypt +✅ DSGVO-konforme Logs +✅ Einfaches Monitoring +✅ Cache-Management +✅ Rate Limiting diff --git a/ansible/nginx-cdn-germany/SETUP.md b/ansible/nginx-cdn-germany/SETUP.md new file mode 100644 index 00000000..0237b4dd --- /dev/null +++ b/ansible/nginx-cdn-germany/SETUP.md @@ -0,0 +1,115 @@ +# SETUP.md - Einrichtungsanleitung + +## 1. Vorbereitung + +### Server vorbereiten +```bash +# Für jeden CDN-Server (als root): +apt update && apt upgrade -y +apt install -y python3 python3-pip +``` + +### SSH-Keys einrichten +```bash +# Auf deinem lokalen Rechner: +ssh-keygen -t rsa -b 4096 -C "cdn-deployment" +ssh-copy-id root@cdn-fra1.example.de +ssh-copy-id root@cdn-ham1.example.de +ssh-copy-id root@cdn-muc1.example.de +``` + +## 2. Konfiguration anpassen + +### Domains und IPs ändern +```bash +# 1. Server-IPs eintragen +vim inventories/production/hosts.yml + +# 2. Domain-Namen anpassen +vim inventories/production/group_vars/all/main.yml +``` + +**Wichtig:** Ändere diese Werte: +- `cdn_domain: "cdn.example.de"` → deine CDN-Domain +- `ssl_email: "admin@example.de"` → deine E-Mail +- `origin_domain: "www.example.de"` → deine Website +- Alle IP-Adressen in `hosts.yml` + +## 3. DNS konfigurieren + +Bevor du deployest, stelle sicher dass deine CDN-Domain zu den Servern zeigt: + +```bash +# A-Records für deine CDN-Domain: +cdn.example.de. IN A 10.0.1.10 # Frankfurt +cdn.example.de. IN A 10.0.2.10 # Hamburg +cdn.example.de. IN A 10.0.3.10 # München +``` + +## 4. Deployment + +```bash +# Testen +make check + +# Deployen +make deploy + +# Health-Check +make health +``` + +## 5. Testen + +```bash +# CDN testen +curl -I https://cdn.example.de/health + +# Cache-Header prüfen +curl -I https://cdn.example.de/some-static-file.css + +# Performance testen +time curl -o /dev/null -s https://cdn.example.de/ +``` + +## 6. Wartung + +```bash +# Cache leeren +make purge-cache + +# Status prüfen +make status + +# SSL erneuern +make renew-ssl + +# Interaktive Verwaltung +make manage +``` + +## Troubleshooting + +### Ansible-Verbindung testen +```bash +ansible all -m ping +``` + +### Nginx-Konfiguration prüfen +```bash +ansible cdn_nodes -m shell -a "nginx -t" +``` + +### Logs anschauen +```bash +ansible cdn_nodes -m shell -a "tail -f /var/log/nginx/error.log" +``` + +### SSL-Probleme +```bash +# SSL-Status prüfen +ansible cdn_nodes -m shell -a "certbot certificates" + +# Manuell erneuern +ansible cdn_nodes -m shell -a "certbot renew --force-renewal" +``` diff --git a/ansible/nginx-cdn-germany/ansible.cfg b/ansible/nginx-cdn-germany/ansible.cfg new file mode 100644 index 00000000..dc5ecbe1 --- /dev/null +++ b/ansible/nginx-cdn-germany/ansible.cfg @@ -0,0 +1,15 @@ +[defaults] +inventory = inventories/production/hosts.yml +host_key_checking = False +timeout = 30 +forks = 5 + +[privilege_escalation] +become = True +become_method = sudo +become_user = root +become_ask_pass = False + +[ssh_connection] +ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no +control_path = /tmp/ansible-ssh-%%h-%%p-%%r diff --git a/ansible/nginx-cdn-germany/init.sh b/ansible/nginx-cdn-germany/init.sh new file mode 100644 index 00000000..17540bad --- /dev/null +++ b/ansible/nginx-cdn-germany/init.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Scripts ausführbar machen +chmod +x scripts/deploy.sh +chmod +x scripts/warm-cache.sh + +echo "✅ CDN-Projekt wurde erfolgreich erstellt!" +echo "" +echo "Nächste Schritte:" +echo "1. Konfiguration anpassen:" +echo " - vim inventories/production/hosts.yml" +echo " - vim inventories/production/group_vars/all/main.yml" +echo "" +echo "2. SETUP.md lesen für detaillierte Anleitung" +echo "" +echo "3. Deployment testen:" +echo " make check" +echo "" +echo "4. CDN deployen:" +echo " make deploy" diff --git a/ansible/nginx-cdn-germany/inventories/production/group_vars/all/main.yml b/ansible/nginx-cdn-germany/inventories/production/group_vars/all/main.yml new file mode 100644 index 00000000..42eea6aa --- /dev/null +++ b/ansible/nginx-cdn-germany/inventories/production/group_vars/all/main.yml @@ -0,0 +1,26 @@ +--- +# Globale Variablen für das CDN + +# Domain-Konfiguration (ÄNDERE DIESE!) +cdn_domain: "cdn.example.de" # Deine CDN-Domain +ssl_email: "admin@example.de" # E-Mail für SSL-Zertifikate +origin_domain: "www.example.de" # Deine Haupt-Website + +# Cache-Einstellungen +cache_settings: + static_files_ttl: "1y" # CSS, JS, Fonts + images_ttl: "30d" # Bilder + html_ttl: "5m" # HTML-Seiten + api_ttl: "0" # APIs (kein Caching) + +# DSGVO-Einstellungen +gdpr_settings: + log_retention_days: 30 + anonymize_ips: true + cookie_consent_required: true + +# Rate Limiting +rate_limits: + api: "10r/s" + static: "100r/s" + images: "50r/s" diff --git a/ansible/nginx-cdn-germany/inventories/production/group_vars/cdn_nodes/main.yml b/ansible/nginx-cdn-germany/inventories/production/group_vars/cdn_nodes/main.yml new file mode 100644 index 00000000..7dc0da77 --- /dev/null +++ b/ansible/nginx-cdn-germany/inventories/production/group_vars/cdn_nodes/main.yml @@ -0,0 +1,22 @@ +--- +# CDN-Node spezifische Konfiguration + +# Nginx Performance +nginx_worker_processes: "auto" +nginx_worker_connections: 2048 +nginx_keepalive_timeout: 65 + +# Performance-Tuning +tcp_optimizations: + tcp_nodelay: "on" + tcp_nopush: "on" + sendfile: "on" + +# Proxy-Einstellungen +proxy_settings: + connect_timeout: "5s" + send_timeout: "10s" + read_timeout: "10s" + buffering: "on" + buffer_size: "64k" + buffers: "8 64k" diff --git a/ansible/nginx-cdn-germany/inventories/production/hosts-grouped-keys.yml.example b/ansible/nginx-cdn-germany/inventories/production/hosts-grouped-keys.yml.example new file mode 100644 index 00000000..de70c8d3 --- /dev/null +++ b/ansible/nginx-cdn-germany/inventories/production/hosts-grouped-keys.yml.example @@ -0,0 +1,47 @@ +--- +# Inventar mit gruppierten SSH-Schlüsseln + +all: + children: + origin_servers: + hosts: + origin1.example.de: + ansible_host: 192.168.1.10 + origin2.example.de: + ansible_host: 192.168.1.11 + vars: + ansible_ssh_private_key_file: ~/.ssh/origin_servers_key + + cdn_nodes: + children: + primary_nodes: + hosts: + cdn-fra1.example.de: + ansible_host: 10.0.1.10 + city: "Frankfurt" + region: "Hessen" + tier: "primary" + cache_size: "50g" + vars: + ansible_ssh_private_key_file: ~/.ssh/cdn_primary_key + + secondary_nodes: + hosts: + cdn-ham1.example.de: + ansible_host: 10.0.2.10 + city: "Hamburg" + region: "Hamburg" + tier: "secondary" + cache_size: "20g" + cdn-muc1.example.de: + ansible_host: 10.0.3.10 + city: "München" + region: "Bayern" + tier: "secondary" + cache_size: "20g" + vars: + ansible_ssh_private_key_file: ~/.ssh/cdn_secondary_key + + vars: + ansible_user: root + ansible_ssh_common_args: '-o StrictHostKeyChecking=no' diff --git a/ansible/nginx-cdn-germany/inventories/production/hosts-individual-keys.yml.example b/ansible/nginx-cdn-germany/inventories/production/hosts-individual-keys.yml.example new file mode 100644 index 00000000..7e37822b --- /dev/null +++ b/ansible/nginx-cdn-germany/inventories/production/hosts-individual-keys.yml.example @@ -0,0 +1,48 @@ +--- +# Inventar mit individuellen SSH-Schlüsseln + +all: + children: + origin_servers: + hosts: + origin1.example.de: + ansible_host: 192.168.1.10 + datacenter: "Frankfurt" + ansible_ssh_private_key_file: ~/.ssh/origin1_key + origin2.example.de: + ansible_host: 192.168.1.11 + datacenter: "Frankfurt" + ansible_ssh_private_key_file: ~/.ssh/origin2_key + + cdn_nodes: + hosts: + # Frankfurt - Primary + cdn-fra1.example.de: + ansible_host: 10.0.1.10 + city: "Frankfurt" + region: "Hessen" + tier: "primary" + cache_size: "50g" + ansible_ssh_private_key_file: ~/.ssh/cdn_fra1_key + + # Hamburg - Secondary + cdn-ham1.example.de: + ansible_host: 10.0.2.10 + city: "Hamburg" + region: "Hamburg" + tier: "secondary" + cache_size: "20g" + ansible_ssh_private_key_file: ~/.ssh/cdn_ham1_key + + # München - Secondary + cdn-muc1.example.de: + ansible_host: 10.0.3.10 + city: "München" + region: "Bayern" + tier: "secondary" + cache_size: "20g" + ansible_ssh_private_key_file: ~/.ssh/cdn_muc1_key + + vars: + ansible_user: root + ansible_ssh_common_args: '-o StrictHostKeyChecking=no' diff --git a/ansible/nginx-cdn-germany/inventories/production/hosts.yml b/ansible/nginx-cdn-germany/inventories/production/hosts.yml new file mode 100644 index 00000000..4e0f1b63 --- /dev/null +++ b/ansible/nginx-cdn-germany/inventories/production/hosts.yml @@ -0,0 +1,45 @@ +--- +# Inventar für deutsches CDN + +all: + children: + origin_servers: + hosts: + origin1.example.de: + ansible_host: 192.168.1.10 # Ändere diese IP + datacenter: "Frankfurt" + origin2.example.de: + ansible_host: 192.168.1.11 # Ändere diese IP + datacenter: "Frankfurt" + + cdn_nodes: + hosts: + # Frankfurt - Primary + cdn-fra1.example.de: + ansible_host: 10.0.1.10 # Ändere diese IP + city: "Frankfurt" + region: "Hessen" + tier: "primary" + cache_size: "50g" + + # Hamburg - Secondary + cdn-ham1.example.de: + ansible_host: 10.0.2.10 # Ändere diese IP + city: "Hamburg" + region: "Hamburg" + tier: "secondary" + cache_size: "20g" + + # München - Secondary + cdn-muc1.example.de: + ansible_host: 10.0.3.10 # Ändere diese IP + city: "München" + region: "Bayern" + tier: "secondary" + cache_size: "20g" + + vars: + # SSH-Konfiguration + ansible_user: root + ansible_ssh_private_key_file: ~/.ssh/id_rsa + ansible_ssh_common_args: '-o StrictHostKeyChecking=no' diff --git a/ansible/nginx-cdn-germany/playbooks/deploy-simple-cdn.yml b/ansible/nginx-cdn-germany/playbooks/deploy-simple-cdn.yml new file mode 100644 index 00000000..e95219f4 --- /dev/null +++ b/ansible/nginx-cdn-germany/playbooks/deploy-simple-cdn.yml @@ -0,0 +1,43 @@ +--- +# Simple CDN Deployment + +- name: Deploy Simple CDN for Germany + hosts: cdn_nodes + become: yes + serial: 1 # Ein Node nach dem anderen + + pre_tasks: + - name: Update system packages + apt: + update_cache: yes + upgrade: dist + + - name: Install required packages + apt: + name: + - nginx + - certbot + - python3-certbot-nginx + - curl + - htop + - bc + state: present + + roles: + - nginx-cdn-config + - ssl-certificates + - simple-monitoring + + post_tasks: + - name: Verify CDN health + uri: + url: "https://{{ inventory_hostname }}/health" + method: GET + status_code: 200 + validate_certs: yes + retries: 5 + delay: 10 + + - name: Display deployment success + debug: + msg: "✅ CDN Node {{ inventory_hostname }} ({{ city }}) successfully deployed!" diff --git a/ansible/nginx-cdn-germany/playbooks/manage-cdn.yml b/ansible/nginx-cdn-germany/playbooks/manage-cdn.yml new file mode 100644 index 00000000..c349bcd2 --- /dev/null +++ b/ansible/nginx-cdn-germany/playbooks/manage-cdn.yml @@ -0,0 +1,68 @@ +--- +# CDN Management Tasks +- name: CDN Management and Maintenance + hosts: cdn_nodes + become: yes + vars_prompt: + - name: action + prompt: "What action? (purge-cache/reload-config/check-health/view-stats/warm-cache)" + private: no + + tasks: + - name: Purge all cache + shell: find /var/cache/nginx/ -type f -delete + when: action == "purge-cache" + + - name: Display cache purge result + debug: + msg: "✅ Cache purged on {{ inventory_hostname }}" + when: action == "purge-cache" + + - name: Reload nginx configuration + systemd: + name: nginx + state: reloaded + when: action == "reload-config" + + - name: Check CDN health + uri: + url: "https://{{ inventory_hostname }}/health" + method: GET + status_code: 200 + register: health_result + when: action == "check-health" + + - name: Display health result + debug: + msg: "{{ health_result.content }}" + when: action == "check-health" + + - name: Show cache and system statistics + shell: | + echo "=== Cache Size ===" + du -sh /var/cache/nginx/ + echo "=== Cache Files ===" + find /var/cache/nginx/ -type f | wc -l + echo "=== System Load ===" + uptime + echo "=== Memory Usage ===" + free -h + echo "=== Disk Usage ===" + df -h / + register: stats_result + when: action == "view-stats" + + - name: Display statistics + debug: + msg: "{{ stats_result.stdout_lines }}" + when: action == "view-stats" + + - name: Warm cache with popular URLs + uri: + url: "https://{{ inventory_hostname }}{{ item }}" + method: GET + loop: + - "/" + - "/health" + ignore_errors: yes + when: action == "warm-cache" diff --git a/ansible/nginx-cdn-germany/roles/nginx-cdn-config/handlers/main.yml b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/handlers/main.yml new file mode 100644 index 00000000..c40e7cbb --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: reload nginx + systemd: + name: nginx + state: reloaded + +- name: restart nginx + systemd: + name: nginx + state: restarted diff --git a/ansible/nginx-cdn-germany/roles/nginx-cdn-config/tasks/main.yml b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/tasks/main.yml new file mode 100644 index 00000000..1da7cfef --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/tasks/main.yml @@ -0,0 +1,64 @@ +--- +# Nginx CDN Konfiguration + +- name: Remove default nginx config + file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: reload nginx + +- name: Create nginx directories + file: + path: "{{ item }}" + state: directory + owner: www-data + group: www-data + mode: '0755' + loop: + - /var/cache/nginx/static + - /var/cache/nginx/images + - /var/cache/nginx/html + - /var/log/nginx/cdn + - /etc/nginx/includes + +- name: Configure nginx main config + template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + backup: yes + notify: reload nginx + +- name: Create nginx includes + template: + src: "{{ item }}.j2" + dest: "/etc/nginx/includes/{{ item }}" + loop: + - security-headers.conf + - rate-limiting.conf + - gzip-settings.conf + notify: reload nginx + +- name: Configure CDN site + template: + src: cdn-site.conf.j2 + dest: /etc/nginx/sites-available/cdn + backup: yes + notify: reload nginx + +- name: Enable CDN site + file: + src: /etc/nginx/sites-available/cdn + dest: /etc/nginx/sites-enabled/cdn + state: link + notify: reload nginx + +- name: Test nginx configuration + command: nginx -t + register: nginx_test + failed_when: nginx_test.rc != 0 + +- name: Start and enable nginx + systemd: + name: nginx + state: started + enabled: yes diff --git a/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/cdn-site.conf.j2 b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/cdn-site.conf.j2 new file mode 100644 index 00000000..0a206d47 --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/cdn-site.conf.j2 @@ -0,0 +1,195 @@ +server { + listen 80; + listen [::]:80; + server_name {{ cdn_domain }}; + + # Redirect HTTP to HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name {{ cdn_domain }}; + + # SSL Configuration (wird von Certbot automatisch gefüllt) + ssl_certificate /etc/letsencrypt/live/{{ cdn_domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ cdn_domain }}/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + # CDN Headers + add_header X-CDN-Node "{{ city }}, Deutschland"; + add_header X-Cache-Status $upstream_cache_status always; + add_header X-Served-By "nginx-{{ inventory_hostname }}"; + + # Security Headers + include /etc/nginx/includes/security-headers.conf; + + # Logging + access_log /var/log/nginx/cdn/{{ cdn_domain }}.access.log cdn_format; + error_log /var/log/nginx/cdn/{{ cdn_domain }}.error.log warn; + + # Rate Limiting + include /etc/nginx/includes/rate-limiting.conf; + + # Gzip Compression + include /etc/nginx/includes/gzip-settings.conf; + + # Nginx Status für Monitoring + location /nginx_status { + stub_status on; + access_log off; + allow 127.0.0.1; + allow 10.0.0.0/8; + deny all; + } + + ## + # Static Files (CSS, JS, Fonts) - Lange Cache-Zeit + ## + location ~* \.(css|js|woff|woff2|ttf|eot|ico)$ { + proxy_pass https://origin_servers; + proxy_ssl_verify off; + + # Cache Settings + proxy_cache static_cache; + proxy_cache_valid 200 302 {{ cache_settings.static_files_ttl }}; + proxy_cache_valid 404 1m; + proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; + proxy_cache_lock on; + proxy_cache_revalidate on; + + # Headers + expires {{ cache_settings.static_files_ttl }}; + add_header Cache-Control "public, immutable"; + add_header Vary "Accept-Encoding"; + + # Rate Limiting + limit_req zone=static_files burst=50 nodelay; + + # Proxy Headers + proxy_set_header Host {{ origin_domain }}; + 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; + } + + ## + # Images - Mittlere Cache-Zeit + ## + location ~* \.(jpg|jpeg|png|gif|webp|svg|avif|bmp|tiff)$ { + proxy_pass https://origin_servers; + proxy_ssl_verify off; + + # Cache Settings + proxy_cache images_cache; + proxy_cache_valid 200 302 {{ cache_settings.images_ttl }}; + proxy_cache_valid 404 5m; + proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; + + # Headers + expires {{ cache_settings.images_ttl }}; + add_header Cache-Control "public"; + add_header Vary "Accept"; + + # Rate Limiting + limit_req zone=images burst=30 nodelay; + + # Proxy Headers + proxy_set_header Host {{ origin_domain }}; + 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 Endpoints - Kein Caching + ## + location /api/ { + proxy_pass https://origin_servers; + proxy_ssl_verify off; + + # Kein Caching + proxy_no_cache 1; + proxy_cache_bypass 1; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + + # Rate Limiting + limit_req zone=api burst=10 nodelay; + + # CORS Headers + add_header Access-Control-Allow-Origin "https://{{ origin_domain }}"; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; + add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With"; + add_header Access-Control-Allow-Credentials true; + + # Proxy Headers + proxy_set_header Host {{ origin_domain }}; + 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; + + # Timeouts + proxy_connect_timeout {{ proxy_settings.connect_timeout }}; + proxy_send_timeout {{ proxy_settings.send_timeout }}; + proxy_read_timeout {{ proxy_settings.read_timeout }}; + } + + ## + # Cache Purge (nur interne IPs) + ## + location ~ /purge(/.*) { + allow 127.0.0.1; + allow 10.0.0.0/8; + allow 192.168.0.0/16; + deny all; + + proxy_cache_purge static_cache $scheme$proxy_host$1; + } + + ## + # Health Check + ## + location /health { + access_log off; + return 200 "OK - CDN Node {{ city }}\nRegion: {{ region }}\nTier: {{ tier }}\nTimestamp: $time_iso8601\n"; + add_header Content-Type text/plain; + } + + ## + # Default Location - HTML Caching + ## + location / { + proxy_pass https://origin_servers; + proxy_ssl_verify off; + + # Cache Settings für HTML + proxy_cache html_cache; + proxy_cache_valid 200 {{ cache_settings.html_ttl }}; + proxy_cache_valid 404 1m; + proxy_cache_bypass $arg_nocache $cookie_sessionid $http_authorization; + + # Headers + add_header Cache-Control "public, max-age=300"; + + # Proxy Headers + proxy_set_header Host {{ origin_domain }}; + 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 X-CDN-Node "{{ city }}"; + + # Timeouts + proxy_connect_timeout {{ proxy_settings.connect_timeout }}; + proxy_send_timeout {{ proxy_settings.send_timeout }}; + proxy_read_timeout {{ proxy_settings.read_timeout }}; + + # Buffering + proxy_buffering {{ proxy_settings.buffering }}; + proxy_buffer_size {{ proxy_settings.buffer_size }}; + proxy_buffers {{ proxy_settings.buffers }}; + } +} diff --git a/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/gzip-settings.conf.j2 b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/gzip-settings.conf.j2 new file mode 100644 index 00000000..83d82eb9 --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/gzip-settings.conf.j2 @@ -0,0 +1,20 @@ +# Gzip Compression Settings + +gzip on; +gzip_vary on; +gzip_min_length 1024; +gzip_comp_level 6; +gzip_proxied any; +gzip_disable "msie6"; + +gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml + application/json + application/ld+json; diff --git a/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/nginx.conf.j2 b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/nginx.conf.j2 new file mode 100644 index 00000000..734947ee --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/nginx.conf.j2 @@ -0,0 +1,75 @@ +user www-data; +worker_processes {{ nginx_worker_processes }}; +pid /run/nginx.pid; +include /etc/nginx/modules-enabled/*.conf; + +events { + worker_connections {{ nginx_worker_connections }}; + use epoll; + multi_accept on; +} + +http { + ## + # Basic Settings + ## + sendfile {{ tcp_optimizations.sendfile }}; + tcp_nopush {{ tcp_optimizations.tcp_nopush }}; + tcp_nodelay {{ tcp_optimizations.tcp_nodelay }}; + keepalive_timeout {{ nginx_keepalive_timeout }}; + types_hash_max_size 2048; + server_tokens off; + + server_names_hash_bucket_size 64; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # DSGVO-konforme Logging + ## + map $remote_addr $anonymized_ip { + ~(?P\d+\.\d+\.\d+)\.\d+ $ip.0; + ~(?P[^:]+:[^:]+:[^:]+:[^:]+):.* $ipv6::; + default 0.0.0.0; + } + + log_format cdn_format '$anonymized_ip - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + 'rt=$request_time ' + 'cache="$upstream_cache_status" ' + 'cdn_node="{{ inventory_hostname }}"'; + + access_log /var/log/nginx/access.log cdn_format; + error_log /var/log/nginx/error.log warn; + + ## + # Cache Paths + ## + proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static_cache:100m + max_size={{ cache_size }} inactive=7d use_temp_path=off; + proxy_cache_path /var/cache/nginx/images levels=1:2 keys_zone=images_cache:100m + max_size={{ cache_size }} inactive=30d use_temp_path=off; + proxy_cache_path /var/cache/nginx/html levels=1:2 keys_zone=html_cache:50m + max_size=5g inactive=1h use_temp_path=off; + + ## + # Upstream zu Origin-Servern + ## + upstream origin_servers { +{% for host in groups['origin_servers'] %} + server {{ hostvars[host]['ansible_default_ipv4']['address'] }}:443 + weight=1 max_fails=3 fail_timeout=30s; +{% endfor %} + keepalive 32; + keepalive_requests 1000; + keepalive_timeout 60s; + } + + ## + # Include configurations + ## + include /etc/nginx/includes/*.conf; + include /etc/nginx/sites-enabled/*; +} diff --git a/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/rate-limiting.conf.j2 b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/rate-limiting.conf.j2 new file mode 100644 index 00000000..60447693 --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/rate-limiting.conf.j2 @@ -0,0 +1,9 @@ +# Rate Limiting Zones + +limit_req_zone $binary_remote_addr zone=api:10m rate={{ rate_limits.api }}; +limit_req_zone $binary_remote_addr zone=static_files:10m rate={{ rate_limits.static }}; +limit_req_zone $binary_remote_addr zone=images:10m rate={{ rate_limits.images }}; + +# Connection Limiting +limit_conn_zone $binary_remote_addr zone=perip:10m; +limit_conn perip 10; diff --git a/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/security-headers.conf.j2 b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/security-headers.conf.j2 new file mode 100644 index 00000000..dbe3196c --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/nginx-cdn-config/templates/security-headers.conf.j2 @@ -0,0 +1,10 @@ +# Security Headers für CDN + +add_header X-Frame-Options "SAMEORIGIN" always; +add_header X-Content-Type-Options "nosniff" always; +add_header X-XSS-Protection "1; mode=block" always; +add_header Referrer-Policy "strict-origin-when-cross-origin" always; +add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + +# DSGVO Header +add_header X-Data-Processing "Art. 6 Abs. 1 lit. f DSGVO" always; diff --git a/ansible/nginx-cdn-germany/roles/simple-monitoring/tasks/main.yml b/ansible/nginx-cdn-germany/roles/simple-monitoring/tasks/main.yml new file mode 100644 index 00000000..1c1f5194 --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/simple-monitoring/tasks/main.yml @@ -0,0 +1,29 @@ +--- +# Einfaches Monitoring ohne Prometheus + +- name: Create simple monitoring script + template: + src: simple-monitor.sh.j2 + dest: /usr/local/bin/cdn-monitor + mode: '0755' + +- name: Setup monitoring cron job + cron: + name: "CDN Health Monitor" + minute: "*/5" + job: "/usr/local/bin/cdn-monitor" + user: root + +- name: Create log rotation for monitoring logs + copy: + content: | + /var/log/nginx/cdn-monitor.log { + weekly + missingok + rotate 4 + compress + delaycompress + notifempty + } + dest: /etc/logrotate.d/cdn-monitor + mode: '0644' diff --git a/ansible/nginx-cdn-germany/roles/simple-monitoring/templates/simple-monitor.sh.j2 b/ansible/nginx-cdn-germany/roles/simple-monitoring/templates/simple-monitor.sh.j2 new file mode 100644 index 00000000..cc432fe6 --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/simple-monitoring/templates/simple-monitor.sh.j2 @@ -0,0 +1,71 @@ +#!/bin/bash +# Einfaches CDN Monitoring für {{ inventory_hostname }} + +LOG_FILE="/var/log/nginx/cdn-monitor.log" +TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') +CDN_DOMAIN="{{ cdn_domain }}" + +# Health Check +health_check() { + local response=$(curl -s -o /dev/null -w "%{http_code}" "https://$CDN_DOMAIN/health") + + if [ "$response" = "200" ]; then + echo "[$TIMESTAMP] ✅ Health check OK" >> $LOG_FILE + return 0 + else + echo "[$TIMESTAMP] ❌ Health check FAILED (HTTP $response)" >> $LOG_FILE + return 1 + fi +} + +# Nginx-Statistiken +nginx_stats() { + local stats=$(curl -s http://127.0.0.1/nginx_status 2>/dev/null) + if [ $? -eq 0 ]; then + local active_conn=$(echo "$stats" | grep "Active connections" | awk '{print $3}') + local total_requests=$(echo "$stats" | grep "server accepts" | awk '{print $3}') + echo "[$TIMESTAMP] 📊 Active: $active_conn, Total: $total_requests" >> $LOG_FILE + fi +} + +# Cache-Größe prüfen +cache_check() { + local cache_size=$(du -sh /var/cache/nginx/ 2>/dev/null | cut -f1) + local cache_files=$(find /var/cache/nginx/ -type f 2>/dev/null | wc -l) + echo "[$TIMESTAMP] 💾 Cache: $cache_size ($cache_files files)" >> $LOG_FILE +} + +# System-Ressourcen +system_check() { + local load=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' ') + local memory=$(free | grep Mem | awk '{printf "%.1f", $3/$2 * 100.0}') + local disk=$(df / | tail -1 | awk '{print $5}' | sed 's/%//') + + echo "[$TIMESTAMP] 🖥️ Load: $load, Memory: ${memory}%, Disk: ${disk}%" >> $LOG_FILE + + # Warnungen bei hoher Auslastung + if (( $(echo "$load > 5.0" | bc -l 2>/dev/null || echo 0) )); then + echo "[$TIMESTAMP] ⚠️ HIGH LOAD WARNING: $load" >> $LOG_FILE + fi + + if (( $(echo "$memory > 90.0" | bc -l 2>/dev/null || echo 0) )); then + echo "[$TIMESTAMP] ⚠️ HIGH MEMORY WARNING: ${memory}%" >> $LOG_FILE + fi + + if [ "$disk" -gt 85 ]; then + echo "[$TIMESTAMP] ⚠️ HIGH DISK USAGE WARNING: ${disk}%" >> $LOG_FILE + fi +} + +# Hauptausführung +main() { + health_check + nginx_stats + cache_check + system_check + + # Log-Datei begrenzen (nur letzte 1000 Zeilen behalten) + tail -n 1000 $LOG_FILE > ${LOG_FILE}.tmp && mv ${LOG_FILE}.tmp $LOG_FILE +} + +main diff --git a/ansible/nginx-cdn-germany/roles/ssl-certificates/tasks/main.yml b/ansible/nginx-cdn-germany/roles/ssl-certificates/tasks/main.yml new file mode 100644 index 00000000..bc1b3339 --- /dev/null +++ b/ansible/nginx-cdn-germany/roles/ssl-certificates/tasks/main.yml @@ -0,0 +1,30 @@ +--- +# SSL-Zertifikate mit Let's Encrypt + +- name: Check if certificate exists + stat: + path: "/etc/letsencrypt/live/{{ cdn_domain }}/fullchain.pem" + register: cert_exists + +- name: Generate SSL certificate with certbot + command: > + certbot certonly --nginx + -d {{ cdn_domain }} + --non-interactive + --agree-tos + --email {{ ssl_email }} + when: not cert_exists.stat.exists + +- name: Setup SSL certificate renewal + cron: + name: "Renew SSL certificates" + minute: "0" + hour: "3" + job: "certbot renew --quiet --deploy-hook 'systemctl reload nginx'" + user: root + +- name: Test SSL certificate renewal (dry-run) + command: certbot renew --dry-run + register: renewal_test + failed_when: renewal_test.rc != 0 + changed_when: false diff --git a/ansible/nginx-cdn-germany/scripts/deploy.sh b/ansible/nginx-cdn-germany/scripts/deploy.sh new file mode 100644 index 00000000..15ac6138 --- /dev/null +++ b/ansible/nginx-cdn-germany/scripts/deploy.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Simple CDN Deployment Script + +set -e + +INVENTORY_FILE="inventories/production/hosts.yml" +PLAYBOOK="playbooks/deploy-simple-cdn.yml" + +echo "🚀 Starting Simple CDN Deployment for Germany..." + +# Pre-deployment checks +echo "🔍 Running pre-deployment checks..." +if ! ansible all -i $INVENTORY_FILE -m ping; then + echo "❌ Some hosts are not reachable. Please check your inventory." + exit 1 +fi + +echo "📋 Testing ansible configuration..." +if ! ansible-playbook $PLAYBOOK -i $INVENTORY_FILE --check --diff; then + echo "❌ Configuration test failed. Please fix errors first." + exit 1 +fi + +read -p "Continue with deployment? (y/N): " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Deployment cancelled." + exit 0 +fi + +# Deployment +echo "🔧 Deploying CDN nodes..." +ansible-playbook $PLAYBOOK -i $INVENTORY_FILE + +# Post-deployment verification +echo "✅ Verifying deployment..." +ansible cdn_nodes -i $INVENTORY_FILE -m uri -a "url=https://{{ inventory_hostname }}/health method=GET status_code=200" + +echo "🎉 CDN Deployment completed successfully!" +echo "" +echo "Next steps:" +echo "1. Update your DNS to point to the CDN nodes" +echo "2. Test your CDN: curl -I https://your-cdn-domain.de/health" +echo "3. Monitor with: ansible-playbook -i $INVENTORY_FILE playbooks/manage-cdn.yml" diff --git a/ansible/nginx-cdn-germany/scripts/ssh-keys.sh b/ansible/nginx-cdn-germany/scripts/ssh-keys.sh new file mode 100644 index 00000000..815184e9 --- /dev/null +++ b/ansible/nginx-cdn-germany/scripts/ssh-keys.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# SSH-Schlüssel Management für CDN + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +show_help() { + echo "CDN SSH Key Management" + echo "" + echo "Usage: $0 [OPTION]" + echo "" + echo "Options:" + echo " single - Ein Schlüssel für alle Nodes (Standard)" + echo " individual - Separater Schlüssel pro Node" + echo " grouped - Gruppierte Schlüssel (Primary/Secondary)" + echo " generate - SSH-Schlüssel generieren" + echo " deploy - Öffentliche Schlüssel zu Servern kopieren" + echo " help - Diese Hilfe anzeigen" +} + +generate_single_key() { + echo "🔑 Generiere einen SSH-Schlüssel für alle CDN-Nodes..." + + if [ ! -f ~/.ssh/cdn_key ]; then + ssh-keygen -t ed25519 -C "cdn-deployment" -f ~/.ssh/cdn_key -N "" + echo "✅ Schlüssel generiert: ~/.ssh/cdn_key" + else + echo "ℹ️ Schlüssel existiert bereits: ~/.ssh/cdn_key" + fi + + # Inventar anpassen + sed -i 's|ansible_ssh_private_key_file: .*|ansible_ssh_private_key_file: ~/.ssh/cdn_key|' \ + "$SCRIPT_DIR/../inventories/production/hosts.yml" + + echo "✅ Inventar aktualisiert" +} + +generate_individual_keys() { + echo "🔑 Generiere individuelle SSH-Schlüssel..." + + NODES=("cdn_fra1" "cdn_ham1" "cdn_muc1" "origin1" "origin2") + + for node in "${NODES[@]}"; do + if [ ! -f ~/.ssh/${node}_key ]; then + ssh-keygen -t ed25519 -C "cdn-${node}" -f ~/.ssh/${node}_key -N "" + echo "✅ Schlüssel generiert: ~/.ssh/${node}_key" + else + echo "ℹ️ Schlüssel existiert bereits: ~/.ssh/${node}_key" + fi + done + + echo "✅ Alle individuellen Schlüssel generiert" + echo "💡 Verwende: cp inventories/production/hosts-individual-keys.yml.example inventories/production/hosts.yml" +} + +generate_grouped_keys() { + echo "🔑 Generiere gruppierte SSH-Schlüssel..." + + GROUPS=("origin_servers" "cdn_primary" "cdn_secondary") + + for group in "${GROUPS[@]}"; do + if [ ! -f ~/.ssh/${group}_key ]; then + ssh-keygen -t ed25519 -C "cdn-${group}" -f ~/.ssh/${group}_key -N "" + echo "✅ Schlüssel generiert: ~/.ssh/${group}_key" + else + echo "ℹ️ Schlüssel existiert bereits: ~/.ssh/${group}_key" + fi + done + + echo "✅ Alle gruppierten Schlüssel generiert" + echo "💡 Verwende: cp inventories/production/hosts-grouped-keys.yml.example inventories/production/hosts.yml" +} + +deploy_keys() { + echo "🚀 Deploye öffentliche Schlüssel zu den Servern..." + + # Lese IPs aus dem Inventar + IPS=$(grep "ansible_host:" "$SCRIPT_DIR/../inventories/production/hosts.yml" | awk '{print $2}' | sort | uniq) + + for ip in $IPS; do + echo "Deploying to $ip..." + + # Versuche verschiedene Schlüssel + for key in ~/.ssh/*_key ~/.ssh/cdn_key ~/.ssh/id_rsa; do + if [ -f "$key" ]; then + echo " Versuche Schlüssel: $key" + if ssh-copy-id -i "${key}.pub" "root@$ip" 2>/dev/null; then + echo " ✅ Erfolgreich: $key -> $ip" + break + fi + fi + done + done +} + +case "$1" in + "single") + generate_single_key + ;; + "individual") + generate_individual_keys + ;; + "grouped") + generate_grouped_keys + ;; + "generate") + echo "Welche Art von Schlüsseln?" + echo "1) Ein Schlüssel für alle (empfohlen für Start)" + echo "2) Individuelle Schlüssel pro Node (sicherste)" + echo "3) Gruppierte Schlüssel (Kompromiss)" + read -p "Wähle (1-3): " choice + + case $choice in + 1) generate_single_key ;; + 2) generate_individual_keys ;; + 3) generate_grouped_keys ;; + *) echo "Ungültige Auswahl" ;; + esac + ;; + "deploy") + deploy_keys + ;; + "help"|*) + show_help + ;; +esac diff --git a/ansible/nginx-cdn-germany/scripts/warm-cache.sh b/ansible/nginx-cdn-germany/scripts/warm-cache.sh new file mode 100644 index 00000000..550b64c6 --- /dev/null +++ b/ansible/nginx-cdn-germany/scripts/warm-cache.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Cache Warming Script + +INVENTORY_FILE="inventories/production/hosts.yml" + +# URLs zum Cache-Warming +URLS=( + "/" + "/health" + # Füge hier deine wichtigsten URLs hinzu: + # "/css/main.css" + # "/js/app.js" + # "/images/logo.png" +) + +echo "🔥 Starting cache warming for all CDN nodes..." + +# Hole alle CDN Node Hostnamen +CDN_NODES=$(ansible-inventory -i $INVENTORY_FILE --list | jq -r '.cdn_nodes.hosts[]' 2>/dev/null || ansible cdn_nodes -i $INVENTORY_FILE --list-hosts | grep -v hosts) + +for node in $CDN_NODES; do + echo "Warming cache for: $node" + + for url in "${URLS[@]}"; do + echo " Warming: $url" + response=$(curl -s -o /dev/null -w "%{http_code}" "https://${node}${url}" || echo "000") + if [ "$response" = "200" ]; then + echo " ✅ OK" + else + echo " ❌ Failed (HTTP $response)" + fi + sleep 0.5 + done + echo "" +done + +echo "🎉 Cache warming completed!"