From 3488d5118fb29b162f165d83af58b2490b58f547 Mon Sep 17 00:00:00 2001 From: "kihong.kim" Date: Sun, 4 Jan 2026 19:05:24 +0900 Subject: [PATCH] Add auto-deployment scripts for Gitea webhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - deploy.sh: Script to pull code and restart Docker containers - webhook-server.py: HTTP server to receive Gitea push events - shorts-maker-webhook.service: Systemd service for webhook server - setup-autodeploy.sh: Setup script for the server 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- deploy.sh | 43 ++++++++++++ setup-autodeploy.sh | 63 +++++++++++++++++ shorts-maker-webhook.service | 21 ++++++ webhook-server.py | 131 +++++++++++++++++++++++++++++++++++ 4 files changed, 258 insertions(+) create mode 100644 deploy.sh create mode 100644 setup-autodeploy.sh create mode 100644 shorts-maker-webhook.service create mode 100644 webhook-server.py diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..4767143 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Auto-deployment script for shorts-maker +# This script is triggered by Gitea webhook on push + +set -e + +# Configuration +APP_DIR="/home/bini/shorts-maker" +LOG_FILE="/var/log/shorts-maker-deploy.log" +COMPOSE_FILE="docker-compose.yml" + +# Logging function +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +log "=== Deployment started ===" + +# Navigate to app directory +cd "$APP_DIR" || { log "ERROR: Cannot cd to $APP_DIR"; exit 1; } + +# Pull latest code +log "Pulling latest code from git..." +git fetch origin +git reset --hard origin/main +log "Git pull completed" + +# Rebuild and restart containers +log "Rebuilding Docker containers..." +docker compose -f "$COMPOSE_FILE" build --no-cache + +log "Restarting containers..." +docker compose -f "$COMPOSE_FILE" down +docker compose -f "$COMPOSE_FILE" up -d + +# Clean up old images +log "Cleaning up old Docker images..." +docker image prune -f + +log "=== Deployment completed successfully ===" + +# Show running containers +docker compose -f "$COMPOSE_FILE" ps diff --git a/setup-autodeploy.sh b/setup-autodeploy.sh new file mode 100644 index 0000000..b73cb19 --- /dev/null +++ b/setup-autodeploy.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Setup script for auto-deployment on the server +# Run this script on the home server as root or with sudo + +set -e + +APP_DIR="/home/bini/shorts-maker" +SERVICE_NAME="shorts-maker-webhook" + +echo "=== Shorts Maker Auto-Deploy Setup ===" + +# 1. Make deploy script executable +echo "1. Setting up deploy script..." +chmod +x "$APP_DIR/deploy.sh" +chmod +x "$APP_DIR/webhook-server.py" + +# 2. Create log files with proper permissions +echo "2. Creating log files..." +touch /var/log/shorts-maker-deploy.log +touch /var/log/webhook-server.log +chown bini:bini /var/log/shorts-maker-deploy.log +chown bini:bini /var/log/webhook-server.log + +# 3. Generate webhook secret +WEBHOOK_SECRET=$(openssl rand -hex 32) +echo "3. Generated webhook secret: $WEBHOOK_SECRET" +echo " (Save this for Gitea webhook configuration!)" + +# 4. Update systemd service with the secret +echo "4. Installing systemd service..." +sed "s/your-secret-here/$WEBHOOK_SECRET/" "$APP_DIR/shorts-maker-webhook.service" > /etc/systemd/system/$SERVICE_NAME.service + +# 5. Reload and start service +echo "5. Starting webhook service..." +systemctl daemon-reload +systemctl enable $SERVICE_NAME +systemctl start $SERVICE_NAME + +# 6. Check status +echo "" +echo "=== Setup Complete ===" +echo "" +systemctl status $SERVICE_NAME --no-pager +echo "" +echo "=== Next Steps ===" +echo "" +echo "1. Configure Gitea webhook:" +echo " - Go to Repository Settings > Webhooks > Add Webhook" +echo " - Target URL: http://your-server-ip:9000/webhook" +echo " - HTTP Method: POST" +echo " - Content Type: application/json" +echo " - Secret: $WEBHOOK_SECRET" +echo " - Trigger: Push Events" +echo "" +echo "2. If using firewall, open port 9000:" +echo " sudo ufw allow 9000/tcp" +echo "" +echo "3. Test by pushing to main branch!" +echo "" +echo "=== Useful Commands ===" +echo "View logs: journalctl -u $SERVICE_NAME -f" +echo "Restart: sudo systemctl restart $SERVICE_NAME" +echo "Manual deploy: sudo -u bini $APP_DIR/deploy.sh" diff --git a/shorts-maker-webhook.service b/shorts-maker-webhook.service new file mode 100644 index 0000000..4009459 --- /dev/null +++ b/shorts-maker-webhook.service @@ -0,0 +1,21 @@ +[Unit] +Description=Shorts Maker Webhook Server +After=network.target docker.service +Wants=docker.service + +[Service] +Type=simple +User=bini +Group=bini +WorkingDirectory=/home/bini/shorts-maker +Environment=WEBHOOK_SECRET=your-secret-here +ExecStart=/usr/bin/python3 /home/bini/shorts-maker/webhook-server.py +Restart=always +RestartSec=10 + +# Logging +StandardOutput=append:/var/log/webhook-server.log +StandardError=append:/var/log/webhook-server.log + +[Install] +WantedBy=multi-user.target diff --git a/webhook-server.py b/webhook-server.py new file mode 100644 index 0000000..0615cb5 --- /dev/null +++ b/webhook-server.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +""" +Simple webhook server for Gitea auto-deployment. +Listens for push events and triggers deploy.sh +""" + +import subprocess +import hmac +import hashlib +import json +import os +from http.server import HTTPServer, BaseHTTPRequestHandler +from datetime import datetime + +# Configuration +WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET', 'your-secret-here') +DEPLOY_SCRIPT = '/home/bini/shorts-maker/deploy.sh' +PORT = 9000 +LOG_FILE = '/var/log/webhook-server.log' + +def log(message): + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + log_line = f"[{timestamp}] {message}" + print(log_line) + try: + with open(LOG_FILE, 'a') as f: + f.write(log_line + '\n') + except: + pass + +def verify_signature(payload, signature): + """Verify Gitea webhook signature""" + if not signature: + return False + expected = hmac.new( + WEBHOOK_SECRET.encode(), + payload, + hashlib.sha256 + ).hexdigest() + return hmac.compare_digest(f"sha256={expected}", signature) + +class WebhookHandler(BaseHTTPRequestHandler): + def do_POST(self): + if self.path != '/webhook': + self.send_response(404) + self.end_headers() + return + + content_length = int(self.headers.get('Content-Length', 0)) + payload = self.rfile.read(content_length) + + # Verify signature if secret is configured + if WEBHOOK_SECRET != 'your-secret-here': + signature = self.headers.get('X-Gitea-Signature') + if not verify_signature(payload, signature): + log("ERROR: Invalid webhook signature") + self.send_response(403) + self.end_headers() + self.wfile.write(b'Invalid signature') + return + + try: + data = json.loads(payload) + ref = data.get('ref', '') + + # Only deploy on push to main branch + if ref == 'refs/heads/main': + log(f"Received push to main from {data.get('pusher', {}).get('name', 'unknown')}") + log(f"Commit: {data.get('after', 'unknown')[:8]}") + + # Run deploy script + log("Starting deployment...") + result = subprocess.run( + ['bash', DEPLOY_SCRIPT], + capture_output=True, + text=True, + timeout=600 # 10 minute timeout + ) + + if result.returncode == 0: + log("Deployment successful!") + self.send_response(200) + self.end_headers() + self.wfile.write(b'Deployment successful') + else: + log(f"Deployment failed: {result.stderr}") + self.send_response(500) + self.end_headers() + self.wfile.write(f'Deployment failed: {result.stderr}'.encode()) + else: + log(f"Ignoring push to {ref}") + self.send_response(200) + self.end_headers() + self.wfile.write(b'Ignored (not main branch)') + + except json.JSONDecodeError: + log("ERROR: Invalid JSON payload") + self.send_response(400) + self.end_headers() + self.wfile.write(b'Invalid JSON') + except subprocess.TimeoutExpired: + log("ERROR: Deployment timed out") + self.send_response(500) + self.end_headers() + self.wfile.write(b'Deployment timed out') + except Exception as e: + log(f"ERROR: {str(e)}") + self.send_response(500) + self.end_headers() + self.wfile.write(f'Error: {str(e)}'.encode()) + + def do_GET(self): + if self.path == '/health': + self.send_response(200) + self.end_headers() + self.wfile.write(b'OK') + else: + self.send_response(404) + self.end_headers() + + def log_message(self, format, *args): + log(f"HTTP: {args[0]}") + +if __name__ == '__main__': + log(f"Starting webhook server on port {PORT}") + server = HTTPServer(('0.0.0.0', PORT), WebhookHandler) + try: + server.serve_forever() + except KeyboardInterrupt: + log("Shutting down webhook server") + server.shutdown()