247 lines
8.8 KiB
Python
247 lines
8.8 KiB
Python
|
|
# -*- 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()
|