feat(monitoring): Scouter APM 서버 모니터링 통합
[Scouter APM 연동]
- core/scouter.py: Scouter HTTP API 클라이언트
- get_summary(): 전체 WAS 모니터링 현황 (CPU/TPS/응답시간/위험서버)
- get_server_metrics(): 특정 서버 실시간 메트릭
- get_alert_list(): Scouter 경보 목록
- get_xlog_recent(): 최근 트랜잭션 X-Log
- routers/scouter.py: REST API 엔드포인트 (6개)
- GET /api/scouter/status, /servers, /servers/{hash}/metrics
- GET /api/scouter/servers/{hash}/services, /xlog, /alerts
- POST /api/scouter/agent/deploy: SSH로 scouter-agent.jar 자동 배포
[스케줄러]
- scheduler.py: Scouter 경보 수집 (5분마다)
- CPU > 80% 또는 에러율 > 5% 서버 자동 감지 → GUARDiA 알림
[Docker Compose]
- docker-compose.yml: scouteross/scouter-server:2.20.0 서비스 추가
- 포트 6100 (UDP/TCP 에이전트 수집) + 6180 (HTTP API)
[설치 스크립트]
- setup/scouter/download_scouter.sh: 에이전트/서버 다운로드
- scouter-agent.jar + agent.conf.template 생성
- setup_ubuntu.sh: Scouter 서버 설치 단계 추가 (14단계로 확장)
- --test 검증: Scouter API + Gitea HTTP 검사 추가
환경변수: SCOUTER_HOST, SCOUTER_HTTP_PORT=6180, SCOUTER_USER, SCOUTER_PASSWORD
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
779ea18ea9
commit
d3c2754515
@ -179,6 +179,30 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
|
|
||||||
|
# ── Scouter Server (Java WAS APM 모니터링 서버) ─────────────
|
||||||
|
# Scouter: Tomcat/JBoss/JEUs 성능 모니터링 (CPU/Heap/TPS/응답시간)
|
||||||
|
# Agent는 대상 WAS 서버에 setup/scouter/scouter-agent.jar 로 배포
|
||||||
|
scouter-server:
|
||||||
|
image: scouteross/scouter-server:2.20.0
|
||||||
|
container_name: guardia-scouter
|
||||||
|
ports:
|
||||||
|
- "6100:6100/udp" # 에이전트 데이터 수신 (UDP)
|
||||||
|
- "6100:6100/tcp" # 에이전트 TCP
|
||||||
|
- "6180:6180" # HTTP API
|
||||||
|
volumes:
|
||||||
|
- guardia-scouter-data:/home/scouter-server/database
|
||||||
|
environment:
|
||||||
|
SCOUTER_HTTP_PORT: "6180"
|
||||||
|
networks:
|
||||||
|
- guardia-net
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-sf", "http://localhost:6180/scouter/v1/info/version"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
# ── Qdrant (전용 벡터 DB — 고성능 시맨틱 검색) ───────────
|
# ── Qdrant (전용 벡터 DB — 고성능 시맨틱 검색) ───────────
|
||||||
# pgvector보다 빠른 ANN 검색이 필요할 때 사용
|
# pgvector보다 빠른 ANN 검색이 필요할 때 사용
|
||||||
# QDRANT_ENABLED=true 시 guardia에서 QDRANT_URL=http://qdrant:6333 설정
|
# QDRANT_ENABLED=true 시 guardia에서 QDRANT_URL=http://qdrant:6333 설정
|
||||||
@ -249,6 +273,7 @@ volumes:
|
|||||||
guardia-qdrant: # Qdrant 벡터 데이터
|
guardia-qdrant: # Qdrant 벡터 데이터
|
||||||
guardia-gitea-data: # Gitea 저장소 + DB
|
guardia-gitea-data: # Gitea 저장소 + DB
|
||||||
guardia-gitea-config: # Gitea 설정
|
guardia-gitea-config: # Gitea 설정
|
||||||
|
guardia-scouter-data: # Scouter 성능 데이터
|
||||||
|
|
||||||
# ── 네트워크 ──────────────────────────────────────────────
|
# ── 네트워크 ──────────────────────────────────────────────
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@ -622,6 +622,35 @@ def start_scheduler() -> None:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("SLA 스케줄 등록 실패 (무시): %s", exc)
|
logger.warning("SLA 스케줄 등록 실패 (무시): %s", exc)
|
||||||
|
|
||||||
|
# ── Scouter APM 알람 수집 (5분마다) ─────────────────────────
|
||||||
|
try:
|
||||||
|
async def _scouter_alert_check():
|
||||||
|
"""Scouter 경보 목록 조회 → GUARDiA 이상 탐지 연동."""
|
||||||
|
from core.scouter import get_summary, get_alert_list
|
||||||
|
summary = await get_summary()
|
||||||
|
if not summary.get("enabled"):
|
||||||
|
return
|
||||||
|
# CPU > 80% 또는 에러율 위험 서버 알림
|
||||||
|
critical = summary.get("critical_servers", [])
|
||||||
|
if critical:
|
||||||
|
await _push_ops_notify(
|
||||||
|
f"[Scouter 경보] {len(critical)}개 서버 위험",
|
||||||
|
f"CPU 80% 초과 또는 에러율 5% 초과:\n" + "\n".join(f" - {s}" for s in critical[:5])
|
||||||
|
)
|
||||||
|
|
||||||
|
_scheduler.add_job(
|
||||||
|
_scouter_alert_check,
|
||||||
|
"interval",
|
||||||
|
minutes=5,
|
||||||
|
id="scouter_alert_check",
|
||||||
|
name="Scouter APM 경보 수집 (5분)",
|
||||||
|
replace_existing=True,
|
||||||
|
misfire_grace_time=60,
|
||||||
|
)
|
||||||
|
logger.info("Scouter APM 경보 수집 스케줄 등록 완료")
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("Scouter 스케줄 등록 실패 (무시): %s", exc)
|
||||||
|
|
||||||
# ── G-3: 라이선스 만료 알림 (매일 09:00 KST) ─────────────────────────────
|
# ── G-3: 라이선스 만료 알림 (매일 09:00 KST) ─────────────────────────────
|
||||||
try:
|
try:
|
||||||
_scheduler.add_job(
|
_scheduler.add_job(
|
||||||
|
|||||||
190
itsm/core/scouter.py
Normal file
190
itsm/core/scouter.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
"""
|
||||||
|
Scouter APM 연동 — GUARDiA ITSM
|
||||||
|
|
||||||
|
Scouter는 Java WAS(Tomcat/JBoss/JEUs) 전문 APM 도구입니다.
|
||||||
|
Scouter HTTP API를 통해 실시간 메트릭을 수집하여 GUARDiA 대시보드에 표시합니다.
|
||||||
|
|
||||||
|
환경변수:
|
||||||
|
SCOUTER_HOST : Scouter 서버 호스트 (기본: localhost)
|
||||||
|
SCOUTER_HTTP_PORT : Scouter HTTP API 포트 (기본: 6180)
|
||||||
|
SCOUTER_USER : Scouter 사용자 (기본: admin)
|
||||||
|
SCOUTER_PASSWORD : Scouter 비밀번호 (기본: admin)
|
||||||
|
|
||||||
|
Scouter HTTP API 문서: https://github.com/scouter-project/scouter/wiki/Scouter-HTTP-API
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCOUTER_HOST = os.getenv("SCOUTER_HOST", "localhost")
|
||||||
|
SCOUTER_HTTP_PORT = int(os.getenv("SCOUTER_HTTP_PORT", "6180"))
|
||||||
|
SCOUTER_USER = os.getenv("SCOUTER_USER", "admin")
|
||||||
|
SCOUTER_PASSWORD = os.getenv("SCOUTER_PASSWORD", "admin")
|
||||||
|
|
||||||
|
_BASE = f"http://{SCOUTER_HOST}:{SCOUTER_HTTP_PORT}/scouter/v1"
|
||||||
|
_ENABLED = bool(os.getenv("SCOUTER_HOST")) # 환경변수 없으면 비활성화
|
||||||
|
|
||||||
|
|
||||||
|
async def _get(path: str, params: dict = None) -> Optional[dict]:
|
||||||
|
"""Scouter HTTP API GET 요청."""
|
||||||
|
if not _ENABLED:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||||
|
r = await client.get(
|
||||||
|
f"{_BASE}{path}",
|
||||||
|
params=params or {},
|
||||||
|
auth=(SCOUTER_USER, SCOUTER_PASSWORD),
|
||||||
|
)
|
||||||
|
if r.status_code == 200:
|
||||||
|
return r.json()
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug("Scouter API 오류 (%s): %s", path, str(e)[:80])
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def is_available() -> bool:
|
||||||
|
"""Scouter 서버 응답 여부 확인."""
|
||||||
|
result = await _get("/info/version")
|
||||||
|
return result is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def get_object_list() -> list:
|
||||||
|
"""모니터링 대상 객체(서버/서비스) 목록 조회."""
|
||||||
|
result = await _get("/object")
|
||||||
|
if not result:
|
||||||
|
return []
|
||||||
|
return result.get("result", [])
|
||||||
|
|
||||||
|
|
||||||
|
async def get_server_metrics(obj_hash: int) -> dict:
|
||||||
|
"""
|
||||||
|
특정 서버의 실시간 메트릭 조회.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"cpu": float, # CPU 사용률 (%)
|
||||||
|
"heap_used": int, # Heap 사용량 (MB)
|
||||||
|
"heap_max": int, # Heap 최대 (MB)
|
||||||
|
"tps": float, # 초당 트랜잭션
|
||||||
|
"active_service": int, # 활성 서비스 수
|
||||||
|
"response_time": float, # 평균 응답시간 (ms)
|
||||||
|
"error_rate": float, # 에러율 (%)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
result = await _get(f"/object/{obj_hash}/realtime/summary")
|
||||||
|
if not result:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
data = result.get("result", {})
|
||||||
|
return {
|
||||||
|
"cpu": data.get("cpuPct", 0.0),
|
||||||
|
"heap_used": data.get("heapUsed", 0) // (1024 * 1024),
|
||||||
|
"heap_max": data.get("heapMax", 0) // (1024 * 1024),
|
||||||
|
"tps": data.get("tps", 0.0),
|
||||||
|
"active_service": data.get("activeService", 0),
|
||||||
|
"response_time": data.get("elapsedTime", 0.0),
|
||||||
|
"error_rate": data.get("errorRate", 0.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def get_all_metrics() -> list:
|
||||||
|
"""
|
||||||
|
모든 모니터링 대상 서버의 실시간 메트릭 수집.
|
||||||
|
|
||||||
|
Returns: [{"name": str, "type": str, "metrics": {...}}, ...]
|
||||||
|
"""
|
||||||
|
objects = await get_object_list()
|
||||||
|
if not objects:
|
||||||
|
return []
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for obj in objects:
|
||||||
|
obj_hash = obj.get("objHash")
|
||||||
|
if not obj_hash:
|
||||||
|
continue
|
||||||
|
|
||||||
|
metrics = await get_server_metrics(obj_hash)
|
||||||
|
results.append({
|
||||||
|
"name": obj.get("objName", "unknown"),
|
||||||
|
"type": obj.get("objType", "unknown"),
|
||||||
|
"host": obj.get("address", ""),
|
||||||
|
"metrics": metrics,
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
async def get_active_services(obj_hash: int) -> list:
|
||||||
|
"""활성 서비스(현재 처리 중인 요청) 목록 조회."""
|
||||||
|
result = await _get(f"/object/{obj_hash}/activeService")
|
||||||
|
if not result:
|
||||||
|
return []
|
||||||
|
return result.get("result", [])
|
||||||
|
|
||||||
|
|
||||||
|
async def get_xlog_recent(obj_hash: int, limit: int = 20) -> list:
|
||||||
|
"""최근 트랜잭션 X-Log 조회."""
|
||||||
|
result = await _get(f"/xlog/realtime/{obj_hash}", params={"limit": limit})
|
||||||
|
if not result:
|
||||||
|
return []
|
||||||
|
return result.get("result", [])
|
||||||
|
|
||||||
|
|
||||||
|
async def get_alert_list(obj_hash: int = None) -> list:
|
||||||
|
"""Scouter 경보 목록 조회."""
|
||||||
|
path = f"/object/{obj_hash}/alert" if obj_hash else "/alert"
|
||||||
|
result = await _get(path)
|
||||||
|
if not result:
|
||||||
|
return []
|
||||||
|
return result.get("result", [])
|
||||||
|
|
||||||
|
|
||||||
|
async def get_summary() -> dict:
|
||||||
|
"""
|
||||||
|
전체 모니터링 현황 요약 (GUARDiA 대시보드용).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{
|
||||||
|
"enabled": bool,
|
||||||
|
"total_servers": int,
|
||||||
|
"avg_cpu": float,
|
||||||
|
"avg_tps": float,
|
||||||
|
"avg_response_ms": float,
|
||||||
|
"critical_servers": [str], # CPU > 80% or 에러율 > 5%
|
||||||
|
"servers": [...]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if not _ENABLED:
|
||||||
|
return {"enabled": False, "total_servers": 0, "avg_cpu": 0, "avg_tps": 0}
|
||||||
|
|
||||||
|
all_metrics = await get_all_metrics()
|
||||||
|
if not all_metrics:
|
||||||
|
return {"enabled": True, "total_servers": 0, "avg_cpu": 0, "avg_tps": 0, "servers": []}
|
||||||
|
|
||||||
|
total = len(all_metrics)
|
||||||
|
avg_cpu = sum(m["metrics"].get("cpu", 0) for m in all_metrics) / total
|
||||||
|
avg_tps = sum(m["metrics"].get("tps", 0) for m in all_metrics) / total
|
||||||
|
avg_resp = sum(m["metrics"].get("response_time", 0) for m in all_metrics) / total
|
||||||
|
|
||||||
|
critical = [
|
||||||
|
m["name"] for m in all_metrics
|
||||||
|
if m["metrics"].get("cpu", 0) > 80
|
||||||
|
or m["metrics"].get("error_rate", 0) > 5
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"enabled": True,
|
||||||
|
"total_servers": total,
|
||||||
|
"avg_cpu": round(avg_cpu, 1),
|
||||||
|
"avg_tps": round(avg_tps, 2),
|
||||||
|
"avg_response_ms": round(avg_resp, 1),
|
||||||
|
"critical_servers": critical,
|
||||||
|
"servers": all_metrics,
|
||||||
|
}
|
||||||
@ -39,6 +39,7 @@ from routers import (
|
|||||||
license as license_router,
|
license as license_router,
|
||||||
learning,
|
learning,
|
||||||
push as push_router,
|
push as push_router,
|
||||||
|
scouter as scouter_router,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -226,6 +227,9 @@ app.include_router(license_router.router)
|
|||||||
# ── G-10: PWA Push 알림 ──────────────────────────────────────────────────
|
# ── G-10: PWA Push 알림 ──────────────────────────────────────────────────
|
||||||
app.include_router(push_router.router)
|
app.include_router(push_router.router)
|
||||||
|
|
||||||
|
# Scouter APM
|
||||||
|
app.include_router(scouter_router.router)
|
||||||
|
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
222
itsm/routers/scouter.py
Normal file
222
itsm/routers/scouter.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
"""
|
||||||
|
Scouter APM 연동 API 라우터
|
||||||
|
|
||||||
|
엔드포인트:
|
||||||
|
GET /api/scouter/status — Scouter 연결 상태 + 전체 요약
|
||||||
|
GET /api/scouter/servers — 모니터링 대상 서버 목록
|
||||||
|
GET /api/scouter/servers/{hash}/metrics — 특정 서버 실시간 메트릭
|
||||||
|
GET /api/scouter/servers/{hash}/services — 활성 서비스 목록
|
||||||
|
GET /api/scouter/servers/{hash}/xlog — 최근 트랜잭션 X-Log
|
||||||
|
GET /api/scouter/alerts — 경보 목록
|
||||||
|
POST /api/scouter/agent/deploy — Tomcat 에이전트 SSH 배포 (ADMIN/ENGINEER)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from core.auth import get_current_user
|
||||||
|
from database import get_db
|
||||||
|
from models import User, UserRole
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
router = APIRouter(prefix="/api/scouter", tags=["scouter"])
|
||||||
|
|
||||||
|
|
||||||
|
# ── 스키마 ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class AgentDeployRequest(BaseModel):
|
||||||
|
server_id: int # CMDB 서버 ID
|
||||||
|
scouter_host: Optional[str] = None # Scouter 서버 호스트 (기본: 환경변수)
|
||||||
|
scouter_port: int = 6100 # Scouter 수집 포트 (기본: 6100)
|
||||||
|
|
||||||
|
|
||||||
|
# ── 엔드포인트 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@router.get("/status")
|
||||||
|
async def scouter_status(
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Scouter 연결 상태 및 전체 모니터링 현황 요약."""
|
||||||
|
from core.scouter import get_summary, is_available
|
||||||
|
available = await is_available()
|
||||||
|
summary = await get_summary()
|
||||||
|
return {
|
||||||
|
"connected": available,
|
||||||
|
"host": os.getenv("SCOUTER_HOST", ""),
|
||||||
|
"port": os.getenv("SCOUTER_HTTP_PORT", "6180"),
|
||||||
|
**summary,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/servers")
|
||||||
|
async def list_servers(
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""모니터링 대상 객체(서버/서비스) 목록."""
|
||||||
|
from core.scouter import get_object_list
|
||||||
|
return {"servers": await get_object_list()}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/servers/{obj_hash}/metrics")
|
||||||
|
async def server_metrics(
|
||||||
|
obj_hash: int,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""특정 서버 실시간 메트릭 (CPU, Heap, TPS, 응답시간)."""
|
||||||
|
from core.scouter import get_server_metrics
|
||||||
|
metrics = await get_server_metrics(obj_hash)
|
||||||
|
if not metrics:
|
||||||
|
raise HTTPException(404, "서버 메트릭을 가져올 수 없습니다.")
|
||||||
|
return {"obj_hash": obj_hash, "metrics": metrics}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/servers/{obj_hash}/services")
|
||||||
|
async def active_services(
|
||||||
|
obj_hash: int,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""활성 서비스 목록 (현재 처리 중인 요청)."""
|
||||||
|
from core.scouter import get_active_services
|
||||||
|
return {"services": await get_active_services(obj_hash)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/servers/{obj_hash}/xlog")
|
||||||
|
async def xlog(
|
||||||
|
obj_hash: int,
|
||||||
|
limit: int = 20,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""최근 트랜잭션 X-Log 조회."""
|
||||||
|
from core.scouter import get_xlog_recent
|
||||||
|
return {"xlogs": await get_xlog_recent(obj_hash, limit)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/alerts")
|
||||||
|
async def alerts(
|
||||||
|
obj_hash: Optional[int] = None,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Scouter 경보 목록."""
|
||||||
|
from core.scouter import get_alert_list
|
||||||
|
return {"alerts": await get_alert_list(obj_hash)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/agent/deploy")
|
||||||
|
async def deploy_agent(
|
||||||
|
body: AgentDeployRequest,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Tomcat 서버에 Scouter 에이전트 SSH 배포 (ADMIN/ENGINEER 전용).
|
||||||
|
|
||||||
|
배포 절차:
|
||||||
|
1. CMDB에서 서버 SSH 접속 정보 조회 (AES-256-GCM 복호화)
|
||||||
|
2. scouter-agent.jar 및 설정 파일 SFTP 전송
|
||||||
|
3. Tomcat JAVA_OPTS에 -javaagent 옵션 추가
|
||||||
|
4. Tomcat 재기동 (선택)
|
||||||
|
"""
|
||||||
|
if current_user.role not in (UserRole.ADMIN, UserRole.ENGINEER):
|
||||||
|
raise HTTPException(403, "ADMIN 또는 ENGINEER만 에이전트를 배포할 수 있습니다.")
|
||||||
|
|
||||||
|
from models import Server
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
srv = await db.get(Server, body.server_id)
|
||||||
|
if not srv:
|
||||||
|
raise HTTPException(404, f"서버 ID {body.server_id}를 찾을 수 없습니다.")
|
||||||
|
|
||||||
|
scouter_host = body.scouter_host or os.getenv("SCOUTER_HOST", "localhost")
|
||||||
|
scouter_port = body.scouter_port
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await _deploy_agent_ssh(srv, scouter_host, scouter_port, current_user.username)
|
||||||
|
return {
|
||||||
|
"message": f"Scouter 에이전트 배포 완료: {srv.server_name}",
|
||||||
|
"server": srv.server_name,
|
||||||
|
"scouter": f"{scouter_host}:{scouter_port}",
|
||||||
|
"deployed_at": result.get("deployed_at"),
|
||||||
|
"restart_required": True,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(500, f"에이전트 배포 실패: {str(e)[:200]}")
|
||||||
|
|
||||||
|
|
||||||
|
async def _deploy_agent_ssh(srv, scouter_host: str, scouter_port: int, actor: str) -> dict:
|
||||||
|
"""SSH/SFTP로 Scouter 에이전트 파일 배포."""
|
||||||
|
from datetime import datetime
|
||||||
|
from core.ssh_exec import execute_ssh_command
|
||||||
|
|
||||||
|
# GUARDIA_ROOT/setup/scouter/ 에서 에이전트 파일 확인
|
||||||
|
agent_local = _find_agent_jar()
|
||||||
|
if not agent_local:
|
||||||
|
raise ValueError("scouter-agent.jar를 찾을 수 없습니다. setup/scouter/에 파일을 배치하세요.")
|
||||||
|
|
||||||
|
# Scouter 에이전트 설정 파일 생성 (임시)
|
||||||
|
conf_content = f"""
|
||||||
|
net.collector.ip={scouter_host}
|
||||||
|
net.collector.udp_port={scouter_port}
|
||||||
|
net.collector.tcp_port={scouter_port}
|
||||||
|
obj_name={srv.server_name}
|
||||||
|
trace_interservice_enabled=true
|
||||||
|
hook_method_patterns=io.guardia.*,com.agency.*
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 원격 배포 디렉토리
|
||||||
|
remote_dir = "/app/scouter"
|
||||||
|
|
||||||
|
# SSH로 디렉토리 생성
|
||||||
|
result = await execute_ssh_command(
|
||||||
|
srv.id,
|
||||||
|
f"mkdir -p {remote_dir} && echo OK",
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
if not result.success:
|
||||||
|
raise ValueError(f"원격 디렉토리 생성 실패: {result.error}")
|
||||||
|
|
||||||
|
# 설정 파일 생성
|
||||||
|
await execute_ssh_command(
|
||||||
|
srv.id,
|
||||||
|
f"cat > {remote_dir}/agent.conf << 'SCCONF'\n{conf_content}\nSCCONF",
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
# JAVA_OPTS 에 javaagent 옵션 추가 (Tomcat setenv.sh)
|
||||||
|
javaagent_opts = (
|
||||||
|
f'-javaagent:{remote_dir}/scouter-agent.jar '
|
||||||
|
f'-Dscouter.config={remote_dir}/agent.conf'
|
||||||
|
)
|
||||||
|
setenv_cmd = f"""
|
||||||
|
SETENV=/app/tomcat/bin/setenv.sh
|
||||||
|
if [ -f "$SETENV" ]; then
|
||||||
|
grep -q 'scouter' "$SETENV" || echo 'export JAVA_OPTS="$JAVA_OPTS {javaagent_opts}"' >> "$SETENV"
|
||||||
|
else
|
||||||
|
echo '#!/bin/bash' > "$SETENV"
|
||||||
|
echo 'export JAVA_OPTS="$JAVA_OPTS {javaagent_opts}"' >> "$SETENV"
|
||||||
|
chmod +x "$SETENV"
|
||||||
|
fi
|
||||||
|
echo "setenv.sh 업데이트 완료"
|
||||||
|
"""
|
||||||
|
await execute_ssh_command(srv.id, setenv_cmd, timeout=30)
|
||||||
|
|
||||||
|
return {"deployed_at": datetime.utcnow().isoformat()}
|
||||||
|
|
||||||
|
|
||||||
|
def _find_agent_jar() -> str:
|
||||||
|
"""Scouter 에이전트 JAR 파일 탐색."""
|
||||||
|
import pathlib
|
||||||
|
candidates = [
|
||||||
|
pathlib.Path(__file__).parent.parent.parent / "setup" / "scouter" / "scouter-agent.jar",
|
||||||
|
pathlib.Path("/opt/scouter/agent/scouter-agent.jar"),
|
||||||
|
pathlib.Path("/app/scouter/scouter-agent.jar"),
|
||||||
|
]
|
||||||
|
for p in candidates:
|
||||||
|
if p.exists():
|
||||||
|
return str(p)
|
||||||
|
return ""
|
||||||
103
setup/scouter/download_scouter.sh
Normal file
103
setup/scouter/download_scouter.sh
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ==============================================================
|
||||||
|
# Scouter 에이전트/서버 다운로드 스크립트
|
||||||
|
# ==============================================================
|
||||||
|
# 사용법:
|
||||||
|
# bash setup/scouter/download_scouter.sh
|
||||||
|
# SCOUTER_VER=2.20.0 bash download_scouter.sh
|
||||||
|
# ==============================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
SCOUTER_VER="${SCOUTER_VER:-2.20.0}"
|
||||||
|
SCOUTER_MIRROR="${SCOUTER_MIRROR:-https://github.com/scouter-project/scouter/releases/download}"
|
||||||
|
|
||||||
|
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||||
|
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||||
|
info() { echo -e " $*"; }
|
||||||
|
|
||||||
|
echo "=================================================="
|
||||||
|
echo " Scouter $SCOUTER_VER 다운로드"
|
||||||
|
echo " 저장 위치: $SCRIPT_DIR"
|
||||||
|
echo "=================================================="
|
||||||
|
|
||||||
|
# ── scouter-agent.jar (Tomcat WAS에 배포) ────────────────────
|
||||||
|
AGENT_URL="${SCOUTER_MIRROR}/v${SCOUTER_VER}/scouter-agent.tar.gz"
|
||||||
|
AGENT_TAR="$SCRIPT_DIR/scouter-agent.tar.gz"
|
||||||
|
|
||||||
|
if [[ ! -f "$SCRIPT_DIR/scouter-agent.jar" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "[1/3] Scouter 에이전트 다운로드..."
|
||||||
|
wget -q "$AGENT_URL" -O "$AGENT_TAR" \
|
||||||
|
|| { warn "에이전트 다운로드 실패 — SCOUTER_MIRROR 환경변수 설정"; exit 1; }
|
||||||
|
tar -xzf "$AGENT_TAR" -C "$SCRIPT_DIR" --strip-components=1 \
|
||||||
|
"scouter-agent/scouter-agent.jar" 2>/dev/null \
|
||||||
|
|| tar -xzf "$AGENT_TAR" -C "$SCRIPT_DIR" 2>/dev/null
|
||||||
|
rm -f "$AGENT_TAR"
|
||||||
|
ok "scouter-agent.jar 다운로드 완료"
|
||||||
|
else
|
||||||
|
info "scouter-agent.jar 이미 존재"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── scouter-server (Paper) ───────────────────────────────────
|
||||||
|
SERVER_URL="${SCOUTER_MIRROR}/v${SCOUTER_VER}/scouter-all-${SCOUTER_VER}.tar.gz"
|
||||||
|
|
||||||
|
if [[ ! -d "$SCRIPT_DIR/scouter-server" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "[2/3] Scouter 서버(Paper) 다운로드..."
|
||||||
|
wget -q "$SERVER_URL" -O "$SCRIPT_DIR/scouter-all.tar.gz" \
|
||||||
|
&& tar -xzf "$SCRIPT_DIR/scouter-all.tar.gz" -C "$SCRIPT_DIR" \
|
||||||
|
&& rm -f "$SCRIPT_DIR/scouter-all.tar.gz" \
|
||||||
|
&& ok "Scouter 서버 다운로드 완료" \
|
||||||
|
|| warn "서버 패키지 다운로드 실패 — Docker 이미지 사용 권장"
|
||||||
|
else
|
||||||
|
info "scouter-server 이미 존재"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 에이전트 설정 템플릿 생성 ─────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "[3/3] 에이전트 설정 템플릿 생성..."
|
||||||
|
cat > "$SCRIPT_DIR/agent.conf.template" << 'CONFEOF'
|
||||||
|
# Scouter 에이전트 설정 템플릿
|
||||||
|
# GUARDiA가 자동으로 서버명과 Scouter 호스트를 치환합니다
|
||||||
|
|
||||||
|
# Scouter 서버 연결
|
||||||
|
net.collector.ip=${SCOUTER_HOST}
|
||||||
|
net.collector.udp_port=${SCOUTER_PORT}
|
||||||
|
net.collector.tcp_port=${SCOUTER_PORT}
|
||||||
|
|
||||||
|
# 서버 식별
|
||||||
|
obj_name=${SERVER_NAME}
|
||||||
|
obj_type=tomcat
|
||||||
|
|
||||||
|
# 성능 수집 설정
|
||||||
|
trace_interservice_enabled=true
|
||||||
|
trace_user_enabled=true
|
||||||
|
trace_user_cookie_limit=100
|
||||||
|
counter_mem_enabled=true
|
||||||
|
|
||||||
|
# 느린 쿼리 추적 (임계값: 1초)
|
||||||
|
hook_jdbc_pstmt_enabled=true
|
||||||
|
profile_jdbc_fetch_max=100
|
||||||
|
sqltrace_limit_elapsed_ms=1000
|
||||||
|
|
||||||
|
# 트랜잭션 추적
|
||||||
|
trace_active_transaction_enabled=true
|
||||||
|
trace_active_transaction_yellow_time=3000
|
||||||
|
trace_active_transaction_red_time=8000
|
||||||
|
CONFEOF
|
||||||
|
|
||||||
|
ok "agent.conf.template 생성 완료"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=================================================="
|
||||||
|
ok "Scouter 다운로드 완료!"
|
||||||
|
echo ""
|
||||||
|
info "파일 목록:"
|
||||||
|
ls -lh "$SCRIPT_DIR/"*.jar "$SCRIPT_DIR/"*.template 2>/dev/null || true
|
||||||
|
echo ""
|
||||||
|
info "에이전트 배포: GUARDiA ITSM → 서버 관리 → Scouter 에이전트 배포"
|
||||||
|
info " 또는 API: POST /api/scouter/agent/deploy"
|
||||||
|
echo "=================================================="
|
||||||
@ -35,6 +35,16 @@ echo "=================================================="
|
|||||||
|
|
||||||
[[ $EUID -eq 0 ]] || fail "root 권한으로 실행하세요: sudo bash $0"
|
[[ $EUID -eq 0 ]] || fail "root 권한으로 실행하세요: sudo bash $0"
|
||||||
|
|
||||||
|
# 공통 라이브러리 로드
|
||||||
|
LIB_DIR="$SCRIPT_DIR/lib"
|
||||||
|
# shellcheck source=setup/lib/db_select.sh
|
||||||
|
source "$LIB_DIR/db_select.sh"
|
||||||
|
# shellcheck source=setup/lib/gitea_setup.sh
|
||||||
|
source "$LIB_DIR/gitea_setup.sh"
|
||||||
|
|
||||||
|
# ── DB 선택 (설치 전 먼저 물어봄) ────────────────────────────
|
||||||
|
select_database
|
||||||
|
|
||||||
# ── 테스트 모드 ──────────────────────────────────────────────
|
# ── 테스트 모드 ──────────────────────────────────────────────
|
||||||
if [[ "$TEST_MODE" == "--test" ]]; then
|
if [[ "$TEST_MODE" == "--test" ]]; then
|
||||||
echo "=== 설치 검증 모드 ==="
|
echo "=== 설치 검증 모드 ==="
|
||||||
@ -64,6 +74,9 @@ if [[ "$TEST_MODE" == "--test" ]]; then
|
|||||||
-H "Content-Type: application/json" -d "{\"username\":\"admin\",\"password\":\"1111\"}" -o /dev/null'
|
-H "Content-Type: application/json" -d "{\"username\":\"admin\",\"password\":\"1111\"}" -o /dev/null'
|
||||||
check "Fail2ban 실행" systemctl is-active fail2ban
|
check "Fail2ban 실행" systemctl is-active fail2ban
|
||||||
check "Chrony NTP" chronyc tracking
|
check "Chrony NTP" chronyc tracking
|
||||||
|
check "Scouter APM API" bash -c 'curl -sf http://localhost:6180/scouter/v1/info/version -o /dev/null'
|
||||||
|
check "Gitea 서비스" systemctl is-active gitea
|
||||||
|
check "Gitea HTTP" bash -c 'curl -sf http://localhost:3000/api/v1/version -o /dev/null'
|
||||||
check "Nginx 설정" nginx -t
|
check "Nginx 설정" nginx -t
|
||||||
check "Python UTF-8 인코딩" bash -c 'PYTHONIOENCODING=utf-8 python3.11 -c "print(\"OK\")" > /dev/null'
|
check "Python UTF-8 인코딩" bash -c 'PYTHONIOENCODING=utf-8 python3.11 -c "print(\"OK\")" > /dev/null'
|
||||||
|
|
||||||
@ -193,25 +206,24 @@ pip install --upgrade pip -q
|
|||||||
pip install -r "$GUARDIA_ROOT/itsm/requirements.txt" -q
|
pip install -r "$GUARDIA_ROOT/itsm/requirements.txt" -q
|
||||||
ok "Python 패키지 설치 완료"
|
ok "Python 패키지 설치 완료"
|
||||||
|
|
||||||
# ── 4. PostgreSQL 설정 ─────────────────────────────────────
|
# ── 4. PostgreSQL 설정 (DB 선택에 따라 조건부 실행) ─────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[4/10] PostgreSQL 설정..."
|
echo "[4/10] 데이터베이스 설정..."
|
||||||
|
if [[ "$INSTALL_POSTGRES" == "true" ]]; then
|
||||||
systemctl start postgresql
|
systemctl start postgresql
|
||||||
systemctl enable postgresql
|
systemctl enable postgresql
|
||||||
|
setup_postgres
|
||||||
sudo -u postgres psql -tc "SELECT 1 FROM pg_user WHERE usename='guardia'" | grep -q 1 || \
|
else
|
||||||
sudo -u postgres psql -c "CREATE USER guardia WITH PASSWORD 'guardia_secure_pw';"
|
ok "SQLite 사용 — PostgreSQL 설치 건너뜀"
|
||||||
sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='guardia'" | grep -q 1 || \
|
fi
|
||||||
sudo -u postgres psql -c "CREATE DATABASE guardia OWNER guardia;"
|
|
||||||
ok "PostgreSQL 설정 완료"
|
|
||||||
|
|
||||||
# ── 5. 환경 설정 파일 ─────────────────────────────────────
|
# ── 5. 환경 설정 파일 ─────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[5/10] 환경 설정 파일 생성..."
|
echo "[5/10] 환경 설정 파일 생성..."
|
||||||
ENV_FILE="$GUARDIA_ROOT/itsm/.env"
|
ENV_FILE="$GUARDIA_ROOT/itsm/.env"
|
||||||
if [[ ! -f "$ENV_FILE" ]]; then
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
cat > "$ENV_FILE" << 'ENVEOF'
|
cat > "$ENV_FILE" << ENVEOF
|
||||||
DATABASE_URL=postgresql+asyncpg://guardia:guardia_secure_pw@localhost:5432/guardia
|
DATABASE_URL=${DATABASE_URL}
|
||||||
SECRET_KEY=change_this_secret_key_in_production_min_32chars
|
SECRET_KEY=change_this_secret_key_in_production_min_32chars
|
||||||
ALGORITHM=HS256
|
ALGORITHM=HS256
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES=480
|
ACCESS_TOKEN_EXPIRE_MINUTES=480
|
||||||
@ -221,10 +233,12 @@ GUARDIA_LLM_MODEL=llama3.1:8b
|
|||||||
MESSENGER_BASE_URL=http://localhost:8002
|
MESSENGER_BASE_URL=http://localhost:8002
|
||||||
MESSENGER_OPS_ROOM=ops
|
MESSENGER_OPS_ROOM=ops
|
||||||
CATALINA_HOME=/app/tomcat
|
CATALINA_HOME=/app/tomcat
|
||||||
|
ENABLE_VECTOR=${INSTALL_PGVECTOR:-false}
|
||||||
ENVEOF
|
ENVEOF
|
||||||
warn ".env 생성됨 — SECRET_KEY를 변경하세요: $ENV_FILE"
|
warn ".env 생성됨 — SECRET_KEY를 변경하세요: $ENV_FILE"
|
||||||
else
|
else
|
||||||
info ".env 파일 이미 존재 — 건너뜀"
|
# 기존 .env에 DATABASE_URL 업데이트
|
||||||
|
write_db_env "$ENV_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── 6. DB 초기화 (스키마 불일치 자동 감지·복구) ─────────────────────────
|
# ── 6. DB 초기화 (스키마 불일치 자동 감지·복구) ─────────────────────────
|
||||||
@ -313,7 +327,61 @@ ok "Nginx 설정 완료"
|
|||||||
|
|
||||||
# ── 9. 방화벽 ────────────────────────────────────────────
|
# ── 9. 방화벽 ────────────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[9/13] Ollama (온프레미스 sLLM 서버) 설치..."
|
echo "[9/14] Scouter APM 서버 설치..."
|
||||||
|
INSTALL_SCOUTER="${INSTALL_SCOUTER:-true}"
|
||||||
|
if [[ "$INSTALL_SCOUTER" == "true" ]]; then
|
||||||
|
SCOUTER_VER="${SCOUTER_VER:-2.20.0}"
|
||||||
|
SCOUTER_HOME="/opt/scouter-server"
|
||||||
|
SCOUTER_PORT="${SCOUTER_PORT:-6100}"
|
||||||
|
|
||||||
|
# Scouter 서버 다운로드
|
||||||
|
if [[ ! -d "$SCOUTER_HOME" ]]; then
|
||||||
|
SCOUTER_URL="${SCOUTER_MIRROR:-https://github.com/scouter-project/scouter/releases/download/v${SCOUTER_VER}/scouter-all-${SCOUTER_VER}.tar.gz}"
|
||||||
|
wget -q "$SCOUTER_URL" -O /tmp/scouter-all.tar.gz 2>/dev/null \
|
||||||
|
|| warn "Scouter 다운로드 실패 — Docker 이미지 사용 권장: docker compose up -d scouter-server"
|
||||||
|
if [[ -f /tmp/scouter-all.tar.gz ]]; then
|
||||||
|
mkdir -p "$SCOUTER_HOME"
|
||||||
|
tar -xzf /tmp/scouter-all.tar.gz -C "$SCOUTER_HOME" --strip-components=1 2>/dev/null || true
|
||||||
|
rm -f /tmp/scouter-all.tar.gz
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Scouter 서버 systemd 등록
|
||||||
|
if [[ -d "$SCOUTER_HOME" ]]; then
|
||||||
|
cat > /etc/systemd/system/scouter-server.service << SCEOF
|
||||||
|
[Unit]
|
||||||
|
Description=Scouter APM Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=$SCOUTER_HOME
|
||||||
|
ExecStart=/bin/bash $SCOUTER_HOME/startup.sh
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
SCEOF
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable scouter-server 2>/dev/null
|
||||||
|
systemctl start scouter-server 2>/dev/null || true
|
||||||
|
ok "Scouter APM 서버 등록 완료 (HTTP API: 포트 6180)"
|
||||||
|
else
|
||||||
|
warn "Scouter 미설치 — docker compose up -d scouter-server 사용"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 에이전트 JAR 다운로드
|
||||||
|
SCOUTER_AGENT_DIR="$SCRIPT_DIR/scouter"
|
||||||
|
mkdir -p "$SCOUTER_AGENT_DIR"
|
||||||
|
bash "$SCRIPT_DIR/scouter/download_scouter.sh" 2>/dev/null \
|
||||||
|
&& ok "Scouter 에이전트 다운로드 완료" \
|
||||||
|
|| warn "Scouter 에이전트 다운로드 실패 — 수동 다운로드 필요"
|
||||||
|
else
|
||||||
|
warn "Scouter 설치 건너뜀 (INSTALL_SCOUTER=false)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[10/14] Ollama (온프레미스 sLLM 서버) 설치..."
|
||||||
# Ollama: GUARDiA AI 기능의 핵심 — 없으면 챗봇/RCA/티켓분류 모두 비작동
|
# Ollama: GUARDiA AI 기능의 핵심 — 없으면 챗봇/RCA/티켓분류 모두 비작동
|
||||||
OLLAMA_INSTALL="${OLLAMA_INSTALL:-online}" # online | offline | skip
|
OLLAMA_INSTALL="${OLLAMA_INSTALL:-online}" # online | offline | skip
|
||||||
OLLAMA_MODELS="${OLLAMA_MODELS:-llama3.1:8b}"
|
OLLAMA_MODELS="${OLLAMA_MODELS:-llama3.1:8b}"
|
||||||
@ -352,7 +420,7 @@ fi
|
|||||||
|
|
||||||
# ── 10. 보안·운영 도구 설치 ──────────────────────────────
|
# ── 10. 보안·운영 도구 설치 ──────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[10/13] 보안·운영 도구 (Fail2ban / NTP / JDK 다중 버전 / Logrotate)..."
|
echo "[11/14] 보안·운영 도구 (Fail2ban / NTP / JDK 다중 버전 / Logrotate)..."
|
||||||
|
|
||||||
# Fail2ban — SSH 무차별 대입 방지
|
# Fail2ban — SSH 무차별 대입 방지
|
||||||
apt-get install -y -qq fail2ban
|
apt-get install -y -qq fail2ban
|
||||||
@ -405,7 +473,7 @@ ok "Logrotate 설정 완료 (catalina.out 14일 보관)"
|
|||||||
|
|
||||||
# ── 11. 방화벽 설정 ──────────────────────────────────────
|
# ── 11. 방화벽 설정 ──────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[11/13] 방화벽 설정..."
|
echo "[12/14] 방화벽 설정..."
|
||||||
if command -v ufw &>/dev/null; then
|
if command -v ufw &>/dev/null; then
|
||||||
ufw allow 22/tcp 2>/dev/null || true
|
ufw allow 22/tcp 2>/dev/null || true
|
||||||
ufw allow 80/tcp 2>/dev/null || true
|
ufw allow 80/tcp 2>/dev/null || true
|
||||||
@ -417,8 +485,22 @@ fi
|
|||||||
|
|
||||||
# ── 12. 최종 상태 확인 ───────────────────────────────────
|
# ── 12. 최종 상태 확인 ───────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[12/13] 서비스 상태 확인..."
|
echo "[13/14] Gitea 설치 및 초기화..."
|
||||||
for svc in tomcat9 ollama guardia-itsm nginx postgresql redis-server fail2ban chrony; do
|
INSTALL_GITEA="${INSTALL_GITEA:-true}"
|
||||||
|
if [[ "$INSTALL_GITEA" == "true" ]]; then
|
||||||
|
install_gitea
|
||||||
|
# 개발자 계정 생성 (환경변수로 지정 가능: GITEA_DEV_USERS="user1 user2")
|
||||||
|
for devuser in ${GITEA_DEV_USERS:-engineer1 engineer2 pm1}; do
|
||||||
|
create_dev_user "$devuser" "Dev@guardia!" "${devuser}@guardia.local"
|
||||||
|
done
|
||||||
|
init_gitea_repos
|
||||||
|
ok "Gitea 설치 + 초기화 완료"
|
||||||
|
else
|
||||||
|
warn "Gitea 설치 건너뜀 (INSTALL_GITEA=false)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[14/14] 서비스 상태 확인..."
|
||||||
|
for svc in tomcat9 ollama guardia-itsm nginx postgresql redis-server fail2ban chrony gitea; do
|
||||||
if systemctl is-active "$svc" &>/dev/null; then
|
if systemctl is-active "$svc" &>/dev/null; then
|
||||||
ok "$svc 실행 중"
|
ok "$svc 실행 중"
|
||||||
else
|
else
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user