diff --git a/docs/mitm.md b/docs/mitm.md index 53834af..4e88946 100644 --- a/docs/mitm.md +++ b/docs/mitm.md @@ -129,20 +129,16 @@ Events dispatched through `tokio::sync::mpsc` channels from MITM → API handler ## Setup -### UID-Scoped iptables (Classic Mode) +### UID-Scoped iptables (Linux) + +The `zerogravity-ls` user and iptables rules are created automatically by `setup-linux.sh`: ```bash -# One-time setup — creates zerogravity-ls user + iptables rule -sudo ./scripts/mitm-redirect.sh install - -# Run proxy (standalone LS + MITM both enabled by default) -RUST_LOG=info ./target/release/zerogravity +./scripts/setup-linux.sh +zg start # Check intercepted usage curl -s http://localhost:8741/v1/usage | jq . - -# Cleanup -sudo ./scripts/mitm-redirect.sh uninstall ``` ### Headless Mode diff --git a/scripts/mitm-redirect.sh b/scripts/mitm-redirect.sh deleted file mode 100755 index 13b84bc..0000000 --- a/scripts/mitm-redirect.sh +++ /dev/null @@ -1,183 +0,0 @@ -#!/usr/bin/env bash -# mitm-redirect.sh — UID-scoped iptables redirect for MITM interception -# -# Creates a dedicated system user for the standalone LS and adds an iptables -# rule that ONLY redirects traffic from that user's UID. No /etc/hosts -# modification, no system-wide changes. -# -# Flow: -# 1. Standalone LS runs as 'zerogravity-ls' user (via sudo -u) -# 2. iptables catches :443 traffic from that UID only → REDIRECT to MITM port -# 3. MITM terminates TLS (Go client trusts our CA via SSL_CERT_FILE) -# 4. MITM forwards upstream, captures usage -# -# What this does NOT affect: -# - Your real Antigravity session (different UID) -# - Any other software on your PC (different UID) -# - DNS resolution (no /etc/hosts changes) -# -# Usage: -# sudo ./scripts/mitm-redirect.sh install [mitm_port] -# sudo ./scripts/mitm-redirect.sh uninstall [mitm_port] -# sudo ./scripts/mitm-redirect.sh status - -set -euo pipefail - -MITM_PORT="${2:-8742}" -LS_USER="zerogravity-ls" -DATA_DIR="/tmp/zerogravity-standalone" -LS_BINARY="/usr/share/antigravity/resources/app/extensions/antigravity/bin/language_server_linux_x64" -SUDOERS_FILE="/etc/sudoers.d/zerogravity-ls" - -install() { - if [[ $EUID -ne 0 ]]; then - echo "Error: must run as root (sudo)" - exit 1 - fi - - echo "[mitm-redirect] Installing UID-scoped iptables redirect → :$MITM_PORT" - echo - - # ── 1. Create system user ─────────────────────────────────────────── - if id "$LS_USER" &>/dev/null; then - echo " ✓ user '$LS_USER' already exists (uid=$(id -u "$LS_USER"))" - else - useradd -r -s /usr/sbin/nologin -d "$DATA_DIR" "$LS_USER" - echo " + created user '$LS_USER' (uid=$(id -u "$LS_USER"))" - fi - local LS_UID - LS_UID=$(id -u "$LS_USER") - - # ── 2. Create data directory (writable by both users) ──────────────── - mkdir -p "$DATA_DIR/.gemini" - chmod 1777 "$DATA_DIR" "$DATA_DIR/.gemini" - echo " + data dir: $DATA_DIR (mode 1777, writable by all)" - - # ── 3. Sudoers entry ──────────────────────────────────────────────── - # Allow the invoking user (SUDO_USER) to run ANY command as zerogravity-ls. - # This is needed for the proxy to spawn the LS binary. - local REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || whoami)}" - cat > "$SUDOERS_FILE" </dev/null || true - - iptables -t nat -A OUTPUT -m owner --uid-owner "$LS_UID" \ - -p tcp --dport 443 -j REDIRECT --to-port "$MITM_PORT" - echo " + iptables: uid=$LS_UID :443 → :$MITM_PORT" - - echo - echo "[mitm-redirect] ✓ Installed (only affects uid=$LS_UID)" - echo " Restart the proxy to take effect:" - echo " RUST_LOG=info ./target/release/zerogravity --standalone" -} - -uninstall() { - if [[ $EUID -ne 0 ]]; then - echo "Error: must run as root (sudo)" - exit 1 - fi - - echo "[mitm-redirect] Removing UID-scoped iptables redirect" - echo - - # Remove iptables rule - if id "$LS_USER" &>/dev/null; then - local LS_UID - LS_UID=$(id -u "$LS_USER") - iptables -t nat -D OUTPUT -m owner --uid-owner "$LS_UID" \ - -p tcp --dport 443 -j REDIRECT --to-port "$MITM_PORT" 2>/dev/null || true - echo " - iptables: removed REDIRECT rule for uid=$LS_UID" - fi - - # Remove sudoers entry - rm -f "$SUDOERS_FILE" - echo " - sudoers: removed $SUDOERS_FILE" - - # Clean data dir - rm -rf "$DATA_DIR" - echo " - data dir: removed $DATA_DIR" - - # Optionally remove user (commented out — user might want to keep it) - # userdel "$LS_USER" 2>/dev/null || true - echo " ℹ user '$LS_USER' kept (run 'sudo userdel $LS_USER' to remove)" - - echo - echo "[mitm-redirect] ✓ Uninstalled." -} - -status() { - echo "[mitm-redirect] Status" - echo - - # Check user - if id "$LS_USER" &>/dev/null; then - local LS_UID - LS_UID=$(id -u "$LS_USER") - echo " user: $LS_USER (uid=$LS_UID) ✓" - else - echo " user: $LS_USER (not found) ✗" - echo - echo " Run: sudo $0 install" - return - fi - - # Check sudoers - if [[ -f "$SUDOERS_FILE" ]]; then - echo " sudoers: $SUDOERS_FILE ✓" - else - echo " sudoers: $SUDOERS_FILE (not found) ✗" - fi - - # Check iptables - echo " iptables:" - if iptables -t nat -L OUTPUT -n 2>/dev/null | grep -q "owner UID match.*$LS_UID"; then - iptables -t nat -L OUTPUT -n -v 2>/dev/null | grep "owner UID" | sed 's/^/ /' - else - echo " (no rules for uid=$LS_UID)" - fi - - # Check data dir - echo " data dir: $(ls -ld "$DATA_DIR" 2>/dev/null || echo '(not found)')" - - # Test sudo - echo - echo " sudo test:" - if sudo -n -u "$LS_USER" true 2>/dev/null; then - echo " ✓ can run as $LS_USER without password" - else - echo " ✗ cannot run as $LS_USER (check sudoers)" - fi -} - -case "${1:-help}" in - install) install ;; - uninstall) uninstall ;; - status) status ;; - *) - echo "Usage: sudo $0 {install|uninstall|status} [mitm_port]" - echo - echo "Redirects ONLY the standalone LS's outgoing :443 traffic through" - echo "the MITM proxy using UID-scoped iptables rules." - echo - echo "This does NOT affect:" - echo " - Your real Antigravity coding session" - echo " - Any other software on your PC" - echo " - DNS resolution (/etc/hosts is untouched)" - echo - echo " install [port] Create user + iptables REDIRECT for that UID" - echo " uninstall [port] Remove iptables rule + sudoers" - echo " status Show current state" - echo - echo "Default MITM port: 8742" - ;; -esac diff --git a/scripts/proxyctl b/scripts/proxyctl deleted file mode 100755 index 5cc1908..0000000 --- a/scripts/proxyctl +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env bash -# proxyctl — manage the antigravity proxy daemon -set -euo pipefail - -SERVICE="antigravity-proxy" -SCRIPT_PATH="$(readlink -f "$0")" -PROJECT_DIR="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)" -PORT="${PROXY_PORT:-8741}" -BASE_URL="http://localhost:${PORT}" - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -DIM='\033[2m' -BOLD='\033[1m' -NC='\033[0m' - -usage() { - echo -e "${BOLD}proxyctl${NC} — antigravity proxy daemon manager" - echo "" - echo -e " ${CYAN}start${NC} Start the proxy daemon" - echo -e " ${CYAN}stop${NC} Stop the proxy daemon" - echo -e " ${CYAN}restart${NC} Rebuild + restart" - echo -e " ${CYAN}rebuild${NC} Build release binary only" - echo -e " ${CYAN}status${NC} Service status + quota + usage" - echo -e " ${CYAN}logs${NC} [N] Show last N lines (default 30)" - echo -e " ${CYAN}logs-follow${NC} [N] Tail last N lines + follow" - echo -e " ${CYAN}logs-all${NC} Full log dump" - echo -e " ${CYAN}test${NC} [msg] Quick test request (gemini-3-flash)" - echo -e " ${CYAN}health${NC} Health check" - echo "" -} - -do_build() { - echo -e "${YELLOW}Building release binary...${NC}" - cd "$PROJECT_DIR" - cargo build --release 2>&1 - echo -e "${GREEN}Build complete.${NC}" -} - -do_start() { - systemctl --user daemon-reload - systemctl --user start "$SERVICE" - echo -e "${GREEN}Started.${NC} Waiting for ready..." - # Wait up to 10s for health - for i in $(seq 1 20); do - if curl -sf "${BASE_URL}/health" >/dev/null 2>&1; then - echo -e "${GREEN}Proxy is up on port ${PORT}.${NC}" - return 0 - fi - sleep 0.5 - done - echo -e "${RED}Proxy didn't become healthy in 10s. Check logs:${NC}" - journalctl --user -u "$SERVICE" --no-pager -n 20 - return 1 -} - -do_stop() { - systemctl --user stop "$SERVICE" 2>/dev/null || true - echo -e "${YELLOW}Stopped.${NC}" -} - -do_restart() { - echo -e "${YELLOW}Stopping...${NC}" - do_stop - do_build - do_start -} - -do_status() { - echo -e "${BOLD}── Service ──${NC}" - systemctl --user status "$SERVICE" --no-pager 2>/dev/null | head -6 || echo -e "${RED}Not running${NC}" - echo "" - - # Health check - if ! curl -sf "${BASE_URL}/health" >/dev/null 2>&1; then - echo -e "${RED}Proxy is not responding on port ${PORT}.${NC}" - return 1 - fi - - echo -e "${BOLD}── Quota ──${NC}" - curl -sf "${BASE_URL}/v1/quota" 2>/dev/null | jq '.' 2>/dev/null || echo -e "${DIM}(no quota data)${NC}" - echo "" - - echo -e "${BOLD}── Usage ──${NC}" - curl -sf "${BASE_URL}/v1/usage" 2>/dev/null | jq '.' 2>/dev/null || echo -e "${DIM}(no usage data)${NC}" - echo "" - - echo -e "${BOLD}── Sessions ──${NC}" - curl -sf "${BASE_URL}/v1/sessions" 2>/dev/null | jq '.' 2>/dev/null || echo -e "${DIM}(no sessions)${NC}" -} - -do_logs() { - local lines="${1:-30}" - journalctl --user -u "$SERVICE" --no-pager -n "$lines" -} - -do_logs_follow() { - local lines="${1:-30}" - journalctl --user -u "$SERVICE" --no-pager -n "$lines" -f -} - -do_logs_all() { - journalctl --user -u "$SERVICE" --no-pager -} - -do_test() { - local msg="${1:-Say hello in exactly 3 words}" - echo -e "${CYAN}Testing:${NC} ${msg}" - curl -sf "${BASE_URL}/v1/responses" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\": \"gemini-3-flash\", - \"input\": \"${msg}\", - \"stream\": false, - \"timeout\": 30 - }" | jq '.' -} - -do_health() { - curl -sf "${BASE_URL}/health" | jq '.' 2>/dev/null && echo -e "${GREEN}Healthy${NC}" || echo -e "${RED}Not responding${NC}" -} - -# ── Main ── -case "${1:-}" in - start) do_start ;; - stop) do_stop ;; - restart) do_restart ;; - rebuild) do_build ;; - status) do_status ;; - logs) do_logs "${2:-30}" ;; - logs-follow) do_logs_follow "${2:-30}" ;; - logs-all) do_logs_all ;; - test) do_test "${2:-}" ;; - health) do_health ;; - *) usage ;; -esac