Files
startover/deploy/webhook-receiver.py
Johngreen 7f59b94dcf Rename project from Re:Link to Startover
Rebrand repository from "Re:Link" to "Startover" across the codebase. Updates include package names and scopes (@relink/* -> @startover/*), import paths, Next.js transpile settings, vitest name, UI text and docs, Dockerfile and CI/workflow names, deploy scripts and repo paths, and example/production env values. Also add auth-related env vars, an apps/web .env symlink, and small formatting/typing cleanups in several TSX/TS files and tests to accommodate the rename.
2026-03-08 20:22:08 +09:00

116 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""
Startover Gitea Webhook Receiver
Listens for push events and triggers deployment.
"""
import hashlib
import hmac
import json
import os
import subprocess
import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
from datetime import datetime
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "startover-deploy-secret")
DEPLOY_SCRIPT = os.path.expanduser("~/startover/deploy.sh")
DEPLOY_BRANCH = "main"
PORT = int(os.environ.get("WEBHOOK_PORT", "9000"))
LOG_FILE = os.path.expanduser("~/startover/webhook.log")
def log(msg: str):
line = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
print(line, flush=True)
with open(LOG_FILE, "a") as f:
f.write(line + "\n")
def verify_signature(payload: bytes, signature: str) -> bool:
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 != "/deploy":
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
signature = self.headers.get("X-Gitea-Signature", "")
if WEBHOOK_SECRET and not verify_signature(payload, signature):
log("REJECTED: Invalid signature")
self.send_response(403)
self.end_headers()
self.wfile.write(b"Invalid signature")
return
try:
data = json.loads(payload)
except json.JSONDecodeError:
self.send_response(400)
self.end_headers()
return
ref = data.get("ref", "")
if ref != f"refs/heads/{DEPLOY_BRANCH}":
log(f"SKIPPED: Push to {ref} (not {DEPLOY_BRANCH})")
self.send_response(200)
self.end_headers()
self.wfile.write(b"Skipped: not target branch")
return
pusher = data.get("pusher", {}).get("login", "unknown")
commits = len(data.get("commits", []))
log(f"DEPLOY TRIGGERED: {pusher} pushed {commits} commit(s) to {DEPLOY_BRANCH}")
# Run deploy script asynchronously
try:
subprocess.Popen(
["bash", DEPLOY_SCRIPT],
stdout=open(LOG_FILE, "a"),
stderr=subprocess.STDOUT,
start_new_session=True,
)
self.send_response(200)
self.end_headers()
self.wfile.write(b"Deploy started")
log("Deploy script launched")
except Exception as e:
log(f"ERROR: Failed to start deploy: {e}")
self.send_response(500)
self.end_headers()
self.wfile.write(f"Deploy failed: {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):
pass # Suppress default logging
if __name__ == "__main__":
log(f"Webhook receiver starting on port {PORT}")
server = HTTPServer(("0.0.0.0", PORT), WebhookHandler)
try:
server.serve_forever()
except KeyboardInterrupt:
log("Webhook receiver stopped")
server.server_close()