Files

247 lines
8.8 KiB
Python
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
"""
CCA 部署脚本 — 通过 paramiko 上传文件到腾讯云服务器
"""
import sys
sys.stdout.reconfigure(encoding='utf-8')
import os
import paramiko
from pathlib import Path
HOST = '101.42.29.217'
PORT = 22
USER = 'root'
PASS = 'liutong65'
CCA_ROOT = Path(__file__).resolve().parent.parent
SRC_DIR = CCA_ROOT / 'src'
DEPLOY_DIR = CCA_ROOT / 'deploy'
# 服务器目标路径
SERVER_CCA_SRC = '/workspace/military_tech_voice/backend/cca_src'
SERVER_FRONTEND = '/workspace/military_tech_voice/frontend'
SERVER_WWW = '/var/www/voice'
SERVER_BACKEND = '/workspace/military_tech_voice/backend'
SERVER_ROUTES = f'{SERVER_BACKEND}/app/routes'
# 需要上传的 src 模块
SRC_MODULES = [
'asr_client.py',
'line_breaker.py',
'ai_line_breaker.py',
'ai_proofreader.py',
'term_normalizer.py',
'hotword_extractor.py',
'srt_writer.py',
'segment_splitter.py',
]
def connect():
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, PORT, USER, PASS)
sftp = ssh.open_sftp()
return ssh, sftp
def run(ssh, cmd):
print(f' $ {cmd}')
_, stdout, stderr = ssh.exec_command(cmd)
out = stdout.read().decode('utf-8', errors='replace').strip()
err = stderr.read().decode('utf-8', errors='replace').strip()
if out:
print(f' {out[:500]}')
if err:
print(f' [stderr] {err[:500]}')
return out
def upload(sftp, local_path, remote_path):
print(f' ↑ {Path(local_path).name}{remote_path}')
sftp.put(str(local_path), remote_path)
def main():
print('=== CCA 部署开始 ===\n')
ssh, sftp = connect()
print('[1/7] 连接成功\n')
# Step 2: 创建 cca_src 目录 + 上传源码
print('[2/7] 上传 CCA 源码模块...')
run(ssh, f'mkdir -p {SERVER_CCA_SRC}')
for module in SRC_MODULES:
local = SRC_DIR / module
if local.exists():
upload(sftp, local, f'{SERVER_CCA_SRC}/{module}')
else:
print(f' ⚠ 跳过(不存在): {module}')
print()
# Step 3: 上传 cca_route.py 到 app/routes/
print('[3/7] 上传 cca_route.py...')
run(ssh, f'mkdir -p {SERVER_ROUTES}')
upload(sftp, DEPLOY_DIR / 'cca_route.py', f'{SERVER_ROUTES}/cca.py')
# 确保 __init__.py 存在
run(ssh, f'touch {SERVER_ROUTES}/__init__.py')
print()
# Step 4: 上传 cca.html 到前端目录
print('[4/7] 上传 cca.html...')
upload(sftp, DEPLOY_DIR / 'cca.html', f'{SERVER_FRONTEND}/cca.html')
upload(sftp, DEPLOY_DIR / 'cca.html', f'{SERVER_WWW}/cca.html')
print()
# Step 5: 配置 .env(追加 CCA 相关变量)
print('[5/7] 配置 .env...')
env_path = f'{SERVER_BACKEND}/.env'
existing_env = ''
try:
with sftp.open(env_path, 'r') as f:
existing_env = f.read().decode('utf-8', errors='replace')
except FileNotFoundError:
pass
env_additions = []
if 'XFYUN_APP_ID' not in existing_env:
env_additions.append('# === CCA 唱词助手凭证 ===')
env_additions.append('# 讯飞录音文件转写(账号1-默认)')
env_additions.append('XFYUN_APP_ID=4c423e35')
env_additions.append('XFYUN_SECRET_KEY=b9e0b97d5dda072c9b4b8fb59e7e3d22')
env_additions.append('# 讯飞备用账号2')
env_additions.append('# XFYUN_APP_ID=52ae3024')
env_additions.append('# XFYUN_SECRET_KEY=d65de0eb282a4339e2b1e14fd119e42e')
if 'DEEPSEEK_API_KEY' not in existing_env:
env_additions.append('# DeepSeek(校对+折行+热词)')
env_additions.append('DEEPSEEK_API_KEY=sk-01a7868a88a04ab494e4f05c1f3f06e2')
if env_additions:
with sftp.open(env_path, 'a') as f:
f.write('\n' + '\n'.join(env_additions) + '\n')
print(' .env 已追加 CCA 凭证')
else:
print(' .env 已有 CCA 凭证,跳过')
print()
# Step 6: 注册 CCA 蓝图到 Flask main.py
print('[6/7] 注册 CCA 蓝图...')
main_py_path = f'{SERVER_BACKEND}/app/main.py'
with sftp.open(main_py_path, 'r') as f:
main_content = f.read().decode('utf-8', errors='replace')
if 'cca' not in main_content.lower():
# 找到最后一个 register_blueprint 的位置,在其后追加
lines = main_content.split('\n')
insert_idx = -1
for i, line in enumerate(lines):
if 'register_blueprint' in line:
insert_idx = i
if insert_idx >= 0:
indent = ' ' # 匹配现有缩进
# 检查现有蓝图注册的缩进
existing_line = lines[insert_idx]
indent = existing_line[:len(existing_line) - len(existing_line.lstrip())]
cca_lines = [
'',
f'{indent}# CCA 唱词助手',
f'{indent}from app.routes.cca import bp as cca_bp',
f'{indent}app.register_blueprint(cca_bp)',
]
for j, cca_line in enumerate(cca_lines):
lines.insert(insert_idx + 1 + j, cca_line)
new_content = '\n'.join(lines)
with sftp.open(main_py_path, 'w') as f:
f.write(new_content)
print(' 已注册 CCA 蓝图')
else:
print(' ⚠ 未找到 register_blueprint,请手动注册')
else:
print(' CCA 蓝图已注册,跳过')
print()
# Step 7: 安装依赖 + 修改 index.html + 重启
print('[7/7] 安装依赖、修改首页、重启服务...')
# 安装 Python 依赖
run(ssh, f'{SERVER_BACKEND}/venv/bin/pip install openai python-docx 2>&1 | tail -5')
# 修改 index.html 添加 CCA 入口按钮
index_path = f'{SERVER_FRONTEND}/index.html'
with sftp.open(index_path, 'r') as f:
index_content = f.read().decode('utf-8', errors='replace')
if '唱词助手' not in index_content and 'cca.html' not in index_content:
# 在导航栏中找到合适位置插入按钮
# 典型位置:header 或 nav 区域的最后一个链接之后
if '<header' in index_content or '<nav' in index_content:
# 找 </header> 或 </nav> 前插入
for marker in ['</nav>', '</header>']:
if marker in index_content:
btn_html = ' <a href="cca.html" class="nav-link">唱词助手</a>\n'
index_content = index_content.replace(marker, btn_html + ' ' + marker)
break
else:
# 备用:在 body 开头加一个浮动按钮
btn_html = '<div style="position:fixed;top:10px;right:10px;z-index:9999"><a href="cca.html" style="background:#4a9eff;color:#fff;padding:8px 16px;border-radius:6px;text-decoration:none;font-size:14px">唱词助手</a></div>\n'
index_content = index_content.replace('<body>', '<body>\n' + btn_html)
with sftp.open(index_path, 'w') as f:
f.write(index_content)
# 同步到 /var/www/voice/
run(ssh, f'cp {SERVER_FRONTEND}/index.html {SERVER_WWW}/index.html')
print(' index.html 已添加唱词助手入口')
else:
print(' index.html 已有唱词助手入口,跳过')
# 增大 Nginx 上传限制(音频文件可能较大)
nginx_conf = '/etc/nginx/nginx.conf'
with sftp.open(nginx_conf, 'r') as f:
nginx_content = f.read().decode('utf-8', errors='replace')
if 'client_max_body_size' not in nginx_content:
nginx_content = nginx_content.replace(
'http {',
'http {\n client_max_body_size 200m;'
)
with sftp.open(nginx_conf, 'w') as f:
f.write(nginx_content)
run(ssh, 'nginx -t && nginx -s reload')
print(' Nginx 已增大上传限制至 200MB')
else:
print(' Nginx 上传限制已配置')
# 重启 Flask 服务
print('\n 重启 Flask...')
# 查找并重启 Flask 进程
run(ssh, 'pkill -f "flask run" || pkill -f "gunicorn" || pkill -f "python.*app" || true')
# 给进程时间退出
import time
time.sleep(2)
# 检查服务启动方式
service_out = run(ssh, 'systemctl list-units --type=service | grep -i voice || systemctl list-units --type=service | grep -i flask || true')
if 'voice' in service_out.lower() or 'flask' in service_out.lower():
service_name = service_out.split()[0] if service_out else ''
if service_name:
run(ssh, f'systemctl restart {service_name}')
else:
# 直接后台启动
run(ssh, f'cd {SERVER_BACKEND} && source venv/bin/activate && nohup python -m flask run --host=0.0.0.0 --port=5000 > /tmp/flask_cca.log 2>&1 &')
print()
print('=== CCA 部署完成 ===')
print(f'访问地址: http://{HOST}/cca.html')
print(f'API 地址: http://{HOST}/api/cca/')
sftp.close()
ssh.close()
if __name__ == '__main__':
main()