Initial commit: AI Interview System
This commit is contained in:
246
deploy/bt_deploy.py
Normal file
246
deploy/bt_deploy.py
Normal file
@@ -0,0 +1,246 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user