commit 938b25f2866d778fee5de403aa463995354a25c4 Author: DESKTOP-TKLFCPRython Date: Fri May 29 18:18:52 2026 +0900 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 diff --git a/01_분석설계서.md b/01_분석설계서.md new file mode 100644 index 0000000..53cff42 --- /dev/null +++ b/01_분석설계서.md @@ -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 상태 조회 | `!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 | 최초 작성 | 개발팀 | diff --git a/02_단위통합테스트계획서.md b/02_단위통합테스트계획서.md new file mode 100644 index 0000000..a7f6be6 --- /dev/null +++ b/02_단위통합테스트계획서.md @@ -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 제목에 `` | 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 기준으로 작성되었으며, 버전 업그레이드 시 갱신이 필요합니다.* diff --git a/03_개발자지침서.md b/03_개발자지침서.md new file mode 100644 index 0000000..94e1fee --- /dev/null +++ b/03_개발자지침서.md @@ -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 +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장)은 특히 엄격히 적용됩니다.* diff --git a/04_운영자지침서.md b/04_운영자지침서.md new file mode 100644 index 0000000..7c7af4f --- /dev/null +++ b/04_운영자지침서.md @@ -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 " \ + -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 기준으로 작성되었습니다.* diff --git a/05_설치가이드_리눅스.md b/05_설치가이드_리눅스.md new file mode 100644 index 0000000..edc06d6 --- /dev/null +++ b/05_설치가이드_리눅스.md @@ -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 +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`를 확인하거나 개발팀에 문의하세요.* diff --git a/06_설치가이드_윈도우서버.md b/06_설치가이드_윈도우서버.md new file mode 100644 index 0000000..4f073bc --- /dev/null +++ b/06_설치가이드_윈도우서버.md @@ -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 + + + + + + + + + + + + + +``` + +--- + +## 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 + +# 프로세스 종료 (필요 시) +Stop-Process -Id -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`를 확인하거나 개발팀에 문의하세요.* diff --git a/07_SI프로젝트관리_분석설계서.md b/07_SI프로젝트관리_분석설계서.md new file mode 100644 index 0000000..9e4de8d --- /dev/null +++ b/07_SI프로젝트관리_분석설계서.md @@ -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 수) +``` + +--- + +*본 설계서는 구현 진행에 따라 갱신됩니다.* diff --git a/08_AI에이전트_Paperclip_설계서.md b/08_AI에이전트_Paperclip_설계서.md new file mode 100644 index 0000000..1f9417f --- /dev/null +++ b/08_AI에이전트_Paperclip_설계서.md @@ -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 " \ + -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 " +``` + +### 11.3 승인 처리 + +```bash +# 승인 +curl -X PATCH http://localhost:8001/api/agents/approvals/5/review \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"status": "APPROVED", "notes": "내용 확인 후 승인"}' + +# 거부 +curl -X PATCH http://localhost:8001/api/agents/approvals/5/review \ + -H "Authorization: Bearer " \ + -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* diff --git a/09_확장개발_Priority1_UI구현.md b/09_확장개발_Priority1_UI구현.md new file mode 100644 index 0000000..a4057be --- /dev/null +++ b/09_확장개발_Priority1_UI구현.md @@ -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 +``` diff --git a/10_확장개발_Priority2_코어모듈.md b/10_확장개발_Priority2_코어모듈.md new file mode 100644 index 0000000..be5e346 --- /dev/null +++ b/10_확장개발_Priority2_코어모듈.md @@ -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 상세 모달 diff --git a/11_확장개발_Priority3_AI에이전트.md b/11_확장개발_Priority3_AI에이전트.md new file mode 100644 index 0000000..7db8918 --- /dev/null +++ b/11_확장개발_Priority3_AI에이전트.md @@ -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 +``` diff --git a/12_확장개발_Priority4_인프라.md b/12_확장개발_Priority4_인프라.md new file mode 100644 index 0000000..a31474d --- /dev/null +++ b/12_확장개발_Priority4_인프라.md @@ -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}" +``` diff --git a/13_확장개발_Priority5_외부연동.md b/13_확장개발_Priority5_외부연동.md new file mode 100644 index 0000000..f0260be --- /dev/null +++ b/13_확장개발_Priority5_외부연동.md @@ -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= +``` + +--- + +## 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); } +}; +``` diff --git a/14_라이선스_키_발급_가이드.md b/14_라이선스_키_발급_가이드.md new file mode 100644 index 0000000..07d6cb2 --- /dev/null +++ b/14_라이선스_키_발급_가이드.md @@ -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= +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 ` 헤더 필요. +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년 | diff --git a/GUARDiA_Paperclip_AI에이전트_구현보고서.pptx b/GUARDiA_Paperclip_AI에이전트_구현보고서.pptx new file mode 100644 index 0000000..10ecbb9 Binary files /dev/null and b/GUARDiA_Paperclip_AI에이전트_구현보고서.pptx differ diff --git a/gen_ppt.js b/gen_ppt.js new file mode 100644 index 0000000..58afd84 --- /dev/null +++ b/gen_ppt.js @@ -0,0 +1,1209 @@ +// GUARDiA × Paperclip AI 에이전트 구현 보고서 PPT 생성 +const pptxgen = require("pptxgenjs"); + +const pres = new pptxgen(); +pres.layout = "LAYOUT_16x9"; +pres.author = "GUARDiA Team"; +pres.title = "GUARDiA × Paperclip AI 에이전트 구현 보고서"; + +// ─── 색상 팔레트 ───────────────────────────────────────── +const C = { + navyDark: "0D1B3E", // 타이틀 배경 + navy: "1E2761", // 사이드바 + blue: "2E5BBA", // 강조 파랑 + lightBlue: "4A90D9", // 서브 강조 + ice: "CADCFC", // 연한 파랑 (배경 요소) + white: "FFFFFF", + offWhite: "F4F7FF", // 콘텐츠 슬라이드 배경 + gray: "64748B", + lightGray: "E2E8F0", + green: "059669", + amber: "D97706", + red: "DC2626", + purple: "7C3AED", + teal: "0891B2", + orange: "EA580C", +}; + +const makeShadow = () => ({ + type: "outer", blur: 6, offset: 3, angle: 135, color: "000000", opacity: 0.12 +}); + +// ─── 슬라이드 헬퍼 ─────────────────────────────────────── +function addTitleSlide(bgColor, title, subtitle, badge) { + const slide = pres.addSlide(); + slide.background = { color: bgColor }; + + // 좌측 강조 바 + slide.addShape(pres.shapes.RECTANGLE, { + x: 0, y: 0, w: 0.12, h: 5.625, + fill: { color: C.lightBlue }, line: { color: C.lightBlue } + }); + + // 우측 장식 원 + slide.addShape(pres.shapes.OVAL, { + x: 7.5, y: -1.2, w: 4.5, h: 4.5, + fill: { color: C.navy, transparency: 60 }, line: { color: C.navy, transparency: 60 } + }); + slide.addShape(pres.shapes.OVAL, { + x: 8.5, y: 2.8, w: 2.5, h: 2.5, + fill: { color: C.blue, transparency: 70 }, line: { color: C.blue, transparency: 70 } + }); + + if (badge) { + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.4, y: 1.3, w: 2.2, h: 0.38, + fill: { color: C.lightBlue }, line: { color: C.lightBlue } + }); + slide.addText(badge, { + x: 0.4, y: 1.3, w: 2.2, h: 0.38, + fontSize: 11, color: C.white, bold: true, align: "center", valign: "middle", margin: 0 + }); + } + + slide.addText(title, { + x: 0.4, y: 1.9, w: 8.5, h: 1.4, + fontSize: 36, color: C.white, bold: true, align: "left", fontFace: "Calibri" + }); + + if (subtitle) { + slide.addText(subtitle, { + x: 0.4, y: 3.45, w: 7.5, h: 0.7, + fontSize: 16, color: C.ice, align: "left", fontFace: "Calibri" + }); + } + + // 하단 날짜 라인 + slide.addShape(pres.shapes.LINE, { + x: 0.4, y: 5.0, w: 9.2, h: 0, + line: { color: C.lightBlue, width: 1 } + }); + slide.addText("2026.05.25 | GUARDiA ITSM", { + x: 0.4, y: 5.1, w: 5, h: 0.35, + fontSize: 10, color: "8FAADC", align: "left", fontFace: "Calibri" + }); + + return slide; +} + +function addContentSlide(title) { + const slide = pres.addSlide(); + slide.background = { color: C.offWhite }; + + // 상단 헤더 바 + slide.addShape(pres.shapes.RECTANGLE, { + x: 0, y: 0, w: 10, h: 0.72, + fill: { color: C.navy }, line: { color: C.navy } + }); + + // 좌측 포인트 바 + slide.addShape(pres.shapes.RECTANGLE, { + x: 0, y: 0.72, w: 0.08, h: 4.9, + fill: { color: C.lightBlue }, line: { color: C.lightBlue } + }); + + slide.addText(title, { + x: 0.25, y: 0, w: 9.5, h: 0.72, + fontSize: 20, color: C.white, bold: true, align: "left", valign: "middle", + fontFace: "Calibri", margin: 0 + }); + + return slide; +} + +function addSectionSlide(number, title, subtitle) { + const slide = pres.addSlide(); + slide.background = { color: C.blue }; + + slide.addShape(pres.shapes.RECTANGLE, { + x: 0, y: 0, w: 0.08, h: 5.625, + fill: { color: C.lightBlue }, line: { color: C.lightBlue } + }); + + slide.addText(`Phase ${number}`, { + x: 0.4, y: 1.4, w: 9, h: 0.7, + fontSize: 16, color: C.ice, bold: false, fontFace: "Calibri" + }); + slide.addText(title, { + x: 0.4, y: 2.0, w: 9, h: 1.2, + fontSize: 32, color: C.white, bold: true, fontFace: "Calibri" + }); + if (subtitle) { + slide.addText(subtitle, { + x: 0.4, y: 3.3, w: 8, h: 0.7, + fontSize: 15, color: C.ice, fontFace: "Calibri" + }); + } + return slide; +} + +function card(slide, x, y, w, h, headerColor, headerText, bodyLines) { + // 카드 본체 + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w, h, + fill: { color: C.white }, + line: { color: C.lightGray, width: 1 }, + shadow: makeShadow() + }); + // 카드 상단 컬러 바 + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w, h: 0.32, + fill: { color: headerColor }, + line: { color: headerColor } + }); + slide.addText(headerText, { + x, y, w, h: 0.32, + fontSize: 11, color: C.white, bold: true, + align: "center", valign: "middle", fontFace: "Calibri", margin: 0 + }); + + const textArr = bodyLines.map((line, i) => ({ + text: line, + options: { + fontSize: 10.5, + color: i === 0 ? C.gray : "1E293B", + bold: i === 0, + breakLine: true + } + })); + // 마지막 요소는 breakLine 제거 + if (textArr.length > 0) textArr[textArr.length - 1].options.breakLine = false; + + slide.addText(textArr, { + x: x + 0.1, y: y + 0.38, w: w - 0.2, h: h - 0.45, + valign: "top", fontFace: "Calibri" + }); +} + +function infoBox(slide, x, y, w, h, accentColor, title, desc) { + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w, h, + fill: { color: C.white }, + line: { color: accentColor, width: 2 }, + shadow: makeShadow() + }); + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 0.1, h, + fill: { color: accentColor }, + line: { color: accentColor } + }); + slide.addText(title, { + x: x + 0.18, y: y + 0.07, w: w - 0.25, h: 0.3, + fontSize: 11.5, color: "1E293B", bold: true, fontFace: "Calibri" + }); + slide.addText(desc, { + x: x + 0.18, y: y + 0.36, w: w - 0.25, h: h - 0.42, + fontSize: 10.5, color: C.gray, fontFace: "Calibri", valign: "top" + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 1: 타이틀 +// ═══════════════════════════════════════════════════════ +addTitleSlide( + C.navyDark, + "GUARDiA × Paperclip\nAI 에이전트 구현 보고서", + "Phase 1~4 설계·구현·테스트 완료 보고", + "2026.05.25 완료" +); + +// ═══════════════════════════════════════════════════════ +// Slide 2: 목차 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("목 차"); + + const items = [ + { n: "01", t: "GUARDiA ITSM 개요 & 배경", c: C.blue }, + { n: "02", t: "Paperclip 프레임워크 소개", c: C.purple }, + { n: "03", t: "Phase 1 — Paperclip 개발 도구 설정", c: C.teal }, + { n: "04", t: "Phase 2 — Ollama 로컬 LLM 구성", c: C.green }, + { n: "05", t: "Phase 3 — GUARDiA 에이전트 엔진", c: C.orange }, + { n: "06", t: "Phase 4 — 자율 운영 대시보드", c: C.blue }, + { n: "07", t: "테스트 결과 요약", c: C.green }, + { n: "08", t: "보안 제약사항 & 향후 로드맵", c: C.navy }, + ]; + + items.forEach((item, i) => { + const col = i < 4 ? 0 : 1; + const row = i % 4; + const x = 0.3 + col * 4.85; + const y = 0.88 + row * 1.1; + + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 4.5, h: 0.9, + fill: { color: C.white }, + line: { color: C.lightGray, width: 1 }, + shadow: makeShadow() + }); + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 0.55, h: 0.9, + fill: { color: item.c }, + line: { color: item.c } + }); + slide.addText(item.n, { + x, y, w: 0.55, h: 0.9, + fontSize: 16, color: C.white, bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + slide.addText(item.t, { + x: x + 0.62, y, w: 3.8, h: 0.9, + fontSize: 12.5, color: "1E293B", bold: false, + align: "left", valign: "middle", fontFace: "Calibri" + }); + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 3: GUARDiA ITSM 개요 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("01 GUARDiA ITSM 개요"); + + // 왼쪽: 기존 기능 + slide.addText("기존 GUARDiA 기능", { + x: 0.25, y: 0.85, w: 4.5, h: 0.4, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const existingFeatures = [ + ["SR 접수·처리", "서비스 요청 워크플로우"], + ["SSL 인증서 관리", "만료 감시 & 갱신 알림"], + ["PM 정기점검", "예방 유지보수 일정"], + ["SI 프로젝트 관리", "WBS·이슈·리스크·테스트"], + ["CMDB", "서버 자산 관리"], + ["메신저·알림", "내부 커뮤니케이션"], + ]; + + existingFeatures.forEach(([title, desc], i) => { + const y = 1.3 + i * 0.63; + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.25, y, w: 4.5, h: 0.55, + fill: { color: C.offWhite }, line: { color: C.lightGray, width: 1 } + }); + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.25, y, w: 0.07, h: 0.55, + fill: { color: C.blue }, line: { color: C.blue } + }); + slide.addText([ + { text: title, options: { bold: true, color: "1E293B", breakLine: true } }, + { text: desc, options: { color: C.gray } } + ], { + x: 0.4, y, w: 4.2, h: 0.55, + fontSize: 10.5, valign: "middle", fontFace: "Calibri" + }); + }); + + // 화살표 + slide.addShape(pres.shapes.RECTANGLE, { + x: 4.85, y: 2.5, w: 0.35, h: 0.6, + fill: { color: C.lightBlue }, line: { color: C.lightBlue } + }); + slide.addText("AI\n추가", { + x: 4.82, y: 2.5, w: 0.41, h: 0.6, + fontSize: 9, color: C.white, bold: true, align: "center", valign: "middle", fontFace: "Calibri", margin: 0 + }); + + // 오른쪽: AI 추가 목표 + slide.addText("AI 에이전트 추가 목표", { + x: 5.3, y: 0.85, w: 4.4, h: 0.4, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const aiGoals = [ + { icon: "⚡", title: "반복 업무 자동화", desc: "장애 분류, KB 등록, SR 자동 생성", color: C.orange }, + { icon: "👁", title: "능동적 모니터링", desc: "SSL·WBS·PM 상태를 AI가 주기 감시", color: C.blue }, + { icon: "🤝", title: "사람-AI 협업", desc: "고위험 액션은 사람 승인 게이트 통과", color: C.green }, + { icon: "🔒", title: "완전 온프레미스", desc: "Ollama 로컬 LLM, 외부 API 완전 차단", color: C.purple }, + ]; + + aiGoals.forEach((g, i) => { + const y = 1.3 + i * 0.98; + slide.addShape(pres.shapes.RECTANGLE, { + x: 5.3, y, w: 4.4, h: 0.85, + fill: { color: C.white }, line: { color: g.color, width: 2 }, + shadow: makeShadow() + }); + slide.addShape(pres.shapes.RECTANGLE, { + x: 5.3, y, w: 0.1, h: 0.85, + fill: { color: g.color }, line: { color: g.color } + }); + slide.addText([ + { text: `${g.icon} ${g.title}`, options: { bold: true, color: "1E293B", breakLine: true } }, + { text: g.desc, options: { color: C.gray } } + ], { + x: 5.48, y, w: 4.1, h: 0.85, + fontSize: 11, valign: "middle", fontFace: "Calibri" + }); + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 4: Paperclip 프레임워크 소개 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("02 Paperclip 프레임워크 소개"); + + // 메인 설명 + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.25, y: 0.85, w: 9.5, h: 0.72, + fill: { color: "EEF2FF" }, line: { color: C.lightBlue, width: 1 } + }); + slide.addText([ + { text: "Paperclip", options: { bold: true, color: C.blue } }, + { text: " — AI 에이전트 오케스트레이션 오픈소스 프레임워크 (github.com/paperclipai/paperclip)", options: { color: "1E293B" } } + ], { + x: 0.35, y: 0.85, w: 9.3, h: 0.72, + fontSize: 13, valign: "middle", fontFace: "Calibri" + }); + + const features = [ + { title: "조직도 구조", desc: "CEO → CTO → 개발자/QA\n계층적 에이전트 관리", color: C.purple, icon: "🏢" }, + { title: "하트비트 시스템", desc: "에이전트가 주기적으로\n깨어나 작업 수행 후 대기", color: C.blue, icon: "💓" }, + { title: "이슈 추적", desc: "GitHub 스타일\n태스크/이슈 관리", color: C.teal, icon: "📋" }, + { title: "거버넌스 게이트", desc: "위험 수준에 따른\n사람 승인 워크플로우", color: C.orange, icon: "🔐" }, + ]; + + features.forEach((f, i) => { + const x = 0.25 + i * 2.4; + slide.addShape(pres.shapes.RECTANGLE, { + x, y: 1.72, w: 2.25, h: 1.95, + fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, + shadow: makeShadow() + }); + slide.addShape(pres.shapes.RECTANGLE, { + x, y: 1.72, w: 2.25, h: 0.45, + fill: { color: f.color }, line: { color: f.color } + }); + slide.addText(`${f.icon} ${f.title}`, { + x, y: 1.72, w: 2.25, h: 0.45, + fontSize: 11, color: C.white, bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + slide.addText(f.desc, { + x: x + 0.1, y: 2.22, w: 2.05, h: 1.4, + fontSize: 11, color: "1E293B", align: "center", valign: "top", fontFace: "Calibri" + }); + }); + + // GUARDiA 적용 전략 + slide.addText("GUARDiA 적용 전략", { + x: 0.25, y: 3.8, w: 9.5, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const strategies = [ + ["개발 시", "Paperclip CLI로 에이전트 페르소나 정의 (CEO/CTO/Dev/QA)", C.purple], + ["런타임", "AgentEngine (Python) 내장 — 외부 Paperclip 서버 불필요", C.blue], + ["LLM", "Ollama localhost:11434 전용 — 외부 LLM API 완전 차단", C.red], + ]; + + strategies.forEach(([label, desc, color], i) => { + const x = 0.25 + i * 3.2; + slide.addShape(pres.shapes.RECTANGLE, { + x, y: 4.2, w: 3.0, h: 1.05, + fill: { color: C.white }, line: { color: color, width: 2 }, + shadow: makeShadow() + }); + slide.addText([ + { text: label, options: { bold: true, color, breakLine: true } }, + { text: desc, options: { color: "1E293B" } } + ], { + x: x + 0.1, y: 4.2, w: 2.8, h: 1.05, + fontSize: 10.5, valign: "middle", fontFace: "Calibri" + }); + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 5: Phase 1 섹션 표지 +// ═══════════════════════════════════════════════════════ +addSectionSlide("1", "Paperclip 개발 도구 설정", "조직도 구성 · 에이전트 페르소나 · 거버넌스 규칙"); + +// ═══════════════════════════════════════════════════════ +// Slide 6: Phase 1 상세 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("03 Phase 1 — Paperclip 개발 도구 설정"); + + // 조직도 트리 + slide.addText("에이전트 조직도", { + x: 0.25, y: 0.85, w: 4.5, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const orgNodes = [ + { label: "CEO", x: 1.9, y: 1.3, color: C.purple }, + { label: "CTO", x: 0.5, y: 2.4, color: C.blue }, + { label: "PM_AGENT", x: 3.3, y: 2.4, color: C.teal }, + { label: "DEVELOPER", x: 0.0, y: 3.5, color: C.green }, + { label: "QA", x: 1.2, y: 3.5, color: C.amber }, + ]; + + // 연결선 + slide.addShape(pres.shapes.LINE, { x: 2.4, y: 1.9, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); + slide.addShape(pres.shapes.LINE, { x: 1.5, y: 2.25, w: 1.8, h: 0, line: { color: C.gray, width: 1.5 } }); + slide.addShape(pres.shapes.LINE, { x: 1.5, y: 2.25, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); + slide.addShape(pres.shapes.LINE, { x: 3.3, y: 2.25, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); + slide.addShape(pres.shapes.LINE, { x: 0.65, y: 3.05, w: 1.55, h: 0, line: { color: C.gray, width: 1.5 } }); + slide.addShape(pres.shapes.LINE, { x: 0.65, y: 3.05, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); + slide.addShape(pres.shapes.LINE, { x: 1.7, y: 3.05, w: 0, h: 0.5, line: { color: C.gray, width: 1.5 } }); + + orgNodes.forEach(node => { + slide.addShape(pres.shapes.RECTANGLE, { + x: node.x, y: node.y, w: 1.2, h: 0.52, + fill: { color: node.color }, line: { color: node.color }, + shadow: makeShadow() + }); + slide.addText(node.label, { + x: node.x, y: node.y, w: 1.2, h: 0.52, + fontSize: 10, color: C.white, bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + }); + + // 파일 구조 + slide.addText("생성 파일 구조", { + x: 5.0, y: 0.85, w: 4.7, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + slide.addShape(pres.shapes.RECTANGLE, { + x: 5.0, y: 1.25, w: 4.7, h: 3.2, + fill: { color: "1E293B" }, line: { color: "334155", width: 1 } + }); + slide.addText([ + { text: "C:\\GUARDiA\\paperclip\\", options: { color: C.lightBlue, bold: true, breakLine: true } }, + { text: "├─ paperclip.config.json", options: { color: "A3E635", breakLine: true } }, + { text: "│ (조직도 · LLM 설정 · 거버넌스)", options: { color: "94A3B8", breakLine: true } }, + { text: "├─ README.md", options: { color: "A3E635", breakLine: true } }, + { text: "└─ agents/", options: { color: "FB923C", breakLine: true } }, + { text: " ├─ ceo.md", options: { color: "A3E635", breakLine: true } }, + { text: " ├─ cto.md", options: { color: "A3E635", breakLine: true } }, + { text: " ├─ developer.md", options: { color: "A3E635", breakLine: true } }, + { text: " └─ qa.md", options: { color: "A3E635" } }, + ], { + x: 5.15, y: 1.35, w: 4.4, h: 3.0, + fontSize: 10.5, valign: "top", fontFace: "Courier New" + }); + + // 거버넌스 표 + slide.addText("거버넌스 규칙", { + x: 0.25, y: 4.1, w: 4.5, h: 0.32, + fontSize: 11, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const govRows = [ + [{ text: "액션", options: { bold: true, fill: { color: C.navy }, color: C.white } }, + { text: "승인 방식", options: { bold: true, fill: { color: C.navy }, color: C.white } }], + ["code_commit / deploy", "사람 승인 필수"], + ["delete_data", "CEO 승인 필수"], + ["CRITICAL 장애 분류", "담당자 승인 필수"], + ["KB 등록 / SSL SR", "자동 승인"], + ]; + + slide.addTable(govRows, { + x: 0.25, y: 4.45, w: 4.5, h: 1.0, + colW: [2.5, 2.0], + border: { pt: 1, color: C.lightGray }, + fontSize: 10, fontFace: "Calibri", + fill: { color: C.white } + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 7: Phase 2 섹션 표지 +// ═══════════════════════════════════════════════════════ +addSectionSlide("2", "Ollama 로컬 LLM 설정", "guardia-agent 커스텀 모델 · 보안 온프레미스 추론"); + +// ═══════════════════════════════════════════════════════ +// Slide 8: Phase 2 상세 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("04 Phase 2 — Ollama 로컬 LLM"); + + // 왼쪽: 아키텍처 + slide.addText("추론 아키텍처", { + x: 0.25, y: 0.85, w: 4.8, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const archItems = [ + { label: "GUARDiA AgentEngine", color: C.blue, y: 1.3 }, + { label: "OllamaClient (HTTP)", color: C.teal, y: 2.0 }, + { label: "Ollama Server :11434", color: C.green, y: 2.7 }, + { label: "guardia-agent 모델", color: C.purple, y: 3.4 }, + ]; + + archItems.forEach((item, i) => { + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.5, y: item.y, w: 3.8, h: 0.55, + fill: { color: item.color }, line: { color: item.color }, + shadow: makeShadow() + }); + slide.addText(item.label, { + x: 0.5, y: item.y, w: 3.8, h: 0.55, + fontSize: 12, color: C.white, bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + if (i < archItems.length - 1) { + slide.addShape(pres.shapes.RECTANGLE, { + x: 2.25, y: item.y + 0.55, w: 0.3, h: 0.15, + fill: { color: C.gray }, line: { color: C.gray } + }); + } + }); + + // 보안 배지 + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.25, y: 4.1, w: 4.5, h: 0.9, + fill: { color: "FEF2F2" }, line: { color: C.red, width: 2 } + }); + slide.addText([ + { text: "🔒 외부 LLM API 완전 차단", options: { bold: true, color: C.red, breakLine: true } }, + { text: "모든 AI 추론은 localhost:11434 (Ollama) 만 허용", options: { color: "7F1D1D" } } + ], { + x: 0.4, y: 4.1, w: 4.3, h: 0.9, + fontSize: 12, valign: "middle", fontFace: "Calibri" + }); + + // 오른쪽: 모델 설정 + slide.addText("guardia-agent 모델 파라미터", { + x: 5.05, y: 0.85, w: 4.7, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + slide.addShape(pres.shapes.RECTANGLE, { + x: 5.05, y: 1.25, w: 4.7, h: 2.0, + fill: { color: "1E293B" }, line: { color: "334155", width: 1 } + }); + slide.addText([ + { text: "FROM llama3.1:8b", options: { color: "A3E635", breakLine: true } }, + { text: "SYSTEM \"\"\"", options: { color: "FB923C", breakLine: true } }, + { text: " GUARDiA ITSM AI 운영 에이전트", options: { color: "94A3B8", breakLine: true } }, + { text: " 한국어 응답 · ITSM 전문화", options: { color: "94A3B8", breakLine: true } }, + { text: " 외부 API 호출 금지", options: { color: C.red, breakLine: true } }, + { text: "\"\"\"", options: { color: "FB923C", breakLine: true } }, + { text: "PARAMETER temperature 0.2", options: { color: "38BDF8", breakLine: true } }, + { text: "PARAMETER num_predict 2048", options: { color: "38BDF8" } }, + ], { + x: 5.2, y: 1.35, w: 4.4, h: 1.8, + fontSize: 10.5, valign: "top", fontFace: "Courier New" + }); + + // OllamaClient API + slide.addText("OllamaClient API", { + x: 5.05, y: 3.35, w: 4.7, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const methods = [ + ["health_check()", "서버 상태 확인"], + ["resolve_model(preferred)", "fallback 모델 선택"], + ["json_generate(prompt)", "JSON 추출 (코드블록 자동 제거)"], + ["pull_model(model)", "모델 다운로드"], + ]; + + methods.forEach(([method, desc], i) => { + slide.addShape(pres.shapes.RECTANGLE, { + x: 5.05, y: 3.75 + i * 0.43, w: 4.7, h: 0.38, + fill: { color: i % 2 === 0 ? C.offWhite : C.white }, + line: { color: C.lightGray, width: 1 } + }); + slide.addText([ + { text: method, options: { bold: true, color: C.blue } }, + { text: " → " + desc, options: { color: C.gray } } + ], { + x: 5.15, y: 3.75 + i * 0.43, w: 4.5, h: 0.38, + fontSize: 10, valign: "middle", fontFace: "Calibri" + }); + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 9: Phase 3 섹션 표지 +// ═══════════════════════════════════════════════════════ +addSectionSlide("3", "GUARDiA 에이전트 엔진", "6종 자율 에이전트 · 하트비트 사이클 · 승인 게이트"); + +// ═══════════════════════════════════════════════════════ +// Slide 10: Phase 3 — 에이전트 종류 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("05 Phase 3 — 6종 에이전트 역할"); + + const agents = [ + { + role: "INCIDENT_TRIAGE", + schedule: "매 15분", + color: C.orange, + lines: ["미배정 장애 자동 분류", "severity / category / reason", "CRITICAL → 사람 승인"] + }, + { + role: "KB_CURATOR", + schedule: "매시간 정각", + color: C.teal, + lines: ["완료 SR → KB 자동 생성", "증상·원인·해결책 초안", "published=False (검토 대기)"] + }, + { + role: "SSL_WATCHER", + schedule: "매일 08:30", + color: C.red, + lines: ["SSL 만료 0~30일 서버 감시", "자동 갱신 SR 생성", "7일↓=CRITICAL, 30일↓=HIGH"] + }, + { + role: "WBS_MONITOR", + schedule: "매일 08:00", + color: C.blue, + lines: ["SI WBS 지연 감지", "3일+ 주의, 10일+ CRITICAL", "리스크 자동 등록"] + }, + { + role: "PM_SUGGESTER", + schedule: "매일 09:00", + color: C.green, + lines: ["PM 미등록 서버 탐지", "권장 점검 일정 제안", "AgentTask 기록"] + }, + { + role: "DEVELOPER", + schedule: "수동 트리거", + color: C.purple, + lines: ["PENDING 태스크 처리", "LLM 코드 생성", "CODE_CHANGE → 사람 승인"] + }, + ]; + + agents.forEach((a, i) => { + const col = i % 3; + const row = Math.floor(i / 3); + const x = 0.25 + col * 3.25; + const y = 0.88 + row * 2.35; + + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 3.1, h: 2.18, + fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, + shadow: makeShadow() + }); + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 3.1, h: 0.5, + fill: { color: a.color }, line: { color: a.color } + }); + slide.addText(a.role, { + x, y, w: 2.3, h: 0.5, + fontSize: 11, color: C.white, bold: true, + align: "left", valign: "middle", fontFace: "Calibri", + indent: 0.1, margin: 0 + }); + slide.addText(a.schedule, { + x: x + 2.3, y, w: 0.8, h: 0.5, + fontSize: 9, color: "FFEAA7", + align: "center", valign: "middle", fontFace: "Calibri", margin: 0 + }); + + a.lines.forEach((line, li) => { + slide.addText([ + { text: "▸ ", options: { color: a.color, bold: true } }, + { text: line, options: { color: "1E293B" } } + ], { + x: x + 0.1, y: y + 0.55 + li * 0.52, w: 2.9, h: 0.48, + fontSize: 10.5, valign: "top", fontFace: "Calibri" + }); + }); + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 11: Phase 3 — 하트비트 & 승인 게이트 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("05 Phase 3 — 하트비트 & 승인 게이트"); + + // 하트비트 플로우 + slide.addText("하트비트 사이클", { + x: 0.25, y: 0.85, w: 4.7, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const flowSteps = [ + { label: "APScheduler\nCron 트리거", color: C.blue, y: 1.3 }, + { label: "status = ACTIVE", color: C.teal, y: 2.05 }, + { label: "Ollama\nhealth_check()", color: C.green, y: 2.8 }, + { label: "_handler() 실행", color: C.orange, y: 3.55 }, + { label: "status = IDLE\n(완료)", color: C.purple, y: 4.3 }, + ]; + + flowSteps.forEach((step, i) => { + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.5, y: step.y, w: 3.0, h: 0.6, + fill: { color: step.color }, line: { color: step.color }, + shadow: makeShadow() + }); + slide.addText(step.label, { + x: 0.5, y: step.y, w: 3.0, h: 0.6, + fontSize: 10.5, color: C.white, bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + if (i < flowSteps.length - 1) { + slide.addShape(pres.shapes.RECTANGLE, { + x: 1.85, y: step.y + 0.6, w: 0.3, h: 0.15, + fill: { color: C.gray }, line: { color: C.gray } + }); + } + }); + + // 오른쪽: 승인 게이트 + slide.addText("승인 게이트 설계", { + x: 4.0, y: 0.85, w: 5.7, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + // AUTO_APPROVED + slide.addShape(pres.shapes.RECTANGLE, { + x: 4.0, y: 1.25, w: 5.7, h: 1.3, + fill: { color: "F0FDF4" }, line: { color: C.green, width: 2 } + }); + slide.addShape(pres.shapes.RECTANGLE, { + x: 4.0, y: 1.25, w: 0.1, h: 1.3, + fill: { color: C.green }, line: { color: C.green } + }); + slide.addText([ + { text: "✅ AUTO_APPROVED ", options: { bold: true, color: C.green } }, + { text: "— 사람 개입 없이 즉시 실행", options: { color: "166534" } } + ], { + x: 4.18, y: 1.3, w: 5.4, h: 0.35, + fontSize: 12, fontFace: "Calibri" + }); + slide.addText([ + { text: "• KB 등록 초안 생성", options: { breakLine: true } }, + { text: "• SSL 갱신 SR 생성 ", options: { breakLine: true } }, + { text: "• PM 일정 제안 ", options: { breakLine: true } }, + { text: "• WBS 리스크 등록 (일반)", options: {} }, + ], { + x: 4.18, y: 1.68, w: 5.4, h: 0.82, + fontSize: 10.5, color: "166534", valign: "top", fontFace: "Calibri" + }); + + // PENDING (사람 승인) + slide.addShape(pres.shapes.RECTANGLE, { + x: 4.0, y: 2.7, w: 5.7, h: 1.5, + fill: { color: "FFF7ED" }, line: { color: C.red, width: 2 } + }); + slide.addShape(pres.shapes.RECTANGLE, { + x: 4.0, y: 2.7, w: 0.1, h: 1.5, + fill: { color: C.red }, line: { color: C.red } + }); + slide.addText([ + { text: "⏳ PENDING ", options: { bold: true, color: C.red } }, + { text: "— 담당자 승인 후 실행", options: { color: "7C2D12" } } + ], { + x: 4.18, y: 2.75, w: 5.4, h: 0.35, + fontSize: 12, fontFace: "Calibri" + }); + slide.addText([ + { text: "• CRITICAL 장애 분류 적용", options: { breakLine: true } }, + { text: "• CODE_CHANGE (개발자 에이전트)", options: { breakLine: true } }, + { text: "• CRITICAL 리스크 등록 (WBS 10일+ 지연)", options: { breakLine: true } }, + { text: "• code_commit / deploy / delete_data", options: {} } + ], { + x: 4.18, y: 3.12, w: 5.4, h: 1.0, + fontSize: 10.5, color: "7C2D12", valign: "top", fontFace: "Calibri" + }); + + // 데이터 모델 요약 + slide.addText("DB 테이블 구조", { + x: 4.0, y: 4.35, w: 5.7, h: 0.32, + fontSize: 11, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const tables = [ + ["tb_agent_config", "에이전트 설정 · 상태 · 통계"], + ["tb_agent_task", "실행된 태스크 · LLM 입출력"], + ["tb_agent_approval", "승인 대기·완료 기록"], + ]; + tables.forEach(([t, d], i) => { + slide.addShape(pres.shapes.RECTANGLE, { + x: 4.0 + i * 1.9, y: 4.7, w: 1.8, h: 0.7, + fill: { color: C.navy }, line: { color: C.navy }, + shadow: makeShadow() + }); + slide.addText([ + { text: t, options: { bold: true, color: C.ice, breakLine: true } }, + { text: d, options: { color: "8FAADC" } } + ], { + x: 4.05 + i * 1.9, y: 4.72, w: 1.7, h: 0.66, + fontSize: 9, align: "center", valign: "middle", fontFace: "Calibri" + }); + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 12: Phase 4 섹션 표지 +// ═══════════════════════════════════════════════════════ +addSectionSlide("4", "자율 운영 대시보드", "agents.html SPA · 실시간 상태 · 승인 워크플로우"); + +// ═══════════════════════════════════════════════════════ +// Slide 13: Phase 4 상세 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("06 Phase 4 — 자율 운영 대시보드"); + + // 접근 경로 배너 + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.25, y: 0.85, w: 9.5, h: 0.5, + fill: { color: C.navy }, line: { color: C.navy } + }); + slide.addText([ + { text: "접근: ", options: { color: C.ice } }, + { text: "http://localhost:8001/agents", options: { bold: true, color: "A3E635" } }, + { text: " | 자동 갱신: 30초 간격", options: { color: C.ice } } + ], { + x: 0.35, y: 0.85, w: 9.3, h: 0.5, + fontSize: 12, valign: "middle", fontFace: "Calibri" + }); + + // 대시보드 구성 카드 + const panels = [ + { title: "LLM 상태 배너", desc: "Ollama 온라인/오프라인\n실시간 연결 상태 표시", color: C.teal }, + { title: "통계 카드 (5종)", desc: "총 에이전트·활성·오늘\n태스크·토큰·승인 대기", color: C.blue }, + { title: "에이전트 탭", desc: "역할 배지 + 상태 펄스\n하트비트/정지/재개 버튼", color: C.purple }, + { title: "조직도 탭", desc: "계층적 트리 렌더링\n에이전트 관계 시각화", color: C.orange }, + { title: "승인 대기 탭", desc: "PENDING 승인 목록\nCRITICAL 강조 표시", color: C.red }, + { title: "태스크 피드 탭", desc: "전체 에이전트 태스크\n실시간 피드 통합 뷰", color: C.green }, + ]; + + panels.forEach((p, i) => { + const col = i % 3; + const row = Math.floor(i / 3); + const x = 0.25 + col * 3.25; + const y = 1.48 + row * 1.85; + + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 3.1, h: 1.65, + fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, + shadow: makeShadow() + }); + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 3.1, h: 0.4, + fill: { color: p.color }, line: { color: p.color } + }); + slide.addText(p.title, { + x, y, w: 3.1, h: 0.4, + fontSize: 11, color: C.white, bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + slide.addText(p.desc, { + x: x + 0.1, y: y + 0.45, w: 2.9, h: 1.15, + fontSize: 11, color: "1E293B", align: "center", valign: "middle", fontFace: "Calibri" + }); + }); + + // API 엔드포인트 수 + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.25, y: 5.15, w: 9.5, h: 0.35, + fill: { color: "EEF2FF" }, line: { color: C.lightBlue, width: 1 } + }); + slide.addText("REST API: 16개 엔드포인트 | GET·POST·PATCH·DELETE | CUSTOMER 역할 전체 차단 | RBAC 기반 권한 제어", { + x: 0.35, y: 5.15, w: 9.3, h: 0.35, + fontSize: 10.5, color: C.blue, align: "center", valign: "middle", fontFace: "Calibri" + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 14: 테스트 결과 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("07 테스트 결과 요약"); + + // 구문 검사 결과 + slide.addText("구문 검사 (Python Syntax Check)", { + x: 0.25, y: 0.85, w: 9.5, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const syntaxFiles = [ + "models.py", "main.py", "core/llm_client.py", + "core/agents.py", "core/scheduler.py", "routers/agents.py" + ]; + + syntaxFiles.forEach((f, i) => { + const x = 0.25 + (i % 3) * 3.25; + const y = 1.25 + Math.floor(i / 3) * 0.6; + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 3.1, h: 0.5, + fill: { color: "F0FDF4" }, line: { color: C.green, width: 1 } + }); + slide.addText([ + { text: "✅ ", options: { color: C.green, bold: true } }, + { text: f, options: { color: "1E293B" } } + ], { + x: x + 0.1, y, w: 2.9, h: 0.5, + fontSize: 11, valign: "middle", fontFace: "Calibri" + }); + }); + + // 심볼 검증 + slide.addText("심볼 검증", { + x: 0.25, y: 2.55, w: 4.7, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const symbols = [ + ["AgentRole enum", "10가지 역할", C.purple], + ["AgentEngine 핸들러", "6개 구현", C.orange], + ["OllamaClient 메서드", "7개 구현", C.teal], + ["API 엔드포인트", "16개 라우터", C.blue], + ["스케줄러 잡", "9개 (기존4+신규5)", C.green], + ["main.py 라우터", "34개 등록", C.navy], + ]; + + symbols.forEach((s, i) => { + const x = 0.25 + (i % 2) * 2.45; + const y = 2.95 + Math.floor(i / 2) * 0.6; + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 2.3, h: 0.52, + fill: { color: C.white }, line: { color: s[2], width: 2 }, + shadow: makeShadow() + }); + slide.addText([ + { text: s[0] + "\n", options: { bold: true, color: "1E293B", fontSize: 10 } }, + { text: s[1], options: { color: s[2], bold: true, fontSize: 11 } } + ], { + x: x + 0.08, y, w: 2.14, h: 0.52, + valign: "middle", fontFace: "Calibri" + }); + }); + + // 스케줄러 잡 목록 + slide.addText("스케줄러 잡 (총 9개)", { + x: 5.2, y: 2.55, w: 4.5, h: 0.35, + fontSize: 13, color: C.navy, bold: true, fontFace: "Calibri" + }); + + const jobs = [ + ["cert_check", "매일 09:00", C.gray], + ["pm_check", "매일 09:05", C.gray], + ["on_call_notify", "매일 08:55", C.gray], + ["batch_cleanup", "매일 02:00", C.gray], + ["agent_incident_triage", "매 15분", C.orange], + ["agent_kb_curator", "매시간", C.teal], + ["agent_ssl_watcher", "매일 08:30", C.red], + ["agent_wbs_monitor", "매일 08:00", C.blue], + ["agent_pm_suggester", "매일 09:00", C.green], + ]; + + jobs.forEach((j, i) => { + const y = 2.95 + i * 0.3; + slide.addShape(pres.shapes.RECTANGLE, { + x: 5.2, y, w: 4.5, h: 0.27, + fill: { color: i < 4 ? C.offWhite : "F0FDF4" }, + line: { color: C.lightGray, width: 1 } + }); + slide.addShape(pres.shapes.RECTANGLE, { + x: 5.2, y, w: 0.07, h: 0.27, + fill: { color: j[2] }, line: { color: j[2] } + }); + slide.addText([ + { text: j[0], options: { bold: i >= 4, color: i >= 4 ? "1E293B" : C.gray } }, + { text: " " + j[1], options: { color: j[2], bold: i >= 4 } } + ], { + x: 5.32, y, w: 4.3, h: 0.27, + fontSize: 9.5, valign: "middle", fontFace: "Calibri" + }); + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 15: 보안 제약사항 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("08 보안 제약사항"); + + const secRules = [ + { + cat: "LLM 보안", + color: C.red, + rules: [ + "외부 LLM API 완전 차단 (OpenAI/Claude 등)", + "Ollama localhost:11434 전용 사용", + "temperature 0.2 고정 (결정론적 응답)", + "HTTP 요청 30초 타임아웃" + ] + }, + { + cat: "서버 정보 보호", + color: C.orange, + rules: [ + "ip_addr — ServerOut 스키마 미포함", + "ssh_user — API 응답 절대 미노출", + "os_pw_enc — AES-256-GCM 암호화 필수", + "file_path — API 응답 완전 제거" + ] + }, + { + cat: "에이전트 액션 제어", + color: C.purple, + rules: [ + "CRITICAL 액션 → 사람 승인 후 실행", + "위험 명령어 차단: rm -rf /, shutdown, mkfs", + "fork bomb 패턴 감지 & 차단", + "경로 순회 방지 (resolve().relative_to)" + ] + }, + { + cat: "접근 제어 (RBAC)", + color: C.blue, + rules: [ + "CUSTOMER 역할 — 에이전트 API 전체 차단", + "에이전트 생성/수정/삭제 — ADMIN만 허용", + "승인 처리 — USER 이상 허용", + "스택트레이스 — API 응답 노출 금지" + ] + } + ]; + + secRules.forEach((sec, i) => { + const x = 0.25 + (i % 2) * 4.9; + const y = 0.88 + Math.floor(i / 2) * 2.35; + + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 4.65, h: 2.2, + fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, + shadow: makeShadow() + }); + slide.addShape(pres.shapes.RECTANGLE, { + x, y, w: 4.65, h: 0.42, + fill: { color: sec.color }, line: { color: sec.color } + }); + slide.addText(sec.cat, { + x, y, w: 4.65, h: 0.42, + fontSize: 12, color: C.white, bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + + const textArr = sec.rules.map((r, ri) => ({ + text: "▸ " + r, + options: { + color: ri === 0 ? "0F172A" : "1E293B", + bold: ri === 0, + fontSize: 10.5, + breakLine: ri < sec.rules.length - 1 + } + })); + slide.addText(textArr, { + x: x + 0.12, y: y + 0.48, w: 4.4, h: 1.65, + valign: "top", fontFace: "Calibri" + }); + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 16: 향후 로드맵 +// ═══════════════════════════════════════════════════════ +{ + const slide = addContentSlide("08 향후 로드맵"); + + const roadmap = [ + { ver: "v1.1", period: "2026 Q3", title: "에이전트 간 메시지 전달", desc: "CEO→CTO 위임 워크플로우\n태스크 자동 위임 체계", color: C.blue }, + { ver: "v1.2", period: "2026 Q3", title: "에이전트 학습", desc: "이전 태스크 기반 파인튜닝\n조직 맞춤형 응답 최적화", color: C.teal }, + { ver: "v1.3", period: "2026 Q4", title: "멀티모달 지원", desc: "스크린샷·로그 이미지 분석\n시각 장애 자동 탐지", color: C.green }, + { ver: "v2.0", period: "2027 Q1", title: "에이전트 마켓플레이스", desc: "커뮤니티 에이전트 공유\nPlug-in 방식 등록/사용", color: C.purple }, + { ver: "v2.1", period: "2027 Q1", title: "엣지 배포", desc: "경량 모델 (llama3.2:1b)\n저사양 서버 지원", color: C.orange }, + ]; + + roadmap.forEach((r, i) => { + const x = 0.25 + i * 1.92; + slide.addShape(pres.shapes.RECTANGLE, { + x, y: 0.88, w: 1.75, h: 4.5, + fill: { color: C.white }, line: { color: C.lightGray, width: 1 }, + shadow: makeShadow() + }); + slide.addShape(pres.shapes.RECTANGLE, { + x, y: 0.88, w: 1.75, h: 0.55, + fill: { color: r.color }, line: { color: r.color } + }); + slide.addText(r.ver, { + x, y: 0.88, w: 1.75, h: 0.55, + fontSize: 16, color: C.white, bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + + slide.addShape(pres.shapes.RECTANGLE, { + x: x + 0.1, y: 1.5, w: 1.55, h: 0.38, + fill: { color: r.color, transparency: 80 }, line: { color: r.color, width: 1 } + }); + slide.addText(r.period, { + x: x + 0.1, y: 1.5, w: 1.55, h: 0.38, + fontSize: 10, color: "1E293B", bold: true, + align: "center", valign: "middle", margin: 0, fontFace: "Calibri" + }); + + slide.addText(r.title, { + x: x + 0.1, y: 2.0, w: 1.55, h: 0.65, + fontSize: 11, color: "0F172A", bold: true, + align: "center", fontFace: "Calibri" + }); + + slide.addShape(pres.shapes.LINE, { + x: x + 0.2, y: 2.7, w: 1.35, h: 0, + line: { color: C.lightGray, width: 1 } + }); + + slide.addText(r.desc, { + x: x + 0.1, y: 2.75, w: 1.55, h: 1.55, + fontSize: 10.5, color: C.gray, + align: "center", valign: "top", fontFace: "Calibri" + }); + }); + + // 하단 현재 완료 배너 + slide.addShape(pres.shapes.RECTANGLE, { + x: 0.25, y: 5.1, w: 9.5, h: 0.38, + fill: { color: C.green }, line: { color: C.green } + }); + slide.addText("현재 완료: Phase 1~4 | 6종 에이전트 | 16개 API | 9개 스케줄러 잡 | 자율 운영 대시보드", { + x: 0.35, y: 5.1, w: 9.3, h: 0.38, + fontSize: 11, color: C.white, bold: true, + align: "center", valign: "middle", fontFace: "Calibri" + }); +} + +// ═══════════════════════════════════════════════════════ +// Slide 17: 마무리 +// ═══════════════════════════════════════════════════════ +{ + const slide = addTitleSlide( + C.navyDark, + "GUARDiA × Paperclip\n구현 완료", + "Phase 1~4 완료 | 온프레미스 AI 자율 운영 체계 구축", + "완료" + ); + + // 요약 통계 카드 + const stats = [ + { num: "6", label: "자율 에이전트", color: C.orange }, + { num: "16", label: "REST API 엔드포인트", color: C.blue }, + { num: "9", label: "스케줄러 잡", color: C.green }, + { num: "100%", label: "구문 검사 통과", color: C.teal }, + ]; + + stats.forEach((s, i) => { + const x = 0.4 + i * 2.3; + slide.addShape(pres.shapes.RECTANGLE, { + x, y: 4.05, w: 2.1, h: 1.3, + fill: { color: s.color, transparency: 15 }, + line: { color: s.color, width: 2 } + }); + slide.addText(s.num, { + x, y: 4.08, w: 2.1, h: 0.7, + fontSize: 28, color: C.white, bold: true, + align: "center", valign: "middle", fontFace: "Calibri", margin: 0 + }); + slide.addText(s.label, { + x, y: 4.75, w: 2.1, h: 0.55, + fontSize: 10, color: C.ice, + align: "center", valign: "top", fontFace: "Calibri", margin: 0 + }); + }); +} + +// ─── 파일 저장 ─────────────────────────────────────── +pres.writeFile({ fileName: "C:/GUARDiA/manual/GUARDiA_Paperclip_AI에이전트_구현보고서.pptx" }) + .then(() => console.log("PPT 생성 완료: GUARDiA_Paperclip_AI에이전트_구현보고서.pptx")) + .catch(err => { console.error("PPT 생성 실패:", err); process.exit(1); });