#!/usr/bin/env bash # Yukun's Blog · 通用子域名反代 + HTTPS 脚本 # # 用法: # ./deploy/proxy.sh <子域名> <端口> 反代子域名 → http://127.0.0.1:<端口>,自动签 HTTPS # ./deploy/proxy.sh remove <子域名> 移除该子域名的反代配置(证书保留) # # 示例: # ./deploy/proxy.sh db 7000 → https://db.sausagetoast.cloud → 127.0.0.1:7000 # ./deploy/proxy.sh remove db # # 子域名可传前缀(自动拼主域名)或完整域名(含点,直接使用)。 # 前置条件:子域名 DNS 已解析到本机、80 端口可达。幂等可重跑。 set -euo pipefail # ====== 配置区(与 deploy.sh 保持一致) ====== REMOTE_USER="root" # SSH 用户 REMOTE_HOST="sausagetoast.cloud" # VPS 地址 MAIN_DOMAIN="sausagetoast.cloud" # 主域名(拼接子域名用) # ============================================ SSH_OPTS="-o StrictHostKeyChecking=accept-new" ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" # 启用 HTTPS 的 awk 程序(与 deploy.sh 共用同一套标记): # ① 取消 443 段注释 ② 80 段切 301 跳转 ③ 移除 80 段反代块 read -r -d '' ENABLE_HTTPS_AWK <<'AWKPROG' || true BEGIN { in443=0; inredir=0; inserv=0 } /@@HTTPS-START@@/ { in443=1; next } /@@HTTPS-END@@/ { in443=0; next } /@@HTTPS-REDIRECT-START@@/ { inredir=1; next } /@@HTTPS-REDIRECT-END@@/ { inredir=0; next } /@@HTTP-SERVE-START@@/ { inserv=1; next } /@@HTTP-SERVE-END@@/ { inserv=0; next } in443 && /^# / { sub(/^# ?/, ""); print; next } in443 && /^#/ { sub(/^#/, ""); print; next } inredir && /^[[:space:]]*#.*return 301 https:/ { sub(/^[[:space:]]*#[[:space:]]*/, ""); print; next } inserv { next } { print } AWKPROG usage() { echo "用法: $0 <子域名> <端口> 或 $0 remove <子域名>" echo " 例: $0 db 7000 → https://db.${MAIN_DOMAIN} → 127.0.0.1:7000" echo " $0 remove db" exit 1 } # 生成反代 location 块(80/443 两处共用,缩进 $1) proxy_block() { local indent="$1" echo "${indent}location / {" echo "${indent} proxy_pass http://127.0.0.1:${PORT};" echo "${indent} proxy_http_version 1.1;" echo "${indent} proxy_set_header Host \$host;" echo "${indent} proxy_set_header X-Real-IP \$remote_addr;" echo "${indent} proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;" echo "${indent} proxy_set_header X-Forwarded-Proto \$scheme;" echo "${indent} proxy_set_header Upgrade \$http_upgrade;" echo "${indent} proxy_set_header Connection \"upgrade\";" echo "${indent} proxy_connect_timeout 60s;" echo "${indent} proxy_send_timeout 300s;" echo "${indent} proxy_read_timeout 300s;" echo "${indent} proxy_buffering off;" echo "${indent} client_max_body_size 100m;" echo "${indent}}" } # 本地生成站点配置 → /tmp/yukun-proxy-.conf gen_conf() { local f="/tmp/yukun-proxy-${FQDN}.conf" { echo "# ${FQDN} · 反代到本地 ${PORT} 端口服务(由 deploy/proxy.sh 生成)" echo "# 首次部署:80 段直接反代(HTTP 通);证书签发后自动启用 443 + 80 跳转。" echo "" echo "server {" echo " listen 80;" echo " listen [::]:80;" echo " server_name ${FQDN};" echo "" echo " # Let's Encrypt 证书验证(certbot webroot 用,无论是否启用 HTTPS 都需保留)" echo " location /.well-known/acme-challenge/ {" echo " root /var/www/html;" echo " }" echo "" echo " # @@HTTPS-REDIRECT-START@@" echo " # 启用 HTTPS 后取消下面这行注释,把 80 段变成跳转:" echo " # location / { return 301 https://\$host\$request_uri; }" echo " # @@HTTPS-REDIRECT-END@@" echo "" echo " # @@HTTP-SERVE-START@@" echo " # 启用 HTTPS 后,此段会被移除(跳转段接管):" proxy_block " " echo " # @@HTTP-SERVE-END@@" echo "}" echo "" echo "# 证书由 proxy.sh 签发到 /etc/letsencrypt/live/${FQDN}/" echo "# @@HTTPS-START@@" echo "# server {" echo "# listen 443 ssl http2;" echo "# listen [::]:443 ssl http2;" echo "# server_name ${FQDN};" echo "#" echo "# ssl_certificate /etc/letsencrypt/live/${FQDN}/fullchain.pem;" echo "# ssl_certificate_key /etc/letsencrypt/live/${FQDN}/privkey.pem;" echo "# ssl_protocols TLSv1.2 TLSv1.3;" echo "# ssl_ciphers HIGH:!aNULL:!MD5;" echo "# ssl_prefer_server_ciphers on;" echo "# ssl_session_cache shared:SSL:10m;" echo "# ssl_session_timeout 1d;" echo "#" proxy_block "# " echo "# add_header Strict-Transport-Security \"max-age=31536000; includeSubDomains\" always;" echo "# }" echo "# @@HTTPS-END@@" } > "$f" echo "$f" } # 校验并规范化子域名参数 normalize_fqdn() { local raw="$1" [[ -n "$raw" ]] || { echo "✗ 缺少子域名参数"; usage; } [[ "$raw" =~ ^[a-zA-Z0-9.-]+$ ]] && [[ "$raw" != *..* ]] && [[ "$raw" != -* ]] && [[ "$raw" != *- ]] \ || { echo "✗ 非法子域名: $raw"; exit 1; } if [[ "$raw" == *.* ]]; then FQDN="$raw" else FQDN="${raw}.${MAIN_DOMAIN}" fi } # ---------------------------------------------------------------- # 子命令:配置反代 + 签证书 + 启用 HTTPS # ---------------------------------------------------------------- cmd_setup() { local SUB="$1" PORT="$2" [[ "$PORT" =~ ^[0-9]+$ ]] && (( PORT >= 1 && PORT <= 65535 )) || { echo "✗ 非法端口: $PORT"; exit 1; } normalize_fqdn "$SUB" local CONF_TMP CONF_TMP="$(gen_conf)" echo "==> 上传 ${FQDN} 的 nginx 配置(反代 → 127.0.0.1:${PORT})到 ${REMOTE_USER}@${REMOTE_HOST} ..." scp $SSH_OPTS "$CONF_TMP" "${REMOTE_USER}@${REMOTE_HOST}:/tmp/yukun-proxy.conf" rm -f "$CONF_TMP" echo "==> 远端安装配置 + 签证书 + 启用 HTTPS ..." ssh $SSH_OPTS "${REMOTE_USER}@${REMOTE_HOST}" \ "SITE='$FQDN' PORT='$PORT' ENABLE_HTTPS_AWK=\"$(printf '%s' "$ENABLE_HTTPS_AWK" | base64)\" bash -s" <<'REMOTE' set -euo pipefail SUDO="" [ "$(id -u)" -ne 0 ] && SUDO="sudo" SITE="$SITE"; PORT="$PORT" ENABLE_HTTPS_AWK_b64="$ENABLE_HTTPS_AWK"; unset ENABLE_HTTPS_AWK # 1) 安装站点配置:优先 sites-available(Debian),其次 conf.d(CentOS) CONF="" if [ -d /etc/nginx/sites-enabled ] || $SUDO [ -d /etc/nginx/sites-enabled ]; then AVAIL=/etc/nginx/sites-available; EN=/etc/nginx/sites-enabled $SUDO mkdir -p "$AVAIL" "$EN" $SUDO cp /tmp/yukun-proxy.conf "$AVAIL/$SITE" $SUDO ln -sfn "$AVAIL/$SITE" "$EN/$SITE" if ! $SUDO grep -q "sites-enabled" /etc/nginx/nginx.conf 2>/dev/null; then echo " nginx.conf 缺 include sites-enabled/*,自动追加" $SUDO sed -i '/http {/a\ include /etc/nginx/sites-enabled/*;' /etc/nginx/nginx.conf fi CONF="$AVAIL/$SITE" echo " 站点: $CONF (+软链接 $EN/$SITE)" else $SUDO mkdir -p /etc/nginx/conf.d $SUDO cp /tmp/yukun-proxy.conf "/etc/nginx/conf.d/$SITE.conf" CONF="/etc/nginx/conf.d/$SITE.conf" echo " 站点: $CONF" fi # 1.5) 清理另一位置的重复配置(防止 conflicting server name 警告) if [ "$CONF" = "/etc/nginx/sites-available/$SITE" ]; then if $SUDO [ -f "/etc/nginx/conf.d/$SITE.conf" ]; then $SUDO rm -f "/etc/nginx/conf.d/$SITE.conf" echo " 已移除重复配置 /etc/nginx/conf.d/$SITE.conf" fi else if $SUDO [ -f "/etc/nginx/sites-available/$SITE" ]; then $SUDO rm -f "/etc/nginx/sites-available/$SITE" "/etc/nginx/sites-enabled/$SITE" echo " 已移除重复配置 sites-available/$SITE" fi fi # 2) 签 HTTPS 证书(已存在则跳过) CERT="/etc/letsencrypt/live/$SITE/fullchain.pem" if $SUDO [ -f "$CERT" ]; then echo "==> 已有证书 $CERT,跳过签发" else if ! command -v certbot >/dev/null 2>&1; then echo "==> 安装 certbot ..." if command -v apt >/dev/null 2>&1; then $SUDO apt update && $SUDO apt install -y certbot python3-certbot-nginx elif command -v dnf >/dev/null 2>&1; then $SUDO dnf install -y certbot python3-certbot-nginx elif command -v yum >/dev/null 2>&1; then $SUDO yum install -y certbot python3-certbot-nginx else echo "✗ 未识别的包管理器,请手动装 certbot"; exit 1; fi fi $SUDO mkdir -p /var/www/html echo "==> 签发证书 ..." $SUDO certbot certonly --webroot -w /var/www/html \ -d "$SITE" \ --non-interactive --agree-tos --register-unsafe --email "root@$SITE" fi # 3) 启用 HTTPS(取消 443 段注释 + 80 段切跳转 + 移除反代块) echo "==> 启用 HTTPS ..." printf '%s' "$ENABLE_HTTPS_AWK_b64" | base64 -d > /tmp/enable-https.awk $SUDO bash -c 'f="$1"; awk -f /tmp/enable-https.awk "$f" > "$f.tmp" && mv "$f.tmp" "$f"' _ "$CONF" rm -f /tmp/enable-https.awk echo "==> 测试 nginx 配置 ..." $SUDO nginx -t echo "==> reload nginx ..." $SUDO systemctl reload nginx || $SUDO systemctl restart nginx rm -f /tmp/yukun-proxy.conf echo "✓ 完成!https://$SITE → http://127.0.0.1:$PORT" REMOTE } # ---------------------------------------------------------------- # 子命令:移除反代配置 # ---------------------------------------------------------------- cmd_remove() { normalize_fqdn "$1" echo "==> 远端移除 $FQDN 的反代配置 ..." ssh $SSH_OPTS "${REMOTE_USER}@${REMOTE_HOST}" "SITE='$FQDN' bash -s" <<'REMOTE' set -euo pipefail SUDO="" [ "$(id -u)" -ne 0 ] && SUDO="sudo" SITE="$SITE" removed=0 # sites-enabled 软链接 if $SUDO [ -L "/etc/nginx/sites-enabled/$SITE" ]; then $SUDO rm -f "/etc/nginx/sites-enabled/$SITE"; removed=1 fi # sites-available 源文件 if $SUDO [ -f "/etc/nginx/sites-available/$SITE" ]; then $SUDO rm -f "/etc/nginx/sites-available/$SITE"; removed=1 fi # conf.d 配置 if $SUDO [ -f "/etc/nginx/conf.d/$SITE.conf" ]; then $SUDO rm -f "/etc/nginx/conf.d/$SITE.conf"; removed=1 fi if [ "$removed" -eq 0 ]; then echo "(未找到 $SITE 的 nginx 配置,可能已移除)" else echo "==> 测试 nginx 配置 ..." $SUDO nginx -t echo "==> reload nginx ..." $SUDO systemctl reload nginx || $SUDO systemctl restart nginx echo "✓ 已移除 $SITE 的反代配置" fi if $SUDO [ -d "/etc/letsencrypt/live/$SITE" ]; then echo " 证书已保留(续期任务仍在):certbot delete --cert-name $SITE 可清理" fi REMOTE } # ---------------------------------------------------------------- case "${1:-}" in remove) [ $# -eq 2 ] || { echo "✗ 用法: $0 remove <子域名>"; exit 1; } cmd_remove "$2" ;; "") usage ;; *) [ $# -eq 2 ] || { echo "✗ 用法: $0 <子域名> <端口>"; exit 1; } cmd_setup "$1" "$2" ;; esac