feat(itsm): G-1~G-12 확장 기능 + 하네스/봇/설치스크립트 구현

G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현
G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건)
G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST)
G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직
G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트
G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트
G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API
G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트
G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트
G-10: core/push_notify.py + routers/push.py + PushSubscription 모델
G-11: approvals 다중승인 (위임/서명/기한초과/마감연장)
G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh

하네스: guardia-orchestrator 확장기능 Phase 반영
봇명령어: /sr /status /license /bulk 슬래시 명령어 추가
설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DESKTOP-TKLFCPRython 2026-05-29 18:18:52 +09:00
commit 938b25f286
16 changed files with 8488 additions and 0 deletions

362
01_분석설계서.md Normal file
View File

@ -0,0 +1,362 @@
# GUARDiA ITSM + Messenger — 분석 및 설계서
> **문서번호**: GUARDIA-DS-001
> **버전**: 1.0
> **작성일**: 2026-05-25
> **작성자**: GUARDiA 개발팀
> **보안등급**: 내부용 (대외비)
---
## 1. 문서 목적
본 문서는 GUARDiA ITSM(IT Service Management) 플랫폼과 GUARDiA Messenger 시스템의 요구사항 분석, 시스템 설계, 데이터 설계, API 설계를 기술한다. 본 문서는 개발자, 운영자, 품질관리자가 시스템을 이해하고 유지보수하는 데 활용한다.
---
## 2. 시스템 개요
### 2-1. GUARDiA ITSM
**GUARDiA ITSM**은 공공기관 IT 운영을 위한 온프레미스 서비스관리 플랫폼이다.
| 항목 | 내용 |
|------|------|
| 목적 | 공공기관 IT 인프라 SR(Service Request) 접수·처리·이력 관리 |
| 운영 방식 | 온프레미스 (인터넷 완전 차단 환경 대응) |
| 주요 사용자 | 운영 엔지니어, PM, 기관 담당자(고객) |
| 기술 스택 | FastAPI, SQLAlchemy (Async), SQLite, Python 3.11+ |
| 인증 방식 | JWT (RS256), 역할 기반 접근 제어 (RBAC) |
### 2-2. GUARDiA Messenger
**GUARDiA Messenger**는 ITSM과 연동되는 사내 메신저로, SR 알림, 봇 명령, 엔지니어 채팅을 지원한다.
| 항목 | 내용 |
|------|------|
| 목적 | 운영팀 실시간 소통 및 ITSM 이벤트 알림 |
| 연동 방식 | GUARDiA ITSM Webhook API |
| 주요 기능 | 채팅, 봇 명령, SR 알림, 공지 |
---
## 3. 요구사항 분석
### 3-1. 기능적 요구사항 (Functional Requirements)
| ID | 분류 | 요구사항 | 우선순위 |
|----|------|----------|---------|
| FR-001 | SR 관리 | SR 접수·파싱·배정·승인·처리·완료 워크플로우 | 필수 |
| FR-002 | SR 관리 | SR 상태 실시간 SSE 이벤트 브로드캐스트 | 필수 |
| FR-003 | 인증 | JWT 기반 역할별 로그인 (ADMIN/PM/ENGINEER/CUSTOMER) | 필수 |
| FR-004 | CMDB | 기관·서버 자산 등록 및 조회 | 필수 |
| FR-005 | SSH | 서버 원격 명령 실행 (asyncssh) | 필수 |
| FR-006 | SSL | SSL 인증서 만료 모니터링 및 갱신 이력 관리 | 필수 |
| FR-007 | PM | 정기 PM 체크리스트 템플릿 및 반복 스케줄 관리 | 필수 |
| FR-008 | 장애 | P1~P4 장애 등록·상태 관리·RCA 기록 | 필수 |
| FR-009 | 당직 | 온콜/당직 일정 관리 | 권장 |
| FR-010 | 배치 | 배치 작업 등록·실행·이력 관리 | 권장 |
| FR-011 | CI/CD | Jenkins 파이프라인 연동 (빌드·배포·콜백) | 선택 |
| FR-012 | 알림 | 이메일(SMTP) + 메신저 Webhook 알림 | 필수 |
| FR-013 | 보고서 | 작업이력·PM결과 Excel 다운로드 | 권장 |
| FR-014 | 감사 | 해시 체인 기반 감사 로그 | 필수 |
| FR-015 | KB | 장애 처리 지식 베이스 문서 관리 | 권장 |
| FR-016 | 상용화 | 라이선스 키 기반 에디션 제어 (COMMUNITY / STANDARD / ENTERPRISE) | 필수 |
| FR-017 | 상용화 | 에디션별 기관·사용자·서버 수량 및 기능(LDAP/PAM/AI/CICD 등) 접근 제한 | 필수 |
### 3-2. 비기능적 요구사항 (Non-Functional Requirements)
| ID | 분류 | 요구사항 |
|----|------|---------|
| NFR-001 | 보안 | AES-256-GCM 서버 자격증명 암호화 |
| NFR-002 | 보안 | API 응답에 IP·SSH계정·비밀번호 절대 미포함 |
| NFR-003 | 보안 | root SSH 직접 접속 차단 |
| NFR-004 | 보안 | 명령어 안전성 검증 (위험 패턴 차단) |
| NFR-005 | 가용성 | 99% 이상 가용성 목표 |
| NFR-006 | 성능 | API 응답 시간 500ms 이하 (DB 조회 기준) |
| NFR-007 | 호환성 | RHEL 8/9, CentOS 7/8, Ubuntu 20.04/22.04 |
| NFR-008 | 호환성 | Windows Server 2019/2022 |
| NFR-009 | 격리 | 외부 인터넷 완전 차단 환경 동작 가능 |
| NFR-010 | 감사 | 모든 SR 처리 이력 블록체인형 해시 체인 보존 |
---
## 4. 시스템 아키텍처
### 4-1. 전체 구성도
```
┌─────────────────────────────────────────────────────────────────┐
│ 클라이언트 레이어 │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ 웹 브라우저 │ │ 모바일 앱 │ │ GUARDiA Messenger │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬─────────────┘ │
└─────────┼─────────────────┼─────────────────────┼───────────────┘
│ HTTPS │ HTTPS │ WebSocket/HTTP
┌─────────▼─────────────────▼─────────────────────▼───────────────┐
│ API 게이트웨이 레이어 │
│ Nginx (Reverse Proxy + TLS Termination) │
└─────────────────────────────┬───────────────────────────────────┘
│ HTTP (내부)
┌─────────────────────────────▼───────────────────────────────────┐
│ 애플리케이션 레이어 │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ GUARDiA ITSM (FastAPI) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │
│ │ │ SR 관리 │ │ SSH 실행 │ │ SSL 감시 │ │ PM 관리 │ │ │
│ │ │ tasks │ │ ssh_exec │ │ssl_mgr │ │ pm │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │
│ │ │ 장애관리 │ │ 배치작업 │ │ CI/CD │ │ Scheduler │ │ │
│ │ │incidents │ │ batch │ │ cicd │ │(APSchedul)│ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └───────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ GUARDiA Messenger (FastAPI) │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│ SQLAlchemy (Async)
┌──────────────────────────────▼──────────────────────────────────┐
│ 데이터 레이어 │
│ SQLite (개발/소규모) → PostgreSQL (운영 권장) │
└─────────────────────────────────────────────────────────────────┘
│ asyncssh
┌──────────────────────────────▼──────────────────────────────────┐
│ 대상 서버 레이어 │
│ WEB (Nginx/Apache) WAS (Tomcat/JBoss) DB (PostgreSQL/Oracle) │
└─────────────────────────────────────────────────────────────────┘
```
### 4-2. GUARDiA ITSM 컴포넌트 설명
| 컴포넌트 | 파일 | 역할 |
|---------|------|------|
| 진입점 | `main.py` | FastAPI 앱, 라우터 등록, lifespan |
| ORM/스키마 | `models.py` | SQLAlchemy 모델 + Pydantic 스키마 |
| DB 세션 | `database.py` | AsyncSession 팩토리 |
| 인증 | `core/auth.py` | JWT 발급/검증 |
| SSH 실행 | `core/ssh_exec.py` | asyncssh 기반 원격 실행 |
| 알림 | `core/notify.py` | SMTP + Messenger Webhook |
| 스케줄러 | `core/scheduler.py` | SSL/PM/계약 만료 자동 감시 |
| Jenkins 클라이언트 | `core/cicd.py` | Jenkins REST API |
| 시드 데이터 | `core/seed.py` | 초기 데이터 자동 생성 |
| 라이선스 엔진 | `core/license.py` | AES-256-GCM 라이선스 생성·검증·캐싱 |
| 라이선스 가드 | `middleware/license_guard.py` | 에디션별 기관·서버·기능 접근 제한 Dependency |
### 4-3. GUARDiA Messenger 컴포넌트
```
C:\GUARDiA\messenger\
├── core/ 실시간 채팅 엔진
├── models/ 메시지·사용자·채널 ORM
└── main.py WebSocket + REST API
```
---
## 5. 데이터 설계
### 5-1. 전체 테이블 목록 (27개)
| 번호 | 테이블명 | 설명 | 관련 기능 |
|-----|---------|------|---------|
| 1 | `tb_user` | 시스템 사용자 | 인증 |
| 2 | `tb_inst_meta` | 기관 메타정보 | CMDB |
| 3 | `tb_inst_contact` | 기관 담당자 연락처 | CMDB |
| 4 | `tb_server_info` | 서버 자산 (암호화) | CMDB/SSH |
| 5 | `tb_sr_request` | SR 마스터 | SR관리 |
| 6 | `tb_sr_approval` | SR 승인 이력 | 승인 |
| 7 | `tb_work_log` | 작업 실행 로그 | 작업 |
| 8 | `tb_audit_log` | 감사 로그 (해시체인) | 감사 |
| 9 | `tb_rating` | 만족도 평가 | 고객 |
| 10 | `tb_shell_script` | SM 스크립트 메타 | 자동화 |
| 11 | `tb_work_timetable` | 작업 일정표 | 타임테이블 |
| 12 | `tb_sr_attachment` | SR 첨부파일 | 첨부 |
| 13 | `tb_notification_log` | 알림 이력 | 알림 |
| 14 | `tb_kb_document` | 지식 베이스 | KB |
| 15 | `tb_engineer_profile` | 엔지니어 스킬 | 자동배정 |
| 16 | `tb_project` | CI/CD 프로젝트 | 배포 |
| 17 | `tb_vibe_session` | 바이브 코딩 세션 | CI/CD |
| 18 | `tb_ssl_history` | SSL 갱신 이력 | SSL |
| 19 | `tb_pm_template` | PM 체크리스트 템플릿 | PM |
| 20 | `tb_pm_schedule` | PM 반복 스케줄 | PM |
| 21 | `tb_pm_result` | PM 점검 결과 | PM |
| 22 | `tb_incident` | 장애 마스터 | 장애관리 |
| 23 | `tb_incident_sr` | 장애↔SR 연결 | 장애관리 |
| 24 | `tb_oncall_schedule` | 온콜/당직 일정 | 당직 |
| 25 | `tb_batch_job` | 배치 작업 | 배치 |
| 26 | `tb_batch_run` | 배치 실행 이력 | 배치 |
| 27 | `tb_license` | 라이선스 등록 이력 | 상용화 라이선스 관리 |
### 5-2. 핵심 테이블 ERD (텍스트 형식)
```
tb_inst_meta (1) ──< (N) tb_server_info
tb_inst_meta (1) ──< (N) tb_sr_request ──< (N) tb_sr_approval
──< (N) tb_work_log
──< (N) tb_audit_log
──< (N) tb_sr_attachment
──< (N) tb_ops_task
tb_sr_request (N) >──< (N) tb_incident (via tb_incident_sr)
tb_server_info (1) ──< (N) tb_ssl_history
tb_server_info (1) ──< (N) tb_batch_job
tb_pm_template (1) ──< (N) tb_pm_result
tb_work_timetable (1) ──< (N) tb_pm_result
```
### 5-3. SR 상태 머신
```
RECEIVED ──→ PARSED ──→ PENDING_APPROVAL ──→ APPROVED ──→ IN_PROGRESS
└──→ REJECTED
IN_PROGRESS ──→ PENDING_PM_VALIDATION ──→ COMPLETED
└──→ FAILED_ROLLBACK
```
---
## 6. API 설계
### 6-1. 인증 방식
```
POST /api/auth/login
요청: { "username": "admin", "password": "****" }
응답: { "access_token": "eyJ...", "token_type": "bearer" }
이후 모든 API 요청 헤더:
Authorization: Bearer eyJ...
```
### 6-2. 주요 API 목록
| 분류 | 메서드 | 경로 | 역할 |
|------|--------|------|------|
| 인증 | POST | `/api/auth/login` | 로그인 |
| SR | GET/POST | `/api/tasks` | SR 목록/생성 |
| SR | PATCH | `/api/tasks/{id}/status` | 상태 변경 |
| SSL | GET | `/api/ssl/expiring` | 만료 임박 목록 |
| SSL | POST | `/api/ssl/check/{id}` | SSH 점검 |
| SSL | POST | `/api/ssl/renew/{id}` | 갱신 기록 |
| PM | GET/POST | `/api/pm/templates` | 체크리스트 템플릿 |
| PM | POST | `/api/pm/schedules/{id}/trigger` | PM 실행 |
| 장애 | GET/POST | `/api/incidents` | 장애 관리 |
| 당직 | GET | `/api/oncall/today` | 오늘 당직자 |
| 배치 | POST | `/api/batch/jobs/{id}/run` | 배치 실행 |
### 6-3. 오류 응답 형식
```json
{
"detail": "오류 요약 메시지 (IP, 스택트레이스 미포함)"
}
```
---
## 7. 보안 설계
### 7-1. 자격증명 암호화
```
평문 비밀번호
▼ AES-256-GCM (12바이트 nonce + 암호문)
▼ Base64 인코딩
tb_server_info.os_pw_enc 컬럼 저장
```
암호화 키: `GUARDIA_ENC_KEY` 환경변수 (32바이트)
### 7-2. JWT 보안
- 알고리즘: HS256
- 만료: 8시간 (기본값, 환경변수로 조정 가능)
- 저장: 클라이언트 메모리 (localStorage 사용 금지 권고)
### 7-3. SSH 보안
- root 계정 직접 접속 금지 (`ssh_user ≠ root` 검증)
- known_hosts 검증 (운영 환경 필수)
- 위험 명령어 패턴 차단:
- `rm -rf /`, `mkfs`, `dd if=`, `shutdown`, `reboot`
### 7-4. API 응답 보안
`ServerOut` 스키마에서 민감정보 제외:
- `ip_addr` — 미포함
- `ssh_user` — 미포함
- `os_pw_enc` — 미포함
### 7-5. 감사 로그 무결성
각 감사 로그 항목은 이전 항목의 해시값을 포함하는 체인 구조:
```
log_hash = SHA256(prev_hash + actor + action + detail + timestamp)
```
---
## 8. 배포 구성
### 8-1. 운영 환경 권장 구성
```
인터넷
│ (차단)
[Nginx] :443 ──TLS──→ [GUARDiA ITSM] :8000
──TLS──→ [GUARDiA Messenger] :8001
[GUARDiA ITSM] ──asyncssh──→ [관리 대상 서버들]
[GUARDiA ITSM] ──SMTP──→ [내부 메일 서버]
```
### 8-2. 최소 서버 사양
| 항목 | 최소 | 권장 |
|------|------|------|
| CPU | 2 코어 | 4 코어 |
| 메모리 | 4 GB | 8 GB |
| 디스크 | 50 GB | 100 GB |
| OS | RHEL 8 이상 / Ubuntu 20.04 이상 | RHEL 9 / Ubuntu 22.04 |
| Python | 3.10 이상 | 3.11 |
---
## 9. GUARDiA Messenger 설계
### 9-1. 주요 기능
| 기능 | 설명 |
|------|------|
| 채팅 | 1:1 및 그룹 채팅 |
| 봇 명령 | `!vibe`, `!build`, `!deploy`, `!sr`, `!health` 등 |
| SR 알림 | ITSM SR 상태 변경 시 자동 알림 |
| 파일 전송 | 이미지·문서 공유 |
### 9-2. 봇 명령어 목록
| 명령어 | 설명 | 예시 |
|--------|------|------|
| `!sr <내용>` | SR 빠른 접수 | `!sr WAS 응답 없음` |
| `!status <SR번호>` | SR 상태 조회 | `!status SR-20260525-AA1234` |
| `!health <서버명>` | 서버 헬스체크 | `!health MOF-WAS-01` |
| `!oncall` | 오늘 당직자 조회 | `!oncall` |
| `!pm <스케줄명>` | PM 즉시 실행 | `!pm 기재부 월간점검` |
| `!ssl <도메인>` | SSL 만료 확인 | `!ssl csv.culture.go.kr` |
---
## 10. 변경 이력
| 버전 | 날짜 | 내용 | 작성자 |
|------|------|------|--------|
| 1.0 | 2026-05-25 | 최초 작성 | 개발팀 |

View File

@ -0,0 +1,593 @@
# GUARDiA ITSM + Messenger — 단위·통합 테스트 계획서
**문서 버전**: 1.0
**작성일**: 2026-05-25
**대상 시스템**: GUARDiA ITSM v1.0, GUARDiA Messenger v1.0
---
## 목차
1. [개요](#1-개요)
2. [테스트 범위 및 전략](#2-테스트-범위-및-전략)
3. [테스트 환경](#3-테스트-환경)
4. [단위 테스트 계획](#4-단위-테스트-계획)
5. [통합 테스트 계획](#5-통합-테스트-계획)
6. [비기능 테스트 계획](#6-비기능-테스트-계획)
7. [테스트 케이스 목록](#7-테스트-케이스-목록)
8. [결함 관리](#8-결함-관리)
9. [완료 기준](#9-완료-기준)
---
## 1. 개요
### 1.1 목적
본 문서는 GUARDiA ITSM 및 GUARDiA Messenger 시스템의 품질 보증을 위한 단위 테스트 및 통합 테스트 계획을 정의한다. 개별 컴포넌트의 정확성 검증(단위)과 컴포넌트 간 상호작용 검증(통합)을 통해 출시 전 결함을 조기에 발견하고 수정한다.
### 1.2 테스트 대상
| 구분 | 대상 모듈 |
|------|-----------|
| ITSM 코어 | auth, dashboard, tasks, approvals, audit, cmdb |
| ITSM 운영 | ssl_manager, pm, incidents, oncall, batch |
| ITSM 지원 | kb, shell_scripts, ssh, attachments, notifications |
| ITSM 고객 | rating, institutions, timetable, work |
| Messenger | bot dispatcher, GUARDiA_ITSM bot, command handlers |
| 공통 | scheduler, seed, database, JWT auth |
### 1.3 테스트 유형 정의
- **단위 테스트(Unit Test)**: 함수/클래스 단위의 독립 검증. 외부 의존성은 mock 처리.
- **통합 테스트(Integration Test)**: DB·Redis·SSH·Messenger 등 실제 의존성과의 연동 검증.
- **E2E 시나리오 테스트**: 사용자 시나리오 전 구간 검증 (API 호출 → DB 저장 → 알림 발송).
---
## 2. 테스트 범위 및 전략
### 2.1 단위 테스트 범위
```
범위 내 (In Scope)
├── 모든 라우터 엔드포인트 (130개 API)
├── core/scheduler.py — 스케줄 함수
├── core/seed.py — 시드 데이터 생성
├── utils/crypto.py — AES-256-GCM 암/복호화
├── utils/ssh_runner.py — SSH 명령 검증
└── schemas/*.py — Pydantic 직렬화/역직렬화
범위 외 (Out of Scope)
├── 3rd party 라이브러리 내부 (FastAPI, SQLAlchemy)
└── OS 수준 시스템 콜
```
### 2.2 통합 테스트 범위
```
범위 내
├── ITSM API ↔ SQLite DB 연동
├── ITSM API ↔ GUARDiA Messenger 알림
├── SSH 실행 → 원격 서버 응답 처리
├── SSL 점검 스크립트 → JSON 파싱
├── 스케줄러 → DB 변경 → 알림 체인
└── PM 스케줄 → WorkTimetable 자동 생성
범위 외
├── 외부 LLM API (완전 금지)
├── 실제 고객 운영 서버 대상 SSH
└── 이메일/SMS 외부 발송
```
### 2.3 테스트 전략
- **TDD 기반**: 핵심 비즈니스 로직은 테스트 먼저 작성
- **AAA 패턴**: Arrange → Act → Assert
- **픽스처 격리**: 각 테스트는 독립 SQLite in-memory DB 사용
- **커버리지 목표**: 단위 80% 이상, 통합 70% 이상
---
## 3. 테스트 환경
### 3.1 단위 테스트 환경
```
OS: 동일 (Linux/Windows 무관)
Python: 3.11+
프레임워크: pytest 8.x + pytest-asyncio
Mock: unittest.mock, respx (httpx mock)
DB: SQLite in-memory (aiosqlite)
의존성: requirements-dev.txt 참조
```
**requirements-dev.txt**:
```
pytest>=8.0
pytest-asyncio>=0.23
pytest-cov>=5.0
httpx>=0.27 # TestClient
respx>=0.21 # httpx mock
faker>=25.0 # 테스트 데이터
freezegun>=1.4 # 시간 고정
```
### 3.2 통합 테스트 환경
```
OS: Ubuntu 22.04 LTS (권장) 또는 Windows Server 2022
Python: 3.11+
DB: SQLite 파일 기반 (테스트 전용)
Messenger: 테스트 채널 (별도 구성)
SSH 대상: localhost SSH 서버 (테스트 전용)
```
### 3.3 디렉토리 구조
```
C:\GUARDiA\itsm\
└── tests\
├── conftest.py # 공통 픽스처
├── unit\
│ ├── test_auth.py
│ ├── test_tasks.py
│ ├── test_ssl_manager.py
│ ├── test_pm.py
│ ├── test_incidents.py
│ ├── test_oncall.py
│ ├── test_batch.py
│ ├── test_scheduler.py
│ └── test_crypto.py
└── integration\
├── test_sr_workflow.py
├── test_approval_chain.py
├── test_pm_workflow.py
├── test_incident_workflow.py
└── test_notification_chain.py
```
---
## 4. 단위 테스트 계획
### 4.1 conftest.py 공통 픽스처
```python
# tests/conftest.py
import pytest
import pytest_asyncio
from httpx import AsyncClient, ASGITransport
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from database import Base, get_db
from main import app
TEST_DB = "sqlite+aiosqlite:///:memory:"
@pytest_asyncio.fixture
async def db_session():
engine = create_async_engine(TEST_DB)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
SessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with SessionLocal() as session:
yield session
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
@pytest_asyncio.fixture
async def client(db_session):
app.dependency_overrides[get_db] = lambda: db_session
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as ac:
yield ac
app.dependency_overrides.clear()
@pytest_asyncio.fixture
async def auth_headers(client):
r = await client.post("/auth/login", json={"username":"admin","password":"admin1234!"})
token = r.json()["access_token"]
return {"Authorization": f"Bearer {token}"}
```
### 4.2 인증 모듈 (auth) 단위 테스트
**파일**: `tests/unit/test_auth.py`
| 테스트 ID | 테스트명 | 설명 | 기대 결과 |
|-----------|----------|------|-----------|
| UT-AUTH-001 | 정상 로그인 | 올바른 계정으로 로그인 | 200 + access_token 반환 |
| UT-AUTH-002 | 잘못된 비밀번호 | 틀린 비밀번호 시도 | 401 Unauthorized |
| UT-AUTH-003 | 존재하지 않는 계정 | 없는 사용자명 | 401 Unauthorized |
| UT-AUTH-004 | 비밀번호 변경 | 현재 PW 확인 후 변경 | 200 + message |
| UT-AUTH-005 | JWT 토큰 만료 | 만료된 토큰으로 API 호출 | 401 Unauthorized |
| UT-AUTH-006 | RBAC — CUSTOMER 권한 제한 | CUSTOMER가 admin API 호출 | 403 Forbidden |
| UT-AUTH-007 | RBAC — ENGINEER 권한 | ENGINEER가 SR 처리 | 200 OK |
| UT-AUTH-008 | 빈 토큰 | Authorization 헤더 없음 | 401 Unauthorized |
```python
# 예시 테스트 코드
@pytest.mark.asyncio
async def test_login_success(client, db_session):
# Arrange: seed admin user
from core.seed import seed_all
await seed_all(db_session)
# Act
r = await client.post("/auth/login", json={"username":"admin","password":"admin1234!"})
# Assert
assert r.status_code == 200
data = r.json()
assert "access_token" in data
assert data["token_type"] == "bearer"
@pytest.mark.asyncio
async def test_login_wrong_password(client, db_session):
from core.seed import seed_all
await seed_all(db_session)
r = await client.post("/auth/login", json={"username":"admin","password":"wrong!"})
assert r.status_code == 401
```
### 4.3 SR 처리 (tasks) 단위 테스트
| 테스트 ID | 테스트명 | 기대 결과 |
|-----------|----------|-----------|
| UT-TASK-001 | SR 생성 (INCIDENT 유형) | 201 + sr_id 반환 |
| UT-TASK-002 | SR 생성 (CHANGE 유형) | 201 + sr_id 반환 |
| UT-TASK-003 | SR 목록 페이징 | 200 + items/total 구조 |
| UT-TASK-004 | SR 상태 변경 OPEN→IN_PROGRESS | 200 + 상태 업데이트 |
| UT-TASK-005 | SR 상태 — 잘못된 전환 | 400 Bad Request |
| UT-TASK-006 | SR 첨부파일 업로드 | 201 + file_id |
| UT-TASK-007 | SR 첨부파일 경로 순회 방지 | 400 Bad Request |
| UT-TASK-008 | 고객(CUSTOMER) SR 자기 것만 조회 | 200 + 본인 SR만 |
| UT-TASK-009 | SR 검색 (제목 키워드) | 200 + 필터 결과 |
| UT-TASK-010 | SR 만족도 평가 | 200 + rating 저장 |
### 4.4 SSL 관리 (ssl_manager) 단위 테스트
| 테스트 ID | 테스트명 | 기대 결과 |
|-----------|----------|-----------|
| UT-SSL-001 | SSL 도메인 등록 | 201 + ssl_id |
| UT-SSL-002 | 중복 도메인 등록 | 409 Conflict |
| UT-SSL-003 | SSL 즉시 점검 — 정상 응답 파싱 | 200 + days_left 계산 |
| UT-SSL-004 | SSL 즉시 점검 — SSH 실패 시 | 200 + error_msg 기록 |
| UT-SSL-005 | 만료 임박 알림 등급 (EXPIRED ≤0) | level=EXPIRED |
| UT-SSL-006 | 만료 임박 알림 등급 (URGENT ≤7) | level=URGENT |
| UT-SSL-007 | 만료 임박 알림 등급 (WARN ≤30) | level=WARN |
| UT-SSL-008 | 정상 인증서 (>30일) | level=OK |
| UT-SSL-009 | SSL 수동 갱신 완료 기록 | 200 + renewed_at 업데이트 |
| UT-SSL-010 | SSL 도메인 삭제 | 204 No Content |
### 4.5 PM 정기점검 (pm) 단위 테스트
| 테스트 ID | 테스트명 | 기대 결과 |
|-----------|----------|-----------|
| UT-PM-001 | PM 템플릿 생성 | 201 + template_id |
| UT-PM-002 | PM 템플릿 목록 조회 | 200 + 카테고리별 목록 |
| UT-PM-003 | PM 스케줄 생성 (MONTHLY) | 201 + next_scheduled 계산 |
| UT-PM-004 | PM 스케줄 생성 (WEEKLY) | 201 + 7일 후 next_scheduled |
| UT-PM-005 | PM 스케줄 생성 (QUARTERLY) | 201 + 90일 후 next_scheduled |
| UT-PM-006 | PM 작업 생성 (WorkTimetable 연동) | 201 + timetable_id |
| UT-PM-007 | PM 결과 저장 — PASS | 200 + result=PASS |
| UT-PM-008 | PM 결과 저장 — FAIL (비고 필수) | 400 if note 없음 |
| UT-PM-009 | PM 완료 처리 | 200 + completed_at 기록 |
| UT-PM-010 | PM Excel 보고서 다운로드 | 200 + Content-Type: xlsx |
| UT-PM-011 | next_scheduled 계산 — MONTHLY 말일 | 말일 처리 정확성 |
| UT-PM-012 | PM 템플릿 소프트 삭제 | 200 + is_active=False |
### 4.6 장애 관리 (incidents) 단위 테스트
| 테스트 ID | 테스트명 | 기대 결과 |
|-----------|----------|-----------|
| UT-INC-001 | 장애 생성 (P1) | 201 + INC-YYYYMMDD-XXXXXX 형식 |
| UT-INC-002 | 장애 생성 (P4) | 201 + 알림 없음 |
| UT-INC-003 | P1/P2 생성 → Messenger 알림 | 알림 함수 호출 확인 (mock) |
| UT-INC-004 | 상태 전환 OPEN→INVESTIGATING | 200 + 타임라인 기록 |
| UT-INC-005 | 잘못된 상태 전환 CLOSED→OPEN | 400 Bad Request |
| UT-INC-006 | 장애 타임라인 조회 | 200 + events 목록 |
| UT-INC-007 | 장애 종결 (RCA 필수) | 400 if rca 없음 |
| UT-INC-008 | 장애 목록 — P1 필터 | 200 + P1 only |
| UT-INC-009 | 장애 통계 (기간별) | 200 + count by priority |
| UT-INC-010 | 장애 SR 연동 | 200 + related_sr_ids 업데이트 |
### 4.7 온콜/당직 (oncall) 단위 테스트
| 테스트 ID | 테스트명 | 기대 결과 |
|-----------|----------|-----------|
| UT-OC-001 | 당직 단건 등록 | 201 + duty_id |
| UT-OC-002 | 중복 당직 등록 (같은 날+교대) | 409 Conflict |
| UT-OC-003 | 당직 일괄 등록 (최대 62건) | 201 + count |
| UT-OC-004 | 당직 일괄 — 초과 (63건) | 422 Validation Error |
| UT-OC-005 | 오늘 당직 조회 | 200 + 교대별 그룹 |
| UT-OC-006 | 월별 당직 조회 | 200 + 해당 월 목록 |
| UT-OC-007 | 당직 삭제 | 204 No Content |
### 4.8 배치 작업 (batch) 단위 테스트
| 테스트 ID | 테스트명 | 기대 결과 |
|-----------|----------|-----------|
| UT-BAT-001 | 배치 작업 생성 | 201 + batch_id |
| UT-BAT-002 | 위험 명령 차단 (rm -rf /) | 400 Bad Request |
| UT-BAT-003 | 위험 명령 차단 (shutdown) | 400 Bad Request |
| UT-BAT-004 | 위험 명령 차단 (fork bomb) | 400 Bad Request |
| UT-BAT-005 | 배치 수동 실행 요청 | 202 Accepted |
| UT-BAT-006 | 배치 실행 이력 조회 | 200 + runs 목록 |
| UT-BAT-007 | 배치 실패 시 SR 자동 생성 | SR 생성 확인 (mock) |
| UT-BAT-008 | 비활성 배치 스케줄 — 실행 안 함 | is_active=False 체크 |
| UT-BAT-009 | 배치 작업 삭제 | 204 No Content |
| UT-BAT-010 | TIMEOUT 처리 (asyncio timeout) | 실행 상태=TIMEOUT |
### 4.9 암호화 유틸 단위 테스트
| 테스트 ID | 테스트명 | 기대 결과 |
|-----------|----------|-----------|
| UT-CRYPTO-001 | AES-256-GCM 암호화/복호화 왕복 | 원문 복원 성공 |
| UT-CRYPTO-002 | 서로 다른 평문 → 다른 암호문 | 결정론적 아님 확인 |
| UT-CRYPTO-003 | 잘못된 키로 복호화 | 예외 발생 |
| UT-CRYPTO-004 | 빈 문자열 암호화 | 정상 처리 |
| UT-CRYPTO-005 | 한국어 포함 문자열 암호화 | UTF-8 왕복 성공 |
### 4.10 스케줄러 단위 테스트
```python
# tests/unit/test_scheduler.py
import pytest
from datetime import datetime
from freezegun import freeze_time
from core.scheduler import _calc_next
from models import PmSchedule, PmFrequency
@pytest.mark.asyncio
async def test_calc_next_weekly():
with freeze_time("2026-01-01"):
sched = PmSchedule(frequency=PmFrequency.WEEKLY)
result = _calc_next(sched, datetime(2026, 1, 1))
assert result == datetime(2026, 1, 8)
@pytest.mark.asyncio
async def test_calc_next_monthly_end_of_month():
# 1월 31일에서 +1달 → 2월 28일 (말일 처리)
sched = PmSchedule(frequency=PmFrequency.MONTHLY, day_of_month=31)
result = _calc_next(sched, datetime(2026, 1, 31))
assert result.day == 28 # 2026년 2월 말일
```
---
## 5. 통합 테스트 계획
### 5.1 SR 처리 전체 워크플로우
**시나리오**: 고객 SR 접수 → 담당자 배정 → 처리 → 완료 → 만족도 평가
```
TC-INT-SR-001: SR 접수부터 완료까지 전체 흐름
1. POST /tasks/ — SR 생성 (CUSTOMER 역할)
2. GET /tasks/{id} — 생성 확인
3. PATCH /assign/{sr_id} — 담당자 배정 (PM 역할)
4. PATCH /tasks/{id}/status — IN_PROGRESS 전환
5. POST /work/ — 처리 내역 등록
6. PATCH /tasks/{id}/status — RESOLVED 전환
7. POST /rating/ — 만족도 5점 등록
8. GET /dashboard/summary — 통계 반영 확인
기대 결과: 전 단계 성공, 최종 SR 상태=RESOLVED, rating=5
```
### 5.2 결재 워크플로우
**시나리오**: CHANGE 유형 SR → 결재 요청 → 승인 → 실행
```
TC-INT-APV-001: 결재 승인 워크플로우
1. POST /tasks/ — CHANGE SR 생성
2. POST /approvals/ — 결재 요청 (결재자 2단계 설정)
3. PATCH /approvals/{id}/approve — 1단계 승인
4. PATCH /approvals/{id}/approve — 2단계 승인
5. GET /tasks/{sr_id} — SR 상태 = APPROVED 확인
6. PATCH /tasks/{id}/status — IMPLEMENTING 전환
TC-INT-APV-002: 결재 거부 워크플로우
1. POST /tasks/ + POST /approvals/
2. PATCH /approvals/{id}/reject — 거부 (사유 필수)
3. GET /tasks/{sr_id} — SR 상태 = REJECTED 확인
```
### 5.3 PM 정기점검 워크플로우
```
TC-INT-PM-001: PM 스케줄 → 자동 작업 생성
1. POST /pm/templates/ — 체크리스트 템플릿 생성
2. POST /pm/schedules/ — MONTHLY 스케줄 등록
3. (스케줄러 _auto_generate_pm 함수 직접 호출)
4. GET /timetable/ — WorkTimetable 생성 확인
5. GET /pm/works/{id} — PM 작업 목록 확인
TC-INT-PM-002: PM 결과 저장 및 Excel 보고서
1. (PM 작업 생성 선행)
2. POST /pm/works/{id}/results — 각 항목 PASS/FAIL 저장
3. PATCH /pm/works/{id}/complete — 완료 처리
4. GET /pm/works/{id}/report — Excel 다운로드
5. openpyxl로 파일 열기 → PASS/FAIL 색상 확인
```
### 5.4 장애 처리 워크플로우
```
TC-INT-INC-001: P1 장애 발생 → 처리 → 종결
1. POST /incidents/ — P1 장애 등록
→ Messenger 알림 mock 확인
2. POST /incidents/{id}/timeline — "장애 감지 완료" 기록
3. PATCH /incidents/{id}/status — INVESTIGATING
4. PATCH /incidents/{id}/status — MITIGATED (조치 내역 포함)
5. PATCH /incidents/{id}/status — RESOLVED (임시 해결)
6. POST /incidents/{id}/close — RCA 문서 포함 종결
7. GET /incidents/{id} — 전체 타임라인 확인
```
### 5.5 배치 실행 및 실패 알림
```
TC-INT-BAT-001: 배치 실행 실패 → SR 자동 생성
1. POST /batch/jobs — 의도적 실패 명령으로 배치 등록
(예: "exit 1" 명령)
2. POST /batch/jobs/{id}/run — 수동 실행
3. (BackgroundTask 완료 대기)
4. GET /batch/jobs/{id}/runs — 상태 = FAILED 확인
5. GET /tasks/ — 자동 생성된 SR 확인 (sr_type=LOG, priority=HIGH)
```
### 5.6 Messenger 알림 통합 테스트
```
TC-INT-MSG-001: ITSM 이벤트 → Messenger 채널 발송
1. mock Messenger API (respx)
2. P1 장애 생성 → /incidents/ POST
3. respx 캡처된 요청에서 채널 ID, 메시지 내용 검증
4. 메시지에 incident_id, priority, title 포함 확인
5. 서버 IP/비밀번호 미포함 확인 (보안)
TC-INT-MSG-002: Bot 명령 처리
1. POST /messenger/webhook — "/itsm sr list" 명령
2. 응답 메시지에 SR 목록 포함 확인
3. POST /messenger/webhook — "/itsm help"
4. 응답 메시지에 명령어 목록 확인
```
---
## 6. 비기능 테스트 계획
### 6.1 성능 테스트
| 항목 | 목표 | 측정 방법 |
|------|------|-----------|
| API 응답 시간 (평균) | ≤ 500ms | locust or k6 |
| API 응답 시간 (P99) | ≤ 2000ms | locust or k6 |
| 동시 사용자 | 50명 이상 정상 처리 | locust 동시 접속 |
| DB 트랜잭션 | 100 TPS 이상 | pytest-benchmark |
| 파일 업로드 | 10MB 이하 ≤ 3초 | httpx 직접 테스트 |
### 6.2 보안 테스트
| 항목 | 테스트 방법 | 기대 결과 |
|------|------------|-----------|
| SQL 인젝션 | 검색 파라미터에 SQL 구문 주입 | 에러 없이 빈 결과 |
| XSS | SR 제목에 `<script>alert(1)</script>` | HTML 이스케이프 |
| JWT 위조 | 임의 서명 토큰 사용 | 401 |
| 경로 순회 | `../../../etc/passwd` 첨부파일 | 400 |
| SSH 명령 인젝션 | `; rm -rf /` 포함 명령 | 400 |
| 서버 정보 노출 | API 응답에 IP/PW 포함 여부 | 미포함 확인 |
| 스택트레이스 노출 | 의도적 오류 발생 | SR ID + 요약만 반환 |
### 6.3 감사 로그 무결성 테스트
```
TC-AUDIT-001: 해시 체인 무결성
1. 여러 감사 로그 생성 (API 호출 10회)
2. GET /audit/logs — 전체 조회
3. 각 로그의 prev_hash → 이전 로그 hash 일치 확인
4. DB에서 중간 로그 직접 수정
5. GET /audit/verify — 무결성 깨짐 감지 확인
```
---
## 7. 테스트 케이스 목록 요약
| 모듈 | 단위 TC | 통합 TC | 합계 |
|------|---------|---------|------|
| auth | 8 | 2 | 10 |
| tasks/SR | 10 | 4 | 14 |
| approvals | 6 | 3 | 9 |
| ssl_manager | 10 | 2 | 12 |
| pm | 12 | 3 | 15 |
| incidents | 10 | 2 | 12 |
| oncall | 7 | 1 | 8 |
| batch | 10 | 2 | 12 |
| scheduler | 6 | 2 | 8 |
| crypto/security | 5 | 3 | 8 |
| messenger | 4 | 3 | 7 |
| audit | 4 | 2 | 6 |
| **합계** | **92** | **29** | **121** |
---
## 8. 결함 관리
### 8.1 결함 등급
| 등급 | 정의 | 처리 기한 |
|------|------|-----------|
| Critical | 시스템 중단, 데이터 손실, 보안 취약점 | 즉시 (24시간 내) |
| Major | 핵심 기능 비정상, 결과 오류 | 3일 내 |
| Minor | 비핵심 기능 문제, UI 오류 | 7일 내 |
| Trivial | 오타, 경미한 표시 오류 | 다음 버전 |
### 8.2 결함 생명주기
```
NEW → ASSIGNED → IN_PROGRESS → FIXED → VERIFIED → CLOSED
REOPENED (재현 시)
```
### 8.3 결함 보고서 양식
```
결함 ID: BUG-YYYY-NNN
제목: [모듈] 결함 요약
심각도: Critical/Major/Minor/Trivial
재현 단계:
1. ...
2. ...
실제 결과: ...
기대 결과: ...
스크린샷/로그: (첨부)
발견일: YYYY-MM-DD
발견자: ...
```
---
## 9. 완료 기준
### 9.1 단계별 완료 기준
**단위 테스트 완료**:
- [ ] 모든 단위 TC 실행 완료
- [ ] 코드 커버리지 80% 이상
- [ ] Critical/Major 결함 0건
**통합 테스트 완료**:
- [ ] 모든 통합 시나리오 TC 실행 완료
- [ ] 주요 워크플로우 E2E 검증 완료
- [ ] Critical/Major 결함 0건
**보안 테스트 완료**:
- [ ] 보안 TC 전 항목 통과
- [ ] API 응답에 민감 정보 미포함 확인
- [ ] 감사 로그 무결성 확인
### 9.2 테스트 실행 명령
```bash
# 전체 단위 테스트
cd C:\GUARDiA\itsm
pytest tests/unit/ -v --cov=. --cov-report=html
# 통합 테스트
pytest tests/integration/ -v --timeout=30
# 특정 모듈
pytest tests/unit/test_incidents.py -v
# 커버리지 HTML 보고서 확인
open htmlcov/index.html
```
---
*본 테스트 계획서는 GUARDiA ITSM v1.0 기준으로 작성되었으며, 버전 업그레이드 시 갱신이 필요합니다.*

951
03_개발자지침서.md Normal file
View File

@ -0,0 +1,951 @@
# GUARDiA ITSM + Messenger — 개발자 지침서
**문서 버전**: 1.0
**작성일**: 2026-05-25
**대상**: GUARDiA 플랫폼 개발자
---
## 목차
1. [개발 환경 설정](#1-개발-환경-설정)
2. [프로젝트 구조](#2-프로젝트-구조)
3. [코딩 컨벤션](#3-코딩-컨벤션)
4. [API 개발 가이드](#4-api-개발-가이드)
5. [데이터베이스 가이드](#5-데이터베이스-가이드)
6. [보안 필수 규칙](#6-보안-필수-규칙)
7. [테스트 작성 가이드](#7-테스트-작성-가이드)
8. [Messenger Bot 개발 가이드](#8-messenger-bot-개발-가이드)
9. [스케줄러 확장 가이드](#9-스케줄러-확장-가이드)
10. [배포 및 CI/CD](#10-배포-및-cicd)
11. [자주 하는 실수 및 주의사항](#11-자주-하는-실수-및-주의사항)
---
## 1. 개발 환경 설정
### 1.1 필수 도구
```
Python 3.11+
Git
VS Code (권장) 또는 PyCharm
SQLite Browser (DB 확인용)
httpie 또는 Postman (API 테스트)
```
### 1.2 로컬 개발 환경 구성
```bash
# 1. 레포지토리 클론
git clone <repo-url>
cd GUARDiA/itsm
# 2. 가상환경 생성 (Python 3.11 사용)
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux/Mac
source .venv/bin/activate
# 3. 의존성 설치
pip install -r requirements.txt
pip install -r requirements-dev.txt # 개발/테스트용
# 4. 환경 변수 설정
cp .env.example .env
# .env 파일 편집 (SECRET_KEY, DB_PATH 등)
# 5. 앱 실행
uvicorn main:app --reload --port 8000
# 6. API 문서 확인
# http://localhost:8000/docs
```
### 1.3 .env 파일 설정
```ini
# .env.example
SECRET_KEY=your-256-bit-secret-key-here-change-in-production
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=480
DB_URL=sqlite+aiosqlite:///./guardia_itsm.db
# 암호화 키 (AES-256-GCM, 32바이트 hex)
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# 라이선스 마스터 키 (64자리 hex, 32바이트)
# 생성: python -c "import secrets; print(secrets.token_hex(32))"
# 운영 환경에서는 반드시 실제 값으로 교체 — 분실 시 기발급 라이선스 전부 무효화됨
GUARDIA_LICENSE_KEY=0000000000000000000000000000000000000000000000000000000000000000
# Messenger 연동
MESSENGER_BASE_URL=http://localhost:8002
MESSENGER_BOT_TOKEN=dev-token
# SSH 실행 타임아웃 (초)
SSH_TIMEOUT=30
# 파일 업로드 경로
UPLOAD_ROOT=./uploads
MAX_FILE_SIZE_MB=10
```
> **주의**: `.env` 파일은 절대 Git에 커밋하지 마세요. `.gitignore`에 포함되어 있습니다.
### 1.4 VS Code 권장 설정
`.vscode/settings.json`:
```json
{
"python.defaultInterpreterPath": ".venv/Scripts/python.exe",
"editor.formatOnSave": true,
"python.formatting.provider": "black",
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": true,
"python.linting.flake8Args": ["--max-line-length=120"]
}
```
---
## 2. 프로젝트 구조
```
C:\GUARDiA\itsm\
├── main.py # FastAPI 앱 진입점
├── database.py # DB 엔진, SessionLocal, get_db
├── models.py # SQLAlchemy ORM 모델 (전체)
├── schemas.py # Pydantic 스키마 (전체)
├── requirements.txt # 운영 의존성
├── requirements-dev.txt # 개발/테스트 의존성
├── .env # 환경 변수 (git 제외)
├── core\
│ ├── seed.py # 초기 데이터 시드
│ ├── scheduler.py # APScheduler 백그라운드 작업
│ └── security.py # JWT, 비밀번호 해싱
├── routers\
│ ├── auth.py # 인증/권한
│ ├── tasks.py # SR 처리
│ ├── approvals.py # 결재
│ ├── ssl_manager.py # SSL 인증서 관리
│ ├── pm.py # 정기점검
│ ├── incidents.py # 장애 관리
│ ├── oncall.py # 온콜/당직
│ ├── batch.py # 배치 작업
│ └── ... # 기타 라우터
├── utils\
│ ├── crypto.py # AES-256-GCM 암/복호화
│ └── ssh_runner.py # SSH 명령 실행
├── static\ # 프론트엔드 (HTML/CSS/JS)
├── uploads\ # 업로드 파일 저장소
│ ├── sr_files\
│ └── workspaces\
├── scripts\sm\ssl\
│ └── ssl_expiry_check.sh # 배포용 SSL 점검 스크립트
└── tests\
├── conftest.py
├── unit\
└── integration\
C:\GUARDiA\messenger\
├── main.py # Messenger 서버
├── models\ # DB 모델
├── core\ # 봇 로직
└── routers\ # API 라우터
```
---
## 3. 코딩 컨벤션
### 3.1 Python 스타일
```python
# 파일 맨 위: 임포트 순서 (isort 준수)
# 1) 표준 라이브러리
import os
import json
from datetime import datetime, timedelta
# 2) 서드파티
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
# 3) 로컬 모듈
from database import get_db
from models import SRRequest
from schemas import SRCreate, SROut
# 줄 최대 길이: 120자
# 들여쓰기: 4 space (탭 금지)
# 문자열: 큰따옴표(") 사용
```
### 3.2 라우터 파일 구조 패턴
새 라우터를 만들 때 반드시 이 구조를 따르세요:
```python
# routers/example.py
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_db
from models import ExampleModel
from schemas import ExampleCreate, ExampleOut, ExampleUpdate
from core.security import get_current_user
from models import User, Role
router = APIRouter(prefix="/example", tags=["example"])
def _require_role(*roles: Role):
"""역할 기반 접근 제어 의존성"""
async def checker(current_user: User = Depends(get_current_user)):
if current_user.role not in roles:
raise HTTPException(status_code=403, detail="권한이 없습니다.")
return current_user
return checker
@router.get("/", response_model=list[ExampleOut])
async def list_examples(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
result = await db.execute(select(ExampleModel).order_by(ExampleModel.created_at.desc()))
return result.scalars().all()
@router.post("/", response_model=ExampleOut, status_code=status.HTTP_201_CREATED)
async def create_example(
body: ExampleCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(_require_role(Role.ADMIN, Role.PM)),
):
obj = ExampleModel(**body.model_dump(), created_by=current_user.id)
db.add(obj)
await db.commit()
await db.refresh(obj)
return obj
```
### 3.3 모델 정의 패턴
```python
# models.py 에 추가할 때
class ExampleModel(Base):
__tablename__ = "tb_example"
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(200), nullable=False)
description = Column(Text, nullable=True)
is_active = Column(Boolean, default=True, nullable=False)
created_by = Column(Integer, ForeignKey("tb_user.id"), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
```
### 3.4 스키마 정의 패턴
```python
# schemas.py 에 추가할 때
class ExampleCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = None
class ExampleUpdate(BaseModel):
title: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = None
class ExampleOut(BaseModel):
id: int
title: str
description: Optional[str]
is_active: bool
created_at: datetime
model_config = ConfigDict(from_attributes=True)
```
### 3.5 명명 규칙
| 대상 | 규칙 | 예시 |
|------|------|------|
| 파일명 | snake_case | `ssl_manager.py` |
| 클래스명 | PascalCase | `SslDomain`, `PmSchedule` |
| 함수/변수 | snake_case | `get_ssl_domains()`, `days_left` |
| DB 테이블 | `tb_` 접두사 | `tb_ssl_domain` |
| 상수 | UPPER_SNAKE | `MAX_FILE_SIZE`, `SSL_WARN_DAYS` |
| 비공개 함수 | `_` 접두사 | `_notify_incident()` |
| API 경로 | kebab-case | `/ssl-manager/domains` |
---
## 4. API 개발 가이드
### 4.1 응답 형식 규칙
```python
# 성공 응답: HTTP 상태 코드로 구분
# 200 OK — 조회, 수정
# 201 Created — 생성
# 202 Accepted — 비동기 실행 (배치, 백그라운드)
# 204 No Content — 삭제
# 에러 응답: 반드시 이 형식 사용
raise HTTPException(
status_code=400,
detail="사용자 친화적인 한국어 메시지"
)
# 절대 금지: 스택트레이스 노출
# raise Exception(traceback.format_exc()) # ❌
```
### 4.2 페이징 구현 패턴
```python
from fastapi import Query
@router.get("/", response_model=dict)
async def list_items(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
db: AsyncSession = Depends(get_db),
):
offset = (page - 1) * size
count_q = select(func.count()).select_from(Item)
total = (await db.execute(count_q)).scalar()
items_q = select(Item).offset(offset).limit(size).order_by(Item.created_at.desc())
items = (await db.execute(items_q)).scalars().all()
return {
"items": items,
"total": total,
"page": page,
"size": size,
"pages": (total + size - 1) // size,
}
```
### 4.3 비동기 DB 쿼리 패턴
```python
# ✅ 올바른 패턴: asyncio 방식
async def get_item(item_id: int, db: AsyncSession) -> Item:
result = await db.execute(select(Item).where(Item.id == item_id))
item = result.scalar_one_or_none()
if item is None:
raise HTTPException(status_code=404, detail="항목을 찾을 수 없습니다.")
return item
# ✅ 여러 조건 필터
query = select(Item).where(
Item.is_active == True,
Item.category == category,
).order_by(Item.created_at.desc())
# ✅ JOIN
query = select(Item).join(User, Item.created_by == User.id)
# ❌ 잘못된 패턴: 동기 방식 사용 금지
# db.query(Item).filter(Item.id == item_id).first() # ❌
```
### 4.4 백그라운드 작업 패턴
```python
from fastapi import BackgroundTasks
@router.post("/{id}/run", status_code=202)
async def run_job(
id: int,
background_tasks: BackgroundTasks,
db: AsyncSession = Depends(get_db),
):
job = await _get_job(id, db)
background_tasks.add_task(_execute_job, job.id)
return {"message": "실행이 요청되었습니다.", "job_id": id}
async def _execute_job(job_id: int):
# 중요: 새 DB 세션 생성 (request-scope 세션 재사용 금지!)
from database import SessionLocal
async with SessionLocal() as db:
job = await _get_job(job_id, db)
# ... 실행 로직
await db.commit()
```
### 4.5 main.py 라우터 등록
새 라우터를 추가할 때:
```python
# main.py
from routers import (
...
new_module, # ← 여기에 추가
)
# lifespan 함수 안에 필요한 초기화 추가
# 라우터 등록 (알파벳 순 권장)
app.include_router(new_module.router)
```
---
## 5. 데이터베이스 가이드
### 5.1 마이그레이션 정책
현재 GUARDiA ITSM은 **Alembic 없이** `init_db()`로 테이블을 자동 생성합니다.
```python
# database.py
async def init_db():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
```
> **주의**: 운영 DB에서는 테이블 삭제/재생성이 일어나지 않습니다. 컬럼 추가/변경 시 수동 ALTER TABLE이 필요합니다.
**신규 컬럼 추가 절차**:
1. `models.py`에 컬럼 추가
2. 운영 DB에 수동 ALTER: `ALTER TABLE tb_xxx ADD COLUMN new_col TEXT;`
3. 기본값 업데이트: `UPDATE tb_xxx SET new_col = 'default' WHERE new_col IS NULL;`
### 5.2 관계 설정 패턴
```python
# 1:N 관계 (부모→자식)
class SRRequest(Base):
__tablename__ = "tb_sr_request"
# ...
work_logs = relationship("WorkLog", back_populates="sr", lazy="select")
class WorkLog(Base):
__tablename__ = "tb_work_log"
sr_id = Column(Integer, ForeignKey("tb_sr_request.id"), nullable=False)
sr = relationship("SRRequest", back_populates="work_logs")
# 쿼리 시 eagerly load (N+1 문제 방지)
from sqlalchemy.orm import selectinload
query = select(SRRequest).options(selectinload(SRRequest.work_logs))
```
### 5.3 enum 정의 패턴
```python
import enum
class SRStatus(str, enum.Enum):
OPEN = "OPEN"
IN_PROGRESS = "IN_PROGRESS"
RESOLVED = "RESOLVED"
CLOSED = "CLOSED"
# 모델에서 사용
class SRRequest(Base):
status = Column(Enum(SRStatus), default=SRStatus.OPEN, nullable=False)
```
### 5.4 트랜잭션 처리
```python
# 여러 테이블 수정 시 트랜잭션 보장
async def complex_operation(db: AsyncSession):
try:
obj1 = Model1(...)
obj2 = Model2(...)
db.add(obj1)
db.add(obj2)
await db.commit() # 모두 성공 시 커밋
except Exception:
await db.rollback() # 하나라도 실패 시 롤백
raise
```
---
## 6. 보안 필수 규칙
> **이 규칙들은 절대 예외가 없습니다. 위반 시 코드 리뷰에서 반려됩니다.**
### 6.1 서버 자격증명 보호
```python
# ✅ 서버 조회 응답에서 민감 필드 제외
class ServerOut(BaseModel):
id: int
hostname: str
server_role: str
# ip_addr, ssh_user, os_pw_enc 절대 포함 금지!
# ❌ 절대 하지 말 것
class ServerOut(BaseModel):
ip_addr: str # ❌ IP 노출 금지
ssh_user: str # ❌ SSH 계정 노출 금지
os_pw_enc: str # ❌ 암호화된 비밀번호도 노출 금지
```
### 6.2 SSH 명령 안전성 검증
```python
_DANGEROUS_PATTERNS = [
"rm -rf /", "rm -rf /*", "mkfs", "dd if=",
"shutdown", "reboot", "halt", "poweroff",
":(){:|:&};:", ">()", "chmod 777 /",
"chown -R root /", "> /dev/sda",
]
def _validate_command(cmd: str) -> None:
for pattern in _DANGEROUS_PATTERNS:
if pattern in cmd:
raise HTTPException(
status_code=400,
detail=f"안전하지 않은 명령어 패턴이 포함되어 있습니다: {pattern}"
)
```
### 6.3 파일 경로 순회 방지
```python
from pathlib import Path
UPLOAD_ROOT = Path(settings.UPLOAD_ROOT).resolve()
def _safe_path(filename: str, subdir: str) -> Path:
target = (UPLOAD_ROOT / subdir / filename).resolve()
if not str(target).startswith(str(UPLOAD_ROOT)):
raise HTTPException(status_code=400, detail="잘못된 파일 경로입니다.")
return target
```
### 6.4 AES-256-GCM 암호화 사용
```python
# utils/crypto.py
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os, base64
def encrypt(plaintext: str, key_hex: str) -> str:
key = bytes.fromhex(key_hex)
nonce = os.urandom(12)
aesgcm = AESGCM(key)
ct = aesgcm.encrypt(nonce, plaintext.encode(), None)
return base64.b64encode(nonce + ct).decode()
def decrypt(ciphertext: str, key_hex: str) -> str:
key = bytes.fromhex(key_hex)
data = base64.b64decode(ciphertext)
nonce, ct = data[:12], data[12:]
aesgcm = AESGCM(key)
return aesgcm.decrypt(nonce, ct, None).decode()
```
### 6.5 Messenger 발송 메시지 보안
```python
# ✅ 올바른 알림 메시지 (서버 정보 제외)
message = f"[장애] {incident.title}\n등급: {incident.priority}\nID: {incident.incident_id}"
# ❌ 절대 금지 (서버 정보 포함)
message = f"서버 {server.ip_addr}에서 장애 발생" # ❌ IP 노출
message = f"SSH 계정: {server.ssh_user}" # ❌ 계정 노출
```
### 6.6 JWT 토큰 처리
```python
# 토큰 생성 시 만료 시간 필수
def create_access_token(data: dict) -> str:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
return jwt.encode({**data, "exp": expire}, settings.SECRET_KEY, algorithm="HS256")
# 의존성에서 자동 검증
async def get_current_user(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)):
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
except JWTError:
raise HTTPException(status_code=401, detail="유효하지 않은 토큰입니다.")
```
### 6.7 root SSH 접속 금지
```python
# 서버 등록/수정 시 검증
if body.ssh_user == "root":
raise HTTPException(status_code=400, detail="root SSH 직접 접속은 허용되지 않습니다.")
```
### 6.8 라이선스 제한 강제
에디션별 자원 한도(기관·사용자·서버 수)를 초과하는 생성 API에는 반드시 Dependency를 추가해야 한다.
```python
from middleware.license_guard import (
check_institution_limit,
check_server_limit,
check_user_limit,
require_feature,
)
# 기관 등록 엔드포인트 예시
@router.post("/", response_model=InstitutionOut, status_code=201)
async def create_institution(
payload: InstitutionCreate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
_: None = Depends(check_institution_limit), # 에디션 한도 초과 시 HTTP 403
):
...
# 서버 등록 엔드포인트 예시
@router.post("/servers", response_model=ServerOut, status_code=201)
async def create_server(
payload: dict,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
_: None = Depends(check_server_limit), # 에디션 한도 초과 시 HTTP 403
):
...
# 특정 기능이 에디션에 포함되는지 체크 (STANDARD 이상 필요한 LDAP 예시)
@router.post("/ldap/sync")
async def ldap_sync(
_: None = Depends(require_feature("LDAP")),
):
...
```
에디션별 기본 한도:
| 에디션 | 기관 | 사용자 | 서버 |
|--------|------|-------|------|
| COMMUNITY | 1 | 10 | 20 |
| STANDARD | 50 | 200 | 500 |
| ENTERPRISE | 무제한 | 무제한 | 무제한 |
---
## 7. 테스트 작성 가이드
### 7.1 단위 테스트 기본 구조
```python
import pytest
import pytest_asyncio
class TestSslManager:
@pytest.mark.asyncio
async def test_register_domain_success(self, client, auth_headers):
# Arrange
payload = {"domain": "example.com", "port": 443, "server_id": 1}
# Act
r = await client.post("/ssl-manager/domains", json=payload, headers=auth_headers)
# Assert
assert r.status_code == 201
data = r.json()
assert data["domain"] == "example.com"
assert "id" in data
@pytest.mark.asyncio
async def test_register_domain_duplicate(self, client, auth_headers):
payload = {"domain": "example.com", "port": 443, "server_id": 1}
await client.post("/ssl-manager/domains", json=payload, headers=auth_headers)
r = await client.post("/ssl-manager/domains", json=payload, headers=auth_headers)
assert r.status_code == 409
```
### 7.2 mock 사용 패턴
```python
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_p1_incident_triggers_notification(client, auth_headers):
with patch("routers.incidents._notify_incident", new_callable=AsyncMock) as mock_notify:
r = await client.post("/incidents/", json={
"title": "DB 서버 다운",
"priority": "P1",
"description": "운영 DB 응답 없음",
}, headers=auth_headers)
assert r.status_code == 201
mock_notify.assert_called_once()
call_args = mock_notify.call_args
assert call_args[0][0].priority == "P1" # 첫 번째 인자 확인
```
### 7.3 시간 고정 테스트
```python
from freezegun import freeze_time
@pytest.mark.asyncio
@freeze_time("2026-01-15 00:00:00")
async def test_pm_schedule_monthly_next_date(client, auth_headers):
r = await client.post("/pm/schedules/", json={
"template_id": 1,
"frequency": "MONTHLY",
"day_of_month": 15,
"server_id": 1,
}, headers=auth_headers)
assert r.status_code == 201
# 1월 15일 기준 → 다음 실행은 2월 15일
assert "2026-02-15" in r.json()["next_scheduled"]
```
---
## 8. Messenger Bot 개발 가이드
### 8.1 GUARDiA_ITSM 봇 명령 추가
명령어 추가는 `C:\GUARDiA\messenger\core\itsm_bot.py` 에서 수행합니다.
```python
# 새 명령어 핸들러 추가 패턴
COMMAND_HANDLERS = {
"/itsm sr list": handle_sr_list,
"/itsm sr create": handle_sr_create,
"/itsm help": handle_help,
"/itsm incident p1": handle_p1_alert, # 새 명령어 추가
}
async def handle_p1_alert(args: list[str], user_id: int) -> str:
"""P1 장애 목록 조회"""
async with httpx.AsyncClient() as client:
r = await client.get(
f"{ITSM_BASE_URL}/incidents/?priority=P1&status=OPEN",
headers={"Authorization": f"Bearer {ITSM_BOT_TOKEN}"},
)
if r.status_code != 200:
return "장애 목록 조회 실패"
incidents = r.json()["items"]
if not incidents:
return "현재 P1 장애 없음"
lines = [f"[{i['incident_id']}] {i['title']}" for i in incidents[:5]]
return "P1 장애 목록:\n" + "\n".join(lines)
```
### 8.2 ITSM → Messenger 알림 발송
```python
# routers/incidents.py 내 알림 함수
async def _notify_incident(incident: IncidentModel) -> None:
"""장애 발생 시 Messenger 채널 알림"""
try:
channel_id = settings.INCIDENT_CHANNEL_ID
# 보안: 서버 IP/PW 미포함
msg = (
f"🚨 **{incident.priority} 장애 발생**\n"
f"ID: {incident.incident_id}\n"
f"제목: {incident.title}\n"
f"등록: {incident.created_at.strftime('%Y-%m-%d %H:%M')}"
)
async with httpx.AsyncClient(timeout=5) as client:
await client.post(
f"{settings.MESSENGER_BASE_URL}/api/messages",
json={"channel_id": channel_id, "content": msg},
headers={"Authorization": f"Bearer {settings.MESSENGER_BOT_TOKEN}"},
)
except Exception:
# 알림 실패가 비즈니스 로직을 방해하면 안 됨
pass
```
---
## 9. 스케줄러 확장 가이드
### 9.1 새 스케줄 작업 추가
`core/scheduler.py`에 새 작업을 추가합니다:
```python
from apscheduler.triggers.cron import CronTrigger
async def _new_scheduled_task() -> None:
"""매주 월요일 오전 9시 실행 예시"""
from database import SessionLocal
async with SessionLocal() as db:
# 작업 수행
pass
def start_scheduler():
# 기존 작업들 ...
_scheduler.add_job(
_new_scheduled_task,
CronTrigger(day_of_week="mon", hour=9, minute=0),
id="new_task",
replace_existing=True,
)
_scheduler.start()
```
### 9.2 스케줄러 테스트
스케줄러는 직접 함수 호출로 테스트합니다:
```python
@pytest.mark.asyncio
async def test_ssl_expiry_scan(db_session):
from core.scheduler import _scan_ssl_expiry
# 테스트 데이터 준비
# ...
await _scan_ssl_expiry() # 직접 호출
# DB 상태 확인
# ...
```
---
## 10. 배포 및 CI/CD
### 10.1 Jenkins 파이프라인 단계
```groovy
pipeline {
stages {
stage('Test') {
steps {
sh 'pytest tests/unit/ --cov=. --cov-report=xml'
}
}
stage('Quality Gate') {
steps {
// SonarQube 분석
sh 'sonar-scanner -Dsonar.projectKey=guardia-itsm'
}
}
stage('Deploy') {
when { branch 'main' }
steps {
sh './scripts/deploy.sh production'
}
}
}
}
```
### 10.2 운영 서버 배포 체크리스트
```
배포 전:
□ 모든 단위 테스트 통과
□ 커버리지 80% 이상
□ SonarQube 품질 게이트 통과
□ .env 운영 설정 확인 (SECRET_KEY, ENCRYPTION_KEY)
□ DB 백업 완료
배포 중:
□ 서비스 점검 공지
□ 현재 버전 백업
□ 새 버전 배포
□ DB 마이그레이션 (필요 시)
□ 서비스 재시작
배포 후:
□ /docs 페이지 접근 확인
□ 로그인 테스트
□ 주요 API 스모크 테스트
□ 스케줄러 동작 확인
```
---
## 11. 자주 하는 실수 및 주의사항
### 11.1 비동기 함수 실수
```python
# ❌ 실수: async 함수에서 동기 DB 호출
async def bad_function(db: AsyncSession):
result = db.execute(select(Item)) # ❌ await 누락
# ✅ 올바른 방법
async def good_function(db: AsyncSession):
result = await db.execute(select(Item)) # ✅
```
### 11.2 DB 세션 누수
```python
# ❌ 실수: 백그라운드 태스크에서 request 세션 사용
@router.post("/")
async def create(background_tasks: BackgroundTasks, db: AsyncSession = Depends(get_db)):
background_tasks.add_task(some_task, db) # ❌ 세션이 request 후 닫힘
# ✅ 올바른 방법: 백그라운드에서 새 세션 생성
async def some_task():
from database import SessionLocal
async with SessionLocal() as db: # ✅ 새 세션
pass
```
### 11.3 N+1 쿼리 문제
```python
# ❌ N+1 쿼리 발생
srs = (await db.execute(select(SRRequest))).scalars().all()
for sr in srs:
print(sr.work_logs) # 각 SR마다 추가 쿼리 발생!
# ✅ selectinload 사용
srs = (await db.execute(
select(SRRequest).options(selectinload(SRRequest.work_logs))
)).scalars().all()
```
### 11.4 환경 변수 검증
```python
# ✅ 앱 시작 시 필수 환경 변수 검증
class Settings(BaseSettings):
SECRET_KEY: str
ENCRYPTION_KEY: str
@validator("ENCRYPTION_KEY")
def encryption_key_must_be_64_hex(cls, v):
if len(v) != 64:
raise ValueError("ENCRYPTION_KEY는 64자리 hex 문자열이어야 합니다.")
return v
```
### 11.5 외부 API 완전 금지
```python
# ❌ 절대 금지: 외부 LLM/AI API 호출
import openai
client = openai.OpenAI(api_key="...") # ❌ 외부 API 사용 금지
# ✅ 내부 sLLM만 허용
async with httpx.AsyncClient() as client:
r = await client.post(f"{settings.SLLM_BASE_URL}/v1/chat/completions", ...)
```
---
*본 지침서를 준수하지 않은 코드는 코드 리뷰에서 반려될 수 있습니다.*
*보안 관련 규칙(6장)은 특히 엄격히 적용됩니다.*

935
04_운영자지침서.md Normal file
View File

@ -0,0 +1,935 @@
# GUARDiA ITSM + Messenger — 운영자 지침서
**문서 버전**: 1.0
**작성일**: 2026-05-25
**대상**: 시스템 운영자, IT 관리자
---
## 목차
1. [시스템 개요](#1-시스템-개요)
2. [일상 운영 절차](#2-일상-운영-절차)
3. [사용자 계정 관리](#3-사용자-계정-관리)
4. [SR(서비스요청) 관리](#4-sr서비스요청-관리)
5. [SSL 인증서 관리](#5-ssl-인증서-관리)
6. [정기점검(PM) 관리](#6-정기점검pm-관리)
7. [장애 관리](#7-장애-관리)
8. [온콜/당직 관리](#8-온콜당직-관리)
9. [배치 작업 관리](#9-배치-작업-관리)
10. [백업 및 복구](#10-백업-및-복구)
11. [모니터링 및 알림](#11-모니터링-및-알림)
12. [보안 운영 지침](#12-보안-운영-지침)
13. [장애 대응 절차](#13-장애-대응-절차)
14. [로그 관리](#14-로그-관리)
15. [주요 설정 파일](#15-주요-설정-파일)
16. [라이선스 관리](#16-라이선스-관리)
---
## 1. 시스템 개요
### 1.1 구성 요소
| 구성요소 | 역할 | 기본 포트 |
|----------|------|-----------|
| GUARDiA ITSM | IT 서비스 관리 플랫폼 | 8000 |
| GUARDiA Messenger | 내부 메신저 + 봇 | 8001 |
| SQLite DB | 데이터 저장소 | — |
| APScheduler | 백그라운드 작업 (SSL 점검, PM) | — |
### 1.2 서비스 URL
```
운영자/엔지니어 화면: http://<서버IP>:8000/
고객 화면: http://<서버IP>:8000/customer
로그인: http://<서버IP>:8000/login
API 문서: http://<서버IP>:8000/docs (운영: 비활성화 권장)
Messenger: http://<서버IP>:8001/
```
### 1.3 프로세스 구성
```
systemd (Linux):
guardia-itsm.service — ITSM 메인 서버
guardia-messenger.service — Messenger 서버
Windows Service:
GUARDiA-ITSM — ITSM 메인 서버
GUARDiA-Messenger — Messenger 서버
```
---
## 2. 일상 운영 절차
### 2.1 매일 아침 점검 체크리스트
```
시간: 매일 09:00
□ 1. 서비스 상태 확인
→ curl http://localhost:8000/ (200 응답 확인)
□ 2. 야간 스케줄러 실행 결과 확인
→ /var/log/guardia/scheduler.log 최신 항목 확인
→ SSL 점검 결과 확인 (00:10 실행)
→ 계약 만료 점검 결과 확인 (01:00 실행)
→ PM 자동 생성 결과 확인 (06:00 실행)
□ 3. 신규 SR 확인
→ ITSM 로그인 → 대시보드 → 미배정 SR 확인
→ P1/P2 긴급 건 우선 배정
□ 4. SSL 만료 임박 도메인 확인
→ ITSM → SSL 관리 → CRITICAL/URGENT 상태 도메인 갱신 조치
□ 5. 오늘 PM 일정 확인
→ ITSM → 정기점검 → 오늘 예정 작업 확인
□ 6. 라이선스 상태 확인
→ ITSM → 사이드바 🔏 라이선스 관리 → 상태 배너 확인
→ 노랑 배너(만료 30일 이내): 갱신 준비 시작
→ 빨강 배너(만료): 즉시 갱신 키 적용
```
### 2.2 매주 점검 체크리스트
```
시간: 매주 월요일 10:00
□ DB 파일 크기 확인 (100MB 이상 시 정리 검토)
□ 로그 파일 로테이션 확인
□ 업로드 파일 디렉토리 크기 확인
□ 미해결 SR 현황 검토 (7일 이상 미처리 건)
□ 만족도 낮은 SR 원인 분석
□ 이번 주 당직 배정 확인 및 알림
□ 만료 예정 SSL 인증서 30일 이내 갱신 계획 수립
```
### 2.3 매월 점검 체크리스트
```
시간: 매월 1일 11:00
□ 월간 SR 통계 보고서 작성
□ PM 점검 완료율 확인 및 미완료 건 추적
□ 보안 패치 현황 확인
□ 사용자 계정 감사 (퇴직자 계정 비활성화 확인)
□ DB 백업 파일 무결성 확인
□ 장애 발생 현황 분석 및 재발 방지 대책 검토
```
---
## 3. 사용자 계정 관리
### 3.1 계정 역할(Role) 정의
| 역할 | 권한 | 사용 대상 |
|------|------|-----------|
| ADMIN | 전체 관리 | 시스템 관리자 |
| PM | SR 배정, 결재, PM 관리 | 프로젝트/서비스 매니저 |
| ENGINEER | SR 처리, 작업 등록 | IT 엔지니어 |
| CUSTOMER | 자신의 SR 조회/생성 | 고객/사용자 |
### 3.2 신규 사용자 등록
**방법 1: API 직접 등록 (ADMIN)**
```
POST /auth/register
{
"username": "user001",
"password": "초기비밀번호!123",
"full_name": "홍길동",
"email": "hong@example.com",
"role": "ENGINEER"
}
```
**방법 2: seed.py 수정 (초기 구성 시)**
```python
# core/seed.py — _seed_users() 함수에 추가
{"username": "new_user", "password": "초기PW!", "role": Role.ENGINEER}
```
### 3.3 비밀번호 정책
```
최소 길이: 8자
필수 조합: 영문 + 숫자 + 특수문자
금지 비밀번호: admin1234, password, 12345678 등
변경 주기: 90일마다 강제 변경 (구현 예정)
초기 비밀번호: 등록 후 첫 로그인 시 변경 강제
```
### 3.4 계정 잠금 및 비활성화
```
# 퇴직자/이탈자 계정 비활성화
PATCH /auth/users/{id}
{"is_active": false}
# 계정 삭제 (데이터 보존 위해 비활성화 권장, 삭제 비권장)
DELETE /auth/users/{id}
```
---
## 4. SR(서비스요청) 관리
### 4.1 SR 유형 및 처리 기한
| SR 유형 | 설명 | 목표 처리 시간 |
|---------|------|----------------|
| INCIDENT | 긴급 장애, 서비스 중단 | P1: 1시간, P2: 4시간 |
| SERVICE | 일반 서비스 요청 | 2 영업일 |
| CHANGE | 변경 요청 (결재 필요) | 5 영업일 |
| LOG | 작업 기록, 배치 결과 | 정보성 (SLA 없음) |
### 4.2 SR 상태 흐름
```
OPEN → IN_PROGRESS → RESOLVED → CLOSED
↓ ↑
REJECTED ← (결재 거부) REOPENED (재오픈)
```
### 4.3 담당자 배정 방법
```
자동 배정:
PATCH /assign/{sr_id}
{"assignee_id": null} → 시스템이 자동 배정
수동 배정:
PATCH /assign/{sr_id}
{"assignee_id": 5} → 특정 엔지니어(ID:5)에게 배정
```
### 4.4 SLA 위반 모니터링
```
SLA 위반 기준:
P1 INCIDENT: 1시간 이내 미처리
P2 INCIDENT: 4시간 이내 미처리
점검 방법:
GET /dashboard/sla-violations
Messenger 알림: 위반 30분 전 자동 알림 (스케줄러)
```
---
## 5. SSL 인증서 관리
### 5.1 SSL 도메인 등록
```
새 도메인 등록:
POST /ssl-manager/domains
{
"domain": "www.example.com",
"port": 443,
"server_id": 1,
"memo": "메인 홈페이지"
}
```
### 5.2 자동 점검 일정
```
매일 00:10 — 모든 등록 도메인 SSL 점검 자동 실행
점검 방법: 관리 서버의 ssl_expiry_check.sh 스크립트 실행
알림 발송 조건:
EXPIRED (만료됨) → 즉시 Critical 알림
URGENT (7일 이내) → 매일 알림
WARN (30일 이내) → 최초 발견 시 알림
OK (30일 초과) → 알림 없음
```
### 5.3 SSL 갱신 처리 절차
```
1단계: 만료 임박 도메인 확인
GET /ssl-manager/domains?level=CRITICAL
GET /ssl-manager/domains?level=URGENT
2단계: 인증서 갱신 (외부 CA에서 수행)
- Let's Encrypt: certbot renew
- 상용 CA: CA 포털에서 갱신 신청
3단계: 서버에 인증서 적용
- Nginx/Apache 인증서 파일 교체
- 웹서버 재시작
4단계: GUARDiA ITSM에 갱신 완료 기록
POST /ssl-manager/domains/{id}/renew
{
"renewed_at": "2026-05-25T10:00:00",
"new_expiry": "2027-05-25T10:00:00",
"memo": "Let's Encrypt 자동 갱신"
}
5단계: 즉시 재점검으로 확인
POST /ssl-manager/domains/{id}/check
```
### 5.4 SSL 스크립트 배포
서버에 점검 스크립트를 배포해야 SSH 점검이 가능합니다:
```bash
# 관리 대상 서버에 스크립트 배포
scp scripts/sm/ssl/ssl_expiry_check.sh user@서버IP:/opt/guardia/scripts/ssl/
ssh user@서버IP "chmod +x /opt/guardia/scripts/ssl/ssl_expiry_check.sh"
# 테스트
ssh user@서버IP "bash /opt/guardia/scripts/ssl/ssl_expiry_check.sh www.example.com"
```
---
## 6. 정기점검(PM) 관리
### 6.1 PM 스케줄 설정
```
PM 주기 옵션:
WEEKLY — 매주
BIWEEKLY — 격주
MONTHLY — 매월
QUARTERLY — 분기별 (3개월)
SEMIANNUAL — 반기별 (6개월)
ANNUAL — 연간
CUSTOM — 커스텀 cron 식
스케줄 등록:
POST /pm/schedules/
{
"name": "운영 서버 월간 점검",
"server_id": 1,
"template_ids": [1, 2, 3],
"frequency": "MONTHLY",
"day_of_month": 15,
"assigned_to": 5
}
```
### 6.2 PM 작업 자동 생성
```
매일 06:00 — 스케줄러가 당일 PM 작업 자동 생성
자동 생성된 작업: GET /pm/works/
수동 생성:
POST /pm/works/
{
"schedule_id": 1,
"planned_date": "2026-06-01",
"notes": "6월 월간 점검"
}
```
### 6.3 PM 결과 입력
```
각 체크리스트 항목 결과 입력:
PATCH /pm/works/{work_id}/results/{result_id}
{
"result": "PASS", // PASS / FAIL / WARNING / NA
"value": "85%", // 측정값
"note": "" // FAIL인 경우 조치 내역 필수
}
PM 완료 처리:
PATCH /pm/works/{work_id}/complete
```
### 6.4 PM 보고서 출력
```
Excel 보고서 다운로드:
GET /pm/works/{work_id}/report
→ Excel 파일 다운로드 (PASS: 녹색, FAIL: 빨간색, WARNING: 노란색)
```
---
## 7. 장애 관리
### 7.1 장애 등급 정의
| 등급 | 영향 | 대응 시간 | 예시 |
|------|------|-----------|------|
| P1 | 전체 서비스 중단 | 1시간 | 운영 DB Down |
| P2 | 주요 기능 장애 | 4시간 | 로그인 불가 |
| P3 | 부분 기능 장애 | 8시간 | 특정 기능 오류 |
| P4 | 경미한 장애 | 2영업일 | UI 오류 |
### 7.2 P1/P2 장애 대응 절차
```
즉시 조치 (15분 이내):
1. ITSM에 장애 등록 (POST /incidents/)
→ Messenger 긴급 알림 자동 발송
2. 온콜 담당자에게 연락
3. 장애 타임라인 기록 시작
초기 조사 (30분 이내):
4. PATCH /incidents/{id}/status — INVESTIGATING
5. 원인 파악 시작
6. 임시 우회 방안 검토
조치 및 복구:
7. PATCH /incidents/{id}/status — MITIGATED (임시 조치 완료)
8. 근본 원인 해결
9. PATCH /incidents/{id}/status — RESOLVED
사후 처리:
10. RCA(근본원인분석) 작성
11. POST /incidents/{id}/close (RCA 포함 필수)
12. 재발 방지 대책 SR 등록
```
### 7.3 장애 타임라인 기록
```
장애 진행 중 모든 주요 활동 기록:
POST /incidents/{id}/timeline
{
"event_type": "INVESTIGATION",
"description": "DB 서버 CPU 100% 확인, 쿼리 분석 중"
}
event_type 옵션:
DETECTION — 최초 감지
INVESTIGATION — 조사 활동
ACTION — 조치 수행
UPDATE — 현황 업데이트
RECOVERY — 복구 완료
POSTMORTEM — 사후 분석
```
---
## 8. 온콜/당직 관리
### 8.1 당직 배정
```
단건 배정:
POST /oncall/duties
{
"duty_date": "2026-06-01",
"shift": "ALL_DAY", // ALL_DAY / DAYTIME / NIGHTTIME
"user_id": 5,
"note": "야간 비상 연락"
}
월간 일괄 배정:
POST /oncall/duties/bulk
{
"duties": [
{"duty_date": "2026-06-01", "shift": "ALL_DAY", "user_id": 5},
{"duty_date": "2026-06-02", "shift": "ALL_DAY", "user_id": 6}
],
"overwrite": false // true: 중복 시 덮어쓰기
}
```
### 8.2 오늘 당직 확인
```
GET /oncall/today
→ 오늘 날짜의 당직자 목록 (교대별)
응답 예시:
{
"date": "2026-06-01",
"duties": {
"ALL_DAY": [{"user": "홍길동", "phone": "010-XXXX-XXXX"}],
"NIGHTTIME": [{"user": "김철수", "phone": "010-XXXX-XXXX"}]
}
}
```
### 8.3 월간 당직 조회
```
GET /oncall/calendar?year=2026&month=6
→ 해당 월 전체 당직 일정
```
---
## 9. 배치 작업 관리
### 9.1 배치 작업 등록
```
POST /batch/jobs
{
"name": "일일 DB 백업",
"server_id": 1,
"command": "bash /opt/scripts/backup.sh",
"cron_expr": "0 2 * * *", // 매일 오전 2시
"timeout_sec": 3600,
"alert_on_fail": true,
"is_active": true
}
```
> **주의**: 다음 명령어 패턴은 등록 자체가 거부됩니다:
> `rm -rf /`, `shutdown`, `reboot`, `mkfs`, `dd if=`, Fork Bomb 등
### 9.2 배치 작업 모니터링
```
최근 실행 이력:
GET /batch/jobs/{id}/runs?limit=20
실행 상태:
RUNNING — 실행 중
SUCCESS — 성공
FAILED — 실패 (alert_on_fail=true 시 SR 자동 생성)
TIMEOUT — 타임아웃 초과
실패 시 자동 SR: GET /tasks/?sr_type=LOG&priority=HIGH
```
### 9.3 수동 실행
```
POST /batch/jobs/{id}/run
→ 202 Accepted (백그라운드 실행 시작)
실행 결과 확인:
GET /batch/jobs/{id}/runs?limit=1 (최신 1건)
```
---
## 10. 백업 및 복구
### 10.1 DB 백업 절차
```bash
# Linux — 수동 백업
cp /opt/guardia/itsm/guardia_itsm.db /backup/guardia_itsm_$(date +%Y%m%d).db
cp /opt/guardia/messenger/guardia_messenger.db /backup/guardia_messenger_$(date +%Y%m%d).db
# 자동 백업 스크립트 (cron 등록 권장)
# crontab -e
0 3 * * * /opt/guardia/scripts/backup.sh >> /var/log/guardia/backup.log 2>&1
# Windows — PowerShell
$date = Get-Date -Format "yyyyMMdd"
Copy-Item "C:\GUARDiA\itsm\guardia_itsm.db" "D:\Backup\guardia_itsm_$date.db"
```
### 10.2 업로드 파일 백업
```bash
# 업로드 파일 디렉토리 전체 백업
tar -czf /backup/uploads_$(date +%Y%m%d).tar.gz /opt/guardia/itsm/uploads/
```
### 10.3 복구 절차
```bash
# 1. 서비스 중지
systemctl stop guardia-itsm
systemctl stop guardia-messenger
# 2. 현재 DB 보존
mv guardia_itsm.db guardia_itsm.db.broken
# 3. 백업 복원
cp /backup/guardia_itsm_20260525.db guardia_itsm.db
# 4. 서비스 재시작
systemctl start guardia-itsm
systemctl start guardia-messenger
# 5. 서비스 상태 확인
curl http://localhost:8000/
```
### 10.4 백업 보관 정책
```
일간 백업: 30일 보관
주간 백업: 12주 보관
월간 백업: 12개월 보관
```
---
## 11. 모니터링 및 알림
### 11.1 시스템 모니터링 지점
| 모니터링 항목 | 임계값 | 조치 |
|--------------|--------|------|
| ITSM 서비스 응답 | 5초 이상 → 경고 | 서비스 재시작 |
| DB 파일 크기 | 500MB 이상 → 경고 | 정리/이관 검토 |
| 디스크 사용률 | 80% 이상 → 경고 | 파일 정리 |
| 업로드 디렉토리 | 10GB 이상 → 경고 | 오래된 파일 정리 |
| 스케줄러 실행 | 오전 06:30까지 미실행 → 경고 | 재시작 |
### 11.2 Messenger 알림 채널
```
장애 알림: #guardia-incidents
SSL 만료 알림: #guardia-ssl
PM 일정 알림: #guardia-pm
배치 실패: #guardia-ops
```
### 11.3 서비스 상태 확인
```bash
# Linux
systemctl status guardia-itsm
systemctl status guardia-messenger
# 로그 실시간 확인
journalctl -u guardia-itsm -f
# Windows
Get-Service -Name "GUARDiA-ITSM"
```
---
## 12. 보안 운영 지침
### 12.1 계정 보안
```
□ 기본 admin 비밀번호 즉시 변경 (초기 설치 후)
□ 퇴직자 계정 당일 비활성화
□ 공용 계정 사용 금지 (1인 1계정 원칙)
□ 분기마다 불필요 계정 감사
□ CUSTOMER 역할 계정은 자신의 SR만 조회 가능 확인
```
### 12.2 API 접근 제어
```
□ 운영 환경에서 /docs 페이지 비활성화 권장
main.py: app = FastAPI(docs_url=None, redoc_url=None)
□ CORS 설정 확인 (운영 환경)
allow_origins: 실제 도메인만 허가 (localhost 제거)
□ 로그인 실패 횟수 모니터링
GET /audit/logs?action=LOGIN_FAIL
```
### 12.3 서버 자격증명 관리
```
□ SSH 비밀번호는 AES-256-GCM 암호화 저장 확인
□ 평문 비밀번호 어디에도 저장 금지
□ ENCRYPTION_KEY는 .env에만 저장, Git 제외
□ API 응답에 IP/PW 포함 여부 정기 검사
□ root SSH 직접 접속 금지 설정 확인
```
### 12.4 감사 로그 관리
```
감사 로그 확인:
GET /audit/logs?start=2026-05-01&end=2026-05-31
무결성 검증:
GET /audit/verify
→ "integrity": true 확인
의심 활동 모니터링:
- 새벽 시간대 로그인
- 다수 SR 한 번에 변경
- 서버 자격증명 접근
```
---
## 13. 장애 대응 절차
### 13.1 ITSM 서비스 응답 없음
```
증상: curl http://localhost:8000/ 타임아웃
1단계 — 프로세스 확인
Linux: ps aux | grep uvicorn
Windows: Get-Process -Name python
2단계 — 로그 확인
Linux: journalctl -u guardia-itsm --since "10 min ago"
Linux: cat /var/log/guardia/itsm.log | tail -100
3단계 — 서비스 재시작
Linux: systemctl restart guardia-itsm
Windows: Restart-Service "GUARDiA-ITSM"
4단계 — 재시작 후 확인
curl http://localhost:8000/
→ 200 응답 확인
```
### 13.2 DB 잠금(Lock) 오류
```
증상: "database is locked" 에러 로그 발생
1단계 — 연결 프로세스 확인
Linux: fuser guardia_itsm.db
2단계 — 임시 잠금 파일 삭제 (서비스 중지 후)
rm guardia_itsm.db-shm guardia_itsm.db-wal
3단계 — 서비스 재시작
systemctl restart guardia-itsm
```
### 13.3 스케줄러 미동작
```
증상: SSL 점검, PM 자동 생성이 실행되지 않음
1단계 — 로그 확인
grep "scheduler" /var/log/guardia/itsm.log
2단계 — APScheduler 설치 확인
pip show apscheduler
3단계 — 서비스 재시작
systemctl restart guardia-itsm
4단계 — 수동 실행으로 대체
ITSM → SSL 관리 → "전체 점검" 버튼
ITSM → 정기점검 → 수동 작업 생성
```
### 13.4 Messenger 연결 실패
```
증상: ITSM 알림이 Messenger에 미전달
1단계 — Messenger 서비스 상태 확인
systemctl status guardia-messenger
2단계 — 연결 설정 확인
.env 파일의 MESSENGER_BASE_URL, MESSENGER_BOT_TOKEN 확인
3단계 — 연결 테스트
curl http://localhost:8001/api/health
4단계 — Messenger 재시작
systemctl restart guardia-messenger
주의: Messenger 알림 실패는 ITSM 기능에 영향 없음 (알림만 안 됨)
```
---
## 14. 로그 관리
### 14.1 로그 파일 위치
```
Linux:
/var/log/guardia/itsm.log — ITSM 애플리케이션 로그
/var/log/guardia/scheduler.log — 스케줄러 실행 로그
/var/log/guardia/messenger.log — Messenger 로그
/var/log/guardia/backup.log — 백업 실행 로그
Windows:
C:\GUARDiA\logs\itsm.log
C:\GUARDiA\logs\scheduler.log
C:\GUARDiA\logs\messenger.log
```
### 14.2 로그 로테이션 설정 (Linux)
```
# /etc/logrotate.d/guardia
/var/log/guardia/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 640 guardia guardia
postrotate
systemctl reload guardia-itsm
endscript
}
```
### 14.3 감사 로그 (DB 내 저장)
```
ITSM 내 모든 주요 활동은 tb_audit_log 테이블에 저장됩니다.
해시 체인으로 무결성 보호.
조회:
GET /audit/logs?limit=100
GET /audit/logs?user_id=5&start=2026-05-01
무결성 검증:
GET /audit/verify
```
---
## 15. 주요 설정 파일
### 15.1 .env 운영 설정
```ini
# 운영 환경 .env
SECRET_KEY=<64자 이상 랜덤 문자열>
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=480
DB_URL=sqlite+aiosqlite:///./guardia_itsm.db
ENCRYPTION_KEY=<64자리 hex 문자열>
MESSENGER_BASE_URL=http://localhost:8001
MESSENGER_BOT_TOKEN=< 토큰>
SSH_TIMEOUT=30
UPLOAD_ROOT=./uploads
MAX_FILE_SIZE_MB=10
# 운영: API 문서 비활성화
DOCS_DISABLED=true
```
### 15.2 서비스 파일 (Linux systemd)
```ini
# /etc/systemd/system/guardia-itsm.service
[Unit]
Description=GUARDiA ITSM Service
After=network.target
[Service]
User=guardia
WorkingDirectory=/opt/guardia/itsm
EnvironmentFile=/opt/guardia/itsm/.env
ExecStart=/opt/guardia/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2
Restart=always
RestartSec=5
StandardOutput=append:/var/log/guardia/itsm.log
StandardError=append:/var/log/guardia/itsm.log
[Install]
WantedBy=multi-user.target
```
### 15.3 방화벽 설정
```bash
# Linux (firewalld)
firewall-cmd --permanent --add-port=8000/tcp # ITSM
firewall-cmd --permanent --add-port=8001/tcp # Messenger
firewall-cmd --reload
# Windows (PowerShell)
New-NetFirewallRule -DisplayName "GUARDiA ITSM" -Direction Inbound -Protocol TCP -LocalPort 8000 -Action Allow
New-NetFirewallRule -DisplayName "GUARDiA Messenger" -Direction Inbound -Protocol TCP -LocalPort 8001 -Action Allow
```
---
## 부록 A: 운영 명령어 빠른 참조
```bash
# 서비스 시작/중지/재시작
systemctl start|stop|restart guardia-itsm
systemctl start|stop|restart guardia-messenger
# 상태 확인
systemctl status guardia-itsm
curl http://localhost:8000/
# 로그 확인
journalctl -u guardia-itsm -f --since "1 hour ago"
# DB 백업
cp guardia_itsm.db guardia_itsm_$(date +%Y%m%d%H%M).db
# 업로드 용량 확인
du -sh uploads/
# 활성 연결 확인
ss -tlnp | grep :8000
```
## 부록 B: 비상 연락망
```
ITSM 운영팀: ...
보안팀: ...
인프라팀: ...
```
---
---
## 16. 라이선스 관리
### 16.1 라이선스 등록 절차
1. ITSM 관리자 계정으로 로그인
2. 사이드바 하단 **🔏 라이선스 관리** 클릭 (`/license` 페이지)
3. **라이선스 키 등록/갱신** 섹션에 `GRD-...` 형식의 키 붙여넣기
4. **라이선스 등록** 버튼 클릭
5. 상단 배너에서 에디션·만료일 확인
**API로 등록 (자동화)**:
```bash
curl -X POST http://localhost:8001/api/license/activate \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"license_key": "GRD-..."}'
```
### 16.2 라이선스 상태 배너 의미
| 배너 색상 | 상태 | 조치 |
|---------|------|------|
| ✅ 초록 | 정상 활성 | — |
| ⚠️ 노랑 | 만료 30일 이내 경고 | 갱신 라이선스 요청 |
| ❌ 빨강 | 만료됨 | 즉시 갱신 키 적용 필요 |
| ⬜ 회색 | 라이선스 미등록 | Community 제한 모드로 동작 |
### 16.3 에디션별 자원 제한
| 에디션 | 기관 수 | 사용자 수 | 서버 수 | 주요 기능 |
|--------|--------|---------|--------|---------|
| COMMUNITY | 1 | 10 | 20 | MFA |
| STANDARD | 50 | 200 | 500 | MFA, LDAP, PAM, AI 에이전트 |
| ENTERPRISE | 무제한 | 무제한 | 무제한 | 전체 기능 |
> **주의**: 한도 초과 시 신규 등록 API가 HTTP 403을 반환한다.
> 기존 등록 데이터는 만료·초과 후에도 삭제되지 않는다.
### 16.4 라이선스 갱신
갱신 라이선스 키를 받은 후 16.1과 동일하게 등록하면 기존 라이선스가 자동으로 비활성화되고 새 라이선스로 교체된다.
자세한 발급·갱신 절차는 **`manual/14_라이선스_키_발급_가이드.md`** 참조.
---
*본 운영자 지침서는 GUARDiA ITSM v1.0 기준으로 작성되었습니다.*

View File

@ -0,0 +1,837 @@
# GUARDiA ITSM + Messenger — 설치 가이드 (Linux)
**문서 버전**: 1.0
**작성일**: 2026-05-25
**지원 OS**: Ubuntu 22.04 LTS, Ubuntu 20.04 LTS, CentOS 8+, RHEL 8+
---
## 목차
1. [설치 전 요구사항 확인](#1-설치-전-요구사항-확인)
2. [시스템 사전 준비](#2-시스템-사전-준비)
3. [Python 환경 설정](#3-python-환경-설정)
4. [GUARDiA ITSM 설치](#4-guardia-itsm-설치)
5. [GUARDiA Messenger 설치](#5-guardia-messenger-설치)
6. [서비스 등록 (systemd)](#6-서비스-등록-systemd)
7. [SSL 점검 스크립트 배포](#7-ssl-점검-스크립트-배포)
8. [방화벽 설정](#8-방화벽-설정)
9. [Nginx 리버스 프록시 설정 (선택)](#9-nginx-리버스-프록시-설정-선택)
10. [설치 검증](#10-설치-검증)
11. [보안 강화 설정](#11-보안-강화-설정)
12. [문제 해결](#12-문제-해결)
---
## 1. 설치 전 요구사항 확인
### 1.1 하드웨어 최소 요구사항
| 항목 | 최소 | 권장 |
|------|------|------|
| CPU | 2코어 | 4코어 이상 |
| RAM | 2GB | 4GB 이상 |
| 디스크 | 20GB | 50GB 이상 |
| 네트워크 | 100Mbps | 1Gbps |
### 1.2 소프트웨어 요구사항
```
OS: Ubuntu 22.04 LTS (권장)
Python: 3.11 이상
openssl: 1.1.1 이상 (SSL 점검에 필요)
sqlite3: 3.35 이상
```
### 1.3 요구사항 사전 확인
```bash
# OS 버전 확인
lsb_release -a
uname -r
# Python 버전 확인
python3 --version
# → Python 3.11.x 이상이어야 함
# openssl 버전 확인
openssl version
# → OpenSSL 1.1.1 이상
# SQLite 버전 확인
sqlite3 --version
# → 3.35.0 이상
# 디스크 여유 공간 확인
df -h /
# → Available 20GB 이상 확인
# 포트 사용 여부 확인 (8000, 8001이 비어 있어야 함)
ss -tlnp | grep -E ':8000|:8001'
# → 아무것도 출력되지 않아야 함
```
---
## 2. 시스템 사전 준비
### 2.1 시스템 업데이트 (Ubuntu)
```bash
sudo apt update && sudo apt upgrade -y
sudo apt install -y git curl wget vim unzip
```
### 2.2 시스템 업데이트 (CentOS/RHEL)
```bash
sudo dnf update -y
sudo dnf install -y git curl wget vim unzip
```
### 2.3 전용 사용자 생성
보안을 위해 root가 아닌 전용 사용자로 실행합니다.
```bash
# guardia 사용자 생성 (로그인 불가 설정)
sudo useradd -r -m -d /opt/guardia -s /bin/bash guardia
sudo passwd guardia # 비밀번호 설정 (관리자만 알아야 함)
# 사용자 확인
id guardia
# → uid=998(guardia) gid=998(guardia) groups=998(guardia)
```
### 2.4 디렉토리 구조 생성
```bash
sudo mkdir -p /opt/guardia/{itsm,messenger}
sudo mkdir -p /opt/guardia/scripts/ssl
sudo mkdir -p /var/log/guardia
sudo mkdir -p /backup/guardia
sudo chown -R guardia:guardia /opt/guardia
sudo chown -R guardia:guardia /var/log/guardia
sudo chown -R guardia:guardia /backup/guardia
# 확인
ls -la /opt/guardia/
```
---
## 3. Python 환경 설정
### 3.1 Python 3.11 설치 (Ubuntu 22.04)
Ubuntu 22.04는 기본적으로 Python 3.10이 설치됩니다. 3.11을 설치합니다.
```bash
# deadsnakes PPA 추가
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt update
# Python 3.11 설치
sudo apt install -y python3.11 python3.11-venv python3.11-dev
# 버전 확인
python3.11 --version
# → Python 3.11.x
```
### 3.2 Python 3.11 설치 (CentOS/RHEL 8)
```bash
# EPEL 및 개발 도구 설치
sudo dnf install -y epel-release
sudo dnf install -y python3.11 python3.11-pip
python3.11 --version
```
### 3.3 pip 업그레이드
```bash
python3.11 -m pip install --upgrade pip
```
---
## 4. GUARDiA ITSM 설치
### 4.1 소스 코드 배포
```bash
# guardia 사용자로 전환
sudo -u guardia bash
# 소스 코드 복사 (파일 서버에서 복사 또는 git clone)
# 방법 1: scp로 복사
# scp -r itsm/ guardia@서버IP:/opt/guardia/itsm/
# 방법 2: git clone (내부 Git 서버)
cd /opt/guardia
git clone http://내부git서버/guardia/itsm.git itsm
# 방법 3: 직접 파일 복사 (zip 사용)
# unzip guardia_itsm_v1.0.zip -d /opt/guardia/itsm/
# 설치 확인
ls /opt/guardia/itsm/
# → main.py database.py models.py schemas.py routers/ core/ utils/ static/ 등
```
### 4.2 가상환경 생성 및 의존성 설치
```bash
# /opt/guardia/itsm 디렉토리에서
cd /opt/guardia/itsm
# 가상환경 생성 (공유 위치)
python3.11 -m venv /opt/guardia/.venv
# 가상환경 활성화
source /opt/guardia/.venv/bin/activate
# pip 업그레이드
pip install --upgrade pip
# 의존성 설치
pip install -r requirements.txt
# 설치 확인
pip list | grep -E "fastapi|uvicorn|sqlalchemy|apscheduler"
# → 패키지 목록 출력 확인
# 가상환경 비활성화
deactivate
```
**requirements.txt 주요 패키지**:
```
fastapi>=0.110
uvicorn[standard]>=0.29
sqlalchemy>=2.0
aiosqlite>=0.20
python-jose[cryptography]>=3.3
passlib[bcrypt]>=1.7
python-multipart>=0.0.9
apscheduler>=3.10
asyncssh>=2.14
openpyxl>=3.1
httpx>=0.27
cryptography>=42.0
croniter>=2.0
```
### 4.3 환경 변수 설정
```bash
# .env 파일 생성
cat > /opt/guardia/itsm/.env << 'EOF'
SECRET_KEY=여기에_64자이상의_랜덤문자열_입력_반드시_변경하세요
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=480
DB_URL=sqlite+aiosqlite:////opt/guardia/itsm/guardia_itsm.db
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# 라이선스 마스터 키 (64자리 hex = 32바이트)
# 생성: python3 -c "import secrets; print(secrets.token_hex(32))"
# ⚠️ 분실 시 기발급 라이선스 전부 무효화 — 안전하게 별도 보관 필수
GUARDIA_LICENSE_KEY=0000000000000000000000000000000000000000000000000000000000000000
MESSENGER_BASE_URL=http://localhost:8001
MESSENGER_BOT_TOKEN=변경하세요
SSH_TIMEOUT=30
UPLOAD_ROOT=/opt/guardia/itsm/uploads
MAX_FILE_SIZE_MB=10
EOF
# 권한 설정 (소유자만 읽기/쓰기)
chmod 600 /opt/guardia/itsm/.env
chown guardia:guardia /opt/guardia/itsm/.env
```
> **중요**: `SECRET_KEY`, `ENCRYPTION_KEY`, `GUARDIA_LICENSE_KEY`는 반드시 강력한 랜덤 값으로 변경하세요!
**랜덤 키 생성 방법**:
```bash
# SECRET_KEY 생성 (64자)
python3 -c "import secrets; print(secrets.token_hex(32))"
# ENCRYPTION_KEY 생성 (64자 hex = AES-256)
python3 -c "import secrets; print(secrets.token_hex(32))"
```
### 4.4 업로드 디렉토리 생성
```bash
mkdir -p /opt/guardia/itsm/uploads/sr_files
mkdir -p /opt/guardia/itsm/uploads/workspaces
chown -R guardia:guardia /opt/guardia/itsm/uploads/
```
### 4.5 초기 실행 및 DB 초기화
```bash
cd /opt/guardia/itsm
source /opt/guardia/.venv/bin/activate
# 테스트 실행 (초기 DB 생성 및 시드 데이터 삽입)
python -m uvicorn main:app --host 127.0.0.1 --port 8000 &
sleep 5
# 정상 동작 확인
curl http://127.0.0.1:8000/
# → {"detail":"Not Found"} 또는 HTML 응답이면 정상
# 로그인 테스트
curl -X POST http://127.0.0.1:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin1234!"}'
# → {"access_token":"...","token_type":"bearer"} 출력 확인
# 프로세스 종료 (systemd 서비스로 실행 예정)
kill %1
deactivate
```
---
## 5. GUARDiA Messenger 설치
### 5.1 소스 코드 배포
```bash
sudo -u guardia bash
cd /opt/guardia
git clone http://내부git서버/guardia/messenger.git messenger
# 또는 파일 복사
```
### 5.2 의존성 설치
```bash
cd /opt/guardia/messenger
source /opt/guardia/.venv/bin/activate
pip install -r requirements.txt
deactivate
```
### 5.3 환경 변수 설정
```bash
cat > /opt/guardia/messenger/.env << 'EOF'
SECRET_KEY=ITSM과_동일한_키_또는_별도_키
ALGORITHM=HS256
DB_URL=sqlite+aiosqlite:////opt/guardia/messenger/guardia_messenger.db
ITSM_BASE_URL=http://localhost:8000
PORT=8001
EOF
chmod 600 /opt/guardia/messenger/.env
chown guardia:guardia /opt/guardia/messenger/.env
```
---
## 6. 서비스 등록 (systemd)
### 6.1 ITSM 서비스 파일 생성
```bash
sudo tee /etc/systemd/system/guardia-itsm.service > /dev/null << 'EOF'
[Unit]
Description=GUARDiA ITSM Service
Documentation=https://내부문서URL
After=network.target network-online.target
Wants=network-online.target
[Service]
Type=simple
User=guardia
Group=guardia
WorkingDirectory=/opt/guardia/itsm
EnvironmentFile=/opt/guardia/itsm/.env
ExecStart=/opt/guardia/.venv/bin/uvicorn main:app \
--host 0.0.0.0 \
--port 8000 \
--workers 2 \
--log-level info
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5
StartLimitInterval=60
StartLimitBurst=3
# 보안 강화
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/guardia/itsm/uploads /var/log/guardia
StandardOutput=append:/var/log/guardia/itsm.log
StandardError=append:/var/log/guardia/itsm.log
[Install]
WantedBy=multi-user.target
EOF
```
### 6.2 Messenger 서비스 파일 생성
```bash
sudo tee /etc/systemd/system/guardia-messenger.service > /dev/null << 'EOF'
[Unit]
Description=GUARDiA Messenger Service
After=network.target guardia-itsm.service
[Service]
Type=simple
User=guardia
Group=guardia
WorkingDirectory=/opt/guardia/messenger
EnvironmentFile=/opt/guardia/messenger/.env
ExecStart=/opt/guardia/.venv/bin/uvicorn main:app \
--host 0.0.0.0 \
--port 8001 \
--workers 1 \
--log-level info
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
StandardOutput=append:/var/log/guardia/messenger.log
StandardError=append:/var/log/guardia/messenger.log
[Install]
WantedBy=multi-user.target
EOF
```
### 6.3 서비스 등록 및 시작
```bash
# systemd 재로드
sudo systemctl daemon-reload
# 서비스 활성화 (부팅 시 자동 시작)
sudo systemctl enable guardia-itsm
sudo systemctl enable guardia-messenger
# 서비스 시작
sudo systemctl start guardia-itsm
sudo systemctl start guardia-messenger
# 상태 확인
sudo systemctl status guardia-itsm
sudo systemctl status guardia-messenger
# 기대 출력:
# ● guardia-itsm.service - GUARDiA ITSM Service
# Active: active (running) since ...
```
### 6.4 로그 확인
```bash
# 실시간 로그 확인
sudo journalctl -u guardia-itsm -f
# 최근 50줄
sudo journalctl -u guardia-itsm -n 50
# 파일 직접 확인
tail -f /var/log/guardia/itsm.log
```
---
## 7. SSL 점검 스크립트 배포
GUARDiA ITSM이 관리하는 모든 서버에 SSL 점검 스크립트를 배포합니다.
```bash
# ITSM 서버에서 관리 대상 서버로 스크립트 복사
# (ITSM 서버에서 실행)
TARGET_SERVERS=("192.168.1.10" "192.168.1.11" "192.168.1.12")
for SERVER in "${TARGET_SERVERS[@]}"; do
echo "배포 중: $SERVER"
ssh guardia@${SERVER} "mkdir -p /opt/guardia/scripts/ssl"
scp /opt/guardia/itsm/scripts/sm/ssl/ssl_expiry_check.sh \
guardia@${SERVER}:/opt/guardia/scripts/ssl/
ssh guardia@${SERVER} "chmod +x /opt/guardia/scripts/ssl/ssl_expiry_check.sh"
# 동작 테스트
ssh guardia@${SERVER} "bash /opt/guardia/scripts/ssl/ssl_expiry_check.sh google.com"
echo "배포 완료: $SERVER"
done
```
---
## 8. 방화벽 설정
### 8.1 UFW (Ubuntu)
```bash
# UFW 활성화 (이미 활성화된 경우 skip)
sudo ufw enable
# GUARDiA 포트 허용
sudo ufw allow 8000/tcp comment "GUARDiA ITSM"
sudo ufw allow 8001/tcp comment "GUARDiA Messenger"
# SSH 허용 (원격 접속용, 이미 설정된 경우 skip)
sudo ufw allow 22/tcp
# 상태 확인
sudo ufw status verbose
```
### 8.2 firewalld (CentOS/RHEL)
```bash
# GUARDiA 포트 허용
sudo firewall-cmd --permanent --add-port=8000/tcp
sudo firewall-cmd --permanent --add-port=8001/tcp
sudo firewall-cmd --reload
# 확인
sudo firewall-cmd --list-ports
```
### 8.3 특정 IP만 허용 (보안 강화)
```bash
# 내부 네트워크(192.168.1.0/24)에서만 접근 허용
sudo ufw allow from 192.168.1.0/24 to any port 8000
sudo ufw allow from 192.168.1.0/24 to any port 8001
sudo ufw deny 8000
sudo ufw deny 8001
```
---
## 9. Nginx 리버스 프록시 설정 (선택)
HTTPS를 적용하거나 표준 포트(80/443)를 사용하려면 Nginx를 앞단에 배치합니다.
### 9.1 Nginx 설치
```bash
# Ubuntu
sudo apt install -y nginx
# CentOS/RHEL
sudo dnf install -y nginx
```
### 9.2 Nginx 설정
```bash
sudo tee /etc/nginx/conf.d/guardia.conf > /dev/null << 'EOF'
# HTTP → HTTPS 리다이렉트
server {
listen 80;
server_name itsm.example.com;
return 301 https://$host$request_uri;
}
# ITSM HTTPS
server {
listen 443 ssl http2;
server_name itsm.example.com;
ssl_certificate /etc/ssl/certs/itsm.example.com.crt;
ssl_certificate_key /etc/ssl/private/itsm.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# 업로드 용량 제한
client_max_body_size 20M;
# ITSM API/UI
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300;
proxy_connect_timeout 10;
}
}
# Messenger HTTPS
server {
listen 443 ssl http2;
server_name messenger.example.com;
ssl_certificate /etc/ssl/certs/messenger.example.com.crt;
ssl_certificate_key /etc/ssl/private/messenger.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://127.0.0.1:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; # WebSocket 지원
}
}
EOF
# 설정 검증
sudo nginx -t
# → configuration file /etc/nginx/nginx.conf syntax is ok
# Nginx 재시작
sudo systemctl restart nginx
sudo systemctl enable nginx
```
---
## 10. 설치 검증
### 10.1 서비스 상태 확인
```bash
# 서비스 실행 상태
sudo systemctl is-active guardia-itsm guardia-messenger
# → active
# → active
# 포트 리스닝 확인
ss -tlnp | grep -E ':8000|:8001'
# → 8000, 8001 포트 출력 확인
```
### 10.2 API 기능 테스트
```bash
# 1. 홈페이지 응답 확인
curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/
# → 200
# 2. 로그인 테스트
curl -s -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin1234!"}' | python3 -m json.tool
# → {"access_token": "eyJ...", "token_type": "bearer"} 출력 확인
# 3. 토큰으로 API 호출
TOKEN=$(curl -s -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin1234!"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s http://localhost:8000/dashboard/summary \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
# → 대시보드 통계 JSON 출력 확인
# 4. Messenger 확인
curl -s http://localhost:8001/
# → 200 응답 확인
```
### 10.3 스케줄러 동작 확인
```bash
# 로그에서 스케줄러 시작 메시지 확인
grep -i "scheduler" /var/log/guardia/itsm.log | tail -5
# → "scheduler started" 또는 "APScheduler started" 메시지 확인
```
### 10.4 설치 검증 스크립트
```bash
cat > /tmp/verify_install.sh << 'EOF'
#!/bin/bash
echo "=== GUARDiA 설치 검증 ==="
check() {
if $1; then echo " [ok] $2"; else echo " [FAIL] $2"; fi
}
# 서비스 상태
systemctl is-active --quiet guardia-itsm
check "[ $? -eq 0 ]" "ITSM 서비스 실행 중"
systemctl is-active --quiet guardia-messenger
check "[ $? -eq 0 ]" "Messenger 서비스 실행 중"
# 포트 확인
ss -tlnp | grep -q ':8000'
check "[ $? -eq 0 ]" "ITSM 포트 8000 리스닝"
ss -tlnp | grep -q ':8001'
check "[ $? -eq 0 ]" "Messenger 포트 8001 리스닝"
# API 응답
CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/)
check "[ '$CODE' = '200' ]" "ITSM API 응답 (200)"
# DB 파일
[ -f /opt/guardia/itsm/guardia_itsm.db ]
check "[ $? -eq 0 ]" "ITSM DB 파일 존재"
echo "=== 검증 완료 ==="
EOF
chmod +x /tmp/verify_install.sh
bash /tmp/verify_install.sh
```
---
## 11. 보안 강화 설정
### 11.1 초기 관리자 비밀번호 변경
```bash
# 로그인 후 즉시 비밀번호 변경
TOKEN=$(curl -s -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin1234!"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -X POST http://localhost:8000/auth/change-password \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"current_password":"admin1234!","new_password":"새로운강력한비밀번호!@#"}'
```
### 11.2 API 문서 비활성화 (운영)
```python
# main.py 수정
app = FastAPI(
title="GUARDiA ITSM",
version="1.0.0",
lifespan=lifespan,
docs_url=None, # /docs 비활성화
redoc_url=None, # /redoc 비활성화
)
```
### 11.3 DB 파일 권한 설정
```bash
# DB 파일은 guardia 사용자만 접근 가능
chmod 600 /opt/guardia/itsm/guardia_itsm.db
chmod 600 /opt/guardia/messenger/guardia_messenger.db
```
### 11.4 자동 백업 설정
```bash
# 백업 스크립트 생성
cat > /opt/guardia/scripts/backup.sh << 'EOF'
#!/bin/bash
BACKUP_DIR=/backup/guardia
DATE=$(date +%Y%m%d_%H%M)
mkdir -p $BACKUP_DIR
cp /opt/guardia/itsm/guardia_itsm.db $BACKUP_DIR/itsm_$DATE.db
cp /opt/guardia/messenger/guardia_messenger.db $BACKUP_DIR/messenger_$DATE.db
# 30일 이전 백업 삭제
find $BACKUP_DIR -name "*.db" -mtime +30 -delete
echo "[$DATE] 백업 완료" >> /var/log/guardia/backup.log
EOF
chmod +x /opt/guardia/scripts/backup.sh
# cron 등록 (매일 오전 3시)
echo "0 3 * * * guardia /opt/guardia/scripts/backup.sh" | sudo tee -a /etc/cron.d/guardia
```
---
## 12. 문제 해결
### 12.1 서비스 시작 실패
```bash
# 상세 에러 확인
sudo journalctl -u guardia-itsm -n 50 --no-pager
# 가상환경 경로 확인
ls -la /opt/guardia/.venv/bin/uvicorn
# → 파일이 없으면: source /opt/guardia/.venv/bin/activate && pip install uvicorn
# .env 파일 권한 확인
ls -la /opt/guardia/itsm/.env
# → -rw------- 1 guardia guardia 이어야 함
```
### 12.2 포트 충돌
```bash
# 포트를 사용 중인 프로세스 확인
sudo lsof -i :8000
sudo lsof -i :8001
# 해당 프로세스 종료 후 재시작
sudo kill -9 <PID>
sudo systemctl start guardia-itsm
```
### 12.3 Python 패키지 설치 오류
```bash
# 개발 헤더 설치 (컴파일이 필요한 패키지)
# Ubuntu
sudo apt install -y python3.11-dev libssl-dev libffi-dev build-essential
# CentOS/RHEL
sudo dnf install -y python3.11-devel openssl-devel libffi-devel gcc
# 재설치
source /opt/guardia/.venv/bin/activate
pip install --upgrade pip
pip install -r /opt/guardia/itsm/requirements.txt
```
### 12.4 DB 권한 오류
```bash
# DB 파일 소유자 및 권한 확인
ls -la /opt/guardia/itsm/guardia_itsm.db
# 수정
sudo chown guardia:guardia /opt/guardia/itsm/guardia_itsm.db
sudo chmod 660 /opt/guardia/itsm/guardia_itsm.db
```
### 12.5 첫 로그인 실패 (seed 데이터 미생성)
```bash
# 수동으로 DB 초기화
sudo -u guardia bash -c "
source /opt/guardia/.venv/bin/activate
cd /opt/guardia/itsm
python -c \"
import asyncio
from database import init_db, SessionLocal
from core.seed import seed_all
async def main():
await init_db()
async with SessionLocal() as db:
await seed_all(db)
print('DB 초기화 완료')
asyncio.run(main())
\"
"
```
---
*설치 중 문제가 발생하면 `/var/log/guardia/itsm.log`를 확인하거나 개발팀에 문의하세요.*

View File

@ -0,0 +1,873 @@
# GUARDiA ITSM + Messenger — 설치 가이드 (Windows Server)
**문서 버전**: 1.0
**작성일**: 2026-05-25
**지원 OS**: Windows Server 2019, Windows Server 2022
---
## 목차
1. [설치 전 요구사항 확인](#1-설치-전-요구사항-확인)
2. [사전 소프트웨어 설치](#2-사전-소프트웨어-설치)
3. [GUARDiA ITSM 설치](#3-guardia-itsm-설치)
4. [GUARDiA Messenger 설치](#4-guardia-messenger-설치)
5. [Windows 서비스 등록 (NSSM)](#5-windows-서비스-등록-nssm)
6. [방화벽 설정](#6-방화벽-설정)
7. [IIS 리버스 프록시 설정 (선택)](#7-iis-리버스-프록시-설정-선택)
8. [설치 검증](#8-설치-검증)
9. [보안 강화 설정](#9-보안-강화-설정)
10. [자동 백업 설정](#10-자동-백업-설정)
11. [문제 해결](#11-문제-해결)
---
## 1. 설치 전 요구사항 확인
### 1.1 하드웨어 최소 요구사항
| 항목 | 최소 | 권장 |
|------|------|------|
| CPU | 2코어 | 4코어 이상 |
| RAM | 4GB | 8GB 이상 |
| 디스크 | 30GB | 60GB 이상 |
| 네트워크 | 100Mbps | 1Gbps |
### 1.2 소프트웨어 요구사항
```
OS: Windows Server 2022 (권장) 또는 Windows Server 2019
Python: 3.11 이상
OpenSSL: Win64 OpenSSL v3.x (SSL 점검 기능 사용 시)
NSSM: 서비스 등록 도구 (선택, Windows Service로 등록 시 필요)
```
### 1.3 설치 전 확인 (PowerShell)
PowerShell을 **관리자 권한**으로 실행하여 확인합니다.
```powershell
# OS 버전 확인
[System.Environment]::OSVersion.Version
Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion
# PowerShell 버전 확인
$PSVersionTable.PSVersion
# → Major 5 이상
# 디스크 여유 공간 확인
Get-PSDrive C | Select-Object Used, Free
# → Free 30GB 이상 확인
# 포트 사용 여부 확인
netstat -an | findstr ":8000"
netstat -an | findstr ":8001"
# → 아무것도 출력되지 않아야 함
```
---
## 2. 사전 소프트웨어 설치
### 2.1 Python 3.11 설치
1. 웹브라우저에서 `python.org/downloads` 접속
2. **Python 3.11.x** (Windows installer 64-bit) 다운로드
3. 설치 시 반드시 체크: **"Add Python to PATH"**
```powershell
# 설치 후 확인
python --version
# → Python 3.11.x
python -m pip --version
# → pip 24.x from ...
```
> 설치 후 PowerShell을 새로 열어야 PATH가 반영됩니다.
### 2.2 Git 설치 (선택)
내부 Git 서버에서 코드를 받는 경우 필요합니다.
1. `git-scm.com/download/win` 에서 설치 파일 다운로드
2. 기본 설정으로 설치
3. 확인: `git --version`
### 2.3 OpenSSL 설치 (SSL 점검 기능 사용 시)
```
1. 웹에서 "Win64 OpenSSL" 검색 → slproweb.com 또는 공식 배포처에서 다운로드
(Win64 OpenSSL v3.x.x Light 권장)
2. 기본 경로 C:\Program Files\OpenSSL-Win64 에 설치
3. 시스템 환경변수 PATH에 추가: C:\Program Files\OpenSSL-Win64\bin
```
```powershell
# 설치 확인
openssl version
# → OpenSSL 3.x.x
```
### 2.4 NSSM 설치 (서비스 등록용)
NSSM은 Python 앱을 Windows Service로 등록하는 도구입니다.
```
1. nssm.cc/download 에서 최신 버전 다운로드
2. 압축 해제 후 nssm.exe (win64 폴더)를 C:\Windows\System32\ 에 복사
```
```powershell
# 확인
nssm version
```
---
## 3. GUARDiA ITSM 설치
### 3.1 설치 디렉토리 생성
PowerShell을 **관리자 권한**으로 실행합니다.
```powershell
# 디렉토리 생성
New-Item -ItemType Directory -Force -Path "C:\GUARDiA\itsm"
New-Item -ItemType Directory -Force -Path "C:\GUARDiA\messenger"
New-Item -ItemType Directory -Force -Path "C:\GUARDiA\logs"
New-Item -ItemType Directory -Force -Path "C:\GUARDiA\backup"
New-Item -ItemType Directory -Force -Path "C:\GUARDiA\scripts\ssl"
New-Item -ItemType Directory -Force -Path "C:\GUARDiA\itsm\uploads\sr_files"
New-Item -ItemType Directory -Force -Path "C:\GUARDiA\itsm\uploads\workspaces"
Write-Host "디렉토리 생성 완료"
```
### 3.2 소스 코드 배포
```powershell
# 방법 1: 파일 복사 (zip 파일로 배포 시)
Expand-Archive -Path "C:\temp\guardia_itsm_v1.0.zip" -DestinationPath "C:\GUARDiA\itsm" -Force
# 방법 2: Git clone (내부 Git 서버)
cd C:\GUARDiA
git clone http://내부git서버/guardia/itsm.git itsm
# 배포 확인
Get-ChildItem C:\GUARDiA\itsm\
# → main.py database.py models.py schemas.py 등 표시
```
### 3.3 가상환경 생성 및 의존성 설치
```powershell
# C:\GUARDiA\itsm 으로 이동
cd C:\GUARDiA\itsm
# 가상환경 생성
python -m venv C:\GUARDiA\.venv
# 가상환경 활성화
C:\GUARDiA\.venv\Scripts\Activate.ps1
# 활성화 오류 시 실행 정책 변경
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# pip 업그레이드
python -m pip install --upgrade pip
# 의존성 설치
pip install -r requirements.txt
# 설치 확인
pip show fastapi uvicorn sqlalchemy apscheduler
# → Name, Version 정보 출력 확인
# 가상환경 비활성화
deactivate
```
**설치 중 오류 발생 시**:
```powershell
# Visual C++ 빌드 도구가 필요한 패키지 오류 시
# Microsoft C++ Build Tools 설치 필요
# visualstudio.microsoft.com/visual-cpp-build-tools/ 에서 다운로드 후 설치
```
### 3.4 환경 변수 파일 (.env) 생성
메모장 또는 VS Code로 `C:\GUARDiA\itsm\.env` 파일을 생성합니다.
```powershell
# PowerShell로 .env 파일 생성
$envContent = @"
SECRET_KEY=여기에_64자이상의_랜덤문자열_반드시_변경하세요
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=480
DB_URL=sqlite+aiosqlite:///C:/GUARDiA/itsm/guardia_itsm.db
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
# 라이선스 마스터 키 (64자리 hex = 32바이트)
# 생성: python -c "import secrets; print(secrets.token_hex(32))"
# 분실 시 기발급 라이선스 전부 무효화 — 안전하게 별도 보관 필수
GUARDIA_LICENSE_KEY=0000000000000000000000000000000000000000000000000000000000000000
MESSENGER_BASE_URL=http://localhost:8001
MESSENGER_BOT_TOKEN=변경하세요
SSH_TIMEOUT=30
UPLOAD_ROOT=C:/GUARDiA/itsm/uploads
MAX_FILE_SIZE_MB=10
"@
$envContent | Out-File -FilePath "C:\GUARDiA\itsm\.env" -Encoding utf8
Write-Host ".env 파일 생성 완료"
```
> **중요**: `SECRET_KEY`, `ENCRYPTION_KEY`, `GUARDIA_LICENSE_KEY`를 반드시 강력한 랜덤 값으로 변경하세요!
```powershell
# GUARDIA_LICENSE_KEY 생성
python -c "import secrets; print(secrets.token_hex(32))"
# → 출력된 64자리 hex를 GUARDIA_LICENSE_KEY에 복사
```
```powershell
# 랜덤 키 생성
python -c "import secrets; print(secrets.token_hex(32))"
# → 출력된 값을 SECRET_KEY에 복사
python -c "import secrets; print(secrets.token_hex(32))"
# → 출력된 값을 ENCRYPTION_KEY에 복사
```
### 3.5 초기 실행 및 DB 초기화 테스트
```powershell
# 가상환경 활성화
C:\GUARDiA\.venv\Scripts\Activate.ps1
# 디렉토리 이동
cd C:\GUARDiA\itsm
# 임시 실행 (초기 DB 생성 확인)
$process = Start-Process -FilePath "C:\GUARDiA\.venv\Scripts\uvicorn.exe" `
-ArgumentList "main:app --host 127.0.0.1 --port 8000" `
-PassThru -NoNewWindow
Start-Sleep -Seconds 8
# 정상 동작 확인
$response = Invoke-WebRequest -Uri "http://127.0.0.1:8000/" -ErrorAction SilentlyContinue
Write-Host "HTTP 응답 코드: $($response.StatusCode)"
# → 200이면 정상
# 로그인 테스트
$body = '{"username":"admin","password":"admin1234!"}'
$result = Invoke-RestMethod -Uri "http://127.0.0.1:8000/auth/login" `
-Method Post -Body $body -ContentType "application/json"
Write-Host "토큰: $($result.access_token.Substring(0,20))..."
# → 토큰 앞부분 출력 확인
# 프로세스 종료
Stop-Process -Id $process.Id
deactivate
```
---
## 4. GUARDiA Messenger 설치
### 4.1 소스 코드 배포
```powershell
# 파일 복사
Expand-Archive -Path "C:\temp\guardia_messenger_v1.0.zip" `
-DestinationPath "C:\GUARDiA\messenger" -Force
# 또는 git clone
cd C:\GUARDiA
git clone http://내부git서버/guardia/messenger.git messenger
```
### 4.2 의존성 설치
```powershell
C:\GUARDiA\.venv\Scripts\Activate.ps1
cd C:\GUARDiA\messenger
pip install -r requirements.txt
deactivate
```
### 4.3 환경 변수 설정
```powershell
$envContent = @"
SECRET_KEY=ITSM과_동일한_키_또는_별도_키
ALGORITHM=HS256
DB_URL=sqlite+aiosqlite:///C:/GUARDiA/messenger/guardia_messenger.db
ITSM_BASE_URL=http://localhost:8000
PORT=8001
"@
$envContent | Out-File -FilePath "C:\GUARDiA\messenger\.env" -Encoding utf8
```
---
## 5. Windows 서비스 등록 (NSSM)
NSSM을 사용하여 GUARDiA를 Windows Service로 등록합니다.
### 5.1 ITSM 서비스 등록
PowerShell을 **관리자 권한**으로 실행합니다.
```powershell
# ITSM 서비스 등록
nssm install GUARDiA-ITSM C:\GUARDiA\.venv\Scripts\uvicorn.exe
# 서비스 파라미터 설정
nssm set GUARDiA-ITSM Application C:\GUARDiA\.venv\Scripts\uvicorn.exe
nssm set GUARDiA-ITSM AppParameters "main:app --host 0.0.0.0 --port 8000 --workers 2"
nssm set GUARDiA-ITSM AppDirectory "C:\GUARDiA\itsm"
# 로그 설정
nssm set GUARDiA-ITSM AppStdout "C:\GUARDiA\logs\itsm.log"
nssm set GUARDiA-ITSM AppStderr "C:\GUARDiA\logs\itsm_error.log"
nssm set GUARDiA-ITSM AppRotateFiles 1
nssm set GUARDiA-ITSM AppRotateBytes 10485760
# 서비스 설명
nssm set GUARDiA-ITSM Description "GUARDiA ITSM IT Service Management"
nssm set GUARDiA-ITSM DisplayName "GUARDiA ITSM"
# 환경 변수 설정 (경로 구분은 \t 탭 문자 사용)
nssm set GUARDiA-ITSM AppEnvironmentExtra "PYTHONDONTWRITEBYTECODE=1"
# 자동 시작 설정
nssm set GUARDiA-ITSM Start SERVICE_AUTO_START
```
### 5.2 Messenger 서비스 등록
```powershell
nssm install GUARDiA-Messenger C:\GUARDiA\.venv\Scripts\uvicorn.exe
nssm set GUARDiA-Messenger Application C:\GUARDiA\.venv\Scripts\uvicorn.exe
nssm set GUARDiA-Messenger AppParameters "main:app --host 0.0.0.0 --port 8001 --workers 1"
nssm set GUARDiA-Messenger AppDirectory "C:\GUARDiA\messenger"
nssm set GUARDiA-Messenger AppStdout "C:\GUARDiA\logs\messenger.log"
nssm set GUARDiA-Messenger AppStderr "C:\GUARDiA\logs\messenger_error.log"
nssm set GUARDiA-Messenger AppRotateFiles 1
nssm set GUARDiA-Messenger Description "GUARDiA Messenger Service"
nssm set GUARDiA-Messenger Start SERVICE_AUTO_START
```
### 5.3 서비스 시작
```powershell
# 서비스 시작
Start-Service GUARDiA-ITSM
Start-Service GUARDiA-Messenger
# 상태 확인
Get-Service GUARDiA-ITSM, GUARDiA-Messenger
# 기대 출력:
# Status Name DisplayName
# ------ ---- -----------
# Running GUARDiA-ITSM GUARDiA ITSM
# Running GUARDiA-Messenger GUARDiA Messenger
```
### 5.4 서비스 관리 명령어
```powershell
# 시작
Start-Service GUARDiA-ITSM
# 중지
Stop-Service GUARDiA-ITSM
# 재시작
Restart-Service GUARDiA-ITSM
# 상태 확인
Get-Service GUARDiA-ITSM | Select-Object Name, Status, StartType
# 로그 확인 (최근 50줄)
Get-Content "C:\GUARDiA\logs\itsm.log" -Tail 50
# 실시간 로그 확인
Get-Content "C:\GUARDiA\logs\itsm.log" -Wait -Tail 20
```
### 5.5 서비스 제거 (재설치 시)
```powershell
# 서비스 제거
nssm remove GUARDiA-ITSM confirm
nssm remove GUARDiA-Messenger confirm
```
---
## 6. 방화벽 설정
### 6.1 Windows Defender 방화벽 규칙 추가
```powershell
# ITSM 포트 허용
New-NetFirewallRule `
-DisplayName "GUARDiA ITSM" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 8000 `
-Action Allow `
-Profile Domain, Private
# Messenger 포트 허용
New-NetFirewallRule `
-DisplayName "GUARDiA Messenger" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 8001 `
-Action Allow `
-Profile Domain, Private
# 규칙 확인
Get-NetFirewallRule -DisplayName "GUARDiA*" | Select-Object DisplayName, Enabled, Action
```
### 6.2 특정 IP 대역만 허용 (보안 강화)
```powershell
# 내부 네트워크 192.168.1.0/24 에서만 접근 허용
New-NetFirewallRule `
-DisplayName "GUARDiA ITSM (내부망)" `
-Direction Inbound `
-Protocol TCP `
-LocalPort 8000 `
-RemoteAddress "192.168.1.0/24" `
-Action Allow
# 기존 전체 허용 규칙 비활성화
Disable-NetFirewallRule -DisplayName "GUARDiA ITSM"
```
---
## 7. IIS 리버스 프록시 설정 (선택)
HTTPS를 적용하거나 80/443 포트를 사용하려면 IIS를 앞단에 배치합니다.
### 7.1 IIS 및 ARR 설치
```powershell
# IIS 설치
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole -All
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
# ARR (Application Request Routing) 설치
# Web Platform Installer에서 "Application Request Routing 3.0" 설치
# 또는: https://www.iis.net/downloads/microsoft/application-request-routing
```
### 7.2 IIS 역방향 프록시 설정
IIS 관리자를 열어 다음을 설정합니다:
```
1. IIS 관리자 → 서버 노드 → Application Request Routing Cache
→ "Enable proxy" 체크
2. 웹사이트 → "URL 재작성" → 규칙 추가
→ 역방향 프록시 → 서버: localhost:8000
3. 바인딩 추가:
- 포트 80 (HTTP)
- 포트 443 (HTTPS, SSL 인증서 선택)
```
**web.config 예시**:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="GUARDiA ITSM" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="http://localhost:8000/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
```
---
## 8. 설치 검증
### 8.1 서비스 상태 확인
```powershell
# 서비스 상태
Get-Service GUARDiA-ITSM, GUARDiA-Messenger
# 포트 리스닝 확인
netstat -an | findstr ":8000"
netstat -an | findstr ":8001"
# → TCP 0.0.0.0:8000 ... LISTENING
```
### 8.2 API 기능 테스트
```powershell
# 1. 홈페이지 응답 확인
$response = Invoke-WebRequest -Uri "http://localhost:8000/"
Write-Host "HTTP 상태: $($response.StatusCode)"
# → 200
# 2. 로그인 테스트
$loginBody = '{"username":"admin","password":"admin1234!"}'
$loginResult = Invoke-RestMethod -Uri "http://localhost:8000/auth/login" `
-Method Post -Body $loginBody -ContentType "application/json"
$token = $loginResult.access_token
Write-Host "로그인 성공: 토큰 앞 20자 = $($token.Substring(0,20))..."
# 3. 대시보드 API 호출
$headers = @{ Authorization = "Bearer $token" }
$dashboard = Invoke-RestMethod -Uri "http://localhost:8000/dashboard/summary" `
-Headers $headers
Write-Host "대시보드 응답:"
$dashboard | ConvertTo-Json
# 4. Messenger 확인
$msgResponse = Invoke-WebRequest -Uri "http://localhost:8001/"
Write-Host "Messenger HTTP 상태: $($msgResponse.StatusCode)"
```
### 8.3 설치 검증 스크립트
```powershell
# C:\GUARDiA\verify_install.ps1 생성
$script = @'
Write-Host "=== GUARDiA 설치 검증 ===" -ForegroundColor Cyan
function Check-Item($condition, $label) {
if ($condition) {
Write-Host " [ok] $label" -ForegroundColor Green
} else {
Write-Host " [FAIL] $label" -ForegroundColor Red
}
}
# 서비스 상태
$itsmSvc = Get-Service GUARDiA-ITSM -ErrorAction SilentlyContinue
Check-Item ($itsmSvc -and $itsmSvc.Status -eq "Running") "ITSM 서비스 실행 중"
$msgSvc = Get-Service GUARDiA-Messenger -ErrorAction SilentlyContinue
Check-Item ($msgSvc -and $msgSvc.Status -eq "Running") "Messenger 서비스 실행 중"
# 포트 확인
$itsm8000 = netstat -an | findstr ":8000" | findstr "LISTENING"
Check-Item ($itsm8000 -ne $null) "ITSM 포트 8000 리스닝"
$msg8001 = netstat -an | findstr ":8001" | findstr "LISTENING"
Check-Item ($msg8001 -ne $null) "Messenger 포트 8001 리스닝"
# API 응답
try {
$r = Invoke-WebRequest "http://localhost:8000/" -TimeoutSec 5
Check-Item ($r.StatusCode -eq 200) "ITSM API 응답 (200)"
} catch {
Check-Item $false "ITSM API 응답 (오류: $_)"
}
# DB 파일
Check-Item (Test-Path "C:\GUARDiA\itsm\guardia_itsm.db") "ITSM DB 파일 존재"
# 로그 파일
Check-Item (Test-Path "C:\GUARDiA\logs\itsm.log") "ITSM 로그 파일 존재"
Write-Host "=== 검증 완료 ===" -ForegroundColor Cyan
'@
$script | Out-File -FilePath "C:\GUARDiA\verify_install.ps1" -Encoding utf8
# 검증 실행
PowerShell -ExecutionPolicy Bypass -File "C:\GUARDiA\verify_install.ps1"
```
---
## 9. 보안 강화 설정
### 9.1 초기 관리자 비밀번호 변경
```powershell
# 로그인
$loginBody = '{"username":"admin","password":"admin1234!"}'
$loginResult = Invoke-RestMethod -Uri "http://localhost:8000/auth/login" `
-Method Post -Body $loginBody -ContentType "application/json"
$token = $loginResult.access_token
# 비밀번호 변경
$pwBody = @{
current_password = "admin1234!"
new_password = "새로운강력한비밀번호!@#456"
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://localhost:8000/auth/change-password" `
-Method Post -Body $pwBody -ContentType "application/json" `
-Headers @{ Authorization = "Bearer $token" }
Write-Host "비밀번호 변경 완료"
```
### 9.2 .env 파일 접근 권한 제한
```powershell
# .env 파일 소유자 확인
Get-Acl "C:\GUARDiA\itsm\.env"
# 서비스 계정(또는 시스템)만 읽기 가능하도록 권한 설정
$acl = Get-Acl "C:\GUARDiA\itsm\.env"
$acl.SetAccessRuleProtection($true, $false) # 상속 차단
# 현재 사용자 전체 제어
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$env:USERNAME, "FullControl", "Allow"
)
$acl.AddAccessRule($rule)
Set-Acl "C:\GUARDiA\itsm\.env" $acl
Write-Host "파일 권한 설정 완료"
```
### 9.3 이벤트 로그 설정
```powershell
# 이벤트 소스 등록 (최초 1회)
New-EventLog -LogName Application -Source "GUARDiA-ITSM" -ErrorAction SilentlyContinue
# 이벤트 로그 크기 설정
Limit-EventLog -LogName Application -MaximumSize 50MB
```
### 9.4 보안 정책 확인
```powershell
# TLS 1.2 이상만 허용 (Windows Server 2019+는 기본 설정)
# 레지스트리 확인
Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server" -ErrorAction SilentlyContinue
# DisabledByDefault = 1 이면 비활성화
```
---
## 10. 자동 백업 설정
### 10.1 백업 스크립트 생성
```powershell
# C:\GUARDiA\scripts\backup.ps1 생성
$backupScript = @'
$date = Get-Date -Format "yyyyMMdd_HHmm"
$backupDir = "C:\GUARDiA\backup"
# 백업 디렉토리 생성
if (-not (Test-Path $backupDir)) {
New-Item -ItemType Directory -Path $backupDir -Force
}
# ITSM DB 백업
$itsmDb = "C:\GUARDiA\itsm\guardia_itsm.db"
if (Test-Path $itsmDb) {
Copy-Item $itsmDb "$backupDir\itsm_$date.db"
Write-Host "$date - ITSM DB 백업 완료"
}
# Messenger DB 백업
$msgDb = "C:\GUARDiA\messenger\guardia_messenger.db"
if (Test-Path $msgDb) {
Copy-Item $msgDb "$backupDir\messenger_$date.db"
Write-Host "$date - Messenger DB 백업 완료"
}
# 업로드 파일 백업 (선택)
# Compress-Archive -Path "C:\GUARDiA\itsm\uploads" -DestinationPath "$backupDir\uploads_$date.zip"
# 30일 이전 백업 삭제
Get-ChildItem $backupDir -Filter "*.db" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Remove-Item -Force
Write-Host "$date - 백업 작업 완료"
"@
New-Item -ItemType Directory -Force -Path "C:\GUARDiA\scripts"
$backupScript | Out-File -FilePath "C:\GUARDiA\scripts\backup.ps1" -Encoding utf8
```
### 10.2 작업 스케줄러 등록
```powershell
# 매일 새벽 3시 자동 실행
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NonInteractive -ExecutionPolicy Bypass -File C:\GUARDiA\scripts\backup.ps1 >> C:\GUARDiA\logs\backup.log 2>&1"
$trigger = New-ScheduledTaskTrigger -Daily -At "03:00"
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
-RunOnlyIfNetworkAvailable $false
Register-ScheduledTask `
-TaskName "GUARDiA-Backup" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-RunLevel Highest `
-Force
Write-Host "백업 작업 스케줄 등록 완료"
# 즉시 실행으로 테스트
Start-ScheduledTask -TaskName "GUARDiA-Backup"
Start-Sleep -Seconds 5
Get-ChildItem "C:\GUARDiA\backup\" | Sort-Object LastWriteTime -Descending | Select-Object -First 3
```
---
## 11. 문제 해결
### 11.1 서비스 시작 실패
```powershell
# 1. 서비스 상태 확인
Get-Service GUARDiA-ITSM | Format-List *
# 2. 이벤트 로그 확인
Get-EventLog -LogName Application -Source "GUARDiA-ITSM" -Newest 10 |
Select-Object TimeGenerated, EntryType, Message
# 3. NSSM 로그 확인
Get-Content "C:\GUARDiA\logs\itsm_error.log" -Tail 30
# 4. 수동 실행으로 오류 확인
cd C:\GUARDiA\itsm
C:\GUARDiA\.venv\Scripts\uvicorn.exe main:app --host 127.0.0.1 --port 8000
# → 직접 오류 메시지 확인
```
### 11.2 Python 패키지 누락 오류
```
오류: ModuleNotFoundError: No module named 'xxx'
해결:
C:\GUARDiA\.venv\Scripts\Activate.ps1
pip install xxx
deactivate
Restart-Service GUARDiA-ITSM
```
### 11.3 포트 충돌
```powershell
# 8000 포트 사용 프로세스 확인
netstat -ano | findstr ":8000"
# → 마지막 컬럼이 PID
# 프로세스 확인
Get-Process -Id <PID>
# 프로세스 종료 (필요 시)
Stop-Process -Id <PID> -Force
```
### 11.4 PowerShell 실행 정책 오류
```
오류: running scripts is disabled on this system
해결:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine
# 또는 현재 사용자만:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```
### 11.5 DB 파일 잠금 오류
```
오류: database is locked
해결:
1. 서비스 중지
Stop-Service GUARDiA-ITSM
2. 잠금 파일 삭제
Remove-Item "C:\GUARDiA\itsm\guardia_itsm.db-shm" -ErrorAction SilentlyContinue
Remove-Item "C:\GUARDiA\itsm\guardia_itsm.db-wal" -ErrorAction SilentlyContinue
3. 서비스 재시작
Start-Service GUARDiA-ITSM
```
### 11.6 인코딩 오류 (한글 깨짐)
```powershell
# .env 파일을 UTF-8 without BOM으로 저장
[System.IO.File]::WriteAllText(
"C:\GUARDiA\itsm\.env",
(Get-Content "C:\GUARDiA\itsm\.env" -Raw),
[System.Text.UTF8Encoding]::new($false) # $false = no BOM
)
```
---
## 부록 A: Windows 서비스 빠른 참조
```powershell
# 서비스 시작/중지/재시작
Start-Service GUARDiA-ITSM
Stop-Service GUARDiA-ITSM
Restart-Service GUARDiA-ITSM
Start-Service GUARDiA-Messenger
Stop-Service GUARDiA-Messenger
Restart-Service GUARDiA-Messenger
# 상태 확인
Get-Service GUARDiA-ITSM, GUARDiA-Messenger
# 로그 확인 (최근 50줄)
Get-Content "C:\GUARDiA\logs\itsm.log" -Tail 50
Get-Content "C:\GUARDiA\logs\itsm_error.log" -Tail 20
# 실시간 로그 모니터링
Get-Content "C:\GUARDiA\logs\itsm.log" -Wait -Tail 10
# 수동 백업
PowerShell -File "C:\GUARDiA\scripts\backup.ps1"
```
## 부록 B: 환경 변수 참조
| 변수명 | 설명 | 예시 값 |
|--------|------|---------|
| SECRET_KEY | JWT 서명 키 (필수 변경) | 랜덤 64자 이상 문자열 |
| ENCRYPTION_KEY | AES-256 암호화 키 (필수 변경) | 랜덤 64자 hex 문자열 |
| ACCESS_TOKEN_EXPIRE_MINUTES | 토큰 만료 시간(분) | 480 (8시간) |
| DB_URL | SQLite DB 경로 | sqlite+aiosqlite:///C:/... |
| MESSENGER_BASE_URL | Messenger 서버 URL | http://localhost:8001 |
| SSH_TIMEOUT | SSH 실행 타임아웃(초) | 30 |
| UPLOAD_ROOT | 파일 업로드 경로 | C:/GUARDiA/itsm/uploads |
| MAX_FILE_SIZE_MB | 최대 업로드 파일 크기(MB) | 10 |
---
*설치 중 문제가 발생하면 `C:\GUARDiA\logs\itsm_error.log`를 확인하거나 개발팀에 문의하세요.*

View File

@ -0,0 +1,432 @@
# GUARDiA ITSM — SI 프로젝트 관리 모듈 분석·설계서
**문서 버전**: 1.0
**작성일**: 2026-05-25
**대상 독자**: 개발자, PM, 이해관계자
---
## 목차
1. [개요 및 목적](#1-개요-및-목적)
2. [기능 범위](#2-기능-범위)
3. [도메인 모델 설계](#3-도메인-모델-설계)
4. [API 엔드포인트 설계](#4-api-엔드포인트-설계)
5. [프로세스 흐름](#5-프로세스-흐름)
6. [추가 고려사항 및 확장 방향](#6-추가-고려사항-및-확장-방향)
7. [SM ↔ SI 통합 연계](#7-sm--si-통합-연계)
---
## 1. 개요 및 목적
### 1.1 현재 vs 확장 범위
```
현재 (SM 모드) 확장 (SI 모드)
──────────────── ────────────────────────────
SR 접수·처리 RFP 요구사항 등록·관리
SSL 인증서 점검 WBS 작성·진척 관리
정기 PM 점검 프로젝트 이슈·리스크 관리
장애 관리 (Incident) 마일스톤·산출물 관리
온콜·당직 관리 변경 요청(CR) 관리
배치 작업 관리 테스트 관리 (계획→실행→결함)
요구사항 추적성 매트릭스(RTM)
Gantt 차트 데이터 제공
안정화 체크리스트
```
### 1.2 SI 프로젝트 생명주기
```
착수 → 분석 → 설계 → 개발 → 테스트 → 구축 → 안정화 → 종료
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
RFP 요구사 설계 WBS 테스트 시스템 헬스체크 최종
등록 항분류 산출 진척 실행 이관 SR자동 보고서
물 관리 결함 체크 생성
```
---
## 2. 기능 범위
### 2.1 핵심 기능 8개
| # | 기능 | 설명 |
|---|------|------|
| F1 | **SI 프로젝트 관리** | 프로젝트 CRUD, 단계(Phase) 전환, 진행률 집계 |
| F2 | **RFP 요구사항 관리** | 요구사항 등록·분류·추적, RTM 생성, Excel 일괄 업로드 |
| F3 | **WBS 관리** | 계층형 WBS 작성, Gantt 데이터, 진척률·지연 감지 |
| F4 | **이슈 관리** | 기술/일정/자원/품질 이슈, 이슈→SR 연동 |
| F5 | **리스크 관리** | 확률×영향 점수, 대응 계획, 리스크→이슈 전환 |
| F6 | **마일스톤·산출물 관리** | 마일스톤 달성 여부, 산출물 제출·승인 워크플로우 |
| F7 | **변경 요청(CR) 관리** | 범위·일정·예산 변경, 결재 연동 |
| F8 | **테스트 관리** | 테스트 계획, 케이스, 실행, 결함, 결과 보고서 |
### 2.2 고려사항 (구현 시 주의)
```
① 요구사항 추적성 (Traceability)
RFP 요구사항 → WBS 항목 → 테스트 케이스 → 결함 → 검증
req_id FK 체인으로 연결, RTM Excel 자동 생성
② WBS 계층 구조
재귀 Self-Join (parent_id → id)
최대 4레벨: 1 프로젝트 > 1.1 단계 > 1.1.1 업무 > 1.1.1.1 작업
진척률 = 자식 노드 평균 (Leaf 노드만 수동 입력)
③ Gantt 데이터 포맷
WBS 항목의 planned_start/end, actual_start/end를 JSON 배열로 반환
프론트엔드에서 D3.js 또는 다른 Gantt 라이브러리로 렌더링
④ 지연 감지 자동화
스케줄러: 매일 07:00 — planned_end < today AND completion_pct < 100 이슈 자동 생성 + 알림
⑤ SM 통합
SI 안정화 단계 종료 후 → SM 모드로 자동 전환
SM 서버(CMDB), SR, PM 스케줄에 연동
⑥ 대용량 파일
산출물 파일 업로드는 기존 attachments 모듈 재사용 (uploads/si_deliverables/)
⑦ 보안
프로젝트별 접근 권한: ADMIN > PM(해당 프로젝트) > ENGINEER(참여자) > 조회 불가
계약금액 등 민감 필드는 ADMIN/PM만 조회
```
---
## 3. 도메인 모델 설계
### 3.1 ER 다이어그램 (핵심 테이블)
```
tb_si_project (SI 프로젝트)
├── tb_si_requirement (요구사항) ─── req_id FK ──► tb_wbs_item
├── tb_wbs_item (WBS 항목) ←── parent_id (self-join)
│ └── tb_wbs_item ←── predecessor FK
├── tb_project_issue (이슈)
│ └── tb_sr_request (SR 연동, nullable FK)
├── tb_project_risk (리스크)
│ └── tb_project_issue (실현 시 이슈 생성)
├── tb_project_milestone (마일스톤)
│ └── tb_project_deliverable (산출물)
├── tb_change_request (변경 요청)
│ └── tb_approval_flow (결재 연동)
└── tb_si_test_plan (테스트 계획)
├── tb_si_test_case (테스트 케이스)
│ └── tb_si_requirement (요구사항 FK)
└── tb_si_test_execution (실행 결과)
└── tb_si_defect (결함)
```
### 3.2 Enum 정의
```python
class ProjectPhase(str, Enum):
INITIATION = "INITIATION" # 착수
ANALYSIS = "ANALYSIS" # 분석
DESIGN = "DESIGN" # 설계
DEVELOPMENT = "DEVELOPMENT" # 개발
TESTING = "TESTING" # 테스트
DEPLOYMENT = "DEPLOYMENT" # 구축/이관
STABILIZATION = "STABILIZATION" # 안정화
CLOSED = "CLOSED" # 종료
class ReqType(str, Enum):
FUNCTIONAL = "FUNCTIONAL" # 기능 요구사항
NON_FUNCTIONAL = "NON_FUNCTIONAL" # 비기능 요구사항
CONSTRAINT = "CONSTRAINT" # 제약 사항
INTERFACE = "INTERFACE" # 인터페이스 요구사항
class ReqStatus(str, Enum):
DRAFT = "DRAFT" # 초안
REVIEWED = "REVIEWED" # 검토 완료
APPROVED = "APPROVED" # 승인
IMPLEMENTED = "IMPLEMENTED" # 구현 완료
VERIFIED = "VERIFIED" # 검증 완료
DEFERRED = "DEFERRED" # 보류
DELETED = "DELETED" # 삭제
class WbsStatus(str, Enum):
NOT_STARTED = "NOT_STARTED"
IN_PROGRESS = "IN_PROGRESS"
COMPLETED = "COMPLETED"
DELAYED = "DELAYED"
ON_HOLD = "ON_HOLD"
class RiskLevel(str, Enum):
HIGH = "HIGH" # 3
MEDIUM = "MEDIUM" # 2
LOW = "LOW" # 1
class IssueType(str, Enum):
TECHNICAL = "TECHNICAL" # 기술
SCHEDULE = "SCHEDULE" # 일정
RESOURCE = "RESOURCE" # 자원
QUALITY = "QUALITY" # 품질
SCOPE = "SCOPE" # 범위
EXTERNAL = "EXTERNAL" # 외부
class CrType(str, Enum):
SCOPE = "SCOPE" # 범위 변경
SCHEDULE = "SCHEDULE" # 일정 변경
BUDGET = "BUDGET" # 예산 변경
QUALITY = "QUALITY" # 품질 기준 변경
class TestResult(str, Enum):
PASS = "PASS"
FAIL = "FAIL"
BLOCKED = "BLOCKED"
SKIP = "SKIP"
class DefectSeverity(str, Enum):
CRITICAL = "CRITICAL"
MAJOR = "MAJOR"
MINOR = "MINOR"
TRIVIAL = "TRIVIAL"
```
---
## 4. API 엔드포인트 설계
### 4.1 SI 프로젝트 (`/api/si/projects`)
| Method | Path | 설명 |
|--------|------|------|
| GET | `/` | 프로젝트 목록 |
| POST | `/` | 프로젝트 생성 |
| GET | `/{id}` | 프로젝트 상세 (진행률 포함) |
| PATCH | `/{id}` | 프로젝트 수정 |
| PATCH | `/{id}/phase` | 단계 전환 |
| GET | `/{id}/dashboard` | 프로젝트 대시보드 (이슈수, 리스크수, WBS 진척률) |
| GET | `/{id}/gantt` | Gantt 차트 데이터 (WBS 전체) |
| POST | `/{id}/members` | 프로젝트 멤버 추가 |
### 4.2 요구사항 (`/api/si/projects/{pid}/requirements`)
| Method | Path | 설명 |
|--------|------|------|
| GET | `/` | 요구사항 목록 (타입/상태 필터) |
| POST | `/` | 요구사항 단건 등록 |
| POST | `/bulk` | Excel 일괄 업로드 (openpyxl 파싱) |
| GET | `/{id}` | 요구사항 상세 |
| PATCH | `/{id}` | 요구사항 수정 |
| PATCH | `/{id}/status` | 상태 변경 |
| GET | `/rtm` | 추적성 매트릭스(RTM) JSON |
| GET | `/rtm/excel` | RTM Excel 다운로드 |
### 4.3 WBS (`/api/si/projects/{pid}/wbs`)
| Method | Path | 설명 |
|--------|------|------|
| GET | `/` | WBS 트리 구조 조회 |
| POST | `/` | WBS 항목 추가 |
| POST | `/bulk` | Excel WBS 일괄 업로드 |
| PATCH | `/{id}` | WBS 항목 수정 |
| PATCH | `/{id}/progress` | 진척률 업데이트 |
| DELETE | `/{id}` | WBS 항목 삭제 (자식 있으면 거부) |
| GET | `/gantt` | Gantt 렌더링용 JSON |
| GET | `/delayed` | 지연 항목 목록 |
### 4.4 이슈 (`/api/si/projects/{pid}/issues`)
| Method | Path | 설명 |
|--------|------|------|
| GET | `/` | 이슈 목록 (타입/상태 필터) |
| POST | `/` | 이슈 등록 |
| GET | `/{id}` | 이슈 상세 |
| PATCH | `/{id}` | 이슈 수정 |
| PATCH | `/{id}/resolve` | 이슈 해결 처리 |
| POST | `/{id}/convert-sr` | ITSM SR로 변환 등록 |
### 4.5 리스크 (`/api/si/projects/{pid}/risks`)
| Method | Path | 설명 |
|--------|------|------|
| GET | `/` | 리스크 목록 (점수 정렬) |
| POST | `/` | 리스크 등록 |
| PATCH | `/{id}` | 리스크 수정 |
| PATCH | `/{id}/occur` | 리스크 실현 → 이슈 자동 생성 |
| GET | `/matrix` | 리스크 매트릭스 데이터 (3×3 격자) |
### 4.6 마일스톤·산출물
| Method | Path | 설명 |
|--------|------|------|
| GET | `/api/si/projects/{pid}/milestones` | 마일스톤 목록 |
| POST | `/api/si/projects/{pid}/milestones` | 마일스톤 등록 |
| PATCH | `/api/si/projects/{pid}/milestones/{id}/achieve` | 달성 처리 |
| GET | `/api/si/projects/{pid}/deliverables` | 산출물 목록 |
| POST | `/api/si/projects/{pid}/deliverables` | 산출물 등록 |
| POST | `/api/si/projects/{pid}/deliverables/{id}/submit` | 제출 처리 |
| PATCH | `/api/si/projects/{pid}/deliverables/{id}/approve` | 승인 처리 |
### 4.7 변경 요청 (`/api/si/projects/{pid}/change-requests`)
| Method | Path | 설명 |
|--------|------|------|
| GET | `/` | CR 목록 |
| POST | `/` | CR 등록 |
| PATCH | `/{id}/approve` | CR 승인 |
| PATCH | `/{id}/reject` | CR 거부 |
| PATCH | `/{id}/implement` | CR 구현 완료 |
### 4.8 테스트 관리 (`/api/si/projects/{pid}/tests`)
| Method | Path | 설명 |
|--------|------|------|
| POST | `/plans` | 테스트 계획 생성 |
| POST | `/plans/{plan_id}/cases` | 테스트 케이스 등록 |
| POST | `/plans/{plan_id}/cases/bulk` | 케이스 일괄 업로드 |
| POST | `/plans/{plan_id}/execute` | 테스트 실행 결과 저장 |
| POST | `/defects` | 결함 등록 |
| PATCH | `/defects/{id}/fix` | 결함 수정 완료 |
| GET | `/plans/{plan_id}/report` | 테스트 결과 보고서 |
---
## 5. 프로세스 흐름
### 5.1 RFP 입력 → WBS 자동 생성 흐름
```
1. RFP 문서 업로드 (PDF/Word/Excel)
2. 요구사항 수동 입력 또는 Excel 일괄 업로드
→ req_id 자동 채번: REQ-F-001 (기능), REQ-NF-001 (비기능)
3. 요구사항 검토 → APPROVED 상태 전환
4. WBS 자동 생성 트리거:
APPROVED 요구사항 카테고리 기준으로 WBS 골격 생성
(분석→설계→개발→테스트 단계별 노드 자동 생성)
5. PM이 WBS 상세 조정 (기간, 담당자 배정)
6. Gantt 차트 확인 → 일정 확정
```
### 5.2 WBS 진척 관리 흐름
```
매일 07:00 스케줄러 실행:
└── planned_end < today AND completion_pct < 100
└── status = DELAYED 자동 변경
└── ProjectIssue 자동 생성 (issue_type=SCHEDULE)
└── Messenger 알림 → PM/담당자
엔지니어: completion_pct 업데이트
└── PATCH /wbs/{id}/progress {"completion_pct": 75}
└── 부모 노드 진척률 자동 재계산 (평균)
└── 프로젝트 전체 진척률 갱신
```
### 5.3 리스크 → 이슈 전환 흐름
```
리스크 생성 (확률×영향 점수 HIGH×HIGH = 9)
└── 점수 6 이상: 관리 필요 알림
└── 점수 9: 즉시 PM 알림
리스크 실현 (PATCH /risks/{id}/occur):
└── risk.status = OCCURRED
└── ProjectIssue 자동 생성
- title: f"[리스크 실현] {risk.title}"
- issue_type: 리스크 타입 매핑
- priority: HIGH
└── Messenger P2 알림
```
### 5.4 테스트 → SM 전환 흐름
```
테스트 완료 조건:
- 전체 TC Pass율 ≥ 95%
- Critical/Major 결함 0건
- 마일스톤 "테스트 완료" 달성
구축(DEPLOYMENT) 단계:
- 서버 정보 → CMDB 자동 등록 (Server 테이블)
- SSL 도메인 → SslDomain 자동 등록
- PM 스케줄 → PmSchedule 자동 생성
안정화(STABILIZATION) 단계:
- 안정화 SR 자동 생성 (sr_type=INCIDENT, priority=HIGH)
- 온콜 당직 스케줄 생성
종료(CLOSED):
- 프로젝트 최종 보고서 Excel 생성
- SM 모드로 전환 완료 플래그
```
---
## 6. 추가 고려사항 및 확장 방향
### 6.1 즉시 구현 필요 (Must)
```
① RTM(Requirements Traceability Matrix) Excel 자동 생성
요구사항 ID → WBS 코드 → 테스트 케이스 ID → 결함 수 → 검증 상태
② WBS Excel 업로드/다운로드
표준 WBS 양식(xlsx) 업로드 → 자동 파싱 → DB 저장
③ 지연 자동 감지 스케줄러
scheduler.py에 _scan_wbs_delay() 추가
④ 안정화→SM 전환 트리거
DEPLOYMENT 완료 시 CMDB/SSL/PM 자동 생성
```
### 6.2 중기 확장 (Should)
```
① Gantt 차트 프론트엔드
WBS Gantt API → static/si_gantt.html 구현
② 공수 관리 (Man-day)
WbsItem에 planned_md, actual_md 추가
③ 예산 관리
SiProject에 budget_total, budget_used 연동
④ 외부 협력사 포털
CUSTOMER Role 확장: 협력사 계정에 특정 WBS만 접근
⑤ sLLM 연동
RFP 텍스트 → 요구사항 자동 추출 (내부 sLLM API)
```
### 6.3 장기 확장 (Nice)
```
① 프로젝트 템플릿
표준 SI 프로젝트 WBS 템플릿 (전자정부, ERP 등)
② 시뮬레이션
일정 변경 시 종료일 영향 시뮬레이션 (Critical Path)
③ 유사 프로젝트 비교
과거 SI 프로젝트 실적 기반 일정/공수 추정 보조
```
---
## 7. SM ↔ SI 통합 연계
```
SI 프로젝트 종료 후 SM 자산 자동 등록:
SiProject.phase = CLOSED
└── 각 서버 → Server(CMDB) 자동 등록
└── 각 도메인 → SslDomain 자동 등록
└── PM 스케줄 → PmSchedule 자동 생성 (MONTHLY)
└── 배치 작업 → BatchJob 이관 (있는 경우)
└── SM SRType.INQUIRY → 안정화 SR 자동 생성
연계 API: POST /api/si/projects/{id}/convert-to-sm
→ 위 자동 등록 트리거
→ 결과 요약 반환 (등록된 서버 수, SSL 수, PM 수)
```
---
*본 설계서는 구현 진행에 따라 갱신됩니다.*

View File

@ -0,0 +1,669 @@
# GUARDiA ITSM — AI 에이전트 (Paperclip × GUARDiA) 설계서
**문서 버전**: 1.0
**작성일**: 2026-05-25
**대상 독자**: 개발자, DevOps 엔지니어, 운영 관리자
---
## 목차
1. [개요 및 목적](#1-개요-및-목적)
2. [아키텍처 설계](#2-아키텍처-설계)
3. [Phase 1 — Paperclip 개발 도구 설정](#3-phase-1--paperclip-개발-도구-설정)
4. [Phase 2 — Ollama 로컬 LLM 설정](#4-phase-2--ollama-로컬-llm-설정)
5. [Phase 3 — GUARDiA 에이전트 엔진](#5-phase-3--guardia-에이전트-엔진)
6. [Phase 4 — 자율 운영 대시보드](#6-phase-4--자율-운영-대시보드)
7. [API 엔드포인트 설계](#7-api-엔드포인트-설계)
8. [보안 제약사항](#8-보안-제약사항)
9. [스케줄러 잡 설계](#9-스케줄러-잡-설계)
10. [테스트 결과](#10-테스트-결과)
11. [운영 가이드](#11-운영-가이드)
12. [향후 로드맵](#12-향후-로드맵)
---
## 1. 개요 및 목적
### 1.1 배경
GUARDiA ITSM은 온프레미스 IT 서비스 관리 플랫폼으로, 서비스 요청(SR), 장애 관리, SSL 인증서 모니터링, PM 일정 관리, SI 프로젝트 관리를 제공합니다.
AI 에이전트 기능 추가를 통해 다음 목표를 달성합니다:
- **반복 업무 자동화**: 장애 분류, KB 등록, SSL 갱신 SR 생성 등 반복적인 운영 업무를 AI가 자동 처리
- **능동적 모니터링**: 에이전트가 주기적으로 시스템 상태를 확인하고 이상 징후를 감지
- **사람-AI 협업**: 고위험 작업은 사람의 승인을 거쳐 실행하는 거버넌스 체계
### 1.2 Paperclip 프레임워크 채택
[Paperclip](https://github.com/paperclipai/paperclip)은 AI 에이전트 오케스트레이션 오픈소스 프레임워크입니다.
| 특징 | 설명 |
|------|------|
| 조직도 구조 | CEO → CTO → 개발자/QA 계층적 에이전트 관리 |
| 하트비트 시스템 | 에이전트가 주기적으로 깨어나 작업 수행 후 대기 |
| 이슈 추적 | GitHub 스타일의 태스크/이슈 관리 |
| 거버넌스 | 위험 수준에 따른 사람 승인 게이트 |
### 1.3 보안 원칙
> **외부 LLM/AI API 완전 금지** — 모든 AI 추론은 Ollama (localhost:11434) 를 통해 온프레미스에서 처리
---
## 2. 아키텍처 설계
### 2.1 전체 구조
```
┌──────────────────────────────────────────────────────────────────┐
│ GUARDiA ITSM │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ FastAPI Layer │ │
│ │ /api/agents/* ←──────────────────── agents.html (SPA) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────▼─────────────────────────────────┐ │
│ │ AgentEngine (core) │ │
│ │ │ │
│ │ INCIDENT_TRIAGE KB_CURATOR SSL_WATCHER │ │
│ │ WBS_MONITOR PM_SUGGESTER DEVELOPER │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────┐ ┌─────▼───────────────────────────────┐ │
│ │ APScheduler │──▶│ OllamaClient (LLM 추론) │ │
│ │ (9 cron jobs) │ │ localhost:11434 │ │
│ └────────────────┘ └─────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ SQLite DB: tb_agent_config | tb_agent_task | tb_agent_approval│
│ └─────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
외부(개발 시에만):
Paperclip CLI ──── paperclip.config.json ──── agents/*.md
```
### 2.2 에이전트 조직도
```
┌─────────────┐
│ CEO │ 전략·결재
└──────┬──────┘
┌────────┴────────┐
┌──────▼──────┐ ┌────▼──────┐
│ CTO │ │ PM_AGENT │ 프로젝트 관리
└──────┬──────┘ └───────────┘
┌──────┴────────┐
┌──▼──────┐ ┌────▼───┐
│DEVELOPER│ │ QA │
└─────────┘ └────────┘
운영 자동화 에이전트 (Ops):
INCIDENT_TRIAGE ← 장애 자동 분류
KB_CURATOR ← 지식베이스 자동 등록
SSL_WATCHER ← SSL 만료 감시
WBS_MONITOR ← WBS 지연 감지
PM_SUGGESTER ← PM 일정 제안
```
---
## 3. Phase 1 — Paperclip 개발 도구 설정
### 3.1 파일 구조
```
C:\GUARDiA\
└── paperclip\
├── paperclip.config.json # 조직도 + 거버넌스 설정
├── README.md # 설치/사용 가이드
└── agents\
├── ceo.md # CEO 에이전트 페르소나
├── cto.md # CTO 에이전트 페르소나
├── developer.md # 개발자 에이전트 페르소나
└── qa.md # QA 에이전트 페르소나
```
### 3.2 설치
```bash
npm install -g @paperclipai/paperclip
# Paperclip 초기화 (프로젝트 루트에서)
cd C:\GUARDiA
paperclip init
# 에이전트 시작
paperclip start
```
### 3.3 조직도 구성 (paperclip.config.json 요약)
```json
{
"org_chart": {
"ceo": { "reports_to": null, "can_approve": ["deploy", "code_commit"] },
"cto": { "reports_to": "ceo" },
"developer": { "reports_to": "cto" },
"qa": { "reports_to": "cto" },
"pm_agent": { "reports_to": "ceo" }
},
"llm": {
"provider": "ollama",
"base_url": "http://localhost:11434",
"models": {
"ceo": "guardia-agent",
"developer": "codellama:7b",
"qa": "codellama:7b"
}
},
"governance": {
"require_human_approval": ["code_commit", "deploy", "delete_data"]
}
}
```
### 3.4 거버넌스 규칙
| 액션 | 승인 방식 | 승인자 |
|------|-----------|--------|
| code_commit | 사람 승인 필수 | CTO 또는 CEO |
| deploy | 사람 승인 필수 | CEO |
| delete_data | 사람 승인 필수 | CEO |
| 장애 분류 (일반) | 자동 승인 | — |
| 장애 분류 (CRITICAL) | 사람 승인 필수 | 담당자 |
| KB 등록 | 자동 승인 | — |
| SSL 갱신 SR 생성 | 자동 승인 | — |
---
## 4. Phase 2 — Ollama 로컬 LLM 설정
### 4.1 파일 구조
```
C:\GUARDiA\
└── ollama\
├── setup.ps1 # 자동 설치 스크립트
└── Modelfile.guardia # 커스텀 모델 정의
```
### 4.2 자동 설치 (setup.ps1)
```powershell
# 실행 방법:
Set-ExecutionPolicy Bypass -Scope Process -Force
.\setup.ps1
# 스크립트 동작:
# 1. OllamaSetup.exe 다운로드
# 2. 설치 후 ollama serve 시작
# 3. 헬스체크 (10회 재시도)
# 4. llama3.1:8b + codellama:7b 풀링
# 5. guardia-agent 커스텀 모델 생성
```
### 4.3 guardia-agent 모델 (Modelfile.guardia)
```
FROM llama3.1:8b
SYSTEM """당신은 GUARDiA ITSM AI 운영 에이전트입니다.
한국어로 응답하며, IT 서비스 관리(ITSM)에 특화되어 있습니다.
보안 원칙: 외부 API 호출 금지, 민감 정보 노출 금지."""
PARAMETER temperature 0.2
PARAMETER num_predict 2048
```
### 4.4 OllamaClient API
| 메서드 | 설명 |
|--------|------|
| `health_check()` | Ollama 서버 상태 확인 |
| `list_models()` | 설치된 모델 목록 조회 |
| `resolve_model(preferred)` | 선호 모델이 없으면 fallback 모델 반환 |
| `chat(messages, model)` | 대화형 추론 |
| `generate(prompt, model)` | 단일 프롬프트 추론 |
| `json_generate(prompt, model)` | JSON 응답 추출 (코드블록 자동 제거) |
| `pull_model(model)` | 모델 다운로드 |
---
## 5. Phase 3 — GUARDiA 에이전트 엔진
### 5.1 파일 구조
```
C:\GUARDiA\itsm\
├── models.py # AgentConfig, AgentTask, AgentApproval 모델 추가
├── core\
│ ├── llm_client.py # OllamaClient 구현
│ └── agents.py # AgentEngine 구현
└── routers\
└── agents.py # 16개 REST API 엔드포인트
```
### 5.2 데이터 모델
#### AgentConfig (tb_agent_config)
| 컬럼 | 타입 | 설명 |
|------|------|------|
| id | INTEGER PK | 에이전트 ID |
| name | VARCHAR | 에이전트 이름 |
| role | VARCHAR | AgentRole enum |
| llm_provider | VARCHAR | ollama (고정) |
| llm_model | VARCHAR | 사용할 LLM 모델 |
| system_prompt | TEXT | 역할 설명 프롬프트 |
| heartbeat_cron | VARCHAR | 크론 표현식 |
| is_active | BOOLEAN | 활성화 여부 |
| status | VARCHAR | IDLE/ACTIVE/WORKING/ERROR/PAUSED |
| last_heartbeat | DATETIME | 마지막 실행 시각 |
| total_tasks | INTEGER | 누적 처리 태스크 수 |
| total_tokens | INTEGER | 누적 사용 토큰 수 |
#### AgentTask (tb_agent_task)
| 컬럼 | 타입 | 설명 |
|------|------|------|
| id | INTEGER PK | 태스크 ID |
| agent_id | INTEGER FK | 에이전트 참조 |
| title | VARCHAR | 태스크 제목 |
| status | VARCHAR | PENDING/IN_PROGRESS/COMPLETED/FAILED |
| input_data | JSON | 입력 데이터 |
| output_data | JSON | LLM 출력 결과 |
| tokens_used | INTEGER | 사용된 토큰 수 |
#### AgentApproval (tb_agent_approval)
| 컬럼 | 타입 | 설명 |
|------|------|------|
| id | INTEGER PK | 승인 ID |
| agent_id | INTEGER FK | 에이전트 참조 |
| task_id | INTEGER FK nullable | 연관 태스크 |
| action_type | VARCHAR | 액션 유형 |
| action_data | JSON | 액션 상세 데이터 |
| status | VARCHAR | PENDING/APPROVED/REJECTED/AUTO_APPROVED |
| reviewed_by | INTEGER FK nullable | 검토자 |
### 5.3 에이전트별 동작
#### INCIDENT_TRIAGE (15분마다 실행)
```
1. 미배정 장애(assigned_to=None, status=OPEN/RECEIVED) 조회
2. LLM JSON 분류 요청:
{ severity: CRITICAL/HIGH/MEDIUM/LOW,
category: HARDWARE/SOFTWARE/NETWORK/...,
reason: "분류 근거" }
3. CRITICAL → AgentApproval(PENDING) 생성
4. 그 외 → AUTO_APPROVED + Incident 등급 즉시 반영
```
#### KB_CURATOR (매시간 정각)
```
1. SR 완료 건 중 KB가 없는 건 조회
2. LLM KB 초안 생성:
{ kb_title, symptom, cause, solution, tags }
3. KBDocument 생성 (published=False, 검토 대기)
4. AgentApproval(AUTO_APPROVED) 기록
```
#### SSL_WATCHER (매일 08:30)
```
1. ssl_expire_date가 0~30일 이내인 서버 조회
2. 기존 SSL 갱신 SR이 없는 경우에만 SR 자동 생성
3. 긴급도: 7일 미만=CRITICAL, 30일 미만=HIGH
```
#### WBS_MONITOR (매일 08:00)
```
1. 진행 중 SI 프로젝트의 WBS 지연 항목 조회
2. 3일+ 지연: 주의, 10일+ 지연: CRITICAL
3. LLM 리스크 분석:
{ risk_level, probability, impact, title, mitigation_plan }
4. ProjectRisk 자동 등록
5. CRITICAL 리스크 → 사람 승인 필요
```
#### PM_SUGGESTER (매일 09:00)
```
1. PM 일정이 없는 서버 조회
2. 권장 PM 일정 제안 (AgentTask 기록)
```
#### DEVELOPER (수동 트리거 or 이슈 등록 시)
```
1. PENDING AgentTask 조회
2. LLM 코드/응답 생성
3. CODE_CHANGE 태스크 → AgentApproval(PENDING) 생성
```
### 5.4 하트비트 사이클
```
에이전트 등록 (AgentConfig 생성)
APScheduler Cron 잡 등록
▼ (스케줄 도달)
status = ACTIVE
Ollama health_check()
│ (실패)──────────────────► status = ERROR
│ (성공)
_handler(db, agent) 실행
├─ 작업 완료 ──► AgentTask(COMPLETED) + status = IDLE
└─ 오류 발생 ──► AgentTask(FAILED) + status = ERROR
last_error 기록
```
---
## 6. Phase 4 — 자율 운영 대시보드
### 6.1 접근 경로
```
http://localhost:8001/agents
```
### 6.2 대시보드 구성
| 영역 | 설명 |
|------|------|
| LLM 상태 배너 | Ollama 온라인/오프라인 상태 실시간 표시 |
| 통계 카드 | 총 에이전트 수, 활성, 오늘 태스크, 오늘 토큰, 승인 대기 |
| 에이전트 탭 | 에이전트 카드 (역할 배지, 상태 펄스 애니메이션) |
| 조직도 탭 | 계층적 트리 렌더링 |
| 승인 대기 탭 | 보류 중 승인 목록 (CRITICAL 강조) |
| 태스크 피드 탭 | 전체 에이전트 태스크 실시간 피드 |
### 6.3 에이전트 상태 색상 코드
| 상태 | 색상 | 설명 |
|------|------|------|
| IDLE | 회색 | 대기 중 |
| ACTIVE | 파랑 | 심장박동 시작 |
| WORKING | 주황 (펄스) | 작업 진행 중 |
| ERROR | 빨강 | 오류 발생 |
| PAUSED | 노랑 | 일시 중지 |
### 6.4 역할별 색상 배지
```
CEO → 보라색 (#6C5CE7)
CTO → 파랑색 (#0984E3)
DEVELOPER → 초록색 (#00B894)
QA → 노랑색 (#FDCB6E)
PM_AGENT → 청록색 (#00CEC9)
INCIDENT_TRIAGE → 빨강 (#E17055)
KB_CURATOR → 민트 (#55EFC4)
SSL_WATCHER → 주황 (#FD79A8)
WBS_MONITOR → 남색 (#74B9FF)
PM_SUGGESTER → 연두 (#A29BFE)
```
---
## 7. API 엔드포인트 설계
### 7.1 전체 목록 (16개)
| HTTP | 경로 | 설명 | 권한 |
|------|------|------|------|
| GET | /api/agents | 에이전트 목록 | USER+ |
| POST | /api/agents | 에이전트 생성 | ADMIN |
| GET | /api/agents/stats | 통계 요약 | USER+ |
| GET | /api/agents/orgchart | 조직도 | USER+ |
| GET | /api/agents/approvals | 승인 대기 목록 | USER+ |
| PATCH | /api/agents/approvals/{id}/review | 승인/거부 | USER+ |
| GET | /api/agents/llm/health | LLM 상태 확인 | USER+ |
| POST | /api/agents/llm/pull | 모델 다운로드 | ADMIN |
| GET | /api/agents/{id} | 에이전트 상세 | USER+ |
| PATCH | /api/agents/{id} | 에이전트 수정 | ADMIN |
| DELETE | /api/agents/{id} | 에이전트 삭제 | ADMIN |
| POST | /api/agents/{id}/heartbeat | 수동 하트비트 | USER+ |
| POST | /api/agents/{id}/pause | 에이전트 일시정지 | ADMIN |
| POST | /api/agents/{id}/resume | 에이전트 재개 | ADMIN |
| GET | /api/agents/{id}/tasks | 태스크 목록 | USER+ |
| POST | /api/agents/{id}/tasks | 태스크 생성 | USER+ |
> CUSTOMER 역할: 모든 에이전트 엔드포인트 접근 불가
### 7.2 주요 스키마
#### AgentConfigOut
```json
{
"id": 1,
"name": "장애 분류 에이전트",
"role": "INCIDENT_TRIAGE",
"description": "미배정 장애를 자동으로 분류합니다",
"llm_model": "guardia-agent",
"heartbeat_cron": "*/15 * * * *",
"is_active": true,
"status": "IDLE",
"last_heartbeat": "2026-05-25T08:15:00",
"total_tasks": 42,
"total_tokens": 15300
}
```
#### AgentApprovalReview
```json
{
"status": "APPROVED",
"notes": "내용 확인 후 승인합니다"
}
```
#### AgentStatsOut
```json
{
"total_agents": 6,
"active_agents": 4,
"today_tasks": 12,
"today_tokens": 5240,
"pending_approvals": 2
}
```
---
## 8. 보안 제약사항
### 8.1 런타임 LLM 보안
| 규칙 | 내용 |
|------|------|
| 외부 API 금지 | 모든 LLM 호출은 localhost:11434 (Ollama) 만 허용 |
| 토큰 최대값 | num_predict: 2048 (무한 루프 방지) |
| 온도 고정 | temperature: 0.2 (결정론적 응답) |
| 타임아웃 | HTTP 요청 30초 타임아웃 |
### 8.2 에이전트 액션 보안
| 규칙 | 내용 |
|------|------|
| CRITICAL 액션 | 반드시 사람 승인 후 실행 |
| 위험 명령어 | `rm -rf /`, `shutdown`, `mkfs` 등 차단 |
| 서버 정보 | `ip_addr`, `ssh_user`, `os_pw_enc` API 응답 미포함 |
| 파일 경로 | `file_path` 컬럼 API 응답 절대 미노출 |
### 8.3 접근 제어
| 역할 | 에이전트 조회 | 에이전트 생성/수정/삭제 | 승인 처리 |
|------|--------------|----------------------|----------|
| CUSTOMER | ✗ | ✗ | ✗ |
| USER | ✓ | ✗ | ✓ |
| OPERATOR | ✓ | ✗ | ✓ |
| ADMIN | ✓ | ✓ | ✓ |
---
## 9. 스케줄러 잡 설계
### 9.1 전체 잡 목록 (9개)
| ID | 잡 이름 | 스케줄 | 설명 |
|----|---------|--------|------|
| 1 | cert_check | 매일 09:00 | SSL 인증서 만료 확인 |
| 2 | pm_check | 매일 09:05 | PM 점검 일정 확인 |
| 3 | on_call_notify | 매일 08:55 | 온콜 교대 알림 |
| 4 | batch_cleanup | 매일 02:00 | 배치 결과 정리 |
| 5 | agent_incident_triage | 매 15분 | 장애 자동 분류 |
| 6 | agent_kb_curator | 매시간 정각 | KB 자동 생성 |
| 7 | agent_ssl_watcher | 매일 08:30 | SSL 만료 감시 |
| 8 | agent_wbs_monitor | 매일 08:00 | WBS 지연 감지 |
| 9 | agent_pm_suggester | 매일 09:00 | PM 일정 제안 |
### 9.2 에이전트 하트비트 흐름
```
APScheduler → _agent_heartbeat_by_role(role)
AgentConfig WHERE role=? AND is_active=True 조회
┌─────────┴───────────┐
없음│ │있음
▼ ▼
skip for each agent:
get_agent_engine().run_heartbeat(id)
```
---
## 10. 테스트 결과
### 10.1 구문 검사 (Syntax Check)
| 파일 | 결과 |
|------|------|
| `models.py` | ✅ 통과 |
| `main.py` | ✅ 통과 |
| `core/llm_client.py` | ✅ 통과 |
| `core/agents.py` | ✅ 통과 |
| `core/scheduler.py` | ✅ 통과 |
| `routers/agents.py` | ✅ 통과 |
### 10.2 심볼 검증
| 항목 | 검증 내용 | 결과 |
|------|-----------|------|
| AgentRole enum | 10가지 역할 정의 | ✅ |
| AgentEngine handlers | 6개 핸들러 구현 | ✅ |
| OllamaClient | 7개 메서드 구현 | ✅ |
| API 엔드포인트 | 16개 라우터 등록 | ✅ |
| 스케줄러 잡 | 9개 (기존 4 + 신규 5) | ✅ |
| main.py 라우터 | 34개 라우터 등록 | ✅ |
### 10.3 보안 검증
| 항목 | 결과 |
|------|------|
| 외부 LLM API 호출 없음 | ✅ |
| ServerOut에 ip_addr 미포함 | ✅ |
| CUSTOMER 역할 차단 | ✅ |
| CRITICAL 승인 게이트 | ✅ |
---
## 11. 운영 가이드
### 11.1 에이전트 등록
```bash
# 장애 분류 에이전트 등록
curl -X POST http://localhost:8001/api/agents \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "장애 분류 에이전트",
"role": "INCIDENT_TRIAGE",
"description": "미배정 장애를 자동으로 분류하고 우선순위를 설정합니다",
"llm_model": "guardia-agent",
"system_prompt": "당신은 IT 장애 분류 전문가입니다...",
"heartbeat_cron": "*/15 * * * *",
"is_active": true
}'
```
### 11.2 수동 하트비트 실행
```bash
# 에이전트 즉시 실행
curl -X POST http://localhost:8001/api/agents/1/heartbeat \
-H "Authorization: Bearer <token>"
```
### 11.3 승인 처리
```bash
# 승인
curl -X PATCH http://localhost:8001/api/agents/approvals/5/review \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"status": "APPROVED", "notes": "내용 확인 후 승인"}'
# 거부
curl -X PATCH http://localhost:8001/api/agents/approvals/5/review \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"status": "REJECTED", "notes": "추가 검토 필요"}'
```
### 11.4 LLM 상태 확인
```bash
# Ollama 서버 상태
curl http://localhost:8001/api/agents/llm/health
# 응답 예시
{
"healthy": true,
"models": ["guardia-agent:latest", "llama3.1:8b", "codellama:7b"],
"version": "0.1.x"
}
```
### 11.5 모니터링 대시보드
```
브라우저 접속: http://localhost:8001/agents
자동 갱신: 30초 간격
```
---
## 12. 향후 로드맵
| 단계 | 기능 | 예상 시기 |
|------|------|----------|
| v1.1 | 에이전트 간 메시지 전달 (CEO→CTO 위임) | 2026 Q3 |
| v1.2 | 에이전트 학습 (이전 태스크 기반 파인튜닝) | 2026 Q3 |
| v1.3 | 멀티모달 지원 (스크린샷 분석) | 2026 Q4 |
| v2.0 | 에이전트 마켓플레이스 (커뮤니티 에이전트) | 2027 Q1 |
| v2.1 | 엣지 배포 (경량 모델: llama3.2:1b) | 2027 Q1 |
---
*이 문서는 GUARDiA ITSM Paperclip × GUARDiA Phase 1~4 구현 내용을 담고 있습니다.*
*Ollama 공식 문서: https://ollama.com*
*Paperclip GitHub: https://github.com/paperclipai/paperclip*

View File

@ -0,0 +1,206 @@
# GUARDiA ITSM — Priority 1: UI 없는 백엔드 모듈 화면 구현
**문서 버전**: 1.0 | **작성일**: 2026-05-25
---
## 개요
백엔드 API는 완성되어 있으나 프론트엔드 HTML 페이지가 없는 7개 모듈의 SPA 구현.
각 페이지는 `/static/style.css` 공유 테마를 사용하며 독립 경로로 접근한다.
---
## 구현 목록
| 페이지 | URL 경로 | 파일 | 백엔드 라우터 |
|--------|---------|------|-------------|
| 장애 관리 | /incidents | static/incidents.html | routers/incidents.py |
| SSL 관리 | /ssl | static/ssl.html | routers/ssl_manager.py |
| PM 점검 | /pm | static/pm.html | routers/pm.py |
| 온콜 관리 | /oncall | static/oncall.html | routers/oncall.py |
| 배치 작업 | /batch | static/batch.html | routers/batch.py |
| 바이브 코딩 | /vibe | static/vibe.html | routers/vibe.py |
| SI 프로젝트 | /si | static/si.html | si_projects/wbs/requirements/issues/risks/milestones/change_requests/tests.py |
| 라이선스 관리 | /license | static/license.html | routers/license.py |
---
## 공통 설계 원칙
### 인증
```javascript
const token = localStorage.getItem('guardia_token');
if (!token) { location.href = '/login'; return; }
async function api(method, path, body) {
const res = await fetch(path, {
method,
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined
});
if (res.status === 401) { localStorage.removeItem('guardia_token'); location.href = '/login'; }
return res;
}
```
### 테마 복원
```javascript
document.body.dataset.theme = localStorage.getItem('guardia_theme') || 'dark';
```
### 상단 네비게이션 바
모든 외부 페이지는 공통 topnav를 가진다:
- GUARDiA ITSM 로고 (/ 링크)
- 페이지 링크: 대시보드, 장애관리, SSL, PM점검, 온콜, 배치, 바이브, SI, AI에이전트
- 우측: 사용자명, 로그아웃 버튼
### 자동 새로고침
```javascript
setInterval(loadData, 30000);
```
---
## 1. 장애 관리 (incidents.html)
### 화면 구성
1. **통계 카드 행** (4개): 전체, OPEN, P1/P2, 평균 MTTR
2. **필터 툴바**: 상태 / 등급 / 키워드 검색
3. **장애 테이블**: INC번호, 제목, 등급배지, 상태, 발생시각, 담당자, MTTR
4. **등록 모달**: 제목, 설명, grade, assigned_to, affected_services, occurred_at
5. **상세 모달**: 전체 정보 + 상태 전환 버튼 + SR 연결 + RCA 종료
### 등급 배지
| 등급 | 색상 | 의미 |
|------|------|------|
| P1 | 빨강 #f87171 | 서비스 전체 중단 |
| P2 | 주황 #fb923c | 주요 기능 영향 |
| P3 | 노랑 #fcd34d | 일부 기능 저하 |
| P4 | 초록 #4ade80 | 경미한 영향 |
### 상태 전환 규칙
```
OPEN → INVESTIGATING | CLOSED
INVESTIGATING → MITIGATED | RESOLVED
MITIGATED → INVESTIGATING | RESOLVED
RESOLVED → CLOSED
CLOSED → (전환 불가)
```
---
## 2. SSL 인증서 관리 (ssl.html)
### 화면 구성
1. **현황 카드** (4개): OK / WARN / URGENT / EXPIRED — 클릭 시 필터
2. **만료 현황 테이블**: 서버명, 만료일, 남은일수 게이지, 경고레벨 배지, 점검/갱신 버튼
3. **days 슬라이더**: 0~90일 범위 조회
4. **갱신 기록 모달**: new_expire_date, renewed_by, notes
5. **갱신 이력 모달**: 서버별 이력 테이블
### 경고 레벨 기준
| 레벨 | 조건 | 배지 색상 |
|------|------|----------|
| OK | days_left > 30 | 초록 |
| WARN | 7 < days_left 30 | 노랑 |
| URGENT | 0 < days_left 7 | 주황 |
| EXPIRED | days_left ≤ 0 | 빨강 |
---
## 3. PM 정기점검 (pm.html)
### 탭 구성
- **탭 1 — 점검 스케줄**: 스케줄 목록, 즉시 실행, 스케줄 등록
- **탭 2 — 점검 결과**: 타임테이블 선택 → 체크리스트 항목별 결과 입력 + Excel 다운로드
- **탭 3 — 체크리스트 템플릿**: 서버역할별 템플릿 CRUD
### 점검 주기 옵션
WEEKLY / BIWEEKLY / MONTHLY / QUARTERLY / SEMIANNUAL / ANNUAL / CUSTOM
### 결과 상태
| 상태 | 의미 | 색상 |
|------|------|------|
| PASS | 정상 | 초록 |
| FAIL | 실패 | 빨강 |
| WARNING | 경고 | 노랑 |
| NA | 해당없음 | 회색 |
---
## 4. 온콜/당직 관리 (oncall.html)
### 탭 구성
- **탭 1 — 월간 캘린더**: 연/월 네비게이션, 날짜 클릭 시 당직 등록 모달
- **탭 2 — 목록 & 일괄 등록**: 당직 목록 테이블 + JSON 일괄 등록
### 상단 배너
```
📞 오늘 당직: [이름] ([시프트]) | 백업: [백업담당자]
```
### 시프트 종류
| 값 | 시간 | 색상 |
|----|------|------|
| ALL_DAY | 24시간 | 파랑 |
| DAYTIME | 09:00~18:00 | 초록 |
| NIGHTTIME | 18:00~익일09:00 | 보라 |
---
## 5. 배치 작업 관리 (batch.html)
### 탭 구성
- **탭 1 — 배치 작업**: 작업 목록, 활성/비활성 토글, 즉시 실행, 등록
- **탭 2 — 실행 이력**: 최근 실행 이력 통합, 상세 모달 (stdout 전체)
### 작업 등록 필드
| 필드 | 설명 |
|------|------|
| job_name | 작업명 |
| server_id | 대상 서버 |
| cron_expr | cron 표현식 (예: `0 2 * * *`) |
| command | 실행 명령어 |
| timeout_sec | 타임아웃 (초) |
| alert_on_fail | 실패 시 SR 자동 생성 여부 |
### 실행 결과 상태
SUCCESS(초록) / FAILED(빨강) / TIMEOUT(주황) / RUNNING(파랑 깜빡)
---
## 6. 바이브 코딩 세션 (vibe.html)
### 탭 구성
- **탭 1 — 활성 세션**: 세션 카드 그리드, 파이프라인 스텝 바
- **탭 2 — 이력**: 전체 세션 이력 테이블
- **탭 3 — 프로젝트**: 등록된 프로젝트 목록 + 등록 모달
### 파이프라인 단계
```
PENDING → CODING → BUILDING → TESTING → DEPLOYING → COMPLETED
```
### Jenkins 연결 상태
- 상단 배너: `GET /api/vibe/jenkins/health` 결과 표시
- 연결됨(초록) / 오프라인(빨강)
---
## 7. SI 프로젝트 관리 (si.html)
### 탭 구성 (7탭)
- **프로젝트**: SI 프로젝트 목록, 등록, 단계 전환
- **WBS**: Gantt 차트 스타일 WBS 트리, 진척률 시각화
- **요구사항**: RFP → 확정 요구사항 관리
- **이슈**: 프로젝트 이슈 목록, 상태 전환
- **리스크**: 위험 매트릭스 (확률 × 영향도), 이슈 전환
- **마일스톤**: 마일스톤 타임라인, 산출물 목록
- **변경요청(CR)**: CR 등록, 영향도 분석, 승인 워크플로우
- **테스트**: 테스트 계획 → 케이스 → 실행 → 결함 관리
### SI 프로젝트 상태 전환
```
PLANNING → ANALYSIS → DESIGN → DEVELOPMENT → TESTING → DEPLOYMENT → COMPLETED
```

View File

@ -0,0 +1,159 @@
# GUARDiA ITSM — Priority 2: 코어 모듈 구현
**문서 버전**: 1.0 | **작성일**: 2026-05-25
---
## 1. core/vibe_bridge.py — Claude CLI SDK 연동
### 목적
`subprocess` 방식 대신 Python SDK를 직접 연동하여 Claude CLI 세션을 관리한다.
### 주요 클래스
```python
class VibeBridge:
"""Claude CLI SDK 비동기 브리지"""
async def start_session(self, sr_id: str, project_path: str) -> str:
"""새 Claude 세션 시작 → session_id 반환"""
async def send_message(self, session_id: str, message: str) -> str:
"""세션에 메시지 전송 → 응답 반환"""
async def get_session_status(self, session_id: str) -> dict:
"""세션 상태 조회 {active, last_response, tokens_used}"""
async def close_session(self, session_id: str) -> bool:
"""세션 종료"""
async def resume_session(self, session_id: str, message: str) -> str:
"""기존 세션 재개"""
```
### 환경 변수
```bash
CLAUDE_CLI_PATH=/usr/local/bin/claude
CLAUDE_WORKSPACE_ROOT=/opt/guardia/workspaces
CLAUDE_SESSION_TIMEOUT=3600 # 세션 타임아웃 (초)
```
### 사용 예시
```python
bridge = VibeBridge()
session_id = await bridge.start_session(
sr_id="SR-20260525-000001",
project_path="/opt/src/myproject"
)
response = await bridge.send_message(session_id, "버그를 수정해주세요")
```
---
## 2. core/deploy_pipeline.py — 배포 파이프라인 오케스트레이터
### 목적
빌드 → 테스트 → 배포 → 헬스체크 → ITSM 콜백의 전체 파이프라인을 관리한다.
### 파이프라인 단계
| 단계 | 함수 | 설명 |
|------|------|------|
| pre_check | `_pre_check()` | 배포 환경 사전 점검 (서버 연결, 디스크 용량) |
| build | `_build()` | tb_project.build_cmd 실행 |
| test | `_test()` | tb_project.test_cmd 실행, 실패 시 중단 |
| backup | `_backup()` | 현재 배포본 백업 (rollback 대비) |
| deploy | `_deploy()` | SSH로 파일 전송 → deploy_path |
| restart | `_restart()` | was_restart_cmd 실행 |
| health_check | `_health_check()` | HTTP GET → health_check_url (10회 재시도) |
| notify | `_notify()` | WorkLog 등록 + 메신저 알림 |
### 클래스 구조
```python
class DeployPipeline:
async def run(self, session_id: int) -> PipelineResult:
"""전체 파이프라인 실행"""
async def rollback(self, session_id: int) -> bool:
"""이전 버전으로 롤백"""
async def get_status(self, session_id: int) -> PipelineStatus:
"""현재 진행 단계 조회"""
```
### 오류 처리
- 각 단계 실패 시 `tb_vibe_session.error_msg` 업데이트
- test 단계 실패 → 배포 중단, SR 상태 = FAILED_ROLLBACK
- health_check 10회 실패 → 자동 rollback 실행
---
## 3. 배치 잡 동적 APScheduler 등록
### 목적
`tb_batch_job` 테이블의 cron_expr을 APScheduler에 동적으로 등록/제거한다.
### 구현 위치
`core/scheduler.py` 확장
### API 연동 흐름
```
POST /api/batch/jobs/{id}/enable
→ scheduler.add_job(
run_batch_job,
CronTrigger.from_crontab(job.cron_expr),
id=f"batch_{job.id}",
args=[job.id],
replace_existing=True
)
POST /api/batch/jobs/{id}/disable
→ scheduler.remove_job(f"batch_{job.id}")
서버 시작 시 (lifespan):
→ 모든 is_active=True 배치 잡 자동 등록
```
### run_batch_job 함수
```python
async def run_batch_job(job_id: int):
async with SessionLocal() as db:
job = await db.get(BatchJob, job_id)
run = BatchRun(job_id=job_id, status="RUNNING", started_at=datetime.utcnow())
db.add(run)
await db.commit()
try:
result = await execute_ssh_command(job.server_id, job.command, job.timeout_sec)
run.status = "SUCCESS" if result.exit_code == 0 else "FAILED"
run.stdout = result.stdout[-5000:] # 마지막 5000자
run.exit_code = result.exit_code
except asyncio.TimeoutError:
run.status = "TIMEOUT"
finally:
run.completed_at = datetime.utcnow()
await db.commit()
# alert_on_fail 처리
if run.status != "SUCCESS" and job.alert_on_fail:
await create_sr_for_batch_failure(job, run, db)
```
---
## 4. SM 스크립트 실행 이력 UI
### 목적
SSH 명령 실행 결과를 `tb_work_log` 기반으로 조회하여 index.html에 표시한다.
### API
```
GET /api/work-logs?sr_id={id}&work_type=SSH_EXEC
```
### index.html 추가 뷰
- 스크립트 관리 뷰에 "실행 이력" 탭 추가
- 테이블: 서버명, 스크립트명, 실행시각, 결과(PASS/FAIL), 소요시간
- 행 클릭 시 stdout/stderr 상세 모달

View File

@ -0,0 +1,197 @@
# GUARDiA ITSM — Priority 3: AI 에이전트 확장
**문서 버전**: 1.0 | **작성일**: 2026-05-25
---
## 1. 장애 RCA 자동 초안 생성
### 목적
INCIDENT_TRIAGE 에이전트 확장. RESOLVED 상태 장애에 대해 Ollama로 RCA 문서 초안을 자동 생성한다.
### 동작 흐름
```
1. tb_incident WHERE status='RESOLVED' AND rca_draft IS NULL 조회
2. LLM 프롬프트 구성:
- 장애 제목, 설명, 등급, 발생~해소 시각, 조치 이력
3. Ollama json_generate() 호출
4. 결과 구조:
{
"root_cause": "근본 원인",
"timeline": "사건 타임라인",
"impact": "영향 범위",
"resolution": "해결 과정",
"preventive_measures": "재발 방지 조치",
"lessons_learned": "교훈"
}
5. tb_incident.rca_draft 컬럼에 저장 (JSON)
6. AgentApproval(PENDING) 생성 — 담당자 검토 후 최종 확정
```
### 구현 위치
`core/agents.py``_incident_triage()` 핸들러 하단 추가
```python
# RESOLVED 장애 RCA 초안 생성
resolved_incidents = await db.execute(
select(Incident).where(
Incident.status == "RESOLVED",
Incident.rca_draft == None
).limit(5)
)
for inc in resolved_incidents.scalars():
rca = await llm.json_generate(rca_prompt(inc), agent.llm_model)
inc.rca_draft = rca
await db.commit()
```
---
## 2. 에이전트 간 메시지 전달
### 목적
CEO 에이전트가 CTO/PM_AGENT에게 태스크를 위임할 수 있도록 에이전트 간 메시지 전달 체계를 구현한다.
### 신규 DB 테이블: tb_agent_message
```sql
CREATE TABLE tb_agent_message (
id INTEGER PRIMARY KEY,
from_agent_id INTEGER REFERENCES tb_agent_config(id),
to_agent_id INTEGER REFERENCES tb_agent_config(id),
message_type VARCHAR(30), -- TASK_DELEGATION / STATUS_UPDATE / ESCALATION
subject VARCHAR(200),
body TEXT,
metadata JSON,
is_read BOOLEAN DEFAULT FALSE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
read_at DATETIME
);
```
### 메시지 타입
| 타입 | 설명 | 트리거 |
|------|------|--------|
| TASK_DELEGATION | 태스크 위임 | CEO → CTO: 개발 태스크 |
| STATUS_UPDATE | 상태 보고 | CTO/Dev → CEO: 완료 보고 |
| ESCALATION | 에스컬레이션 | 하위 에이전트 → 상위 에이전트 |
### API 엔드포인트 추가
```
GET /api/agents/{id}/messages 수신 메시지 목록
POST /api/agents/{id}/messages 메시지 전송
PATCH /api/agents/messages/{msg_id}/read 읽음 처리
```
---
## 3. KB 품질 검토 자동화
### 목적
KB_CURATOR가 생성한 초안에 자동으로 품질 점수를 부여하고, 기준 이상이면 자동 발행한다.
### 품질 평가 기준
| 항목 | 가중치 | 설명 |
|------|--------|------|
| completeness | 40% | 증상/원인/해결책 3섹션 완성도 |
| clarity | 30% | 문장 명확성 및 가독성 |
| actionability | 30% | 실제 조치 가능한 구체적 내용 여부 |
### 동작 흐름
```python
# kb_curator 핸들러 확장
quality = await llm.json_generate(
quality_prompt(kb_doc),
agent.llm_model
)
# quality = {"completeness": 85, "clarity": 78, "actionability": 82, "overall": 82}
kb_doc.quality_score = quality.get("overall", 0)
if kb_doc.quality_score >= 80:
kb_doc.published = True # 자동 발행
approval.status = "AUTO_APPROVED"
else:
approval.status = "PENDING" # 수동 검토 필요
```
### 모델 변경 (models.py)
`KBDocument``quality_score: int` 컬럼 추가
---
## 4. WBS 지연 완료 예측
### 목적
WBS_MONITOR 핸들러 확장. 완료율 추이를 분석하여 프로젝트 완료 예상일을 계산한다.
### 예측 알고리즘
```python
# 최근 7일간 완료율 기울기 계산
prev_rate = wbs_snapshot_7days_ago.completion_rate # 예: 45%
curr_rate = current_completion_rate # 예: 52%
daily_delta = (curr_rate - prev_rate) / 7 # 1%/일
remaining = 100 - curr_rate # 48%
est_days = remaining / daily_delta if daily_delta > 0 else 999
est_completion = date.today() + timedelta(days=est_days)
# planned_end_date와 비교 → 초과 시 리스크 등록
if est_completion > project.planned_end_date:
delay_days = (est_completion - project.planned_end_date).days
# WBS 리스크 자동 등록
```
### 저장 위치
`si_project.estimated_completion` 컬럼 업데이트 (또는 AgentTask에 기록)
---
## 5. 에이전트 파인튜닝 파이프라인
### 목적
누적된 `tb_agent_task(COMPLETED)` 데이터를 기반으로 Ollama 커스텀 모델을 파인튜닝한다.
### 파이프라인 단계
```
1. 데이터 수집
SELECT input_data, output_data FROM tb_agent_task
WHERE status='COMPLETED' AND tokens_used > 0
LIMIT 1000
2. JSONL 파일 생성 (Ollama fine-tune 포맷)
{"prompt": "...", "response": "..."}
→ /opt/guardia/finetune/guardia-agent-v2.jsonl
3. Modelfile 생성
FROM guardia-agent
TRAIN /opt/guardia/finetune/guardia-agent-v2.jsonl
4. ollama create 실행
ollama create guardia-agent-v2 -f Modelfile.guardia-v2
5. 헬스체크 후 active model 전환
AgentConfig.llm_model = "guardia-agent-v2"
```
### API 엔드포인트
```
POST /api/agents/finetune/start 파인튜닝 시작 (ADMIN only)
GET /api/agents/finetune/status 진행 상태 조회
```
### 구현 위치
`core/llm_client.py``fine_tune()` 메서드 추가
```python
async def fine_tune(self, dataset_path: str, model_name: str) -> bool:
"""Ollama 모델 파인튜닝 실행"""
proc = await asyncio.create_subprocess_exec(
"ollama", "create", model_name,
"-f", f"Modelfile.{model_name}",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
return proc.returncode == 0
```

View File

@ -0,0 +1,175 @@
# GUARDiA ITSM — Priority 4: 인프라 / 플랫폼
**문서 버전**: 1.0 | **작성일**: 2026-05-25
---
## 1. PostgreSQL 마이그레이션
### database.py 변경
```python
import os
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
# 환경변수 기반 DB 자동 선택
DATABASE_URL = os.getenv(
"DATABASE_URL",
"sqlite+aiosqlite:///./guardia.db" # 기본: SQLite (개발)
)
# PostgreSQL 예시:
# postgresql+asyncpg://guardia:password@localhost:5432/guardia
engine = create_async_engine(
DATABASE_URL,
echo=False,
pool_pre_ping=True,
pool_size=10 if "postgresql" in DATABASE_URL else 5,
max_overflow=20 if "postgresql" in DATABASE_URL else 0,
)
SessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
```
### Alembic 설정
```bash
# 설치
pip install alembic asyncpg
# 초기화
cd C:\GUARDiA\itsm
alembic init migrations
# alembic.ini 수정
# sqlalchemy.url = postgresql+asyncpg://user:pass@localhost:5432/guardia
# 첫 마이그레이션 생성
alembic revision --autogenerate -m "initial_schema"
# 적용
alembic upgrade head
```
### 마이그레이션 파일 위치
```
itsm/
├── alembic.ini
└── migrations/
├── env.py
└── versions/
└── 001_initial_schema.py
```
### SQLite → PostgreSQL 차이점 주의사항
| 항목 | SQLite | PostgreSQL |
|------|--------|-----------|
| JSON 컬럼 | 문자열 저장 | JSONB (인덱싱 가능) |
| 날짜 함수 | `strftime()` | `date_trunc()`, `extract()` |
| 자동 증가 | `INTEGER PRIMARY KEY` | `SERIAL` 또는 `BIGSERIAL` |
| 동시성 | 파일 잠금 | MVCC (완전 동시 지원) |
---
## 2. 멀티테넌트 지원
### 설계 방식: 공유 스키마 + tenant_id 컬럼
```python
# models.py — 주요 테이블에 tenant_id 추가
class Institution(Base):
tenant_id: str = Column(String(20), nullable=False, index=True)
class SRRequest(Base):
tenant_id: str = Column(String(20), nullable=False, index=True)
class Server(Base):
tenant_id: str = Column(String(20), nullable=False, index=True)
```
### JWT에 tenant_id 포함
```python
# core/auth.py
def create_access_token(user: User) -> str:
payload = {
"sub": str(user.id),
"role": user.role,
"tenant_id": user.inst_code, # 기관코드를 tenant_id로 사용
"exp": ...
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
```
### 자동 필터링 미들웨어
```python
# 모든 조회 쿼리에 tenant_id 자동 추가
async def get_tenant_db(current_user: User = Depends(get_current_user)):
async with SessionLocal() as db:
db.tenant_id = current_user.tenant_id
yield db
```
---
## 3. RBAC 세분화
### 신규 역할: DEPLOY_ENGINEER
```python
class UserRole(str, Enum):
ADMIN = "ADMIN"
PM = "PM"
ENGINEER = "ENGINEER"
DEPLOY_ENGINEER = "DEPLOY_ENGINEER" # 신규
CUSTOMER = "CUSTOMER"
```
### 권한 매핑
| 기능 | ADMIN | PM | ENGINEER | DEPLOY_ENGINEER | CUSTOMER |
|------|-------|-----|---------|-----------------|---------|
| SR 조회 | ✅ | ✅ | ✅ | ✅ | ✅ (자신만) |
| SR 생성 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 배포 트리거 | ✅ | ✅ | ❌ | ✅ | ❌ |
| 배치 즉시 실행 | ✅ | ❌ | ❌ | ✅ | ❌ |
| 서버 자격증명 조회 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 에이전트 관리 | ✅ | ❌ | ❌ | ❌ | ❌ |
| 사용자 관리 | ✅ | ❌ | ❌ | ❌ | ❌ |
### 구현
```python
# core/auth.py
DEPLOY_ROLES = {UserRole.ADMIN, UserRole.PM, UserRole.DEPLOY_ENGINEER}
def require_deploy_role(current_user: User = Depends(get_current_user)):
if current_user.role not in DEPLOY_ROLES:
raise HTTPException(403, "배포 권한이 없습니다")
return current_user
```
---
## 4. 배포 이력 UI
### vibe.html 내 배포 이력 탭 추가
- Jenkins 빌드 번호별 조회
- 아티팩트 목록 (다운로드 링크)
- 롤백 버튼 (이전 빌드로 롤백)
### Jenkins 빌드 이력 API (core/cicd.py)
```python
async def get_build_history(project_name: str, limit: int = 10) -> list[BuildInfo]:
"""Jenkins 빌드 이력 조회"""
url = f"{JENKINS_URL}/job/{project_name}/api/json?tree=builds[number,result,timestamp,duration,artifacts[*]]"
...
async def get_artifact_url(project_name: str, build_number: int, artifact: str) -> str:
"""빌드 아티팩트 다운로드 URL 반환"""
return f"{JENKINS_URL}/job/{project_name}/{build_number}/artifact/{artifact}"
```

View File

@ -0,0 +1,243 @@
# GUARDiA ITSM — Priority 5: 외부 시스템 연동 고도화
**문서 버전**: 1.0 | **작성일**: 2026-05-25
---
## 1. Jenkins 운영 배포 승인 연동
### 목적
Jenkins Declarative Pipeline의 `input()` 단계와 ITSM 승인 API를 양방향으로 연동한다.
운영(prd) 배포 시 ITSM에서 PM이 승인해야만 Jenkins 파이프라인이 진행된다.
### 흐름
```
GUARDiA Vibe 세션 → "배포(prd)" 버튼 클릭
▼ POST /api/vibe/{id}/deploy { environment: "prd" }
▼ Jenkins Job 트리거 (core/cicd.py)
Jenkins Pipeline:
stage('Request ITSM Approval') {
steps {
sh """
curl -X POST ${ITSM_URL}/api/vibe/${SESSION_ID}/request-approval \
-H "Authorization: Bearer ${ITSM_TOKEN}" \
-d '{"environment": "prd", "build_number": ${BUILD_NUMBER}}'
"""
}
}
stage('Wait for ITSM Approval') {
steps {
timeout(time: 60, unit: 'MINUTES') {
waitUntil {
def result = sh(
script: "curl -s ${ITSM_URL}/api/vibe/${SESSION_ID}/approval-status",
returnStdout: true
).trim()
return result == '"APPROVED"'
}
}
}
}
stage('Deploy to Production') { ... }
```
### ITSM API 신규 엔드포인트
```
POST /api/vibe/{id}/request-approval
→ VibeDeploy 승인 요청 생성 (SRApproval 또는 AgentApproval 재사용)
→ PM에게 메신저 알림
GET /api/vibe/{id}/approval-status
→ "PENDING" | "APPROVED" | "REJECTED"
PATCH /api/vibe/{id}/approve
→ PM이 ITSM UI에서 승인 처리
→ Jenkins 폴링 응답에 "APPROVED" 반환
```
---
## 2. SonarQube Quality Gate 연동
### 목적
Jenkins 빌드 완료 후 SonarQube 분석 결과를 ITSM에 자동 등록한다.
Quality Gate 실패 시 SR을 생성하여 개발팀에 통보한다.
### core/cicd.py 추가 함수
```python
async def get_sonarqube_result(project_key: str) -> SonarResult:
"""SonarQube Quality Gate 결과 조회"""
url = f"{SONARQUBE_URL}/api/qualitygates/project_status?projectKey={project_key}"
async with httpx.AsyncClient() as client:
r = await client.get(url, headers={"Authorization": f"Bearer {SONARQUBE_TOKEN}"})
data = r.json()
status = data["projectStatus"]["status"] # OK | WARN | ERROR
metrics = {
c["metricKey"]: c["value"]
for c in data["projectStatus"]["conditions"]
}
return SonarResult(
status=status,
coverage=metrics.get("coverage", "N/A"),
bugs=metrics.get("bugs", "0"),
vulnerabilities=metrics.get("vulnerabilities", "0"),
code_smells=metrics.get("code_smells", "0"),
)
async def handle_sonar_gate_failure(session_id: int, result: SonarResult, db: AsyncSession):
"""Quality Gate 실패 시 SR 자동 생성"""
if result.status == "ERROR":
sr = SRRequest(
title=f"SonarQube Quality Gate 실패 — 세션 {session_id}",
description=f"취약점: {result.vulnerabilities}, 버그: {result.bugs}, 커버리지: {result.coverage}%",
sr_type="DEPLOY",
priority="HIGH",
)
db.add(sr)
await db.commit()
```
### Jenkins 연동 (Jenkinsfile에 추가)
```groovy
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('SonarQube') {
sh 'mvn sonar:sonar -Dsonar.projectKey=${PROJECT_KEY}'
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: false
}
// ITSM에 결과 전송
sh """
curl -X POST ${ITSM_URL}/api/vibe/sonar-result \
-d '{"session_id": ${SESSION_ID}, "project_key": "${PROJECT_KEY}"}'
"""
}
}
```
### 환경 변수
```bash
SONARQUBE_URL=http://sonar.agency.go.kr:9000
SONARQUBE_TOKEN=<SonarQube API Token>
```
---
## 3. SSL 자동 갱신 (Let's Encrypt)
### 목적
certbot과 연동하여 SSL 인증서를 자동으로 갱신하고, 결과를 ITSM에 기록한다.
### scripts/sm/ssl/ssl_auto_renew.sh
```bash
#!/bin/bash
# SSL 자동 갱신 스크립트 (서버에서 실행)
set -euo pipefail
ITSM_URL="${ITSM_URL:-http://localhost:8001}"
SERVER_ID="${SERVER_ID:-}"
ITSM_TOKEN="${ITSM_TOKEN:-}"
# certbot 갱신
if certbot renew --quiet --non-interactive \
--deploy-hook "systemctl reload nginx 2>/dev/null || systemctl reload apache2 2>/dev/null"; then
# 새 만료일 조회
NEW_EXPIRE=$(openssl x509 -in /etc/letsencrypt/live/*/cert.pem -noout -enddate \
| cut -d= -f2)
NEW_EXPIRE_ISO=$(date -d "$NEW_EXPIRE" +"%Y-%m-%d")
# ITSM에 갱신 기록
curl -s -X POST "${ITSM_URL}/api/ssl/renew/${SERVER_ID}" \
-H "Authorization: Bearer ${ITSM_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"new_expire_date\": \"${NEW_EXPIRE_ISO}\", \"renewed_by\": \"certbot-auto\", \"notes\": \"Let's Encrypt 자동 갱신\"}"
echo "[OK] SSL 갱신 완료: ${NEW_EXPIRE_ISO}"
else
echo "[WARN] SSL 갱신 불필요 (아직 유효기간 충분)"
fi
```
### 배치 작업 등록
`tb_batch_job`에 자동 갱신 배치 잡 등록:
- `cron_expr`: `0 3 * * *` (매일 03:00)
- `command`: `SSL_WATCHER=true bash /opt/guardia/scripts/ssl/ssl_auto_renew.sh`
- `alert_on_fail`: true
---
## 4. SSE 스트리밍 확장
### 목적
배치 실행 로그와 빌드 로그를 SSE로 실시간 스트리밍한다.
### core/events.py 확장
```python
# 배치 실행 로그 스트리밍
async def stream_batch_log(run_id: int) -> AsyncGenerator[str, None]:
"""tb_batch_run.stdout 청크 단위 SSE 전송"""
last_len = 0
for _ in range(300): # 최대 5분 (1초 간격)
async with SessionLocal() as db:
run = await db.get(BatchRun, run_id)
if run.stdout and len(run.stdout) > last_len:
new_data = run.stdout[last_len:]
last_len = len(run.stdout)
yield f"data: {json.dumps({'chunk': new_data})}\n\n"
if run.status not in ("RUNNING",):
yield f"data: {json.dumps({'done': True, 'status': run.status})}\n\n"
break
await asyncio.sleep(1)
# Jenkins 빌드 로그 스트리밍
async def stream_build_log(session_id: int) -> AsyncGenerator[str, None]:
"""Jenkins Build Log API 폴링 → SSE 전송"""
# GET {JENKINS_URL}/job/{name}/{number}/logText/progressiveText?start=0
offset = 0
while True:
log_chunk, next_offset = await cicd.get_progressive_log(session_id, offset)
if log_chunk:
yield f"data: {json.dumps({'chunk': log_chunk, 'offset': next_offset})}\n\n"
offset = next_offset
done = await cicd.is_build_complete(session_id)
if done:
yield f"data: {json.dumps({'done': True})}\n\n"
break
await asyncio.sleep(2)
```
### 신규 엔드포인트
```
GET /api/batch/runs/{run_id}/stream 배치 로그 실시간 스트리밍 (SSE)
GET /api/vibe/{id}/build/stream 빌드 로그 실시간 스트리밍 (SSE)
```
### 프론트엔드 연결
```javascript
// batch.html — 실행 로그 스트리밍
const es = new EventSource(`/api/batch/runs/${runId}/stream?token=${token}`);
es.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.chunk) logBox.textContent += data.chunk;
if (data.done) { es.close(); updateRunStatus(data.status); }
};
```

View File

@ -0,0 +1,647 @@
# GUARDiA ITSM — 라이선스 키 발급 및 적용 가이드
> **문서번호**: GUARDIA-LIC-001
> **버전**: 1.0
> **작성일**: 2026-05-28
> **작성자**: GUARDiA 개발팀
> **보안등급**: 내부용 (대외비) — 마스터 키 및 생성 절차는 벤더 내부 한정
---
## 목차
1. [라이선스 시스템 개요](#1-라이선스-시스템-개요)
2. [에디션별 제한 및 기능](#2-에디션별-제한-및-기능)
3. [마스터 키 생성 및 보관](#3-마스터-키-생성-및-보관)
4. [라이선스 키 발급 (벤더 전용)](#4-라이선스-키-발급-벤더-전용)
5. [라이선스 키 고객 적용 절차](#5-라이선스-키-고객-적용-절차)
6. [라이선스 갱신 절차](#6-라이선스-갱신-절차)
7. [라이선스 상태 모니터링](#7-라이선스-상태-모니터링)
8. [CLI 레퍼런스](#8-cli-레퍼런스)
9. [API 레퍼런스](#9-api-레퍼런스)
10. [문제 해결](#10-문제-해결)
---
## 1. 라이선스 시스템 개요
### 1.1 보안 설계
GUARDiA 라이선스는 **완전 오프라인 검증** 방식으로, 외부 인터넷 연결 없이 검증된다.
```
라이선스 키 구조:
GRD-{base64url(iv[12B] + ciphertext + gcm_tag[16B] + hmac_sig[8B])}
암호화: AES-256-GCM (iv 12B + GCM tag 16B)
서명: HMAC-SHA256 (앞 8B prefix — 위변조 즉시 탐지)
키 파생: SHA-256(master_key + "guardia-aes-v1") → AES 키
SHA-256(master_key + "guardia-license-hmac-v1") → HMAC 키
```
**검증 흐름:**
```
고객 서버가 키 수신
▼ HMAC 서명 검증 (위변조 즉시 차단)
▼ AES-256-GCM 복호화 (마스터 키 불일치 시 실패)
▼ JSON 페이로드 파싱 (만료일, 에디션, 고객명 등)
▼ 만료일 비교 → valid: true / expired: true
```
### 1.2 라이선스 페이로드 구성
```json
{
"license_id": "GRD-A1B2C3",
"edition": "ENTERPRISE",
"customer": "서울특별시청",
"issued_at": "2026-05-28T00:00:00+00:00",
"expires_at": "2027-05-28T00:00:00+00:00",
"limits": {
"max_institutions": -1,
"max_users": -1,
"max_servers": -1,
"features": ["MFA", "LDAP", "PAM", "AI_AGENTS",
"VULN_SCAN", "CICD", "ANALYTICS", "FINOPS"]
}
}
```
---
## 2. 에디션별 제한 및 기능
| 에디션 | 기관 수 | 사용자 수 | 서버 수 | 활성화 기능 | 대상 |
|--------|--------|---------|--------|-----------|------|
| **COMMUNITY** | 1 | 10 | 20 | MFA | 소규모 단일 기관, 평가판 |
| **STANDARD** | 50 | 200 | 500 | MFA, LDAP, PAM, AI_AGENTS | 중규모 기관·지자체 |
| **ENTERPRISE** | 무제한(-1) | 무제한(-1) | 무제한(-1) | 전체 기능 | 광역단체, 대규모 멀티테넌트 |
### 기능별 에디션 요구사항
| 기능 코드 | 기능명 | 최소 에디션 |
|----------|-------|-----------|
| `MFA` | 2단계 인증 (TOTP/OTP) | COMMUNITY |
| `LDAP` | LDAP/AD 디렉토리 연동 | STANDARD |
| `PAM` | 특권 접근 관리 | STANDARD |
| `AI_AGENTS` | AI 에이전트 오케스트레이션 | STANDARD |
| `VULN_SCAN` | 보안 취약점 자동 스캔 | ENTERPRISE |
| `CICD` | CI/CD Jenkins 연동 | ENTERPRISE |
| `ANALYTICS` | 고급 분석 대시보드 | ENTERPRISE |
| `FINOPS` | 비용 분석 (FinOps) | ENTERPRISE |
---
## 3. 마스터 키 생성 및 보관
> **경고**: 마스터 키는 절대 외부 유출 금지. 분실 시 기존 발급 라이선스 모두 무효화됨.
### 3.1 마스터 키 생성
마스터 키는 GUARDiA 벤더 내부 시스템에서 **1회 생성**하여 안전하게 보관한다.
```bash
# 방법 1: Python (권장)
python3 -c "import secrets; print(secrets.token_hex(32))"
# 방법 2: OpenSSL
openssl rand -hex 32
# 결과 예시 (64자리 hex = 32바이트):
# a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607
```
### 3.2 마스터 키 보관 정책
| 보관 방법 | 설명 |
|---------|------|
| **운영 서버** | 환경변수 `GUARDIA_LICENSE_KEY` — systemd 서비스 파일 또는 .env (권한 600) |
| **비상 백업** | 오프라인 금고 (USB 암호화 드라이브) |
| **접근 권한** | 벤더 최고 관리자 2인 이상 알 고리즘 — 단독 접근 금지 |
### 3.3 .env 파일 설정 (운영 서버)
```ini
# C:\GUARDiA\itsm\.env (권한: 소유자 읽기/쓰기만)
GUARDIA_LICENSE_KEY=a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607
```
```bash
# Linux: 파일 권한 보호
chmod 600 /opt/guardia/itsm/.env
chown guardia:guardia /opt/guardia/itsm/.env
```
---
## 4. 라이선스 키 발급 (벤더 전용)
> 이 절차는 GUARDiA 벤더(개발사) 내부 운영자만 실행한다.
### 4.1 발급 전 확인 사항
```
□ 고객명 (정식 기관명, 계약서 표기 명칭과 동일)
□ 에디션 (COMMUNITY / STANDARD / ENTERPRISE)
□ 유효 기간 (일수) 예: 365일(1년), 730일(2년)
□ 커스텀 한도 여부 (특수 계약 시: max_institutions, max_users, max_servers 별도 지정)
□ GUARDIA_LICENSE_KEY 환경변수 설정 확인
```
### 4.2 CLI를 이용한 발급
```bash
# 발급 서버(벤더 내부 PC)에서 실행
cd C:\GUARDiA\itsm
# 방법 1: 환경변수에 마스터 키 설정 후 발급
$env:GUARDIA_LICENSE_KEY = "a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607"
python -m core.license --customer "서울특별시청" --edition ENTERPRISE --days 365
# 방법 2: --key 인수로 직접 지정 (환경변수 없어도 가능)
python -m core.license \
--customer "인천광역시청" \
--edition STANDARD \
--days 730 \
--key a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607
# 커스텀 라이선스 ID 지정 (선택)
python -m core.license --customer "부산광역시청" --edition ENTERPRISE --days 365 --lid GRD-BUSAN2026
```
### 4.3 발급 결과 예시
```
============================================================
GUARDiA 라이선스 키 생성 완료
============================================================
고객명 : 서울특별시청
에디션 : ENTERPRISE
라이선스ID: GRD-A1B2C3
발급일시 : 2026-05-28T00:00:00+00:00
만료일시 : 2027-05-28T00:00:00+00:00
유효기간 : 365일
============================================================
라이선스 키:
GRD-eyJsaWNlbnNlX2lkIjoiR1JELUExQjJDMyIsImVkaXRpb24iOiJFTlRFUlBSSVNFIn0...
```
### 4.4 Python 코드를 이용한 발급 (자동화)
```python
from datetime import datetime, timedelta, timezone
from core.license import generate_license_key, validate_license, LicenseEdition
MASTER_KEY = "a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607"
# 1년짜리 ENTERPRISE 라이선스 생성
lic_key = generate_license_key(
customer = "서울특별시청",
edition = LicenseEdition.ENTERPRISE,
expires_at = datetime.now(timezone.utc) + timedelta(days=365),
master_key_hex = MASTER_KEY,
)
print(f"키: {lic_key}")
# 검증
status = validate_license(lic_key, master_key_hex=MASTER_KEY)
print(f"유효: {status['valid']}, 남은 일수: {status['days_remaining']}")
```
### 4.5 커스텀 한도 지정 (특수 계약)
```python
custom_limits = {
"max_institutions": 10, # 기관 10개
"max_users": 100, # 사용자 100명
"max_servers": 200, # 서버 200대
"features": ["MFA", "LDAP", "PAM"],
}
lic_key = generate_license_key(
customer = "경기도청",
edition = LicenseEdition.STANDARD,
expires_at = datetime.now(timezone.utc) + timedelta(days=365),
custom_limits = custom_limits,
master_key_hex = MASTER_KEY,
)
```
---
## 5. 라이선스 키 고객 적용 절차
### 5.1 방법 A: 웹 UI (권장)
1. ITSM 관리자 계정으로 로그인
2. 사이드바 **🔏 라이선스 관리** 클릭 → `/license`
3. **라이선스 키 등록/갱신** 섹션에 `GRD-...` 키 붙여넣기
4. **라이선스 등록** 버튼 클릭
5. 상단 배너에서 에디션·만료일 확인
```
[상태 배너 예시]
✅ ENTERPRISE 라이선스 활성 — 365일 남음 (서울특별시청)
```
### 5.2 방법 B: API (자동화/스크립트)
```bash
# 1. 관리자 로그인 후 토큰 획득
TOKEN=$(curl -s -X POST http://localhost:8001/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"Admin!1234"}' \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
# 2. 라이선스 등록
curl -X POST http://localhost:8001/api/license/activate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"license_key": "GRD-eyJsaWNlbnNlX2lkIjoiR1JELUExQjJDMyI..."
}'
# 성공 응답 예시:
# {
# "message": "ENTERPRISE 라이선스가 활성화되었습니다 (365일 남음).",
# "license_id": "GRD-A1B2C3",
# "edition": "ENTERPRISE",
# "customer": "서울특별시청",
# "expires_at": "2027-05-28T00:00:00+00:00",
# "days_remaining": 365,
# "valid": true
# }
```
### 5.3 방법 C: .env 파일 사전 구성 (설치 시점 적용)
배포 자동화 파이프라인에서 설치 시점에 라이선스를 미리 구성할 때:
```ini
# C:\GUARDiA\itsm\.env
SECRET_KEY=<jwt-secret>
GUARDIA_LICENSE_KEY=<마스터_키_64자리_hex>
```
서버 기동 시 ITSM이 자동으로 라이선스 상태를 출력한다:
```
[LICENSE] ENTERPRISE 라이선스 활성 (365일 남음) — 서울특별시청
```
단, `.env`에 마스터 키만 있고 DB에 라이선스 레코드가 없으면 활성화는 안 된다.
**DB에 키를 등록하는 것은 API 호출(방법 A/B) 필수.**
---
## 6. 라이선스 갱신 절차
### 6.1 갱신 타이밍
| 시점 | 시스템 반응 | 권장 조치 |
|------|-----------|---------|
| 만료 30일 전 | 웹 UI 황색 배너 경고, 서버 시작 시 경고 로그 | 갱신 라이선스 발급 준비 |
| 만료 7일 전 | 매일 경고 | 즉시 갱신 신청 |
| 만료 당일 | `expired: true`, 기능 제한 시작 | 긴급 갱신 |
| 만료 후 | 기존 데이터 보존, 신규 등록만 차단 | 갱신 라이선스 즉시 적용 |
> **중요**: 만료 후에도 기존 등록된 기관·서버·사용자 데이터는 삭제되지 않는다.
> 신규 생성(기관 추가, 서버 추가)만 한도 초과 시 차단된다.
### 6.2 갱신 라이선스 발급
동일 CLI로 새 키를 발급하면 된다. 라이선스 ID는 새로 자동 생성된다:
```bash
python -m core.license \
--customer "서울특별시청" \
--edition ENTERPRISE \
--days 365 \
--key <마스터_키>
# 기존 라이선스는 자동 비활성화되고 새 라이선스로 교체된다.
```
### 6.3 갱신 적용
**웹 UI**: `/license` → 새 `GRD-...` 키 입력 → **라이선스 등록**
(기존 활성 라이선스는 자동으로 비활성화됨)
**API**:
```bash
curl -X POST http://localhost:8001/api/license/activate \
-H "Authorization: Bearer $TOKEN" \
-d '{"license_key": "GRD-<새_키>"}'
```
---
## 7. 라이선스 상태 모니터링
### 7.1 웹 UI 상태 배너
`/license` 페이지의 상단 배너로 즉시 확인:
| 배너 색상 | 의미 |
|---------|------|
| 초록 | 정상 활성 |
| 노랑 | 만료 30일 이내 경고 |
| 빨강 | 만료됨 |
| 회색 | 라이선스 미등록 (Community 제한 모드) |
### 7.2 사용량 현황
`/license` 페이지 **사용량 현황** 섹션에서 기관/사용자/서버 현재 수 vs 한도를 확인할 수 있다:
```
기관 [██████████░] 48/50
사용자 [████░░░░░░░] 80/200
서버 [███░░░░░░░░] 150/500
```
### 7.3 API로 상태 확인
```bash
curl http://localhost:8001/api/license/status \
-H "Authorization: Bearer $TOKEN"
```
```json
{
"activated": true,
"valid": true,
"expired": false,
"expiry_warning": false,
"license_id": "GRD-A1B2C3",
"edition": "ENTERPRISE",
"customer": "서울특별시청",
"issued_at": "2026-05-28T00:00:00+00:00",
"expires_at": "2027-05-28T00:00:00+00:00",
"days_remaining": 365,
"limits": {
"max_institutions": -1,
"max_users": -1,
"max_servers": -1,
"features": ["MFA","LDAP","PAM","AI_AGENTS","VULN_SCAN","CICD","ANALYTICS","FINOPS"]
},
"message": "ENTERPRISE 라이선스 활성 (365일 남음)"
}
```
### 7.4 서버 로그 모니터링
```bash
# 시스템 기동 시 라이선스 상태 로그 확인
journalctl -u guardia-itsm -n 50 | grep LICENSE
# 정상:
# [LICENSE] ENTERPRISE 라이선스 활성 (365일 남음) — 서울특별시청
# 경고:
# [LICENSE] 경고: 라이선스 만료 25일 남음
# 만료:
# [LICENSE] 경고: 라이선스가 만료되었습니다. 갱신이 필요합니다.
# 미설정:
# [LICENSE] GUARDIA_LICENSE_KEY 미설정 — Community 모드로 실행됩니다.
```
---
## 8. CLI 레퍼런스
```
usage: python -m core.license [-h] --customer CUSTOMER
--edition {COMMUNITY,STANDARD,ENTERPRISE}
--days DAYS
[--lid LID]
[--key KEY]
GUARDiA 라이선스 키 생성 도구
필수 인수:
--customer 고객/기관 이름 (예: "서울특별시청")
--edition 라이선스 에디션: COMMUNITY | STANDARD | ENTERPRISE
--days 유효 기간 (일, 예: 365)
선택 인수:
--lid 라이선스 ID 직접 지정 (기본: GRD-{6자리랜덤hex})
--key 마스터 키 hex 64자리 (기본: GUARDIA_LICENSE_KEY 환경변수)
-h, --help 도움말 출력
```
### 사용 예시
```bash
# ENTERPRISE 1년
python -m core.license --customer "서울특별시청" --edition ENTERPRISE --days 365
# STANDARD 2년
python -m core.license --customer "경기도청" --edition STANDARD --days 730
# COMMUNITY 30일 평가판
python -m core.license --customer "테스트기관" --edition COMMUNITY --days 30
# 마스터 키 직접 지정 (환경변수 없을 때)
python -m core.license --customer "부산광역시청" --edition ENTERPRISE --days 365 \
--key a3f8c2d1e5b4970682f1a9c3d7e2b5f4180c6e9a2d4b7f0e3c8a5d2b1f9e607
# 커스텀 라이선스 ID
python -m core.license --customer "인천광역시청" --edition STANDARD --days 365 \
--lid GRD-INCHEON26
```
---
## 9. API 레퍼런스
모든 API는 `Authorization: Bearer <JWT_TOKEN>` 헤더 필요.
ADMIN 전용 API는 ADMIN 역할 계정만 호출 가능.
### GET /api/license/status
현재 라이선스 상태 조회 (로그인한 모든 사용자 접근 가능).
**응답:**
```json
{
"activated": true,
"valid": true,
"expired": false,
"expiry_warning": false,
"license_id": "GRD-A1B2C3",
"edition": "ENTERPRISE",
"customer": "서울특별시청",
"issued_at": "2026-05-28T00:00:00+00:00",
"expires_at": "2027-05-28T00:00:00+00:00",
"days_remaining": 365,
"limits": { ... },
"message": "ENTERPRISE 라이선스 활성 (365일 남음)"
}
```
---
### POST /api/license/activate
라이선스 키 등록 및 활성화 **(ADMIN 전용)**.
**요청 본문:**
```json
{ "license_key": "GRD-eyJ..." }
```
**응답 (성공):**
```json
{
"message": "ENTERPRISE 라이선스가 활성화되었습니다 (365일 남음).",
"license_id": "GRD-A1B2C3",
"edition": "ENTERPRISE",
"customer": "서울특별시청",
"expires_at": "2027-05-28T00:00:00+00:00",
"days_remaining": 365,
"valid": true
}
```
**오류:**
| 코드 | 원인 |
|------|------|
| 400 | 키 형식 오류, 서명 불일치, 복호화 실패 |
| 403 | ADMIN 권한 없음 |
| 500 | GUARDIA_LICENSE_KEY 환경변수 미설정 |
---
### POST /api/license/verify
등록 없이 키 검증만 수행 **(ADMIN 전용)**.
**요청 본문:** `{ "license_key": "GRD-eyJ..." }`
**응답:** 라이선스 페이로드 전체 반환 (status와 동일 구조)
---
### DELETE /api/license/
활성 라이선스 비활성화 **(ADMIN 전용)**.
시스템이 Community 제한 모드로 전환된다.
**응답:** `{ "message": "라이선스가 비활성화되었습니다..." }`
---
### GET /api/license/history
등록 이력 전체 조회 **(ADMIN 전용)**.
**응답:**
```json
[
{
"id": 2,
"license_id": "GRD-A1B2C3",
"edition": "ENTERPRISE",
"customer": "서울특별시청",
"issued_at": "2026-05-28T00:00:00",
"expires_at": "2027-05-28T00:00:00",
"is_active": true,
"activated_by": "admin",
"activated_at": "2026-05-28T09:00:00"
},
{
"id": 1,
"license_id": "GRD-OLD001",
"edition": "STANDARD",
"is_active": false,
...
}
]
```
---
## 10. 문제 해결
### 10.1 "GUARDIA_LICENSE_KEY 환경변수가 설정되지 않았습니다"
```
원인: 서버 .env 파일에 GUARDIA_LICENSE_KEY가 없거나 서비스가 .env를 읽지 못함
해결:
1. .env 파일에 키 추가:
GUARDIA_LICENSE_KEY=<64자리 hex>
2. 서비스 재시작:
systemctl restart guardia-itsm (Linux)
Restart-Service GUARDiA-ITSM (Windows)
```
### 10.2 "라이선스 서명 검증 실패 — 위변조 또는 잘못된 키"
```
원인 1: 키가 다른 마스터 키로 생성됨 (고객사 서버의 GUARDIA_LICENSE_KEY와 발급 시 키가 다름)
원인 2: 키 문자열이 복사 중 잘림
해결:
1. 키를 처음부터 끝까지 정확히 복사했는지 확인 (앞뒤 공백 없이)
2. 발급 시 사용한 마스터 키 == 운영 서버의 GUARDIA_LICENSE_KEY 확인
3. 다른 환경에서 발급된 키라면 해당 환경의 마스터 키로 서버를 재설정
```
### 10.3 "라이선스 복호화 실패 — 마스터 키 불일치"
```
원인: HMAC 통과했으나 AES 복호화 실패 — 마스터 키 파생 문자가 다름
해결: 마스터 키가 정확히 64자리 hex인지 확인
python3 -c "print(len('YOUR_KEY'))" # 반드시 64 출력
```
### 10.4 "라이선스 한도 초과: ENTERPRISE 에디션으로 업그레이드하세요"
```
원인: 현재 등록된 기관/서버/사용자 수가 에디션 한도를 초과함
해결:
방법 1: 상위 에디션 라이선스 발급 후 적용
방법 2: 불필요한 기관/서버를 삭제하여 한도 이하로 줄임
```
### 10.5 만료된 라이선스 등록 시도
```
동작: 만료된 키도 DB에 등록 가능하나 valid: false, expired: true 반환됨
등록 시 경고 메시지: "경고: 만료된 라이선스입니다 (만료일: ...). 갱신이 필요합니다."
해결: 새 라이선스를 발급받아 즉시 교체 등록
```
### 10.6 라이선스 캐시 갱신 안 됨 (변경 내용 미반영)
```
원인: 인메모리 캐시(TTL 1시간)가 남아있어 이전 상태를 반환
해결: 라이선스를 activate/deactivate하면 캐시가 자동 무효화됨
긴급 시 ITSM 서비스 재시작으로 강제 캐시 초기화
```
---
## 부록 A: 라이선스 발급 체크리스트 (벤더용)
```
고객사 발급 전 체크리스트:
□ 계약서에 명시된 기관명과 동일한지 확인
□ 에디션이 계약 내용과 일치하는지 확인
□ 유효 기간이 계약 기간과 일치하는지 확인
□ 마스터 키가 해당 고객사 서버에 적용된 키와 동일한지 확인
□ 발급된 키를 암호화된 채널(이메일 암호화, 보안 메신저)로 전달
□ 발급 이력 대장에 기록 (날짜, 고객명, 에디션, 만료일, 발급자)
```
## 부록 B: 라이선스 발급 이력 대장 양식
| 번호 | 발급일 | 고객명 | 에디션 | 라이선스 ID | 만료일 | 발급자 | 비고 |
|-----|-------|-------|--------|-----------|-------|--------|------|
| 001 | 2026-05-28 | 서울특별시청 | ENTERPRISE | GRD-A1B2C3 | 2027-05-28 | 홍길동 | 신규 |
| 002 | 2026-05-28 | 경기도청 | STANDARD | GRD-D4E5F6 | 2028-05-28 | 홍길동 | 신규 2년 |

Binary file not shown.

1209
gen_ppt.js Normal file

File diff suppressed because it is too large Load Diff