#!/bin/bash # jsite前端自动化部署脚本 # 基于next.js + traefik开发 # # 后端调用示例: # 1. 基本部署: ./install_jsite.sh # 2. 指定项目: ./install_jsite.sh --site-name myproject -r http://git.example.com/myproject # 3. 跳过Docker: ./install_jsite.sh --skip-docker # 4. 跳过Traefik: ./install_jsite.sh --skip-traefik # 5. 强制更新: ./install_jsite.sh --force-update # 6. 完整参数: ./install_jsite.sh --site-name myproject -r http://git.example.com/myproject -n 18 --skip-docker --force-update # # 返回码: # 0 - 成功 # 1 - 参数错误 # 2 - 权限错误 # 3 - 其他错误 set -e # 遇到错误时退出 # 设置非交互式环境变量,避免交互式配置 export DEBIAN_FRONTEND=noninteractive export DEBCONF_NONINTERACTIVE_SEEN=true export UCF_FORCE_CONFNEW=1 export UCF_FORCE_CONFOLD=1 # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # 默认参数 GIT_REPO="http://git.jingrow.com:3000/jsite/jingrow" NODE_VERSION="22" SKIP_DOCKER=false SKIP_TRAEFIK=false SKIP_DEPENDENCIES=false FORCE_UPDATE=false SKIP_PM2=false # 端口管理参数 START_PORT=3001 PORT_INCREMENT=1 # .env文件参数 SITE_URL="starrbud.com" REVALIDATE_TOKEN="535bc122f3e364c" SITE_NAME="jingrow" SERVER_URL="https://admin.jingrow.com" API_KEY="535bc122f3e364c" API_SECRET="8629a3b12fc1cc2" # 显示帮助信息 show_help() { echo "用法: $0 [选项]" echo "" echo "选项:" echo " -r, --repo URL Git仓库地址" echo " -n, --node-version VERSION Node.js版本 (默认: 22)" echo " --skip-docker 跳过Docker安装" echo " --skip-traefik 跳过Traefik安装和启动" echo " --skip-dependencies 跳过项目依赖安装" echo " --skip-pm2 跳过PM2安装和启动" echo " --force-update 强制更新项目(如果已存在)" echo "" echo "端口配置:" echo " --start-port PORT 起始端口 (默认: 3001)" echo " --port-increment INCREMENT 端口增量 (默认: 1)" echo "" echo ".env文件配置:" echo " --site-url URL 网站URL (默认: http://192.168.2.200:3001)" echo " --revalidate-token TOKEN 重新验证令牌 (默认: 535bc122f3e364c)" echo " --site-name NAME 站点名称 (默认: jingrow)" echo " --server-url URL 服务器URL (默认: https://admin.jingrow.com)" echo " --api-key KEY API密钥 (默认: 535bc122f3e364c)" echo " --api-secret SECRET API密钥 (默认: 8629a3b12fc1cc2)" echo "" echo " -h, --help 显示此帮助信息" echo "" echo "示例:" echo " $0 --site-name myproject -r http://git.example.com/myproject" echo " $0 --skip-docker --skip-traefik" echo " $0 --force-update" echo " $0 --site-url http://example.com --site-name myproject" echo " $0 --start-port 3005 --port-increment 10" } # 解析命令行参数 parse_arguments() { while [[ $# -gt 0 ]]; do case $1 in -r|--repo) GIT_REPO="$2" shift 2 ;; -n|--node-version) NODE_VERSION="$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 ;; --start-port) START_PORT="$2" shift 2 ;; --port-increment) PORT_INCREMENT="$2" shift 2 ;; --site-url) SITE_URL="$2" shift 2 ;; --revalidate-token) REVALIDATE_TOKEN="$2" shift 2 ;; --site-name) SITE_NAME="$2" shift 2 ;; --server-url) SERVER_URL="$2" shift 2 ;; --api-key) API_KEY="$2" shift 2 ;; --api-secret) API_SECRET="$2" shift 2 ;; -h|--help) show_help exit 0 ;; *) echo -e "${RED}[ERROR]${NC} 未知参数: $1" show_help exit 1 ;; esac done } # 日志函数 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 } # 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..." # 检查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 " 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查询已分配的端口 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 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 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 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是否可用 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 # 移除已存在的条目(如果存在) grep -v "\"$site_name\"" "$port_file" > "${port_file}.tmp" 2>/dev/null || true mv "${port_file}.tmp" "$port_file" # 在最后一个大括号前添加新条目 sed -i "s/}$/ \"$site_name\": $port\n}/" "$port_file" 2>/dev/null || echo "{\"$site_name\": $port}" > "$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 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 "当前端口分配情况:" 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") # 构建本地地址URL(用于PUBLIC_SITE_URL) local public_site_url="http://127.0.0.1:$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 JINGROW_SITE_NAME=$SITE_NAME JINGROW_SERVER_URL=$SERVER_URL JINGROW_API_KEY=$API_KEY JINGROW_API_SECRET=$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 # 检查项目是否已经在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 ") if [ "$PM2_STATUS" != "0" ] && [ -n "$PM2_STATUS" ]; then if [ "$FORCE_UPDATE" = true ]; then log_warning "项目已在PM2中运行,重新启动..." su - jingrow -c " export NVM_DIR=\"\$HOME/.nvm\" [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" cd /home/jingrow/jsite/$SITE_NAME pm2 delete $SITE_NAME 2>/dev/null || true 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 else log_warning "项目已在PM2中运行,跳过启动" return 0 fi else log_info "启动新项目..." 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 fi # 等待一下让PM2启动完成 sleep 5 # 检查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_error "PM2启动失败,项目未正常运行" 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 # 保存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 # 将jingrow用户添加到docker组 usermod -aG docker jingrow log_success "将jingrow用户添加到docker组" } # 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 # 使用jingrow用户启动traefik su - jingrow -c "cd /home/jingrow/traefik-docker && docker compose up -d" log_success "traefik服务启动完成" } # 7.4. 解析SITE_URL并生成Host规则 generate_host_rule() { local site_url="$1" # 移除协议前缀(如果存在) local domain=$(echo "$site_url" | sed -E 's|^https?://||') # 移除端口号(如果存在) domain=$(echo "$domain" | sed -E 's|:[0-9]+$||') # 检查是否为一级域名(不包含子域名) if [[ "$domain" =~ ^[^.]+\.[^.]+$ ]]; then # 一级域名,添加 www 前缀 echo "Host(\`$domain\`) || Host(\`www.$domain\`)" else # 二级或更多级域名,只使用原域名 echo "Host(\`$domain\`)" fi } # 7.5. 创建traefik网站配置文件 create_traefik_website_config() { log_info "创建traefik网站配置文件..." # 获取项目端口 local project_port=$(get_or_assign_port "$SITE_NAME") # 生成Host规则 local host_rule=$(generate_host_rule "$SITE_URL") # 创建conf.d/website目录(如果不存在) local website_dir="/home/jingrow/traefik-docker/conf.d/website" if [ ! -d "$website_dir" ]; then mkdir -p "$website_dir" chown jingrow:jingrow "$website_dir" log_success "创建traefik website配置目录" fi # 创建网站配置文件 local config_file="$website_dir/$SITE_NAME.yml" # 检查配置文件是否已存在 if [ -f "$config_file" ]; then if [ "$FORCE_UPDATE" = true ]; then log_warning "traefik网站配置文件已存在,强制更新..." else log_warning "traefik网站配置文件已存在,跳过创建" return fi fi # 创建网站配置文件 cat > "$config_file" << EOF # $SITE_NAME 网站配置 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://localhost:$project_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" chmod 644 "$config_file" log_success "traefik网站配置文件创建完成: $config_file (端口: $project_port)" log_info "Host规则: $host_rule" # 重启traefik以加载新配置 log_info "重启traefik以加载新配置..." su - jingrow -c "cd /home/jingrow/traefik-docker && docker compose restart" 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 "=== 部署完成 ===" echo "" 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" echo "" log_info ".env文件配置:" echo " - PUBLIC_SITE_URL: $SITE_URL" echo " - JINGROW_SITE_NAME: $SITE_NAME" echo " - JINGROW_SERVER_URL: $SERVER_URL" echo " - REVALIDATE_TOKEN: $REVALIDATE_TOKEN" echo " - JINGROW_API_KEY: $API_KEY" echo " - JINGROW_API_SECRET: $API_SECRET" echo "" log_info "PM2管理命令:" echo " - 查看状态: pm2 list" echo " - 查看日志: pm2 logs $SITE_NAME" echo " - 重启项目: pm2 restart $SITE_NAME" echo " - 停止项目: pm2 stop $SITE_NAME" echo " - 删除项目: pm2 delete $SITE_NAME" echo " - 监控界面: pm2 monit" echo "" log_info "Traefik配置信息:" echo " - 网站配置文件: /home/jingrow/traefik-docker/conf.d/website/$SITE_NAME.yml" echo " - 访问域名: $(generate_host_rule "$SITE_URL" | sed 's/Host(`//g' | sed 's/`)//g' | sed 's/ || / 或 /g')" echo " - 后端端口: $(get_or_assign_port "$SITE_NAME")" echo "" # 显示端口分配情况 show_port_assignments echo "" log_info "下一步操作:" echo " 1. 进入项目目录: cd /home/jingrow/jsite/$SITE_NAME" echo " 2. 查看PM2状态: pm2 list" echo " 3. 查看项目日志: 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" echo "" log_warning "注意:请确保防火墙允许80、443、8080端口访问" } # 主函数 main() { log_info "开始jsite前端自动化部署..." 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: $SERVER_URL" echo " - 重新验证令牌: $REVALIDATE_TOKEN" echo " - API密钥: $API_KEY" echo " - API密钥: $API_SECRET" echo "" check_root create_jingrow_user if [ "$SKIP_DOCKER" = false ]; then install_docker else log_warning "跳过Docker安装" fi install_nodejs if [ "$SKIP_PM2" = false ]; then install_pm2 else log_warning "跳过PM2安装" fi 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 if [ "$SKIP_PM2" = false ]; then 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 start_traefik create_traefik_website_config else log_warning "跳过Traefik安装和启动" fi show_deployment_info log_success "部署脚本执行完成!" } # 执行主函数 parse_arguments "$@" main "$@"