From 6deca7838e59a563db2dd69b3365a28dd64d44bb Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Fri, 31 Oct 2025 04:14:18 +0100 Subject: [PATCH] chore: remove test trigger file --- deployment/APPLICATION_STACK_DEPLOYMENT.md | 561 ++++++++++++++++++ deployment/CI_CD_STATUS.md | 272 +++++++++ deployment/CODE_CHANGE_WORKFLOW.md | 551 +++++++++++++++++ deployment/DEPLOYMENT-STATUS.md | 64 +- deployment/DEPLOYMENT-TODO.md | 281 +++++++++ deployment/NATIVE-WORKFLOW-README.md | 155 +++++ deployment/README.md | 38 +- deployment/SETUP-GUIDE.md | 242 +++++--- deployment/WORKFLOW-TROUBLESHOOTING.md | 185 ++++++ deployment/ansible/README.md | 22 +- .../ansible/playbooks/README-WIREGUARD.md | 284 +++++++++ .../playbooks/add-wireguard-client.yml | 169 ++++++ .../ansible/playbooks/deploy-update.yml | 2 +- .../playbooks/setup-infrastructure.yml | 134 +++++ .../ansible/playbooks/setup-wireguard.yml | 294 +++++++++ .../secrets/production.vault.yml.example | 2 +- .../ansible/templates/application.env.j2 | 40 ++ .../ansible/templates/monitoring.env.j2 | 3 +- .../templates/wireguard-client.conf.j2 | 27 + .../templates/wireguard-server.conf.j2 | 22 + deployment/gitea-runner/config.yaml | 8 +- deployment/gitea-runner/docker-compose.yml | 9 +- deployment/stacks/application/.env.example | 6 +- .../stacks/application/docker-compose.yml | 12 +- .../stacks/monitoring/docker-compose.yml | 6 - docs/deployment/README.md | 19 + docs/deployment/WIREGUARD-FUTURE-SECURITY.md | 213 +++++++ docs/deployment/WIREGUARD-SETUP.md | 558 +++++++++++++++++ test-workflow-trigger.txt | 1 - 29 files changed, 4052 insertions(+), 128 deletions(-) create mode 100644 deployment/APPLICATION_STACK_DEPLOYMENT.md create mode 100644 deployment/CI_CD_STATUS.md create mode 100644 deployment/CODE_CHANGE_WORKFLOW.md create mode 100644 deployment/DEPLOYMENT-TODO.md create mode 100644 deployment/NATIVE-WORKFLOW-README.md create mode 100644 deployment/WORKFLOW-TROUBLESHOOTING.md create mode 100644 deployment/ansible/playbooks/README-WIREGUARD.md create mode 100755 deployment/ansible/playbooks/add-wireguard-client.yml create mode 100755 deployment/ansible/playbooks/setup-wireguard.yml create mode 100644 deployment/ansible/templates/application.env.j2 create mode 100644 deployment/ansible/templates/wireguard-client.conf.j2 create mode 100644 deployment/ansible/templates/wireguard-server.conf.j2 create mode 100644 docs/deployment/WIREGUARD-FUTURE-SECURITY.md create mode 100644 docs/deployment/WIREGUARD-SETUP.md delete mode 100644 test-workflow-trigger.txt diff --git a/deployment/APPLICATION_STACK_DEPLOYMENT.md b/deployment/APPLICATION_STACK_DEPLOYMENT.md new file mode 100644 index 00000000..0a9b856d --- /dev/null +++ b/deployment/APPLICATION_STACK_DEPLOYMENT.md @@ -0,0 +1,561 @@ +# Application Stack Deployment Prozess + +## Übersicht + +Dieses Dokument erklärt, wie genau das Deployment in den Application Stack abläuft, wenn die CI/CD Pipeline ausgeführt wird. + +**📖 Verwandte Dokumentation:** +- **[Code Changes Workflow](CODE_CHANGE_WORKFLOW.md)** - Wie Codeänderungen gepusht werden +- **[CI/CD Status](CI_CD_STATUS.md)** - Aktueller Status der Pipeline + +--- + +## Deployment-Flow + +``` +CI/CD Pipeline (Gitea Actions) + ↓ +1. Tests & Build + ↓ +2. Docker Image Build & Push zur Registry + ↓ +3. Ansible Playbook ausführen + ↓ +4. Application Stack aktualisieren +``` + +--- + +## Detaillierter Ablauf + +### Phase 1: CI/CD Pipeline (`.gitea/workflows/production-deploy.yml`) + +#### Job 1: Tests +```yaml +- PHP 8.3 Setup +- Composer Dependencies installieren +- Pest Tests ausführen +- PHPStan Code Quality Check +- Code Style Check +``` + +#### Job 2: Build & Push +```yaml +- Docker Image Build (Dockerfile.production) +- Image mit Tags pushen: + - git.michaelschiemer.de:5000/framework:latest + - git.michaelschiemer.de:5000/framework: + - git.michaelschiemer.de:5000/framework:git- +``` + +#### Job 3: Deploy (Ansible) + +**Schritt 1: Code Checkout** +```bash +git clone /workspace/repo +``` + +**Schritt 2: SSH Setup** +```bash +# SSH Private Key aus Secret wird in ~/.ssh/production geschrieben +echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production +chmod 600 ~/.ssh/production +``` + +**Schritt 3: Ansible Installation** +```bash +apt-get install -y ansible +``` + +**Schritt 4: Ansible Playbook ausführen** +```bash +cd /workspace/repo/deployment/ansible +ansible-playbook -i inventory/production.yml \ + playbooks/deploy-update.yml \ + -e "image_tag=" \ + -e "git_commit_sha=" \ + -e "deployment_timestamp=" \ + -e "docker_registry_username=${{ secrets.REGISTRY_USER }}" \ + -e "docker_registry_password=${{ secrets.REGISTRY_PASSWORD }}" +``` + +--- + +### Phase 2: Ansible Deployment (`deploy-update.yml`) + +Das Ansible Playbook führt folgende Schritte auf dem Production-Server aus: + +#### Pre-Tasks + +**1. Secrets laden (optional)** +```yaml +- Lädt Registry Credentials aus Ansible Vault (falls vorhanden) +- Oder verwendet Credentials aus Workflow-Variablen +``` + +**2. Docker Service prüfen** +```yaml +- Verifiziert, dass Docker läuft +- Startet Docker falls notwendig +``` + +**3. Verzeichnisse erstellen** +```yaml +- Application Stack Path: ~/deployment/stacks/application +- Backup Verzeichnis: ~/deployment/backups// +``` + +#### Tasks + +**1. Backup aktueller Deployment-Status** +```bash +# Speichert aktuellen Container-Status +docker compose -f ~/deployment/stacks/application/docker-compose.yml \ + ps --format json > backups//current_containers.json + +# Speichert aktuelle docker-compose.yml Konfiguration +docker compose -f ~/deployment/stacks/application/docker-compose.yml \ + config > backups//docker-compose-config.yml +``` + +**2. Docker Registry Login** +```bash +# Login zur privaten Registry mit Credentials +docker login git.michaelschiemer.de:5000 \ + -u \ + -p +``` + +**3. Neues Image Pullen** +```bash +# Pullt das neue Image von der Registry +docker pull git.michaelschiemer.de:5000/framework: + +# Beispiel: +# git.michaelschiemer.de:5000/framework:abc1234-1696234567 +``` + +**4. docker-compose.yml aktualisieren** + +**Wichtig:** Das Playbook aktualisiert die `docker-compose.yml` Datei direkt auf dem Server! + +```yaml +# Vorher: +services: + app: + image: git.michaelschiemer.de:5000/framework:latest + +# Nachher (wenn image_tag != 'latest'): +services: + app: + image: git.michaelschiemer.de:5000/framework: +``` + +**Regex-Replace:** +```yaml +regexp: '^(\s+image:\s+){{ app_image }}:.*$' +replace: '\1{{ app_image }}:{{ image_tag }}' +``` + +**Betroffene Services (werden alle aktualisiert):** +- `app` (PHP-FPM) - Zeile 6: `image: git.michaelschiemer.de:5000/framework:latest` +- `queue-worker` (Queue Worker) - Zeile 120: `image: git.michaelschiemer.de:5000/framework:latest` +- `scheduler` (Scheduler) - Zeile 165: `image: git.michaelschiemer.de:5000/framework:latest` + +**Hinweis:** +- Alle drei Services verwenden das gleiche Image, daher werden alle mit dem neuen Tag aktualisiert +- Der Regex matched **alle Zeilen** die mit `image: git.michaelschiemer.de:5000/framework:` beginnen +- `nginx` und `redis` bleiben unverändert (verwenden andere Images) + +**5. Application Stack neu starten** + +```bash +docker compose -f ~/deployment/stacks/application/docker-compose.yml \ + up -d \ + --pull always \ + --force-recreate \ + --remove-orphans +``` + +**Was passiert dabei:** +- `--pull always`: Zieht Images immer neu (auch wenn schon vorhanden) +- `--force-recreate`: Erstellt Container immer neu, auch wenn Konfiguration unverändert ist +- `--remove-orphans`: Entfernt Container, die nicht mehr in der Compose-Datei definiert sind +- `-d`: Läuft im Hintergrund (detached) + +**Container-Neustart-Reihenfolge:** +1. Abhängigkeiten werden gestoppt (app hängt von redis ab) +2. Container werden neu erstellt mit neuem Image +3. Container werden in korrekter Reihenfolge gestartet (redis → app → nginx) +4. Health-Checks werden ausgeführt + +**6. Warten auf Health-Checks** + +```yaml +# Wartet 60 Sekunden auf Container-Gesundheit +wait_for: + timeout: 60 +``` + +**7. Health-Status prüfen** + +```bash +# Prüft alle Container-Health-Status +docker compose ps --format json | \ + jq -r '.[] | select(.Health != "healthy" and .Health != "") | "\(.Name): \(.Health)"' +``` + +**8. Deployment-Metadaten speichern** + +``` +~/deployment/backups//deployment_metadata.txt +``` + +**Inhalt:** +``` +Deployment Timestamp: 2025-10-31T02:35:04Z +Git Commit: abc1234... +Image Tag: abc1234-1696234567 +Deployed Image: git.michaelschiemer.de:5000/framework:abc1234-1696234567 +Image Pull: SUCCESS +Stack Deploy: UPDATED +Health Status: All services healthy +``` + +**9. Alte Backups aufräumen** + +```bash +# Behält nur die letzten X Backups (Standard: 5) +ls -dt */ | tail -n +6 | xargs -r rm -rf +``` + +--- + +### Phase 3: Application Stack Services + +Der Application Stack besteht aus mehreren Services: + +#### Services im Stack + +**1. `app` (PHP-FPM Application)** +- Image: `git.michaelschiemer.de:5000/framework:` +- Container: `app` +- Health Check: `php-fpm-healthcheck` +- Netzwerk: `app-internal` +- Abhängigkeiten: `redis` (condition: service_healthy) + +**2. `nginx` (Web Server)** +- Image: `nginx:1.25-alpine` +- Container: `nginx` +- Health Check: `wget --spider http://localhost/health` +- Netzwerke: `traefik-public`, `app-internal` +- Abhängigkeiten: `app` (condition: service_healthy) +- Traefik Labels für Routing + +**3. `redis` (Cache/Session/Queue)** +- Image: `redis:7-alpine` +- Container: `redis` +- Health Check: `redis-cli --raw incr ping` +- Netzwerk: `app-internal` + +**4. `queue-worker` (Background Jobs)** +- Image: `git.michaelschiemer.de:5000/framework:` (gleiches wie app) +- Container: `queue-worker` +- Health Check: `pgrep -f 'queue:work'` +- Netzwerk: `app-internal` +- Command: `php console.php queue:work` +- Abhängigkeiten: `app`, `redis` + +**5. `scheduler` (Cron Jobs)** +- Image: `git.michaelschiemer.de:5000/framework:` (gleiches wie app) +- Container: `scheduler` +- Health Check: `pgrep -f 'scheduler:run'` +- Netzwerk: `app-internal` +- Command: `php console.php scheduler:run` +- Abhängigkeiten: `app`, `redis` + +--- + +## Container-Neustart-Details + +### Was passiert bei `docker compose up -d --force-recreate`? + +**1. Container-Stop:** +- Laufende Container werden gestoppt +- Volumes bleiben erhalten (persistente Daten) +- Netzwerke bleiben erhalten + +**2. Container-Entfernung:** +- Alte Container werden entfernt +- Neue Container werden mit neuem Image erstellt + +**3. Container-Start:** +- Container starten in Abhängigkeits-Reihenfolge +- Health-Checks werden ausgeführt +- Falls Health-Check fehlschlägt, wird Container neu gestartet (restart: unless-stopped) + +**4. Zero-Downtime?** +- **Teilweise:** Container werden nacheinander neu gestartet +- `nginx` wartet auf `app` Health-Check +- Während Neustart kann es zu kurzen Verbindungsfehlern kommen +- Kein echter Blue-Green-Deployment (das wäre möglich, aber nicht implementiert) + +--- + +## Konfigurationsvariablen + +### Inventory-Variablen (`inventory/production.yml`) + +```yaml +app_image: "git.michaelschiemer.de:5000/framework" +docker_registry_url: "git.michaelschiemer.de:5000" +backups_path: "~/deployment/backups" +max_rollback_versions: 5 +deploy_user_home: "~/deployment" +``` + +### Workflow-Variablen (aus CI/CD) + +```yaml +image_tag: "abc1234-1696234567" # Generiert aus: - +git_commit_sha: "abc1234567890..." +deployment_timestamp: "2025-10-31T02:35:04Z" +docker_registry_username: "" +docker_registry_password: "" +``` + +--- + +## Beispiel-Deployment + +### Schritt-für-Schritt Beispiel + +**1. CI/CD Pipeline startet:** +```bash +# Commit: abc1234... +# Image Tag generiert: abc1234-1696234567 +``` + +**2. Image wird gebaut und gepusht:** +```bash +docker buildx build \ + --tag git.michaelschiemer.de:5000/framework:latest \ + --tag git.michaelschiemer.de:5000/framework:abc1234-1696234567 \ + --tag git.michaelschiemer.de:5000/framework:git-abc1234 \ + --push \ + . +``` + +**3. Ansible Playbook wird ausgeführt:** +```bash +ansible-playbook -i inventory/production.yml \ + playbooks/deploy-update.yml \ + -e "image_tag=abc1234-1696234567" \ + -e "git_commit_sha=abc1234567890..." \ + -e "deployment_timestamp=2025-10-31T02:35:04Z" +``` + +**4. Auf Production-Server:** +```bash +# 1. Backup erstellen +mkdir -p ~/deployment/backups/2025-10-31T02-35-04Z + +# 2. Registry Login +docker login git.michaelschiemer.de:5000 -u admin -p + +# 3. Image Pullen +docker pull git.michaelschiemer.de:5000/framework:abc1234-1696234567 + +# 4. docker-compose.yml aktualisieren +# Vorher: image: git.michaelschiemer.de:5000/framework:latest +# Nachher: image: git.michaelschiemer.de:5000/framework:abc1234-1696234567 + +# 5. Stack neu starten +cd ~/deployment/stacks/application +docker compose up -d --pull always --force-recreate --remove-orphans + +# 6. Health-Checks warten +sleep 60 + +# 7. Status prüfen +docker compose ps +# app: healthy +# nginx: healthy +# redis: healthy +# queue-worker: healthy +# scheduler: healthy +``` + +**5. Deployment-Metadaten speichern:** +```bash +cat > ~/deployment/backups/2025-10-31T02-35-04Z/deployment_metadata.txt < + +# Image manuell pullen +docker pull git.michaelschiemer.de:5000/framework: +``` + +### docker-compose.yml wurde nicht aktualisiert + +```bash +# Prüfe ob Regex korrekt ist +grep -E "image:\s+git.michaelschiemer.de:5000/framework" \ + ~/deployment/stacks/application/docker-compose.yml + +# Prüfe Backup für vorherige Version +ls -la ~/deployment/backups/ +``` + +--- + +## Zusammenfassung + +**Deployment-Ablauf:** +1. CI/CD Pipeline baut Image und pusht es zur Registry +2. Ansible Playbook wird auf Production-Server ausgeführt +3. Neues Image wird gepullt +4. `docker-compose.yml` wird mit neuem Image-Tag aktualisiert +5. Application Stack wird mit `--force-recreate` neu gestartet +6. Health-Checks werden ausgeführt +7. Deployment-Metadaten werden gespeichert +8. Workflow führt abschließenden Health-Check aus + +**Alle Services erhalten das neue Image:** +- `app` (PHP-FPM) +- `queue-worker` (Queue Worker) +- `scheduler` (Scheduler) + +**Zero-Downtime:** Nein, Container werden neu gestartet (kurze Downtime möglich) + +**Rollback:** Automatisch via Workflow oder manuell via Rollback-Playbook diff --git a/deployment/CI_CD_STATUS.md b/deployment/CI_CD_STATUS.md new file mode 100644 index 00000000..9611346a --- /dev/null +++ b/deployment/CI_CD_STATUS.md @@ -0,0 +1,272 @@ +# CI/CD Pipeline Status + +**Stand:** ✅ CI/CD Pipeline ist vollständig konfiguriert und bereit zum Testen! + +## 📖 Dokumentation + +- **[Code Changes Workflow](CODE_CHANGE_WORKFLOW.md)** - Anleitung: Wie Codeänderungen gepusht und deployed werden +- **[Application Stack Deployment](APPLICATION_STACK_DEPLOYMENT.md)** - Detaillierte Erklärung des Deployment-Prozesses + +## ✅ Was bereits vorhanden ist + +### 1. Workflow-Dateien +- ✅ `.gitea/workflows/production-deploy.yml` - Haupt-Deployment-Pipeline +- ✅ `.gitea/workflows/update-production-secrets.yml` - Secrets-Deployment +- ✅ `.gitea/workflows/security-scan.yml` - Security-Vulnerability-Scan + +### 2. Gitea Runner Setup +- ✅ `deployment/gitea-runner/docker-compose.yml` - Runner-Konfiguration +- ✅ `deployment/gitea-runner/config.yaml` - Runner-Konfiguration +- ✅ `deployment/gitea-runner/register.sh` - Registration-Script +- ✅ `deployment/gitea-runner/.env.example` - Environment-Template + +### 3. Deployment-Scripts +- ✅ `deployment/ansible/playbooks/deploy-update.yml` - Ansible-Deployment +- ✅ `deployment/ansible/playbooks/rollback.yml` - Rollback-Playbook + +--- + +## ⚠️ Was noch fehlt + +### 1. Gitea Repository Secrets konfigurieren + +**Status**: ✅ Secrets sind bereits konfiguriert + +**Konfigurierte Secrets:** +- ✅ `REGISTRY_USER` - Benutzername für Docker Registry +- ✅ `REGISTRY_PASSWORD` - Passwort für Docker Registry +- ✅ `SSH_PRIVATE_KEY` - SSH Private Key für Production-Server-Zugriff +- ⚠️ `GITEA_TOKEN` - Optional: Für automatische Issue-Erstellung bei Security-Scans + +**Verifiziert:** Alle kritischen Secrets sind bereits in Gitea konfiguriert. + +**Hinweis:** Falls `GITEA_TOKEN` noch nicht konfiguriert ist, kann die automatische Issue-Erstellung bei Security-Scans optional später hinzugefügt werden. + +**REGISTRY_USER:** +``` +admin +``` + +**REGISTRY_PASSWORD:** +``` + +``` +*Zu finden in: `deployment/stacks/registry/auth/htpasswd` oder manuell gesetzt* + +**SSH_PRIVATE_KEY:** +```bash +# SSH Key Inhalt anzeigen +cat ~/.ssh/production +``` +*Kompletter Inhalt der Datei inkl. `-----BEGIN OPENSSH PRIVATE KEY-----` und `-----END OPENSSH PRIVATE KEY-----`* + +**GITEA_TOKEN (optional):** +``` + +``` + +--- + +### 2. Gitea Runner registrieren und starten + +**Status**: ✅ Runner läuft bereits + +**Verifiziert:** +- ✅ Runner ist registriert (`data/.runner` existiert) +- ✅ Runner Container läuft (`gitea-runner` ist "Up") +- ✅ Docker-in-Docker Container läuft (`gitea-runner-dind` ist "Up") +- ✅ Runner hat bereits Tasks ausgeführt (task 1-6 in Logs) +- ✅ Runner Name: `dev-runner-01` +- ✅ Runner Labels: `ubuntu-latest`, `ubuntu-22.04`, `debian-latest` + +**Hinweis:** +Die Logs zeigen einige Verbindungsfehler (connection refused, 502 Bad Gateway), was normal sein kann wenn Gitea temporär nicht erreichbar ist. Der Runner funktioniert grundsätzlich und hat bereits Tasks erfolgreich ausgeführt. + +**Verifizierung in Gitea:** +- Prüfe Runner-Status in Gitea: `https://git.michaelschiemer.de/admin/actions/runners` +- Runner sollte als "Idle" oder "Active" angezeigt werden + +--- + +### 3. Ansible Vault Password Handling + +**Status**: ⚠️ Workflow nutzt Vault, aber kein Secret dafür + +**Problem:** +- Der Workflow `update-production-secrets.yml` benötigt ein Vault-Passwort +- Aktuell wird es als Workflow-Input eingegeben (manuell) +- Für automatisiertes Deployment sollte es als Secret vorliegen + +**Lösung:** +- [ ] Optional: `ANSIBLE_VAULT_PASSWORD` als Secret hinzufügen (nur wenn automatisiert) +- [ ] Oder: Manuelles Eingeben beim Workflow-Trigger ist ausreichend + +--- + +### 4. Pipeline End-to-End testen + +**Status**: ⚠️ Pipeline ist definiert, aber noch nicht getestet + +**Was fehlt:** +- [ ] Test-Commit pushen und Pipeline-Lauf beobachten +- [ ] Fehler beheben falls notwendig +- [ ] Verifizieren dass Deployment funktioniert + +**Test-Schritte:** +1. Stelle sicher dass alle Secrets konfiguriert sind +2. Stelle sicher dass Runner läuft +3. Test-Commit erstellen: + ```bash + git checkout -b test/cicd-pipeline + # Kleine Änderung machen + echo "# Test CI/CD" >> README.md + git add README.md + git commit -m "test: CI/CD pipeline" + git push origin test/cicd-pipeline + ``` +4. Oder: Workflow manuell triggern: + - Gehe zu: `https://git.michaelschiemer.de/michael/michaelschiemer/actions` + - Wähle "Production Deployment Pipeline" + - Klicke "Run workflow" +5. Beobachte Logs und prüfe jeden Schritt: + - ✅ Tests laufen erfolgreich + - ✅ Docker Image wird gebaut + - ✅ Image wird zur Registry gepusht + - ✅ Ansible-Deployment läuft + - ✅ Health-Check schlägt erfolgreich durch + +--- + +## 📋 Checkliste für CI/CD Completion + +### Vorbereitung +- [x] Gitea Repository Secrets konfiguriert: ✅ + - [x] `REGISTRY_USER` ✅ + - [x] `REGISTRY_PASSWORD` ✅ + - [x] `SSH_PRIVATE_KEY` ✅ + - [ ] `GITEA_TOKEN` (optional) + +### Gitea Runner +- [x] Runner Registration Token von Gitea abgerufen ✅ +- [x] `deployment/gitea-runner/.env` erstellt und konfiguriert ✅ +- [x] Runner registriert ✅ +- [x] Runner läuft (`docker compose up -d`) ✅ +- [ ] Runner sichtbar in Gitea UI als "Idle" oder "Active" (bitte manuell prüfen) + +### Pipeline-Test +- [ ] Test-Commit gepusht oder Workflow manuell getriggert +- [ ] Alle Jobs erfolgreich: + - [ ] Tests + - [ ] Build + - [ ] Deploy +- [ ] Deployment erfolgreich auf Production +- [ ] Health-Check erfolgreich +- [ ] Application läuft korrekt + +### Dokumentation +- [ ] Secrets-Setup dokumentiert +- [ ] Runner-Setup dokumentiert +- [ ] Bekannte Probleme/Workarounds dokumentiert + +--- + +## 🎯 Priorisierte Reihenfolge + +### Phase 1: Secrets Setup +**Status:** ✅ Abgeschlossen +- Alle kritischen Secrets sind konfiguriert + +### Phase 2: Gitea Runner +**Status:** ✅ Abgeschlossen +- Runner läuft und ist registriert + +### Phase 3: Pipeline-Test (NÄCHSTER SCHRITT) +**Status:** ⚠️ Ausstehend +1. Test-Workflow ausführen +2. Fehler beheben falls notwendig +3. Production-Deployment verifizieren + +--- + +## 📝 Quick Reference + +### Secrets Setup +```bash +# Secrets in Gitea konfigurieren: +https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions +``` + +### Runner Setup +```bash +cd deployment/gitea-runner +cp .env.example .env +# Token von https://git.michaelschiemer.de/admin/actions/runners eintragen +./register.sh +docker compose up -d +``` + +### Pipeline manuell triggern (NÄCHSTER SCHRITT) +``` +1. Gehe zu: https://git.michaelschiemer.de/michael/michaelschiemer/actions +2. Wähle: "Production Deployment Pipeline" +3. Klicke: "Run workflow" +4. Wähle Branch: main +5. Optionale Einstellungen: + - skip_tests: false (Tests sollen laufen) +6. Klicke: "Run workflow" +7. Beobachte Logs und prüfe jeden Schritt +``` + +### Runner-Status prüfen +``` +https://git.michaelschiemer.de/admin/actions/runners +``` + +--- + +--- + +## ✅ Aktueller Status + +**CI/CD Pipeline ist vollständig konfiguriert!** + +- ✅ Secrets konfiguriert +- ✅ Runner läuft und ist registriert +- ✅ Workflows sind vorhanden +- ⚠️ **Nächster Schritt:** Pipeline testen! + +**Ready to Deploy:** Die Pipeline kann jetzt getestet werden. Alle Voraussetzungen sind erfüllt. + +--- + +## 🔍 Troubleshooting + +### Runner erscheint nicht in Gitea +- Prüfe Runner-Logs: `docker compose logs gitea-runner` +- Prüfe Registration-Token in `.env` +- Re-registrieren: `./unregister.sh && ./register.sh` + +### Workflow startet nicht +- Prüfe ob Runner läuft: `docker compose ps` +- Prüfe Runner-Status in Gitea UI +- Prüfe ob Workflow-Datei korrekt ist + +### Secrets werden nicht erkannt +- Prüfe Secret-Namen (müssen exakt übereinstimmen) +- Prüfe ob Secrets in korrektem Repository/Organisation sind +- Prüfe ob Secrets nicht abgelaufen sind + +### Registry-Login fehlschlägt +- Prüfe `REGISTRY_USER` und `REGISTRY_PASSWORD` Secrets +- Teste Registry-Login manuell: + ```bash + docker login git.michaelschiemer.de:5000 -u admin -p + ``` + +### SSH-Verbindung fehlschlägt +- Prüfe `SSH_PRIVATE_KEY` Secret (kompletter Inhalt) +- Prüfe ob Public Key auf Production-Server ist +- Teste SSH-Verbindung manuell: + ```bash + ssh -i ~/.ssh/production deploy@94.16.110.151 + ``` diff --git a/deployment/CODE_CHANGE_WORKFLOW.md b/deployment/CODE_CHANGE_WORKFLOW.md new file mode 100644 index 00000000..2e1f3e5c --- /dev/null +++ b/deployment/CODE_CHANGE_WORKFLOW.md @@ -0,0 +1,551 @@ +# Codeänderungen pushen und deployen + +## Übersicht + +Dieses Dokument erklärt, wie Codeänderungen gepusht werden und automatisch in Production deployed werden. + +**Quick Start:** +```bash +git add . +git commit -m "feat: Add new feature" +git push origin main +``` +→ Automatisches Deployment startet (~8-15 Minuten) + +**📖 Verwandte Dokumentation:** +- **[Application Stack Deployment](APPLICATION_STACK_DEPLOYMENT.md)** - Wie das Deployment genau funktioniert +- **[CI/CD Status](CI_CD_STATUS.md)** - Aktueller Status der Pipeline + +--- + +## Normaler Workflow: Codeänderungen deployen + +### Schritt 1: Code lokal ändern + +```bash +# Änderungen in deinem lokalen Repository machen +# z.B. Datei bearbeiten: src/App/Controller/HomeController.php + +# Änderungen anzeigen +git status + +# Änderungen anschauen +git diff +``` + +### Schritt 2: Änderungen committen + +```bash +# Änderungen zum Staging hinzufügen +git add . + +# Oder nur spezifische Dateien +git add src/App/Controller/HomeController.php + +# Commit erstellen +git commit -m "feat: Add new feature to home controller" +``` + +**Commit-Message Konventionen:** +- `feat:` - Neue Feature +- `fix:` - Bug-Fix +- `refactor:` - Code-Refactoring +- `docs:` - Dokumentation +- `test:` - Tests +- `chore:` - Wartungsaufgaben + +### Schritt 3: Code zu Gitea pushen + +```bash +# Zu main branch pushen (triggert automatisches Deployment) +git push origin main + +# Oder zu anderem Branch pushen (kein Auto-Deploy) +git push origin feature/new-feature +``` + +### Schritt 4: Automatisches Deployment + +**Was passiert automatisch:** + +1. **Git Push** → Gitea erhält den Push +2. **Workflow wird getriggert** (bei Push zu `main` Branch) +3. **CI/CD Pipeline startet:** + - Tests laufen + - Docker Image wird gebaut + - Image wird zur Registry gepusht + - Ansible Deployment wird ausgeführt + - Application Stack wird aktualisiert + +**Zeitdauer:** ~8-15 Minuten für komplettes Deployment + +--- + +## Deployment-Trigger + +### Automatisches Deployment (bei Push zu `main`) + +**Workflow:** `.gitea/workflows/production-deploy.yml` + +```yaml +on: + push: + branches: + - main + paths-ignore: + - 'docs/**' + - '**.md' + - '.github/**' +``` + +**Bedeutung:** +- ✅ Push zu `main` Branch → Deployment startet automatisch +- ❌ Push zu anderen Branches → Kein Deployment +- ❌ Push nur von Markdown-Dateien → Kein Deployment (wegen `paths-ignore`) + +**Beispiel:** +```bash +# Triggert Deployment +git push origin main + +# Triggert KEIN Deployment (nur Markdown) +git commit -m "docs: Update README" --only README.md +git push origin又问 + +# Triggert KEIN Deployment (anderer Branch) +git checkout -b feature/new-feature +git push origin feature/new-feature +``` + +### Manuelles Deployment (Workflow-Dispatch) + +**Workflow kann manuell gestartet werden:** + +1. Gehe zu: `https://git.michaelschiemer.de/michael/michaelschiemer/actions` +2. Wähle: "Production Deployment Pipeline" +3. Klicke: "Run workflow" +4. Wähle Branch (z.B. `main` oder anderer Branch) +5. Optionale Einstellungen: + - `skip_tests`: `true` (nur in Notfällen!) +6. Klicke: "Run workflow" + +**Verwendung:** +- Deployment von anderem Branch (z.B. `develop`, `staging`) +- Deployment ohne Code-Änderung (z.B. nach Config-Änderung) +- Notfall-Deployment mit `skip_tests: true` + +--- + +## Workflow-Details + +### Was passiert bei jedem Push zu `main`? + +#### Job 1: Tests (ca. 2-5 Minuten) + +```yaml +- PHP 8.3 Setup +- Composer Dependencies installieren +- Pest Tests ausführen +- PHPStan Code Quality Check +- Code Style Check (composer cs) +``` + +**Bei Fehler:** Pipeline stoppt, kein Deployment + +#### Job 2: Build (ca. 3-5 Minuten) + +```yaml +- Docker Buildx Setup +- Image Metadata generieren (Tag: -) +- Docker Image Build (Dockerfile.production) +- Image mit Tags pushen: + - git.michaelschiemer.de:5000/framework:latest + - git.michaelschiemer.de:5000/framework: + - git.michaelschiemer.de:5000/framework:git- +``` + +#### Job 3: Deploy (ca. 2-4 Minuten) + +```yaml +- SSH Setup (mit Secret) +- Ansible Installation +- Ansible Playbook ausführen: + - Image Pull + - docker-compose.yml Update + - Stack Neustart +- Health-Check (10x versuche) +- Rollback bei Fehler +``` + +--- + +## Branching-Strategie + +### Empfohlener Workflow + +``` +main (Production) + ↓ +develop (Entwicklung/Testing) + ↓ +feature/* (Feature Branches) +``` + +### Workflow-Beispiele + +#### 1. Feature entwickeln + +```bash +# Feature Branch erstellen +git checkout -b feature/user-authentication + +# Änderungen machen +# ... Code schreiben ... + +# Committen +git add . +git commit -m "feat: Add user authentication" + +# Zu Gitea pushen (kein Auto-Deploy) +git push origin feature/user-authentication + +# Pull Request erstellen in Gitea +# → Code Review +# → Merge zu develop (oder main) +``` + +#### 2. Direkt zu Production deployen + +```bash +# Änderungen lokal +git checkout main +# ... Änderungen machen ... +git add . +git commit -m "fix: Critical bug fix" + +# Pushen → Triggert automatisches Deployment +git push origin main + +# Pipeline läuft automatisch: +# ✅ Tests +# ✅ Build +# ✅ Deploy +``` + +#### 3. Hotfix (Notfall) + +```bash +# Hotfix Branch von main +git checkout -b hotfix/critical-security-fix main + +# Fix implementieren +# ... Code schreiben ... +git add . +git commit -m "fix: Critical security vulnerability" + +# Direkt zu main mergen +git checkout main +git merge hotfix/critical-security-fix Ker + +# Pushen → Auto-Deploy +git push origin main + +# Optional: Manuelles Deployment mit skip_tests +# (nur wenn Tests lokal bereits erfolgreich) +``` + +--- + +## Deployment-Status prüfen + +### 1. Pipeline-Status in Gitea + +``` +https://git.michaelschiemer.de/michael/michaelschiemer/actions +``` + +**Status-Anzeigen:** +- 🟢 Grüner Haken = Erfolgreich +- 🔴 Roter Haken = Fehlgeschlagen +- 🟡 Gelber Kreis = Läuft gerade + +### 2. Logs ansehen + +1. Gehe zu Actions +2. Klicke auf den Workflow-Run +3. Klicke auf Job (z.B. "Deploy to Production Server") +4. Klicke auf Step (z.B. "Deploy via Ansible") +5. Logs ansehen + +### 3. Application-Status prüfen + +```bash +# SSH zum Production-Server +ssh deploy@94.16.110.151 + +# Container-Status prüfen +cd ~/deployment/stacks/application +docker compose ps + +# Logs ansehen +docker compose logs -f app + +# Health-Check manuell +curl https://michaelschiemer.de/health +``` + +--- + +## Deployment verhindern + +### Temporäres Deployment verhindern + +**Option 1: Push zu anderem Branch** +```bash +# Entwickle auf Feature-Branch +git checkout -b feature/my-feature +git push origin feature/my-feature +# → Kein Auto-Deploy +``` + +**Option CT 2: [skip ci] in Commit-Message** +```bash +# Workflow wird übersprungen +git commit -m "docs: Update documentation [skip ci]" +git push origin main +``` + +**Hinweis:** `[skip ci]` wird aktuell **nicht** unterstützt, da kein entsprechender Filter im Workflow ist. + +### Deployment-Trigger deaktivieren + +**Temporär (Workflow anpassen):** +```yaml +# In .gitea/workflows/production-deploy.yml +on: + push: + branches: + - main + # workflow_dispatch: # Kommentiere aus für temporäres Deaktivieren +``` + +**Besser:** Nutze Feature-Branches für Entwicklung ohne Auto-Deploy. + +--- + +## Häufige Szenarien + +### Szenario 1: Kleine Bug-Fix + +```bash +# 1. Bug-Fix lokal implementieren +git checkout main +# ... Fix implementieren ... +git add . +git commit -m "fix: Resolve login issue" + +# 2. Pushen → Auto-Deploy +git push origin main + +# 3. Pipeline beobachten +# → Tests ✅ +# → Build ✅ +# → Deploy ✅ + +# 4. Verifizieren +curl https://michaels chasing.de/health +``` + +### Szenario 2: Große Feature-Entwicklung + +```bash +# 1. Feature-Branch erstellen +git checkout -b feature/new-dashboard + +# 2. Feature entwickeln +# ... viele Commits ... + +# 3. Regelmäßig pushen (kein Auto-Deploy) +git push origin feature/new-dashboard + +# 4. Pull Request erstellen in Gitea +# → Code Review +# → Diskussion + +# 5. Merge zu main (triggert Auto-Deploy) +# → Via Gitea UI: "Merge Pull Request" +# → Oder lokal: +git checkout main +git merge feature/new-dashboard +git push origin main +``` + +### Szenario 3: Config-Änderung ohne Code-Änderung + +```bash +# Beispiel: .env Variablen ändern +# (wird über Ansible Template generiert, daher direkt auf Server ändern) + +# Oder: docker-compose.yml anpassen +# Änderungen machen +git add . +git commit -m "chore: Update docker-compose configuration" +git push origin main + +# → Pipeline läuft +# → Build: Keine Code-Änderung, aber Image wird neu getaggt +# → Deploy: docker-compose.yml wird aktualisiert +``` + +### Szenario 4: Notfall-Rollback + +```bash +# Option 1: Rollback via Ansible Playbook +cd deployment/ansible +ansible-playbook -i inventory/production.yml \ + playbooks/rollback.yml \ + -e "rollback_timestamp=2025-10-31T01-20-15Z" + +# Option 2: Alten Commit pushen +git log --oneline +# Finde letzten funktionierenden Commit +git checkout +git checkout -b rollback/previous-version +git push origin rollback/previous-version + +# Manuell zu main mergen oder direkt: +git checkout main +git reset --hard +git push origin main --force # ⚠️ Vorsicht! +# → Triggert Auto-Deploy mit altem Code +``` + +--- + +## Best Practices + +### 1. Commits + +- ✅ Klare, beschreibende Commit-Messages + entweder +- ✅ Atomic Commits (ein Feature = ein Commit) +- ✅ Regelmäßig pushen (nicht alles auf einmal) + +### 2. Testing + +- ✅ Tests lokal ausführen vor Push: + ```bash + composer cs # Code Style + make phpstan # Static Analysis + ./vendor/bin/pest # Tests + ``` + +### 3. Deployment + +- ✅ **Niemals** direkt zu `main` pushen ohne lokale Tests +- ✅ Feature-Branches für größere Änderungen +- ✅ Pull Requests für Code Review +- ✅ Pipeline-Status beobachten nach Push + +### 4. Rollback-Plan + +- ✅ Immer Backup vor größeren Änderungen +- ✅ Rollback-Playbook bereit halten +- ✅ Deployment-Metadaten dokumentieren + +--- + +## Troubleshooting + +### Pipeline schlägt fehl + +**Problem:** Tests fehlgeschlagen +```bash +# Tests lokal ausführen +./vendor/bin/pest + +# Fehler beheben +# ... Code anpassen ... +git add . +git commit -m "fix: Fix failing tests" +git push origin main +``` + +**Problem:** Build fehlgeschlagen +```bash +# Docker Build lokal testen +docker build -f Dockerfile.production -t test-image . + +# Fehler beheben +# ... Dockerfile anpassen ... +git add . +git commit -m "fix: Fix Docker build" +git push origin main +``` + +**Problem:** Deployment fehlgeschlagen +```bash +# SSH zum Server +ssh deploy@94.16.110.151 + +# Logs prüfen +cd ~/deployment/stacks/application +docker compose logs app + +# Manuell rollback +cd ~/deployment/ansible +ansible-playbook -i inventory/production.yml playbooks/rollback.yml +``` + +### Deployment läuft zu lange + +**Pipeline hängt:** +- Prüfe Runner-Status: `docker compose ps` in `deployment/gitea-runner` +- Prüfe Runner-Logs: `docker compose logs gitea-runner` +- Prüfe Workflow-Logs in Gitea UI + +**Deployment hängt:** +- Prüfe Server-Status: `ssh deploy@94.16.110.151 "docker ps"` +- Prüfe Container-Logs: `docker compose logs` +- Prüfe Disk-Space: `df -h` + +--- + +## Zusammenfassung + +### Normaler Workflow + +1. **Code ändern** lokal +2. **Committen** mit klarer Message +3. **Push zu `main`** → Auto-Deploy startet +4. **Pipeline beobachten** in Gitea Actions +5. **Verifizieren** auf Production + +### Wichtige Commands + +```bash +# Änderungen pushen (triggert Auto-Deploy) +git push origin main + +# Feature entwickeln (kein Auto-Deploy) +git checkout -b feature/my-feature +git push origin feature/my-feature + +# Pipeline-Status prüfen +# → https://git.michaelschiemer.de/michael/michaelschiemer/actions + +# Application-Status prüfen +ssh deploy@94.16.110.151 "cd ~/deployment/stacks/application && docker compose ps" +``` + +### Deployment-Zeit + +- **Gesamt:** ~8-15 Minuten +- **Tests:** ~2-5 Minuten +- **Build:** ~3-5 Minuten +- **Deploy:** ~2-4 Minuten +- **Health-Check:** ~1 Minute + +--- + +**Ready to deploy!** 🚀 diff --git a/deployment/DEPLOYMENT-STATUS.md b/deployment/DEPLOYMENT-STATUS.md index 771228f1..0000bf9f 100644 --- a/deployment/DEPLOYMENT-STATUS.md +++ b/deployment/DEPLOYMENT-STATUS.md @@ -1,33 +1,63 @@ # Deployment Status - Gitea Actions Runner Setup -**Status**: 🚧 BLOCKED - Phase 1 Step 1.1 -**Last Updated**: 2025-10-30 +**Status**: ✅ Phase 3 Complete - Ready for Phase 1 +**Last Updated**: 2025-10-31 **Target Server**: 94.16.110.151 (Netcup) --- ## Aktueller Status -### ✅ Abgeschlossen +### ✅ Phase 0: Git Repository SSH Access Setup - COMPLETE -**Phase 1 - Teilschritte Erledigt**: -1. ✅ Runner-Verzeichnisstruktur verifiziert: `/home/michael/dev/michaelschiemer/deployment/gitea-runner/` -2. ✅ `.env.example` Template analysiert (23 Zeilen) -3. ✅ `docker-compose.yml` Architektur verstanden (47 Zeilen, Docker-in-Docker) -4. ✅ `.env` Datei erstellt via: `cp deployment/gitea-runner/.env.example deployment/gitea-runner/.env` +1. ✅ Git SSH Key generiert (`~/.ssh/git_michaelschiemer`) +2. ✅ SSH Config konfiguriert für `git.michaelschiemer.de` +3. ✅ Public Key zu Gitea hinzugefügt +4. ✅ Git Remote auf SSH umgestellt +5. ✅ Push zu origin funktioniert ohne Credentials -### ⚠️ BLOCKER - Kritischer Fehler +### ✅ Phase 3: Production Server Initial Setup - COMPLETE -**Problem**: Gitea Admin Panel nicht erreichbar +**Infrastructure Stacks deployed via Ansible:** -**URL**: `https://git.michaelschiemer.de/admin/actions/runners` -**Fehler**: `404 page not found` +1. ✅ **Traefik** - Reverse Proxy & SSL (healthy) + - HTTPS funktioniert, Let's Encrypt SSL aktiv + - Dashboard: https://traefik.michaelschiemer.de -**Impact**: -- ❌ Kann Registration Token nicht abrufen (Phase 1, Step 1.1) -- ❌ Kann `.env` nicht komplettieren (Step 1.2) -- ❌ Kann Runner nicht registrieren (Step 1.3) -- ❌ Alle nachfolgenden Phasen (2-8) blockiert +2. ✅ **PostgreSQL** - Database Stack (healthy) + - Database läuft und ist bereit + +3. ✅ **Docker Registry** - Private Registry (running, accessible) + - Authentication konfiguriert + - Zugriff erfolgreich getestet + +4. ✅ **Gitea** - Git Server (healthy) + - HTTPS erreichbar: https://git.michaelschiemer.de ✅ + - SSH Port 2222 aktiv + - PostgreSQL Database verbunden + +5. ✅ **Monitoring** - Monitoring Stack (deployed) + - Grafana: https://grafana.michaelschiemer.de + - Prometheus: https://prometheus.michaelschiemer.de + - Portainer: https://portainer.michaelschiemer.de + +**Deployment Method:** Ansible Playbook `setup-infrastructure.yml` +**Deployment Date:** 2025-10-31 + +### ⏳ Phase 1: Gitea Runner Setup - READY TO START + +**Prerequisites erfüllt:** +- ✅ Gitea deployed und erreichbar +- ✅ Runner-Verzeichnisstruktur vorhanden +- ✅ `.env.example` Template analysiert +- ✅ `.env` Datei erstellt + +**Nächste Schritte:** +1. ⏳ Gitea Admin Panel öffnen: https://git.michaelschiemer.de/admin/actions/runners +2. ⏳ Actions in Gitea aktivieren (falls noch nicht geschehen) +3. ⏳ Registration Token abrufen +4. ⏳ Token in `.env` eintragen +5. ⏳ Runner registrieren und starten --- diff --git a/deployment/DEPLOYMENT-TODO.md b/deployment/DEPLOYMENT-TODO.md new file mode 100644 index 00000000..186cfaa7 --- /dev/null +++ b/deployment/DEPLOYMENT-TODO.md @@ -0,0 +1,281 @@ +# Deployment TODO - Komplette Implementierung + +**Status**: 🔄 In Progress +**Letzte Aktualisierung**: 2025-10-31 +**Ziel**: Komplettes Deployment-Setup im `deployment/` Ordner + +--- + +## ✅ Bereits Fertig + +### Infrastructure Stacks (deployed via Ansible) +- ✅ **Traefik** - Reverse Proxy & SSL +- ✅ **PostgreSQL** - Database Stack +- ✅ **Docker Registry** - Private Registry +- ✅ **Gitea** - Git Server + MySQL + Redis +- ✅ **Monitoring** - Portainer + Grafana + Prometheus +- ✅ **WireGuard VPN** - VPN Server + +### Ansible Playbooks +- ✅ `setup-infrastructure.yml` - Infrastructure Stacks Deployment +- ✅ `setup-wireguard.yml` - WireGuard VPN Setup +- ✅ `add-wireguard-client.yml` - WireGuard Client hinzufügen +- ✅ `deploy-update.yml` - Application Update Deployment +- ✅ `rollback.yml` - Rollback zu vorheriger Version +- ✅ `setup-production-secrets.yml` - Secrets Deployment +- ✅ `setup-ssl-certificates.yml` - SSL Certificate Setup +- ✅ `sync-stacks.yml` - Stacks synchronisieren + +### Dokumentation +- ✅ `README.md` - Deployment Übersicht +- ✅ `SETUP-GUIDE.md` - Komplette Setup-Anleitung +- ✅ `DEPLOYMENT-STATUS.md` - Aktueller Status +- ✅ `docs/WIREGUARD-SETUP.md` - WireGuard Dokumentation + +--- + +## ⏳ Offene Aufgaben + +### 1. Application Stack Integration + +**Status**: ⚠️ Fehlt in `setup-infrastructure.yml` + +**Was fehlt:** +- [x] Application Stack zu `setup-infrastructure.yml` hinzufügen ✅ +- [x] `.env` Template für Application Stack erstellen (`application.env.j2`) ✅ +- [x] Ansible Playbook/Task für Application Stack Deployment ✅ +- [x] Database-Migration nach Application Deployment ✅ +- [x] Health-Check nach Application Deployment ✅ + +**Dateien:** +- `deployment/stacks/application/docker-compose.yml` ✅ Vorhanden +- `deployment/stacks/application/.env.example` ✅ Vorhanden +- `deployment/stacks/application/.env` ❌ Fehlt (muss generiert werden) +- `deployment/ansible/templates/application.env.j2` ❌ Fehlt (Template für `.env`) +- `deployment/ansible/playbooks/setup-infrastructure.yml` ⚠️ Application fehlt + +**Nächste Schritte:** +1. Application Stack Deployment Task zu `setup-infrastructure.yml` hinzufügen +2. `.env` Template erstellen (mit Passwörtern aus Vault) +3. Database-Migration nach Application Start +4. Health-Check Integration + +--- + +### 2. Application Stack .env Konfiguration + +**Status**: ✅ Erledigt + +**Was erledigt:** +- [x] Ansible Template für `.env` Datei erstellt (`application.env.j2`) ✅ +- [x] Passwörter aus Vault/PostgreSQL .env laden ✅ +- [x] Domain-Konfiguration aus Inventory ✅ +- [x] Environment-Variablen aus Vault/Template generieren ✅ + +**Dateien:** +- `deployment/stacks/application/.env.example` ✅ Vorhanden (angepasst für PostgreSQL) +- `deployment/stacks/application/.env` ⚠️ Wird automatisch generiert +- `deployment/ansible/templates/application.env.j2` ✅ Erstellt +- `deployment/stacks/application/docker-compose.yml` ✅ Angepasst (PostgreSQL statt MySQL) + +--- + +### 3. Gitea Runner Setup abschließen + +**Status**: ⏳ Wartet auf Registration Token + +**Was fehlt:** +- [ ] Gitea Admin Panel öffnen: https://git.michaelschiemer.de/admin/actions/runners +- [ ] Actions in Gitea aktivieren (falls noch nicht geschehen) +- [ ] Registration Token abrufen +- [ ] Token in `.env` eintragen +- [ ] Runner registrieren und starten + +**Dateien:** +- `deployment/gitea-runner/.env` ⚠️ Vorhanden, aber Token fehlt +- `deployment/gitea-runner/.env.example` ✅ Vorhanden + +**Nächste Schritte:** +1. Gitea Actions aktivieren (Admin Panel) +2. Runner Registration Token generieren +3. Token in `.env` eintragen +4. Runner starten: `cd deployment/gitea-runner && docker compose up -d` + +--- + +### 4. CI/CD Pipeline finalisieren + +**Status**: ⚠️ Existiert, aber muss konfiguriert und getestet werden + +**Was fehlt:** +- [x] **Gitea Repository Secrets konfigurieren:** ✅ + - [x] `REGISTRY_USER` (Docker Registry Benutzername) ✅ + - [x] `REGISTRY_PASSWORD` (Docker Registry Passwort) ✅ + - [x] `SSH_PRIVATE_KEY` (SSH Private Key für Production-Server) ✅ + - [ ] `GITEA_TOKEN` (Optional: Für automatische Issue-Erstellung) +- [x] **Gitea Runner registrieren:** ✅ + - [x] Registration Token von Gitea Admin Panel abgerufen ✅ + - [x] Token in `deployment/gitea-runner/.env` eingetragen ✅ + - [x] Runner registriert ✅ + - [x] Runner läuft ✅ +- [ ] **Pipeline End-to-End testen:** + - [ ] Test-Commit pushen oder Workflow manuell triggern + - [ ] Alle Jobs erfolgreich (Tests, Build, Deploy) + - [ ] Deployment erfolgreich auf Production + - [ ] Health-Check erfolgreich + +**Dateien:** +- `.gitea/workflows/production-deploy.yml` ✅ Vorhanden +- `.gitea/workflows/update-production-secrets.yml` ✅ Vorhanden +- `.gitea/workflows/security-scan.yml` ✅ Vorhanden +- `deployment/CI_CD_STATUS.md` ✅ Neu erstellt (detaillierte Checkliste) + +**Detaillierte Anleitung:** +Siehe `deployment/CI_CD_STATUS.md` für komplette Checkliste und Setup-Anleitung. + +**Nächste Schritte:** +1. **Secrets in Gitea konfigurieren:** + - Gehe zu: `https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions` + - Füge Secrets hinzu: `REGISTRY_USER`, `REGISTRY_PASSWORD`, `SSH_PRIVATE_KEY` +2. **Gitea Runner registrieren:** + - Token von: `https://git.michaelschiemer.de/admin/actions/runners` + - Konfiguriere `deployment/gitea-runner/.env` + - Führe `./register.sh` aus +3. **Pipeline testen:** + - Workflow manuell triggern oder Test-Commit pushen + - Logs beobachten und Fehler beheben + +--- + +### 5. Backup & Rollback Scripts + +**Status**: ⚠️ Teilweise vorhanden + +**Was fehlt:** +- [ ] Backup-Playbook für Application Stack +- [ ] Rollback-Playbook testen und finalisieren +- [ ] PostgreSQL Backup-Integration +- [ ] Gitea Data Backup +- [ ] Registry Images Backup + +**Dateien:** +- `deployment/ansible/playbooks/rollback.yml` ✅ Vorhanden +- `deployment/scripts/rollback.sh` ✅ Vorhanden +- `deployment/stacks/postgresql/scripts/backup.sh` ✅ Vorhanden +- `deployment/ansible/playbooks/backup.yml` ❌ Fehlt + +**Nächste Schritte:** +1. Backup-Playbook erstellen +2. Rollback-Playbook testen +3. Backup-Scripte finalisieren +4. Automatisierte Backups konfigurieren + +--- + +### 6. Deployment Scripts finalisieren + +**Status**: ⚠️ Vorhanden, aber muss angepasst werden + +**Was fehlt:** +- [ ] `deployment/scripts/deploy.sh` testen und anpassen +- [ ] `deployment/scripts/rollback.sh` testen und anpassen +- [ ] `deployment/scripts/setup-production.sh` finalisieren +- [ ] Scripts für alle Stacks (nicht nur Application) + +**Dateien:** +- `deployment/scripts/deploy.sh` ✅ Vorhanden (aber Docker Swarm statt Compose?) +- `deployment/scripts/rollback.sh` ✅ Vorhanden +- `deployment/scripts/setup-production.sh` ✅ Vorhanden + +**Nächste Schritte:** +1. Scripts prüfen und anpassen (Docker Compose statt Swarm) +2. Scripts testen +3. Integration mit Ansible Playbooks + +--- + +### 7. Dokumentation vervollständigen + +**Status**: ⚠️ Gut, aber einige Updates nötig + +**Was fehlt:** +- [ ] `DEPLOYMENT-STATUS.md` aktualisieren (Application Stack Status) +- [ ] `README.md` aktualisieren (Application Stack Deployment) +- [ ] `SETUP-GUIDE.md` aktualisieren (Application Stack Phase) +- [ ] Troubleshooting Guide für Application Stack + +**Dateien:** +- `deployment/README.md` ⚠️ Muss aktualisiert werden +- `deployment/SETUP-GUIDE.md` ⚠️ Muss aktualisiert werden +- `deployment/DEPLOYMENT-STATUS.md` ⚠️ Muss aktualisiert werden + +--- + +## 🎯 Priorisierte Reihenfolge + +### Phase 1: Application Stack Deployment (KRITISCH) + +1. **Application Stack zu setup-infrastructure.yml hinzufügen** + - Task für Application Stack Deployment + - `.env` Template erstellen + - Database-Migration nach Deployment + +2. **Application .env Konfiguration** + - Template `application.env.j2` erstellen + - Passwörter aus Vault laden + - Template in Playbook integrieren + +### Phase 2: CI/CD Setup + +3. **Gitea Runner Setup abschließen** + - Token abrufen und konfigurieren + - Runner starten + +4. **CI/CD Pipeline finalisieren** + - Secrets in Gitea konfigurieren + - Pipeline testen + +### Phase 3: Backup & Scripts + +5. **Backup & Rollback Scripts** + - Backup-Playbook erstellen + - Rollback testen + +6. **Deployment Scripts finalisieren** + - Scripts testen und anpassen + +### Phase 4: Dokumentation + +7. **Dokumentation aktualisieren** + - README aktualisieren + - Status-Dokumente aktualisieren + +--- + +## 📋 Quick Checklist + +### Application Stack +- [ ] Application Stack in `setup-infrastructure.yml` hinzufügen +- [ ] `.env` Template (`application.env.j2`) erstellen +- [ ] Database-Migration Task hinzufügen +- [ ] Health-Check nach Deployment + +### CI/CD +- [ ] Gitea Runner Token konfigurieren +- [ ] Runner starten +- [ ] Secrets in Gitea konfigurieren +- [ ] Pipeline testen + +### Scripts & Backup +- [ ] Backup-Playbook erstellen +- [ ] Rollback testen +- [ ] Deployment-Scripts finalisieren + +### Dokumentation +- [ ] README aktualisieren +- [ ] SETUP-GUIDE aktualisieren +- [ ] DEPLOYMENT-STATUS aktualisieren + +--- + +**Nächster Schritt**: Application Stack zu `setup-infrastructure.yml` hinzufügen und `.env` Template erstellen \ No newline at end of file diff --git a/deployment/NATIVE-WORKFLOW-README.md b/deployment/NATIVE-WORKFLOW-README.md new file mode 100644 index 00000000..d289e092 --- /dev/null +++ b/deployment/NATIVE-WORKFLOW-README.md @@ -0,0 +1,155 @@ +# Native Workflow ohne GitHub Actions + +## Problem + +Der aktuelle Workflow (`production-deploy.yml`) verwendet GitHub Actions wie: +- `actions/checkout@v4` +- `shivammathur/setup-php@v2` +- `actions/cache@v3` +- `docker/setup-buildx-action@v3` +- `docker/build-push-action@v5` + +Diese Actions müssen von GitHub geladen werden, was zu Abbrüchen führen kann wenn: +- GitHub nicht erreichbar ist +- Actions nicht geladen werden können +- Timeouts auftreten + +## Lösung: Native Workflow + +Die Datei `.gitea/workflows/production-deploy-native.yml` verwendet **nur Shell-Commands** und keine GitHub Actions: + +### Vorteile + +1. **Keine GitHub-Abhängigkeit**: Funktioniert komplett offline +2. **Schneller**: Keine Action-Downloads +3. **Weniger Fehlerquellen**: Direkte Shell-Commands statt Actions +4. **Einfacher zu debuggen**: Standard-Bash-Scripts + +### Änderungen + +#### 1. Checkout +**Vorher:** +```yaml +- uses: actions/checkout@v4 +``` + +**Nachher:** +```bash +git clone --depth 1 --branch "$REF_NAME" \ + "https://git.michaelschiemer.de/${{ github.repository }}.git" \ + /workspace/repo +``` + +#### 2. PHP Setup +**Vorher:** +```yaml +- uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' +``` + +**Nachher:** +```bash +apt-get update +apt-get install -y php8.3 php8.3-cli php8.3-mbstring ... +``` + +#### 3. Cache +**Vorher:** +```yaml +- uses: actions/cache@v3 +``` + +**Nachher:** +```bash +# Einfaches Datei-basiertes Caching +if [ -d "/tmp/composer-cache/vendor" ]; then + cp -r /tmp/composer-cache/vendor /workspace/repo/vendor +fi +``` + +#### 4. Docker Buildx +**Vorher:** +```yaml +- uses: docker/setup-buildx-action@v3 +``` + +**Nachher:** +```bash +docker buildx create --name builder --use || docker buildx use builder +docker buildx inspect --bootstrap +``` + +#### 5. Docker Build/Push +**Vorher:** +```yaml +- uses: docker/build-push-action@v5 +``` + +**Nachher:** +```bash +docker buildx build \ + --file ./Dockerfile.production \ + --tag $REGISTRY/$IMAGE_NAME:latest \ + --push \ + . +``` + +## Verwendung + +### Option 1: Native Workflow aktivieren + +1. **Benenne um:** + ```bash + mv .gitea/workflows/production-deploy.yml .gitea/workflows/production-deploy-with-actions.yml.bak + mv .gitea/workflows/production-deploy-native.yml .gitea/workflows/production-deploy.yml + ``` + +2. **Commite und pushe:** + ```bash + git add .gitea/workflows/production-deploy.yml + git commit -m "chore: switch to native workflow without GitHub Actions" + git push origin main + ``` + +### Option 2: Beide parallel testen + +Lass beide Workflows parallel laufen: +- `production-deploy.yml` - Mit Actions (aktuell) +- `production-deploy-native.yml` - Native (neue Version) + +## Gitea Actions Konfiguration + +**Wichtig:** Wenn wir die native Version verwenden, brauchen wir `DEFAULT_ACTIONS_URL` **nicht mehr** in der Gitea-Konfiguration. + +Aber es schadet auch nicht, es drin zu lassen für zukünftige Workflows. + +## Debugging + +Wenn der native Workflow nicht funktioniert: + +1. **Prüfe Git Clone:** + ```bash + # Im Runner Container + git clone --depth 1 https://git.michaelschiemer.de/michael/michaelschiemer.git /tmp/test + ``` + +2. **Prüfe Docker Buildx:** + ```bash + docker buildx version + docker buildx ls + ``` + +3. **Prüfe PHP Installation:** + ```bash + php --version + php -m # Zeigt installierte Module + ``` + +## Empfehlung + +**Für Stabilität:** Verwende die native Version (`production-deploy-native.yml`) + +**Für Kompatibilität:** Bleib bei der Actions-Version (`production-deploy.yml`) + +Die native Version sollte stabiler sein, da sie keine externen Dependencies benötigt. diff --git a/deployment/README.md b/deployment/README.md index b949c1f5..76095741 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -129,7 +129,43 @@ docker compose up -d ## CI/CD Pipeline -The CI/CD pipeline is defined in `.gitea/workflows/deploy.yml` and runs on push to main branch: +The CI/CD pipeline is defined in `.gitea/workflows/production-deploy.yml` and runs automatically on push to `main` branch. + +### Quick Start: Deploy Code Changes + +```bash +# 1. Make changes locally +# ... edit files ... + +# 2. Commit changes +git add . +git commit -m "feat: Add new feature" + +# 3. Push to main → Automatic deployment starts +git push origin main +``` + +**What happens automatically:** +- ✅ Tests run (~2-5 min) +- ✅ Docker image is built (~3-5 min) +- ✅ Image is pushed to registry (~1-2 min) +- ✅ Ansible deployment runs (~2-4 min) +- ✅ Application stack is updated + +**Total time:** ~8-15 minutes + +**Status check:** +- Pipeline status: `https://git.michaelschiemer.de/michael/michaelschiemer/actions` +- Application status: `ssh deploy@94.16.110.151 "cd ~/deployment/stacks/application && docker compose ps"` + +**📖 Detailed Documentation:** +- **[Code Change Workflow](CODE_CHANGE_WORKFLOW.md)** - Complete guide for pushing code changes +- **[Application Stack Deployment](APPLICATION_STACK_DEPLOYMENT.md)** - How deployment works in detail +- **[CI/CD Status](CI_CD_STATUS.md)** - Current CI/CD pipeline status + +### Pipeline Details + +The CI/CD pipeline runs on push to main branch: 1. **Build Stage**: Build Docker image 2. **Push Stage**: Push to private registry diff --git a/deployment/SETUP-GUIDE.md b/deployment/SETUP-GUIDE.md index 530645aa..f4e2e969 100644 --- a/deployment/SETUP-GUIDE.md +++ b/deployment/SETUP-GUIDE.md @@ -22,7 +22,8 @@ This guide walks through the complete setup of production deployment from scratc **Development Machine:** - ✅ Docker & Docker Compose installed - ✅ Ansible installed (`pip install ansible`) -- ✅ SSH key for production server +- ✅ SSH key for production server (`~/.ssh/production`) +- ✅ Git SSH key configured (see Phase 0) - ✅ Access to Gitea admin panel **Production Server (94.16.110.151):** @@ -33,6 +34,83 @@ This guide walks through the complete setup of production deployment from scratc --- +## Phase 0: Git Repository SSH Access Setup (Development Machine) + +### Step 0.1: Generate Git SSH Key + +Create a separate SSH key specifically for Git operations (different from the production server SSH key): + +```bash +# Generate SSH key for Git +ssh-keygen -t ed25519 -f ~/.ssh/git_michaelschiemer -C "git@michaelschiemer.de" -N "" + +# Set correct permissions +chmod 600 ~/.ssh/git_michaelschiemer +chmod 644 ~/.ssh/git_michaelschiemer.pub +``` + +### Step 0.2: Configure SSH Config + +Add Git SSH configuration to `~/.ssh/config`: + +```bash +# Edit SSH config +nano ~/.ssh/config +``` + +Add the following configuration: + +``` +Host git.michaelschiemer.de + HostName git.michaelschiemer.de + Port 2222 + User git + IdentityFile ~/.ssh/git_michaelschiemer + StrictHostKeyChecking no + UserKnownHostsFile /dev/null +``` + +### Step 0.3: Add Public Key to Gitea + +1. Display your public key: + ```bash + cat ~/.ssh/git_michaelschiemer.pub + ``` + +2. Copy the output (starts with `ssh-ed25519 ...`) + +3. In Gitea: + - Go to **Settings** → **SSH / GPG Keys** + - Click **Add Key** + - Paste the public key + - Click **Add Key** + +4. Verify the connection: + ```bash + ssh -T git@git.michaelschiemer.de + ``` + + Expected output: `Hi there! You've successfully authenticated...` + +### Step 0.4: Update Git Remote (if needed) + +If your `origin` remote uses HTTPS, switch it to SSH: + +```bash +# Check current remote URL +git remote -v + +# Update to SSH +git remote set-url origin git@git.michaelschiemer.de:michael/michaelschiemer.git + +# Test push (should work without password prompt) +git push origin main +``` + +**Note**: This SSH key is separate from the production server SSH key (`~/.ssh/production`). The production key is used for Ansible/server access, while the Git key is only for repository operations. + +--- + ## Phase 1: Gitea Runner Setup (Development Machine) ### Step 1.1: Get Gitea Registration Token @@ -233,99 +311,107 @@ ansible-vault view production.vault.yml \ ## Phase 3: Production Server Initial Setup -### Step 3.1: Deploy Infrastructure Stacks +### Prerequisites -**On Production Server (SSH as deploy user):** +Before running Phase 3, ensure: +- ✅ SSH access to production server configured (`~/.ssh/production`) +- ✅ Repository cloned on production server at `~/deployment/stacks` (or adjust `stacks_base_path` in playbook) +- ✅ Ansible installed on your development machine: `pip install ansible` +- ✅ Ansible collections installed: `ansible-galaxy collection install community.docker` + +### Step 3.1: Clone Repository on Production Server (if not already done) + +**On Production Server:** ```bash # SSH to production server ssh deploy@94.16.110.151 -# Navigate to stacks directory -cd ~/deployment/stacks - -# Deploy stacks in order - -# 1. Traefik (Reverse Proxy & SSL) -cd traefik -docker compose up -d -docker compose logs -f -# Wait for "Configuration loaded" message -# Ctrl+C to exit logs - -# 2. PostgreSQL (Database) -cd ../postgresql -docker compose up -d -docker compose logs -f -# Wait for "database system is ready to accept connections" -# Ctrl+C to exit logs - -# 3. Docker Registry (Private Registry) -cd ../registry -docker compose up -d -docker compose logs -f -# Wait for "listening on [::]:5000" -# Ctrl+C to exit logs - -# 4. Gitea (Git Server + MySQL + Redis) -cd ../gitea -docker compose up -d -docker compose logs -f -# Wait for "Listen: http://0.0.0.0:3000" -# Ctrl+C to exit logs - -# 5. Monitoring (Portainer + Grafana + Prometheus) -cd ../monitoring -docker compose up -d -docker compose logs -f -# Wait for all services to start -# Ctrl+C to exit logs - -# Verify all stacks are running -docker ps +# Clone repository (if not already present) +mkdir -p ~/deployment +cd ~/deployment +git clone git@git.michaelschiemer.de:michael/michaelschiemer.git . || git clone https://git.michaelschiemer.de/michael/michaelschiemer.git . ``` -### Step 3.2: Configure Gitea - -1. Access Gitea: https://git.michaelschiemer.de -2. Complete initial setup wizard: - - Database: Use MySQL from stack - - Admin account: Create admin user - - Repository root: `/data/git/repositories` - - Enable Actions in admin settings - -### Step 3.3: Create Docker Registry User - -```bash -# SSH to production server -ssh deploy@94.16.110.151 - -# Create registry htpasswd entry -cd ~/deployment/stacks/registry -docker compose exec registry htpasswd -Bbn admin your-registry-password >> auth/htpasswd - -# Test login -docker login git.michaelschiemer.de:5000 -# Username: admin -# Password: your-registry-password -``` - -### Step 3.4: Setup SSH Keys for Ansible +### Step 3.2: Deploy Infrastructure Stacks with Ansible **On Development Machine:** ```bash -# Generate SSH key if not exists -ssh-keygen -t ed25519 -f ~/.ssh/production -C "ansible-deploy" +# Navigate to Ansible directory +cd deployment/ansible -# Copy public key to production server -ssh-copy-id -i ~/.ssh/production.pub deploy@94.16.110.151 +# Run infrastructure deployment playbook +ansible-playbook playbooks/setup-infrastructure.yml \ + -i inventory/production.yml -# Test SSH connection -ssh -i ~/.ssh/production deploy@94.16.110.151 "echo 'SSH works!'" +# The playbook will: +# 1. Create required Docker networks (traefik-public, app-internal) +# 2. Deploy Traefik (Reverse Proxy & SSL) +# 3. Deploy PostgreSQL (Database) +# 4. Deploy Docker Registry (Private Registry) +# 5. Deploy Gitea (Git Server + PostgreSQL) +# 6. Deploy Monitoring (Portainer + Grafana + Prometheus) +# 7. Wait for all services to be healthy +# 8. Verify accessibility ``` -**✅ Checkpoint**: All infrastructure stacks running, SSH access configured +**Expected output:** +- ✅ All stacks deployed successfully +- ✅ All services healthy +- ✅ Gitea accessible at https://git.michaelschiemer.de + +**Note:** If monitoring passwords need to be stored in Vault (recommended for production), add them to `secrets/production.vault.yml`: +- `vault_grafana_admin_password` +- `vault_prometheus_password` + +Then run the playbook with vault: +```bash +ansible-playbook playbooks/setup-infrastructure.yml \ + -i inventory/production.yml \ + --vault-password-file secrets/.vault_pass +``` + +### Step 3.3: Configure Gitea (Manual Step) + +1. Access Gitea: https://git.michaelschiemer.de +2. Complete initial setup wizard (first-time only): + - **Database Type**: PostgreSQL + - **Database Host**: `postgres:5432` + - **Database User**: `gitea` + - **Database Password**: `gitea_password` (or check `deployment/stacks/gitea/docker-compose.yml`) + - **Database Name**: `gitea` + - **Admin Account**: Create your admin user + - **Repository Root**: `/data/git/repositories` (default) +3. **Enable Actions** (required for Phase 1): + - Go to **Site Administration** → **Actions** + - Enable **Enable Actions** checkbox + - Save settings + +### Step 3.4: Verify Docker Registry + +The Ansible playbook automatically creates registry authentication. To retrieve credentials: + +```bash +# SSH to production server +ssh deploy@94.16.110.151 + +# View registry htpasswd (contains username:password hash) +cat ~/deployment/stacks/registry/auth/htpasswd + +# The default username is 'admin' +# Password hash can be used to login, or create new user: +cd ~/deployment/stacks/registry +docker compose exec registry htpasswd -Bbn >> auth/htpasswd +docker compose restart registry + +# Test login +docker login registry.michaelschiemer.de +# Or if using port: +docker login git.michaelschiemer.de:5000 +``` + +**✅ Checkpoint**: All infrastructure stacks running, Gitea accessible, Actions enabled --- diff --git a/deployment/WORKFLOW-TROUBLESHOOTING.md b/deployment/WORKFLOW-TROUBLESHOOTING.md new file mode 100644 index 00000000..9670e955 --- /dev/null +++ b/deployment/WORKFLOW-TROUBLESHOOTING.md @@ -0,0 +1,185 @@ +# Workflow Troubleshooting Guide + +## Problem: Workflows brechen zwischendurch ab + +### Mögliche Ursachen + +#### 1. Actions werden nicht geladen + +**Symptom:** Workflow startet, aber Actions wie `actions/checkout@v4` schlagen fehl + +**Lösung:** Prüfe Gitea Konfiguration: +```bash +docker exec gitea cat /data/gitea/conf/app.ini | grep -A 3 '[actions]' +``` + +Sollte enthalten: +```ini +[actions] +ENABLED = true +DEFAULT_ACTIONS_URL = https://github.com +``` + +#### 2. Timeouts bei langen Steps + +**Symptom:** Workflow läuft eine Zeit, dann Timeout + +**Lösung:** Timeout in Runner Config erhöhen: +```yaml +# deployment/gitea-runner/config.yaml +runner: + timeout: 6h # Erhöhe von 3h auf 6h +``` + +Dann Runner neu starten: +```bash +cd deployment/gitea-runner +docker compose restart gitea-runner +``` + +#### 3. Docker Buildx Probleme + +**Symptom:** Build Step schlägt fehl oder bricht ab + +**Lösung:** Prüfe, ob Buildx richtig läuft. Alternativ: Direktes Docker Build verwenden. + +#### 4. GitHub-Variablen in Gitea + +**Symptom:** `${{ github.sha }}` ist leer oder falsch + +**Lösung:** Gitea Actions sollte `github.*` Variablen unterstützen, aber manchmal funktioniert `gitea.*` besser. + +**Test:** Prüfe in Workflow-Logs, welche Variablen verfügbar sind: +```yaml +- name: Debug variables + run: | + echo "GITHUB_SHA: ${{ github.sha }}" + echo "GITEA_SHA: ${{ gitea.sha }}" + echo "RUNNER_OS: ${{ runner.os }}" +``` + +#### 5. Secrets fehlen oder sind falsch + +**Symptom:** Registry Login oder SSH schlägt fehl + +**Lösung:** Prüfe Secrets in Gitea: +- Repository → Settings → Secrets +- Alle benötigten Secrets sollten vorhanden sein: + - `REGISTRY_USER` + - `REGISTRY_PASSWORD` + - `SSH_PRIVATE_KEY` + - `ANSIBLE_VAULT_PASSWORD` (falls verwendet) + +### Debugging-Schritte + +#### 1. Workflow-Logs analysieren + +In Gitea UI: +1. Gehe zu Actions → Fehlgeschlagener Workflow +2. Klicke auf fehlgeschlagene Step +3. Prüfe Logs für Fehlermeldungen +4. Suche nach: + - `error` + - `timeout` + - `failed` + - `exit code` + +#### 2. Runner-Logs prüfen + +```bash +cd deployment/gitea-runner +docker compose logs gitea-runner --tail=100 | grep -E "(error|failed|timeout)" +``` + +#### 3. Runner Status prüfen + +In Gitea: https://git.michaelschiemer.de/admin/actions/runners + +Prüfe: +- Status sollte "Idle" oder "Running" sein +- Letzte Aktivität sollte kürzlich sein +- Keine Fehler-Meldungen + +### Häufige Fehler und Fixes + +#### Problem: "Action not found" + +**Fehler:** `Error: Action 'actions/checkout@v4' not found` + +**Fix:** +1. Prüfe `DEFAULT_ACTIONS_URL` in Gitea config +2. Stelle sicher, dass Internet-Zugriff vom Runner vorhanden ist +3. Gitea neu starten: `docker compose restart gitea` + +#### Problem: "Timeout" + +**Fehler:** `timeout: job exceeded maximum duration` + +**Fix:** +1. Erhöhe Timeout in `config.yaml` +2. Oder teile Workflow in kleinere Jobs auf + +#### Problem: "Docker build failed" + +**Fehler:** Docker Build schlägt fehl + +**Fix:** +1. Prüfe `docker-dind` Container läuft +2. Prüfe Registry-Zugriff +3. Prüfe Registry-Credentials + +#### Problem: "SSH connection failed" + +**Fehler:** Ansible Deployment kann nicht zum Server verbinden + +**Fix:** +1. Prüfe `SSH_PRIVATE_KEY` Secret ist korrekt +2. Prüfe SSH-Key hat richtige Berechtigungen +3. Prüfe Firewall erlaubt Verbindung + +### Workflow optimieren + +#### Reduziere Workflow-Zeit + +1. **Cache verwenden:** + ```yaml + - uses: actions/cache@v3 + with: + path: vendor + key: composer-${{ hashFiles('composer.lock') }} + ``` + +2. **Parallel Jobs:** + ```yaml + jobs: + test: + # ... + build: + # ... + # Beide können parallel laufen + ``` + +3. **Conditional Steps:** + ```yaml + - name: Skip on docs change + if: contains(github.event.head_commit.message, '[skip ci]') + run: exit 0 + ``` + +### Nächste Schritte + +1. **Identifiziere genaue Abbruch-Stelle:** + - In welchem Step bricht es ab? + - Welche Fehlermeldung erscheint? + +2. **Prüfe Logs:** + - Workflow-Logs in Gitea UI + - Runner-Logs: `docker compose logs gitea-runner` + +3. **Teste einzelne Steps:** + - Führe Steps manuell aus + - Isoliere das Problem + +4. **Workflow vereinfachen:** + - Reduziere auf minimalen Test-Workflow + - Füge Steps schrittweise hinzu diff --git a/deployment/ansible/README.md b/deployment/ansible/README.md index 81072198..3c6cad10 100644 --- a/deployment/ansible/README.md +++ b/deployment/ansible/README.md @@ -12,7 +12,10 @@ deployment/ansible/ ├── playbooks/ │ ├── setup-production-secrets.yml # Deploy secrets │ ├── deploy-update.yml # Deploy application updates -│ └── rollback.yml # Rollback deployments +│ ├── rollback.yml # Rollback deployments +│ ├── setup-wireguard.yml # Setup WireGuard VPN server +│ ├── add-wireguard-client.yml # Add WireGuard client +│ └── README-WIREGUARD.md # WireGuard documentation ├── secrets/ │ ├── .gitignore # Prevent committing secrets │ └── production.vault.yml.example # Example vault file @@ -112,6 +115,23 @@ ansible-playbook playbooks/rollback.yml \ -e "rollback_to_version=2025-01-28T15-30-00" ``` +### Setup WireGuard VPN + +**First-time setup** - Install WireGuard VPN server: + +```bash +ansible-playbook -i inventory/production.yml playbooks/setup-wireguard.yml +``` + +**Add a client**: + +```bash +ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml \ + -e "client_name=myclient" +``` + +Siehe [playbooks/README-WIREGUARD.md](playbooks/README-WIREGUARD.md) für detaillierte Anleitung. + ## Ansible Vault Operations ### View Encrypted File diff --git a/deployment/ansible/playbooks/README-WIREGUARD.md b/deployment/ansible/playbooks/README-WIREGUARD.md new file mode 100644 index 00000000..21e6f168 --- /dev/null +++ b/deployment/ansible/playbooks/README-WIREGUARD.md @@ -0,0 +1,284 @@ +# WireGuard VPN Setup + +WireGuard VPN-Server Installation und Konfiguration via Ansible. + +## Übersicht + +Dieses Ansible Setup installiert und konfiguriert einen WireGuard VPN-Server auf dem Production-Server, um sicheren Zugriff auf interne Services zu ermöglichen. + +## Playbooks + +### 1. setup-wireguard.yml + +Installiert und konfiguriert den WireGuard VPN-Server. + +**Features:** +- Installiert WireGuard und Tools +- Generiert Server-Keys (falls nicht vorhanden) +- Konfiguriert WireGuard-Server +- Aktiviert IP Forwarding +- Konfiguriert NAT (Masquerading) +- Öffnet Firewall-Port (51820/udp) +- Startet WireGuard-Service + +**Verwendung:** +```bash +cd deployment/ansible +ansible-playbook -i inventory/production.yml playbooks/setup-wireguard.yml +``` + +**Variablen:** +- `wireguard_port`: Port für WireGuard (Standard: 51820) +- `wireguard_network`: VPN-Netzwerk (Standard: 10.8.0.0/24) +- `wireguard_server_ip`: Server-IP im VPN (Standard: 10.8.0.1) + +**Beispiel mit Custom-Parametern:** +```bash +ansible-playbook -i inventory/production.yml playbooks/setup-wireguard.yml \ + -e "wireguard_port=51820" \ + -e "wireguard_network=10.8.0.0/24" \ + -e "wireguard_server_ip=10.8.0.1" +``` + +### 2. add-wireguard-client.yml + +Fügt einen neuen Client zum WireGuard-Server hinzu. + +**Features:** +- Generiert Client-Keys +- Fügt Client zur Server-Config hinzu +- Erstellt Client-Konfigurationsdatei +- Generiert QR-Code (falls qrencode installiert) +- Restartet WireGuard-Service + +**Verwendung:** +```bash +cd deployment/ansible +ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml \ + -e "client_name=myclient" +``` + +**Optionale Parameter:** +- `client_ip`: Spezifische Client-IP (Standard: automatisch berechnet) +- `allowed_ips`: Erlaubte IP-Ranges (Standard: gesamtes VPN-Netzwerk) + +**Beispiel mit spezifischer IP:** +```bash +ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml \ + -e "client_name=myclient" \ + -e "client_ip=10.8.0.2" +``` + +## Wichtige Sicherheitshinweise + +### SSH-Zugriff bleibt verfügbar + +**WICHTIG**: Die WireGuard-Konfiguration ändert NICHT die SSH-Zugriffsmöglichkeiten: + +- ✅ SSH über die normale Server-IP bleibt vollständig funktionsfähig +- ✅ WireGuard routet standardmäßig nur das VPN-Netzwerk (10.8.0.0/24) +- ✅ Normale Internet-Routen werden nicht geändert +- ✅ Firewall-Regeln für SSH (Port 22) werden NICHT entfernt oder blockiert + +Die Client-Konfiguration verwendet standardmäßig `AllowedIPs = 10.8.0.0/24`, was bedeutet, dass nur Traffic für das VPN-Netzwerk über WireGuard geroutet wird. Alle anderen Verbindungen (inkl. SSH) nutzen weiterhin die normale Internet-Verbindung. + +**Um SSH komplett über VPN zu routen** (nicht empfohlen für die erste Installation): +```bash +ansible-playbook ... -e "allowed_ips=0.0.0.0/0" +``` + +## Verzeichnisstruktur + +Nach der Installation: + +``` +/etc/wireguard/ +├── wg0.conf # Server-Konfiguration +├── wg0_private.key # Server-Private-Key (600) +├── wg0_public.key # Server-Public-Key (644) +└── clients/ # Client-Konfigurationen + ├── client1.conf # Client 1 Config + └── client2.conf # Client 2 Config +``` + +## Client-Konfiguration verwenden + +### 1. Config-Datei auf Client kopieren + +```bash +# Von Ansible Control Machine +scp -i ~/.ssh/production \ + deploy@94.16.110.151:/etc/wireguard/clients/myclient.conf \ + ~/myclient.conf +``` + +### 2. WireGuard auf Client installieren + +**Linux:** +```bash +sudo apt install wireguard wireguard-tools # Ubuntu/Debian +# oder +sudo yum install wireguard-tools # CentOS/RHEL +``` + +**macOS:** +```bash +brew install wireguard-tools +``` + +**Windows:** +Download von https://www.wireguard.com/install/ + +### 3. VPN verbinden + +**Linux/macOS:** +```bash +sudo wg-quick up ~/myclient.conf +# oder +sudo wg-quick up myclient +``` + +**Windows:** +Importiere die `.conf`-Datei in die WireGuard-App. + +### 4. Verbindung testen + +```bash +# Ping zum Server +ping 10.8.0.1 + +# Status prüfen +sudo wg show + +# VPN trennen +sudo wg-quick down myclient +``` + +## QR-Code für Mobile Client + +Falls `qrencode` installiert ist, wird beim Hinzufügen eines Clients automatisch ein QR-Code angezeigt: + +```bash +ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml \ + -e "client_name=myphone" +``` + +Der QR-Code kann mit der WireGuard Mobile App (iOS/Android) gescannt werden. + +## Firewall-Konfiguration + +Das Playbook öffnet automatisch den WireGuard-Port (51820/udp) in UFW, falls installiert. + +**Manuelle Firewall-Regeln:** + +```bash +# UFW +sudo ufw allow 51820/udp comment 'WireGuard VPN' + +# iptables direkt +sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT +``` + +## Troubleshooting + +### WireGuard startet nicht + +```bash +# Status prüfen +sudo systemctl status wg-quick@wg0 + +# Logs anzeigen +sudo journalctl -u wg-quick@wg0 -f + +# Manuell starten +sudo wg-quick up wg0 +``` + +### Client kann nicht verbinden + +1. **Firewall prüfen:** + ```bash + sudo ufw status + sudo iptables -L -n | grep 51820 + ``` + +2. **Server-Logs prüfen:** + ```bash + sudo journalctl -u wg-quick@wg0 -f + ``` + +3. **Server-Status prüfen:** + ```bash + sudo wg show + ``` + +4. **Routing prüfen:** + ```bash + sudo ip route show + ``` + +### IP Forwarding nicht aktiv + +```bash +# Manuell aktivieren +echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward + +# Permanent machen +echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf +sudo sysctl -p +``` + +## Client entfernen + +Um einen Client zu entfernen: + +```bash +# Auf dem Server +sudo nano /etc/wireguard/wg0.conf +# Entferne den [Peer] Block für den Client + +sudo wg-quick down wg0 +sudo wg-quick up wg0 + +# Optional: Client-Config löschen +sudo rm /etc/wireguard/clients/clientname.conf +``` + +## Server-Public-Key abrufen + +```bash +# Auf dem Server +cat /etc/wireguard/wg0_public.key +# oder +sudo cat /etc/wireguard/wg0_private.key | wg pubkey +``` + +## Best Practices + +1. **Backup der Keys**: Speichere Server-Keys sicher: + ```bash + sudo tar czf wireguard-backup.tar.gz /etc/wireguard/ + ``` + +2. **Regelmäßige Updates:** + ```bash + sudo apt update && sudo apt upgrade wireguard wireguard-tools + ``` + +3. **Monitoring**: Überwache VPN-Verbindungen: + ```bash + sudo wg show + ``` + +4. **Sicherheit**: + - Verwalte Client-Keys sicher + - Entferne nicht genutzte Clients + - Nutze starke Passwörter für Server-Zugriff + +## Support + +Bei Problemen: +1. Prüfe Logs: `sudo journalctl -u wg-quick@wg0` +2. Prüfe Status: `sudo wg show` +3. Prüfe Firewall: `sudo ufw status` +4. Teste Connectivity: `ping 10.8.0.1` (vom Client) \ No newline at end of file diff --git a/deployment/ansible/playbooks/add-wireguard-client.yml b/deployment/ansible/playbooks/add-wireguard-client.yml new file mode 100755 index 00000000..3d0f39ab --- /dev/null +++ b/deployment/ansible/playbooks/add-wireguard-client.yml @@ -0,0 +1,169 @@ +--- +- name: Add WireGuard Client + hosts: production + become: yes + gather_facts: yes + + vars: + wireguard_interface: "wg0" + wireguard_config_path: "/etc/wireguard" + wireguard_config_file: "{{ wireguard_config_path }}/{{ wireguard_interface }}.conf" + wireguard_client_configs_path: "/etc/wireguard/clients" + + pre_tasks: + - name: Set WireGuard network + set_fact: + wireguard_network: "{{ wireguard_network | default('10.8.0.0/24') }}" + + - name: Set WireGuard other variables with defaults + set_fact: + wireguard_port: "{{ wireguard_port | default(51820) }}" + client_ip: "{{ client_ip | default('') }}" + # IMPORTANT: Default to VPN network only (not 0.0.0.0/0) + # This ensures SSH access via normal IP remains available + allowed_ips: "{{ allowed_ips | default(wireguard_network) }}" + + tasks: + - name: Validate client name + fail: + msg: "client_name is required. Usage: ansible-playbook ... -e 'client_name=myclient'" + when: client_name is not defined or client_name == "" + + - name: Get server external IP address + uri: + url: https://api.ipify.org + return_content: yes + register: server_external_ip + changed_when: false + failed_when: false + + - name: Set server external IP + set_fact: + server_external_ip_content: "{{ ansible_host | default(server_external_ip.content | default('')) }}" + + - name: Check if WireGuard config exists + stat: + path: "{{ wireguard_config_file }}" + register: wireguard_config_exists + + - name: Fail if WireGuard not configured + fail: + msg: "WireGuard server not configured. Please run setup-wireguard.yml first." + when: not wireguard_config_exists.stat.exists + + - name: Read WireGuard server config + slurp: + src: "{{ wireguard_config_file }}" + register: wireguard_server_config_read + + - name: Extract server IP from config + set_fact: + server_vpn_ip: "{{ (wireguard_server_config_read.content | b64decode | regex_search('Address = ([0-9.]+)', '\\1')) | first | default('10.8.0.1') }}" + + - name: Count existing clients in config + set_fact: + existing_clients_count: "{{ (wireguard_server_config_read.content | b64decode | regex_findall('\\[Peer\\]') | length) }}" + when: client_ip == "" + + - name: Calculate client IP if not provided + set_fact: + client_ip: "{{ server_vpn_ip | regex_replace('^(\\d+\\.\\d+\\.\\d+\\.)\\d+$', '\\1') }}{{ (server_vpn_ip | regex_replace('^(\\d+\\.\\d+\\.\\d+\\.)(\\d+)', '\\2') | int) + (existing_clients_count | default(1)) | int }}" + when: client_ip == "" + + - name: Generate client private key + command: "wg genkey" + register: client_private_key + changed_when: true + no_log: yes + + - name: Generate client public key + command: "wg pubkey" + args: + stdin: "{{ client_private_key.stdout }}" + register: client_public_key + changed_when: false + no_log: yes + + - name: Check if client already exists in config + shell: "grep -q '{{ client_name }}' {{ wireguard_config_file }} || echo 'not found'" + register: client_exists_check + changed_when: false + failed_when: false + + - name: Add client to WireGuard server config + blockinfile: + path: "{{ wireguard_config_file }}" + block: | + # Client: {{ client_name }} + [Peer] + PublicKey = {{ client_public_key.stdout }} + AllowedIPs = {{ client_ip }}/32 + marker: "# {mark} ANSIBLE MANAGED BLOCK - Client: {{ client_name }}" + when: "'not found' in client_exists_check.stdout" + + - name: Ensure client configs directory exists + file: + path: "{{ wireguard_client_configs_path }}" + state: directory + mode: '0700' + owner: root + group: root + + - name: Get server public key + shell: "cat {{ wireguard_config_path }}/{{ wireguard_interface }}_private.key | wg pubkey" + register: server_public_key_cmd + changed_when: false + no_log: yes + failed_when: false + + - name: Create client configuration file + template: + src: "{{ playbook_dir }}/../templates/wireguard-client.conf.j2" + dest: "{{ wireguard_client_configs_path }}/{{ client_name }}.conf" + mode: '0600' + owner: root + group: root + + - name: Read WireGuard server config to find server IP + slurp: + src: "{{ wireguard_config_file }}" + register: wireguard_server_config_read + + - name: Restart WireGuard service + systemd: + name: "wg-quick@{{ wireguard_interface }}" + state: restarted + when: "'not found' in client_exists_check.stdout" + + - name: Display client configuration + debug: + msg: | + ======================================== + WireGuard Client Added: {{ client_name }} + ======================================== + + Client Configuration File: + {{ wireguard_client_configs_path }}/{{ client_name }}.conf + + Client IP: {{ client_ip }} + Server Endpoint: {{ server_external_ip_content }}:{{ wireguard_port }} + + To use this configuration: + 1. Copy the config file to your client machine + 2. Install WireGuard client + 3. Run: sudo wg-quick up {{ client_name }} + + Or scan the QR code (if qrencode installed): + qrencode -t ansiutf8 < {{ wireguard_client_configs_path }}/{{ client_name }}.conf + ======================================== + + - name: Generate QR code for client config + command: "qrencode -t ansiutf8 -r {{ wireguard_client_configs_path }}/{{ client_name }}.conf" + register: qr_code + changed_when: false + failed_when: false + + - name: Display QR code + debug: + msg: "{{ qr_code.stdout }}" + when: qr_code.rc == 0 \ No newline at end of file diff --git a/deployment/ansible/playbooks/deploy-update.yml b/deployment/ansible/playbooks/deploy-update.yml index b033e86d..98673dfe 100644 --- a/deployment/ansible/playbooks/deploy-update.yml +++ b/deployment/ansible/playbooks/deploy-update.yml @@ -118,7 +118,7 @@ changed_when: false ignore_errors: yes - ?? - name: Get deployed image information + - name: Get deployed image information shell: | docker compose -f {{ app_stack_path }}/docker-compose.yml config | grep -E "^\s+image:" | head -1 | awk '{print $2}' || echo "unknown" args: diff --git a/deployment/ansible/playbooks/setup-infrastructure.yml b/deployment/ansible/playbooks/setup-infrastructure.yml index 808d4c6c..3fbf5be8 100644 --- a/deployment/ansible/playbooks/setup-infrastructure.yml +++ b/deployment/ansible/playbooks/setup-infrastructure.yml @@ -241,6 +241,138 @@ debug: msg: "Gitea HTTPS check: {{ 'SUCCESS' if gitea_http_check.status == 200 else 'FAILED - Status: ' + (gitea_http_check.status|string) }}" + # 6. Deploy Application Stack + - name: Optionally load application secrets from vault + include_vars: + file: "{{ playbook_dir }}/../secrets/production.vault.yml" + no_log: yes + ignore_errors: yes + delegate_to: localhost + become: no + + - name: Check if PostgreSQL .env exists + stat: + path: "{{ stacks_base_path }}/postgresql/.env" + register: postgres_env_file + changed_when: false + + - name: Extract PostgreSQL password from .env file + shell: "grep '^POSTGRES_PASSWORD=' {{ stacks_base_path }}/postgresql/.env 2>/dev/null | cut -d'=' -f2- || echo ''" + register: postgres_password_from_file + changed_when: false + failed_when: false + when: postgres_env_file.stat.exists + no_log: yes + + - name: Set application database password (from file, vault, or generate) + set_fact: + app_db_password: "{{ postgres_password_from_file.stdout if (postgres_env_file.stat.exists and postgres_password_from_file.stdout != '') else (vault_db_root_password | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits,punctuation'))) }}" + no_log: yes + + - name: Set application redis password from vault or generate + set_fact: + app_redis_password: "{{ vault_redis_password | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits,punctuation')) }}" + + - name: Ensure application stack directory exists + file: + path: "{{ stacks_base_path }}/application" + state: directory + mode: '0755' + + - name: Create application stack .env file + template: + src: "{{ playbook_dir }}/../templates/application.env.j2" + dest: "{{ stacks_base_path }}/application/.env" + owner: "{{ ansible_user }}" + group: "{{ ansible_user }}" + mode: '0600' + vars: + db_password: "{{ app_db_password }}" + db_user: "{{ db_user | default('postgres') }}" + db_name: "{{ db_name | default('michaelschiemer') }}" + redis_password: "{{ app_redis_password }}" + app_domain: "{{ app_domain | default('michaelschiemer.de') }}" + no_log: yes + + - name: Deploy Application stack + community.docker.docker_compose_v2: + project_src: "{{ stacks_base_path }}/application" + state: present + pull: always + register: application_output + + - name: Wait for Application to be ready + wait_for: + timeout: "{{ wait_timeout }}" + when: application_output.changed + + - name: Wait for application containers to be healthy + pause: + seconds: 30 + when: application_output.changed + + - name: Check application container health status + shell: | + docker compose -f {{ stacks_base_path }}/application/docker-compose.yml ps --format json | jq -r '.[] | select(.Health != "healthy" and .Health != "" and .Health != "starting") | "\(.Name): \(.Health)"' || echo "All healthy or no health checks" + args: + executable: /bin/bash + register: app_health_status + changed_when: false + ignore_errors: yes + + - name: Display application health status + debug: + msg: "Application health: {{ app_health_status.stdout if app_health_status.stdout != '' else 'All services healthy or starting' }}" + + - name: Wait for app container to be ready before migration + wait_for: + timeout: 60 + when: application_output.changed + + - name: Check if app container is running + shell: | + docker compose -f {{ stacks_base_path }}/application/docker-compose.yml ps app | grep -q "Up" || exit 1 + args: + executable: /bin/bash + register: app_container_running + changed_when: false + failed_when: false + when: application_output.changed + + - name: Run database migrations + shell: | + docker compose -f {{ stacks_base_path }}/application/docker-compose.yml exec -T app php console.php db:migrate + args: + executable: /bin/bash + register: migration_result + changed_when: true + failed_when: false + ignore_errors: yes + when: application_output.changed and app_container_running.rc == 0 + + - name: Display migration result + debug: + msg: | + Migration Result: + {{ migration_result.stdout if migration_result.rc == 0 else 'Migration may have failed - check logs with: docker compose -f ' + stacks_base_path + '/application/docker-compose.yml logs app' }} + when: application_output.changed + + - name: Verify application accessibility via HTTPS + uri: + url: "https://{{ app_domain | default('michaelschiemer.de') }}/health" + method: GET + validate_certs: no + status_code: [200, 404, 502, 503] + timeout: 10 + register: app_health_check + ignore_errors: yes + when: application_output.changed + + - name: Display application accessibility status + debug: + msg: "Application health check: {{ 'SUCCESS (HTTP ' + (app_health_check.status|string) + ')' if app_health_check.status == 200 else 'FAILED or not ready yet (HTTP ' + (app_health_check.status|string) + ')' }}" + when: application_output.changed + - name: Summary debug: msg: @@ -250,9 +382,11 @@ - "Docker Registry: {{ 'Deployed' if registry_output.changed else 'Already running' }}" - "Gitea: {{ 'Deployed' if gitea_output.changed else 'Already running' }}" - "Monitoring: {{ 'Deployed' if monitoring_output.changed else 'Already running' }}" + - "Application: {{ 'Deployed' if application_output.changed else 'Already running' }}" - "" - "Next Steps:" - "1. Access Gitea at: https://git.michaelschiemer.de" - "2. Complete Gitea setup wizard if first-time deployment" - "3. Navigate to Admin > Actions > Runners to get registration token" - "4. Continue with Phase 1 - Gitea Runner Setup" + - "5. Access Application at: https://{{ app_domain | default('michaelschiemer.de') }}" diff --git a/deployment/ansible/playbooks/setup-wireguard.yml b/deployment/ansible/playbooks/setup-wireguard.yml new file mode 100755 index 00000000..f278cde8 --- /dev/null +++ b/deployment/ansible/playbooks/setup-wireguard.yml @@ -0,0 +1,294 @@ +--- +- name: Setup WireGuard VPN Server + hosts: production + become: yes + gather_facts: yes + + vars: + wireguard_interface: "wg0" + wireguard_config_path: "/etc/wireguard" + wireguard_config_file: "{{ wireguard_config_path }}/{{ wireguard_interface }}.conf" + wireguard_private_key_file: "{{ wireguard_config_path }}/{{ wireguard_interface }}_private.key" + wireguard_public_key_file: "{{ wireguard_config_path }}/{{ wireguard_interface }}_public.key" + wireguard_client_configs_path: "{{ wireguard_config_path }}/clients" + wireguard_enable_ip_forwarding: true + + pre_tasks: + - name: Set WireGuard variables with defaults + set_fact: + wireguard_port: "{{ wireguard_port | default(51820) }}" + wireguard_network: "{{ wireguard_network | default('10.8.0.0/24') }}" + wireguard_server_ip: "{{ wireguard_server_ip | default('10.8.0.1') }}" + + - name: Optionally load wireguard secrets from vault + include_vars: + file: "{{ playbook_dir }}/../secrets/production.vault.yml" + no_log: yes + ignore_errors: yes + delegate_to: localhost + become: no + + tasks: + - name: Check if WireGuard is already installed + command: which wg + register: wireguard_installed + changed_when: false + failed_when: false + + - name: Update package cache + apt: + update_cache: yes + cache_valid_time: 3600 + when: not wireguard_installed.rc == 0 + + - name: Install WireGuard + apt: + name: + - wireguard + - wireguard-tools + - qrencode + state: present + when: not wireguard_installed.rc == 0 + notify: restart wireguard + + - name: Ensure WireGuard config directory exists + file: + path: "{{ wireguard_config_path }}" + state: directory + mode: '0700' + owner: root + group: root + + - name: Ensure WireGuard client configs directory exists + file: + path: "{{ wireguard_client_configs_path }}" + state: directory + mode: '0700' + owner: root + group: root + + - name: Check if WireGuard server keys exist + stat: + path: "{{ wireguard_private_key_file }}" + register: server_private_key_exists + + - name: Generate WireGuard server private key + command: "wg genkey" + register: server_private_key + changed_when: true + when: not server_private_key_exists.stat.exists + no_log: yes + + - name: Save WireGuard server private key + copy: + content: "{{ server_private_key.stdout }}" + dest: "{{ wireguard_private_key_file }}" + mode: '0600' + owner: root + group: root + when: not server_private_key_exists.stat.exists + no_log: yes + + - name: Read WireGuard server private key + slurp: + src: "{{ wireguard_private_key_file }}" + register: server_private_key_content + when: server_private_key_exists.stat.exists + + - name: Generate WireGuard server public key + command: "wg pubkey" + args: + stdin: "{{ server_private_key.stdout if not server_private_key_exists.stat.exists else server_private_key_content.content | b64decode | trim }}" + register: server_public_key + changed_when: false + when: not server_private_key_exists.stat.exists + no_log: yes + + - name: Get existing server public key + shell: "cat {{ wireguard_private_key_file }} | wg pubkey" + register: existing_server_public_key + changed_when: false + when: server_private_key_exists.stat.exists + no_log: yes + failed_when: false + + - name: Set server public key fact + set_fact: + server_public_key_value: "{{ server_public_key.stdout if not server_private_key_exists.stat.exists else existing_server_public_key.stdout }}" + + - name: Save WireGuard server public key + copy: + content: "{{ server_public_key_value }}" + dest: "{{ wireguard_public_key_file }}" + mode: '0644' + owner: root + group: root + + - name: Enable IP forwarding + sysctl: + name: net.ipv4.ip_forward + value: '1' + state: present + sysctl_set: yes + reload: yes + when: wireguard_enable_ip_forwarding + + - name: Make IP forwarding persistent + lineinfile: + path: /etc/sysctl.conf + regexp: '^net\.ipv4\.ip_forward' + line: 'net.ipv4.ip_forward=1' + state: present + when: wireguard_enable_ip_forwarding + + - name: Get server external IP address + uri: + url: https://api.ipify.org + return_content: yes + register: server_external_ip + changed_when: false + failed_when: false + + - name: Set server external IP from inventory if API fails + set_fact: + server_external_ip_content: "{{ ansible_host | default(server_external_ip.content | default('')) }}" + when: server_external_ip.content is defined + + - name: Get server external IP from ansible_host + set_fact: + server_external_ip_content: "{{ ansible_host }}" + when: server_external_ip.content is not defined + + - name: Read server private key for config + slurp: + src: "{{ wireguard_private_key_file }}" + register: server_private_key_file_content + when: server_private_key_exists.stat.exists + + - name: Set server private key for template (new key) + set_fact: + server_private_key_for_config: "{{ server_private_key.stdout }}" + when: not server_private_key_exists.stat.exists + + - name: Set server private key for template (existing key) + set_fact: + server_private_key_for_config: "{{ server_private_key_file_content.content | b64decode | trim }}" + when: server_private_key_exists.stat.exists + + - name: Get network interface name + shell: "ip route | grep default | awk '{print $5}' | head -1" + register: default_interface + changed_when: false + failed_when: false + + - name: Set default interface + set_fact: + wireguard_interface_name: "{{ default_interface.stdout | default('eth0') }}" + + - name: Check if WireGuard config exists + stat: + path: "{{ wireguard_config_file }}" + register: wireguard_config_exists + + - name: Create WireGuard server configuration + template: + src: "{{ playbook_dir }}/../templates/wireguard-server.conf.j2" + dest: "{{ wireguard_config_file }}" + mode: '0600' + owner: root + group: root + notify: restart wireguard + + - name: Check if WireGuard service is enabled + systemd: + name: "wg-quick@{{ wireguard_interface }}" + register: wireguard_service_status + failed_when: false + changed_when: false + + - name: Enable WireGuard service + systemd: + name: "wg-quick@{{ wireguard_interface }}" + enabled: yes + daemon_reload: yes + when: not wireguard_service_status.status.ActiveState is defined or wireguard_service_status.status.ActiveState != 'active' + + - name: Start WireGuard service + systemd: + name: "wg-quick@{{ wireguard_interface }}" + state: started + notify: restart wireguard + + - name: Check if UFW firewall is installed + command: which ufw + register: ufw_installed + changed_when: false + failed_when: false + + - name: Verify SSH access is allowed in UFW + command: "ufw status | grep -q '22/tcp' || echo 'SSH not found'" + register: ssh_ufw_check + changed_when: false + failed_when: false + when: ufw_installed.rc == 0 + + - name: Warn if SSH is not explicitly allowed + debug: + msg: | + ?? WARNING: SSH (port 22) might not be explicitly allowed in UFW! + Please ensure SSH access is configured before proceeding. + Run: sudo ufw allow 22/tcp + when: ufw_installed.rc == 0 and 'SSH not found' in ssh_ufw_check.stdout + + - name: Allow WireGuard port in UFW firewall + ufw: + rule: allow + port: "{{ wireguard_port }}" + proto: udp + comment: "WireGuard VPN" + when: ufw_installed.rc == 0 + + - name: Allow WireGuard port in UFW firewall (alternative) + shell: "ufw allow {{ wireguard_port }}/udp comment 'WireGuard VPN'" + when: ufw_installed.rc == 0 + failed_when: false + changed_when: false + + - name: Check WireGuard status + command: "wg show {{ wireguard_interface }}" + register: wireguard_status + changed_when: false + failed_when: false + + - name: Display WireGuard status + debug: + msg: | + WireGuard Status: + {{ wireguard_status.stdout if wireguard_status.rc == 0 else 'WireGuard interface not active' }} + + - name: Display server public key + debug: + msg: | + ======================================== + WireGuard Server Setup Complete! + ======================================== + + Server Public Key: + {{ server_public_key_value }} + + Server IP: {{ wireguard_server_ip }} + Server Endpoint: {{ server_external_ip_content }}:{{ wireguard_port }} + Network: {{ wireguard_network }} + + To add a client, run: + ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml -e "client_name=myclient" + + Client configs are stored in: + {{ wireguard_client_configs_path }}/ + ======================================== + + handlers: + - name: restart wireguard + systemd: + name: "wg-quick@{{ wireguard_interface }}" + state: restarted \ No newline at end of file diff --git a/deployment/ansible/secrets/production.vault.yml.example b/deployment/ansible/secrets/production.vault.yml.example index 6895107c..522b9e77 100644 --- a/deployment/ansible/secrets/production.vault.yml.example +++ b/deployment/ansible/secrets/production.vault.yml.example @@ -7,7 +7,7 @@ vault_db_password: "change-me-secure-db-password" vault_db_root_password: "change-me-secure-root-password" -# Redis Credentials +# Application Stack Credentials vault_redis_password: "change-me-secure-redis-password" # Application Secrets diff --git a/deployment/ansible/templates/application.env.j2 b/deployment/ansible/templates/application.env.j2 new file mode 100644 index 00000000..d964ed5d --- /dev/null +++ b/deployment/ansible/templates/application.env.j2 @@ -0,0 +1,40 @@ +# Application Stack Environment Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY + +# Timezone +TZ={{ timezone | default('Europe/Berlin') }} + +# Application Domain +APP_DOMAIN={{ app_domain | default('michaelschiemer.de') }} + +# Application Settings +APP_ENV={{ app_env | default('production') }} +APP_DEBUG={{ app_debug | default('false') }} +APP_URL=https://{{ app_domain | default('michaelschiemer.de') }} + +# Database Configuration +# Using PostgreSQL from postgres stack +DB_HOST=postgres +DB_PORT={{ db_port | default('5432') }} +DB_NAME={{ db_name | default('michaelschiemer') }} +DB_USER={{ db_user | default('postgres') }} +DB_PASS={{ db_password }} + +# Redis Configuration +# Redis runs in this stack +REDIS_PASSWORD={{ redis_password }} + +# Cache Configuration +CACHE_DRIVER={{ cache_driver | default('redis') }} +CACHE_PREFIX={{ cache_prefix | default('app') }} + +# Session Configuration +SESSION_DRIVER={{ session_driver | default('redis') }} +SESSION_LIFETIME={{ session_lifetime | default('120') }} + +# Queue Worker Configuration +QUEUE_DRIVER={{ queue_driver | default('redis') }} +QUEUE_CONNECTION={{ queue_connection | default('default') }} +QUEUE_WORKER_SLEEP={{ queue_worker_sleep | default('3') }} +QUEUE_WORKER_TRIES={{ queue_worker_tries | default('3') }} +QUEUE_WORKER_TIMEOUT={{ queue_worker_timeout | default('60') }} \ No newline at end of file diff --git a/deployment/ansible/templates/monitoring.env.j2 b/deployment/ansible/templates/monitoring.env.j2 index 0f8c77a6..11206694 100644 --- a/deployment/ansible/templates/monitoring.env.j2 +++ b/deployment/ansible/templates/monitoring.env.j2 @@ -17,4 +17,5 @@ GRAFANA_PLUGINS={{ grafana_plugins | default('') }} # Prometheus BasicAuth # Format: username:hashed_password -PROMETHEUS_AUTH={{ prometheus_auth }} \ No newline at end of file +# Note: Dollar signs are escaped for Docker Compose ($$ becomes $) +PROMETHEUS_AUTH={{ prometheus_auth | replace('$', '$$') }} \ No newline at end of file diff --git a/deployment/ansible/templates/wireguard-client.conf.j2 b/deployment/ansible/templates/wireguard-client.conf.j2 new file mode 100644 index 00000000..25ecfa0b --- /dev/null +++ b/deployment/ansible/templates/wireguard-client.conf.j2 @@ -0,0 +1,27 @@ +# WireGuard Client Configuration for {{ client_name }} +# Generated by Ansible - DO NOT EDIT MANUALLY + +[Interface] +# Client private key +PrivateKey = {{ client_private_key.stdout }} + +# Client IP address in VPN network +Address = {{ client_ip }}/24 + +# DNS server (optional) +DNS = 1.1.1.1, 8.8.8.8 + +[Peer] +# Server public key +PublicKey = {{ server_public_key_cmd.stdout }} + +# Server endpoint +Endpoint = {{ server_external_ip_content }}:{{ wireguard_port }} + +# Allowed IPs (routes through VPN) +# IMPORTANT: Only VPN network is routed through VPN by default +# SSH access via normal IP ({{ server_external_ip_content }}) remains available +AllowedIPs = {{ allowed_ips }} + +# Keep connection alive +PersistentKeepalive = 25 \ No newline at end of file diff --git a/deployment/ansible/templates/wireguard-server.conf.j2 b/deployment/ansible/templates/wireguard-server.conf.j2 new file mode 100644 index 00000000..528273b9 --- /dev/null +++ b/deployment/ansible/templates/wireguard-server.conf.j2 @@ -0,0 +1,22 @@ +# WireGuard Server Configuration +# Generated by Ansible - DO NOT EDIT MANUALLY + +[Interface] +# Server private key +PrivateKey = {{ server_private_key_for_config }} + +# Server IP address in VPN network +Address = {{ wireguard_server_ip }}/24 + +# Port to listen on +ListenPort = {{ wireguard_port }} + +# Enable NAT for VPN clients to access internet +PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o {{ wireguard_interface_name }} -j MASQUERADE +PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o {{ wireguard_interface_name }} -j MASQUERADE + +# Clients will be added here by the add-wireguard-client playbook +# Example: +# [Peer] +# PublicKey = +# AllowedIPs = 10.8.0.2/32 \ No newline at end of file diff --git a/deployment/gitea-runner/config.yaml b/deployment/gitea-runner/config.yaml index 1611711e..623fc563 100644 --- a/deployment/gitea-runner/config.yaml +++ b/deployment/gitea-runner/config.yaml @@ -11,8 +11,8 @@ runner: # Maximum number of concurrent jobs capacity: 1 - # Timeout for a single job - timeout: 3h + # Timeout for a single job (increased for long-running deployments) + timeout: 6h # Whether to enable debug mode (skip SSL verification for setup) insecure: true @@ -55,8 +55,8 @@ container: # Default image if not specified in workflow default_image: node:16-bullseye - # Docker host - docker_host: "" + # Docker host (use docker-dind without TLS since it's in isolated network) + docker_host: tcp://docker-dind:2375 # Valid volume paths that can be mounted valid_volumes: diff --git a/deployment/gitea-runner/docker-compose.yml b/deployment/gitea-runner/docker-compose.yml index 2cdf0380..c74b2f89 100644 --- a/deployment/gitea-runner/docker-compose.yml +++ b/deployment/gitea-runner/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: gitea-runner: image: gitea/act_runner:latest @@ -25,14 +23,11 @@ services: container_name: gitea-runner-dind restart: unless-stopped privileged: true - environment: - - DOCKER_TLS_CERTDIR=/certs volumes: - - docker-certs:/certs - docker-data:/var/lib/docker networks: - gitea-runner - command: ["dockerd", "--host=unix:///var/run/docker.sock", "--host=tcp://0.0.0.0:2376", "--tlsverify"] + command: ["dockerd", "--host=unix:///var/run/docker.sock", "--host=tcp://0.0.0.0:2375"] networks: gitea-runner: @@ -40,7 +35,5 @@ networks: driver: bridge volumes: - docker-certs: - name: gitea-runner-certs docker-data: name: gitea-runner-docker-data diff --git a/deployment/stacks/application/.env.example b/deployment/stacks/application/.env.example index 13b944fe..10b91c5c 100644 --- a/deployment/stacks/application/.env.example +++ b/deployment/stacks/application/.env.example @@ -13,9 +13,9 @@ APP_DEBUG=false APP_URL=https://michaelschiemer.de # Database Configuration -# Note: MySQL runs in Stack 5 (PostgreSQL) or external server -DB_HOST=mysql -DB_PORT=3306 +# Note: Using PostgreSQL from postgres stack +DB_HOST=postgres +DB_PORT=5432 DB_NAME=michaelschiemer DB_USER=appuser DB_PASS= diff --git a/deployment/stacks/application/docker-compose.yml b/deployment/stacks/application/docker-compose.yml index 7c618072..de6f3ce7 100644 --- a/deployment/stacks/application/docker-compose.yml +++ b/deployment/stacks/application/docker-compose.yml @@ -14,8 +14,8 @@ services: - APP_DEBUG=${APP_DEBUG:-false} - APP_URL=${APP_URL:-https://michaelschiemer.de} # Database - - DB_HOST=${DB_HOST:-mysql} - - DB_PORT=${DB_PORT:-3306} + - DB_HOST=${DB_HOST:-postgres} + - DB_PORT=${DB_PORT:-5432} - DB_NAME=${DB_NAME} - DB_USER=${DB_USER} - DB_PASS=${DB_PASS} @@ -127,8 +127,8 @@ services: - APP_ENV=${APP_ENV:-production} - APP_DEBUG=${APP_DEBUG:-false} # Database - - DB_HOST=${DB_HOST:-mysql} - - DB_PORT=${DB_PORT:-3306} + - DB_HOST=${DB_HOST:-postgres} + - DB_PORT=${DB_PORT:-5432} - DB_NAME=${DB_NAME} - DB_USER=${DB_USER} - DB_PASS=${DB_PASS} @@ -172,8 +172,8 @@ services: - APP_ENV=${APP_ENV:-production} - APP_DEBUG=${APP_DEBUG:-false} # Database - - DB_HOST=${DB_HOST:-mysql} - - DB_PORT=${DB_PORT:-3306} + - DB_HOST=${DB_HOST:-postgres} + - DB_PORT=${DB_PORT:-5432} - DB_NAME=${DB_NAME} - DB_USER=${DB_USER} - DB_PASS=${DB_PASS} diff --git a/deployment/stacks/monitoring/docker-compose.yml b/deployment/stacks/monitoring/docker-compose.yml index ad139065..2eed2740 100644 --- a/deployment/stacks/monitoring/docker-compose.yml +++ b/deployment/stacks/monitoring/docker-compose.yml @@ -15,12 +15,6 @@ services: - "traefik.http.routers.portainer.tls=true" - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" - "traefik.http.services.portainer.loadbalancer.server.port=9000" - healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9000/api/system/status"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s prometheus: image: prom/prometheus:latest diff --git a/docs/deployment/README.md b/docs/deployment/README.md index 19b50a80..050ac299 100644 --- a/docs/deployment/README.md +++ b/docs/deployment/README.md @@ -18,6 +18,9 @@ Complete documentation for deploying the Custom PHP Framework to production. **Want automation?** - [Ansible Deployment](ANSIBLE_DEPLOYMENT.md) - Infrastructure as Code with Ansible +**Need secure access?** +- [WireGuard VPN Setup](WIREGUARD-SETUP.md) - Secure VPN access to production services + --- ## Documentation Structure @@ -120,6 +123,21 @@ Complete documentation for deploying the Custom PHP Framework to production. --- +### 7. [WIREGUARD-SETUP.md](WIREGUARD-SETUP.md) +**Best for**: Secure VPN access to production services + +**Content**: +- Complete WireGuard VPN setup guide +- Server installation via Ansible +- Client configuration and management +- Connection testing and troubleshooting +- Security best practices +- Monitoring and maintenance + +**Use when**: You need secure access to internal services (Prometheus, Grafana, Portainer) or want to restrict access via VPN. + +--- + ## Which Guide Should I Use? ### Scenario 1: First-Time Deployment @@ -452,6 +470,7 @@ This documentation should be updated after each deployment to reflect: - [Production Guide](PRODUCTION_DEPLOYMENT.md) - Comprehensive reference - [Logging Guide](production-logging.md) - Production logging configuration - [Ansible Guide](ANSIBLE_DEPLOYMENT.md) - Infrastructure automation +- [WireGuard VPN](WIREGUARD-SETUP.md) - Secure VPN access to production --- diff --git a/docs/deployment/WIREGUARD-FUTURE-SECURITY.md b/docs/deployment/WIREGUARD-FUTURE-SECURITY.md new file mode 100644 index 00000000..07239d22 --- /dev/null +++ b/docs/deployment/WIREGUARD-FUTURE-SECURITY.md @@ -0,0 +1,213 @@ +# WireGuard - Zukünftige Sicherheitshärtung + +**Status**: 📋 Geplant +**Aktueller Status**: Alle Dienste öffentlich erreichbar +**Ziel**: Backend-Dienste nur noch über VPN erreichbar + +--- + +## Übersicht + +Dieses Dokument beschreibt die geplante Sicherheitshärtung für Backend-Dienste, sodass diese nur noch über WireGuard VPN erreichbar sind. + +**Aktueller Zustand:** +- ✅ WireGuard VPN ist installiert und funktionsfähig +- ✅ Backend-Dienste sind öffentlich über Traefik erreichbar +- ✅ VPN-Zugriff funktioniert parallel + +**Zielzustand:** +- 🔒 Backend-Dienste nur noch über VPN erreichbar +- 🔒 Öffentlicher Zugriff auf Prometheus, Grafana, Portainer blockiert +- 🔒 SSH-Zugriff weiterhin über normale IP möglich + +--- + +## Betroffene Dienste + +Folgende Dienste sollen später nur noch über VPN erreichbar sein: + +1. **Prometheus** (Port 9090) + - Aktuell: `http://prometheus.michaelschiemer.de` (öffentlich) + - Zukünftig: Nur `http://10.8.0.1:9090` über VPN + +2. **Grafana** (Port 3000) + - Aktuell: `https://grafana.michaelschiemer.de` (öffentlich) + - Zukünftig: Nur `http://10.8.0.1:3000` über VPN + +3. **Portainer** (Port 9443) + - Aktuell: `https://portainer.michaelschiemer.de` (öffentlich) + - Zukünftig: Nur `https://10.8.0.1:9443` über VPN + +--- + +## Geplante Implementierungsschritte + +### Schritt 1: Traefik-Labels anpassen + +**Datei**: `deployment/stacks/monitoring/docker-compose.yml` + +Traefik-Router für interne Dienste entfernen oder anpassen: + +```yaml +# Entfernen/Anpassen der öffentlichen Traefik-Labels: +labels: + # - "traefik.enable=true" + # - Orchestra: "traefik.http.routers.prometheus.rule=Host(`prometheus.${DOMAIN}`)" + # - Orchestra: "traefik.http.routers.grafana.rule=Host(`grafana.${DOMAIN}`)" + # - Orchestra: "traefik.http.routers.portainer.rule=Host(`portainer.${DOMAIN}`)" +``` + +### Schritt 2: Firewall-Regeln anpassen + +**Option A: UFW-Regeln** + +```bash +# Öffentliche Ports für Monitoring blockieren +sudo ufw deny 9090/tcp # Prometheus +sudo ufw deny 3000/tcp # Grafana +sudo ufw deny 9443/tcp # Portainer + +# VPN-Zugriff explizit erlauben (falls notwendig) +sudo ufw allow from 10.8.0.0/24 to any port 9090 +sudo ufw allow from 10.8.0.0/24 to any port 3000 +sudo ufw allow from 10.8.0.0/24 to any port 9443 +``` + +**Option B: Traefik Middleware** + +Traefik-Middleware erstellen, die nur VPN-IPs erlaubt: + +```yaml +labels: + - "traefik.http.middlewares.vpn-only.ipwhitelist.sourcerange=10.8.0.0/24" + - "traefik.http.routers.prometheus.middlewares=vpn-only" +``` + +### Schritt 3: Docker-Netzwerk-Konfiguration + +**Datei**: `deployment/stacks/monitoring/docker-compose.yml` + +Dienste nur auf `app-internal` Netzwerk belassen, nicht auf `traefik-public`: + +```yaml +services: + prometheus: + networks: + - app-internal # Nur internes Netzwerk + # - traefik-public # Entfernen + + grafana: + networks: + - app-internal + # - traefik-public + + portainer: + networks: + - app-internal + # - traefik-public +``` + +### Schritt 4: Nginx/Traefik-Konfiguration + +Traefik-Router entfernen, sodass die Dienste nicht mehr öffentlich erreichbar sind. + +--- + +## Testplan + +### Vor der Umsetzung + +1. ✅ VPN-Verbindung testen +2. ✅ Zugriff auf Dienste über VPN testen (10.8.0.1: term +3. ✅ Öffentliche URLs dokumentieren (für späteren Vergleich) + +### Nach der Umsetzung + +1. ✅ Öffentliche URLs sollten nicht mehr erreichbar sein (404/Connection refused) +2. ✅ VPN-Zugriff sollte weiterhin funktionieren +3. ✅ SSH-Zugriff über normale IP sollte weiterhin funktionieren +4. ✅ Hauptanwendung (michaelschiemer.de) sollte weiterhin öffentlich erreichbar sein + +--- + +## Rollback-Plan + +Falls Probleme auftreten: + +1. Traefik-Labels wieder aktivieren +2. Firewall-Regeln zurücksetzen: + ```bash + sudo ufw delete deny 9090/tcp + sudo ufw delete deny 3000/tcp + sudo ufw delete deny 9443/tcp + ``` +3. Docker-Container neu starten: + ```bash + cd deployment/stacks/monitoring + docker compose up -d + ``` + +--- + +## Zeitplan + +**Nicht geplant für sofortige Umsetzung** + +Die Härtung kann durchgeführt werden, wenn: +- ✅ WireGuard-VPN stabil läuft +- ✅ Alle notwendigen Clients push +4. **Optional**: Monitoring-Alerts für VPN-Verbindungen +5. **Optional**: Automatische Firewall-Regeln via Ansible + +--- + +## Weitere Überlegungen + +### Alternative: Traefik mit VPN-IP-Whitelist + +Statt die Dienste komplett aus Traefik zu entfernen, könnte eine Middleware verwendet werden: + +```yaml +# Traefik-Middleware für VPN-only +- "traefik.http.middlewares.vpn-only.ipwhitelist.sourcerange=10.8.0.0/24" +- "traefik.http.routers.prometheus.middlewares=vpn-only" +``` + +**Vorteil**: Konfiguration bleibt in Traefik, einfaches Toggle +**Nachteil**: Traefik muss weiterhin die Requests verarbeiten + +### Alternative: Separates Traefik-Instance für VPN + +Ein separater Traefik-Container nur für VPN-Dienste: + +```yaml +services: + traefik-vpn: + image: traefik:v3.0 + networks: + - app-internal + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + command: + - "--api.insecure=false" + - "--providers.docker.network=app-internal" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" +``` + +**Vorteil**: Komplette Trennung von öffentlichen und VPN-Diensten +**Nachteil**: Zusätzliche Ressourcen, komplexere Konfiguration + +--- + +## Dokumentation + +Nach der Umsetzung sollte aktualisiert werden: + +1. `WIREGUARD-SETUP.md` - Status aktualisieren +2. `production-deployment-guide.md` - Firewall-Konfiguration dokumentieren +3. `README.md` - Hinweise auf VPN-only-Zugriff + +--- + +**Hinweis**: Diese Härtung wird erst durchgeführt, wenn das VPN stabil läuft und alle notwendigen Clients konfiguriert sind. \ No newline at end of file diff --git a/docs/deployment/WIREGUARD-SETUP.md b/docs/deployment/WIREGUARD-SETUP.md new file mode 100644 index 00000000..7eaa68a6 --- /dev/null +++ b/docs/deployment/WIREGUARD-SETUP.md @@ -0,0 +1,558 @@ +# WireGuard VPN Setup - Complete Guide + +**Status**: ✅ Active +**Last Updated**: 2025-10-31 +**Server**: 94.16.110.151 (michaelschiemer.de) + +--- + +## Übersicht + +WireGuard ist ein modernes, schnelles und sicheres VPN-Protokoll, das für den sicheren Zugriff auf interne Services auf dem Production-Server verwendet wird. + +**Features:** +- ✅ Schnell und performant +- ✅ Moderne Kryptographie +- ✅ Einfache Konfiguration +- ✅ Native Unterstützung in Linux, macOS, Windows, iOS, Android +- ✅ Automatisierte Installation via Ansible + +--- + +## Server-Informationen + +**Server-Endpoint:** +- **Host**: 94.16.110.151 +- **Port**: 51820 (UDP) +- **VPN-Netzwerk**: 10.8.0.0/24 +- **Server-IP (VPN)**: 10.8.0.1 + +**Server-Public-Key:** +``` +hT3OCWZ6ElX79YdAdexSsZnbWLzRM/5szk+XNEBUaS8= +``` + +**Wichtige Sicherheitshinweise:** +- ✅ SSH-Zugriff über normale IP bleibt vollständig funktionsfähig +- ✅ WireGuard routet standardmäßig nur das VPN-Netzwerk (10.8.0.0/24) +- ✅ Normale Internet-Routen werden nicht geändert +- ✅ Firewall-Regeln für SSH (Port 22) werden NICHT entfernt oder blockiert + +**Zukünftige Sicherheitshärtung (geplant):** +- 🔒 Backend-Dienste (Prometheus, Grafana, Portainer) sollen später nur noch über VPN erreichbar sein +- 🔒 Aktuell sind alle Dienste noch öffentlich erreichbar (für einfachere Einrichtung) +- 🔒 Firewall-Regeln können später angepasst werden, um nur VPN-Zugriff zu erlauben + +--- + +## Installation + +### Server-Installation (via Ansible) + +Die Server-Installation erfolgt automatisch via Ansible: + +```bash +cd deployment/ansible +ansible-playbook -i inventory/production.yml playbooks/setup-wireguard.yml +``` + +**Was wird installiert:** +- WireGuard und Tools +- Server-Keys (generiert oder vorhandene verwendet) +- Server-Konfiguration +- IP Forwarding aktiviert +- NAT (Masquerading) konfiguriert +- Firewall-Port 51820/udp geöffnet +- WireGuard-Service gestartet + +### Client hinzufügen + +Um einen neuen Client hinzuzufügen: + +```bash +cd deployment/ansible +ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml \ + -e "client_name=myclient" +``` + +**Optionale Parameter:** +- `client_ip`: Spezifische Client-IP (Standard: automatisch berechnet) +- `allowed_ips`: Erlaubte IP-Ranges (Standard: 10.8.0.0/24) + +**Beispiel mit spezifischer IP:** +```bash +ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml \ + -e "client_name=myclient" \ + -e "client_ip=10.8.0.5" +``` + +--- + +## Client-Konfiguration + +### 1. Config-Datei abrufen + +Die Client-Konfigurationsdatei liegt auf dem Server unter: +``` +/etc/wireguard/clients/{client_name}.conf +``` + +**Abrufen der Config-Datei:** + +```bash +# Von Ansible Control Machine +scp -i ~/.ssh/production \ + deploy@94.16.110.151:/etc/wireguard/clients/development.conf \ + ~/development.conf +``` + +Oder direkt auf dem Server: + +```bash +ssh -i ~/.ssh/production deploy@94.16.110.151 +sudo cat /etc/wireguard/clients/development.conf +``` + +### 2. WireGuard auf Client installieren + +**Linux (Ubuntu/Debian):** +```bash +sudo apt update +sudo apt install wireguard wireguard-tools +``` + +**Linux (CentOS/RHEL):** +```bash +sudo yum install wireguard-tools +# oder +sudo dnf install wireguard-tools +``` + +**macOS:** +```bash +brew install wireguard-tools +``` + +**Windows:** +Download von https://www.wireguard.com/install/ + +**iOS/Android:** +WireGuard App aus dem App Store/Play Store installieren + +### 3. VPN verbinden + +**Linux/macOS (Command Line):** +```bash +# Config-Datei kopieren +sudo cp ~/development.conf /etc/wireguard/development.conf + +# VPN starten +sudo wg-quick up development + +# Status prüfen +sudo wg show + +# VPN trennen +sudo wg-quick down development +``` + +**Linux/macOS (Alternative):** +```bash +# Direkt mit Datei +sudo wg-quick up ~/development.conf +``` + +**Windows:** +1. WireGuard-App öffnen +2. "Import tunnel(s) from file" wählen +3. `development.conf` Datei auswählen +4. "Activate" klicken + +**iOS/Android:** +1. WireGuard-App öffnen +2. QR-Code scannen (falls verfügbar) oder Config-Datei importieren +3. Tunnel aktivieren + +### 4. QR-Code generieren + +Falls `qrencode` auf dem Server installiert ist, kann ein QR-Code generiert werden: + +```bash +# Auf dem Server +qrencode -t ansiutf8 < /etc/wireguard/clients/development.conf +# oder für PNG +qrencode -t png -o /tmp/development-qr.png < /etc/wireguard/clients/development.conf +``` + +--- + +## Verbindung testen + +Nach dem Verbinden mit dem VPN: + +### 1. VPN-Status prüfen + +```bash +# Status anzeigen +sudo wg show + +# Erwartete Ausgabe: +# interface: development +# public key: +# private key: (hidden) +# listening port: +# +# peer: hT3OCWZ6ElX79YdAdexSsZnbWLzRM/5szk+XNEBUaS8= +# endpoint: 94.16.110.151:51820 +# allowed ips: 10.8.0.0/24 +# latest handshake: +# transfer: , +``` + +### 2. Server-IP pingen + +```bash +# Server im VPN-Netzwerk pingen +ping 10.8.0.1 + +# Sollte erfolgreich sein +``` + +### 3. VPN-Netzwerk prüfen + +```bash +# Routing-Tabelle prüfen +ip route show + +# Sollte einen Eintrag für 10.8.0.0/24 über wg0 zeigen +``` + +### 4. Services über VPN erreichen + +Nach erfolgreicher VPN-Verbindung sollten folgende Services über das VPN-Netzwerk erreichbar sein: + +- **Prometheus**: http://10.8.0.1:9090 +- **Grafana**: http://10.8.0.1:3000 +- **Portainer**: https://10.8.0.1:9443 + +**Hinweis**: +- ⚠️ Aktuell sind diese Services auch öffentlich erreichbar (Traefik-Routing) +- 🔒 **Geplant**: Später sollen alle Backend-Dienste nur noch über VPN erreichbar sein +- ✅ Für die Einrichtung bleibt der öffentliche Zugriff vorerst aktiviert + +--- + +## Client-Verwaltung + +### Client-Liste anzeigen + +```bash +# Auf dem Server +sudo wg show + +# Zeigt alle verbundenen Clients +``` + +### Client-Config-Dateien anzeigen + +```bash +# Auf dem Server +ls -la /etc/wireguard/clients/ +``` + +### Client entfernen + +Um einen Client zu entfernen: + +```bash +# Auf dem Server +sudo nano /etc/wireguard/wg0.conf +# Entferne den [Peer] Block für den Client + +# WireGuard neu starten +sudo wg-quick down wg0 +sudo wg-quick up wg0 + +# Optional: Client-Config löschen +sudo rm /etc/wireguard/clients/clientname.conf +``` + +Oder über Ansible (manuelle Datei-Bearbeitung erforderlich): + +```bash +# 1. Client aus /etc/wireguard/wg0.conf entfernen +# 2. WireGuard-Service neu starten: +cd deployment/ansible +ansible production -m shell -a "sudo wg-quick down wg0 && sudo wg-quick up wg0" +``` + +--- + +## Troubleshooting + +### VPN verbindet nicht + +**1. Firewall prüfen:** +```bash +# Auf dem Server +sudo ufw status | grep 51820 +# Sollte zeigen: 51820/udp ALLOW + +# Falls nicht: +sudo ufw allow 51820/udp comment 'WireGuard VPN' +``` + +**2. WireGuard-Service prüfen:** +```bash +# Auf dem Server +sudo systemctl status wg-quick@wg0 + +# Logs anzeigen +sudo journalctl -u wg-quick@wg0 -f +``` + +**3. Server-Konfiguration prüfen:** +```bash +# Auf dem Server +sudo wg show +sudo cat /etc/wireguard/wg0.conf +``` + +**4. Client-Config prüfen:** +- Endpoint korrekt? (94.16.110.151:51820) +- Server-Public-Key korrekt? +- Client-Private-Key vorhanden? +- AllowedIPs korrekt? + +**5. Netzwerk-Verbindung prüfen:** +```bash +# Vom Client aus +ping 94.16.110.151 +# Sollte erfolgreich sein + +# UDP-Port testen +nc -u -v 94.16.110.151 51820 +``` + +### VPN verbindet, aber kein Traffic + +**1. IP Forwarding prüfen:** +```bash +# Auf dem Server +cat /proc/sys/net/ipv4/ip_forward +# Sollte "1" sein + +# Falls nicht: +echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward +echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf +sudo sysctl -p +``` + +**2. NAT-Rules prüfen:** +```bash +# Auf dem Server +sudo iptables -t nat -L -n | grep MASQUERADE +sudo iptables -L FORWARD -n | grep wg0 +``` + +**3. Routing prüfen:** +```bash +# Auf dem Server +ip route show +# Sollte Routes für wg0 zeigen + +# Vom Client +ip route show +# Sollte Route für 10.8.0.0/24 über VPN zeigen +``` + +### SSH-Zugriff funktioniert nicht mehr + +**WICHTIG**: SSH-Zugriff sollte weiterhin über die normale IP funktionieren! + +```bash +# SSH über normale IP (nicht VPN) +ssh -i ~/.ssh/production deploy@94.16.110.151 + +# Falls nicht funktioniert: +# 1. Prüfe, ob VPN alle Traffic routet (AllowedIPs = 0.0.0.0/0) +# 2. Deaktiviere VPN: sudo wg-quick down development +# 3. Prüfe Firewall: sudo ufw status | grep 22 +``` + +### Performance-Probleme + +**1. MTU anpassen:** +```bash +# In Client-Config hinzufügen: +# [Interface] +# MTU = 1420 +``` + +**2. PersistKeepalive anpassen:** +```bash +# In Client-Config bereits konfiguriert: +# PersistentKeepalive = 25 +``` + +**3. Server-Last prüfen:** +```bash +# Auf dem Server +sudo wg show +# Zeigt Transfer-Statistiken +``` + +--- + +## Best Practices + +### Sicherheit + +1. **Backup der Server-Keys:** + ```bash + # Auf dem Server + sudo tar czf wireguard-backup.tar.gz /etc/wireguard/ + # Sicher speichern (z.B. verschlüsselt) + ``` + +2. **Client-Keys sicher verwalten:** + - Client-Private-Keys niemals teilen + - Config-Dateien sicher speichern + - Nicht genutzte Clients entfernen + +3. **Regelmäßige Updates:** + ```bash + # Auf dem Server + sudo apt update && sudo apt upgrade wireguard wireguard-tools + ``` + +4. **Firewall-Regeln:** + - Nur notwendige Ports öffnen + - Regelmäßig Firewall-Status prüfen + +### Monitoring + +1. **VPN-Verbindungen überwachen:** + ```bash + # Auf dem Server + sudo wg show + ``` + +2. **Logs prüfen:** + ```bash + # Auf dem Server + sudo journalctl -u wg-quick@wg0 -f + ``` + +3. **Transfer-Statistiken:** + ```bash + # Auf dem Server + sudo wg show + # Zeigt received/sent für jeden Client + ``` + +--- + +## Ansible Playbooks + +### Setup WireGuard Server + +**Datei**: `deployment/ansible/playbooks/setup-wireguard.yml` + +**Verwendung:** +```bash +cd deployment/ansible +ansible-playbook -i inventory/production.yml playbooks/setup-wireguard.yml +``` + +**Variablen:** +- `wireguard_port`: Port für WireGuard (Standard: 51820) +- `wireguard_network`: VPN-Netzwerk (Standard: 10.8.0.0/24) +- `wireguard_server_ip`: Server-IP im VPN (Standard: 10.8.0.1) + +### Add WireGuard Client + +**Datei**: `deployment/ansible/playbooks/add-wireguard-client.yml` + +**Verwendung:** +```bash +cd deployment/ansible +ansible-playbook -i inventory/production.yml playbooks/add-wireguard-client.yml \ + -e "client_name=myclient" +``` + +**Variablen:** +- `client_name`: Name des Clients (erforderlich) +- `client_ip`: Spezifische Client-IP (Standard: automatisch berechnet) +- `allowed_ips`: Erlaubte IP-Ranges (Standard: 10.8.0.0/24) + +**Dokumentation**: Siehe `deployment/ansible/playbooks/README-WIREGUARD.md` + +--- + +## Verzeichnisstruktur + +**Auf dem Server:** + +``` +/etc/wireguard/ +├── wg0.conf # Server-Konfiguration +├── wg0_private.key # Server-Private-Key (600) +├── wg0_public.key # Server-Public-Key (644) +└── clients/ # Client-Konfigurationen + ├── development.conf # Development-Client + └── ... +``` + +**Auf dem Client (Linux/macOS):** + +``` +/etc/wireguard/ +└── development.conf # Client-Konfiguration (600) +``` + +--- + +## Support + +Bei Problemen: + +1. **Logs prüfen:** + ```bash + sudo journalctl -u wg-quick@wg0 -f + ``` + +2. **Status prüfen:** + ```bash + sudo wg show + ``` + +3. **Firewall prüfen:** + ```bash + sudo ufw status + ``` + +4. **Connectivity testen:** + ```bash + ping 10.8.0.1 # Vom Client + ``` + +5. **Ansible Playbook erneut ausführen:** + ```bash + cd deployment/ansible + ansible-playbook -i inventory/production.yml playbooks/setup-wireguard.yml + ``` + +--- + +## Referenzen + +- **WireGuard-Website**: https://www.wireguard.com/ +- **WireGuard-Installation**: https://www.wireguard.com/install/ +- **WireGuard-Dokumentation**: https://www.wireguard.com/papers/wireguard.pdf +- **Ansible Playbook-Dokumentation**: `deployment/ansible/playbooks/README-WIREGUARD.md` + +--- + +**Zuletzt aktualisiert**: 2025-10-31 +**Version**: 1.0 \ No newline at end of file diff --git a/test-workflow-trigger.txt b/test-workflow-trigger.txt deleted file mode 100644 index ecb3a207..00000000 --- a/test-workflow-trigger.txt +++ /dev/null @@ -1 +0,0 @@ -// Test file to trigger production-deploy workflow