Files
vue-driven-cloud-storage/install.sh

2518 lines
76 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
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://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
}
################################################################################
# 安装依赖环境
################################################################################
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 "依赖环境安装完成"
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 # 端口可用
}
configure_ports() {
print_step "端口配置"
echo ""
# 检测80端口
if ! check_port_available 80; then
print_warning "检测到 80 端口已被占用"
echo ""
echo "80端口被其他服务占用您可以:"
echo -e "${GREEN}[1]${NC} 使用其他HTTP端口 (例如: 8080, 8888)"
echo -e "${GREEN}[2]${NC} 停止占用80端口的服务"
echo ""
read -p "请选择 [1-2]: " port_choice < /dev/tty
if [[ "$port_choice" == "1" ]]; 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
else
print_info "请手动停止占用80端口的服务后重新运行此脚本"
echo ""
print_info "查看端口占用: netstat -tunlp | grep :80"
print_info "或者: ss -tunlp | grep :80"
exit 1
fi
else
print_success "80 端口可用"
fi
# 检测443端口仅在使用HTTPS时需要
if [[ "$USE_DOMAIN" == "true" ]] && [[ "$SSL_METHOD" != "8" ]]; then
if ! check_port_available 443; then
print_warning "检测到 443 端口已被占用"
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
else
print_success "443 端口可用"
fi
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"
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
;;
2)
deploy_acme_letsencrypt || ssl_fallback
;;
3)
deploy_acme_zerossl || ssl_fallback
;;
4)
deploy_acme_buypass || ssl_fallback
;;
5)
deploy_aliyun_ssl || ssl_fallback
;;
6)
deploy_tencent_ssl || ssl_fallback
;;
7)
deploy_manual_ssl
;;
8)
print_info "跳过HTTPS配置"
return 0
;;
esac
}
ssl_fallback() {
print_error "SSL证书部署失败"
echo ""
print_warning "建议尝试备选方案:"
echo -e "${GREEN}[2]${NC} acme.sh + Let's Encrypt (推荐)"
echo -e "${GREEN}[3]${NC} acme.sh + ZeroSSL"
echo -e "${GREEN}[4]${NC} acme.sh + Buypass"
echo -e "${GREEN}[8]${NC} 暂不配置HTTPS"
echo ""
while true; do
read -p "请选择备选方案 [2-4/8]: " retry_choice < /dev/tty
case $retry_choice in
2)
deploy_acme_letsencrypt && return 0
;;
3)
deploy_acme_zerossl && return 0
;;
4)
deploy_acme_buypass && return 0
;;
8)
print_info "跳过HTTPS配置"
SSL_METHOD=8
return 0
;;
*)
print_error "无效选项"
;;
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
# 申请证书
certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos --email "admin@${DOMAIN}" --redirect
# 配置自动续期
systemctl enable certbot.timer
print_success "Certbot SSL证书部署成功"
return 0
}
deploy_acme_letsencrypt() {
print_step "使用 acme.sh + Let's Encrypt 部署SSL证书..."
# 安装acme.sh
if [[ ! -d ~/.acme.sh ]]; then
curl https://get.acme.sh | sh
fi
# 申请证书
~/.acme.sh/acme.sh --issue -d "$DOMAIN" --nginx
# 安装证书
mkdir -p /etc/nginx/ssl
~/.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"
print_success "acme.sh SSL证书部署成功"
return 0
}
deploy_acme_zerossl() {
print_step "使用 acme.sh + ZeroSSL 部署SSL证书..."
# 安装acme.sh
if [[ ! -d ~/.acme.sh ]]; then
curl https://get.acme.sh | sh
fi
# 申请证书
~/.acme.sh/acme.sh --server zerossl --issue -d "$DOMAIN" --nginx
# 安装证书
mkdir -p /etc/nginx/ssl
~/.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"
print_success "ZeroSSL证书部署成功"
return 0
}
deploy_acme_buypass() {
print_step "使用 acme.sh + Buypass 部署SSL证书..."
# 安装acme.sh
if [[ ! -d ~/.acme.sh ]]; then
curl https://get.acme.sh | sh
fi
# 申请证书
~/.acme.sh/acme.sh --server buypass --issue -d "$DOMAIN" --nginx
# 安装证书
mkdir -p /etc/nginx/ssl
~/.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"
print_success "Buypass SSL证书部署成功"
return 0
}
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"
# 使用国内镜像加速
if [[ "$USE_ALIYUN_MIRROR" == "true" ]]; then
npm config set registry https://registry.npmmirror.com
fi
npm install --production
print_success "后端依赖安装完成"
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
EOF
print_success "配置文件创建完成"
echo ""
}
create_data_directories() {
print_step "创建数据目录..."
mkdir -p "${PROJECT_DIR}/backend/data"
mkdir -p "${PROJECT_DIR}/backend/storage"
print_success "数据目录创建完成"
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/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}"
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"
# 使用国内镜像加速(如果之前选择了)
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
if [[ -d "node_modules" ]]; then
print_info "清理旧依赖..."
rm -rf node_modules package-lock.json
fi
npm install --production
print_success "依赖更新完成"
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 ""
while true; do
read -p "请输入选项 [1-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
;;
*)
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
# 部署SSL证书
deploy_ssl
# 配置Nginx
configure_nginx
# 启动后端服务
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
}
# 执行主流程
if [[ "$MODE" == "uninstall" ]]; then
uninstall_main
elif [[ "$MODE" == "update" ]]; then
update_main
elif [[ "$MODE" == "repair" ]]; then
repair_main
else
main
fi
################################################################################
# 修复功能
################################################################################
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配置..."
# 备份现有配置
if [[ -f "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" ]]; then
cp "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf" "/etc/nginx/sites-enabled/${PROJECT_NAME}.conf.backup.$(date +%Y%m%d%H%M%S)"
print_success "已备份现有配置"
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 "验证服务状态..."
# 检查后端
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
}