Files
011-ai-interview/deploy/bt_deploy.py
2026-01-23 13:57:48 +08:00

247 lines
7.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()