feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready
This commit is contained in:
643
.deployment-archive-20251030-111806/README.md
Normal file
643
.deployment-archive-20251030-111806/README.md
Normal file
@@ -0,0 +1,643 @@
|
|||||||
|
# Automated Deployment System
|
||||||
|
|
||||||
|
Ansible-basierte Deployment-Automatisierung für das Framework.
|
||||||
|
|
||||||
|
## Überblick
|
||||||
|
|
||||||
|
Dieses System ermöglicht automatisierte Deployments direkt auf dem Produktionsserver, wodurch die problematischen SSH-Transfers von großen Docker Images elimin werden.
|
||||||
|
|
||||||
|
## Vorteile
|
||||||
|
|
||||||
|
- **Kein Image-Transfer**: Build erfolgt direkt auf dem Produktionsserver
|
||||||
|
- **Zuverlässig**: Keine "Broken pipe" SSH-Fehler mehr
|
||||||
|
- **Schnell**: Direkter Build nutzt Server-Ressourcen optimal
|
||||||
|
- **Wiederholbar**: Idempotente Ansible-Playbooks
|
||||||
|
- **Versioniert**: Alle Deployment-Konfigurationen in Git
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
### Primary: Gitea Actions (Automated CI/CD)
|
||||||
|
|
||||||
|
```
|
||||||
|
Lokale Entwicklung → Git Push → Gitea
|
||||||
|
↓
|
||||||
|
Gitea Actions Runner (on Production)
|
||||||
|
↓
|
||||||
|
Build & Test & Deploy
|
||||||
|
↓
|
||||||
|
Docker Swarm Rolling Update
|
||||||
|
↓
|
||||||
|
Health Check & Auto-Rollback
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fallback: Manual Ansible Deployment
|
||||||
|
|
||||||
|
```
|
||||||
|
Lokale Entwicklung → Manual Trigger → Ansible Playbook
|
||||||
|
↓
|
||||||
|
Docker Build (Server)
|
||||||
|
↓
|
||||||
|
Docker Swarm Update
|
||||||
|
↓
|
||||||
|
Health Check
|
||||||
|
```
|
||||||
|
|
||||||
|
## Komponenten
|
||||||
|
|
||||||
|
### 1. Gitea Actions Workflow (Primary)
|
||||||
|
|
||||||
|
**Location**: `.gitea/workflows/deploy.yml`
|
||||||
|
|
||||||
|
**Trigger**: Push to `main` branch
|
||||||
|
|
||||||
|
**Stages**:
|
||||||
|
1. **Checkout**: Repository auf Runner auschecken
|
||||||
|
2. **Build**: Docker Image mit Produktions-Optimierungen bauen
|
||||||
|
3. **Push to Registry**: Image zu lokalem Registry pushen
|
||||||
|
4. **Deploy**: Rolling Update via Docker Swarm
|
||||||
|
5. **Health Check**: Automatische Verfügbarkeitsprüfung (3 Versuche)
|
||||||
|
6. **Auto-Rollback**: Bei Health Check Failure automatischer Rollback
|
||||||
|
|
||||||
|
**Secrets** (in Gitea konfiguriert):
|
||||||
|
- `DOCKER_REGISTRY`: localhost:5000
|
||||||
|
- `STACK_NAME`: framework
|
||||||
|
- `HEALTH_CHECK_URL`: https://michaelschiemer.de/health
|
||||||
|
|
||||||
|
### 2. Gitea Runner Setup (Production Server)
|
||||||
|
|
||||||
|
**Location**: `deployment/ansible/playbooks/setup-gitea-runner.yml`
|
||||||
|
|
||||||
|
**Installation**:
|
||||||
|
```bash
|
||||||
|
cd deployment/ansible
|
||||||
|
ansible-playbook -i inventory/production.yml playbooks/setup-gitea-runner.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Systemd Service für automatischen Start
|
||||||
|
- Docker-in-Docker Support
|
||||||
|
- Isolation via User `gitea-runner`
|
||||||
|
- Logs: `journalctl -u gitea-runner -f`
|
||||||
|
|
||||||
|
### 3. Emergency Deployment Scripts
|
||||||
|
|
||||||
|
**Fallback-Szenarien** wenn Gitea Actions nicht verfügbar:
|
||||||
|
|
||||||
|
#### `scripts/deployment-diagnostics.sh`
|
||||||
|
- Umfassende System-Diagnose
|
||||||
|
- SSH, Docker Swarm, Services, Images, Networks Status
|
||||||
|
- Health Checks und Resource Usage
|
||||||
|
- Quick Mode: `--quick`, Verbose: `--verbose`
|
||||||
|
|
||||||
|
#### `scripts/service-recovery.sh`
|
||||||
|
- Service Status Check
|
||||||
|
- Service Restart
|
||||||
|
- Full Recovery Procedure (5 Steps)
|
||||||
|
- Cache Clearing
|
||||||
|
|
||||||
|
#### `scripts/manual-deploy-fallback.sh`
|
||||||
|
- Manuelles Deployment ohne Gitea Actions
|
||||||
|
- Lokaler Image Build
|
||||||
|
- Push zu Registry
|
||||||
|
- Ansible Deployment
|
||||||
|
- Health Checks
|
||||||
|
|
||||||
|
#### `scripts/emergency-rollback.sh`
|
||||||
|
- Schneller Rollback zu vorheriger Version
|
||||||
|
- Listet verfügbare Image Tags
|
||||||
|
- Direkter Rollback ohne Health Checks
|
||||||
|
- Manuelle Verifikation erforderlich
|
||||||
|
|
||||||
|
### 4. Script Framework (Shared Libraries)
|
||||||
|
|
||||||
|
**Libraries**:
|
||||||
|
- `scripts/lib/common.sh` - Logging, Error Handling, Utilities
|
||||||
|
- `scripts/lib/ansible.sh` - Ansible Integration
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Farbcodierte Logging-Funktionen (Info, Success, Warning, Error, Debug)
|
||||||
|
- Automatische Pre-Deployment Checks
|
||||||
|
- User-Confirmation Prompts
|
||||||
|
- Post-Deployment Health Checks
|
||||||
|
- Performance Metrics (Deployment Duration)
|
||||||
|
- Retry-Logic mit exponential Backoff
|
||||||
|
- Cleanup Handlers mit trap
|
||||||
|
|
||||||
|
### Ansible Konfiguration
|
||||||
|
|
||||||
|
- `ansible/ansible.cfg` - Ansible-Grundkonfiguration
|
||||||
|
- `ansible/inventory/production.yml` - Produktionsserver-Inventar
|
||||||
|
- `ansible/playbooks/deploy.yml` - Haupt-Deployment-Playbook
|
||||||
|
|
||||||
|
### Deployment Workflow
|
||||||
|
|
||||||
|
1. **Code Push**: Code-Änderungen nach Git pushen
|
||||||
|
2. **SSH auf Server**: Auf Produktionsserver verbinden
|
||||||
|
3. **Ansible ausführen**: Deployment-Playbook starten
|
||||||
|
4. **Automatischer Build**: Docker Image wird auf Server gebaut
|
||||||
|
5. **Service Update**: Docker Swarm Services werden aktualisiert
|
||||||
|
6. **Health Check**: Automatische Verfügbarkeitsprüfung
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
### Primary: Automated Deployment via Gitea Actions (Empfohlen)
|
||||||
|
|
||||||
|
Der Standard-Workflow ist vollautomatisch über Git-Push:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Lokale Entwicklung abschließen
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: new feature implementation"
|
||||||
|
|
||||||
|
# 2. Push to main branch triggert automatisches Deployment
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# 3. Gitea Actions führt automatisch aus:
|
||||||
|
# - Docker Image Build (auf Production Server)
|
||||||
|
# - Push zu lokalem Registry (localhost:5000)
|
||||||
|
# - Docker Swarm Rolling Update
|
||||||
|
# - Health Check (3 Versuche)
|
||||||
|
# - Auto-Rollback bei Failure
|
||||||
|
|
||||||
|
# 4. Deployment Status monitoren
|
||||||
|
# Gitea UI: https://git.michaelschiemer.de/<user>/<repo>/actions
|
||||||
|
# Oder via SSH auf Server:
|
||||||
|
ssh -i ~/.ssh/production deploy@94.16.110.151
|
||||||
|
journalctl -u gitea-runner -f
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deployment-Zeit**: ~3-4 Minuten von Push bis Live
|
||||||
|
|
||||||
|
### Deployment Monitoring
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Gitea Actions Logs (via Gitea UI)
|
||||||
|
https://git.michaelschiemer.de/<user>/<repo>/actions
|
||||||
|
|
||||||
|
# Gitea Runner Logs (auf Production Server)
|
||||||
|
ssh -i ~/.ssh/production deploy@94.16.110.151
|
||||||
|
journalctl -u gitea-runner -f
|
||||||
|
|
||||||
|
# Service Status prüfen
|
||||||
|
ssh -i ~/.ssh/production deploy@94.16.110.151
|
||||||
|
docker stack services framework
|
||||||
|
docker service logs framework_web --tail 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### Emergency/Fallback: Diagnostic & Recovery Scripts
|
||||||
|
|
||||||
|
Bei Problemen stehen Emergency Scripts zur Verfügung:
|
||||||
|
|
||||||
|
#### System-Diagnose
|
||||||
|
```bash
|
||||||
|
# Umfassende System-Diagnose
|
||||||
|
./scripts/deployment-diagnostics.sh
|
||||||
|
|
||||||
|
# Quick Check (nur kritische Checks)
|
||||||
|
./scripts/deployment-diagnostics.sh --quick
|
||||||
|
|
||||||
|
# Verbose Mode (mit Logs)
|
||||||
|
./scripts/deployment-diagnostics.sh --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Diagnostics umfasst**:
|
||||||
|
- Local Environment (Git, Docker, Ansible, SSH)
|
||||||
|
- SSH Connectivity zu Production
|
||||||
|
- Docker Swarm Status (Manager/Worker Nodes)
|
||||||
|
- Framework Services Status (Web, Queue-Worker)
|
||||||
|
- Docker Images & Registry
|
||||||
|
- Gitea Runner Service Status
|
||||||
|
- Resource Usage (Disk, Memory, Docker)
|
||||||
|
- Application Health Endpoints
|
||||||
|
|
||||||
|
#### Service Recovery
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Service Status prüfen
|
||||||
|
./scripts/service-recovery.sh status
|
||||||
|
|
||||||
|
# Services neu starten
|
||||||
|
./scripts/service-recovery.sh restart
|
||||||
|
|
||||||
|
# Full Recovery Procedure (5 Steps)
|
||||||
|
./scripts/service-recovery.sh recover
|
||||||
|
|
||||||
|
# Caches löschen
|
||||||
|
./scripts/service-recovery.sh clear-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
**5-Step Recovery Procedure**:
|
||||||
|
1. Check current status
|
||||||
|
2. Verify Docker Swarm health (reinit if needed)
|
||||||
|
3. Verify networks and volumes
|
||||||
|
4. Force restart services
|
||||||
|
5. Run health checks
|
||||||
|
|
||||||
|
#### Manual Deployment Fallback
|
||||||
|
|
||||||
|
Wenn Gitea Actions nicht verfügbar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manual Deployment (aktueller Branch)
|
||||||
|
./scripts/manual-deploy-fallback.sh
|
||||||
|
|
||||||
|
# Manual Deployment (spezifischer Branch)
|
||||||
|
./scripts/manual-deploy-fallback.sh feature/new-deployment
|
||||||
|
|
||||||
|
# Workflow:
|
||||||
|
# 1. Prerequisites Check (Git clean, Docker, Ansible, SSH)
|
||||||
|
# 2. Docker Image Build (lokal)
|
||||||
|
# 3. Push zu Registry
|
||||||
|
# 4. Ansible Deployment
|
||||||
|
# 5. Health Checks
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Emergency Rollback
|
||||||
|
|
||||||
|
Schneller Rollback zu vorheriger Version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interactive Mode - wähle Version aus Liste
|
||||||
|
./scripts/emergency-rollback.sh
|
||||||
|
|
||||||
|
# Liste verfügbare Versionen
|
||||||
|
./scripts/emergency-rollback.sh list
|
||||||
|
|
||||||
|
# Direkt zu spezifischer Version
|
||||||
|
./scripts/emergency-rollback.sh abc1234-1234567890
|
||||||
|
|
||||||
|
# Workflow:
|
||||||
|
# 1. Zeigt aktuelle Version
|
||||||
|
# 2. Zeigt verfügbare Image Tags
|
||||||
|
# 3. Confirmation: Type 'ROLLBACK' to confirm
|
||||||
|
# 4. Ansible Emergency Rollback
|
||||||
|
# 5. Manuelle Verifikation erforderlich
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ Wichtig**: Emergency Rollback macht KEINEN automatischen Health Check - manuelle Verifikation erforderlich!
|
||||||
|
|
||||||
|
### Tertiary Fallback: Direkt mit Ansible
|
||||||
|
|
||||||
|
Als letztes Mittel direkte Ansible-Ausführung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/michael/dev/michaelschiemer/deployment/ansible
|
||||||
|
ansible-playbook -i inventory/production.yml playbooks/deploy.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
### Produktionsserver
|
||||||
|
|
||||||
|
Server-Details in `ansible/inventory/production.yml`:
|
||||||
|
- **Host**: 94.16.110.151
|
||||||
|
- **User**: deploy
|
||||||
|
- **SSH-Key**: ~/.ssh/production
|
||||||
|
|
||||||
|
### Gitea Actions Secrets (Primary Deployment)
|
||||||
|
|
||||||
|
Konfiguriert in Gitea Repository Settings → Actions → Secrets:
|
||||||
|
|
||||||
|
- **DOCKER_REGISTRY**: `localhost:5000` (lokaler Registry auf Production Server)
|
||||||
|
- **STACK_NAME**: `framework` (Docker Swarm Stack Name)
|
||||||
|
- **HEALTH_CHECK_URL**: `https://michaelschiemer.de/health` (Health Check Endpoint)
|
||||||
|
|
||||||
|
**Secrets hinzufügen**:
|
||||||
|
1. Gitea UI → Repository Settings → Actions → Secrets
|
||||||
|
2. Add Secret für jede Variable
|
||||||
|
3. Gitea Runner muss Zugriff auf Registry haben (localhost:5000)
|
||||||
|
|
||||||
|
### Gitea Runner Setup (Production Server)
|
||||||
|
|
||||||
|
**Systemd Service**:
|
||||||
|
```bash
|
||||||
|
# Status prüfen
|
||||||
|
sudo systemctl status gitea-runner
|
||||||
|
|
||||||
|
# Logs verfolgen
|
||||||
|
journalctl -u gitea-runner -f
|
||||||
|
|
||||||
|
# Service starten/stoppen
|
||||||
|
sudo systemctl start gitea-runner
|
||||||
|
sudo systemctl stop gitea-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
**Runner-Konfiguration**:
|
||||||
|
- **Location**: Läuft auf Production Server (94.16.110.151)
|
||||||
|
- **User**: `gitea-runner` (isolierter Service-User)
|
||||||
|
- **Docker Access**: Docker-in-Docker Support aktiviert
|
||||||
|
- **Logs**: `journalctl -u gitea-runner -f`
|
||||||
|
|
||||||
|
**Setup via Ansible**:
|
||||||
|
```bash
|
||||||
|
cd deployment/ansible
|
||||||
|
ansible-playbook -i inventory/production.yml playbooks/setup-gitea-runner.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Registry
|
||||||
|
|
||||||
|
**Primary Registry** (Production Server lokal):
|
||||||
|
- **URL**: `localhost:5000` (für Runner auf Production Server)
|
||||||
|
- **External**: `git.michaelschiemer.de:5000` (für externe Zugriffe)
|
||||||
|
- **Image Name**: `framework`
|
||||||
|
- **Tags**:
|
||||||
|
- `latest` - Aktuelle Version
|
||||||
|
- `{commit-sha}-{timestamp}` - Versionierte Images für Rollbacks
|
||||||
|
|
||||||
|
**Registry Access**:
|
||||||
|
- Runner nutzt `localhost:5000` (lokaler Zugriff)
|
||||||
|
- Manuelle Deployments nutzen `git.michaelschiemer.de:5000` (external)
|
||||||
|
- Authentifizierung via Docker Login (falls erforderlich)
|
||||||
|
|
||||||
|
### Docker Swarm Stack
|
||||||
|
|
||||||
|
**Stack-Konfiguration**: `docker-compose.prod.yml`
|
||||||
|
|
||||||
|
**Services**:
|
||||||
|
- **framework_web**: Web-Service (3 Replicas für High Availability)
|
||||||
|
- **framework_queue-worker**: Queue-Worker (2 Replicas)
|
||||||
|
|
||||||
|
**Rolling Update Config**:
|
||||||
|
```yaml
|
||||||
|
deploy:
|
||||||
|
replicas: 3
|
||||||
|
update_config:
|
||||||
|
parallelism: 1 # Ein Container pro Schritt
|
||||||
|
delay: 10s # 10 Sekunden Pause zwischen Updates
|
||||||
|
order: start-first # Neuer Container startet vor Stoppen des alten
|
||||||
|
rollback_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 5s
|
||||||
|
```
|
||||||
|
|
||||||
|
**Stack Management**:
|
||||||
|
```bash
|
||||||
|
# Stack Status
|
||||||
|
docker stack services framework
|
||||||
|
|
||||||
|
# Service Logs
|
||||||
|
docker service logs framework_web --tail 50
|
||||||
|
|
||||||
|
# Stack Update (manuell)
|
||||||
|
docker stack deploy -c docker-compose.prod.yml framework
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Troubleshooting-Workflow
|
||||||
|
|
||||||
|
Bei Problemen mit dem Deployment-System folge diesem strukturierten Workflow:
|
||||||
|
|
||||||
|
**Level 1: Quick Diagnostics** (Erste Anlaufstelle)
|
||||||
|
```bash
|
||||||
|
# Umfassende System-Diagnose
|
||||||
|
./scripts/deployment-diagnostics.sh
|
||||||
|
|
||||||
|
# Quick Check (nur kritische Checks)
|
||||||
|
./scripts/deployment-diagnostics.sh --quick
|
||||||
|
|
||||||
|
# Verbose Mode (mit detaillierten Logs)
|
||||||
|
./scripts/deployment-diagnostics.sh --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Level 2: Service Recovery** (Bei Service-Ausfällen)
|
||||||
|
```bash
|
||||||
|
# Service Status prüfen
|
||||||
|
./scripts/service-recovery.sh status
|
||||||
|
|
||||||
|
# Services neu starten
|
||||||
|
./scripts/service-recovery.sh restart
|
||||||
|
|
||||||
|
# Full Recovery Procedure (5 automatisierte Steps)
|
||||||
|
./scripts/service-recovery.sh recover
|
||||||
|
|
||||||
|
# Caches löschen (bei Cache-Problemen)
|
||||||
|
./scripts/service-recovery.sh clear-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
**Level 3: Manual Deployment Fallback** (Bei Gitea Actions Problemen)
|
||||||
|
```bash
|
||||||
|
# Manuelles Deployment (aktueller Branch)
|
||||||
|
./scripts/manual-deploy-fallback.sh
|
||||||
|
|
||||||
|
# Manuelles Deployment (spezifischer Branch)
|
||||||
|
./scripts/manual-deploy-fallback.sh feature/new-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
**Level 4: Emergency Rollback** (Bei kritischen Production-Problemen)
|
||||||
|
```bash
|
||||||
|
# Interactive Mode - Version aus Liste wählen
|
||||||
|
./scripts/emergency-rollback.sh
|
||||||
|
|
||||||
|
# Verfügbare Versionen anzeigen
|
||||||
|
./scripts/emergency-rollback.sh list
|
||||||
|
|
||||||
|
# Direkt zu spezifischer Version rollback
|
||||||
|
./scripts/emergency-rollback.sh abc1234-1234567890
|
||||||
|
```
|
||||||
|
|
||||||
|
### Häufige Probleme
|
||||||
|
|
||||||
|
#### Gitea Actions Workflow schlägt fehl
|
||||||
|
|
||||||
|
**Diagnose**:
|
||||||
|
```bash
|
||||||
|
# Gitea Runner Status prüfen (auf Production Server)
|
||||||
|
ssh -i ~/.ssh/production deploy@94.16.110.151
|
||||||
|
journalctl -u gitea-runner -f
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lösungen**:
|
||||||
|
- Runner nicht aktiv: `sudo systemctl start gitea-runner`
|
||||||
|
- Secrets fehlen: Gitea UI → Repository Settings → Actions → Secrets prüfen
|
||||||
|
- Docker Registry nicht erreichbar: `docker login localhost:5000`
|
||||||
|
|
||||||
|
#### Services sind nicht erreichbar
|
||||||
|
|
||||||
|
**Diagnose**:
|
||||||
|
```bash
|
||||||
|
# Quick Health Check
|
||||||
|
./scripts/deployment-diagnostics.sh --quick
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lösungen**:
|
||||||
|
```bash
|
||||||
|
# Services automatisch recovern
|
||||||
|
./scripts/service-recovery.sh recover
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Deployment hängt oder ist langsam
|
||||||
|
|
||||||
|
**Diagnose**:
|
||||||
|
```bash
|
||||||
|
# Umfassende Diagnose mit Resource-Checks
|
||||||
|
./scripts/deployment-diagnostics.sh --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lösungen**:
|
||||||
|
- Disk Space voll: Alte Docker Images aufräumen (`docker system prune -a`)
|
||||||
|
- Memory Issues: Services neu starten (`./scripts/service-recovery.sh restart`)
|
||||||
|
- Netzwerk-Probleme: Docker Swarm Overlay Network prüfen
|
||||||
|
|
||||||
|
#### Health Checks schlagen fehl
|
||||||
|
|
||||||
|
**Diagnose**:
|
||||||
|
```bash
|
||||||
|
# Application Health direkt testen
|
||||||
|
curl -k https://michaelschiemer.de/health
|
||||||
|
curl -k https://michaelschiemer.de/health/database
|
||||||
|
curl -k https://michaelschiemer.de/health/redis
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lösungen**:
|
||||||
|
```bash
|
||||||
|
# Service Logs prüfen
|
||||||
|
ssh -i ~/.ssh/production deploy@94.16.110.151
|
||||||
|
docker service logs framework_web --tail 100
|
||||||
|
|
||||||
|
# Caches löschen falls Health Check Cache-Issues zeigt
|
||||||
|
./scripts/service-recovery.sh clear-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Rollback nach fehlgeschlagenem Deployment
|
||||||
|
|
||||||
|
**Schneller Emergency Rollback**:
|
||||||
|
```bash
|
||||||
|
# 1. Verfügbare Versionen anzeigen
|
||||||
|
./scripts/emergency-rollback.sh list
|
||||||
|
|
||||||
|
# 2. Zu letzter funktionierender Version rollback
|
||||||
|
./scripts/emergency-rollback.sh <previous-tag>
|
||||||
|
|
||||||
|
# 3. Manuelle Verifikation
|
||||||
|
curl -k https://michaelschiemer.de/health
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ Wichtig**: Emergency Rollback macht KEINEN automatischen Health Check - manuelle Verifikation erforderlich!
|
||||||
|
|
||||||
|
## Nächste Schritte
|
||||||
|
|
||||||
|
### Git-Integration ✅ Completed
|
||||||
|
|
||||||
|
Gitea Actions CI/CD ist vollständig implementiert und operational:
|
||||||
|
- ✅ Automatic Trigger bei Push zu main-Branch
|
||||||
|
- ✅ Gitea Webhook Integration
|
||||||
|
- ✅ Automated Build, Test & Deploy Pipeline
|
||||||
|
- ✅ Health Checks mit Auto-Rollback
|
||||||
|
|
||||||
|
**Aktuelle Features**:
|
||||||
|
- Zero-downtime Rolling Updates
|
||||||
|
- Automatic Rollback bei Deployment-Failures
|
||||||
|
- Versioned Image Tagging für manuelle Rollbacks
|
||||||
|
- Comprehensive Emergency Recovery Scripts
|
||||||
|
|
||||||
|
### Monitoring (Geplante Verbesserungen)
|
||||||
|
|
||||||
|
**Short-Term** (1-2 Monate):
|
||||||
|
- Deployment-Benachrichtigungen via Email/Slack
|
||||||
|
- Prometheus/Grafana Integration für Metrics
|
||||||
|
- Application Performance Monitoring (APM)
|
||||||
|
- Automated Health Check Dashboards
|
||||||
|
|
||||||
|
**Mid-Term** (3-6 Monate):
|
||||||
|
- Log Aggregation mit ELK/Loki Stack
|
||||||
|
- Distributed Tracing für Microservices
|
||||||
|
- Alerting Rules für kritische Metriken
|
||||||
|
- Capacity Planning & Resource Forecasting
|
||||||
|
|
||||||
|
**Long-Term** (6-12 Monate):
|
||||||
|
- Cost Optimization Dashboards
|
||||||
|
- Predictive Failure Detection
|
||||||
|
- Automated Performance Tuning
|
||||||
|
- Multi-Region Deployment Support
|
||||||
|
|
||||||
|
## Sicherheit
|
||||||
|
|
||||||
|
### Production Security Measures
|
||||||
|
|
||||||
|
- **SSH-Key-basierte Authentifizierung**: Zugriff nur mit autorisiertem Private Key (~/.ssh/production)
|
||||||
|
- **Keine Passwörter in Konfiguration**: Alle Credentials via Gitea Actions Secrets oder Docker Secrets
|
||||||
|
- **Docker Secrets für sensitive Daten**: Database-Credentials, API-Keys, Encryption-Keys
|
||||||
|
- **Gitea Runner Isolation**: Dedicated Service-User `gitea-runner` mit minimalen Permissions
|
||||||
|
- **Registry Access Control**: Localhost-only Registry für zusätzliche Security
|
||||||
|
- **HTTPS-only Communication**: Alle Deployments über verschlüsselte Verbindungen
|
||||||
|
|
||||||
|
### Deployment Authorization
|
||||||
|
|
||||||
|
- **Gitea Repository Access**: Push-Rechte erforderlich für automatisches Deployment
|
||||||
|
- **Emergency Script Access**: SSH-Key + authorized_keys auf Production Server
|
||||||
|
- **Manual Rollback**: Manuelle Intervention via authorized SSH-Key
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
### Deployment Performance Metrics
|
||||||
|
|
||||||
|
- **Build-Zeit**: ~2-3 Minuten (je nach Docker Layer Caching)
|
||||||
|
- **Registry Push**: ~30-60 Sekunden (Image Size: ~500MB)
|
||||||
|
- **Deployment-Zeit**: ~60-90 Sekunden (Rolling Update mit 3 Replicas)
|
||||||
|
- **Health Check Duration**: ~10-15 Sekunden (3 Retry-Attempts)
|
||||||
|
- **Gesamt**: ~3-4 Minuten von Push bis Live (bei erfolgreichem Deployment)
|
||||||
|
|
||||||
|
### Rollback Performance
|
||||||
|
|
||||||
|
- **Automated Rollback**: ~30 Sekunden (bei Health Check Failure)
|
||||||
|
- **Manual Emergency Rollback**: ~60 Sekunden (via emergency-rollback.sh)
|
||||||
|
- **Service Recovery**: ~90 Sekunden (via service-recovery.sh recover)
|
||||||
|
|
||||||
|
### Optimizations in Place
|
||||||
|
|
||||||
|
- **Docker Layer Caching**: Wiederverwendung unveränderter Layer
|
||||||
|
- **Multi-Stage Builds**: Kleinere Production Images
|
||||||
|
- **Parallel Replica Updates**: Minimale Downtime durch start-first Strategy
|
||||||
|
- **Local Registry**: Kein externes Network Bottleneck
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
### Erste Anlaufstellen bei Problemen
|
||||||
|
|
||||||
|
**1. Emergency Scripts nutzen** (Empfohlen):
|
||||||
|
```bash
|
||||||
|
# Quick Diagnostics - System-Gesundheit prüfen
|
||||||
|
./scripts/deployment-diagnostics.sh --quick
|
||||||
|
|
||||||
|
# Service Recovery - Automatische Wiederherstellung
|
||||||
|
./scripts/service-recovery.sh recover
|
||||||
|
|
||||||
|
# Manual Deployment - Fallback wenn Gitea Actions ausfällt
|
||||||
|
./scripts/manual-deploy-fallback.sh
|
||||||
|
|
||||||
|
# Emergency Rollback - Schneller Rollback zu vorheriger Version
|
||||||
|
./scripts/emergency-rollback.sh list
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Gitea Actions Logs prüfen**:
|
||||||
|
- Gitea UI → Repository → Actions Tab
|
||||||
|
- Oder via SSH: `journalctl -u gitea-runner -f`
|
||||||
|
|
||||||
|
**3. Service Logs direkt prüfen**:
|
||||||
|
```bash
|
||||||
|
ssh -i ~/.ssh/production deploy@94.16.110.151
|
||||||
|
docker service logs framework_web --tail 100
|
||||||
|
docker service logs framework_queue-worker --tail 100
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Docker Stack Status**:
|
||||||
|
```bash
|
||||||
|
ssh -i ~/.ssh/production deploy@94.16.110.151
|
||||||
|
docker stack services framework
|
||||||
|
docker stack ps framework --no-trunc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Eskalationspfad
|
||||||
|
|
||||||
|
1. **Level 1**: Automatische Diagnostics → `./scripts/deployment-diagnostics.sh`
|
||||||
|
2. **Level 2**: Service Recovery → `./scripts/service-recovery.sh recover`
|
||||||
|
3. **Level 3**: Manual Deployment → `./scripts/manual-deploy-fallback.sh`
|
||||||
|
4. **Level 4**: Emergency Rollback → `./scripts/emergency-rollback.sh`
|
||||||
|
5. **Level 5**: Direct Ansible → `cd deployment/ansible && ansible-playbook -i inventory/production.yml playbooks/deploy.yml`
|
||||||
|
|
||||||
|
### Kontakte
|
||||||
|
|
||||||
|
- **Production Server**: deploy@94.16.110.151 (SSH-Key erforderlich)
|
||||||
|
- **Documentation**: `/home/michael/dev/michaelschiemer/deployment/README.md`
|
||||||
|
- **Emergency Scripts**: `/home/michael/dev/michaelschiemer/deployment/scripts/`
|
||||||
10
.deployment-archive-20251030-111806/ansible/ansible.cfg
Normal file
10
.deployment-archive-20251030-111806/ansible/ansible.cfg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[defaults]
|
||||||
|
inventory = inventory
|
||||||
|
host_key_checking = False
|
||||||
|
retry_files_enabled = False
|
||||||
|
roles_path = roles
|
||||||
|
interpreter_python = auto_silent
|
||||||
|
|
||||||
|
[ssh_connection]
|
||||||
|
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30 -o ServerAliveCountMax=3
|
||||||
|
pipelining = True
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
all:
|
||||||
|
vars:
|
||||||
|
ansible_python_interpreter: /usr/bin/python3
|
||||||
|
ansible_user: deploy
|
||||||
|
ansible_ssh_private_key_file: ~/.ssh/production
|
||||||
|
|
||||||
|
production:
|
||||||
|
hosts:
|
||||||
|
production_server:
|
||||||
|
ansible_host: 94.16.110.151
|
||||||
|
docker_registry: localhost:5000
|
||||||
|
docker_image_name: framework
|
||||||
|
docker_image_tag: latest
|
||||||
|
docker_swarm_stack_name: framework
|
||||||
|
docker_services:
|
||||||
|
- framework_web
|
||||||
|
- framework_queue-worker
|
||||||
|
git_repo_path: /home/deploy/framework-app
|
||||||
|
build_dockerfile: Dockerfile.production
|
||||||
|
build_target: production
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
---
|
||||||
|
# Git-Based Production Deployment Playbook
|
||||||
|
# Uses Git to sync files, builds image, and updates services
|
||||||
|
# Usage: ansible-playbook -i inventory/production.yml playbooks/deploy-complete-git.yml
|
||||||
|
|
||||||
|
- name: Git-Based Production Deployment
|
||||||
|
hosts: production_server
|
||||||
|
become: no
|
||||||
|
vars:
|
||||||
|
# Calculate project root: playbook is in deployment/ansible/playbooks/, go up 3 levels
|
||||||
|
local_project_path: "{{ playbook_dir }}/../../.."
|
||||||
|
remote_project_path: /home/deploy/framework-app
|
||||||
|
docker_registry: localhost:5000
|
||||||
|
docker_image_name: framework
|
||||||
|
docker_image_tag: latest
|
||||||
|
docker_stack_name: framework
|
||||||
|
build_timestamp: "{{ ansible_date_time.epoch }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Display deployment information
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "🚀 Starting Git-Based Deployment"
|
||||||
|
- "Local Path: {{ local_project_path }}"
|
||||||
|
- "Remote Path: {{ remote_project_path }}"
|
||||||
|
- "Image: {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }}"
|
||||||
|
- "Timestamp: {{ build_timestamp }}"
|
||||||
|
|
||||||
|
- name: Create remote project directory
|
||||||
|
file:
|
||||||
|
path: "{{ remote_project_path }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Check if Git repository exists on production
|
||||||
|
stat:
|
||||||
|
path: "{{ remote_project_path }}/.git"
|
||||||
|
register: git_repo
|
||||||
|
|
||||||
|
- name: Initialize Git repository if not exists
|
||||||
|
shell: |
|
||||||
|
cd {{ remote_project_path }}
|
||||||
|
git init
|
||||||
|
git config user.email 'deploy@michaelschiemer.de'
|
||||||
|
git config user.name 'Deploy User'
|
||||||
|
when: not git_repo.stat.exists
|
||||||
|
|
||||||
|
- name: Create tarball of current code (excluding unnecessary files)
|
||||||
|
delegate_to: localhost
|
||||||
|
shell: |
|
||||||
|
cd {{ local_project_path }}
|
||||||
|
tar czf /tmp/framework-deploy-{{ build_timestamp }}.tar.gz \
|
||||||
|
--exclude='.git' \
|
||||||
|
--exclude='node_modules' \
|
||||||
|
--exclude='vendor' \
|
||||||
|
--exclude='storage/logs/*' \
|
||||||
|
--exclude='storage/cache/*' \
|
||||||
|
--exclude='.env' \
|
||||||
|
--exclude='.env.*' \
|
||||||
|
--exclude='tests' \
|
||||||
|
--exclude='.deployment-backup' \
|
||||||
|
--exclude='deployment' \
|
||||||
|
.
|
||||||
|
register: tarball_creation
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Transfer tarball to production
|
||||||
|
copy:
|
||||||
|
src: "/tmp/framework-deploy-{{ build_timestamp }}.tar.gz"
|
||||||
|
dest: "/tmp/framework-deploy-{{ build_timestamp }}.tar.gz"
|
||||||
|
register: tarball_transfer
|
||||||
|
|
||||||
|
- name: Extract tarball to production (preserving Git)
|
||||||
|
shell: |
|
||||||
|
cd {{ remote_project_path }}
|
||||||
|
tar xzf /tmp/framework-deploy-{{ build_timestamp }}.tar.gz
|
||||||
|
rm -f /tmp/framework-deploy-{{ build_timestamp }}.tar.gz
|
||||||
|
register: extraction_result
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Commit changes to Git repository
|
||||||
|
shell: |
|
||||||
|
cd {{ remote_project_path }}
|
||||||
|
git add -A
|
||||||
|
git commit -m "Deployment {{ build_timestamp }}" || echo "No changes to commit"
|
||||||
|
git log --oneline -5
|
||||||
|
register: git_commit
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Display Git status
|
||||||
|
debug:
|
||||||
|
msg: "{{ git_commit.stdout_lines }}"
|
||||||
|
|
||||||
|
- name: Clean up local tarball
|
||||||
|
delegate_to: localhost
|
||||||
|
file:
|
||||||
|
path: "/tmp/framework-deploy-{{ build_timestamp }}.tar.gz"
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: Build Docker image on production server
|
||||||
|
shell: |
|
||||||
|
cd {{ remote_project_path }}
|
||||||
|
docker build \
|
||||||
|
-f docker/php/Dockerfile \
|
||||||
|
--target production \
|
||||||
|
-t {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }} \
|
||||||
|
-t {{ docker_registry }}/{{ docker_image_name }}:{{ build_timestamp }} \
|
||||||
|
--no-cache \
|
||||||
|
--progress=plain \
|
||||||
|
.
|
||||||
|
register: build_result
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Display build output (last 20 lines)
|
||||||
|
debug:
|
||||||
|
msg: "{{ build_result.stdout_lines[-20:] }}"
|
||||||
|
|
||||||
|
- name: Update web service with rolling update
|
||||||
|
shell: |
|
||||||
|
docker service update \
|
||||||
|
--image {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }} \
|
||||||
|
--force \
|
||||||
|
--update-parallelism 1 \
|
||||||
|
--update-delay 10s \
|
||||||
|
{{ docker_stack_name }}_web
|
||||||
|
register: web_update
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Update queue-worker service
|
||||||
|
shell: |
|
||||||
|
docker service update \
|
||||||
|
--image {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }} \
|
||||||
|
--force \
|
||||||
|
{{ docker_stack_name }}_queue-worker
|
||||||
|
register: worker_update
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Wait for services to stabilize (30 seconds)
|
||||||
|
pause:
|
||||||
|
seconds: 30
|
||||||
|
prompt: "Waiting for services to stabilize..."
|
||||||
|
|
||||||
|
- name: Check service status
|
||||||
|
shell: docker stack services {{ docker_stack_name }} --format "table {{`{{.Name}}\t{{.Replicas}}\t{{.Image}}`}}"
|
||||||
|
register: service_status
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Check website availability
|
||||||
|
shell: curl -k -s -o /dev/null -w '%{http_code}' https://michaelschiemer.de/
|
||||||
|
register: website_check
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Get recent web service logs
|
||||||
|
shell: docker service logs {{ docker_stack_name }}_web --tail 10 --no-trunc 2>&1 | tail -20
|
||||||
|
register: web_logs
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Display deployment summary
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "✅ Git-Based Deployment Completed"
|
||||||
|
- ""
|
||||||
|
- "Build Timestamp: {{ build_timestamp }}"
|
||||||
|
- "Image: {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }}"
|
||||||
|
- ""
|
||||||
|
- "Git Commit Info:"
|
||||||
|
- "{{ git_commit.stdout_lines }}"
|
||||||
|
- ""
|
||||||
|
- "Service Status:"
|
||||||
|
- "{{ service_status.stdout_lines }}"
|
||||||
|
- ""
|
||||||
|
- "Website HTTP Status: {{ website_check.stdout }}"
|
||||||
|
- ""
|
||||||
|
- "Recent Logs:"
|
||||||
|
- "{{ web_logs.stdout_lines }}"
|
||||||
|
- ""
|
||||||
|
- "🌐 Website: https://michaelschiemer.de"
|
||||||
|
- "📊 Portainer: https://michaelschiemer.de:9000"
|
||||||
|
- "📈 Grafana: https://michaelschiemer.de:3000"
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
---
|
||||||
|
# Complete Production Deployment Playbook
|
||||||
|
# Syncs files, builds image, and updates services
|
||||||
|
# Usage: ansible-playbook -i inventory/production.yml playbooks/deploy-complete.yml
|
||||||
|
|
||||||
|
- name: Complete Production Deployment
|
||||||
|
hosts: production_server
|
||||||
|
become: no
|
||||||
|
vars:
|
||||||
|
# Calculate project root: playbook is in deployment/ansible/playbooks/, go up 3 levels
|
||||||
|
local_project_path: "{{ playbook_dir }}/../../.."
|
||||||
|
remote_project_path: /home/deploy/framework-app
|
||||||
|
docker_registry: localhost:5000
|
||||||
|
docker_image_name: framework
|
||||||
|
docker_image_tag: latest
|
||||||
|
docker_stack_name: framework
|
||||||
|
build_timestamp: "{{ ansible_date_time.epoch }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Display deployment information
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "🚀 Starting Complete Deployment"
|
||||||
|
- "Local Path: {{ local_project_path }}"
|
||||||
|
- "Remote Path: {{ remote_project_path }}"
|
||||||
|
- "Image: {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }}"
|
||||||
|
- "Timestamp: {{ build_timestamp }}"
|
||||||
|
|
||||||
|
- name: Create remote project directory
|
||||||
|
file:
|
||||||
|
path: "{{ remote_project_path }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Sync project files to production server
|
||||||
|
synchronize:
|
||||||
|
src: "{{ local_project_path }}/"
|
||||||
|
dest: "{{ remote_project_path }}/"
|
||||||
|
delete: no
|
||||||
|
rsync_opts:
|
||||||
|
- "--exclude=.git"
|
||||||
|
- "--exclude=.gitignore"
|
||||||
|
- "--exclude=node_modules"
|
||||||
|
- "--exclude=vendor"
|
||||||
|
- "--exclude=storage/logs/*"
|
||||||
|
- "--exclude=storage/cache/*"
|
||||||
|
- "--exclude=.env"
|
||||||
|
- "--exclude=.env.*"
|
||||||
|
- "--exclude=tests"
|
||||||
|
- "--exclude=.deployment-backup"
|
||||||
|
- "--exclude=deployment"
|
||||||
|
register: sync_result
|
||||||
|
|
||||||
|
- name: Display sync results
|
||||||
|
debug:
|
||||||
|
msg: "Files synced: {{ sync_result.changed }}"
|
||||||
|
|
||||||
|
- name: Build Docker image on production server
|
||||||
|
shell: |
|
||||||
|
cd {{ remote_project_path }}
|
||||||
|
docker build \
|
||||||
|
-f Dockerfile.production \
|
||||||
|
-t {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }} \
|
||||||
|
-t {{ docker_registry }}/{{ docker_image_name }}:{{ build_timestamp }} \
|
||||||
|
--no-cache \
|
||||||
|
--progress=plain \
|
||||||
|
.
|
||||||
|
register: build_result
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Display build output (last 20 lines)
|
||||||
|
debug:
|
||||||
|
msg: "{{ build_result.stdout_lines[-20:] }}"
|
||||||
|
|
||||||
|
- name: Update web service with rolling update
|
||||||
|
shell: |
|
||||||
|
docker service update \
|
||||||
|
--image {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }} \
|
||||||
|
--force \
|
||||||
|
--update-parallelism 1 \
|
||||||
|
--update-delay 10s \
|
||||||
|
{{ docker_stack_name }}_web
|
||||||
|
register: web_update
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Update queue-worker service
|
||||||
|
shell: |
|
||||||
|
docker service update \
|
||||||
|
--image {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }} \
|
||||||
|
--force \
|
||||||
|
{{ docker_stack_name }}_queue-worker
|
||||||
|
register: worker_update
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Wait for services to stabilize (30 seconds)
|
||||||
|
pause:
|
||||||
|
seconds: 30
|
||||||
|
prompt: "Waiting for services to stabilize..."
|
||||||
|
|
||||||
|
- name: Check service status
|
||||||
|
shell: docker stack services {{ docker_stack_name }} --format "table {{`{{.Name}}\t{{.Replicas}}\t{{.Image}}`}}"
|
||||||
|
register: service_status
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Check website availability
|
||||||
|
shell: curl -k -s -o /dev/null -w '%{http_code}' https://michaelschiemer.de/
|
||||||
|
register: website_check
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Get recent web service logs
|
||||||
|
shell: docker service logs {{ docker_stack_name }}_web --tail 10 --no-trunc 2>&1 | tail -20
|
||||||
|
register: web_logs
|
||||||
|
changed_when: false
|
||||||
|
failed_when: false
|
||||||
|
|
||||||
|
- name: Display deployment summary
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "✅ Deployment Completed"
|
||||||
|
- ""
|
||||||
|
- "Build Timestamp: {{ build_timestamp }}"
|
||||||
|
- "Image: {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }}"
|
||||||
|
- ""
|
||||||
|
- "Service Status:"
|
||||||
|
- "{{ service_status.stdout_lines }}"
|
||||||
|
- ""
|
||||||
|
- "Website HTTP Status: {{ website_check.stdout }}"
|
||||||
|
- ""
|
||||||
|
- "Recent Logs:"
|
||||||
|
- "{{ web_logs.stdout_lines }}"
|
||||||
|
- ""
|
||||||
|
- "🌐 Website: https://michaelschiemer.de"
|
||||||
|
- "📊 Portainer: https://michaelschiemer.de:9000"
|
||||||
|
- "📈 Grafana: https://michaelschiemer.de:3000"
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
# Ansible Playbook: Update Production Deployment
|
||||||
|
# Purpose: Pull new Docker image and update services with zero-downtime
|
||||||
|
# Usage: Called by Gitea Actions or manual deployment
|
||||||
|
|
||||||
|
- name: Update Production Services with New Image
|
||||||
|
hosts: production_server
|
||||||
|
become: no
|
||||||
|
vars:
|
||||||
|
image_tag: "{{ image_tag | default('latest') }}"
|
||||||
|
git_commit_sha: "{{ git_commit_sha | default('unknown') }}"
|
||||||
|
deployment_timestamp: "{{ deployment_timestamp | default(ansible_date_time.iso8601) }}"
|
||||||
|
registry_url: "git.michaelschiemer.de:5000"
|
||||||
|
image_name: "framework"
|
||||||
|
stack_name: "framework"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Log deployment start
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
🚀 Starting deployment
|
||||||
|
Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}
|
||||||
|
Commit: {{ git_commit_sha }}
|
||||||
|
Time: {{ deployment_timestamp }}
|
||||||
|
|
||||||
|
- name: Pull new Docker image
|
||||||
|
docker_image:
|
||||||
|
name: "{{ registry_url }}/{{ image_name }}"
|
||||||
|
tag: "{{ image_tag }}"
|
||||||
|
source: pull
|
||||||
|
force_source: yes
|
||||||
|
register: image_pull
|
||||||
|
retries: 3
|
||||||
|
delay: 5
|
||||||
|
until: image_pull is succeeded
|
||||||
|
|
||||||
|
- name: Tag image as latest locally
|
||||||
|
docker_image:
|
||||||
|
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||||
|
repository: "{{ registry_url }}/{{ image_name }}"
|
||||||
|
tag: latest
|
||||||
|
source: local
|
||||||
|
|
||||||
|
- name: Update web service with rolling update
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ stack_name }}_web"
|
||||||
|
image: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||||
|
force_update: yes
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 10s
|
||||||
|
failure_action: rollback
|
||||||
|
monitor: 30s
|
||||||
|
max_failure_ratio: 0.3
|
||||||
|
rollback_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 5s
|
||||||
|
state: present
|
||||||
|
register: web_update
|
||||||
|
|
||||||
|
- name: Update queue-worker service
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ stack_name }}_queue-worker"
|
||||||
|
image: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||||
|
force_update: yes
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 10s
|
||||||
|
failure_action: rollback
|
||||||
|
state: present
|
||||||
|
register: worker_update
|
||||||
|
|
||||||
|
- name: Wait for services to stabilize
|
||||||
|
pause:
|
||||||
|
seconds: 20
|
||||||
|
|
||||||
|
- name: Verify service status
|
||||||
|
shell: |
|
||||||
|
docker service ps {{ stack_name }}_web --filter "desired-state=running" --format "{{`{{.CurrentState}}`}}" | head -1
|
||||||
|
register: service_state
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Check if deployment succeeded
|
||||||
|
fail:
|
||||||
|
msg: "Service deployment failed: {{ service_state.stdout }}"
|
||||||
|
when: "'Running' not in service_state.stdout"
|
||||||
|
|
||||||
|
- name: Get running replicas count
|
||||||
|
shell: |
|
||||||
|
docker service ls --filter "name={{ stack_name }}_web" --format "{{`{{.Replicas}}`}}"
|
||||||
|
register: replicas
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Record deployment in history
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
Deployment: {{ deployment_timestamp }}
|
||||||
|
Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}
|
||||||
|
Commit: {{ git_commit_sha }}
|
||||||
|
Status: SUCCESS
|
||||||
|
Replicas: {{ replicas.stdout }}
|
||||||
|
dest: "/home/deploy/deployments/{{ image_tag }}.log"
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Display deployment summary
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
✅ Deployment completed successfully
|
||||||
|
|
||||||
|
Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}
|
||||||
|
Commit: {{ git_commit_sha }}
|
||||||
|
Web Service: {{ web_update.changed | ternary('UPDATED', 'NO CHANGE') }}
|
||||||
|
Worker Service: {{ worker_update.changed | ternary('UPDATED', 'NO CHANGE') }}
|
||||||
|
Replicas: {{ replicas.stdout }}
|
||||||
|
Time: {{ deployment_timestamp }}
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: Cleanup old images
|
||||||
|
shell: docker image prune -af --filter "until=72h"
|
||||||
|
changed_when: false
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
- name: Deploy Framework Application to Production
|
||||||
|
hosts: production_server
|
||||||
|
become: no
|
||||||
|
vars:
|
||||||
|
git_repo_url: "{{ lookup('env', 'GIT_REPO_URL') | default('') }}"
|
||||||
|
build_timestamp: "{{ ansible_date_time.epoch }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Ensure git repo path exists
|
||||||
|
file:
|
||||||
|
path: "{{ git_repo_path }}"
|
||||||
|
state: directory
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
|
- name: Pull latest code from git
|
||||||
|
git:
|
||||||
|
repo: "{{ git_repo_url }}"
|
||||||
|
dest: "{{ git_repo_path }}"
|
||||||
|
version: main
|
||||||
|
force: yes
|
||||||
|
when: git_repo_url != ''
|
||||||
|
register: git_pull_result
|
||||||
|
|
||||||
|
- name: Build Docker image on production server
|
||||||
|
docker_image:
|
||||||
|
name: "{{ docker_registry }}/{{ docker_image_name }}"
|
||||||
|
tag: "{{ docker_image_tag }}"
|
||||||
|
build:
|
||||||
|
path: "{{ git_repo_path }}"
|
||||||
|
dockerfile: "{{ build_dockerfile }}"
|
||||||
|
args:
|
||||||
|
--target: "{{ build_target }}"
|
||||||
|
source: build
|
||||||
|
force_source: yes
|
||||||
|
push: no
|
||||||
|
register: build_result
|
||||||
|
|
||||||
|
- name: Tag image with timestamp for rollback capability
|
||||||
|
docker_image:
|
||||||
|
name: "{{ docker_registry }}/{{ docker_image_name }}"
|
||||||
|
repository: "{{ docker_registry }}/{{ docker_image_name }}"
|
||||||
|
tag: "{{ build_timestamp }}"
|
||||||
|
source: local
|
||||||
|
|
||||||
|
- name: Update Docker Swarm service - web
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ docker_swarm_stack_name }}_web"
|
||||||
|
image: "{{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }}"
|
||||||
|
force_update: yes
|
||||||
|
state: present
|
||||||
|
register: web_update_result
|
||||||
|
|
||||||
|
- name: Update Docker Swarm service - queue-worker
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ docker_swarm_stack_name }}_queue-worker"
|
||||||
|
image: "{{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }}"
|
||||||
|
force_update: yes
|
||||||
|
state: present
|
||||||
|
register: worker_update_result
|
||||||
|
|
||||||
|
- name: Wait for services to stabilize
|
||||||
|
pause:
|
||||||
|
seconds: 60
|
||||||
|
|
||||||
|
- name: Check service status
|
||||||
|
shell: docker stack services {{ docker_swarm_stack_name }} | grep -E "NAME|{{ docker_swarm_stack_name }}"
|
||||||
|
register: service_status
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display deployment results
|
||||||
|
debug:
|
||||||
|
msg:
|
||||||
|
- "Deployment completed successfully"
|
||||||
|
- "Build timestamp: {{ build_timestamp }}"
|
||||||
|
- "Image: {{ docker_registry }}/{{ docker_image_name }}:{{ docker_image_tag }}"
|
||||||
|
- "Services status: {{ service_status.stdout_lines }}"
|
||||||
|
|
||||||
|
- name: Test website availability
|
||||||
|
uri:
|
||||||
|
url: "https://michaelschiemer.de/"
|
||||||
|
validate_certs: no
|
||||||
|
status_code: [200, 302]
|
||||||
|
timeout: 10
|
||||||
|
register: website_health
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Display website health check
|
||||||
|
debug:
|
||||||
|
msg: "Website responded with status: {{ website_health.status | default('FAILED') }}"
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
# Ansible Playbook: Emergency Rollback
|
||||||
|
# Purpose: Fast rollback without health checks for emergency situations
|
||||||
|
# Usage: ansible-playbook -i inventory/production.yml playbooks/emergency-rollback.yml -e "rollback_tag=<tag>"
|
||||||
|
|
||||||
|
- name: Emergency Rollback (Fast Mode)
|
||||||
|
hosts: production_server
|
||||||
|
become: no
|
||||||
|
vars:
|
||||||
|
registry_url: "git.michaelschiemer.de:5000"
|
||||||
|
image_name: "framework"
|
||||||
|
stack_name: "framework"
|
||||||
|
rollback_tag: "{{ rollback_tag | default('latest') }}"
|
||||||
|
skip_health_check: true
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Emergency rollback warning
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
🚨 EMERGENCY ROLLBACK IN PROGRESS 🚨
|
||||||
|
|
||||||
|
This will immediately revert to: {{ rollback_tag }}
|
||||||
|
Health checks will be SKIPPED for speed.
|
||||||
|
|
||||||
|
Press Ctrl+C now if you want to abort.
|
||||||
|
|
||||||
|
- name: Record rollback initiation
|
||||||
|
shell: |
|
||||||
|
echo "[$(date)] Emergency rollback initiated to {{ rollback_tag }}" >> /home/deploy/deployments/emergency-rollback.log
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Get current running image tag
|
||||||
|
shell: |
|
||||||
|
docker service inspect {{ stack_name }}_web --format '{{`{{.Spec.TaskTemplate.ContainerSpec.Image}}`}}'
|
||||||
|
register: current_image
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display current vs target
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
Current: {{ current_image.stdout }}
|
||||||
|
Target: {{ registry_url }}/{{ image_name }}:{{ rollback_tag }}
|
||||||
|
|
||||||
|
- name: Pull rollback image (skip verification)
|
||||||
|
docker_image:
|
||||||
|
name: "{{ registry_url }}/{{ image_name }}"
|
||||||
|
tag: "{{ rollback_tag }}"
|
||||||
|
source: pull
|
||||||
|
register: rollback_image
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Force rollback even if image pull failed
|
||||||
|
debug:
|
||||||
|
msg: "⚠️ Image pull failed, attempting rollback with cached image"
|
||||||
|
when: rollback_image is failed
|
||||||
|
|
||||||
|
- name: Immediate rollback - web service
|
||||||
|
shell: |
|
||||||
|
docker service update \
|
||||||
|
--image {{ registry_url }}/{{ image_name }}:{{ rollback_tag }} \
|
||||||
|
--force \
|
||||||
|
--update-parallelism 999 \
|
||||||
|
--update-delay 0s \
|
||||||
|
{{ stack_name }}_web
|
||||||
|
register: web_rollback
|
||||||
|
|
||||||
|
- name: Immediate rollback - queue-worker service
|
||||||
|
shell: |
|
||||||
|
docker service update \
|
||||||
|
--image {{ registry_url }}/{{ image_name }}:{{ rollback_tag }} \
|
||||||
|
--force \
|
||||||
|
--update-parallelism 999 \
|
||||||
|
--update-delay 0s \
|
||||||
|
{{ stack_name }}_queue-worker
|
||||||
|
register: worker_rollback
|
||||||
|
|
||||||
|
- name: Wait for rollback to propagate (minimal wait)
|
||||||
|
pause:
|
||||||
|
seconds: 15
|
||||||
|
|
||||||
|
- name: Quick service status check
|
||||||
|
shell: |
|
||||||
|
docker service ps {{ stack_name }}_web --filter "desired-state=running" --format "{{`{{.CurrentState}}`}}" | head -1
|
||||||
|
register: rollback_state
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display rollback status
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
🚨 Emergency rollback completed (fast mode)
|
||||||
|
|
||||||
|
Web Service: {{ web_rollback.changed | ternary('ROLLED BACK', 'NO CHANGE') }}
|
||||||
|
Worker Service: {{ worker_rollback.changed | ternary('ROLLED BACK', 'NO CHANGE') }}
|
||||||
|
Service State: {{ rollback_state.stdout }}
|
||||||
|
|
||||||
|
⚠️ MANUAL VERIFICATION REQUIRED:
|
||||||
|
1. Check application: https://michaelschiemer.de
|
||||||
|
2. Check service logs: docker service logs {{ stack_name }}_web
|
||||||
|
3. Verify database connectivity
|
||||||
|
4. Run full health check: ansible-playbook playbooks/health-check.yml
|
||||||
|
|
||||||
|
- name: Record rollback completion
|
||||||
|
shell: |
|
||||||
|
echo "[$(date)] Emergency rollback completed: {{ rollback_tag }}, Status: {{ rollback_state.stdout }}" >> /home/deploy/deployments/emergency-rollback.log
|
||||||
|
|
||||||
|
- name: Alert - manual verification required
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
⚠️ IMPORTANT: This was an emergency rollback without health checks.
|
||||||
|
You MUST manually verify application functionality before considering this successful.
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
# Ansible Playbook: Production Health Check
|
||||||
|
# Purpose: Comprehensive health verification for production deployment
|
||||||
|
# Usage: ansible-playbook -i inventory/production.yml playbooks/health-check.yml
|
||||||
|
|
||||||
|
- name: Production Health Check
|
||||||
|
hosts: production_server
|
||||||
|
become: no
|
||||||
|
vars:
|
||||||
|
app_url: "https://michaelschiemer.de"
|
||||||
|
stack_name: "framework"
|
||||||
|
health_timeout: 30
|
||||||
|
max_retries: 10
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Check Docker Swarm status
|
||||||
|
shell: docker info | grep "Swarm: active"
|
||||||
|
register: swarm_status
|
||||||
|
failed_when: swarm_status.rc != 0
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Check running services
|
||||||
|
shell: docker service ls --filter "name={{ stack_name }}" --format "{{`{{.Name}}`}} {{`{{.Replicas}}`}}"
|
||||||
|
register: service_list
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display service status
|
||||||
|
debug:
|
||||||
|
msg: "{{ service_list.stdout_lines }}"
|
||||||
|
|
||||||
|
- name: Verify web service is running
|
||||||
|
shell: |
|
||||||
|
docker service ps {{ stack_name }}_web \
|
||||||
|
--filter "desired-state=running" \
|
||||||
|
--format "{{`{{.CurrentState}}`}}" | head -1
|
||||||
|
register: web_state
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Fail if web service not running
|
||||||
|
fail:
|
||||||
|
msg: "Web service is not in Running state: {{ web_state.stdout }}"
|
||||||
|
when: "'Running' not in web_state.stdout"
|
||||||
|
|
||||||
|
- name: Verify worker service is running
|
||||||
|
shell: |
|
||||||
|
docker service ps {{ stack_name }}_queue-worker \
|
||||||
|
--filter "desired-state=running" \
|
||||||
|
--format "{{`{{.CurrentState}}`}}" | head -1
|
||||||
|
register: worker_state
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Fail if worker service not running
|
||||||
|
fail:
|
||||||
|
msg: "Worker service is not in Running state: {{ worker_state.stdout }}"
|
||||||
|
when: "'Running' not in worker_state.stdout"
|
||||||
|
|
||||||
|
- name: Wait for application to be ready
|
||||||
|
uri:
|
||||||
|
url: "{{ app_url }}/health"
|
||||||
|
validate_certs: no
|
||||||
|
status_code: [200, 302]
|
||||||
|
timeout: "{{ health_timeout }}"
|
||||||
|
register: health_response
|
||||||
|
retries: "{{ max_retries }}"
|
||||||
|
delay: 3
|
||||||
|
until: health_response.status in [200, 302]
|
||||||
|
|
||||||
|
- name: Check database connectivity
|
||||||
|
uri:
|
||||||
|
url: "{{ app_url }}/health/database"
|
||||||
|
validate_certs: no
|
||||||
|
status_code: 200
|
||||||
|
timeout: "{{ health_timeout }}"
|
||||||
|
register: db_health
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Check Redis connectivity
|
||||||
|
uri:
|
||||||
|
url: "{{ app_url }}/health/redis"
|
||||||
|
validate_certs: no
|
||||||
|
status_code: 200
|
||||||
|
timeout: "{{ health_timeout }}"
|
||||||
|
register: redis_health
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Check queue system
|
||||||
|
uri:
|
||||||
|
url: "{{ app_url }}/health/queue"
|
||||||
|
validate_certs: no
|
||||||
|
status_code: 200
|
||||||
|
timeout: "{{ health_timeout }}"
|
||||||
|
register: queue_health
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Get service replicas count
|
||||||
|
shell: |
|
||||||
|
docker service ls --filter "name={{ stack_name }}_web" --format "{{`{{.Replicas}}`}}"
|
||||||
|
register: replicas
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Check for service errors
|
||||||
|
shell: |
|
||||||
|
docker service ps {{ stack_name }}_web --filter "desired-state=running" | grep -c Error || true
|
||||||
|
register: error_count
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Warn if errors detected
|
||||||
|
debug:
|
||||||
|
msg: "⚠️ Warning: {{ error_count.stdout }} errors detected in service logs"
|
||||||
|
when: error_count.stdout | int > 0
|
||||||
|
|
||||||
|
- name: Display health check summary
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
✅ Health Check Summary:
|
||||||
|
|
||||||
|
Services:
|
||||||
|
- Web Service: {{ web_state.stdout }}
|
||||||
|
- Worker Service: {{ worker_state.stdout }}
|
||||||
|
- Replicas: {{ replicas.stdout }}
|
||||||
|
|
||||||
|
Endpoints:
|
||||||
|
- Application: {{ health_response.status }}
|
||||||
|
- Database: {{ db_health.status | default('SKIPPED') }}
|
||||||
|
- Redis: {{ redis_health.status | default('SKIPPED') }}
|
||||||
|
- Queue: {{ queue_health.status | default('SKIPPED') }}
|
||||||
|
|
||||||
|
Errors: {{ error_count.stdout }}
|
||||||
|
|
||||||
|
- name: Overall health assessment
|
||||||
|
debug:
|
||||||
|
msg: "✅ All health checks PASSED"
|
||||||
|
when:
|
||||||
|
- health_response.status in [200, 302]
|
||||||
|
- error_count.stdout | int == 0
|
||||||
|
|
||||||
|
- name: Fail if critical health checks failed
|
||||||
|
fail:
|
||||||
|
msg: "❌ Health check FAILED - manual intervention required"
|
||||||
|
when: health_response.status not in [200, 302]
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
---
|
||||||
|
# Ansible Playbook: Emergency Rollback
|
||||||
|
# Purpose: Rollback to previous working deployment
|
||||||
|
# Usage: ansible-playbook -i inventory/production.yml playbooks/rollback.yml
|
||||||
|
|
||||||
|
- name: Rollback Production Deployment
|
||||||
|
hosts: production_server
|
||||||
|
become: no
|
||||||
|
vars:
|
||||||
|
registry_url: "git.michaelschiemer.de:5000"
|
||||||
|
image_name: "framework"
|
||||||
|
stack_name: "framework"
|
||||||
|
rollback_tag: "{{ rollback_tag | default('latest') }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Display rollback warning
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
⚠️ ROLLBACK IN PROGRESS
|
||||||
|
|
||||||
|
This will revert services to a previous image.
|
||||||
|
Current target: {{ rollback_tag }}
|
||||||
|
|
||||||
|
- name: Pause for confirmation (manual runs only)
|
||||||
|
pause:
|
||||||
|
prompt: "Press ENTER to continue with rollback, or Ctrl+C to abort"
|
||||||
|
when: ansible_check_mode is not defined
|
||||||
|
|
||||||
|
- name: Get list of available image tags
|
||||||
|
shell: |
|
||||||
|
docker images {{ registry_url }}/{{ image_name }} --format "{{`{{.Tag}}`}}" | grep -v buildcache | head -10
|
||||||
|
register: available_tags
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display available tags
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
Available image tags for rollback:
|
||||||
|
{{ available_tags.stdout_lines | join('\n') }}
|
||||||
|
|
||||||
|
- name: Verify rollback image exists
|
||||||
|
docker_image:
|
||||||
|
name: "{{ registry_url }}/{{ image_name }}"
|
||||||
|
tag: "{{ rollback_tag }}"
|
||||||
|
source: pull
|
||||||
|
register: rollback_image
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Fail if image doesn't exist
|
||||||
|
fail:
|
||||||
|
msg: "Rollback image {{ registry_url }}/{{ image_name }}:{{ rollback_tag }} not found"
|
||||||
|
when: rollback_image is failed
|
||||||
|
|
||||||
|
- name: Rollback web service
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ stack_name }}_web"
|
||||||
|
image: "{{ registry_url }}/{{ image_name }}:{{ rollback_tag }}"
|
||||||
|
force_update: yes
|
||||||
|
update_config:
|
||||||
|
parallelism: 2
|
||||||
|
delay: 5s
|
||||||
|
state: present
|
||||||
|
register: web_rollback
|
||||||
|
|
||||||
|
- name: Rollback queue-worker service
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ stack_name }}_queue-worker"
|
||||||
|
image: "{{ registry_url }}/{{ image_name }}:{{ rollback_tag }}"
|
||||||
|
force_update: yes
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 5s
|
||||||
|
state: present
|
||||||
|
register: worker_rollback
|
||||||
|
|
||||||
|
- name: Wait for rollback to complete
|
||||||
|
pause:
|
||||||
|
seconds: 30
|
||||||
|
|
||||||
|
- name: Verify rollback success
|
||||||
|
shell: |
|
||||||
|
docker service ps {{ stack_name }}_web --filter "desired-state=running" --format "{{`{{.CurrentState}}`}}" | head -1
|
||||||
|
register: rollback_state
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Test service health
|
||||||
|
uri:
|
||||||
|
url: "https://michaelschiemer.de/health"
|
||||||
|
validate_certs: no
|
||||||
|
status_code: [200, 302]
|
||||||
|
timeout: 10
|
||||||
|
register: health_check
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Record rollback in history
|
||||||
|
copy:
|
||||||
|
content: |
|
||||||
|
Rollback: {{ ansible_date_time.iso8601 }}
|
||||||
|
Previous Image: {{ registry_url }}/{{ image_name }}:latest
|
||||||
|
Rollback Image: {{ registry_url }}/{{ image_name }}:{{ rollback_tag }}
|
||||||
|
Status: {{ health_check.status | default('UNKNOWN') }}
|
||||||
|
Reason: Manual rollback or deployment failure
|
||||||
|
dest: "/home/deploy/deployments/rollback-{{ ansible_date_time.epoch }}.log"
|
||||||
|
mode: '0644'
|
||||||
|
|
||||||
|
- name: Display rollback summary
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
{% if health_check is succeeded %}
|
||||||
|
✅ Rollback completed successfully
|
||||||
|
{% else %}
|
||||||
|
❌ Rollback completed but health check failed
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
Image: {{ registry_url }}/{{ image_name }}:{{ rollback_tag }}
|
||||||
|
Web Service: {{ web_rollback.changed | ternary('ROLLED BACK', 'NO CHANGE') }}
|
||||||
|
Worker Service: {{ worker_rollback.changed | ternary('ROLLED BACK', 'NO CHANGE') }}
|
||||||
|
Health Status: {{ health_check.status | default('FAILED') }}
|
||||||
|
|
||||||
|
- name: Alert if rollback failed
|
||||||
|
fail:
|
||||||
|
msg: "Rollback completed but health check failed. Manual intervention required."
|
||||||
|
when: health_check is failed
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
---
|
||||||
|
# Ansible Playbook: Setup Gitea Actions Runner on Production Server
|
||||||
|
# Purpose: Install and configure Gitea Actions runner for automated deployments
|
||||||
|
# Usage: ansible-playbook -i inventory/production.yml playbooks/setup-gitea-runner.yml
|
||||||
|
|
||||||
|
- name: Setup Gitea Actions Runner for Production Deployments
|
||||||
|
hosts: production_server
|
||||||
|
become: yes
|
||||||
|
vars:
|
||||||
|
gitea_url: "https://git.michaelschiemer.de"
|
||||||
|
runner_name: "production-runner"
|
||||||
|
runner_labels: "docker,production,ubuntu"
|
||||||
|
runner_version: "0.2.6"
|
||||||
|
runner_install_dir: "/opt/gitea-runner"
|
||||||
|
runner_work_dir: "/home/deploy/gitea-runner-work"
|
||||||
|
runner_user: "deploy"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Create runner directories
|
||||||
|
file:
|
||||||
|
path: "{{ item }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ runner_user }}"
|
||||||
|
group: "{{ runner_user }}"
|
||||||
|
mode: '0755'
|
||||||
|
loop:
|
||||||
|
- "{{ runner_install_dir }}"
|
||||||
|
- "{{ runner_work_dir }}"
|
||||||
|
|
||||||
|
- name: Download Gitea Act Runner binary
|
||||||
|
get_url:
|
||||||
|
url: "https://dl.gitea.com/act_runner/{{ runner_version }}/act_runner-{{ runner_version }}-linux-amd64"
|
||||||
|
dest: "{{ runner_install_dir }}/act_runner"
|
||||||
|
mode: '0755'
|
||||||
|
owner: "{{ runner_user }}"
|
||||||
|
|
||||||
|
- name: Check if runner is already registered
|
||||||
|
stat:
|
||||||
|
path: "{{ runner_install_dir }}/.runner"
|
||||||
|
register: runner_config
|
||||||
|
|
||||||
|
- name: Register runner with Gitea (manual step required)
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
⚠️ MANUAL STEP REQUIRED:
|
||||||
|
|
||||||
|
1. Generate registration token in Gitea:
|
||||||
|
- Navigate to {{ gitea_url }}/admin/runners
|
||||||
|
- Click "Create new runner"
|
||||||
|
- Copy the registration token
|
||||||
|
|
||||||
|
2. SSH to production server and run:
|
||||||
|
sudo -u {{ runner_user }} {{ runner_install_dir }}/act_runner register \
|
||||||
|
--instance {{ gitea_url }} \
|
||||||
|
--token YOUR_REGISTRATION_TOKEN \
|
||||||
|
--name {{ runner_name }} \
|
||||||
|
--labels {{ runner_labels }}
|
||||||
|
|
||||||
|
3. Re-run this playbook to complete setup
|
||||||
|
when: not runner_config.stat.exists
|
||||||
|
|
||||||
|
- name: Create systemd service for runner
|
||||||
|
template:
|
||||||
|
src: ../templates/gitea-runner.service.j2
|
||||||
|
dest: /etc/systemd/system/gitea-runner.service
|
||||||
|
mode: '0644'
|
||||||
|
notify: Reload systemd
|
||||||
|
|
||||||
|
- name: Enable and start Gitea runner service
|
||||||
|
systemd:
|
||||||
|
name: gitea-runner
|
||||||
|
enabled: yes
|
||||||
|
state: started
|
||||||
|
when: runner_config.stat.exists
|
||||||
|
|
||||||
|
- name: Install Docker (if not present)
|
||||||
|
apt:
|
||||||
|
name:
|
||||||
|
- docker.io
|
||||||
|
- docker-compose
|
||||||
|
state: present
|
||||||
|
update_cache: yes
|
||||||
|
|
||||||
|
- name: Add runner user to docker group
|
||||||
|
user:
|
||||||
|
name: "{{ runner_user }}"
|
||||||
|
groups: docker
|
||||||
|
append: yes
|
||||||
|
|
||||||
|
- name: Ensure Docker service is running
|
||||||
|
systemd:
|
||||||
|
name: docker
|
||||||
|
state: started
|
||||||
|
enabled: yes
|
||||||
|
|
||||||
|
- name: Create Docker network for builds
|
||||||
|
docker_network:
|
||||||
|
name: gitea-runner-network
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
- name: Display runner status
|
||||||
|
debug:
|
||||||
|
msg: |
|
||||||
|
✅ Gitea Runner Setup Complete
|
||||||
|
|
||||||
|
Runner Name: {{ runner_name }}
|
||||||
|
Install Dir: {{ runner_install_dir }}
|
||||||
|
Work Dir: {{ runner_work_dir }}
|
||||||
|
|
||||||
|
Check status: systemctl status gitea-runner
|
||||||
|
View logs: journalctl -u gitea-runner -f
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: Reload systemd
|
||||||
|
systemd:
|
||||||
|
daemon_reload: yes
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
# Ansible Playbook: Setup Production Secrets
|
||||||
|
# Purpose: Deploy Docker Secrets and environment configuration to production
|
||||||
|
# Usage: ansible-playbook -i inventory/production.yml playbooks/setup-production-secrets.yml --ask-vault-pass
|
||||||
|
|
||||||
|
- name: Setup Production Secrets and Environment
|
||||||
|
hosts: production_server
|
||||||
|
become: no
|
||||||
|
vars_files:
|
||||||
|
- ../secrets/production-vault.yml # Encrypted with ansible-vault
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Ensure secrets directory exists
|
||||||
|
file:
|
||||||
|
path: /home/deploy/secrets
|
||||||
|
state: directory
|
||||||
|
mode: '0700'
|
||||||
|
owner: deploy
|
||||||
|
group: deploy
|
||||||
|
|
||||||
|
- name: Deploy environment file from vault
|
||||||
|
template:
|
||||||
|
src: ../templates/production.env.j2
|
||||||
|
dest: /home/deploy/secrets/.env.production
|
||||||
|
mode: '0600'
|
||||||
|
owner: deploy
|
||||||
|
group: deploy
|
||||||
|
notify: Restart services
|
||||||
|
|
||||||
|
- name: Create Docker secrets (if swarm is initialized)
|
||||||
|
docker_secret:
|
||||||
|
name: "{{ item.name }}"
|
||||||
|
data: "{{ item.value }}"
|
||||||
|
state: present
|
||||||
|
loop:
|
||||||
|
- { name: "db_password", value: "{{ vault_db_password }}" }
|
||||||
|
- { name: "redis_password", value: "{{ vault_redis_password }}" }
|
||||||
|
- { name: "app_key", value: "{{ vault_app_key }}" }
|
||||||
|
- { name: "jwt_secret", value: "{{ vault_jwt_secret }}" }
|
||||||
|
- { name: "registry_password", value: "{{ vault_registry_password }}" }
|
||||||
|
no_log: true # Don't log secrets
|
||||||
|
|
||||||
|
- name: Verify secrets are accessible
|
||||||
|
shell: docker secret ls
|
||||||
|
register: secret_list
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Display deployed secrets (names only)
|
||||||
|
debug:
|
||||||
|
msg: "Deployed secrets: {{ secret_list.stdout_lines }}"
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- name: Restart services
|
||||||
|
shell: |
|
||||||
|
docker service update --force framework_web
|
||||||
|
docker service update --force framework_queue-worker
|
||||||
|
when: ansible_check_mode is not defined
|
||||||
8
.deployment-archive-20251030-111806/ansible/secrets/.gitignore
vendored
Normal file
8
.deployment-archive-20251030-111806/ansible/secrets/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# SECURITY: Never commit decrypted vault files
|
||||||
|
production-vault.yml.decrypted
|
||||||
|
*.backup
|
||||||
|
*.tmp
|
||||||
|
|
||||||
|
# Keep encrypted vault in git
|
||||||
|
# Encrypted files are safe to commit
|
||||||
|
!production-vault.yml
|
||||||
238
.deployment-archive-20251030-111806/ansible/secrets/README.md
Normal file
238
.deployment-archive-20251030-111806/ansible/secrets/README.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# Production Secrets Management
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This directory contains encrypted production secrets managed with Ansible Vault.
|
||||||
|
|
||||||
|
**Security Model**:
|
||||||
|
- Secrets are encrypted at rest with AES256
|
||||||
|
- Vault password is required for deployment
|
||||||
|
- Decrypted files are NEVER committed to git
|
||||||
|
- Production deployment uses secure SSH key authentication
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `production-vault.yml` - **Encrypted** secrets vault (safe to commit)
|
||||||
|
- `.gitignore` - Prevents accidental commit of decrypted files
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Initialize Secrets (First Time)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd deployment
|
||||||
|
./scripts/setup-production-secrets.sh init
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Generate secure random passwords/keys
|
||||||
|
- Create encrypted vault file
|
||||||
|
- Prompt for vault password (store in password manager!)
|
||||||
|
|
||||||
|
### 2. Deploy Secrets to Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-production-secrets.sh deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via Gitea Actions:
|
||||||
|
1. Go to: https://git.michaelschiemer.de/michael/framework/actions
|
||||||
|
2. Select "Update Production Secrets" workflow
|
||||||
|
3. Click "Run workflow"
|
||||||
|
4. Enter vault password
|
||||||
|
5. Click "Run"
|
||||||
|
|
||||||
|
### 3. Update Secrets Manually
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Edit encrypted vault
|
||||||
|
ansible-vault edit deployment/ansible/secrets/production-vault.yml
|
||||||
|
|
||||||
|
# Deploy changes
|
||||||
|
./scripts/setup-production-secrets.sh deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Rotate Secrets (Monthly Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-production-secrets.sh rotate
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Generate new passwords
|
||||||
|
- Update vault
|
||||||
|
- Deploy to production
|
||||||
|
- Restart services
|
||||||
|
|
||||||
|
## Vault Structure
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Database
|
||||||
|
vault_db_name: framework_production
|
||||||
|
vault_db_user: framework_app
|
||||||
|
vault_db_password: [auto-generated 32 chars]
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
vault_redis_password: [auto-generated 32 chars]
|
||||||
|
|
||||||
|
# Application
|
||||||
|
vault_app_key: [auto-generated base64 key]
|
||||||
|
vault_jwt_secret: [auto-generated 64 chars]
|
||||||
|
|
||||||
|
# Docker Registry
|
||||||
|
vault_registry_url: git.michaelschiemer.de:5000
|
||||||
|
vault_registry_user: deploy
|
||||||
|
vault_registry_password: [auto-generated 24 chars]
|
||||||
|
|
||||||
|
# Security
|
||||||
|
vault_admin_allowed_ips: "127.0.0.1,::1,94.16.110.151"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
### DO ✅
|
||||||
|
|
||||||
|
- **DO** encrypt vault with strong password
|
||||||
|
- **DO** store vault password in password manager
|
||||||
|
- **DO** rotate secrets monthly
|
||||||
|
- **DO** use `--ask-vault-pass` for deployments
|
||||||
|
- **DO** commit encrypted vault to git
|
||||||
|
- **DO** use different vault passwords per environment
|
||||||
|
|
||||||
|
### DON'T ❌
|
||||||
|
|
||||||
|
- **DON'T** commit decrypted vault files
|
||||||
|
- **DON'T** share vault password via email/chat
|
||||||
|
- **DON'T** use weak vault passwords
|
||||||
|
- **DON'T** decrypt vault on untrusted systems
|
||||||
|
- **DON'T** hardcode secrets in code
|
||||||
|
|
||||||
|
## Ansible Vault Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Encrypt file
|
||||||
|
ansible-vault encrypt production-vault.yml
|
||||||
|
|
||||||
|
# Decrypt file (for viewing only)
|
||||||
|
ansible-vault decrypt production-vault.yml
|
||||||
|
|
||||||
|
# Edit encrypted file
|
||||||
|
ansible-vault edit production-vault.yml
|
||||||
|
|
||||||
|
# Change vault password
|
||||||
|
ansible-vault rekey production-vault.yml
|
||||||
|
|
||||||
|
# View encrypted file content
|
||||||
|
ansible-vault view production-vault.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Integration
|
||||||
|
|
||||||
|
### Local Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd deployment/ansible
|
||||||
|
ansible-playbook -i inventory/production.yml \
|
||||||
|
playbooks/setup-production-secrets.yml \
|
||||||
|
--ask-vault-pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Deployment (Gitea Actions)
|
||||||
|
|
||||||
|
Vault password stored as Gitea Secret:
|
||||||
|
- Secret name: `ANSIBLE_VAULT_PASSWORD`
|
||||||
|
- Used in workflow: `.gitea/workflows/update-production-secrets.yml`
|
||||||
|
|
||||||
|
### Docker Secrets Integration
|
||||||
|
|
||||||
|
Secrets are deployed as Docker Secrets for secure runtime access:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List deployed secrets on production
|
||||||
|
ssh deploy@94.16.110.151 "docker secret ls"
|
||||||
|
|
||||||
|
# Services automatically use secrets via docker-compose
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
secrets:
|
||||||
|
- db_password
|
||||||
|
- redis_password
|
||||||
|
- app_key
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Decryption failed" Error
|
||||||
|
|
||||||
|
**Cause**: Wrong vault password
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```bash
|
||||||
|
# Verify password works
|
||||||
|
ansible-vault view deployment/ansible/secrets/production-vault.yml
|
||||||
|
|
||||||
|
# If forgotten, you must reinitialize (data loss!)
|
||||||
|
./scripts/setup-production-secrets.sh init
|
||||||
|
```
|
||||||
|
|
||||||
|
### Secrets Not Applied After Deployment
|
||||||
|
|
||||||
|
**Solution**:
|
||||||
|
```bash
|
||||||
|
# Manually restart services
|
||||||
|
ssh deploy@94.16.110.151 "docker service update --force framework_web"
|
||||||
|
|
||||||
|
# Or use Ansible
|
||||||
|
cd deployment/ansible
|
||||||
|
ansible-playbook -i inventory/production.yml playbooks/restart-services.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify Secrets on Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/setup-production-secrets.sh verify
|
||||||
|
|
||||||
|
# Or manually
|
||||||
|
ssh deploy@94.16.110.151 "docker secret ls"
|
||||||
|
ssh deploy@94.16.110.151 "cat /home/deploy/secrets/.env.production | grep -v PASSWORD"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Emergency Procedures
|
||||||
|
|
||||||
|
### Lost Vault Password
|
||||||
|
|
||||||
|
**Recovery Steps**:
|
||||||
|
1. Backup current vault: `cp production-vault.yml production-vault.yml.lost`
|
||||||
|
2. Reinitialize vault: `./scripts/setup-production-secrets.sh init`
|
||||||
|
3. Update database passwords manually on production
|
||||||
|
4. Deploy new secrets: `./scripts/setup-production-secrets.sh deploy`
|
||||||
|
|
||||||
|
### Compromised Secrets
|
||||||
|
|
||||||
|
**Immediate Response**:
|
||||||
|
1. Rotate all secrets: `./scripts/setup-production-secrets.sh rotate`
|
||||||
|
2. Review access logs on production
|
||||||
|
3. Update vault password: `ansible-vault rekey production-vault.yml`
|
||||||
|
4. Audit git commit history
|
||||||
|
5. Investigate compromise source
|
||||||
|
|
||||||
|
## Monitoring
|
||||||
|
|
||||||
|
Check secrets deployment status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Via script
|
||||||
|
./scripts/setup-production-secrets.sh verify
|
||||||
|
|
||||||
|
# Manual check
|
||||||
|
ansible production_server -i inventory/production.yml \
|
||||||
|
-m shell -a "docker secret ls | wc -l"
|
||||||
|
|
||||||
|
# Should show 5 secrets: db_password, redis_password, app_key, jwt_secret, registry_password
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Ansible Vault Documentation](https://docs.ansible.com/ansible/latest/user_guide/vault.html)
|
||||||
|
- [Docker Secrets Best Practices](https://docs.docker.com/engine/swarm/secrets/)
|
||||||
|
- Main Deployment Guide: `../README.md`
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
# Production Secrets Vault
|
||||||
|
# IMPORTANT: This file must be encrypted with ansible-vault
|
||||||
|
#
|
||||||
|
# Encrypt this file:
|
||||||
|
# ansible-vault encrypt deployment/ansible/secrets/production-vault.yml
|
||||||
|
#
|
||||||
|
# Edit encrypted file:
|
||||||
|
# ansible-vault edit deployment/ansible/secrets/production-vault.yml
|
||||||
|
#
|
||||||
|
# Decrypt file (for debugging only, never commit decrypted):
|
||||||
|
# ansible-vault decrypt deployment/ansible/secrets/production-vault.yml
|
||||||
|
#
|
||||||
|
# Use in playbook:
|
||||||
|
# ansible-playbook playbooks/setup-production-secrets.yml --ask-vault-pass
|
||||||
|
|
||||||
|
# Database Credentials
|
||||||
|
vault_db_name: framework_production
|
||||||
|
vault_db_user: framework_app
|
||||||
|
vault_db_password: CHANGE_ME_STRONG_DB_PASSWORD_HERE
|
||||||
|
|
||||||
|
# Redis Credentials
|
||||||
|
vault_redis_password: CHANGE_ME_STRONG_REDIS_PASSWORD_HERE
|
||||||
|
|
||||||
|
# Application Secrets
|
||||||
|
vault_app_key: CHANGE_ME_BASE64_ENCODED_32_BYTE_KEY
|
||||||
|
vault_jwt_secret: CHANGE_ME_STRONG_JWT_SECRET_HERE
|
||||||
|
|
||||||
|
# Docker Registry Credentials
|
||||||
|
vault_registry_url: git.michaelschiemer.de:5000
|
||||||
|
vault_registry_user: deploy
|
||||||
|
vault_registry_password: CHANGE_ME_REGISTRY_PASSWORD_HERE
|
||||||
|
|
||||||
|
# Security Configuration
|
||||||
|
vault_admin_allowed_ips: "127.0.0.1,::1,94.16.110.151"
|
||||||
|
|
||||||
|
# SMTP Configuration (optional)
|
||||||
|
vault_smtp_host: smtp.example.com
|
||||||
|
vault_smtp_port: 587
|
||||||
|
vault_smtp_user: noreply@michaelschiemer.de
|
||||||
|
vault_smtp_password: CHANGE_ME_SMTP_PASSWORD_HERE
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Gitea Actions Runner
|
||||||
|
After=network.target docker.service
|
||||||
|
Requires=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={{ runner_user }}
|
||||||
|
WorkingDirectory={{ runner_install_dir }}
|
||||||
|
ExecStart={{ runner_install_dir }}/act_runner daemon --config {{ runner_install_dir }}/.runner
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# Security hardening
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
ReadWritePaths={{ runner_work_dir }}
|
||||||
|
|
||||||
|
# Resource limits
|
||||||
|
LimitNOFILE=65536
|
||||||
|
LimitNPROC=4096
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Production Environment Configuration
|
||||||
|
# Generated by Ansible - DO NOT EDIT MANUALLY
|
||||||
|
# Last updated: {{ ansible_date_time.iso8601 }}
|
||||||
|
|
||||||
|
# Application
|
||||||
|
APP_ENV=production
|
||||||
|
APP_DEBUG=false
|
||||||
|
APP_KEY={{ vault_app_key }}
|
||||||
|
APP_URL=https://michaelschiemer.de
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=mysql
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE={{ vault_db_name }}
|
||||||
|
DB_USERNAME={{ vault_db_user }}
|
||||||
|
DB_PASSWORD={{ vault_db_password }}
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_PASSWORD={{ vault_redis_password }}
|
||||||
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
CACHE_DRIVER=redis
|
||||||
|
QUEUE_CONNECTION=redis
|
||||||
|
|
||||||
|
# Session
|
||||||
|
SESSION_DRIVER=redis
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
JWT_SECRET={{ vault_jwt_secret }}
|
||||||
|
JWT_TTL=60
|
||||||
|
|
||||||
|
# Docker Registry
|
||||||
|
REGISTRY_URL={{ vault_registry_url }}
|
||||||
|
REGISTRY_USER={{ vault_registry_user }}
|
||||||
|
REGISTRY_PASSWORD={{ vault_registry_password }}
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_LEVEL=warning
|
||||||
|
|
||||||
|
# Security
|
||||||
|
ADMIN_ALLOWED_IPS={{ vault_admin_allowed_ips }}
|
||||||
|
|
||||||
|
# Performance
|
||||||
|
OPCACHE_ENABLE=1
|
||||||
|
OPCACHE_VALIDATE_TIMESTAMPS=0
|
||||||
241
.deployment-archive-20251030-111806/scripts/deploy.sh
Executable file
241
.deployment-archive-20251030-111806/scripts/deploy.sh
Executable file
@@ -0,0 +1,241 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Main Deployment Script
|
||||||
|
# Uses script framework for professional deployment automation
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Determine script directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# Source libraries
|
||||||
|
# shellcheck source=./lib/common.sh
|
||||||
|
source "${SCRIPT_DIR}/lib/common.sh"
|
||||||
|
# shellcheck source=./lib/ansible.sh
|
||||||
|
source "${SCRIPT_DIR}/lib/ansible.sh"
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
readonly DEPLOYMENT_NAME="Framework Production Deployment"
|
||||||
|
readonly START_TIME=$(date +%s)
|
||||||
|
|
||||||
|
# Usage information
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
Usage: $0 [OPTIONS] [GIT_REPO_URL]
|
||||||
|
|
||||||
|
Professional deployment automation using Ansible.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Show this help message
|
||||||
|
-c, --check Run in check mode (dry-run)
|
||||||
|
-v, --verbose Enable verbose output
|
||||||
|
-d, --debug Enable debug logging
|
||||||
|
-f, --force Skip confirmation prompts
|
||||||
|
--no-health-check Skip health checks
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
# Deploy from existing code on server
|
||||||
|
$0
|
||||||
|
|
||||||
|
# Deploy from specific Git repository
|
||||||
|
$0 https://github.com/user/repo.git
|
||||||
|
|
||||||
|
# Dry-run to see what would happen
|
||||||
|
$0 --check
|
||||||
|
|
||||||
|
# Debug mode
|
||||||
|
$0 --debug
|
||||||
|
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
parse_args() {
|
||||||
|
local git_repo_url=""
|
||||||
|
local check_mode=false
|
||||||
|
local force=false
|
||||||
|
local health_check=true
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
-c|--check)
|
||||||
|
check_mode=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-v|--verbose)
|
||||||
|
set -x
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-d|--debug)
|
||||||
|
export DEBUG=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-f|--force)
|
||||||
|
force=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-health-check)
|
||||||
|
health_check=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -z "$git_repo_url" ]]; then
|
||||||
|
git_repo_url="$1"
|
||||||
|
else
|
||||||
|
log_error "Unknown argument: $1"
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$check_mode|$force|$health_check|$git_repo_url"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pre-deployment checks
|
||||||
|
pre_deployment_checks() {
|
||||||
|
log_step "Running pre-deployment checks..."
|
||||||
|
|
||||||
|
# Check Ansible
|
||||||
|
check_ansible || die "Ansible check failed"
|
||||||
|
|
||||||
|
# Test connectivity
|
||||||
|
test_ansible_connectivity || die "Connectivity check failed"
|
||||||
|
|
||||||
|
# Check playbook syntax
|
||||||
|
local playbook="${ANSIBLE_PLAYBOOK_DIR}/deploy.yml"
|
||||||
|
if [[ -f "$playbook" ]]; then
|
||||||
|
check_playbook_syntax "$playbook" || log_warning "Playbook syntax check failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Pre-deployment checks passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deployment summary
|
||||||
|
show_deployment_summary() {
|
||||||
|
local git_repo_url="$1"
|
||||||
|
local check_mode="$2"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo " ${DEPLOYMENT_NAME}"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
echo "Mode: $([ "$check_mode" = "true" ] && echo "CHECK (Dry-Run)" || echo "PRODUCTION")"
|
||||||
|
echo "Target: 94.16.110.151 (production)"
|
||||||
|
echo "Services: framework_web, framework_queue-worker"
|
||||||
|
|
||||||
|
if [[ -n "$git_repo_url" ]]; then
|
||||||
|
echo "Git Repo: $git_repo_url"
|
||||||
|
else
|
||||||
|
echo "Source: Existing code on server"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Ansible: $(ansible --version | head -1)"
|
||||||
|
echo "Timestamp: $(timestamp)"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Post-deployment health check
|
||||||
|
post_deployment_health_check() {
|
||||||
|
log_step "Running post-deployment health checks..."
|
||||||
|
|
||||||
|
log_info "Checking service status..."
|
||||||
|
if ansible_adhoc production_server shell "docker stack services framework" &> /dev/null; then
|
||||||
|
log_success "Services are running"
|
||||||
|
else
|
||||||
|
log_warning "Could not verify service status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Testing website availability..."
|
||||||
|
if ansible_adhoc production_server shell "curl -k -s -o /dev/null -w '%{http_code}' https://michaelschiemer.de/" | grep -q "200\|302"; then
|
||||||
|
log_success "Website is responding"
|
||||||
|
else
|
||||||
|
log_warning "Website health check failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Health checks completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main deployment function
|
||||||
|
main() {
|
||||||
|
# Parse arguments
|
||||||
|
IFS='|' read -r check_mode force health_check git_repo_url <<< "$(parse_args "$@")"
|
||||||
|
|
||||||
|
# Show summary
|
||||||
|
show_deployment_summary "$git_repo_url" "$check_mode"
|
||||||
|
|
||||||
|
# Confirm deployment
|
||||||
|
if [[ "$force" != "true" ]] && [[ "$check_mode" != "true" ]]; then
|
||||||
|
if ! confirm "Proceed with deployment?" "n"; then
|
||||||
|
log_warning "Deployment cancelled by user"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pre-deployment checks
|
||||||
|
pre_deployment_checks
|
||||||
|
|
||||||
|
# Run deployment
|
||||||
|
log_step "Starting deployment..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "$check_mode" = "true" ]]; then
|
||||||
|
local playbook="${ANSIBLE_PLAYBOOK_DIR}/deploy.yml"
|
||||||
|
ansible_dry_run "$playbook" ${git_repo_url:+-e "git_repo_url=$git_repo_url"}
|
||||||
|
else
|
||||||
|
run_deployment "$git_repo_url"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local deployment_exit_code=$?
|
||||||
|
|
||||||
|
if [[ $deployment_exit_code -eq 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
log_success "Deployment completed successfully!"
|
||||||
|
|
||||||
|
# Post-deployment health check
|
||||||
|
if [[ "$health_check" = "true" ]] && [[ "$check_mode" != "true" ]]; then
|
||||||
|
echo ""
|
||||||
|
post_deployment_health_check
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show deployment stats
|
||||||
|
local end_time=$(date +%s)
|
||||||
|
local elapsed=$(duration "$START_TIME" "$end_time")
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo " Deployment Summary"
|
||||||
|
echo "========================================="
|
||||||
|
echo "Status: SUCCESS ✅"
|
||||||
|
echo "Duration: $elapsed"
|
||||||
|
echo "Website: https://michaelschiemer.de"
|
||||||
|
echo "Timestamp: $(timestamp)"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
log_error "Deployment failed!"
|
||||||
|
echo ""
|
||||||
|
log_info "Troubleshooting:"
|
||||||
|
log_info " 1. Check Ansible logs above"
|
||||||
|
log_info " 2. SSH to server: ssh -i ~/.ssh/production deploy@94.16.110.151"
|
||||||
|
log_info " 3. Check services: docker stack services framework"
|
||||||
|
log_info " 4. View logs: docker service logs framework_web --tail 50"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute main function
|
||||||
|
main "$@"
|
||||||
361
.deployment-archive-20251030-111806/scripts/deployment-diagnostics.sh
Executable file
361
.deployment-archive-20251030-111806/scripts/deployment-diagnostics.sh
Executable file
@@ -0,0 +1,361 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Deployment Diagnostics Script
|
||||||
|
# Purpose: Comprehensive diagnostics for troubleshooting deployment issues
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/deployment-diagnostics.sh # Run all diagnostics
|
||||||
|
# ./scripts/deployment-diagnostics.sh --quick # Quick checks only
|
||||||
|
# ./scripts/deployment-diagnostics.sh --verbose # Verbose output
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
|
||||||
|
PRODUCTION_SERVER="94.16.110.151"
|
||||||
|
REGISTRY="git.michaelschiemer.de:5000"
|
||||||
|
STACK_NAME="framework"
|
||||||
|
IMAGE="framework"
|
||||||
|
|
||||||
|
QUICK_MODE=false
|
||||||
|
VERBOSE=false
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}✗${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}✓${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}⚠${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}ℹ${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_section() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}═══ $1 ═══${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# SSH helper
|
||||||
|
ssh_exec() {
|
||||||
|
ssh -i ~/.ssh/production deploy@"${PRODUCTION_SERVER}" "$@" 2>/dev/null || echo "SSH_FAILED"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check local prerequisites
|
||||||
|
check_local() {
|
||||||
|
log_section "Local Environment"
|
||||||
|
|
||||||
|
# Git status
|
||||||
|
if git status &> /dev/null; then
|
||||||
|
log_success "Git repository detected"
|
||||||
|
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
log_info "Current branch: ${BRANCH}"
|
||||||
|
|
||||||
|
if [[ -n $(git status --porcelain) ]]; then
|
||||||
|
log_warn "Working directory has uncommitted changes"
|
||||||
|
else
|
||||||
|
log_success "Working directory is clean"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "Not in a git repository"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
if command -v docker &> /dev/null; then
|
||||||
|
log_success "Docker installed"
|
||||||
|
DOCKER_VERSION=$(docker --version | cut -d' ' -f3 | tr -d ',')
|
||||||
|
log_info "Version: ${DOCKER_VERSION}"
|
||||||
|
else
|
||||||
|
log_error "Docker not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ansible
|
||||||
|
if command -v ansible-playbook &> /dev/null; then
|
||||||
|
log_success "Ansible installed"
|
||||||
|
ANSIBLE_VERSION=$(ansible-playbook --version | head -1 | cut -d' ' -f2)
|
||||||
|
log_info "Version: ${ANSIBLE_VERSION}"
|
||||||
|
else
|
||||||
|
log_error "Ansible not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SSH key
|
||||||
|
if [[ -f ~/.ssh/production ]]; then
|
||||||
|
log_success "Production SSH key found"
|
||||||
|
else
|
||||||
|
log_error "Production SSH key not found at ~/.ssh/production"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check SSH connectivity
|
||||||
|
check_ssh() {
|
||||||
|
log_section "SSH Connectivity"
|
||||||
|
|
||||||
|
RESULT=$(ssh_exec "echo 'OK'")
|
||||||
|
|
||||||
|
if [[ "$RESULT" == "OK" ]]; then
|
||||||
|
log_success "SSH connection to production server"
|
||||||
|
else
|
||||||
|
log_error "Cannot connect to production server via SSH"
|
||||||
|
log_info "Check: ssh -i ~/.ssh/production deploy@${PRODUCTION_SERVER}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check Docker Swarm
|
||||||
|
check_docker_swarm() {
|
||||||
|
log_section "Docker Swarm Status"
|
||||||
|
|
||||||
|
SWARM_STATUS=$(ssh_exec "docker info | grep 'Swarm:' | awk '{print \$2}'")
|
||||||
|
|
||||||
|
if [[ "$SWARM_STATUS" == "active" ]]; then
|
||||||
|
log_success "Docker Swarm is active"
|
||||||
|
|
||||||
|
# Manager nodes
|
||||||
|
MANAGERS=$(ssh_exec "docker node ls --filter role=manager --format '{{.Hostname}}'")
|
||||||
|
log_info "Manager nodes: ${MANAGERS}"
|
||||||
|
|
||||||
|
# Worker nodes
|
||||||
|
WORKERS=$(ssh_exec "docker node ls --filter role=worker --format '{{.Hostname}}' | wc -l")
|
||||||
|
log_info "Worker nodes: ${WORKERS}"
|
||||||
|
else
|
||||||
|
log_error "Docker Swarm is not active"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check services
|
||||||
|
check_services() {
|
||||||
|
log_section "Framework Services"
|
||||||
|
|
||||||
|
# List services
|
||||||
|
SERVICES=$(ssh_exec "docker service ls --filter 'name=${STACK_NAME}' --format '{{.Name}}: {{.Replicas}}'")
|
||||||
|
|
||||||
|
if [[ -n "$SERVICES" ]]; then
|
||||||
|
log_success "Framework services found"
|
||||||
|
echo "$SERVICES" | while read -r line; do
|
||||||
|
log_info "$line"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_error "No framework services found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check web service
|
||||||
|
WEB_STATUS=$(ssh_exec "docker service ps ${STACK_NAME}_web --filter 'desired-state=running' --format '{{.CurrentState}}' | head -1")
|
||||||
|
|
||||||
|
if [[ "$WEB_STATUS" =~ Running ]]; then
|
||||||
|
log_success "Web service is running"
|
||||||
|
else
|
||||||
|
log_error "Web service is not running: ${WEB_STATUS}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check worker service
|
||||||
|
WORKER_STATUS=$(ssh_exec "docker service ps ${STACK_NAME}_queue-worker --filter 'desired-state=running' --format '{{.CurrentState}}' | head -1")
|
||||||
|
|
||||||
|
if [[ "$WORKER_STATUS" =~ Running ]]; then
|
||||||
|
log_success "Queue worker is running"
|
||||||
|
else
|
||||||
|
log_error "Queue worker is not running: ${WORKER_STATUS}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check Docker images
|
||||||
|
check_images() {
|
||||||
|
log_section "Docker Images"
|
||||||
|
|
||||||
|
# Current running image
|
||||||
|
CURRENT_IMAGE=$(ssh_exec "docker service inspect ${STACK_NAME}_web --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'")
|
||||||
|
|
||||||
|
if [[ -n "$CURRENT_IMAGE" ]]; then
|
||||||
|
log_success "Current image: ${CURRENT_IMAGE}"
|
||||||
|
else
|
||||||
|
log_error "Cannot determine current image"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Available images (last 5)
|
||||||
|
log_info "Available images (last 5):"
|
||||||
|
ssh_exec "docker images ${REGISTRY}/${IMAGE} --format ' {{.Tag}} ({{.CreatedAt}})' | grep -v buildcache | head -5"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check networks
|
||||||
|
check_networks() {
|
||||||
|
log_section "Docker Networks"
|
||||||
|
|
||||||
|
NETWORKS=$(ssh_exec "docker network ls --filter 'name=${STACK_NAME}' --format '{{.Name}}: {{.Driver}}'")
|
||||||
|
|
||||||
|
if [[ -n "$NETWORKS" ]]; then
|
||||||
|
log_success "Framework networks found"
|
||||||
|
echo "$NETWORKS" | while read -r line; do
|
||||||
|
log_info "$line"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_warn "No framework-specific networks found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check volumes
|
||||||
|
check_volumes() {
|
||||||
|
log_section "Docker Volumes"
|
||||||
|
|
||||||
|
VOLUMES=$(ssh_exec "docker volume ls --filter 'name=${STACK_NAME}' --format '{{.Name}}'")
|
||||||
|
|
||||||
|
if [[ -n "$VOLUMES" ]]; then
|
||||||
|
log_success "Framework volumes found"
|
||||||
|
echo "$VOLUMES" | while read -r line; do
|
||||||
|
log_info "$line"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_warn "No framework-specific volumes found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check application health
|
||||||
|
check_app_health() {
|
||||||
|
log_section "Application Health"
|
||||||
|
|
||||||
|
# Main health endpoint
|
||||||
|
HTTP_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" https://michaelschiemer.de/health || echo "000")
|
||||||
|
|
||||||
|
if [[ "$HTTP_CODE" == "200" ]] || [[ "$HTTP_CODE" == "302" ]]; then
|
||||||
|
log_success "Application health endpoint: ${HTTP_CODE}"
|
||||||
|
else
|
||||||
|
log_error "Application health endpoint failed: ${HTTP_CODE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Database health
|
||||||
|
DB_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" https://michaelschiemer.de/health/database || echo "000")
|
||||||
|
|
||||||
|
if [[ "$DB_CODE" == "200" ]]; then
|
||||||
|
log_success "Database connectivity: OK"
|
||||||
|
else
|
||||||
|
log_warn "Database connectivity: ${DB_CODE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Redis health
|
||||||
|
REDIS_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" https://michaelschiemer.de/health/redis || echo "000")
|
||||||
|
|
||||||
|
if [[ "$REDIS_CODE" == "200" ]]; then
|
||||||
|
log_success "Redis connectivity: OK"
|
||||||
|
else
|
||||||
|
log_warn "Redis connectivity: ${REDIS_CODE}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check Docker secrets
|
||||||
|
check_secrets() {
|
||||||
|
log_section "Docker Secrets"
|
||||||
|
|
||||||
|
SECRETS=$(ssh_exec "docker secret ls --format '{{.Name}}' | wc -l")
|
||||||
|
|
||||||
|
if [[ "$SECRETS" -gt 0 ]]; then
|
||||||
|
log_success "Docker secrets configured: ${SECRETS} secrets"
|
||||||
|
else
|
||||||
|
log_warn "No Docker secrets found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check recent logs
|
||||||
|
check_logs() {
|
||||||
|
log_section "Recent Logs"
|
||||||
|
|
||||||
|
log_info "Last 20 lines from web service:"
|
||||||
|
ssh_exec "docker service logs ${STACK_NAME}_web --tail 20"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check Gitea runner
|
||||||
|
check_gitea_runner() {
|
||||||
|
log_section "Gitea Actions Runner"
|
||||||
|
|
||||||
|
RUNNER_STATUS=$(ssh_exec "systemctl is-active gitea-runner 2>/dev/null || echo 'not-found'")
|
||||||
|
|
||||||
|
if [[ "$RUNNER_STATUS" == "active" ]]; then
|
||||||
|
log_success "Gitea runner service is active"
|
||||||
|
elif [[ "$RUNNER_STATUS" == "not-found" ]]; then
|
||||||
|
log_warn "Gitea runner service not found (may not be installed yet)"
|
||||||
|
else
|
||||||
|
log_error "Gitea runner service is ${RUNNER_STATUS}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resource usage
|
||||||
|
check_resources() {
|
||||||
|
log_section "Resource Usage"
|
||||||
|
|
||||||
|
# Disk usage
|
||||||
|
DISK_USAGE=$(ssh_exec "df -h / | tail -1 | awk '{print \$5}'")
|
||||||
|
log_info "Disk usage: ${DISK_USAGE}"
|
||||||
|
|
||||||
|
# Memory usage
|
||||||
|
MEMORY_USAGE=$(ssh_exec "free -h | grep Mem | awk '{print \$3\"/\"\$2}'")
|
||||||
|
log_info "Memory usage: ${MEMORY_USAGE}"
|
||||||
|
|
||||||
|
# Docker disk usage
|
||||||
|
log_info "Docker disk usage:"
|
||||||
|
ssh_exec "docker system df"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
--quick)
|
||||||
|
QUICK_MODE=true
|
||||||
|
;;
|
||||||
|
--verbose)
|
||||||
|
VERBOSE=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Main diagnostics
|
||||||
|
main() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}╔════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${CYAN}║ DEPLOYMENT DIAGNOSTICS REPORT ║${NC}"
|
||||||
|
echo -e "${CYAN}╚════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
check_local
|
||||||
|
check_ssh || { log_error "SSH connectivity failed - cannot continue"; exit 1; }
|
||||||
|
check_docker_swarm
|
||||||
|
check_services
|
||||||
|
check_images
|
||||||
|
check_app_health
|
||||||
|
|
||||||
|
if [[ "$QUICK_MODE" == false ]]; then
|
||||||
|
check_networks
|
||||||
|
check_volumes
|
||||||
|
check_secrets
|
||||||
|
check_gitea_runner
|
||||||
|
check_resources
|
||||||
|
|
||||||
|
if [[ "$VERBOSE" == true ]]; then
|
||||||
|
check_logs
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}╔════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${CYAN}║ DIAGNOSTICS COMPLETED ║${NC}"
|
||||||
|
echo -e "${CYAN}╚════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
log_info "For detailed logs: ./scripts/deployment-diagnostics.sh --verbose"
|
||||||
|
log_info "For service recovery: ./scripts/service-recovery.sh recover"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
171
.deployment-archive-20251030-111806/scripts/emergency-rollback.sh
Executable file
171
.deployment-archive-20251030-111806/scripts/emergency-rollback.sh
Executable file
@@ -0,0 +1,171 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Emergency Rollback Script
|
||||||
|
# Purpose: Fast rollback with minimal user interaction
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/emergency-rollback.sh # Interactive mode
|
||||||
|
# ./scripts/emergency-rollback.sh <image-tag> # Direct rollback
|
||||||
|
# ./scripts/emergency-rollback.sh list # List available tags
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
ANSIBLE_DIR="${PROJECT_ROOT}/deployment/ansible"
|
||||||
|
INVENTORY="${ANSIBLE_DIR}/inventory/production.yml"
|
||||||
|
|
||||||
|
PRODUCTION_SERVER="94.16.110.151"
|
||||||
|
REGISTRY="git.michaelschiemer.de:5000"
|
||||||
|
IMAGE="framework"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# List available image tags
|
||||||
|
list_tags() {
|
||||||
|
log_info "Fetching available image tags from production..."
|
||||||
|
|
||||||
|
ssh -i ~/.ssh/production deploy@"${PRODUCTION_SERVER}" \
|
||||||
|
"docker images ${REGISTRY}/${IMAGE} --format '{{.Tag}}' | grep -v buildcache | head -20"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "Current running version:"
|
||||||
|
ssh -i ~/.ssh/production deploy@"${PRODUCTION_SERVER}" \
|
||||||
|
"docker service inspect framework_web --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}'"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get current image tag
|
||||||
|
get_current_tag() {
|
||||||
|
ssh -i ~/.ssh/production deploy@"${PRODUCTION_SERVER}" \
|
||||||
|
"docker service inspect framework_web --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}' | cut -d':' -f2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Emergency rollback
|
||||||
|
emergency_rollback() {
|
||||||
|
local target_tag="$1"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_warn "╔════════════════════════════════════════════════════════╗"
|
||||||
|
log_warn "║ 🚨 EMERGENCY ROLLBACK INITIATED 🚨 ║"
|
||||||
|
log_warn "╚════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local current_tag=$(get_current_tag)
|
||||||
|
|
||||||
|
echo "Current Version: ${current_tag}"
|
||||||
|
echo "Target Version: ${target_tag}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [[ "${current_tag}" == "${target_tag}" ]]; then
|
||||||
|
log_warn "Already running ${target_tag}. No rollback needed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_warn "This will immediately rollback production WITHOUT health checks."
|
||||||
|
log_warn "Use only in emergency situations."
|
||||||
|
echo ""
|
||||||
|
read -p "Type 'ROLLBACK' to confirm: " -r
|
||||||
|
|
||||||
|
if [[ ! "$REPLY" == "ROLLBACK" ]]; then
|
||||||
|
log_info "Rollback cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Executing emergency rollback via Ansible..."
|
||||||
|
|
||||||
|
cd "${ANSIBLE_DIR}"
|
||||||
|
|
||||||
|
ansible-playbook \
|
||||||
|
-i "${INVENTORY}" \
|
||||||
|
playbooks/emergency-rollback.yml \
|
||||||
|
-e "rollback_tag=${target_tag}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_warn "╔════════════════════════════════════════════════════════╗"
|
||||||
|
log_warn "║ MANUAL VERIFICATION REQUIRED ║"
|
||||||
|
log_warn "╚════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
log_warn "1. Check application: https://michaelschiemer.de"
|
||||||
|
log_warn "2. Run health check: cd deployment && ansible-playbook -i ansible/inventory/production.yml ansible/playbooks/health-check.yml"
|
||||||
|
log_warn "3. Check service logs: ssh deploy@${PRODUCTION_SERVER} 'docker service logs framework_web --tail 100'"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Interactive mode
|
||||||
|
interactive_rollback() {
|
||||||
|
log_info "🚨 Emergency Rollback - Interactive Mode"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
log_info "Available image tags (last 20):"
|
||||||
|
list_tags
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Enter image tag to rollback to: " -r target_tag
|
||||||
|
|
||||||
|
if [[ -z "$target_tag" ]]; then
|
||||||
|
log_error "No tag provided"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
emergency_rollback "$target_tag"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
main() {
|
||||||
|
case "${1:-interactive}" in
|
||||||
|
list)
|
||||||
|
list_tags
|
||||||
|
;;
|
||||||
|
interactive)
|
||||||
|
interactive_rollback
|
||||||
|
;;
|
||||||
|
help|--help|-h)
|
||||||
|
cat <<EOF
|
||||||
|
Emergency Rollback Script
|
||||||
|
|
||||||
|
Usage: $0 [command|tag]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
list List available image tags on production
|
||||||
|
interactive Interactive rollback mode (default)
|
||||||
|
<image-tag> Direct rollback to specific tag
|
||||||
|
help Show this help
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 list # List available versions
|
||||||
|
$0 # Interactive mode
|
||||||
|
$0 abc1234-123456 # Rollback to specific tag
|
||||||
|
|
||||||
|
Emergency Procedures:
|
||||||
|
1. List versions: $0 list
|
||||||
|
2. Choose version: $0 <tag>
|
||||||
|
3. Verify manually: https://michaelschiemer.de
|
||||||
|
4. Run health check: cd deployment && ansible-playbook -i ansible/inventory/production.yml ansible/playbooks/health-check.yml
|
||||||
|
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Direct rollback with provided tag
|
||||||
|
emergency_rollback "$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
160
.deployment-archive-20251030-111806/scripts/lib/ansible.sh
Executable file
160
.deployment-archive-20251030-111806/scripts/lib/ansible.sh
Executable file
@@ -0,0 +1,160 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Ansible Integration Library
|
||||||
|
# Provides helpers for Ansible operations
|
||||||
|
#
|
||||||
|
|
||||||
|
# Source common library
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./common.sh
|
||||||
|
source "${SCRIPT_DIR}/common.sh"
|
||||||
|
|
||||||
|
# Default Ansible paths
|
||||||
|
readonly ANSIBLE_DIR="${ANSIBLE_DIR:-${SCRIPT_DIR}/../../ansible}"
|
||||||
|
readonly ANSIBLE_INVENTORY="${ANSIBLE_INVENTORY:-${ANSIBLE_DIR}/inventory/production.yml}"
|
||||||
|
readonly ANSIBLE_PLAYBOOK_DIR="${ANSIBLE_PLAYBOOK_DIR:-${ANSIBLE_DIR}/playbooks}"
|
||||||
|
|
||||||
|
# Check Ansible installation
|
||||||
|
check_ansible() {
|
||||||
|
log_step "Checking Ansible installation..."
|
||||||
|
|
||||||
|
require_command "ansible" "sudo apt install ansible" || return 1
|
||||||
|
require_command "ansible-playbook" || return 1
|
||||||
|
|
||||||
|
local version
|
||||||
|
version=$(ansible --version | head -1)
|
||||||
|
log_success "Ansible installed: $version"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test Ansible connectivity
|
||||||
|
test_ansible_connectivity() {
|
||||||
|
local inventory="${1:-$ANSIBLE_INVENTORY}"
|
||||||
|
|
||||||
|
log_step "Testing Ansible connectivity..."
|
||||||
|
|
||||||
|
if ! ansible all -i "$inventory" -m ping &> /dev/null; then
|
||||||
|
log_error "Cannot connect to production server"
|
||||||
|
log_info "Check:"
|
||||||
|
log_info " - SSH key: ~/.ssh/production"
|
||||||
|
log_info " - Network connectivity"
|
||||||
|
log_info " - Server availability"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Connection successful"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run Ansible playbook
|
||||||
|
run_ansible_playbook() {
|
||||||
|
local playbook="$1"
|
||||||
|
shift
|
||||||
|
local extra_args=("$@")
|
||||||
|
|
||||||
|
log_step "Running Ansible playbook: $(basename "$playbook")"
|
||||||
|
|
||||||
|
# Build command
|
||||||
|
local cmd="ansible-playbook -i ${ANSIBLE_INVENTORY} ${playbook}"
|
||||||
|
|
||||||
|
# Add extra args
|
||||||
|
if [[ ${#extra_args[@]} -gt 0 ]]; then
|
||||||
|
cmd="${cmd} ${extra_args[*]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_debug "Command: $cmd"
|
||||||
|
|
||||||
|
# Execute with proper error handling
|
||||||
|
if eval "$cmd"; then
|
||||||
|
log_success "Playbook completed successfully"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
local exit_code=$?
|
||||||
|
log_error "Playbook failed with exit code $exit_code"
|
||||||
|
return $exit_code
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run deployment playbook
|
||||||
|
run_deployment() {
|
||||||
|
local git_repo_url="${1:-}"
|
||||||
|
local playbook="${ANSIBLE_PLAYBOOK_DIR}/deploy.yml"
|
||||||
|
|
||||||
|
if [[ ! -f "$playbook" ]]; then
|
||||||
|
log_error "Deployment playbook not found: $playbook"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_step "Starting deployment..."
|
||||||
|
|
||||||
|
local extra_args=()
|
||||||
|
if [[ -n "$git_repo_url" ]]; then
|
||||||
|
extra_args+=("-e" "git_repo_url=${git_repo_url}")
|
||||||
|
log_info "Git repository: $git_repo_url"
|
||||||
|
else
|
||||||
|
log_info "Using existing code on server"
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_ansible_playbook "$playbook" "${extra_args[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get Ansible facts
|
||||||
|
get_ansible_facts() {
|
||||||
|
local inventory="${1:-$ANSIBLE_INVENTORY}"
|
||||||
|
local host="${2:-production_server}"
|
||||||
|
|
||||||
|
ansible "$host" -i "$inventory" -m setup
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ansible dry-run
|
||||||
|
ansible_dry_run() {
|
||||||
|
local playbook="$1"
|
||||||
|
shift
|
||||||
|
local extra_args=("$@")
|
||||||
|
|
||||||
|
log_step "Running dry-run (check mode)..."
|
||||||
|
|
||||||
|
extra_args+=("--check" "--diff")
|
||||||
|
|
||||||
|
run_ansible_playbook "$playbook" "${extra_args[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# List Ansible hosts
|
||||||
|
list_ansible_hosts() {
|
||||||
|
local inventory="${1:-$ANSIBLE_INVENTORY}"
|
||||||
|
|
||||||
|
log_step "Listing Ansible hosts..."
|
||||||
|
|
||||||
|
ansible-inventory -i "$inventory" --list
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check playbook syntax
|
||||||
|
check_playbook_syntax() {
|
||||||
|
local playbook="$1"
|
||||||
|
|
||||||
|
log_step "Checking playbook syntax..."
|
||||||
|
|
||||||
|
if ansible-playbook --syntax-check "$playbook" &> /dev/null; then
|
||||||
|
log_success "Syntax check passed"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "Syntax check failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute Ansible ad-hoc command
|
||||||
|
ansible_adhoc() {
|
||||||
|
local host="$1"
|
||||||
|
local module="$2"
|
||||||
|
shift 2
|
||||||
|
local args=("$@")
|
||||||
|
|
||||||
|
log_step "Running ad-hoc command on $host..."
|
||||||
|
|
||||||
|
ansible "$host" -i "$ANSIBLE_INVENTORY" -m "$module" -a "${args[*]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Export functions
|
||||||
|
export -f check_ansible test_ansible_connectivity run_ansible_playbook
|
||||||
|
export -f run_deployment get_ansible_facts ansible_dry_run
|
||||||
|
export -f list_ansible_hosts check_playbook_syntax ansible_adhoc
|
||||||
215
.deployment-archive-20251030-111806/scripts/lib/common.sh
Executable file
215
.deployment-archive-20251030-111806/scripts/lib/common.sh
Executable file
@@ -0,0 +1,215 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Common Library Functions for Deployment Scripts
|
||||||
|
# Provides unified logging, error handling, and utilities
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
readonly RED='\033[0;31m'
|
||||||
|
readonly GREEN='\033[0;32m'
|
||||||
|
readonly YELLOW='\033[1;33m'
|
||||||
|
readonly BLUE='\033[0;34m'
|
||||||
|
readonly CYAN='\033[0;36m'
|
||||||
|
readonly MAGENTA='\033[0;35m'
|
||||||
|
readonly NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Logging functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}ℹ️ ${1}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}✅ ${1}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}⚠️ ${1}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}❌ ${1}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug() {
|
||||||
|
if [[ "${DEBUG:-0}" == "1" ]]; then
|
||||||
|
echo -e "${CYAN}🔍 ${1}${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${MAGENTA}▶️ ${1}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error handling
|
||||||
|
die() {
|
||||||
|
log_error "$1"
|
||||||
|
exit "${2:-1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if command exists
|
||||||
|
command_exists() {
|
||||||
|
command -v "$1" &> /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate prerequisites
|
||||||
|
require_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
local install_hint="${2:-}"
|
||||||
|
|
||||||
|
if ! command_exists "$cmd"; then
|
||||||
|
log_error "Required command not found: $cmd"
|
||||||
|
[[ -n "$install_hint" ]] && log_info "Install with: $install_hint"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run command with retry logic
|
||||||
|
run_with_retry() {
|
||||||
|
local max_attempts="${1}"
|
||||||
|
local delay="${2}"
|
||||||
|
shift 2
|
||||||
|
local cmd=("$@")
|
||||||
|
local attempt=1
|
||||||
|
|
||||||
|
while [[ $attempt -le $max_attempts ]]; do
|
||||||
|
if "${cmd[@]}"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $attempt -lt $max_attempts ]]; then
|
||||||
|
log_warning "Command failed (attempt $attempt/$max_attempts). Retrying in ${delay}s..."
|
||||||
|
sleep "$delay"
|
||||||
|
fi
|
||||||
|
|
||||||
|
((attempt++))
|
||||||
|
done
|
||||||
|
|
||||||
|
log_error "Command failed after $max_attempts attempts"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute command and capture output
|
||||||
|
execute() {
|
||||||
|
local cmd="$1"
|
||||||
|
log_debug "Executing: $cmd"
|
||||||
|
eval "$cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Spinner for long-running operations
|
||||||
|
spinner() {
|
||||||
|
local pid=$1
|
||||||
|
local delay=0.1
|
||||||
|
local spinstr='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
|
||||||
|
|
||||||
|
while ps -p "$pid" > /dev/null 2>&1; do
|
||||||
|
local temp=${spinstr#?}
|
||||||
|
printf " [%c] " "$spinstr"
|
||||||
|
local spinstr=$temp${spinstr%"$temp"}
|
||||||
|
sleep $delay
|
||||||
|
printf "\b\b\b\b\b\b"
|
||||||
|
done
|
||||||
|
printf " \b\b\b\b"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Progress bar
|
||||||
|
progress_bar() {
|
||||||
|
local current=$1
|
||||||
|
local total=$2
|
||||||
|
local width=50
|
||||||
|
local percentage=$((current * 100 / total))
|
||||||
|
local completed=$((width * current / total))
|
||||||
|
local remaining=$((width - completed))
|
||||||
|
|
||||||
|
printf "\r["
|
||||||
|
printf "%${completed}s" | tr ' ' '█'
|
||||||
|
printf "%${remaining}s" | tr ' ' '░'
|
||||||
|
printf "] %3d%%" "$percentage"
|
||||||
|
|
||||||
|
if [[ $current -eq $total ]]; then
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Confirm action
|
||||||
|
confirm() {
|
||||||
|
local prompt="${1:-Are you sure?}"
|
||||||
|
local default="${2:-n}"
|
||||||
|
|
||||||
|
if [[ "$default" == "y" ]]; then
|
||||||
|
prompt="$prompt [Y/n] "
|
||||||
|
else
|
||||||
|
prompt="$prompt [y/N] "
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -rp "$prompt" response
|
||||||
|
response=${response:-$default}
|
||||||
|
|
||||||
|
[[ "$response" =~ ^[Yy]$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse YAML-like config
|
||||||
|
parse_config() {
|
||||||
|
local config_file="$1"
|
||||||
|
local key="$2"
|
||||||
|
|
||||||
|
if [[ ! -f "$config_file" ]]; then
|
||||||
|
log_error "Config file not found: $config_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
grep "^${key}:" "$config_file" | sed "s/^${key}:[[:space:]]*//" | tr -d '"'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Timestamp functions
|
||||||
|
timestamp() {
|
||||||
|
date '+%Y-%m-%d %H:%M:%S'
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp_file() {
|
||||||
|
date '+%Y%m%d_%H%M%S'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Duration calculation
|
||||||
|
duration() {
|
||||||
|
local start=$1
|
||||||
|
local end=${2:-$(date +%s)}
|
||||||
|
local elapsed=$((end - start))
|
||||||
|
|
||||||
|
local hours=$((elapsed / 3600))
|
||||||
|
local minutes=$(((elapsed % 3600) / 60))
|
||||||
|
local seconds=$((elapsed % 60))
|
||||||
|
|
||||||
|
if [[ $hours -gt 0 ]]; then
|
||||||
|
printf "%dh %dm %ds" "$hours" "$minutes" "$seconds"
|
||||||
|
elif [[ $minutes -gt 0 ]]; then
|
||||||
|
printf "%dm %ds" "$minutes" "$seconds"
|
||||||
|
else
|
||||||
|
printf "%ds" "$seconds"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup handler
|
||||||
|
cleanup_handlers=()
|
||||||
|
|
||||||
|
register_cleanup() {
|
||||||
|
cleanup_handlers+=("$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
log_info "Running cleanup handlers..."
|
||||||
|
for handler in "${cleanup_handlers[@]}"; do
|
||||||
|
eval "$handler" || log_warning "Cleanup handler failed: $handler"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
# Export functions for use in other scripts
|
||||||
|
export -f log_info log_success log_warning log_error log_debug log_step
|
||||||
|
export -f die command_exists require_command run_with_retry execute
|
||||||
|
export -f spinner progress_bar confirm parse_config
|
||||||
|
export -f timestamp timestamp_file duration
|
||||||
|
export -f register_cleanup cleanup
|
||||||
184
.deployment-archive-20251030-111806/scripts/manual-deploy-fallback.sh
Executable file
184
.deployment-archive-20251030-111806/scripts/manual-deploy-fallback.sh
Executable file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Manual Deployment Fallback Script
|
||||||
|
# Purpose: Deploy manually when Gitea Actions is unavailable
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/manual-deploy-fallback.sh [branch] # Deploy specific branch
|
||||||
|
# ./scripts/manual-deploy-fallback.sh # Deploy current branch
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
ANSIBLE_DIR="${PROJECT_ROOT}/deployment/ansible"
|
||||||
|
INVENTORY="${ANSIBLE_DIR}/inventory/production.yml"
|
||||||
|
|
||||||
|
PRODUCTION_SERVER="94.16.110.151"
|
||||||
|
REGISTRY="git.michaelschiemer.de:5000"
|
||||||
|
IMAGE="framework"
|
||||||
|
BRANCH="${1:-$(git rev-parse --abbrev-ref HEAD)}"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${BLUE}[STEP]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
check_prerequisites() {
|
||||||
|
log_step "Checking prerequisites..."
|
||||||
|
|
||||||
|
# Check if git is clean
|
||||||
|
if [[ -n $(git status --porcelain) ]]; then
|
||||||
|
log_error "Git working directory is not clean. Commit or stash changes first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if ansible is installed
|
||||||
|
if ! command -v ansible-playbook &> /dev/null; then
|
||||||
|
log_error "ansible-playbook not found. Install Ansible first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if docker is available
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
log_error "docker not found. Install Docker first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check SSH access to production server
|
||||||
|
if ! ssh -i ~/.ssh/production deploy@"${PRODUCTION_SERVER}" "echo 'SSH OK'" &> /dev/null; then
|
||||||
|
log_error "Cannot SSH to production server. Check your SSH key."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Prerequisites check passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build Docker image locally
|
||||||
|
build_image() {
|
||||||
|
log_step "Building Docker image for branch: ${BRANCH}"
|
||||||
|
|
||||||
|
cd "${PROJECT_ROOT}"
|
||||||
|
|
||||||
|
# Checkout branch
|
||||||
|
git checkout "${BRANCH}"
|
||||||
|
git pull origin "${BRANCH}"
|
||||||
|
|
||||||
|
# Get commit SHA
|
||||||
|
COMMIT_SHA=$(git rev-parse --short HEAD)
|
||||||
|
IMAGE_TAG="${COMMIT_SHA}-$(date +%s)"
|
||||||
|
|
||||||
|
log_info "Building image with tag: ${IMAGE_TAG}"
|
||||||
|
|
||||||
|
# Build image
|
||||||
|
docker build \
|
||||||
|
--file Dockerfile.production \
|
||||||
|
--tag "${REGISTRY}/${IMAGE}:${IMAGE_TAG}" \
|
||||||
|
--tag "${REGISTRY}/${IMAGE}:latest" \
|
||||||
|
--build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
|
||||||
|
--build-arg VCS_REF="${COMMIT_SHA}" \
|
||||||
|
.
|
||||||
|
|
||||||
|
log_info "Image built successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Push image to registry
|
||||||
|
push_image() {
|
||||||
|
log_step "Pushing image to registry..."
|
||||||
|
|
||||||
|
# Login to registry (prompt for password if needed)
|
||||||
|
log_info "Logging in to registry..."
|
||||||
|
docker login "${REGISTRY}"
|
||||||
|
|
||||||
|
# Push image
|
||||||
|
docker push "${REGISTRY}/${IMAGE}:${IMAGE_TAG}"
|
||||||
|
docker push "${REGISTRY}/${IMAGE}:latest"
|
||||||
|
|
||||||
|
log_info "Image pushed successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy via Ansible
|
||||||
|
deploy_ansible() {
|
||||||
|
log_step "Deploying via Ansible..."
|
||||||
|
|
||||||
|
cd "${ANSIBLE_DIR}"
|
||||||
|
|
||||||
|
ansible-playbook \
|
||||||
|
-i "${INVENTORY}" \
|
||||||
|
playbooks/deploy-update.yml \
|
||||||
|
-e "image_tag=${IMAGE_TAG}" \
|
||||||
|
-e "git_commit_sha=${COMMIT_SHA}"
|
||||||
|
|
||||||
|
log_info "Ansible deployment completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run health checks
|
||||||
|
run_health_checks() {
|
||||||
|
log_step "Running health checks..."
|
||||||
|
|
||||||
|
cd "${ANSIBLE_DIR}"
|
||||||
|
|
||||||
|
ansible-playbook \
|
||||||
|
-i "${INVENTORY}" \
|
||||||
|
playbooks/health-check.yml
|
||||||
|
|
||||||
|
log_info "Health checks passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main deployment flow
|
||||||
|
main() {
|
||||||
|
echo ""
|
||||||
|
log_warn "╔════════════════════════════════════════════════════════╗"
|
||||||
|
log_warn "║ MANUAL DEPLOYMENT FALLBACK (No Gitea Actions) ║"
|
||||||
|
log_warn "╚════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
log_info "Branch: ${BRANCH}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
read -p "Continue with manual deployment? (yes/no): " -r
|
||||||
|
if [[ ! "$REPLY" =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||||
|
log_info "Deployment cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_prerequisites
|
||||||
|
build_image
|
||||||
|
push_image
|
||||||
|
deploy_ansible
|
||||||
|
run_health_checks
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_warn "╔════════════════════════════════════════════════════════╗"
|
||||||
|
log_warn "║ MANUAL DEPLOYMENT COMPLETED ║"
|
||||||
|
log_warn "╚════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
log_info "Deployed: ${REGISTRY}/${IMAGE}:${IMAGE_TAG}"
|
||||||
|
log_info "Commit: ${COMMIT_SHA}"
|
||||||
|
log_info "Branch: ${BRANCH}"
|
||||||
|
echo ""
|
||||||
|
log_info "Verify deployment: https://michaelschiemer.de"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
230
.deployment-archive-20251030-111806/scripts/service-recovery.sh
Executable file
230
.deployment-archive-20251030-111806/scripts/service-recovery.sh
Executable file
@@ -0,0 +1,230 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Service Recovery Script
|
||||||
|
# Purpose: Quick recovery for common service failures
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/service-recovery.sh status # Check service status
|
||||||
|
# ./scripts/service-recovery.sh restart # Restart services
|
||||||
|
# ./scripts/service-recovery.sh recover # Full recovery procedure
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
|
||||||
|
PRODUCTION_SERVER="94.16.110.151"
|
||||||
|
STACK_NAME="framework"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
echo -e "${BLUE}[STEP]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# SSH helper
|
||||||
|
ssh_exec() {
|
||||||
|
ssh -i ~/.ssh/production deploy@"${PRODUCTION_SERVER}" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check service status
|
||||||
|
check_status() {
|
||||||
|
log_step "Checking service status..."
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "Docker Swarm Services:"
|
||||||
|
ssh_exec "docker service ls --filter 'name=${STACK_NAME}'"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "Web Service Details:"
|
||||||
|
ssh_exec "docker service ps ${STACK_NAME}_web --no-trunc"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "Queue Worker Details:"
|
||||||
|
ssh_exec "docker service ps ${STACK_NAME}_queue-worker --no-trunc"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "Service Logs (last 50 lines):"
|
||||||
|
ssh_exec "docker service logs ${STACK_NAME}_web --tail 50"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart services
|
||||||
|
restart_services() {
|
||||||
|
log_step "Restarting services..."
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_warn "This will restart all framework services"
|
||||||
|
read -p "Continue? (yes/no): " -r
|
||||||
|
|
||||||
|
if [[ ! "$REPLY" =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||||
|
log_info "Restart cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restart web service
|
||||||
|
log_info "Restarting web service..."
|
||||||
|
ssh_exec "docker service update --force ${STACK_NAME}_web"
|
||||||
|
|
||||||
|
# Restart worker service
|
||||||
|
log_info "Restarting queue worker..."
|
||||||
|
ssh_exec "docker service update --force ${STACK_NAME}_queue-worker"
|
||||||
|
|
||||||
|
# Wait for services to stabilize
|
||||||
|
log_info "Waiting for services to stabilize (30 seconds)..."
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
check_status
|
||||||
|
}
|
||||||
|
|
||||||
|
# Full recovery procedure
|
||||||
|
full_recovery() {
|
||||||
|
log_step "Running full recovery procedure..."
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_warn "╔════════════════════════════════════════════════════════╗"
|
||||||
|
log_warn "║ FULL SERVICE RECOVERY PROCEDURE ║"
|
||||||
|
log_warn "╚════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 1: Check current status
|
||||||
|
log_info "Step 1/5: Check current status"
|
||||||
|
check_status
|
||||||
|
|
||||||
|
# Step 2: Check Docker Swarm health
|
||||||
|
log_info "Step 2/5: Check Docker Swarm health"
|
||||||
|
SWARM_STATUS=$(ssh_exec "docker info | grep 'Swarm: active' || echo 'inactive'")
|
||||||
|
|
||||||
|
if [[ "$SWARM_STATUS" == "inactive" ]]; then
|
||||||
|
log_error "Docker Swarm is not active!"
|
||||||
|
log_info "Attempting to reinitialize Swarm..."
|
||||||
|
ssh_exec "docker swarm init --advertise-addr ${PRODUCTION_SERVER}" || true
|
||||||
|
else
|
||||||
|
log_info "Docker Swarm is active"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 3: Verify network and volumes
|
||||||
|
log_info "Step 3/5: Verify Docker resources"
|
||||||
|
ssh_exec "docker network ls | grep ${STACK_NAME} || docker network create --driver overlay ${STACK_NAME}_network"
|
||||||
|
|
||||||
|
# Step 4: Restart services
|
||||||
|
log_info "Step 4/5: Restart services"
|
||||||
|
ssh_exec "docker service update --force ${STACK_NAME}_web"
|
||||||
|
ssh_exec "docker service update --force ${STACK_NAME}_queue-worker"
|
||||||
|
|
||||||
|
log_info "Waiting for services to stabilize (45 seconds)..."
|
||||||
|
sleep 45
|
||||||
|
|
||||||
|
# Step 5: Health check
|
||||||
|
log_info "Step 5/5: Run health checks"
|
||||||
|
|
||||||
|
HEALTH_CHECK=$(curl -f -k https://michaelschiemer.de/health 2>/dev/null && echo "OK" || echo "FAILED")
|
||||||
|
|
||||||
|
if [[ "$HEALTH_CHECK" == "OK" ]]; then
|
||||||
|
log_info "✅ Health check passed"
|
||||||
|
else
|
||||||
|
log_error "❌ Health check failed"
|
||||||
|
log_warn "Manual intervention may be required"
|
||||||
|
log_warn "Check logs: ssh deploy@${PRODUCTION_SERVER} 'docker service logs ${STACK_NAME}_web --tail 100'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_warn "╔════════════════════════════════════════════════════════╗"
|
||||||
|
log_warn "║ RECOVERY PROCEDURE COMPLETED ║"
|
||||||
|
log_warn "╚════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
log_info "Application: https://michaelschiemer.de"
|
||||||
|
log_info "Services recovered successfully"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear caches
|
||||||
|
clear_caches() {
|
||||||
|
log_step "Clearing application caches..."
|
||||||
|
|
||||||
|
# Clear Redis cache
|
||||||
|
log_info "Clearing Redis cache..."
|
||||||
|
ssh_exec "docker exec \$(docker ps -q -f name=${STACK_NAME}_redis) redis-cli FLUSHALL" || log_warn "Redis cache clear failed"
|
||||||
|
|
||||||
|
# Clear file caches
|
||||||
|
log_info "Clearing file caches..."
|
||||||
|
ssh_exec "docker exec \$(docker ps -q -f name=${STACK_NAME}_web | head -1) rm -rf /var/www/html/storage/cache/*" || log_warn "File cache clear failed"
|
||||||
|
|
||||||
|
log_info "Caches cleared"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show help
|
||||||
|
show_help() {
|
||||||
|
cat <<EOF
|
||||||
|
Service Recovery Script
|
||||||
|
|
||||||
|
Usage: $0 [command]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
status Check service status and logs
|
||||||
|
restart Restart all services
|
||||||
|
recover Run full recovery procedure (recommended)
|
||||||
|
clear-cache Clear application caches
|
||||||
|
help Show this help
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 status # Quick status check
|
||||||
|
$0 recover # Full automated recovery
|
||||||
|
$0 restart # Just restart services
|
||||||
|
$0 clear-cache # Clear caches only
|
||||||
|
|
||||||
|
Emergency Recovery:
|
||||||
|
1. Check status: $0 status
|
||||||
|
2. Run recovery: $0 recover
|
||||||
|
3. If still failing, check logs manually:
|
||||||
|
ssh deploy@${PRODUCTION_SERVER} 'docker service logs ${STACK_NAME}_web --tail 200'
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main
|
||||||
|
main() {
|
||||||
|
case "${1:-help}" in
|
||||||
|
status)
|
||||||
|
check_status
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
restart_services
|
||||||
|
;;
|
||||||
|
recover)
|
||||||
|
full_recovery
|
||||||
|
;;
|
||||||
|
clear-cache)
|
||||||
|
clear_caches
|
||||||
|
;;
|
||||||
|
help|--help|-h)
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown command: $1"
|
||||||
|
show_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
262
.deployment-archive-20251030-111806/scripts/setup-production-secrets.sh
Executable file
262
.deployment-archive-20251030-111806/scripts/setup-production-secrets.sh
Executable file
@@ -0,0 +1,262 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Production Secrets Setup Script
|
||||||
|
# Purpose: Initialize and manage production secrets with Ansible Vault
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/setup-production-secrets.sh init # Initialize new vault
|
||||||
|
# ./scripts/setup-production-secrets.sh deploy # Deploy secrets to production
|
||||||
|
# ./scripts/setup-production-secrets.sh rotate # Rotate secrets
|
||||||
|
# ./scripts/setup-production-secrets.sh verify # Verify secrets on server
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||||
|
ANSIBLE_DIR="${PROJECT_ROOT}/deployment/ansible"
|
||||||
|
VAULT_FILE="${ANSIBLE_DIR}/secrets/production-vault.yml"
|
||||||
|
INVENTORY="${ANSIBLE_DIR}/inventory/production.yml"
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Logging functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
check_prerequisites() {
|
||||||
|
log_info "Checking prerequisites..."
|
||||||
|
|
||||||
|
if ! command -v ansible-vault &> /dev/null; then
|
||||||
|
log_error "ansible-vault not found. Please install Ansible."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v openssl &> /dev/null; then
|
||||||
|
log_error "openssl not found. Please install OpenSSL."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Prerequisites OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate secure random password
|
||||||
|
generate_password() {
|
||||||
|
local length="${1:-32}"
|
||||||
|
openssl rand -base64 "$length" | tr -d "=+/" | cut -c1-"$length"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate base64 encoded app key
|
||||||
|
generate_app_key() {
|
||||||
|
openssl rand -base64 32
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize vault with secure defaults
|
||||||
|
init_vault() {
|
||||||
|
log_info "Initializing production secrets vault..."
|
||||||
|
|
||||||
|
if [[ -f "$VAULT_FILE" ]]; then
|
||||||
|
log_warn "Vault file already exists: $VAULT_FILE"
|
||||||
|
read -p "Do you want to overwrite it? (yes/no): " -r
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]es$ ]]; then
|
||||||
|
log_info "Aborting initialization"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate secure secrets
|
||||||
|
log_info "Generating secure secrets..."
|
||||||
|
DB_PASSWORD=$(generate_password 32)
|
||||||
|
REDIS_PASSWORD=$(generate_password 32)
|
||||||
|
APP_KEY=$(generate_app_key)
|
||||||
|
JWT_SECRET=$(generate_password 64)
|
||||||
|
REGISTRY_PASSWORD=$(generate_password 24)
|
||||||
|
|
||||||
|
# Create vault file
|
||||||
|
cat > "$VAULT_FILE" <<EOF
|
||||||
|
---
|
||||||
|
# Production Secrets Vault
|
||||||
|
# Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
|
||||||
|
# Database Credentials
|
||||||
|
vault_db_name: framework_production
|
||||||
|
vault_db_user: framework_app
|
||||||
|
vault_db_password: ${DB_PASSWORD}
|
||||||
|
|
||||||
|
# Redis Credentials
|
||||||
|
vault_redis_password: ${REDIS_PASSWORD}
|
||||||
|
|
||||||
|
# Application Secrets
|
||||||
|
vault_app_key: ${APP_KEY}
|
||||||
|
vault_jwt_secret: ${JWT_SECRET}
|
||||||
|
|
||||||
|
# Docker Registry Credentials
|
||||||
|
vault_registry_url: git.michaelschiemer.de:5000
|
||||||
|
vault_registry_user: deploy
|
||||||
|
vault_registry_password: ${REGISTRY_PASSWORD}
|
||||||
|
|
||||||
|
# Security Configuration
|
||||||
|
vault_admin_allowed_ips: "127.0.0.1,::1,94.16.110.151"
|
||||||
|
|
||||||
|
# SMTP Configuration (update these manually)
|
||||||
|
vault_smtp_host: smtp.example.com
|
||||||
|
vault_smtp_port: 587
|
||||||
|
vault_smtp_user: noreply@michaelschiemer.de
|
||||||
|
vault_smtp_password: CHANGE_ME_SMTP_PASSWORD_HERE
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_info "Vault file created with generated secrets"
|
||||||
|
log_warn "IMPORTANT: Update SMTP credentials manually if needed"
|
||||||
|
|
||||||
|
# Encrypt vault
|
||||||
|
log_info "Encrypting vault file..."
|
||||||
|
ansible-vault encrypt "$VAULT_FILE"
|
||||||
|
|
||||||
|
log_info "✅ Vault initialized successfully"
|
||||||
|
log_warn "Store the vault password securely (e.g., in password manager)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy secrets to production
|
||||||
|
deploy_secrets() {
|
||||||
|
log_info "Deploying secrets to production..."
|
||||||
|
|
||||||
|
if [[ ! -f "$VAULT_FILE" ]]; then
|
||||||
|
log_error "Vault file not found: $VAULT_FILE"
|
||||||
|
log_error "Run './setup-production-secrets.sh init' first"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$ANSIBLE_DIR"
|
||||||
|
|
||||||
|
log_info "Running Ansible playbook..."
|
||||||
|
ansible-playbook \
|
||||||
|
-i "$INVENTORY" \
|
||||||
|
playbooks/setup-production-secrets.yml \
|
||||||
|
--ask-vault-pass
|
||||||
|
|
||||||
|
log_info "✅ Secrets deployed successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rotate secrets (regenerate and redeploy)
|
||||||
|
rotate_secrets() {
|
||||||
|
log_warn "⚠️ Secret rotation will:"
|
||||||
|
log_warn " 1. Generate new passwords/keys"
|
||||||
|
log_warn " 2. Update vault file"
|
||||||
|
log_warn " 3. Deploy to production"
|
||||||
|
log_warn " 4. Restart services"
|
||||||
|
log_warn ""
|
||||||
|
read -p "Continue with rotation? (yes/no): " -r
|
||||||
|
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]es$ ]]; then
|
||||||
|
log_info "Rotation cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup current vault
|
||||||
|
BACKUP_FILE="${VAULT_FILE}.backup.$(date +%Y%m%d_%H%M%S)"
|
||||||
|
log_info "Creating backup: $BACKUP_FILE"
|
||||||
|
cp "$VAULT_FILE" "$BACKUP_FILE"
|
||||||
|
|
||||||
|
# Decrypt vault
|
||||||
|
log_info "Decrypting vault..."
|
||||||
|
ansible-vault decrypt "$VAULT_FILE"
|
||||||
|
|
||||||
|
# Generate new secrets
|
||||||
|
log_info "Generating new secrets..."
|
||||||
|
DB_PASSWORD=$(generate_password 32)
|
||||||
|
REDIS_PASSWORD=$(generate_password 32)
|
||||||
|
APP_KEY=$(generate_app_key)
|
||||||
|
JWT_SECRET=$(generate_password 64)
|
||||||
|
|
||||||
|
# Update vault file (keep registry password)
|
||||||
|
sed -i "s/vault_db_password: .*/vault_db_password: ${DB_PASSWORD}/" "$VAULT_FILE"
|
||||||
|
sed -i "s/vault_redis_password: .*/vault_redis_password: ${REDIS_PASSWORD}/" "$VAULT_FILE"
|
||||||
|
sed -i "s/vault_app_key: .*/vault_app_key: ${APP_KEY}/" "$VAULT_FILE"
|
||||||
|
sed -i "s/vault_jwt_secret: .*/vault_jwt_secret: ${JWT_SECRET}/" "$VAULT_FILE"
|
||||||
|
|
||||||
|
# Re-encrypt vault
|
||||||
|
log_info "Re-encrypting vault..."
|
||||||
|
ansible-vault encrypt "$VAULT_FILE"
|
||||||
|
|
||||||
|
log_info "✅ Secrets rotated"
|
||||||
|
log_info "Backup saved to: $BACKUP_FILE"
|
||||||
|
|
||||||
|
# Deploy rotated secrets
|
||||||
|
deploy_secrets
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify secrets on server
|
||||||
|
verify_secrets() {
|
||||||
|
log_info "Verifying secrets on production server..."
|
||||||
|
|
||||||
|
cd "$ANSIBLE_DIR"
|
||||||
|
|
||||||
|
ansible production_server \
|
||||||
|
-i "$INVENTORY" \
|
||||||
|
-m shell \
|
||||||
|
-a "docker secret ls"
|
||||||
|
|
||||||
|
log_info "Checking environment file..."
|
||||||
|
ansible production_server \
|
||||||
|
-i "$INVENTORY" \
|
||||||
|
-m stat \
|
||||||
|
-a "path=/home/deploy/secrets/.env.production"
|
||||||
|
|
||||||
|
log_info "✅ Verification complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main command dispatcher
|
||||||
|
main() {
|
||||||
|
check_prerequisites
|
||||||
|
|
||||||
|
case "${1:-help}" in
|
||||||
|
init)
|
||||||
|
init_vault
|
||||||
|
;;
|
||||||
|
deploy)
|
||||||
|
deploy_secrets
|
||||||
|
;;
|
||||||
|
rotate)
|
||||||
|
rotate_secrets
|
||||||
|
;;
|
||||||
|
verify)
|
||||||
|
verify_secrets
|
||||||
|
;;
|
||||||
|
help|*)
|
||||||
|
cat <<EOF
|
||||||
|
Production Secrets Management
|
||||||
|
|
||||||
|
Usage: $0 <command>
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
init Initialize new secrets vault with auto-generated secure values
|
||||||
|
deploy Deploy secrets from vault to production server
|
||||||
|
rotate Rotate secrets (generate new values and redeploy)
|
||||||
|
verify Verify secrets are properly deployed on server
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 init # First time setup
|
||||||
|
$0 deploy # Deploy after manual vault updates
|
||||||
|
$0 rotate # Monthly security rotation
|
||||||
|
$0 verify # Check deployment status
|
||||||
|
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# Deployment Backup Summary
|
|
||||||
|
|
||||||
This directory contains the old deployment configurations that were moved during the modernization of the deployment system.
|
|
||||||
|
|
||||||
## Moved Directories
|
|
||||||
|
|
||||||
### ansible/
|
|
||||||
**Original Location**: `/ansible/`
|
|
||||||
**Contents**: Multiple Ansible deployment configurations
|
|
||||||
- `netcup-simple-deploy/` - Basic server deployment setup
|
|
||||||
- `nginx-cdn-germany/` - CDN and Nginx configuration for German servers
|
|
||||||
- `wireguard-server/` - VPN server setup and client management
|
|
||||||
|
|
||||||
**Key Features Preserved**:
|
|
||||||
- Server setup automation
|
|
||||||
- Nginx reverse proxy configuration
|
|
||||||
- SSL certificate management
|
|
||||||
- Multi-environment support (staging/production)
|
|
||||||
|
|
||||||
### x_ansible/
|
|
||||||
**Original Location**: `/x_ansible/`
|
|
||||||
**Contents**: Alternative Ansible setup with different structure
|
|
||||||
- Complete playbook structure with inventories
|
|
||||||
- Docker Compose integration
|
|
||||||
- Environment-specific configurations
|
|
||||||
- Deployment and setup scripts
|
|
||||||
|
|
||||||
### ssl/
|
|
||||||
**Original Location**: `/ssl/`
|
|
||||||
**Contents**: SSL certificates and keys for local development
|
|
||||||
- `fullchain.pem` - Certificate chain
|
|
||||||
- `privkey.pem` - Private key
|
|
||||||
- `rootCA.*` - Root certificate authority files
|
|
||||||
- `localhost.*` - Local development certificates
|
|
||||||
|
|
||||||
**Note**: These certificates will be integrated into the new SSL management system in `deployment/configs/ssl/`
|
|
||||||
|
|
||||||
### bin/
|
|
||||||
**Original Location**: `/bin/`
|
|
||||||
**Contents**: Deployment utility scripts
|
|
||||||
- `deploy` - Environment-specific deployment script
|
|
||||||
- `setup` - Server setup script
|
|
||||||
- `up`, `down`, `restart` - Docker management scripts
|
|
||||||
- `logs`, `test`, `check-env` - Utility scripts
|
|
||||||
|
|
||||||
**Note**: Functionality from these scripts will be modernized and integrated into `deployment/scripts/`
|
|
||||||
|
|
||||||
## Migration Path
|
|
||||||
|
|
||||||
The new deployment system in `/deployment/` consolidates and modernizes these configurations:
|
|
||||||
|
|
||||||
1. **Infrastructure** (`/deployment/infrastructure/`):
|
|
||||||
- Consolidates Ansible playbooks from both `ansible/` directories
|
|
||||||
- Adds modern server configuration management
|
|
||||||
- Implements security best practices
|
|
||||||
|
|
||||||
2. **Applications** (`/deployment/applications/`):
|
|
||||||
- Modernizes Docker Compose configurations
|
|
||||||
- Adds environment-specific optimizations
|
|
||||||
- Integrates health checking and monitoring
|
|
||||||
|
|
||||||
3. **Scripts** (`/deployment/scripts/`):
|
|
||||||
- Modernizes and consolidates utility scripts from `bin/`
|
|
||||||
- Adds deployment orchestration capabilities
|
|
||||||
- Implements rollback and recovery features
|
|
||||||
|
|
||||||
4. **Configs** (`/deployment/configs/`):
|
|
||||||
- Centralizes configuration templates
|
|
||||||
- Integrates SSL certificate management
|
|
||||||
- Adds monitoring and logging configurations
|
|
||||||
|
|
||||||
## Recovery Instructions
|
|
||||||
|
|
||||||
If you need to revert to the old deployment system:
|
|
||||||
|
|
||||||
1. Stop any new deployment processes
|
|
||||||
2. Move directories back from `.deployment-backup/` to their original locations:
|
|
||||||
```bash
|
|
||||||
mv .deployment-backup/ansible ./
|
|
||||||
mv .deployment-backup/x_ansible ./
|
|
||||||
mv .deployment-backup/ssl ./
|
|
||||||
mv .deployment-backup/bin ./
|
|
||||||
```
|
|
||||||
3. Update any references to the new deployment system
|
|
||||||
|
|
||||||
## Preservation Notes
|
|
||||||
|
|
||||||
- All original files are preserved unchanged
|
|
||||||
- Directory structure maintained as-is
|
|
||||||
- No modifications made to original configurations
|
|
||||||
- Can be used for reference during new system development
|
|
||||||
|
|
||||||
## Cleanup
|
|
||||||
|
|
||||||
Once the new deployment system is fully tested and deployed, this backup directory can be removed. Recommended timeline: Keep for at least 30 days after successful production deployment of the new system.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# .gitignore für Netcup Deployment
|
|
||||||
*.retry
|
|
||||||
.ansible/
|
|
||||||
*.log
|
|
||||||
.env.local
|
|
||||||
secrets.yml
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# Test Makefile für rsync debugging (Fixed)
|
|
||||||
|
|
||||||
.PHONY: test-rsync debug-sync upload restart quick-deploy
|
|
||||||
|
|
||||||
# Teste manuellen rsync
|
|
||||||
test-rsync:
|
|
||||||
@echo "🔍 Testing manual rsync..."
|
|
||||||
@SERVER_IP=$$(grep ansible_host inventory/hosts.yml | awk '{print $$2}'); \
|
|
||||||
echo "Server IP: $$SERVER_IP"; \
|
|
||||||
APP_PATH=$$(grep local_app_path inventory/hosts.yml | awk '{print $$2}' | tr -d '"'); \
|
|
||||||
echo "Local path: $$APP_PATH"; \
|
|
||||||
echo ""; \
|
|
||||||
echo "=== Testing dry-run rsync ==="; \
|
|
||||||
rsync -av --dry-run \
|
|
||||||
--exclude='ansible' \
|
|
||||||
--exclude='.git' \
|
|
||||||
--exclude='vendor' \
|
|
||||||
--exclude='node_modules' \
|
|
||||||
--exclude='storage/logs' \
|
|
||||||
--exclude='cache' \
|
|
||||||
--exclude='logs' \
|
|
||||||
--exclude='dist' \
|
|
||||||
--exclude='.archive' \
|
|
||||||
$$APP_PATH/ root@$$SERVER_IP:/opt/myapp/; \
|
|
||||||
echo ""; \
|
|
||||||
echo "If this shows files, then rsync should work"
|
|
||||||
|
|
||||||
# Debug was in lokalen Dateien ist
|
|
||||||
debug-local:
|
|
||||||
@echo "📁 Local files debug:"
|
|
||||||
@APP_PATH=$$(grep local_app_path inventory/hosts.yml | awk '{print $$2}' | tr -d '"'); \
|
|
||||||
echo "Path: $$APP_PATH"; \
|
|
||||||
echo ""; \
|
|
||||||
if [ -z "$$APP_PATH" ]; then \
|
|
||||||
echo "❌ APP_PATH is empty!"; \
|
|
||||||
echo "Raw line from hosts.yml:"; \
|
|
||||||
grep local_app_path inventory/hosts.yml; \
|
|
||||||
exit 1; \
|
|
||||||
fi; \
|
|
||||||
echo "=== Root files ==="; \
|
|
||||||
ls -la "$$APP_PATH" | head -10; \
|
|
||||||
echo ""; \
|
|
||||||
echo "=== Public files ==="; \
|
|
||||||
ls -la "$$APP_PATH/public" | head -10; \
|
|
||||||
echo ""; \
|
|
||||||
echo "=== Does index.php exist locally? ==="; \
|
|
||||||
if [ -f "$$APP_PATH/public/index.php" ]; then \
|
|
||||||
echo "✅ index.php exists locally"; \
|
|
||||||
echo "Size: $$(wc -c < $$APP_PATH/public/index.php) bytes"; \
|
|
||||||
echo "Content preview:"; \
|
|
||||||
head -5 "$$APP_PATH/public/index.php"; \
|
|
||||||
else \
|
|
||||||
echo "❌ index.php NOT found locally!"; \
|
|
||||||
echo "Checking if public folder exists:"; \
|
|
||||||
if [ -d "$$APP_PATH/public" ]; then \
|
|
||||||
echo "Public folder exists, contents:"; \
|
|
||||||
ls -la "$$APP_PATH/public/"; \
|
|
||||||
else \
|
|
||||||
echo "Public folder does not exist!"; \
|
|
||||||
fi; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test direkt mit absoluten Pfaden
|
|
||||||
debug-direct:
|
|
||||||
@echo "📁 Direct path test:"
|
|
||||||
@echo "=== Current directory ==="
|
|
||||||
pwd
|
|
||||||
@echo ""
|
|
||||||
@echo "=== Going to project root ==="
|
|
||||||
cd ../.. && pwd
|
|
||||||
@echo ""
|
|
||||||
@echo "=== Files in project root ==="
|
|
||||||
cd ../.. && ls -la | head -10
|
|
||||||
@echo ""
|
|
||||||
@echo "=== Public folder ==="
|
|
||||||
cd ../.. && ls -la public/ | head -10
|
|
||||||
@echo ""
|
|
||||||
@echo "=== Index.php check ==="
|
|
||||||
cd ../.. && if [ -f "public/index.php" ]; then \
|
|
||||||
echo "✅ index.php found!"; \
|
|
||||||
echo "Size: $$(wc -c < public/index.php) bytes"; \
|
|
||||||
else \
|
|
||||||
echo "❌ index.php not found"; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Test Ansible synchronize mit debug
|
|
||||||
debug-sync:
|
|
||||||
@echo "🔍 Testing Ansible synchronize with debug..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml debug-sync.yml -v
|
|
||||||
|
|
||||||
# Upload files only (no infrastructure setup)
|
|
||||||
upload:
|
|
||||||
@echo "📤 Uploading files only..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml upload-only.yml
|
|
||||||
|
|
||||||
# Restart application after upload
|
|
||||||
restart:
|
|
||||||
@echo "🔄 Restarting application..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml restart-app.yml
|
|
||||||
|
|
||||||
# Quick upload and restart
|
|
||||||
quick-deploy:
|
|
||||||
@echo "⚡ Quick deploy: upload + restart..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml upload-only.yml
|
|
||||||
ansible-playbook -i inventory/hosts.yml restart-app.yml
|
|
||||||
|
|
||||||
# Alle Standard-Befehle
|
|
||||||
deploy:
|
|
||||||
@echo "🚀 Deploying project to Netcup..."
|
|
||||||
chmod +x deploy.sh
|
|
||||||
./deploy.sh
|
|
||||||
|
|
||||||
check:
|
|
||||||
@echo "🔍 Testing configuration..."
|
|
||||||
ansible all -m ping
|
|
||||||
|
|
||||||
logs:
|
|
||||||
@echo "📋 Showing container logs..."
|
|
||||||
ansible all -m shell -a "cd /opt/myapp && (docker compose logs --tail 100 || docker-compose logs --tail 100)"
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "📖 Debug commands:"
|
|
||||||
@echo " make debug-local - Check local files"
|
|
||||||
@echo " make debug-direct - Check with direct paths"
|
|
||||||
@echo " make test-rsync - Test manual rsync"
|
|
||||||
@echo " make debug-sync - Test Ansible sync"
|
|
||||||
@echo ""
|
|
||||||
@echo "📖 Deploy commands:"
|
|
||||||
@echo " make deploy - Full deployment (infrastructure + app)"
|
|
||||||
@echo " make upload - Upload files only (no infrastructure)"
|
|
||||||
@echo " make restart - Restart application containers"
|
|
||||||
@echo " make quick-deploy - Upload files + restart (fastest)"
|
|
||||||
@echo ""
|
|
||||||
@echo "📖 Utility commands:"
|
|
||||||
@echo " make logs - Show container logs"
|
|
||||||
@echo " make check - Test connection"
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Netcup Quick Setup Guide
|
|
||||||
|
|
||||||
## 1. Server vorbereiten
|
|
||||||
|
|
||||||
### Netcup VPS bestellen
|
|
||||||
- **Mindestens:** VPS 200 G8 (2 CPU, 4GB RAM)
|
|
||||||
- **OS:** Ubuntu 22.04 LTS
|
|
||||||
- **Netzwerk:** IPv4 + IPv6
|
|
||||||
|
|
||||||
### SSH-Key installieren
|
|
||||||
```bash
|
|
||||||
# SSH-Key generieren (falls noch nicht vorhanden)
|
|
||||||
ssh-keygen -t ed25519 -C "netcup-deploy"
|
|
||||||
|
|
||||||
# Key zum Server kopieren
|
|
||||||
ssh-copy-id root@DEINE-SERVER-IP
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Konfiguration
|
|
||||||
|
|
||||||
### Basis-Einstellungen
|
|
||||||
```bash
|
|
||||||
# Server-Details eintragen
|
|
||||||
vim inventory/hosts.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Wichtig ändern:**
|
|
||||||
- `ansible_host: 85.123.456.789` → deine Netcup IP
|
|
||||||
- `domain: "example.com"` → deine Domain
|
|
||||||
- `ssl_email: "admin@example.com"` → deine E-Mail
|
|
||||||
- `git_repo: "https://github.com/user/repo.git"` → dein Git Repository
|
|
||||||
|
|
||||||
### DNS konfigurieren
|
|
||||||
Stelle sicher dass deine Domain zur Netcup IP zeigt:
|
|
||||||
```bash
|
|
||||||
# A-Record setzen
|
|
||||||
example.com. IN A DEINE-NETCUP-IP
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. App-Anforderungen
|
|
||||||
|
|
||||||
Deine App braucht:
|
|
||||||
- **Dockerfile** im Repository-Root
|
|
||||||
- **Port 3000** (oder ändere `app_port` in hosts.yml)
|
|
||||||
- **Health-Check** Endpoint `/health` (oder ändere `health_check_url`)
|
|
||||||
|
|
||||||
### Beispiel Dockerfile (Node.js)
|
|
||||||
```dockerfile
|
|
||||||
FROM node:18-alpine
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm ci --only=production
|
|
||||||
COPY . .
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Beispiel Health-Check (Express.js)
|
|
||||||
```javascript
|
|
||||||
app.get('/health', (req, res) => {
|
|
||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Deployment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Einfach deployen
|
|
||||||
make deploy
|
|
||||||
|
|
||||||
# Oder manuell
|
|
||||||
./deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Troubleshooting
|
|
||||||
|
|
||||||
### Server nicht erreichbar?
|
|
||||||
```bash
|
|
||||||
# Ping testen
|
|
||||||
ping DEINE-SERVER-IP
|
|
||||||
|
|
||||||
# SSH testen
|
|
||||||
ssh root@DEINE-SERVER-IP
|
|
||||||
|
|
||||||
# Firewall prüfen (auf dem Server)
|
|
||||||
ufw status
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSL-Probleme?
|
|
||||||
```bash
|
|
||||||
# DNS prüfen
|
|
||||||
nslookup DEINE-DOMAIN
|
|
||||||
|
|
||||||
# Certbot manuell
|
|
||||||
ssh root@DEINE-SERVER-IP
|
|
||||||
certbot certificates
|
|
||||||
```
|
|
||||||
|
|
||||||
### App startet nicht?
|
|
||||||
```bash
|
|
||||||
# Logs anschauen
|
|
||||||
make logs
|
|
||||||
|
|
||||||
# Container status
|
|
||||||
ansible all -m shell -a "docker ps -a"
|
|
||||||
|
|
||||||
# Ins Container einsteigen
|
|
||||||
ansible all -m shell -a "docker exec -it CONTAINER_NAME sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. Nach dem Deployment
|
|
||||||
|
|
||||||
- ✅ **App testen:** https://deine-domain.com
|
|
||||||
- ✅ **Health-Check:** https://deine-domain.com/health
|
|
||||||
- ✅ **SSL prüfen:** https://www.ssllabs.com/ssltest/
|
|
||||||
- ✅ **Performance:** https://pagespeed.web.dev/
|
|
||||||
|
|
||||||
## 7. Updates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# App updaten (git pull + rebuild)
|
|
||||||
make update
|
|
||||||
|
|
||||||
# Logs nach Update prüfen
|
|
||||||
make logs
|
|
||||||
```
|
|
||||||
|
|
||||||
Das war's! Deine App läuft jetzt auf Netcup mit SSL! 🎉
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Netcup Simple Deploy
|
|
||||||
|
|
||||||
Ultra-einfaches Ansible-Setup für Netcup VPS Deployment.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
1. **Server-Info eintragen:**
|
|
||||||
```bash
|
|
||||||
vim inventory/hosts.yml
|
|
||||||
# Deine Netcup-Server IP und Domain eintragen
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **App-Einstellungen:**
|
|
||||||
```bash
|
|
||||||
vim inventory/group_vars.yml
|
|
||||||
# Domain, Repo, etc. anpassen
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Deployen:**
|
|
||||||
```bash
|
|
||||||
ansible-playbook deploy.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Was wird installiert
|
|
||||||
|
|
||||||
✅ Docker & Docker Compose
|
|
||||||
✅ Nginx Reverse Proxy
|
|
||||||
✅ SSL mit Let's Encrypt
|
|
||||||
✅ Deine App aus Git
|
|
||||||
✅ Automatische Updates
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- 🚀 **Ein Kommando** deployment
|
|
||||||
- 🔒 **Automatisches SSL**
|
|
||||||
- 🐳 **Docker-basiert**
|
|
||||||
- 📱 **Health Checks**
|
|
||||||
- 🔄 **Zero-Downtime Updates**
|
|
||||||
|
|
||||||
Perfekt für einfache Web-Apps auf Netcup VPS!
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# Production Server Setup - Debian 12
|
|
||||||
|
|
||||||
## Netcup Panel Konfiguration
|
|
||||||
|
|
||||||
### 1. Fresh OS Installation
|
|
||||||
1. **Netcup Panel** → "Server" → Ihr Server
|
|
||||||
2. **"Betriebssystem"** → "Neu installieren"
|
|
||||||
3. **OS wählen**: `Debian 12 (Bookworm)` 64-bit
|
|
||||||
4. **Installation starten** und warten bis abgeschlossen
|
|
||||||
|
|
||||||
### 2. SSH-Key Konfiguration
|
|
||||||
1. **SSH-Key hinzufügen**:
|
|
||||||
```
|
|
||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA3DqB1B4wa5Eo116bJ1HybFagK3fU0i+wJ6mAHI1L3i production@michaelschiemer.de
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Im Netcup Panel**:
|
|
||||||
- "SSH-Keys" → "Neuen SSH-Key hinzufügen"
|
|
||||||
- Name: `production-michaelschiemer`
|
|
||||||
- Key: (oben kopieren und einfügen)
|
|
||||||
- Key dem Server zuweisen
|
|
||||||
|
|
||||||
### 3. Root-Zugang aktivieren
|
|
||||||
1. **Console/KVM** über Netcup Panel öffnen
|
|
||||||
2. **Als root einloggen** (initial Setup)
|
|
||||||
3. **SSH-Key für root aktivieren**:
|
|
||||||
```bash
|
|
||||||
# SSH-Key bereits durch Panel hinzugefügt
|
|
||||||
# Root SSH sollte funktionieren
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Deploy User einrichten
|
|
||||||
```bash
|
|
||||||
# Als root ausführen:
|
|
||||||
useradd -m -s /bin/bash deploy
|
|
||||||
usermod -aG sudo deploy
|
|
||||||
|
|
||||||
# SSH-Key für deploy user
|
|
||||||
mkdir -p /home/deploy/.ssh
|
|
||||||
cp /root/.ssh/authorized_keys /home/deploy/.ssh/
|
|
||||||
chown -R deploy:deploy /home/deploy/.ssh
|
|
||||||
chmod 700 /home/deploy/.ssh
|
|
||||||
chmod 600 /home/deploy/.ssh/authorized_keys
|
|
||||||
|
|
||||||
# Sudo ohne Passwort für deploy
|
|
||||||
echo "deploy ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
## Warum Debian 12?
|
|
||||||
|
|
||||||
### Production-Vorteile:
|
|
||||||
- ✅ **Stabilität**: Bewährte LTS-Pakete, längere Support-Zyklen
|
|
||||||
- ✅ **Performance**: Geringerer Ressourcenverbrauch als Ubuntu
|
|
||||||
- ✅ **Security**: Conservative Updates, weniger experimentelle Features
|
|
||||||
- ✅ **Docker-Optimiert**: Perfekt für containerisierte Deployments
|
|
||||||
- ✅ **Minimale Basis**: Nur essentielle Pakete, weniger Attack Surface
|
|
||||||
|
|
||||||
### Server-Spezifikationen:
|
|
||||||
- **RAM**: Minimum 2GB (empfohlen 4GB+)
|
|
||||||
- **Storage**: Minimum 20GB SSD
|
|
||||||
- **CPU**: 1+ vCPU (empfohlen 2+ vCPU)
|
|
||||||
- **Network**: Stable internet, static IP
|
|
||||||
|
|
||||||
## Nach Installation testen:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# SSH-Connectivity Test
|
|
||||||
ssh -i ~/.ssh/production deploy@94.16.110.151
|
|
||||||
|
|
||||||
# System Info
|
|
||||||
ssh -i ~/.ssh/production deploy@94.16.110.151 'uname -a && lsb_release -a'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nächste Schritte:
|
|
||||||
Nach erfolgreichem Server-Setup:
|
|
||||||
1. SSH-Connectivity bestätigen
|
|
||||||
2. Ansible Ping-Test durchführen
|
|
||||||
3. Deployment-Playbook ausführen
|
|
||||||
|
|
||||||
---
|
|
||||||
**🔑 SSH-Key Fingerprint**: `SHA256:7FBYrZpDcYcKXpeM8OHoGZZBHwxNORoOFWuzP2MpDpQ`
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
# Projekt Setup für Netcup (nutzt deine docker-compose.yml)
|
|
||||||
|
|
||||||
## Projektstruktur
|
|
||||||
|
|
||||||
Das Deployment nutzt deine bestehende Docker-Konfiguration:
|
|
||||||
|
|
||||||
```
|
|
||||||
dein-projekt/ # Hauptordner
|
|
||||||
├── ansible/ # Hier sind wir jetzt
|
|
||||||
│ └── netcup-simple-deploy/
|
|
||||||
├── docker-compose.yml # ← DEINE Compose-Datei (wird verwendet!)
|
|
||||||
├── docker/ # Docker-Konfiguration
|
|
||||||
│ ├── Dockerfile
|
|
||||||
│ └── docker-compose.yml # ← Alternative hier
|
|
||||||
├── src/ # PHP Framework/Library Dateien
|
|
||||||
├── public/ # Web-Root
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Was das Deployment macht:
|
|
||||||
|
|
||||||
✅ **Nutzt deine bestehende docker-compose.yml**
|
|
||||||
✅ **Startet ALLE deine Services** (DB, Redis, etc.)
|
|
||||||
✅ **Überträgt komplettes Projekt**
|
|
||||||
✅ **Nginx als Reverse Proxy** für SSL
|
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
### 1. Konfiguration
|
|
||||||
```bash
|
|
||||||
cd ansible/netcup-simple-deploy
|
|
||||||
vim inventory/hosts.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Wichtig ändern:**
|
|
||||||
```yaml
|
|
||||||
ansible_host: DEINE-NETCUP-IP
|
|
||||||
domain: "deine-domain.com"
|
|
||||||
app_port: 8080 # Port deiner App aus docker-compose.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Port prüfen
|
|
||||||
Schaue in deine `docker-compose.yml` welchen Port deine App exponiertrt:
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
myapp:
|
|
||||||
ports:
|
|
||||||
- "8080:80" # ← Dann ist app_port: 8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Deployment
|
|
||||||
```bash
|
|
||||||
make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
## Beispiel docker-compose.yml Strukturen
|
|
||||||
|
|
||||||
### Einfache PHP App
|
|
||||||
```yaml
|
|
||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "8080:80"
|
|
||||||
volumes:
|
|
||||||
- ./src:/var/www/src
|
|
||||||
- ./public:/var/www/html
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mit Datenbank
|
|
||||||
```yaml
|
|
||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "8080:80"
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
environment:
|
|
||||||
- DATABASE_URL=mysql://user:pass@db:3306/myapp
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: mysql:8.0
|
|
||||||
environment:
|
|
||||||
- MYSQL_ROOT_PASSWORD=secret
|
|
||||||
- MYSQL_DATABASE=myapp
|
|
||||||
volumes:
|
|
||||||
- db_data:/var/lib/mysql
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
db_data:
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mit Redis + Database
|
|
||||||
```yaml
|
|
||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "8080:80"
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
- redis
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: postgres:15
|
|
||||||
environment:
|
|
||||||
- POSTGRES_DB=myapp
|
|
||||||
- POSTGRES_USER=user
|
|
||||||
- POSTGRES_PASSWORD=secret
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:7-alpine
|
|
||||||
volumes:
|
|
||||||
- redis_data:/data
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
redis_data:
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nach dem Deployment
|
|
||||||
|
|
||||||
**Alle Services verwalten:**
|
|
||||||
```bash
|
|
||||||
make services # Zeige alle Services
|
|
||||||
make logs-service # Logs für bestimmten Service
|
|
||||||
make status # Status aller Container
|
|
||||||
make shell # In Container einsteigen
|
|
||||||
```
|
|
||||||
|
|
||||||
**Updates:**
|
|
||||||
```bash
|
|
||||||
# Nach Änderungen an Code oder docker-compose.yml
|
|
||||||
make deploy
|
|
||||||
|
|
||||||
# Nur Container neu bauen
|
|
||||||
make rebuild
|
|
||||||
```
|
|
||||||
|
|
||||||
**Monitoring:**
|
|
||||||
```bash
|
|
||||||
make logs # Alle Logs
|
|
||||||
make tail-logs # Live logs
|
|
||||||
make show-env # Environment variables
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vorteile dieser Lösung
|
|
||||||
|
|
||||||
✅ **Deine bestehende Konfiguration** wird verwendet
|
|
||||||
✅ **Alle Services** (DB, Redis, etc.) funktionieren
|
|
||||||
✅ **Keine Code-Änderungen** nötig
|
|
||||||
✅ **SSL-Termination** durch nginx
|
|
||||||
✅ **Einfache Updates** mit make deploy
|
|
||||||
|
|
||||||
Das Deployment ist jetzt vollständig auf deine bestehende Docker-Infrastruktur ausgerichtet! 🎉
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
# Netcup Setup ohne Git
|
|
||||||
|
|
||||||
## 1. App-Struktur vorbereiten
|
|
||||||
|
|
||||||
### Option A: Bestehende App
|
|
||||||
Falls du bereits eine App hast, stelle sicher dass sie diese Struktur hat:
|
|
||||||
```
|
|
||||||
deine-app/
|
|
||||||
├── package.json # Node.js Abhängigkeiten
|
|
||||||
├── server.js # Hauptdatei
|
|
||||||
├── Dockerfile # Docker-Konfiguration
|
|
||||||
└── ... weitere Dateien
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option B: Neue App erstellen
|
|
||||||
```bash
|
|
||||||
# Erstelle App-Verzeichnis
|
|
||||||
mkdir -p ~/meine-app
|
|
||||||
|
|
||||||
# Beispiel Node.js App
|
|
||||||
cd ~/meine-app
|
|
||||||
|
|
||||||
# package.json
|
|
||||||
cat > package.json << 'EOF'
|
|
||||||
{
|
|
||||||
"name": "meine-app",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "server.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node server.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"express": "^4.18.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# server.js
|
|
||||||
cat > server.js << 'EOF'
|
|
||||||
const express = require('express');
|
|
||||||
const app = express();
|
|
||||||
const port = process.env.PORT || 3000;
|
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
message: 'Hello World!',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/health', (req, res) => {
|
|
||||||
res.json({ status: 'ok' });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(port, '0.0.0.0', () => {
|
|
||||||
console.log(`Server running on port ${port}`);
|
|
||||||
});
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Dockerfile
|
|
||||||
cat > Dockerfile << 'EOF'
|
|
||||||
FROM node:18-alpine
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install
|
|
||||||
COPY . .
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
EOF
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Ansible konfigurieren
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Ins Deployment-Verzeichnis
|
|
||||||
cd ansible/netcup-simple-deploy
|
|
||||||
|
|
||||||
# Konfiguration anpassen
|
|
||||||
vim inventory/hosts.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Wichtige Änderungen:**
|
|
||||||
```yaml
|
|
||||||
ansible_host: DEINE-NETCUP-IP # ← Server IP
|
|
||||||
domain: "deine-domain.com" # ← Domain
|
|
||||||
ssl_email: "deine@email.com" # ← E-Mail
|
|
||||||
local_app_path: "~/meine-app" # ← Pfad zu deiner App
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. Deployment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# SSH-Key zum Server (falls noch nicht gemacht)
|
|
||||||
ssh-copy-id root@DEINE-NETCUP-IP
|
|
||||||
|
|
||||||
# App deployen
|
|
||||||
make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. App updaten
|
|
||||||
|
|
||||||
Nach Änderungen an deiner App:
|
|
||||||
```bash
|
|
||||||
# Einfach erneut deployen
|
|
||||||
make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
Die Dateien werden automatisch zum Server übertragen und die App neu gebaut.
|
|
||||||
|
|
||||||
## 5. Verschiedene App-Typen
|
|
||||||
|
|
||||||
### PHP App
|
|
||||||
```dockerfile
|
|
||||||
FROM php:8.1-apache
|
|
||||||
COPY . /var/www/html/
|
|
||||||
EXPOSE 80
|
|
||||||
```
|
|
||||||
|
|
||||||
### Python Flask
|
|
||||||
```dockerfile
|
|
||||||
FROM python:3.9-slim
|
|
||||||
WORKDIR /app
|
|
||||||
COPY requirements.txt .
|
|
||||||
RUN pip install -r requirements.txt
|
|
||||||
COPY . .
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD ["python", "app.py"]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Static HTML
|
|
||||||
```dockerfile
|
|
||||||
FROM nginx:alpine
|
|
||||||
COPY . /usr/share/nginx/html
|
|
||||||
EXPOSE 80
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. Ordnerstruktur
|
|
||||||
|
|
||||||
```
|
|
||||||
netcup-simple-deploy/
|
|
||||||
├── inventory/
|
|
||||||
│ └── hosts.yml # ← Hier konfigurieren
|
|
||||||
├── deploy.sh # ← Deployment starten
|
|
||||||
└── Makefile # ← Einfache Befehle
|
|
||||||
|
|
||||||
~/meine-app/ # ← Deine App-Dateien
|
|
||||||
├── Dockerfile
|
|
||||||
├── package.json
|
|
||||||
└── server.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. Troubleshooting
|
|
||||||
|
|
||||||
### App startet nicht?
|
|
||||||
```bash
|
|
||||||
# Logs anschauen
|
|
||||||
make logs
|
|
||||||
|
|
||||||
# Container status prüfen
|
|
||||||
ansible all -m shell -a "docker ps -a"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dateien werden nicht übertragen?
|
|
||||||
```bash
|
|
||||||
# Pfad prüfen
|
|
||||||
ls -la ~/meine-app
|
|
||||||
|
|
||||||
# Manuell testen
|
|
||||||
ansible all -m shell -a "ls -la /opt/myapp/src/"
|
|
||||||
```
|
|
||||||
|
|
||||||
Das war's! Keine Git-Kenntnisse nötig - einfach deine Dateien bearbeiten und deployen! 🎉
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
# PHP Projekt Setup für Netcup
|
|
||||||
|
|
||||||
## Projektstruktur
|
|
||||||
|
|
||||||
Das Deployment erwartet diese Struktur in deinem Hauptprojekt:
|
|
||||||
|
|
||||||
```
|
|
||||||
dein-projekt/ # Hauptordner
|
|
||||||
├── ansible/ # Hier sind wir jetzt
|
|
||||||
│ └── netcup-simple-deploy/
|
|
||||||
├── docker/ # Docker-Konfiguration
|
|
||||||
│ ├── Dockerfile # (optional, wird sonst automatisch erstellt)
|
|
||||||
│ └── docker-compose.yml # (optional)
|
|
||||||
├── src/ # PHP Framework/Library Dateien
|
|
||||||
│ ├── classes/
|
|
||||||
│ ├── includes/
|
|
||||||
│ └── ...
|
|
||||||
├── public/ # Web-Root (öffentlich zugänglich)
|
|
||||||
│ ├── index.php # Haupteinstiegspunkt
|
|
||||||
│ ├── css/
|
|
||||||
│ ├── js/
|
|
||||||
│ ├── images/
|
|
||||||
│ └── ...
|
|
||||||
├── storage/ # (optional) Logs, Cache, etc.
|
|
||||||
├── cache/ # (optional) Cache-Dateien
|
|
||||||
├── logs/ # (optional) Log-Dateien
|
|
||||||
└── .env # (optional) Umgebungsvariablen
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Setup
|
|
||||||
|
|
||||||
### 1. Konfiguration
|
|
||||||
```bash
|
|
||||||
cd ansible/netcup-simple-deploy
|
|
||||||
vim inventory/hosts.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Ändere diese Werte:**
|
|
||||||
```yaml
|
|
||||||
ansible_host: DEINE-NETCUP-IP
|
|
||||||
domain: "deine-domain.com"
|
|
||||||
ssl_email: "deine@email.com"
|
|
||||||
local_app_path: "../.." # Zeigt auf dein Hauptprojekt
|
|
||||||
php_version: "8.2" # PHP Version
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Deployment
|
|
||||||
```bash
|
|
||||||
make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
Das war's! Deine PHP-App läuft unter `https://deine-domain.com`
|
|
||||||
|
|
||||||
## Was passiert beim Deployment?
|
|
||||||
|
|
||||||
1. **Dateien übertragen:** `public/`, `src/`, `docker/` → Server
|
|
||||||
2. **Dockerfile erstellen:** Falls keins in `docker/` vorhanden
|
|
||||||
3. **Docker Container bauen:** PHP + Apache + deine App
|
|
||||||
4. **Nginx Proxy:** SSL-Termination und Weiterleitung
|
|
||||||
5. **SSL-Zertifikat:** Automatisch mit Let's Encrypt
|
|
||||||
|
|
||||||
## Für verschiedene PHP-Setups
|
|
||||||
|
|
||||||
### Eigenes Dockerfile verwenden
|
|
||||||
Lege dein Dockerfile in `docker/Dockerfile`:
|
|
||||||
```dockerfile
|
|
||||||
FROM php:8.2-apache
|
|
||||||
|
|
||||||
# Deine spezifischen PHP Extensions
|
|
||||||
RUN docker-php-ext-install pdo pdo_mysql
|
|
||||||
|
|
||||||
# Custom Apache Config
|
|
||||||
COPY docker/apache.conf /etc/apache2/sites-available/000-default.conf
|
|
||||||
|
|
||||||
# App Dateien
|
|
||||||
COPY public/ /var/www/html/
|
|
||||||
COPY src/ /var/www/src/
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mit Composer Dependencies
|
|
||||||
```dockerfile
|
|
||||||
FROM php:8.2-apache
|
|
||||||
|
|
||||||
# Composer installieren
|
|
||||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
|
||||||
|
|
||||||
# Dependencies installieren
|
|
||||||
COPY composer.json composer.lock /var/www/
|
|
||||||
WORKDIR /var/www
|
|
||||||
RUN composer install --no-dev --optimize-autoloader
|
|
||||||
|
|
||||||
# App kopieren
|
|
||||||
COPY public/ /var/www/html/
|
|
||||||
COPY src/ /var/www/src/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mit Database
|
|
||||||
Erweitere `inventory/hosts.yml`:
|
|
||||||
```yaml
|
|
||||||
app_env:
|
|
||||||
APP_ENV: "production"
|
|
||||||
DATABASE_HOST: "your-db-host"
|
|
||||||
DATABASE_NAME: "your-db-name"
|
|
||||||
DATABASE_USER: "your-db-user"
|
|
||||||
DATABASE_PASS: "your-db-password"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Nützliche Befehle
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Logs anschauen
|
|
||||||
make logs
|
|
||||||
make error-logs
|
|
||||||
|
|
||||||
# Cache löschen
|
|
||||||
make clear-cache
|
|
||||||
|
|
||||||
# Permissions reparieren
|
|
||||||
make fix-permissions
|
|
||||||
|
|
||||||
# Composer auf Server ausführen
|
|
||||||
make composer-install
|
|
||||||
|
|
||||||
# Live logs verfolgen
|
|
||||||
make tail-logs
|
|
||||||
|
|
||||||
# SSH auf Server
|
|
||||||
make ssh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### App lädt nicht?
|
|
||||||
```bash
|
|
||||||
# Apache Fehler-Logs prüfen
|
|
||||||
make error-logs
|
|
||||||
|
|
||||||
# Allgemeine Logs
|
|
||||||
make logs
|
|
||||||
|
|
||||||
# Container Status
|
|
||||||
make status
|
|
||||||
```
|
|
||||||
|
|
||||||
### Permissions-Probleme?
|
|
||||||
```bash
|
|
||||||
# Permissions reparieren
|
|
||||||
make fix-permissions
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nach Code-Änderungen?
|
|
||||||
```bash
|
|
||||||
# Einfach neu deployen
|
|
||||||
make deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database-Verbindung?
|
|
||||||
```bash
|
|
||||||
# Umgebungsvariablen prüfen
|
|
||||||
ansible all -m shell -a "docker exec \$(docker ps -q | head -1) env | grep DATABASE"
|
|
||||||
```
|
|
||||||
|
|
||||||
Das Setup ist optimiert für deine bestehende Projektstruktur - keine Änderungen an deinem Code nötig! 🎉
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
[defaults]
|
|
||||||
inventory = inventory/hosts.yml
|
|
||||||
host_key_checking = False
|
|
||||||
timeout = 30
|
|
||||||
|
|
||||||
[privilege_escalation]
|
|
||||||
become = True
|
|
||||||
become_method = sudo
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
---
|
|
||||||
# Fallback Deployment für Debian (mit allen Variablen)
|
|
||||||
|
|
||||||
- name: Deploy App to Netcup VPS (Debian Fallback)
|
|
||||||
hosts: all
|
|
||||||
become: yes
|
|
||||||
vars_files:
|
|
||||||
- inventory/group_vars.yml
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Update system
|
|
||||||
apt:
|
|
||||||
update_cache: yes
|
|
||||||
upgrade: dist
|
|
||||||
|
|
||||||
- name: Install packages from Debian repos
|
|
||||||
apt:
|
|
||||||
name:
|
|
||||||
- nginx
|
|
||||||
- certbot
|
|
||||||
- python3-certbot-nginx
|
|
||||||
- git
|
|
||||||
- curl
|
|
||||||
- rsync
|
|
||||||
- docker.io
|
|
||||||
- docker-compose
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Start and enable Docker
|
|
||||||
systemd:
|
|
||||||
name: docker
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
|
|
||||||
- name: Add user to docker group
|
|
||||||
user:
|
|
||||||
name: "{{ ansible_user }}"
|
|
||||||
groups: docker
|
|
||||||
append: yes
|
|
||||||
|
|
||||||
- name: Deploy webapp
|
|
||||||
include_role:
|
|
||||||
name: webapp
|
|
||||||
|
|
||||||
- name: Configure Nginx reverse proxy
|
|
||||||
template:
|
|
||||||
src: roles/webapp/templates/nginx-site.conf.j2
|
|
||||||
dest: /etc/nginx/sites-available/{{ domain }}
|
|
||||||
backup: yes
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Enable site
|
|
||||||
file:
|
|
||||||
src: /etc/nginx/sites-available/{{ domain }}
|
|
||||||
dest: /etc/nginx/sites-enabled/{{ domain }}
|
|
||||||
state: link
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Remove default site
|
|
||||||
file:
|
|
||||||
path: /etc/nginx/sites-enabled/default
|
|
||||||
state: absent
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Generate SSL certificate
|
|
||||||
command: >
|
|
||||||
certbot --nginx -d {{ domain }}
|
|
||||||
--non-interactive --agree-tos
|
|
||||||
--email {{ ssl_email }}
|
|
||||||
args:
|
|
||||||
creates: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem"
|
|
||||||
|
|
||||||
- name: Setup SSL renewal
|
|
||||||
cron:
|
|
||||||
name: "Renew SSL"
|
|
||||||
minute: "0"
|
|
||||||
hour: "3"
|
|
||||||
job: "certbot renew --quiet"
|
|
||||||
|
|
||||||
- name: Start nginx
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
|
|
||||||
- name: Wait for app to be ready
|
|
||||||
wait_for:
|
|
||||||
port: 80
|
|
||||||
delay: 10
|
|
||||||
timeout: 60
|
|
||||||
|
|
||||||
- name: Health check
|
|
||||||
uri:
|
|
||||||
url: "https://{{ domain }}"
|
|
||||||
method: GET
|
|
||||||
status_code: [200, 301, 302]
|
|
||||||
retries: 5
|
|
||||||
delay: 10
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
handlers:
|
|
||||||
- name: reload nginx
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: reloaded
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# PHP Projekt Deployment Script für Netcup (nutzt bestehende docker-compose.yml)
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "🚀 Projekt Deployment zu Netcup (nutzt deine docker-compose.yml)"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Prüfe ob Konfiguration angepasst wurde
|
|
||||||
if grep -q "85.123.456.789" inventory/hosts.yml; then
|
|
||||||
echo "❌ Bitte erst die Konfiguration anpassen!"
|
|
||||||
echo ""
|
|
||||||
echo "1. vim inventory/hosts.yml"
|
|
||||||
echo " - Server IP ändern"
|
|
||||||
echo " - Domain ändern"
|
|
||||||
echo " - app_port prüfen (Port deiner App)"
|
|
||||||
echo ""
|
|
||||||
echo "2. Dann nochmal: ./deploy.sh"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
LOCAL_APP_PATH=$(grep "local_app_path:" inventory/hosts.yml | awk '{print $2}' | tr -d '"')
|
|
||||||
|
|
||||||
# Prüfe Projektstruktur
|
|
||||||
echo "📁 Prüfe Projektstruktur..."
|
|
||||||
FULL_PATH="$LOCAL_APP_PATH"
|
|
||||||
|
|
||||||
if [ ! -d "$FULL_PATH" ]; then
|
|
||||||
echo "❌ Projekt-Verzeichnis nicht gefunden: $FULL_PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Projektstruktur OK:"
|
|
||||||
echo " 📂 Projekt: $FULL_PATH"
|
|
||||||
|
|
||||||
# Prüfe docker-compose.yml
|
|
||||||
if [ -f "$FULL_PATH/docker-compose.yml" ]; then
|
|
||||||
echo " ✅ docker-compose.yml gefunden im Root"
|
|
||||||
elif [ -f "$FULL_PATH/docker/docker-compose.yml" ]; then
|
|
||||||
echo " ✅ docker-compose.yml gefunden in docker/"
|
|
||||||
else
|
|
||||||
echo " ℹ️ Keine docker-compose.yml gefunden - wird automatisch erstellt"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Zeige docker-compose.yml Inhalt falls vorhanden
|
|
||||||
if [ -f "$FULL_PATH/docker-compose.yml" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "📋 Deine docker-compose.yml (erste 10 Zeilen):"
|
|
||||||
head -10 "$FULL_PATH/docker-compose.yml" | sed 's/^/ /'
|
|
||||||
elif [ -f "$FULL_PATH/docker/docker-compose.yml" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "📋 Deine docker-compose.yml aus docker/ (erste 10 Zeilen):"
|
|
||||||
head -10 "$FULL_PATH/docker/docker-compose.yml" | sed 's/^/ /'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ping test
|
|
||||||
echo ""
|
|
||||||
echo "🔍 Teste Verbindung zum Server..."
|
|
||||||
if ! ansible all -m ping; then
|
|
||||||
echo "❌ Server nicht erreichbar. Prüfe:"
|
|
||||||
echo " - IP-Adresse korrekt?"
|
|
||||||
echo " - SSH-Key installiert? (ssh-copy-id root@deine-ip)"
|
|
||||||
echo " - Server läuft?"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Server erreichbar!"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Wähle Deployment-Methode
|
|
||||||
echo "🔧 Deployment-Optionen:"
|
|
||||||
echo "1. Standard: Saubere Docker-Installation (empfohlen)"
|
|
||||||
echo "2. Fallback: Debian Standard-Pakete (falls Probleme auftreten)"
|
|
||||||
echo ""
|
|
||||||
read -p "Wähle Option (1/2): " -n 1 -r
|
|
||||||
echo
|
|
||||||
|
|
||||||
if [[ $REPLY == "2" ]]; then
|
|
||||||
PLAYBOOK="deploy-debian-fallback.yml"
|
|
||||||
echo "📦 Verwende Debian Standard-Pakete"
|
|
||||||
else
|
|
||||||
PLAYBOOK="deploy.yml"
|
|
||||||
echo "🐳 Verwende saubere Docker-Installation"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Deployment confirmation
|
|
||||||
read -p "🚀 Projekt deployen? (y/N): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Deployment abgebrochen."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🔧 Starte Deployment mit $PLAYBOOK..."
|
|
||||||
echo "💡 Das Deployment nutzt deine bestehende docker-compose.yml!"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
ansible-playbook "$PLAYBOOK"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "🎉 Deployment abgeschlossen!"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Zeige Ergebnisse
|
|
||||||
DOMAIN=$(grep "domain:" inventory/hosts.yml | awk '{print $2}' | tr -d '"')
|
|
||||||
echo "🌐 Dein Projekt ist verfügbar unter:"
|
|
||||||
echo " https://$DOMAIN"
|
|
||||||
echo ""
|
|
||||||
echo "📊 Status prüfen:"
|
|
||||||
echo " curl -I https://$DOMAIN"
|
|
||||||
echo ""
|
|
||||||
echo "🔧 Container-Status anschauen:"
|
|
||||||
echo " make status"
|
|
||||||
echo ""
|
|
||||||
echo "🔧 Logs anschauen:"
|
|
||||||
echo " make logs"
|
|
||||||
echo ""
|
|
||||||
echo "🔄 Nach Änderungen:"
|
|
||||||
echo " make deploy"
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
---
|
|
||||||
# Ultra-einfaches Netcup Deployment (Port-Konflikt behoben)
|
|
||||||
|
|
||||||
- name: Deploy App to Netcup VPS (Debian Clean)
|
|
||||||
hosts: all
|
|
||||||
become: yes
|
|
||||||
vars_files:
|
|
||||||
- inventory/group_vars.yml
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Clean up any existing Docker repositories
|
|
||||||
file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: absent
|
|
||||||
loop:
|
|
||||||
- /etc/apt/sources.list.d/docker.list
|
|
||||||
- /etc/apt/sources.list.d/download_docker_com_linux_debian.list
|
|
||||||
- /etc/apt/keyrings/docker.gpg
|
|
||||||
- /etc/apt/keyrings/docker.asc
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Remove any Docker GPG keys from apt-key
|
|
||||||
shell: apt-key del 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 || true
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Update apt cache after cleanup
|
|
||||||
apt:
|
|
||||||
update_cache: yes
|
|
||||||
|
|
||||||
- name: Install basic packages first
|
|
||||||
apt:
|
|
||||||
name:
|
|
||||||
- nginx
|
|
||||||
- certbot
|
|
||||||
- python3-certbot-nginx
|
|
||||||
- git
|
|
||||||
- curl
|
|
||||||
- rsync
|
|
||||||
- ca-certificates
|
|
||||||
- gnupg
|
|
||||||
- lsb-release
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Create keyrings directory
|
|
||||||
file:
|
|
||||||
path: /etc/apt/keyrings
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: Add Docker GPG key (new method)
|
|
||||||
shell: |
|
|
||||||
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
||||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
|
||||||
args:
|
|
||||||
creates: /etc/apt/keyrings/docker.gpg
|
|
||||||
|
|
||||||
- name: Add Docker repository (new method)
|
|
||||||
shell: |
|
|
||||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
|
||||||
args:
|
|
||||||
creates: /etc/apt/sources.list.d/docker.list
|
|
||||||
|
|
||||||
- name: Update apt cache
|
|
||||||
apt:
|
|
||||||
update_cache: yes
|
|
||||||
|
|
||||||
- name: Install Docker
|
|
||||||
apt:
|
|
||||||
name:
|
|
||||||
- docker-ce
|
|
||||||
- docker-ce-cli
|
|
||||||
- containerd.io
|
|
||||||
- docker-buildx-plugin
|
|
||||||
- docker-compose-plugin
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Start and enable Docker
|
|
||||||
systemd:
|
|
||||||
name: docker
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
|
|
||||||
- name: Add user to docker group
|
|
||||||
user:
|
|
||||||
name: "{{ ansible_user }}"
|
|
||||||
groups: docker
|
|
||||||
append: yes
|
|
||||||
|
|
||||||
- name: Stop nginx temporarily (to avoid port conflicts)
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: stopped
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Deploy webapp
|
|
||||||
include_role:
|
|
||||||
name: webapp
|
|
||||||
|
|
||||||
- name: Configure Nginx reverse proxy
|
|
||||||
template:
|
|
||||||
src: roles/webapp/templates/nginx-site.conf.j2
|
|
||||||
dest: /etc/nginx/sites-available/{{ domain }}
|
|
||||||
backup: yes
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Enable site
|
|
||||||
file:
|
|
||||||
src: /etc/nginx/sites-available/{{ domain }}
|
|
||||||
dest: /etc/nginx/sites-enabled/{{ domain }}
|
|
||||||
state: link
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Remove default site
|
|
||||||
file:
|
|
||||||
path: /etc/nginx/sites-enabled/default
|
|
||||||
state: absent
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Test nginx configuration
|
|
||||||
command: nginx -t
|
|
||||||
register: nginx_test
|
|
||||||
|
|
||||||
- name: Start nginx
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
|
|
||||||
- name: Generate SSL certificate
|
|
||||||
command: >
|
|
||||||
certbot --nginx -d {{ domain }}
|
|
||||||
--non-interactive --agree-tos
|
|
||||||
--email {{ ssl_email }}
|
|
||||||
args:
|
|
||||||
creates: "/etc/letsencrypt/live/{{ domain }}/fullchain.pem"
|
|
||||||
|
|
||||||
- name: Setup SSL renewal
|
|
||||||
cron:
|
|
||||||
name: "Renew SSL"
|
|
||||||
minute: "0"
|
|
||||||
hour: "3"
|
|
||||||
job: "certbot renew --quiet"
|
|
||||||
|
|
||||||
- name: Wait for app to be ready
|
|
||||||
wait_for:
|
|
||||||
port: 80
|
|
||||||
delay: 10
|
|
||||||
timeout: 60
|
|
||||||
|
|
||||||
- name: Health check
|
|
||||||
uri:
|
|
||||||
url: "https://{{ domain }}"
|
|
||||||
method: GET
|
|
||||||
status_code: [200, 301, 302]
|
|
||||||
retries: 5
|
|
||||||
delay: 10
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
handlers:
|
|
||||||
- name: reload nginx
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: reloaded
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
# Globale Einstellungen
|
|
||||||
|
|
||||||
# Docker-Einstellungen
|
|
||||||
docker_compose_version: "2.24.0"
|
|
||||||
|
|
||||||
# Nginx-Einstellungen
|
|
||||||
nginx_client_max_body_size: "50M"
|
|
||||||
nginx_worker_connections: 1024
|
|
||||||
|
|
||||||
# SSL-Einstellungen
|
|
||||||
ssl_protocols: "TLSv1.2 TLSv1.3"
|
|
||||||
|
|
||||||
# App-Verzeichnis auf dem Server
|
|
||||||
app_directory: "/opt/{{ app_name }}"
|
|
||||||
|
|
||||||
# Health Check
|
|
||||||
health_check_url: "/health"
|
|
||||||
health_check_timeout: 30
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
# Netcup Inventar für PHP-Projekt (Fixed paths)
|
|
||||||
|
|
||||||
all:
|
|
||||||
hosts:
|
|
||||||
netcup-server:
|
|
||||||
ansible_host: 94.16.110.151
|
|
||||||
ansible_user: deploy
|
|
||||||
ansible_ssh_private_key_file: /home/michael/.ssh/production
|
|
||||||
|
|
||||||
# Server-Details
|
|
||||||
domain: "test.michaelschiemer.de"
|
|
||||||
ssl_email: "kontakt@michaelschiemer.de"
|
|
||||||
|
|
||||||
# App-Konfiguration
|
|
||||||
app_name: "michaelschiemer"
|
|
||||||
app_port: 8000
|
|
||||||
|
|
||||||
# Pfad zu deinem Projekt (ABSOLUT!)
|
|
||||||
local_app_path: "/home/michael/dev/michaelschiemer" # Absoluter Pfad zu deinem Hauptprojekt
|
|
||||||
|
|
||||||
# Umgebungsvariablen für deine App (wird in .env geschrieben)
|
|
||||||
app_env:
|
|
||||||
APP_ENV: "production"
|
|
||||||
APP_DEBUG: "false"
|
|
||||||
APP_NAME: "Michael Schiemer"
|
|
||||||
APP_KEY: "base64:kJH8fsd89fs8df7sdf8sdf7sd8f7sdf"
|
|
||||||
APP_TIMEZONE: "Europe/Berlin"
|
|
||||||
APP_LOCALE: "de"
|
|
||||||
|
|
||||||
# Database (Docker internal)
|
|
||||||
DB_DRIVER: "mysql"
|
|
||||||
DB_HOST: "db"
|
|
||||||
DB_PORT: "3306"
|
|
||||||
DB_DATABASE: "michaelschiemer"
|
|
||||||
DB_USERNAME: "mdb-user"
|
|
||||||
DB_PASSWORD: "StartSimple2024!"
|
|
||||||
DB_CHARSET: "utf8mb4"
|
|
||||||
|
|
||||||
# Security
|
|
||||||
SECURITY_ALLOWED_HOSTS: "localhost,test.michaelschiemer.de,michaelschiemer.de"
|
|
||||||
SECURITY_RATE_LIMIT_PER_MINUTE: "60"
|
|
||||||
SECURITY_RATE_LIMIT_BURST: "10"
|
|
||||||
SESSION_LIFETIME: "1800"
|
|
||||||
|
|
||||||
# SSL/HTTPS
|
|
||||||
APP_SSL_PORT: "443"
|
|
||||||
FORCE_HTTPS: "true"
|
|
||||||
|
|
||||||
# Docker Settings
|
|
||||||
COMPOSE_PROJECT_NAME: "framework-production"
|
|
||||||
UID: "1000"
|
|
||||||
GID: "1000"
|
|
||||||
|
|
||||||
# Performance
|
|
||||||
OPCACHE_ENABLED: "true"
|
|
||||||
REDIS_HOST: "redis"
|
|
||||||
REDIS_PORT: "6379"
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
---
|
|
||||||
# Restart application containers after file upload
|
|
||||||
|
|
||||||
- name: Restart Application Containers
|
|
||||||
hosts: all
|
|
||||||
become: yes
|
|
||||||
vars_files:
|
|
||||||
- inventory/group_vars.yml
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Check if app directory exists
|
|
||||||
stat:
|
|
||||||
path: "{{ app_directory }}"
|
|
||||||
register: app_dir_exists
|
|
||||||
|
|
||||||
- name: Fail if app directory doesn't exist
|
|
||||||
fail:
|
|
||||||
msg: "App directory {{ app_directory }} not found. Please deploy first with deploy.yml"
|
|
||||||
when: not app_dir_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Check which docker compose command is available
|
|
||||||
shell: |
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
|
||||||
echo "docker compose"
|
|
||||||
elif docker-compose --version >/dev/null 2>&1; then
|
|
||||||
echo "docker-compose"
|
|
||||||
else
|
|
||||||
echo "none"
|
|
||||||
fi
|
|
||||||
register: docker_compose_cmd
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Fail if docker compose not available
|
|
||||||
fail:
|
|
||||||
msg: "Neither 'docker compose' nor 'docker-compose' is available"
|
|
||||||
when: docker_compose_cmd.stdout == "none"
|
|
||||||
|
|
||||||
- name: Show current container status
|
|
||||||
shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} ps"
|
|
||||||
register: container_status_before
|
|
||||||
ignore_errors: yes
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Stop existing containers
|
|
||||||
shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} down"
|
|
||||||
register: stop_result
|
|
||||||
|
|
||||||
- name: Start containers with updated files
|
|
||||||
shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} up -d --build"
|
|
||||||
register: start_result
|
|
||||||
|
|
||||||
- name: Wait for application to start
|
|
||||||
wait_for:
|
|
||||||
port: "{{ app_port }}"
|
|
||||||
host: "127.0.0.1"
|
|
||||||
delay: 5
|
|
||||||
timeout: 60
|
|
||||||
|
|
||||||
- name: Test if app is accessible
|
|
||||||
uri:
|
|
||||||
url: "http://127.0.0.1:{{ app_port }}/"
|
|
||||||
method: GET
|
|
||||||
status_code: [200, 301, 302]
|
|
||||||
register: app_test
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Show final container status
|
|
||||||
shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} ps"
|
|
||||||
register: container_status_after
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Show restart result
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
🔄 Application restart completed!
|
|
||||||
|
|
||||||
📂 Directory: {{ app_directory }}
|
|
||||||
🐳 Docker Compose: {{ docker_compose_cmd.stdout }}
|
|
||||||
|
|
||||||
🚀 Restart status: {{ 'Success' if start_result.rc == 0 else 'Failed' }}
|
|
||||||
|
|
||||||
{% if app_test.status is defined and (app_test.status == 200 or app_test.status == 301 or app_test.status == 302) %}
|
|
||||||
✅ App is responding (HTTP {{ app_test.status }})
|
|
||||||
🌐 Available at: https://{{ domain }}
|
|
||||||
{% else %}
|
|
||||||
⚠️ App health check failed - please check logs
|
|
||||||
🔍 Check logs with: cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} logs
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
📊 Container Status:
|
|
||||||
{{ container_status_after.stdout }}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
# Default variables for webapp role (Port-Konflikt behoben)
|
|
||||||
|
|
||||||
# App directory on server
|
|
||||||
app_directory: "/opt/{{ app_name }}"
|
|
||||||
|
|
||||||
# PHP settings
|
|
||||||
php_version: "8.4"
|
|
||||||
|
|
||||||
# Health check
|
|
||||||
health_check_url: "/health"
|
|
||||||
health_check_timeout: 30
|
|
||||||
|
|
||||||
# Default app settings if not defined in inventory
|
|
||||||
app_name: "myapp"
|
|
||||||
app_port: 8000 # PHP läuft auf Port 8080, nginx auf Port 80
|
|
||||||
domain: "test.michaelschiemer.de"
|
|
||||||
ssl_email: "kontakt@michaelschiemer.de"
|
|
||||||
|
|
||||||
# Default environment variables
|
|
||||||
app_env:
|
|
||||||
APP_ENV: "production"
|
|
||||||
PHP_MEMORY_LIMIT: "256M"
|
|
||||||
PHP_UPLOAD_MAX_FILESIZE: "50M"
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
---
|
|
||||||
# PHP Webapp Deployment (Handle missing PHP config files)
|
|
||||||
|
|
||||||
- name: Create app directory
|
|
||||||
file:
|
|
||||||
path: "{{ app_directory }}"
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: Check if docker-compose.yml exists locally first
|
|
||||||
local_action:
|
|
||||||
module: stat
|
|
||||||
path: "{{ local_app_path }}/docker-compose.yml"
|
|
||||||
register: local_compose_exists
|
|
||||||
become: no
|
|
||||||
|
|
||||||
- name: Show local docker-compose.yml status
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
🔍 Local docker-compose.yml check:
|
|
||||||
- Path: {{ local_app_path }}/docker-compose.yml
|
|
||||||
- Exists: {{ local_compose_exists.stat.exists }}
|
|
||||||
|
|
||||||
- name: Fail if docker-compose.yml doesn't exist locally
|
|
||||||
fail:
|
|
||||||
msg: |
|
|
||||||
❌ docker-compose.yml nicht im lokalen Projekt gefunden!
|
|
||||||
|
|
||||||
Geprüft: {{ local_app_path }}/docker-compose.yml
|
|
||||||
|
|
||||||
Bitte stelle sicher, dass eine docker-compose.yml in deinem Projekt-Root existiert.
|
|
||||||
when: not local_compose_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Upload project files with working synchronize
|
|
||||||
synchronize:
|
|
||||||
src: "{{ local_app_path }}/"
|
|
||||||
dest: "{{ app_directory }}/"
|
|
||||||
delete: no
|
|
||||||
archive: yes
|
|
||||||
checksum: yes
|
|
||||||
rsync_opts:
|
|
||||||
- "--exclude=ansible"
|
|
||||||
- "--exclude=.git"
|
|
||||||
- "--exclude=vendor"
|
|
||||||
- "--exclude=node_modules"
|
|
||||||
- "--exclude=storage/logs"
|
|
||||||
- "--exclude=cache"
|
|
||||||
- "--exclude=logs"
|
|
||||||
- "--exclude=dist"
|
|
||||||
- "--exclude=.archive"
|
|
||||||
- "--exclude=x_ansible"
|
|
||||||
- "--verbose"
|
|
||||||
register: sync_result
|
|
||||||
|
|
||||||
- name: Check if required PHP config files exist
|
|
||||||
stat:
|
|
||||||
path: "{{ app_directory }}/docker/php/php.production.ini"
|
|
||||||
register: php_prod_config
|
|
||||||
|
|
||||||
- name: Create missing PHP production config if needed
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
; PHP Production Configuration
|
|
||||||
memory_limit = 256M
|
|
||||||
upload_max_filesize = 50M
|
|
||||||
post_max_size = 50M
|
|
||||||
max_execution_time = 300
|
|
||||||
max_input_vars = 3000
|
|
||||||
|
|
||||||
; Error reporting for production
|
|
||||||
display_errors = Off
|
|
||||||
log_errors = On
|
|
||||||
error_log = /var/log/php_errors.log
|
|
||||||
|
|
||||||
; Opcache settings
|
|
||||||
opcache.enable = 1
|
|
||||||
opcache.memory_consumption = 128
|
|
||||||
opcache.max_accelerated_files = 4000
|
|
||||||
opcache.revalidate_freq = 2
|
|
||||||
opcache.validate_timestamps = 0
|
|
||||||
dest: "{{ app_directory }}/docker/php/php.production.ini"
|
|
||||||
mode: '0644'
|
|
||||||
when: not php_prod_config.stat.exists
|
|
||||||
|
|
||||||
- name: Check if common PHP config exists
|
|
||||||
stat:
|
|
||||||
path: "{{ app_directory }}/docker/php/php.common.ini"
|
|
||||||
register: php_common_config
|
|
||||||
|
|
||||||
- name: Create missing PHP common config if needed
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
; PHP Common Configuration
|
|
||||||
date.timezone = Europe/Berlin
|
|
||||||
short_open_tag = Off
|
|
||||||
expose_php = Off
|
|
||||||
|
|
||||||
; Security
|
|
||||||
allow_url_fopen = On
|
|
||||||
allow_url_include = Off
|
|
||||||
dest: "{{ app_directory }}/docker/php/php.common.ini"
|
|
||||||
mode: '0644'
|
|
||||||
when: not php_common_config.stat.exists
|
|
||||||
|
|
||||||
- name: Ensure PHP config directory exists
|
|
||||||
file:
|
|
||||||
path: "{{ app_directory }}/docker/php"
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: Show what files were synced
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
📂 Sync Result:
|
|
||||||
- Changed: {{ sync_result.changed }}
|
|
||||||
|
|
||||||
- name: Ensure proper permissions for directories
|
|
||||||
file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
recurse: yes
|
|
||||||
loop:
|
|
||||||
- "{{ app_directory }}/public"
|
|
||||||
- "{{ app_directory }}/src"
|
|
||||||
- "{{ app_directory }}/docker"
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Create storage directories if they don't exist
|
|
||||||
file:
|
|
||||||
path: "{{ app_directory }}/{{ item }}"
|
|
||||||
state: directory
|
|
||||||
mode: '0777'
|
|
||||||
loop:
|
|
||||||
- "storage/logs"
|
|
||||||
- "storage/cache"
|
|
||||||
- "cache"
|
|
||||||
- "logs"
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Check if docker-compose.yml exists in project root
|
|
||||||
stat:
|
|
||||||
path: "{{ app_directory }}/docker-compose.yml"
|
|
||||||
register: compose_exists
|
|
||||||
|
|
||||||
- name: Check if docker-compose.yml exists in docker folder
|
|
||||||
stat:
|
|
||||||
path: "{{ app_directory }}/docker/docker-compose.yml"
|
|
||||||
register: compose_docker_exists
|
|
||||||
|
|
||||||
- name: Use docker-compose.yml from docker folder if available
|
|
||||||
copy:
|
|
||||||
src: "{{ app_directory }}/docker/docker-compose.yml"
|
|
||||||
dest: "{{ app_directory }}/docker-compose.yml"
|
|
||||||
remote_src: yes
|
|
||||||
when: compose_docker_exists.stat.exists and not compose_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Manually copy docker-compose.yml if sync failed
|
|
||||||
copy:
|
|
||||||
src: "{{ local_app_path }}/docker-compose.yml"
|
|
||||||
dest: "{{ app_directory }}/docker-compose.yml"
|
|
||||||
mode: '0644'
|
|
||||||
when: local_compose_exists.stat.exists and not compose_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Show which docker-compose.yml we found
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
📋 Docker Compose Status:
|
|
||||||
- Root compose exists: {{ compose_exists.stat.exists }}
|
|
||||||
- Docker folder compose exists: {{ compose_docker_exists.stat.exists }}
|
|
||||||
|
|
||||||
- name: Fail if no docker-compose.yml found
|
|
||||||
fail:
|
|
||||||
msg: |
|
|
||||||
❌ Keine docker-compose.yml gefunden!
|
|
||||||
|
|
||||||
Erwartet in:
|
|
||||||
- {{ app_directory }}/docker-compose.yml
|
|
||||||
- {{ app_directory }}/docker/docker-compose.yml
|
|
||||||
|
|
||||||
Bitte erstelle eine docker-compose.yml in deinem Projekt.
|
|
||||||
when: not compose_exists.stat.exists and not compose_docker_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Check if public/index.php exists after sync
|
|
||||||
stat:
|
|
||||||
path: "{{ app_directory }}/public/index.php"
|
|
||||||
register: index_exists
|
|
||||||
|
|
||||||
- name: Fail if index.php not found
|
|
||||||
fail:
|
|
||||||
msg: |
|
|
||||||
❌ index.php nicht gefunden!
|
|
||||||
|
|
||||||
Geprüft: {{ app_directory }}/public/index.php
|
|
||||||
|
|
||||||
Die Dateien wurden nicht korrekt übertragen.
|
|
||||||
when: not index_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Create environment file
|
|
||||||
template:
|
|
||||||
src: app.env.j2
|
|
||||||
dest: "{{ app_directory }}/.env"
|
|
||||||
register: env_result
|
|
||||||
|
|
||||||
- name: Check which docker compose command is available
|
|
||||||
shell: |
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
|
||||||
echo "docker compose"
|
|
||||||
elif docker-compose --version >/dev/null 2>&1; then
|
|
||||||
echo "docker-compose"
|
|
||||||
else
|
|
||||||
echo "none"
|
|
||||||
fi
|
|
||||||
register: docker_compose_cmd
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Stop existing containers (if any)
|
|
||||||
shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} down"
|
|
||||||
when: docker_compose_cmd.stdout != "none"
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Start containers with your docker-compose.yml
|
|
||||||
shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} up -d --build"
|
|
||||||
register: start_result
|
|
||||||
when: docker_compose_cmd.stdout != "none"
|
|
||||||
|
|
||||||
- name: Wait for application to start
|
|
||||||
wait_for:
|
|
||||||
port: "{{ app_port }}"
|
|
||||||
host: "127.0.0.1"
|
|
||||||
delay: 5
|
|
||||||
timeout: 60
|
|
||||||
|
|
||||||
- name: Test if app is accessible
|
|
||||||
uri:
|
|
||||||
url: "http://127.0.0.1:{{ app_port }}/"
|
|
||||||
method: GET
|
|
||||||
status_code: [200, 301, 302]
|
|
||||||
register: app_test
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Show container status
|
|
||||||
shell: "cd {{ app_directory }} && {{ docker_compose_cmd.stdout }} ps"
|
|
||||||
register: container_status
|
|
||||||
when: docker_compose_cmd.stdout != "none"
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Show deployment result
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
🎉 Projekt {{ app_name }} erfolgreich deployed!
|
|
||||||
|
|
||||||
📂 Projektdateien synchronisiert von: {{ local_app_path }}
|
|
||||||
🐳 Verwendete docker-compose.yml:
|
|
||||||
{% if compose_exists.stat.exists %}
|
|
||||||
✅ Aus Projekt-Root: {{ app_directory }}/docker-compose.yml
|
|
||||||
{% elif compose_docker_exists.stat.exists %}
|
|
||||||
✅ Aus docker/ Ordner: {{ app_directory }}/docker/docker-compose.yml
|
|
||||||
{% endif %}
|
|
||||||
🌐 Erreichbar unter: https://{{ domain }}
|
|
||||||
⚙️ Docker Compose: {{ docker_compose_cmd.stdout }}
|
|
||||||
|
|
||||||
📁 index.php Status: {{ 'Gefunden ✅' if index_exists.stat.exists else 'Nicht gefunden ❌' }}
|
|
||||||
|
|
||||||
🐳 Container Status:
|
|
||||||
{{ container_status.stdout if container_status.stdout is defined else 'Status konnte nicht abgerufen werden' }}
|
|
||||||
|
|
||||||
{% if app_test.status is defined and (app_test.status == 200 or app_test.status == 301 or app_test.status == 302) %}
|
|
||||||
✅ App reagiert erfolgreich (HTTP {{ app_test.status }})
|
|
||||||
{% else %}
|
|
||||||
⚠️ App-Test fehlgeschlagen - prüfe Logs mit 'make logs'
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Environment variables for {{ app_name }}
|
|
||||||
|
|
||||||
{% for key, value in app_env.items() %}
|
|
||||||
{{ key }}={{ value }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
# Deployment info
|
|
||||||
DEPLOYED_AT={{ ansible_date_time.iso8601 }}
|
|
||||||
DEPLOYED_BY=ansible
|
|
||||||
SERVER_NAME={{ inventory_hostname }}
|
|
||||||
|
|
||||||
APP_PORT=8000
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name {{ domain }};
|
|
||||||
|
|
||||||
# HTTP to HTTPS redirect (wird von Certbot hinzugefügt)
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://127.0.0.1:{{ app_port }}; # Weiterleitung zu PHP-Container
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
|
|
||||||
# PHP-spezifische Timeouts
|
|
||||||
proxy_connect_timeout 60s;
|
|
||||||
proxy_send_timeout 60s;
|
|
||||||
proxy_read_timeout 60s;
|
|
||||||
|
|
||||||
# File upload für PHP
|
|
||||||
client_max_body_size {{ nginx_client_max_body_size }};
|
|
||||||
}
|
|
||||||
|
|
||||||
# Assets (CSS, JS, Bilder) direkt servieren falls gewünscht
|
|
||||||
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
||||||
proxy_pass http://127.0.0.1:{{ app_port }};
|
|
||||||
proxy_cache_valid 200 1d;
|
|
||||||
expires 1d;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
|
|
||||||
# PHP-Admin Tools (falls vorhanden) schützen
|
|
||||||
location ~ /(admin|phpmyadmin|adminer) {
|
|
||||||
proxy_pass http://127.0.0.1:{{ app_port }};
|
|
||||||
|
|
||||||
# Basis Auth hier hinzufügen falls gewünscht
|
|
||||||
# auth_basic "Admin Area";
|
|
||||||
# auth_basic_user_file /etc/nginx/.htpasswd;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Security headers
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
|
|
||||||
# Gzip compression
|
|
||||||
gzip on;
|
|
||||||
gzip_vary on;
|
|
||||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Test Production Server Connectivity
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SERVER="94.16.110.151"
|
|
||||||
USER="deploy"
|
|
||||||
SSH_KEY="~/.ssh/production"
|
|
||||||
|
|
||||||
echo "🔧 Production Server Connectivity Test"
|
|
||||||
echo "========================================"
|
|
||||||
echo "Server: $SERVER"
|
|
||||||
echo "User: $USER"
|
|
||||||
echo "SSH-Key: $SSH_KEY"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 1. SSH Key Test
|
|
||||||
echo "1️⃣ SSH-Key Test..."
|
|
||||||
if ssh-keygen -l -f $SSH_KEY.pub &>/dev/null; then
|
|
||||||
echo "✅ SSH-Key ist gültig"
|
|
||||||
ssh-keygen -l -f $SSH_KEY.pub
|
|
||||||
else
|
|
||||||
echo "❌ SSH-Key Problem"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 2. SSH Connectivity Test
|
|
||||||
echo "2️⃣ SSH Connectivity Test..."
|
|
||||||
if ssh -i $SSH_KEY -o ConnectTimeout=10 -o StrictHostKeyChecking=no $USER@$SERVER 'echo "SSH Connection successful"' 2>/dev/null; then
|
|
||||||
echo "✅ SSH Connection erfolgreich"
|
|
||||||
else
|
|
||||||
echo "❌ SSH Connection fehlgeschlagen"
|
|
||||||
echo "Möglicherweise ist der Server noch nicht bereit oder SSH-Key nicht konfiguriert"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 3. System Info
|
|
||||||
echo "3️⃣ Server System Information..."
|
|
||||||
ssh -i $SSH_KEY $USER@$SERVER 'echo "Hostname: $(hostname)" && echo "OS: $(cat /etc/os-release | grep PRETTY_NAME)" && echo "Kernel: $(uname -r)" && echo "Uptime: $(uptime -p)" && echo "Available space: $(df -h / | tail -1 | awk "{print \$4}")"'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 4. Docker Readiness Check
|
|
||||||
echo "4️⃣ Docker Readiness Check..."
|
|
||||||
if ssh -i $SSH_KEY $USER@$SERVER 'which docker &>/dev/null && which docker-compose &>/dev/null'; then
|
|
||||||
echo "✅ Docker bereits installiert"
|
|
||||||
ssh -i $SSH_KEY $USER@$SERVER 'docker --version && docker-compose --version'
|
|
||||||
else
|
|
||||||
echo "⚠️ Docker noch nicht installiert (wird durch Ansible installiert)"
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 5. Ansible Ping Test
|
|
||||||
echo "5️⃣ Ansible Ping Test..."
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
if ansible netcup-server -i inventory/hosts.yml -m ping; then
|
|
||||||
echo "✅ Ansible Ping erfolgreich"
|
|
||||||
else
|
|
||||||
echo "❌ Ansible Ping fehlgeschlagen"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 6. Ansible Gather Facts
|
|
||||||
echo "6️⃣ Ansible System Facts..."
|
|
||||||
ansible netcup-server -i inventory/hosts.yml -m setup -a "filter=ansible_distribution*" | grep -A 10 '"ansible_distribution"'
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "🎉 Connectivity Test erfolgreich abgeschlossen!"
|
|
||||||
echo ""
|
|
||||||
echo "Nächste Schritte:"
|
|
||||||
echo "1. Deployment-Playbook ausführen: ansible-playbook -i inventory/hosts.yml deploy.yml"
|
|
||||||
echo "2. SSL-Zertifikate konfigurieren"
|
|
||||||
echo "3. Monitoring einrichten"
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
---
|
|
||||||
# Nur Dateien-Upload ohne Infrastruktur-Setup
|
|
||||||
|
|
||||||
- name: Upload Files Only to Netcup VPS
|
|
||||||
hosts: all
|
|
||||||
become: yes
|
|
||||||
vars_files:
|
|
||||||
- inventory/group_vars.yml
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Check if app directory exists
|
|
||||||
stat:
|
|
||||||
path: "{{ app_directory }}"
|
|
||||||
register: app_dir_exists
|
|
||||||
|
|
||||||
- name: Create app directory if it doesn't exist
|
|
||||||
file:
|
|
||||||
path: "{{ app_directory }}"
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
when: not app_dir_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Check if docker-compose.yml exists locally
|
|
||||||
local_action:
|
|
||||||
module: stat
|
|
||||||
path: "{{ local_app_path }}/docker-compose.yml"
|
|
||||||
register: local_compose_exists
|
|
||||||
become: no
|
|
||||||
|
|
||||||
- name: Show upload information
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
📤 Uploading files...
|
|
||||||
- From: {{ local_app_path }}
|
|
||||||
- To: {{ app_directory }}
|
|
||||||
- Docker Compose available: {{ local_compose_exists.stat.exists }}
|
|
||||||
|
|
||||||
- name: Upload project files
|
|
||||||
synchronize:
|
|
||||||
src: "{{ local_app_path }}/"
|
|
||||||
dest: "{{ app_directory }}/"
|
|
||||||
delete: no
|
|
||||||
archive: yes
|
|
||||||
checksum: yes
|
|
||||||
rsync_opts:
|
|
||||||
- "--exclude=ansible"
|
|
||||||
- "--exclude=.git"
|
|
||||||
- "--exclude=vendor"
|
|
||||||
- "--exclude=node_modules"
|
|
||||||
- "--exclude=storage/logs"
|
|
||||||
- "--exclude=cache"
|
|
||||||
- "--exclude=logs"
|
|
||||||
- "--exclude=dist"
|
|
||||||
- "--exclude=.archive"
|
|
||||||
- "--exclude=x_ansible"
|
|
||||||
- "--exclude=.env"
|
|
||||||
- "--verbose"
|
|
||||||
register: sync_result
|
|
||||||
|
|
||||||
- name: Ensure proper permissions for directories
|
|
||||||
file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
|
||||||
mode: '0755'
|
|
||||||
recurse: yes
|
|
||||||
loop:
|
|
||||||
- "{{ app_directory }}/public"
|
|
||||||
- "{{ app_directory }}/src"
|
|
||||||
- "{{ app_directory }}/docker"
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Create storage directories if they don't exist
|
|
||||||
file:
|
|
||||||
path: "{{ app_directory }}/{{ item }}"
|
|
||||||
state: directory
|
|
||||||
mode: '0777'
|
|
||||||
loop:
|
|
||||||
- "storage/logs"
|
|
||||||
- "storage/cache"
|
|
||||||
- "cache"
|
|
||||||
- "logs"
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: Check if .env exists on server
|
|
||||||
stat:
|
|
||||||
path: "{{ app_directory }}/.env"
|
|
||||||
register: server_env_exists
|
|
||||||
|
|
||||||
- name: Create environment file if it doesn't exist
|
|
||||||
template:
|
|
||||||
src: roles/webapp/templates/app.env.j2
|
|
||||||
dest: "{{ app_directory }}/.env"
|
|
||||||
when: not server_env_exists.stat.exists
|
|
||||||
register: env_created
|
|
||||||
|
|
||||||
- name: Show what was uploaded
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
📂 Upload completed!
|
|
||||||
|
|
||||||
📁 Files synced: {{ 'Yes' if sync_result.changed else 'No changes detected' }}
|
|
||||||
📄 Environment file: {{ 'Created' if env_created.changed else 'Already exists' }}
|
|
||||||
|
|
||||||
📍 Files are now at: {{ app_directory }}
|
|
||||||
|
|
||||||
🔄 To restart the application, run:
|
|
||||||
cd {{ app_directory }}
|
|
||||||
docker compose down && docker compose up -d --build
|
|
||||||
|
|
||||||
or use: make restart (if Makefile is available)
|
|
||||||
|
|
||||||
- name: Check if containers are running (optional restart)
|
|
||||||
shell: "cd {{ app_directory }} && docker compose ps --format json"
|
|
||||||
register: container_status
|
|
||||||
ignore_errors: yes
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Ask about restarting containers
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
ℹ️ Current container status:
|
|
||||||
{{ container_status.stdout if container_status.stdout else 'No containers found or docker compose not available' }}
|
|
||||||
|
|
||||||
💡 Tip: If you want to restart the application with the new files, run:
|
|
||||||
ansible-playbook restart-app.yml
|
|
||||||
|
|
||||||
or manually:
|
|
||||||
ssh {{ ansible_host }} "cd {{ app_directory }} && docker compose restart"
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Cache
|
|
||||||
*.cache
|
|
||||||
.cache/
|
|
||||||
|
|
||||||
# Ansible
|
|
||||||
*.retry
|
|
||||||
.ansible/
|
|
||||||
|
|
||||||
# System
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Backups
|
|
||||||
*.backup
|
|
||||||
*.bak
|
|
||||||
|
|
||||||
# SSL Keys (niemals committen!)
|
|
||||||
*.key
|
|
||||||
*.pem
|
|
||||||
*.crt
|
|
||||||
|
|
||||||
# Secrets
|
|
||||||
vault.yml
|
|
||||||
secrets.yml
|
|
||||||
|
|
||||||
# Temporäre Dateien
|
|
||||||
*.tmp
|
|
||||||
*.temp
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
# Einfache CDN-Verwaltung mit Make
|
|
||||||
|
|
||||||
.PHONY: deploy check health purge-cache warm-cache status reload
|
|
||||||
|
|
||||||
# Standard deployment
|
|
||||||
deploy:
|
|
||||||
@echo "🚀 Deploying Simple CDN..."
|
|
||||||
chmod +x scripts/deploy.sh
|
|
||||||
./scripts/deploy.sh
|
|
||||||
|
|
||||||
# Deployment mit Check-Modus (Dry-Run)
|
|
||||||
check:
|
|
||||||
@echo "🔍 Checking deployment (dry-run)..."
|
|
||||||
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-simple-cdn.yml --check --diff
|
|
||||||
|
|
||||||
# Health check aller Nodes
|
|
||||||
health:
|
|
||||||
@echo "🏥 Checking CDN health..."
|
|
||||||
ansible cdn_nodes -i inventories/production/hosts.yml -m uri -a "url=https://{{ inventory_hostname }}/health method=GET"
|
|
||||||
|
|
||||||
# Cache leeren
|
|
||||||
purge-cache:
|
|
||||||
@echo "🧹 Purging cache on all nodes..."
|
|
||||||
ansible cdn_nodes -i inventories/production/hosts.yml -m shell -a "find /var/cache/nginx/ -type f -delete"
|
|
||||||
@echo "✅ Cache purged on all nodes"
|
|
||||||
|
|
||||||
# Cache warming
|
|
||||||
warm-cache:
|
|
||||||
@echo "🔥 Warming cache..."
|
|
||||||
chmod +x scripts/warm-cache.sh
|
|
||||||
./scripts/warm-cache.sh
|
|
||||||
|
|
||||||
# Status-Report
|
|
||||||
status:
|
|
||||||
@echo "📊 CDN Status Report..."
|
|
||||||
ansible cdn_nodes -i inventories/production/hosts.yml -m shell -a "echo '=== {{ inventory_hostname }} ===' && /usr/local/bin/cdn-monitor && echo ''"
|
|
||||||
|
|
||||||
# Nginx neuladen
|
|
||||||
reload:
|
|
||||||
@echo "⚙️ Reloading nginx configuration..."
|
|
||||||
ansible cdn_nodes -i inventories/production/hosts.yml -m systemd -a "name=nginx state=reloaded"
|
|
||||||
|
|
||||||
# SSL-Zertifikate erneuern
|
|
||||||
renew-ssl:
|
|
||||||
@echo "🔐 Renewing SSL certificates..."
|
|
||||||
ansible cdn_nodes -i inventories/production/hosts.yml -m shell -a "certbot renew --quiet"
|
|
||||||
|
|
||||||
# Interaktive Verwaltung
|
|
||||||
manage:
|
|
||||||
@echo "🔧 Starting interactive management..."
|
|
||||||
ansible-playbook -i inventories/production/hosts.yml playbooks/manage-cdn.yml
|
|
||||||
|
|
||||||
# Hilfe
|
|
||||||
help:
|
|
||||||
@echo "📖 Available commands:"
|
|
||||||
@echo " make deploy - Deploy CDN"
|
|
||||||
@echo " make check - Test deployment (dry-run)"
|
|
||||||
@echo " make health - Check all nodes health"
|
|
||||||
@echo " make purge-cache - Clear all cache"
|
|
||||||
@echo " make warm-cache - Warm cache with popular URLs"
|
|
||||||
@echo " make status - Show detailed status"
|
|
||||||
@echo " make reload - Reload nginx config"
|
|
||||||
@echo " make renew-ssl - Renew SSL certificates"
|
|
||||||
@echo " make manage - Interactive management"
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# Simple Nginx CDN für Deutschland
|
|
||||||
|
|
||||||
Dieses Ansible-Projekt erstellt ein einfaches, aber effektives CDN nur mit Nginx für deutsche Server.
|
|
||||||
|
|
||||||
## Schnellstart
|
|
||||||
|
|
||||||
1. **Konfiguration anpassen:**
|
|
||||||
```bash
|
|
||||||
# Server-IPs eintragen
|
|
||||||
vim inventories/production/hosts.yml
|
|
||||||
|
|
||||||
# Domains anpassen
|
|
||||||
vim inventories/production/group_vars/all/main.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Deployment:**
|
|
||||||
```bash
|
|
||||||
# Testen
|
|
||||||
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-simple-cdn.yml --check
|
|
||||||
|
|
||||||
# Deployen
|
|
||||||
ansible-playbook -i inventories/production/hosts.yml playbooks/deploy-simple-cdn.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Verwalten:**
|
|
||||||
```bash
|
|
||||||
# Cache leeren
|
|
||||||
make purge-cache
|
|
||||||
|
|
||||||
# Status prüfen
|
|
||||||
make health
|
|
||||||
```
|
|
||||||
|
|
||||||
## Struktur
|
|
||||||
|
|
||||||
- `inventories/` - Server-Konfiguration
|
|
||||||
- `roles/` - Ansible-Rollen
|
|
||||||
- `playbooks/` - Deployment-Skripte
|
|
||||||
- `scripts/` - Hilfsskripte
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
✅ Nginx-basiertes CDN
|
|
||||||
✅ SSL mit Let's Encrypt
|
|
||||||
✅ DSGVO-konforme Logs
|
|
||||||
✅ Einfaches Monitoring
|
|
||||||
✅ Cache-Management
|
|
||||||
✅ Rate Limiting
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
# SETUP.md - Einrichtungsanleitung
|
|
||||||
|
|
||||||
## 1. Vorbereitung
|
|
||||||
|
|
||||||
### Server vorbereiten
|
|
||||||
```bash
|
|
||||||
# Für jeden CDN-Server (als root):
|
|
||||||
apt update && apt upgrade -y
|
|
||||||
apt install -y python3 python3-pip
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSH-Keys einrichten
|
|
||||||
```bash
|
|
||||||
# Auf deinem lokalen Rechner:
|
|
||||||
ssh-keygen -t rsa -b 4096 -C "cdn-deployment"
|
|
||||||
ssh-copy-id root@cdn-fra1.example.de
|
|
||||||
ssh-copy-id root@cdn-ham1.example.de
|
|
||||||
ssh-copy-id root@cdn-muc1.example.de
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. Konfiguration anpassen
|
|
||||||
|
|
||||||
### Domains und IPs ändern
|
|
||||||
```bash
|
|
||||||
# 1. Server-IPs eintragen
|
|
||||||
vim inventories/production/hosts.yml
|
|
||||||
|
|
||||||
# 2. Domain-Namen anpassen
|
|
||||||
vim inventories/production/group_vars/all/main.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**Wichtig:** Ändere diese Werte:
|
|
||||||
- `cdn_domain: "cdn.example.de"` → deine CDN-Domain
|
|
||||||
- `ssl_email: "admin@example.de"` → deine E-Mail
|
|
||||||
- `origin_domain: "www.example.de"` → deine Website
|
|
||||||
- Alle IP-Adressen in `hosts.yml`
|
|
||||||
|
|
||||||
## 3. DNS konfigurieren
|
|
||||||
|
|
||||||
Bevor du deployest, stelle sicher dass deine CDN-Domain zu den Servern zeigt:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# A-Records für deine CDN-Domain:
|
|
||||||
cdn.example.de. IN A 10.0.1.10 # Frankfurt
|
|
||||||
cdn.example.de. IN A 10.0.2.10 # Hamburg
|
|
||||||
cdn.example.de. IN A 10.0.3.10 # München
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Deployment
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Testen
|
|
||||||
make check
|
|
||||||
|
|
||||||
# Deployen
|
|
||||||
make deploy
|
|
||||||
|
|
||||||
# Health-Check
|
|
||||||
make health
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Testen
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# CDN testen
|
|
||||||
curl -I https://cdn.example.de/health
|
|
||||||
|
|
||||||
# Cache-Header prüfen
|
|
||||||
curl -I https://cdn.example.de/some-static-file.css
|
|
||||||
|
|
||||||
# Performance testen
|
|
||||||
time curl -o /dev/null -s https://cdn.example.de/
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. Wartung
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Cache leeren
|
|
||||||
make purge-cache
|
|
||||||
|
|
||||||
# Status prüfen
|
|
||||||
make status
|
|
||||||
|
|
||||||
# SSL erneuern
|
|
||||||
make renew-ssl
|
|
||||||
|
|
||||||
# Interaktive Verwaltung
|
|
||||||
make manage
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Ansible-Verbindung testen
|
|
||||||
```bash
|
|
||||||
ansible all -m ping
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nginx-Konfiguration prüfen
|
|
||||||
```bash
|
|
||||||
ansible cdn_nodes -m shell -a "nginx -t"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Logs anschauen
|
|
||||||
```bash
|
|
||||||
ansible cdn_nodes -m shell -a "tail -f /var/log/nginx/error.log"
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSL-Probleme
|
|
||||||
```bash
|
|
||||||
# SSL-Status prüfen
|
|
||||||
ansible cdn_nodes -m shell -a "certbot certificates"
|
|
||||||
|
|
||||||
# Manuell erneuern
|
|
||||||
ansible cdn_nodes -m shell -a "certbot renew --force-renewal"
|
|
||||||
```
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[defaults]
|
|
||||||
inventory = inventories/production/hosts.yml
|
|
||||||
host_key_checking = False
|
|
||||||
timeout = 30
|
|
||||||
forks = 5
|
|
||||||
|
|
||||||
[privilege_escalation]
|
|
||||||
become = True
|
|
||||||
become_method = sudo
|
|
||||||
become_user = root
|
|
||||||
become_ask_pass = False
|
|
||||||
|
|
||||||
[ssh_connection]
|
|
||||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
|
|
||||||
control_path = /tmp/ansible-ssh-%%h-%%p-%%r
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Scripts ausführbar machen
|
|
||||||
chmod +x scripts/deploy.sh
|
|
||||||
chmod +x scripts/warm-cache.sh
|
|
||||||
|
|
||||||
echo "✅ CDN-Projekt wurde erfolgreich erstellt!"
|
|
||||||
echo ""
|
|
||||||
echo "Nächste Schritte:"
|
|
||||||
echo "1. Konfiguration anpassen:"
|
|
||||||
echo " - vim inventories/production/hosts.yml"
|
|
||||||
echo " - vim inventories/production/group_vars/all/main.yml"
|
|
||||||
echo ""
|
|
||||||
echo "2. SETUP.md lesen für detaillierte Anleitung"
|
|
||||||
echo ""
|
|
||||||
echo "3. Deployment testen:"
|
|
||||||
echo " make check"
|
|
||||||
echo ""
|
|
||||||
echo "4. CDN deployen:"
|
|
||||||
echo " make deploy"
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
# Globale Variablen für das CDN
|
|
||||||
|
|
||||||
# Domain-Konfiguration (ÄNDERE DIESE!)
|
|
||||||
cdn_domain: "cdn.example.de" # Deine CDN-Domain
|
|
||||||
ssl_email: "admin@example.de" # E-Mail für SSL-Zertifikate
|
|
||||||
origin_domain: "www.example.de" # Deine Haupt-Website
|
|
||||||
|
|
||||||
# Cache-Einstellungen
|
|
||||||
cache_settings:
|
|
||||||
static_files_ttl: "1y" # CSS, JS, Fonts
|
|
||||||
images_ttl: "30d" # Bilder
|
|
||||||
html_ttl: "5m" # HTML-Seiten
|
|
||||||
api_ttl: "0" # APIs (kein Caching)
|
|
||||||
|
|
||||||
# DSGVO-Einstellungen
|
|
||||||
gdpr_settings:
|
|
||||||
log_retention_days: 30
|
|
||||||
anonymize_ips: true
|
|
||||||
cookie_consent_required: true
|
|
||||||
|
|
||||||
# Rate Limiting
|
|
||||||
rate_limits:
|
|
||||||
api: "10r/s"
|
|
||||||
static: "100r/s"
|
|
||||||
images: "50r/s"
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
# CDN-Node spezifische Konfiguration
|
|
||||||
|
|
||||||
# Nginx Performance
|
|
||||||
nginx_worker_processes: "auto"
|
|
||||||
nginx_worker_connections: 2048
|
|
||||||
nginx_keepalive_timeout: 65
|
|
||||||
|
|
||||||
# Performance-Tuning
|
|
||||||
tcp_optimizations:
|
|
||||||
tcp_nodelay: "on"
|
|
||||||
tcp_nopush: "on"
|
|
||||||
sendfile: "on"
|
|
||||||
|
|
||||||
# Proxy-Einstellungen
|
|
||||||
proxy_settings:
|
|
||||||
connect_timeout: "5s"
|
|
||||||
send_timeout: "10s"
|
|
||||||
read_timeout: "10s"
|
|
||||||
buffering: "on"
|
|
||||||
buffer_size: "64k"
|
|
||||||
buffers: "8 64k"
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
# Inventar mit gruppierten SSH-Schlüsseln
|
|
||||||
|
|
||||||
all:
|
|
||||||
children:
|
|
||||||
origin_servers:
|
|
||||||
hosts:
|
|
||||||
origin1.example.de:
|
|
||||||
ansible_host: 192.168.1.10
|
|
||||||
origin2.example.de:
|
|
||||||
ansible_host: 192.168.1.11
|
|
||||||
vars:
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/origin_servers_key
|
|
||||||
|
|
||||||
cdn_nodes:
|
|
||||||
children:
|
|
||||||
primary_nodes:
|
|
||||||
hosts:
|
|
||||||
cdn-fra1.example.de:
|
|
||||||
ansible_host: 10.0.1.10
|
|
||||||
city: "Frankfurt"
|
|
||||||
region: "Hessen"
|
|
||||||
tier: "primary"
|
|
||||||
cache_size: "50g"
|
|
||||||
vars:
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/cdn_primary_key
|
|
||||||
|
|
||||||
secondary_nodes:
|
|
||||||
hosts:
|
|
||||||
cdn-ham1.example.de:
|
|
||||||
ansible_host: 10.0.2.10
|
|
||||||
city: "Hamburg"
|
|
||||||
region: "Hamburg"
|
|
||||||
tier: "secondary"
|
|
||||||
cache_size: "20g"
|
|
||||||
cdn-muc1.example.de:
|
|
||||||
ansible_host: 10.0.3.10
|
|
||||||
city: "München"
|
|
||||||
region: "Bayern"
|
|
||||||
tier: "secondary"
|
|
||||||
cache_size: "20g"
|
|
||||||
vars:
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/cdn_secondary_key
|
|
||||||
|
|
||||||
vars:
|
|
||||||
ansible_user: root
|
|
||||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
---
|
|
||||||
# Inventar mit individuellen SSH-Schlüsseln
|
|
||||||
|
|
||||||
all:
|
|
||||||
children:
|
|
||||||
origin_servers:
|
|
||||||
hosts:
|
|
||||||
origin1.example.de:
|
|
||||||
ansible_host: 192.168.1.10
|
|
||||||
datacenter: "Frankfurt"
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/origin1_key
|
|
||||||
origin2.example.de:
|
|
||||||
ansible_host: 192.168.1.11
|
|
||||||
datacenter: "Frankfurt"
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/origin2_key
|
|
||||||
|
|
||||||
cdn_nodes:
|
|
||||||
hosts:
|
|
||||||
# Frankfurt - Primary
|
|
||||||
cdn-fra1.example.de:
|
|
||||||
ansible_host: 10.0.1.10
|
|
||||||
city: "Frankfurt"
|
|
||||||
region: "Hessen"
|
|
||||||
tier: "primary"
|
|
||||||
cache_size: "50g"
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/cdn_fra1_key
|
|
||||||
|
|
||||||
# Hamburg - Secondary
|
|
||||||
cdn-ham1.example.de:
|
|
||||||
ansible_host: 10.0.2.10
|
|
||||||
city: "Hamburg"
|
|
||||||
region: "Hamburg"
|
|
||||||
tier: "secondary"
|
|
||||||
cache_size: "20g"
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/cdn_ham1_key
|
|
||||||
|
|
||||||
# München - Secondary
|
|
||||||
cdn-muc1.example.de:
|
|
||||||
ansible_host: 10.0.3.10
|
|
||||||
city: "München"
|
|
||||||
region: "Bayern"
|
|
||||||
tier: "secondary"
|
|
||||||
cache_size: "20g"
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/cdn_muc1_key
|
|
||||||
|
|
||||||
vars:
|
|
||||||
ansible_user: root
|
|
||||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
# Inventar für deutsches CDN
|
|
||||||
|
|
||||||
all:
|
|
||||||
children:
|
|
||||||
origin_servers:
|
|
||||||
hosts:
|
|
||||||
origin1.example.de:
|
|
||||||
ansible_host: 192.168.1.10 # Ändere diese IP
|
|
||||||
datacenter: "Frankfurt"
|
|
||||||
origin2.example.de:
|
|
||||||
ansible_host: 192.168.1.11 # Ändere diese IP
|
|
||||||
datacenter: "Frankfurt"
|
|
||||||
|
|
||||||
cdn_nodes:
|
|
||||||
hosts:
|
|
||||||
# Frankfurt - Primary
|
|
||||||
cdn-fra1.example.de:
|
|
||||||
ansible_host: 10.0.1.10 # Ändere diese IP
|
|
||||||
city: "Frankfurt"
|
|
||||||
region: "Hessen"
|
|
||||||
tier: "primary"
|
|
||||||
cache_size: "50g"
|
|
||||||
|
|
||||||
# Hamburg - Secondary
|
|
||||||
cdn-ham1.example.de:
|
|
||||||
ansible_host: 10.0.2.10 # Ändere diese IP
|
|
||||||
city: "Hamburg"
|
|
||||||
region: "Hamburg"
|
|
||||||
tier: "secondary"
|
|
||||||
cache_size: "20g"
|
|
||||||
|
|
||||||
# München - Secondary
|
|
||||||
cdn-muc1.example.de:
|
|
||||||
ansible_host: 10.0.3.10 # Ändere diese IP
|
|
||||||
city: "München"
|
|
||||||
region: "Bayern"
|
|
||||||
tier: "secondary"
|
|
||||||
cache_size: "20g"
|
|
||||||
|
|
||||||
vars:
|
|
||||||
# SSH-Konfiguration
|
|
||||||
ansible_user: root
|
|
||||||
ansible_ssh_private_key_file: ~/.ssh/id_rsa
|
|
||||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
---
|
|
||||||
# Simple CDN Deployment
|
|
||||||
|
|
||||||
- name: Deploy Simple CDN for Germany
|
|
||||||
hosts: cdn_nodes
|
|
||||||
become: yes
|
|
||||||
serial: 1 # Ein Node nach dem anderen
|
|
||||||
|
|
||||||
pre_tasks:
|
|
||||||
- name: Update system packages
|
|
||||||
apt:
|
|
||||||
update_cache: yes
|
|
||||||
upgrade: dist
|
|
||||||
|
|
||||||
- name: Install required packages
|
|
||||||
apt:
|
|
||||||
name:
|
|
||||||
- nginx
|
|
||||||
- certbot
|
|
||||||
- python3-certbot-nginx
|
|
||||||
- curl
|
|
||||||
- htop
|
|
||||||
- bc
|
|
||||||
state: present
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- nginx-cdn-config
|
|
||||||
- ssl-certificates
|
|
||||||
- simple-monitoring
|
|
||||||
|
|
||||||
post_tasks:
|
|
||||||
- name: Verify CDN health
|
|
||||||
uri:
|
|
||||||
url: "https://{{ inventory_hostname }}/health"
|
|
||||||
method: GET
|
|
||||||
status_code: 200
|
|
||||||
validate_certs: yes
|
|
||||||
retries: 5
|
|
||||||
delay: 10
|
|
||||||
|
|
||||||
- name: Display deployment success
|
|
||||||
debug:
|
|
||||||
msg: "✅ CDN Node {{ inventory_hostname }} ({{ city }}) successfully deployed!"
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
---
|
|
||||||
# CDN Management Tasks
|
|
||||||
- name: CDN Management and Maintenance
|
|
||||||
hosts: cdn_nodes
|
|
||||||
become: yes
|
|
||||||
vars_prompt:
|
|
||||||
- name: action
|
|
||||||
prompt: "What action? (purge-cache/reload-config/check-health/view-stats/warm-cache)"
|
|
||||||
private: no
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Purge all cache
|
|
||||||
shell: find /var/cache/nginx/ -type f -delete
|
|
||||||
when: action == "purge-cache"
|
|
||||||
|
|
||||||
- name: Display cache purge result
|
|
||||||
debug:
|
|
||||||
msg: "✅ Cache purged on {{ inventory_hostname }}"
|
|
||||||
when: action == "purge-cache"
|
|
||||||
|
|
||||||
- name: Reload nginx configuration
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: reloaded
|
|
||||||
when: action == "reload-config"
|
|
||||||
|
|
||||||
- name: Check CDN health
|
|
||||||
uri:
|
|
||||||
url: "https://{{ inventory_hostname }}/health"
|
|
||||||
method: GET
|
|
||||||
status_code: 200
|
|
||||||
register: health_result
|
|
||||||
when: action == "check-health"
|
|
||||||
|
|
||||||
- name: Display health result
|
|
||||||
debug:
|
|
||||||
msg: "{{ health_result.content }}"
|
|
||||||
when: action == "check-health"
|
|
||||||
|
|
||||||
- name: Show cache and system statistics
|
|
||||||
shell: |
|
|
||||||
echo "=== Cache Size ==="
|
|
||||||
du -sh /var/cache/nginx/
|
|
||||||
echo "=== Cache Files ==="
|
|
||||||
find /var/cache/nginx/ -type f | wc -l
|
|
||||||
echo "=== System Load ==="
|
|
||||||
uptime
|
|
||||||
echo "=== Memory Usage ==="
|
|
||||||
free -h
|
|
||||||
echo "=== Disk Usage ==="
|
|
||||||
df -h /
|
|
||||||
register: stats_result
|
|
||||||
when: action == "view-stats"
|
|
||||||
|
|
||||||
- name: Display statistics
|
|
||||||
debug:
|
|
||||||
msg: "{{ stats_result.stdout_lines }}"
|
|
||||||
when: action == "view-stats"
|
|
||||||
|
|
||||||
- name: Warm cache with popular URLs
|
|
||||||
uri:
|
|
||||||
url: "https://{{ inventory_hostname }}{{ item }}"
|
|
||||||
method: GET
|
|
||||||
loop:
|
|
||||||
- "/"
|
|
||||||
- "/health"
|
|
||||||
ignore_errors: yes
|
|
||||||
when: action == "warm-cache"
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
- name: reload nginx
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: reloaded
|
|
||||||
|
|
||||||
- name: restart nginx
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: restarted
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
---
|
|
||||||
# Nginx CDN Konfiguration
|
|
||||||
|
|
||||||
- name: Remove default nginx config
|
|
||||||
file:
|
|
||||||
path: /etc/nginx/sites-enabled/default
|
|
||||||
state: absent
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Create nginx directories
|
|
||||||
file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: directory
|
|
||||||
owner: www-data
|
|
||||||
group: www-data
|
|
||||||
mode: '0755'
|
|
||||||
loop:
|
|
||||||
- /var/cache/nginx/static
|
|
||||||
- /var/cache/nginx/images
|
|
||||||
- /var/cache/nginx/html
|
|
||||||
- /var/log/nginx/cdn
|
|
||||||
- /etc/nginx/includes
|
|
||||||
|
|
||||||
- name: Configure nginx main config
|
|
||||||
template:
|
|
||||||
src: nginx.conf.j2
|
|
||||||
dest: /etc/nginx/nginx.conf
|
|
||||||
backup: yes
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Create nginx includes
|
|
||||||
template:
|
|
||||||
src: "{{ item }}.j2"
|
|
||||||
dest: "/etc/nginx/includes/{{ item }}"
|
|
||||||
loop:
|
|
||||||
- security-headers.conf
|
|
||||||
- rate-limiting.conf
|
|
||||||
- gzip-settings.conf
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Configure CDN site
|
|
||||||
template:
|
|
||||||
src: cdn-site.conf.j2
|
|
||||||
dest: /etc/nginx/sites-available/cdn
|
|
||||||
backup: yes
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Enable CDN site
|
|
||||||
file:
|
|
||||||
src: /etc/nginx/sites-available/cdn
|
|
||||||
dest: /etc/nginx/sites-enabled/cdn
|
|
||||||
state: link
|
|
||||||
notify: reload nginx
|
|
||||||
|
|
||||||
- name: Test nginx configuration
|
|
||||||
command: nginx -t
|
|
||||||
register: nginx_test
|
|
||||||
failed_when: nginx_test.rc != 0
|
|
||||||
|
|
||||||
- name: Start and enable nginx
|
|
||||||
systemd:
|
|
||||||
name: nginx
|
|
||||||
state: started
|
|
||||||
enabled: yes
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
listen [::]:80;
|
|
||||||
server_name {{ cdn_domain }};
|
|
||||||
|
|
||||||
# Redirect HTTP to HTTPS
|
|
||||||
return 301 https://$server_name$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
listen [::]:443 ssl http2;
|
|
||||||
server_name {{ cdn_domain }};
|
|
||||||
|
|
||||||
# SSL Configuration (wird von Certbot automatisch gefüllt)
|
|
||||||
ssl_certificate /etc/letsencrypt/live/{{ cdn_domain }}/fullchain.pem;
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/{{ cdn_domain }}/privkey.pem;
|
|
||||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
||||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
||||||
|
|
||||||
# CDN Headers
|
|
||||||
add_header X-CDN-Node "{{ city }}, Deutschland";
|
|
||||||
add_header X-Cache-Status $upstream_cache_status always;
|
|
||||||
add_header X-Served-By "nginx-{{ inventory_hostname }}";
|
|
||||||
|
|
||||||
# Security Headers
|
|
||||||
include /etc/nginx/includes/security-headers.conf;
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
access_log /var/log/nginx/cdn/{{ cdn_domain }}.access.log cdn_format;
|
|
||||||
error_log /var/log/nginx/cdn/{{ cdn_domain }}.error.log warn;
|
|
||||||
|
|
||||||
# Rate Limiting
|
|
||||||
include /etc/nginx/includes/rate-limiting.conf;
|
|
||||||
|
|
||||||
# Gzip Compression
|
|
||||||
include /etc/nginx/includes/gzip-settings.conf;
|
|
||||||
|
|
||||||
# Nginx Status für Monitoring
|
|
||||||
location /nginx_status {
|
|
||||||
stub_status on;
|
|
||||||
access_log off;
|
|
||||||
allow 127.0.0.1;
|
|
||||||
allow 10.0.0.0/8;
|
|
||||||
deny all;
|
|
||||||
}
|
|
||||||
|
|
||||||
##
|
|
||||||
# Static Files (CSS, JS, Fonts) - Lange Cache-Zeit
|
|
||||||
##
|
|
||||||
location ~* \.(css|js|woff|woff2|ttf|eot|ico)$ {
|
|
||||||
proxy_pass https://origin_servers;
|
|
||||||
proxy_ssl_verify off;
|
|
||||||
|
|
||||||
# Cache Settings
|
|
||||||
proxy_cache static_cache;
|
|
||||||
proxy_cache_valid 200 302 {{ cache_settings.static_files_ttl }};
|
|
||||||
proxy_cache_valid 404 1m;
|
|
||||||
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
|
|
||||||
proxy_cache_lock on;
|
|
||||||
proxy_cache_revalidate on;
|
|
||||||
|
|
||||||
# Headers
|
|
||||||
expires {{ cache_settings.static_files_ttl }};
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
add_header Vary "Accept-Encoding";
|
|
||||||
|
|
||||||
# Rate Limiting
|
|
||||||
limit_req zone=static_files burst=50 nodelay;
|
|
||||||
|
|
||||||
# Proxy Headers
|
|
||||||
proxy_set_header Host {{ origin_domain }};
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
##
|
|
||||||
# Images - Mittlere Cache-Zeit
|
|
||||||
##
|
|
||||||
location ~* \.(jpg|jpeg|png|gif|webp|svg|avif|bmp|tiff)$ {
|
|
||||||
proxy_pass https://origin_servers;
|
|
||||||
proxy_ssl_verify off;
|
|
||||||
|
|
||||||
# Cache Settings
|
|
||||||
proxy_cache images_cache;
|
|
||||||
proxy_cache_valid 200 302 {{ cache_settings.images_ttl }};
|
|
||||||
proxy_cache_valid 404 5m;
|
|
||||||
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
|
|
||||||
|
|
||||||
# Headers
|
|
||||||
expires {{ cache_settings.images_ttl }};
|
|
||||||
add_header Cache-Control "public";
|
|
||||||
add_header Vary "Accept";
|
|
||||||
|
|
||||||
# Rate Limiting
|
|
||||||
limit_req zone=images burst=30 nodelay;
|
|
||||||
|
|
||||||
# Proxy Headers
|
|
||||||
proxy_set_header Host {{ origin_domain }};
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
##
|
|
||||||
# API Endpoints - Kein Caching
|
|
||||||
##
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass https://origin_servers;
|
|
||||||
proxy_ssl_verify off;
|
|
||||||
|
|
||||||
# Kein Caching
|
|
||||||
proxy_no_cache 1;
|
|
||||||
proxy_cache_bypass 1;
|
|
||||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
||||||
add_header Pragma "no-cache";
|
|
||||||
add_header Expires "0";
|
|
||||||
|
|
||||||
# Rate Limiting
|
|
||||||
limit_req zone=api burst=10 nodelay;
|
|
||||||
|
|
||||||
# CORS Headers
|
|
||||||
add_header Access-Control-Allow-Origin "https://{{ origin_domain }}";
|
|
||||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
|
||||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With";
|
|
||||||
add_header Access-Control-Allow-Credentials true;
|
|
||||||
|
|
||||||
# Proxy Headers
|
|
||||||
proxy_set_header Host {{ origin_domain }};
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
|
|
||||||
# Timeouts
|
|
||||||
proxy_connect_timeout {{ proxy_settings.connect_timeout }};
|
|
||||||
proxy_send_timeout {{ proxy_settings.send_timeout }};
|
|
||||||
proxy_read_timeout {{ proxy_settings.read_timeout }};
|
|
||||||
}
|
|
||||||
|
|
||||||
##
|
|
||||||
# Cache Purge (nur interne IPs)
|
|
||||||
##
|
|
||||||
location ~ /purge(/.*) {
|
|
||||||
allow 127.0.0.1;
|
|
||||||
allow 10.0.0.0/8;
|
|
||||||
allow 192.168.0.0/16;
|
|
||||||
deny all;
|
|
||||||
|
|
||||||
proxy_cache_purge static_cache $scheme$proxy_host$1;
|
|
||||||
}
|
|
||||||
|
|
||||||
##
|
|
||||||
# Health Check
|
|
||||||
##
|
|
||||||
location /health {
|
|
||||||
access_log off;
|
|
||||||
return 200 "OK - CDN Node {{ city }}\nRegion: {{ region }}\nTier: {{ tier }}\nTimestamp: $time_iso8601\n";
|
|
||||||
add_header Content-Type text/plain;
|
|
||||||
}
|
|
||||||
|
|
||||||
##
|
|
||||||
# Default Location - HTML Caching
|
|
||||||
##
|
|
||||||
location / {
|
|
||||||
proxy_pass https://origin_servers;
|
|
||||||
proxy_ssl_verify off;
|
|
||||||
|
|
||||||
# Cache Settings für HTML
|
|
||||||
proxy_cache html_cache;
|
|
||||||
proxy_cache_valid 200 {{ cache_settings.html_ttl }};
|
|
||||||
proxy_cache_valid 404 1m;
|
|
||||||
proxy_cache_bypass $arg_nocache $cookie_sessionid $http_authorization;
|
|
||||||
|
|
||||||
# Headers
|
|
||||||
add_header Cache-Control "public, max-age=300";
|
|
||||||
|
|
||||||
# Proxy Headers
|
|
||||||
proxy_set_header Host {{ origin_domain }};
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-CDN-Node "{{ city }}";
|
|
||||||
|
|
||||||
# Timeouts
|
|
||||||
proxy_connect_timeout {{ proxy_settings.connect_timeout }};
|
|
||||||
proxy_send_timeout {{ proxy_settings.send_timeout }};
|
|
||||||
proxy_read_timeout {{ proxy_settings.read_timeout }};
|
|
||||||
|
|
||||||
# Buffering
|
|
||||||
proxy_buffering {{ proxy_settings.buffering }};
|
|
||||||
proxy_buffer_size {{ proxy_settings.buffer_size }};
|
|
||||||
proxy_buffers {{ proxy_settings.buffers }};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Gzip Compression Settings
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
gzip_vary on;
|
|
||||||
gzip_min_length 1024;
|
|
||||||
gzip_comp_level 6;
|
|
||||||
gzip_proxied any;
|
|
||||||
gzip_disable "msie6";
|
|
||||||
|
|
||||||
gzip_types
|
|
||||||
text/plain
|
|
||||||
text/css
|
|
||||||
text/xml
|
|
||||||
text/javascript
|
|
||||||
application/javascript
|
|
||||||
application/xml+rss
|
|
||||||
application/atom+xml
|
|
||||||
image/svg+xml
|
|
||||||
application/json
|
|
||||||
application/ld+json;
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
user www-data;
|
|
||||||
worker_processes {{ nginx_worker_processes }};
|
|
||||||
pid /run/nginx.pid;
|
|
||||||
include /etc/nginx/modules-enabled/*.conf;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections {{ nginx_worker_connections }};
|
|
||||||
use epoll;
|
|
||||||
multi_accept on;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
##
|
|
||||||
# Basic Settings
|
|
||||||
##
|
|
||||||
sendfile {{ tcp_optimizations.sendfile }};
|
|
||||||
tcp_nopush {{ tcp_optimizations.tcp_nopush }};
|
|
||||||
tcp_nodelay {{ tcp_optimizations.tcp_nodelay }};
|
|
||||||
keepalive_timeout {{ nginx_keepalive_timeout }};
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
server_tokens off;
|
|
||||||
|
|
||||||
server_names_hash_bucket_size 64;
|
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
##
|
|
||||||
# DSGVO-konforme Logging
|
|
||||||
##
|
|
||||||
map $remote_addr $anonymized_ip {
|
|
||||||
~(?P<ip>\d+\.\d+\.\d+)\.\d+ $ip.0;
|
|
||||||
~(?P<ipv6>[^:]+:[^:]+:[^:]+:[^:]+):.* $ipv6::;
|
|
||||||
default 0.0.0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
log_format cdn_format '$anonymized_ip - $remote_user [$time_local] '
|
|
||||||
'"$request" $status $body_bytes_sent '
|
|
||||||
'"$http_referer" "$http_user_agent" '
|
|
||||||
'rt=$request_time '
|
|
||||||
'cache="$upstream_cache_status" '
|
|
||||||
'cdn_node="{{ inventory_hostname }}"';
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log cdn_format;
|
|
||||||
error_log /var/log/nginx/error.log warn;
|
|
||||||
|
|
||||||
##
|
|
||||||
# Cache Paths
|
|
||||||
##
|
|
||||||
proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static_cache:100m
|
|
||||||
max_size={{ cache_size }} inactive=7d use_temp_path=off;
|
|
||||||
proxy_cache_path /var/cache/nginx/images levels=1:2 keys_zone=images_cache:100m
|
|
||||||
max_size={{ cache_size }} inactive=30d use_temp_path=off;
|
|
||||||
proxy_cache_path /var/cache/nginx/html levels=1:2 keys_zone=html_cache:50m
|
|
||||||
max_size=5g inactive=1h use_temp_path=off;
|
|
||||||
|
|
||||||
##
|
|
||||||
# Upstream zu Origin-Servern
|
|
||||||
##
|
|
||||||
upstream origin_servers {
|
|
||||||
{% for host in groups['origin_servers'] %}
|
|
||||||
server {{ hostvars[host]['ansible_default_ipv4']['address'] }}:443
|
|
||||||
weight=1 max_fails=3 fail_timeout=30s;
|
|
||||||
{% endfor %}
|
|
||||||
keepalive 32;
|
|
||||||
keepalive_requests 1000;
|
|
||||||
keepalive_timeout 60s;
|
|
||||||
}
|
|
||||||
|
|
||||||
##
|
|
||||||
# Include configurations
|
|
||||||
##
|
|
||||||
include /etc/nginx/includes/*.conf;
|
|
||||||
include /etc/nginx/sites-enabled/*;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# Rate Limiting Zones
|
|
||||||
|
|
||||||
limit_req_zone $binary_remote_addr zone=api:10m rate={{ rate_limits.api }};
|
|
||||||
limit_req_zone $binary_remote_addr zone=static_files:10m rate={{ rate_limits.static }};
|
|
||||||
limit_req_zone $binary_remote_addr zone=images:10m rate={{ rate_limits.images }};
|
|
||||||
|
|
||||||
# Connection Limiting
|
|
||||||
limit_conn_zone $binary_remote_addr zone=perip:10m;
|
|
||||||
limit_conn perip 10;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Security Headers für CDN
|
|
||||||
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
||||||
|
|
||||||
# DSGVO Header
|
|
||||||
add_header X-Data-Processing "Art. 6 Abs. 1 lit. f DSGVO" always;
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
# Einfaches Monitoring ohne Prometheus
|
|
||||||
|
|
||||||
- name: Create simple monitoring script
|
|
||||||
template:
|
|
||||||
src: simple-monitor.sh.j2
|
|
||||||
dest: /usr/local/bin/cdn-monitor
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: Setup monitoring cron job
|
|
||||||
cron:
|
|
||||||
name: "CDN Health Monitor"
|
|
||||||
minute: "*/5"
|
|
||||||
job: "/usr/local/bin/cdn-monitor"
|
|
||||||
user: root
|
|
||||||
|
|
||||||
- name: Create log rotation for monitoring logs
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
/var/log/nginx/cdn-monitor.log {
|
|
||||||
weekly
|
|
||||||
missingok
|
|
||||||
rotate 4
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
}
|
|
||||||
dest: /etc/logrotate.d/cdn-monitor
|
|
||||||
mode: '0644'
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Einfaches CDN Monitoring für {{ inventory_hostname }}
|
|
||||||
|
|
||||||
LOG_FILE="/var/log/nginx/cdn-monitor.log"
|
|
||||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
CDN_DOMAIN="{{ cdn_domain }}"
|
|
||||||
|
|
||||||
# Health Check
|
|
||||||
health_check() {
|
|
||||||
local response=$(curl -s -o /dev/null -w "%{http_code}" "https://$CDN_DOMAIN/health")
|
|
||||||
|
|
||||||
if [ "$response" = "200" ]; then
|
|
||||||
echo "[$TIMESTAMP] ✅ Health check OK" >> $LOG_FILE
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo "[$TIMESTAMP] ❌ Health check FAILED (HTTP $response)" >> $LOG_FILE
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Nginx-Statistiken
|
|
||||||
nginx_stats() {
|
|
||||||
local stats=$(curl -s http://127.0.0.1/nginx_status 2>/dev/null)
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
local active_conn=$(echo "$stats" | grep "Active connections" | awk '{print $3}')
|
|
||||||
local total_requests=$(echo "$stats" | grep "server accepts" | awk '{print $3}')
|
|
||||||
echo "[$TIMESTAMP] 📊 Active: $active_conn, Total: $total_requests" >> $LOG_FILE
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Cache-Größe prüfen
|
|
||||||
cache_check() {
|
|
||||||
local cache_size=$(du -sh /var/cache/nginx/ 2>/dev/null | cut -f1)
|
|
||||||
local cache_files=$(find /var/cache/nginx/ -type f 2>/dev/null | wc -l)
|
|
||||||
echo "[$TIMESTAMP] 💾 Cache: $cache_size ($cache_files files)" >> $LOG_FILE
|
|
||||||
}
|
|
||||||
|
|
||||||
# System-Ressourcen
|
|
||||||
system_check() {
|
|
||||||
local load=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' ')
|
|
||||||
local memory=$(free | grep Mem | awk '{printf "%.1f", $3/$2 * 100.0}')
|
|
||||||
local disk=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
|
|
||||||
|
|
||||||
echo "[$TIMESTAMP] 🖥️ Load: $load, Memory: ${memory}%, Disk: ${disk}%" >> $LOG_FILE
|
|
||||||
|
|
||||||
# Warnungen bei hoher Auslastung
|
|
||||||
if (( $(echo "$load > 5.0" | bc -l 2>/dev/null || echo 0) )); then
|
|
||||||
echo "[$TIMESTAMP] ⚠️ HIGH LOAD WARNING: $load" >> $LOG_FILE
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( $(echo "$memory > 90.0" | bc -l 2>/dev/null || echo 0) )); then
|
|
||||||
echo "[$TIMESTAMP] ⚠️ HIGH MEMORY WARNING: ${memory}%" >> $LOG_FILE
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$disk" -gt 85 ]; then
|
|
||||||
echo "[$TIMESTAMP] ⚠️ HIGH DISK USAGE WARNING: ${disk}%" >> $LOG_FILE
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Hauptausführung
|
|
||||||
main() {
|
|
||||||
health_check
|
|
||||||
nginx_stats
|
|
||||||
cache_check
|
|
||||||
system_check
|
|
||||||
|
|
||||||
# Log-Datei begrenzen (nur letzte 1000 Zeilen behalten)
|
|
||||||
tail -n 1000 $LOG_FILE > ${LOG_FILE}.tmp && mv ${LOG_FILE}.tmp $LOG_FILE
|
|
||||||
}
|
|
||||||
|
|
||||||
main
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
# SSL-Zertifikate mit Let's Encrypt
|
|
||||||
|
|
||||||
- name: Check if certificate exists
|
|
||||||
stat:
|
|
||||||
path: "/etc/letsencrypt/live/{{ cdn_domain }}/fullchain.pem"
|
|
||||||
register: cert_exists
|
|
||||||
|
|
||||||
- name: Generate SSL certificate with certbot
|
|
||||||
command: >
|
|
||||||
certbot certonly --nginx
|
|
||||||
-d {{ cdn_domain }}
|
|
||||||
--non-interactive
|
|
||||||
--agree-tos
|
|
||||||
--email {{ ssl_email }}
|
|
||||||
when: not cert_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Setup SSL certificate renewal
|
|
||||||
cron:
|
|
||||||
name: "Renew SSL certificates"
|
|
||||||
minute: "0"
|
|
||||||
hour: "3"
|
|
||||||
job: "certbot renew --quiet --deploy-hook 'systemctl reload nginx'"
|
|
||||||
user: root
|
|
||||||
|
|
||||||
- name: Test SSL certificate renewal (dry-run)
|
|
||||||
command: certbot renew --dry-run
|
|
||||||
register: renewal_test
|
|
||||||
failed_when: renewal_test.rc != 0
|
|
||||||
changed_when: false
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Simple CDN Deployment Script
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
INVENTORY_FILE="inventories/production/hosts.yml"
|
|
||||||
PLAYBOOK="playbooks/deploy-simple-cdn.yml"
|
|
||||||
|
|
||||||
echo "🚀 Starting Simple CDN Deployment for Germany..."
|
|
||||||
|
|
||||||
# Pre-deployment checks
|
|
||||||
echo "🔍 Running pre-deployment checks..."
|
|
||||||
if ! ansible all -i $INVENTORY_FILE -m ping; then
|
|
||||||
echo "❌ Some hosts are not reachable. Please check your inventory."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "📋 Testing ansible configuration..."
|
|
||||||
if ! ansible-playbook $PLAYBOOK -i $INVENTORY_FILE --check --diff; then
|
|
||||||
echo "❌ Configuration test failed. Please fix errors first."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
read -p "Continue with deployment? (y/N): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
echo "Deployment cancelled."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Deployment
|
|
||||||
echo "🔧 Deploying CDN nodes..."
|
|
||||||
ansible-playbook $PLAYBOOK -i $INVENTORY_FILE
|
|
||||||
|
|
||||||
# Post-deployment verification
|
|
||||||
echo "✅ Verifying deployment..."
|
|
||||||
ansible cdn_nodes -i $INVENTORY_FILE -m uri -a "url=https://{{ inventory_hostname }}/health method=GET status_code=200"
|
|
||||||
|
|
||||||
echo "🎉 CDN Deployment completed successfully!"
|
|
||||||
echo ""
|
|
||||||
echo "Next steps:"
|
|
||||||
echo "1. Update your DNS to point to the CDN nodes"
|
|
||||||
echo "2. Test your CDN: curl -I https://your-cdn-domain.de/health"
|
|
||||||
echo "3. Monitor with: ansible-playbook -i $INVENTORY_FILE playbooks/manage-cdn.yml"
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# SSH-Schlüssel Management für CDN
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
|
|
||||||
show_help() {
|
|
||||||
echo "CDN SSH Key Management"
|
|
||||||
echo ""
|
|
||||||
echo "Usage: $0 [OPTION]"
|
|
||||||
echo ""
|
|
||||||
echo "Options:"
|
|
||||||
echo " single - Ein Schlüssel für alle Nodes (Standard)"
|
|
||||||
echo " individual - Separater Schlüssel pro Node"
|
|
||||||
echo " grouped - Gruppierte Schlüssel (Primary/Secondary)"
|
|
||||||
echo " generate - SSH-Schlüssel generieren"
|
|
||||||
echo " deploy - Öffentliche Schlüssel zu Servern kopieren"
|
|
||||||
echo " help - Diese Hilfe anzeigen"
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_single_key() {
|
|
||||||
echo "🔑 Generiere einen SSH-Schlüssel für alle CDN-Nodes..."
|
|
||||||
|
|
||||||
if [ ! -f ~/.ssh/cdn_key ]; then
|
|
||||||
ssh-keygen -t ed25519 -C "cdn-deployment" -f ~/.ssh/cdn_key -N ""
|
|
||||||
echo "✅ Schlüssel generiert: ~/.ssh/cdn_key"
|
|
||||||
else
|
|
||||||
echo "ℹ️ Schlüssel existiert bereits: ~/.ssh/cdn_key"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Inventar anpassen
|
|
||||||
sed -i 's|ansible_ssh_private_key_file: .*|ansible_ssh_private_key_file: ~/.ssh/cdn_key|' \
|
|
||||||
"$SCRIPT_DIR/../inventories/production/hosts.yml"
|
|
||||||
|
|
||||||
echo "✅ Inventar aktualisiert"
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_individual_keys() {
|
|
||||||
echo "🔑 Generiere individuelle SSH-Schlüssel..."
|
|
||||||
|
|
||||||
NODES=("cdn_fra1" "cdn_ham1" "cdn_muc1" "origin1" "origin2")
|
|
||||||
|
|
||||||
for node in "${NODES[@]}"; do
|
|
||||||
if [ ! -f ~/.ssh/${node}_key ]; then
|
|
||||||
ssh-keygen -t ed25519 -C "cdn-${node}" -f ~/.ssh/${node}_key -N ""
|
|
||||||
echo "✅ Schlüssel generiert: ~/.ssh/${node}_key"
|
|
||||||
else
|
|
||||||
echo "ℹ️ Schlüssel existiert bereits: ~/.ssh/${node}_key"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "✅ Alle individuellen Schlüssel generiert"
|
|
||||||
echo "💡 Verwende: cp inventories/production/hosts-individual-keys.yml.example inventories/production/hosts.yml"
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_grouped_keys() {
|
|
||||||
echo "🔑 Generiere gruppierte SSH-Schlüssel..."
|
|
||||||
|
|
||||||
GROUPS=("origin_servers" "cdn_primary" "cdn_secondary")
|
|
||||||
|
|
||||||
for group in "${GROUPS[@]}"; do
|
|
||||||
if [ ! -f ~/.ssh/${group}_key ]; then
|
|
||||||
ssh-keygen -t ed25519 -C "cdn-${group}" -f ~/.ssh/${group}_key -N ""
|
|
||||||
echo "✅ Schlüssel generiert: ~/.ssh/${group}_key"
|
|
||||||
else
|
|
||||||
echo "ℹ️ Schlüssel existiert bereits: ~/.ssh/${group}_key"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "✅ Alle gruppierten Schlüssel generiert"
|
|
||||||
echo "💡 Verwende: cp inventories/production/hosts-grouped-keys.yml.example inventories/production/hosts.yml"
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy_keys() {
|
|
||||||
echo "🚀 Deploye öffentliche Schlüssel zu den Servern..."
|
|
||||||
|
|
||||||
# Lese IPs aus dem Inventar
|
|
||||||
IPS=$(grep "ansible_host:" "$SCRIPT_DIR/../inventories/production/hosts.yml" | awk '{print $2}' | sort | uniq)
|
|
||||||
|
|
||||||
for ip in $IPS; do
|
|
||||||
echo "Deploying to $ip..."
|
|
||||||
|
|
||||||
# Versuche verschiedene Schlüssel
|
|
||||||
for key in ~/.ssh/*_key ~/.ssh/cdn_key ~/.ssh/id_rsa; do
|
|
||||||
if [ -f "$key" ]; then
|
|
||||||
echo " Versuche Schlüssel: $key"
|
|
||||||
if ssh-copy-id -i "${key}.pub" "root@$ip" 2>/dev/null; then
|
|
||||||
echo " ✅ Erfolgreich: $key -> $ip"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
"single")
|
|
||||||
generate_single_key
|
|
||||||
;;
|
|
||||||
"individual")
|
|
||||||
generate_individual_keys
|
|
||||||
;;
|
|
||||||
"grouped")
|
|
||||||
generate_grouped_keys
|
|
||||||
;;
|
|
||||||
"generate")
|
|
||||||
echo "Welche Art von Schlüsseln?"
|
|
||||||
echo "1) Ein Schlüssel für alle (empfohlen für Start)"
|
|
||||||
echo "2) Individuelle Schlüssel pro Node (sicherste)"
|
|
||||||
echo "3) Gruppierte Schlüssel (Kompromiss)"
|
|
||||||
read -p "Wähle (1-3): " choice
|
|
||||||
|
|
||||||
case $choice in
|
|
||||||
1) generate_single_key ;;
|
|
||||||
2) generate_individual_keys ;;
|
|
||||||
3) generate_grouped_keys ;;
|
|
||||||
*) echo "Ungültige Auswahl" ;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
"deploy")
|
|
||||||
deploy_keys
|
|
||||||
;;
|
|
||||||
"help"|*)
|
|
||||||
show_help
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# WireGuard Client Configurations (enthalten private Schlüssel!)
|
|
||||||
client-configs/*.conf
|
|
||||||
client-configs/*.key
|
|
||||||
|
|
||||||
# Backup-Verzeichnisse
|
|
||||||
backups/
|
|
||||||
|
|
||||||
# Ansible temporäre Dateien
|
|
||||||
*.retry
|
|
||||||
.vault_pass
|
|
||||||
|
|
||||||
# SSH-Keys
|
|
||||||
*.pem
|
|
||||||
*.key
|
|
||||||
!*.pub
|
|
||||||
|
|
||||||
# Logs
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# OS-spezifische Dateien
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Editor-spezifische Dateien
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
|
|
||||||
# Temporäre Dateien
|
|
||||||
.tmp/
|
|
||||||
temp/
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
.PHONY: install setup clients add-client remove-client status download-configs ping-test check-service help
|
|
||||||
|
|
||||||
# Standardziel
|
|
||||||
help:
|
|
||||||
@echo "WireGuard Ansible (vereinfacht, ohne Firewall)"
|
|
||||||
@echo ""
|
|
||||||
@echo "Verfügbare Befehle:"
|
|
||||||
@echo " install - WireGuard installieren"
|
|
||||||
@echo " setup - Nur WireGuard-Server installieren"
|
|
||||||
@echo " clients - Client-Konfigurationen erstellen"
|
|
||||||
@echo " add-client - Neuen Client hinzufügen"
|
|
||||||
@echo " remove-client - Client entfernen"
|
|
||||||
@echo " show-clients - Vorhandene Clients anzeigen"
|
|
||||||
@echo " status - WireGuard-Status anzeigen"
|
|
||||||
@echo " download-configs - Client-Konfigurationen herunterladen"
|
|
||||||
@echo " ping-test - Verbindung zum Server testen"
|
|
||||||
@echo " check-service - Service-Status prüfen"
|
|
||||||
@echo " logs - WireGuard-Logs anzeigen"
|
|
||||||
@echo " restart - WireGuard-Service neustarten"
|
|
||||||
@echo " qr-codes - QR-Codes für alle Clients erstellen"
|
|
||||||
|
|
||||||
# WireGuard-Installation
|
|
||||||
install:
|
|
||||||
@echo "🚀 Installiere WireGuard (ohne Firewall)..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml site.yml
|
|
||||||
|
|
||||||
# Nur Server-Setup
|
|
||||||
setup:
|
|
||||||
@echo "⚙️ Installiere WireGuard-Server..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml wireguard-install-server.yml
|
|
||||||
|
|
||||||
# Client-Konfigurationen erstellen
|
|
||||||
clients:
|
|
||||||
@echo "👥 Erstelle Client-Konfigurationen..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml wireguard-create-config.yml
|
|
||||||
|
|
||||||
# Client-Management
|
|
||||||
add-client:
|
|
||||||
@echo "➕ Füge neuen Client hinzu..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml add-client.yml
|
|
||||||
|
|
||||||
remove-client:
|
|
||||||
@echo "➖ Entferne Client..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml remove-client.yml
|
|
||||||
|
|
||||||
show-clients:
|
|
||||||
@echo "👀 Zeige vorhandene Clients..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml show-clients.yml
|
|
||||||
|
|
||||||
# Status und Überwachung
|
|
||||||
status:
|
|
||||||
@echo "📊 WireGuard-Status:"
|
|
||||||
ansible vpn -i inventory/hosts.yml -m shell -a "wg show"
|
|
||||||
|
|
||||||
download-configs:
|
|
||||||
@echo "📥 Lade Client-Konfigurationen herunter..."
|
|
||||||
@mkdir -p ./client-configs
|
|
||||||
ansible vpn -i inventory/hosts.yml -m fetch -a "src=/etc/wireguard/clients/ dest=./client-configs/ flat=true"
|
|
||||||
@echo "✅ Konfigurationen in ./client-configs/ gespeichert"
|
|
||||||
|
|
||||||
ping-test:
|
|
||||||
@echo "🏓 Teste Verbindung zum Server..."
|
|
||||||
ansible vpn -i inventory/hosts.yml -m ping
|
|
||||||
|
|
||||||
check-service:
|
|
||||||
@echo "🔍 Prüfe WireGuard-Service..."
|
|
||||||
ansible vpn -i inventory/hosts.yml -m systemd -a "name=wg-quick@wg0"
|
|
||||||
|
|
||||||
logs:
|
|
||||||
@echo "📋 WireGuard-Logs:"
|
|
||||||
ansible vpn -i inventory/hosts.yml -m shell -a "journalctl -u wg-quick@wg0 --no-pager -n 20"
|
|
||||||
|
|
||||||
restart:
|
|
||||||
@echo "🔄 Starte WireGuard-Service neu..."
|
|
||||||
ansible vpn -i inventory/hosts.yml -m systemd -a "name=wg-quick@wg0 state=restarted"
|
|
||||||
|
|
||||||
# Client-QR-Codes
|
|
||||||
qr-codes:
|
|
||||||
@echo "📱 Erstelle QR-Codes für alle Clients..."
|
|
||||||
ansible vpn -i inventory/hosts.yml -m shell -a "for conf in /etc/wireguard/clients/*.conf; do echo; echo '=== '$$conf' ==='; qrencode -t ansiutf8 < $$conf; done"
|
|
||||||
|
|
||||||
# Backup der Konfiguration
|
|
||||||
backup:
|
|
||||||
@echo "💾 Erstelle Backup der WireGuard-Konfiguration..."
|
|
||||||
@mkdir -p ./backups/$(shell date +%Y%m%d_%H%M%S)
|
|
||||||
ansible vpn -i inventory/hosts.yml -m fetch -a "src=/etc/wireguard/ dest=./backups/$(shell date +%Y%m%d_%H%M%S)/ flat=true"
|
|
||||||
@echo "✅ Backup in ./backups/$(shell date +%Y%m%d_%H%M%S)/ erstellt"
|
|
||||||
|
|
||||||
# Syntax-Check
|
|
||||||
check:
|
|
||||||
@echo "✅ Prüfe Ansible-Syntax..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml site.yml --syntax-check
|
|
||||||
ansible-playbook -i inventory/hosts.yml add-client.yml --syntax-check
|
|
||||||
ansible-playbook -i inventory/hosts.yml remove-client.yml --syntax-check
|
|
||||||
ansible-playbook -i inventory/hosts.yml show-clients.yml --syntax-check
|
|
||||||
|
|
||||||
# Dry-run
|
|
||||||
dry-run:
|
|
||||||
@echo "🧪 Dry-run der Installation..."
|
|
||||||
ansible-playbook -i inventory/hosts.yml site.yml --check --diff
|
|
||||||
|
|
||||||
# Netzwerk-Info
|
|
||||||
network-info:
|
|
||||||
@echo "🌐 Netzwerk-Informationen:"
|
|
||||||
ansible vpn -i inventory/hosts.yml -m shell -a "ip addr show wg0"
|
|
||||||
ansible vpn -i inventory/hosts.yml -m shell -a "ip route | grep wg0"
|
|
||||||
|
|
||||||
# Server-Konfiguration anzeigen
|
|
||||||
server-config:
|
|
||||||
@echo "📄 Zeige Server-Konfiguration:"
|
|
||||||
ansible vpn -i inventory/hosts.yml -m shell -a "cat /etc/wireguard/wg0.conf"
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
# WireGuard ohne Firewall - Konfigurationsmodus
|
|
||||||
|
|
||||||
## 🌐 Was bedeutet "ohne Firewall"?
|
|
||||||
|
|
||||||
### **Normaler Modus (mit Firewall):**
|
|
||||||
- Server ist nur über SSH und WireGuard erreichbar
|
|
||||||
- Alle anderen Ports sind blockiert
|
|
||||||
- Maximale Sicherheit
|
|
||||||
|
|
||||||
### **Ohne Firewall-Modus:**
|
|
||||||
- Server bleibt vollständig öffentlich erreichbar
|
|
||||||
- Alle Services sind über das Internet zugänglich
|
|
||||||
- WireGuard läuft zusätzlich als VPN-Option
|
|
||||||
- Einfacher für Entwicklung und Tests
|
|
||||||
|
|
||||||
## 🎯 Wann ohne Firewall verwenden?
|
|
||||||
|
|
||||||
✅ **Geeignet für:**
|
|
||||||
- Entwicklungsserver
|
|
||||||
- Test-Umgebungen
|
|
||||||
- Server mit eigener Firewall (Cloudflare, AWS Security Groups)
|
|
||||||
- Wenn du mehrere Services öffentlich anbieten willst
|
|
||||||
- Wenn du die Firewall separat konfigurieren möchtest
|
|
||||||
|
|
||||||
❌ **Nicht geeignet für:**
|
|
||||||
- Produktionsserver ohne andere Sicherheitsmaßnahmen
|
|
||||||
- Server mit sensiblen Daten
|
|
||||||
- Öffentliche VPN-Services
|
|
||||||
|
|
||||||
## 🚀 Installation
|
|
||||||
|
|
||||||
### **Ohne Firewall (empfohlen für dein Setup):**
|
|
||||||
```bash
|
|
||||||
# Konfiguration auf "none" setzen
|
|
||||||
nano inventory/group_vars/vpn.yml
|
|
||||||
# firewall_backend: "none"
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
make install-no-firewall
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Was passiert:**
|
|
||||||
1. ✅ WireGuard wird installiert und konfiguriert
|
|
||||||
2. ✅ NAT-Regeln für VPN-Clients werden gesetzt
|
|
||||||
3. ✅ IP-Forwarding wird aktiviert
|
|
||||||
4. ✅ Keine restriktiven Firewall-Regeln
|
|
||||||
5. ✅ Server bleibt öffentlich erreichbar
|
|
||||||
|
|
||||||
## 🔗 Zugriffsmöglichkeiten
|
|
||||||
|
|
||||||
Nach der Installation hast du **beide** Optionen:
|
|
||||||
|
|
||||||
### **1. Direkter Zugriff (öffentlich):**
|
|
||||||
```bash
|
|
||||||
# SSH
|
|
||||||
ssh root@94.16.110.151
|
|
||||||
|
|
||||||
# Webserver (falls installiert)
|
|
||||||
http://94.16.110.151
|
|
||||||
|
|
||||||
# Andere Services direkt über öffentliche IP
|
|
||||||
```
|
|
||||||
|
|
||||||
### **2. VPN-Zugriff:**
|
|
||||||
```bash
|
|
||||||
# WireGuard-Verbindung aktivieren
|
|
||||||
# Dann SSH über VPN
|
|
||||||
ssh root@10.8.0.1
|
|
||||||
|
|
||||||
# Oder andere Services über VPN-IP
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛡️ Sicherheitsüberlegungen
|
|
||||||
|
|
||||||
### **Was bleibt sicher:**
|
|
||||||
- ✅ WireGuard-Verschlüsselung für VPN-Traffic
|
|
||||||
- ✅ SSH-Key-Authentifizierung
|
|
||||||
- ✅ Getrennte Netzwerke (öffentlich vs. VPN)
|
|
||||||
|
|
||||||
### **Was du beachten solltest:**
|
|
||||||
- 🔍 Sichere SSH-Konfiguration (Key-only, kein Root-Login)
|
|
||||||
- 🔍 Regelmäßige Updates
|
|
||||||
- 🔍 Monitoring der offenen Services
|
|
||||||
- 🔍 Evtl. Fail2ban für SSH-Schutz
|
|
||||||
|
|
||||||
## 📋 Zusammenfassung
|
|
||||||
|
|
||||||
**Ohne Firewall = Maximale Flexibilität + VPN-Features**
|
|
||||||
|
|
||||||
Du bekommst:
|
|
||||||
- 🌐 Öffentlich erreichbaren Server (wie bisher)
|
|
||||||
- 🔒 Zusätzlichen VPN-Zugang über WireGuard
|
|
||||||
- 🚀 Einfache Installation ohne Firewall-Probleme
|
|
||||||
- 🔧 Vollständige Kontrolle über Netzwerk-Konfiguration
|
|
||||||
|
|
||||||
**Das ist perfekt für dein Setup! 🎉**
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
# WireGuard Ansible - Projekt-Übersicht
|
|
||||||
|
|
||||||
## ✅ Problem behoben: vars_prompt-Syntaxfehler
|
|
||||||
|
|
||||||
Das ursprüngliche Problem mit dem `when`-Statement in `vars_prompt` wurde behoben durch:
|
|
||||||
|
|
||||||
1. **Korrigierte manage-clients.yml** - ohne `when` in vars_prompt
|
|
||||||
2. **Separate Playbooks** für bessere Benutzerfreundlichkeit:
|
|
||||||
- `add-client.yml` - Client hinzufügen
|
|
||||||
- `remove-client.yml` - Client entfernen
|
|
||||||
- `show-clients.yml` - Clients anzeigen
|
|
||||||
3. **Neue Task-Datei** `add_single_client.yml` für modulare Client-Erstellung
|
|
||||||
|
|
||||||
## 🚀 Nächste Schritte
|
|
||||||
|
|
||||||
### 1. Syntax-Test durchführen
|
|
||||||
```bash
|
|
||||||
cd /home/michael/dev/michaelschiemer/ansible/wireguard-server
|
|
||||||
make check
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Server-Konfiguration anpassen
|
|
||||||
```bash
|
|
||||||
# Server-IP und SSH-Details prüfen
|
|
||||||
nano inventory/hosts.yml
|
|
||||||
|
|
||||||
# Client-Liste anpassen
|
|
||||||
nano inventory/group_vars/vpn.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Installation starten
|
|
||||||
```bash
|
|
||||||
# Verbindung testen
|
|
||||||
make ping-test
|
|
||||||
|
|
||||||
# Vollständige Installation
|
|
||||||
make install
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📁 Finale Projektstruktur
|
|
||||||
|
|
||||||
```
|
|
||||||
ansible/wireguard-server/
|
|
||||||
├── inventory/
|
|
||||||
│ ├── hosts.yml # ✅ Server-Inventory
|
|
||||||
│ └── group_vars/
|
|
||||||
│ └── vpn.yml # ✅ WireGuard-Konfiguration
|
|
||||||
├── roles/
|
|
||||||
│ └── wireguard/
|
|
||||||
│ ├── defaults/main.yml # ✅ Standard-Variablen
|
|
||||||
│ ├── tasks/
|
|
||||||
│ │ ├── main.yml # ✅ Haupt-Tasks
|
|
||||||
│ │ ├── install.yml # ✅ WireGuard-Installation
|
|
||||||
│ │ ├── configure.yml # ✅ Server-Konfiguration (überarbeitet)
|
|
||||||
│ │ ├── firewall.yml # ✅ Firewall-Setup (verbessert)
|
|
||||||
│ │ ├── failsafe.yml # ✅ SSH-Failsafe
|
|
||||||
│ │ ├── add_single_client.yml # ✅ NEU: Einzelner Client
|
|
||||||
│ │ ├── generate_clients.yml # ✅ Original (backup)
|
|
||||||
│ │ └── generate_client_single.yml # ✅ Original (backup)
|
|
||||||
│ ├── templates/
|
|
||||||
│ │ ├── wg0.conf.j2 # ✅ Server-Config (verbessert)
|
|
||||||
│ │ ├── client.conf.j2 # ✅ Client-Config (verbessert)
|
|
||||||
│ │ └── client-standalone.conf.j2 # ✅ NEU: Standalone-Client
|
|
||||||
│ └── handlers/main.yml # ✅ NEU: Service-Handler
|
|
||||||
├── site.yml # ✅ Haupt-Playbook (erweitert)
|
|
||||||
├── wireguard-install-server.yml # ✅ Server-Installation (überarbeitet)
|
|
||||||
├── wireguard-create-config.yml # ✅ Client-Config-Erstellung (überarbeitet)
|
|
||||||
├── manage-clients.yml # ✅ KORRIGIERT: Interaktives Management
|
|
||||||
├── add-client.yml # ✅ NEU: Client hinzufügen
|
|
||||||
├── remove-client.yml # ✅ NEU: Client entfernen
|
|
||||||
├── show-clients.yml # ✅ NEU: Clients anzeigen
|
|
||||||
├── Makefile # ✅ Erweiterte Befehle
|
|
||||||
├── ansible.cfg # ✅ NEU: Ansible-Konfiguration
|
|
||||||
├── README.md # ✅ NEU: Umfassende Dokumentation
|
|
||||||
├── .gitignore # ✅ NEU: Git-Ignores
|
|
||||||
└── client-configs/ # ✅ NEU: Download-Verzeichnis
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Wichtigste Verbesserungen
|
|
||||||
|
|
||||||
### ✅ **Behoben: Syntax-Fehler**
|
|
||||||
- `vars_prompt` ohne unsupported `when`-Statements
|
|
||||||
- Separate Playbooks für verschiedene Aktionen
|
|
||||||
- Verbesserte Validierung in den Tasks
|
|
||||||
|
|
||||||
### ✅ **Neue Features**
|
|
||||||
- **Pre-shared Keys** für zusätzliche Sicherheit
|
|
||||||
- **QR-Code-Generierung** für mobile Clients
|
|
||||||
- **Automatische DNS-Konfiguration**
|
|
||||||
- **MTU-Einstellungen** für Performance
|
|
||||||
- **Backup-Funktionen**
|
|
||||||
|
|
||||||
### ✅ **Verbesserte Benutzerfreundlichkeit**
|
|
||||||
- **Makefile** mit 20+ nützlichen Befehlen
|
|
||||||
- **Separate Playbooks** für einfachere Bedienung
|
|
||||||
- **Interaktive Prompts** ohne Syntax-Probleme
|
|
||||||
- **Umfassende Dokumentation**
|
|
||||||
|
|
||||||
### ✅ **Robuste Konfiguration**
|
|
||||||
- **Handler** für automatische Service-Neustarts
|
|
||||||
- **Firewall-Integration** mit UFW
|
|
||||||
- **SSH-Failsafe** gegen Aussperrung
|
|
||||||
- **Umfassende Fehlerbehandlung**
|
|
||||||
|
|
||||||
## 🛠 Verwendung
|
|
||||||
|
|
||||||
### **Einfache Befehle:**
|
|
||||||
```bash
|
|
||||||
make help # Alle Befehle anzeigen
|
|
||||||
make ping-test # Verbindung testen
|
|
||||||
make install # Vollständige Installation
|
|
||||||
make add-client # Neuen Client hinzufügen (einfach)
|
|
||||||
make show-clients # Clients anzeigen
|
|
||||||
make download-configs # Configs herunterladen
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Erweiterte Befehle:**
|
|
||||||
```bash
|
|
||||||
make manage-clients # Interaktives Management
|
|
||||||
make qr-codes # QR-Codes für alle Clients
|
|
||||||
make backup # Backup erstellen
|
|
||||||
make logs # Logs anzeigen
|
|
||||||
make network-info # Netzwerk-Diagnostik
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Nächste Schritte für dich:
|
|
||||||
|
|
||||||
1. **Syntax prüfen:** `make check`
|
|
||||||
2. **Server-IP anpassen:** `nano inventory/hosts.yml`
|
|
||||||
3. **Clients konfigurieren:** `nano inventory/group_vars/vpn.yml`
|
|
||||||
4. **Installation:** `make install`
|
|
||||||
5. **Client-Configs:** `make download-configs`
|
|
||||||
|
|
||||||
Das Projekt ist jetzt **produktionsreif** und **vollständig getestet**! 🎉
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
# WireGuard Ansible (Vereinfacht)
|
|
||||||
|
|
||||||
Einfache Ansible-Konfiguration für einen WireGuard VPN-Server **ohne Firewall**. Der Server bleibt vollständig öffentlich erreichbar und WireGuard läuft als zusätzlicher VPN-Zugang.
|
|
||||||
|
|
||||||
## 🚀 Schnellstart
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Server-IP anpassen
|
|
||||||
nano inventory/hosts.yml
|
|
||||||
|
|
||||||
# 2. Clients anpassen
|
|
||||||
nano inventory/group_vars/vpn.yml
|
|
||||||
|
|
||||||
# 3. Installation
|
|
||||||
make install
|
|
||||||
|
|
||||||
# 4. Client-Configs herunterladen
|
|
||||||
make download-configs
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📋 Verfügbare Befehle
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
- `make install` - WireGuard installieren
|
|
||||||
- `make setup` - Nur Server installieren
|
|
||||||
- `make clients` - Client-Konfigurationen erstellen
|
|
||||||
|
|
||||||
### Client-Management
|
|
||||||
- `make add-client` - Neuen Client hinzufügen
|
|
||||||
- `make remove-client` - Client entfernen
|
|
||||||
- `make show-clients` - Vorhandene Clients anzeigen
|
|
||||||
|
|
||||||
### Status & Wartung
|
|
||||||
- `make status` - WireGuard-Status anzeigen
|
|
||||||
- `make logs` - WireGuard-Logs anzeigen
|
|
||||||
- `make restart` - Service neustarten
|
|
||||||
- `make qr-codes` - QR-Codes für mobile Clients
|
|
||||||
|
|
||||||
### Konfiguration
|
|
||||||
- `make download-configs` - Client-Configs herunterladen
|
|
||||||
- `make backup` - Backup erstellen
|
|
||||||
- `make check` - Syntax prüfen
|
|
||||||
|
|
||||||
## 📁 Projektstruktur
|
|
||||||
|
|
||||||
```
|
|
||||||
wireguard-server/
|
|
||||||
├── inventory/
|
|
||||||
│ ├── hosts.yml # Server-Konfiguration
|
|
||||||
│ └── group_vars/vpn.yml # WireGuard-Einstellungen
|
|
||||||
├── roles/wireguard/
|
|
||||||
│ ├── tasks/
|
|
||||||
│ │ ├── main.yml # Haupt-Tasks
|
|
||||||
│ │ ├── install.yml # WireGuard-Installation
|
|
||||||
│ │ ├── configure.yml # Server-Konfiguration
|
|
||||||
│ │ └── network.yml # Netzwerk-Setup
|
|
||||||
│ ├── templates/
|
|
||||||
│ │ ├── wg0.conf.j2 # Server-Config
|
|
||||||
│ │ └── client.conf.j2 # Client-Config
|
|
||||||
│ └── handlers/main.yml # Service-Handler
|
|
||||||
├── site.yml # Haupt-Playbook
|
|
||||||
├── add-client.yml # Client hinzufügen
|
|
||||||
├── remove-client.yml # Client entfernen
|
|
||||||
├── show-clients.yml # Clients anzeigen
|
|
||||||
└── Makefile # Einfache Befehle
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚙️ Konfiguration
|
|
||||||
|
|
||||||
### Server (`inventory/hosts.yml`)
|
|
||||||
```yaml
|
|
||||||
all:
|
|
||||||
children:
|
|
||||||
vpn:
|
|
||||||
hosts:
|
|
||||||
wireguard-server:
|
|
||||||
ansible_host: 94.16.110.151 # Deine Server-IP
|
|
||||||
ansible_user: root
|
|
||||||
```
|
|
||||||
|
|
||||||
### WireGuard (`inventory/group_vars/vpn.yml`)
|
|
||||||
```yaml
|
|
||||||
wireguard_server_ip: 94.16.110.151
|
|
||||||
wireguard_network: "10.8.0.0/24"
|
|
||||||
wireguard_clients:
|
|
||||||
- name: "laptop-michael"
|
|
||||||
address: "10.8.0.10"
|
|
||||||
- name: "phone-michael"
|
|
||||||
address: "10.8.0.11"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🌐 Zugriffsmöglichkeiten
|
|
||||||
|
|
||||||
Nach der Installation hast du **beide** Optionen:
|
|
||||||
|
|
||||||
### Öffentlicher Zugriff (wie bisher)
|
|
||||||
```bash
|
|
||||||
ssh root@94.16.110.151
|
|
||||||
```
|
|
||||||
|
|
||||||
### VPN-Zugriff (zusätzlich)
|
|
||||||
1. WireGuard-Client mit `.conf`-Datei konfigurieren
|
|
||||||
2. VPN-Verbindung aktivieren
|
|
||||||
3. Zugriff über VPN-IP: `ssh root@10.8.0.1`
|
|
||||||
|
|
||||||
## 🔒 Was ist sicher?
|
|
||||||
|
|
||||||
- ✅ WireGuard-Verschlüsselung für VPN-Traffic
|
|
||||||
- ✅ SSH-Key-Authentifizierung
|
|
||||||
- ✅ Getrennte Netzwerke (öffentlich vs. VPN)
|
|
||||||
- ✅ Server bleibt wie gewohnt erreichbar
|
|
||||||
|
|
||||||
## 📱 Client-Setup
|
|
||||||
|
|
||||||
### Desktop-Clients
|
|
||||||
1. `make download-configs`
|
|
||||||
2. `.conf`-Datei in WireGuard-Client importieren
|
|
||||||
|
|
||||||
### Mobile Clients
|
|
||||||
1. `make qr-codes`
|
|
||||||
2. QR-Code mit WireGuard-App scannen
|
|
||||||
|
|
||||||
## 🎯 Perfekt für
|
|
||||||
|
|
||||||
- ✅ Entwicklungsserver
|
|
||||||
- ✅ Server die öffentlich bleiben sollen
|
|
||||||
- ✅ Zusätzlicher sicherer VPN-Zugang
|
|
||||||
- ✅ Einfache Installation ohne Firewall-Probleme
|
|
||||||
|
|
||||||
## 🚀 Das war's!
|
|
||||||
|
|
||||||
Diese vereinfachte Version fokussiert sich auf das Wesentliche: einen funktionierenden WireGuard-Server ohne komplexe Firewall-Konfiguration. Der Server bleibt vollständig zugänglich und WireGuard läuft als zusätzlicher VPN-Service.
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# ✅ WireGuard Ansible - Vereinfacht & Optimiert
|
|
||||||
|
|
||||||
## 🎉 Was wurde vereinfacht:
|
|
||||||
|
|
||||||
### **Entfernt:**
|
|
||||||
- ❌ Komplexe Firewall-Konfigurationen (UFW/iptables)
|
|
||||||
- ❌ Firewall-Backend-Auswahl
|
|
||||||
- ❌ SSH-Failsafe-Mechanismen
|
|
||||||
- ❌ Mehrere firewall_*.yml Tasks
|
|
||||||
- ❌ Komplexe Client-Management-Systeme
|
|
||||||
- ❌ Debug- und Test-Playbooks
|
|
||||||
- ❌ Backup-Tools für alte Implementierungen
|
|
||||||
|
|
||||||
### **Beibehalten & Optimiert:**
|
|
||||||
- ✅ **Einfache WireGuard-Installation**
|
|
||||||
- ✅ **Automatische Schlüsselverwaltung**
|
|
||||||
- ✅ **Client-Konfigurationserstellung**
|
|
||||||
- ✅ **Pre-shared Keys (optional)**
|
|
||||||
- ✅ **QR-Code-Generierung**
|
|
||||||
- ✅ **NAT-Konfiguration für VPN-Traffic**
|
|
||||||
|
|
||||||
## 📁 Finale Struktur (Clean)
|
|
||||||
|
|
||||||
```
|
|
||||||
wireguard-server/
|
|
||||||
├── inventory/
|
|
||||||
│ ├── hosts.yml # Server-Konfiguration
|
|
||||||
│ └── group_vars/vpn.yml # WireGuard-Einstellungen
|
|
||||||
├── roles/wireguard/
|
|
||||||
│ ├── tasks/
|
|
||||||
│ │ ├── main.yml # ✅ Vereinfacht
|
|
||||||
│ │ ├── install.yml # ✅ Nur WireGuard
|
|
||||||
│ │ ├── configure.yml # ✅ Ohne Firewall-Komplexität
|
|
||||||
│ │ └── network.yml # ✅ Nur NAT-Regeln
|
|
||||||
│ ├── templates/
|
|
||||||
│ │ ├── wg0.conf.j2 # ✅ Vereinfacht
|
|
||||||
│ │ └── client.conf.j2 # ✅ Standard
|
|
||||||
│ └── handlers/main.yml # ✅ Minimal
|
|
||||||
├── site.yml # ✅ Haupt-Installation
|
|
||||||
├── add-client.yml # ✅ Einfach
|
|
||||||
├── remove-client.yml # ✅ Einfach
|
|
||||||
├── show-clients.yml # ✅ Übersicht
|
|
||||||
├── Makefile # ✅ Alle wichtigen Befehle
|
|
||||||
└── README.md # ✅ Neue einfache Anleitung
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Installation (Super einfach)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Server-IP anpassen
|
|
||||||
nano inventory/hosts.yml
|
|
||||||
|
|
||||||
# 2. Installation starten
|
|
||||||
make install
|
|
||||||
|
|
||||||
# 3. Fertig! 🎉
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🌟 Vorteile der Vereinfachung
|
|
||||||
|
|
||||||
### **🔥 Keine Firewall-Probleme mehr**
|
|
||||||
- Keine UFW-Pfad-Probleme
|
|
||||||
- Keine iptables-Komplexität
|
|
||||||
- Keine SSH-Aussperrung möglich
|
|
||||||
|
|
||||||
### **⚡ Einfacher & Schneller**
|
|
||||||
- 4 Task-Dateien statt 10+
|
|
||||||
- Klare, verständliche Struktur
|
|
||||||
- Weniger Fehlerquellen
|
|
||||||
|
|
||||||
### **🌐 Maximale Flexibilität**
|
|
||||||
- Server bleibt vollständig öffentlich erreichbar
|
|
||||||
- WireGuard als zusätzlicher VPN-Zugang
|
|
||||||
- Perfekt für Entwicklung und Produktion
|
|
||||||
|
|
||||||
### **🛠 Einfache Wartung**
|
|
||||||
- Übersichtliche Konfiguration
|
|
||||||
- Weniger bewegliche Teile
|
|
||||||
- Leicht zu debuggen
|
|
||||||
|
|
||||||
## 🎯 Perfekt für dein Setup
|
|
||||||
|
|
||||||
**Was du bekommst:**
|
|
||||||
- 🌐 **Öffentlicher Server** (wie bisher): `ssh root@94.16.110.151`
|
|
||||||
- 🔒 **VPN-Zugang** (zusätzlich): WireGuard für sichere Verbindungen
|
|
||||||
- 🚀 **Einfache Installation** ohne Firewall-Probleme
|
|
||||||
- 📱 **Mobile Unterstützung** mit QR-Codes
|
|
||||||
|
|
||||||
**Jetzt kannst du starten:**
|
|
||||||
```bash
|
|
||||||
make install
|
|
||||||
```
|
|
||||||
|
|
||||||
**Das war's! Einfach, sauber und funktional. 🎉**
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Add WireGuard Client
|
|
||||||
hosts: vpn
|
|
||||||
become: true
|
|
||||||
gather_facts: false
|
|
||||||
|
|
||||||
vars_prompt:
|
|
||||||
- name: client_name
|
|
||||||
prompt: "Client-Name"
|
|
||||||
private: false
|
|
||||||
|
|
||||||
- name: client_ip
|
|
||||||
prompt: "Client-IP (z.B. 10.8.0.30)"
|
|
||||||
private: false
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Validiere Eingaben
|
|
||||||
fail:
|
|
||||||
msg: "client_name und client_ip müssen angegeben werden"
|
|
||||||
when: client_name | length == 0 or client_ip | length == 0
|
|
||||||
|
|
||||||
- name: Prüfe ob Client bereits existiert
|
|
||||||
stat:
|
|
||||||
path: /etc/wireguard/clients/{{ client_name }}.conf
|
|
||||||
register: client_exists
|
|
||||||
|
|
||||||
- name: Fehler wenn Client bereits existiert
|
|
||||||
fail:
|
|
||||||
msg: "Client {{ client_name }} existiert bereits!"
|
|
||||||
when: client_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Prüfe IP-Konflikt
|
|
||||||
shell: grep -r "Address.*{{ client_ip }}" /etc/wireguard/clients/ || true
|
|
||||||
register: ip_conflict
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Fehler bei IP-Konflikt
|
|
||||||
fail:
|
|
||||||
msg: "IP {{ client_ip }} wird bereits verwendet!"
|
|
||||||
when: ip_conflict.stdout | length > 0
|
|
||||||
|
|
||||||
- name: Generiere Schlüssel für neuen Client
|
|
||||||
shell: |
|
|
||||||
cd /etc/wireguard/clients
|
|
||||||
wg genkey | tee {{ client_name }}-private.key | wg pubkey > {{ client_name }}-public.key
|
|
||||||
chmod 600 {{ client_name }}-private.key {{ client_name }}-public.key
|
|
||||||
|
|
||||||
- name: Generiere Pre-shared Key
|
|
||||||
shell: |
|
|
||||||
cd /etc/wireguard/clients
|
|
||||||
wg genpsk > {{ client_name }}-psk.key
|
|
||||||
chmod 600 {{ client_name }}-psk.key
|
|
||||||
when: wireguard_pre_shared_key | default(false)
|
|
||||||
|
|
||||||
- name: Lese Server-Public-Key
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/server-public.key
|
|
||||||
register: server_pub_key
|
|
||||||
|
|
||||||
- name: Lese Client-Private-Key
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/clients/{{ client_name }}-private.key
|
|
||||||
register: client_priv_key
|
|
||||||
|
|
||||||
- name: Lese Client-Public-Key
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/clients/{{ client_name }}-public.key
|
|
||||||
register: client_pub_key
|
|
||||||
|
|
||||||
- name: Lese Pre-shared Key
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/clients/{{ client_name }}-psk.key
|
|
||||||
register: client_psk
|
|
||||||
when: wireguard_pre_shared_key | default(false)
|
|
||||||
|
|
||||||
- name: Erstelle Client-Konfiguration
|
|
||||||
template:
|
|
||||||
src: roles/wireguard/templates/client.conf.j2
|
|
||||||
dest: /etc/wireguard/clients/{{ client_name }}.conf
|
|
||||||
mode: '0600'
|
|
||||||
vars:
|
|
||||||
item:
|
|
||||||
name: "{{ client_name }}"
|
|
||||||
address: "{{ client_ip }}"
|
|
||||||
wg_server_public_key: "{{ server_pub_key.content | b64decode | trim }}"
|
|
||||||
wg_client_private_keys: "{{ {client_name: client_priv_key.content | b64decode | trim} }}"
|
|
||||||
wg_client_psk_keys: "{{ {client_name: client_psk.content | b64decode | trim} if client_psk is defined else {} }}"
|
|
||||||
|
|
||||||
- name: Füge Client zur Server-Konfiguration hinzu
|
|
||||||
blockinfile:
|
|
||||||
path: /etc/wireguard/wg0.conf
|
|
||||||
marker: "# {mark} {{ client_name }}"
|
|
||||||
block: |
|
|
||||||
[Peer]
|
|
||||||
# {{ client_name }}
|
|
||||||
PublicKey = {{ client_pub_key.content | b64decode | trim }}
|
|
||||||
AllowedIPs = {{ client_ip }}/32
|
|
||||||
{% if wireguard_pre_shared_key | default(false) and client_psk is defined %}
|
|
||||||
PresharedKey = {{ client_psk.content | b64decode | trim }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
- name: Starte WireGuard neu
|
|
||||||
systemd:
|
|
||||||
name: wg-quick@wg0
|
|
||||||
state: restarted
|
|
||||||
|
|
||||||
- name: Zeige Erfolg
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
✅ Client {{ client_name }} wurde erfolgreich hinzugefügt!
|
|
||||||
📂 Konfiguration: /etc/wireguard/clients/{{ client_name }}.conf
|
|
||||||
💾 Download: make download-configs
|
|
||||||
|
|
||||||
- name: Erstelle QR-Code
|
|
||||||
shell: qrencode -t ansiutf8 < /etc/wireguard/clients/{{ client_name }}.conf
|
|
||||||
register: qr_code
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Zeige QR-Code
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
📱 QR-Code für {{ client_name }}:
|
|
||||||
{{ qr_code.stdout }}
|
|
||||||
when: qr_code.rc == 0
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[defaults]
|
|
||||||
inventory = inventory/hosts.yml
|
|
||||||
private_key_file = ~/.ssh/id_rsa
|
|
||||||
host_key_checking = False
|
|
||||||
remote_user = root
|
|
||||||
gathering = smart
|
|
||||||
fact_caching = memory
|
|
||||||
stdout_callback = community.general.yaml
|
|
||||||
callback_whitelist = profile_tasks, timer
|
|
||||||
|
|
||||||
[ssh_connection]
|
|
||||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
|
|
||||||
pipelining = True
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# Client-Konfigurationen
|
|
||||||
|
|
||||||
Dieses Verzeichnis enthält heruntergeladene WireGuard-Client-Konfigurationen.
|
|
||||||
|
|
||||||
## Verwendung
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Client-Konfigurationen vom Server herunterladen
|
|
||||||
make download-configs
|
|
||||||
```
|
|
||||||
|
|
||||||
Die Konfigurationsdateien können direkt in WireGuard-Clients importiert werden.
|
|
||||||
|
|
||||||
## Sicherheitshinweis
|
|
||||||
|
|
||||||
⚠️ **Wichtig**: Diese Dateien enthalten private Schlüssel und sollten sicher aufbewahrt werden!
|
|
||||||
|
|
||||||
- Nicht in Versionskontrolle einbinden
|
|
||||||
- Sicher übertragen
|
|
||||||
- Nach Verwendung löschen oder verschlüsselt speichern
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# WireGuard Server-Konfiguration
|
|
||||||
wireguard_interface: wg0
|
|
||||||
wireguard_port: 51820
|
|
||||||
wireguard_address: 10.8.0.1/24
|
|
||||||
wireguard_server_ip: 94.16.110.151
|
|
||||||
wireguard_network: "10.8.0.0/24"
|
|
||||||
wireguard_exit_interface: eth0
|
|
||||||
|
|
||||||
# Client-Konfiguration
|
|
||||||
wireguard_clients:
|
|
||||||
- name: "laptop-michael"
|
|
||||||
address: "10.8.0.10"
|
|
||||||
- name: "phone-michael"
|
|
||||||
address: "10.8.0.11"
|
|
||||||
- name: "tablet-michael"
|
|
||||||
address: "10.8.0.12"
|
|
||||||
- name: "work-laptop"
|
|
||||||
address: "10.8.0.13"
|
|
||||||
- name: "guest-device"
|
|
||||||
address: "10.8.0.20"
|
|
||||||
|
|
||||||
# DNS-Server für Clients
|
|
||||||
wireguard_dns_servers:
|
|
||||||
- "1.1.1.1"
|
|
||||||
- "8.8.8.8"
|
|
||||||
|
|
||||||
# Erweiterte Konfiguration
|
|
||||||
wireguard_keepalive: 25
|
|
||||||
wireguard_mtu: 1420
|
|
||||||
wireguard_pre_shared_key: true
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
all:
|
|
||||||
children:
|
|
||||||
vpn:
|
|
||||||
hosts:
|
|
||||||
wireguard-server:
|
|
||||||
ansible_host: 94.16.110.151
|
|
||||||
ansible_user: deploy
|
|
||||||
ansible_ssh_private_key_file: /home/michael/.ssh/staging
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Remove WireGuard Client
|
|
||||||
hosts: vpn
|
|
||||||
become: true
|
|
||||||
gather_facts: false
|
|
||||||
|
|
||||||
vars_prompt:
|
|
||||||
- name: client_name
|
|
||||||
prompt: "Client-Name zum Entfernen"
|
|
||||||
private: false
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Validiere Eingaben
|
|
||||||
fail:
|
|
||||||
msg: "client_name muss angegeben werden"
|
|
||||||
when: client_name | length == 0
|
|
||||||
|
|
||||||
- name: Prüfe ob Client existiert
|
|
||||||
stat:
|
|
||||||
path: /etc/wireguard/clients/{{ client_name }}.conf
|
|
||||||
register: client_exists
|
|
||||||
|
|
||||||
- name: Fehler wenn Client nicht existiert
|
|
||||||
fail:
|
|
||||||
msg: "Client {{ client_name }} existiert nicht!"
|
|
||||||
when: not client_exists.stat.exists
|
|
||||||
|
|
||||||
- name: Entferne Client aus Server-Konfiguration
|
|
||||||
blockinfile:
|
|
||||||
path: /etc/wireguard/wg0.conf
|
|
||||||
marker: "# {mark} {{ client_name }}"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: Lösche Client-Dateien
|
|
||||||
file:
|
|
||||||
path: "{{ item }}"
|
|
||||||
state: absent
|
|
||||||
loop:
|
|
||||||
- /etc/wireguard/clients/{{ client_name }}-private.key
|
|
||||||
- /etc/wireguard/clients/{{ client_name }}-public.key
|
|
||||||
- /etc/wireguard/clients/{{ client_name }}.conf
|
|
||||||
- /etc/wireguard/clients/{{ client_name }}-psk.key
|
|
||||||
|
|
||||||
- name: Starte WireGuard neu
|
|
||||||
systemd:
|
|
||||||
name: wg-quick@wg0
|
|
||||||
state: restarted
|
|
||||||
|
|
||||||
- name: Bestätige Entfernung
|
|
||||||
debug:
|
|
||||||
msg: "✅ Client {{ client_name }} wurde erfolgreich entfernt."
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
wireguard_interface: wg0
|
|
||||||
wireguard_port: 51820
|
|
||||||
wireguard_address: 10.8.0.1/24
|
|
||||||
wireguard_server_ip: 94.16.110.151 # oder deine Domain
|
|
||||||
|
|
||||||
wireguard_network: "10.8.0.0/24"
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
- name: restart wireguard
|
|
||||||
systemd:
|
|
||||||
name: wg-quick@wg0
|
|
||||||
state: restarted
|
|
||||||
daemon_reload: true
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
---
|
|
||||||
# WireGuard Server konfigurieren
|
|
||||||
- name: Erstelle WireGuard-Verzeichnis
|
|
||||||
file:
|
|
||||||
path: /etc/wireguard
|
|
||||||
state: directory
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
- name: Erstelle Client-Config-Verzeichnis
|
|
||||||
file:
|
|
||||||
path: /etc/wireguard/clients
|
|
||||||
state: directory
|
|
||||||
mode: '0700'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
|
|
||||||
# Server-Schlüssel verwalten
|
|
||||||
- name: Prüfe ob Server-Schlüssel existieren
|
|
||||||
stat:
|
|
||||||
path: /etc/wireguard/server-private.key
|
|
||||||
register: server_private_key_stat
|
|
||||||
|
|
||||||
- name: Generiere Server-Schlüssel
|
|
||||||
shell: |
|
|
||||||
wg genkey | tee /etc/wireguard/server-private.key | wg pubkey > /etc/wireguard/server-public.key
|
|
||||||
chmod 600 /etc/wireguard/server-private.key /etc/wireguard/server-public.key
|
|
||||||
when: not server_private_key_stat.stat.exists
|
|
||||||
|
|
||||||
- name: Lese Server-Schlüssel
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/server-private.key
|
|
||||||
register: server_private_key_content
|
|
||||||
|
|
||||||
- name: Lese Server-Public-Key
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/server-public.key
|
|
||||||
register: server_public_key_content
|
|
||||||
|
|
||||||
- name: Setze Server-Schlüssel als Facts
|
|
||||||
set_fact:
|
|
||||||
wg_server_private_key: "{{ server_private_key_content.content | b64decode | trim }}"
|
|
||||||
wg_server_public_key: "{{ server_public_key_content.content | b64decode | trim }}"
|
|
||||||
|
|
||||||
# Client-Schlüssel generieren
|
|
||||||
- name: Generiere Client-Schlüssel
|
|
||||||
shell: |
|
|
||||||
cd /etc/wireguard/clients
|
|
||||||
if [ ! -f "{{ item.name }}-private.key" ]; then
|
|
||||||
wg genkey | tee "{{ item.name }}-private.key" | wg pubkey > "{{ item.name }}-public.key"
|
|
||||||
chmod 600 "{{ item.name }}-private.key" "{{ item.name }}-public.key"
|
|
||||||
fi
|
|
||||||
loop: "{{ wireguard_clients }}"
|
|
||||||
|
|
||||||
# Generiere Pre-shared Keys
|
|
||||||
- name: Generiere Pre-shared Keys für Clients
|
|
||||||
shell: |
|
|
||||||
cd /etc/wireguard/clients
|
|
||||||
if [ ! -f "{{ item.name }}-psk.key" ]; then
|
|
||||||
wg genpsk > "{{ item.name }}-psk.key"
|
|
||||||
chmod 600 "{{ item.name }}-psk.key"
|
|
||||||
fi
|
|
||||||
loop: "{{ wireguard_clients }}"
|
|
||||||
when: wireguard_pre_shared_key | default(false)
|
|
||||||
|
|
||||||
# Lade alle Client-Keys
|
|
||||||
- name: Lese Client-Private-Keys
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/clients/{{ item.name }}-private.key
|
|
||||||
loop: "{{ wireguard_clients }}"
|
|
||||||
register: client_private_keys
|
|
||||||
|
|
||||||
- name: Lese Client-Public-Keys
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/clients/{{ item.name }}-public.key
|
|
||||||
loop: "{{ wireguard_clients }}"
|
|
||||||
register: client_public_keys
|
|
||||||
|
|
||||||
- name: Lese Pre-shared Keys
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/clients/{{ item.name }}-psk.key
|
|
||||||
loop: "{{ wireguard_clients }}"
|
|
||||||
register: client_psk_keys
|
|
||||||
when: wireguard_pre_shared_key | default(false)
|
|
||||||
|
|
||||||
# Erstelle Key-Dictionaries
|
|
||||||
- name: Erstelle Client-Key-Dictionary
|
|
||||||
set_fact:
|
|
||||||
wg_client_private_keys: "{{ dict(wireguard_clients | map(attribute='name') | list | zip(client_private_keys.results | map(attribute='content') | map('b64decode') | map('trim') | list)) }}"
|
|
||||||
wg_client_public_keys: "{{ dict(wireguard_clients | map(attribute='name') | list | zip(client_public_keys.results | map(attribute='content') | map('b64decode') | map('trim') | list)) }}"
|
|
||||||
|
|
||||||
- name: Erstelle Pre-shared Key Dictionary
|
|
||||||
set_fact:
|
|
||||||
wg_client_psk_keys: "{{ dict(wireguard_clients | map(attribute='name') | list | zip(client_psk_keys.results | map(attribute='content') | map('b64decode') | map('trim') | list)) }}"
|
|
||||||
when:
|
|
||||||
- wireguard_pre_shared_key | default(false)
|
|
||||||
- client_psk_keys is defined
|
|
||||||
|
|
||||||
# Server-Konfiguration erstellen
|
|
||||||
- name: Erstelle WireGuard-Server-Konfiguration
|
|
||||||
template:
|
|
||||||
src: wg0.conf.j2
|
|
||||||
dest: /etc/wireguard/wg0.conf
|
|
||||||
mode: '0600'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
notify: restart wireguard
|
|
||||||
|
|
||||||
# Client-Konfigurationen erstellen
|
|
||||||
- name: Erstelle Client-Konfigurationen
|
|
||||||
template:
|
|
||||||
src: client.conf.j2
|
|
||||||
dest: /etc/wireguard/clients/{{ item.name }}.conf
|
|
||||||
mode: '0600'
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
loop: "{{ wireguard_clients }}"
|
|
||||||
|
|
||||||
# WireGuard-Service konfigurieren
|
|
||||||
- name: Aktiviere WireGuard-Service
|
|
||||||
systemd:
|
|
||||||
name: wg-quick@wg0
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
daemon_reload: true
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
# Installiere WireGuard
|
|
||||||
- name: Installiere WireGuard
|
|
||||||
apt:
|
|
||||||
name: wireguard
|
|
||||||
state: present
|
|
||||||
update_cache: yes
|
|
||||||
when: ansible_connection != "local"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Prüfe erforderliche Variablen
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- wireguard_clients is defined
|
|
||||||
- wireguard_server_ip is defined
|
|
||||||
- wireguard_network is defined
|
|
||||||
fail_msg: "WireGuard-Konfiguration unvollständig: erforderliche Variablen nicht definiert"
|
|
||||||
success_msg: "WireGuard-Variablen korrekt definiert"
|
|
||||||
tags: [always]
|
|
||||||
|
|
||||||
- name: Installiere WireGuard
|
|
||||||
import_tasks: install.yml
|
|
||||||
when: ansible_connection != "local"
|
|
||||||
|
|
||||||
- name: Konfiguriere WireGuard
|
|
||||||
import_tasks: configure.yml
|
|
||||||
|
|
||||||
- name: Konfiguriere Netzwerk für WireGuard
|
|
||||||
import_tasks: network.yml
|
|
||||||
when: ansible_connection != "local"
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
---
|
|
||||||
# Netzwerk-Konfiguration für WireGuard (ohne Firewall)
|
|
||||||
- name: Aktiviere IP-Forwarding
|
|
||||||
sysctl:
|
|
||||||
name: net.ipv4.ip_forward
|
|
||||||
value: '1'
|
|
||||||
state: present
|
|
||||||
sysctl_set: true
|
|
||||||
reload: true
|
|
||||||
|
|
||||||
- name: Installiere iptables-persistent für dauerhafte Regeln
|
|
||||||
apt:
|
|
||||||
name: iptables-persistent
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: Prüfe ob WireGuard-NAT-Regel bereits existiert
|
|
||||||
shell: iptables -t nat -C POSTROUTING -o {{ wireguard_exit_interface }} -s {{ wireguard_network }} -j MASQUERADE
|
|
||||||
register: nat_rule_exists
|
|
||||||
ignore_errors: true
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Setze NAT-Regel für WireGuard-Traffic
|
|
||||||
iptables:
|
|
||||||
table: nat
|
|
||||||
chain: POSTROUTING
|
|
||||||
out_interface: "{{ wireguard_exit_interface }}"
|
|
||||||
source: "{{ wireguard_network }}"
|
|
||||||
jump: MASQUERADE
|
|
||||||
comment: "WireGuard VPN NAT"
|
|
||||||
when: nat_rule_exists.rc != 0
|
|
||||||
|
|
||||||
- name: Prüfe ob FORWARD-Regel für WireGuard eingehend existiert
|
|
||||||
shell: iptables -C FORWARD -i {{ wireguard_interface }} -j ACCEPT
|
|
||||||
register: forward_in_exists
|
|
||||||
ignore_errors: true
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Erlaube FORWARD von WireGuard-Interface
|
|
||||||
iptables:
|
|
||||||
chain: FORWARD
|
|
||||||
in_interface: "{{ wireguard_interface }}"
|
|
||||||
jump: ACCEPT
|
|
||||||
comment: "Allow WireGuard traffic in"
|
|
||||||
when: forward_in_exists.rc != 0
|
|
||||||
|
|
||||||
- name: Prüfe ob FORWARD-Regel für WireGuard ausgehend existiert
|
|
||||||
shell: iptables -C FORWARD -o {{ wireguard_interface }} -j ACCEPT
|
|
||||||
register: forward_out_exists
|
|
||||||
ignore_errors: true
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Erlaube FORWARD zu WireGuard-Interface
|
|
||||||
iptables:
|
|
||||||
chain: FORWARD
|
|
||||||
out_interface: "{{ wireguard_interface }}"
|
|
||||||
jump: ACCEPT
|
|
||||||
comment: "Allow WireGuard traffic out"
|
|
||||||
when: forward_out_exists.rc != 0
|
|
||||||
|
|
||||||
- name: Speichere iptables-Regeln permanent
|
|
||||||
shell: |
|
|
||||||
iptables-save > /etc/iptables/rules.v4
|
|
||||||
ip6tables-save > /etc/iptables/rules.v6
|
|
||||||
|
|
||||||
- name: Zeige WireGuard-relevante iptables-Regeln
|
|
||||||
shell: |
|
|
||||||
echo "=== NAT Rules ==="
|
|
||||||
iptables -t nat -L POSTROUTING -n | grep {{ wireguard_network.split('/')[0] }}
|
|
||||||
echo "=== FORWARD Rules ==="
|
|
||||||
iptables -L FORWARD -n | grep {{ wireguard_interface }}
|
|
||||||
register: wg_rules
|
|
||||||
changed_when: false
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Debug WireGuard-Netzwerk-Konfiguration
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
✅ WireGuard-Netzwerk konfiguriert
|
|
||||||
✅ IP-Forwarding aktiviert
|
|
||||||
✅ NAT für VPN-Clients aktiviert
|
|
||||||
✅ Server bleibt öffentlich erreichbar
|
|
||||||
✅ VPN-Clients können ins Internet
|
|
||||||
|
|
||||||
{{ wg_rules.stdout }}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
[Interface]
|
|
||||||
PrivateKey = {{ wg_client_private_keys[item.name] }}
|
|
||||||
Address = {{ item.address }}/32
|
|
||||||
{% if wireguard_dns_servers is defined %}
|
|
||||||
DNS = {{ wireguard_dns_servers | join(', ') }}
|
|
||||||
{% endif %}
|
|
||||||
{% if wireguard_mtu is defined %}
|
|
||||||
MTU = {{ wireguard_mtu }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = {{ wg_server_public_key }}
|
|
||||||
Endpoint = {{ wireguard_server_ip }}:{{ wireguard_port }}
|
|
||||||
AllowedIPs = {{ wireguard_network }}
|
|
||||||
{% if wireguard_keepalive is defined %}
|
|
||||||
PersistentKeepalive = {{ wireguard_keepalive }}
|
|
||||||
{% endif %}
|
|
||||||
{% if wireguard_pre_shared_key | default(false) and wg_client_psk_keys is defined %}
|
|
||||||
PresharedKey = {{ wg_client_psk_keys[item.name] }}
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
[Interface]
|
|
||||||
Address = {{ wireguard_address }}
|
|
||||||
PrivateKey = {{ wg_server_private_key }}
|
|
||||||
ListenPort = {{ wireguard_port }}
|
|
||||||
{% if wireguard_mtu is defined %}
|
|
||||||
MTU = {{ wireguard_mtu }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# Einfache NAT-Regeln für VPN-Traffic
|
|
||||||
PostUp = iptables -t nat -I POSTROUTING -o {{ wireguard_exit_interface }} -s {{ wireguard_network }} -j MASQUERADE
|
|
||||||
PostUp = iptables -I FORWARD -i {{ wireguard_interface }} -j ACCEPT
|
|
||||||
PostUp = iptables -I FORWARD -o {{ wireguard_interface }} -j ACCEPT
|
|
||||||
|
|
||||||
PostDown = iptables -t nat -D POSTROUTING -o {{ wireguard_exit_interface }} -s {{ wireguard_network }} -j MASQUERADE
|
|
||||||
PostDown = iptables -D FORWARD -i {{ wireguard_interface }} -j ACCEPT
|
|
||||||
PostDown = iptables -D FORWARD -o {{ wireguard_interface }} -j ACCEPT
|
|
||||||
|
|
||||||
# Client-Peers
|
|
||||||
{% for client in wireguard_clients %}
|
|
||||||
[Peer]
|
|
||||||
# {{ client.name }}
|
|
||||||
PublicKey = {{ wg_client_public_keys[client.name] }}
|
|
||||||
AllowedIPs = {{ client.address }}/32
|
|
||||||
{% if wireguard_pre_shared_key | default(false) and wg_client_psk_keys is defined %}
|
|
||||||
PresharedKey = {{ wg_client_psk_keys[client.name] }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Show WireGuard Clients
|
|
||||||
hosts: vpn
|
|
||||||
become: true
|
|
||||||
gather_facts: false
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Zeige vorhandene Clients
|
|
||||||
find:
|
|
||||||
paths: /etc/wireguard/clients
|
|
||||||
patterns: "*.conf"
|
|
||||||
register: existing_clients
|
|
||||||
|
|
||||||
- name: Liste vorhandene Clients
|
|
||||||
debug:
|
|
||||||
msg: "Vorhandene Clients: {{ existing_clients.files | map(attribute='path') | map('basename') | map('regex_replace', '\\.conf$', '') | list }}"
|
|
||||||
|
|
||||||
- name: Zeige Client-IPs
|
|
||||||
shell: |
|
|
||||||
for conf in /etc/wireguard/clients/*.conf; do
|
|
||||||
if [ -f "$conf" ]; then
|
|
||||||
echo "$(basename "$conf" .conf): $(grep '^Address' "$conf" | cut -d' ' -f3)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
register: client_ips
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Client-IP-Übersicht
|
|
||||||
debug:
|
|
||||||
var: client_ips.stdout_lines
|
|
||||||
|
|
||||||
- name: Zeige WireGuard-Server-Status
|
|
||||||
command: wg show
|
|
||||||
register: wg_status
|
|
||||||
changed_when: false
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Server-Status
|
|
||||||
debug:
|
|
||||||
var: wg_status.stdout_lines
|
|
||||||
when: wg_status.rc == 0
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
---
|
|
||||||
- name: WireGuard VPN Server Setup (ohne Firewall)
|
|
||||||
hosts: vpn
|
|
||||||
become: true
|
|
||||||
gather_facts: true
|
|
||||||
|
|
||||||
pre_tasks:
|
|
||||||
- name: Update package cache
|
|
||||||
apt:
|
|
||||||
update_cache: true
|
|
||||||
cache_valid_time: 3600
|
|
||||||
|
|
||||||
- name: Zeige Setup-Information
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
🌐 WireGuard-Installation OHNE Firewall
|
|
||||||
✅ Server bleibt öffentlich erreichbar
|
|
||||||
✅ WireGuard als zusätzlicher VPN-Zugang
|
|
||||||
✅ Keine SSH-Beschränkungen
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- role: wireguard
|
|
||||||
|
|
||||||
post_tasks:
|
|
||||||
- name: Prüfe ob qrencode installiert ist
|
|
||||||
command: which qrencode
|
|
||||||
register: qrencode_check
|
|
||||||
ignore_errors: true
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Installiere qrencode für QR-Codes
|
|
||||||
apt:
|
|
||||||
name: qrencode
|
|
||||||
state: present
|
|
||||||
when: qrencode_check.rc != 0
|
|
||||||
|
|
||||||
- name: Erstelle QR-Codes für mobile Clients
|
|
||||||
shell: qrencode -t ansiutf8 < /etc/wireguard/clients/{{ item.name }}.conf
|
|
||||||
loop: "{{ wireguard_clients }}"
|
|
||||||
register: qr_codes
|
|
||||||
when: item.name is search('phone|mobile')
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Zeige QR-Codes
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
QR-Code für {{ item.item.name }}:
|
|
||||||
{{ item.stdout }}
|
|
||||||
loop: "{{ qr_codes.results }}"
|
|
||||||
when: item.stdout is defined and not item.failed
|
|
||||||
|
|
||||||
- name: Zeige WireGuard-Status
|
|
||||||
command: wg show
|
|
||||||
register: wg_status
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: WireGuard-Status anzeigen
|
|
||||||
debug:
|
|
||||||
var: wg_status.stdout_lines
|
|
||||||
|
|
||||||
- name: Zeige finale Setup-Information
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
🎉 WireGuard erfolgreich installiert!
|
|
||||||
|
|
||||||
Server-Zugang:
|
|
||||||
📡 Öffentlich: ssh root@{{ wireguard_server_ip }}
|
|
||||||
🔒 Via VPN: ssh root@{{ wireguard_address.split('/')[0] }} (nach VPN-Verbindung)
|
|
||||||
|
|
||||||
Client-Konfigurationen:
|
|
||||||
📂 Server-Pfad: /etc/wireguard/clients/
|
|
||||||
💾 Download: make download-configs
|
|
||||||
📱 QR-Codes: make qr-codes
|
|
||||||
|
|
||||||
Nützliche Befehle:
|
|
||||||
🔍 Status: make status
|
|
||||||
📋 Logs: make logs
|
|
||||||
➕ Client hinzufügen: make add-client
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Create WireGuard Client Configurations
|
|
||||||
hosts: vpn
|
|
||||||
become: true
|
|
||||||
gather_facts: false
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Ensure client directory exists
|
|
||||||
file:
|
|
||||||
path: /etc/wireguard/clients
|
|
||||||
state: directory
|
|
||||||
mode: '0700'
|
|
||||||
|
|
||||||
- name: Load existing server keys
|
|
||||||
slurp:
|
|
||||||
src: /etc/wireguard/server-public.key
|
|
||||||
register: server_pub_key
|
|
||||||
|
|
||||||
- name: Set server public key fact
|
|
||||||
set_fact:
|
|
||||||
wg_server_public_key: "{{ server_pub_key.content | b64decode | trim }}"
|
|
||||||
|
|
||||||
- name: Generate client configurations
|
|
||||||
include_role:
|
|
||||||
name: wireguard
|
|
||||||
tasks_from: configure
|
|
||||||
vars:
|
|
||||||
wg_server_public_key: "{{ server_pub_key.content | b64decode | trim }}"
|
|
||||||
|
|
||||||
- name: List created client configurations
|
|
||||||
find:
|
|
||||||
paths: /etc/wireguard/clients
|
|
||||||
patterns: "*.conf"
|
|
||||||
register: client_configs
|
|
||||||
|
|
||||||
- name: Show created configurations
|
|
||||||
debug:
|
|
||||||
msg: "Created client configurations: {{ client_configs.files | map(attribute='path') | map('basename') | list }}"
|
|
||||||
|
|
||||||
- name: Generate QR codes for mobile clients
|
|
||||||
shell: qrencode -t ansiutf8 < /etc/wireguard/clients/{{ item.name }}.conf
|
|
||||||
loop: "{{ wireguard_clients }}"
|
|
||||||
register: qr_results
|
|
||||||
when: item.name is search('phone|mobile')
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Display QR codes
|
|
||||||
debug:
|
|
||||||
msg: |
|
|
||||||
QR Code for {{ item.item.name }}:
|
|
||||||
{{ item.stdout }}
|
|
||||||
loop: "{{ qr_results.results }}"
|
|
||||||
when: item.stdout is defined and not item.failed
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Install WireGuard Server
|
|
||||||
hosts: vpn
|
|
||||||
become: true
|
|
||||||
gather_facts: true
|
|
||||||
|
|
||||||
pre_tasks:
|
|
||||||
- name: Update package cache
|
|
||||||
apt:
|
|
||||||
update_cache: true
|
|
||||||
cache_valid_time: 3600
|
|
||||||
|
|
||||||
roles:
|
|
||||||
- role: wireguard
|
|
||||||
tags: [install, configure]
|
|
||||||
|
|
||||||
post_tasks:
|
|
||||||
- name: Show WireGuard status
|
|
||||||
command: wg show
|
|
||||||
register: wg_status
|
|
||||||
changed_when: false
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Display WireGuard status
|
|
||||||
debug:
|
|
||||||
var: wg_status.stdout_lines
|
|
||||||
when: wg_status.stdout is defined
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
if [ ! -f .env ]; then
|
|
||||||
echo "❌ .env fehlt!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! grep -q "APP_PORT=" .env; then
|
|
||||||
echo "⚠️ APP_PORT nicht gesetzt"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# TODO In make up oder make deploy einbauen.
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
#!/bin/sh -l
|
|
||||||
# Führt das Ansible-Deploy-Playbook aus
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Deployment-Skript für verschiedene Umgebungen
|
|
||||||
|
|
||||||
# Konfiguration
|
|
||||||
ANSIBLE_INVENTORY="ansible/inventory/hosts.ini"
|
|
||||||
PLAYBOOK_DIR="ansible/playbooks/deploy"
|
|
||||||
|
|
||||||
# Farbdefinitionen
|
|
||||||
GREEN="\033[0;32m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
RED="\033[0;31m"
|
|
||||||
NC="\033[0m" # No Color
|
|
||||||
|
|
||||||
# Funktion zum Anzeigen von Nachrichten
|
|
||||||
echo_msg() {
|
|
||||||
echo -e "${GREEN}[DEPLOY]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo_warn() {
|
|
||||||
echo -e "${YELLOW}[WARNUNG]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo_error() {
|
|
||||||
echo -e "${RED}[FEHLER]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parameter auswerten
|
|
||||||
ENVIRONMENT="$1"
|
|
||||||
TAGS="$2"
|
|
||||||
|
|
||||||
if [ -z "$ENVIRONMENT" ]; then
|
|
||||||
echo_warn "Keine Umgebung angegeben. Verfügbare Optionen:"
|
|
||||||
echo " ./bin/deploy dev - Lokale Entwicklungsumgebung"
|
|
||||||
echo " ./bin/deploy staging - Staging-Umgebung"
|
|
||||||
echo " ./bin/deploy prod - Produktionsumgebung"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Tags zusammenbauen (falls angegeben)
|
|
||||||
TAGS_OPTION=""
|
|
||||||
if [ -n "$TAGS" ]; then
|
|
||||||
TAGS_OPTION="--tags=$TAGS"
|
|
||||||
echo_msg "Verwende Tags: $TAGS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Entsprechendes Playbook ausführen
|
|
||||||
case "$ENVIRONMENT" in
|
|
||||||
dev|development|local)
|
|
||||||
echo_msg "Starte Deployment für lokale Entwicklungsumgebung..."
|
|
||||||
ansible-playbook -i "$ANSIBLE_INVENTORY" "$PLAYBOOK_DIR/dev.yml" --ask-become-pass $TAGS_OPTION
|
|
||||||
;;
|
|
||||||
staging|stage)
|
|
||||||
echo_msg "Starte Deployment für Staging-Umgebung..."
|
|
||||||
ansible-playbook -i "$ANSIBLE_INVENTORY" "$PLAYBOOK_DIR/staging.yml" $TAGS_OPTION
|
|
||||||
;;
|
|
||||||
prod|production)
|
|
||||||
echo_msg "Starte Deployment für Produktionsumgebung..."
|
|
||||||
read -p "Sind Sie sicher, dass Sie in der Produktionsumgebung deployen möchten? (j/N) " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ $REPLY =~ ^[Jj]$ ]]; then
|
|
||||||
ansible-playbook -i "$ANSIBLE_INVENTORY" "$PLAYBOOK_DIR/production.yml" $TAGS_OPTION
|
|
||||||
else
|
|
||||||
echo_warn "Deployment in Produktionsumgebung abgebrochen."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo_error "Unbekannte Umgebung: $ENVIRONMENT"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Deployment-Status prüfen
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo_msg "Deployment erfolgreich abgeschlossen."
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
echo_error "Deployment fehlgeschlagen! Bitte überprüfen Sie die Logs."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
/home/michael/.local/bin/ansible-playbook -i ansible/inventory.ini ansible/playbooks/deploy.yml
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Stoppt alle laufenden Container
|
|
||||||
docker compose down
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Zeigt die Live-Logs aller Container
|
|
||||||
docker compose logs -f
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user