#!/bin/bash if command -v nginx &> /dev/null; then echo "✅ Nginx 已安装,跳过安装步骤" else echo "🚀 安装 Nginx" # 检测系统类型 if command -v apt &> /dev/null; then # Debian/Ubuntu 系统 apt install -y nginx elif command -v yum &> /dev/null; then # CentOS/RHEL 系统 yum install -y nginx elif command -v dnf &> /dev/null; then # 较新版本的 Fedora/RHEL 系统 dnf install -y nginx else echo "❌ 不支持的操作系统:未找到支持的包管理器" exit 1 fi fi # 函数:尝试使用指定的包管理器安装 try_install() { local cmd=$1 if command -v "$cmd" &> /dev/null; then echo "📦 尝试使用 $cmd 安装 snapd..." if sudo "$cmd" install -y snapd; then return 0 fi fi return 1 } # 检查 Certbot 是否已安装 if command -v certbot &> /dev/null; then echo "✅ Certbot 已安装,跳过安装步骤" else echo "🚀 安装 Certbot" # 检测系统类型 if [ -f /etc/debian_version ]; then # Ubuntu/Debian 系统 apt install -y certbot python3-certbot-nginx # 也可以使用snapd安装 # apt install -y snapd # # 使用snap安装 certbot # snap install --classic certbot # # Prepare the Certbot command # sudo ln -s /snap/bin/certbot /usr/bin/certbot elif [ -f /etc/redhat-release ]; then # CentOS/RHEL 系统 # 首先安装 EPEL 仓库 yum install -y epel-release # 安装 snapd # 检查是否已安装 if command -v snap &> /dev/null; then echo "✅ snapd 已经安装" exit 0 fi echo "🔍 检测系统包管理器..." # 尝试使用 dnf 安装 if try_install "dnf"; then echo "✅ 使用 dnf 安装 snapd 成功" # 尝试使用 yum 安装 elif try_install "yum"; then echo "✅ 使用 yum 安装 snapd 成功" # 尝试使用 apt-get 安装(适用于 Debian/Ubuntu) elif try_install "apt-get"; then echo "✅ 使用 apt-get 安装 snapd 成功" else echo "❌ 无法安装 snapd,未找到支持的包管理器" exit 1 fi # 启用 snapd 服务 systemctl enable --now snapd.socket # 创建符号链接 ln -s /var/lib/snapd/snap /snap # 安装 certbot snap install --classic certbot # Prepare the Certbot command sudo ln -s /snap/bin/certbot /usr/bin/certbot else echo "❌ 不支持的操作系统" exit 1 fi fi # 验证安装 echo "🔍 验证安装" if command -v nginx &> /dev/null && command -v certbot &> /dev/null; then echo "✅ Nginx 和 Certbot 安装成功" nginx -v certbot --version else echo "❌ 安装验证失败" exit 1 fi # ===== 域名配置交互与文件生成 ===== sanitize_domain() { local domain=$1 domain="${domain#http://}" domain="${domain#https://}" domain="${domain%%/*}" domain="${domain%;}" echo "$domain" | xargs } validate_domain_value() { local domain=$1 [[ "$domain" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$ ]] } get_env_domain() { local domain="${HTTPS_DOMAIN:-${EASYAI_PROXY_DOMAIN:-${DEPLOY_DOMAIN:-}}}" if [ -z "$domain" ]; then return 0 fi domain="$(sanitize_domain "$domain")" if ! validate_domain_value "$domain"; then echo "❌ 域名变量格式不正确: $domain" >&2 exit 1 fi echo "$domain" } is_non_interactive() { [ "${HTTPS_NON_INTERACTIVE:-${DEPLOY_NON_INTERACTIVE:-0}}" = "1" ] || [ "${CI:-}" = "true" ] || [ ! -t 0 ] } prompt_domain() { local domain domain="$(get_env_domain)" if [ -n "$domain" ]; then echo "$domain" return 0 fi if is_non_interactive; then echo "❌ 当前为非交互环境,HTTPS 配置需要传入域名变量" >&2 echo " 请设置 DEPLOY_DOMAIN=<域名> 或 EASYAI_PROXY_DOMAIN=<域名>" >&2 exit 1 fi while true; do read -r -p "🌐 请输入域名(例如 demo.example.com): " domain domain="$(sanitize_domain "$domain")" if [ -n "$domain" ] && validate_domain_value "$domain"; then echo "$domain" return 0 fi echo "❌ 域名格式不正确,请重新输入。" done } escape_sed() { echo "$1" | sed 's/[.[\*^$()+?{}|/]/\\&/g' } get_primary_domain_from_conf() { local conf_file=$1 awk ' /server_name/ { for (i = 2; i <= NF; i++) { gsub(";", "", $i) if ($i !~ /^www\./ && $i !~ /^\*/ && $i != "localhost") { print $i exit } } } ' "$conf_file" } create_conf_by_template() { local domain=$1 local target_file=$2 # 始终优先使用仓库维护的完整代理模板,避免仅生成前端 location /。 if [ -f "./easyai-proxy.conf.sample" ]; then cp "./easyai-proxy.conf.sample" "./$target_file" sed -i.bak "s/51easyai\.com/$domain/g" "./$target_file" rm -f "./$target_file.bak" return 0 fi if [ -f "./demo.51easyai.com.conf" ]; then cp "./demo.51easyai.com.conf" "./$target_file" sed -i.bak "s/www\.demo\.51easyai\.com/www.$domain/g; s/demo\.51easyai\.com/$domain/g" "./$target_file" rm -f "./$target_file.bak" return 0 fi if [ -f "./2.conf" ]; then cp "./2.conf" "./$target_file" sed -i.bak "s/www\.2/www.$domain/g; s/\b2\b/$domain/g" "./$target_file" rm -f "./$target_file.bak" return 0 fi cat > "./$target_file" <&2 exit 1 ;; esac } upsert_domain_for_conf() { local conf_file=$1 local env_domain="${2:-}" local env_action="${3:-}" local mode local new_domain echo "ℹ️ 检测到当前目录已有配置文件: $conf_file" if [ -n "$env_domain" ]; then new_domain="$(sanitize_domain "$env_domain")" if ! validate_domain_value "$new_domain"; then echo "❌ 域名变量格式不正确: $new_domain" exit 1 fi if conf_contains_domain "$conf_file" "$new_domain"; then echo "ℹ️ 配置文件已包含域名 $new_domain,无需修改。" return 0 fi mode="$(normalize_domain_action "$env_action")" echo "使用变量配置域名: $new_domain, action=$([ "$mode" = "1" ] && echo replace || echo add)" else while true; do echo "请选择域名处理方式:" echo " 1) 替换当前域名" echo " 2) 新增一个域名" read -r -p "请输入选项 (1/2): " mode case "$mode" in 1|2) break ;; *) echo "❌ 无效选项,请输入 1 或 2" ;; esac done new_domain=$(prompt_domain) fi if [ "$mode" = "1" ]; then local old_domain old_domain=$(get_primary_domain_from_conf "./$conf_file") if [ -z "$old_domain" ]; then echo "⚠️ 未能自动识别旧域名,将直接尝试新增域名。" mode="2" else local old_domain_escaped local new_domain_escaped old_domain_escaped=$(escape_sed "$old_domain") new_domain_escaped=$(escape_sed "$new_domain") sed -i.bak "s/www\\.$old_domain_escaped/www.$new_domain_escaped/g; s/$old_domain_escaped/$new_domain_escaped/g" "./$conf_file" rm -f "./$conf_file.bak" echo "✅ 已将域名从 $old_domain 替换为 $new_domain" fi fi if [ "$mode" = "2" ]; then local changed=0 if ! grep -Eq "server_name[^;]*[[:space:]]$new_domain([[:space:];]|$)" "./$conf_file"; then sed -i.bak "/server_name/s/;/ $new_domain;/g" "./$conf_file" changed=1 fi if ! grep -Eq "server_name[^;]*[[:space:]]www\\.$new_domain([[:space:];]|$)" "./$conf_file"; then sed -i.bak "/server_name/s/;/ www.$new_domain;/g" "./$conf_file" changed=1 fi rm -f "./$conf_file.bak" if [ "$changed" -eq 1 ]; then echo "✅ 已新增域名:$new_domain(含 www.$new_domain)" else echo "ℹ️ 配置文件已包含该域名,无需新增。" fi fi } detect_existing_proxy_conf() { local preferred_file=$1 local domain_filter="${2:-}" local -a candidates=() local -a matched_candidates=() local conf if [ -n "$preferred_file" ] && [ -f "./$preferred_file" ]; then echo "$preferred_file" return 0 fi while IFS= read -r conf; do if grep -Eq "^[[:space:]]*server_name[[:space:]]+" "./$conf"; then candidates+=("$conf") fi done < <(ls -1 *.conf 2>/dev/null || true) if [ "${#candidates[@]}" -eq 0 ]; then return 1 fi if [ -n "$domain_filter" ]; then for conf in "${candidates[@]}"; do if conf_contains_domain "$conf" "$domain_filter"; then matched_candidates+=("$conf") fi done if [ "${#matched_candidates[@]}" -eq 1 ]; then echo "${matched_candidates[0]}" return 0 fi if [ "${#matched_candidates[@]}" -gt 1 ] && is_non_interactive; then echo "❌ 多个配置文件都包含 $domain_filter,请通过 EASYAI_PROXY_CONF 指定文件" >&2 exit 1 fi fi if [ "${#candidates[@]}" -eq 1 ]; then echo "${candidates[0]}" return 0 fi if is_non_interactive; then echo "❌ 检测到多个可用配置文件,请通过 EASYAI_PROXY_CONF 指定文件" >&2 exit 1 fi echo "⚠️ 检测到多个可用配置文件,请选择一个:" >&2 local i for i in "${!candidates[@]}"; do printf " %s) %s\n" "$((i + 1))" "${candidates[$i]}" >&2 done while true; do read -r -p "请输入序号: " i if [[ "$i" =~ ^[0-9]+$ ]] && [ "$i" -ge 1 ] && [ "$i" -le "${#candidates[@]}" ]; then echo "${candidates[$((i - 1))]}" return 0 fi echo "❌ 无效序号,请重新输入。" done } echo "🚀 复制当前目录的配置文件到nginx配置文件目录" # 支持 EASYAI_PROXY_CONF 指定配置文件(如 51easyai.com.conf) PREFERRED_CONF_FILE="${EASYAI_PROXY_CONF:-}" DEFAULT_NEW_CONF_FILE="${EASYAI_PROXY_CONF:-easyai-proxy.conf}" HTTPS_DOMAIN_INPUT="$(get_env_domain)" CONF_FILE=$(detect_existing_proxy_conf "$PREFERRED_CONF_FILE" "$HTTPS_DOMAIN_INPUT") if [ -n "$CONF_FILE" ]; then upsert_domain_for_conf "$CONF_FILE" "$HTTPS_DOMAIN_INPUT" "${EASYAI_PROXY_DOMAIN_ACTION:-}" else CONF_FILE="$DEFAULT_NEW_CONF_FILE" echo "ℹ️ 当前目录未找到可用 nginx 配置文件(*.conf 且包含 server_name)" input_domain=$(prompt_domain) create_conf_by_template "$input_domain" "$CONF_FILE" echo "✅ 已根据域名 $input_domain 创建配置文件: ./$CONF_FILE" fi cp "./$CONF_FILE" "/etc/nginx/conf.d/$CONF_FILE" echo "🚀 重载nginx" sudo nginx -s reload # 停止 Nginx 服务以释放 80 端口 sudo nginx -s stop echo "🚀 使用certbot 自动配置证书" # 从 Nginx 配置文件中提取所有域名 SERVER_NAME_LINES=$( if [ -f "/etc/nginx/conf.d/$CONF_FILE" ]; then grep "server_name" "/etc/nginx/conf.d/$CONF_FILE" 2>/dev/null || true else find /etc/nginx/conf.d/ -name "*.conf" -exec grep "server_name" {} \; 2>/dev/null || true fi ) DOMAINS=$(echo "$SERVER_NAME_LINES" | \ grep -v "#" | \ awk '{for(i=2;i<=NF;i++) if($i!=";") print $i}' | \ sed 's/;//g' | \ sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | \ grep -E '^[A-Za-z0-9.-]+$' | \ grep -vE '^(\*|localhost)$' | \ sort -u | \ tr '\n' ' ') if [ -n "$DOMAINS" ]; then # 停止 Nginx 服务 echo "停止 Nginx 服务..." sudo nginx -s stop # 构建域名参数字符串 DOMAIN_ARGS=() for domain in $DOMAINS; do DOMAIN_ARGS+=("-d" "$domain") done # 使用 certbot --nginx 插件安装证书 sudo certbot --nginx \ --non-interactive \ --agree-tos \ --email "${CERTBOT_EMAIL:-wangbo@51easyai.com}" \ --rsa-key-size 2048 \ --preferred-challenges http \ --force-renewal \ "${DOMAIN_ARGS[@]}" # 启动 Nginx 服务 echo "启动 Nginx 服务..." sudo nginx fi