feat: Phase LIVE integration - Production automation and deployment fixes

## Railway Deployment Fixes
- Fix railway.toml startCommand (remove incorrect 'cd backend')
- Enhance Dockerfile with security, health checks, and proper user permissions
- Add comprehensive deployment fix documentation (RAILWAY_DEPLOY_FIX.md)

## Phase Q/Q2 Integration
- Add GitHub webhooks router (/api/webhooks/github) for PR automation
- Integrate Prism Console at /prism endpoint
- Add GITHUB_TOKEN and GITHUB_WEBHOOK_SECRET to config
- Update .env.example with webhook secret

## Documentation
- Create comprehensive GitHub setup guide (GITHUB_SETUP_GUIDE.md)
- Document branch protection, merge queue, and webhook configuration
- Include troubleshooting and testing procedures

## Related
- Phase Q: Merge Queue & Automation (PR #78 - merged)
- Phase Q2: PR Action Intelligence (PR #85 - open)
- Phase 2.5: Infrastructure decisions (PR #63 - open)

This brings the automation stack online and stabilizes Railway deployments.
This commit is contained in:
Claude
2025-11-18 06:29:06 +00:00
parent 1a80c59d8b
commit deab4e79a2
8 changed files with 951 additions and 6 deletions

View File

@@ -56,6 +56,10 @@ class Settings(BaseSettings):
BLOCKCHAIN_DIFFICULTY: int = 4
MINING_REWARD: float = 50.0
# GitHub Automation (Phase Q)
GITHUB_TOKEN: str = ""
GITHUB_WEBHOOK_SECRET: str = ""
class Config:
env_file = ".env"
case_sensitive = True

View File

@@ -15,7 +15,7 @@ from app.routers import (
digitalocean, github, huggingface, vscode, games, browser, dashboard,
railway, vercel, stripe, twilio, slack, discord, sentry, api_health, agents,
capture, identity_center, notifications_center, creator, compliance_ops,
search, cloudflare, system
search, cloudflare, system, webhooks
)
from app.services.crypto import rotate_plaintext_wallet_keys
@@ -156,6 +156,16 @@ app.include_router(api_health.router)
# Agent Library
app.include_router(agents.router)
# GitHub Webhooks (Phase Q automation)
app.include_router(webhooks.router)
# Prism Console (Phase 2.5) - Admin interface at /prism
prism_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "prism-console")
if os.path.exists(prism_dir):
app.mount("/prism", StaticFiles(directory=prism_dir, html=True), name="prism")
print(f"✅ Prism Console mounted at /prism")
# Static file serving for the BlackRoad OS front-end
static_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static")
@@ -175,6 +185,7 @@ if os.path.exists(static_dir):
"version": settings.APP_VERSION,
"environment": settings.ENVIRONMENT,
"docs": "/api/docs",
"prism": "/prism",
"status": "operational",
"note": "Front-end not found. API is operational."
}
@@ -188,6 +199,7 @@ else:
"version": settings.APP_VERSION,
"environment": settings.ENVIRONMENT,
"docs": "/api/docs",
"prism": "/prism",
"status": "operational",
"note": "API-only mode. Front-end not deployed."
}

View File

@@ -0,0 +1,152 @@
"""
GitHub Webhooks Router
Receives and processes GitHub webhook events for PR automation.
Part of Phase Q/Q2 - Merge Queue & Automation Strategy.
Related docs: OPERATOR_PR_EVENT_HANDLERS.md, MERGE_QUEUE_PLAN.md
"""
from fastapi import APIRouter, Request, HTTPException, Depends, Header
from sqlalchemy.ext.asyncio import AsyncSession
import hmac
import hashlib
from typing import Optional
import logging
from ..database import get_db
from ..services import github_events
from ..config import settings
router = APIRouter(prefix="/api/webhooks", tags=["Webhooks"])
logger = logging.getLogger(__name__)
def verify_github_signature(payload: bytes, signature: str, secret: str) -> bool:
"""Verify GitHub webhook signature"""
if not signature or not signature.startswith("sha256="):
return False
expected_signature = "sha256=" + hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@router.post("/github")
async def github_webhook(
request: Request,
x_hub_signature_256: Optional[str] = Header(None),
x_github_event: Optional[str] = Header(None),
db: AsyncSession = Depends(get_db)
):
"""
Receive GitHub webhook events
This endpoint receives webhook events from GitHub for:
- Pull requests (opened, closed, merged, labeled, etc.)
- Pull request reviews (submitted, approved, changes_requested)
- Status checks (check_run, check_suite completed)
Events are processed by the github_events service and logged to the database.
**Security**: Webhooks are verified using HMAC-SHA256 signature.
**Setup**: In GitHub repository settings → Webhooks:
- Payload URL: https://your-domain.com/api/webhooks/github
- Content type: application/json
- Secret: Set GITHUB_WEBHOOK_SECRET environment variable
- Events: Pull requests, Pull request reviews, Statuses, Check runs
"""
# Read request body
body = await request.body()
# Verify signature if secret is configured
webhook_secret = getattr(settings, 'GITHUB_WEBHOOK_SECRET', None)
if webhook_secret and x_hub_signature_256:
if not verify_github_signature(body, x_hub_signature_256, webhook_secret):
logger.warning("Invalid GitHub webhook signature")
raise HTTPException(status_code=401, detail="Invalid signature")
elif webhook_secret and not x_hub_signature_256:
logger.warning("Missing GitHub webhook signature")
raise HTTPException(status_code=401, detail="Missing signature")
# Verify event type header
if not x_github_event:
raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
# Parse JSON payload
try:
payload = await request.json()
except Exception as e:
logger.error(f"Failed to parse webhook payload: {e}")
raise HTTPException(status_code=400, detail="Invalid JSON payload")
# Log webhook received
logger.info(
f"GitHub webhook received: {x_github_event} | "
f"Action: {payload.get('action')} | "
f"PR: #{payload.get('pull_request', {}).get('number', 'N/A')}"
)
# Process event asynchronously
try:
await github_events.handle_event(
event_type=x_github_event,
payload=payload,
db=db
)
except Exception as e:
logger.error(f"Error processing GitHub event: {e}", exc_info=True)
# Return 200 even on processing errors to avoid GitHub retries
# Errors are logged for debugging
return {"status": "received", "processing_error": str(e)}
return {
"status": "received",
"event": x_github_event,
"action": payload.get("action"),
"pr_number": payload.get("pull_request", {}).get("number")
}
@router.get("/github/ping")
async def github_webhook_ping():
"""
Ping endpoint for testing GitHub webhook configuration
Use this to verify your webhook is reachable before setting it up in GitHub.
"""
return {
"status": "ok",
"message": "GitHub webhook endpoint is reachable",
"configured": hasattr(settings, 'GITHUB_WEBHOOK_SECRET') and settings.GITHUB_WEBHOOK_SECRET is not None
}
@router.get("/status")
async def webhooks_status():
"""
Get webhook configuration status
Returns information about which webhooks are configured and ready.
"""
return {
"github": {
"endpoint": "/api/webhooks/github",
"configured": hasattr(settings, 'GITHUB_WEBHOOK_SECRET') and settings.GITHUB_WEBHOOK_SECRET is not None,
"events_supported": [
"pull_request",
"pull_request_review",
"pull_request_review_comment",
"status",
"check_run",
"check_suite"
]
}
}