Some checks are pending
Check Python Version Consistency / Check Python Version (push) Waiting to run
7.6 KiB
7.6 KiB
OSVx MCP — CI First Run (Gitea Webhook)
This is a minimal, commands-only runbook to set up the Gitea webhook CI/CD for staging deploys.
Assumes you are on the VM as chris_christiansen with the repo at ~/OSVauco.
0. Prerequisites
cd ~/OSVauco
git status
git rev-parse --abbrev-ref HEAD
which python3
which gcloud
gcloud config get-value project
Ensure:
- You’re on
feat/osvx-mcp-full-catalog gcloudproject ispropane-will-491900-m5
1. Create the webhook listener script
mkdir -p ci
cat > ci/deploy-webhook-listener.py << 'EOF'
#!/usr/bin/env python3
import hashlib
import hmac
import json
import os
import subprocess
from http.server import BaseHTTPRequestHandler, HTTPServer
PORT = int(os.getenv("WEBHOOK_PORT", "9999"))
WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET", "")
REPO_PATH = os.getenv("REPO_PATH", "/home/chris_christiansen/OSVauco")
CI_BRANCH = os.getenv("CI_BRANCH", "feat/osvx-mcp-full-catalog")
DEPLOY_SCRIPT = os.getenv("DEPLOY_SCRIPT", "./deploy-mcp.sh")
DEPLOY_ENV = os.getenv("DEPLOY_ENV", "staging")
def verify_signature(payload_bytes, signature_header):
if not signature_header or not signature_header.startswith("sha256="):
return False
expected_sig = signature_header.split("=", 1)[1]
computed = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
payload_bytes,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(computed, expected_sig)
class WebhookHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
print(f"[webhook] {args[0]}", flush=True)
def do_POST(self):
content_length = int(self.headers.get("Content-Length", 0))
payload_bytes = self.rfile.read(content_length)
signature = self.headers.get("X-Gitea-Delivery", "")
if WEBHOOK_SECRET and not verify_signature(payload_bytes, signature):
self.send_response(401)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"error": "invalid signature"}).encode())
return
try:
payload = json.loads(payload_bytes.decode("utf-8"))
except Exception:
self.send_response(400)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"error": "invalid json"}).encode())
return
ref = payload.get("ref", "")
after = payload.get("after", "")
if after == "0000000000000000000000000000000000000000":
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"status": "ignored deletion"}).encode())
return
expected_ref = f"refs/heads/{CI_BRANCH}"
if ref != expected_ref:
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(
json.dumps({"status": "ignored", "reason": "wrong branch", "ref": ref}).encode()
)
return
self.send_response(202)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(
json.dumps({
"status": "deploying",
"branch": CI_BRANCH,
"env": DEPLOY_ENV,
}).encode()
)
try:
subprocess.run(["git", "-C", REPO_PATH, "pull"], check=True, capture_output=True, text=True)
subprocess.run([DEPLOY_SCRIPT, DEPLOY_ENV], cwd=REPO_PATH, check=True)
except Exception as e:
print(f"[webhook] deploy failed: {e}", flush=True)
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(json.dumps({"status": "ok", "service": "osvx-mcp-webhook"}).encode())
def main():
if not WEBHOOK_SECRET:
print("WARNING: WEBHOOK_SECRET not set; webhook signature verification disabled.", flush=True)
server = HTTPServer(("0.0.0.0", PORT), WebhookHandler)
print(f"[webhook] listening on port {PORT}", flush=True)
server.serve_forever()
if __name__ == "__main__":
main()
EOF
chmod +x ci/deploy-webhook-listener.py
2. Generate webhook secret
export WEBHOOK_SECRET="$(openssl rand -hex 16)"
echo "WEBHOOK_SECRET=$WEBHOOK_SECRET"
Save this value; you’ll need it for Gitea and systemd.
3. Quick manual test (optional)
export WEBHOOK_PORT=9999
export REPO_PATH="/home/chris_christiansen/OSVauco"
export CI_BRANCH="feat/osvx-mcp-full-catalog"
export DEPLOY_SCRIPT="./deploy-mcp.sh"
export DEPLOY_ENV="staging"
python3 ci/deploy-webhook-listener.py &
sleep 1
curl http://127.0.0.1:9999
# Expect: {"status":"ok","service":"osvx-mcp-webhook"}
kill %1 2>/dev/null || true
4. Create systemd service
sudo tee /etc/systemd/system/osvx-mcp-webhook.service > /dev/null << EOF
[Unit]
Description=OSVx MCP Gitea Webhook Listener
After=network.target
[Service]
Type=simple
User=chris_christiansen
Group=chris_christiansen
WorkingDirectory=/home/chris_christiansen/OSVauco
Environment="WEBHOOK_SECRET=$WEBHOOK_SECRET"
Environment="WEBHOOK_PORT=9999"
Environment="REPO_PATH=/home/chris_christiansen/OSVauco"
Environment="CI_BRANCH=feat/osvx-mcp-full-catalog"
Environment="DEPLOY_SCRIPT=./deploy-mcp.sh"
Environment="DEPLOY_ENV=staging"
ExecStart=/usr/bin/python3 /home/chris_christiansen/OSVauco/ci/deploy-webhook-listener.py
Restart=always
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
5. Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable osvx-mcp-webhook
sudo systemctl start osvx-mcp-webhook
sudo systemctl status osvx-mcp-webhook --no-pager
Verify:
curl http://127.0.0.1:9999
journalctl -u osvx-mcp-webhook -n 20 --no-pager
Expected health response:
{"status":"ok","service":"osvx-mcp-webhook"}
6. Configure Gitea webhook
In Gitea UI for chris/osvauco:
- Settings → Webhooks → Add Webhook
- Payload URL:
http://<VM_IP>:9999/ - Secret:
$WEBHOOK_SECRET(the value from step 2) - Events: “Push events”
- Save
Optionally restrict to branch feat/osvx-mcp-full-catalog if supported.
7. Test with a real push
From your dev machine:
cd ~/OSVauco
git add .
git commit -m "ci: test webhook deploy"
git push origin feat/osvx-mcp-full-catalog
On the VM, watch logs:
journalctl -u osvx-mcp-webhook -f
You should see:
- Webhook received
git pull./deploy-mcp.sh stagingrunning
Then verify in GCP:
gcloud run services describe osvx-mcp-staging --project=propane-will-491900-m5 --region=us-central1
Check the last deployment timestamp and revision.
8. Manual prod deploy (after staging is validated)
cd ~/OSVauco
./deploy-mcp.sh prod
Verify:
gcloud run services describe osvx-mcp-prod --project=propane-will-491900-m5 --region=us-central1
9. Troubleshooting (quick)
- Health check fails:
systemctl status osvx-mcp-webhook --no-pager journalctl -u osvx-mcp-webhook -n 50 --no-pager - Webhook not received:
- Check Gitea webhook “Recent Deliveries”.
- Ensure
http://<VM_IP>:9999is reachable from the Gitea server.
- Deploy fails:
- Run
./deploy-mcp.sh stagingmanually to confirm it works. - Check GCP credentials and permissions.
- Run
This runbook implements the CI/CD strategy described in CI_GITEA_WEBHOOK.md and PLATFORM_MAP.md.