问题: - Certbot使用--nginx插件时需要已存在的server block - 原执行顺序在Nginx配置前就部署SSL,导致找不到server block 改进: 1. 改用certonly --webroot模式(不自动修改Nginx配置) 2. 使用项目frontend目录作为webroot进行域名验证 3. 手动创建证书软链接到/etc/nginx/ssl/(与其他方法统一) 4. 配合新的两阶段Nginx部署流程: - 阶段1: configure_nginx_http_first() 先创建HTTP server block - 阶段2: deploy_ssl() 可以通过HTTP验证域名 - 阶段3: configure_nginx_final() 根据SSL结果配置HTTPS 修复错误: "Could not automatically find a matching server block for [domain]" 相关提交: - 7c4e1ed (重构Nginx配置为两阶段部署) -18512d9(增强acme.sh验证) -4a73a8c(Gitee镜像加速)
3463 lines
109 KiB
Bash
3463 lines
109 KiB
Bash
#!/bin/bash
|
||
|
||
################################################################################
|
||
# 玩玩云 (WanWanYun) - 一键部署/卸载/更新脚本
|
||
# 项目地址: https://gitee.com/yu-yon/vue-driven-cloud-storage
|
||
# 版本: v1.2.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"
|
||
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://gitee.com/yu-yon/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 gitee.com &> /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证书配置
|
||
################################################################################
|
||
|
||
choose_ssl_method() {
|
||
echo ""
|
||
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}[4]${NC} acme.sh + Buypass"
|
||
echo " - 挪威免费CA,有效期180天"
|
||
echo ""
|
||
echo -e "${YELLOW}【云服务商证书】${NC}"
|
||
echo -e "${GREEN}[5]${NC} 阿里云免费证书 (需提供AccessKey)"
|
||
echo -e "${GREEN}[6]${NC} 腾讯云免费证书 (需提供SecretKey)"
|
||
echo ""
|
||
echo -e "${YELLOW}【其他选项】${NC}"
|
||
echo -e "${GREEN}[7]${NC} 使用已有证书 (手动上传)"
|
||
echo -e "${GREEN}[8]${NC} 暂不配置HTTPS (可后续配置)"
|
||
echo ""
|
||
|
||
while true; do
|
||
read -p "请输入选项 [1-8]: " ssl_choice < /dev/tty
|
||
case $ssl_choice in
|
||
1|2|3|4|5|6|7|8)
|
||
SSL_METHOD=$ssl_choice
|
||
break
|
||
;;
|
||
*)
|
||
print_error "无效选项,请重新选择"
|
||
;;
|
||
esac
|
||
done
|
||
echo ""
|
||
}
|
||
|
||
deploy_ssl() {
|
||
if [[ "$USE_DOMAIN" != "true" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
case $SSL_METHOD in
|
||
1)
|
||
deploy_certbot || ssl_fallback "1"
|
||
;;
|
||
2)
|
||
deploy_acme_letsencrypt || ssl_fallback "2"
|
||
;;
|
||
3)
|
||
deploy_acme_zerossl || ssl_fallback "3"
|
||
;;
|
||
4)
|
||
deploy_acme_buypass || ssl_fallback "4"
|
||
;;
|
||
5)
|
||
deploy_aliyun_ssl || 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=()
|
||
|
||
# 方案1: Certbot
|
||
if [[ "$failed_method" != "1" ]]; then
|
||
echo -e "${GREEN}[1]${NC} Certbot (Let's Encrypt官方工具)"
|
||
available_options+=("1")
|
||
fi
|
||
|
||
# 方案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
|
||
|
||
# 方案4: acme.sh + Buypass
|
||
if [[ "$failed_method" != "4" ]]; then
|
||
echo -e "${GREEN}[4]${NC} acme.sh + Buypass"
|
||
available_options+=("4")
|
||
fi
|
||
|
||
# 方案5: 阿里云(注释掉,未实现)
|
||
# if [[ "$failed_method" != "5" ]]; then
|
||
# echo -e "${GREEN}[5]${NC} 阿里云免费证书"
|
||
# available_options+=("5")
|
||
# fi
|
||
|
||
# 方案6: 腾讯云(注释掉,未实现)
|
||
# if [[ "$failed_method" != "6" ]]; then
|
||
# echo -e "${GREEN}[6]${NC} 腾讯云免费证书"
|
||
# available_options+=("6")
|
||
# 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
|
||
1)
|
||
deploy_certbot && return 0
|
||
# 如果再次失败,继续调用fallback但排除方案1
|
||
ssl_fallback "1"
|
||
return $?
|
||
;;
|
||
2)
|
||
deploy_acme_letsencrypt && return 0
|
||
# 如果再次失败,继续调用fallback但排除方案2
|
||
ssl_fallback "2"
|
||
return $?
|
||
;;
|
||
3)
|
||
deploy_acme_zerossl && return 0
|
||
ssl_fallback "3"
|
||
return $?
|
||
;;
|
||
4)
|
||
deploy_acme_buypass && return 0
|
||
ssl_fallback "4"
|
||
return $?
|
||
;;
|
||
8)
|
||
print_info "跳过HTTPS配置"
|
||
SSL_METHOD=8
|
||
return 0
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
deploy_certbot() {
|
||
print_step "使用 Certbot 部署SSL证书..."
|
||
|
||
# 安装certbot
|
||
case $PKG_MANAGER in
|
||
apt)
|
||
apt-get install -y certbot python3-certbot-nginx
|
||
;;
|
||
yum)
|
||
yum install -y certbot python3-certbot-nginx
|
||
;;
|
||
dnf)
|
||
dnf install -y certbot python3-certbot-nginx
|
||
;;
|
||
zypper)
|
||
zypper install -y certbot python3-certbot-nginx
|
||
;;
|
||
esac
|
||
|
||
# 申请证书(使用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
|
||
print_error "Certbot SSL证书申请失败"
|
||
echo ""
|
||
print_warning "常见失败原因:"
|
||
echo " 1. 域名未正确解析到此服务器"
|
||
echo " 2. 防火墙阻止了80端口"
|
||
echo " 3. Nginx未正确配置或未启动"
|
||
echo " 4. Let's Encrypt速率限制"
|
||
echo ""
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
deploy_acme_letsencrypt() {
|
||
print_step "使用 acme.sh + Let's Encrypt 部署SSL证书..."
|
||
|
||
# 安装acme.sh
|
||
if [[ ! -d ~/.acme.sh ]]; then
|
||
echo ""
|
||
print_info "正在安装 acme.sh..."
|
||
|
||
# 检测是否在中国大陆,使用镜像加速
|
||
if curl -s --connect-timeout 3 https://www.google.com > /dev/null 2>&1; then
|
||
# 海外网络 - 使用官方源
|
||
if curl -fsSL https://get.acme.sh | sh; then
|
||
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
|
||
print_success "acme.sh 安装成功"
|
||
else
|
||
print_error "acme.sh 安装失败"
|
||
return 1
|
||
fi
|
||
else
|
||
# 中国大陆 - 使用Gitee镜像(官方方法)
|
||
print_info "检测到国内网络,使用Gitee镜像加速..."
|
||
|
||
# 设置环境变量,让acme.sh使用Gitee源
|
||
export ACME_USE_GITEE=1
|
||
|
||
if curl -fsSL https://gitee.com/neilpang/acme.sh/raw/master/acme.sh | sh; then
|
||
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
|
||
print_success "acme.sh 安装成功"
|
||
else
|
||
print_error "acme.sh 安装失败"
|
||
echo ""
|
||
print_warning "解决方案:"
|
||
echo " 1. 检查网络连接"
|
||
echo " 2. 尝试手动安装: export ACME_USE_GITEE=1 && curl https://gitee.com/neilpang/acme.sh/raw/master/acme.sh | sh"
|
||
echo " 3. 或访问: https://github.com/acmesh-official/acme.sh/wiki/Install-in-China"
|
||
echo ""
|
||
return 1
|
||
fi
|
||
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 证书..."
|
||
if ~/.acme.sh/acme.sh --issue -d "$DOMAIN" --nginx; then
|
||
print_success "证书申请成功"
|
||
else
|
||
print_error "证书申请失败"
|
||
echo ""
|
||
print_warning "常见失败原因:"
|
||
echo " 1. 域名未正确解析到此服务器"
|
||
echo " 2. Nginx未正确配置"
|
||
echo " 3. 80端口被占用或防火墙阻止"
|
||
echo ""
|
||
return 1
|
||
fi
|
||
|
||
# 安装证书
|
||
echo ""
|
||
print_info "正在安装证书到Nginx..."
|
||
mkdir -p /etc/nginx/ssl
|
||
if ~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
|
||
--key-file /etc/nginx/ssl/${DOMAIN}.key \
|
||
--fullchain-file /etc/nginx/ssl/${DOMAIN}.crt \
|
||
--reloadcmd "systemctl reload nginx"; then
|
||
print_success "证书安装成功"
|
||
return 0
|
||
else
|
||
print_error "证书安装失败"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
deploy_acme_zerossl() {
|
||
print_step "使用 acme.sh + ZeroSSL 部署SSL证书..."
|
||
|
||
# 安装acme.sh(使用与Let's Encrypt相同的逻辑)
|
||
if [[ ! -d ~/.acme.sh ]]; then
|
||
echo ""
|
||
print_info "正在安装 acme.sh..."
|
||
|
||
# 检测网络环境
|
||
if curl -s --connect-timeout 3 https://www.google.com > /dev/null 2>&1; then
|
||
# 海外网络
|
||
if curl -fsSL https://get.acme.sh | sh; then
|
||
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
|
||
print_success "acme.sh 安装成功"
|
||
else
|
||
print_error "acme.sh 安装失败"
|
||
return 1
|
||
fi
|
||
else
|
||
# 中国大陆 - 使用Gitee镜像
|
||
print_info "检测到国内网络,使用Gitee镜像加速..."
|
||
export ACME_USE_GITEE=1
|
||
if curl -fsSL https://gitee.com/neilpang/acme.sh/raw/master/acme.sh | sh; then
|
||
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
|
||
print_success "acme.sh 安装成功"
|
||
else
|
||
print_error "acme.sh 安装失败"
|
||
return 1
|
||
fi
|
||
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 证书..."
|
||
if ~/.acme.sh/acme.sh --server zerossl --issue -d "$DOMAIN" --nginx; then
|
||
print_success "证书申请成功"
|
||
else
|
||
print_error "证书申请失败"
|
||
return 1
|
||
fi
|
||
|
||
# 安装证书
|
||
echo ""
|
||
print_info "正在安装证书到Nginx..."
|
||
mkdir -p /etc/nginx/ssl
|
||
if ~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
|
||
--key-file /etc/nginx/ssl/${DOMAIN}.key \
|
||
--fullchain-file /etc/nginx/ssl/${DOMAIN}.crt \
|
||
--reloadcmd "systemctl reload nginx"; then
|
||
print_success "证书安装成功"
|
||
return 0
|
||
else
|
||
print_error "证书安装失败"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
deploy_acme_buypass() {
|
||
print_step "使用 acme.sh + Buypass 部署SSL证书..."
|
||
|
||
# 安装acme.sh
|
||
if [[ ! -d ~/.acme.sh ]]; then
|
||
echo ""
|
||
print_info "正在安装 acme.sh..."
|
||
|
||
# 检测网络环境
|
||
if curl -s --connect-timeout 3 https://www.google.com > /dev/null 2>&1; then
|
||
# 海外网络
|
||
if curl -fsSL https://get.acme.sh | sh; then
|
||
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
|
||
print_success "acme.sh 安装成功"
|
||
else
|
||
print_error "acme.sh 安装失败"
|
||
return 1
|
||
fi
|
||
else
|
||
# 中国大陆 - 使用Gitee镜像
|
||
print_info "检测到国内网络,使用Gitee镜像加速..."
|
||
export ACME_USE_GITEE=1
|
||
if curl -fsSL https://gitee.com/neilpang/acme.sh/raw/master/acme.sh | sh; then
|
||
source ~/.bashrc 2>/dev/null || source ~/.profile 2>/dev/null || true
|
||
print_success "acme.sh 安装成功"
|
||
else
|
||
print_error "acme.sh 安装失败"
|
||
return 1
|
||
fi
|
||
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 证书..."
|
||
if ~/.acme.sh/acme.sh --server buypass --issue -d "$DOMAIN" --nginx; then
|
||
print_success "证书申请成功"
|
||
else
|
||
print_error "证书申请失败"
|
||
return 1
|
||
fi
|
||
|
||
# 安装证书
|
||
echo ""
|
||
print_info "正在安装证书到Nginx..."
|
||
mkdir -p /etc/nginx/ssl
|
||
if ~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
|
||
--key-file /etc/nginx/ssl/${DOMAIN}.key \
|
||
--fullchain-file /etc/nginx/ssl/${DOMAIN}.crt \
|
||
--reloadcmd "systemctl reload nginx"; then
|
||
print_success "证书安装成功"
|
||
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 "正在从Gitee下载项目..."
|
||
|
||
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)
|
||
|
||
cat > "${PROJECT_DIR}/backend/.env" << EOF
|
||
# 管理员账号
|
||
ADMIN_USERNAME=${ADMIN_USERNAME}
|
||
ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||
|
||
# JWT密钥
|
||
JWT_SECRET=${JWT_SECRET}
|
||
|
||
# 数据库路径
|
||
DATABASE_PATH=./data/database.db
|
||
|
||
# 存储目录
|
||
STORAGE_ROOT=./storage
|
||
|
||
# 服务端口
|
||
PORT=${BACKEND_PORT}
|
||
|
||
# 环境
|
||
NODE_ENV=production
|
||
|
||
# 公开端口(nginx监听的端口,用于生成分享链接)
|
||
# 如果使用标准端口(80/443)或未配置,分享链接将不包含端口号
|
||
PUBLIC_PORT=${HTTP_PORT}
|
||
EOF
|
||
|
||
print_success "配置文件创建完成"
|
||
echo ""
|
||
}
|
||
|
||
create_data_directories() {
|
||
print_step "创建数据目录..."
|
||
|
||
mkdir -p "${PROJECT_DIR}/backend/data"
|
||
mkdir -p "${PROJECT_DIR}/backend/storage"
|
||
|
||
print_success "数据目录创建完成"
|
||
echo ""
|
||
}
|
||
|
||
build_upload_tool() {
|
||
print_step "下载上传工具..."
|
||
|
||
cd "${PROJECT_DIR}/upload-tool"
|
||
|
||
# 检查是否已存在可执行文件并验证大小
|
||
if [[ -f "dist/玩玩云上传工具.exe" ]]; then
|
||
FILE_SIZE=$(stat -f%z "dist/玩玩云上传工具.exe" 2>/dev/null || stat -c%s "dist/玩玩云上传工具.exe" 2>/dev/null || echo "0")
|
||
if [[ $FILE_SIZE -gt 30000000 ]]; then
|
||
FILE_SIZE_MB=$(( FILE_SIZE / 1024 / 1024 ))
|
||
print_success "上传工具已存在(${FILE_SIZE_MB}MB),跳过下载"
|
||
echo ""
|
||
return 0
|
||
else
|
||
print_warning "现有文件大小异常(${FILE_SIZE}字节),重新下载..."
|
||
rm -f "dist/玩玩云上传工具.exe"
|
||
fi
|
||
fi
|
||
|
||
# 创建dist目录
|
||
mkdir -p dist
|
||
|
||
# 下载地址(Windows版本)
|
||
TOOL_DOWNLOAD_URL="http://a.haory.top/e/e82/玩玩云上传工具.exe"
|
||
TOOL_FILENAME="玩玩云上传工具.exe"
|
||
|
||
print_info "正在下载上传工具(约43MB,可能需要1-2分钟)..."
|
||
|
||
# 尝试下载(最多3次重试)
|
||
DOWNLOAD_SUCCESS=false
|
||
for attempt in 1 2 3; do
|
||
print_info "尝试下载 ($attempt/3)..."
|
||
|
||
if command -v wget &> /dev/null; then
|
||
# wget: 超时300秒,重试3次
|
||
if wget --timeout=300 --tries=3 --no-check-certificate -q --show-progress -O "dist/${TOOL_FILENAME}" "$TOOL_DOWNLOAD_URL" 2>&1; then
|
||
DOWNLOAD_SUCCESS=true
|
||
break
|
||
fi
|
||
elif command -v curl &> /dev/null; then
|
||
# curl: 连接超时60秒,总超时300秒
|
||
if curl --connect-timeout 60 --max-time 300 -L -# -o "dist/${TOOL_FILENAME}" "$TOOL_DOWNLOAD_URL" 2>&1; then
|
||
DOWNLOAD_SUCCESS=true
|
||
break
|
||
fi
|
||
else
|
||
print_warning "未找到wget或curl,无法下载上传工具"
|
||
print_info "用户仍可使用网页上传(本地存储)或SFTP客户端"
|
||
echo ""
|
||
return 0
|
||
fi
|
||
|
||
# 如果不是最后一次尝试,等待后重试
|
||
if [[ $attempt -lt 3 ]]; then
|
||
print_warning "下载失败,5秒后重试..."
|
||
sleep 5
|
||
fi
|
||
done
|
||
|
||
# 验证下载结果
|
||
if [[ "$DOWNLOAD_SUCCESS" == "true" ]] && [[ -f "dist/${TOOL_FILENAME}" ]]; then
|
||
FILE_SIZE=$(stat -f%z "dist/${TOOL_FILENAME}" 2>/dev/null || stat -c%s "dist/${TOOL_FILENAME}" 2>/dev/null || echo "0")
|
||
FILE_SIZE_MB=$(( FILE_SIZE / 1024 / 1024 ))
|
||
|
||
if [[ $FILE_SIZE -gt 30000000 ]]; then
|
||
print_success "上传工具下载完成: ${FILE_SIZE_MB}MB"
|
||
echo ""
|
||
else
|
||
print_error "下载的文件大小异常(${FILE_SIZE}字节),可能下载不完整"
|
||
rm -f "dist/${TOOL_FILENAME}"
|
||
print_warning "可手动下载: ${TOOL_DOWNLOAD_URL}"
|
||
print_info "用户仍可使用网页上传(本地存储)或SFTP客户端"
|
||
echo ""
|
||
fi
|
||
else
|
||
print_error "上传工具下载失败(已重试3次)"
|
||
print_warning "可能的原因:"
|
||
echo " 1. 网络连接问题或下载速度过慢"
|
||
echo " 2. CDN链接不可访问: ${TOOL_DOWNLOAD_URL}"
|
||
echo " 3. 防火墙拦截HTTP连接"
|
||
print_info "您可以稍后手动下载并放置到: ${PROJECT_DIR}/upload-tool/dist/"
|
||
print_info "用户仍可使用网页上传(本地存储)或SFTP客户端"
|
||
echo ""
|
||
fi
|
||
}
|
||
|
||
################################################################################
|
||
# Nginx配置 - 分步骤执行
|
||
################################################################################
|
||
|
||
# 步骤1: 先配置HTTP Nginx(为SSL证书验证做准备)
|
||
configure_nginx_http_first() {
|
||
print_step "配置基础HTTP Nginx(用于SSL证书验证)..."
|
||
|
||
# 总是先配置HTTP模式
|
||
local server_name="${DOMAIN:-_}"
|
||
|
||
cat > /etc/nginx/sites-available/${PROJECT_NAME}.conf << EOF
|
||
server {
|
||
listen ${HTTP_PORT};
|
||
server_name ${server_name};
|
||
|
||
# 文件上传大小限制(10GB)
|
||
client_max_body_size 10G;
|
||
|
||
# 前端静态文件
|
||
location / {
|
||
root ${PROJECT_DIR}/frontend;
|
||
index app.html;
|
||
try_files \$uri \$uri/ /app.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;
|
||
|
||
# 上传超时设置
|
||
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;
|
||
}
|
||
|
||
# 上传工具下载
|
||
location /download-tool {
|
||
alias ${PROJECT_DIR}/upload-tool/dist;
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# 创建软链接
|
||
ln -sf /etc/nginx/sites-available/${PROJECT_NAME}.conf /etc/nginx/sites-enabled/${PROJECT_NAME}.conf
|
||
|
||
# 删除默认站点
|
||
rm -f /etc/nginx/sites-enabled/default
|
||
|
||
# 测试nginx配置
|
||
if ! nginx -t; then
|
||
print_error "Nginx配置测试失败"
|
||
return 1
|
||
fi
|
||
|
||
# 启动Nginx
|
||
systemctl restart nginx
|
||
if ! systemctl is-active --quiet nginx; then
|
||
print_error "Nginx启动失败"
|
||
return 1
|
||
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
|
||
systemctl reload nginx
|
||
|
||
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
|
||
systemctl restart nginx
|
||
|
||
print_success "Nginx配置完成"
|
||
echo ""
|
||
}
|
||
|
||
configure_nginx_http() {
|
||
local server_name="${DOMAIN:-_}"
|
||
|
||
cat > /etc/nginx/sites-available/${PROJECT_NAME}.conf << EOF
|
||
server {
|
||
listen ${HTTP_PORT};
|
||
server_name ${server_name};
|
||
|
||
# 文件上传大小限制(10GB)
|
||
client_max_body_size 10G;
|
||
|
||
# 前端静态文件
|
||
location / {
|
||
root ${PROJECT_DIR}/frontend;
|
||
index app.html;
|
||
try_files \$uri \$uri/ /app.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;
|
||
|
||
# 上传超时设置(大文件上传需要更长时间,设置为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;
|
||
}
|
||
|
||
# 上传工具下载
|
||
location /download-tool {
|
||
alias ${PROJECT_DIR}/upload-tool/dist;
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# 创建软链接
|
||
ln -sf /etc/nginx/sites-available/${PROJECT_NAME}.conf /etc/nginx/sites-enabled/${PROJECT_NAME}.conf
|
||
|
||
# 删除默认站点
|
||
rm -f /etc/nginx/sites-enabled/default
|
||
}
|
||
|
||
configure_nginx_https() {
|
||
cat > /etc/nginx/sites-available/${PROJECT_NAME}.conf << EOF
|
||
server {
|
||
listen ${HTTP_PORT};
|
||
server_name ${DOMAIN};
|
||
return 301 https://\$server_name:\${HTTPS_PORT}\$request_uri;
|
||
}
|
||
|
||
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;
|
||
|
||
# 前端静态文件
|
||
location / {
|
||
root ${PROJECT_DIR}/frontend;
|
||
index app.html;
|
||
try_files \$uri \$uri/ /app.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;
|
||
|
||
# 上传超时设置(大文件上传需要更长时间,设置为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;
|
||
}
|
||
|
||
# 上传工具下载
|
||
location /download-tool {
|
||
alias ${PROJECT_DIR}/upload-tool/dist;
|
||
}
|
||
}
|
||
EOF
|
||
|
||
# 创建软链接
|
||
ln -sf /etc/nginx/sites-available/${PROJECT_NAME}.conf /etc/nginx/sites-enabled/${PROJECT_NAME}.conf
|
||
|
||
# 删除默认站点
|
||
rm -f /etc/nginx/sites-enabled/default
|
||
}
|
||
|
||
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 | grep -q ":${BACKEND_PORT}"; then
|
||
print_success "后端端口监听正常 (${BACKEND_PORT})"
|
||
else
|
||
print_error "后端端口监听异常"
|
||
return 1
|
||
fi
|
||
|
||
# 检查Nginx
|
||
if systemctl is-active --quiet nginx; then
|
||
print_success "Nginx服务运行正常"
|
||
else
|
||
print_error "Nginx服务异常"
|
||
return 1
|
||
fi
|
||
|
||
# 检查数据库
|
||
if [[ -f "${PROJECT_DIR}/backend/ftp-manager.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}"
|
||
case $SSL_METHOD in
|
||
1)
|
||
echo " 自动续期: 已配置Certbot自动续期"
|
||
;;
|
||
2|3|4)
|
||
echo " 自动续期: 已配置acme.sh自动续期"
|
||
;;
|
||
esac
|
||
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 " ✓ 从Gitee拉取最新代码"
|
||
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 "正在从Gitee拉取最新代码..."
|
||
|
||
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
|
||
|
||
# 更新上传工具
|
||
if [[ -d "/tmp/${PROJECT_NAME}-update/upload-tool" ]]; then
|
||
rm -rf "${PROJECT_DIR}/upload-tool"
|
||
cp -r "/tmp/${PROJECT_NAME}-update/upload-tool" "${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 "ftp-manager.db" ]]; then
|
||
CURRENT_LIMIT=$(sqlite3 ftp-manager.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 ftp-manager.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
|
||
|
||
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
|
||
if systemctl is-active --quiet nginx; then
|
||
systemctl reload nginx
|
||
print_success "Nginx已重载"
|
||
fi
|
||
|
||
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_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_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 "${RED}[4]${NC} 卸载 玩玩云"
|
||
echo -e "${GRAY}[0]${NC} 退出脚本"
|
||
echo ""
|
||
|
||
while true; do
|
||
read -p "请输入选项 [0-4]: " 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 "切换到卸载模式..."
|
||
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://gitee.com/yu-yon/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh"
|
||
echo " 更新: wget https://gitee.com/yu-yon/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh --update"
|
||
echo " 卸载: wget https://gitee.com/yu-yon/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh --uninstall"
|
||
echo " 修复: wget https://gitee.com/yu-yon/vue-driven-cloud-storage/raw/master/install.sh && bash install.sh --repair"
|
||
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
|
||
|
||
# 打包上传工具
|
||
build_upload_tool
|
||
|
||
# 先配置基础HTTP Nginx(SSL证书申请需要)
|
||
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 " ✓ 重新生成Nginx配置(应用最新配置)"
|
||
echo " ✓ 重启后端服务"
|
||
echo " ✓ 重载Nginx服务"
|
||
echo ""
|
||
echo "【将会保留】"
|
||
echo " ✓ 数据库文件(用户数据)"
|
||
echo " ✓ 用户上传的文件"
|
||
echo " ✓ .env 配置文件"
|
||
echo " ✓ SSL证书"
|
||
echo -e "${NC}"
|
||
echo ""
|
||
|
||
print_info "适用场景: 更新配置、修复nginx配置错误、重新应用设置"
|
||
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配置
|
||
if [[ -f "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" ]]; then
|
||
# 尝试从现有配置读取端口
|
||
EXISTING_HTTP_PORT=$(grep "listen" "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" | grep -v "ssl" | head -1 | awk '{print $2}' | tr -d ';' || echo "80")
|
||
HTTP_PORT=${EXISTING_HTTP_PORT:-80}
|
||
|
||
# 检查是否有HTTPS配置
|
||
if grep -q "listen.*ssl" "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf"; then
|
||
EXISTING_HTTPS_PORT=$(grep "listen.*ssl" "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" | 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" "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" | 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
|
||
|
||
# 备份当前配置到 /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/"
|
||
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
|
||
if systemctl is-active --quiet nginx; then
|
||
systemctl reload nginx
|
||
print_success "Nginx已重载"
|
||
else
|
||
print_warning "Nginx未运行,尝试启动..."
|
||
systemctl start nginx
|
||
print_success "Nginx已启动"
|
||
fi
|
||
|
||
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; 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 " ✓ 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
|
||
|
||
# 重新生成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
|
||
}
|
||
# 执行主流程
|
||
if [[ "$MODE" == "uninstall" ]]; then
|
||
uninstall_main
|
||
elif [[ "$MODE" == "update" ]]; then
|
||
update_main
|
||
elif [[ "$MODE" == "repair" ]]; then
|
||
repair_main
|
||
else
|
||
main
|
||
fi
|
||
|