Add deploy webhook for CI/CD

- New /api/v1/webhook/deploy endpoint
- Verifies Gitea HMAC-SHA256 signature
- Only deploys on push to main/master branch
- Runs git pull and service restart in background

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-01-13 03:03:11 -04:00
parent 62f06813a4
commit 9141a3eedf
1 changed files with 50 additions and 0 deletions

View File

@ -1652,6 +1652,56 @@ def create_app() -> Flask:
response.status_code = status
return response
@app.route("/api/v1/webhook/deploy", methods=["POST"])
def webhook_deploy() -> Response:
"""Auto-deploy webhook triggered by Gitea on push to main."""
import hmac
import subprocess
secret = os.environ.get("CMDFORGE_DEPLOY_WEBHOOK_SECRET", "")
if not secret:
return error_response("UNAUTHORIZED", "Deploy webhook secret not configured", 401)
# Verify Gitea signature (X-Gitea-Signature is HMAC-SHA256)
signature = request.headers.get("X-Gitea-Signature", "")
if not signature:
return error_response("UNAUTHORIZED", "Missing signature", 401)
expected = hmac.new(
secret.encode(),
request.data,
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return error_response("UNAUTHORIZED", "Invalid signature", 401)
# Parse payload to check branch
try:
payload = json.loads(request.data)
ref = payload.get("ref", "")
# Only deploy on push to main branch
if ref not in ("refs/heads/main", "refs/heads/master"):
return jsonify({"data": {"deployed": False, "reason": f"Ignoring ref {ref}"}})
except (json.JSONDecodeError, KeyError):
pass # Proceed anyway if we can't parse
# Run deploy in background (so we can respond before restart kills us)
deploy_script = """
cd /srv/mergerfs/data_pool/home/rob/cmdforge-registry && \
git pull origin main && \
sleep 1 && \
systemctl --user restart cmdforge-web
"""
subprocess.Popen(
["bash", "-c", deploy_script],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True,
)
return jsonify({"data": {"deployed": True, "message": "Deploy triggered"}})
return app