#!/bin/sh # webdev — launch ungoogled-chromium restricted to internal URLs only. # Intended for local web development. Blocks all external navigation. # # Allowed: # localhost, 127.0.0.1, ::1 # 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (LAN) # 100.64.0.0/10 (Tailscale CGNAT range) # *.localhost, *.local # # Usage: # webdev — opens to localhost:3000 # webdev 8080 — opens to localhost:8080 # webdev http://10.0.0.142 — opens specific internal URL CHROMIUM="chromium" command -v ungoogled-chromium >/dev/null 2>&1 && CHROMIUM="ungoogled-chromium" command -v chromium >/dev/null 2>&1 || { echo "error: no chromium binary found" >&2; exit 1; } PROFILE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/webdev-chromium" # Proxy PAC that blocks everything except internal addresses. # Chromium evaluates this for every request — DIRECT means allow, PROXY means block. PAC_FILE="$PROFILE_DIR/internal-only.pac" mkdir -p "$PROFILE_DIR" cat > "$PAC_FILE" << 'PACEOF' function FindProxyForURL(url, host) { // Strip brackets from IPv6 var h = host.replace(/[\[\]]/g, ""); // Always allow localhost if (h === "localhost" || h === "127.0.0.1" || h === "::1" || shExpMatch(h, "*.localhost") || shExpMatch(h, "*.local")) { return "DIRECT"; } // Allow private IPv4 ranges (LAN) if (isInNet(h, "10.0.0.0", "255.0.0.0") || isInNet(h, "172.16.0.0", "255.240.0.0") || isInNet(h, "192.168.0.0", "255.255.0.0")) { return "DIRECT"; } // Allow Tailscale CGNAT range (100.64.0.0/10) if (isInNet(h, "100.64.0.0", "255.192.0.0")) { return "DIRECT"; } // Block everything else by routing to a non-existent proxy return "PROXY 0.0.0.0:9"; } PACEOF # Determine start URL if [ -z "$1" ]; then URL="http://localhost:3000" elif echo "$1" | grep -qE '^[0-9]+$'; then URL="http://localhost:$1" else URL="$1" fi exec "$CHROMIUM" \ --user-data-dir="$PROFILE_DIR" \ --proxy-pac-url="file://$PAC_FILE" \ --no-first-run \ --disable-background-networking \ --disable-client-side-phishing-detection \ --disable-default-apps \ --disable-extensions \ --disable-sync \ --disable-translate \ --disable-domain-reliability \ --no-pings \ --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE localhost, EXCLUDE *.localhost, EXCLUDE *.local, EXCLUDE 127.0.0.1, EXCLUDE 10.*, EXCLUDE 172.16.*, EXCLUDE 172.17.*, EXCLUDE 172.18.*, EXCLUDE 172.19.*, EXCLUDE 172.20.*, EXCLUDE 172.21.*, EXCLUDE 172.22.*, EXCLUDE 172.23.*, EXCLUDE 172.24.*, EXCLUDE 172.25.*, EXCLUDE 172.26.*, EXCLUDE 172.27.*, EXCLUDE 172.28.*, EXCLUDE 172.29.*, EXCLUDE 172.30.*, EXCLUDE 172.31.*, EXCLUDE 192.168.*, EXCLUDE 100.64.*, EXCLUDE 100.65.*, EXCLUDE 100.66.*, EXCLUDE 100.67.*, EXCLUDE 100.68.*, EXCLUDE 100.69.*, EXCLUDE 100.70.*, EXCLUDE 100.71.*, EXCLUDE 100.72.*, EXCLUDE 100.73.*, EXCLUDE 100.74.*, EXCLUDE 100.75.*, EXCLUDE 100.76.*, EXCLUDE 100.77.*, EXCLUDE 100.78.*, EXCLUDE 100.79.*, EXCLUDE 100.80.*, EXCLUDE 100.81.*, EXCLUDE 100.82.*, EXCLUDE 100.83.*, EXCLUDE 100.84.*, EXCLUDE 100.85.*, EXCLUDE 100.86.*, EXCLUDE 100.87.*, EXCLUDE 100.88.*, EXCLUDE 100.89.*, EXCLUDE 100.90.*, EXCLUDE 100.91.*, EXCLUDE 100.92.*, EXCLUDE 100.93.*, EXCLUDE 100.94.*, EXCLUDE 100.95.*, EXCLUDE 100.96.*, EXCLUDE 100.97.*, EXCLUDE 100.98.*, EXCLUDE 100.99.*, EXCLUDE 100.100.*, EXCLUDE 100.101.*, EXCLUDE 100.102.*, EXCLUDE 100.103.*, EXCLUDE 100.104.*, EXCLUDE 100.105.*, EXCLUDE 100.106.*, EXCLUDE 100.107.*, EXCLUDE 100.108.*, EXCLUDE 100.109.*, EXCLUDE 100.110.*, EXCLUDE 100.111.*, EXCLUDE 100.112.*, EXCLUDE 100.113.*, EXCLUDE 100.114.*, EXCLUDE 100.115.*, EXCLUDE 100.116.*, EXCLUDE 100.117.*, EXCLUDE 100.118.*, EXCLUDE 100.119.*, EXCLUDE 100.120.*, EXCLUDE 100.121.*, EXCLUDE 100.122.*, EXCLUDE 100.123.*, EXCLUDE 100.124.*, EXCLUDE 100.125.*, EXCLUDE 100.126.*, EXCLUDE 100.127.*" \ "$URL"