CI/CD: webhook receiver + deploy automatico su push

- deploy.sh: git pull, pip install, migrate, collectstatic, restart gunicorn
- webhook_receiver.py: HTTP server con verifica HMAC-SHA256 Gitea
- olimpic-nastri-webhook.service: systemd unit per il receiver
- Nginx: aggiunto proxy /webhook/deploy → porta 9000
- sudoers: restart gunicorn senza password per deploy automatico
This commit is contained in:
automationkriz
2026-04-05 15:02:25 +00:00
parent 312db89a6a
commit 006bb24215
4 changed files with 143 additions and 0 deletions

37
deploy.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/bin/bash
# Deploy automatico — Diario Conversazioni Olimpic Nastri
# Chiamato dal webhook receiver dopo ogni push su main
set -e
PROJECT_DIR="/home/marco/olimpic_nastri"
VENV="$PROJECT_DIR/nastrivenv/bin"
LOG="/home/marco/olimpic_nastri/deploy.log"
echo "========================================" >> "$LOG"
echo "Deploy avviato: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG"
cd "$PROJECT_DIR"
# Pull ultime modifiche
echo "[1/5] Git pull..." >> "$LOG"
git pull origin main >> "$LOG" 2>&1
# Installa eventuali nuove dipendenze
echo "[2/5] Pip install..." >> "$LOG"
"$VENV/pip" install -r requirements.txt --quiet >> "$LOG" 2>&1
# Applica migrazioni database
echo "[3/5] Migrazioni..." >> "$LOG"
"$VENV/python" manage.py migrate --noinput >> "$LOG" 2>&1
# Raccoglie file statici
echo "[4/5] Collectstatic..." >> "$LOG"
"$VENV/python" manage.py collectstatic --noinput >> "$LOG" 2>&1
# Riavvia Gunicorn
echo "[5/5] Restart Gunicorn..." >> "$LOG"
sudo systemctl restart olimpic-nastri-gunicorn.service >> "$LOG" 2>&1
echo "Deploy completato: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG"
echo "========================================" >> "$LOG"

View File

@@ -26,6 +26,13 @@ server {
alias /home/marco/olimpic_nastri/media/; alias /home/marco/olimpic_nastri/media/;
} }
location /webhook/deploy {
proxy_pass http://127.0.0.1:9000/deploy;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / { location / {
proxy_pass http://unix:/run/olimpic_nastri/gunicorn.sock; proxy_pass http://unix:/run/olimpic_nastri/gunicorn.sock;
proxy_set_header Host $host; proxy_set_header Host $host;

View File

@@ -0,0 +1,14 @@
[Unit]
Description=Webhook receiver per deploy automatico Diario Olimpic Nastri
After=network.target
[Service]
User=marco
Group=www-data
WorkingDirectory=/home/marco/olimpic_nastri
ExecStart=/usr/bin/python3 /home/marco/olimpic_nastri/webhook_receiver.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target

85
webhook_receiver.py Normal file
View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""
Webhook receiver per Gitea — Diario Conversazioni Olimpic Nastri.
Ascolta su porta 9000, verifica la firma HMAC del payload Gitea,
e lancia deploy.sh quando riceve un push sul branch main.
"""
import hashlib
import hmac
import json
import subprocess
import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
WEBHOOK_SECRET = "c91dca86b9a87d9f25e07a63354da9f2469998f9"
DEPLOY_SCRIPT = "/home/marco/olimpic_nastri/deploy.sh"
LISTEN_PORT = 9000
class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/deploy":
self.send_response(404)
self.end_headers()
return
content_length = int(self.headers.get("Content-Length", 0))
if content_length > 1_000_000: # max 1 MB payload
self.send_response(413)
self.end_headers()
return
body = self.rfile.read(content_length)
# Verifica firma HMAC-SHA256 di Gitea
signature = self.headers.get("X-Gitea-Signature", "")
expected = hmac.new(
WEBHOOK_SECRET.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
self.send_response(403)
self.end_headers()
self.wfile.write(b"Firma non valida")
return
# Controlla che sia un push su main
try:
payload = json.loads(body)
except json.JSONDecodeError:
self.send_response(400)
self.end_headers()
return
ref = payload.get("ref", "")
if ref != "refs/heads/main":
self.send_response(200)
self.end_headers()
self.wfile.write(b"Push ignorato (non main)")
return
# Lancia il deploy in background
subprocess.Popen([DEPLOY_SCRIPT], close_fds=True)
self.send_response(200)
self.end_headers()
self.wfile.write(b"Deploy avviato")
def log_message(self, format, *args):
print(f"[webhook] {args[0]}", flush=True)
def main():
server = HTTPServer(("127.0.0.1", LISTEN_PORT), WebhookHandler)
print(f"Webhook receiver in ascolto su 127.0.0.1:{LISTEN_PORT}", flush=True)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
server.server_close()
if __name__ == "__main__":
main()