# -*- coding: utf-8 -*- import subprocess import sys import time import os # 服务器配置 SERVER_HOST = "45.249.208.118" SERVER_USER = "root" SERVER_PASSWORD = "lxvhNYFZ4160" SERVER_PROJECT_DIR = "/root/youyidian-3d" LOCAL_PACKAGE = "project-deploy.tar.gz" def print_step(step, message): print(f"\n{'='*60}") print(f"步骤 {step}: {message}") print('='*60) def run_ssh_command(command): """执行SSH命令""" full_cmd = f'ssh {SERVER_USER}@{SERVER_HOST} "{command}"' print(f"\n执行命令: {command}\n") # 使用 plink 或 ssh (需要手动输入密码) result = subprocess.run(full_cmd, shell=True, capture_output=False, text=True) return result.returncode == 0 def main(): print("\n" + "="*60) print(" FLNXI素材库系统 - 自动部署脚本") print(f" 目标服务器: {SERVER_HOST}") print("="*60) # 检查部署包是否存在 if not os.path.exists(LOCAL_PACKAGE): print(f"\n[ERROR] Cannot find: {LOCAL_PACKAGE}") print("Please create package first") return False file_size = os.path.getsize(LOCAL_PACKAGE) / 1024 / 1024 print(f"\n[OK] Found package: {LOCAL_PACKAGE} ({file_size:.2f} MB)") # 步骤1: 上传文件 print_step(1, "上传部署包到服务器") print(f"正在上传 {LOCAL_PACKAGE} 到 {SERVER_HOST}:/root/") print(f"密码: {SERVER_PASSWORD}") print("\n请在提示时输入密码...") time.sleep(2) upload_cmd = f"scp {LOCAL_PACKAGE} {SERVER_USER}@{SERVER_HOST}:/root/" result = subprocess.run(upload_cmd, shell=True) if result.returncode != 0: print("\n[ERROR] Upload failed!") return False print("\n[OK] Upload success!") # 步骤2: 创建项目目录 print_step(2, "创建项目目录") commands = [ f"mkdir -p {SERVER_PROJECT_DIR}", f"cd {SERVER_PROJECT_DIR} && pwd" ] for cmd in commands: print(f"执行: {cmd}") result = subprocess.run( f'ssh {SERVER_USER}@{SERVER_HOST} "{"; ".join(commands)}"', shell=True ) # 步骤3: 备份现有文件 print_step(3, "备份现有上传文件") backup_cmd = f""" cd {SERVER_PROJECT_DIR} && \ if [ -d uploads ]; then \ mkdir -p backups && \ echo '正在备份uploads目录...' && \ cp -r uploads backups/uploads-$(date +%Y%m%d-%H%M%S) 2>/dev/null || true && \ echo '[OK] Backup complete'; \ else \ echo '[WARN] No uploads directory found, skip backup'; \ fi """ subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{backup_cmd}"', shell=True) # 步骤4: 解压文件 print_step(4, "解压新版本") extract_cmd = f""" cd {SERVER_PROJECT_DIR} && \ echo '正在解压项目文件...' && \ tar -xzf /root/{LOCAL_PACKAGE} && \ rm /root/{LOCAL_PACKAGE} && \ echo '[OK] Extract complete' && \ ls -lh """ subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{extract_cmd}"', shell=True) # 步骤5: 恢复上传目录 print_step(5, "恢复上传目录") restore_cmd = f""" cd {SERVER_PROJECT_DIR} && \ if [ -d backups ]; then \ latest=$(ls -td backups/uploads-* 2>/dev/null | head -1); \ if [ -n "$latest" ]; then \ echo "从 $latest 恢复上传文件..."; \ mkdir -p uploads; \ cp -r "$latest"/* uploads/ 2>/dev/null || true; \ echo '[OK] Restore complete'; \ fi; \ fi && \ mkdir -p uploads && \ chmod -R 777 uploads && \ echo '[OK] Permissions set' """ subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{restore_cmd}"', shell=True) # 步骤6: 查看当前Docker状态 print_step(6, "检查Docker状态") subprocess.run( f'ssh {SERVER_USER}@{SERVER_HOST} "docker ps -a"', shell=True ) # 步骤7: 停止旧容器 print_step(7, "停止旧容器") stop_cmd = f""" cd {SERVER_PROJECT_DIR} && \ echo '正在停止旧容器...' && \ docker compose down 2>/dev/null || docker-compose down 2>/dev/null || true && \ echo '[OK] Old containers stopped' """ subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{stop_cmd}"', shell=True) # 步骤8: 构建并启动新容器 print_step(8, "构建并启动新容器") print("[WARN] This step may take several minutes, please wait...") build_cmd = f""" cd {SERVER_PROJECT_DIR} && \ echo '正在构建镜像...' && \ docker compose up -d --build --force-recreate || docker-compose up -d --build --force-recreate && \ echo '[OK] Containers started' """ subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{build_cmd}"', shell=True) # 步骤9: 等待容器启动 print_step(9, "等待容器完全启动") print("等待20秒...") for i in range(20, 0, -1): print(f"\r剩余 {i} 秒...", end="", flush=True) time.sleep(1) print("\n") # 步骤10: 数据库迁移 print_step(10, "执行数据库迁移") migrate_cmd = f""" cd {SERVER_PROJECT_DIR} && \ echo '正在执行数据库迁移...' && \ docker compose exec -T app npx prisma db push --accept-data-loss --schema=/app/prisma/schema.prisma 2>/dev/null || \ docker-compose exec -T app npx prisma db push --accept-data-loss --schema=/app/prisma/schema.prisma 2>/dev/null || \ echo '[WARN] Database migration failed, may need manual execution' """ subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "{migrate_cmd}"', shell=True) # 步骤11: 查看最终状态 print_step(11, "查看部署结果") print("\n容器运行状态:") subprocess.run(f'ssh {SERVER_USER}@{SERVER_HOST} "docker ps"', shell=True) print("\n\n应用日志(最后30行):") subprocess.run( f'ssh {SERVER_USER}@{SERVER_HOST} "cd {SERVER_PROJECT_DIR} && (docker compose logs --tail=30 app 2>/dev/null || docker-compose logs --tail=30 app)"', shell=True ) # 完成 print("\n" + "="*60) print(" [OK] Deployment Complete!") print("="*60) print(f"\n访问地址: http://{SERVER_HOST}:端口号") print("(请根据docker-compose.yml中的端口配置访问)") print("\n常用命令:") print(f" 查看日志: ssh {SERVER_USER}@{SERVER_HOST} 'cd {SERVER_PROJECT_DIR} && docker compose logs -f app'") print(f" 重启服务: ssh {SERVER_USER}@{SERVER_HOST} 'cd {SERVER_PROJECT_DIR} && docker compose restart'") print(f" 停止服务: ssh {SERVER_USER}@{SERVER_HOST} 'cd {SERVER_PROJECT_DIR} && docker compose down'") print("="*60) return True if __name__ == "__main__": try: success = main() sys.exit(0 if success else 1) except KeyboardInterrupt: print("\n\n[INFO] User interrupted") sys.exit(1) except Exception as e: print(f"\n\n[ERROR] Error occurred: {e}") sys.exit(1)