#!/bin/bash # 部署配置 SERVER_IP="180.76.240.104" SSH_PORT="22" # SSH连接端口 SERVER_USER="root" # 根据实际情况修改用户名 REMOTE_DIR="/var/www/chinese-family-tree" # 服务器上的部署目录 APP_PORT="8006" # Next.js 应用端口(对外访问端口) SSH_HOST="baidu-server" # 数据库配置 DB_NAME="family_tree" DB_USER="family_tree_user" DB_PASSWORD="FamilyTree2024" # 简化密码,避免特殊字符问题 DB_PASSWORD_ENCODED="FamilyTree2024" # URL编码后的密码(如果密码有特殊字符需要编码) # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}中国家谱系统 - 一键部署脚本${NC}" echo -e "${GREEN}========================================${NC}" # 0. 部署前检查 echo -e "\n${YELLOW}[0/9] 部署前检查...${NC}" # 检查必要命令 REQUIRED_COMMANDS=("pnpm" "rsync" "ssh" "nc") for cmd in "${REQUIRED_COMMANDS[@]}"; do if ! command -v $cmd &> /dev/null; then echo -e "${RED}✗ 缺少必要命令: $cmd${NC}" echo -e "${YELLOW}请先安装: $cmd${NC}" exit 1 fi done # 检查本地.env文件 if [ ! -f .env ]; then echo -e "${RED}✗ 本地.env文件不存在${NC}" echo -e "${YELLOW}请先创建.env文件配置数据库等信息${NC}" exit 1 fi echo -e "${GREEN}✓ 部署前检查通过${NC}" # 1. 检查并创建SSH密钥 echo -e "\n${YELLOW}[1/9] 检查SSH密钥...${NC}" echo -e "${GREEN}✓ 使用已配置的SSH免密登录(~/.ssh/config: ${SSH_HOST})${NC}" # 2. 测试服务器连接 echo -e "\n${YELLOW}[2/9] 测试服务器连接...${NC}" echo -e "${YELLOW}正在测试 ${SERVER_USER}@${SERVER_IP}:${SSH_PORT}...${NC}" # 测试端口是否开放 nc -z -w5 $SERVER_IP $SSH_PORT 2>/dev/null if [ $? -ne 0 ]; then echo -e "${RED}✗ 无法连接到SSH端口 ${SSH_PORT}${NC}" echo -e "${YELLOW}请检查:${NC}" echo -e " 1. 服务器IP是否正确: ${SERVER_IP}" echo -e " 2. SSH端口是否正确: ${SSH_PORT}" echo -e " 3. 服务器防火墙是否开放端口 ${SSH_PORT}" echo -e " 4. 服务器SSH服务是否运行" echo -e "\n${YELLOW}提示:可以尝试手动连接测试:${NC}" echo -e " ssh -p ${SSH_PORT} ${SERVER_USER}@${SERVER_IP}" exit 1 fi echo -e "${GREEN}✓ SSH端口 ${SSH_PORT} 可访问${NC}" # 3. 配置免密登录 echo -e "\n${YELLOW}[3/9] 配置免密登录...${NC}" # 检查是否已经配置免密登录 ssh -o BatchMode=yes -o ConnectTimeout=5 $SSH_HOST "echo '已配置免密登录'" 2>/dev/null if [ $? -eq 0 ]; then echo -e "${GREEN}✓ 免密登录已配置${NC}" else echo -e "${RED}✗ 免密登录未配置或无法使用${NC}" echo -e "${YELLOW}请先在本机配置好免密登录后再重试:${NC}" echo -e " ssh ${SSH_HOST}" exit 1 fi # 4. 本地构建 echo -e "\n${YELLOW}[4/9] 本地构建项目...${NC}" echo -e "${YELLOW}安装依赖...${NC}" pnpm install echo -e "${YELLOW}生成Prisma客户端...${NC}" npx prisma generate echo -e "${YELLOW}构建Next.js应用...${NC}" pnpm build if [ $? -eq 0 ]; then echo -e "${GREEN}✓ 项目构建成功${NC}" else echo -e "${RED}✗ 项目构建失败${NC}" exit 1 fi # 5. 在服务器上创建目录和安装依赖 echo -e "\n${YELLOW}[5/9] 配置服务器环境...${NC}" ssh $SSH_HOST << 'ENDSSH' # 安装Node.js (如果未安装) if ! command -v node &> /dev/null; then echo "安装Node.js..." curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt-get install -y nodejs fi # 安装pnpm (如果未安装) if ! command -v pnpm &> /dev/null; then echo "安装pnpm..." npm install -g pnpm fi # 安装PM2 (如果未安装) if ! command -v pm2 &> /dev/null; then echo "安装PM2..." npm install -g pm2 fi # 创建部署目录 mkdir -p /var/www/chinese-family-tree ENDSSH echo -e "${GREEN}✓ 服务器环境配置完成${NC}" # 6. 配置数据库 echo -e "\n${YELLOW}[6/9] 配置PostgreSQL数据库...${NC}" ssh $SSH_HOST bash << ENDSSH # 检查PostgreSQL是否安装 if ! command -v psql &> /dev/null; then echo "PostgreSQL未安装,正在安装..." apt-get update apt-get install -y postgresql postgresql-contrib systemctl start postgresql systemctl enable postgresql fi # 检查数据库是否存在 DB_EXISTS=\$(sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'") if [ "\$DB_EXISTS" = "1" ]; then echo "数据库 $DB_NAME 已存在" else echo "创建数据库和用户..." sudo -u postgres psql << EOF CREATE DATABASE $DB_NAME; CREATE USER $DB_USER WITH PASSWORD '$DB_PASSWORD'; ALTER DATABASE $DB_NAME OWNER TO $DB_USER; \\c $DB_NAME GRANT ALL ON SCHEMA public TO $DB_USER; GRANT USAGE, CREATE ON SCHEMA public TO $DB_USER; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO $DB_USER; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO $DB_USER; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO $DB_USER; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCES TO $DB_USER; EOF echo "✓ 数据库创建成功" fi ENDSSH if [ $? -eq 0 ]; then echo -e "${GREEN}✓ 数据库配置完成${NC}" else echo -e "${RED}✗ 数据库配置失败${NC}" exit 1 fi # 7. 上传文件到服务器 echo -e "\n${YELLOW}[7/9] 上传文件到服务器...${NC}" # 排除不需要上传的文件 rsync -avz --progress --delete \ --exclude 'node_modules' \ --exclude '.git' \ --exclude '.next/cache' \ --exclude '.env' \ --exclude '.env.local' \ --exclude '*.log' \ --exclude '*.md' \ --exclude 'test-*.sh' \ --exclude 'test-*.js' \ --exclude 'check-server.sh' \ --exclude 'deploy-manual.sh' \ --exclude 'setup-ssh-key.sh' \ --exclude '*.tar.gz' \ --exclude 'public/uploads' \ -e "ssh" \ ./ $SSH_HOST:$REMOTE_DIR/ if [ $? -eq 0 ]; then echo -e "${GREEN}✓ 文件上传成功${NC}" else echo -e "${RED}✗ 文件上传失败${NC}" exit 1 fi # 同步上传文件目录(如果存在) if [ -d "public/uploads" ] && [ "$(ls -A public/uploads 2>/dev/null)" ]; then echo -e "${YELLOW}同步上传文件目录...${NC}" rsync -avz --progress \ -e "ssh" \ ./public/uploads/ $SSH_HOST:$REMOTE_DIR/public/uploads/ if [ $? -eq 0 ]; then echo -e "${GREEN}✓ 上传文件同步成功${NC}" else echo -e "${YELLOW}⚠ 上传文件同步失败(非致命错误)${NC}" fi else echo -e "${YELLOW}⚠ 本地无上传文件需要同步${NC}" fi # 8. 配置环境变量 echo -e "\n${YELLOW}[8/9] 配置环境变量...${NC}" ssh $SSH_HOST bash << ENDSSH cd $REMOTE_DIR # 检查.env文件是否存在 if [ ! -f .env ]; then echo "创建.env文件..." # 生成随机密钥 NEXTAUTH_SECRET_VALUE=\$(openssl rand -base64 32) # 创建临时文件 cat > .env.tmp << 'ENVEOF' # 数据库配置 DATABASE_URL="postgresql://DB_USER_PLACEHOLDER:DB_PASSWORD_PLACEHOLDER@localhost:5432/DB_NAME_PLACEHOLDER" # NextAuth 配置 NEXTAUTH_URL="http://SERVER_IP_PLACEHOLDER:APP_PORT_PLACEHOLDER" NEXTAUTH_SECRET="NEXTAUTH_SECRET_PLACEHOLDER" NEXTAUTH_TRUST_HOST="true" # 邮箱服务器配置 SMTP_HOST="smtp.163.com" SMTP_PORT="465" SMTP_USER="cftservice@163.com" SMTP_PASS="SChncqpqS9TDPUD2" SMTP_FROM="cftservice@163.com" # 生产环境 NODE_ENV="production" ENVEOF # 替换占位符 sed "s/DB_USER_PLACEHOLDER/$DB_USER/g" .env.tmp | \\ sed "s/DB_PASSWORD_PLACEHOLDER/$DB_PASSWORD_ENCODED/g" | \\ sed "s/DB_NAME_PLACEHOLDER/$DB_NAME/g" | \\ sed "s/SERVER_IP_PLACEHOLDER/$SERVER_IP/g" | \\ sed "s/APP_PORT_PLACEHOLDER/$APP_PORT/g" | \\ sed "s/NEXTAUTH_SECRET_PLACEHOLDER/\$NEXTAUTH_SECRET_VALUE/g" > .env rm .env.tmp echo "✓ .env文件创建成功" else echo "✓ .env文件已存在" fi ENDSSH if [ $? -eq 0 ]; then echo -e "${GREEN}✓ 环境变量配置完成${NC}" else echo -e "${RED}✗ 环境变量配置失败${NC}" exit 1 fi # 9. 在服务器上安装依赖和启动应用 echo -e "\n${YELLOW}[9/9] 安装依赖并启动应用...${NC}" ssh $SSH_HOST bash << ENDSSH cd $REMOTE_DIR # 配置pnpm使用国内镜像(如果还没配置) pnpm config get registry | grep -q "npmmirror" || pnpm config set registry https://registry.npmmirror.com # 安装生产依赖(包括prisma) echo "安装依赖..." pnpm install --prod # 批准prisma构建脚本 echo "配置Prisma..." if ! command -v prisma &> /dev/null; then # 如果prisma命令不存在,安装完整依赖 pnpm install fi # 生成Prisma客户端 echo "生成Prisma客户端..." npx prisma generate || pnpm exec prisma generate # 运行数据库迁移 echo "运行数据库迁移..." npx prisma migrate deploy || pnpm exec prisma migrate deploy # 确保数据库用户有正确的权限 echo "检查数据库权限..." sudo -u postgres psql -d $DB_NAME << 'SQLEOF' GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO $DB_USER; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO $DB_USER; GRANT USAGE, CREATE ON SCHEMA public TO $DB_USER; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON TABLES TO $DB_USER; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL PRIVILEGES ON SEQUENCES TO $DB_USER; SQLEOF echo "✓ 数据库权限已确认" # 使用PM2启动应用(监听指定端口) echo "启动应用..." pm2 delete chinese-family-tree 2>/dev/null || true PORT=$APP_PORT pm2 start npm --name "chinese-family-tree" -- start # 保存PM2配置 pm2 save # 配置开机自启(如果还没配置) if ! systemctl is-enabled pm2-root &>/dev/null; then pm2 startup systemd -u root --hp /root fi echo "✓ 应用已启动!" ENDSSH if [ $? -eq 0 ]; then echo -e "\n${GREEN}========================================${NC}" echo -e "${GREEN}✓ 部署成功!${NC}" echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}应用访问地址: http://${SERVER_IP}:${APP_PORT}${NC}" echo -e "${YELLOW}提示:${NC}" echo -e " - 查看应用日志: ssh ${SSH_HOST} 'pm2 logs chinese-family-tree'" echo -e " - 重启应用: ssh ${SSH_HOST} 'pm2 restart chinese-family-tree'" echo -e " - 停止应用: ssh ${SSH_HOST} 'pm2 stop chinese-family-tree'" echo -e "${GREEN}========================================${NC}" else echo -e "${RED}✗ 部署失败${NC}" exit 1 fi