""" 部署汇总系统到 NAS — 含完成后端重构 上传文件 → 删旧DB → 重建容器 """ import paramiko, base64, os, sys, time NAS_HOST = "100.85.152.112" NAS_USER = "simon" NAS_PASS = "Nas82829330" NAS_PATH = "/volume1/docker/gaokao-portal" SUDO_DOCKER = "echo 'Nas82829330' | sudo -S /usr/local/bin/docker" PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) FILES_TO_DEPLOY = [ ("deploy/backend/app.py", "backend/app.py"), ("deploy/backend/seed_data.py", "backend/seed_data.py"), ("deploy/nginx/html/index.html", "nginx/html/index.html"), ("deploy/nginx/html/dashboard.html", "nginx/html/dashboard.html"), ("deploy/nginx/html/summary.html", "nginx/html/summary.html"), ("deploy/nginx/html/css/style.css", "nginx/html/css/style.css"), ("deploy/nginx/html/questionnaire.html", "nginx/html/questionnaire.html"), ("deploy/nginx/html/霍兰德职业兴趣测试-自评版.html", "nginx/html/霍兰德职业兴趣测试-自评版.html"), ("deploy/nginx/html/MBTI性格测试-自评版.html", "nginx/html/MBTI性格测试-自评版.html"), ] def run_cmd(client, cmd, desc, timeout=60): print(f" [{desc}] ", end="", flush=True) stdin, stdout, stderr = client.exec_command(cmd, timeout=timeout) out = stdout.read().decode("utf-8", errors="replace").strip() err = stderr.read().decode("utf-8", errors="replace").strip() status = stdout.channel.recv_exit_status() if status != 0 and err: print(f"WARN (exit={status})\n {err[:200]}") elif out: print(f"OK\n {out[:200]}") else: print("OK") return out, err, status def upload_base64(client, local_rel, remote_rel): """用 base64 编码上传文件到 NAS,避免中文路径编码问题""" local_path = os.path.join(PROJECT_ROOT, local_rel) remote_path = f"{NAS_PATH}/{remote_rel}" with open(local_path, 'rb') as f: data = base64.b64encode(f.read()).decode() print(f" 上传: {local_rel} → {remote_path} ({len(data)} bytes base64)", flush=True) channel = client.get_transport().open_session() channel.exec_command(f"base64 -d > '{remote_path}' 2>/dev/null && echo OK || echo FAIL") channel.sendall(data.encode() + b'\n') channel.shutdown_write() result = channel.recv(1024).decode().strip() channel.close() print(f" 结果: {result}") def main(): print("=" * 60) print("部署汇总系统到 NAS") print("=" * 60) client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: print(f"\n🔌 连接 {NAS_HOST}:22 ...") client.connect(NAS_HOST, 22, NAS_USER, NAS_PASS, timeout=15) print("✅ 已连接") # 1. 上传所有文件 print("\n📤 上传文件...") for local_rel, remote_rel in FILES_TO_DEPLOY: upload_base64(client, local_rel, remote_rel) # 2. 删除旧数据库(重新 seed 以获得 target_user 列) print("\n🗑️ 删除旧数据库...") run_cmd(client, f"rm -f {NAS_PATH}/backend/data/gaokao.db", "删除 gaokao.db") # 3. 重建并启动容器 print("\n🔨 重建容器(build + up)...") run_cmd(client, f"cd '{NAS_PATH}' && {SUDO_DOCKER} compose down 2>&1", "停止旧容器", timeout=60) out, err, status = run_cmd( client, f"cd '{NAS_PATH}' && {SUDO_DOCKER} compose up -d --build 2>&1", "构建并启动", timeout=180 ) # 等待容器就绪 print("\n⏳ 等待容器就绪...") for i in range(8): time.sleep(5) out, _, _ = run_cmd( client, f"cd '{NAS_PATH}' && {SUDO_DOCKER} compose ps --format json 2>&1", f"状态检查 {i+1}/8" ) if '"running"' in out: print(" ✅ 检测到运行中容器") break # 4. 验证 print("\n🧪 验证 API...") run_cmd(client, f"curl -s http://localhost:8080/api/completion-status | python3 -m json.tool 2>&1 | head -20", "completion-status API") print("\n" + "=" * 60) print("✅ 部署完成") print(f" 内网访问: http://100.85.152.112:8080") print(f" 问卷面板: http://100.85.152.112:8080/dashboard.html") print(f" 汇总报告: http://100.85.152.112:8080/summary.html") print("=" * 60) except Exception as e: print(f"\n❌ 错误: {e}") sys.exit(1) finally: client.close() if __name__ == "__main__": main()