# GHCS v0.2 Step2 · deploy_router.py · 一键部署代码 ## 文件1 · `backend/deploy_router.py`(新建) ```python """ GHCS v0.2 - deploy router 一键部署:git pull → install deps → restart service """ import os import logging from datetime import datetime from fastapi import APIRouter, Depends, HTTPException from auth import verify_token logger = logging.getLogger("ghcs.deploy") router = APIRouter(prefix="/api/deploy", tags=["deploy"]) SSH_KEY_PATH = os.getenv("GHCS_SSH_KEY", os.path.expanduser("~/.ssh/id_rsa")) GHCS_SERVER = { "host": "43.139.207.172", "port": 22, "username": "root", "key_filename": SSH_KEY_PATH, } # 可部署的服务白名单 DEPLOY_WHITELIST = { "console-api": { "name": "GHCS console-api", "server": GHCS_SERVER, "repo_dir": "/root/guanghu-dev/ghcs-mono", "steps": [ "cd /root/guanghu-dev/ghcs-mono && git pull origin main", "cd /root/guanghu-dev/ghcs-mono/backend && source venv/bin/activate && pip install -r requirements.txt -q", "systemctl restart console-api", ], "verify_cmd": "systemctl is-active console-api", }, } def _ssh_exec(server_config, command, timeout=60): """SSH远程执行命令""" import paramiko client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: client.connect( hostname=server_config["host"], port=server_config["port"], username=server_config["username"], key_filename=server_config["key_filename"], timeout=10, ) stdin, stdout, stderr = client.exec_command(command, timeout=timeout) exit_code = stdout.channel.recv_exit_status() return { "stdout": stdout.read().decode("utf-8").strip(), "stderr": stderr.read().decode("utf-8").strip(), "exit_code": exit_code, } except Exception as e: raise HTTPException(status_code=500, detail=f"SSH error: {str(e)}") finally: client.close() @router.post("/{service_key}") async def deploy_service(service_key: str, username: str = Depends(verify_token)): """一键部署:git pull → install → restart""" if service_key not in DEPLOY_WHITELIST: raise HTTPException(status_code=404, detail=f"unknown service: {service_key}") svc = DEPLOY_WHITELIST[service_key] logger.info(f"[DEPLOY] user={username} service={service_key} time={datetime.now().isoformat()}") results = [] for i, step in enumerate(svc["steps"]): logger.info(f"[DEPLOY STEP {i+1}] {step[:80]}") result = _ssh_exec(svc["server"], step, timeout=120) results.append({ "step": i + 1, "command": step[:80], "stdout": result["stdout"][-500:] if result["stdout"] else "", "stderr": result["stderr"][-500:] if result["stderr"] else "", "exit_code": result["exit_code"], }) if result["exit_code"] != 0: logger.error(f"[DEPLOY FAILED] step={i+1} stderr={result['stderr']}") return { "service": service_key, "success": False, "failed_step": i + 1, "steps": results, "timestamp": datetime.now().isoformat(), } # 验证服务状态 verify = _ssh_exec(svc["server"], svc["verify_cmd"]) status = verify["stdout"] logger.info(f"[DEPLOY OK] service={service_key} status={status}") return { "service": service_key, "success": True, "status_after": status, "steps": results, "timestamp": datetime.now().isoformat(), } @router.get("/{service_key}/check") async def check_deploy_status(service_key: str, username: str = Depends(verify_token)): """检查服务器代码是否落后于 Gitea""" if service_key not in DEPLOY_WHITELIST: raise HTTPException(status_code=404, detail=f"unknown service: {service_key}") svc = DEPLOY_WHITELIST[service_key] repo_dir = svc["repo_dir"] result = _ssh_exec( svc["server"], f"cd {repo_dir} && git fetch origin main -q 2>&1 && echo 'LOCAL:' && git log --oneline -1 HEAD && echo 'REMOTE:' && git log --oneline -1 origin/main", ) lines = result["stdout"].split("\n") local_commit = "" remote_commit = "" for i, line in enumerate(lines): if line.startswith("LOCAL:") and i + 1 < len(lines): local_commit = lines[i + 1] if line.startswith("REMOTE:") and i + 1 < len(lines): remote_commit = lines[i + 1] return { "service": service_key, "server_commit": local_commit, "gitea_commit": remote_commit, "needs_deploy": local_commit != remote_commit, } ``` --- ## 文件2 · `backend/main.py`(改动两处) ### 改动 A · 顶部加一行 import 在 `from servers_router import router as servers_router` 后面加一行: ```python from deploy_router import router as deploy_router ``` ### 改动 B · 挂载 router 在 `app.include_router(servers_router)` 后面加一行: ```python app.include_router(deploy_router) ``` --- ## 接口说明 | 接口 | 方法 | 作用 | | --- | --- | --- | | `POST /api/deploy/console-api` | POST | 一键部署 GHCS · git pull → pip install → restart | | `GET /api/deploy/console-api/check` | GET | 检查服务器代码是否落后于 Gitea | --- ## 部署流程 1. Mac 本地改代码 → `git push origin main` 2. 调 `POST /api/deploy/console-api` 3. 服务器自动执行:`git pull` → `pip install` → `systemctl restart` 4. 返回每一步的执行结果 + 最终服务状态 --- ## ⚠️ 注意 - venv 路径假设在 `/root/guanghu-dev/ghcs-mono/backend/venv/`,如果实际路径不同需要改 steps 里的 source 路径 - 当前只支持部署 console-api(GHCS 自身),后续可以扩展 DEPLOY_WHITELIST 加入其他服务