184 lines
5.4 KiB
Bash
Executable File
184 lines
5.4 KiB
Bash
Executable File
#!/bin/bash
|
|
DOMAIN=$1
|
|
TARGET_RAW=$2
|
|
PATTERNS=$3
|
|
REAL_IP_OVERRIDE=$4
|
|
HOSTS_LINE="127.0.0.1 $DOMAIN"
|
|
|
|
# Parse TARGET: support both host:port and full URLs (http:// or https://)
|
|
if [[ "$TARGET_RAW" =~ ^https?:// ]]; then
|
|
TARGET_SCHEME=$(echo "$TARGET_RAW" | sed -E 's|^(https?)://.*|\1|')
|
|
TARGET_HOST=$(echo "$TARGET_RAW" | sed -E 's|^https?://([^/:]+).*|\1|')
|
|
TARGET_PORT=$(echo "$TARGET_RAW" | sed -E 's|^https?://[^/:]+:?([0-9]*)/?\s*$|\1|')
|
|
if [ -z "$TARGET_PORT" ]; then
|
|
if [ "$TARGET_SCHEME" = "https" ]; then
|
|
TARGET_PORT=443
|
|
else
|
|
TARGET_PORT=80
|
|
fi
|
|
fi
|
|
TARGET="${TARGET_HOST}:${TARGET_PORT}"
|
|
else
|
|
TARGET_SCHEME="http"
|
|
TARGET="$TARGET_RAW"
|
|
fi
|
|
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
|
|
|
|
if [ "$TARGET_SCHEME" = "https" ]; then
|
|
# For HTTPS targets, use proxy_pass directly (no upstream block)
|
|
# so nginx handles SNI correctly per-request
|
|
LOCAL_DEV_BLOCK="
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name $DOMAIN;
|
|
|
|
ssl_certificate $CERT_DIR/$DOMAIN.pem;
|
|
ssl_certificate_key $CERT_DIR/$DOMAIN-key.pem;
|
|
|
|
resolver 8.8.8.8 1.1.1.1 ipv6=off;
|
|
|
|
# Passthrough patterns go to real upstream
|
|
$PASSTHROUGH_LOCATIONS
|
|
|
|
# Everything else goes to local dev server
|
|
location / {
|
|
set \$backend \"${TARGET_SCHEME}://${TARGET_HOST}:${TARGET_PORT}\";
|
|
proxy_pass \$backend;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host ${TARGET_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;
|
|
proxy_ssl_server_name on;
|
|
proxy_ssl_name ${TARGET_HOST};
|
|
}
|
|
}"
|
|
else
|
|
# For plain host:port targets, use upstream block as before
|
|
LOCAL_DEV_BLOCK="
|
|
upstream local_dev {
|
|
server $TARGET;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}"
|
|
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;
|
|
$LOCAL_DEV_BLOCK
|
|
}
|
|
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;"
|