247 lines
7.1 KiB
Python
247 lines
7.1 KiB
Python
#!/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()
|