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:
37
deploy.sh
Executable file
37
deploy.sh
Executable 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"
|
||||
@@ -26,6 +26,13 @@ server {
|
||||
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 / {
|
||||
proxy_pass http://unix:/run/olimpic_nastri/gunicorn.sock;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
14
olimpic-nastri-webhook.service
Normal file
14
olimpic-nastri-webhook.service
Normal 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
85
webhook_receiver.py
Normal 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()
|
||||
Reference in New Issue
Block a user