2047 lines
66 KiB
Bash
2047 lines
66 KiB
Bash
#!/bin/bash
|
||
|
||
# ========================================
|
||
# jsite前端自动化部署脚本
|
||
# ========================================
|
||
|
||
# 颜色定义
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# ========================================
|
||
# 默认参数配置
|
||
# ========================================
|
||
|
||
# 基础配置
|
||
SITE_NAME="jingrow"
|
||
GIT_REPO="http://git.jingrow.com:3000/jsite/jingrow"
|
||
NODE_VERSION="22"
|
||
START_PORT=3001
|
||
PORT_INCREMENT=1
|
||
|
||
# jsite管理相关配置
|
||
JSITE_BASE_DIR="/home/jingrow/jsite"
|
||
TRAEFIK_CONFIG_DIR="/home/jingrow/traefik-docker/conf.d/website"
|
||
|
||
# 任务模式
|
||
MODE="deploy" # 默认模式:deploy, create, delete, build, start, stop, restart, status, list, logs
|
||
|
||
# 跳过选项
|
||
SKIP_DOCKER=false
|
||
SKIP_TRAEFIK=false
|
||
SKIP_DEPENDENCIES=false
|
||
SKIP_PM2=false
|
||
FORCE_UPDATE=false
|
||
|
||
# 网络配置
|
||
PUBLIC_IP="" # 公网IP地址 (用于内网IP不可用时)
|
||
|
||
# .env文件参数
|
||
SITE_URL="example.com"
|
||
REVALIDATE_TOKEN=""
|
||
BACKEND_SERVER_URL="https://admin.example.com"
|
||
BACKEND_API_KEY=""
|
||
BACKEND_API_SECRET=""
|
||
|
||
# ========================================
|
||
# 工具函数
|
||
# ========================================
|
||
|
||
# 智能IP选择:优先使用内网IP,没有内网IP时使用公网IP
|
||
get_optimal_host_ip() {
|
||
local host_ip=""
|
||
|
||
# 方法1: 尝试获取内网IP
|
||
local private_ip=$(ip route get 8.8.8.8 2>/dev/null | awk '{print $7}' | head -1)
|
||
if [ -n "$private_ip" ] && [ "$private_ip" != "8.8.8.8" ]; then
|
||
# 测试内网IP是否可达
|
||
if ping -c 2 -W 1 "$private_ip" >/dev/null 2>&1; then
|
||
host_ip="$private_ip"
|
||
# 注意:这里不输出日志,避免污染返回值
|
||
else
|
||
# 注意:这里不输出日志,避免污染返回值
|
||
:
|
||
fi
|
||
fi
|
||
|
||
# 方法2: 如果没有内网IP或内网IP不可达,使用公网IP
|
||
if [ -z "$host_ip" ]; then
|
||
if [ -n "$PUBLIC_IP" ]; then
|
||
host_ip="$PUBLIC_IP"
|
||
# 注意:这里不输出日志,避免污染返回值
|
||
else
|
||
# 尝试自动获取公网IP
|
||
local auto_public_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || curl -s --max-time 5 ipinfo.io/ip 2>/dev/null)
|
||
if [ -n "$auto_public_ip" ]; then
|
||
host_ip="$auto_public_ip"
|
||
# 注意:这里不输出日志,避免污染返回值
|
||
else
|
||
# 最后回退:使用第一个可用IP
|
||
host_ip=$(hostname -I | awk '{print $1}')
|
||
# 注意:这里不输出日志,避免污染返回值
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
echo "$host_ip"
|
||
}
|
||
|
||
# PM2命令执行函数(统一处理环境变量)
|
||
execute_pm2_command() {
|
||
local command="$1"
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
$command
|
||
" 2>/dev/null
|
||
}
|
||
|
||
# 解析命令行参数
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
# 基础配置
|
||
--site-name)
|
||
SITE_NAME="$2"
|
||
shift 2
|
||
;;
|
||
--git-repo)
|
||
GIT_REPO="$2"
|
||
shift 2
|
||
;;
|
||
--node-version)
|
||
NODE_VERSION="$2"
|
||
shift 2
|
||
;;
|
||
--start-port)
|
||
START_PORT="$2"
|
||
shift 2
|
||
;;
|
||
--port-increment)
|
||
PORT_INCREMENT="$2"
|
||
shift 2
|
||
;;
|
||
|
||
# 跳过选项
|
||
--skip-docker)
|
||
SKIP_DOCKER=true
|
||
shift
|
||
;;
|
||
--skip-traefik)
|
||
SKIP_TRAEFIK=true
|
||
shift
|
||
;;
|
||
--skip-dependencies)
|
||
SKIP_DEPENDENCIES=true
|
||
shift
|
||
;;
|
||
--skip-pm2)
|
||
SKIP_PM2=true
|
||
shift
|
||
;;
|
||
--force-update)
|
||
FORCE_UPDATE=true
|
||
shift
|
||
;;
|
||
|
||
# 任务模式
|
||
--mode)
|
||
MODE="$2"
|
||
shift 2
|
||
;;
|
||
|
||
# 网络配置
|
||
--public-ip)
|
||
PUBLIC_IP="$2"
|
||
shift 2
|
||
;;
|
||
|
||
# .env文件参数
|
||
--site-url)
|
||
SITE_URL="$2"
|
||
shift 2
|
||
;;
|
||
--revalidate-token)
|
||
REVALIDATE_TOKEN="$2"
|
||
shift 2
|
||
;;
|
||
--backend-server-url)
|
||
BACKEND_SERVER_URL="$2"
|
||
shift 2
|
||
;;
|
||
--backend-api-key)
|
||
BACKEND_API_KEY="$2"
|
||
shift 2
|
||
;;
|
||
--backend-api-secret)
|
||
BACKEND_API_SECRET="$2"
|
||
shift 2
|
||
;;
|
||
|
||
# 帮助信息
|
||
-h|--help)
|
||
echo "用法: $0 [选项]"
|
||
echo ""
|
||
echo "任务模式 (--mode):"
|
||
echo " deploy 完整部署模式 (默认)"
|
||
echo " create 创建新网站"
|
||
echo " create_and_start 创建并启动网站(包含构建和启动)"
|
||
echo " delete 删除网站"
|
||
echo " build 构建网站"
|
||
echo " start 启动网站"
|
||
echo " stop 停止网站"
|
||
echo " restart 重启网站"
|
||
echo " status 查看网站状态"
|
||
echo " list 列出所有网站"
|
||
echo " logs 查看网站日志"
|
||
echo ""
|
||
echo "基础配置:"
|
||
echo " --site-name NAME 项目名称 (默认: jingrow)"
|
||
echo " --git-repo URL Git仓库地址"
|
||
echo " --node-version VER Node.js版本 (默认: 22)"
|
||
echo " --start-port PORT 起始端口 (默认: 3001)"
|
||
echo " --port-increment INC 端口增量 (默认: 1)"
|
||
echo ""
|
||
echo "跳过选项:"
|
||
echo " --skip-docker 跳过Docker安装"
|
||
echo " --skip-traefik 跳过Traefik安装"
|
||
echo " --skip-dependencies 跳过依赖安装"
|
||
echo " --skip-pm2 跳过PM2安装"
|
||
echo " --force-update 强制更新所有文件"
|
||
echo ""
|
||
echo "网络配置:"
|
||
echo " --public-ip IP 公网IP地址 (用于内网IP不可用时)"
|
||
echo ""
|
||
echo ".env文件配置:"
|
||
echo " --site-url URL 网站URL (默认: starrbud.com)"
|
||
echo " --revalidate-token TK 重新验证令牌"
|
||
echo " --backend-server-url URL 服务器URL (默认: https://admin.jingrow.com)"
|
||
echo " --backend-api-key KEY API密钥"
|
||
echo " --backend-api-secret SECRET API密钥"
|
||
echo ""
|
||
echo " -h, --help 显示此帮助信息"
|
||
echo ""
|
||
echo "示例:"
|
||
echo " # 完整部署"
|
||
echo " $0 --mode deploy --site-name myproject --public-ip 8.217.167.199"
|
||
echo " # 创建网站"
|
||
echo " $0 --mode create --site-name myproject --git-repo http://git.example.com/project"
|
||
echo " # 创建并启动网站"
|
||
echo " $0 --mode create_and_start --site-name myproject --git-repo http://git.example.com/project"
|
||
echo " # 管理网站"
|
||
echo " $0 --mode start --site-name myproject"
|
||
echo " $0 --mode stop --site-name myproject"
|
||
echo " $0 --mode status --site-name myproject"
|
||
echo " $0 --mode list"
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo "未知选项: $1"
|
||
echo "使用 --help 查看帮助信息"
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
set -e # 遇到错误时退出
|
||
|
||
# 设置非交互式环境变量,避免交互式配置
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
export DEBCONF_NONINTERACTIVE_SEEN=true
|
||
export UCF_FORCE_CONFNEW=1
|
||
export UCF_FORCE_CONFOLD=1
|
||
|
||
# 日志函数
|
||
log_info() {
|
||
echo -e "${BLUE}[INFO]${NC} $1"
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||
}
|
||
|
||
log_warning() {
|
||
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||
}
|
||
|
||
# 检查是否为root用户
|
||
check_root() {
|
||
if [[ $EUID -eq 0 ]]; then
|
||
log_info "以root用户身份运行脚本"
|
||
else
|
||
log_error "请以root用户身份运行此脚本"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# 检查jsite基础目录
|
||
check_jsite_base_dir() {
|
||
if [ ! -d "$JSITE_BASE_DIR" ]; then
|
||
log_info "创建jsite基础目录: $JSITE_BASE_DIR"
|
||
mkdir -p "$JSITE_BASE_DIR"
|
||
chown "jingrow:jingrow" "$JSITE_BASE_DIR"
|
||
fi
|
||
}
|
||
|
||
# 检查网站目录是否存在
|
||
check_site_exists() {
|
||
local site_name="$1"
|
||
[ -d "$JSITE_BASE_DIR/$site_name" ]
|
||
}
|
||
|
||
# 获取网站状态
|
||
get_site_status() {
|
||
local site_name="$1"
|
||
|
||
if ! check_site_exists "$site_name"; then
|
||
echo "not_exists"
|
||
return
|
||
fi
|
||
|
||
# 检查PM2进程状态
|
||
local pm2_status=$(su - "jingrow" -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list 2>/dev/null | grep -w '$site_name' | grep -c 'online' || echo '0'
|
||
")
|
||
|
||
if [ "$pm2_status" != "0" ]; then
|
||
echo "running"
|
||
else
|
||
echo "stopped"
|
||
fi
|
||
}
|
||
|
||
# 1. 创建jingrow用户
|
||
create_jingrow_user() {
|
||
log_info "开始创建jingrow用户..."
|
||
|
||
if id "jingrow" &>/dev/null; then
|
||
log_warning "用户jingrow已存在"
|
||
else
|
||
useradd -m -s /bin/bash jingrow
|
||
log_success "用户jingrow创建成功"
|
||
fi
|
||
|
||
# 确保home目录存在
|
||
if [ ! -d "/home/jingrow" ]; then
|
||
mkdir -p /home/jingrow
|
||
chown jingrow:jingrow /home/jingrow
|
||
log_success "创建/home/jingrow目录"
|
||
fi
|
||
}
|
||
|
||
# 2. 安装nvm和node.js
|
||
install_nodejs() {
|
||
log_info "开始安装nvm和node.js..."
|
||
|
||
# 切换到jingrow用户目录
|
||
cd /home/jingrow
|
||
|
||
# 下载并安装nvm
|
||
if [ ! -d "/home/jingrow/.nvm" ]; then
|
||
log_info "下载并安装nvm..."
|
||
su - jingrow -c "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash"
|
||
log_success "nvm安装完成"
|
||
else
|
||
log_warning "nvm已存在"
|
||
fi
|
||
|
||
# 加载nvm并安装node.js
|
||
log_info "安装Node.js v$NODE_VERSION..."
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
nvm install $NODE_VERSION
|
||
nvm use $NODE_VERSION
|
||
nvm alias default $NODE_VERSION
|
||
"
|
||
|
||
# 验证安装
|
||
NODE_VERSION_OUTPUT=$(su - jingrow -c 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && node -v')
|
||
NPM_VERSION=$(su - jingrow -c 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && npm -v')
|
||
|
||
log_success "Node.js版本: $NODE_VERSION_OUTPUT"
|
||
log_success "npm版本: $NPM_VERSION"
|
||
}
|
||
|
||
# 2.5. 安装PM2
|
||
install_pm2() {
|
||
log_info "开始安装PM2..."
|
||
|
||
# 确保NVM环境变量被正确配置到用户配置文件中
|
||
log_info "配置NVM环境变量..."
|
||
|
||
# 检查 ~/.bashrc 中是否已有NVM配置
|
||
if ! su - jingrow -c "grep -q 'NVM_DIR' ~/.bashrc 2>/dev/null"; then
|
||
log_info "在 ~/.bashrc 中添加NVM配置..."
|
||
su - jingrow -c "echo 'export NVM_DIR=\"\$HOME/.nvm\"' >> ~/.bashrc"
|
||
su - jingrow -c "echo '[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"' >> ~/.bashrc"
|
||
su - jingrow -c "echo '[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\"' >> ~/.bashrc"
|
||
fi
|
||
|
||
# 检查 ~/.profile 中是否已有NVM配置(用于非交互式shell)
|
||
if ! su - jingrow -c "grep -q 'NVM_DIR' ~/.profile 2>/dev/null"; then
|
||
log_info "在 ~/.profile 中添加NVM配置..."
|
||
su - jingrow -c "echo 'export NVM_DIR=\"\$HOME/.nvm\"' >> ~/.profile"
|
||
su - jingrow -c "echo '[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"' >> ~/.profile"
|
||
fi
|
||
|
||
# 检查PM2是否已安装
|
||
PM2_VERSION=$(su - jingrow -c 'export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && pm2 -v 2>/dev/null || echo "not_installed"')
|
||
|
||
if [ "$PM2_VERSION" != "not_installed" ]; then
|
||
log_warning "PM2已安装,版本: $PM2_VERSION"
|
||
else
|
||
log_info "安装PM2..."
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
npm install -g pm2
|
||
"
|
||
if [ $? -ne 0 ]; then
|
||
log_error "PM2安装失败"
|
||
return 1
|
||
fi
|
||
log_success "PM2安装完成"
|
||
fi
|
||
|
||
# 设置PM2开机自启(避免交互式输出)
|
||
log_info "配置PM2开机自启..."
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 startup 2>/dev/null || true
|
||
"
|
||
log_success "PM2开机自启配置完成"
|
||
}
|
||
|
||
# 2.6. 端口管理函数
|
||
get_available_port() {
|
||
local site_name="$1"
|
||
local base_port="$START_PORT"
|
||
local increment="$PORT_INCREMENT"
|
||
|
||
# 端口分配文件路径
|
||
local port_file="/home/jingrow/jsite/site_port.json"
|
||
|
||
# 确保jsite目录存在
|
||
if [ ! -d "/home/jingrow/jsite" ]; then
|
||
mkdir -p /home/jingrow/jsite
|
||
chown jingrow:jingrow /home/jingrow/jsite
|
||
fi
|
||
|
||
# 检查项目是否已有分配的端口
|
||
if [ -f "$port_file" ]; then
|
||
# 确保jq工具已安装
|
||
ensure_jq_installed
|
||
|
||
# 使用jq查询已分配的端口
|
||
if command -v jq &> /dev/null; then
|
||
local existing_port=$(jq -r ".$site_name // empty" "$port_file" 2>/dev/null || echo "")
|
||
if [ -n "$existing_port" ] && [ "$existing_port" != "null" ] && [ "$existing_port" != "empty" ]; then
|
||
echo "$existing_port"
|
||
return 0
|
||
fi
|
||
else
|
||
# 如果没有jq,使用grep和cut来解析JSON
|
||
local existing_port=$(grep -o "\"$site_name\"[[:space:]]*:[[:space:]]*[0-9]*" "$port_file" 2>/dev/null | cut -d: -f2 | tr -d ' ' || echo "")
|
||
if [ -n "$existing_port" ]; then
|
||
echo "$existing_port"
|
||
return 0
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# 如果没有分配的端口,检查是否使用用户指定的起始端口
|
||
local next_port="$base_port"
|
||
|
||
# 检查用户是否传入了自定义起始端口(通过比较START_PORT是否等于默认值3001)
|
||
local user_specified_port=false
|
||
if [ "$base_port" != "3001" ]; then
|
||
user_specified_port=true
|
||
fi
|
||
|
||
if [ "$user_specified_port" = true ]; then
|
||
# 用户传入了自定义端口,优先使用用户指定的端口
|
||
# 检查用户指定的端口是否已被使用
|
||
local port_available=true
|
||
if [ -f "$port_file" ]; then
|
||
# 确保jq工具已安装
|
||
ensure_jq_installed
|
||
|
||
if command -v jq &> /dev/null; then
|
||
local used_ports=$(jq -r '.[]' "$port_file" 2>/dev/null || echo "")
|
||
if echo "$used_ports" | grep -q "^$base_port$"; then
|
||
port_available=false
|
||
fi
|
||
else
|
||
local used_ports=$(grep -o ":[[:space:]]*[0-9]*" "$port_file" 2>/dev/null | cut -d: -f2 | tr -d ' ' || echo "")
|
||
if echo "$used_ports" | grep -q "^$base_port$"; then
|
||
port_available=false
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
if [ "$port_available" = true ]; then
|
||
# 用户指定的端口可用,直接使用
|
||
next_port="$base_port"
|
||
else
|
||
# 用户指定的端口已被使用,使用最大端口号 + 1
|
||
if [ -f "$port_file" ]; then
|
||
# 确保jq工具已安装
|
||
ensure_jq_installed
|
||
|
||
if command -v jq &> /dev/null; then
|
||
local max_port=$(jq -r 'max(.[])' "$port_file" 2>/dev/null || echo "$base_port")
|
||
if [ -n "$max_port" ] && [ "$max_port" != "null" ]; then
|
||
next_port=$((max_port + increment))
|
||
fi
|
||
else
|
||
local max_port=$(grep -o ":[[:space:]]*[0-9]*" "$port_file" 2>/dev/null | cut -d: -f2 | tr -d ' ' | sort -n | tail -1 || echo "$base_port")
|
||
if [ -n "$max_port" ]; then
|
||
next_port=$((max_port + increment))
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
else
|
||
# 用户没有传入自定义端口,使用自动分配逻辑
|
||
if [ -f "$port_file" ]; then
|
||
# 确保jq工具已安装
|
||
ensure_jq_installed
|
||
|
||
if command -v jq &> /dev/null; then
|
||
# 使用jq找到最大端口号
|
||
local max_port=$(jq -r 'max(.[])' "$port_file" 2>/dev/null || echo "$base_port")
|
||
if [ -n "$max_port" ] && [ "$max_port" != "null" ]; then
|
||
next_port=$((max_port + increment))
|
||
fi
|
||
else
|
||
# 使用grep和sort找到最大端口号
|
||
local max_port=$(grep -o ":[[:space:]]*[0-9]*" "$port_file" 2>/dev/null | cut -d: -f2 | tr -d ' ' | sort -n | tail -1 || echo "$base_port")
|
||
if [ -n "$max_port" ]; then
|
||
next_port=$((max_port + increment))
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
echo "$next_port"
|
||
return 0
|
||
}
|
||
|
||
# 2.7. 保存端口分配
|
||
save_port_assignment() {
|
||
local site_name="$1"
|
||
local port="$2"
|
||
|
||
local port_file="/home/jingrow/jsite/site_port.json"
|
||
|
||
# 确保jsite目录存在
|
||
if [ ! -d "/home/jingrow/jsite" ]; then
|
||
mkdir -p /home/jingrow/jsite
|
||
chown jingrow:jingrow /home/jingrow/jsite
|
||
fi
|
||
|
||
# 确保jq工具已安装
|
||
ensure_jq_installed
|
||
|
||
# 检查jq是否可用
|
||
if command -v jq &> /dev/null; then
|
||
# 使用jq保存到JSON文件
|
||
if [ -f "$port_file" ]; then
|
||
# 更新现有文件
|
||
jq ". + {\"$site_name\": $port}" "$port_file" > "${port_file}.tmp" && mv "${port_file}.tmp" "$port_file"
|
||
else
|
||
# 创建新文件
|
||
echo "{\"$site_name\": $port}" > "$port_file"
|
||
fi
|
||
else
|
||
# 如果没有jq,使用更安全的文本处理方式
|
||
if [ -f "$port_file" ]; then
|
||
# 提取现有条目并重新构建
|
||
local existing_entries=$(grep -o '"[^"]*"[[:space:]]*:[[:space:]]*[0-9]*' "$port_file" 2>/dev/null | grep -v "\"$site_name\"" || echo "")
|
||
|
||
# 重新构建JSON文件
|
||
echo "{" > "$port_file"
|
||
if [ -n "$existing_entries" ]; then
|
||
echo "$existing_entries" | sed 's/$/,/' | head -n -1 >> "$port_file"
|
||
echo "$existing_entries" | tail -n 1 >> "$port_file"
|
||
echo "," >> "$port_file"
|
||
fi
|
||
echo " \"$site_name\": $port" >> "$port_file"
|
||
echo "}" >> "$port_file"
|
||
else
|
||
# 创建新文件
|
||
echo "{\"$site_name\": $port}" > "$port_file"
|
||
fi
|
||
fi
|
||
|
||
# 设置文件权限
|
||
chown jingrow:jingrow "$port_file"
|
||
chmod 644 "$port_file"
|
||
|
||
log_success "端口 $port 已分配给项目 $site_name"
|
||
}
|
||
|
||
# 2.8. 获取或分配端口(确保一致性)
|
||
get_or_assign_port() {
|
||
local site_name="$1"
|
||
|
||
# 获取或分配端口
|
||
local port=$(get_available_port "$site_name")
|
||
|
||
if [ -n "$port" ]; then
|
||
# 检查是否已经保存过这个端口分配
|
||
local port_file="/home/jingrow/jsite/site_port.json"
|
||
local already_saved=false
|
||
|
||
if [ -f "$port_file" ]; then
|
||
# 确保jq工具已安装
|
||
ensure_jq_installed
|
||
|
||
if command -v jq &> /dev/null; then
|
||
local existing_port=$(jq -r ".$site_name // empty" "$port_file" 2>/dev/null || echo "")
|
||
if [ -n "$existing_port" ] && [ "$existing_port" != "null" ] && [ "$existing_port" != "empty" ]; then
|
||
already_saved=true
|
||
fi
|
||
else
|
||
local existing_port=$(grep -o "\"$site_name\"[[:space:]]*:[[:space:]]*[0-9]*" "$port_file" 2>/dev/null | cut -d: -f2 | tr -d ' ' || echo "")
|
||
if [ -n "$existing_port" ]; then
|
||
already_saved=true
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# 如果没有保存过,则保存端口分配(重定向日志输出)
|
||
if [ "$already_saved" = false ]; then
|
||
save_port_assignment "$site_name" "$port" >/dev/null 2>&1
|
||
fi
|
||
|
||
echo "$port"
|
||
return 0
|
||
else
|
||
log_error "无法为项目 $site_name 分配端口"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 2.9. 显示端口分配情况
|
||
show_port_assignments() {
|
||
local port_file="/home/jingrow/jsite/site_port.json"
|
||
|
||
if [ ! -f "$port_file" ]; then
|
||
log_info "暂无端口分配记录"
|
||
return 0
|
||
fi
|
||
|
||
log_info "当前端口分配情况:"
|
||
# 确保jq工具已安装
|
||
ensure_jq_installed
|
||
|
||
if command -v jq &> /dev/null; then
|
||
# 使用jq格式化输出
|
||
jq -r 'to_entries[] | " - \(.key): \(.value)"' "$port_file" 2>/dev/null || log_warning "无法解析端口分配文件"
|
||
else
|
||
# 使用grep和sed格式化输出
|
||
grep -o '"[^"]*"[[:space:]]*:[[:space:]]*[0-9]*' "$port_file" 2>/dev/null | sed 's/"//g' | sed 's/[[:space:]]*:[[:space:]]*/: /' | sed 's/^/ - /' || log_warning "无法解析端口分配文件"
|
||
fi
|
||
}
|
||
|
||
# 3. 克隆jsite项目
|
||
clone_jsite_project() {
|
||
log_info "开始克隆jsite项目: $SITE_NAME..."
|
||
|
||
cd /home/jingrow
|
||
|
||
# 创建jsite目录(如果不存在)
|
||
if [ ! -d "/home/jingrow/jsite" ]; then
|
||
mkdir -p /home/jingrow/jsite
|
||
chown jingrow:jingrow /home/jingrow/jsite
|
||
log_success "创建jsite目录"
|
||
fi
|
||
|
||
# 检查项目子目录是否已存在
|
||
if [ -d "/home/jingrow/jsite/$SITE_NAME" ]; then
|
||
if [ "$FORCE_UPDATE" = true ]; then
|
||
log_warning "项目目录已存在,强制更新..."
|
||
rm -rf "/home/jingrow/jsite/$SITE_NAME"
|
||
cd /home/jingrow/jsite
|
||
su - jingrow -c "cd /home/jingrow/jsite && git clone $GIT_REPO $SITE_NAME"
|
||
if [ $? -ne 0 ]; then
|
||
log_error "项目克隆失败: $GIT_REPO"
|
||
return 1
|
||
fi
|
||
log_success "jsite/$SITE_NAME项目更新完成"
|
||
else
|
||
log_warning "jsite/$SITE_NAME目录已存在,跳过克隆"
|
||
fi
|
||
else
|
||
cd /home/jingrow/jsite
|
||
log_info "克隆项目: $GIT_REPO"
|
||
su - jingrow -c "cd /home/jingrow/jsite && git clone $GIT_REPO $SITE_NAME"
|
||
if [ $? -ne 0 ]; then
|
||
log_error "项目克隆失败: $GIT_REPO"
|
||
return 1
|
||
fi
|
||
log_success "jsite/$SITE_NAME项目克隆完成"
|
||
fi
|
||
|
||
# 检查项目是否成功克隆
|
||
if [ ! -d "/home/jingrow/jsite/$SITE_NAME" ]; then
|
||
log_error "项目目录不存在,克隆可能失败"
|
||
return 1
|
||
fi
|
||
|
||
# 检查package.json是否存在
|
||
if [ ! -f "/home/jingrow/jsite/$SITE_NAME/package.json" ]; then
|
||
log_error "package.json文件不存在,请检查项目是否正确克隆"
|
||
return 1
|
||
fi
|
||
|
||
log_success "项目克隆和验证完成"
|
||
}
|
||
|
||
# 4. 创建.env文件
|
||
create_env_file() {
|
||
log_info "创建.env文件..."
|
||
|
||
cd /home/jingrow/jsite/$SITE_NAME
|
||
|
||
# 获取项目端口
|
||
local project_port=$(get_or_assign_port "$SITE_NAME")
|
||
|
||
# 智能IP选择:优先使用内网IP,没有内网IP时使用公网IP(与traefik配置保持一致)
|
||
local host_ip=$(get_optimal_host_ip)
|
||
|
||
# 输出IP选择结果
|
||
if [[ "$host_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||
log_success "使用IP地址: $host_ip"
|
||
else
|
||
log_warning "IP地址格式异常: $host_ip"
|
||
return 1
|
||
fi
|
||
|
||
# 构建PUBLIC_SITE_URL(优先使用内网IP,没有内网IP时使用公网IP)
|
||
local public_site_url="http://$host_ip:$project_port"
|
||
|
||
# 检查.env文件是否已存在
|
||
if [ -f "/home/jingrow/jsite/$SITE_NAME/.env" ]; then
|
||
if [ "$FORCE_UPDATE" = true ]; then
|
||
log_warning ".env文件已存在,强制更新..."
|
||
else
|
||
log_warning ".env文件已存在,跳过创建"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
# 创建.env文件
|
||
cat > "/home/jingrow/jsite/$SITE_NAME/.env" << EOF
|
||
# 网站配置
|
||
PUBLIC_SITE_URL=$public_site_url
|
||
|
||
# 重新验证令牌
|
||
REVALIDATE_TOKEN=$REVALIDATE_TOKEN
|
||
|
||
# 项目配置
|
||
BACKEND_SITE_NAME=$SITE_NAME
|
||
|
||
# 服务器配置
|
||
BACKEND_SERVER_URL=$BACKEND_SERVER_URL
|
||
BACKEND_API_KEY=$BACKEND_API_KEY
|
||
BACKEND_API_SECRET=$BACKEND_API_SECRET
|
||
EOF
|
||
|
||
# 设置文件权限
|
||
chown jingrow:jingrow "/home/jingrow/jsite/$SITE_NAME/.env"
|
||
chmod 600 "/home/jingrow/jsite/$SITE_NAME/.env"
|
||
|
||
log_success ".env文件创建完成 (端口: $project_port, PUBLIC_SITE_URL: $public_site_url)"
|
||
}
|
||
|
||
# 4.5. 创建PM2配置文件
|
||
create_pm2_config() {
|
||
log_info "创建PM2配置文件..."
|
||
|
||
cd /home/jingrow/jsite/$SITE_NAME
|
||
|
||
# 获取项目端口
|
||
local project_port=$(get_or_assign_port "$SITE_NAME")
|
||
|
||
# 创建logs目录
|
||
mkdir -p "/home/jingrow/jsite/$SITE_NAME/logs"
|
||
|
||
# 检查package.json中的scripts
|
||
local start_script="npm start"
|
||
if [ -f "/home/jingrow/jsite/$SITE_NAME/package.json" ]; then
|
||
if grep -q '"start"' "/home/jingrow/jsite/$SITE_NAME/package.json"; then
|
||
start_script="npm start"
|
||
elif grep -q '"dev"' "/home/jingrow/jsite/$SITE_NAME/package.json"; then
|
||
start_script="npm run dev"
|
||
else
|
||
log_warning "未找到start或dev脚本,使用默认npm start"
|
||
fi
|
||
fi
|
||
|
||
# 检查项目是否使用ES模块
|
||
local is_es_module=false
|
||
if [ -f "/home/jingrow/jsite/$SITE_NAME/package.json" ]; then
|
||
if grep -q '"type": "module"' "/home/jingrow/jsite/$SITE_NAME/package.json"; then
|
||
is_es_module=true
|
||
fi
|
||
fi
|
||
|
||
# 使用.cjs扩展名来避免ES模块问题
|
||
cat > "/home/jingrow/jsite/$SITE_NAME/ecosystem.config.cjs" << EOF
|
||
module.exports = {
|
||
apps: [{
|
||
name: '$SITE_NAME',
|
||
script: 'npm',
|
||
args: 'start',
|
||
cwd: '/home/jingrow/jsite/$SITE_NAME',
|
||
instances: 1,
|
||
autorestart: true,
|
||
watch: false,
|
||
max_memory_restart: '1G',
|
||
env: {
|
||
NODE_ENV: 'production',
|
||
PORT: $project_port
|
||
},
|
||
env_file: '.env',
|
||
log_file: './logs/combined.log',
|
||
out_file: './logs/out.log',
|
||
error_file: './logs/error.log',
|
||
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
||
merge_logs: true,
|
||
time: true,
|
||
min_uptime: '10s',
|
||
max_restarts: 5
|
||
}]
|
||
};
|
||
EOF
|
||
|
||
# 设置文件权限
|
||
chown -R jingrow:jingrow "/home/jingrow/jsite/$SITE_NAME/ecosystem.config.cjs"
|
||
chown -R jingrow:jingrow "/home/jingrow/jsite/$SITE_NAME/logs"
|
||
|
||
log_success "PM2配置文件创建完成 (端口: $project_port, 启动脚本: $start_script, 模块类型: $([ "$is_es_module" = true ] && echo "ES模块" || echo "CommonJS"))"
|
||
}
|
||
|
||
# 4.6. 用PM2启动项目
|
||
start_project_with_pm2() {
|
||
log_info "使用PM2启动项目: $SITE_NAME..."
|
||
|
||
cd /home/jingrow/jsite/$SITE_NAME
|
||
|
||
# 检查项目目录是否存在
|
||
if [ ! -d "/home/jingrow/jsite/$SITE_NAME" ]; then
|
||
log_error "项目目录不存在: /home/jingrow/jsite/$SITE_NAME"
|
||
return 1
|
||
fi
|
||
|
||
# 检查package.json是否存在
|
||
if [ ! -f "/home/jingrow/jsite/$SITE_NAME/package.json" ]; then
|
||
log_error "package.json文件不存在,请检查项目是否正确克隆"
|
||
return 1
|
||
fi
|
||
|
||
# 检查ecosystem.config.cjs是否存在
|
||
if [ ! -f "/home/jingrow/jsite/$SITE_NAME/ecosystem.config.cjs" ]; then
|
||
log_error "ecosystem.config.cjs文件不存在,请检查PM2配置是否正确创建"
|
||
return 1
|
||
fi
|
||
|
||
# 确保NVM环境变量被正确加载
|
||
log_info "加载NVM环境变量..."
|
||
|
||
# 检查项目是否已经在PM2中运行
|
||
PM2_STATUS=$(su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list | grep -w '$SITE_NAME' | wc -l
|
||
")
|
||
|
||
# 检查项目是否真的在运行(状态为online)
|
||
PM2_RUNNING=$(su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list | grep -w '$SITE_NAME' | grep -c 'online' || echo '0'
|
||
")
|
||
|
||
# 如果项目存在,先删除它(不管状态如何)
|
||
if [ "$PM2_STATUS" != "0" ] && [ -n "$PM2_STATUS" ]; then
|
||
log_warning "项目已存在于PM2中,删除旧进程..."
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 delete $SITE_NAME 2>/dev/null || true
|
||
"
|
||
# 等待一下确保删除完成
|
||
sleep 2
|
||
fi
|
||
|
||
# 启动新项目
|
||
log_info "启动项目: $SITE_NAME..."
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
cd /home/jingrow/jsite/$SITE_NAME
|
||
pm2 start ecosystem.config.cjs
|
||
"
|
||
|
||
if [ $? -ne 0 ]; then
|
||
log_error "PM2启动失败,请检查项目配置和依赖"
|
||
log_info "检查PM2日志:"
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 logs $SITE_NAME --lines 10
|
||
"
|
||
return 1
|
||
fi
|
||
|
||
# 等待PM2启动完成,最多等待60秒
|
||
log_info "等待PM2启动完成..."
|
||
local wait_time=0
|
||
local max_wait=60
|
||
local pm2_running="0"
|
||
local max_retries=3
|
||
local retry_count=0
|
||
|
||
while [ $wait_time -lt $max_wait ] && [ "$pm2_running" = "0" ]; do
|
||
sleep 2
|
||
wait_time=$((wait_time + 2))
|
||
|
||
# 检查PM2进程状态
|
||
pm2_running=$(su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list | grep -w '$SITE_NAME' | grep -c 'online' || echo '0'
|
||
")
|
||
|
||
if [ "$pm2_running" = "0" ]; then
|
||
log_info "等待PM2启动... (${wait_time}s/${max_wait}s)"
|
||
|
||
# 如果等待时间超过30秒,检查是否有错误
|
||
if [ $wait_time -gt 30 ] && [ $retry_count -lt $max_retries ]; then
|
||
log_warning "启动时间较长,检查PM2状态和日志..."
|
||
|
||
# 检查PM2状态
|
||
local pm2_status=$(su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list | grep -w '$SITE_NAME' || echo 'not_found'
|
||
")
|
||
|
||
if [ "$pm2_status" != "not_found" ]; then
|
||
log_info "PM2状态: $pm2_status"
|
||
fi
|
||
|
||
# 检查PM2日志
|
||
local pm2_logs=$(su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 logs $SITE_NAME --lines 5 --nostream 2>/dev/null || echo 'no_logs'
|
||
")
|
||
|
||
if [ "$pm2_logs" != "no_logs" ]; then
|
||
log_info "PM2日志: $pm2_logs"
|
||
fi
|
||
|
||
retry_count=$((retry_count + 1))
|
||
fi
|
||
fi
|
||
done
|
||
|
||
# 检查PM2进程是否真的启动了
|
||
if [ "$pm2_running" = "0" ]; then
|
||
log_error "PM2启动失败,项目未正常运行(等待${wait_time}秒后仍未能启动)"
|
||
log_info "检查PM2状态:"
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list
|
||
"
|
||
log_info "检查PM2日志:"
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 logs $SITE_NAME --lines 20
|
||
"
|
||
return 1
|
||
fi
|
||
|
||
log_success "PM2进程已启动,开始健康检查..."
|
||
|
||
# 获取项目端口
|
||
local project_port=$(get_or_assign_port "$SITE_NAME")
|
||
|
||
# 等待应用完全启动
|
||
sleep 3
|
||
|
||
# 检查端口是否被监听(使用多种方法)
|
||
log_info "检查端口 $project_port 是否被监听..."
|
||
local port_check=0
|
||
local max_port_checks=10
|
||
local port_check_count=0
|
||
|
||
while [ $port_check_count -lt $max_port_checks ] && [ $port_check -eq 0 ]; do
|
||
# 方法1: 使用netstat
|
||
port_check=$(netstat -tlnp 2>/dev/null | grep ":$project_port " | wc -l || echo "0")
|
||
|
||
# 方法2: 如果netstat失败,使用ss
|
||
if [ $port_check -eq 0 ]; then
|
||
port_check=$(ss -tlnp 2>/dev/null | grep ":$project_port " | wc -l || echo "0")
|
||
fi
|
||
|
||
# 方法3: 如果ss也失败,使用lsof
|
||
if [ $port_check -eq 0 ]; then
|
||
port_check=$(lsof -i :$project_port 2>/dev/null | wc -l || echo "0")
|
||
fi
|
||
|
||
if [ $port_check -eq 0 ]; then
|
||
sleep 1
|
||
port_check_count=$((port_check_count + 1))
|
||
log_info "等待端口 $project_port 启动... (${port_check_count}/${max_port_checks})"
|
||
fi
|
||
done
|
||
|
||
if [ $port_check -eq 0 ]; then
|
||
log_error "项目启动失败:端口 $project_port 未被监听"
|
||
log_info "检查PM2状态:"
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list
|
||
"
|
||
log_info "检查PM2日志:"
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 logs $SITE_NAME --lines 20
|
||
"
|
||
return 1
|
||
fi
|
||
|
||
log_success "端口 $project_port 已被监听"
|
||
|
||
# 检查PM2进程状态
|
||
log_info "检查PM2进程状态..."
|
||
local pm2_status=$(su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list | grep -w '$SITE_NAME' || echo 'not_found'
|
||
")
|
||
|
||
if [ "$pm2_status" != "not_found" ]; then
|
||
log_success "PM2进程状态正常"
|
||
else
|
||
log_error "PM2进程状态异常"
|
||
return 1
|
||
fi
|
||
|
||
log_success "项目已成功启动并监听端口 $project_port"
|
||
|
||
# 保存PM2配置
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 save
|
||
"
|
||
|
||
# 显示PM2状态
|
||
log_info "PM2项目状态:"
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list
|
||
"
|
||
|
||
log_success "项目已使用PM2启动完成"
|
||
}
|
||
|
||
# 5. 安装traefik
|
||
install_traefik() {
|
||
log_info "开始安装traefik..."
|
||
|
||
cd /home/jingrow
|
||
|
||
# 检查traefik目录是否已存在
|
||
if [ -d "/home/jingrow/traefik-docker" ]; then
|
||
if [ "$FORCE_UPDATE" = true ]; then
|
||
log_warning "traefik-docker目录已存在,强制更新..."
|
||
rm -rf /home/jingrow/traefik-docker
|
||
else
|
||
log_warning "traefik-docker目录已存在,跳过克隆"
|
||
return
|
||
fi
|
||
fi
|
||
|
||
# 从Git仓库克隆traefik配置
|
||
su - jingrow -c "cd /home/jingrow && git clone http://git.jingrow.com:3000/go/traefik-docker"
|
||
log_success "traefik项目克隆完成"
|
||
|
||
# 设置目录权限
|
||
chown -R jingrow:jingrow /home/jingrow/traefik-docker
|
||
log_success "设置traefik目录权限"
|
||
|
||
# 设置 acme.json 文件权限(Let's Encrypt 要求 600 权限)
|
||
if [ -f "/home/jingrow/traefik-docker/acme.json" ]; then
|
||
chmod 600 /home/jingrow/traefik-docker/acme.json
|
||
chown jingrow:jingrow /home/jingrow/traefik-docker/acme.json
|
||
log_success "设置 acme.json 文件权限为 600"
|
||
else
|
||
log_warning "acme.json 文件不存在,请检查 Traefik 配置"
|
||
fi
|
||
}
|
||
|
||
# 6. 安装Docker(如果未安装)
|
||
install_docker() {
|
||
log_info "检查Docker安装状态..."
|
||
|
||
if command -v docker &> /dev/null; then
|
||
log_warning "Docker已安装"
|
||
else
|
||
log_info "开始安装Docker..."
|
||
|
||
# 设置非交互式环境
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
export DEBCONF_NONINTERACTIVE_SEEN=true
|
||
|
||
# 更新包索引
|
||
apt-get update
|
||
|
||
# 安装必要的包(避免交互式配置)
|
||
apt-get install -y --force-yes apt-transport-https ca-certificates curl gnupg lsb-release
|
||
|
||
# 添加Docker官方GPG密钥(非交互式)
|
||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||
|
||
# 设置稳定版仓库
|
||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||
|
||
# 更新包索引
|
||
apt-get update
|
||
|
||
# 安装Docker Engine(避免交互式配置)
|
||
apt-get install -y --force-yes docker-ce docker-ce-cli containerd.io docker-compose-plugin
|
||
|
||
# 启动Docker服务
|
||
systemctl start docker
|
||
systemctl enable docker
|
||
|
||
log_success "Docker安装完成"
|
||
fi
|
||
|
||
# 配置Docker镜像仓库地址
|
||
log_info "配置Docker镜像仓库地址..."
|
||
|
||
# 创建Docker配置目录
|
||
mkdir -p /etc/docker
|
||
|
||
# 创建或更新daemon.json配置文件
|
||
cat > /etc/docker/daemon.json << EOF
|
||
{
|
||
"registry-mirrors": [
|
||
"https://k487mmwn.mirror.aliyuncs.com",
|
||
"https://mirror.ccs.tencentyun.com",
|
||
"https://docker.mirrors.ustc.edu.cn",
|
||
"https://hub-mirror.c.163.com"
|
||
]
|
||
}
|
||
EOF
|
||
|
||
# 重新加载systemd配置并重启Docker服务
|
||
log_info "重新加载systemd配置并重启Docker服务..."
|
||
systemctl daemon-reload
|
||
systemctl restart docker
|
||
|
||
# 验证配置是否生效
|
||
log_info "验证Docker镜像仓库配置..."
|
||
if docker info | grep -q "k487mmwn.mirror.aliyuncs.com"; then
|
||
log_success "Docker镜像仓库配置成功"
|
||
else
|
||
log_warning "Docker镜像仓库配置可能未生效,请手动检查"
|
||
fi
|
||
|
||
# 将jingrow用户添加到docker组
|
||
usermod -aG docker jingrow
|
||
log_success "将jingrow用户添加到docker组"
|
||
}
|
||
|
||
# 6.1 安装jq工具(如果未安装)
|
||
install_jq() {
|
||
log_info "检查jq工具安装状态..."
|
||
|
||
if command -v jq &> /dev/null; then
|
||
log_warning "jq工具已安装"
|
||
else
|
||
log_info "开始安装jq工具..."
|
||
|
||
# 设置非交互式环境
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
export DEBCONF_NONINTERACTIVE_SEEN=true
|
||
|
||
# 更新包索引
|
||
apt-get update
|
||
|
||
# 安装jq工具
|
||
apt-get install -y --force-yes jq
|
||
|
||
if command -v jq &> /dev/null; then
|
||
log_success "jq工具安装完成"
|
||
else
|
||
log_error "jq工具安装失败"
|
||
return 1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# 6.2 确保jq工具已安装
|
||
ensure_jq_installed() {
|
||
if ! command -v jq &> /dev/null; then
|
||
log_warning "jq工具未安装,正在安装..."
|
||
if ! install_jq; then
|
||
log_error "jq工具安装失败,无法处理JSON文件"
|
||
return 1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# 7. 启动traefik
|
||
start_traefik() {
|
||
log_info "启动traefik服务..."
|
||
|
||
cd /home/jingrow/traefik-docker
|
||
|
||
# 检查docker-compose.yml文件是否存在
|
||
if [ ! -f "/home/jingrow/traefik-docker/docker-compose.yml" ]; then
|
||
log_error "docker-compose.yml文件不存在,请检查traefik项目是否正确克隆"
|
||
return 1
|
||
fi
|
||
|
||
# 检查traefik是否已经在运行,如果运行则重启,否则启动
|
||
local traefik_running=$(su - jingrow -c "cd /home/jingrow/traefik-docker && docker compose ps -q" 2>/dev/null | wc -l)
|
||
|
||
if [ "$traefik_running" -gt 0 ]; then
|
||
log_info "traefik已在运行,重启服务以加载新配置..."
|
||
su - jingrow -c "cd /home/jingrow/traefik-docker && docker compose restart"
|
||
else
|
||
log_info "启动新的traefik服务..."
|
||
su - jingrow -c "cd /home/jingrow/traefik-docker && docker compose up -d"
|
||
fi
|
||
|
||
log_success "traefik服务启动/重启完成"
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
# 8. 安装项目依赖
|
||
install_project_dependencies() {
|
||
log_info "安装jsite/$SITE_NAME项目依赖..."
|
||
|
||
cd /home/jingrow/jsite/$SITE_NAME
|
||
|
||
# 检查package.json是否存在
|
||
if [ ! -f "/home/jingrow/jsite/$SITE_NAME/package.json" ]; then
|
||
log_error "package.json文件不存在,请检查项目是否正确克隆"
|
||
return 1
|
||
fi
|
||
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
cd /home/jingrow/jsite/$SITE_NAME
|
||
npm install
|
||
"
|
||
|
||
if [ $? -ne 0 ]; then
|
||
log_error "项目依赖安装失败"
|
||
return 1
|
||
fi
|
||
|
||
log_success "项目依赖安装完成"
|
||
|
||
# 构建项目
|
||
log_info "构建jsite/$SITE_NAME项目..."
|
||
|
||
# 获取当前内存大小(MB)并减去200MB作为构建内存限制
|
||
local total_memory_mb=$(free -m | awk 'NR==2{print $2}')
|
||
local build_memory_mb=$((total_memory_mb - 300))
|
||
|
||
# 确保内存限制至少为512MB
|
||
if [ "$build_memory_mb" -lt 512 ]; then
|
||
build_memory_mb=512
|
||
log_warning "可用内存不足,设置构建内存限制为512MB"
|
||
fi
|
||
|
||
log_info "设置构建内存限制为 ${build_memory_mb}MB (总内存: ${total_memory_mb}MB)"
|
||
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
export NODE_OPTIONS=\"--max-old-space-size=$build_memory_mb\"
|
||
cd /home/jingrow/jsite/$SITE_NAME
|
||
npm run build
|
||
"
|
||
|
||
if [ $? -ne 0 ]; then
|
||
log_error "项目构建失败"
|
||
return 1
|
||
fi
|
||
|
||
log_success "项目构建完成 (内存限制: ${build_memory_mb}MB)"
|
||
}
|
||
|
||
# 9. 显示部署信息
|
||
show_deployment_info() {
|
||
log_success "=== 部署完成 ==="
|
||
|
||
log_info "部署信息:"
|
||
echo " - 用户: jingrow"
|
||
echo " - jsite目录: /home/jingrow/jsite"
|
||
echo " - 项目目录: /home/jingrow/jsite/$SITE_NAME"
|
||
echo " - 项目端口: $(get_or_assign_port "$SITE_NAME")"
|
||
echo " - Traefik目录: /home/jingrow/traefik-docker"
|
||
echo " - Traefik管理界面: http://localhost:8080"
|
||
echo " - Traefik网站配置: /home/jingrow/traefik-docker/conf.d/website/$SITE_NAME.yml"
|
||
echo " - PM2配置文件: /home/jingrow/jsite/$SITE_NAME/ecosystem.config.cjs"
|
||
echo " - PM2日志目录: /home/jingrow/jsite/$SITE_NAME/logs"
|
||
|
||
if [ -n "$PUBLIC_IP" ]; then
|
||
echo " - 指定公网IP: $PUBLIC_IP"
|
||
fi
|
||
|
||
log_info ".env文件配置:"
|
||
if [ -f "/home/jingrow/jsite/$SITE_NAME/.env" ]; then
|
||
echo " - PUBLIC_SITE_URL: $(grep '^PUBLIC_SITE_URL=' "/home/jingrow/jsite/$SITE_NAME/.env" | cut -d'=' -f2-)"
|
||
echo " - BACKEND_SITE_NAME: $(grep '^BACKEND_SITE_NAME=' "/home/jingrow/jsite/$SITE_NAME/.env" | cut -d'=' -f2-)"
|
||
echo " - BACKEND_SERVER_URL: $(grep '^BACKEND_SERVER_URL=' "/home/jingrow/jsite/$SITE_NAME/.env" | cut -d'=' -f2-)"
|
||
echo " - REVALIDATE_TOKEN: $(grep '^REVALIDATE_TOKEN=' "/home/jingrow/jsite/$SITE_NAME/.env" | cut -d'=' -f2-)"
|
||
echo " - BACKEND_API_KEY: $(grep '^BACKEND_API_KEY=' "/home/jingrow/jsite/$SITE_NAME/.env" | cut -d'=' -f2-)"
|
||
echo " - BACKEND_API_SECRET: $(grep '^BACKEND_API_SECRET=' "/home/jingrow/jsite/$SITE_NAME/.env" | cut -d'=' -f2-)"
|
||
else
|
||
echo " - .env文件不存在"
|
||
fi
|
||
|
||
log_info "PM2管理命令:"
|
||
echo " - 查看状态: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 list'"
|
||
echo " - 查看日志: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 logs $SITE_NAME'"
|
||
echo " - 重启项目: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 restart $SITE_NAME'"
|
||
echo " - 停止项目: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 stop $SITE_NAME'"
|
||
echo " - 删除项目: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 delete $SITE_NAME'"
|
||
echo " - 监控界面: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 monit'"
|
||
|
||
log_info "Traefik配置信息:"
|
||
echo " - 网站配置文件: /home/jingrow/traefik-docker/conf.d/website/$SITE_NAME.yml"
|
||
echo " - 访问域名: $SITE_URL"
|
||
echo " - 后端端口: $(get_or_assign_port "$SITE_NAME")"
|
||
|
||
# 显示当前端口分配情况
|
||
show_port_assignments
|
||
|
||
# 显示PM2状态
|
||
log_info "当前PM2状态:"
|
||
su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list
|
||
" 2>/dev/null || log_warning "无法获取PM2状态,请手动执行: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 list'"
|
||
|
||
log_info "下一步操作:"
|
||
echo " 1. 进入项目目录: cd /home/jingrow/jsite/$SITE_NAME"
|
||
echo " 2. 查看PM2状态: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 list'"
|
||
echo " 3. 查看项目日志: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 logs $SITE_NAME'"
|
||
echo " 4. 访问Traefik管理界面: http://localhost:8080"
|
||
echo " 5. 访问项目: $SITE_URL"
|
||
echo " 6. 检查traefik配置: cat /home/jingrow/traefik-docker/conf.d/website/$SITE_NAME.yml"
|
||
echo " 7. 查看端口分配: cat /home/jingrow/jsite/site_port.json"
|
||
|
||
log_warning "注意:请确保防火墙允许80、443、8080端口访问"
|
||
log_success "部署脚本执行完成!"
|
||
}
|
||
|
||
# ========================================
|
||
# jsite管理功能函数
|
||
# ========================================
|
||
|
||
# 生成Host规则
|
||
generate_host_rule() {
|
||
local domain="$1"
|
||
|
||
# 移除协议前缀
|
||
domain=$(echo "$domain" | sed -E 's|^https?://||')
|
||
# 移除端口号
|
||
domain=$(echo "$domain" | sed -E 's|:[0-9]+$||')
|
||
|
||
# 检查是否为一级域名
|
||
if [[ "$domain" =~ ^[^.]+\.[^.]+$ ]]; then
|
||
echo "Host(\`$domain\`) || Host(\`www.$domain\`)"
|
||
else
|
||
echo "Host(\`$domain\`)"
|
||
fi
|
||
}
|
||
|
||
# 创建traefik网站配置文件
|
||
create_traefik_website_config() {
|
||
local site_name="$1"
|
||
local domain="$2"
|
||
local port="$3"
|
||
|
||
# 自动创建traefik配置目录(如果不存在)
|
||
if [ ! -d "$TRAEFIK_CONFIG_DIR" ]; then
|
||
log_info "创建Traefik配置目录: $TRAEFIK_CONFIG_DIR"
|
||
mkdir -p "$TRAEFIK_CONFIG_DIR"
|
||
chown jingrow:jingrow "$TRAEFIK_CONFIG_DIR"
|
||
chmod 755 "$TRAEFIK_CONFIG_DIR"
|
||
fi
|
||
|
||
local config_file="$TRAEFIK_CONFIG_DIR/$site_name.yml"
|
||
local host_rule=$(generate_host_rule "$domain")
|
||
|
||
# 获取本机IP地址
|
||
local host_ip=$(get_optimal_host_ip)
|
||
|
||
# 创建配置文件
|
||
cat > "$config_file" << EOF
|
||
http:
|
||
routers:
|
||
main-https:
|
||
rule: &host_rule $host_rule
|
||
entryPoints:
|
||
- websecure
|
||
service: main-service
|
||
tls:
|
||
certResolver: myresolver
|
||
|
||
main-http-redirect:
|
||
rule: *host_rule
|
||
entryPoints:
|
||
- web
|
||
middlewares:
|
||
- redirect-to-https
|
||
service: noop
|
||
|
||
services:
|
||
main-service:
|
||
loadBalancer:
|
||
servers:
|
||
- url: "http://$host_ip:$port"
|
||
noop:
|
||
loadBalancer:
|
||
servers:
|
||
- url: "http://127.0.0.1:65535" # 占位用,无实际后端,仅用于HTTP跳转
|
||
|
||
middlewares:
|
||
redirect-to-https:
|
||
redirectScheme:
|
||
scheme: https
|
||
permanent: true
|
||
EOF
|
||
|
||
chown "jingrow:jingrow" "$config_file" 2>/dev/null || true
|
||
chmod 644 "$config_file"
|
||
|
||
log_success "Traefik配置文件已创建: $config_file"
|
||
log_info "域名: $domain -> $host_ip:$port"
|
||
}
|
||
|
||
# 删除traefik网站配置文件
|
||
remove_traefik_website_config() {
|
||
local site_name="$1"
|
||
local config_file="$TRAEFIK_CONFIG_DIR/$site_name.yml"
|
||
|
||
if [ -f "$config_file" ]; then
|
||
rm -f "$config_file"
|
||
log_success "Traefik配置文件已删除: $config_file"
|
||
fi
|
||
}
|
||
|
||
# 重启traefik服务以加载新配置
|
||
restart_traefik() {
|
||
local traefik_dir="/home/jingrow/traefik-docker"
|
||
|
||
if [ -d "$traefik_dir" ] && [ -f "$traefik_dir/docker-compose.yml" ]; then
|
||
log_info "重启Traefik服务以加载新配置..."
|
||
su - "jingrow" -c "cd '$traefik_dir' && docker compose restart" 2>/dev/null || {
|
||
log_warning "无法重启Traefik服务,请手动重启"
|
||
}
|
||
fi
|
||
}
|
||
|
||
# 创建新网站
|
||
create_site() {
|
||
log_info "开始创建网站: $SITE_NAME"
|
||
log_info "Git仓库: $GIT_REPO"
|
||
log_info "域名: $SITE_URL"
|
||
|
||
# 检查网站是否已存在
|
||
if check_site_exists "$SITE_NAME"; then
|
||
log_error "网站 $SITE_NAME 已存在"
|
||
return 1
|
||
fi
|
||
|
||
# 获取并分配端口
|
||
local port=$(get_or_assign_port "$SITE_NAME")
|
||
|
||
# 克隆项目
|
||
log_info "克隆项目: $GIT_REPO"
|
||
if ! su - "jingrow" -c "cd '$JSITE_BASE_DIR' && git clone '$GIT_REPO' '$SITE_NAME'"; then
|
||
log_error "项目克隆失败"
|
||
return 1
|
||
fi
|
||
|
||
# 检查package.json是否存在
|
||
if [ ! -f "$JSITE_BASE_DIR/$SITE_NAME/package.json" ]; then
|
||
log_error "package.json文件不存在,这可能不是一个有效的Node.js项目"
|
||
rm -rf "$JSITE_BASE_DIR/$SITE_NAME"
|
||
return 1
|
||
fi
|
||
|
||
# 保存端口分配
|
||
save_port_assignment "$SITE_NAME" "$port"
|
||
|
||
# 创建.env文件
|
||
create_env_file
|
||
|
||
# 创建PM2配置文件
|
||
create_pm2_config
|
||
|
||
# 创建traefik配置文件
|
||
if create_traefik_website_config "$SITE_NAME" "$SITE_URL" "$port"; then
|
||
restart_traefik
|
||
fi
|
||
|
||
log_success "网站 $SITE_NAME 创建成功"
|
||
log_info "项目路径: $JSITE_BASE_DIR/$SITE_NAME"
|
||
log_info "分配端口: $port"
|
||
log_info "绑定域名: $SITE_URL"
|
||
|
||
# 提示下一步操作
|
||
log_info "下一步操作:"
|
||
echo " 1. 构建项目: $0 --mode build --site-name $SITE_NAME"
|
||
echo " 2. 启动项目: $0 --mode start --site-name $SITE_NAME"
|
||
}
|
||
|
||
# 创建并启动网站(包含构建和启动)
|
||
create_and_start_site() {
|
||
log_info "开始创建并启动网站: $SITE_NAME"
|
||
log_info "Git仓库: $GIT_REPO"
|
||
log_info "域名: $SITE_URL"
|
||
|
||
# 检查网站是否已存在
|
||
if check_site_exists "$SITE_NAME"; then
|
||
log_error "网站 $SITE_NAME 已存在"
|
||
return 1
|
||
fi
|
||
|
||
# 获取并分配端口
|
||
local port=$(get_or_assign_port "$SITE_NAME")
|
||
|
||
# 克隆项目
|
||
log_info "克隆项目: $GIT_REPO"
|
||
if ! su - jingrow -c "cd '$JSITE_BASE_DIR' && git clone '$GIT_REPO' '$SITE_NAME'"; then
|
||
log_error "项目克隆失败"
|
||
return 1
|
||
fi
|
||
|
||
# 检查package.json是否存在
|
||
if [ ! -f "$JSITE_BASE_DIR/$SITE_NAME/package.json" ]; then
|
||
log_error "package.json文件不存在,这可能不是一个有效的Node.js项目"
|
||
rm -rf "$JSITE_BASE_DIR/$SITE_NAME"
|
||
return 1
|
||
fi
|
||
|
||
# 保存端口分配
|
||
save_port_assignment "$SITE_NAME" "$port"
|
||
|
||
# 创建.env文件
|
||
create_env_file
|
||
|
||
# 创建PM2配置文件
|
||
create_pm2_config
|
||
|
||
# 创建traefik配置文件
|
||
if create_traefik_website_config "$SITE_NAME" "$SITE_URL" "$port"; then
|
||
restart_traefik
|
||
fi
|
||
|
||
# 安装项目依赖
|
||
log_info "安装项目依赖..."
|
||
if ! su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
cd '$JSITE_BASE_DIR/$SITE_NAME'
|
||
npm install
|
||
"; then
|
||
log_error "依赖安装失败"
|
||
return 1
|
||
fi
|
||
|
||
# 构建项目
|
||
log_info "构建项目..."
|
||
if ! su - jingrow -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
cd '$JSITE_BASE_DIR/$SITE_NAME'
|
||
npm run build
|
||
"; then
|
||
log_error "项目构建失败"
|
||
return 1
|
||
fi
|
||
|
||
# 启动项目
|
||
log_info "启动项目..."
|
||
if ! start_project_with_pm2; then
|
||
log_error "项目启动失败"
|
||
return 1
|
||
fi
|
||
|
||
log_success "网站 $SITE_NAME 创建、构建和启动成功"
|
||
log_info "项目路径: $JSITE_BASE_DIR/$SITE_NAME"
|
||
log_info "分配端口: $port"
|
||
log_info "绑定域名: $SITE_URL"
|
||
|
||
# 显示部署信息
|
||
log_info "部署信息:"
|
||
echo " - 用户: jingrow"
|
||
echo " - jsite目录: $JSITE_BASE_DIR"
|
||
echo " - 项目目录: $JSITE_BASE_DIR/$SITE_NAME"
|
||
echo " - 项目端口: $port"
|
||
echo " - Traefik目录: /home/jingrow/traefik-docker"
|
||
echo " - Traefik管理界面: http://localhost:8080"
|
||
echo " - Traefik网站配置: $TRAEFIK_CONFIG_DIR/$SITE_NAME.yml"
|
||
echo " - PM2配置文件: $JSITE_BASE_DIR/$SITE_NAME/ecosystem.config.cjs"
|
||
echo " - PM2日志目录: $JSITE_BASE_DIR/$SITE_NAME/logs"
|
||
|
||
log_info "PM2管理命令:"
|
||
echo " - 查看状态: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 list'"
|
||
echo " - 查看日志: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 logs $SITE_NAME'"
|
||
echo " - 重启项目: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 restart $SITE_NAME'"
|
||
echo " - 停止项目: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 stop $SITE_NAME'"
|
||
echo " - 删除项目: su - jingrow -c 'export NVM_DIR=\"\$HOME/.nvm\" && [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && pm2 delete $SITE_NAME'"
|
||
|
||
log_info "访问信息:"
|
||
echo " - 网站域名: $SITE_URL"
|
||
echo " - 本地访问: http://localhost:$port"
|
||
|
||
log_success "网站创建、构建和启动完成!"
|
||
}
|
||
|
||
# 删除网站
|
||
delete_site() {
|
||
log_info "开始删除网站: $SITE_NAME"
|
||
|
||
# 停止PM2进程
|
||
local status=$(get_site_status "$SITE_NAME")
|
||
if [ "$status" = "running" ]; then
|
||
log_info "停止运行中的进程..."
|
||
if ! stop_site; then
|
||
log_warning "停止进程失败,继续删除操作"
|
||
fi
|
||
elif [ "$status" = "not_exists" ]; then
|
||
log_warning "网站 $SITE_NAME 不存在,跳过停止操作"
|
||
else
|
||
log_info "网站已停止,无需停止操作"
|
||
fi
|
||
|
||
# 删除PM2配置
|
||
log_info "删除PM2配置..."
|
||
su - "jingrow" -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 delete '$SITE_NAME' 2>/dev/null || true
|
||
"
|
||
log_success "PM2配置已清理"
|
||
|
||
# 删除traefik配置文件
|
||
log_info "删除Traefik配置文件..."
|
||
if remove_traefik_website_config "$SITE_NAME"; then
|
||
log_success "Traefik配置文件已删除"
|
||
else
|
||
log_warning "Traefik配置文件删除失败或不存在"
|
||
fi
|
||
|
||
# 删除端口分配记录
|
||
log_info "删除端口分配记录..."
|
||
local port_file="$JSITE_BASE_DIR/site_port.json"
|
||
# 确保jq工具已安装
|
||
ensure_jq_installed
|
||
|
||
if [ -f "$port_file" ] && command -v jq &> /dev/null; then
|
||
if jq "del(.$SITE_NAME)" "$port_file" > "${port_file}.tmp" 2>/dev/null; then
|
||
mv "${port_file}.tmp" "$port_file"
|
||
chown "jingrow:jingrow" "$port_file" 2>/dev/null || true
|
||
log_success "端口分配记录已删除"
|
||
else
|
||
log_warning "端口分配记录删除失败"
|
||
fi
|
||
else
|
||
log_warning "端口分配文件不存在或jq命令不可用"
|
||
fi
|
||
|
||
# 删除项目目录
|
||
log_info "删除项目目录: $JSITE_BASE_DIR/$SITE_NAME"
|
||
if [ -d "$JSITE_BASE_DIR/$SITE_NAME" ]; then
|
||
if rm -rf "$JSITE_BASE_DIR/$SITE_NAME"; then
|
||
log_success "项目目录已删除"
|
||
else
|
||
log_error "删除项目目录失败"
|
||
return 1
|
||
fi
|
||
else
|
||
log_warning "项目目录不存在"
|
||
fi
|
||
|
||
# 重启traefik以移除配置
|
||
log_info "重启Traefik服务..."
|
||
if restart_traefik; then
|
||
log_success "Traefik服务已重启"
|
||
else
|
||
log_warning "重启Traefik失败,请手动重启"
|
||
fi
|
||
|
||
log_success "网站 $SITE_NAME 已删除"
|
||
}
|
||
|
||
# 构建网站
|
||
build_site() {
|
||
log_info "构建网站: $SITE_NAME"
|
||
|
||
# 安装依赖
|
||
log_info "安装依赖..."
|
||
if ! su - "jingrow" -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
cd '$JSITE_BASE_DIR/$SITE_NAME'
|
||
npm install
|
||
"; then
|
||
log_error "依赖安装失败"
|
||
return 1
|
||
fi
|
||
|
||
# 构建项目
|
||
log_info "构建项目..."
|
||
if ! su - "jingrow" -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
cd '$JSITE_BASE_DIR/$SITE_NAME'
|
||
npm run build
|
||
"; then
|
||
log_error "项目构建失败"
|
||
return 1
|
||
fi
|
||
|
||
log_success "网站 $SITE_NAME 构建完成"
|
||
}
|
||
|
||
# 启动网站
|
||
start_site() {
|
||
log_info "启动网站: $SITE_NAME"
|
||
restart_site
|
||
return $?
|
||
}
|
||
|
||
# 停止网站
|
||
stop_site() {
|
||
local status=$(get_site_status "$SITE_NAME")
|
||
if [ "$status" = "not_exists" ]; then
|
||
log_error "网站 $SITE_NAME 不存在"
|
||
return 1
|
||
fi
|
||
|
||
if [ "$status" = "stopped" ]; then
|
||
log_warning "网站 $SITE_NAME 已停止"
|
||
return 0
|
||
fi
|
||
|
||
log_info "停止网站: $SITE_NAME"
|
||
|
||
if ! su - "jingrow" -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 stop '$SITE_NAME'
|
||
"; then
|
||
log_error "网站停止失败"
|
||
return 1
|
||
fi
|
||
|
||
log_success "网站 $SITE_NAME 已停止"
|
||
}
|
||
|
||
# 重启网站
|
||
restart_site() {
|
||
local status=$(get_site_status "$SITE_NAME")
|
||
if [ "$status" = "not_exists" ]; then
|
||
log_error "网站 $SITE_NAME 不存在"
|
||
return 1
|
||
fi
|
||
|
||
log_info "重启网站: $SITE_NAME"
|
||
|
||
if [ "$status" = "running" ]; then
|
||
if ! su - "jingrow" -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 restart '$SITE_NAME'
|
||
"; then
|
||
log_error "网站重启失败"
|
||
return 1
|
||
fi
|
||
else
|
||
# 如果未运行,则启动
|
||
start_site
|
||
return $?
|
||
fi
|
||
|
||
log_success "网站 $SITE_NAME 重启成功"
|
||
}
|
||
|
||
# 查看网站状态
|
||
show_site_status() {
|
||
local status=$(get_site_status "$SITE_NAME")
|
||
|
||
case "$status" in
|
||
"not_exists")
|
||
log_error "网站 $SITE_NAME 不存在"
|
||
return 1
|
||
;;
|
||
"running")
|
||
log_success "网站 $SITE_NAME 正在运行"
|
||
;;
|
||
"stopped")
|
||
log_warning "网站 $SITE_NAME 已停止"
|
||
;;
|
||
esac
|
||
|
||
# 显示PM2详细状态
|
||
if [ "$status" != "not_exists" ]; then
|
||
log_info "PM2状态:"
|
||
su - "jingrow" -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 list | grep -E '(App name|$SITE_NAME)' || echo '未找到PM2进程'
|
||
"
|
||
fi
|
||
}
|
||
|
||
# 列出所有网站
|
||
list_sites() {
|
||
log_info "jsite网站列表:"
|
||
|
||
if [ ! -d "$JSITE_BASE_DIR" ] || [ -z "$(ls -A "$JSITE_BASE_DIR" 2>/dev/null)" ]; then
|
||
echo " 暂无网站"
|
||
return 0
|
||
fi
|
||
|
||
for site_dir in "$JSITE_BASE_DIR"/*; do
|
||
if [ -d "$site_dir" ]; then
|
||
local site_name=$(basename "$site_dir")
|
||
local status=$(get_site_status "$site_name")
|
||
|
||
case "$status" in
|
||
"running")
|
||
echo " - $site_name (运行中)"
|
||
;;
|
||
"stopped")
|
||
echo " - $site_name (已停止)"
|
||
;;
|
||
*)
|
||
echo " - $site_name (未知状态)"
|
||
;;
|
||
esac
|
||
fi
|
||
done
|
||
}
|
||
|
||
# 查看网站日志
|
||
show_site_logs() {
|
||
local status=$(get_site_status "$SITE_NAME")
|
||
if [ "$status" = "not_exists" ]; then
|
||
log_error "网站 $SITE_NAME 不存在"
|
||
return 1
|
||
fi
|
||
|
||
log_info "网站 $SITE_NAME 的日志:"
|
||
su - "jingrow" -c "
|
||
export NVM_DIR=\"\$HOME/.nvm\"
|
||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\"
|
||
pm2 logs '$SITE_NAME'
|
||
"
|
||
}
|
||
|
||
# ========================================
|
||
# 主函数
|
||
# ========================================
|
||
|
||
# 主函数
|
||
main() {
|
||
log_info "开始jsite管理脚本..."
|
||
echo ""
|
||
log_info "执行模式: $MODE"
|
||
log_info "目标网站: $SITE_NAME"
|
||
echo ""
|
||
|
||
# 检查用户和基础目录
|
||
check_root
|
||
create_jingrow_user
|
||
check_jsite_base_dir
|
||
|
||
# 根据mode执行不同的任务
|
||
case "$MODE" in
|
||
"deploy")
|
||
# 完整部署模式
|
||
log_info "执行完整部署模式..."
|
||
echo ""
|
||
log_info "部署参数:"
|
||
echo " - 项目名称: $SITE_NAME"
|
||
echo " - Git仓库: $GIT_REPO"
|
||
echo " - Node.js版本: $NODE_VERSION"
|
||
echo " - 跳过Docker: $SKIP_DOCKER"
|
||
echo " - 跳过Traefik: $SKIP_TRAEFIK"
|
||
echo " - 跳过依赖安装: $SKIP_DEPENDENCIES"
|
||
echo " - 跳过PM2: $SKIP_PM2"
|
||
echo " - 强制更新: $FORCE_UPDATE"
|
||
echo ""
|
||
log_info "端口配置:"
|
||
echo " - 起始端口: $START_PORT"
|
||
echo " - 端口增量: $PORT_INCREMENT"
|
||
echo ""
|
||
log_info ".env文件参数:"
|
||
echo " - 网站URL: $SITE_URL"
|
||
echo " - 站点名称: $SITE_NAME"
|
||
echo " - 服务器URL: $BACKEND_SERVER_URL"
|
||
echo " - 重新验证令牌: $REVALIDATE_TOKEN"
|
||
echo " - API密钥: $BACKEND_API_KEY"
|
||
echo " - API密钥: $BACKEND_API_SECRET"
|
||
echo ""
|
||
|
||
if [ "$SKIP_DOCKER" = false ]; then
|
||
install_docker
|
||
else
|
||
log_warning "跳过Docker安装"
|
||
fi
|
||
|
||
# 安装jq工具(用于处理JSON文件)
|
||
install_jq
|
||
|
||
install_nodejs
|
||
|
||
if ! clone_jsite_project; then
|
||
log_error "项目克隆失败,部署终止"
|
||
exit 1
|
||
fi
|
||
|
||
create_env_file
|
||
|
||
if [ "$SKIP_DEPENDENCIES" = false ]; then
|
||
if ! install_project_dependencies; then
|
||
log_error "项目依赖安装失败,部署终止"
|
||
exit 1
|
||
fi
|
||
else
|
||
log_warning "跳过项目依赖安装"
|
||
fi
|
||
|
||
# PM2安装和项目启动(在项目准备完成后)
|
||
if [ "$SKIP_PM2" = false ]; then
|
||
log_info "开始PM2安装和项目启动..."
|
||
install_pm2
|
||
create_pm2_config
|
||
if ! start_project_with_pm2; then
|
||
log_error "PM2启动失败,部署终止"
|
||
log_info "请检查以下内容:"
|
||
log_info "1. 项目依赖是否正确安装"
|
||
log_info "2. package.json中是否有start脚本"
|
||
log_info "3. 项目端口是否被占用"
|
||
log_info "4. 查看PM2日志: pm2 logs $SITE_NAME"
|
||
exit 1
|
||
fi
|
||
else
|
||
log_warning "跳过PM2安装和项目启动"
|
||
fi
|
||
|
||
if [ "$SKIP_TRAEFIK" = false ]; then
|
||
install_traefik
|
||
# 直接创建traefik配置
|
||
local project_port=$(get_or_assign_port "$SITE_NAME")
|
||
create_traefik_website_config "$SITE_NAME" "$SITE_URL" "$project_port"
|
||
start_traefik
|
||
else
|
||
log_warning "跳过Traefik安装和启动"
|
||
fi
|
||
|
||
show_deployment_info
|
||
;;
|
||
|
||
"create")
|
||
# 创建新网站
|
||
create_site
|
||
;;
|
||
|
||
"create_and_start")
|
||
# 创建并启动网站(包含构建和启动)
|
||
create_and_start_site
|
||
;;
|
||
|
||
"delete")
|
||
# 删除网站
|
||
delete_site
|
||
;;
|
||
|
||
"build")
|
||
# 构建网站
|
||
build_site
|
||
;;
|
||
|
||
"start")
|
||
# 启动网站
|
||
start_site
|
||
;;
|
||
|
||
"stop")
|
||
# 停止网站
|
||
stop_site
|
||
;;
|
||
|
||
"restart")
|
||
# 重启网站
|
||
restart_site
|
||
;;
|
||
|
||
"status")
|
||
# 查看网站状态
|
||
show_site_status
|
||
;;
|
||
|
||
"list")
|
||
# 列出所有网站
|
||
list_sites
|
||
;;
|
||
|
||
"logs")
|
||
# 查看网站日志
|
||
show_site_logs
|
||
;;
|
||
|
||
*)
|
||
log_error "不支持的mode: $MODE"
|
||
echo "支持的mode: deploy, create, create_and_start, delete, build, start, stop, restart, status, list, logs"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
log_success "脚本执行完成!"
|
||
}
|
||
|
||
# 执行主函数
|
||
main "$@" |