主要更新: - 自动检测端口占用(80, 443, 40001) - 支持自定义HTTP/HTTPS/后端端口 - 端口冲突时智能提示并允许自定义 - 所有配置(Nginx、.env)自动使用自定义端口 - 完成提示中显示实际使用的端口 - 健康检查使用动态端口验证 功能特性: - HTTP端口(默认80,可自定义如8080) - HTTPS端口(默认443,可自定义如8443) - 后端端口(默认40001,可自定义如40002) - 使用netstat/ss检测端口占用 - 端口范围验证(1024-65535) - 避免与现有项目冲突 更新内容: - 全局变量: HTTP_PORT, HTTPS_PORT, BACKEND_PORT - 新增: check_port_available() 检测端口 - 新增: configure_ports() 配置端口 - 修改: configure_nginx_http() 使用自定义端口 - 修改: configure_nginx_https() 使用自定义端口 - 修改: create_env_file() PORT使用BACKEND_PORT - 修改: health_check() 检查自定义后端端口 - 修改: print_completion() 显示端口信息 - 新增: 一键部署命令.txt 到仓库
1806 lines
53 KiB
Bash
1806 lines
53 KiB
Bash
#!/bin/bash
|
||
|
||
################################################################################
|
||
# 玩玩云 (WanWanYun) - 一键部署/卸载脚本
|
||
# 项目地址: https://gitee.com/yu-yon/vue-driven-cloud-storage
|
||
# 版本: v1.1.3
|
||
################################################################################
|
||
|
||
set -e
|
||
|
||
# 检查运行模式
|
||
MODE="install"
|
||
if [[ "$1" == "--uninstall" ]] || [[ "$1" == "-u" ]] || [[ "$1" == "uninstall" ]]; then
|
||
MODE="uninstall"
|
||
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};
|
||
|
||
# 前端静态文件
|
||
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;
|
||
}
|
||
|
||
# 分享页面
|
||
location /s {
|
||
alias ${PROJECT_DIR}/frontend;
|
||
try_files /share.html =404;
|
||
}
|
||
|
||
# 静态资源
|
||
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;
|
||
|
||
# 前端静态文件
|
||
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;
|
||
}
|
||
|
||
# 分享页面
|
||
location /s {
|
||
alias ${PROJECT_DIR}/frontend;
|
||
try_files /share.html =404;
|
||
}
|
||
|
||
# 静态资源
|
||
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 ""
|
||
}
|
||
|
||
################################################################################
|
||
# 主流程
|
||
################################################################################
|
||
|
||
main() {
|
||
print_banner
|
||
|
||
# 检查root权限
|
||
check_root
|
||
|
||
# 系统检测
|
||
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
|
||
else
|
||
main
|
||
fi
|