guardia-manager/.claude/skills/manager-api/SKILL.md
DESKTOP-TKLFCPRython 10cc76d6e6 refactor: 101.79.17.164 → zioinfo.co.kr 전체 도메인 변환 + Manager UI 배포
- 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>
2026-05-31 10:09:17 +09:00

7.0 KiB

name description
manager-api 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 기본 구조

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 — 서버 상태 및 서비스 제어

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 — 배포 트리거

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 — 설정 관리

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

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)

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