#!/usr/bin/env python3 """ 通过宝塔 API 自动部署 AI 面试系统 """ import requests import time import hashlib import os import base64 import json # 禁用 SSL 警告 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" DOMAIN = "interview.test.ai.ireborn.com.cn" def bt_api(action, data=None, files=None): """调用宝塔 API""" 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: if files: response = requests.post(url, data=data, files=files, timeout=300, verify=False) else: response = requests.post(url, data=data, timeout=60, verify=False) return response.json() except requests.exceptions.JSONDecodeError: return {"status": True, "msg": response.text} except Exception as e: return {"status": False, "msg": str(e)} def create_directory(path): """创建目录""" print(f"📁 创建目录: {path}") result = bt_api("files?action=CreateDir", {"path": path}) if result.get("status") or "已存在" in str(result.get("msg", "")): print(" ✅ 目录已就绪") return True print(f" ⚠️ {result}") return True # 继续执行 def upload_file(local_path, remote_dir): """上传文件到服务器""" filename = os.path.basename(local_path) print(f"📤 上传文件: {filename}") with open(local_path, 'rb') as f: files = {'f_path': (filename, f)} data = {'f_path': remote_dir, 'f_name': filename} result = bt_api("files?action=upload", data, files) if result.get("status") or result.get("msg") == "上传成功": print(f" ✅ 上传成功") return True print(f" ❌ 上传失败: {result}") return False def exec_shell(command): """执行 Shell 命令""" print(f"🔧 执行命令: {command[:80]}...") result = bt_api("files?action=ExecShell", {"command": command}) return result def create_website(domain): """创建网站""" print(f"🌐 创建网站: {domain}") data = { "webname": json.dumps({"domain": domain, "domainlist": [], "count": 0}), "path": f"/www/wwwroot/{domain}", "type_id": 0, "type": "PHP", "version": "00", "port": "80", "ps": "AI面试系统", "ftp": "false", "sql": "false" } result = bt_api("site?action=AddSite", data) if result.get("status") or result.get("siteStatus"): print(" ✅ 网站创建成功") return True if "已存在" in str(result.get("msg", "")): print(" ⚠️ 网站已存在") return True print(f" 结果: {result}") return True def set_proxy(domain, target_url): """设置反向代理""" print(f"🔄 设置反向代理: {domain} -> {target_url}") # 获取站点 ID sites_result = bt_api("site?action=GetSitesSort") site_id = None if sites_result.get("data"): for site in sites_result["data"]: if site.get("name") == domain: site_id = site.get("id") break if not site_id: print(" ⚠️ 未找到站点 ID,尝试继续...") return True # 设置代理 proxy_data = { "sitename": domain, "proxyname": "ai-interview", "proxydir": "/", "proxysite": target_url, "proxysend": "0", "cache": "0", "cacheTime": "1", "subfilter": "[]", "type": "1", "advanced": "0" } result = bt_api("site?action=CreateProxy", proxy_data) print(f" 结果: {result}") return True def write_remote_file(path, content): """写入远程文件""" print(f"📝 写入文件: {path}") # 使用 base64 编码内容 encoded = base64.b64encode(content.encode()).decode() result = bt_api("files?action=SaveFileBody", { "path": path, "data": content, "encoding": "utf-8" }) if result.get("status"): print(" ✅ 文件写入成功") return True print(f" 结果: {result}") return True def main(): print("=" * 60) print("🚀 AI 语音面试系统 - 自动部署") print("=" * 60) print() # 1. 创建部署目录 print("\n📦 步骤 1: 创建目录结构") create_directory(DEPLOY_PATH) create_directory(f"{DEPLOY_PATH}/frontend") create_directory(f"{DEPLOY_PATH}/backend") create_directory(f"{DEPLOY_PATH}/deploy") create_directory(f"{DEPLOY_PATH}/deploy/nginx") # 2. 写入配置文件 print("\n📝 步骤 2: 写入配置文件") # .env 文件 env_content = """COZE_PAT_TOKEN=pat_nd1wU47WyPS9GCIyJ1clnH8h1WOQXGrYELX8w73TnSZaYbFdYD4swIhzcETBUbfT COZE_BOT_ID=7595113005181386792 COZE_WORKFLOW_A_ID=7597357422713798710 COZE_WORKFLOW_C_ID=7597376294612107318 FILE_SERVER_URL=https://files.test.ai.ireborn.com.cn FILE_SERVER_TOKEN=ai_interview_2026_secret """ write_remote_file(f"{DEPLOY_PATH}/deploy/.env", env_content) # 3. 上传部署包 print("\n📤 步骤 3: 上传部署包") tar_path = "/Users/a111/Documents/AgentWD/projects/ai-interview-deploy.tar.gz" if os.path.exists(tar_path): upload_file(tar_path, "/www/wwwroot") else: print(f" ⚠️ 部署包不存在: {tar_path}") # 4. 解压并部署 print("\n🔧 步骤 4: 解压部署包") commands = [ f"cd /www/wwwroot && tar -xzf ai-interview-deploy.tar.gz -C {DEPLOY_PATH}", f"cd {DEPLOY_PATH}/deploy && cp .env .env.bak 2>/dev/null || true", ] for cmd in commands: exec_shell(cmd) time.sleep(1) # 5. 启动 Docker print("\n🐳 步骤 5: 启动 Docker 容器") docker_commands = [ f"cd {DEPLOY_PATH}/deploy && docker-compose down 2>/dev/null || true", f"cd {DEPLOY_PATH}/deploy && docker-compose up -d --build", ] for cmd in docker_commands: print(f" 执行: {cmd}") result = exec_shell(cmd) print(f" 结果: {result}") time.sleep(3) # 6. 创建网站和反向代理 print("\n🌐 步骤 6: 配置网站") create_website(DOMAIN) time.sleep(2) set_proxy(DOMAIN, "http://127.0.0.1:3000") # 7. 检查状态 print("\n🔍 步骤 7: 检查部署状态") result = exec_shell("docker ps --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'") print(f" 容器状态: {result}") print() print("=" * 60) print("✅ 部署完成!") print("=" * 60) print() print(f"🌐 访问地址:") print(f" 用户端: http://{DOMAIN}") print(f" 管理后台: http://{DOMAIN}/admin") print() print("⚠️ 如需 HTTPS,请在宝塔面板中为该网站申请 SSL 证书") print("=" * 60) if __name__ == "__main__": main()