185 lines
5.9 KiB
Markdown
185 lines
5.9 KiB
Markdown
|
|
# 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 加入其他服务
|