185 lines
5.9 KiB
Markdown
Raw Normal View History

# 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-apiGHCS 自身),后续可以扩展 DEPLOY_WHITELIST 加入其他服务