fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled
Some checks failed
Deploy Application / deploy (push) Has been cancelled
This commit is contained in:
86
deployment/legacy/gitea-workflows/AUTO_SETUP_SECRETS.md
Normal file
86
deployment/legacy/gitea-workflows/AUTO_SETUP_SECRETS.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Automatisches Secrets-Setup
|
||||
|
||||
## Schnellstart
|
||||
|
||||
```bash
|
||||
# Interaktives Script (fragt nach Token)
|
||||
bash scripts/setup-gitea-secrets-interactive.sh
|
||||
```
|
||||
|
||||
Das Script:
|
||||
1. Fragt nach einem Gitea Access Token
|
||||
2. Setzt automatisch alle drei Secrets via API
|
||||
|
||||
## Token generieren
|
||||
|
||||
Falls noch kein Token vorhanden:
|
||||
|
||||
1. **Gehe zu Gitea Settings:**
|
||||
```
|
||||
https://git.michaelschiemer.de/user/settings/applications
|
||||
```
|
||||
|
||||
2. **Klicke "Generate New Token"**
|
||||
|
||||
3. **Konfiguration:**
|
||||
- Name: `secrets-setup` (oder beliebig)
|
||||
- Disponível scopes:
|
||||
- ✅ `write:repository` (mindestens)
|
||||
- ✅ Oder wähle alle für volle Berechtigung
|
||||
|
||||
4. **Kopiere den Token** (wird nur einmal angezeigt!)
|
||||
|
||||
## Script ausführen
|
||||
|
||||
```bash
|
||||
# Script starten
|
||||
bash scripts/setup-gitea-secrets-interactive.sh
|
||||
|
||||
# Token eingeben (wird nicht angezeigt)
|
||||
Gitea Token: <token-einfügen>
|
||||
|
||||
# Script setzt automatisch:
|
||||
# ✅ REGISTRY_USER = admin
|
||||
# ✅ REGISTRY_PASSWORD = registry-secure-password-3125
|
||||
# ✅ SSH_PRIVATE_KEY = <aus ~/.ssh/production>
|
||||
```
|
||||
|
||||
## Verifizierung
|
||||
|
||||
Nach erfolgreichem Setup:
|
||||
|
||||
1. **Prüfe in Gitea UI:**
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions
|
||||
```
|
||||
|
||||
2. **Sollte zeigen:**
|
||||
- ✅ REGISTRY_USER
|
||||
- ✅ REGISTRY_PASSWORD
|
||||
- ✅ SSH_PRIVATE_KEY
|
||||
|
||||
Alle drei Secrets sollten "Hidden" als Wert anzeigen.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Token ungültig"
|
||||
- Prüfe, ob Token korrekt kopiert wurde (keine Leerzeichen)
|
||||
- Prüfe, ob Token die Berechtigung `write:repository` hat
|
||||
|
||||
### "Repository nicht gefunden"
|
||||
- Prüfe Repository-Name: `michael/michaelschiemer`
|
||||
- Prüfe, ob du Zugriff auf das Repository hast
|
||||
|
||||
### "HTTP 403 Forbidden"
|
||||
- Token hat keine ausreichenden Berechtigungen
|
||||
- Generiere neuen Token mit `write:repository` scope
|
||||
|
||||
### API nicht erreichbar
|
||||
- Prüfe Gitea URL: `https://git.michaelschiemer.de`
|
||||
- Prüfe Netzwerkverbindung
|
||||
|
||||
## Alternative: Manuelles Setup
|
||||
|
||||
Falls das automatische Setup nicht funktioniert:
|
||||
|
||||
Siehe `.gitea/workflows/QUICK_SECRETS_SETUP.md` für manuelle Anleitung.
|
||||
90
deployment/legacy/gitea-workflows/CI_CD_CHECKLIST.md
Normal file
90
deployment/legacy/gitea-workflows/CI_CD_CHECKLIST.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# CI/CD Pipeline - Setup Checklist
|
||||
|
||||
## ✅ Status
|
||||
|
||||
### 1. Repository Secrets in Gitea ✅ BEREIT
|
||||
**Pfad**: Repository → Settings → Secrets
|
||||
|
||||
**Schnelles Setup:**
|
||||
```bash
|
||||
bash scripts/prepare-secrets.sh
|
||||
```
|
||||
|
||||
**Erforderliche Secrets:**
|
||||
- [ ] **REGISTRY_USER**: `admin`
|
||||
- [ ] **REGISTRY_PASSWORD**: `registry-secure-password-2025`
|
||||
- [ ] **SSH_PRIVATE_KEY**: Vollständiger Inhalt von `~/.ssh/production`
|
||||
|
||||
**Details**: Siehe `.gitea/workflows/QUICK_SECRETS_SETUP.md`
|
||||
|
||||
**Hinweis**: Alle Secrets müssen in Gitea konfiguriert werden, bevor die Pipeline läuft.
|
||||
|
||||
### 2. Docker Registry ✅
|
||||
- ✅ Registry läuft auf `registry.michaelschiemer.de`
|
||||
- ✅ Authentifizierung konfiguriert (admin/registry-secure-password-2025)
|
||||
- ✅ Erreichbar via HTTP auf `127.0.0.1:5000`
|
||||
- ✅ Image `framework` bereits vorhanden
|
||||
|
||||
**Registry-Test**:
|
||||
```bash
|
||||
curl -u admin:registry-secure-password-2025 http://127.0.0.1:5000/v2/_catalog
|
||||
# Erwartete Ausgabe: {"repositories":["framework"]}
|
||||
```
|
||||
|
||||
### 3. Ansible Playbooks ✅
|
||||
- ✅ `deploy-update.yml` - Aktualisiert für Docker Compose
|
||||
- ✅ `rollback.yml` - Aktualisiert für Docker Compose
|
||||
- ✅ `setup-infrastructure.yml` - Enthält Registry-Setup
|
||||
- ✅ `sync-stacks.yml` - Synchronisiert Stack-Konfigurationen
|
||||
|
||||
### 4. Workflow-Konfiguration ✅
|
||||
- ✅ `.gitea/workflows/production-deploy.yml` - Haupt-Workflow
|
||||
- ✅ `Dockerfile.production` - Production Dockerfile erstellt
|
||||
- ✅ Application Stack konfiguriert (`deployment/stacks/application/docker-compose.yml`)
|
||||
|
||||
## 📋 Nächste Schritte
|
||||
|
||||
### Schritt 1: Secrets in Gitea hinzufügen
|
||||
Siehe `.gitea/workflows/SECRETS_SETUP.md` für detaillierte Anleitung.
|
||||
|
||||
### Schritt 2: Workflow manuell testen
|
||||
1. Gehe zu: `https://git.michaelschiemer.de/<username>/michaelschiemer/actions`
|
||||
2. Wähle "Production Deployment Pipeline"
|
||||
3. Klicke "Run workflow"
|
||||
4. Wähle Branch `main`
|
||||
5. Beobachte die Ausführung
|
||||
|
||||
### Schritt 3: Automatisches Deployment testen
|
||||
1. Push zu `main` Branch
|
||||
2. Workflow sollte automatisch starten
|
||||
3. Prüfe Logs in Gitea Actions
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Registry nicht erreichbar
|
||||
```bash
|
||||
# Prüfe Registry-Status
|
||||
ssh deploy@94.16.110.151 "docker ps | grep registry"
|
||||
|
||||
# Teste Registry-Erreichbarkeit
|
||||
ssh deploy@94.16.110.151 "curl -u admin:registry-secure-password-2025 http://127.0.0.1:5000/v2/_catalog"
|
||||
```
|
||||
|
||||
### Workflow schlägt bei Registry-Login fehl
|
||||
- Prüfe, ob `REGISTRY_USER` und `REGISTRY_PASSWORD` Secrets korrekt gesetzt sind
|
||||
- Prüfe, ob das Passwort in Gitea mit dem Server übereinstimmt
|
||||
|
||||
### Image Pull schlägt fehl
|
||||
- Prüfe, ob das Image in der Registry existiert:
|
||||
```bash
|
||||
curl -u admin:registry-secure-password-2025 http://127.0.0.1:5000/v2/framework/tags/list
|
||||
```
|
||||
- Prüfe Registry-Logs für Fehler
|
||||
|
||||
### Deployment schlägt fehl
|
||||
- Prüfe Ansible-Logs im Workflow
|
||||
- Prüfe, ob `deployment/stacks/application/docker-compose.yml` auf dem Server existiert
|
||||
- Prüfe Docker-Logs auf dem Server:
|
||||
```bash
|
||||
ssh deploy@94.16.110.151 "cd ~/deployment/stacks/application && docker compose logs"
|
||||
```
|
||||
101
deployment/legacy/gitea-workflows/MANUAL_SETUP_SIMPLE.md
Normal file
101
deployment/legacy/gitea-workflows/MANUAL_SETUP_SIMPLE.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Manuelles Secrets-Setup - Einfache Anleitung
|
||||
|
||||
Da das automatische Setup Probleme macht, hier die manuelle Lösung:
|
||||
|
||||
## Schritt 1: Gehe zu Gitea Secrets-Seite
|
||||
|
||||
Öffne im Browser:
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions
|
||||
```
|
||||
|
||||
**Hinweis**: Falls diese Seite nicht existiert oder einen 404 gibt:
|
||||
- Prüfe, ob das Repository wirklich `michael/michaelschiemer` heißt
|
||||
- Prüfe, ob du Zugriff auf das Repository hast
|
||||
- Prüfe, ob Actions aktiviert ist
|
||||
|
||||
## Schritt 2: Füge die drei Secrets hinzu
|
||||
|
||||
Für jedes Secret: **Klicke "New Secret"**, fülle aus, **Save**
|
||||
|
||||
### Secret 1: REGISTRY_USER
|
||||
|
||||
- **Name**: `REGISTRY_USER`
|
||||
- **Value**: `admin`
|
||||
- **Save**
|
||||
|
||||
### Secret 2: REGISTRY_PASSWORD
|
||||
|
||||
- **Name**: `REGISTRY_PASSWORD`
|
||||
- **Value**: `registry-secure-password-2025`
|
||||
- **Save**
|
||||
|
||||
### Secret 3: SSH_PRIVATE_KEY
|
||||
|
||||
- **Name**: `SSH_PRIVATE_KEY`
|
||||
- **Value**: Führe aus `cat ~/.ssh/production` und kopiere den KOMPLETTEN Inhalt
|
||||
```bash
|
||||
cat ~/.ssh/production
|
||||
```
|
||||
|
||||
**Wichtig**: Kopiere ALLES, inklusive:
|
||||
- `-----BEGIN OPENSSH PRIVATE KEY-----`
|
||||
- Alle Zeilen dazwischen
|
||||
- `-----END OPENSSH PRIVATE KEY-----`
|
||||
|
||||
- **Save**
|
||||
|
||||
## Schritt 3: Verifizierung
|
||||
|
||||
Nach dem Setup sollten alle drei Secrets in der Liste erscheinen:
|
||||
- ✅ REGISTRY_USER
|
||||
- ✅ REGISTRY_PASSWORD
|
||||
- ✅ SSH_PRIVATE_KEY
|
||||
|
||||
Alle zeigen "Hidden" als Wert.
|
||||
|
||||
## Falls die Secrets-Seite nicht erreichbar ist
|
||||
|
||||
### Option A: Repository-Name prüfen
|
||||
|
||||
```bash
|
||||
# Prüfe aktuelle Remote-URL
|
||||
git remote get-url origin
|
||||
|
||||
# Sollte zeigen:
|
||||
# https://git.michaelschiemer.de/<owner>/<repo>.git
|
||||
```
|
||||
|
||||
Falls der Name anders ist, verwende die korrekte URL.
|
||||
|
||||
### Option B: Repository erstellen
|
||||
|
||||
Falls das Repository noch nicht existiert:
|
||||
|
||||
1. Gehe zu: `https://git.michaelschiemer.de/repos/new`
|
||||
2. Erstelle das Repository `michaelschiemer`
|
||||
3. Dann gehe zu: `https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions`
|
||||
|
||||
### Option C: Actions aktivieren
|
||||
|
||||
Falls Actions nicht aktiviert ist:
|
||||
|
||||
1. Gehe zu Repository Settings
|
||||
2. Prüfe, ob "Actions" aktiviert ist
|
||||
3. Falls nicht, aktiviere es in den Repository-Einstellungen
|
||||
|
||||
## Nächster Schritt
|
||||
|
||||
Nach erfolgreichem Setup der Secrets:
|
||||
|
||||
1. **Teste den Workflow:**
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer/actions
|
||||
```
|
||||
|
||||
2. **Oder pushe einen Commit:**
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Der Workflow sollte dann automatisch starten.
|
||||
87
deployment/legacy/gitea-workflows/MANUAL_SETUP_WITH_TOKEN.md
Normal file
87
deployment/legacy/gitea-workflows/MANUAL_SETUP_WITH_TOKEN.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Secrets Setup mit Token - Schritt für Schritt
|
||||
|
||||
## ✅ Du hast bereits ein Token - Perfekt!
|
||||
|
||||
## Option 1: Automatisches Setup (Empfohlen)
|
||||
|
||||
Führe einfach dieses Kommando aus (ersetze `<DEIN_TOKEN>` mit deinem Token):
|
||||
|
||||
```bash
|
||||
bash scripts/setup-gitea-secrets-with-token.sh <DEIN_TOKEN>
|
||||
```
|
||||
|
||||
**Beispiel:**
|
||||
```bash
|
||||
bash scripts/setup-gitea-secrets-with-token.sh ghp_1234567890abcdefghijklmnopqrstuvwxyz
|
||||
```
|
||||
|
||||
Das Script:
|
||||
1. ✅ Testet die API-Verbindung
|
||||
2. ✅ Setzt automatisch `REGISTRY_USER` = `admin`
|
||||
3. ✅ Setzt automatisch `REGISTRY_PASSWORD` = `registry-secure-password-2025`
|
||||
4. ✅ Setzt automatisch `SSH_PRIVATE_KEY` = Inhalt von `~/.ssh/production`
|
||||
|
||||
## Option 2: Manuell über Gitea UI
|
||||
|
||||
Falls das automatische Setup nicht funktioniert:
|
||||
|
||||
### Schritt 1: Gehe zu Secrets-Seite
|
||||
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions
|
||||
```
|
||||
|
||||
### Schritt 2: Füge jedes Secret einzeln hinzu
|
||||
|
||||
**REGISTRY_USER:**
|
||||
1. Klicke "New Secret"
|
||||
2. Name: `REGISTRY_USER`
|
||||
3. Value: `admin`
|
||||
4. Save
|
||||
|
||||
**REGISTRY_PASSWORD:**
|
||||
1. Klicke "New Secret"
|
||||
2. Name: `REGISTRY_PASSWORD`
|
||||
3. Value: `registry-secure-password-2025`
|
||||
4. Save
|
||||
|
||||
**SSH_PRIVATE_KEY:**
|
||||
1. Klicke "New Secret"
|
||||
2. Name: `SSH_PRIVATE_KEY`
|
||||
3. Value: Kompletter Inhalt von `~/.ssh/production`
|
||||
```bash
|
||||
cat ~/.ssh/production
|
||||
```
|
||||
(Kopiere ALLES, inklusive `-----BEGIN` und `-----END` Zeilen)
|
||||
4. Save
|
||||
|
||||
## Verifizierung
|
||||
|
||||
Nach dem Setup:
|
||||
|
||||
1. **Prüfe in Gitea UI:**
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions
|
||||
```
|
||||
|
||||
2. **Sollte zeigen:**
|
||||
- ✅ REGISTRY_USER
|
||||
- ✅ REGISTRY_PASSWORD
|
||||
- ✅ SSH_PRIVATE_KEY
|
||||
|
||||
Alle drei Secrets sollten "Hidden" als Wert anzeigen.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "API-Verbindung fehlgeschlagen"
|
||||
- Prüfe Token-Kopierung (keine Leerzeichen)
|
||||
- Prüfe Token-Berechtigung: `write:repository` Scope nötig
|
||||
- Prüfe Repository-Name: `michael/michaelschiemer`
|
||||
|
||||
### "HTTP 403 Forbidden"
|
||||
- Token hat keine ausreichenden Berechtigungen
|
||||
- Generiere neuen Token mit `write:repository` scope
|
||||
|
||||
### "HTTP 404 Not Found"
|
||||
- Repository existiert nicht oder falscher Name
|
||||
- Prüfe: `https://git.michaelschiemer.de/michael/michaelschiemer`
|
||||
119
deployment/legacy/gitea-workflows/NEXT_STEPS.md
Normal file
119
deployment/legacy/gitea-workflows/NEXT_STEPS.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# ✅ Secrets erfolgreich gesetzt!
|
||||
|
||||
## Status
|
||||
|
||||
✅ Repository erstellt
|
||||
✅ Secrets konfiguriert:
|
||||
- REGISTRY_USER
|
||||
- REGISTRY_PASSWORD
|
||||
- SSH_PRIVATE_KEY
|
||||
|
||||
## 🚀 Nächster Schritt: CI/CD Workflow testen
|
||||
|
||||
### Option 1: Workflow manuell triggern (Empfohlen)
|
||||
|
||||
1. **Gehe zu Actions:**
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer/actions
|
||||
```
|
||||
|
||||
2. **Workflow auswählen:**
|
||||
- Suche nach "Production Deployment Pipeline"
|
||||
- Klicke auf den Workflow
|
||||
|
||||
3. **Workflow starten:**
|
||||
- Klicke "Run workflow" (rechts oben)
|
||||
- Branch: `main`
|
||||
- `skip_tests`: deaktiviert (Tests sollen laufen)
|
||||
- Klicke "Run workflow"
|
||||
|
||||
4. **Logs beobachten:**
|
||||
- Prüfe jeden Schritt in den Logs
|
||||
- Er_warte ~8-15 Minuten für komplette Ausführung
|
||||
|
||||
### Option 2: Automatisches Deployment via Commit
|
||||
|
||||
```bash
|
||||
# Stelle sicher, dass alles committed ist
|
||||
git add .
|
||||
git commit -m "chore: CI/CD pipeline setup complete"
|
||||
|
||||
# Push zu main - startet automatisch den Workflow
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## 📊 Workflow-Schritte
|
||||
|
||||
Der Workflow führt folgende Schritte aus:
|
||||
|
||||
1. **Tests** (~2-5 Min)
|
||||
- PHP Setup
|
||||
- Composer Dependencies
|
||||
- Tests ausführen
|
||||
|
||||
2. **Build** (~3-5 Min)
|
||||
- Multi-Stage Docker Build
|
||||
- Composer Dependencies (Production)
|
||||
- Frontend Build (npm)
|
||||
- Final Production Image
|
||||
|
||||
3. **Push** (~1-2 Min)
|
||||
- Docker Login zur Registry
|
||||
- Image Tag generieren
|
||||
- Image zur Registry pushen
|
||||
|
||||
4. **Deploy** (~2-4 Min)
|
||||
- Ansible Playbook ausführen
|
||||
- Image auf Production pullen
|
||||
- Application Stack aktualisieren
|
||||
- Services neu starten
|
||||
|
||||
**Gesamtzeit: ~8-15 Minuten**
|
||||
|
||||
## ✅ Erfolgreiche Ausführung erkennen
|
||||
|
||||
Der Workflow ist erfolgreich, wenn:
|
||||
|
||||
- ✅ Alle Jobs grün sind
|
||||
- ✅ Keine Fehler in den Logs
|
||||
- ✅ "Deploy via Ansible" erfolgreich
|
||||
- ✅ Application läuft auf Production-Server
|
||||
|
||||
## 🔍 Verifizierung nach Deployment
|
||||
|
||||
```bash
|
||||
# Prüfe Application-Status
|
||||
ssh deploy@94.16.110.151 "cd ~/deployment/stacks/application && docker compose ps"
|
||||
|
||||
# Prüfe Logs
|
||||
ssh deploy@94.16.110.151 "cd ~/deployment/stacks/application && docker compose logs --tail=50"
|
||||
|
||||
# Prüfe Health Endpoint
|
||||
curl -k https://michaelschiemer.de/health
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Workflow startet nicht
|
||||
- Prüfe, ob `.gitea/workflows/production-deploy.yml` im Repository ist
|
||||
- Prüfe Workflow-Syntax
|
||||
|
||||
### "Secret not found" Fehler
|
||||
- Prüfe Secrets-Seite: `https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions`
|
||||
- Prüfe, ob Namen exakt übereinstimmen (Groß-/Kleinschreibung!)
|
||||
|
||||
### Registry Login fehlgeschlagen
|
||||
- Prüfe `REGISTRY_USER` und `REGISTRY_PASSWORD` Secrets
|
||||
- Teste Registry: `curl -u admin:registry-secure-password-2025 http://127.0.0.1:5000/v2/_catalog`
|
||||
|
||||
### Deployment fehlgeschlagen
|
||||
- Prüfe `SSH_PRIVATE_KEY` Secret
|
||||
- Prüfe Ansible-Verbindung: `ssh deploy@94.16.110.151 "echo OK"`
|
||||
- Prüfe Server-Logs
|
||||
|
||||
## 🎉 Nach erfolgreichem Test
|
||||
|
||||
Du kannst jetzt:
|
||||
- ✅ Commits pushen → Automatisches Deployment
|
||||
- ✅ Workflow manuell triggern für kontrollierte Deployments
|
||||
- ✅ Branch-Protection aktivieren für sichere Deployments
|
||||
@@ -0,0 +1,31 @@
|
||||
# ⚠️ DEPRECATED: production-deploy.yml
|
||||
|
||||
Diese Datei ist **deprecated** und wird durch getrennte Workflows ersetzt:
|
||||
|
||||
## Neue Workflows
|
||||
|
||||
### 1. `build-image.yml` - Automatischer Image-Build
|
||||
- **Trigger**: Automatisch bei Push zu `main`/`develop`
|
||||
- **Zweck**: Baut Docker Images und pusht sie zur Registry
|
||||
- **Vorteil**: Images sind sofort verfügbar, auch ohne Deployment
|
||||
|
||||
### 2. `deploy-production.yml` - Deployment Workflow
|
||||
- **Trigger**:
|
||||
- Manuell über `workflow_dispatch`
|
||||
- Optional: Automatisch nach erfolgreichem Build (konfigurierbar)
|
||||
- **Zweck**: Deployed vorhandene Images auf Production Server
|
||||
- **Vorteil**: Flexibles Deployment, schneller wenn Image bereits vorhanden
|
||||
|
||||
## Migration
|
||||
|
||||
Die alte `production-deploy.yml` kann entfernt werden, sobald:
|
||||
1. ✅ `build-image.yml` erfolgreich getestet wurde
|
||||
2. ✅ `deploy-production.yml` erfolgreich getestet wurde
|
||||
3. ✅ Team ist mit neuen Workflows vertraut
|
||||
|
||||
## Vorteile der Trennung
|
||||
|
||||
- ⚡ **Schnelleres Feedback**: Images werden sofort gebaut (ca. 5-8 Min statt 10-15 Min)
|
||||
- 🎯 **Flexibleres Deployment**: Man kann wählen, wann und welches Image deployed wird
|
||||
- 🔄 **Parallele Ausführung**: Mehrere Builds können gleichzeitig laufen
|
||||
- 📦 **Image-Caching**: Builds sind unabhängig von Deployments
|
||||
104
deployment/legacy/gitea-workflows/QUICK_SECRETS_SETUP.md
Normal file
104
deployment/legacy/gitea-workflows/QUICK_SECRETS_SETUP.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Quick Secrets Setup für Gitea CI/CD
|
||||
|
||||
## Zusammenfassung der benötigten Werte
|
||||
|
||||
### REGISTRY_USER
|
||||
```
|
||||
admin
|
||||
```
|
||||
|
||||
### REGISTRY_PASSWORD
|
||||
```
|
||||
registry-secure-password-2025
|
||||
```
|
||||
|
||||
### SSH_PRIVATE_KEY
|
||||
Kopiere den kompletten Inhalt von φ~/.ssh/production`:
|
||||
|
||||
```bash
|
||||
# Zeige SSH Key Inhalt (für Copy-Paste)
|
||||
cat ~/.ssh/production
|
||||
```
|
||||
|
||||
**Wichtig**: Der komplette Inhalt muss kopiert werden, inklusive:
|
||||
- `-----BEGIN OPENSSH PRIVATE KEY-----`
|
||||
- Alle Zeilen dazwischen
|
||||
- `-----END OPENSSH PRIVATE KEY-----`
|
||||
|
||||
## Schnelle Vorbereitung
|
||||
|
||||
Führe das Helper-Script aus, um alle Werte anzuzeigen:
|
||||
|
||||
```bash
|
||||
bash scripts/prepare-secrets.sh
|
||||
```
|
||||
|
||||
Dies zeigt alle drei Secrets an, die du kopieren kannst.
|
||||
|
||||
## Manuelle Einrichtung in Gitea
|
||||
|
||||
1. **Gehe zu Repository Settings:**
|
||||
```
|
||||
https://git.michaelschiemer.de/<username>/michaelschiemer/settings/secrets
|
||||
```
|
||||
|
||||
2. **Klicke auf "New Secret"**
|
||||
|
||||
3. **Füge jedes Secret hinzu:**
|
||||
|
||||
**REGISTRY_USER:**
|
||||
- Name: `REGISTRY_USER`
|
||||
- Value: `admin`
|
||||
- Save
|
||||
|
||||
**REGISTRY_PASSWORD:**
|
||||
- Name: `REGISTRY_PASSWORD`
|
||||
- Value: `registry-secure-password-2025`
|
||||
- Save
|
||||
|
||||
**SSH_PRIVATE_KEY:**
|
||||
- Name: `SSH_PRIVATE_KEY`
|
||||
- Value: `<kompletter Inhalt von cat ~/.ssh/production>`
|
||||
- Save
|
||||
|
||||
## Verifizierung
|
||||
|
||||
Nach dem Setup sollten alle drei Secrets in der Liste erscheinen:
|
||||
- ✅ REGISTRY_USER
|
||||
- ✅ REGISTRY_PASSWORD
|
||||
- ✅ SSH_PRIVATE_KEY
|
||||
|
||||
Alle zeigen "Hidden" als Wert.
|
||||
|
||||
## Nächster Schritt
|
||||
|
||||
Sobald die Secrets konfiguriert sind:
|
||||
|
||||
1. **Workflow manuell testen:**
|
||||
```
|
||||
https://git.michaelschiemer.de/<username>/michaelschiemer/actions
|
||||
```
|
||||
- Wähle "Production Deployment Pipeline"
|
||||
- Klicke "Run workflow"
|
||||
- Wähle Branch `main`
|
||||
|
||||
2. **Oder automatisches Deployment testen:**
|
||||
- Pushe einen Commit zu `main`
|
||||
- Workflow startet automatisch
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Secrets werden nicht erkannt
|
||||
- Prüfe, ob die Namen exakt übereinstimmen (Groß-/Kleinschreibung!)
|
||||
- Prüfe, ob keine Leerzeichen am Anfang/Ende
|
||||
|
||||
### SSH Key Fehler
|
||||
- Stelle sicher, dass der komplette Key kopiert wurde
|
||||
- Prüfe, dass `-----BEGIN` und `-----END` Zeilen enthalten sind
|
||||
|
||||
### Registry Login Fehler
|
||||
- Prüfe, ob `REGISTRY_USER` und `REGISTRY_PASSWORD` korrekt sind
|
||||
- Teste Registry-Erreichbarkeit:
|
||||
```bash
|
||||
curl -u admin:registry-secure-password-2025 http://127.0.0.1:5000/v2/_catalog
|
||||
```
|
||||
741
deployment/legacy/gitea-workflows/README.md
Normal file
741
deployment/legacy/gitea-workflows/README.md
Normal file
@@ -0,0 +1,741 @@
|
||||
# Gitea CI/CD Workflows Documentation
|
||||
|
||||
Comprehensive guide for the automated deployment workflows using Gitea Actions.
|
||||
|
||||
## Overview
|
||||
|
||||
This project uses Gitea Actions for automated deployments to staging and production environments. The workflows are designed for:
|
||||
|
||||
- **Zero-downtime deployments** via rolling updates
|
||||
- **Automatic rollback** on deployment failures
|
||||
- **Environment-specific configurations** using Docker Compose overlays
|
||||
- **Database protection** with automated backups (production)
|
||||
- **Comprehensive health checks** and smoke tests
|
||||
- **Deployment audit trail** via persistent logs
|
||||
|
||||
## Workflow Files
|
||||
|
||||
### 1. Staging Deployment (`deploy-staging.yml`)
|
||||
|
||||
**Purpose:** Automated deployment to staging environment for testing and validation.
|
||||
|
||||
**Triggers:**
|
||||
- Push to `staging` branch
|
||||
- Manual workflow dispatch
|
||||
|
||||
**Target:** `https://staging.michaelschiemer.de`
|
||||
|
||||
**Key Features:**
|
||||
- Fast deployment cycle (30-second health check)
|
||||
- Basic health verification
|
||||
- Keeps 5 deployment backups
|
||||
- No database backup (non-critical environment)
|
||||
|
||||
**Workflow Steps:**
|
||||
1. Build Docker image with `ENV=staging`
|
||||
2. Push to private registry (localhost:5000)
|
||||
3. Deploy to staging server via SSH
|
||||
4. Basic health checks
|
||||
5. Automatic rollback on failure
|
||||
|
||||
---
|
||||
|
||||
### 2. Production Deployment (`deploy-production.yml`)
|
||||
|
||||
**Purpose:** Production deployment with enhanced safety features and verification.
|
||||
|
||||
**Triggers:**
|
||||
- Push to `main` or `production` branches
|
||||
- Manual workflow dispatch
|
||||
|
||||
**Target:** `https://michaelschiemer.de`
|
||||
|
||||
**Key Features:**
|
||||
- **Database backup** before deployment (aborts on backup failure)
|
||||
- **Database migrations** after container startup
|
||||
- **Extended health checks** (60-second wait, multiple verification layers)
|
||||
- **Smoke tests** for functional verification
|
||||
- **Deployment logging** for audit trail
|
||||
- **Graceful shutdown** for active request handling
|
||||
- Keeps 10 deployment backups
|
||||
|
||||
**Workflow Steps:**
|
||||
1. Build Docker image with `ENV=production`
|
||||
2. Push to private registry
|
||||
3. Create database backup (optional via `skip_backup` input)
|
||||
4. Gracefully stop current containers
|
||||
5. Deploy new containers
|
||||
6. Run database migrations
|
||||
7. Extended health verification
|
||||
8. Smoke tests (main page + API)
|
||||
9. Automatic rollback on failure
|
||||
10. Log deployment outcome
|
||||
11. Clean up build artifacts
|
||||
|
||||
---
|
||||
|
||||
## Required Gitea Secrets
|
||||
|
||||
Configure these secrets in your Gitea repository settings (`Settings` → `Secrets`).
|
||||
|
||||
### Staging Secrets
|
||||
|
||||
| Secret Name | Description | Example Value |
|
||||
|-------------|-------------|---------------|
|
||||
| `STAGING_HOST` | Staging server hostname or IP | `staging.example.com` or `203.0.113.42` |
|
||||
| `STAGING_USER` | SSH username for staging server | `deploy` or `www-data` |
|
||||
| `STAGING_SSH_KEY` | Private SSH key (PEM format) | `-----BEGIN RSA PRIVATE KEY-----...` |
|
||||
| `STAGING_SSH_PORT` | SSH port (optional, defaults to 22) | `22` or `2222` |
|
||||
|
||||
### Production Secrets
|
||||
|
||||
| Secret Name | Description | Example Value |
|
||||
|-------------|-------------|---------------|
|
||||
| `PRODUCTION_HOST` | Production server hostname or IP | `michaelschiemer.de` or `198.51.100.10` |
|
||||
| `PRODUCTION_USER` | SSH username for production server | `deploy` or `www-data` |
|
||||
| `PRODUCTION_SSH_KEY` | Private SSH key (PEM format) | `-----BEGIN RSA PRIVATE KEY-----...` |
|
||||
| `PRODUCTION_SSH_PORT` | SSH port (optional, defaults to 22) | `22` or `2222` |
|
||||
|
||||
**SSH Key Generation:**
|
||||
```bash
|
||||
# Generate SSH key pair (on your local machine)
|
||||
ssh-keygen -t rsa -b 4096 -f deployment_key -C "gitea-deployment"
|
||||
|
||||
# Copy public key to target server
|
||||
ssh-copy-id -i deployment_key.pub deploy@server.example.com
|
||||
|
||||
# Add private key to Gitea secrets (entire content)
|
||||
cat deployment_key
|
||||
```
|
||||
|
||||
**Security Best Practices:**
|
||||
- Use dedicated deployment user with minimal permissions
|
||||
- Restrict SSH key to specific commands via `authorized_keys` options
|
||||
- Rotate SSH keys regularly (quarterly recommended)
|
||||
- Never commit SSH keys to repository
|
||||
|
||||
---
|
||||
|
||||
## Manual Workflow Triggering
|
||||
|
||||
### Via Gitea UI
|
||||
|
||||
1. Navigate to your repository
|
||||
2. Click `Actions` tab
|
||||
3. Select the workflow (`Deploy to Staging` or `Deploy to Production`)
|
||||
4. Click `Run workflow`
|
||||
5. Choose branch
|
||||
6. Set input parameters (if applicable)
|
||||
7. Click `Run workflow`
|
||||
|
||||
### Via Git Push
|
||||
|
||||
**Staging Deployment:**
|
||||
```bash
|
||||
# Push to staging branch
|
||||
git checkout staging
|
||||
git merge develop
|
||||
git push origin staging
|
||||
# Workflow triggers automatically
|
||||
```
|
||||
|
||||
**Production Deployment:**
|
||||
```bash
|
||||
# Push to main/production branch
|
||||
git checkout main
|
||||
git merge staging
|
||||
git push origin main
|
||||
# Workflow triggers automatically
|
||||
```
|
||||
|
||||
### Workflow Input Parameters
|
||||
|
||||
**Production Workflow:**
|
||||
- `force_rebuild`: Force rebuild Docker image even if code hasn't changed (default: `false`)
|
||||
- `skip_backup`: Skip database backup step - **NOT RECOMMENDED** (default: `false`)
|
||||
|
||||
**Use Case for `skip_backup`:**
|
||||
Emergency hotfix deployment when backup would cause unacceptable delay. Only use if:
|
||||
- Recent backup exists
|
||||
- Issue is critical (security vulnerability, production down)
|
||||
- Backup failure is blocking deployment
|
||||
|
||||
---
|
||||
|
||||
## Deployment Monitoring
|
||||
|
||||
### Real-Time Monitoring
|
||||
|
||||
**Via Gitea UI:**
|
||||
1. Navigate to `Actions` tab
|
||||
2. Click on running workflow
|
||||
3. View real-time logs for each step
|
||||
4. Check for errors or warnings
|
||||
|
||||
**Via Server Logs:**
|
||||
```bash
|
||||
# SSH to target server
|
||||
ssh deploy@server.example.com
|
||||
|
||||
# Staging logs
|
||||
tail -f /opt/framework-staging/current/storage/logs/app.log
|
||||
|
||||
# Production logs
|
||||
tail -f /opt/framework-production/current/storage/logs/app.log
|
||||
|
||||
# Deployment log (production only)
|
||||
tail -f /opt/framework-production/deployment.log
|
||||
```
|
||||
|
||||
### Deployment Status Verification
|
||||
|
||||
**Check Container Status:**
|
||||
```bash
|
||||
# Staging
|
||||
cd /opt/framework-staging/current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml ps
|
||||
|
||||
# Production
|
||||
cd /opt/framework-production/current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
```
|
||||
|
||||
**Health Check Endpoints:**
|
||||
```bash
|
||||
# Staging
|
||||
curl -k https://staging.michaelschiemer.de/health
|
||||
|
||||
# Production
|
||||
curl -k https://michaelschiemer.de/health
|
||||
curl -k https://michaelschiemer.de/api/health
|
||||
```
|
||||
|
||||
**Expected Health Response:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"timestamp": "2025-01-28T15:30:00Z",
|
||||
"version": "2.x",
|
||||
"services": {
|
||||
"database": "connected",
|
||||
"redis": "connected",
|
||||
"queue": "running"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback Procedures
|
||||
|
||||
### Automatic Rollback
|
||||
|
||||
Both workflows include automatic rollback on deployment failure:
|
||||
|
||||
**Trigger Conditions:**
|
||||
- Build failure
|
||||
- Health check failure
|
||||
- Smoke test failure (production)
|
||||
- Database migration failure (production)
|
||||
|
||||
**Rollback Process:**
|
||||
1. Stop failed deployment containers
|
||||
2. Restore most recent backup deployment
|
||||
3. Start restored containers
|
||||
4. Verify rollback success
|
||||
5. Log rollback event
|
||||
|
||||
**Note:** Automatic rollback restores the application, but **database changes are NOT rolled back automatically**. See Manual Database Rollback below.
|
||||
|
||||
---
|
||||
|
||||
### Manual Rollback
|
||||
|
||||
**When to use:**
|
||||
- Issue discovered after successful deployment
|
||||
- Need to rollback to specific version (not just previous)
|
||||
|
||||
#### Application Rollback
|
||||
|
||||
**Staging:**
|
||||
```bash
|
||||
ssh deploy@staging.example.com
|
||||
|
||||
cd /opt/framework-staging
|
||||
|
||||
# List available backups
|
||||
ls -lt backup_*
|
||||
|
||||
# Stop current deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml down
|
||||
cd ..
|
||||
|
||||
# Restore specific backup
|
||||
rm -rf current
|
||||
cp -r backup_20250128_143000 current
|
||||
|
||||
# Start restored deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml up -d
|
||||
|
||||
# Verify
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml ps
|
||||
curl -k https://staging.michaelschiemer.de/health
|
||||
```
|
||||
|
||||
**Production:**
|
||||
```bash
|
||||
ssh deploy@michaelschiemer.de
|
||||
|
||||
cd /opt/framework-production
|
||||
|
||||
# List available backups
|
||||
ls -lt backup_*
|
||||
|
||||
# Stop current deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml down
|
||||
cd ..
|
||||
|
||||
# Restore specific backup
|
||||
rm -rf current
|
||||
cp -r backup_20250128_150000 current
|
||||
|
||||
# Start restored deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# Wait for services
|
||||
sleep 30
|
||||
|
||||
# Verify
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
curl -k https://michaelschiemer.de/health
|
||||
curl -k https://michaelschiemer.de/api/health
|
||||
```
|
||||
|
||||
#### Database Rollback (Production Only)
|
||||
|
||||
**CRITICAL:** Database rollback is a destructive operation. Only perform if:
|
||||
- You have confirmed backup from before problematic deployment
|
||||
- You understand data loss implications
|
||||
- Issue cannot be fixed forward
|
||||
|
||||
**Process:**
|
||||
```bash
|
||||
ssh deploy@michaelschiemer.de
|
||||
|
||||
cd /opt/framework-production/current
|
||||
|
||||
# List available database backups
|
||||
ls -lt storage/backups/backup_*.sql
|
||||
|
||||
# Verify backup integrity
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app \
|
||||
php console.php db:verify-backup --file=storage/backups/backup_20250128_150000.sql
|
||||
|
||||
# Stop application to prevent new writes
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml stop production-app
|
||||
|
||||
# Restore database from backup
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app \
|
||||
php console.php db:restore --file=storage/backups/backup_20250128_150000.sql --force
|
||||
|
||||
# Start application
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml start production-app
|
||||
|
||||
# Verify
|
||||
curl -k https://michaelschiemer.de/health
|
||||
```
|
||||
|
||||
**Database Rollback Best Practices:**
|
||||
- Always create new backup before rollback
|
||||
- Document rollback reason in deployment log
|
||||
- Notify team immediately
|
||||
- Review application logs for data consistency issues
|
||||
- Consider rolling forward with fix instead
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### 1. Workflow Fails: "Permission denied (publickey)"
|
||||
|
||||
**Cause:** SSH authentication failed
|
||||
|
||||
**Solutions:**
|
||||
- Verify SSH key is correctly added to Gitea secrets (entire key content)
|
||||
- Ensure public key is in `~/.ssh/authorized_keys` on target server
|
||||
- Check SSH key permissions on server (`chmod 600 ~/.ssh/authorized_keys`)
|
||||
- Test SSH connection manually: `ssh -i deployment_key deploy@server.example.com`
|
||||
|
||||
#### 2. Health Check Fails
|
||||
|
||||
**Staging:**
|
||||
```bash
|
||||
# Check container status
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml ps
|
||||
|
||||
# Check logs
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml logs staging-app
|
||||
|
||||
# Check PHP-FPM
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml exec staging-app php -v
|
||||
```
|
||||
|
||||
**Production:**
|
||||
```bash
|
||||
# Extended diagnostics
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml logs production-app
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app php -v
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app pgrep php-fpm
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-redis redis-cli ping
|
||||
```
|
||||
|
||||
#### 3. Database Backup Fails (Production)
|
||||
|
||||
**Symptoms:**
|
||||
- Workflow aborts at step [0/6]
|
||||
- Error: "Database backup failed - deployment aborted"
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check database connection
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
php console.php db:status
|
||||
|
||||
# Check backup directory permissions
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
ls -la storage/backups/
|
||||
|
||||
# Fix permissions if needed
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
chown -R www-data:www-data storage/backups/
|
||||
|
||||
# Test backup manually
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
php console.php db:backup --output=storage/backups/test_backup.sql
|
||||
```
|
||||
|
||||
#### 4. Database Migration Fails (Production)
|
||||
|
||||
**Symptoms:**
|
||||
- Workflow fails at step [5/6]
|
||||
- Error: "Database migration failed"
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check migration status
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
php console.php db:status
|
||||
|
||||
# Review migration logs
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml logs production-app | grep migration
|
||||
|
||||
# Run migration manually with verbose output
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
php console.php db:migrate --force --verbose
|
||||
|
||||
# If migration is stuck, rollback and retry
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
php console.php db:rollback 1
|
||||
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
php console.php db:migrate --force
|
||||
```
|
||||
|
||||
#### 5. Image Push Fails
|
||||
|
||||
**Symptoms:**
|
||||
- Workflow fails at "Push image to private registry"
|
||||
- Error: "connection refused" or "unauthorized"
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check registry is accessible from CI runner
|
||||
curl http://localhost:5000/v2/_catalog
|
||||
|
||||
# Verify registry authentication (if configured)
|
||||
docker login localhost:5000
|
||||
|
||||
# Check registry container is running
|
||||
docker ps | grep registry
|
||||
```
|
||||
|
||||
#### 6. Smoke Tests Fail (Production)
|
||||
|
||||
**Symptoms:**
|
||||
- Health checks pass but smoke tests fail
|
||||
- Error: "Main page failed" or "API health check failed"
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Test endpoints manually
|
||||
curl -v -k https://michaelschiemer.de/
|
||||
curl -v -k https://michaelschiemer.de/api/health
|
||||
|
||||
# Check Traefik routing
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml logs production-nginx
|
||||
|
||||
# Check application logs
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml logs production-app | tail -100
|
||||
|
||||
# Verify Traefik labels
|
||||
docker inspect production-nginx | grep -A 20 Labels
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Staging vs Production Differences
|
||||
|
||||
Comprehensive comparison of workflow behaviors.
|
||||
|
||||
| Feature | Staging | Production |
|
||||
|---------|---------|------------|
|
||||
| **Trigger Branches** | `staging` | `main`, `production` |
|
||||
| **Image Tag** | `staging` | `latest` |
|
||||
| **Deployment Directory** | `/opt/framework-staging/` | `/opt/framework-production/` |
|
||||
| **Database Backup** | ❌ No | ✅ Yes (with abort on failure) |
|
||||
| **Database Migrations** | ❌ Manual | ✅ Automatic |
|
||||
| **Health Check Wait** | 30 seconds | 60 seconds |
|
||||
| **Health Checks** | Basic (container status, PHP version, HTTP via nginx) | Extended (+ PHP-FPM, Traefik, Redis) |
|
||||
| **Smoke Tests** | ❌ No | ✅ Yes (main page + API) |
|
||||
| **Backup Retention** | 5 backups | 10 backups |
|
||||
| **Container Shutdown** | `docker-compose down` (immediate) | `docker-compose stop` (graceful) |
|
||||
| **Deployment Logging** | ❌ No | ✅ Yes (deployment.log) |
|
||||
| **Build Artifact Cleanup** | ❌ No | ✅ Yes |
|
||||
| **Target URL** | https://staging.michaelschiemer.de | https://michaelschiemer.de |
|
||||
| **Manual Inputs** | `force_rebuild` | `force_rebuild`, `skip_backup` |
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Development Workflow
|
||||
|
||||
**Recommended Branch Flow:**
|
||||
```
|
||||
develop → staging → main (production)
|
||||
```
|
||||
|
||||
**Process:**
|
||||
1. Develop features on feature branches
|
||||
2. Merge to `develop` branch
|
||||
3. When ready for testing: `git merge develop` → `staging`
|
||||
4. Deploy to staging automatically
|
||||
5. Test on staging environment
|
||||
6. If tests pass: `git merge staging` → `main`
|
||||
7. Deploy to production automatically
|
||||
|
||||
### Pre-Deployment Checklist
|
||||
|
||||
**Staging:**
|
||||
- [ ] All tests pass locally
|
||||
- [ ] Code reviewed and approved
|
||||
- [ ] No breaking changes without migration path
|
||||
- [ ] Dependencies updated in composer.json/package.json
|
||||
|
||||
**Production:**
|
||||
- [ ] Tested on staging environment
|
||||
- [ ] Database migrations tested on staging
|
||||
- [ ] Performance impact assessed
|
||||
- [ ] Rollback plan documented
|
||||
- [ ] Team notified of deployment window
|
||||
- [ ] Recent database backup verified
|
||||
- [ ] Monitoring alerts configured
|
||||
|
||||
### Post-Deployment Verification
|
||||
|
||||
**Staging:**
|
||||
```bash
|
||||
# Basic checks
|
||||
curl -k https://staging.michaelschiemer.de/health
|
||||
curl -k https://staging.michaelschiemer.de/api/health
|
||||
|
||||
# Manual testing of new features
|
||||
```
|
||||
|
||||
**Production:**
|
||||
```bash
|
||||
# Automated checks (from CI workflow)
|
||||
curl -k https://michaelschiemer.de/
|
||||
curl -k https://michaelschiemer.de/api/health
|
||||
|
||||
# Manual verification
|
||||
# - Test critical user flows
|
||||
# - Check analytics/monitoring dashboards
|
||||
# - Review error logs
|
||||
# - Verify database migrations applied
|
||||
|
||||
# Check deployment log
|
||||
ssh deploy@michaelschiemer.de tail /opt/framework-production/deployment.log
|
||||
```
|
||||
|
||||
### Deployment Scheduling
|
||||
|
||||
**Staging:** Deploy anytime during business hours
|
||||
|
||||
**Production:**
|
||||
- **Preferred Window:** Off-peak hours (e.g., 2-6 AM local time)
|
||||
- **Emergency Deployments:** Anytime (use `skip_backup` if necessary)
|
||||
- **Major Releases:** Schedule during maintenance window with advance notice
|
||||
|
||||
---
|
||||
|
||||
## Emergency Procedures
|
||||
|
||||
### Production Down - Complete Outage
|
||||
|
||||
**Immediate Response:**
|
||||
```bash
|
||||
# 1. Check container status
|
||||
ssh deploy@michaelschiemer.de
|
||||
cd /opt/framework-production/current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
|
||||
# 2. If containers stopped, restart
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# 3. If restart fails, rollback
|
||||
cd /opt/framework-production
|
||||
rm -rf current
|
||||
cp -r $(ls -dt backup_* | head -n1) current
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# 4. Verify recovery
|
||||
curl -k https://michaelschiemer.de/health
|
||||
|
||||
# 5. Investigate root cause
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml logs production-app
|
||||
```
|
||||
|
||||
### Database Corruption
|
||||
|
||||
**Recovery Steps:**
|
||||
```bash
|
||||
# 1. Stop application immediately
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml stop production-app
|
||||
|
||||
# 2. Verify most recent backup
|
||||
ls -lt /opt/framework-production/current/storage/backups/
|
||||
|
||||
# 3. Restore from backup (see Database Rollback section)
|
||||
|
||||
# 4. Verify data integrity
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec production-app \
|
||||
php console.php db:verify-integrity
|
||||
|
||||
# 5. Restart application
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml start production-app
|
||||
```
|
||||
|
||||
### Failed Deployment with No Rollback
|
||||
|
||||
**If automatic rollback fails:**
|
||||
```bash
|
||||
# 1. SSH to server
|
||||
ssh deploy@michaelschiemer.de
|
||||
|
||||
# 2. Manual rollback (see Manual Rollback section)
|
||||
|
||||
# 3. If rollback unavailable, emergency restore
|
||||
cd /opt/framework-production
|
||||
git clone https://git.michaelschiemer.de/michael/framework.git emergency-deploy
|
||||
cd emergency-deploy
|
||||
git checkout <last-known-good-commit>
|
||||
|
||||
# 4. Build and deploy manually
|
||||
docker build -f docker/php/Dockerfile -t localhost:5000/framework:emergency .
|
||||
docker push localhost:5000/framework:emergency
|
||||
|
||||
# 5. Update docker-compose.prod.yml to use emergency tag
|
||||
cd /opt/framework-production/current
|
||||
# Edit docker-compose.prod.yml: image: localhost:5000/framework:emergency
|
||||
|
||||
# 6. Deploy
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring and Alerting
|
||||
|
||||
### Recommended Monitoring
|
||||
|
||||
**Application Metrics:**
|
||||
- Response time (target: <200ms p95)
|
||||
- Error rate (target: <0.1%)
|
||||
- Request throughput
|
||||
- Queue depth
|
||||
|
||||
**Infrastructure Metrics:**
|
||||
- Container health status
|
||||
- CPU usage (target: <70%)
|
||||
- Memory usage (target: <80%)
|
||||
- Disk space (alert: <20% free)
|
||||
|
||||
**Database Metrics:**
|
||||
- Query performance
|
||||
- Connection pool utilization
|
||||
- Replication lag (if applicable)
|
||||
- Backup success rate
|
||||
|
||||
### Alert Configuration
|
||||
|
||||
**Critical Alerts (immediate notification):**
|
||||
- Production deployment failed
|
||||
- Automatic rollback triggered
|
||||
- Health check failure (3 consecutive)
|
||||
- Database backup failure
|
||||
- Container restart loop
|
||||
|
||||
**Warning Alerts (review within 1 hour):**
|
||||
- Staging deployment failed
|
||||
- Smoke test failure
|
||||
- Slow health check response (>5s)
|
||||
- Disk space <30%
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Main Documentation:** `deployment/NEW_ARCHITECTURE.md`
|
||||
- **Architecture Analysis:** `deployment/legacy/ARCHITECTURE_ANALYSIS.md`
|
||||
- **Docker Compose Files:** Root directory (`docker-compose.*.yml`)
|
||||
- **Framework Documentation:** `docs/` directory
|
||||
- **Troubleshooting Guide:** `docs/guides/troubleshooting.md`
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Regular Tasks
|
||||
|
||||
**Weekly:**
|
||||
- Review deployment logs
|
||||
- Check backup retention
|
||||
- Verify health check reliability
|
||||
- Update dependencies (staging first)
|
||||
|
||||
**Monthly:**
|
||||
- Rotate SSH keys
|
||||
- Review and clean old backups (>30 days)
|
||||
- Test rollback procedures
|
||||
- Update workflow documentation
|
||||
|
||||
**Quarterly:**
|
||||
- Disaster recovery drill
|
||||
- Performance baseline review
|
||||
- Security audit of deployment process
|
||||
- Workflow optimization review
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-01-28
|
||||
**Workflow Version:** 1.0
|
||||
**Maintained by:** DevOps Team
|
||||
30
deployment/legacy/gitea-workflows/REGISTRY_INFO.md
Normal file
30
deployment/legacy/gitea-workflows/REGISTRY_INFO.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Docker Registry Information
|
||||
|
||||
## Registry Details
|
||||
|
||||
- **URL**: `registry.michaelschiemer.de` (intern) oder `registry.michaelschiemer.de` (via Traefik)
|
||||
- **Standard Credentials**:
|
||||
- **Username**: `admin`
|
||||
- **Password**: `registry-secure-password-2025`
|
||||
|
||||
⚠️ **WICHTIG**: Das Passwort sollte in Produktion geändert werden!
|
||||
|
||||
## Für Gitea Secrets
|
||||
|
||||
Verwende folgende Werte in den Gitea Repository Secrets:
|
||||
|
||||
- **REGISTRY_USER**: `admin`
|
||||
- **REGISTRY_PASSWORD**: `registry-secure-password-2025` (oder das aktuell gesetzte Passwort)
|
||||
|
||||
## Registry Test
|
||||
|
||||
```bash
|
||||
# Login testen
|
||||
echo "registry-secure-password-2025" | docker login registry.michaelschiemer.de -u admin --password-stdin
|
||||
|
||||
# Images auflisten
|
||||
curl -u admin:registry-secure-password-2025 http://registry.michaelschiemer.de/v2/_catalog
|
||||
|
||||
# Oder via Traefik (HTTPS)
|
||||
curl -u admin:registry-secure-password-2025 https://registry.michaelschiemer.de/v2/_catalog
|
||||
```
|
||||
86
deployment/legacy/gitea-workflows/SECRETS_SETUP.md
Normal file
86
deployment/legacy/gitea-workflows/SECRETS_SETUP.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Gitea Repository Secrets Setup
|
||||
|
||||
## Erforderliche Secrets
|
||||
|
||||
Diese Secrets müssen in Gitea konfiguriert werden unter:
|
||||
**Repository → Settings → Secrets**
|
||||
|
||||
### 1. REGISTRY_USER
|
||||
- **Beschreibung**: Benutzername für Docker Registry Login
|
||||
- **Typ**: String
|
||||
- **Wert**: Standardmäßig `admin` oder der Benutzername für die Registry
|
||||
- **Verwendung**: Docker Registry Authentication beim Image Push
|
||||
|
||||
### 2. REGISTRY_PASSWORD
|
||||
- **Beschreibung**: Passwort für Docker Registry Login
|
||||
- **Typ**: Password (versteckt)
|
||||
- **Wert**: Das Passwort für die Docker Registry auf `registry.michaelschiemer.de`
|
||||
- **Verwendung**: Docker Registry Authentication beim Image Push
|
||||
|
||||
### 3. SSH_PRIVATE_KEY
|
||||
- **Beschreibung**: SSH Private Key für Zugriff auf Production Server
|
||||
- **Typ**: SSH Key (versteckt)
|
||||
- **Wert**: Der komplette Inhalt der SSH-Private-Key-Datei (~/.ssh/production)
|
||||
- **Verwendung**: SSH-Verbindung zum Production-Server für Ansible Deployment
|
||||
|
||||
## Setup-Anleitung
|
||||
|
||||
### Schritt 1: SSH Key erstellen/exportieren
|
||||
|
||||
```bash
|
||||
# Falls noch nicht vorhanden, SSH Key für Production erstellen
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/production -C "gitea-ci-cd"
|
||||
|
||||
# SSH Key Inhalt anzeigen (für Copy-Paste)
|
||||
cat ~/.ssh/production
|
||||
```
|
||||
|
||||
**⚠️ Wichtig**: Der komplette Inhalt der Datei (inkl. `-----BEGIN OPENSSH PRIVATE KEY-----` und `-----END OPENSSH PRIVATE KEY-----`) muss in das Secret eingefügt werden.
|
||||
|
||||
### Schritt 2: Docker Registry Credentials prüfen
|
||||
|
||||
Die Registry läuft auf dem Production-Server. Prüfe die Credentials:
|
||||
|
||||
```bash
|
||||
# SSH zum Production-Server
|
||||
ssh deploy@94.16.110.151
|
||||
|
||||
# Prüfe, ob Registry läuft
|
||||
docker ps | grep registry
|
||||
|
||||
# Prüfe Registry-Konfiguration (falls vorhanden)
|
||||
cat ~/deployment/stacks/registry/docker-compose.yml 2>/dev/null || echo "Registry Config nicht gefunden"
|
||||
```
|
||||
|
||||
**Hinweis**: Falls die Registry noch nicht konfiguriert ist, müssen die Credentials festgelegt werden.
|
||||
|
||||
### Schritt 3: Secrets in Gitea hinzufügen
|
||||
|
||||
1. Gehe zu: `https://git.michaelschiemer.de/<username>/michaelschiemer/settings/secrets`
|
||||
2. Klicke auf **"Add Secret"**
|
||||
3. Füge jedes Secret einzeln hinzu:
|
||||
|
||||
**REGISTRY_USER**:
|
||||
- Name: `REGISTRY_USER`
|
||||
- Value: `admin` (oder der tatsächliche Registry-Benutzername)
|
||||
- Save
|
||||
|
||||
**REGISTRY_PASSWORD**:
|
||||
- Name: `REGISTRY_PASSWORD`
|
||||
- Value: `<registry-password>`
|
||||
- Save
|
||||
|
||||
**SSH_PRIVATE_KEY**:
|
||||
- Name: `SSH_PRIVATE_KEY`
|
||||
- Value: `<kompletter-inhalt-von-~/.ssh/production>`
|
||||
- Save
|
||||
|
||||
### Schritt 4: Secrets verifizieren
|
||||
|
||||
Nach dem Hinzufügen sollten alle drei Secrets in der Liste erscheinen mit "Hidden" als Wert.
|
||||
|
||||
**✅ Checkliste**:
|
||||
- [ ] REGISTRY_USER hinzugefügt
|
||||
- [ ] REGISTRY_PASSWORD hinzugefügt
|
||||
- [ ] SSH_PRIVATE_KEY hinzugefügt
|
||||
- [ ] Alle Secrets zeigen "Hidden" als Wert
|
||||
481
deployment/legacy/gitea-workflows/SECRETS_SETUP_GUIDE.md
Normal file
481
deployment/legacy/gitea-workflows/SECRETS_SETUP_GUIDE.md
Normal file
@@ -0,0 +1,481 @@
|
||||
# Gitea Secrets Configuration Guide
|
||||
|
||||
**Purpose:** Step-by-step guide to configure all required secrets for staging and production deployments.
|
||||
|
||||
**Prerequisites:**
|
||||
- Admin access to Gitea repository
|
||||
- SSH access to staging and production servers
|
||||
- OpenSSH installed locally
|
||||
|
||||
---
|
||||
|
||||
## Quick Start Checklist
|
||||
|
||||
- [ ] Generate SSH key pair
|
||||
- [ ] Distribute public key to target servers
|
||||
- [ ] Configure staging secrets in Gitea
|
||||
- [ ] Configure production secrets in Gitea
|
||||
- [ ] Test SSH connection manually
|
||||
- [ ] Verify secrets are accessible to workflows
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Generate SSH Keys
|
||||
|
||||
### Option A: Generate New Deployment Keys (Recommended)
|
||||
|
||||
```bash
|
||||
# Navigate to project root
|
||||
cd /home/michael/dev/michaelschiemer
|
||||
|
||||
# Create SSH keys directory
|
||||
mkdir -p .gitea/ssh-keys
|
||||
cd .gitea/ssh-keys
|
||||
|
||||
# Generate staging key
|
||||
ssh-keygen -t rsa -b 4096 -f gitea-staging-deploy \
|
||||
-C "gitea-staging-deployment" -N ""
|
||||
|
||||
# Generate production key
|
||||
ssh-keygen -t rsa -b 4096 -f gitea-production-deploy \
|
||||
-C "gitea-production-deployment" -N ""
|
||||
|
||||
# Verify keys created
|
||||
ls -la
|
||||
# Expected output:
|
||||
# gitea-staging-deploy (private key)
|
||||
# gitea-staging-deploy.pub (public key)
|
||||
# gitea-production-deploy (private key)
|
||||
# gitea-production-deploy.pub (public key)
|
||||
```
|
||||
|
||||
**⚠️ Security Note:**
|
||||
- Private keys should NEVER be committed to git
|
||||
- Add `.gitea/ssh-keys/` to `.gitignore` if not already present
|
||||
- Store private keys securely (e.g., password manager, Vault)
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Distribute Public Keys to Servers
|
||||
|
||||
### Staging Server Setup
|
||||
|
||||
```bash
|
||||
# Copy public key to staging server
|
||||
ssh-copy-id -i .gitea/ssh-keys/gitea-staging-deploy.pub deploy@YOUR_STAGING_HOST
|
||||
|
||||
# Or manually (if ssh-copy-id not available):
|
||||
cat .gitea/ssh-keys/gitea-staging-deploy.pub | \
|
||||
ssh deploy@YOUR_STAGING_HOST "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
|
||||
|
||||
# Set proper permissions on server
|
||||
ssh deploy@YOUR_STAGING_HOST "chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"
|
||||
|
||||
# Test connection
|
||||
ssh -i .gitea/ssh-keys/gitea-staging-deploy deploy@YOUR_STAGING_HOST "echo 'Staging SSH connection successful'"
|
||||
```
|
||||
|
||||
### Production Server Setup
|
||||
|
||||
```bash
|
||||
# Copy public key to production server
|
||||
ssh-copy-id -i .gitea/ssh-keys/gitea-production-deploy.pub deploy@YOUR_PRODUCTION_HOST
|
||||
|
||||
# Or manually:
|
||||
cat .gitea/ssh-keys/gitea-production-deploy.pub | \
|
||||
ssh deploy@YOUR_PRODUCTION_HOST "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
|
||||
|
||||
# Set proper permissions
|
||||
ssh deploy@YOUR_PRODUCTION_HOST "chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"
|
||||
|
||||
# Test connection
|
||||
ssh -i .gitea/ssh-keys/gitea-production-deploy deploy@YOUR_PRODUCTION_HOST "echo 'Production SSH connection successful'"
|
||||
```
|
||||
|
||||
**Deployment User Requirements:**
|
||||
- User must exist on target server (e.g., `deploy`, `www-data`, `ubuntu`)
|
||||
- User must have sudo privileges for Docker commands
|
||||
- User must have write access to deployment directories:
|
||||
- Staging: `/opt/framework-staging/`
|
||||
- Production: `/opt/framework-production/`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Prepare Secret Values
|
||||
|
||||
### Extract Private Key Content
|
||||
|
||||
```bash
|
||||
cd /home/michael/dev/michaelschiemer/.gitea/ssh-keys
|
||||
|
||||
# Display staging private key (copy entire output)
|
||||
echo "=== STAGING_SSH_KEY ==="
|
||||
cat gitea-staging-deploy
|
||||
echo ""
|
||||
|
||||
# Display production private key (copy entire output)
|
||||
echo "=== PRODUCTION_SSH_KEY ==="
|
||||
cat gitea-production-deploy
|
||||
echo ""
|
||||
```
|
||||
|
||||
**Important:** Copy the **entire key content** including:
|
||||
```
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
[key content here]
|
||||
-----END RSA PRIVATE KEY-----
|
||||
```
|
||||
|
||||
### Determine Server Details
|
||||
|
||||
**Staging Server:**
|
||||
```bash
|
||||
# Get staging hostname/IP (replace with your actual server)
|
||||
STAGING_HOST="staging.michaelschiemer.de" # or IP: 203.0.113.42
|
||||
|
||||
# Get SSH port (default: 22)
|
||||
STAGING_SSH_PORT="22"
|
||||
|
||||
# Get deployment user
|
||||
STAGING_USER="deploy"
|
||||
```
|
||||
|
||||
**Production Server:**
|
||||
```bash
|
||||
# Get production hostname/IP
|
||||
PRODUCTION_HOST="michaelschiemer.de" # or IP: 198.51.100.10
|
||||
|
||||
# Get SSH port (default: 22)
|
||||
PRODUCTION_SSH_PORT="22"
|
||||
|
||||
# Get deployment user
|
||||
PRODUCTION_USER="deploy"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Configure Secrets in Gitea
|
||||
|
||||
### Access Gitea Secrets Configuration
|
||||
|
||||
1. **Navigate to Repository:**
|
||||
- Open Gitea web interface
|
||||
- Go to your framework repository
|
||||
|
||||
2. **Access Secrets Settings:**
|
||||
- Click `Settings` (⚙️ icon)
|
||||
- Click `Secrets` in left sidebar
|
||||
- Or direct URL: `https://git.michaelschiemer.de/michael/framework/settings/secrets`
|
||||
|
||||
3. **Add New Secret:**
|
||||
- Click `Add Secret` button
|
||||
- Fill in `Name` and `Value` fields
|
||||
- Click `Add Secret` to save
|
||||
|
||||
---
|
||||
|
||||
### Staging Secrets Configuration
|
||||
|
||||
**Secret 1: STAGING_HOST**
|
||||
- **Name:** `STAGING_HOST`
|
||||
- **Value:** `staging.michaelschiemer.de` (or your staging server hostname/IP)
|
||||
- **Description:** Staging server hostname or IP address
|
||||
|
||||
**Secret 2: STAGING_USER**
|
||||
- **Name:** `STAGING_USER`
|
||||
- **Value:** `deploy` (or your deployment user)
|
||||
- **Description:** SSH username for staging deployments
|
||||
|
||||
**Secret 3: STAGING_SSH_KEY**
|
||||
- **Name:** `STAGING_SSH_KEY`
|
||||
- **Value:** [Paste entire content of `gitea-staging-deploy` private key]
|
||||
- **Description:** Private SSH key for staging authentication
|
||||
- **⚠️ Important:** Include `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` lines
|
||||
|
||||
**Secret 4: STAGING_SSH_PORT**
|
||||
- **Name:** `STAGING_SSH_PORT`
|
||||
- **Value:** `22` (or your custom SSH port)
|
||||
- **Description:** SSH port for staging server (optional, defaults to 22)
|
||||
|
||||
---
|
||||
|
||||
### Production Secrets Configuration
|
||||
|
||||
**Secret 5: PRODUCTION_HOST**
|
||||
- **Name:** `PRODUCTION_HOST`
|
||||
- **Value:** `michaelschiemer.de` (or your production server hostname/IP)
|
||||
- **Description:** Production server hostname or IP address
|
||||
|
||||
**Secret 6: PRODUCTION_USER**
|
||||
- **Name:** `PRODUCTION_USER`
|
||||
- **Value:** `deploy` (or your deployment user)
|
||||
- **Description:** SSH username for production deployments
|
||||
|
||||
**Secret 7: PRODUCTION_SSH_KEY**
|
||||
- **Name:** `PRODUCTION_SSH_KEY`
|
||||
- **Value:** [Paste entire content of `gitea-production-deploy` private key]
|
||||
- **Description:** Private SSH key for production authentication
|
||||
- **⚠️ Important:** Include `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` lines
|
||||
|
||||
**Secret 8: PRODUCTION_SSH_PORT**
|
||||
- **Name:** `PRODUCTION_SSH_PORT`
|
||||
- **Value:** `22` (or your custom SSH port)
|
||||
- **Description:** SSH port for production server (optional, defaults to 22)
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Verify Secrets Configuration
|
||||
|
||||
### Via Gitea UI
|
||||
|
||||
1. Navigate to `Settings` → `Secrets`
|
||||
2. Verify all 8 secrets are listed:
|
||||
- ✅ STAGING_HOST
|
||||
- ✅ STAGING_USER
|
||||
- ✅ STAGING_SSH_KEY
|
||||
- ✅ STAGING_SSH_PORT
|
||||
- ✅ PRODUCTION_HOST
|
||||
- ✅ PRODUCTION_USER
|
||||
- ✅ PRODUCTION_SSH_KEY
|
||||
- ✅ PRODUCTION_SSH_PORT
|
||||
|
||||
3. Check that secrets show "Last Updated" timestamp
|
||||
|
||||
**Note:** Secret values are masked in the UI for security (you cannot view them after saving).
|
||||
|
||||
---
|
||||
|
||||
### Manual SSH Connection Test
|
||||
|
||||
Test SSH connections using the same credentials that workflows will use:
|
||||
|
||||
```bash
|
||||
cd /home/michael/dev/michaelschiemer/.gitea/ssh-keys
|
||||
|
||||
# Test staging connection
|
||||
ssh -i gitea-staging-deploy deploy@YOUR_STAGING_HOST \
|
||||
"echo 'Staging SSH test successful'; docker --version"
|
||||
|
||||
# Test production connection
|
||||
ssh -i gitea-production-deploy deploy@YOUR_PRODUCTION_HOST \
|
||||
"echo 'Production SSH test successful'; docker --version"
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
```
|
||||
Staging SSH test successful
|
||||
Docker version 24.0.7, build afdd53b
|
||||
```
|
||||
|
||||
**If Connection Fails:**
|
||||
- Verify hostname/IP is correct
|
||||
- Check SSH port (try specifying: `ssh -p 2222 ...`)
|
||||
- Verify public key is in `~/.ssh/authorized_keys` on server
|
||||
- Check server firewall allows SSH connections
|
||||
- Verify user has Docker permissions: `ssh user@host "docker ps"`
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Test Workflow Access to Secrets
|
||||
|
||||
### Trigger Test Workflow
|
||||
|
||||
Create a minimal test workflow to verify secrets are accessible:
|
||||
|
||||
**File:** `.gitea/workflows/test-secrets.yml`
|
||||
```yaml
|
||||
name: Test Secrets Configuration
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test-secrets:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Test staging secrets
|
||||
run: |
|
||||
echo "Testing staging secrets..."
|
||||
echo "STAGING_HOST: ${{ secrets.STAGING_HOST }}"
|
||||
echo "STAGING_USER: ${{ secrets.STAGING_USER }}"
|
||||
echo "STAGING_SSH_PORT: ${{ secrets.STAGING_SSH_PORT }}"
|
||||
echo "STAGING_SSH_KEY length: ${#STAGING_SSH_KEY}"
|
||||
env:
|
||||
STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
||||
|
||||
- name: Test production secrets
|
||||
run: |
|
||||
echo "Testing production secrets..."
|
||||
echo "PRODUCTION_HOST: ${{ secrets.PRODUCTION_HOST }}"
|
||||
echo "PRODUCTION_USER: ${{ secrets.PRODUCTION_USER }}"
|
||||
echo "PRODUCTION_SSH_PORT: ${{ secrets.PRODUCTION_SSH_PORT }}"
|
||||
echo "PRODUCTION_SSH_KEY length: ${#PRODUCTION_SSH_KEY}"
|
||||
env:
|
||||
PRODUCTION_SSH_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
|
||||
- name: Test SSH connection to staging
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.STAGING_HOST }}
|
||||
username: ${{ secrets.STAGING_USER }}
|
||||
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
||||
script: |
|
||||
echo "Staging SSH connection successful"
|
||||
docker --version
|
||||
docker-compose --version
|
||||
|
||||
- name: Test SSH connection to production
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
|
||||
script: |
|
||||
echo "Production SSH connection successful"
|
||||
docker --version
|
||||
docker-compose --version
|
||||
```
|
||||
|
||||
**Run Test:**
|
||||
1. Commit and push test workflow: `git add .gitea/workflows/test-secrets.yml && git commit -m "Add secrets test workflow" && git push`
|
||||
2. Go to Gitea Actions tab
|
||||
3. Select "Test Secrets Configuration" workflow
|
||||
4. Click "Run workflow"
|
||||
5. Monitor execution logs
|
||||
|
||||
**Expected Result:**
|
||||
- All secrets should be accessible
|
||||
- SSH connections should succeed
|
||||
- Docker/docker-compose versions should be displayed
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### SSH Key Management
|
||||
|
||||
**✅ Do:**
|
||||
- Use 4096-bit RSA keys (or Ed25519)
|
||||
- Generate separate keys for staging and production
|
||||
- Rotate keys quarterly
|
||||
- Store private keys in secure password manager
|
||||
- Use dedicated deployment user (not root)
|
||||
- Restrict deployment user permissions
|
||||
|
||||
**❌ Don't:**
|
||||
- Commit private keys to git
|
||||
- Share keys between environments
|
||||
- Use personal SSH keys for deployments
|
||||
- Store keys in plain text files
|
||||
- Reuse keys across projects
|
||||
|
||||
---
|
||||
|
||||
### Secret Rotation Schedule
|
||||
|
||||
**Quarterly (Every 3 Months):**
|
||||
1. Generate new SSH key pairs
|
||||
2. Add new public keys to servers (keep old keys active)
|
||||
3. Update Gitea secrets with new private keys
|
||||
4. Test deployments with new keys
|
||||
5. Remove old public keys from servers
|
||||
6. Delete old private keys securely
|
||||
|
||||
**Template for Rotation:**
|
||||
```bash
|
||||
# Generate new keys with date suffix
|
||||
ssh-keygen -t rsa -b 4096 -f gitea-staging-deploy-2025-04 \
|
||||
-C "gitea-staging-deployment-2025-04" -N ""
|
||||
|
||||
# Repeat process from Step 2 onwards
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Access Control
|
||||
|
||||
**Gitea Repository Permissions:**
|
||||
- Limit "Secrets" access to repository admins only
|
||||
- Require 2FA for admin accounts
|
||||
- Audit secret access logs regularly
|
||||
|
||||
**Server Access Control:**
|
||||
- Deployment user should have minimal required permissions
|
||||
- Use sudo configuration for Docker commands only
|
||||
- Monitor SSH access logs
|
||||
- Implement IP whitelisting if possible
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Secret Not Found in Workflow
|
||||
|
||||
**Symptoms:**
|
||||
- Workflow fails with "secret not found" error
|
||||
- Secret value is empty in workflow logs
|
||||
|
||||
**Solutions:**
|
||||
1. Verify secret name matches exactly (case-sensitive)
|
||||
2. Check secret is created at repository level (not organization or user level)
|
||||
3. Ensure workflow has access to repository secrets
|
||||
4. Try re-creating the secret
|
||||
|
||||
---
|
||||
|
||||
### Issue: SSH Authentication Failed
|
||||
|
||||
**Symptoms:**
|
||||
- Workflow fails: "Permission denied (publickey)"
|
||||
- Cannot connect to server via SSH
|
||||
|
||||
**Solutions:**
|
||||
1. Verify entire private key content is in secret (including BEGIN/END lines)
|
||||
2. Check public key is in `~/.ssh/authorized_keys` on server
|
||||
3. Verify SSH key format (PEM vs OpenSSH format)
|
||||
4. Check server SSH configuration allows public key authentication
|
||||
5. Test manual connection: `ssh -i key-file user@host`
|
||||
|
||||
---
|
||||
|
||||
### Issue: Docker Permission Denied
|
||||
|
||||
**Symptoms:**
|
||||
- SSH connection succeeds but Docker commands fail
|
||||
- Error: "permission denied while trying to connect to Docker daemon"
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Add deployment user to docker group (on server)
|
||||
ssh deploy@server
|
||||
sudo usermod -aG docker deploy
|
||||
|
||||
# Log out and back in for group changes to take effect
|
||||
exit
|
||||
ssh deploy@server
|
||||
|
||||
# Verify Docker access
|
||||
docker ps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After secrets are configured and tested:
|
||||
|
||||
1. ✅ Secrets configured in Gitea
|
||||
2. ⏳ Test staging deployment workflow
|
||||
3. ⏳ Test production deployment workflow
|
||||
4. ⏳ Update main deployment documentation
|
||||
5. ⏳ Set up monitoring and alerting
|
||||
|
||||
**Continue to:** `Testing Deployment Workflows` section in main README.md
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-01-28
|
||||
**Guide Version:** 1.0
|
||||
**Security Review Date:** 2025-01-28
|
||||
2
deployment/legacy/gitea-workflows/TEST.md
Normal file
2
deployment/legacy/gitea-workflows/TEST.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Workflow Test - Fri Oct 31 09:29:43 PM CET 2025
|
||||
# Workflow Test 2 - Fri Oct 31 09:33:19 PM CET 2025
|
||||
132
deployment/legacy/gitea-workflows/TEST_WORKFLOW.md
Normal file
132
deployment/legacy/gitea-workflows/TEST_WORKFLOW.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# CI/CD Workflow Testen
|
||||
|
||||
## ✅ Secrets sind konfiguriert - Perfekt!
|
||||
|
||||
## Option 1: Workflow manuell triggern (Empfohlen)
|
||||
|
||||
### Schritt 1: Gehe zu Actions
|
||||
|
||||
Öffne im Browser:
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer renewal/actions
|
||||
```
|
||||
|
||||
### Schritt 2: Workflow auswählen
|
||||
|
||||
1. Suche nach "Production Deployment Pipeline"
|
||||
2. Klicke auf den Workflow
|
||||
|
||||
### Schritt 3: Workflow manuell starten
|
||||
|
||||
1. Klicke auf "Run workflow" (rechts oben)
|
||||
2. Wähle Branch: `main`
|
||||
3. Optional: `skip_tests` deaktiviert lassen (Tests sollen laufen)
|
||||
4. Klicke "Run workflow"
|
||||
|
||||
### Schritt 4: Logs beobachten
|
||||
|
||||
Der Workflow führt folgende Schritte aus:
|
||||
|
||||
1. **Checkout code** - Code wird ausgecheckt
|
||||
2. **Run Tests** - PHP Tests werden ausgeführt
|
||||
3. **Build Docker Image** - Docker Image wird gebaut
|
||||
4. **Push to Registry** - Image wird zur Registry gepusht
|
||||
5. **Deploy via Ansible** - Deployment auf Production-Server
|
||||
|
||||
**Beobachte die Logs** und prüfe jeden Schritt!
|
||||
|
||||
## Option 2: Automatisches Deployment via Commit
|
||||
|
||||
### Test-Commit pushen
|
||||
|
||||
```bash
|
||||
# Stelle sicher, dass alles committed ist
|
||||
git add .
|
||||
git commit -m "test: CI/CD workflow test" || echo "Keine Änderungen"
|
||||
|
||||
# Push zu main Branch
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Der Workflow startet automatisch nach dem Push.
|
||||
|
||||
## Was passiert beim Workflow?
|
||||
|
||||
### 1. Tests (ca. 2-5 Minuten)
|
||||
- PHP Version Setup
|
||||
- Composer Dependencies installieren
|
||||
- Tests ausführen
|
||||
|
||||
### 2. Build (ca. 3-5 Minuten)
|
||||
Live - Multi-Stage Docker Build:
|
||||
- Composer Dependencies (Production)
|
||||
- Frontend Build (npm)
|
||||
- Finales Production Image
|
||||
|
||||
### 3. Push (ca. 1-2 Minuten)
|
||||
- Docker Login zur Registry
|
||||
- Image Tag generieren (SHA + Timestamp)
|
||||
- Image zur Registry pushen
|
||||
|
||||
### 4. Deploy (ca. 2-4 Minuten)
|
||||
- Ansible Playbook ausführen
|
||||
- Image auf Production-Server pullen
|
||||
- Application Stack aktualisieren
|
||||
- Services neu starten
|
||||
|
||||
**Gesamtzeit: ~8-15 Minuten**
|
||||
|
||||
## Workflow-Status prüfen
|
||||
|
||||
### In Gitea UI:
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer/actions
|
||||
```
|
||||
|
||||
### Via Command Line:
|
||||
```bash
|
||||
# Prüfe ob Workflow läuft
|
||||
curl -s -H "Authorization: token <DEIN_TOKEN>" \
|
||||
"https://git.michaelschiemer.de/api/v1/repos/michael/michaelschiemer/actions/runs" | \
|
||||
jq '.workflow_runs[0] | {status, conclusion, created_at}'
|
||||
```
|
||||
|
||||
## Erfolgreiche Ausführung erkennen
|
||||
|
||||
Der Workflow ist erfolgreich, wenn:
|
||||
|
||||
✅ Alle Jobs grün sind
|
||||
✅ Keine Fehler in den Logs
|
||||
✅ Letzter Schritt "Deploy via Ansible" erfolgreich
|
||||
✅ Application läuft auf Production-Server
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Workflow startet nicht
|
||||
- Prüfe, ob `.gitea/workflows/production-deploy.yml` im Repository ist
|
||||
- Prüfe, ob Workflow-Syntax korrekt ist
|
||||
- Prüfe Gitea Actions ist aktiviert
|
||||
|
||||
### "Secret not found" Fehler
|
||||
- Prüfe, ob alle drei Secrets gesetzt sind
|
||||
- Prüfe, ob Namen exakt übereinstimmen (Groß-/Kleinschreibung!)
|
||||
|
||||
### Registry Login fehlgeschlagen
|
||||
- Prüfe `REGISTRY_USER` und `REGISTRY_PASSWORD` Secrets
|
||||
- Prüfe Registry erreichbar: `curl -u admin:registry-secure-password-2025 http://127.0.0.1:5000/v2/_catalog`
|
||||
|
||||
### Deployment fehlgeschlagen
|
||||
- Prüfe `SSH_PRIVATE_KEY` Secret
|
||||
- Prüfe Ansible-Verbindung zum Server
|
||||
- Prüfe Server-Logs: `ssh deploy@94.16.110.151 "docker compose -f ~/deployment/stacks/application/docker-compose.yml logs"`
|
||||
|
||||
## Nächste Schritte nach erfolgreichem Test
|
||||
|
||||
1. ✅ Workflow funktioniert
|
||||
2. ✅ Automatisches Deployment getestet
|
||||
3. ✅ Production-Stack läuft
|
||||
|
||||
**Du kannst jetzt:**
|
||||
- Normale Commits pushen → Automatisches Deployment
|
||||
- Workflow manuell triggern für kontrollierte Deployments
|
||||
- Branch-Protection aktivieren für sichere Deployments
|
||||
75
deployment/legacy/gitea-workflows/TOKEN_ISSUE_FIX.md
Normal file
75
deployment/legacy/gitea-workflows/TOKEN_ISSUE_FIX.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Token-Probleme beheben
|
||||
|
||||
## Problem
|
||||
|
||||
Das Token hat nicht die richtigen Scopes oder das Repository wurde nicht gefunden.
|
||||
|
||||
## Lösung: Neuen Token mit richtigen Scopes erstellen
|
||||
|
||||
### Schritt 1: Token neu generieren
|
||||
|
||||
1. **Gehe zu Gitea Settings:**
|
||||
```
|
||||
https://git.michaelschiemer.de/user/settings/applications
|
||||
```
|
||||
|
||||
2. **Falls bereits ein Token existiert:**
|
||||
- Lösche den alten Token (falls nötig)
|
||||
- Oder erstelle einen neuen mit anderen Namen
|
||||
|
||||
3. **Klicke "Generate New Token"**
|
||||
|
||||
4. **WICHTIG - Diese Scopes aktivieren:**
|
||||
- ✅ `read:user` (mindestens erforderlich)
|
||||
- ✅ `write:repository` (für Secrets schreiben)
|
||||
- ✅ Oder wähle **alle Scopes** für volle Berechtigung
|
||||
|
||||
5. **Token kopieren** (wird nur einmal angezeigt!)
|
||||
|
||||
### Schritt 2: Repository-Name prüfen
|
||||
|
||||
Prüfe, ob das Repository wirklich `michael/michaelschiemer` heißt:
|
||||
|
||||
```bash
|
||||
# Prüfe Remote-URL
|
||||
git remote get-url origin
|
||||
|
||||
# Sollte zeigen:
|
||||
# https://git.michaelschiemer.de/michael/michaelschiemer.git
|
||||
```
|
||||
|
||||
Falls der Name anders ist, setze die Umgebungsvariable:
|
||||
```bash
|
||||
REPO_OWNER=<owner> REPO_NAME=<name> bash scripts/setup-gitea-secrets-with-token.sh <token>
|
||||
```
|
||||
|
||||
### Schritt 3: Script erneut ausführen
|
||||
|
||||
```bash
|
||||
bash scripts/setup-gitea-secrets-with-token.sh <NEUER_TOKEN>
|
||||
```
|
||||
|
||||
## Alternative: Manuelles Setup über UI
|
||||
|
||||
Falls das automatische Setup weiterhin Probleme macht:
|
||||
|
||||
1. **Gehe zu:**
|
||||
```
|
||||
https://git.michaelschiemer.de/michael/michaelschiemer/settings/secrets/actions
|
||||
```
|
||||
|
||||
2. **Füge manuell hinzu:**
|
||||
- `REGISTRY_USER` = `admin`
|
||||
- `REGISTRY_PASSWORD` = `registry-secure-password-2025`
|
||||
- `SSH_PRIVATE_KEY` = `cat ~/.ssh/production`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "token does not have at least one of required scope(s)"
|
||||
→ Token benötigt `read:user` Scope - neuen Token mit diesem Scope generieren
|
||||
|
||||
### "The target couldn't be found" (404)
|
||||
→ Repository existiert nicht oder falscher Name - prüfe Repository-URL
|
||||
|
||||
### "404 page not found" bei Secrets-Endpoint
|
||||
→ Actions möglicherweise nicht aktiviert - prüfe in Gitea Admin-Panel
|
||||
1335
deployment/legacy/gitea-workflows/build-image.yml
Normal file
1335
deployment/legacy/gitea-workflows/build-image.yml
Normal file
File diff suppressed because it is too large
Load Diff
256
deployment/legacy/gitea-workflows/cache-warm.yml
Normal file
256
deployment/legacy/gitea-workflows/cache-warm.yml
Normal file
@@ -0,0 +1,256 @@
|
||||
name: 🧊 Warm Docker Build Cache
|
||||
|
||||
run-name: Warm Cache - ${{ inputs.branch || 'main' }}
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 4 * * 0'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch to use for warming caches'
|
||||
required: false
|
||||
default: 'main'
|
||||
|
||||
env:
|
||||
REGISTRY: registry.michaelschiemer.de
|
||||
IMAGE_NAME: framework
|
||||
RUNTIME_IMAGE_NAME: framework-runtime
|
||||
|
||||
jobs:
|
||||
warm:
|
||||
name: Refresh Buildx Caches
|
||||
runs-on: docker-build
|
||||
steps:
|
||||
- name: Install git and tooling
|
||||
shell: sh
|
||||
run: |
|
||||
if ! command -v bash >/dev/null 2>&1 || ! command -v git >/dev/null 2>&1; then
|
||||
apk add --no-cache git bash curl
|
||||
fi
|
||||
bash --version
|
||||
git --version
|
||||
|
||||
- name: Determine target ref
|
||||
id: target
|
||||
shell: bash
|
||||
run: |
|
||||
TARGET="${{ inputs.branch }}"
|
||||
if [ -z "$TARGET" ]; then
|
||||
TARGET="main"
|
||||
fi
|
||||
echo "target_ref=$TARGET" >> "$GITHUB_OUTPUT"
|
||||
echo "TARGET_REF=$TARGET" >> $GITHUB_ENV
|
||||
echo "BRANCH_NAME=$TARGET" >> $GITHUB_ENV
|
||||
|
||||
- name: Download CI helpers
|
||||
shell: bash
|
||||
env:
|
||||
CI_TOKEN: ${{ secrets.CI_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REF="${{ steps.target.outputs.target_ref }}"
|
||||
URL="https://git.michaelschiemer.de/${{ github.repository }}/raw/${REF}/scripts/ci/clone_repo.sh"
|
||||
mkdir -p /tmp/ci-tools
|
||||
if [ -n "$CI_TOKEN" ]; then
|
||||
curl -sfL -u "$CI_TOKEN:x-oauth-basic" "$URL" -o /tmp/ci-tools/clone_repo.sh
|
||||
else
|
||||
curl -sfL "$URL" -o /tmp/ci-tools/clone_repo.sh
|
||||
fi
|
||||
chmod +x /tmp/ci-tools/clone_repo.sh
|
||||
|
||||
- name: Checkout repository
|
||||
shell: bash
|
||||
env:
|
||||
TARGET_REF: ${{ steps.target.outputs.target_ref }}
|
||||
run: |
|
||||
export CI_REPOSITORY="${{ github.repository }}"
|
||||
export CI_TOKEN="${{ secrets.CI_TOKEN }}"
|
||||
export CI_REF_NAME="$TARGET_REF"
|
||||
export CI_INPUT_BRANCH="$TARGET_REF"
|
||||
export CI_DEFAULT_BRANCH="main"
|
||||
export CI_TARGET_DIR="/workspace/repo"
|
||||
export CI_FETCH_DEPTH="1"
|
||||
/tmp/ci-tools/clone_repo.sh
|
||||
cd /workspace/repo
|
||||
git rev-parse HEAD
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
shell: bash
|
||||
run: |
|
||||
docker buildx version || echo "Buildx nicht gefunden"
|
||||
if ! docker buildx ls 2>/dev/null | grep -q builder; then
|
||||
docker buildx create --name builder --use --driver docker-container
|
||||
else
|
||||
docker buildx use builder
|
||||
fi
|
||||
docker buildx inspect --bootstrap
|
||||
|
||||
- name: Login to registry
|
||||
id: login
|
||||
shell: bash
|
||||
run: |
|
||||
REGISTRY_USER="${{ secrets.REGISTRY_USER }}"
|
||||
REGISTRY_PASSWORD="${{ secrets.REGISTRY_PASSWORD }}"
|
||||
REGISTRY_URL="${{ env.REGISTRY }}"
|
||||
DEPLOYMENT_HOST="94.16.110.151"
|
||||
|
||||
if [ -z "$REGISTRY_USER" ] || [ -z "$REGISTRY_PASSWORD" ]; then
|
||||
echo "❌ Error: Registry credentials missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔐 Logging in to registry..."
|
||||
|
||||
HOST_IP=$(ip route | grep default | awk '{print $3}' 2>/dev/null | head -1 || echo "$DEPLOYMENT_HOST")
|
||||
|
||||
REGISTRY_URLS=(
|
||||
"registry.michaelschiemer.de"
|
||||
"$REGISTRY_URL"
|
||||
"$DEPLOYMENT_HOST"
|
||||
"$DEPLOYMENT_HOST:5000"
|
||||
"${HOST_IP}:5000"
|
||||
)
|
||||
|
||||
LOGIN_SUCCESS=false
|
||||
|
||||
for TEST_URL in "${REGISTRY_URLS[@]}"; do
|
||||
echo "🔍 Testing registry: $TEST_URL"
|
||||
|
||||
if [[ "$TEST_URL" == *":5000" ]]; then
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://$TEST_URL/v2/" 2>&1 || echo "000")
|
||||
|
||||
if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "200" ]; then
|
||||
set +e
|
||||
LOGIN_OUTPUT=$(echo "$REGISTRY_PASSWORD" | docker login "$TEST_URL" -u "$REGISTRY_USER" --password-stdin 2>&1)
|
||||
LOGIN_EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ $LOGIN_EXIT_CODE -eq 0 ]; then
|
||||
REGISTRY_URL="$TEST_URL"
|
||||
LOGIN_SUCCESS=true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
else
|
||||
HTTPS_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" "https://$TEST_URL/v2/" 2>&1 || echo "000")
|
||||
|
||||
if [ "$HTTPS_CODE" = "401" ] || [ "$HTTPS_CODE" = "200" ]; then
|
||||
set +e
|
||||
LOGIN_OUTPUT=$(echo "$REGISTRY_PASSWORD" | docker login "$TEST_URL" -u "$REGISTRY_USER" --password-stdin 2>&1)
|
||||
LOGIN_EXIT_CODE=$?
|
||||
set -e
|
||||
|
||||
if [ $LOGIN_EXIT_CODE -eq 0 ]; then
|
||||
REGISTRY_URL="$TEST_URL"
|
||||
LOGIN_SUCCESS=true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$LOGIN_SUCCESS" = false ]; then
|
||||
echo "❌ Registry login failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Registry login successful: $REGISTRY_URL"
|
||||
echo "REGISTRY_URL=$REGISTRY_URL" >> $GITHUB_ENV
|
||||
echo "CACHE_REGISTRY=${{ env.REGISTRY }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Warm runtime base cache
|
||||
shell: bash
|
||||
env:
|
||||
REGISTRY_URL: ${{ env.REGISTRY_URL }}
|
||||
CACHE_REGISTRY: ${{ env.CACHE_REGISTRY }}
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
|
||||
TARGET_REGISTRY="$CACHE_REGISTRY"
|
||||
if [ -z "$TARGET_REGISTRY" ]; then
|
||||
TARGET_REGISTRY="$REGISTRY_URL"
|
||||
fi
|
||||
|
||||
IMAGE_NAME="${{ env.RUNTIME_IMAGE_NAME }}"
|
||||
DATE_TAG="warm-$(date -u +%Y%m%d%H%M)"
|
||||
BRANCH_NAME="${{ env.BRANCH_NAME || 'main' }}"
|
||||
|
||||
# Build cache sources - multiple sources for better cache hit rate
|
||||
CACHE_SOURCES=(
|
||||
"type=registry,ref=${TARGET_REGISTRY}/${IMAGE_NAME}:buildcache"
|
||||
"type=registry,ref=${TARGET_REGISTRY}/${IMAGE_NAME}:${BRANCH_NAME}-cache"
|
||||
"type=registry,ref=${TARGET_REGISTRY}/${IMAGE_NAME}:latest"
|
||||
)
|
||||
|
||||
CACHE_FROM_ARGS=""
|
||||
for CACHE_SRC in "${CACHE_SOURCES[@]}"; do
|
||||
CACHE_FROM_ARGS="${CACHE_FROM_ARGS} --cache-from ${CACHE_SRC}"
|
||||
done
|
||||
|
||||
docker buildx build \
|
||||
--platform linux/amd64 \
|
||||
--file ./Dockerfile.production \
|
||||
--target runtime-base \
|
||||
--build-arg RUNTIME_IMAGE=runtime-base \
|
||||
${CACHE_FROM_ARGS} \
|
||||
--cache-to type=registry,ref="${TARGET_REGISTRY}/${IMAGE_NAME}:buildcache",mode=max \
|
||||
--cache-to type=registry,ref="${TARGET_REGISTRY}/${IMAGE_NAME}:${BRANCH_NAME}-cache",mode=max \
|
||||
--tag "$TARGET_REGISTRY/$IMAGE_NAME:$DATE_TAG" \
|
||||
--push \
|
||||
.
|
||||
|
||||
- name: Warm production cache
|
||||
shell: bash
|
||||
env:
|
||||
REGISTRY_URL: ${{ env.REGISTRY_URL }}
|
||||
CACHE_REGISTRY: ${{ env.CACHE_REGISTRY }}
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
|
||||
REGISTRY_TO_USE="$REGISTRY_URL"
|
||||
CACHE_TARGET="$CACHE_REGISTRY"
|
||||
if [ -z "$CACHE_TARGET" ]; then
|
||||
CACHE_TARGET="$REGISTRY_TO_USE"
|
||||
fi
|
||||
|
||||
IMAGE_NAME="${{ env.IMAGE_NAME }}"
|
||||
DATE_TAG="warm-$(date -u +%Y%m%d%H%M)"
|
||||
BRANCH_NAME="${{ env.BRANCH_NAME || 'main' }}"
|
||||
|
||||
DEFAULT_RUNTIME="$CACHE_TARGET/${{ env.RUNTIME_IMAGE_NAME }}:latest"
|
||||
RUNTIME_ARG="runtime-base"
|
||||
if docker pull "$DEFAULT_RUNTIME" >/dev/null 2>&1; then
|
||||
RUNTIME_ARG="$DEFAULT_RUNTIME"
|
||||
fi
|
||||
|
||||
# Build cache sources - multiple sources for better cache hit rate
|
||||
CACHE_SOURCES=(
|
||||
"type=registry,ref=${CACHE_TARGET}/${IMAGE_NAME}:buildcache"
|
||||
"type=registry,ref=${REGISTRY_TO_USE}/${IMAGE_NAME}:${BRANCH_NAME}-cache"
|
||||
"type=registry,ref=${REGISTRY_TO_USE}/${IMAGE_NAME}:latest"
|
||||
)
|
||||
|
||||
CACHE_FROM_ARGS=""
|
||||
for CACHE_SRC in "${CACHE_SOURCES[@]}"; do
|
||||
CACHE_FROM_ARGS="${CACHE_FROM_ARGS} --cache-from ${CACHE_SRC}"
|
||||
done
|
||||
|
||||
docker buildx build \
|
||||
--platform linux/amd64 \
|
||||
--file ./Dockerfile.production \
|
||||
--build-arg RUNTIME_IMAGE="$RUNTIME_ARG" \
|
||||
${CACHE_FROM_ARGS} \
|
||||
--cache-to type=registry,ref="${CACHE_TARGET}/${IMAGE_NAME}:buildcache",mode=max \
|
||||
--cache-to type=registry,ref="${REGISTRY_TO_USE}/${IMAGE_NAME}:${BRANCH_NAME}-cache",mode=max \
|
||||
--tag "$REGISTRY_TO_USE/$IMAGE_NAME:$DATE_TAG" \
|
||||
--push \
|
||||
.
|
||||
|
||||
- name: Cleanup warm tags
|
||||
shell: bash
|
||||
env:
|
||||
REGISTRY_URL: ${{ env.REGISTRY_URL }}
|
||||
CACHE_REGISTRY: ${{ env.CACHE_REGISTRY }}
|
||||
run: |
|
||||
echo "ℹ️ Cleanup of warm tags deferred to registry retention policy"
|
||||
133
deployment/legacy/gitea-workflows/ci.yml
Normal file
133
deployment/legacy/gitea-workflows/ci.yml
Normal file
@@ -0,0 +1,133 @@
|
||||
name: ✅ Continuous Integration
|
||||
|
||||
run-name: CI Checks - ${{ github.ref_name || github.head_ref }}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
- staging
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- staging
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Run Tests & Quality Checks
|
||||
runs-on: php-ci
|
||||
steps:
|
||||
- name: Download CI helpers
|
||||
shell: bash
|
||||
env:
|
||||
CI_TOKEN: ${{ secrets.CI_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REF="${{ github.sha }}"
|
||||
if [ -z "$REF" ]; then
|
||||
REF="${{ github.ref_name }}"
|
||||
fi
|
||||
if [ -z "$REF" ]; then
|
||||
REF="${{ github.head_ref }}"
|
||||
fi
|
||||
if [ -z "$REF" ]; then
|
||||
REF="${{ github.base_ref || 'develop' }}"
|
||||
fi
|
||||
URL="https://git.michaelschiemer.de/${{ github.repository }}/raw/${REF}/scripts/ci/clone_repo.sh"
|
||||
mkdir -p /tmp/ci-tools
|
||||
if [ -n "$CI_TOKEN" ]; then
|
||||
curl -sfL -u "$CI_TOKEN:x-oauth-basic" "$URL" -o /tmp/ci-tools/clone_repo.sh
|
||||
else
|
||||
curl -sfL "$URL" -o /tmp/ci-tools/clone_repo.sh
|
||||
fi
|
||||
chmod +x /tmp/ci-tools/clone_repo.sh
|
||||
|
||||
- name: Checkout code
|
||||
env:
|
||||
REF_NAME_GITHUB: ${{ github.ref_name }}
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
run: |
|
||||
REF_NAME="$REF_NAME_GITHUB"
|
||||
if [ -z "$REF_NAME" ]; then
|
||||
REF_NAME="$HEAD_REF"
|
||||
fi
|
||||
if [ -z "$REF_NAME" ]; then
|
||||
REF_NAME="$BASE_REF"
|
||||
fi
|
||||
if [ -z "$REF_NAME" ]; then
|
||||
REF_NAME="develop"
|
||||
fi
|
||||
|
||||
export CI_REPOSITORY="${{ github.repository }}"
|
||||
export CI_TOKEN="${{ secrets.CI_TOKEN }}"
|
||||
export CI_REF_NAME="$REF_NAME"
|
||||
export CI_INPUT_BRANCH="$HEAD_REF"
|
||||
export CI_DEFAULT_BRANCH="develop"
|
||||
export CI_TARGET_DIR="/workspace/repo"
|
||||
export CI_FETCH_DEPTH="1"
|
||||
|
||||
/tmp/ci-tools/clone_repo.sh
|
||||
|
||||
cd /workspace/repo
|
||||
|
||||
# Note: Composer caching via actions/cache@v4 requires Node.js in runner
|
||||
# Skipped for now - vendor/ is cached in runner workspace
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
composer install --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-req=php
|
||||
|
||||
- name: PHPStan (baseline)
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
make phpstan || echo "⚠️ phpstan skipped/failed"
|
||||
|
||||
- name: Lint PHP (dry run)
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
make cs || echo "⚠️ php-cs-fixer dry run issues detected"
|
||||
|
||||
- name: Validate .env.base for secrets
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
if [ -f .env.base ]; then
|
||||
echo "🔍 Checking .env.base for secrets..."
|
||||
# Check for potential secrets (case-insensitive)
|
||||
if grep -qiE "(password|secret|key|token|encryption|vault)" .env.base | grep -v "^#" | grep -v "FILE=" | grep -v "^$$" > /dev/null; then
|
||||
echo "::error::.env.base contains potential secrets! Secrets should be in .env.local or Docker Secrets."
|
||||
echo "⚠️ Found potential secrets in .env.base:"
|
||||
grep -iE "(password|secret|key|token|encryption|vault)" .env.base | grep -v "^#" | grep -v "FILE=" | grep -v "^$$" || true
|
||||
echo ""
|
||||
echo "💡 Move secrets to:"
|
||||
echo " - .env.local (for local development)"
|
||||
echo " - Docker Secrets (for production/staging)"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ .env.base does not contain secrets"
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ .env.base not found (optional during migration)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🔍 Checking docker-compose.base.yml for hardcoded passwords..."
|
||||
if grep -E "(PASSWORD|SECRET|TOKEN).*:-[^}]*[^}]}" docker-compose.base.yml 2>/dev/null | grep -v "^#" | grep -v "FILE=" > /dev/null; then
|
||||
echo "::error::docker-compose.base.yml contains hardcoded password fallbacks! Passwords must be set explicitly."
|
||||
echo "⚠️ Found hardcoded password fallbacks:"
|
||||
grep -E "(PASSWORD|SECRET|TOKEN).*:-[^}]*[^}]}" docker-compose.base.yml | grep -v "^#" | grep -v "FILE=" || true
|
||||
echo ""
|
||||
echo "💡 Remove fallback values (:-...) from base file"
|
||||
echo " Passwords must be set in .env.local or via Docker Secrets"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ docker-compose.base.yml does not contain hardcoded password fallbacks"
|
||||
fi
|
||||
|
||||
- name: Tests temporarily skipped
|
||||
run: |
|
||||
echo "⚠️ Tests temporarily skipped due to PHP 8.5 compatibility issues"
|
||||
312
deployment/legacy/gitea-workflows/deploy-production.yml
Normal file
312
deployment/legacy/gitea-workflows/deploy-production.yml
Normal file
@@ -0,0 +1,312 @@
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- production
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_rebuild:
|
||||
description: 'Force rebuild Docker image'
|
||||
required: false
|
||||
default: 'false'
|
||||
skip_backup:
|
||||
description: 'Skip database backup (not recommended)'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
env:
|
||||
REGISTRY: localhost:5000
|
||||
IMAGE_NAME: framework
|
||||
IMAGE_TAG: latest
|
||||
COMPOSE_PROJECT_NAME: framework-production
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
echo "Building Docker image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}"
|
||||
docker build \
|
||||
--file docker/php/Dockerfile \
|
||||
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
|
||||
--build-arg ENV=production \
|
||||
--build-arg COMPOSER_INSTALL_FLAGS="--no-dev --optimize-autoloader --no-interaction" \
|
||||
.
|
||||
|
||||
- name: Push image to private registry
|
||||
run: |
|
||||
echo "Pushing image to registry..."
|
||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Prepare deployment files
|
||||
run: |
|
||||
echo "Preparing deployment files..."
|
||||
mkdir -p deployment-production
|
||||
cp docker-compose.base.yml deployment-production/
|
||||
cp docker-compose.prod.yml deployment-production/
|
||||
cp -r docker deployment-production/
|
||||
|
||||
# Create deployment script
|
||||
cat > deployment-production/deploy.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=================================================="
|
||||
echo "Starting Production Deployment"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# Database backup (unless explicitly skipped)
|
||||
if [ "${SKIP_BACKUP}" != "true" ]; then
|
||||
echo "[0/6] Creating database backup..."
|
||||
BACKUP_FILE="backup_$(date +%Y%m%d_%H%M%S).sql"
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app \
|
||||
php console.php db:backup --output="/var/www/html/storage/backups/${BACKUP_FILE}" || {
|
||||
echo "⚠️ Database backup failed - deployment aborted"
|
||||
exit 1
|
||||
}
|
||||
echo "✅ Database backup created: ${BACKUP_FILE}"
|
||||
else
|
||||
echo "⚠️ Database backup skipped (not recommended for production)"
|
||||
fi
|
||||
|
||||
# Pull latest images
|
||||
echo "[1/6] Pulling latest Docker images..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml pull
|
||||
|
||||
# Stop existing containers gracefully
|
||||
echo "[2/6] Stopping existing containers (graceful shutdown)..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml stop
|
||||
|
||||
# Start new containers
|
||||
echo "[3/6] Starting new containers..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# Wait for services to be healthy (longer timeout for production)
|
||||
echo "[4/6] Waiting for services to be healthy..."
|
||||
sleep 30
|
||||
|
||||
# Run database migrations
|
||||
echo "[5/6] Running database migrations..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app \
|
||||
php console.php db:migrate --force || {
|
||||
echo "⚠️ Database migration failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Verify deployment
|
||||
echo "[6/6] Verifying deployment..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
|
||||
# Cleanup old containers
|
||||
echo "Cleaning up old containers..."
|
||||
docker system prune -f
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo "Production Deployment Complete"
|
||||
echo "=================================================="
|
||||
EOF
|
||||
|
||||
chmod +x deployment-production/deploy.sh
|
||||
|
||||
- name: Deploy to production server
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
|
||||
script: |
|
||||
# Create deployment directory
|
||||
mkdir -p /opt/framework-production
|
||||
cd /opt/framework-production
|
||||
|
||||
# Backup current deployment
|
||||
if [ -d "current" ]; then
|
||||
echo "Backing up current deployment..."
|
||||
timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
mv current "backup_${timestamp}"
|
||||
# Keep only last 10 backups for production
|
||||
ls -dt backup_* | tail -n +11 | xargs rm -rf
|
||||
fi
|
||||
|
||||
# Create new deployment directory
|
||||
mkdir -p current
|
||||
cd current
|
||||
|
||||
- name: Copy deployment files
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
|
||||
source: "deployment-production/*"
|
||||
target: "/opt/framework-production/current/"
|
||||
strip_components: 1
|
||||
|
||||
- name: Execute deployment
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
|
||||
script: |
|
||||
cd /opt/framework-production/current
|
||||
|
||||
# Set skip backup flag if provided
|
||||
export SKIP_BACKUP="${{ github.event.inputs.skip_backup || 'false' }}"
|
||||
|
||||
# Execute deployment script
|
||||
./deploy.sh
|
||||
|
||||
- name: Health check
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
|
||||
script: |
|
||||
cd /opt/framework-production/current
|
||||
|
||||
# Wait for services to be fully ready (longer for production)
|
||||
echo "Waiting 60 seconds for services to initialize..."
|
||||
sleep 60
|
||||
|
||||
# Check container status
|
||||
echo "Checking container status..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
|
||||
# Check service health
|
||||
echo "Checking service health..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app php -v
|
||||
|
||||
# Check PHP-FPM is running
|
||||
echo "Checking PHP-FPM process..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-app pgrep php-fpm
|
||||
|
||||
# Test HTTP endpoint (via Traefik)
|
||||
echo "Testing production endpoint..."
|
||||
curl -f -k https://michaelschiemer.de/health || {
|
||||
echo "⚠️ Health check endpoint failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check Redis connection
|
||||
echo "Checking Redis connection..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml exec -T production-redis redis-cli ping
|
||||
|
||||
echo ""
|
||||
echo "✅ All health checks passed!"
|
||||
|
||||
- name: Smoke tests
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
|
||||
script: |
|
||||
echo "Running production smoke tests..."
|
||||
|
||||
# Test main page
|
||||
curl -f -k https://michaelschiemer.de/ > /dev/null 2>&1 && echo "✅ Main page accessible" || {
|
||||
echo "❌ Main page failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Test API health
|
||||
curl -f -k https://michaelschiemer.de/api/health > /dev/null 2>&1 && echo "✅ API health check passed" || {
|
||||
echo "❌ API health check failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "✅ Smoke tests completed successfully"
|
||||
|
||||
- name: Rollback on failure
|
||||
if: failure()
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
|
||||
script: |
|
||||
cd /opt/framework-production
|
||||
|
||||
if [ -d "$(ls -dt backup_* 2>/dev/null | head -n1)" ]; then
|
||||
echo "🚨 Rolling back to previous deployment..."
|
||||
latest_backup=$(ls -dt backup_* | head -n1)
|
||||
|
||||
# Stop current broken deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml down
|
||||
cd ..
|
||||
|
||||
# Restore backup
|
||||
rm -rf current
|
||||
cp -r "$latest_backup" current
|
||||
|
||||
# Start restored deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml up -d
|
||||
|
||||
# Wait for services
|
||||
sleep 30
|
||||
|
||||
# Verify rollback
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.prod.yml ps
|
||||
|
||||
echo "✅ Rollback complete - previous version restored"
|
||||
else
|
||||
echo "❌ No backup available for rollback"
|
||||
echo "⚠️ MANUAL INTERVENTION REQUIRED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Notify deployment status
|
||||
if: always()
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRODUCTION_HOST }}
|
||||
username: ${{ secrets.PRODUCTION_USER }}
|
||||
key: ${{ secrets.PRODUCTION_SSH_KEY }}
|
||||
port: ${{ secrets.PRODUCTION_SSH_PORT || 22 }}
|
||||
script: |
|
||||
if [ "${{ job.status }}" == "success" ]; then
|
||||
echo "✅ Production deployment successful"
|
||||
echo "URL: https://michaelschiemer.de"
|
||||
echo "Deployed at: $(date)"
|
||||
|
||||
# Log deployment
|
||||
echo "$(date) - Deployment SUCCESS - Commit: ${{ github.sha }}" >> /opt/framework-production/deployment.log
|
||||
else
|
||||
echo "❌ Production deployment failed - rollback executed"
|
||||
|
||||
# Log deployment failure
|
||||
echo "$(date) - Deployment FAILED - Commit: ${{ github.sha }}" >> /opt/framework-production/deployment.log
|
||||
|
||||
# Send alert (placeholder - implement actual alerting)
|
||||
echo "⚠️ ALERT: Production deployment failed. Manual intervention may be required."
|
||||
fi
|
||||
|
||||
- name: Clean up build artifacts
|
||||
if: always()
|
||||
run: |
|
||||
echo "Cleaning up deployment artifacts..."
|
||||
rm -rf deployment-production
|
||||
echo "✅ Cleanup complete"
|
||||
212
deployment/legacy/gitea-workflows/deploy-staging.yml
Normal file
212
deployment/legacy/gitea-workflows/deploy-staging.yml
Normal file
@@ -0,0 +1,212 @@
|
||||
name: Deploy to Staging
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- staging
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_rebuild:
|
||||
description: 'Force rebuild Docker image'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
env:
|
||||
REGISTRY: localhost:5000
|
||||
IMAGE_NAME: framework
|
||||
IMAGE_TAG: staging
|
||||
COMPOSE_PROJECT_NAME: framework-staging
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
echo "Building Docker image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}"
|
||||
docker build \
|
||||
--file docker/php/Dockerfile \
|
||||
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} \
|
||||
--build-arg ENV=staging \
|
||||
--build-arg COMPOSER_INSTALL_FLAGS="--no-dev --optimize-autoloader --no-interaction" \
|
||||
.
|
||||
|
||||
- name: Push image to private registry
|
||||
run: |
|
||||
echo "Pushing image to registry..."
|
||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Prepare deployment files
|
||||
run: |
|
||||
echo "Preparing deployment files..."
|
||||
mkdir -p deployment-staging
|
||||
cp docker-compose.base.yml deployment-staging/
|
||||
cp docker-compose.staging.yml deployment-staging/
|
||||
cp -r docker deployment-staging/
|
||||
|
||||
# Create deployment script
|
||||
cat > deployment-staging/deploy.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=================================================="
|
||||
echo "Starting Staging Deployment"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
|
||||
# Pull latest images
|
||||
echo "[1/5] Pulling latest Docker images..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml pull
|
||||
|
||||
# Stop existing containers
|
||||
echo "[2/5] Stopping existing containers..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml down
|
||||
|
||||
# Start new containers
|
||||
echo "[3/5] Starting new containers..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml up -d
|
||||
|
||||
# Wait for services to be healthy
|
||||
echo "[4/5] Waiting for services to be healthy..."
|
||||
sleep 10
|
||||
|
||||
# Verify health
|
||||
echo "[5/5] Verifying deployment..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml ps
|
||||
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo "Staging Deployment Complete"
|
||||
echo "=================================================="
|
||||
EOF
|
||||
|
||||
chmod +x deployment-staging/deploy.sh
|
||||
|
||||
- name: Deploy to staging server
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.STAGING_HOST }}
|
||||
username: ${{ secrets.STAGING_USER }}
|
||||
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
||||
script: |
|
||||
# Create deployment directory
|
||||
mkdir -p /opt/framework-staging
|
||||
cd /opt/framework-staging
|
||||
|
||||
# Backup current deployment
|
||||
if [ -d "current" ]; then
|
||||
echo "Backing up current deployment..."
|
||||
timestamp=$(date +%Y%m%d_%H%M%S)
|
||||
mv current "backup_${timestamp}"
|
||||
# Keep only last 5 backups
|
||||
ls -dt backup_* | tail -n +6 | xargs rm -rf
|
||||
fi
|
||||
|
||||
# Create new deployment directory
|
||||
mkdir -p current
|
||||
cd current
|
||||
|
||||
- name: Copy deployment files
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.STAGING_HOST }}
|
||||
username: ${{ secrets.STAGING_USER }}
|
||||
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
||||
source: "deployment-staging/*"
|
||||
target: "/opt/framework-staging/current/"
|
||||
strip_components: 1
|
||||
|
||||
- name: Execute deployment
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.STAGING_HOST }}
|
||||
username: ${{ secrets.STAGING_USER }}
|
||||
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
||||
script: |
|
||||
cd /opt/framework-staging/current
|
||||
|
||||
# Execute deployment script
|
||||
./deploy.sh
|
||||
|
||||
- name: Health check
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.STAGING_HOST }}
|
||||
username: ${{ secrets.STAGING_USER }}
|
||||
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
||||
script: |
|
||||
cd /opt/framework-staging/current
|
||||
|
||||
# Wait for services to be fully ready
|
||||
echo "Waiting 30 seconds for services to initialize..."
|
||||
sleep 30
|
||||
|
||||
# Check container status
|
||||
echo "Checking container status..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml ps
|
||||
|
||||
# Check service health
|
||||
echo "Checking service health..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-app php -v
|
||||
|
||||
# Test HTTP endpoint (via internal network)
|
||||
echo "Testing HTTP endpoint..."
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml exec -T staging-nginx wget -q -O- http://localhost/health || echo "Health check endpoint not yet available"
|
||||
|
||||
echo ""
|
||||
echo "Health check complete!"
|
||||
|
||||
- name: Rollback on failure
|
||||
if: failure()
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.STAGING_HOST }}
|
||||
username: ${{ secrets.STAGING_USER }}
|
||||
key: ${{ secrets.STAGING_SSH_KEY }}
|
||||
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
||||
script: |
|
||||
cd /opt/framework-staging
|
||||
|
||||
if [ -d "$(ls -dt backup_* | head -n1)" ]; then
|
||||
echo "Rolling back to previous deployment..."
|
||||
latest_backup=$(ls -dt backup_* | head -n1)
|
||||
|
||||
# Stop current broken deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml down
|
||||
cd ..
|
||||
|
||||
# Restore backup
|
||||
rm -rf current
|
||||
cp -r "$latest_backup" current
|
||||
|
||||
# Start restored deployment
|
||||
cd current
|
||||
docker-compose -f docker-compose.base.yml -f docker-compose.staging.yml up -d
|
||||
|
||||
echo "Rollback complete!"
|
||||
else
|
||||
echo "No backup available for rollback"
|
||||
fi
|
||||
|
||||
- name: Notify deployment status
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ job.status }}" == "success" ]; then
|
||||
echo "✅ Staging deployment successful"
|
||||
echo "URL: https://staging.michaelschiemer.de"
|
||||
else
|
||||
echo "❌ Staging deployment failed - rollback executed"
|
||||
fi
|
||||
286
deployment/legacy/gitea-workflows/manual-deploy.yml
Normal file
286
deployment/legacy/gitea-workflows/manual-deploy.yml
Normal file
@@ -0,0 +1,286 @@
|
||||
name: 🚀 Manual Deployment
|
||||
|
||||
run-name: Manual Deploy - ${{ inputs.environment }} - ${{ inputs.image_tag || 'latest' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
environment:
|
||||
description: 'Deployment environment'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- staging
|
||||
- production
|
||||
image_tag:
|
||||
description: 'Image tag to deploy (e.g. abc1234-1696234567, git-abc1234). Leave empty for latest'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
branch:
|
||||
description: 'Branch to checkout (default: main for production, staging for staging)'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
env:
|
||||
REGISTRY: registry.michaelschiemer.de
|
||||
IMAGE_NAME: framework
|
||||
DEPLOYMENT_HOST: 94.16.110.151
|
||||
|
||||
jobs:
|
||||
determine-image:
|
||||
name: Determine Deployment Image
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
image_url: ${{ steps.image.outputs.image_url }}
|
||||
image_tag: ${{ steps.image.outputs.image_tag }}
|
||||
registry_host: ${{ env.REGISTRY }}
|
||||
image_name: ${{ env.IMAGE_NAME }}
|
||||
steps:
|
||||
- name: Determine image to deploy
|
||||
id: image
|
||||
shell: bash
|
||||
run: |
|
||||
REGISTRY="${{ env.REGISTRY }}"
|
||||
IMAGE_NAME="${{ env.IMAGE_NAME }}"
|
||||
INPUT_TAG="${{ inputs.image_tag }}"
|
||||
|
||||
if [ -z "$INPUT_TAG" ] || [ "$INPUT_TAG" = "" ]; then
|
||||
IMAGE_TAG="latest"
|
||||
else
|
||||
IMAGE_TAG="$INPUT_TAG"
|
||||
fi
|
||||
|
||||
IMAGE_URL="${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
|
||||
echo "image_url=${IMAGE_URL}" >> "$GITHUB_OUTPUT"
|
||||
echo "image_tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "📦 Deployment Image:"
|
||||
echo " URL: ${IMAGE_URL}"
|
||||
echo " Tag: ${IMAGE_TAG}"
|
||||
echo ""
|
||||
echo "ℹ️ Image will be validated during deployment"
|
||||
|
||||
deploy-staging:
|
||||
name: Deploy to Staging
|
||||
needs: determine-image
|
||||
if: inputs.environment == 'staging'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging.michaelschiemer.de
|
||||
steps:
|
||||
- name: Determine branch name
|
||||
id: branch
|
||||
shell: bash
|
||||
run: |
|
||||
INPUT_BRANCH="${{ inputs.branch }}"
|
||||
if [ -z "$INPUT_BRANCH" ] || [ "$INPUT_BRANCH" = "" ]; then
|
||||
REF_NAME="staging"
|
||||
else
|
||||
REF_NAME="$INPUT_BRANCH"
|
||||
fi
|
||||
echo "BRANCH=$REF_NAME" >> $GITHUB_OUTPUT
|
||||
echo "📋 Branch: $REF_NAME"
|
||||
|
||||
- name: Checkout deployment scripts
|
||||
run: |
|
||||
REF_NAME="${{ steps.branch.outputs.BRANCH }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
else
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo || \
|
||||
git clone --depth 1 \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
fi
|
||||
|
||||
cd /workspace/repo
|
||||
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production
|
||||
chmod 600 ~/.ssh/production
|
||||
ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Create Ansible Vault password file
|
||||
run: |
|
||||
if [ -n "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" ]; then
|
||||
echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass
|
||||
chmod 600 /tmp/vault_pass
|
||||
echo "✅ Vault password file created"
|
||||
else
|
||||
echo "⚠️ ANSIBLE_VAULT_PASSWORD secret not set, using empty password file"
|
||||
touch /tmp/vault_pass
|
||||
chmod 600 /tmp/vault_pass
|
||||
fi
|
||||
|
||||
- name: Deploy to Staging (Complete)
|
||||
run: |
|
||||
cd /workspace/repo/deployment/ansible
|
||||
ansible-playbook -i inventory/production.yml \
|
||||
playbooks/deploy-complete.yml \
|
||||
-e "deployment_environment=staging" \
|
||||
-e "deployment_hosts=production" \
|
||||
-e "git_branch=${{ steps.branch.outputs.BRANCH }}" \
|
||||
-e "image_tag=${{ needs.determine-image.outputs.image_tag }}" \
|
||||
-e "docker_registry=${{ needs.determine-image.outputs.registry_host }}" \
|
||||
-e "docker_registry_username=${{ secrets.REGISTRY_USER }}" \
|
||||
-e "docker_registry_password=${{ secrets.REGISTRY_PASSWORD }}" \
|
||||
--vault-password-file /tmp/vault_pass \
|
||||
--private-key ~/.ssh/production
|
||||
|
||||
- name: Wait for deployment to stabilize
|
||||
run: sleep 30
|
||||
|
||||
- name: Health check
|
||||
id: health
|
||||
run: |
|
||||
echo "🔍 Performing health checks with exponential backoff..."
|
||||
DELAY=2
|
||||
MAX_DELAY=60
|
||||
MAX_ATTEMPTS=5
|
||||
|
||||
for i in $(seq 1 $MAX_ATTEMPTS); do
|
||||
if curl -f -k -s https://staging.michaelschiemer.de/health > /dev/null 2>&1; then
|
||||
echo "✅ Health check passed (attempt $i/$MAX_ATTEMPTS)"
|
||||
exit 0
|
||||
fi
|
||||
if [ $i -lt $MAX_ATTEMPTS ]; then
|
||||
echo "⏳ Waiting for staging service... (attempt $i/$MAX_ATTEMPTS, delay ${DELAY}s)"
|
||||
sleep $DELAY
|
||||
DELAY=$((DELAY * 2))
|
||||
[ $DELAY -gt $MAX_DELAY ] && DELAY=$MAX_DELAY
|
||||
fi
|
||||
done
|
||||
echo "❌ Health check failed after $MAX_ATTEMPTS attempts"
|
||||
exit 1
|
||||
|
||||
- name: Notify deployment success
|
||||
if: success()
|
||||
run: |
|
||||
echo "🚀 Staging deployment successful!"
|
||||
echo "URL: https://staging.michaelschiemer.de"
|
||||
echo "Image: ${{ needs.determine-image.outputs.image_url }}"
|
||||
|
||||
deploy-production:
|
||||
name: Deploy to Production
|
||||
needs: determine-image
|
||||
if: inputs.environment == 'production'
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: deploy-production
|
||||
cancel-in-progress: false
|
||||
environment:
|
||||
name: production
|
||||
url: https://michaelschiemer.de
|
||||
steps:
|
||||
- name: Determine branch name
|
||||
id: branch
|
||||
shell: bash
|
||||
run: |
|
||||
INPUT_BRANCH="${{ inputs.branch }}"
|
||||
if [ -z "$INPUT_BRANCH" ] || [ "$INPUT_BRANCH" = "" ]; then
|
||||
REF_NAME="main"
|
||||
else
|
||||
REF_NAME="$INPUT_BRANCH"
|
||||
fi
|
||||
echo "BRANCH=$REF_NAME" >> $GITHUB_OUTPUT
|
||||
echo "📋 Branch: $REF_NAME"
|
||||
|
||||
- name: Checkout deployment scripts
|
||||
run: |
|
||||
REF_NAME="${{ steps.branch.outputs.BRANCH }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
else
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo || \
|
||||
git clone --depth 1 \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
fi
|
||||
|
||||
cd /workspace/repo
|
||||
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production
|
||||
chmod 600 ~/.ssh/production
|
||||
ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Create Ansible Vault password file
|
||||
run: |
|
||||
if [ -n "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" ]; then
|
||||
echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass
|
||||
chmod 600 /tmp/vault_pass
|
||||
echo "✅ Vault password file created"
|
||||
else
|
||||
echo "⚠️ ANSIBLE_VAULT_PASSWORD secret not set, using empty password file"
|
||||
touch /tmp/vault_pass
|
||||
chmod 600 /tmp/vault_pass
|
||||
fi
|
||||
|
||||
- name: Deploy to Production (Complete)
|
||||
run: |
|
||||
cd /workspace/repo/deployment/ansible
|
||||
ansible-playbook -i inventory/production.yml \
|
||||
playbooks/deploy-complete.yml \
|
||||
-e "deployment_environment=production" \
|
||||
-e "deployment_hosts=production" \
|
||||
-e "git_branch=${{ steps.branch.outputs.BRANCH }}" \
|
||||
-e "image_tag=${{ needs.determine-image.outputs.image_tag }}" \
|
||||
-e "docker_registry=${{ needs.determine-image.outputs.registry_host }}" \
|
||||
-e "docker_registry_username=${{ secrets.REGISTRY_USER }}" \
|
||||
-e "docker_registry_password=${{ secrets.REGISTRY_PASSWORD }}" \
|
||||
--vault-password-file /tmp/vault_pass \
|
||||
--private-key ~/.ssh/production
|
||||
|
||||
- name: Wait for deployment to stabilize
|
||||
run: sleep 30
|
||||
|
||||
- name: Health check
|
||||
id: health
|
||||
run: |
|
||||
echo "🔍 Performing health checks with exponential backoff..."
|
||||
DELAY=2
|
||||
MAX_DELAY=60
|
||||
MAX_ATTEMPTS=5
|
||||
|
||||
for i in $(seq 1 $MAX_ATTEMPTS); do
|
||||
if curl -f -k -s https://michaelschiemer.de/health > /dev/null 2>&1; then
|
||||
echo "✅ Health check passed (attempt $i/$MAX_ATTEMPTS)"
|
||||
exit 0
|
||||
fi
|
||||
if [ $i -lt $MAX_ATTEMPTS ]; then
|
||||
echo "⏳ Waiting for production service... (attempt $i/$MAX_ATTEMPTS, delay ${DELAY}s)"
|
||||
sleep $DELAY
|
||||
DELAY=$((DELAY * 2))
|
||||
[ $DELAY -gt $MAX_DELAY ] && DELAY=$MAX_DELAY
|
||||
fi
|
||||
done
|
||||
echo "❌ Health check failed after $MAX_ATTEMPTS attempts"
|
||||
exit 1
|
||||
|
||||
- name: Notify deployment success
|
||||
if: success()
|
||||
run: |
|
||||
echo "🚀 Production deployment successful!"
|
||||
echo "URL: https://michaelschiemer.de"
|
||||
echo "Image: ${{ needs.determine-image.outputs.image_url }}"
|
||||
|
||||
89
deployment/legacy/gitea-workflows/monitor-performance.yml
Normal file
89
deployment/legacy/gitea-workflows/monitor-performance.yml
Normal file
@@ -0,0 +1,89 @@
|
||||
name: 📊 Monitor Workflow Performance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
lookback_hours:
|
||||
description: 'Hours to look back for metrics'
|
||||
required: false
|
||||
default: '24'
|
||||
type: string
|
||||
|
||||
env:
|
||||
DEPLOYMENT_HOST: 94.16.110.151
|
||||
|
||||
jobs:
|
||||
monitor:
|
||||
name: Monitor Workflow Performance
|
||||
runs-on: php-ci
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production
|
||||
chmod 600 ~/.ssh/production
|
||||
ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Create Ansible Vault password file
|
||||
run: |
|
||||
if [ -n "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" ]; then
|
||||
echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass
|
||||
chmod 600 /tmp/vault_pass
|
||||
echo "✅ Vault password file created"
|
||||
else
|
||||
echo "⚠️ ANSIBLE_VAULT_PASSWORD secret not set, using empty password file"
|
||||
touch /tmp/vault_pass
|
||||
chmod 600 /tmp/vault_pass
|
||||
fi
|
||||
|
||||
- name: Run performance monitoring
|
||||
run: |
|
||||
cd /workspace/repo/deployment/ansible
|
||||
ansible-playbook -i inventory/production.yml \
|
||||
playbooks/monitor-workflow-performance.yml \
|
||||
-e "monitoring_lookback_hours=${{ github.event.inputs.lookback_hours || '24' }}" \
|
||||
--vault-password-file /tmp/vault_pass \
|
||||
--private-key ~/.ssh/production
|
||||
|
||||
- name: Collect metrics files
|
||||
run: |
|
||||
ssh -i ~/.ssh/production deploy@${{ env.DEPLOYMENT_HOST }} \
|
||||
"find /home/deploy/monitoring/workflow-metrics -name 'workflow_metrics_*.json' -mtime -1 -exec cat {} \; | jq -s '.'" \
|
||||
> /tmp/combined_metrics.json || echo "[]" > /tmp/combined_metrics.json
|
||||
|
||||
- name: Display metrics summary
|
||||
run: |
|
||||
if [ -f /tmp/combined_metrics.json ] && [ -s /tmp/combined_metrics.json ]; then
|
||||
echo "📊 Performance Metrics Summary:"
|
||||
echo "=================================="
|
||||
cat /tmp/combined_metrics.json | jq -r '
|
||||
.[] |
|
||||
"Timestamp: \(.timestamp)",
|
||||
"System Load: \(.system_metrics.load_average)",
|
||||
"CPU Usage: \(.system_metrics.cpu_usage_percent)%",
|
||||
"Memory: \(.system_metrics.memory_usage)",
|
||||
"Gitea Runner: \(.gitea_metrics.runner_status)",
|
||||
"Gitea API Response: \(.gitea_metrics.api_response_time_ms)ms",
|
||||
"Workflow Log Entries: \(.gitea_metrics.workflow_log_entries_last_24h)",
|
||||
"---"
|
||||
' || echo "⚠️ Could not parse metrics"
|
||||
else
|
||||
echo "⚠️ No metrics collected"
|
||||
fi
|
||||
|
||||
- name: Upload metrics as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: workflow-metrics
|
||||
path: /tmp/combined_metrics.json
|
||||
retention-days: 30
|
||||
if: always()
|
||||
|
||||
304
deployment/legacy/gitea-workflows/security-scan.yml
Normal file
304
deployment/legacy/gitea-workflows/security-scan.yml
Normal file
@@ -0,0 +1,304 @@
|
||||
name: Security Vulnerability Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, staging, develop ]
|
||||
pull_request:
|
||||
branches: [ main, staging, develop ]
|
||||
schedule:
|
||||
# Daily security scan at 2 AM UTC
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-changes:
|
||||
name: Check for Dependency Changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
dependencies_changed: ${{ steps.filter.outputs.dependencies_changed }}
|
||||
steps:
|
||||
- name: Download CI helpers
|
||||
shell: bash
|
||||
env:
|
||||
CI_TOKEN: ${{ secrets.CI_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REF="${{ github.sha }}"
|
||||
if [ -z "$REF" ]; then
|
||||
REF="${{ github.ref_name }}"
|
||||
fi
|
||||
if [ -z "$REF" ]; then
|
||||
REF="${{ github.head_ref }}"
|
||||
fi
|
||||
if [ -z "$REF" ]; then
|
||||
REF="main"
|
||||
fi
|
||||
URL="https://git.michaelschiemer.de/${{ github.repository }}/raw/${REF}/scripts/ci/clone_repo.sh"
|
||||
mkdir -p /tmp/ci-tools
|
||||
if [ -n "$CI_TOKEN" ]; then
|
||||
curl -sfL -u "$CI_TOKEN:x-oauth-basic" "$URL" -o /tmp/ci-tools/clone_repo.sh
|
||||
else
|
||||
curl -sfL "$URL" -o /tmp/ci-tools/clone_repo.sh
|
||||
fi
|
||||
chmod +x /tmp/ci-tools/clone_repo.sh
|
||||
|
||||
- name: Analyse changed files
|
||||
id: filter
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
REF_NAME="${{ github.ref_name }}"
|
||||
if [ -z "$REF_NAME" ]; then
|
||||
REF_NAME="${{ github.head_ref }}"
|
||||
fi
|
||||
if [ -z "$REF_NAME" ]; then
|
||||
REF_NAME="main"
|
||||
fi
|
||||
|
||||
REPO="${{ github.repository }}"
|
||||
WORKDIR="/workspace/repo"
|
||||
|
||||
export CI_REPOSITORY="$REPO"
|
||||
export CI_TOKEN="${{ secrets.CI_TOKEN }}"
|
||||
export CI_REF_NAME="$REF_NAME"
|
||||
export CI_DEFAULT_BRANCH="main"
|
||||
export CI_TARGET_DIR="$WORKDIR"
|
||||
export CI_FETCH_DEPTH="2"
|
||||
|
||||
/tmp/ci-tools/clone_repo.sh
|
||||
|
||||
cd "$WORKDIR"
|
||||
|
||||
# For scheduled or manual runs, always run the scan
|
||||
if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "dependencies_changed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "ℹ️ Scheduled/manual run - will scan dependencies"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CHANGED_FILES=""
|
||||
EVENT_BEFORE="${{ github.event.before }}"
|
||||
|
||||
if [ "${{ github.event_name }}" = "push" ] && [ -n "$EVENT_BEFORE" ]; then
|
||||
if git rev-parse "$EVENT_BEFORE" >/dev/null 2>&1; then
|
||||
CHANGED_FILES="$(git diff --name-only "$EVENT_BEFORE" HEAD || true)"
|
||||
else
|
||||
git fetch origin "$EVENT_BEFORE" --depth 1 || true
|
||||
if git rev-parse "$EVENT_BEFORE" >/dev/null 2>&1; then
|
||||
CHANGED_FILES="$(git diff --name-only "$EVENT_BEFORE" HEAD || true)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
if git rev-parse HEAD^ >/dev/null 2>&1; then
|
||||
CHANGED_FILES="$(git diff --name-only HEAD^ HEAD || true)"
|
||||
else
|
||||
git fetch origin "$REF_NAME" --depth 50 || true
|
||||
if git rev-parse HEAD^ >/dev/null 2>&1; then
|
||||
CHANGED_FILES="$(git diff --name-only HEAD^ HEAD || true)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
DEPENDENCIES_CHANGED=false
|
||||
|
||||
if [ -n "$CHANGED_FILES" ]; then
|
||||
while IFS= read -r FILE; do
|
||||
[ -z "$FILE" ] && continue
|
||||
if echo "$FILE" | grep -Eq "^(composer\.json|composer\.lock)$"; then
|
||||
DEPENDENCIES_CHANGED=true
|
||||
break
|
||||
fi
|
||||
done <<< "$CHANGED_FILES"
|
||||
fi
|
||||
|
||||
echo "dependencies_changed=$DEPENDENCIES_CHANGED" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ "$DEPENDENCIES_CHANGED" = "true" ]; then
|
||||
echo "ℹ️ Dependencies changed - security scan will run"
|
||||
else
|
||||
echo "ℹ️ No dependency changes detected - skipping security scan"
|
||||
fi
|
||||
|
||||
security-audit:
|
||||
needs: check-changes
|
||||
if: needs.check-changes.outputs.dependencies_changed == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
||||
name: Composer Security Audit
|
||||
runs-on: php-ci # Uses pre-built PHP 8.5 CI image with Composer pre-installed
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
run: |
|
||||
# For pull_request events, use the head ref (source branch)
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
REF_NAME="${{ github.head_ref || github.event.pull_request.head.ref }}"
|
||||
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
REF_NAME="${{ inputs.branch || github.ref_name }}"
|
||||
else
|
||||
REF_NAME="${{ github.ref_name }}"
|
||||
fi
|
||||
|
||||
# Fallback to main if REF_NAME is still empty
|
||||
if [ -z "$REF_NAME" ] || [ "$REF_NAME" = "" ]; then
|
||||
REF_NAME="main"
|
||||
fi
|
||||
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
echo "📋 Cloning branch: $REF_NAME"
|
||||
echo "📦 Repository: $REPO"
|
||||
|
||||
# Use CI token if available, otherwise try public access
|
||||
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
else
|
||||
# Try public HTTPS (works if repository is public)
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo || \
|
||||
# Fallback: Try to use Gitea's internal runner access
|
||||
git clone --depth 1 \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
fi
|
||||
|
||||
cd /workspace/repo
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "dir=$(composer global config cache-dir 2>/dev/null | cut -d' ' -f3 || echo "$HOME/.composer/cache")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ steps.composer-cache.outputs.dir }}
|
||||
vendor/
|
||||
key: ${{ runner.os }}-composer-security-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-composer-security-
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
# Validate composer.json (less strict - lock file might be updated during install)
|
||||
composer validate --no-check-lock || echo "⚠️ composer.lock might need update, but continuing..."
|
||||
# Try to update lock file if needed
|
||||
composer update --lock --no-interaction || echo "⚠️ Could not update lock file, but continuing..."
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
# TEMPORARY WORKAROUND: Ignore PHP 8.5 platform requirement until dependencies officially support PHP 8.5
|
||||
# TODO: Remove --ignore-platform-req=php when dependencies are updated (estimated: 1 month)
|
||||
composer install --prefer-dist --no-progress --no-dev --ignore-platform-req=php
|
||||
|
||||
- name: Run Composer Security Audit
|
||||
id: security-audit
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
composer audit --format=json > audit-result.json || true
|
||||
cat audit-result.json
|
||||
|
||||
- name: Parse audit results
|
||||
id: parse-audit
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
if [ -f audit-result.json ]; then
|
||||
# jq is pre-installed in php-ci image
|
||||
jq --version
|
||||
|
||||
ADVISORIES=$(jq -r '.advisories | length' audit-result.json 2>/dev/null || echo "0")
|
||||
ABANDONED=$(jq -r '.abandoned | length' audit-result.json 2>/dev/null || echo "0")
|
||||
|
||||
echo "advisories_count=$ADVISORIES" >> $GITHUB_OUTPUT
|
||||
echo "abandoned_count=$ABANDONED" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "## Security Audit Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Vulnerabilities Found:** $ADVISORIES" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Abandoned Packages:** $ABANDONED" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "$ADVISORIES" -gt 0 ]; then
|
||||
echo "has_vulnerabilities=true" >> $GITHUB_OUTPUT
|
||||
echo "- **Status:** ❌ Security vulnerabilities detected - review required" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Display vulnerability details
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Vulnerability Details" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||
jq '.advisories' audit-result.json >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
exit 1
|
||||
else
|
||||
echo "has_vulnerabilities=false" >> $GITHUB_OUTPUT
|
||||
echo "- **Status:** ✅ No security vulnerabilities detected" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "$ABANDONED" -gt 0 ]; then
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Abandoned Packages" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
jq -r '.abandoned | to_entries[] | "- **\(.key)**: \(.value // "No replacement suggested")"' audit-result.json >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Save audit results
|
||||
if: always()
|
||||
run: |
|
||||
cd /workspace/repo
|
||||
if [ -f audit-result.json ]; then
|
||||
mkdir -p /tmp/artifacts
|
||||
cp audit-result.json /tmp/artifacts/security-audit-results-${{ github.run_number }}.json || true
|
||||
echo "✅ Audit results saved"
|
||||
fi
|
||||
|
||||
- name: Create Gitea issue on vulnerability (scheduled runs only)
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
run: |
|
||||
# Prepare issue body
|
||||
ISSUE_TITLE="🚨 Security Vulnerabilities Detected in Dependencies"
|
||||
ISSUE_BODY="## Security Audit Report\n\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}**Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}**Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\n"
|
||||
|
||||
if [ -f audit-result.json ]; then
|
||||
# Add vulnerability details
|
||||
ISSUE_BODY="${ISSUE_BODY}### Vulnerabilities Found\n\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}\`\`\`json\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}$(cat audit-result.json)\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}\`\`\`\n\n"
|
||||
fi
|
||||
|
||||
ISSUE_BODY="${ISSUE_BODY}### Action Required\n\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}1. Review the vulnerability details above\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}2. Update affected packages: \`composer update <package>\`\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}3. Run tests: \`make test\`\n"
|
||||
ISSUE_BODY="${ISSUE_BODY}4. Verify with: \`make security-check\`\n"
|
||||
|
||||
# Create issue using Gitea API
|
||||
# Note: Requires CI_TOKEN secret to be configured
|
||||
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
|
||||
curl -X POST \
|
||||
-H "Authorization: token ${{ secrets.CI_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"title\":\"${ISSUE_TITLE}\",\"body\":\"${ISSUE_BODY}\",\"labels\":[\"security\",\"dependencies\",\"automated\"]}" \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/issues"
|
||||
else
|
||||
echo "⚠️ CI_TOKEN not configured - skipping issue creation"
|
||||
echo "Please add CI_TOKEN as repository secret for automated issue creation"
|
||||
fi
|
||||
|
||||
- name: Notify on failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "::error::Security vulnerabilities detected! Check the audit results for details."
|
||||
echo "Run 'make security-check' locally to review vulnerabilities."
|
||||
72
deployment/legacy/gitea-workflows/system-maintenance.yml
Normal file
72
deployment/legacy/gitea-workflows/system-maintenance.yml
Normal file
@@ -0,0 +1,72 @@
|
||||
name: System Maintenance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Täglich um 02:30 UTC (≈ 03:30 CET/04:30 CEST)
|
||||
- cron: '30 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DEPLOYMENT_HOST: 94.16.110.151
|
||||
|
||||
jobs:
|
||||
run-maintenance:
|
||||
name: Run Ansible System Maintenance
|
||||
runs-on: php-ci
|
||||
environment:
|
||||
name: production
|
||||
url: https://michaelschiemer.de
|
||||
|
||||
steps:
|
||||
- name: Checkout deployment repository
|
||||
run: |
|
||||
REF_NAME="${{ github.ref_name }}"
|
||||
if [ -z "$REF_NAME" ]; then
|
||||
REF_NAME="main"
|
||||
fi
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
echo "📋 Cloning branch: $REF_NAME"
|
||||
|
||||
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
else
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo || \
|
||||
git clone --depth 1 \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
fi
|
||||
|
||||
cd /workspace/repo
|
||||
|
||||
- name: Prepare SSH access
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production
|
||||
chmod 600 ~/.ssh/production
|
||||
ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Verify Ansible availability
|
||||
run: ansible --version
|
||||
|
||||
- name: Run system maintenance playbook
|
||||
run: |
|
||||
cd /workspace/repo/deployment/ansible
|
||||
ansible-playbook -i inventory/production.yml \
|
||||
playbooks/system-maintenance.yml
|
||||
|
||||
- name: Cleanup SSH key
|
||||
if: always()
|
||||
run: rm -f ~/.ssh/production
|
||||
|
||||
- name: Report success
|
||||
if: success()
|
||||
run: echo "✅ System maintenance completed"
|
||||
|
||||
- name: Report failure
|
||||
if: failure()
|
||||
run: echo "❌ System maintenance failed – review Ansible logs"
|
||||
@@ -0,0 +1,89 @@
|
||||
name: Update Production Secrets
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
vault_password:
|
||||
description: 'Ansible Vault Password'
|
||||
required: true
|
||||
type: password
|
||||
|
||||
env:
|
||||
DEPLOYMENT_HOST: 94.16.110.151
|
||||
|
||||
jobs:
|
||||
deploy-secrets:
|
||||
name: Deploy Secrets to Production
|
||||
runs-on: php-ci # Uses pre-built PHP 8.5 CI image with Ansible
|
||||
environment:
|
||||
name: production-secrets
|
||||
url: https://michaelschiemer.de
|
||||
|
||||
steps:
|
||||
- name: Checkout deployment configuration
|
||||
run: |
|
||||
REF_NAME="${{ github.ref_name }}"
|
||||
REPO="${{ github.repository }}"
|
||||
if [ -z "$REF_NAME" ]; then
|
||||
REF_NAME="main"
|
||||
fi
|
||||
|
||||
if [ -n "${{ secrets.CI_TOKEN }}" ]; then
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://${{ secrets.CI_TOKEN }}@git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
else
|
||||
git clone --depth 1 --branch "$REF_NAME" \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo || \
|
||||
git clone --depth 1 \
|
||||
"https://git.michaelschiemer.de/${REPO}.git" \
|
||||
/workspace/repo
|
||||
fi
|
||||
|
||||
cd /workspace/repo
|
||||
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/production
|
||||
chmod 600 ~/.ssh/production
|
||||
ssh-keyscan -H ${{ env.DEPLOYMENT_HOST }} >> ~/.ssh/known_hosts
|
||||
|
||||
# Ansible is pre-installed in php-ci image
|
||||
- name: Verify Ansible installation
|
||||
run: ansible --version
|
||||
|
||||
- name: Create vault password file
|
||||
run: |
|
||||
echo "${{ github.event.inputs.vault_password }}" > /tmp/.vault_pass
|
||||
chmod 600 /tmp/.vault_pass
|
||||
|
||||
- name: Deploy secrets via Ansible
|
||||
run: |
|
||||
cd deployment/ansible
|
||||
ansible-playbook -i inventory/production.yml \
|
||||
playbooks/setup-production-secrets.yml \
|
||||
--vault-password-file /tmp/.vault_pass
|
||||
|
||||
- name: Cleanup vault password
|
||||
if: always()
|
||||
run: |
|
||||
rm -f /tmp/.vault_pass
|
||||
|
||||
- name: Verify secrets deployment
|
||||
run: |
|
||||
ssh -i ~/.ssh/production deploy@${{ env.DEPLOYMENT_HOST }} \
|
||||
"docker secret ls && test -f /home/deploy/secrets/.env.production"
|
||||
|
||||
- name: Notify deployment success
|
||||
if: success()
|
||||
run: |
|
||||
echo "✅ Secrets deployed successfully to production"
|
||||
echo "Services will be restarted automatically"
|
||||
|
||||
- name: Notify deployment failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "❌ Secrets deployment failed"
|
||||
echo "Check Ansible logs for details"
|
||||
Reference in New Issue
Block a user