From 0e9b3e35dfd2c92f80419b5e9ddbec7d31fcd0b0 Mon Sep 17 00:00:00 2001 From: Janis Jansons Date: Thu, 8 Jan 2026 20:33:45 +0200 Subject: [PATCH] Initial commit --- .claude/settings.local.json | 7 ++ .gitignore | 3 + README.md | 69 +++++++++++++++++++ dev-proxy.sh | 133 ++++++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .gitignore create mode 100644 README.md create mode 100755 dev-proxy.sh diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..00d0363 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(curl:*)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abb77c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pem +*.key +debug-*.conf \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2017cf1 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# dev-proxy + +A local development proxy that intercepts a production domain and routes most traffic to your local dev server while passing through specific API patterns to the real backend. + +## Dependencies + +### macOS (Homebrew) +```bash +brew install nginx mkcert nss bind +mkcert -install +``` + +### Arch Linux (pacman) +```bash +sudo pacman -S nginx mkcert nss bind +mkcert -install +``` + +## Usage + +```bash +./dev-proxy.sh "" [real-ip] +``` + +### Parameters + +| Parameter | Description | Example | +|-----------|-------------|---------| +| `domain` | Production domain to intercept | `example.com` | +| `local-target` | Local dev server address | `127.0.0.1:3000` | +| `passthrough-patterns` | Comma-separated URL patterns to forward to real backend | `"api/*,assets/*"` | +| `real-ip` | (Optional) Override resolved IP for the real backend | `1.2.3.4` | + +### Example + +```bash +./dev-proxy.sh myapp.example.com 127.0.0.1:3000 "api/*,auth/*,static/*" +``` + +This will: +1. Add `127.0.0.1 myapp.example.com` to `/etc/hosts` +2. Generate a trusted SSL certificate using mkcert +3. Start nginx on port 443 +4. Route `/api/*`, `/auth/*`, `/static/*` to the real `myapp.example.com` backend +5. Route everything else to your local dev server at `127.0.0.1:3000` + +Press `Ctrl+C` to stop. The script automatically cleans up `/etc/hosts` and temporary files on exit. + +## How It Works + +``` +Browser → nginx (localhost:443) → Local dev server (localhost:3000) + → Real backend (for passthrough patterns) +``` + +The proxy: +- Modifies `/etc/hosts` to point the domain to localhost +- Uses mkcert for trusted local HTTPS certificates +- Routes requests based on URL patterns +- Supports WebSocket connections (for HMR/hot reload) +- Sets proper SNI headers for the real backend + +## Troubleshooting + +**Certificate not trusted**: Run `mkcert -install` to install the root CA. + +**Port 443 in use**: Stop any existing web servers or other instances of the proxy. + +**DNS still resolving to real IP**: Clear DNS cache or wait for TTL to expire. The script uses Google DNS (8.8.8.8) to resolve the real IP before modifying hosts. diff --git a/dev-proxy.sh b/dev-proxy.sh new file mode 100755 index 0000000..754a451 --- /dev/null +++ b/dev-proxy.sh @@ -0,0 +1,133 @@ +#!/bin/bash +DOMAIN=$1 +TARGET=$2 +PATTERNS=$3 +REAL_IP_OVERRIDE=$4 +HOSTS_LINE="127.0.0.1 $DOMAIN" +NGINX_CONF=$(mktemp) +CERT_DIR=$(pwd) +CLEANUP_DONE=0 + +# Resolve real IP before we mess with hosts +if [ -n "$REAL_IP_OVERRIDE" ]; then + REAL_IP="$REAL_IP_OVERRIDE" + echo "Using provided IP: $REAL_IP" +else + # Use external DNS (8.8.8.8) to bypass /etc/hosts + # Get final IP (not CNAME) by filtering for IP addresses only + REAL_IP=$(dig +short "$DOMAIN" @8.8.8.8 | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1) + if [ -z "$REAL_IP" ]; then + echo "Could not resolve $DOMAIN" + exit 1 + fi + # Check if resolved IP is localhost (proxy loop) + if [[ "$REAL_IP" == "127.0.0.1" || "$REAL_IP" == "::1" || "$REAL_IP" == "localhost" ]]; then + echo "ERROR: $DOMAIN resolves to localhost ($REAL_IP)" + echo "This would cause a proxy loop." + echo "Provide the real IP as the 4th argument:" + echo " $0 $DOMAIN $TARGET \"$PATTERNS\" " + exit 1 + fi + echo "Resolved $DOMAIN to $REAL_IP" +fi + +cleanup() { + [ "$CLEANUP_DONE" -eq 1 ] && return + CLEANUP_DONE=1 + echo "Cleaning up..." + if [[ "$OSTYPE" == "darwin"* ]]; then + sudo sed -i "" "\|$HOSTS_LINE|d" /etc/hosts + else + sudo sed -i "\|$HOSTS_LINE|d" /etc/hosts + fi + rm -f "$DOMAIN.pem" "$DOMAIN-key.pem" "$NGINX_CONF" + echo "Done" +} + +trap cleanup EXIT + +echo "$HOSTS_LINE" | sudo tee -a /etc/hosts +mkcert "$DOMAIN" + +# Build passthrough location blocks +PASSTHROUGH_LOCATIONS="" +if [ -n "$PATTERNS" ]; then + IFS=',' read -ra PATTERN_ARRAY <<< "$PATTERNS" + for pattern in "${PATTERN_ARRAY[@]}"; do + # Remove trailing * from pattern if present + clean_pattern="${pattern%\*}" + PASSTHROUGH_LOCATIONS+=" + # Passthrough for /${clean_pattern} + location ~* ^/${clean_pattern} { + proxy_pass https://$REAL_IP; + proxy_ssl_server_name on; + proxy_ssl_name $DOMAIN; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_http_version 1.1; + proxy_set_header Connection \"\"; + } +" + done +fi + +cat > "$NGINX_CONF" <