Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

5.9 KiB
Raw Permalink Blame History

GHCS v0.2 Step2 · deploy_router.py · 一键部署代码

文件1 · backend/deploy_router.py(新建)

"""
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 后面加一行:

from deploy_router import router as deploy_router

改动 B · 挂载 router

app.include_router(servers_router) 后面加一行:

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 pullpip installsystemctl restart
  4. 返回每一步的执行结果 + 最终服务状态

⚠️ 注意

  • venv 路径假设在 /root/guanghu-dev/ghcs-mono/backend/venv/,如果实际路径不同需要改 steps 里的 source 路径
  • 当前只支持部署 console-apiGHCS 自身),后续可以扩展 DEPLOY_WHITELIST 加入其他服务