From 8bc8c9750439d7cac63b1f1490c47c03919fae79 Mon Sep 17 00:00:00 2001 From: Riz Ashraf Date: Thu, 8 Oct 2026 11:33:13 +0100 Subject: [PATCH] feat: use UDP rendezvous for git pre-push hook Replaces the Python-based pre-push hook and long-polling HTTP logic with a native shell script using udp-send --wait to block on an ephemeral UDP port in the kernel. mcp-memory-server acts as the broker and replies to subscribers. --- scripts/git-pre-push.sh | 45 ++++++++++++++++++++++++ server/src/lib.rs | 78 +++++++++++++++++++++++++++++------------ 2 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 scripts/git-pre-push.sh diff --git a/scripts/git-pre-push.sh b/scripts/git-pre-push.sh new file mode 100644 index 0000000..64d0e3e --- /dev/null +++ b/scripts/git-pre-push.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Native shell pre-push hook using UDP Rendezvous + +PORT=${MCP_UDP_PORT1:-3001} + +while read local_ref local_sha remote_ref remote_sha; do + if [ "$local_sha" = "0000000000000000000000000000000000000000" ] || [ "$local_ref" = "(delete)" ]; then + continue + fi + if [[ "$local_ref" == refs/tags/* ]]; then + continue + fi + + BRANCH=${local_ref#refs/heads/} + + echo -e "\033[36m[Pre-Push Gatekeeper]\033[0m Checking gate for branch '$BRANCH'..." + + # Check if gate is already authorized or blocked + if mcp-memory-server gate verify --action push --target "$BRANCH" --consume 2>/dev/null; then + echo -e "\033[32m[Pre-Push Gatekeeper]\033[0m Push AUTHORIZED by MCP Memory gate for branch '$BRANCH'." + continue + else + EXIT_CODE=$? + if [ $EXIT_CODE -eq 1 ]; then + echo -e "\033[31m[Pre-Push Gatekeeper]\033[0m Push BLOCKED by MCP Memory gate for branch '$BRANCH'." + exit 1 + fi + + # If exit code is 2 (Not Found), we wait via UDP Rendezvous + echo -e "\033[33m[Pre-Push Gatekeeper]\033[0m No pre-authorized gate for '$BRANCH'. Waiting for approval via UDP rendezvous..." + + # Payload for UDP + PAYLOAD="{\"type\":\"gate_wait\",\"action\":\"push\",\"target\":\"$BRANCH\"}" + + if udp-send --wait "$PORT" "$PAYLOAD"; then + echo -e "\033[32m[Pre-Push Gatekeeper]\033[0m Push AUTHORIZED via UDP rendezvous for branch '$BRANCH'." + continue + else + echo -e "\033[31m[Pre-Push Gatekeeper]\033[0m Push DENIED or timed out via UDP rendezvous for branch '$BRANCH'." + exit 1 + fi + fi +done + +exit 0 diff --git a/server/src/lib.rs b/server/src/lib.rs index db6be44..ee91dc9 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -335,32 +335,66 @@ pub async fn run_server(state: Arc) -> Result<(), Box = HashMap::new(); let mut buf = vec![0u8; 65536]; + let mut event_rx = udp_state.handler.state.event_bus_tx.subscribe(); + loop { - if let Ok((len, _addr)) = socket.recv_from(&mut buf).await - && let Ok(payload) = - serde_json::from_slice::(&buf[..len]) - { - udp_state - .handler - .state - .record_terminal_history(payload.clone()); + tokio::select! { + recv_res = socket_rx.recv_from(&mut buf) => { + if let Ok((len, addr)) = recv_res { + if let Ok(payload) = serde_json::from_slice::(&buf[..len]) { + udp_state + .handler + .state + .record_terminal_history(payload.clone()); - let ws_msg = serde_json::json!({ - "type": "terminal_telemetry", - "data": payload - }); - let msg_str = ws_msg.to_string(); + let ws_msg = serde_json::json!({ + "type": "terminal_telemetry", + "data": payload + }); + let msg_str = ws_msg.to_string(); - let senders: Vec<_> = udp_state - .clients - .read() - .unwrap_or_else(|e| e.into_inner()) - .values() - .cloned() - .collect(); - for tx in senders { - let _ = tx.try_send(msg_str.clone()); + let senders: Vec<_> = udp_state + .clients + .read() + .unwrap_or_else(|e| e.into_inner()) + .values() + .cloned() + .collect(); + for tx in senders { + let _ = tx.try_send(msg_str.clone()); + } + } else if let Ok(gate_wait) = serde_json::from_slice::(&buf[..len]) { + if gate_wait.get("type").and_then(|t| t.as_str()) == Some("gate_wait") { + if let (Some(action), Some(target)) = ( + gate_wait.get("action").and_then(|a| a.as_str()), + gate_wait.get("target").and_then(|t| t.as_str()) + ) { + subscribers.insert((action.to_string(), target.to_string()), addr); + } + } + } + } + } + Ok(event) = event_rx.recv() => { + if event.topic == "gate:event" { + if let (Some(action), Some(target), Some(status)) = ( + event.payload.get("action").and_then(|a| a.as_str()), + event.payload.get("target").and_then(|t| t.as_str()), + event.payload.get("status").and_then(|s| s.as_str()), + ) { + if status == "authorized" || status == "blocked" { + if let Some(addr) = subscribers.remove(&(action.to_string(), target.to_string())) { + let response = if status == "authorized" { b"APPROVED" } else { b"REJECTED" }; + let _ = socket.send_to(response, addr).await; + } + } + } + } } } }