Files
vue-driven-cloud-storage/install.sh
237899745 4350113979 fix: 修复配额说明重复和undefined问题
- 在editStorageForm中初始化oss_storage_quota_value和oss_quota_unit
- 删除重复的旧配额说明块,保留新的当前配额设置显示

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:39:53 +08:00

4764 lines
157 KiB
Bash
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
################################################################################
# 玩玩云 (WanWanYun) - 一键部署/卸载/更新脚本
# 项目地址: https://git.workyai.cn/237899745/vue-driven-cloud-storage
# 版本: v3.1.0
################################################################################
set -e
# 检查运行模式
MODE="install"
if [[ "$1" == "--uninstall" ]] || [[ "$1" == "-u" ]] || [[ "$1" == "uninstall" ]]; then
MODE="uninstall"
elif [[ "$1" == "--update" ]] || [[ "$1" == "--upgrade" ]] || [[ "$1" == "update" ]]; then
MODE="update"
elif [[ "$1" == "--repair" ]] || [[ "$1" == "--fix" ]] || [[ "$1" == "repair" ]]; then
MODE="repair"
elif [[ "$1" == "--ssl" ]] || [[ "$1" == "--cert" ]] || [[ "$1" == "ssl" ]]; then
MODE="ssl"
fi
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m' # No Color
# 全局变量
PROJECT_NAME="wanwanyun"
PROJECT_DIR="/var/www/${PROJECT_NAME}"
REPO_URL="https://git.workyai.cn/237899745/vue-driven-cloud-storage.git"
NODE_VERSION="20"
ADMIN_USERNAME=""
ADMIN_PASSWORD=""
DOMAIN=""
USE_DOMAIN=false
SSL_METHOD=""
HTTP_PORT="80"
HTTPS_PORT="443"
BACKEND_PORT="40001"
################################################################################
# 工具函数
################################################################################
print_banner() {
clear
echo -e "${CYAN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ 🌩️ 玩玩云 一键部署脚本 ║"
echo "║ ║"
echo "║ Cloud Storage Platform ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
print_step() {
echo -e "\n${BLUE}$1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}$1${NC}"
}
print_info() {
echo -e "${CYAN} $1${NC}"
}
# 检测操作系统
detect_os() {
if [[ -f /etc/os-release ]]; then
. /etc/os-release
OS=$ID
OS_VERSION=$VERSION_ID
OS_NAME=$NAME
else
print_error "无法检测操作系统"
exit 1
fi
# 统一操作系统标识和包管理器检测
case $OS in
ubuntu)
PKG_MANAGER="apt"
;;
debian)
PKG_MANAGER="apt"
;;
centos)
if [[ "${OS_VERSION%%.*}" -ge 8 ]]; then
PKG_MANAGER="dnf"
else
PKG_MANAGER="yum"
fi
;;
rhel|redhat)
OS="rhel"
if [[ "${OS_VERSION%%.*}" -ge 8 ]]; then
PKG_MANAGER="dnf"
else
PKG_MANAGER="yum"
fi
;;
rocky|rockylinux)
OS="rocky"
PKG_MANAGER="dnf"
;;
almalinux|alma)
OS="almalinux"
PKG_MANAGER="dnf"
;;
fedora)
PKG_MANAGER="dnf"
;;
opensuse|opensuse-leap|opensuse-tumbleweed)
OS="opensuse"
PKG_MANAGER="zypper"
;;
*)
# 自动检测包管理器作为后备方案
print_warning "未识别的操作系统: $OS,尝试自动检测包管理器"
if command -v apt-get &> /dev/null; then
PKG_MANAGER="apt"
print_info "检测到APT包管理器"
elif command -v dnf &> /dev/null; then
PKG_MANAGER="dnf"
print_info "检测到DNF包管理器"
elif command -v yum &> /dev/null; then
PKG_MANAGER="yum"
print_info "检测到YUM包管理器"
elif command -v zypper &> /dev/null; then
PKG_MANAGER="zypper"
print_info "检测到Zypper包管理器"
else
print_error "无法检测到支持的包管理器"
exit 1
fi
;;
esac
}
# 检测系统架构
detect_arch() {
ARCH=$(uname -m)
case $ARCH in
x86_64)
ARCH="amd64"
;;
aarch64)
ARCH="arm64"
;;
*)
print_error "不支持的系统架构: $ARCH"
exit 1
;;
esac
}
# 检测root权限
check_root() {
if [[ $EUID -ne 0 ]]; then
print_error "此脚本需要root权限运行"
print_info "请使用: sudo bash install.sh"
exit 1
fi
}
################################################################################
# 环境检测
################################################################################
system_check() {
print_step "正在检测系统环境..."
# 检测操作系统
detect_os
print_success "操作系统: $OS $OS_VERSION"
# 检测架构
detect_arch
print_success "系统架构: $ARCH"
# 检测内存
TOTAL_MEM=$(free -m | awk '/^Mem:/{print $2}')
if [[ $TOTAL_MEM -lt 512 ]]; then
print_warning "内存不足512MB可能影响性能"
else
print_success "可用内存: ${TOTAL_MEM}MB"
fi
# 检测磁盘空间
DISK_AVAIL=$(df -m / | awk 'NR==2 {print $4}')
if [[ $DISK_AVAIL -lt 2048 ]]; then
print_warning "磁盘空间不足2GB可能影响运行"
else
print_success "可用磁盘: ${DISK_AVAIL}MB"
fi
# 检测网络
if ping -c 1 git.workyai.cn &> /dev/null; then
print_success "网络连接正常"
else
print_error "无法连接到网络"
exit 1
fi
# 检测公网IP
PUBLIC_IP=$(curl -s ifconfig.me || curl -s icanhazip.com || echo "未知")
print_info "公网IP: $PUBLIC_IP"
echo ""
}
################################################################################
# 软件源配置
################################################################################
choose_mirror() {
print_step "选择软件包安装源"
echo ""
echo "请选择软件源:"
echo -e "${GREEN}[1]${NC} 官方源 (国外服务器推荐)"
echo -e "${GREEN}[2]${NC} 阿里云镜像源 (国内服务器推荐,速度更快)"
echo ""
while true; do
read -p "请输入选项 [1-2]: " mirror_choice < /dev/tty
case $mirror_choice in
1)
print_info "使用官方源"
USE_ALIYUN_MIRROR=false
break
;;
2)
print_info "使用阿里云镜像源"
USE_ALIYUN_MIRROR=true
configure_aliyun_mirror
break
;;
*)
print_error "无效选项,请重新选择"
;;
esac
done
echo ""
}
configure_aliyun_mirror() {
print_step "配置阿里云镜像源..."
case $OS in
ubuntu)
# 备份原有源
if [[ ! -f /etc/apt/sources.list.bak ]]; then
cp /etc/apt/sources.list /etc/apt/sources.list.bak
fi
# 配置Ubuntu阿里云源
cat > /etc/apt/sources.list << EOF
deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs) main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-backports main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-security main restricted universe multiverse
EOF
print_success "阿里云源配置完成"
;;
debian)
# 备份原有源
if [[ ! -f /etc/apt/sources.list.bak ]]; then
cp /etc/apt/sources.list /etc/apt/sources.list.bak
fi
# 配置Debian阿里云源
cat > /etc/apt/sources.list << EOF
deb http://mirrors.aliyun.com/debian/ $(lsb_release -cs) main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian/ $(lsb_release -cs)-updates main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian/ $(lsb_release -cs)-backports main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian-security $(lsb_release -cs)-security main contrib non-free non-free-firmware
EOF
print_success "阿里云源配置完成"
;;
centos)
# 备份并配置CentOS阿里云源
if [[ -f /etc/yum.repos.d/CentOS-Base.repo ]]; then
if [[ ! -f /etc/yum.repos.d/CentOS-Base.repo.bak ]]; then
cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak
fi
fi
CENTOS_VERSION="${OS_VERSION%%.*}"
if [[ "$CENTOS_VERSION" == "7" ]]; then
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
elif [[ "$CENTOS_VERSION" == "8" ]]; then
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo
sed -i 's/mirrors.cloud.aliyuncs.com/mirrors.aliyun.com/g' /etc/yum.repos.d/CentOS-Base.repo
fi
yum clean all
yum makecache
print_success "阿里云源配置完成"
;;
rhel)
# RHEL使用EPEL和阿里云镜像
print_info "配置RHEL阿里云镜像源..."
yum install -y epel-release
print_success "阿里云源配置完成"
;;
rocky)
# 备份并配置Rocky Linux阿里云源
if [[ -d /etc/yum.repos.d ]]; then
mkdir -p /etc/yum.repos.d/backup
cp /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ 2>/dev/null || true
fi
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \
-i.bak /etc/yum.repos.d/rocky*.repo
dnf clean all
dnf makecache
print_success "阿里云源配置完成"
;;
almalinux)
# 备份并配置AlmaLinux阿里云源
if [[ -d /etc/yum.repos.d ]]; then
mkdir -p /etc/yum.repos.d/backup
cp /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ 2>/dev/null || true
fi
sed -e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^# baseurl=https://repo.almalinux.org|baseurl=https://mirrors.aliyun.com|g' \
-i.bak /etc/yum.repos.d/almalinux*.repo
dnf clean all
dnf makecache
print_success "阿里云源配置完成"
;;
fedora)
# 备份并配置Fedora阿里云源
if [[ -d /etc/yum.repos.d ]]; then
mkdir -p /etc/yum.repos.d/backup
cp /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ 2>/dev/null || true
fi
sed -e 's|^metalink=|#metalink=|g' \
-e 's|^#baseurl=http://download.example/pub/fedora/linux|baseurl=https://mirrors.aliyun.com/fedora|g' \
-i.bak /etc/yum.repos.d/fedora*.repo /etc/yum.repos.d/fedora-updates*.repo
dnf clean all
dnf makecache
print_success "阿里云源配置完成"
;;
opensuse)
# 配置openSUSE阿里云源
print_info "配置openSUSE阿里云镜像源..."
zypper mr -da
zypper ar -fcg https://mirrors.aliyun.com/opensuse/distribution/leap/\$releasever/repo/oss/ aliyun-oss
zypper ar -fcg https://mirrors.aliyun.com/opensuse/distribution/leap/\$releasever/repo/non-oss/ aliyun-non-oss
zypper ar -fcg https://mirrors.aliyun.com/opensuse/update/leap/\$releasever/oss/ aliyun-update-oss
zypper ar -fcg https://mirrors.aliyun.com/opensuse/update/leap/\$releasever/non-oss/ aliyun-update-non-oss
zypper ref
print_success "阿里云源配置完成"
;;
*)
print_warning "当前系统($OS)暂不支持阿里云镜像源自动配置,使用官方源"
;;
esac
}
################################################################################
check_cpp_compiler() {
print_step "检查C++编译器版本..."
# 检查g++是否已安装
if ! command -v g++ &> /dev/null; then
print_warning "g++未安装,将在依赖安装时自动安装"
return
fi
# 获取g++版本号
GXX_VERSION=$(g++ --version | head -n1 | grep -oP '\d+\.\d+\.\d+' | head -1 | cut -d'.' -f1)
if [[ -z "$GXX_VERSION" ]]; then
print_warning "无法检测g++版本,跳过版本检查"
return
fi
print_info "当前g++版本: $GXX_VERSION.x"
# better-sqlite3 v11+ 需要C++20支持g++ 10+
if [[ $GXX_VERSION -lt 10 ]]; then
print_warning "g++版本过低需要10+以支持C++20正在升级..."
echo ""
case $PKG_MANAGER in
apt)
# Ubuntu/Debian: 使用toolchain PPA
print_info "添加Ubuntu Toolchain PPA..."
add-apt-repository ppa:ubuntu-toolchain-r/test -y || {
print_error "添加PPA失败"
return 1
}
apt-get update
print_info "安装g++-11..."
apt-get install -y g++-11 gcc-11 || {
print_error "g++-11安装失败"
return 1
}
# 设置为默认编译器
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100
# 验证
NEW_VERSION=$(g++ --version | head -n1 | grep -oP '\d+\.\d+\.\d+' | head -1 | cut -d'.' -f1)
print_success "g++已升级到版本: $NEW_VERSION.x"
;;
yum|dnf)
# CentOS/RHEL: 使用devtoolset或gcc-toolset
if [[ "$OS" == "centos" ]] && [[ "$OS_VERSION" == "7" ]]; then
# CentOS 7 使用devtoolset-11
print_info "安装devtoolset-11..."
yum install -y centos-release-scl
yum install -y devtoolset-11-gcc devtoolset-11-gcc-c++
# 启用devtoolset-11
echo "source /opt/rh/devtoolset-11/enable" >> /etc/profile
source /opt/rh/devtoolset-11/enable
print_success "devtoolset-11安装完成"
else
# CentOS 8+ 使用gcc-toolset-11
print_info "安装gcc-toolset-11..."
$PKG_MANAGER install -y gcc-toolset-11-gcc gcc-toolset-11-gcc-c++
# 启用gcc-toolset-11
echo "source /opt/rh/gcc-toolset-11/enable" >> /etc/profile
source /opt/rh/gcc-toolset-11/enable
print_success "gcc-toolset-11安装完成"
fi
;;
zypper)
# OpenSUSE
print_info "升级g++..."
zypper install -y gcc11-c++ || {
print_error "g++升级失败"
return 1
}
# 设置为默认
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100
print_success "g++已升级"
;;
*)
print_warning "不支持的包管理器无法自动升级g++"
print_warning "请手动升级g++到10或更高版本"
return 1
;;
esac
echo ""
else
print_success "g++版本满足要求10+"
echo ""
fi
}
# 安装依赖环境
################################################################################
install_dependencies() {
print_step "正在安装依赖环境..."
case $PKG_MANAGER in
apt)
apt-get update
apt-get install -y curl wget git unzip lsb-release build-essential python3
install_nodejs_apt
install_nginx_apt
;;
yum)
yum install -y curl wget git unzip redhat-lsb-core gcc-c++ make python3
install_nodejs_yum
install_nginx_yum
;;
dnf)
dnf install -y curl wget git unzip redhat-lsb-core gcc-c++ make python3
install_nodejs_dnf
install_nginx_dnf
;;
zypper)
zypper install -y curl wget git unzip lsb-release gcc-c++ make python3
install_nodejs_zypper
install_nginx_zypper
;;
*)
print_error "不支持的包管理器: $PKG_MANAGER"
exit 1
;;
esac
install_pm2
print_success "依赖环境安装完成"
# 检查并升级C++编译器(如果需要)
check_cpp_compiler
echo ""
}
install_nodejs_apt() {
if command -v node &> /dev/null; then
NODE_VER=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
if [[ $NODE_VER -ge $NODE_VERSION ]]; then
print_success "Node.js 已安装: $(node -v)"
return
fi
fi
print_info "正在安装 Node.js ${NODE_VERSION}.x..."
curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
apt-get install -y nodejs
print_success "Node.js 安装完成: $(node -v)"
}
install_nodejs_yum() {
if command -v node &> /dev/null; then
NODE_VER=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
if [[ $NODE_VER -ge $NODE_VERSION ]]; then
print_success "Node.js 已安装: $(node -v)"
return
fi
fi
print_info "正在安装 Node.js ${NODE_VERSION}.x..."
curl -fsSL https://rpm.nodesource.com/setup_${NODE_VERSION}.x | bash -
yum install -y nodejs
print_success "Node.js 安装完成: $(node -v)"
}
install_nginx_apt() {
if command -v nginx &> /dev/null; then
print_success "Nginx 已安装: $(nginx -v 2>&1 | cut -d'/' -f2)"
return
fi
print_info "正在安装 Nginx..."
apt-get install -y nginx
systemctl enable nginx
print_success "Nginx 安装完成"
}
install_nginx_yum() {
if command -v nginx &> /dev/null; then
print_success "Nginx 已安装: $(nginx -v 2>&1 | cut -d'/' -f2)"
return
fi
print_info "正在安装 Nginx..."
yum install -y nginx
systemctl enable nginx
print_success "Nginx 安装完成"
}
install_nodejs_dnf() {
if command -v node &> /dev/null; then
NODE_VER=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
if [[ $NODE_VER -ge $NODE_VERSION ]]; then
print_success "Node.js 已安装: $(node -v)"
return
fi
fi
print_info "正在安装 Node.js ${NODE_VERSION}.x..."
curl -fsSL https://rpm.nodesource.com/setup_${NODE_VERSION}.x | bash -
dnf install -y nodejs
print_success "Node.js 安装完成: $(node -v)"
}
install_nginx_dnf() {
if command -v nginx &> /dev/null; then
print_success "Nginx 已安装: $(nginx -v 2>&1 | cut -d'/' -f2)"
return
fi
print_info "正在安装 Nginx..."
dnf install -y nginx
systemctl enable nginx
print_success "Nginx 安装完成"
}
install_nodejs_zypper() {
if command -v node &> /dev/null; then
NODE_VER=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
if [[ $NODE_VER -ge $NODE_VERSION ]]; then
print_success "Node.js 已安装: $(node -v)"
return
fi
fi
print_info "正在安装 Node.js ${NODE_VERSION}.x..."
# openSUSE使用官方仓库的Node.js
zypper install -y nodejs${NODE_VERSION}
print_success "Node.js 安装完成: $(node -v)"
}
install_nginx_zypper() {
if command -v nginx &> /dev/null; then
print_success "Nginx 已安装: $(nginx -v 2>&1 | cut -d'/' -f2)"
return
fi
print_info "正在安装 Nginx..."
zypper install -y nginx
systemctl enable nginx
print_success "Nginx 安装完成"
}
install_pm2() {
if command -v pm2 &> /dev/null; then
print_success "PM2 已安装: $(pm2 -v)"
return
fi
print_info "正在安装 PM2..."
npm install -g pm2
pm2 startup
print_success "PM2 安装完成"
}
################################################################################
# 智能端口检测和配置
################################################################################
# 检查端口是否可用(保留用于兼容性)
check_port_available() {
local port=$1
if command -v netstat &> /dev/null; then
if netstat -tuln | grep -q ":${port} "; then
return 1 # 端口被占用
fi
elif command -v ss &> /dev/null; then
if ss -tuln | grep -q ":${port} "; then
return 1 # 端口被占用
fi
fi
return 0 # 端口可用
}
# 智能检测端口状态和占用进程
check_port_status() {
local port=$1
# 1. 检查端口是否被监听
if command -v netstat &> /dev/null; then
if ! netstat -tuln 2>/dev/null | grep -q ":${port} "; then
echo "available"
return 0
fi
elif command -v ss &> /dev/null; then
if ! ss -tuln 2>/dev/null | grep -q ":${port} "; then
echo "available"
return 0
fi
else
echo "available"
return 0
fi
# 2. 端口被占用,检查是什么进程
local process=""
if command -v netstat &> /dev/null; then
process=$(netstat -tulnp 2>/dev/null | grep ":${port} " | awk '{print $7}' | cut -d'/' -f2 | head -1)
fi
if [[ -z "$process" ]] && command -v ss &> /dev/null; then
# 使用sed替代grep -oP以提高兼容性
process=$(ss -tulnp 2>/dev/null | grep ":${port} " | sed -n 's/.*users:(("\([^"]*\)".*/\1/p' | head -1)
fi
# 3. 根据进程返回状态始终返回0以避免set -e导致脚本退出
if [[ -z "$process" ]]; then
# 无法获取进程名(可能权限不足)
echo "occupied"
elif [[ "$process" == "nginx" ]] || [[ "$process" =~ ^nginx: ]]; then
# Nginx占用
echo "nginx"
elif [[ "$process" == "apache2" ]] || [[ "$process" == "httpd" ]] || [[ "$process" =~ apache ]]; then
# Apache占用
echo "apache"
else
# 其他进程
echo "other:$process"
fi
# 始终返回0避免set -e导致脚本退出
return 0
}
# 改进的端口配置函数
configure_ports() {
print_step "智能端口配置"
echo ""
# 全局标志是否共用Nginx端口
SHARE_NGINX=false
# ========== 检测80端口 ==========
port_80_status=$(check_port_status 80)
case $port_80_status in
"available")
print_success "80 端口可用"
HTTP_PORT=80
;;
"nginx")
print_info "检测到 Nginx 已占用 80 端口"
echo ""
echo "🎯 好消息可以通过虚拟主机配置与现有Nginx共用此端口"
echo ""
echo "请选择部署方式:"
echo ""
echo -e "${GREEN}[1]${NC} 共用80端口推荐"
echo " ✅ 需要配置不同的域名"
echo " ✅ 访问: http://your-domain.com"
echo " ✅ 不需要端口号"
echo ""
echo -e "${GREEN}[2]${NC} 使用其他HTTP端口"
echo " 独立端口"
echo " 访问: http://your-domain.com:8080"
echo ""
while true; do
read -p "请选择 [1-2]: " choice < /dev/tty
if [[ "$choice" == "1" ]]; then
HTTP_PORT=80
SHARE_NGINX=true
print_success "将与现有Nginx共用80端口虚拟主机模式"
print_info "提示: 请确保使用不同的域名区分站点"
break
elif [[ "$choice" == "2" ]]; then
# 选择其他端口的逻辑
while true; do
read -p "请输入HTTP端口 [建议: 8080]: " custom_port < /dev/tty
custom_port=${custom_port:-8080}
if [[ ! "$custom_port" =~ ^[0-9]+$ ]] || [[ $custom_port -lt 1024 ]] || [[ $custom_port -gt 65535 ]]; then
print_error "端口范围: 1024-65535"
continue
fi
if ! check_port_available $custom_port; then
print_error "端口 $custom_port 已被占用,请选择其他端口"
continue
fi
HTTP_PORT=$custom_port
print_success "将使用 HTTP 端口: $HTTP_PORT"
break
done
break
else
print_error "无效选项,请重新选择"
fi
done
;;
"apache")
print_warning "检测到 Apache 已占用 80 端口"
echo ""
echo "⚠️ Apache和Nginx不能同时监听同一端口"
echo ""
echo "请选择解决方案:"
echo ""
echo -e "${GREEN}[1]${NC} 停止Apache改用Nginx"
echo " ⚠️ 需要迁移Apache配置"
echo ""
echo -e "${GREEN}[2]${NC} 使用其他HTTP端口推荐"
echo " ✅ 不影响现有Apache服务"
echo ""
while true; do
read -p "请选择 [1-2]: " choice < /dev/tty
if [[ "$choice" == "1" ]]; then
print_info "正在停止Apache..."
systemctl stop apache2 2>/dev/null || systemctl stop httpd 2>/dev/null || true
systemctl disable apache2 2>/dev/null || systemctl disable httpd 2>/dev/null || true
HTTP_PORT=80
print_success "Apache已停止将使用80端口"
break
elif [[ "$choice" == "2" ]]; then
# 选择其他端口
while true; do
read -p "请输入HTTP端口 [建议: 8080]: " custom_port < /dev/tty
custom_port=${custom_port:-8080}
if [[ ! "$custom_port" =~ ^[0-9]+$ ]] || [[ $custom_port -lt 1024 ]] || [[ $custom_port -gt 65535 ]]; then
print_error "端口范围: 1024-65535"
continue
fi
if ! check_port_available $custom_port; then
print_error "端口 $custom_port 已被占用,请选择其他端口"
continue
fi
HTTP_PORT=$custom_port
print_success "将使用 HTTP 端口: $HTTP_PORT"
break
done
break
else
print_error "无效选项,请重新选择"
fi
done
;;
"occupied"|other:*)
process=${port_80_status#other:}
if [[ "$port_80_status" == "occupied" ]]; then
print_warning "80 端口已被占用(无法识别进程)"
else
print_warning "80 端口被进程 ${process} 占用"
fi
echo ""
echo "请选择其他HTTP端口"
while true; do
read -p "请输入HTTP端口 [建议: 8080]: " custom_port < /dev/tty
custom_port=${custom_port:-8080}
if [[ ! "$custom_port" =~ ^[0-9]+$ ]] || [[ $custom_port -lt 1024 ]] || [[ $custom_port -gt 65535 ]]; then
print_error "端口范围: 1024-65535"
continue
fi
if ! check_port_available $custom_port; then
print_error "端口 $custom_port 已被占用,请选择其他端口"
continue
fi
HTTP_PORT=$custom_port
print_success "将使用 HTTP 端口: $HTTP_PORT"
break
done
;;
esac
echo ""
# ========== 检测443端口仅在使用HTTPS时需要==========
if [[ "$USE_DOMAIN" == "true" ]] && [[ "$SSL_METHOD" != "8" ]]; then
port_443_status=$(check_port_status 443)
case $port_443_status in
"available")
print_success "443 端口可用"
HTTPS_PORT=443
;;
"nginx")
print_info "检测到 Nginx 已占用 443 端口"
echo ""
if [[ "$SHARE_NGINX" == "true" ]]; then
# 如果HTTP端口也是共用的默认继续共用
echo "🎯 将继续与现有Nginx共用443端口虚拟主机模式"
HTTPS_PORT=443
print_success "将与现有Nginx共用443端口"
else
echo "请选择部署方式:"
echo ""
echo -e "${GREEN}[1]${NC} 共用443端口"
echo " ✅ 需要配置不同的域名"
echo ""
echo -e "${GREEN}[2]${NC} 使用其他HTTPS端口"
echo " 独立端口如8443"
echo ""
while true; do
read -p "请选择 [1-2]: " choice < /dev/tty
if [[ "$choice" == "1" ]]; then
HTTPS_PORT=443
SHARE_NGINX=true
print_success "将与现有Nginx共用443端口"
break
elif [[ "$choice" == "2" ]]; then
# 选择其他端口
while true; do
read -p "请输入HTTPS端口 [建议: 8443]: " custom_https_port < /dev/tty
custom_https_port=${custom_https_port:-8443}
if [[ ! "$custom_https_port" =~ ^[0-9]+$ ]] || [[ $custom_https_port -lt 1024 ]] || [[ $custom_https_port -gt 65535 ]]; then
print_error "端口范围: 1024-65535"
continue
fi
if ! check_port_available $custom_https_port; then
print_error "端口 $custom_https_port 已被占用,请选择其他端口"
continue
fi
HTTPS_PORT=$custom_https_port
print_success "将使用 HTTPS 端口: $HTTPS_PORT"
break
done
break
else
print_error "无效选项,请重新选择"
fi
done
fi
;;
"apache"|"occupied"|other:*)
# Apache或其他进程占用443需要换端口
if [[ "$port_443_status" == "apache" ]]; then
print_warning "检测到 Apache 已占用 443 端口"
elif [[ "$port_443_status" == "occupied" ]]; then
print_warning "443 端口已被占用"
else
process=${port_443_status#other:}
print_warning "443 端口被进程 ${process} 占用"
fi
echo ""
while true; do
read -p "请输入HTTPS端口 [建议: 8443]: " custom_https_port < /dev/tty
custom_https_port=${custom_https_port:-8443}
if [[ ! "$custom_https_port" =~ ^[0-9]+$ ]] || [[ $custom_https_port -lt 1024 ]] || [[ $custom_https_port -gt 65535 ]]; then
print_error "端口范围: 1024-65535"
continue
fi
if ! check_port_available $custom_https_port; then
print_error "端口 $custom_https_port 已被占用,请选择其他端口"
continue
fi
HTTPS_PORT=$custom_https_port
print_success "将使用 HTTPS 端口: $HTTPS_PORT"
break
done
;;
esac
echo ""
fi
# ========== 检测后端端口 ==========
if ! check_port_available 40001; then
print_warning "检测到 40001 端口已被占用"
echo ""
while true; do
read -p "请输入后端服务端口 [建议: 40002]: " custom_backend_port < /dev/tty
custom_backend_port=${custom_backend_port:-40002}
if [[ ! "$custom_backend_port" =~ ^[0-9]+$ ]] || [[ $custom_backend_port -lt 1024 ]] || [[ $custom_backend_port -gt 65535 ]]; then
print_error "端口范围: 1024-65535"
continue
fi
if ! check_port_available $custom_backend_port; then
print_error "端口 $custom_backend_port 已被占用,请选择其他端口"
continue
fi
BACKEND_PORT=$custom_backend_port
print_success "将使用后端端口: $BACKEND_PORT"
break
done
else
print_success "40001 端口可用"
fi
echo ""
print_info "端口配置摘要:"
echo " - HTTP端口: $HTTP_PORT"
if [[ "$USE_DOMAIN" == "true" ]] && [[ "$SSL_METHOD" != "8" ]]; then
echo " - HTTPS端口: $HTTPS_PORT"
fi
echo " - 后端端口: $BACKEND_PORT"
if [[ "$SHARE_NGINX" == "true" ]]; then
echo " - 模式: 虚拟主机共用端口 ✅"
fi
echo ""
}
################################################################################
# 访问模式选择
################################################################################
choose_access_mode() {
print_step "选择访问模式"
echo ""
echo "请选择访问模式:"
echo -e "${GREEN}[1]${NC} 域名模式 (推荐支持HTTPS)"
echo -e "${GREEN}[2]${NC} IP模式 (仅HTTP适合测试)"
echo ""
while true; do
read -p "请输入选项 [1-2]: " mode_choice < /dev/tty
case $mode_choice in
1)
USE_DOMAIN=true
configure_domain
break
;;
2)
USE_DOMAIN=false
PUBLIC_IP=$(curl -s ifconfig.me || curl -s icanhazip.com || echo "未知")
print_info "将使用 IP 模式访问: http://${PUBLIC_IP}"
echo ""
break
;;
*)
print_error "无效选项,请重新选择"
;;
esac
done
}
configure_domain() {
echo ""
while true; do
read -p "请输入您的域名 (例如: wwy.example.com): " DOMAIN < /dev/tty
if [[ -z "$DOMAIN" ]]; then
print_error "域名不能为空"
continue
fi
# 验证域名格式
if [[ ! "$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])?)*$ ]]; then
print_error "域名格式不正确"
continue
fi
# 验证域名解析
print_info "正在验证域名解析..."
DOMAIN_IP=$(dig +short "$DOMAIN" | tail -n1)
PUBLIC_IP=$(curl -s ifconfig.me || curl -s icanhazip.com)
if [[ "$DOMAIN_IP" == "$PUBLIC_IP" ]]; then
print_success "域名已正确解析到当前服务器IP"
break
else
print_warning "域名未解析到当前服务器IP"
print_info "域名解析IP: $DOMAIN_IP"
print_info "当前服务器IP: $PUBLIC_IP"
read -p "是否继续? (y/n): " continue_choice < /dev/tty
if [[ "$continue_choice" == "y" || "$continue_choice" == "Y" ]]; then
break
fi
fi
done
choose_ssl_method
}
################################################################################
# SSL证书配置
################################################################################
# 配置acme.sh自动续期
setup_acme_auto_renew() {
echo ""
print_step "配置SSL证书自动续期..."
# acme.sh安装时会自动创建cron任务这里验证并确保其正常工作
# 1. 检查cron服务是否运行
if systemctl is-active --quiet cron 2>/dev/null || systemctl is-active --quiet crond 2>/dev/null; then
print_success "Cron服务运行正常"
else
print_warning "Cron服务未运行正在启动..."
systemctl start cron 2>/dev/null || systemctl start crond 2>/dev/null || true
systemctl enable cron 2>/dev/null || systemctl enable crond 2>/dev/null || true
fi
# 2. 检查acme.sh cron任务
if crontab -l 2>/dev/null | grep -q "acme.sh.*--cron"; then
print_success "acme.sh自动续期任务已配置"
else
print_warning "未检测到acme.sh cron任务正在添加..."
# acme.sh会自动安装cron这里手动触发一次
~/.acme.sh/acme.sh --install-cronjob 2>/dev/null || true
fi
# 3. 显示续期信息
echo ""
print_info "SSL证书自动续期已配置:"
echo " - 检查频率: 每天自动检查"
echo " - 续期时机: 证书到期前30天自动续期"
echo " - 续期后操作: 自动重载Nginx"
echo ""
# 显示下次续期时间
if [[ -f ~/.acme.sh/${DOMAIN}/${DOMAIN}.conf ]]; then
NEXT_RENEW=$(grep "Le_NextRenewTime=" ~/.acme.sh/${DOMAIN}/${DOMAIN}.conf 2>/dev/null | cut -d'=' -f2)
if [[ -n "$NEXT_RENEW" ]]; then
RENEW_DATE=$(date -d "@${NEXT_RENEW}" "+%Y年%m月%d日 %H:%M:%S" 2>/dev/null || date -r ${NEXT_RENEW} "+%Y年%m月%d日 %H:%M:%S" 2>/dev/null || echo "未知")
print_info "预计续期时间: ${RENEW_DATE}"
fi
fi
# 4. 测试续期命令(不实际续期,只检查)
print_info "验证续期配置..."
if ~/.acme.sh/acme.sh --list 2>/dev/null | grep -q "$DOMAIN"; then
print_success "证书续期配置验证通过"
else
print_warning "证书列表中未找到域名,续期可能需要手动配置"
fi
echo ""
}
choose_ssl_method() {
echo ""
print_step "选择SSL证书部署方式"
echo ""
echo -e "${YELLOW}【推荐方案】${NC}"
echo -e "${GREEN}[1]${NC} acme.sh + Let's Encrypt"
echo " - 纯Shell脚本轻量级稳定"
echo " - 自动续期,无需手动操作"
echo ""
echo -e "${YELLOW}【备选方案】${NC}"
echo -e "${GREEN}[2]${NC} acme.sh + ZeroSSL"
echo " - Let's Encrypt的免费替代品"
echo -e "${GREEN}[3]${NC} acme.sh + Buypass"
echo " - 挪威免费CA有效期180天"
echo ""
echo -e "${YELLOW}【云服务商证书】${NC}"
echo -e "${GREEN}[4]${NC} 阿里云免费证书 (需提供AccessKey)"
echo -e "${GREEN}[5]${NC} 腾讯云免费证书 (需提供SecretKey)"
echo ""
echo -e "${YELLOW}【其他选项】${NC}"
echo -e "${GREEN}[6]${NC} 使用已有证书 (手动上传)"
echo -e "${GREEN}[7]${NC} 暂不配置HTTPS (可后续配置)"
echo ""
while true; do
read -p "请输入选项 [1-7]: " ssl_choice < /dev/tty
case $ssl_choice in
1)
SSL_METHOD="2" # acme.sh + Let's Encrypt
break
;;
2)
SSL_METHOD="3" # acme.sh + ZeroSSL
break
;;
3)
SSL_METHOD="5" # acme.sh + Buypass
break
;;
4)
SSL_METHOD="4" # 阿里云
break
;;
5)
SSL_METHOD="6" # 腾讯云
break
;;
6)
SSL_METHOD="7" # 手动上传
break
;;
7)
SSL_METHOD="8" # 不配置HTTPS
break
;;
*)
print_error "无效选项,请重新选择"
;;
esac
done
echo ""
}
deploy_ssl() {
if [[ "$USE_DOMAIN" != "true" ]]; then
return 0
fi
case $SSL_METHOD in
2)
deploy_acme_letsencrypt || ssl_fallback "2"
;;
3)
deploy_acme_zerossl || ssl_fallback "3"
;;
4)
deploy_aliyun_ssl || ssl_fallback "4"
;;
5)
deploy_acme_buypass || ssl_fallback "5"
;;
6)
deploy_tencent_ssl || ssl_fallback "6"
;;
7)
deploy_manual_ssl
;;
8)
print_info "跳过HTTPS配置"
return 0
;;
esac
}
ssl_fallback() {
local failed_method=$1 # 接收失败的方案编号
print_error "SSL证书部署失败"
echo ""
print_warning "建议尝试备选方案:"
echo ""
# 动态显示可用选项(排除已失败的)
local available_options=()
# 方案2: acme.sh + Let's Encrypt
if [[ "$failed_method" != "2" ]]; then
echo -e "${GREEN}[2]${NC} acme.sh + Let's Encrypt"
available_options+=("2")
fi
# 方案3: acme.sh + ZeroSSL
if [[ "$failed_method" != "3" ]]; then
echo -e "${GREEN}[3]${NC} acme.sh + ZeroSSL"
available_options+=("3")
fi
# 方案5: acme.sh + Buypass
if [[ "$failed_method" != "5" ]]; then
echo -e "${GREEN}[5]${NC} acme.sh + Buypass"
available_options+=("5")
fi
# 方案8: 不配置HTTPS
echo -e "${GREEN}[8]${NC} 暂不配置HTTPS"
available_options+=("8")
echo ""
echo -e "${YELLOW}提示: 方案 $failed_method 已失败,已从列表中移除${NC}"
echo ""
while true; do
read -p "请选择备选方案: " retry_choice < /dev/tty
# 检查输入是否在可用选项中
if [[ ! " ${available_options[@]} " =~ " ${retry_choice} " ]]; then
print_error "无效选项或该方案已失败"
continue
fi
case $retry_choice in
2)
deploy_acme_letsencrypt && return 0
# 如果再次失败继续调用fallback但排除方案2
ssl_fallback "2"
return $?
;;
3)
deploy_acme_zerossl && return 0
ssl_fallback "3"
return $?
;;
5)
deploy_acme_buypass && return 0
ssl_fallback "5"
return $?
;;
8)
print_info "跳过HTTPS配置"
SSL_METHOD=8
return 0
;;
esac
done
}
deploy_certbot() {
print_step "使用 Certbot 部署SSL证书..."
# 检查certbot是否已安装
if ! command -v certbot &> /dev/null; then
print_info "正在安装 Certbot..."
# 安装certbot
case $PKG_MANAGER in
apt)
# Ubuntu/Debian: 优先使用snap官方推荐避免Python依赖冲突
if command -v snap &> /dev/null; then
print_info "使用snap安装Certbot官方推荐方式..."
snap install --classic certbot 2>/dev/null || true
ln -sf /snap/bin/certbot /usr/bin/certbot 2>/dev/null || true
# 验证snap安装是否成功
if /snap/bin/certbot --version &> /dev/null; then
print_success "Certbot (snap版) 安装成功"
else
print_warning "snap安装失败尝试apt安装..."
# 修复urllib3依赖问题
apt-get remove -y python3-urllib3 2>/dev/null || true
apt-get install -y certbot python3-certbot-nginx
fi
else
print_info "snap不可用使用apt安装..."
# 修复urllib3依赖问题
apt-get remove -y python3-urllib3 2>/dev/null || true
apt-get install -y certbot python3-certbot-nginx
fi
;;
yum)
# 修复urllib3依赖问题
yum remove -y python3-urllib3 2>/dev/null || true
yum install -y certbot python3-certbot-nginx
;;
dnf)
# 修复urllib3依赖问题
dnf remove -y python3-urllib3 2>/dev/null || true
dnf install -y certbot python3-certbot-nginx
;;
zypper)
zypper install -y certbot python3-certbot-nginx
;;
esac
# 最终验证certbot是否可用
if ! command -v certbot &> /dev/null; then
print_error "Certbot安装失败"
return 1
fi
else
print_success "Certbot 已安装: $(certbot --version 2>&1 | head -1)"
fi
# 修复已安装certbot的urllib3依赖冲突
if ! certbot --version &> /dev/null; then
print_warning "检测到Certbot依赖问题正在修复..."
case $PKG_MANAGER in
apt)
apt-get remove -y python3-urllib3 2>/dev/null || true
apt-get install --reinstall -y certbot python3-certbot-nginx
;;
yum|dnf)
$PKG_MANAGER remove -y python3-urllib3 2>/dev/null || true
$PKG_MANAGER reinstall -y certbot python3-certbot-nginx
;;
esac
# 再次验证
if ! certbot --version &> /dev/null; then
print_error "Certbot依赖修复失败建议尝试其他SSL方案"
return 1
fi
print_success "Certbot依赖已修复"
fi
# 申请证书使用webroot模式不自动修改Nginx配置
echo ""
print_info "正在申请 Let's Encrypt 证书..."
if certbot certonly --webroot -w "${PROJECT_DIR}/frontend" -d "$DOMAIN" --non-interactive --agree-tos --email "admin@${DOMAIN}"; then
# 将证书复制到Nginx SSL目录
mkdir -p /etc/nginx/ssl
ln -sf "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" "/etc/nginx/ssl/${DOMAIN}.crt"
ln -sf "/etc/letsencrypt/live/${DOMAIN}/privkey.pem" "/etc/nginx/ssl/${DOMAIN}.key"
# 配置自动续期
systemctl enable certbot.timer 2>/dev/null || true
print_success "Certbot SSL证书申请成功"
return 0
else
# 检查证书是否已存在
if [[ -d "/etc/letsencrypt/live/${DOMAIN}" ]]; then
print_warning "检测到证书已存在,使用已有证书"
mkdir -p /etc/nginx/ssl
ln -sf "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" "/etc/nginx/ssl/${DOMAIN}.crt"
ln -sf "/etc/letsencrypt/live/${DOMAIN}/privkey.pem" "/etc/nginx/ssl/${DOMAIN}.key"
print_success "已有证书已链接到Nginx目录"
return 0
else
print_error "Certbot SSL证书申请失败"
echo ""
print_warning "常见失败原因:"
echo " 1. 域名未正确解析到此服务器"
echo " 2. 防火墙阻止了80端口"
echo " 3. Nginx未正确配置或未启动"
echo " 4. Let's Encrypt速率限制"
echo ""
return 1
fi
fi
}
deploy_acme_letsencrypt() {
print_step "使用 acme.sh + Let's Encrypt 部署SSL证书..."
# 安装acme.sh
if [[ ! -d ~/.acme.sh ]] || [[ ! -f ~/.acme.sh/acme.sh ]]; then
echo ""
print_info "正在安装 acme.sh..."
# 如果目录存在但文件不存在,先清理
if [[ -d ~/.acme.sh ]] && [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_warning "检测到不完整的安装,正在清理..."
rm -rf ~/.acme.sh
fi
print_info "使用 GitHub 官方源(国内可能较慢,请耐心等待)"
# 使用官方安装方法直接通过curl管道执行
print_info "正在下载并安装..."
if curl -fsSL https://get.acme.sh | sh -s email=admin@example.com; then
install_result=$?
print_info "安装脚本执行完成,退出码: $install_result"
else
install_result=$?
print_error "安装脚本执行失败,退出码: $install_result"
fi
# 重新加载环境变量
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
# 等待文件系统同步
print_info "等待安装完成..."
sleep 3
# 验证安装是否真正成功
if [[ -d ~/.acme.sh ]] && [[ -f ~/.acme.sh/acme.sh ]]; then
print_success "acme.sh 安装成功"
else
print_error "acme.sh 安装失败"
echo ""
print_warning "诊断信息:"
echo " - 安装命令退出码: $install_result"
echo " - 目录 ~/.acme.sh 存在: $([ -d ~/.acme.sh ] && echo '是' || echo '否')"
echo " - 文件 ~/.acme.sh/acme.sh 存在: $([ -f ~/.acme.sh/acme.sh ] && echo '是' || echo '否')"
echo " - HOME变量: $HOME"
echo " - 当前用户: $(whoami)"
echo ""
if [[ -d ~/.acme.sh ]]; then
print_info "~/.acme.sh 目录内容:"
ls -la ~/.acme.sh/ 2>&1 | head -15 || echo " 无法列出目录"
echo ""
fi
print_info "尝试查找acme.sh安装位置..."
find /root -name "acme.sh" -type f 2>/dev/null | head -5 || echo " 未找到"
echo ""
print_warning "可能的原因:"
echo " 1. 网络连接问题或下载超时"
echo " 2. GitHub访问受限国内网络"
echo " 3. curl版本过低或不支持某些功能"
echo ""
print_warning "建议尝试其他SSL方案:"
echo " 1. 返回选择 Certbot (推荐)"
echo " 2. 或选择 [8] 暂不配置HTTPS"
echo ""
return 1
fi
fi
# 确认acme.sh可用
echo ""
print_info "验证 acme.sh 安装..."
# 等待文件系统同步
sleep 2
# 检查安装目录
if [[ ! -d ~/.acme.sh ]]; then
print_error "安装目录不存在: ~/.acme.sh"
return 1
fi
# 检查主脚本文件
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "主脚本文件不存在: ~/.acme.sh/acme.sh"
print_info "目录内容:"
ls -la ~/.acme.sh/ 2>&1 | head -10 || echo "无法列出目录"
return 1
fi
# 检查脚本是否可执行
if [[ ! -x ~/.acme.sh/acme.sh ]]; then
print_warning "脚本不可执行,正在添加执行权限..."
chmod +x ~/.acme.sh/acme.sh
fi
# 测试脚本是否能运行
if ! ~/.acme.sh/acme.sh --version &> /dev/null; then
print_error "acme.sh 无法运行"
return 1
fi
print_success "acme.sh 验证通过"
# 申请证书
echo ""
print_info "正在申请 Let's Encrypt 证书..."
# 再次确认acme.sh存在
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "acme.sh文件不存在: ~/.acme.sh/acme.sh"
return 1
fi
# 使用webroot模式申请证书更可靠
# 先尝试正常申请,如果证书已存在则使用--force强制更新
if ~/.acme.sh/acme.sh --issue -d "$DOMAIN" --webroot "${PROJECT_DIR}/frontend"; then
print_success "证书申请成功"
else
# 检查是否是因为证书已存在
if ~/.acme.sh/acme.sh --list | grep -q "$DOMAIN"; then
print_warning "检测到证书已存在,使用已有证书"
print_success "将直接安装现有证书"
else
print_error "证书申请失败"
echo ""
print_warning "常见失败原因:"
echo " 1. 域名未正确解析到此服务器"
echo " 2. Nginx未正确配置或未启动"
echo " 3. 80端口被占用或防火墙阻止"
echo " 4. 前端目录权限不足"
echo ""
return 1
fi
fi
# 安装证书
echo ""
print_info "正在安装证书到Nginx..."
# 再次确认acme.sh存在
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "acme.sh文件不存在: ~/.acme.sh/acme.sh"
return 1
fi
mkdir -p /etc/nginx/ssl
# 确保nginx服务已启动证书安装时需要reload
if ! systemctl is-active --quiet nginx 2>/dev/null && ! pgrep -x nginx > /dev/null 2>&1; then
print_warning "Nginx未运行正在启动..."
systemctl start nginx 2>/dev/null || /www/server/nginx/sbin/nginx 2>/dev/null || true
sleep 2
fi
# 先不带reload命令安装证书避免nginx未启动导致失败
if ~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
--key-file /etc/nginx/ssl/${DOMAIN}.key \
--fullchain-file /etc/nginx/ssl/${DOMAIN}.crt; then
print_success "证书文件已安装到: /etc/nginx/ssl/"
# 手动reload nginx
if systemctl is-active --quiet nginx 2>/dev/null; then
systemctl reload nginx && print_success "Nginx配置已重载"
elif pgrep -x nginx > /dev/null; then
nginx -s reload && print_success "Nginx配置已重载"
else
print_warning "Nginx未运行将在后续步骤启动"
fi
# 配置自动续期
setup_acme_auto_renew
return 0
else
print_error "证书安装失败"
return 1
fi
}
deploy_acme_zerossl() {
print_step "使用 acme.sh + ZeroSSL 部署SSL证书..."
# 安装acme.sh使用改进的安装逻辑
if [[ ! -d ~/.acme.sh ]] || [[ ! -f ~/.acme.sh/acme.sh ]]; then
echo ""
print_info "正在安装 acme.sh..."
# 如果目录存在但文件不存在,先清理
if [[ -d ~/.acme.sh ]] && [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_warning "检测到不完整的安装,正在清理..."
rm -rf ~/.acme.sh
fi
print_info "使用 GitHub 官方源(国内可能较慢,请耐心等待)"
# 使用官方安装方法直接通过curl管道执行
print_info "正在下载并安装..."
if curl -fsSL https://get.acme.sh | sh -s email=admin@example.com; then
install_result=$?
print_info "安装脚本执行完成,退出码: $install_result"
else
install_result=$?
print_error "安装脚本执行失败,退出码: $install_result"
fi
# 重新加载环境变量
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
# 等待文件系统同步
print_info "等待安装完成..."
sleep 3
# 验证安装
if [[ -d ~/.acme.sh ]] && [[ -f ~/.acme.sh/acme.sh ]]; then
print_success "acme.sh 安装成功"
else
print_error "acme.sh 安装失败"
echo ""
print_warning "诊断信息:"
echo " - 安装命令退出码: $install_result"
echo " - 目录 ~/.acme.sh 存在: $([ -d ~/.acme.sh ] && echo '是' || echo '否')"
echo " - 文件 ~/.acme.sh/acme.sh 存在: $([ -f ~/.acme.sh/acme.sh ] && echo '是' || echo '否')"
echo ""
if [[ -d ~/.acme.sh ]]; then
print_info "~/.acme.sh 目录内容:"
ls -la ~/.acme.sh/ 2>&1 | head -15 || echo " 无法列出目录"
echo ""
fi
return 1
fi
fi
# 确认acme.sh可用
echo ""
print_info "验证 acme.sh 安装..."
# 等待文件系统同步
sleep 2
# 检查安装目录
if [[ ! -d ~/.acme.sh ]]; then
print_error "安装目录不存在: ~/.acme.sh"
return 1
fi
# 检查主脚本文件
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "主脚本文件不存在: ~/.acme.sh/acme.sh"
print_info "目录内容:"
ls -la ~/.acme.sh/ 2>&1 | head -10 || echo "无法列出目录"
return 1
fi
# 检查脚本是否可执行
if [[ ! -x ~/.acme.sh/acme.sh ]]; then
print_warning "脚本不可执行,正在添加执行权限..."
chmod +x ~/.acme.sh/acme.sh
fi
# 测试脚本是否能运行
if ! ~/.acme.sh/acme.sh --version &> /dev/null; then
print_error "acme.sh 无法运行"
return 1
fi
print_success "acme.sh 验证通过"
# 申请证书
echo ""
print_info "正在申请 ZeroSSL 证书..."
# 再次确认acme.sh存在
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "acme.sh文件不存在: ~/.acme.sh/acme.sh"
return 1
fi
# 使用webroot模式申请证书更可靠
if ~/.acme.sh/acme.sh --server zerossl --issue -d "$DOMAIN" --webroot "${PROJECT_DIR}/frontend"; then
print_success "证书申请成功"
else
# 检查是否是因为证书已存在
if ~/.acme.sh/acme.sh --list | grep -q "$DOMAIN"; then
print_warning "检测到证书已存在,使用已有证书"
print_success "将直接安装现有证书"
else
print_error "证书申请失败"
return 1
fi
fi
# 安装证书
echo ""
print_info "正在安装证书到Nginx..."
# 再次确认acme.sh存在
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "acme.sh文件不存在: ~/.acme.sh/acme.sh"
return 1
fi
mkdir -p /etc/nginx/ssl
# 确保nginx服务已启动证书安装时需要reload
if ! systemctl is-active --quiet nginx 2>/dev/null && ! pgrep -x nginx > /dev/null 2>&1; then
print_warning "Nginx未运行正在启动..."
systemctl start nginx 2>/dev/null || /www/server/nginx/sbin/nginx 2>/dev/null || true
sleep 2
fi
# 先不带reload命令安装证书避免nginx未启动导致失败
if ~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
--key-file /etc/nginx/ssl/${DOMAIN}.key \
--fullchain-file /etc/nginx/ssl/${DOMAIN}.crt; then
print_success "证书文件已安装到: /etc/nginx/ssl/"
# 手动reload nginx
if systemctl is-active --quiet nginx 2>/dev/null; then
systemctl reload nginx && print_success "Nginx配置已重载"
elif pgrep -x nginx > /dev/null; then
nginx -s reload && print_success "Nginx配置已重载"
else
print_warning "Nginx未运行将在后续步骤启动"
fi
# 配置自动续期
setup_acme_auto_renew
return 0
else
print_error "证书安装失败"
return 1
fi
}
deploy_acme_buypass() {
print_step "使用 acme.sh + Buypass 部署SSL证书..."
# 安装acme.sh使用改进的安装逻辑
if [[ ! -d ~/.acme.sh ]] || [[ ! -f ~/.acme.sh/acme.sh ]]; then
echo ""
print_info "正在安装 acme.sh..."
# 如果目录存在但文件不存在,先清理
if [[ -d ~/.acme.sh ]] && [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_warning "检测到不完整的安装,正在清理..."
rm -rf ~/.acme.sh
fi
print_info "使用 GitHub 官方源(国内可能较慢,请耐心等待)"
# 使用官方安装方法直接通过curl管道执行
print_info "正在下载并安装..."
if curl -fsSL https://get.acme.sh | sh -s email=admin@example.com; then
install_result=$?
print_info "安装脚本执行完成,退出码: $install_result"
else
install_result=$?
print_error "安装脚本执行失败,退出码: $install_result"
fi
# 重新加载环境变量
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
# 等待文件系统同步
print_info "等待安装完成..."
sleep 3
# 验证安装
if [[ -d ~/.acme.sh ]] && [[ -f ~/.acme.sh/acme.sh ]]; then
print_success "acme.sh 安装成功"
else
print_error "acme.sh 安装失败"
echo ""
print_warning "诊断信息:"
echo " - 安装命令退出码: $install_result"
echo " - 目录 ~/.acme.sh 存在: $([ -d ~/.acme.sh ] && echo '是' || echo '否')"
echo " - 文件 ~/.acme.sh/acme.sh 存在: $([ -f ~/.acme.sh/acme.sh ] && echo '是' || echo '否')"
echo ""
if [[ -d ~/.acme.sh ]]; then
print_info "~/.acme.sh 目录内容:"
ls -la ~/.acme.sh/ 2>&1 | head -15 || echo " 无法列出目录"
echo ""
fi
return 1
fi
fi
# 确认acme.sh可用
echo ""
print_info "验证 acme.sh 安装..."
# 等待文件系统同步
sleep 2
# 检查安装目录
if [[ ! -d ~/.acme.sh ]]; then
print_error "安装目录不存在: ~/.acme.sh"
return 1
fi
# 检查主脚本文件
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "主脚本文件不存在: ~/.acme.sh/acme.sh"
print_info "目录内容:"
ls -la ~/.acme.sh/ 2>&1 | head -10 || echo "无法列出目录"
return 1
fi
# 检查脚本是否可执行
if [[ ! -x ~/.acme.sh/acme.sh ]]; then
print_warning "脚本不可执行,正在添加执行权限..."
chmod +x ~/.acme.sh/acme.sh
fi
# 测试脚本是否能运行
if ! ~/.acme.sh/acme.sh --version &> /dev/null; then
print_error "acme.sh 无法运行"
return 1
fi
print_success "acme.sh 验证通过"
# 申请证书
echo ""
print_info "正在申请 Buypass 证书..."
# 再次确认acme.sh存在
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "acme.sh文件不存在: ~/.acme.sh/acme.sh"
return 1
fi
# 使用webroot模式申请证书更可靠
if ~/.acme.sh/acme.sh --server buypass --issue -d "$DOMAIN" --webroot "${PROJECT_DIR}/frontend"; then
print_success "证书申请成功"
else
# 检查是否是因为证书已存在
if ~/.acme.sh/acme.sh --list | grep -q "$DOMAIN"; then
print_warning "检测到证书已存在,使用已有证书"
print_success "将直接安装现有证书"
else
print_error "证书申请失败"
return 1
fi
fi
# 安装证书
echo ""
print_info "正在安装证书到Nginx..."
# 再次确认acme.sh存在
if [[ ! -f ~/.acme.sh/acme.sh ]]; then
print_error "acme.sh文件不存在: ~/.acme.sh/acme.sh"
return 1
fi
mkdir -p /etc/nginx/ssl
# 确保nginx服务已启动证书安装时需要reload
if ! systemctl is-active --quiet nginx 2>/dev/null && ! pgrep -x nginx > /dev/null 2>&1; then
print_warning "Nginx未运行正在启动..."
systemctl start nginx 2>/dev/null || /www/server/nginx/sbin/nginx 2>/dev/null || true
sleep 2
fi
# 先不带reload命令安装证书避免nginx未启动导致失败
if ~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
--key-file /etc/nginx/ssl/${DOMAIN}.key \
--fullchain-file /etc/nginx/ssl/${DOMAIN}.crt; then
print_success "证书文件已安装到: /etc/nginx/ssl/"
# 手动reload nginx
if systemctl is-active --quiet nginx 2>/dev/null; then
systemctl reload nginx && print_success "Nginx配置已重载"
elif pgrep -x nginx > /dev/null; then
nginx -s reload && print_success "Nginx配置已重载"
else
print_warning "Nginx未运行将在后续步骤启动"
fi
# 配置自动续期
setup_acme_auto_renew
return 0
else
print_error "证书安装失败"
return 1
fi
}
deploy_aliyun_ssl() {
print_step "使用阿里云免费证书..."
print_warning "此功能需要您提供阿里云AccessKey"
echo ""
read -p "阿里云AccessKey ID: " ALIYUN_ACCESS_KEY_ID < /dev/tty
read -p "阿里云AccessKey Secret: " ALIYUN_ACCESS_KEY_SECRET < /dev/tty
# 这里需要调用阿里云API申请证书
# 暂时返回失败,提示用户使用其他方案
print_error "阿里云证书申请功能开发中,请选择其他方案"
return 1
}
deploy_tencent_ssl() {
print_step "使用腾讯云免费证书..."
print_warning "此功能需要您提供腾讯云SecretKey"
echo ""
read -p "腾讯云SecretId: " TENCENT_SECRET_ID < /dev/tty
read -p "腾讯云SecretKey: " TENCENT_SECRET_KEY < /dev/tty
# 这里需要调用腾讯云API申请证书
# 暂时返回失败,提示用户使用其他方案
print_error "腾讯云证书申请功能开发中,请选择其他方案"
return 1
}
deploy_manual_ssl() {
print_step "使用已有证书..."
echo ""
print_info "请将以下文件上传到服务器:"
print_info "- 证书文件: /tmp/ssl_cert.crt"
print_info "- 私钥文件: /tmp/ssl_key.key"
echo ""
read -p "上传完成后按回车继续..." < /dev/tty
if [[ -f /tmp/ssl_cert.crt ]] && [[ -f /tmp/ssl_key.key ]]; then
mkdir -p /etc/nginx/ssl
cp /tmp/ssl_cert.crt /etc/nginx/ssl/${DOMAIN}.crt
cp /tmp/ssl_key.key /etc/nginx/ssl/${DOMAIN}.key
chmod 600 /etc/nginx/ssl/${DOMAIN}.key
print_success "证书文件已复制"
return 0
else
print_error "证书文件未找到"
return 1
fi
}
################################################################################
# 项目部署
################################################################################
create_project_directory() {
print_step "创建项目目录..."
if [[ -d "$PROJECT_DIR" ]]; then
print_warning "项目目录已存在"
read -p "是否删除并重新创建? (y/n): " recreate < /dev/tty
if [[ "$recreate" == "y" || "$recreate" == "Y" ]]; then
rm -rf "$PROJECT_DIR"
else
print_error "部署已取消"
exit 1
fi
fi
mkdir -p "$PROJECT_DIR"
print_success "项目目录已创建: $PROJECT_DIR"
echo ""
}
download_project() {
print_step "正在从仓库下载项目..."
cd /tmp
if [[ -d "${PROJECT_NAME}" ]]; then
rm -rf "${PROJECT_NAME}"
fi
git clone "$REPO_URL" "${PROJECT_NAME}"
# 复制文件到项目目录
cp -r "/tmp/${PROJECT_NAME}"/* "$PROJECT_DIR/"
# 清理临时文件
rm -rf "/tmp/${PROJECT_NAME}"
print_success "项目下载完成"
echo ""
}
configure_admin_account() {
print_step "配置管理员账号"
echo ""
while true; do
read -p "管理员用户名 [默认: admin]: " ADMIN_USERNAME < /dev/tty
ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
if [[ ${#ADMIN_USERNAME} -lt 3 ]]; then
print_error "用户名至少3个字符"
continue
fi
break
done
while true; do
read -s -p "管理员密码至少6位: " ADMIN_PASSWORD < /dev/tty
echo ""
if [[ ${#ADMIN_PASSWORD} -lt 6 ]]; then
print_error "密码至少6个字符"
continue
fi
read -s -p "确认密码: " ADMIN_PASSWORD_CONFIRM < /dev/tty
echo ""
if [[ "$ADMIN_PASSWORD" != "$ADMIN_PASSWORD_CONFIRM" ]]; then
print_error "两次密码不一致"
continue
fi
break
done
print_success "管理员账号配置完成"
echo ""
}
install_backend_dependencies() {
print_step "安装后端依赖..."
cd "${PROJECT_DIR}/backend"
# 确保Python可用node-gyp需要
if ! command -v python &> /dev/null; then
if command -v python3 &> /dev/null; then
# 创建python软链接指向python3
if [[ "$OS" == "ubuntu" ]] || [[ "$OS" == "debian" ]]; then
ln -sf /usr/bin/python3 /usr/bin/python || true
else
# CentOS/RHEL/其他系统
alternatives --install /usr/bin/python python /usr/bin/python3 1 &> /dev/null || \
ln -sf /usr/bin/python3 /usr/bin/python || true
fi
print_info "已配置Python环境python -> python3"
else
print_warning "未找到Python某些依赖可能需要手动处理"
fi
fi
# 使用国内镜像加速
if [[ "$USE_ALIYUN_MIRROR" == "true" ]]; then
npm config set registry https://registry.npmmirror.com
fi
print_info "正在安装依赖包包含数据库native模块可能需要几分钟..."
# 安装依赖,捕获错误
if PYTHON=python3 npm install --production; then
print_success "后端依赖安装完成"
else
print_error "依赖安装失败"
echo ""
print_warning "可能的解决方案:"
echo " 1. 检查网络连接"
echo " 2. 手动执行: cd ${PROJECT_DIR}/backend && npm install --production"
echo " 3. 查看详细错误日志: ~/.npm/_logs/"
echo ""
# 询问是否继续
read -p "是否忽略错误继续安装?(y/n): " continue_install < /dev/tty
if [[ "$continue_install" != "y" ]] && [[ "$continue_install" != "Y" ]]; then
exit 1
fi
fi
echo ""
}
create_env_file() {
print_step "创建配置文件..."
# 生成随机JWT密钥
JWT_SECRET=$(openssl rand -base64 32)
# 生成随机Session密钥
SESSION_SECRET=$(openssl rand -hex 32)
# ========== CORS 安全配置自动生成 ==========
# 根据部署模式自动配置 ALLOWED_ORIGINS 和 COOKIE_SECURE
if [[ "$USE_DOMAIN" == "true" ]]; then
# 域名模式
if [[ "$SSL_METHOD" == "8" || -z "$SSL_METHOD" ]]; then
# HTTP 模式
PROTOCOL="http"
COOKIE_SECURE_VALUE="false"
PORT_VALUE=${HTTP_PORT:-80}
ENFORCE_HTTPS_VALUE="false"
else
# HTTPS 模式
PROTOCOL="https"
COOKIE_SECURE_VALUE="true"
PORT_VALUE=${HTTPS_PORT:-443}
ENFORCE_HTTPS_VALUE="true"
fi
# 生成 ALLOWED_ORIGINS (标准端口不需要显示端口号)
if [[ "$PORT_VALUE" == "80" ]] || [[ "$PORT_VALUE" == "443" ]]; then
ALLOWED_ORIGINS_VALUE="${PROTOCOL}://${DOMAIN}"
else
ALLOWED_ORIGINS_VALUE="${PROTOCOL}://${DOMAIN}:${PORT_VALUE}"
fi
print_info "CORS 配置: ${ALLOWED_ORIGINS_VALUE}"
else
# IP 模式(开发/测试环境)
# 留空,后端默认允许所有来源(适合开发环境)
ALLOWED_ORIGINS_VALUE=""
COOKIE_SECURE_VALUE="false"
ENFORCE_HTTPS_VALUE="false"
print_warning "IP 模式下 CORS 将允许所有来源(仅适合开发环境)"
print_info "生产环境建议使用域名模式"
fi
cat > "${PROJECT_DIR}/backend/.env" << EOF
# 管理员账号
ADMIN_USERNAME=${ADMIN_USERNAME}
ADMIN_PASSWORD=${ADMIN_PASSWORD}
# JWT密钥
JWT_SECRET=${JWT_SECRET}
# Session密钥用于会话管理
SESSION_SECRET=${SESSION_SECRET}
# 数据库路径
DATABASE_PATH=./data/database.db
# 存储目录
STORAGE_ROOT=./storage
# 服务端口
PORT=${BACKEND_PORT}
# 环境
NODE_ENV=production
# 强制HTTPS生产环境建议开启
ENFORCE_HTTPS=${ENFORCE_HTTPS_VALUE}
# CORS 跨域配置
# 允许访问的前端域名(多个用逗号分隔)
# 生产环境必须配置具体域名,开发环境可留空
ALLOWED_ORIGINS=${ALLOWED_ORIGINS_VALUE}
# Cookie 安全配置
# HTTPS 环境必须设置为 true
COOKIE_SECURE=${COOKIE_SECURE_VALUE}
# 信任代理配置(重要安全配置)
# 在 Nginx/CDN 后部署时必须配置,否则无法正确识别客户端 IP 和协议
# 配置选项:
# - false: 不信任代理(直接暴露,默认值)
# - 1: 信任前 1 跳代理(单层 Nginx推荐
# - 2: 信任前 2 跳代理CDN + Nginx
# - loopback: 仅信任本地回环地址
# 警告:不要设置为 true这会信任所有代理存在 IP/协议伪造风险!
TRUST_PROXY=1
# 公开端口nginx监听的端口用于生成分享链接
# 如果使用标准端口(80/443)或未配置,分享链接将不包含端口号
PUBLIC_PORT=${HTTP_PORT}
EOF
print_success "配置文件创建完成"
# 显示安全提示
if [[ -z "$ALLOWED_ORIGINS_VALUE" ]]; then
echo ""
print_warning "⚠️ 安全提示:"
print_info " 当前配置允许所有域名访问CORS: *"
print_info " 这仅适合开发环境,生产环境存在安全风险"
print_info " 建议在生产环境使用域名模式部署"
echo ""
fi
echo ""
}
create_data_directories() {
print_step "创建数据目录..."
mkdir -p "${PROJECT_DIR}/backend/data"
mkdir -p "${PROJECT_DIR}/backend/storage"
print_success "数据目录创建完成"
echo ""
}
################################################################################
# Nginx配置 - 分步骤执行
################################################################################
# 安全重启/重载Nginx带回退
restart_nginx_safe() {
print_info "尝试重启/重载 Nginx..."
# 如果有systemd并存在nginx服务优先使用不强制重启避免80/443冲突
if command -v systemctl &> /dev/null && systemctl list-unit-files | grep -q "^nginx.service"; then
if systemctl is-active --quiet nginx; then
if systemctl reload nginx 2>/dev/null; then
print_success "已通过 systemctl reload 重载 Nginx"
return 0
fi
else
if systemctl start nginx 2>/dev/null; then
print_success "已通过 systemctl start 启动 Nginx"
return 0
else
print_warning "systemctl 启动失败,可能端口被占用或已有其他反代在跑"
fi
fi
fi
# 宝塔路径优先尝试
if [[ -x /www/server/nginx/sbin/nginx ]]; then
if /www/server/nginx/sbin/nginx -t 2>/dev/null; then
if /www/server/nginx/sbin/nginx -s reload 2>/dev/null; then
print_success "已通过宝塔 nginx -s reload 重载"
return 0
fi
if /www/server/nginx/sbin/nginx 2>/dev/null; then
print_success "已通过宝塔 nginx 启动"
return 0
fi
else
print_error "宝塔 Nginx 配置测试失败"
/www/server/nginx/sbin/nginx -t 2>&1 || true
fi
fi
# 直接使用nginx命令
if command -v nginx &> /dev/null; then
if ! nginx -t 2>/dev/null; then
print_error "nginx -t 配置测试失败,请检查配置"
nginx -t 2>&1 || true
return 1
fi
if nginx -s reload 2>/dev/null; then
print_success "已使用 nginx -s reload 重载配置"
return 0
fi
# reload失败尝试直接启动
if nginx 2>/dev/null; then
print_success "已直接启动 Nginx"
return 0
fi
fi
print_error "未能成功启动/重载 Nginx请手动检查端口占用/安装状态)"
return 1
}
# 确保已安装Nginx更新/修复模式下可能未安装)
ensure_nginx_installed() {
if command -v nginx &> /dev/null || [[ -x /www/server/nginx/sbin/nginx ]]; then
return 0
fi
print_warning "未检测到 Nginx。若你已有其它反向代理占用80/443可跳过安装并手动配置如需本脚本自动配置请先安装Nginx后再运行。"
return 0
}
# 步骤1: 先配置HTTP Nginx为SSL证书验证做准备
configure_nginx_http_first() {
print_step "配置基础HTTP Nginx用于SSL证书验证..."
# 确保已安装Nginx
ensure_nginx_installed || return 1
# 总是先配置HTTP模式
local server_name="${DOMAIN:-_}"
# 检测Nginx配置目录结构并创建必要的目录
if [[ -d /www/server/nginx ]]; then
# 宝塔面板 (BT Panel)
NGINX_CONF_DIR="/www/server/panel/vhost/nginx"
NGINX_ENABLED_DIR=""
USE_SYMLINK=false
IS_BT_PANEL=true
# 确保目录存在
mkdir -p ${NGINX_CONF_DIR}
print_info "检测到宝塔面板使用宝塔Nginx配置目录"
elif [[ -d /etc/nginx/sites-available ]] || [[ "$PKG_MANAGER" == "apt" ]]; then
# Debian/Ubuntu: 使用sites-available
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
USE_SYMLINK=true
IS_BT_PANEL=false
# 确保目录存在
mkdir -p ${NGINX_CONF_DIR}
mkdir -p ${NGINX_ENABLED_DIR}
else
# CentOS/RHEL: 使用conf.d
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_ENABLED_DIR=""
USE_SYMLINK=false
IS_BT_PANEL=false
# 确保目录存在
mkdir -p ${NGINX_CONF_DIR}
fi
cat > ${NGINX_CONF_DIR}/${PROJECT_NAME}.conf << EOF
server {
listen ${HTTP_PORT};
server_name ${server_name};
# 文件上传大小限制10GB
client_max_body_size 10G;
# ========== 安全响应头 ==========
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# 隐藏Nginx版本号
server_tokens off;
# ========== 禁止访问隐藏文件 ==========
location ~ /\\. {
deny all;
return 404;
}
# ========== 禁止访问敏感文件 ==========
location ~ \\.(env|git|config|key|pem|crt|sql|bak|backup|old|log)$ {
deny all;
return 404;
}
# 前端静态文件
location / {
root ${PROJECT_DIR}/frontend;
index index.html;
try_files \$uri \$uri/ /index.html;
}
# 后端API
location /api {
proxy_pass http://localhost:${BACKEND_PORT};
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host \$host;
proxy_cache_bypass \$http_upgrade;
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;
# Cookie传递配置验证码session需要
proxy_set_header Cookie \$http_cookie;
proxy_pass_header Set-Cookie;
# 上传超时设置
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 300s;
}
# 分享页面
location /s/ {
proxy_pass http://localhost:${BACKEND_PORT};
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;
}
# 静态资源
location /libs {
alias ${PROJECT_DIR}/frontend/libs;
expires 30d;
}
}
}
EOF
# 根据系统类型处理配置文件
if [[ "$USE_SYMLINK" == "true" ]]; then
# Debian/Ubuntu: 创建软链接
ln -sf ${NGINX_CONF_DIR}/${PROJECT_NAME}.conf ${NGINX_ENABLED_DIR}/${PROJECT_NAME}.conf
# 删除默认站点
rm -f ${NGINX_ENABLED_DIR}/default
elif [[ "$IS_BT_PANEL" != "true" ]]; then
# CentOS/RHEL (非宝塔): conf.d中的.conf文件会自动加载
rm -f /etc/nginx/conf.d/default.conf
fi
# 宝塔面板:配置文件已自动包含,无需额外操作
# 测试nginx配置
if ! nginx -t; then
print_error "Nginx配置测试失败"
return 1
fi
# 启动或重载Nginx
if [[ "$IS_BT_PANEL" == "true" ]]; then
# 宝塔面板:尝试多种方式
print_info "宝塔环境尝试重载Nginx..."
# 优先使用最可靠的方式: 直接使用nginx命令reload
if [[ -f /www/server/nginx/sbin/nginx ]]; then
# 先测试配置
if /www/server/nginx/sbin/nginx -t 2>/dev/null; then
# 配置测试通过尝试reload
if /www/server/nginx/sbin/nginx -s reload 2>/dev/null; then
print_success "已使用nginx -s reload重载配置"
else
# reload失败尝试重启
print_warning "reload失败尝试重启Nginx..."
/www/server/nginx/sbin/nginx -s stop 2>/dev/null || true
sleep 2
if /www/server/nginx/sbin/nginx 2>/dev/null; then
print_success "Nginx已重新启动"
else
print_error "Nginx启动失败请手动检查"
# 不退出脚本,继续后续步骤
fi
fi
else
print_error "Nginx配置测试失败"
# 显示配置错误但不退出脚本
/www/server/nginx/sbin/nginx -t 2>&1 || true
fi
fi
# 备用方式: 尝试systemctl某些宝塔环境也支持
if systemctl is-active --quiet nginx 2>/dev/null; then
systemctl reload nginx 2>/dev/null && print_info "已使用systemctl重载配置" || true
fi
else
# 标准Nginx重启带回退
restart_nginx_safe || return 1
fi
# 验证Nginx是否运行
sleep 2
if [[ "$IS_BT_PANEL" == "true" ]]; then
# 宝塔:检查进程
if pgrep -x nginx > /dev/null; then
print_success "Nginx运行正常"
else
print_error "Nginx未运行"
print_warning "请在宝塔面板中手动启动Nginx或运行"
print_warning "/www/server/nginx/sbin/nginx"
return 1
fi
else
# 标准Nginx使用systemctl检查
if command -v systemctl &> /dev/null && systemctl list-unit-files | grep -q "^nginx.service"; then
if ! systemctl is-active --quiet nginx; then
print_error "Nginx启动失败"
return 1
fi
elif ! pgrep -x nginx > /dev/null; then
print_error "Nginx启动失败"
return 1
fi
fi
print_success "基础HTTP Nginx配置完成"
echo ""
}
# 步骤2: 根据SSL结果配置最终Nginx
configure_nginx_final() {
print_step "配置最终Nginx..."
# 检查SSL是否成功部署
local ssl_deployed=false
if [[ "$USE_DOMAIN" == "true" ]] && [[ "$SSL_METHOD" != "8" ]]; then
# 检查SSL证书文件是否存在
if [[ -f /etc/nginx/ssl/${DOMAIN}.crt ]] && [[ -f /etc/nginx/ssl/${DOMAIN}.key ]]; then
ssl_deployed=true
print_info "检测到SSL证书配置HTTPS..."
else
print_warning "SSL证书不存在保持HTTP配置"
fi
fi
# 根据SSL状态配置
if [[ "$ssl_deployed" == "true" ]]; then
# 配置HTTPS
configure_nginx_https
else
# 保持HTTP已在第一步配置这里只需确认
print_info "使用HTTP配置"
fi
# 测试nginx配置
if ! nginx -t; then
print_error "Nginx配置测试失败"
return 1
fi
# 重载nginx - 兼容宝塔面板
if [[ "$IS_BT_PANEL" == "true" ]]; then
# 宝塔面板:尝试多种方式
print_info "宝塔环境重载Nginx配置..."
# 优先使用最可靠的方式: 直接使用nginx命令reload
if [[ -f /www/server/nginx/sbin/nginx ]]; then
if /www/server/nginx/sbin/nginx -s reload 2>/dev/null; then
print_success "已使用nginx -s reload重载配置"
else
# reload失败尝试重启
print_warning "reload失败尝试重启Nginx..."
/www/server/nginx/sbin/nginx -s stop 2>/dev/null || true
sleep 2
if /www/server/nginx/sbin/nginx 2>/dev/null; then
print_success "已启动Nginx"
else
print_warning "Nginx启动失败请手动检查"
fi
fi
fi
# 备用方式: 尝试systemctl
if systemctl is-active --quiet nginx 2>/dev/null; then
systemctl reload nginx 2>/dev/null && print_info "已使用systemctl重载配置" || true
fi
else
# 标准Nginx重载带回退
restart_nginx_safe || return 1
fi
print_success "Nginx最终配置完成"
echo ""
}
configure_nginx() {
print_step "配置Nginx..."
if [[ "$USE_DOMAIN" == "true" ]]; then
if [[ "$SSL_METHOD" == "8" ]]; then
# HTTP配置
configure_nginx_http
else
# HTTPS配置
configure_nginx_https
fi
else
# IP模式HTTP配置
configure_nginx_http
fi
# 测试nginx配置
nginx -t
# 重启nginx
restart_nginx_safe || return 1
print_success "Nginx配置完成"
echo ""
}
configure_nginx_http() {
local server_name="${DOMAIN:-_}"
# 确保已安装Nginx
ensure_nginx_installed || return 1
# 检测Nginx配置目录结构并创建必要的目录
if [[ -d /www/server/nginx ]]; then
# 宝塔面板
NGINX_CONF_DIR="/www/server/panel/vhost/nginx"
USE_SYMLINK=false
IS_BT_PANEL=true
mkdir -p ${NGINX_CONF_DIR}
elif [[ -d /etc/nginx/sites-available ]] || [[ "$PKG_MANAGER" == "apt" ]]; then
# Debian/Ubuntu
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
USE_SYMLINK=true
IS_BT_PANEL=false
mkdir -p ${NGINX_CONF_DIR}
mkdir -p ${NGINX_ENABLED_DIR}
else
# CentOS/RHEL
NGINX_CONF_DIR="/etc/nginx/conf.d"
USE_SYMLINK=false
IS_BT_PANEL=false
mkdir -p ${NGINX_CONF_DIR}
fi
cat > ${NGINX_CONF_DIR}/${PROJECT_NAME}.conf << EOF
server {
listen ${HTTP_PORT};
server_name ${server_name};
# 文件上传大小限制10GB
client_max_body_size 10G;
# ========== 安全响应头 ==========
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# 隐藏Nginx版本号
server_tokens off;
# ========== 禁止访问隐藏文件 ==========
location ~ /\\. {
deny all;
return 404;
}
# ========== 禁止访问敏感文件 ==========
location ~ \\.(env|git|config|key|pem|crt|sql|bak|backup|old|log)$ {
deny all;
return 404;
}
# 前端静态文件
location / {
root ${PROJECT_DIR}/frontend;
index index.html;
try_files \$uri \$uri/ /index.html;
}
# 后端API
location /api {
proxy_pass http://localhost:${BACKEND_PORT};
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host \$host;
proxy_cache_bypass \$http_upgrade;
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;
# Cookie传递配置验证码session需要
proxy_set_header Cookie \$http_cookie;
proxy_pass_header Set-Cookie;
# 上传超时设置大文件上传需要更长时间设置为1小时
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 300s;
}
# 分享页面(代理到后端处理重定向)
location /s/ {
proxy_pass http://localhost:${BACKEND_PORT};
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;
}
# 静态资源
location /libs {
alias ${PROJECT_DIR}/frontend/libs;
expires 30d;
}
}
}
EOF
# 根据系统类型处理配置文件
if [[ "$USE_SYMLINK" == "true" ]]; then
# Debian/Ubuntu: 创建软链接
ln -sf ${NGINX_CONF_DIR}/${PROJECT_NAME}.conf ${NGINX_ENABLED_DIR}/${PROJECT_NAME}.conf
# 删除默认站点
rm -f ${NGINX_ENABLED_DIR}/default
elif [[ "$IS_BT_PANEL" != "true" ]]; then
# CentOS/RHEL (非宝塔): conf.d中的.conf文件会自动加载
rm -f /etc/nginx/conf.d/default.conf
fi
}
configure_nginx_https() {
# 确保已安装Nginx
ensure_nginx_installed || return 1
# 检测Nginx配置目录结构并创建必要的目录
if [[ -d /www/server/nginx ]]; then
# 宝塔面板
NGINX_CONF_DIR="/www/server/panel/vhost/nginx"
USE_SYMLINK=false
IS_BT_PANEL=true
mkdir -p ${NGINX_CONF_DIR}
elif [[ -d /etc/nginx/sites-available ]] || [[ "$PKG_MANAGER" == "apt" ]]; then
# Debian/Ubuntu
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
USE_SYMLINK=true
IS_BT_PANEL=false
mkdir -p ${NGINX_CONF_DIR}
mkdir -p ${NGINX_ENABLED_DIR}
else
# CentOS/RHEL
NGINX_CONF_DIR="/etc/nginx/conf.d"
USE_SYMLINK=false
IS_BT_PANEL=false
mkdir -p ${NGINX_CONF_DIR}
fi
# 根据HTTPS端口生成正确的重定向URL
if [[ "$HTTPS_PORT" == "443" ]]; then
REDIRECT_URL="https://\$server_name\$request_uri"
else
REDIRECT_URL="https://\$server_name:${HTTPS_PORT}\$request_uri"
fi
cat > ${NGINX_CONF_DIR}/${PROJECT_NAME}.conf << EOF
server {
listen ${HTTP_PORT};
server_name ${DOMAIN};
return 301 ${REDIRECT_URL};
}
server {
listen ${HTTPS_PORT} ssl http2;
server_name ${DOMAIN};
ssl_certificate /etc/nginx/ssl/${DOMAIN}.crt;
ssl_certificate_key /etc/nginx/ssl/${DOMAIN}.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# 文件上传大小限制10GB
client_max_body_size 10G;
# ========== 安全响应头 ==========
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 隐藏Nginx版本号
server_tokens off;
# ========== 禁止访问隐藏文件 ==========
location ~ /\\. {
deny all;
return 404;
}
# ========== 禁止访问敏感文件 ==========
location ~ \\.(env|git|config|key|pem|crt|sql|bak|backup|old|log)$ {
deny all;
return 404;
}
# 前端静态文件
location / {
root ${PROJECT_DIR}/frontend;
index index.html;
try_files \$uri \$uri/ /index.html;
}
# 后端API
location /api {
proxy_pass http://localhost:${BACKEND_PORT};
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host \$host;
proxy_cache_bypass \$http_upgrade;
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;
# Cookie传递配置验证码session需要
proxy_set_header Cookie \$http_cookie;
proxy_pass_header Set-Cookie;
# 上传超时设置大文件上传需要更长时间设置为1小时
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 300s;
}
# 分享页面(代理到后端处理重定向)
location /s/ {
proxy_pass http://localhost:${BACKEND_PORT};
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;
}
# 静态资源
location /libs {
alias ${PROJECT_DIR}/frontend/libs;
expires 30d;
}
}
}
EOF
# 根据系统类型处理配置文件
if [[ "$USE_SYMLINK" == "true" ]]; then
# Debian/Ubuntu: 创建软链接
ln -sf ${NGINX_CONF_DIR}/${PROJECT_NAME}.conf ${NGINX_ENABLED_DIR}/${PROJECT_NAME}.conf
# 删除默认站点
rm -f ${NGINX_ENABLED_DIR}/default
elif [[ "$IS_BT_PANEL" != "true" ]]; then
# CentOS/RHEL (非宝塔): conf.d中的.conf文件会自动加载
rm -f /etc/nginx/conf.d/default.conf
fi
}
start_backend_service() {
print_step "启动后端服务..."
cd "${PROJECT_DIR}/backend"
# 使用PM2启动
pm2 start server.js --name ${PROJECT_NAME}-backend
pm2 save
print_success "后端服务已启动"
echo ""
}
################################################################################
# 健康检查
################################################################################
health_check() {
print_step "正在进行健康检查..."
sleep 3
# 检查后端服务
if pm2 status | grep -q "${PROJECT_NAME}-backend.*online"; then
print_success "后端服务运行正常"
else
print_error "后端服务启动失败"
print_info "查看日志: pm2 logs ${PROJECT_NAME}-backend"
return 1
fi
# 检查端口
if netstat -tunlp 2>/dev/null | grep -q ":${BACKEND_PORT}" || ss -tunlp 2>/dev/null | grep -q ":${BACKEND_PORT}"; then
print_success "后端端口监听正常 (${BACKEND_PORT})"
else
print_error "后端端口监听异常"
return 1
fi
# 检查Nginx
if [[ -d /www/server/nginx ]]; then
# 宝塔面板:检查进程
if pgrep -x nginx > /dev/null; then
print_success "Nginx服务运行正常"
else
print_error "Nginx服务异常"
return 1
fi
else
# 标准Nginx使用systemctl检查
if systemctl is-active --quiet nginx; then
print_success "Nginx服务运行正常"
else
print_error "Nginx服务异常"
return 1
fi
fi
# 检查数据库
if [[ -f "${PROJECT_DIR}/backend/data/database.db" ]]; then
print_success "数据库初始化成功"
else
print_warning "数据库文件不存在"
fi
# 检查存储目录
if [[ -d "${PROJECT_DIR}/backend/storage" ]]; then
print_success "文件存储目录就绪"
else
print_warning "存储目录不存在"
fi
echo ""
return 0
}
################################################################################
# 完成提示
################################################################################
print_completion() {
clear
echo -e "${GREEN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ 🎉 部署成功! ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
echo ""
# 访问地址
if [[ "$USE_DOMAIN" == "true" ]]; then
if [[ "$SSL_METHOD" == "8" ]]; then
if [[ "$HTTP_PORT" == "80" ]]; then
echo -e "${CYAN}访问地址:${NC} http://${DOMAIN}"
else
echo -e "${CYAN}访问地址:${NC} http://${DOMAIN}:${HTTP_PORT}"
fi
else
if [[ "$HTTPS_PORT" == "443" ]]; then
echo -e "${CYAN}访问地址:${NC} https://${DOMAIN}"
else
echo -e "${CYAN}访问地址:${NC} https://${DOMAIN}:${HTTPS_PORT}"
fi
fi
else
PUBLIC_IP=$(curl -s ifconfig.me || curl -s icanhazip.com || echo "服务器IP")
if [[ "$HTTP_PORT" == "80" ]]; then
echo -e "${CYAN}访问地址:${NC} http://${PUBLIC_IP}"
else
echo -e "${CYAN}访问地址:${NC} http://${PUBLIC_IP}:${HTTP_PORT}"
fi
fi
echo -e "${CYAN}管理员账号:${NC} ${ADMIN_USERNAME}"
echo -e "${CYAN}管理员密码:${NC} ********"
echo ""
# 端口信息
if [[ "$HTTP_PORT" != "80" ]] || [[ "$HTTPS_PORT" != "443" ]] || [[ "$BACKEND_PORT" != "40001" ]]; then
echo -e "${YELLOW}端口配置:${NC}"
echo " HTTP端口: $HTTP_PORT"
if [[ "$USE_DOMAIN" == "true" ]] && [[ "$SSL_METHOD" != "8" ]]; then
echo " HTTPS端口: $HTTPS_PORT"
fi
echo " 后端端口: $BACKEND_PORT"
echo ""
fi
# 常用命令
echo -e "${YELLOW}常用命令:${NC}"
echo " 查看服务状态: pm2 status"
echo " 查看日志: pm2 logs ${PROJECT_NAME}-backend"
echo " 重启服务: pm2 restart ${PROJECT_NAME}-backend"
echo " 停止服务: pm2 stop ${PROJECT_NAME}-backend"
echo ""
# 配置文件位置
echo -e "${YELLOW}配置文件位置:${NC}"
echo " 后端配置: ${PROJECT_DIR}/backend/.env"
echo " Nginx配置: /etc/nginx/sites-enabled/${PROJECT_NAME}.conf"
echo " 数据库: ${PROJECT_DIR}/backend/data/database.db"
echo " 文件存储: ${PROJECT_DIR}/backend/storage"
echo ""
# SSL续期提示
if [[ "$USE_DOMAIN" == "true" ]] && [[ "$SSL_METHOD" != "8" ]]; then
echo -e "${YELLOW}SSL证书自动续期:${NC}"
echo " - 方式: acme.sh cron任务"
echo " - 频率: 每天自动检查"
echo " - 时机: 证书到期前30天自动续期"
echo " - 检查任务: crontab -l | grep acme"
echo " - 查看证书: ~/.acme.sh/acme.sh --list"
echo " - 手动续期: ~/.acme.sh/acme.sh --renew -d $DOMAIN --force"
echo ""
fi
echo -e "${GREEN}祝您使用愉快!${NC}"
echo ""
}
################################################################################
# 卸载功能
################################################################################
print_uninstall_banner() {
clear
echo -e "${RED}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ ⚠️ 玩玩云 卸载模式 ║"
echo "║ ║"
echo "║ Uninstall Mode ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
confirm_uninstall() {
print_uninstall_banner
echo -e "${YELLOW}"
echo "本脚本将执行以下操作:"
echo ""
echo "【将要删除】"
echo " ✓ PM2 进程: ${PROJECT_NAME}-backend"
echo " ✓ 项目目录: ${PROJECT_DIR}"
echo " ✓ Nginx 配置: /etc/nginx/sites-enabled/${PROJECT_NAME}.conf"
echo " ✓ 数据库文件: ${PROJECT_DIR}/backend/data/"
echo " ✓ 用户文件: ${PROJECT_DIR}/backend/storage/"
echo ""
echo "【将会保留】"
echo " ✓ Node.js"
echo " ✓ Nginx (程序本身)"
echo " ✓ PM2 (程序本身)"
echo " ✓ 编译工具 (build-essential等)"
echo -e "${NC}"
echo ""
print_warning "此操作不可逆,所有数据将被永久删除!"
echo ""
read -p "确定要卸载吗? (yes/no): " confirm < /dev/tty
if [[ "$confirm" != "yes" ]]; then
print_info "已取消卸载"
exit 0
fi
echo ""
read -p "请再次确认 (yes/no): " confirm2 < /dev/tty
if [[ "$confirm2" != "yes" ]]; then
print_info "已取消卸载"
exit 0
fi
echo ""
}
uninstall_backup_data() {
print_step "备份用户数据..."
if [[ ! -d "$PROJECT_DIR" ]]; then
print_info "项目目录不存在,跳过备份"
return
fi
BACKUP_DIR="/root/${PROJECT_NAME}-backup-$(date +%Y%m%d-%H%M%S)"
echo ""
read -p "是否备份用户数据? (y/n): " backup_choice < /dev/tty
if [[ "$backup_choice" == "y" || "$backup_choice" == "Y" ]]; then
mkdir -p "$BACKUP_DIR"
# 备份数据库
if [[ -d "${PROJECT_DIR}/backend/data" ]]; then
cp -r "${PROJECT_DIR}/backend/data" "$BACKUP_DIR/"
print_success "数据库已备份"
fi
# 备份用户文件
if [[ -d "${PROJECT_DIR}/backend/storage" ]]; then
cp -r "${PROJECT_DIR}/backend/storage" "$BACKUP_DIR/"
print_success "用户文件已备份"
fi
# 备份配置文件
if [[ -f "${PROJECT_DIR}/backend/.env" ]]; then
cp "${PROJECT_DIR}/backend/.env" "$BACKUP_DIR/"
print_success "配置文件已备份"
fi
print_success "备份已保存到: $BACKUP_DIR"
echo ""
else
print_warning "跳过备份,数据将被永久删除"
echo ""
fi
}
uninstall_stop_pm2() {
print_step "停止PM2进程..."
if command -v pm2 &> /dev/null; then
if pm2 list | grep -q "${PROJECT_NAME}-backend"; then
pm2 delete ${PROJECT_NAME}-backend
pm2 save --force
print_success "PM2进程已停止并删除"
else
print_info "PM2进程不存在跳过"
fi
else
print_info "PM2未安装跳过"
fi
}
uninstall_nginx_config() {
print_step "删除Nginx配置..."
local need_reload=false
# 删除sites-enabled软链接
if [[ -L /etc/nginx/sites-enabled/${PROJECT_NAME}.conf ]]; then
rm -f /etc/nginx/sites-enabled/${PROJECT_NAME}.conf
print_success "删除 sites-enabled 配置"
need_reload=true
fi
# 删除sites-available配置文件
if [[ -f /etc/nginx/sites-available/${PROJECT_NAME}.conf ]]; then
rm -f /etc/nginx/sites-available/${PROJECT_NAME}.conf
print_success "删除 sites-available 配置"
fi
# 测试并重载nginx
if [[ "$need_reload" == true ]] && command -v nginx &> /dev/null; then
if nginx -t &> /dev/null; then
systemctl reload nginx
print_success "Nginx配置已重载"
else
print_warning "Nginx配置测试失败请手动检查"
fi
fi
}
uninstall_ssl_certificates() {
print_step "清理SSL证书..."
# 删除nginx SSL证书目录下的项目证书
local cert_removed=false
if [[ -d /etc/nginx/ssl ]]; then
# 使用find查找包含项目名或域名的证书
find /etc/nginx/ssl -name "*${PROJECT_NAME}*" -type f -delete 2>/dev/null && cert_removed=true
fi
if [[ "$cert_removed" == true ]]; then
print_success "已删除Nginx SSL证书"
else
print_info "未发现Nginx SSL证书"
fi
# 提示acme.sh证书
if [[ -d ~/.acme.sh ]]; then
if ~/.acme.sh/acme.sh --list 2>/dev/null | grep -q "Main_Domain"; then
print_info "检测到acme.sh证书"
print_warning "如需删除,请手动运行: ~/.acme.sh/acme.sh --remove -d <your-domain>"
fi
fi
}
uninstall_project_directory() {
print_step "删除项目目录..."
if [[ -d "$PROJECT_DIR" ]]; then
# 统计大小
SIZE=$(du -sh "$PROJECT_DIR" 2>/dev/null | cut -f1 || echo "未知")
print_info "项目目录大小: $SIZE"
# 删除目录
rm -rf "$PROJECT_DIR"
print_success "项目目录已删除: $PROJECT_DIR"
else
print_info "项目目录不存在: $PROJECT_DIR"
fi
}
uninstall_temp_files() {
print_step "清理临时文件..."
local cleaned=false
# 清理/tmp下的临时文件
if [[ -d "/tmp/${PROJECT_NAME}" ]]; then
rm -rf "/tmp/${PROJECT_NAME}"
print_success "删除临时目录"
cleaned=true
fi
# 清理npm缓存
if command -v npm &> /dev/null; then
npm cache clean --force &> /dev/null || true
print_success "清理npm缓存"
cleaned=true
fi
if [[ "$cleaned" == false ]]; then
print_info "无需清理"
fi
}
uninstall_check_residual() {
print_step "检查残留文件..."
local residual_found=false
# 检查项目目录
if [[ -d "$PROJECT_DIR" ]]; then
print_warning "残留: 项目目录 $PROJECT_DIR"
residual_found=true
fi
# 检查Nginx配置
if [[ -f /etc/nginx/sites-enabled/${PROJECT_NAME}.conf ]] || [[ -f /etc/nginx/sites-available/${PROJECT_NAME}.conf ]]; then
print_warning "残留: Nginx配置文件"
residual_found=true
fi
# 检查PM2进程
if command -v pm2 &> /dev/null; then
if pm2 list | grep -q "${PROJECT_NAME}"; then
print_warning "残留: PM2进程"
residual_found=true
fi
fi
if [[ "$residual_found" == false ]]; then
print_success "未发现残留文件"
fi
}
print_uninstall_completion() {
clear
echo -e "${GREEN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ ✓ 卸载完成! ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
echo ""
echo -e "${CYAN}已删除内容:${NC}"
echo " ✓ PM2进程"
echo " ✓ 项目目录"
echo " ✓ Nginx配置"
echo " ✓ 数据库和用户文件"
echo ""
echo -e "${CYAN}保留的环境:${NC}"
echo " ✓ Node.js $(node -v 2>/dev/null || echo '(未安装)')"
echo " ✓ Nginx $(nginx -v 2>&1 | cut -d'/' -f2 || echo '(未安装)')"
echo " ✓ PM2 $(pm2 -v 2>/dev/null || echo '(未安装)')"
echo " ✓ 编译工具 (build-essential 等)"
echo ""
if [[ -n "$BACKUP_DIR" ]] && [[ -d "$BACKUP_DIR" ]]; then
echo -e "${YELLOW}备份位置:${NC}"
echo " $BACKUP_DIR"
echo ""
fi
echo -e "${GREEN}感谢使用玩玩云!${NC}"
echo ""
}
################################################################################
# 更新功能
################################################################################
print_update_banner() {
clear
echo -e "${BLUE}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ 🔄 玩玩云 更新模式 ║"
echo "║ ║"
echo "║ Update Mode ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
confirm_update() {
print_update_banner
echo -e "${YELLOW}"
echo "本脚本将执行以下操作:"
echo ""
echo "【将要更新】"
echo " ✓ 从仓库拉取最新代码"
echo " ✓ 更新后端依赖npm install"
echo " ✓ 重启后端服务"
echo ""
echo "【将会保留】"
echo " ✓ 数据库文件(用户数据)"
echo " ✓ 用户上传的文件"
echo " ✓ .env 配置文件"
echo " ✓ Nginx 配置"
echo -e "${NC}"
echo ""
print_info "建议在更新前备份重要数据"
echo ""
read -p "确定要更新吗? (y/n): " confirm < /dev/tty
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
print_info "已取消更新"
exit 0
fi
echo ""
}
update_check_project() {
print_step "检查项目是否已安装..."
if [[ ! -d "$PROJECT_DIR" ]]; then
print_error "项目未安装: $PROJECT_DIR"
print_info "请先运行安装命令"
exit 1
fi
if [[ ! -f "${PROJECT_DIR}/backend/server.js" ]]; then
print_error "项目目录不完整"
exit 1
fi
print_success "项目已安装: $PROJECT_DIR"
}
update_backup_important_files() {
print_step "备份重要文件..."
TEMP_BACKUP="/tmp/${PROJECT_NAME}-update-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$TEMP_BACKUP"
# 备份数据库
if [[ -d "${PROJECT_DIR}/backend/data" ]]; then
cp -r "${PROJECT_DIR}/backend/data" "$TEMP_BACKUP/"
print_success "数据库已备份"
fi
# 备份用户文件
if [[ -d "${PROJECT_DIR}/backend/storage" ]]; then
cp -r "${PROJECT_DIR}/backend/storage" "$TEMP_BACKUP/"
print_success "用户文件已备份"
fi
# 备份配置文件
if [[ -f "${PROJECT_DIR}/backend/.env" ]]; then
cp "${PROJECT_DIR}/backend/.env" "$TEMP_BACKUP/"
print_success "配置文件已备份"
fi
print_success "备份完成: $TEMP_BACKUP"
echo ""
}
update_stop_services() {
print_step "停止服务..."
if command -v pm2 &> /dev/null; then
if pm2 list | grep -q "${PROJECT_NAME}-backend"; then
pm2 stop ${PROJECT_NAME}-backend
print_success "后端服务已停止"
fi
fi
}
update_pull_latest_code() {
print_step "正在从仓库拉取最新代码..."
cd /tmp
if [[ -d "${PROJECT_NAME}-update" ]]; then
rm -rf "${PROJECT_NAME}-update"
fi
# 克隆最新代码
git clone "$REPO_URL" "${PROJECT_NAME}-update"
# 更新前端文件
print_info "更新前端文件..."
if [[ -d "/tmp/${PROJECT_NAME}-update/frontend" ]]; then
rm -rf "${PROJECT_DIR}/frontend"
cp -r "/tmp/${PROJECT_NAME}-update/frontend" "${PROJECT_DIR}/"
fi
# 更新后端代码文件(但不覆盖 data、storage、.env
print_info "更新后端代码..."
if [[ -d "/tmp/${PROJECT_NAME}-update/backend" ]]; then
cd "/tmp/${PROJECT_NAME}-update/backend"
# 复制后端文件,但排除 data、storage、.env、node_modules
for item in *; do
if [[ "$item" != "data" ]] && [[ "$item" != "storage" ]] && [[ "$item" != ".env" ]] && [[ "$item" != "node_modules" ]]; then
rm -rf "${PROJECT_DIR}/backend/$item"
cp -r "$item" "${PROJECT_DIR}/backend/"
fi
done
fi
# 确保备份的重要文件存在
print_info "确保数据完整性..."
# 如果 data 目录不存在,从备份恢复
if [[ ! -d "${PROJECT_DIR}/backend/data" ]] && [[ -d "$TEMP_BACKUP/data" ]]; then
print_warning "检测到 data 目录丢失,正在从备份恢复..."
cp -r "$TEMP_BACKUP/data" "${PROJECT_DIR}/backend/"
print_success "数据库已恢复"
fi
# 如果 storage 目录不存在,从备份恢复
if [[ ! -d "${PROJECT_DIR}/backend/storage" ]] && [[ -d "$TEMP_BACKUP/storage" ]]; then
print_warning "检测到 storage 目录丢失,正在从备份恢复..."
cp -r "$TEMP_BACKUP/storage" "${PROJECT_DIR}/backend/"
print_success "用户文件已恢复"
fi
# 如果 .env 文件不存在,从备份恢复
if [[ ! -f "${PROJECT_DIR}/backend/.env" ]] && [[ -f "$TEMP_BACKUP/.env" ]]; then
print_warning "检测到 .env 文件丢失,正在从备份恢复..."
cp "$TEMP_BACKUP/.env" "${PROJECT_DIR}/backend/"
print_success "配置文件已恢复"
fi
# 清理临时文件
rm -rf "/tmp/${PROJECT_NAME}-update"
rm -rf "$TEMP_BACKUP"
print_success "代码更新完成"
echo ""
}
update_install_dependencies() {
print_step "更新后端依赖..."
cd "${PROJECT_DIR}/backend"
# 确保Python可用node-gyp需要
if ! command -v python &> /dev/null; then
if command -v python3 &> /dev/null; then
if [[ "$OS" == "ubuntu" ]] || [[ "$OS" == "debian" ]]; then
ln -sf /usr/bin/python3 /usr/bin/python || true
else
alternatives --install /usr/bin/python python /usr/bin/python3 1 &> /dev/null || \
ln -sf /usr/bin/python3 /usr/bin/python || true
fi
print_info "已配置Python环境"
fi
fi
# 使用国内镜像加速(如果之前选择了)
if command -v npm &> /dev/null; then
current_registry=$(npm config get registry)
if [[ "$current_registry" =~ "npmmirror" ]] || [[ "$current_registry" =~ "taobao" ]]; then
print_info "检测到使用国内镜像源"
fi
fi
# 清理旧的node_modules
# 检查并升级C++编译器(如果需要)
check_cpp_compiler
if [[ -d "node_modules" ]]; then
print_info "清理旧依赖..."
rm -rf node_modules package-lock.json
fi
print_info "正在重新安装依赖(可能需要几分钟)..."
if PYTHON=python3 npm install --production; then
print_success "依赖更新完成"
else
print_error "依赖更新失败"
print_warning "请检查错误日志: ~/.npm/_logs/"
fi
echo ""
}
update_migrate_database() {
print_step "迁移数据库配置..."
cd "${PROJECT_DIR}/backend"
# 检查是否需要升级上传限制从100MB升级到10GB
if command -v sqlite3 &> /dev/null; then
if [[ -f "data/database.db" ]]; then
CURRENT_LIMIT=$(sqlite3 data/database.db "SELECT value FROM system_settings WHERE key = 'max_upload_size';" 2>/dev/null || echo "")
if [[ "$CURRENT_LIMIT" == "104857600" ]]; then
print_info "检测到旧的上传限制(100MB)正在升级到10GB..."
sqlite3 data/database.db "UPDATE system_settings SET value = '10737418240' WHERE key = 'max_upload_size';"
print_success "上传限制已升级: 100MB → 10GB"
elif [[ "$CURRENT_LIMIT" == "10737418240" ]]; then
print_success "上传限制已是最新: 10GB"
elif [[ -n "$CURRENT_LIMIT" ]]; then
print_info "当前上传限制: ${CURRENT_LIMIT} 字节"
else
print_info "数据库配置正常"
fi
fi
else
print_warning "sqlite3未安装跳过数据库迁移检查"
fi
# ========== 安全配置迁移 ==========
print_step "检查安全配置..."
if [[ -f ".env" ]]; then
# 检查 CORS 配置
CURRENT_CORS=$(grep "^ALLOWED_ORIGINS=" .env | cut -d'=' -f2-)
if [[ "$CURRENT_CORS" == "*" ]]; then
print_warning "⚠️ 检测到不安全的CORS配置: ALLOWED_ORIGINS=*"
echo ""
echo "这是一个严重的安全风险攻击者可以从任何域名访问你的API。"
echo ""
# 尝试从域名配置自动修复
if [[ -f "/etc/nginx/sites-enabled/${PROJECT_NAME}" ]] || [[ -f "/etc/nginx/conf.d/${PROJECT_NAME}.conf" ]]; then
# 尝试从Nginx配置读取域名
NGINX_DOMAIN=$(grep "server_name" /etc/nginx/sites-enabled/${PROJECT_NAME} 2>/dev/null | grep -v "_" | awk '{print $2}' | sed 's/;//g' | head -1)
if [[ -z "$NGINX_DOMAIN" ]]; then
NGINX_DOMAIN=$(grep "server_name" /etc/nginx/conf.d/${PROJECT_NAME}.conf 2>/dev/null | grep -v "_" | awk '{print $2}' | sed 's/;//g' | head -1)
fi
if [[ -n "$NGINX_DOMAIN" ]] && [[ "$NGINX_DOMAIN" != "localhost" ]]; then
# 检测是否使用HTTPS
if grep -q "listen.*443.*ssl" /etc/nginx/sites-enabled/${PROJECT_NAME} 2>/dev/null || \
grep -q "listen.*443.*ssl" /etc/nginx/conf.d/${PROJECT_NAME}.conf 2>/dev/null; then
FIXED_CORS="https://${NGINX_DOMAIN}"
else
FIXED_CORS="http://${NGINX_DOMAIN}"
fi
print_info "检测到域名: ${NGINX_DOMAIN}"
echo ""
print_warning "建议将CORS设置为: ${FIXED_CORS}"
echo ""
read -p "是否自动修复CORS配置[y/n]: " -n 1 -r < /dev/tty
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
# 备份原配置
cp .env .env.backup.$(date +%Y%m%d_%H%M%S)
# 修复CORS配置
sed -i "s|^ALLOWED_ORIGINS=.*|ALLOWED_ORIGINS=${FIXED_CORS}|" .env
print_success "✓ CORS配置已修复: ${FIXED_CORS}"
print_info "原配置已备份到: .env.backup.*"
else
print_warning "跳过自动修复,请手动编辑 .env 文件修改 ALLOWED_ORIGINS"
print_info "推荐值: ALLOWED_ORIGINS=${FIXED_CORS}"
fi
else
print_warning "无法自动修复请手动编辑backend/.env文件"
print_info "将 ALLOWED_ORIGINS=* 改为你的实际域名"
print_info "示例: ALLOWED_ORIGINS=https://yourdomain.com"
fi
else
print_warning "无法自动修复请手动编辑backend/.env文件"
print_info "将 ALLOWED_ORIGINS=* 改为你的实际域名"
print_info "示例: ALLOWED_ORIGINS=https://yourdomain.com"
fi
echo ""
elif [[ -z "$CURRENT_CORS" ]]; then
print_warning "⚠️ ALLOWED_ORIGINS未配置"
print_info "生产环境必须配置具体的域名"
else
print_success "✓ CORS配置安全: ${CURRENT_CORS}"
fi
# 检查 NODE_ENV
CURRENT_ENV=$(grep "^NODE_ENV=" .env | cut -d'=' -f2-)
if [[ "$CURRENT_ENV" != "production" ]]; then
print_warning "⚠️ 当前环境: ${CURRENT_ENV:-未设置}"
print_info "生产环境建议设置为: NODE_ENV=production"
else
print_success "✓ 环境配置: production"
fi
else
print_error "❌ .env 文件不存在!"
fi
echo ""
}
update_restart_services() {
print_step "重启服务..."
cd "${PROJECT_DIR}/backend"
if command -v pm2 &> /dev/null; then
pm2 restart ${PROJECT_NAME}-backend
pm2 save
print_success "后端服务已重启"
fi
# 重载Nginx兼容宝塔/自管Nginx
restart_nginx_safe || print_warning "Nginx重载失败请检查端口占用或手动重载"
echo ""
}
update_check_version() {
print_step "检查更新后的版本..."
# 检查package.json版本
if [[ -f "${PROJECT_DIR}/backend/package.json" ]]; then
VERSION=$(grep '"version"' "${PROJECT_DIR}/backend/package.json" | head -1 | awk -F'"' '{print $4}')
if [[ -n "$VERSION" ]]; then
print_success "当前版本: v$VERSION"
fi
fi
echo ""
}
print_update_completion() {
clear
echo -e "${GREEN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ 🎉 更新成功! ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
echo ""
echo -e "${CYAN}更新内容:${NC}"
echo " ✓ 代码已更新到最新版本"
echo " ✓ 依赖已更新"
echo " ✓ 服务已重启"
echo ""
echo -e "${CYAN}保留的数据:${NC}"
echo " ✓ 数据库(用户、分享链接等)"
echo " ✓ 用户文件storage目录"
echo " ✓ 配置文件(.env"
echo ""
echo -e "${YELLOW}常用命令:${NC}"
echo " 查看服务状态: pm2 status"
echo " 查看日志: pm2 logs ${PROJECT_NAME}-backend"
echo " 重启服务: pm2 restart ${PROJECT_NAME}-backend"
echo ""
echo -e "${GREEN}更新完成,祝您使用愉快!${NC}"
echo ""
}
update_patch_env() {
print_step "检查 .env 新增配置..."
if [[ -f "${PROJECT_DIR}/backend/.env" ]]; then
# 检查 ENFORCE_HTTPS
if ! grep -q "^ENFORCE_HTTPS=" "${PROJECT_DIR}/backend/.env"; then
# 基于现有配置做一个合理的默认值如果已启用安全Cookie或公开端口为443优先设为true
COOKIE_SECURE_CUR=$(grep "^COOKIE_SECURE=" "${PROJECT_DIR}/backend/.env" | cut -d'=' -f2- | tr '[:upper:]' '[:lower:]')
PUBLIC_PORT_CUR=$(grep "^PUBLIC_PORT=" "${PROJECT_DIR}/backend/.env" | cut -d'=' -f2-)
ALLOWED_CUR=$(grep "^ALLOWED_ORIGINS=" "${PROJECT_DIR}/backend/.env" | cut -d'=' -f2-)
ENFORCE_DEFAULT="false"
if [[ "$COOKIE_SECURE_CUR" == "true" ]] || [[ "$PUBLIC_PORT_CUR" == "443" ]]; then
ENFORCE_DEFAULT="true"
elif echo "$ALLOWED_CUR" | grep -qi "https://"; then
ENFORCE_DEFAULT="true"
fi
echo "ENFORCE_HTTPS=${ENFORCE_DEFAULT}" >> "${PROJECT_DIR}/backend/.env"
print_warning "已为现有 .env 补充 ENFORCE_HTTPS=${ENFORCE_DEFAULT}(如生产请确认设为 true 并重启)"
else
print_info ".env 已包含 ENFORCE_HTTPS保持不变"
fi
# 检查 TRUST_PROXY反向代理配置对于HTTPS强制模式非常重要
if ! grep -q "^TRUST_PROXY=" "${PROJECT_DIR}/backend/.env"; then
# 默认设置为1假设使用单层Nginx反向代理
echo "TRUST_PROXY=1" >> "${PROJECT_DIR}/backend/.env"
print_warning "已为现有 .env 补充 TRUST_PROXY=1Nginx反向代理场景必需"
else
print_info ".env 已包含 TRUST_PROXY保持不变"
fi
# 检查 SESSION_SECRET会话安全配置生产环境必需
if ! grep -q "^SESSION_SECRET=" "${PROJECT_DIR}/backend/.env"; then
# 自动生成随机 Session 密钥
NEW_SESSION_SECRET=$(openssl rand -hex 32)
echo "SESSION_SECRET=${NEW_SESSION_SECRET}" >> "${PROJECT_DIR}/backend/.env"
print_warning "已为现有 .env 补充 SESSION_SECRET已自动生成安全密钥"
else
print_info ".env 已包含 SESSION_SECRET保持不变"
fi
else
print_warning "未找到 ${PROJECT_DIR}/backend/.env请手动确认配置"
fi
echo ""
}
# 迁移旧数据库文件
update_migrate_database() {
print_step "检查数据库迁移..."
local OLD_DB="${PROJECT_DIR}/backend/data/database.db"
local OLD_DB_V2="${PROJECT_DIR}/backend/database.db"
# 如果旧数据库存在且新数据库不存在,执行迁移
if [[ -f "$OLD_DB_V2" ]]; then
if [[ ! -f "$OLD_DB" ]]; then
# 创建新目录
mkdir -p "${PROJECT_DIR}/backend/data"
# 迁移数据库
mv "$OLD_DB_V2" "$OLD_DB"
print_success "数据库已迁移: database.db -> data/database.db"
# 同时迁移 journal 文件(如果存在)
if [[ -f "${OLD_DB_V2}-journal" ]]; then
mv "${OLD_DB_V2}-journal" "${OLD_DB}-journal"
fi
if [[ -f "${OLD_DB_V2}-wal" ]]; then
mv "${OLD_DB_V2}-wal" "${OLD_DB}-wal"
fi
if [[ -f "${OLD_DB_V2}-shm" ]]; then
mv "${OLD_DB_V2}-shm" "${OLD_DB}-shm"
fi
else
# 新旧数据库都存在,警告用户
print_warning "检测到新旧数据库同时存在!"
print_warning " 旧: ${OLD_DB_V2}"
print_warning " 新: ${OLD_DB}"
print_warning "请手动确认数据后删除旧数据库文件"
fi
else
print_info "无需迁移数据库"
fi
echo ""
}
update_main() {
# 检查root权限
check_root
# 确认更新
confirm_update
# 检查项目
update_check_project
# 备份重要文件
update_backup_important_files
# 停止服务
update_stop_services
# 拉取最新代码
update_pull_latest_code
# 更新依赖
update_install_dependencies
# 迁移数据库配置
update_migrate_database
# 补充新配置项
update_patch_env
# 重启服务
update_restart_services
# 健康检查
if ! health_check; then
print_error "健康检查未通过,请检查日志"
print_info "查看日志: pm2 logs ${PROJECT_NAME}-backend"
exit 1
fi
# 检查版本
update_check_version
# 完成提示
print_update_completion
}
################################################################################
# 主流程
################################################################################
main() {
print_banner
# 检查root权限
check_root
# 如果没有通过命令行参数指定模式,则显示交互式选择
if [[ "$MODE" == "install" ]] && [[ "$1" != "--skip-mode-select" ]]; then
# 检测是否可以使用交互式输入
if [[ -t 0 ]] || [[ -c /dev/tty ]]; then
print_step "请选择操作模式"
echo ""
echo -e "${GREEN}[1]${NC} 安装/部署 玩玩云"
echo -e "${BLUE}[2]${NC} 更新/升级 玩玩云"
echo -e "${YELLOW}[3]${NC} 修复/重新配置 玩玩云"
echo -e "${PURPLE}[4]${NC} SSL证书管理安装/续签/更换证书)"
echo -e "${RED}[5]${NC} 卸载 玩玩云"
echo -e "${GRAY}[0]${NC} 退出脚本"
echo ""
while true; do
read -p "请输入选项 [0-5]: " mode_choice < /dev/tty
case $mode_choice in
1)
print_success "已选择: 安装模式"
echo ""
break
;;
2)
print_info "切换到更新模式..."
echo ""
update_main
exit 0
;;
3)
print_info "切换到修复模式..."
echo ""
repair_main
exit 0
;;
4)
print_info "切换到SSL证书管理模式..."
echo ""
ssl_main
exit 0
;;
5)
print_info "切换到卸载模式..."
echo ""
uninstall_main
exit 0
;;
0)
print_info "正在退出脚本..."
echo ""
exit 0
;;
*)
print_error "无效选项,请重新选择"
;;
esac
done
else
# 管道执行时的提示
print_info "检测到通过管道执行脚本"
print_info "默认进入安装模式"
print_warning "如需其他操作,请下载脚本后运行"
echo ""
echo -e "${YELLOW}提示:${NC}"
echo " 安装: wget https://git.workyai.cn/237899745/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh"
echo " 更新: wget https://git.workyai.cn/237899745/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh --update"
echo " 修复: wget https://git.workyai.cn/237899745/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh --repair"
echo " SSL管理: wget https://git.workyai.cn/237899745/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh --ssl"
echo " 卸载: wget https://git.workyai.cn/237899745/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh --uninstall"
echo ""
sleep 2
fi
fi
# 系统检测
system_check
# 选择软件源
choose_mirror
# 安装依赖
install_dependencies
# 选择访问模式
choose_access_mode
# 端口配置
configure_ports
# 配置管理员账号
configure_admin_account
# 创建项目目录
create_project_directory
# 下载项目
download_project
# 安装后端依赖
install_backend_dependencies
# 创建配置文件
create_env_file
# 创建数据目录
create_data_directories
# 先配置基础HTTP NginxSSL证书申请需要
configure_nginx_http_first
# 部署SSL证书需要HTTP server block进行验证
deploy_ssl
# 根据SSL结果配置最终Nginx
configure_nginx_final
# 启动后端服务
start_backend_service
# 健康检查
if ! health_check; then
print_error "健康检查未通过,请检查日志"
exit 1
fi
# 完成提示
print_completion
}
uninstall_main() {
# 检查root权限
check_root
# 确认卸载
confirm_uninstall
# 备份数据
uninstall_backup_data
# 停止PM2进程
uninstall_stop_pm2
# 删除Nginx配置
uninstall_nginx_config
# 清理SSL证书
uninstall_ssl_certificates
# 删除项目目录
uninstall_project_directory
# 清理临时文件
uninstall_temp_files
# 检查残留
uninstall_check_residual
# 完成提示
print_uninstall_completion
}
################################################################################
# 修复功能
################################################################################
print_repair_banner() {
clear
echo -e "${BLUE}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ 🔧 玩玩云 修复模式 ║"
echo "║ ║"
echo "║ Repair Mode ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
confirm_repair() {
print_repair_banner
echo -e "${YELLOW}"
echo "本脚本将执行以下操作:"
echo ""
echo "【将会重新配置】"
echo " ✓ 补充缺失的环境变量TRUST_PROXY、ENFORCE_HTTPS等"
echo " ✓ 重新生成Nginx配置应用最新配置"
echo " ✓ 重启后端服务"
echo " ✓ 重载Nginx服务"
echo ""
echo "【将会保留】"
echo " ✓ 数据库文件(用户数据)"
echo " ✓ 用户上传的文件"
echo " ✓ .env 配置文件(仅补充缺失项)"
echo " ✓ SSL证书"
echo -e "${NC}"
echo ""
print_info "适用场景: 更新配置、修复nginx配置错误、修复HTTPS访问问题、重新应用设置"
echo ""
read -p "确定要执行修复吗? (y/n): " confirm < /dev/tty
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
print_info "已取消修复"
exit 0
fi
echo ""
}
repair_check_project() {
print_step "检查项目是否已安装..."
if [[ ! -d "$PROJECT_DIR" ]]; then
print_error "项目未安装: $PROJECT_DIR"
print_info "请先运行安装命令: bash install.sh"
exit 1
fi
if [[ ! -f "${PROJECT_DIR}/backend/server.js" ]]; then
print_error "项目目录不完整"
exit 1
fi
print_success "项目已安装: $PROJECT_DIR"
echo ""
}
repair_load_existing_config() {
print_step "读取现有配置..."
# 从.env读取端口配置
if [[ -f "${PROJECT_DIR}/backend/.env" ]]; then
BACKEND_PORT=$(grep "^PORT=" "${PROJECT_DIR}/backend/.env" | cut -d'=' -f2 || echo "40001")
print_success "后端端口: $BACKEND_PORT"
else
print_warning ".env文件不存在使用默认端口40001"
BACKEND_PORT="40001"
fi
# 检查现有nginx配置兼容宝塔/标准)
local nginx_conf_path=""
if [[ -f "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" ]]; then
nginx_conf_path="/etc/nginx/sites-enabled/${PROJECT_NAME}.conf"
elif [[ -f "/www/server/panel/vhost/nginx/${PROJECT_NAME}.conf" ]]; then
nginx_conf_path="/www/server/panel/vhost/nginx/${PROJECT_NAME}.conf"
fi
if [[ -n "$nginx_conf_path" ]]; then
# 尝试从现有配置读取端口
EXISTING_HTTP_PORT=$(grep "listen" "$nginx_conf_path" | grep -v "ssl" | head -1 | awk '{print $2}' | tr -d ';' || echo "80")
HTTP_PORT=${EXISTING_HTTP_PORT:-80}
# 检查是否有HTTPS配置
if grep -q "listen.*ssl" "$nginx_conf_path"; then
EXISTING_HTTPS_PORT=$(grep "listen.*ssl" "$nginx_conf_path" | head -1 | awk '{print $2}' | tr -d ';' || echo "443")
HTTPS_PORT=${EXISTING_HTTPS_PORT:-443}
SSL_METHOD="existing"
print_success "检测到HTTPS配置端口: $HTTPS_PORT"
else
SSL_METHOD="8"
print_info "未检测到HTTPS配置"
fi
# 检查是否有域名
SERVER_NAME=$(grep "server_name" "$nginx_conf_path" | head -1 | awk '{print $2}' | tr -d ';' || echo "_")
if [[ "$SERVER_NAME" != "_" ]] && [[ "$SERVER_NAME" != "localhost" ]]; then
DOMAIN="$SERVER_NAME"
USE_DOMAIN=true
print_success "检测到域名: $DOMAIN"
else
USE_DOMAIN=false
print_info "使用IP模式"
fi
print_success "HTTP端口: $HTTP_PORT"
else
print_warning "未找到现有nginx配置将使用默认配置"
HTTP_PORT="80"
HTTPS_PORT="443"
SSL_METHOD="8"
USE_DOMAIN=false
fi
echo ""
}
repair_regenerate_nginx_config() {
print_step "重新生成Nginx配置..."
# 清理旧的备份文件避免nginx读取到错误配置
rm -f /etc/nginx/sites-enabled/${PROJECT_NAME}.conf.backup.* 2>/dev/null || true
rm -f /etc/nginx/sites-available/${PROJECT_NAME}.conf.backup.* 2>/dev/null || true
rm -f /www/server/panel/vhost/nginx/${PROJECT_NAME}.conf.backup.* 2>/dev/null || true
# 备份当前配置到 /root/
if [[ -f "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" ]]; then
cp "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" "/root/nginx-backup-${PROJECT_NAME}.conf.$(date +%Y%m%d%H%M%S)"
print_success "已备份现有配置到 /root/"
elif [[ -f "/www/server/panel/vhost/nginx/${PROJECT_NAME}.conf" ]]; then
cp "/www/server/panel/vhost/nginx/${PROJECT_NAME}.conf" "/root/nginx-backup-${PROJECT_NAME}.conf.$(date +%Y%m%d%H%M%S)"
print_success "已备份宝塔配置到 /root/"
fi
# 调用现有的configure_nginx函数
configure_nginx
print_success "Nginx配置已重新生成"
echo ""
}
repair_restart_services() {
print_step "重启服务..."
# 重启后端
if command -v pm2 &> /dev/null; then
if pm2 list | grep -q "${PROJECT_NAME}-backend"; then
pm2 restart ${PROJECT_NAME}-backend
print_success "后端服务已重启"
else
print_warning "后端服务未运行,尝试启动..."
cd "${PROJECT_DIR}/backend"
pm2 start server.js --name ${PROJECT_NAME}-backend
pm2 save
print_success "后端服务已启动"
fi
fi
# 重载Nginx兼容宝塔/自管Nginx
restart_nginx_safe || print_warning "Nginx未能启动/重载,请检查端口占用或手动重载"
echo ""
}
repair_verify_services() {
print_step "验证服务状态..."
# 等待服务启动
sleep 3
# 检查后端
if pm2 status | grep -q "${PROJECT_NAME}-backend.*online"; then
print_success "后端服务运行正常"
else
print_error "后端服务状态异常"
print_info "查看日志: pm2 logs ${PROJECT_NAME}-backend"
fi
# 检查Nginx
if systemctl is-active --quiet nginx 2>/dev/null || pgrep -x nginx > /dev/null || [[ -x /www/server/nginx/sbin/nginx ]] && pgrep -f "/www/server/nginx/sbin/nginx" > /dev/null; then
print_success "Nginx服务运行正常"
else
print_error "Nginx服务异常"
fi
# 检查端口
if netstat -tunlp 2>/dev/null | grep -q ":${BACKEND_PORT}" || ss -tunlp 2>/dev/null | grep -q ":${BACKEND_PORT}"; then
print_success "后端端口监听正常 (${BACKEND_PORT})"
else
print_warning "后端端口监听异常"
fi
echo ""
}
print_repair_completion() {
clear
echo -e "${GREEN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ ✓ 修复完成! ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
echo ""
echo -e "${CYAN}修复内容:${NC}"
echo " ✓ 环境配置已检查并补充缺失项TRUST_PROXY、ENFORCE_HTTPS等"
echo " ✓ Nginx配置已更新到最新版本"
echo " ✓ 服务已重启"
echo ""
echo -e "${CYAN}保留的数据:${NC}"
echo " ✓ 数据库(用户、分享链接等)"
echo " ✓ 用户文件storage目录"
echo " ✓ 配置文件(.env仅补充缺失项"
echo ""
# 显示访问地址
if [[ "$USE_DOMAIN" == "true" ]]; then
if [[ "$SSL_METHOD" == "8" ]] || [[ "$SSL_METHOD" == "" ]]; then
if [[ "$HTTP_PORT" == "80" ]]; then
echo -e "${CYAN}访问地址:${NC} http://${DOMAIN}"
else
echo -e "${CYAN}访问地址:${NC} http://${DOMAIN}:${HTTP_PORT}"
fi
else
if [[ "$HTTPS_PORT" == "443" ]]; then
echo -e "${CYAN}访问地址:${NC} https://${DOMAIN}"
else
echo -e "${CYAN}访问地址:${NC} https://${DOMAIN}:${HTTPS_PORT}"
fi
fi
else
PUBLIC_IP=$(curl -s ifconfig.me || curl -s icanhazip.com || echo "服务器IP")
if [[ "$HTTP_PORT" == "80" ]]; then
echo -e "${CYAN}访问地址:${NC} http://${PUBLIC_IP}"
else
echo -e "${CYAN}访问地址:${NC} http://${PUBLIC_IP}:${HTTP_PORT}"
fi
fi
echo ""
echo -e "${YELLOW}常用命令:${NC}"
echo " 查看服务状态: pm2 status"
echo " 查看日志: pm2 logs ${PROJECT_NAME}-backend"
echo " 重启服务: pm2 restart ${PROJECT_NAME}-backend"
echo ""
echo -e "${GREEN}修复完成,请测试功能是否正常!${NC}"
echo ""
}
repair_main() {
# 检查root权限
check_root
# 检测操作系统
detect_os
# 确认修复
confirm_repair
# 检查项目
repair_check_project
# 读取现有配置
repair_load_existing_config
# 迁移数据库(如果需要)
update_migrate_database
# 补充缺失的环境配置(如 TRUST_PROXY, ENFORCE_HTTPS 等)
update_patch_env
# 重新生成nginx配置
repair_regenerate_nginx_config
# 重启服务
repair_restart_services
# 验证服务
repair_verify_services
# 健康检查
if ! health_check; then
print_warning "部分健康检查未通过,请查看日志"
print_info "查看日志: pm2 logs ${PROJECT_NAME}-backend"
fi
# 完成提示
print_repair_completion
}
################################################################################
# SSL证书管理功能
################################################################################
print_ssl_banner() {
clear
echo -e "${GREEN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ 🔐 SSL证书管理模式 ║"
echo "║ ║"
echo "║ SSL Certificate Manager ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
confirm_ssl_operation() {
print_ssl_banner
echo -e "${YELLOW}"
echo "本脚本将执行以下操作:"
echo ""
echo "【SSL证书管理】"
echo " ✓ 检测现有域名配置"
echo " ✓ 选择SSL证书部署方案"
echo " ✓ 申请/更换/续签证书"
echo " ✓ 更新Nginx HTTPS配置"
echo " ✓ 重载服务"
echo ""
echo "【将会保留】"
echo " ✓ 数据库文件(用户数据)"
echo " ✓ 用户上传的文件"
echo " ✓ 后端配置文件(.env"
echo " ✓ 现有HTTP配置"
echo -e "${NC}"
echo ""
print_info "适用场景: 初次配置HTTPS、更换证书方案、证书续签"
echo ""
read -p "确定要继续吗? (y/n): " confirm < /dev/tty
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
print_info "已取消操作"
exit 0
fi
echo ""
}
ssl_check_project() {
print_step "检查项目是否已安装..."
if [[ ! -d "$PROJECT_DIR" ]]; then
print_error "项目未安装: $PROJECT_DIR"
print_info "请先运行安装命令: bash install.sh"
exit 1
fi
if [[ ! -f "${PROJECT_DIR}/backend/server.js" ]]; then
print_error "项目目录不完整"
exit 1
fi
print_success "项目已安装: $PROJECT_DIR"
echo ""
}
ssl_load_existing_config() {
print_step "读取现有配置..."
# 从.env读取后端端口
if [[ -f "${PROJECT_DIR}/backend/.env" ]]; then
BACKEND_PORT=$(grep "^PORT=" "${PROJECT_DIR}/backend/.env" | cut -d'=' -f2 || echo "40001")
print_success "后端端口: $BACKEND_PORT"
else
BACKEND_PORT="40001"
print_warning ".env文件不存在使用默认端口: $BACKEND_PORT"
fi
# 检查现有nginx配置
local nginx_conf=""
if [[ -f "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" ]]; then
nginx_conf="/etc/nginx/sites-enabled/${PROJECT_NAME}.conf"
elif [[ -f "/etc/nginx/conf.d/${PROJECT_NAME}.conf" ]]; then
nginx_conf="/etc/nginx/conf.d/${PROJECT_NAME}.conf"
elif [[ -f "/www/server/panel/vhost/nginx/${PROJECT_NAME}.conf" ]]; then
nginx_conf="/www/server/panel/vhost/nginx/${PROJECT_NAME}.conf"
fi
if [[ -n "$nginx_conf" ]]; then
# 读取HTTP端口
EXISTING_HTTP_PORT=$(grep "listen" "$nginx_conf" | grep -v "ssl" | grep -v "#" | head -1 | awk '{print $2}' | tr -d ';' || echo "80")
HTTP_PORT=${EXISTING_HTTP_PORT:-80}
# 检查是否有HTTPS配置
if grep -q "listen.*ssl" "$nginx_conf"; then
EXISTING_HTTPS_PORT=$(grep "listen.*ssl" "$nginx_conf" | head -1 | awk '{print $2}' | tr -d ';' || echo "443")
HTTPS_PORT=${EXISTING_HTTPS_PORT:-443}
print_info "检测到现有HTTPS配置端口: $HTTPS_PORT"
else
HTTPS_PORT="443"
print_info "未检测到HTTPS配置将使用默认端口: 443"
fi
# 读取域名
SERVER_NAME=$(grep "server_name" "$nginx_conf" | head -1 | awk '{print $2}' | tr -d ';' || echo "")
if [[ -n "$SERVER_NAME" ]] && [[ "$SERVER_NAME" != "_" ]] && [[ "$SERVER_NAME" != "localhost" ]]; then
DOMAIN="$SERVER_NAME"
USE_DOMAIN=true
print_success "检测到域名: $DOMAIN"
else
USE_DOMAIN=false
print_warning "未检测到域名配置"
fi
print_success "HTTP端口: $HTTP_PORT"
else
print_error "未找到Nginx配置文件"
exit 1
fi
echo ""
}
ssl_configure_domain() {
print_step "配置域名"
echo ""
# 如果已有域名,询问是否使用
if [[ "$USE_DOMAIN" == "true" ]] && [[ -n "$DOMAIN" ]]; then
print_info "检测到现有域名: $DOMAIN"
read -p "是否使用此域名? (y/n): " use_existing < /dev/tty
if [[ "$use_existing" == "y" || "$use_existing" == "Y" ]]; then
print_success "使用现有域名: $DOMAIN"
echo ""
return 0
fi
fi
# 输入新域名
while true; do
read -p "请输入您的域名 (例如: wwy.example.com): " DOMAIN < /dev/tty
if [[ -z "$DOMAIN" ]]; then
print_error "域名不能为空"
continue
fi
# 验证域名格式
if [[ ! "$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])?)*$ ]]; then
print_error "域名格式不正确"
continue
fi
# 验证域名解析
print_info "正在验证域名解析..."
DOMAIN_IP=$(dig +short "$DOMAIN" 2>/dev/null | tail -n1 || nslookup "$DOMAIN" 2>/dev/null | grep -A1 "Name:" | tail -1 | awk '{print $2}')
PUBLIC_IP=$(curl -s ifconfig.me || curl -s icanhazip.com || echo "")
if [[ -n "$DOMAIN_IP" ]] && [[ "$DOMAIN_IP" == "$PUBLIC_IP" ]]; then
print_success "域名已正确解析到当前服务器IP"
USE_DOMAIN=true
break
else
print_warning "域名未解析到当前服务器IP"
print_info "域名解析IP: ${DOMAIN_IP:-未解析}"
print_info "当前服务器IP: $PUBLIC_IP"
read -p "是否继续? (y/n): " continue_choice < /dev/tty
if [[ "$continue_choice" == "y" || "$continue_choice" == "Y" ]]; then
USE_DOMAIN=true
break
fi
fi
done
echo ""
}
ssl_choose_method() {
print_step "选择SSL证书部署方式"
echo ""
echo -e "${YELLOW}【推荐方案】${NC}"
echo -e "${GREEN}[1]${NC} Certbot (Let's Encrypt官方工具)"
echo " - 最稳定可靠,支持自动续期"
echo ""
echo -e "${YELLOW}【备选方案】${NC}"
echo -e "${GREEN}[2]${NC} acme.sh + Let's Encrypt"
echo " - 纯Shell脚本更轻量级"
echo -e "${GREEN}[3]${NC} acme.sh + ZeroSSL"
echo " - Let's Encrypt的免费替代品"
echo -e "${GREEN}[5]${NC} acme.sh + Buypass"
echo " - 挪威免费CA有效期180天"
echo ""
echo -e "${YELLOW}【云服务商证书】${NC}"
echo -e "${GREEN}[4]${NC} 阿里云免费证书 (需提供AccessKey)"
echo -e "${GREEN}[6]${NC} 腾讯云免费证书 (需提供SecretKey)"
echo ""
echo -e "${YELLOW}【其他选项】${NC}"
echo -e "${GREEN}[7]${NC} 使用已有证书 (手动上传)"
echo -e "${GREEN}[8]${NC} 移除HTTPS配置 (改回HTTP)"
echo -e "${GREEN}[0]${NC} 取消操作"
echo ""
while true; do
read -p "请输入选项 [0-8]: " ssl_choice < /dev/tty
case $ssl_choice in
1|2|3|4|5|6|7)
SSL_METHOD=$ssl_choice
break
;;
8)
SSL_METHOD=$ssl_choice
print_warning "将移除HTTPS配置改回HTTP模式"
read -p "确定要继续吗? (y/n): " confirm_remove < /dev/tty
if [[ "$confirm_remove" == "y" || "$confirm_remove" == "Y" ]]; then
break
fi
;;
0)
print_info "已取消操作"
exit 0
;;
*)
print_error "无效选项,请重新选择"
;;
esac
done
echo ""
}
ssl_deploy_certificate() {
print_step "部署SSL证书..."
# 如果选择移除HTTPS
if [[ "$SSL_METHOD" == "8" ]]; then
print_info "将移除HTTPS配置..."
# 配置为HTTP模式
configure_nginx_http
return 0
fi
# 部署证书
deploy_ssl
# 检查证书是否部署成功
if [[ -f "/etc/nginx/ssl/${DOMAIN}.crt" ]] && [[ -f "/etc/nginx/ssl/${DOMAIN}.key" ]]; then
print_success "证书文件已部署"
else
print_warning "证书文件未找到将使用HTTP配置"
SSL_METHOD="8"
fi
}
ssl_update_nginx_config() {
print_step "更新Nginx配置..."
if [[ "$SSL_METHOD" == "8" ]]; then
# HTTP配置
configure_nginx_http
else
# HTTPS配置
configure_nginx_https
fi
# 测试nginx配置
if ! nginx -t 2>&1; then
print_error "Nginx配置测试失败"
print_info "请检查配置文件"
return 1
fi
print_success "Nginx配置已更新"
echo ""
}
ssl_reload_services() {
print_step "重载服务..."
# 重载Nginx
if [[ -d /www/server/nginx ]]; then
# 宝塔面板
print_info "宝塔环境重载Nginx..."
if [[ -f /www/server/nginx/sbin/nginx ]]; then
/www/server/nginx/sbin/nginx -s reload 2>/dev/null
if [[ $? -eq 0 ]]; then
print_success "Nginx已重载"
else
/www/server/nginx/sbin/nginx 2>/dev/null
print_success "Nginx已启动"
fi
fi
systemctl reload nginx 2>/dev/null || true
else
# 标准Nginx
systemctl reload nginx
print_success "Nginx已重载"
fi
# 重启后端服务更新PUBLIC_PORT配置
if command -v pm2 &> /dev/null; then
if pm2 list | grep -q "${PROJECT_NAME}-backend"; then
pm2 restart ${PROJECT_NAME}-backend
print_success "后端服务已重启"
fi
fi
echo ""
}
ssl_verify_deployment() {
print_step "验证部署..."
# 检查Nginx
if [[ -d /www/server/nginx ]]; then
if pgrep -x nginx > /dev/null; then
print_success "Nginx运行正常"
else
print_error "Nginx未运行"
fi
else
if systemctl is-active --quiet nginx; then
print_success "Nginx运行正常"
else
print_error "Nginx未运行"
fi
fi
# 检查SSL证书
if [[ "$SSL_METHOD" != "8" ]]; then
if [[ -f "/etc/nginx/ssl/${DOMAIN}.crt" ]]; then
print_success "SSL证书已部署: /etc/nginx/ssl/${DOMAIN}.crt"
# 显示证书信息
CERT_EXPIRY=$(openssl x509 -enddate -noout -in "/etc/nginx/ssl/${DOMAIN}.crt" 2>/dev/null | cut -d= -f2)
if [[ -n "$CERT_EXPIRY" ]]; then
print_info "证书有效期至: $CERT_EXPIRY"
fi
else
print_warning "SSL证书文件未找到"
fi
fi
echo ""
}
print_ssl_completion() {
clear
echo -e "${GREEN}"
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ ✓ SSL配置完成 ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
echo ""
# 显示访问地址
if [[ "$SSL_METHOD" == "8" ]]; then
if [[ "$HTTP_PORT" == "80" ]]; then
echo -e "${CYAN}访问地址:${NC} http://${DOMAIN}"
else
echo -e "${CYAN}访问地址:${NC} http://${DOMAIN}:${HTTP_PORT}"
fi
echo -e "${YELLOW}模式:${NC} HTTP"
else
if [[ "$HTTPS_PORT" == "443" ]]; then
echo -e "${CYAN}访问地址:${NC} https://${DOMAIN}"
else
echo -e "${CYAN}访问地址:${NC} https://${DOMAIN}:${HTTPS_PORT}"
fi
echo -e "${YELLOW}模式:${NC} HTTPS"
# SSL信息
echo ""
echo -e "${YELLOW}SSL证书:${NC}"
case $SSL_METHOD in
1)
echo " 方案: Certbot (Let's Encrypt)"
echo " 续期: 自动续期已配置"
;;
2)
echo " 方案: acme.sh + Let's Encrypt"
echo " 续期: 自动续期已配置"
;;
3)
echo " 方案: acme.sh + ZeroSSL"
echo " 续期: 自动续期已配置"
;;
4)
echo " 方案: acme.sh + Buypass"
echo " 续期: 自动续期已配置"
;;
7)
echo " 方案: 手动上传证书"
echo " 续期: 需手动更新证书文件"
;;
esac
fi
echo ""
echo -e "${YELLOW}常用命令:${NC}"
echo " 查看证书信息: openssl x509 -text -noout -in /etc/nginx/ssl/${DOMAIN}.crt"
echo " 测试HTTPS: curl -I https://${DOMAIN}"
echo " 查看Nginx日志: tail -f /var/log/nginx/error.log"
echo " 重新配置SSL: bash install.sh --ssl"
echo ""
echo -e "${GREEN}SSL配置完成${NC}"
echo ""
}
ssl_main() {
# 检查root权限
check_root
# 检测操作系统
detect_os
# 确认操作
confirm_ssl_operation
# 检查项目
ssl_check_project
# 读取现有配置
ssl_load_existing_config
# 配置域名
if [[ "$USE_DOMAIN" != "true" ]] || [[ -z "$DOMAIN" ]]; then
ssl_configure_domain
fi
# 选择SSL方案
ssl_choose_method
# 先配置基础HTTP NginxSSL验证需要
configure_nginx_http_first
# 部署SSL证书
ssl_deploy_certificate
# 更新Nginx配置
ssl_update_nginx_config
# 重载服务
ssl_reload_services
# 验证部署
ssl_verify_deployment
# 完成提示
print_ssl_completion
}
# 执行主流程
if [[ "$MODE" == "uninstall" ]]; then
uninstall_main
elif [[ "$MODE" == "update" ]]; then
update_main
elif [[ "$MODE" == "repair" ]]; then
repair_main
elif [[ "$MODE" == "ssl" ]]; then
ssl_main
else
main
fi