Initial commit
This commit is contained in:
7
.claude/settings.local.json
Normal file
7
.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(curl:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.pem
|
||||
*.key
|
||||
debug-*.conf
|
||||
69
README.md
Normal file
69
README.md
Normal file
@@ -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 <domain> <local-target> "<passthrough-patterns>" [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.
|
||||
133
dev-proxy.sh
Executable file
133
dev-proxy.sh
Executable file
@@ -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\" <real-ip>"
|
||||
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" <<EOF
|
||||
worker_processes 1;
|
||||
error_log /dev/stderr info;
|
||||
pid /tmp/nginx-dev-proxy.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
access_log /dev/stdout combined;
|
||||
|
||||
# Upstream for local dev server
|
||||
upstream local_dev {
|
||||
server $TARGET;
|
||||
}
|
||||
|
||||
# Upstream for real server
|
||||
upstream real_server {
|
||||
server $REAL_IP:443;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name $DOMAIN;
|
||||
|
||||
ssl_certificate $CERT_DIR/$DOMAIN.pem;
|
||||
ssl_certificate_key $CERT_DIR/$DOMAIN-key.pem;
|
||||
|
||||
# Passthrough patterns go to real upstream
|
||||
$PASSTHROUGH_LOCATIONS
|
||||
|
||||
# Everything else goes to local dev server
|
||||
location / {
|
||||
proxy_pass http://local_dev;
|
||||
proxy_http_version 1.1;
|
||||
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_set_header X-Forwarded-Host \$host;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Save config for debugging
|
||||
cp "$NGINX_CONF" "$(pwd)/debug-nginx.conf"
|
||||
echo "Generated nginx config saved to: $(pwd)/debug-nginx.conf"
|
||||
echo "--- Generated nginx config ---"
|
||||
cat "$NGINX_CONF"
|
||||
echo "--- End nginx config ---"
|
||||
|
||||
# Run nginx (needs sudo for port 443)
|
||||
sudo nginx -c "$NGINX_CONF" -g "daemon off;"
|
||||
Reference in New Issue
Block a user