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:
parent
62f06813a4
commit
9141a3eedf
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue