astro-yukunhh-blog/deploy/proxy.sh

274 lines
10 KiB
Bash
Raw Normal View History

#!/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-<FQDN>.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