From b9fc7415057d97cbd7e682761255378c7288aa6b Mon Sep 17 00:00:00 2001 From: Florian Kramer Date: Wed, 10 Jun 2026 11:17:08 +0200 Subject: [PATCH] Add deployment workflow (docker-compose, deploy.sh, webhook receiver) --- _aufmass_web/.env.example | 14 +++--------- deploy.sh | 28 +++++++++++++++++++++++ docker-compose.deploy.yml | 34 +++++++++++++++++++++++++++ webhook_deploy.py | 48 +++++++++++++++++++++++++++++++++++++++ webhook_deploy.service | 16 +++++++++++++ 5 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 deploy.sh create mode 100644 docker-compose.deploy.yml create mode 100644 webhook_deploy.py create mode 100644 webhook_deploy.service diff --git a/_aufmass_web/.env.example b/_aufmass_web/.env.example index c6f6675..1a63cc8 100644 --- a/_aufmass_web/.env.example +++ b/_aufmass_web/.env.example @@ -1,11 +1,3 @@ -# AufmaßWeb – Konfiguration -# Kopiere diese Datei nach .env und passe die Werte an - -SECRET_KEY=ersetzen-mit-sicherem-schluessel-mindestens-32-zeichen - -# SQLite (Entwicklung lokal): -# DATABASE_URL=sqlite:///data/aufmass.db - -# PostgreSQL (Produktion / Docker): -DATABASE_URL=postgresql://user:password@host:5432/aufmassweb -FLASK_ENV=production +DATABASE_URL=postgresql://aufmass:aufmass_secret@localhost:5432/aufmassweb +SECRET_KEY=change-me-in-production +REGISTRATION_ENABLED=false diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..8bd86d6 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e + +REPO_DIR="/opt/aufmassweb" +COMPOSE_FILE="docker-compose.deploy.yml" +LOG_FILE="/var/log/aufmassweb-deploy.log" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" +} + +log "=== Deployment gestartet ===" + +cd "$REPO_DIR" + +log "Pull von Gitea..." +git pull origin main 2>&1 | tee -a "$LOG_FILE" + +log "Docker Compose build..." +docker compose -f "$COMPOSE_FILE" build 2>&1 | tee -a "$LOG_FILE" + +log "Docker Compose up (Rolling-Restart)..." +docker compose -f "$COMPOSE_FILE" up -d --remove-orphans 2>&1 | tee -a "$LOG_FILE" + +log "Alte Images bereinigen..." +docker image prune -f 2>&1 | tee -a "$LOG_FILE" + +log "=== Deployment erfolgreich abgeschlossen ===" diff --git a/docker-compose.deploy.yml b/docker-compose.deploy.yml new file mode 100644 index 0000000..1a87e50 --- /dev/null +++ b/docker-compose.deploy.yml @@ -0,0 +1,34 @@ +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + volumes: + - pgdata:/var/lib/postgresql/data + environment: + POSTGRES_DB: aufmassweb + POSTGRES_USER: aufmass + POSTGRES_PASSWORD: ${DB_PASSWORD:-aufmass_secret} + healthcheck: + test: ["CMD", "pg_isready", "-U", "aufmass", "-d", "aufmassweb"] + interval: 10s + timeout: 5s + retries: 5 + + web: + build: ./_aufmass_web + restart: unless-stopped + ports: + - "5000:5000" + volumes: + - ./_aufmass_web/data:/app/data + - ./daten:/app/daten + environment: + DATABASE_URL: postgresql://aufmass:${DB_PASSWORD:-aufmass_secret}@postgres:5432/aufmassweb + SECRET_KEY: ${SECRET_KEY:-change-me-in-production} + REGISTRATION_ENABLED: "false" + depends_on: + postgres: + condition: service_healthy + +volumes: + pgdata: diff --git a/webhook_deploy.py b/webhook_deploy.py new file mode 100644 index 0000000..24ea6fb --- /dev/null +++ b/webhook_deploy.py @@ -0,0 +1,48 @@ +import os +import subprocess +import hmac +import hashlib +import json +from flask import Flask, request, jsonify + +app = Flask(__name__) + +WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "change-me") +DEPLOY_SCRIPT = os.environ.get("DEPLOY_SCRIPT", "/opt/aufmassweb/deploy.sh") + +def verify_signature(payload, signature): + if not signature: + return False + expected = "sha256=" + hmac.new(WEBHOOK_SECRET.encode(), payload, hashlib.sha256).hexdigest() + return hmac.compare_digest(expected, signature) + +@app.route("/webhook", methods=["POST"]) +def webhook(): + signature = request.headers.get("X-Gitea-Signature", "") + payload = request.get_data() + + if not verify_signature(payload, signature): + return jsonify({"error": "invalid signature"}), 403 + + data = request.get_json(silent=True) or {} + ref = data.get("ref", "") + if ref != "refs/heads/main": + return jsonify({"message": f"ignored push to {ref}"}), 200 + + result = subprocess.run( + ["bash", DEPLOY_SCRIPT], + capture_output=True, text=True, timeout=300 + ) + + return jsonify({ + "status": "ok" if result.returncode == 0 else "error", + "stdout": result.stdout, + "stderr": result.stderr, + }), (200 if result.returncode == 0 else 500) + +@app.route("/health", methods=["GET"]) +def health(): + return jsonify({"status": "ok"}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5001) diff --git a/webhook_deploy.service b/webhook_deploy.service new file mode 100644 index 0000000..f8c915a --- /dev/null +++ b/webhook_deploy.service @@ -0,0 +1,16 @@ +[Unit] +Description=Webhook Receiver für AufmaßWeb Deployment +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/aufmassweb +ExecStart=/usr/bin/python3 /opt/aufmassweb/webhook_deploy.py +Restart=always +RestartSec=5 +Environment=WEBHOOK_SECRET=change-me +Environment=DEPLOY_SCRIPT=/opt/aufmassweb/deploy.sh + +[Install] +WantedBy=multi-user.target