- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스) - Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포 - 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
219 lines
7.0 KiB
Markdown
219 lines
7.0 KiB
Markdown
---
|
|
name: manager-api
|
|
description: >
|
|
GUARDiA Manager 경량 FastAPI 백엔드를 구현하는 스킬.
|
|
GUARDiA ITSM API가 커버하지 않는 시스템 수준 작업(서버 프로세스 관리,
|
|
Nginx 제어, 배포 트리거, .env 설정 관리)을 담당한다.
|
|
트리거: Manager 백엔드 구현, 시스템 API 작성, 서비스 제어 API,
|
|
배포 이력 API, 설정 관리 API 요청 시.
|
|
---
|
|
|
|
# GUARDiA Manager 백엔드 구현 스킬
|
|
|
|
## 구현 범위 (GUARDiA ITSM과 역할 분담)
|
|
|
|
**Manager Backend(8002)가 담당:**
|
|
- 서버 리소스 조회 (psutil: CPU, 메모리, 디스크)
|
|
- 서비스 시작/중지/재시작 (systemctl via subprocess)
|
|
- Nginx 설정 테스트/리로드
|
|
- Deploy Webhook 트리거 (포트 9999 호출)
|
|
- .env 파일 조회/수정 (보안: SECRET 키 마스킹)
|
|
- Gitea API 프록시 (CORS 우회)
|
|
|
|
**GUARDiA ITSM API(8001)에서 직접 호출 (재구현 금지):**
|
|
- 사용자/인증, SR, 감사 로그, CMDB, 배포 이력, API Key 관리 등
|
|
|
|
## 프로젝트 구조
|
|
|
|
```
|
|
backend/
|
|
├── main.py
|
|
├── requirements.txt
|
|
├── core/
|
|
│ └── auth.py ← GUARDiA ITSM JWT 검증
|
|
├── routers/
|
|
│ ├── system.py ← 서버 상태, 서비스 제어
|
|
│ ├── deploy.py ← 배포 트리거, Gitea 연동
|
|
│ └── config.py ← .env, Nginx 설정
|
|
└── .env
|
|
```
|
|
|
|
## main.py 기본 구조
|
|
|
|
```python
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
import os
|
|
|
|
app = FastAPI(title="GUARDiA Manager API", version="1.0.0")
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=[os.environ.get("MANAGER_ALLOWED_ORIGINS", "http://localhost:5173")],
|
|
allow_methods=["*"], allow_headers=["*"], allow_credentials=True,
|
|
)
|
|
|
|
from routers import system, deploy, config
|
|
app.include_router(system.router, prefix="/api/system", tags=["system"])
|
|
app.include_router(deploy.router, prefix="/api/deploy", tags=["deploy"])
|
|
app.include_router(config.router, prefix="/api/config", tags=["config"])
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "ok", "service": "guardia-manager"}
|
|
```
|
|
|
|
## routers/system.py — 서버 상태 및 서비스 제어
|
|
|
|
```python
|
|
import psutil, subprocess
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from core.auth import verify_guardia_token
|
|
|
|
router = APIRouter()
|
|
|
|
ALLOWED_SERVICES = { # 허용된 서비스만 제어 가능
|
|
"nginx", "zioinfo", "guardia", "gitea", "jenkins",
|
|
"postgresql", "ollama", "zioinfo-deploy"
|
|
}
|
|
|
|
@router.get("/resources")
|
|
async def get_resources(user=Depends(verify_guardia_token)):
|
|
"""CPU/메모리/디스크 현황"""
|
|
return {
|
|
"cpu_percent": psutil.cpu_percent(interval=0.1),
|
|
"memory": {
|
|
"total_gb": round(psutil.virtual_memory().total / 1e9, 1),
|
|
"used_gb": round(psutil.virtual_memory().used / 1e9, 1),
|
|
"percent": psutil.virtual_memory().percent,
|
|
},
|
|
"disk": {
|
|
"total_gb": round(psutil.disk_usage('/').total / 1e9, 1),
|
|
"used_gb": round(psutil.disk_usage('/').used / 1e9, 1),
|
|
"percent": psutil.disk_usage('/').percent,
|
|
},
|
|
}
|
|
|
|
@router.get("/services")
|
|
async def list_services(user=Depends(verify_guardia_token)):
|
|
"""허용된 서비스 상태 목록"""
|
|
result = {}
|
|
for svc in ALLOWED_SERVICES:
|
|
proc = subprocess.run(
|
|
["systemctl", "is-active", svc],
|
|
capture_output=True, text=True
|
|
)
|
|
result[svc] = proc.stdout.strip()
|
|
return result
|
|
|
|
@router.post("/services/{name}/restart")
|
|
async def restart_service(name: str, user=Depends(verify_guardia_token)):
|
|
"""서비스 재시작 (admin 역할 필요)"""
|
|
if user.get("role") != "admin":
|
|
raise HTTPException(status_code=403, detail="admin 권한 필요")
|
|
if name not in ALLOWED_SERVICES:
|
|
raise HTTPException(status_code=400, detail="허용되지 않은 서비스")
|
|
result = subprocess.run(
|
|
["systemctl", "restart", name],
|
|
capture_output=True, text=True
|
|
)
|
|
if result.returncode != 0:
|
|
raise HTTPException(status_code=500, detail=result.stderr[:200])
|
|
return {"message": f"{name} 재시작 완료"}
|
|
```
|
|
|
|
## routers/deploy.py — 배포 트리거
|
|
|
|
```python
|
|
import httpx
|
|
from fastapi import APIRouter, Depends
|
|
from core.auth import verify_guardia_token
|
|
|
|
router = APIRouter()
|
|
DEPLOY_WEBHOOK = "http://localhost:9999/"
|
|
|
|
@router.post("/trigger/{repo}")
|
|
async def trigger_deploy(repo: str, user=Depends(verify_guardia_token)):
|
|
"""Deploy Webhook 수동 트리거"""
|
|
async with httpx.AsyncClient() as client:
|
|
resp = await client.post(DEPLOY_WEBHOOK,
|
|
json={"repo": repo, "triggered_by": user.get("sub")},
|
|
timeout=10)
|
|
return {"status": resp.status_code, "message": "배포 트리거됨"}
|
|
|
|
@router.get("/history")
|
|
async def deploy_history(user=Depends(verify_guardia_token)):
|
|
"""배포 로그 마지막 100줄"""
|
|
try:
|
|
with open("/var/log/zioinfo/deploy.log") as f:
|
|
lines = f.readlines()[-100:]
|
|
return {"lines": lines}
|
|
except FileNotFoundError:
|
|
return {"lines": []}
|
|
```
|
|
|
|
## routers/config.py — 설정 관리
|
|
|
|
```python
|
|
import os, re
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from core.auth import verify_guardia_token
|
|
|
|
router = APIRouter()
|
|
ENV_PATH = "/opt/guardia/app/.env"
|
|
SENSITIVE_KEYS = {"SECRET", "PASSWORD", "KEY", "TOKEN", "PASS"}
|
|
|
|
def mask_sensitive(key: str, value: str) -> str:
|
|
"""보안 키 값 마스킹"""
|
|
if any(s in key.upper() for s in SENSITIVE_KEYS):
|
|
return "****"
|
|
return value
|
|
|
|
@router.get("/env")
|
|
async def get_env(user=Depends(verify_guardia_token)):
|
|
"""환경변수 목록 (보안 값 마스킹)"""
|
|
if user.get("role") != "admin":
|
|
raise HTTPException(status_code=403, detail="admin 권한 필요")
|
|
result = {}
|
|
try:
|
|
with open(ENV_PATH) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line and not line.startswith("#") and "=" in line:
|
|
key, _, val = line.partition("=")
|
|
result[key.strip()] = mask_sensitive(key.strip(), val.strip())
|
|
except FileNotFoundError:
|
|
pass
|
|
return result
|
|
|
|
@router.post("/nginx/reload")
|
|
async def nginx_reload(user=Depends(verify_guardia_token)):
|
|
"""Nginx 설정 테스트 후 리로드"""
|
|
import subprocess
|
|
test = subprocess.run(["nginx", "-t"], capture_output=True, text=True)
|
|
if test.returncode != 0:
|
|
raise HTTPException(status_code=400, detail=f"Nginx 설정 오류: {test.stderr[:200]}")
|
|
subprocess.run(["systemctl", "reload", "nginx"])
|
|
return {"message": "Nginx 리로드 완료"}
|
|
```
|
|
|
|
## requirements.txt
|
|
|
|
```txt
|
|
fastapi==0.115.0
|
|
uvicorn[standard]==0.30.0
|
|
httpx==0.27.0
|
|
psutil==5.9.8
|
|
python-jose[cryptography]==3.3.0
|
|
python-dotenv==1.0.1
|
|
```
|
|
|
|
## .env (Manager Backend)
|
|
|
|
```env
|
|
MANAGER_PORT=8002
|
|
GUARDIA_API=http://localhost:8001
|
|
GUARDIA_JWT_SECRET=guardia-jwt-production-secret-2026-please-change
|
|
MANAGER_ALLOWED_ORIGINS=http://localhost:5173,http://zioinfo.co.kr:8080
|
|
```
|