feat(deployment): update Semaphore stack and Traefik configuration
- Add QUICKSTART.md and SETUP_REPOSITORY.md for Semaphore stack - Add playbooks directory for Semaphore deployment - Update Semaphore docker-compose.yml, env.example, and README - Add Traefik local configuration files - Disable semaphore.yml in Traefik dynamic config - Update docker-compose.local.yml and build-image workflow
This commit is contained in:
201
deployment/stacks/semaphore/QUICKSTART.md
Normal file
201
deployment/stacks/semaphore/QUICKSTART.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# ?? Quick Start: Semaphore mit Repository verbinden
|
||||
|
||||
Kurze Schritt-f?r-Schritt-Anleitung, um Semaphore mit deinem Git-Repository zu verbinden.
|
||||
|
||||
## Schritt 1: Semaphore starten
|
||||
|
||||
```bash
|
||||
cd deployment/stacks/semaphore
|
||||
cp env.example .env
|
||||
```
|
||||
|
||||
**WICHTIG**: Generiere einen Encryption Key:
|
||||
```bash
|
||||
head -c32 /dev/urandom | base64
|
||||
```
|
||||
Kopiere den Key und setze ihn in `.env`:
|
||||
```env
|
||||
SEMAPHORE_ACCESS_KEY_ENCRYPTION=<dein-generierter-key>
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Schritt 2: Semaphore ?ffnen
|
||||
|
||||
?ffne im Browser: **http://localhost:9300**
|
||||
|
||||
**Login**:
|
||||
- Username: `admin`
|
||||
- Password: `admin`
|
||||
|
||||
## Schritt 3: Projekt erstellen
|
||||
|
||||
1. Klicke auf **"New Project"** oder **"Create Project"**
|
||||
2. Name: **"michaelschiemer"**
|
||||
3. Klicke auf **"Create"**
|
||||
|
||||
## Schritt 4: Inventory erstellen (Hosts definieren)
|
||||
|
||||
### 4.1 Inventory anlegen
|
||||
|
||||
1. Gehe zu **Inventories** ? **New Inventory**
|
||||
2. Name: **"Production Hosts"**
|
||||
3. Klicke auf **"Create"**
|
||||
|
||||
### 4.2 Host hinzuf?gen
|
||||
|
||||
1. Klicke auf dein Inventory ? **"Add Host"**
|
||||
2. F?lle aus:
|
||||
|
||||
```
|
||||
Name: production
|
||||
Address: 94.16.110.151
|
||||
SSH Username: deploy
|
||||
SSH Port: 22
|
||||
```
|
||||
|
||||
3. Klicke auf **"Save"**
|
||||
|
||||
### 4.3 SSH-Key hinzuf?gen
|
||||
|
||||
1. Gehe zu **Keys** (in der Seitenleiste)
|
||||
2. Klicke auf **"New Key"**
|
||||
3. Name: **"Deployment Key"**
|
||||
4. F?ge deinen SSH Private Key ein (aus `~/.ssh/production`):
|
||||
```bash
|
||||
cat ~/.ssh/production
|
||||
```
|
||||
5. Klicke auf **"Save"**
|
||||
6. Gehe zur?ck zu deinem Inventory und w?hle den Key bei deinem Host aus
|
||||
|
||||
## Schritt 5: Template erstellen (Playbook verwenden)
|
||||
|
||||
### 5.1 Template f?r CI Tests
|
||||
|
||||
1. Gehe zu **Templates** ? **New Template**
|
||||
2. **Name**: "Run CI Tests"
|
||||
3. **Inventory**: W?hle "Production Hosts"
|
||||
4. **Playbook Path**: `/tmp/semaphore/playbooks/ci-tests.yml`
|
||||
5. **Variables** (klicke auf "Variables"):
|
||||
```yaml
|
||||
repo_url: https://git.michaelschiemer.de/michael/michaelschiemer.git
|
||||
repo_branch: main
|
||||
```
|
||||
6. Klicke auf **"Save"**
|
||||
|
||||
### 5.2 Template f?r Docker Build
|
||||
|
||||
1. **Templates** ? **New Template**
|
||||
2. **Name**: "Build Docker Image"
|
||||
3. **Inventory**: "Production Hosts"
|
||||
4. **Playbook Path**: `/tmp/semaphore/playbooks/docker-build.yml`
|
||||
5. **Variables**:
|
||||
```yaml
|
||||
repo_url: https://git.michaelschiemer.de/michael/michaelschiemer.git
|
||||
repo_branch: main
|
||||
registry_url: registry.michaelschiemer.de
|
||||
registry_user: admin
|
||||
registry_password: <dein-registry-passwort>
|
||||
image_name: framework
|
||||
image_tag: latest
|
||||
```
|
||||
6. **Save**
|
||||
|
||||
### 5.3 Template f?r Staging Deployment
|
||||
|
||||
1. **Templates** ? **New Template**
|
||||
2. **Name**: "Deploy to Staging"
|
||||
3. **Inventory**: "Production Hosts"
|
||||
4. **Playbook Path**: `/tmp/semaphore/playbooks/deploy-staging.yml`
|
||||
5. **Variables**:
|
||||
```yaml
|
||||
registry_url: registry.michaelschiemer.de
|
||||
registry_user: admin
|
||||
registry_password: <dein-registry-passwort>
|
||||
image_name: framework
|
||||
image_tag: latest
|
||||
```
|
||||
6. **Save**
|
||||
|
||||
### 5.4 Template f?r Production Deployment
|
||||
|
||||
1. **Templates** ? **New Template**
|
||||
2. **Name**: "Deploy to Production"
|
||||
3. **Inventory**: "Production Hosts"
|
||||
4. **Playbook Path**: `/tmp/semaphore/playbooks/deploy-production.yml`
|
||||
5. **Variables**:
|
||||
```yaml
|
||||
registry_url: registry.michaelschiemer.de
|
||||
registry_user: admin
|
||||
registry_password: <dein-registry-passwort>
|
||||
image_name: framework
|
||||
image_tag: latest
|
||||
```
|
||||
6. **Save**
|
||||
|
||||
## Schritt 6: Task ausf?hren
|
||||
|
||||
1. Gehe zu **Templates**
|
||||
2. Klicke auf ein Template (z.B. "Run CI Tests")
|
||||
3. Klicke auf **"Run"**
|
||||
4. Beobachte die Ausf?hrung in Echtzeit
|
||||
|
||||
## ? Fertig!
|
||||
|
||||
Semaphore ist jetzt mit deinem Repository verbunden! Du kannst:
|
||||
|
||||
- ? CI/CD-Tasks manuell starten
|
||||
- ? Playbooks aus dem Repository verwenden
|
||||
- ? Deployments auf deine Server ausf?hren
|
||||
|
||||
## ?? Verf?gbare Playbooks
|
||||
|
||||
Die folgenden Playbooks sind bereits im Repository und k?nnen in Semaphore verwendet werden:
|
||||
|
||||
1. **`/tmp/semaphore/playbooks/ci-tests.yml`** - PHP Tests & Quality Checks
|
||||
2. **`/tmp/semaphore/playbooks/docker-build.yml`** - Docker Image Build & Push
|
||||
3. **`/tmp/semaphore/playbooks/deploy-staging.yml`** - Staging Deployment
|
||||
4. **`/tmp/semaphore/playbooks/deploy-production.yml`** - Production Deployment
|
||||
|
||||
## ?? Vorhandene Ansible-Playbooks
|
||||
|
||||
Die vorhandenen Ansible-Playbooks aus `deployment/ansible/playbooks/` sind auch verf?gbar unter:
|
||||
- `/tmp/semaphore/repo-playbooks/`
|
||||
|
||||
Zum Beispiel:
|
||||
- `/tmp/semaphore/repo-playbooks/deploy-update.yml`
|
||||
- `/tmp/semaphore/repo-playbooks/rollback.yml`
|
||||
|
||||
## ?? Tipps
|
||||
|
||||
### Docker Socket f?r Build-Tasks
|
||||
|
||||
Falls Docker-Build-Tasks ausgef?hrt werden sollen, f?ge den Docker Socket hinzu:
|
||||
|
||||
In `docker-compose.yml` bei Semaphore-Service:
|
||||
```yaml
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
```
|
||||
|
||||
Dann Semaphore neu starten:
|
||||
```bash
|
||||
docker compose restart semaphore
|
||||
```
|
||||
|
||||
### Git-Integration
|
||||
|
||||
Die Playbooks k?nnen direkt auf das Git-Repository zugreifen (HTTPS). F?r private Repositories oder SSH-Zugriff:
|
||||
|
||||
1. Erstelle einen SSH-Key f?r Git in Semaphore (Keys ? New Key)
|
||||
2. F?ge den Public Key zu deinem Git-Repository hinzu
|
||||
3. Verwende SSH-URL in Playbook-Variablen:
|
||||
```yaml
|
||||
repo_url: git@git.michaelschiemer.de:michael/michaelschiemer.git
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Hilfe**: Weitere Details in `SETUP_REPOSITORY.md` und `README.md`
|
||||
@@ -6,9 +6,9 @@ Selbst-gehostete Semaphore CI/CD-Plattform für lokale Entwicklung, die es ermö
|
||||
|
||||
**Features**:
|
||||
- **Selbst-gehostet**: Läuft vollständig lokal auf dem Entwicklungsrechner
|
||||
- **Isoliert**: Keine externen Zugriffe, nur localhost (127.0.0.1)
|
||||
- **Lokal isoliert**: NUR über localhost (127.0.0.1) erreichbar - KEIN externer Zugriff aus Sicherheitsgründen!
|
||||
- **MySQL-Backend**: Persistente Datenbank für Projekte, Tasks und Templates
|
||||
- **Web-UI**: Intuitive Benutzeroberfläche für Pipeline-Management
|
||||
- **Web-UI**: Intuitive Benutzeroberfläche für Pipeline-Management (nur lokal)
|
||||
- **Ansible-Integration**: Native Unterstützung für Ansible-Playbooks
|
||||
- **Docker-basiert**: Einfaches Setup und Wartung
|
||||
|
||||
@@ -26,7 +26,7 @@ Selbst-gehostete Semaphore CI/CD-Plattform für lokale Entwicklung, die es ermö
|
||||
## Voraussetzungen
|
||||
|
||||
- Docker und Docker Compose installiert
|
||||
- Port 3001 auf localhost frei verfügbar (3000 wird von Gitea verwendet)
|
||||
- Port 9300 auf localhost frei verfügbar (3000 wird von Gitea verwendet)
|
||||
- Ausreichend Speicherplatz für Docker Volumes (~500MB initial)
|
||||
|
||||
## Verzeichnisstruktur
|
||||
@@ -75,7 +75,9 @@ docker compose up -d
|
||||
|
||||
### 4. Semaphore Web-UI öffnen
|
||||
|
||||
Öffne im Browser: http://localhost:3001
|
||||
Öffne im Browser: **http://localhost:9300**
|
||||
|
||||
⚠️ **WICHTIG**: Semaphore ist NUR lokal zugänglich (127.0.0.1). Es gibt KEINEN externen Zugriff aus Sicherheitsgründen.
|
||||
|
||||
**Standard-Login**:
|
||||
- **Username**: `admin` (oder Wert aus `SEMAPHORE_ADMIN`)
|
||||
@@ -104,8 +106,8 @@ MYSQL_PASSWORD=semaphore
|
||||
#### Semaphore-Konfiguration
|
||||
|
||||
```env
|
||||
# Port-Binding (Standard: 3001)
|
||||
SEMAPHORE_PORT=3001
|
||||
# Port-Binding (Standard: 9300)
|
||||
SEMAPHORE_PORT=9300
|
||||
|
||||
# Admin-Benutzer
|
||||
SEMAPHORE_ADMIN=admin
|
||||
@@ -203,7 +205,7 @@ docker compose up -d
|
||||
|
||||
### 1. Projekt erstellen
|
||||
|
||||
1. Öffne http://localhost:3001 im Browser
|
||||
1. Öffne http://localhost:9300 im Browser
|
||||
2. Melde dich mit Admin-Credentials an
|
||||
3. Klicke auf "New Project"
|
||||
4. Gib einen Projektnamen ein (z.B. "My Project")
|
||||
@@ -289,9 +291,9 @@ Semaphore kann Docker-Images aus der lokalen Registry verwenden:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port-Konflikt (Port 3000 vs 3001)
|
||||
### Port-Konflikt (Port 3000 vs 9300)
|
||||
|
||||
**Problem**: Port 3000 ist standardmäßig von Gitea belegt, daher verwendet Semaphore Port 3001.
|
||||
**Problem**: Port 3000 ist standardmäßig von Gitea belegt, daher verwendet Semaphore Port 9300.
|
||||
|
||||
**Lösung**: Wenn du einen anderen Port verwenden möchtest, setze `SEMAPHORE_PORT` in der `.env` Datei:
|
||||
```env
|
||||
@@ -309,7 +311,7 @@ docker compose logs semaphore
|
||||
|
||||
**Häufige Ursachen**:
|
||||
- MySQL ist noch nicht bereit (warte auf Health-Check)
|
||||
- Port 3001 ist bereits belegt: `netstat -tuln | grep 3001` (oder auf Windows: `netstat -ano | findstr :3001`)
|
||||
- Port 9300 ist bereits belegt: `netstat -tuln | grep 9300` (oder auf Windows: `netstat -ano | findstr :9300`)
|
||||
- Falsche Datenbank-Credentials
|
||||
|
||||
**Lösung**:
|
||||
@@ -382,26 +384,44 @@ docker compose exec semaphore wget --no-verbose --spider http://localhost:3000/a
|
||||
|
||||
## Sicherheit
|
||||
|
||||
### Lokale Entwicklung (Aktuell)
|
||||
### 🔒 Lokaler Zugriff (Nur localhost)
|
||||
|
||||
- ✅ Nur localhost-Zugriff (127.0.0.1:3000)
|
||||
- ✅ Isoliertes Netzwerk (kein externer Zugriff)
|
||||
- ✅ Keine Traefik-Integration
|
||||
- ⚠️ Standard-Passwörter (nur für lokale Entwicklung)
|
||||
Semaphore ist absichtlich NUR lokal zugänglich aus Sicherheitsgründen:
|
||||
|
||||
### Für Produktion
|
||||
- ✅ **Nur localhost-Zugriff**: Port 9300 gebunden an 127.0.0.1 (nicht 0.0.0.0)
|
||||
- ✅ **Keine externen Netzwerke**: Kein externer Zugriff möglich
|
||||
- ✅ **Keine Traefik-Integration**: Keine öffentliche Route konfiguriert
|
||||
- ✅ **Isoliertes Netzwerk**: Nur internes Docker-Netzwerk
|
||||
- ⚠️ **Standard-Passwörter**: Nur für lokale Entwicklung - ändern bei Bedarf
|
||||
|
||||
Wenn du Semaphore später für Produktion nutzen willst:
|
||||
### ⚠️ KEINE Produktions-Nutzung über Internet!
|
||||
|
||||
1. **Starke Passwörter**: Ändere alle Passwörter in `.env`
|
||||
**Semaphore sollte NICHT öffentlich zugänglich gemacht werden!**
|
||||
|
||||
Gründe:
|
||||
1. **Sicherheitsrisiko**: Semaphore hat Zugriff auf SSH-Keys und Deployment-Credentials
|
||||
2. **Privilegierter Zugriff**: Kann auf Produktions-Server zugreifen
|
||||
3. **Keine Multi-Factor-Authentication**: Standardmäßig keine 2FA
|
||||
4. **Exploits**: Selbst-gehostete Software kann Sicherheitslücken haben
|
||||
|
||||
### Für lokale Entwicklung (Empfohlen)
|
||||
|
||||
Wenn du Semaphore lokal nutzen willst:
|
||||
|
||||
1. **Starke Passwörter**: Ändere alle Passwörter in `.env`:
|
||||
```env
|
||||
SEMAPHORE_ADMIN_PASSWORD=<starkes-passwort>
|
||||
MYSQL_PASSWORD=<starkes-passwort>
|
||||
MYSQL_ROOT_PASSWORD=<starkes-passwort>
|
||||
```
|
||||
2. **Encryption Key**: Generiere einen sicheren Key:
|
||||
```bash
|
||||
head -c32 /dev/urandom | base64
|
||||
```
|
||||
3. **Traefik-Integration**: Füge Traefik-Labels für HTTPS hinzu
|
||||
4. **LDAP/SSO**: Konfiguriere externe Authentifizierung
|
||||
5. **Backup-Strategie**: Regelmäßige MySQL-Backups einrichten
|
||||
6. **Resource Limits**: Füge Memory/CPU-Limits hinzu
|
||||
Setze in `.env`: `SEMAPHORE_ACCESS_KEY_ENCRYPTION=<generierter-key>`
|
||||
3. **SSH-Keys**: Stelle sicher, dass SSH-Keys sicher gespeichert sind
|
||||
4. **Backup-Strategie**: Regelmäßige MySQL-Backups einrichten
|
||||
5. **Resource Limits**: Füge Memory/CPU-Limits hinzu (optional)
|
||||
|
||||
## Wartung
|
||||
|
||||
@@ -537,7 +557,7 @@ docker compose logs --tail=100
|
||||
docker compose ps
|
||||
|
||||
# Semaphore-Health
|
||||
curl http://localhost:3001/api/health
|
||||
curl http://localhost:9300/api/health
|
||||
|
||||
# MySQL-Health
|
||||
docker compose exec mysql mysqladmin ping -h localhost -u root -psemaphore_root
|
||||
@@ -550,7 +570,7 @@ docker compose exec mysql mysqladmin ping -h localhost -u root -psemaphore_root
|
||||
**Nächste Schritte**:
|
||||
1. `cp env.example .env` ausführen
|
||||
2. `docker compose up -d` starten
|
||||
3. http://localhost:3001 öffnen
|
||||
3. http://localhost:9300 öffnen
|
||||
4. Mit Admin-Credentials anmelden
|
||||
5. Erstes Projekt und Template erstellen
|
||||
|
||||
|
||||
355
deployment/stacks/semaphore/SETUP_REPOSITORY.md
Normal file
355
deployment/stacks/semaphore/SETUP_REPOSITORY.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# Git-Repository in Semaphore Self-Hosted integrieren
|
||||
|
||||
Diese Anleitung beschreibt, wie du dein Git-Repository in Semaphore Self-Hosted (Ansible-UI) integrierst.
|
||||
|
||||
## ?? ?bersicht
|
||||
|
||||
Semaphore Self-Hosted ist eine Web-UI f?r Ansible, die es erm?glicht:
|
||||
- Ansible-Playbooks ?ber eine grafische Oberfl?che auszuf?hren
|
||||
- CI/CD-Workflows mit Ansible zu automatisieren
|
||||
- Git-Repositories als Playbook-Quellen zu verwenden
|
||||
|
||||
## ?? Schritt 1: Semaphore starten
|
||||
|
||||
### 1.1 Environment-Datei erstellen
|
||||
|
||||
```bash
|
||||
cd deployment/stacks/semaphore
|
||||
cp env.example .env
|
||||
```
|
||||
|
||||
### 1.2 Encryption Key generieren (WICHTIG!)
|
||||
|
||||
```bash
|
||||
# Linux/WSL
|
||||
head -c32 /dev/urandom | base64
|
||||
|
||||
# Windows PowerShell
|
||||
-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | % {[char]$_}) | ConvertTo-Base64
|
||||
```
|
||||
|
||||
Kopiere den generierten Key und setze ihn in `.env`:
|
||||
```env
|
||||
SEMAPHORE_ACCESS_KEY_ENCRYPTION=<dein-generierter-key>
|
||||
```
|
||||
|
||||
### 1.3 Stack starten
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### 1.4 Web-UI ?ffnen
|
||||
|
||||
?ffne im Browser: **http://localhost:9300**
|
||||
|
||||
?? **WICHTIG**: Semaphore ist NUR lokal zug?nglich (127.0.0.1). Es gibt KEINEN externen Zugriff aus Sicherheitsgr?nden!
|
||||
|
||||
**Standard-Login**:
|
||||
- Username: `admin`
|
||||
- Password: `admin`
|
||||
|
||||
## ?? Schritt 2: Projekt in Semaphore erstellen
|
||||
|
||||
### 2.1 Neues Projekt anlegen
|
||||
|
||||
1. Melde dich in Semaphore an
|
||||
2. Klicke auf **"New Project"** oder **"Create Project"**
|
||||
3. Gib einen Projektnamen ein: **"michaelschiemer"**
|
||||
4. Klicke auf **"Create"**
|
||||
|
||||
### 2.2 Git-Repository als Playbook-Quelle hinzuf?gen
|
||||
|
||||
Semaphore Self-Hosted kann Playbooks direkt aus Git-Repositories laden. Du hast zwei Optionen:
|
||||
|
||||
**Option A: Playbooks aus Repository-Clone verwenden** (Empfohlen)
|
||||
|
||||
1. Clone dein Repository lokal:
|
||||
```bash
|
||||
git clone https://git.michaelschiemer.de/michael/michaelschiemer.git
|
||||
cd michaelschiemer
|
||||
```
|
||||
|
||||
2. Erstelle ein Playbook-Verzeichnis f?r Semaphore:
|
||||
```bash
|
||||
mkdir -p deployment/stacks/semaphore/playbooks
|
||||
cp deployment/ansible/playbooks/*.yml deployment/stacks/semaphore/playbooks/
|
||||
```
|
||||
|
||||
**Option B: Playbooks direkt in Semaphore einf?gen**
|
||||
|
||||
Semaphore kann auch Playbooks direkt im Web-UI erstellen/bearbeiten. F?r Git-Integration ist Option A besser.
|
||||
|
||||
## ??? Schritt 3: Inventory erstellen
|
||||
|
||||
Ein Inventory definiert die Hosts, auf denen Playbooks ausgef?hrt werden.
|
||||
|
||||
### 3.1 Inventory anlegen
|
||||
|
||||
1. Gehe zu deinem Projekt ? **Inventories** ? **New Inventory**
|
||||
2. Name: **"Production Hosts"** oder **"Local Hosts"**
|
||||
3. Klicke auf **"Create"**
|
||||
|
||||
### 3.2 Host hinzuf?gen
|
||||
|
||||
1. Klicke auf dein Inventory ? **Add Host**
|
||||
2. F?lle folgende Felder aus:
|
||||
|
||||
**F?r Production-Deployment:**
|
||||
```
|
||||
Name: production
|
||||
Address: 94.16.110.151
|
||||
SSH Username: deploy
|
||||
SSH Port: 22
|
||||
```
|
||||
|
||||
**F?r Staging-Deployment:**
|
||||
```
|
||||
Name: staging
|
||||
Address: 94.16.110.151 # oder dein Staging-Host
|
||||
SSH Username: deploy
|
||||
SSH Port: 22
|
||||
```
|
||||
|
||||
### 3.3 SSH-Key hinzuf?gen
|
||||
|
||||
1. Gehe zu **Keys** (Seitenleiste)
|
||||
2. Klicke auf **"New Key"**
|
||||
3. Gib einen Namen ein: **"Deployment Key"**
|
||||
4. F?ge deinen SSH Private Key ein (aus `~/.ssh/production` oder ?hnlich)
|
||||
5. W?hle den Key im Host-Inventory aus
|
||||
|
||||
## ?? Schritt 4: Template erstellen
|
||||
|
||||
Templates verbinden Playbooks mit Inventories und definieren Parameter.
|
||||
|
||||
### 4.1 Template f?r Tests erstellen
|
||||
|
||||
1. Gehe zu **Templates** ? **New Template**
|
||||
2. Template-Name: **"Run PHP Tests"**
|
||||
3. Inventory: W?hle dein Inventory
|
||||
4. Playbook: Erstelle ein Playbook oder verwende ein vorhandenes:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Run PHP Tests
|
||||
hosts: localhost
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Checkout repository
|
||||
git:
|
||||
repo: https://git.michaelschiemer.de/michael/michaelschiemer.git
|
||||
dest: /tmp/ci-build
|
||||
version: main
|
||||
|
||||
- name: Install dependencies
|
||||
command: composer install --no-interaction --prefer-dist
|
||||
args:
|
||||
chdir: /tmp/ci-build
|
||||
|
||||
- name: Run tests
|
||||
command: ./vendor/bin/pest
|
||||
args:
|
||||
chdir: /tmp/ci-build
|
||||
register: test_result
|
||||
|
||||
- name: Show test results
|
||||
debug:
|
||||
var: test_result.stdout_lines
|
||||
```
|
||||
|
||||
5. Speichere das Template
|
||||
|
||||
### 4.2 Template f?r Build erstellen
|
||||
|
||||
Erstelle ein Template f?r Docker Image Build:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Build and Push Docker Image
|
||||
hosts: localhost
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- name: Checkout repository
|
||||
git:
|
||||
repo: https://git.michaelschiemer.de/michael/michaelschiemer.git
|
||||
dest: /tmp/ci-build
|
||||
version: main
|
||||
|
||||
- name: Login to Docker registry
|
||||
docker_login:
|
||||
username: "{{ registry_user }}"
|
||||
password: "{{ registry_password }}"
|
||||
registry_url: "{{ registry_url }}"
|
||||
vars:
|
||||
registry_user: "admin"
|
||||
registry_url: "registry.michaelschiemer.de"
|
||||
|
||||
- name: Build Docker image
|
||||
docker_image:
|
||||
name: "{{ registry_url }}/framework:{{ image_tag }}"
|
||||
tag: "{{ image_tag }}"
|
||||
source: build
|
||||
build:
|
||||
path: /tmp/ci-build
|
||||
dockerfile: Dockerfile.production
|
||||
push: yes
|
||||
vars:
|
||||
registry_url: "registry.michaelschiemer.de"
|
||||
image_tag: "latest"
|
||||
```
|
||||
|
||||
### 4.3 Template f?r Deployment erstellen
|
||||
|
||||
Erstelle ein Template f?r Production-Deployment (verwendet die vorhandenen Ansible-Playbooks):
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Deploy to Production
|
||||
hosts: production
|
||||
gather_facts: yes
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Checkout deployment scripts
|
||||
git:
|
||||
repo: https://git.michaelschiemer.de/michael/michaelschiemer.git
|
||||
dest: /tmp/deployment
|
||||
version: main
|
||||
|
||||
- name: Include deployment playbook
|
||||
include_role:
|
||||
name: deployment/ansible/playbooks/deploy-update.yml
|
||||
```
|
||||
|
||||
Oder verwende die vorhandenen Ansible-Playbooks direkt:
|
||||
|
||||
**Vorteil**: Die vorhandenen Playbooks in `deployment/ansible/playbooks/` k?nnen direkt verwendet werden!
|
||||
|
||||
1. Erstelle ein Template: **"Deploy to Production"**
|
||||
2. W?hle Inventory: **"Production Hosts"**
|
||||
3. Playbook-Pfad: **`deployment/ansible/playbooks/deploy-update.yml`**
|
||||
4. Speichere
|
||||
|
||||
## ?? Schritt 5: CI/CD-Workflow einrichten
|
||||
|
||||
### 5.1 Git-Webhook konfigurieren (Optional)
|
||||
|
||||
Semaphore kann Webhooks von Git-Repositories empfangen, um automatisch Tasks zu starten.
|
||||
|
||||
1. Gehe zu deinem Git-Repository (Gitea)
|
||||
2. Settings ? Webhooks ? Add Webhook
|
||||
3. Webhook-URL: `http://localhost:9300/api/hook/git` (nur lokal!)
|
||||
4. Content-Type: `application/json`
|
||||
5. Secret: Optional, aber empfohlen
|
||||
|
||||
### 5.2 Task manuell starten
|
||||
|
||||
1. Gehe zu **Templates**
|
||||
2. Klicke auf dein Template (z.B. "Run PHP Tests")
|
||||
3. Klicke auf **"Run"**
|
||||
4. Beobachte die Ausf?hrung in Echtzeit
|
||||
|
||||
### 5.3 Task automatisch starten
|
||||
|
||||
Semaphore kann Tasks basierend auf Git-Events starten:
|
||||
|
||||
1. Gehe zu Template ? **Settings**
|
||||
2. Aktiviere **"Auto Run on Push"**
|
||||
3. W?hle Branch: `main` oder `staging`
|
||||
4. Speichere
|
||||
|
||||
## ?? Verwendung vorhandener Ansible-Playbooks
|
||||
|
||||
### Vorhandene Playbooks verwenden
|
||||
|
||||
Die vorhandenen Ansible-Playbooks in `deployment/ansible/playbooks/` k?nnen direkt in Semaphore verwendet werden:
|
||||
|
||||
1. **Mounte Playbooks als Volume** (in `docker-compose.yml`):
|
||||
```yaml
|
||||
volumes:
|
||||
- ./../../ansible/playbooks:/tmp/semaphore/playbooks:ro
|
||||
```
|
||||
|
||||
2. **Erstelle Templates**, die auf diese Playbooks verweisen:
|
||||
- Playbook-Pfad: `/tmp/semaphore/playbooks/deploy-update.yml`
|
||||
- Inventory: W?hle dein Production-Inventory
|
||||
|
||||
### Beispiel-Templates
|
||||
|
||||
#### Template: Deploy Update
|
||||
- Name: "Deploy Update"
|
||||
- Inventory: "Production Hosts"
|
||||
- Playbook: `/tmp/semaphore/playbooks/deploy-update.yml`
|
||||
- Variables:
|
||||
```yaml
|
||||
registry_url: registry.michaelschiemer.de
|
||||
image_name: framework
|
||||
image_tag: latest
|
||||
```
|
||||
|
||||
#### Template: Rollback
|
||||
- Name: "Rollback"
|
||||
- Inventory: "Production Hosts"
|
||||
- Playbook: `/tmp/semaphore/playbooks/rollback.yml`
|
||||
- Variables:
|
||||
```yaml
|
||||
registry_url: registry.michaelschiemer.de
|
||||
image_name: framework
|
||||
```
|
||||
|
||||
## ?? Erweiterte Konfiguration
|
||||
|
||||
### Docker Socket f?r Build-Tasks
|
||||
|
||||
F?r Docker-Build-Tasks muss der Semaphore-Container Zugriff auf den Docker-Socket haben:
|
||||
|
||||
**In `docker-compose.yml` hinzuf?gen:**
|
||||
```yaml
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
```
|
||||
|
||||
### Environment-Variablen f?r Templates
|
||||
|
||||
Du kannst Environment-Variablen in Templates verwenden:
|
||||
|
||||
1. Gehe zu Template ? **Variables**
|
||||
2. F?ge Variablen hinzu:
|
||||
- `registry_url`: `registry.michaelschiemer.de`
|
||||
- `registry_user`: `admin`
|
||||
- `image_name`: `framework`
|
||||
|
||||
### Git-Integration mit SSH-Keys
|
||||
|
||||
F?r private Repositories:
|
||||
|
||||
1. Gehe zu **Keys**
|
||||
2. Erstelle einen SSH-Key f?r Git-Access
|
||||
3. F?ge den Public Key zu deinem Git-Repository hinzu (Deploy Keys)
|
||||
4. Verwende SSH-URL in Git-Tasks:
|
||||
```yaml
|
||||
git:
|
||||
repo: git@git.michaelschiemer.de:michael/michaelschiemer.git
|
||||
```
|
||||
|
||||
## ?? N?tzliche Links
|
||||
|
||||
- [Semaphore Self-Hosted Dokumentation](https://docs.ansible-semaphore.com/)
|
||||
- [Ansible Semaphore GitHub](https://github.com/ansible-semaphore/semaphore)
|
||||
- [Ansible Playbook Dokumentation](https://docs.ansible.com/ansible/latest/playbook_guide/index.html)
|
||||
|
||||
## ? Checkliste
|
||||
|
||||
- [ ] Semaphore gestartet (http://localhost:9300)
|
||||
- [ ] Projekt erstellt
|
||||
- [ ] Inventory mit Hosts erstellt
|
||||
- [ ] SSH-Keys f?r Host-Zugriff konfiguriert
|
||||
- [ ] Template f?r Tests erstellt
|
||||
- [ ] Template f?r Build erstellt
|
||||
- [ ] Template f?r Deployment erstellt
|
||||
- [ ] Erstes Template erfolgreich ausgef?hrt
|
||||
- [ ] Git-Webhook konfiguriert (optional)
|
||||
|
||||
---
|
||||
|
||||
**N?chste Schritte**: Starte Semaphore und erstelle dein erstes Template!
|
||||
@@ -38,24 +38,12 @@ services:
|
||||
networks:
|
||||
- semaphore-internal
|
||||
ports:
|
||||
# Only bind to localhost, not external interfaces
|
||||
# Default port 3001 to avoid conflict with Gitea (port 3000)
|
||||
- "127.0.0.1:${SEMAPHORE_PORT:-3001}:3000"
|
||||
labels:
|
||||
# Traefik configuration
|
||||
- "traefik.enable=true"
|
||||
# HTTP Router (redirects to HTTPS)
|
||||
- "traefik.http.routers.semaphore.rule=Host(`semaphore.michaelschiemer.de`)"
|
||||
- "traefik.http.routers.semaphore.entrypoints=web"
|
||||
- "traefik.http.routers.semaphore.middlewares=redirect-to-https"
|
||||
# HTTPS Router
|
||||
- "traefik.http.routers.semaphore-secure.rule=Host(`semaphore.michaelschiemer.de`)"
|
||||
- "traefik.http.routers.semaphore-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.semaphore-secure.tls=true"
|
||||
- "traefik.http.routers.semaphore-secure.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.semaphore-secure.service=semaphore"
|
||||
# Service definition (use localhost port binding)
|
||||
# Note: Dynamic config in traefik/dynamic/semaphore.yml takes precedence
|
||||
# ONLY bind to localhost (127.0.0.1) - NOT accessible externally!
|
||||
# Default port 9300 to avoid conflict with Gitea (port 3000)
|
||||
# SECURITY: This ensures Semaphore is only accessible locally
|
||||
- "127.0.0.1:${SEMAPHORE_PORT:-9300}:3000"
|
||||
# NO Traefik labels - Semaphore should only be accessible locally!
|
||||
# External access is disabled for security reasons.
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
# Database Configuration
|
||||
@@ -80,6 +68,9 @@ services:
|
||||
- SEMAPHORE_WEBHOOK_URL=${SEMAPHORE_WEBHOOK_URL:-}
|
||||
volumes:
|
||||
- semaphore-data:/etc/semaphore
|
||||
# Mount playbooks from repository so Semaphore can access them
|
||||
- ../../../deployment/stacks/semaphore/playbooks:/tmp/semaphore/playbooks:ro
|
||||
- ../../../deployment/ansible/playbooks:/tmp/semaphore/repo-playbooks:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
healthcheck:
|
||||
|
||||
@@ -14,10 +14,10 @@ MYSQL_PASSWORD=semaphore
|
||||
# Semaphore Configuration
|
||||
# ============================================
|
||||
|
||||
# Port binding (default: 3001)
|
||||
# Port binding (default: 9300)
|
||||
# Only accessible via localhost (127.0.0.1)
|
||||
# Note: Changed from 3000 to avoid conflict with Gitea
|
||||
SEMAPHORE_PORT=3001
|
||||
SEMAPHORE_PORT=9300
|
||||
|
||||
# Admin User Configuration
|
||||
SEMAPHORE_ADMIN=admin
|
||||
|
||||
103
deployment/stacks/semaphore/playbooks/ci-tests.yml
Normal file
103
deployment/stacks/semaphore/playbooks/ci-tests.yml
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
# CI Tests Playbook f?r Semaphore
|
||||
# F?hrt PHP Tests und Quality Checks aus
|
||||
|
||||
- name: Run CI Tests and Quality Checks
|
||||
hosts: localhost
|
||||
gather_facts: no
|
||||
vars:
|
||||
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
|
||||
repo_branch: "{{ repo_branch | default('main') }}"
|
||||
build_dir: "/tmp/ci-build"
|
||||
|
||||
tasks:
|
||||
- name: Clean up build directory
|
||||
file:
|
||||
path: "{{ build_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Checkout repository
|
||||
git:
|
||||
repo: "{{ repo_url }}"
|
||||
dest: "{{ build_dir }}"
|
||||
version: "{{ repo_branch }}"
|
||||
force: yes
|
||||
register: git_result
|
||||
|
||||
- name: Display checked out commit
|
||||
debug:
|
||||
msg: "Checked out commit: {{ git_result.after }}"
|
||||
|
||||
- name: Install Composer if not present
|
||||
get_url:
|
||||
url: https://getcomposer.org/installer
|
||||
dest: /tmp/composer-installer.php
|
||||
mode: '0755'
|
||||
when: ansible_facts.os_family == "Debian"
|
||||
|
||||
- name: Install Composer (Debian)
|
||||
shell: php /tmp/composer-installer.php && mv composer.phar /usr/local/bin/composer
|
||||
when: ansible_facts.os_family == "Debian"
|
||||
args:
|
||||
creates: /usr/local/bin/composer
|
||||
|
||||
- name: Install PHP dependencies
|
||||
command: composer install --no-interaction --prefer-dist --optimize-autoloader --ignore-platform-req=php
|
||||
args:
|
||||
chdir: "{{ build_dir }}"
|
||||
register: composer_result
|
||||
|
||||
- name: Display composer output
|
||||
debug:
|
||||
var: composer_result.stdout_lines
|
||||
when: composer_result.stdout_lines is defined
|
||||
|
||||
- name: Run PHP tests
|
||||
command: ./vendor/bin/pest
|
||||
args:
|
||||
chdir: "{{ build_dir }}"
|
||||
register: test_result
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display test results
|
||||
debug:
|
||||
msg: "{{ test_result.stdout_lines }}"
|
||||
when: test_result.stdout_lines is defined
|
||||
|
||||
- name: Run PHPStan
|
||||
command: composer phpstan
|
||||
args:
|
||||
chdir: "{{ build_dir }}"
|
||||
register: phpstan_result
|
||||
ignore_errors: yes
|
||||
when: test_result.rc == 0
|
||||
|
||||
- name: Display PHPStan results
|
||||
debug:
|
||||
msg: "{{ phpstan_result.stdout_lines }}"
|
||||
when: phpstan_result.stdout_lines is defined and phpstan_result.rc == 0
|
||||
|
||||
- name: Run code style check
|
||||
command: composer cs
|
||||
args:
|
||||
chdir: "{{ build_dir }}"
|
||||
register: cs_result
|
||||
ignore_errors: yes
|
||||
when: test_result.rc == 0
|
||||
|
||||
- name: Display code style results
|
||||
debug:
|
||||
msg: "{{ cs_result.stdout_lines }}"
|
||||
when: cs_result.stdout_lines is defined
|
||||
|
||||
- name: Fail if tests failed
|
||||
fail:
|
||||
msg: "Tests failed! Check output above."
|
||||
when: test_result.rc != 0
|
||||
|
||||
- name: Summary
|
||||
debug:
|
||||
msg:
|
||||
- "? CI Tests completed successfully!"
|
||||
- "Commit: {{ git_result.after }}"
|
||||
- "Branch: {{ repo_branch }}"
|
||||
129
deployment/stacks/semaphore/playbooks/deploy-production.yml
Normal file
129
deployment/stacks/semaphore/playbooks/deploy-production.yml
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
# Production Deployment Playbook f?r Semaphore
|
||||
# Deployed die Anwendung auf Production-Server
|
||||
|
||||
- name: Deploy to Production
|
||||
hosts: production
|
||||
gather_facts: yes
|
||||
become: yes
|
||||
vars:
|
||||
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
|
||||
image_name: "{{ image_name | default('framework') }}"
|
||||
image_tag: "{{ image_tag | default('latest') }}"
|
||||
registry_user: "{{ registry_user | default('admin') }}"
|
||||
registry_password: "{{ registry_password | required }}"
|
||||
deployment_path: "{{ deployment_path | default('~/deployment/stacks/application') }}"
|
||||
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
|
||||
|
||||
tasks:
|
||||
- name: Display deployment info
|
||||
debug:
|
||||
msg:
|
||||
- "?? Starting production deployment..."
|
||||
- "Host: {{ inventory_hostname }}"
|
||||
- "Registry: {{ registry_url }}"
|
||||
- "Image: {{ image_name }}:{{ image_tag }}"
|
||||
- "Path: {{ deployment_path }}"
|
||||
|
||||
- name: Ensure deployment directory exists
|
||||
file:
|
||||
path: "{{ deployment_path }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Login to Docker registry
|
||||
docker_login:
|
||||
username: "{{ registry_user }}"
|
||||
password: "{{ registry_password }}"
|
||||
registry_url: "{{ registry_url }}"
|
||||
|
||||
- name: Pull Docker image
|
||||
docker_image:
|
||||
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||
source: pull
|
||||
|
||||
- name: Check if docker-compose files exist
|
||||
stat:
|
||||
path: "{{ deployment_path }}/docker-compose.base.yml"
|
||||
register: base_compose_exists
|
||||
|
||||
- name: Check if docker-compose.production.yml exists
|
||||
stat:
|
||||
path: "{{ deployment_path }}/docker-compose.production.yml"
|
||||
register: production_compose_exists
|
||||
|
||||
- name: Copy docker-compose files if missing
|
||||
copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ deployment_path }}/{{ item.dest }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "docker-compose.base.yml", dest: "docker-compose.base.yml" }
|
||||
- { src: "docker-compose.production.yml", dest: "docker-compose.production.yml" }
|
||||
when: not (item.dest == "docker-compose.base.yml" and base_compose_exists.stat.exists) or
|
||||
not (item.dest == "docker-compose.production.yml" and production_compose_exists.stat.exists)
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Update docker-compose.production.yml with new image
|
||||
replace:
|
||||
path: "{{ deployment_path }}/docker-compose.production.yml"
|
||||
regexp: 'image:.*{{ image_name }}:.*'
|
||||
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
|
||||
|
||||
- name: Update docker-compose.production.yml with new image (alternative format)
|
||||
replace:
|
||||
path: "{{ deployment_path }}/docker-compose.production.yml"
|
||||
regexp: 'image:.*{{ image_name }}@.*'
|
||||
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
|
||||
|
||||
- name: Ensure Docker networks exist
|
||||
docker_network:
|
||||
name: traefik-public
|
||||
state: present
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Deploy services
|
||||
docker_compose_v2:
|
||||
project_src: "{{ deployment_path }}"
|
||||
files:
|
||||
- "{{ deployment_path }}/docker-compose.base.yml"
|
||||
- "{{ deployment_path }}/docker-compose.production.yml"
|
||||
pull: missing
|
||||
state: present
|
||||
recreate: always
|
||||
|
||||
- name: Wait for services to start
|
||||
pause:
|
||||
seconds: 15
|
||||
|
||||
- name: Check container status
|
||||
command: docker compose -f {{ deployment_path }}/docker-compose.base.yml -f {{ deployment_path }}/docker-compose.production.yml ps
|
||||
register: container_status
|
||||
|
||||
- name: Display container status
|
||||
debug:
|
||||
var: container_status.stdout_lines
|
||||
|
||||
- name: Health check
|
||||
uri:
|
||||
url: https://michaelschiemer.de/health
|
||||
method: GET
|
||||
status_code: [200, 201, 204]
|
||||
validate_certs: no
|
||||
register: health_result
|
||||
retries: 10
|
||||
delay: 10
|
||||
until: health_result.status == 200
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display health check result
|
||||
debug:
|
||||
msg: "? Health check passed!" if health_result.status == 200 else "?? Health check failed"
|
||||
|
||||
- name: Summary
|
||||
debug:
|
||||
msg:
|
||||
- "? Production deployment completed!"
|
||||
- "Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||
- "URL: https://michaelschiemer.de"
|
||||
110
deployment/stacks/semaphore/playbooks/deploy-staging.yml
Normal file
110
deployment/stacks/semaphore/playbooks/deploy-staging.yml
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
# Staging Deployment Playbook f?r Semaphore
|
||||
# Deployed die Anwendung auf Staging-Server
|
||||
|
||||
- name: Deploy to Staging
|
||||
hosts: staging
|
||||
gather_facts: yes
|
||||
become: yes
|
||||
vars:
|
||||
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
|
||||
image_name: "{{ image_name | default('framework') }}"
|
||||
image_tag: "{{ image_tag | default('latest') }}"
|
||||
registry_user: "{{ registry_user | default('admin') }}"
|
||||
registry_password: "{{ registry_password | required }}"
|
||||
deployment_path: "{{ deployment_path | default('~/deployment/stacks/staging') }}"
|
||||
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
|
||||
|
||||
tasks:
|
||||
- name: Display deployment info
|
||||
debug:
|
||||
msg:
|
||||
- "?? Starting staging deployment..."
|
||||
- "Host: {{ inventory_hostname }}"
|
||||
- "Registry: {{ registry_url }}"
|
||||
- "Image: {{ image_name }}:{{ image_tag }}"
|
||||
- "Path: {{ deployment_path }}"
|
||||
|
||||
- name: Ensure deployment directory exists
|
||||
file:
|
||||
path: "{{ deployment_path }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Login to Docker registry
|
||||
docker_login:
|
||||
username: "{{ registry_user }}"
|
||||
password: "{{ registry_password }}"
|
||||
registry_url: "{{ registry_url }}"
|
||||
|
||||
- name: Pull Docker image
|
||||
docker_image:
|
||||
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||
source: pull
|
||||
|
||||
- name: Check if docker-compose files exist
|
||||
stat:
|
||||
path: "{{ deployment_path }}/docker-compose.base.yml"
|
||||
register: base_compose_exists
|
||||
|
||||
- name: Check if docker-compose.staging.yml exists
|
||||
stat:
|
||||
path: "{{ deployment_path }}/docker-compose.staging.yml"
|
||||
register: staging_compose_exists
|
||||
|
||||
- name: Copy docker-compose files if missing
|
||||
copy:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ deployment_path }}/{{ item.dest }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: "docker-compose.base.yml", dest: "docker-compose.base.yml" }
|
||||
- { src: "docker-compose.staging.yml", dest: "docker-compose.staging.yml" }
|
||||
when: not (item.dest == "docker-compose.base.yml" and base_compose_exists.stat.exists) or
|
||||
not (item.dest == "docker-compose.staging.yml" and staging_compose_exists.stat.exists)
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Update docker-compose.staging.yml with new image
|
||||
replace:
|
||||
path: "{{ deployment_path }}/docker-compose.staging.yml"
|
||||
regexp: 'image:.*{{ image_name }}:.*'
|
||||
replace: 'image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}'
|
||||
|
||||
- name: Ensure Docker networks exist
|
||||
docker_network:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- traefik-public
|
||||
- staging-internal
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Deploy services
|
||||
docker_compose_v2:
|
||||
project_src: "{{ deployment_path }}"
|
||||
files:
|
||||
- "{{ deployment_path }}/docker-compose.base.yml"
|
||||
- "{{ deployment_path }}/docker-compose.staging.yml"
|
||||
pull: missing
|
||||
state: present
|
||||
recreate: always
|
||||
|
||||
- name: Wait for services to start
|
||||
pause:
|
||||
seconds: 15
|
||||
|
||||
- name: Check container status
|
||||
command: docker compose -f {{ deployment_path }}/docker-compose.base.yml -f {{ deployment_path }}/docker-compose.staging.yml ps
|
||||
register: container_status
|
||||
|
||||
- name: Display container status
|
||||
debug:
|
||||
var: container_status.stdout_lines
|
||||
|
||||
- name: Summary
|
||||
debug:
|
||||
msg:
|
||||
- "? Staging deployment completed!"
|
||||
- "Image: {{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||
- "URL: https://staging.michaelschiemer.de"
|
||||
103
deployment/stacks/semaphore/playbooks/docker-build.yml
Normal file
103
deployment/stacks/semaphore/playbooks/docker-build.yml
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
# Docker Build Playbook f?r Semaphore
|
||||
# Baut Docker Image und pusht es zur Registry
|
||||
|
||||
- name: Build and Push Docker Image
|
||||
hosts: localhost
|
||||
gather_facts: no
|
||||
vars:
|
||||
repo_url: "{{ repo_url | default('https://git.michaelschiemer.de/michael/michaelschiemer.git') }}"
|
||||
repo_branch: "{{ repo_branch | default('main') }}"
|
||||
build_dir: "/tmp/ci-build"
|
||||
registry_url: "{{ registry_url | default('registry.michaelschiemer.de') }}"
|
||||
image_name: "{{ image_name | default('framework') }}"
|
||||
image_tag: "{{ image_tag | default('latest') }}"
|
||||
registry_user: "{{ registry_user | default('admin') }}"
|
||||
registry_password: "{{ registry_password | required }}"
|
||||
|
||||
tasks:
|
||||
- name: Clean up build directory
|
||||
file:
|
||||
path: "{{ build_dir }}"
|
||||
state: absent
|
||||
|
||||
- name: Checkout repository
|
||||
git:
|
||||
repo: "{{ repo_url }}"
|
||||
dest: "{{ build_dir }}"
|
||||
version: "{{ repo_branch }}"
|
||||
force: yes
|
||||
register: git_result
|
||||
|
||||
- name: Get short commit SHA
|
||||
shell: echo "{{ git_result.after }}" | cut -c1-7
|
||||
register: short_sha
|
||||
|
||||
- name: Generate image tags
|
||||
set_fact:
|
||||
tags:
|
||||
- "{{ registry_url }}/{{ image_name }}:latest"
|
||||
- "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||
- "{{ registry_url }}/{{ image_name }}:git-{{ short_sha.stdout }}"
|
||||
|
||||
- name: Display image tags
|
||||
debug:
|
||||
msg:
|
||||
- "??? Building Docker image..."
|
||||
- "Registry: {{ registry_url }}"
|
||||
- "Image: {{ image_name }}"
|
||||
- "Tags: {{ tags | join(', ') }}"
|
||||
|
||||
- name: Ensure Docker is available
|
||||
command: docker --version
|
||||
register: docker_version
|
||||
|
||||
- name: Display Docker version
|
||||
debug:
|
||||
msg: "Docker version: {{ docker_version.stdout }}"
|
||||
|
||||
- name: Login to Docker registry
|
||||
docker_login:
|
||||
username: "{{ registry_user }}"
|
||||
password: "{{ registry_password }}"
|
||||
registry_url: "{{ registry_url }}"
|
||||
register: login_result
|
||||
|
||||
- name: Verify registry login
|
||||
debug:
|
||||
msg: "? Successfully logged in to {{ registry_url }}"
|
||||
when: login_result.failed == false
|
||||
|
||||
- name: Build Docker image
|
||||
docker_image:
|
||||
name: "{{ registry_url }}/{{ image_name }}"
|
||||
tag: "{{ image_tag }}"
|
||||
source: build
|
||||
build:
|
||||
path: "{{ build_dir }}"
|
||||
dockerfile: Dockerfile.production
|
||||
push: yes
|
||||
state: present
|
||||
register: build_result
|
||||
|
||||
- name: Tag image with additional tags
|
||||
docker_image:
|
||||
name: "{{ registry_url }}/{{ image_name }}:{{ image_tag }}"
|
||||
repository: "{{ registry_url }}/{{ image_name }}"
|
||||
tag: "{{ item }}"
|
||||
source: local
|
||||
push: yes
|
||||
state: present
|
||||
loop:
|
||||
- "latest"
|
||||
- "git-{{ short_sha.stdout }}"
|
||||
when: build_result.changed
|
||||
|
||||
- name: Summary
|
||||
debug:
|
||||
msg:
|
||||
- "? Docker image built and pushed successfully!"
|
||||
- "Registry: {{ registry_url }}"
|
||||
- "Image: {{ image_name }}"
|
||||
- "Tags: {{ tags | join(', ') }}"
|
||||
- "Commit: {{ git_result.after }}"
|
||||
@@ -16,6 +16,63 @@ Traefik acts as the central reverse proxy for all services, handling:
|
||||
- Zus?tzlich durch BasicAuth gesch?tzt
|
||||
- ?ffentlicher Zugriff ist blockiert
|
||||
|
||||
## Local Development
|
||||
|
||||
For local development, use the separate local configuration to avoid port conflicts and Let's Encrypt errors:
|
||||
|
||||
### Quick Start (Local Development)
|
||||
|
||||
```bash
|
||||
# Ensure Docker network exists
|
||||
docker network create traefik-public 2>/dev/null || true
|
||||
|
||||
# Start Traefik with local configuration
|
||||
cd deployment/stacks/traefik
|
||||
docker compose -f docker-compose.local.yml up -d
|
||||
|
||||
# Check logs
|
||||
docker compose -f docker-compose.local.yml logs -f
|
||||
|
||||
# Access dashboard at http://localhost:8080/dashboard/
|
||||
```
|
||||
|
||||
### Local Development Configuration
|
||||
|
||||
The local configuration (`docker-compose.local.yml` and `traefik.local.yml`) differs from production:
|
||||
|
||||
- **Bridge network** instead of `host` mode (avoids port conflicts)
|
||||
- **Port mappings**: `8080:80` only (HTTP-only for local development)
|
||||
- Note: HTTPS not needed locally - avoids port conflicts with web container (8443:443)
|
||||
- **HTTP-only** (no ACME/Let's Encrypt) for local development
|
||||
- **Dashboard**: Accessible at `http://localhost:8080/dashboard/` (HTTP, no authentication)
|
||||
- Also available: `http://localhost:8080/api/rawdata` and `http://localhost:8080/api/http/routers`
|
||||
- **No `acme.json`** required
|
||||
- **Console logging** (human-readable) instead of JSON file logs
|
||||
|
||||
### Local Development vs Production
|
||||
|
||||
| Feature | Local (`docker-compose.local.yml`) | Production (`docker-compose.yml`) |
|
||||
|---------|-----------------------------------|----------------------------------|
|
||||
| Network Mode | Bridge | Host |
|
||||
| Ports | 8080:80 (HTTP only) | Direct binding (80, 443) |
|
||||
| SSL/TLS | HTTP-only | HTTPS with Let's Encrypt |
|
||||
| Dashboard | `http://localhost:8080/dashboard/` | `https://traefik.michaelschiemer.de` |
|
||||
| Authentication | None (local dev) | VPN + BasicAuth |
|
||||
| Logging | Console (human-readable) | JSON files |
|
||||
| ACME | Disabled | Enabled |
|
||||
|
||||
### Troubleshooting Local Development
|
||||
|
||||
**Container restarts in loop:**
|
||||
- Check if port 8080 is already in use: `netstat -tlnp | grep ':8080' || ss -tlnp | grep ':8080'`
|
||||
- Verify Docker network exists: `docker network ls | grep traefik-public`
|
||||
- Check logs: `docker compose -f docker-compose.local.yml logs -f traefik`
|
||||
|
||||
**Services not accessible through Traefik:**
|
||||
- Ensure services are on `traefik-public` network
|
||||
- Verify Traefik labels are correctly configured
|
||||
- Check that services are running: `docker compose ps`
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Docker Network**
|
||||
|
||||
59
deployment/stacks/traefik/docker-compose.local.yml
Normal file
59
deployment/stacks/traefik/docker-compose.local.yml
Normal file
@@ -0,0 +1,59 @@
|
||||
# Local Development Configuration for Traefik
|
||||
# Usage: docker compose -f docker-compose.local.yml up -d
|
||||
#
|
||||
# This configuration is optimized for local development:
|
||||
# - Bridge network instead of host mode
|
||||
# - Port mapping: 8080:80 (HTTP only - HTTPS not needed for local dev)
|
||||
# Note: 8443:443 is used by the web container, and we don't need HTTPS for Traefik locally
|
||||
# - No ACME/Let's Encrypt (HTTP-only)
|
||||
# - Simplified healthcheck
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: traefik-local
|
||||
restart: unless-stopped
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
# Use bridge network for local development (avoids port conflicts)
|
||||
# Ports 80/443 might be in use by other services
|
||||
# For local development, we only use HTTP (no HTTPS needed)
|
||||
# Note: 8443:443 is used by the web container
|
||||
ports:
|
||||
- "8080:80" # HTTP on port 80 (mapped to host port 8080)
|
||||
- "8080:8080" # Traefik API entrypoint (for api.insecure=true dashboard)
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
volumes:
|
||||
# Docker socket for service discovery
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
# Static configuration for local development
|
||||
- ./traefik.local.yml:/traefik.yml:ro
|
||||
# Dynamic configuration (shared with production config)
|
||||
# Note: These configs reference letsencrypt resolver which we don't configure locally
|
||||
# This will cause harmless errors in logs but won't break functionality
|
||||
- ./dynamic:/dynamic:ro
|
||||
networks:
|
||||
- traefik-public
|
||||
labels:
|
||||
# Note: With api.insecure=true, Traefik should automatically serve the dashboard
|
||||
# at /dashboard/ and /api/ without needing router labels.
|
||||
# However, if this doesn't work in bridge network mode, we may need explicit routing.
|
||||
# For now, we'll try without labels and see if api.insecure=true works directly.
|
||||
- "traefik.enable=true"
|
||||
healthcheck:
|
||||
# Use wget or curl to check Traefik ping endpoint
|
||||
# The ping endpoint is configured in traefik.local.yml on the 'web' entrypoint
|
||||
# Try ping endpoint first, if that fails, try API endpoint
|
||||
test: ["CMD-SHELL", "wget --quiet --spider http://localhost:80/ping || wget --quiet --spider http://localhost:80/api/rawdata || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
# Create this network if it doesn't exist:
|
||||
# docker network create traefik-public
|
||||
|
||||
98
deployment/stacks/traefik/traefik.local.yml
Normal file
98
deployment/stacks/traefik/traefik.local.yml
Normal file
@@ -0,0 +1,98 @@
|
||||
# Local Development Configuration for Traefik
|
||||
# This configuration is optimized for local development without Let's Encrypt/ACME
|
||||
|
||||
# Global Configuration
|
||||
global:
|
||||
checkNewVersion: true
|
||||
sendAnonymousUsage: false
|
||||
|
||||
# API and Dashboard
|
||||
# For local development, we enable insecure access on port 8080
|
||||
# Dashboard automatically accessible at:
|
||||
# - http://localhost:8080/dashboard/ (with trailing slash)
|
||||
# - http://localhost:8080/api/rawdata
|
||||
# - http://localhost:8080/api/http/routers
|
||||
api:
|
||||
dashboard: true
|
||||
insecure: true # HTTP-only for local development - enables direct dashboard access
|
||||
# Note: With insecure=true, dashboard is accessible at:
|
||||
# - http://localhost:8080/dashboard/
|
||||
# - http://localhost:8080/api/rawdata
|
||||
# - http://localhost:8080/api/http/routers
|
||||
# The insecure mode works directly on the entrypoint (web) without needing router labels
|
||||
|
||||
# Entry Points
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
# No redirects for local development - HTTP is acceptable
|
||||
|
||||
websecure:
|
||||
address: ":443"
|
||||
# Note: Even though we don't use HTTPS locally, we need this entrypoint
|
||||
# because dynamic configurations (gitea.yml, semaphore.yml) reference it
|
||||
# We use HTTP only, but the entrypoint must exist to avoid errors
|
||||
|
||||
traefik:
|
||||
address: ":8080"
|
||||
# This entrypoint is used by api.insecure=true for dashboard access
|
||||
# It must be on port 8080 (which maps to host port 8080) to match our port mapping
|
||||
|
||||
# Certificate Resolvers
|
||||
# Note: For local development, we don't configure ACME/Let's Encrypt
|
||||
# Dynamic configs (gitea.yml, semaphore.yml) that reference letsencrypt will show errors
|
||||
# but won't break Traefik functionality. We can ignore these errors for local dev.
|
||||
# If you need to test with real certificates locally, configure ACME manually.
|
||||
# certificatesResolvers:
|
||||
# letsencrypt:
|
||||
# acme:
|
||||
# email: your-email@example.com
|
||||
# storage: /tmp/acme.json
|
||||
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
||||
# Providers
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "unix:///var/run/docker.sock"
|
||||
exposedByDefault: false
|
||||
# Use Docker bridge network for local development
|
||||
network: traefik-public
|
||||
watch: true
|
||||
|
||||
file:
|
||||
directory: /dynamic
|
||||
watch: true
|
||||
# Note: Dynamic configs (gitea.yml, semaphore.yml) will show errors
|
||||
# because they reference letsencrypt resolver which we don't configure locally
|
||||
# These errors are harmless and won't affect local development
|
||||
|
||||
# Forwarded Headers Configuration
|
||||
# Simplified for local development
|
||||
forwardedHeaders:
|
||||
trustedIPs:
|
||||
- "127.0.0.1/32" # Localhost
|
||||
- "172.17.0.0/16" # Docker bridge network
|
||||
- "172.18.0.0/16" # Docker user-defined networks
|
||||
insecure: true # Allow insecure forwarded headers for local dev
|
||||
|
||||
# Logging - Console output for local development (easier to debug)
|
||||
log:
|
||||
level: INFO
|
||||
format: common # Human-readable format for local development
|
||||
|
||||
# Access Logs - Console output for local development
|
||||
accessLog:
|
||||
format: common # Human-readable format for local development
|
||||
|
||||
# Metrics (optional for local development)
|
||||
# Can be enabled if needed for monitoring
|
||||
# metrics:
|
||||
# prometheus:
|
||||
# addEntryPointsLabels: true
|
||||
# addRoutersLabels: true
|
||||
# addServicesLabels: true
|
||||
|
||||
# Ping endpoint for health checks
|
||||
ping:
|
||||
entryPoint: web
|
||||
|
||||
Reference in New Issue
Block a user