OSVauco/docs/CI_FIRST_RUN.md

273 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```bash
cd ~/OSVauco
git status
git rev-parse --abbrev-ref HEAD
which python3
which gcloud
gcloud config get-value project
```
**Ensure:**
- Youre on `feat/osvx-mcp-full-catalog`
- `gcloud` project is `propane-will-491900-m5`
### 1. Create the webhook listener script
```bash
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
```bash
export WEBHOOK_SECRET="$(openssl rand -hex 16)"
echo "WEBHOOK_SECRET=$WEBHOOK_SECRET"
```
Save this value; youll need it for Gitea and systemd.
### 3. Quick manual test (optional)
```bash
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
```bash
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
```bash
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:
```bash
curl http://127.0.0.1:9999
journalctl -u osvx-mcp-webhook -n 20 --no-pager
```
Expected health response:
```json
{"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:
```bash
cd ~/OSVauco
git add .
git commit -m "ci: test webhook deploy"
git push origin feat/osvx-mcp-full-catalog
```
On the VM, watch logs:
```bash
journalctl -u osvx-mcp-webhook -f
```
You should see:
- Webhook received
- `git pull`
- `./deploy-mcp.sh staging` running
Then verify in GCP:
```bash
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)
```bash
cd ~/OSVauco
./deploy-mcp.sh prod
```
Verify:
```bash
gcloud run services describe osvx-mcp-prod --project=propane-will-491900-m5 --region=us-central1
```
### 9. Troubleshooting (quick)
- **Health check fails:**
```bash
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>:9999` is reachable from the Gitea server.
- **Deploy fails:**
- Run `./deploy-mcp.sh staging` manually to confirm it works.
- Check GCP credentials and permissions.
*This runbook implements the CI/CD strategy described in `CI_GITEA_WEBHOOK.md` and `PLATFORM_MAP.md`.*