From a75d9b4d34705f06024df127a5defe6d95f48f35 Mon Sep 17 00:00:00 2001 From: Stanislav Hubacek Date: Tue, 7 Apr 2026 20:16:22 +0200 Subject: [PATCH] CI/CD --- scripts/deploy_caddy.sh | 8 +++- scripts/deploy_from_git.sh | 27 +++++++++++ scripts/webhook_listener.py | 95 +++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 scripts/deploy_from_git.sh create mode 100755 scripts/webhook_listener.py diff --git a/scripts/deploy_caddy.sh b/scripts/deploy_caddy.sh index b87a0a8..5060e2a 100755 --- a/scripts/deploy_caddy.sh +++ b/scripts/deploy_caddy.sh @@ -21,7 +21,13 @@ fi cp "$WRAPPER" "$TARGET_CADDYFILE" echo "[4/5] Validating Caddy config..." -caddy validate --config "$TARGET_CADDYFILE" +if ! caddy validate --config "$TARGET_CADDYFILE"; then + echo "[ERROR] Validation failed, restoring backup..." + if [[ -f "$BACKUP_CADDYFILE" ]]; then + cp "$BACKUP_CADDYFILE" "$TARGET_CADDYFILE" + fi + exit 1 +fi echo "[5/5] Reloading Caddy..." systemctl reload caddy diff --git a/scripts/deploy_from_git.sh b/scripts/deploy_from_git.sh new file mode 100644 index 0000000..86cfa53 --- /dev/null +++ b/scripts/deploy_from_git.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="/opt/homelab" +BRANCH="main" + +echo "[1/4] Switching to repo..." +cd "$REPO_DIR" + +echo "[2/4] Fetching changes..." +git fetch origin "$BRANCH" + +LOCAL="$(git rev-parse HEAD)" +REMOTE="$(git rev-parse origin/$BRANCH)" + +if [[ "$LOCAL" == "$REMOTE" ]]; then + echo "[3/4] No changes to deploy." + exit 0 +fi + +echo "[3/4] Pulling latest changes..." +git pull --ff-only origin "$BRANCH" + +echo "[4/4] Running Caddy deployment..." +"$REPO_DIR/scripts/deploy_caddy.sh" + +echo "[Done] Git-based deployment completed." \ No newline at end of file diff --git a/scripts/webhook_listener.py b/scripts/webhook_listener.py new file mode 100755 index 0000000..740f0e3 --- /dev/null +++ b/scripts/webhook_listener.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +import hashlib +import hmac +import json +import os +import subprocess +from http.server import BaseHTTPRequestHandler, HTTPServer + +HOST = "127.0.0.1" +PORT = 9001 +SECRET = os.environ.get("GITEA_WEBHOOK_SECRET", "e250429f3c716721c5c37c61c5032e916e98756523bc99a9cb7949854d2810da") +DEPLOY_SCRIPT = "/opt/homelab/scripts/deploy_from_git.sh" +BRANCH = "refs/heads/main" + + +class Handler(BaseHTTPRequestHandler): + def do_POST(self): + if self.path != "/deploy": + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not found") + return + + content_length = int(self.headers.get("Content-Length", "0")) + body = self.rfile.read(content_length) + + signature = self.headers.get("X-Gitea-Signature", "") + if not SECRET: + self.send_response(500) + self.end_headers() + self.wfile.write(b"Missing server secret") + return + + expected = hmac.new( + SECRET.encode("utf-8"), + body, + hashlib.sha256 + ).hexdigest() + + if not hmac.compare_digest(signature, expected): + self.send_response(403) + self.end_headers() + self.wfile.write(b"Invalid signature") + return + + try: + payload = json.loads(body.decode("utf-8")) + except Exception: + self.send_response(400) + self.end_headers() + self.wfile.write(b"Invalid JSON") + return + + ref = payload.get("ref", "") + if ref != BRANCH: + self.send_response(200) + self.end_headers() + self.wfile.write(f"Ignored ref: {ref}".encode("utf-8")) + return + + try: + result = subprocess.run( + [DEPLOY_SCRIPT], + capture_output=True, + text=True, + check=True, + ) + output = (result.stdout or "") + (result.stderr or "") + self.send_response(200) + self.end_headers() + self.wfile.write(output.encode("utf-8")) + except subprocess.CalledProcessError as exc: + output = (exc.stdout or "") + (exc.stderr or "") + self.send_response(500) + self.end_headers() + self.wfile.write(output.encode("utf-8")) + + def do_GET(self): + if self.path == "/health": + self.send_response(200) + self.end_headers() + self.wfile.write(b"ok") + return + + self.send_response(404) + self.end_headers() + self.wfile.write(b"Not found") + + def log_message(self, fmt, *args): + return + + +if __name__ == "__main__": + server = HTTPServer((HOST, PORT), Handler) + server.serve_forever() \ No newline at end of file