#!/usr/bin/env python3 """ 上传所有代码到服务器并部署 """ import requests import time import hashlib import os import base64 import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) BT_PANEL = "http://47.107.172.23:8888" BT_API_KEY = "PKdfnaInQL0P5ghB8SvwbrGcIpXWaEvq" DEPLOY_PATH = "/www/wwwroot/ai-interview" LOCAL_PROJECT = "/Users/a111/Documents/AgentWD/projects/011-ai-interview-2601" def bt_api(action, data=None, timeout=60): if data is None: data = {} request_time = int(time.time()) request_token = hashlib.md5( f"{request_time}{hashlib.md5(BT_API_KEY.encode()).hexdigest()}".encode() ).hexdigest() data['request_time'] = request_time data['request_token'] = request_token url = f"{BT_PANEL}/{action}" try: response = requests.post(url, data=data, timeout=timeout, verify=False) try: return response.json() except: return {"status": True, "msg": response.text[:1000]} except Exception as e: return {"status": False, "msg": str(e)} def run_task(shell_body, task_name, wait_time=30): result = bt_api("crontab?action=AddCrontab", { "name": task_name, "type": "minute-n", "where1": "1", "sType": "toShell", "sBody": shell_body, }) if not result.get("status") or not result.get("id"): print(f" 创建任务失败: {result}") return None cron_id = result["id"] bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=600) print(f" 任务 {cron_id} 启动,等待 {wait_time}s...") time.sleep(wait_time) log_result = bt_api("crontab?action=GetLogs", {"id": cron_id}) bt_api("crontab?action=DelCrontab", {"id": cron_id}) return log_result def upload_file_via_task(remote_path, content): """通过计划任务上传文件""" encoded = base64.b64encode(content.encode('utf-8')).decode('utf-8') dir_path = os.path.dirname(remote_path) script = f"""#!/bin/bash mkdir -p {dir_path} echo '{encoded}' | base64 -d > {remote_path} """ result = bt_api("crontab?action=AddCrontab", { "name": f"upload_{int(time.time())}", "type": "minute-n", "where1": "1", "sType": "toShell", "sBody": script, }) if result.get("status") and result.get("id"): cron_id = result["id"] bt_api("crontab?action=StartTask", {"id": cron_id}, timeout=60) time.sleep(2) bt_api("crontab?action=DelCrontab", {"id": cron_id}) return True return False def main(): print("=" * 60) print("🚀 完整部署 - 上传所有代码") print("=" * 60) # 前端文件列表 frontend_files = [ "package.json", "pnpm-lock.yaml", "vite.config.ts", "tsconfig.json", "tsconfig.node.json", "tailwind.config.js", "postcss.config.js", "index.html", "src/main.ts", "src/App.vue", "src/styles/index.css", "src/router/index.ts", "src/api/index.ts", "src/api/request.ts", "src/api/candidate.ts", "src/composables/index.ts", "src/composables/useRTC.ts", "src/composables/useCozeRealtime.ts", "src/types/index.ts", "src/types/env.d.ts", "src/layouts/AdminLayout.vue", "src/layouts/InterviewLayout.vue", "src/pages/interview/index.vue", "src/pages/interview/info.vue", "src/pages/interview/call.vue", "src/pages/interview/complete.vue", "src/pages/admin/index.vue", "src/pages/admin/login.vue", "src/pages/admin/dashboard.vue", "src/pages/admin/interviews.vue", "src/pages/admin/interview-detail.vue", "src/pages/admin/configs.vue", "src/pages/admin/layout.vue", "src/pages/admin/[id].vue", ] # 后端文件列表 backend_files = [ "main.py", "requirements.txt", "app/__init__.py", "app/config.py", "app/schemas.py", "app/routers/__init__.py", "app/routers/admin.py", "app/routers/candidate.py", "app/routers/chat.py", "app/routers/files.py", "app/routers/init.py", "app/routers/room.py", "app/routers/upload.py", "app/services/__init__.py", "app/services/coze_service.py", ] # 部署配置文件 deploy_files = [ "docker-compose.yml", "Dockerfile.backend", "Dockerfile.frontend", "nginx/frontend.conf", ] print("\n📤 步骤 1: 上传前端代码...") for f in frontend_files: local_path = os.path.join(LOCAL_PROJECT, "frontend", f) if os.path.exists(local_path): with open(local_path, 'r', encoding='utf-8') as fp: content = fp.read() remote_path = f"{DEPLOY_PATH}/frontend/{f}" print(f" 📝 {f}") upload_file_via_task(remote_path, content) else: print(f" ⚠️ 跳过: {f}") print("\n📤 步骤 2: 上传后端代码...") for f in backend_files: local_path = os.path.join(LOCAL_PROJECT, "backend", f) if os.path.exists(local_path): with open(local_path, 'r', encoding='utf-8') as fp: content = fp.read() remote_path = f"{DEPLOY_PATH}/backend/{f}" print(f" 📝 {f}") upload_file_via_task(remote_path, content) print("\n📤 步骤 3: 上传部署配置...") for f in deploy_files: local_path = os.path.join(LOCAL_PROJECT, "deploy", f) if os.path.exists(local_path): with open(local_path, 'r', encoding='utf-8') as fp: content = fp.read() remote_path = f"{DEPLOY_PATH}/deploy/{f}" print(f" 📝 {f}") upload_file_via_task(remote_path, content) print("\n🐳 步骤 4: 构建并启动 Docker...") build_script = f"""#!/bin/bash cd {DEPLOY_PATH}/deploy echo "目录结构:" ls -la {DEPLOY_PATH}/ ls -la {DEPLOY_PATH}/frontend/ | head -10 ls -la {DEPLOY_PATH}/backend/ | head -10 echo "" echo "停止旧容器..." docker-compose down 2>/dev/null || true echo "" echo "删除旧镜像..." docker rmi deploy-backend deploy-frontend 2>/dev/null || true echo "" echo "构建并启动..." docker-compose up -d --build 2>&1 sleep 15 echo "" echo "========== 容器状态 ==========" docker ps -a --format 'table {{{{.Names}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}' echo "" echo "========== 后端日志 ==========" docker logs --tail 20 ai-interview-backend 2>&1 echo "" echo "========== 测试 ==========" curl -s http://127.0.0.1:8000/health 2>&1 || echo "后端未响应" echo "" curl -s -o /dev/null -w "前端: %{{http_code}}" http://127.0.0.1:3000 2>&1 """ result = run_task(build_script, f"build_{int(time.time())}", wait_time=180) if result and result.get("msg"): print("\n📋 构建结果:") print("-" * 60) print(result["msg"][-3000:]) print("\n" + "=" * 60) print("🌐 http://interview.test.ai.ireborn.com.cn") print("=" * 60) if __name__ == "__main__": main()