diff --git a/18_zio서버_설치SW_목록.md b/18_zio서버_설치SW_목록.md new file mode 100644 index 0000000..2729b16 --- /dev/null +++ b/18_zio서버_설치SW_목록.md @@ -0,0 +1,248 @@ +# zio-server 설치 SW 목록 및 구성 명세 + +> **서버**: zio-server | **IP**: zioinfo.co.kr | **작성일**: 2026-05-30 +> **OS**: Ubuntu 24.04.1 LTS (Noble Numbat) | **Provider**: NCloud (네이버 클라우드) + +--- + +## 1. 서버 사양 + +| 항목 | 사양 | +|------|------| +| CPU | AMD EPYC 9454P (2 vCPU) | +| RAM | 7.8 GB | +| Disk | 99 GB (사용 15 GB / 여유 80 GB) | +| OS | Ubuntu 24.04.1 LTS x86_64 | +| 네트워크 | 공인 IP zioinfo.co.kr | + +--- + +## 2. 설치 소프트웨어 목록 + +### 2-1. 런타임 / 언어 + +| SW | 버전 | 설치 경로 | 용도 | +|----|------|-----------|------| +| OpenJDK | 21.0.10 | `/usr/lib/jvm/java-21-openjdk-amd64` | Spring Boot 실행 | +| Node.js | 20.20.2 (LTS) | `/usr/bin/node` | React 빌드 | +| npm | 10.8.2 | `/usr/bin/npm` | 패키지 관리 | +| Python | 3.12.3 | `/usr/bin/python3` | GUARDiA ITSM, 배포 스크립트 | +| Apache Maven | 3.8.7 | `/usr/share/maven` | Spring Boot 빌드 | + +### 2-2. 웹 서버 / 프록시 + +| SW | 버전 | 설치 경로 | 용도 | +|----|------|-----------|------| +| Nginx | 1.24.0 | `/etc/nginx` | 리버스 프록시, 정적 파일 서빙 | + +### 2-3. 데이터베이스 + +| SW | 버전 | 설치 경로 | 용도 | +|----|------|-----------|------| +| PostgreSQL | 16.11 | `/var/lib/postgresql/16` | GUARDiA ITSM DB, Gitea DB | +| SQLite (내장) | 3.45.3 | JAR 내장 | 지오정보기술 홈페이지 DB | + +**PostgreSQL 데이터베이스 목록:** + +| DB 이름 | 소유자 | 용도 | +|---------|--------|------| +| `guardia_db` | guardia | GUARDiA ITSM 운영 DB | +| `zioinfo_db` | guardia | 지오정보기술 예비 DB | +| `gitea_db` | gitea | Gitea Git 서버 DB | + +### 2-4. AI / LLM + +| SW | 버전 | 설치 경로 | 용도 | +|----|------|-----------|------| +| Ollama | 0.24.0 | `/usr/local/bin/ollama` | 온프레미스 LLM 엔진 | +| Llama3:8b | — | `~/.ollama/models` | GUARDiA AI 에이전트 LLM (4.7 GB) | + +### 2-5. DevOps / CI-CD + +| SW | 버전 | 설치 경로 | 용도 | +|----|------|-----------|------| +| Gitea | 1.22.3 | `/opt/gitea/bin/gitea` | 온프레미스 Git 서버 | +| Jenkins | 2.504.1 LTS | `/opt/jenkins/jenkins.war` | CI/CD 파이프라인 (초기설정 필요) | +| ZioInfo Deploy Server | — | `/opt/zioinfo/deploy_server.py` | Gitea 웹훅 → 자동 빌드·배포 (Python) | + +### 2-6. 애플리케이션 서비스 + +| 서비스 | 기술 스택 | 버전 | 용도 | +|--------|----------|------|------| +| 지오정보기술 홈페이지 | Spring Boot 3.2.5 + React | 1.0.0 | (주)지오정보기술 기업 홈페이지 | +| GUARDiA ITSM | Python FastAPI + PostgreSQL | — | AI 기반 인프라 자율 운영 ITSM | + +--- + +## 3. 포트 구성 + +| 포트 | 프로토콜 | 서비스 | 외부 접근 | +|------|----------|--------|----------| +| 22 | TCP | SSH | ✅ 허용 (키 인증) | +| 80 | TCP | Nginx (홈페이지) | ✅ 허용 | +| 443 | TCP | HTTPS (미설정) | — | +| 3000 | TCP | Gitea Git 서버 | ✅ 허용 | +| 8001 | TCP | GUARDiA ITSM (FastAPI) | ✅ 허용 | +| 8080 | TCP | Jenkins CI/CD | ✅ 허용 | +| 8082 | TCP | Spring Boot (홈페이지 API) | ✅ 허용 | +| 8088 | TCP | Nginx → Jenkins 프록시 | ✅ 허용 | +| 9001 | TCP | Nginx → GUARDiA 프록시 | ✅ 허용 | +| 9999 | TCP | Deploy Webhook 서버 | ✅ 허용 (내부 권장) | +| 5432 | TCP | PostgreSQL | ❌ 내부 전용 (127.0.0.1) | +| 11434 | TCP | Ollama LLM API | ❌ 내부 전용 (127.0.0.1) | + +--- + +## 4. 디렉터리 구조 + +``` +/ +├── etc/ +│ ├── nginx/ +│ │ ├── sites-available/ +│ │ │ ├── zioinfo # 홈페이지 Nginx 설정 +│ │ │ ├── guardia # GUARDiA ITSM Nginx 설정 +│ │ │ ├── gitea # Gitea Nginx 설정 +│ │ │ └── jenkins # Jenkins Nginx 설정 +│ │ └── sites-enabled/ # 활성 설정 심볼릭 링크 +│ ├── gitea/ +│ │ └── app.ini # Gitea 설정 파일 +│ └── systemd/system/ +│ ├── zioinfo.service # 홈페이지 Spring Boot 서비스 +│ ├── zioinfo-deploy.service # CI/CD 웹훅 서버 +│ ├── guardia.service # GUARDiA ITSM 서비스 +│ ├── gitea.service # Gitea 서비스 +│ ├── jenkins.service # Jenkins 서비스 +│ └── ollama.service # Ollama LLM 서비스 +│ +├── opt/ +│ ├── zioinfo/ +│ │ ├── app/ +│ │ │ ├── app.jar # Spring Boot 실행 JAR +│ │ │ └── data/ # SQLite DB 디렉터리 +│ │ ├── src/ # 소스 코드 (Gitea 클론) +│ │ └── deploy_server.py # CI/CD 웹훅 서버 +│ ├── guardia/ +│ │ ├── app/ # GUARDiA ITSM 소스 +│ │ ├── venv/ # Python 가상환경 +│ │ ├── logs/ # 애플리케이션 로그 +│ │ └── uploads/ # 업로드 파일 +│ ├── gitea/ +│ │ └── bin/gitea # Gitea 바이너리 +│ └── jenkins/ +│ └── jenkins.war # Jenkins WAR 파일 +│ +├── var/ +│ ├── www/ +│ │ └── zioinfo/ # React 빌드 정적 파일 +│ ├── lib/ +│ │ ├── jenkins/ # Jenkins 홈 디렉터리 +│ │ └── gitea/ # Gitea 데이터 디렉터리 +│ │ └── data/repositories/ # Git 저장소 +│ └── log/ +│ └── zioinfo/ +│ ├── spring.log # Spring Boot 로그 +│ └── deploy.log # CI/CD 배포 로그 +│ +└── home/ + └── git/ # Gitea 시스템 사용자 홈 +``` + +--- + +## 5. 계정 및 자격증명 + +> **보안 주의**: 이 문서는 내부망 전용입니다. 외부 공개 금지. + +### 5-1. 시스템 계정 + +| 계정 | 역할 | 비밀번호 | +|------|------|---------| +| `root` | 서버 관리자 | `1q2w3e!Q` | +| `ubuntu` | 앱 실행 계정 (예비) | `ubuntu123` | +| `jenkins` | Jenkins / Spring Boot 실행 | 시스템 계정 (로그인 불가) | +| `git` | Gitea 실행 | 시스템 계정 (로그인 불가) | +| `postgres` | PostgreSQL 관리 | 시스템 계정 | + +### 5-2. 서비스 계정 + +| 서비스 | 아이디 | 비밀번호 | 권한 | +|--------|--------|---------|------| +| **홈페이지 관리자** | `admin` | `Admin@2026!` | 전체 관리 | +| **Gitea** | `zio` | `Zio@Admin2026!` | 관리자 | +| **Jenkins** | `admin` | `Admin@2026!` | 전체 | +| **PostgreSQL (guardia)** | `guardia` | `G@urd1a_2026!` | guardia_db, zioinfo_db | +| **PostgreSQL (gitea)** | `gitea` | `G1tea_2026!` | gitea_db | +| **GUARDiA ITSM** | `admin` | `Admin@2026!` | 전체 | + +### 5-3. SSH 접속 + +```bash +# PEM 키 방식 (권장) +ssh -i "zio-server-key.pem" root@zioinfo.co.kr + +# 비밀번호 방식 +ssh root@zioinfo.co.kr +# 비밀번호: 1q2w3e!Q +``` + +--- + +## 6. Python 가상환경 패키지 (GUARDiA) + +경로: `/opt/guardia/venv` + +| 패키지 | 버전 | 용도 | +|--------|------|------| +| fastapi | 0.115.0 | API 프레임워크 | +| uvicorn | 0.30.0 | ASGI 서버 | +| sqlalchemy | 2.0.35 | ORM | +| asyncpg | 0.29.0 | PostgreSQL 비동기 드라이버 | +| psycopg2-binary | 2.9.9 | PostgreSQL 드라이버 | +| paramiko | 3.4.0 | SSH/SFTP 에이전트리스 연결 | +| python-jose | 3.3.0 | JWT 인증 | +| passlib | 1.7.4 | 비밀번호 해싱 | +| openpyxl | 3.1.5 | Excel 출력 | +| cryptography | 43.0.1 | AES-256 암호화 | + +--- + +## 7. Spring Boot 의존성 (홈페이지) + +| 의존성 | 버전 | 용도 | +|--------|------|------| +| spring-boot-starter-web | 3.2.5 | REST API | +| spring-boot-starter-data-jpa | 3.2.5 | ORM | +| spring-boot-starter-security | 3.2.5 | JWT 인증 | +| spring-boot-starter-mail | 3.2.5 | 이메일 발송 | +| sqlite-jdbc | 3.45.3.0 | SQLite 드라이버 | +| jjwt-api | 0.12.3 | JWT 처리 | +| lombok | — | 코드 생성 | + +--- + +## 8. Gitea 저장소 목록 + +| 저장소 | URL | 용도 | +|--------|-----|------| +| `zio/zioinfo-web` | `http://zioinfo.co.kr:3000/zio/zioinfo-web` | 홈페이지 소스 | +| `zio/guardia-itsm` | `http://zioinfo.co.kr:3000/zio/guardia-itsm` | GUARDiA ITSM 소스 | + +--- + +## 9. 방화벽 (UFW) 규칙 + +```bash +# 현재 활성 규칙 조회 +ufw status + +# 주요 허용 포트 +22/tcp (SSH) +80/tcp (HTTP - 홈페이지) +443/tcp (HTTPS - 미사용) +3000/tcp (Gitea) +8001/tcp (GUARDiA ITSM) +8080/tcp (Jenkins) +8082/tcp (Spring Boot API) +9999/tcp (Deploy Webhook) +``` diff --git a/19_zio서버_운영가이드.md b/19_zio서버_운영가이드.md new file mode 100644 index 0000000..b34b702 --- /dev/null +++ b/19_zio서버_운영가이드.md @@ -0,0 +1,649 @@ +# zio-server 운영 가이드 + +> **서버**: zio-server | **IP**: zioinfo.co.kr +> **작성일**: 2026-05-30 | **대상**: 서버 운영자 / 개발자 + +--- + +## 1. 서비스 접속 주소 + +| 서비스 | URL | 계정 | +|--------|-----|------| +| 지오정보기술 홈페이지 | http://zioinfo.co.kr | — | +| 홈페이지 관리자 | http://zioinfo.co.kr/admin | admin / Admin@2026! | +| GUARDiA ITSM | http://zioinfo.co.kr:8001 | admin / Admin@2026! | +| Gitea (Git 서버) | http://zioinfo.co.kr:3000 | zio / Zio@Admin2026! | +| Jenkins (CI/CD) | http://zioinfo.co.kr:8080 | admin / Admin@2026! | + +--- + +## 2. 서버 접속 + +```bash +# SSH 키 인증 (권장) +ssh -i "zio-server-key.pem" root@zioinfo.co.kr + +# 비밀번호 인증 +ssh root@zioinfo.co.kr +# 비밀번호: 1q2w3e!Q +``` + +--- + +## 3. 서비스 관리 + +### 3-1. 전체 상태 확인 + +```bash +# 모든 서비스 상태 한 번에 확인 +for svc in nginx zioinfo zioinfo-deploy guardia gitea jenkins postgresql ollama; do + status=$(systemctl is-active $svc 2>/dev/null) + printf "%-22s %s\n" $svc $status +done +``` + +### 3-2. 개별 서비스 명령 + +```bash +# 서비스 시작 / 중지 / 재시작 / 상태 +systemctl start <서비스명> +systemctl stop <서비스명> +systemctl restart <서비스명> +systemctl status <서비스명> + +# 부팅 자동시작 설정 / 해제 +systemctl enable <서비스명> +systemctl disable <서비스명> +``` + +**서비스명 목록:** + +| 서비스명 | 설명 | +|---------|------| +| `nginx` | 웹 프록시 서버 | +| `zioinfo` | 지오정보기술 홈페이지 (Spring Boot, 포트 8082) | +| `zioinfo-deploy` | CI/CD 웹훅 서버 (포트 9999) | +| `guardia` | GUARDiA ITSM (FastAPI, 포트 8001) | +| `gitea` | Git 서버 (포트 3000) | +| `jenkins` | CI/CD 파이프라인 (포트 8080) | +| `postgresql` | 데이터베이스 (포트 5432) | +| `ollama` | LLM 엔진 (포트 11434) | + +--- + +## 4. 로그 확인 + +```bash +# Spring Boot 홈페이지 로그 +tail -f /var/log/zioinfo/spring.log + +# CI/CD 배포 로그 (실시간) +tail -f /var/log/zioinfo/deploy.log + +# GUARDiA ITSM 로그 +tail -f /opt/guardia/logs/guardia.log +tail -f /opt/guardia/logs/error.log + +# Nginx 접근 로그 +tail -f /var/log/nginx/access.log +tail -f /var/log/nginx/error.log + +# systemd 서비스 로그 (최근 50줄) +journalctl -u zioinfo -n 50 --no-pager +journalctl -u guardia -n 50 --no-pager +journalctl -u gitea -n 50 --no-pager + +# 실시간 로그 스트리밍 +journalctl -u zioinfo -f +``` + +--- + +## 5. 홈페이지 배포 (CI/CD) + +### 5-1. 자동 배포 흐름 + +``` +로컬 코드 수정 + ↓ +git add . && git commit -m "메시지" + ↓ +git push gitea main:main ← Gitea에 push + ↓ +Gitea 웹훅 → localhost:9999 호출 + ↓ +자동 빌드 파이프라인 실행: + ① git pull (소스 갱신) + ② npm build (React 빌드) + ③ mvn package (Spring Boot JAR 빌드) + ④ 파일 복사 (JAR → /opt/zioinfo/app, 정적 → /var/www/zioinfo) + ⑤ systemctl restart zioinfo + ↓ +배포 완료 (약 2~4분 소요) +``` + +### 5-2. 로컬에서 Gitea push + +```bash +# 최초 remote 설정 (1회) +cd workspace/zioinfo-web +git remote add gitea http://zio:Zio%40Admin2026%21@zioinfo.co.kr:3000/zio/zioinfo-web.git + +# 이후 배포 +git add . +git commit -m "feat: 변경 내용 설명" +git push gitea main:main +``` + +### 5-3. 배포 상태 모니터링 + +```bash +# 배포 로그 실시간 확인 +ssh root@zioinfo.co.kr "tail -f /var/log/zioinfo/deploy.log" + +# 배포 완료 후 서비스 상태 +ssh root@zioinfo.co.kr "systemctl is-active zioinfo && curl -s -o /dev/null -w 'HTTP %{http_code}' http://localhost:8082/api/company" +``` + +### 5-4. 수동 배포 (긴급 시) + +```bash +# 서버에서 직접 수동 배포 +ssh root@zioinfo.co.kr + +# 소스 갱신 +cd /opt/zioinfo/src && git pull origin main + +# React 빌드 +cd frontend && npm ci && npm run build + +# Spring Boot 빌드 +cd ../backend && mvn clean package -DskipTests -q + +# 배포 +cp target/zioinfo-web-*.jar /opt/zioinfo/app/app.jar +cp -r src/main/resources/static/. /var/www/zioinfo/ +systemctl restart zioinfo +``` + +--- + +## 6. GUARDiA ITSM 관리 + +### 6-1. 서비스 재시작 + +```bash +systemctl restart guardia +journalctl -u guardia -n 20 --no-pager +``` + +### 6-2. Python 패키지 업데이트 + +```bash +source /opt/guardia/venv/bin/activate +pip install -r /opt/guardia/app/requirements.txt +deactivate +systemctl restart guardia +``` + +### 6-3. 환경변수 설정 + +```bash +# .env 파일 편집 +nano /opt/guardia/app/.env + +# 주요 항목: +# DATABASE_URL=postgresql+asyncpg://guardia:G@urd1a_2026!@localhost:5432/guardia_db +# OLLAMA_BASE_URL=http://localhost:11434 +# LLM_MODEL=llama3:8b +# SECRET_KEY= +``` + +--- + +## 7. 데이터베이스 관리 + +### 7-1. PostgreSQL 접속 + +```bash +# postgres 관리자로 접속 +sudo -u postgres psql + +# guardia DB 접속 +psql -h 127.0.0.1 -U guardia -d guardia_db +비밀번호: G@urd1a_2026! + +# gitea DB 접속 +psql -h 127.0.0.1 -U gitea -d gitea_db +비밀번호: G1tea_2026! +``` + +### 7-2. 데이터베이스 백업 + +```bash +# 전체 백업 +pg_dump -U guardia guardia_db > /opt/guardia/backups/guardia_$(date +%Y%m%d).sql +pg_dump -U gitea gitea_db > /opt/gitea/backup/gitea_$(date +%Y%m%d).sql + +# 복원 +psql -U guardia guardia_db < /opt/guardia/backups/guardia_20260530.sql +``` + +### 7-3. 홈페이지 SQLite 백업 + +```bash +# SQLite DB 파일 직접 복사 +cp /opt/zioinfo/app/data/zioinfo.db /opt/zioinfo/app/data/zioinfo_$(date +%Y%m%d).db.bak +``` + +--- + +## 8. Nginx 관리 + +### 8-1. 설정 테스트 및 리로드 + +```bash +# 설정 문법 검사 +nginx -t + +# 무중단 리로드 (설정 변경 반영) +systemctl reload nginx + +# 설정 파일 위치 +ls /etc/nginx/sites-available/ +``` + +### 8-2. 사이트 설정 파일 + +| 파일 | 역할 | +|------|------| +| `/etc/nginx/sites-available/zioinfo` | 홈페이지 (포트 80 → 8082) | +| `/etc/nginx/sites-available/guardia` | GUARDiA (포트 9001 → 8001) | +| `/etc/nginx/sites-available/gitea` | Gitea (포트 3001 → 3000) | +| `/etc/nginx/sites-available/jenkins` | Jenkins (포트 8088 → 8080) | + +### 8-3. Nginx 설정 변경 절차 + +```bash +# 1. 설정 파일 편집 +nano /etc/nginx/sites-available/zioinfo + +# 2. 문법 검사 +nginx -t + +# 3. 적용 +systemctl reload nginx +``` + +--- + +## 9. Gitea 관리 + +### 9-1. 저장소 관리 + +```bash +# API로 저장소 목록 확인 +curl -s http://localhost:3000/api/v1/repos/search -u "zio:Zio@Admin2026!" \ + | python3 -c "import json,sys; [print(r['full_name']) for r in json.load(sys.stdin)['data']]" + +# 저장소 직접 경로 +ls /var/lib/gitea/data/repositories/zio/ +``` + +### 9-2. 웹훅 확인 + +```bash +# zioinfo-web 웹훅 목록 +curl -s http://localhost:3000/api/v1/repos/zio/zioinfo-web/hooks \ + -u "zio:Zio@Admin2026!" | python3 -m json.tool +``` + +### 9-3. 설정 파일 + +```bash +# Gitea 설정 편집 +nano /etc/gitea/app.ini +systemctl restart gitea +``` + +--- + +## 10. Jenkins 초기 설정 (최초 1회) + +Jenkins는 브라우저에서 초기 설정을 완료해야 합니다. + +### 10-1. 초기 설정 절차 + +1. 브라우저에서 `http://zioinfo.co.kr:8080` 접속 +2. 초기 비밀번호 입력: + ```bash + cat /var/lib/jenkins/secrets/initialAdminPassword + ``` +3. **"Install suggested plugins"** 선택 +4. 추가 플러그인 설치: + - **Pipeline** (워크플로우 파이프라인) + - **Git** (Git 연동) + - **Gitea** (Gitea 웹훅 연동) +5. 관리자 계정 생성: `admin / Admin@2026!` +6. Jenkins URL 설정: `http://zioinfo.co.kr:8080` + +### 10-2. 파이프라인 Job 생성 + +초기 설정 완료 후: + +``` +Jenkins → New Item → "zioinfo-web" → Pipeline 선택 +→ Pipeline 탭 → Definition: "Pipeline script from SCM" +→ SCM: Git +→ Repository URL: http://localhost:3000/zio/zioinfo-web.git +→ Branch: main +→ Script Path: Jenkinsfile +→ 저장 +``` + +--- + +## 11. Ollama / LLM 관리 + +### 11-1. 모델 관리 + +```bash +# 설치된 모델 목록 +ollama list + +# 모델 다운로드 (4.7GB, 시간 소요) +ollama pull llama3:8b + +# 모델 테스트 +ollama run llama3:8b "안녕하세요, 간단한 테스트입니다." + +# API 호출 테스트 +curl http://localhost:11434/api/generate \ + -d '{"model":"llama3:8b","prompt":"Hello","stream":false}' +``` + +### 11-2. 서비스 재시작 + +```bash +systemctl restart ollama +# 재시작 후 약 10~30초 대기 (모델 로딩) +``` + +--- + +## 12. 리소스 모니터링 + +### 12-1. 실시간 모니터링 + +```bash +# 전체 리소스 현황 +htop + +# 디스크 사용량 +df -h + +# 메모리 사용량 +free -h + +# 포트 사용 현황 +ss -tlnp + +# 프로세스별 리소스 +ps aux --sort=-%cpu | head -15 +ps aux --sort=-%mem | head -15 +``` + +### 12-2. 서비스별 리소스 현황 + +| 서비스 | CPU | RAM | +|--------|-----|-----| +| Nginx | ~0.1% | ~50 MB | +| Spring Boot (홈페이지) | ~0.5% | ~400 MB | +| GUARDiA ITSM (FastAPI) | ~0.5% | ~300 MB | +| PostgreSQL | ~0.3% | ~256 MB | +| Ollama + Llama3:8b | ~1.0% (유휴) | ~4.7 GB | +| Jenkins | ~0.2% | ~512 MB | +| Gitea | ~0.1% | ~150 MB | +| **합계** | **~2.7%** | **~6.4 GB** | + +--- + +## 13. 보안 운영 + +### 13-1. SSH 보안 강화 (권장) + +```bash +# root 비밀번호 로그인 비활성화 (키 인증만 허용) +sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config +systemctl restart sshd +``` + +### 13-2. 방화벽 규칙 관리 + +```bash +# 현재 규칙 확인 +ufw status numbered + +# 규칙 추가 +ufw allow <포트>/tcp + +# 규칙 삭제 +ufw delete <번호> + +# 특정 IP만 허용 (예: 관리자 IP) +ufw allow from 203.xxx.xxx.xxx to any port 8080 +``` + +### 13-3. SSL/HTTPS 설정 (도메인 보유 시) + +```bash +# Let's Encrypt 인증서 발급 +apt install certbot python3-certbot-nginx +certbot --nginx -d zioinfo.co.kr -d www.zioinfo.co.kr + +# 자동 갱신 확인 +certbot renew --dry-run +``` + +--- + +## 14. 장애 대응 + +### 14-1. 홈페이지 접속 불가 + +```bash +# 1. Nginx 상태 확인 +systemctl status nginx +nginx -t + +# 2. Spring Boot 상태 확인 +systemctl status zioinfo +curl -s http://localhost:8082/api/company + +# 3. 로그 확인 +tail -50 /var/log/zioinfo/spring.log + +# 4. 재시작 +systemctl restart zioinfo +systemctl restart nginx +``` + +### 14-2. 배포 실패 + +```bash +# 1. 배포 로그 확인 +tail -50 /var/log/zioinfo/deploy.log + +# 2. CI/CD 서버 상태 +systemctl status zioinfo-deploy + +# 3. 소스 상태 확인 +git -C /opt/zioinfo/src status +git -C /opt/zioinfo/src log --oneline -5 + +# 4. 수동 배포 실행 (5-4절 참고) +``` + +### 14-3. GUARDiA ITSM 오류 + +```bash +# 1. 서비스 상태 +systemctl status guardia +tail -20 /opt/guardia/logs/error.log + +# 2. PostgreSQL 연결 확인 +psql -h 127.0.0.1 -U guardia -d guardia_db -c "SELECT 1" + +# 3. 재시작 +systemctl restart guardia +``` + +### 14-4. 데이터베이스 연결 오류 + +```bash +# PostgreSQL 상태 +systemctl status postgresql +sudo -u postgres psql -c "\l" + +# 재시작 +systemctl restart postgresql + +# 연결 확인 +psql -h 127.0.0.1 -U guardia -d guardia_db -c "SELECT count(*) FROM pg_tables" +``` + +### 14-5. 메모리 부족 + +```bash +# 메모리 사용량 확인 +free -h +ps aux --sort=-%mem | head -10 + +# 가장 많이 사용하는 서비스 재시작 (Ollama가 대부분 점유) +systemctl restart ollama + +# Swap 임시 설정 (재부팅 시 사라짐) +fallocate -l 4G /swapfile +chmod 600 /swapfile +mkswap /swapfile +swapon /swapfile +``` + +--- + +## 15. 백업 및 복구 + +### 15-1. 정기 백업 스크립트 + +```bash +#!/bin/bash +# /opt/backup/daily_backup.sh +DATE=$(date +%Y%m%d_%H%M) +BACKUP_DIR="/opt/backup/${DATE}" +mkdir -p "$BACKUP_DIR" + +# DB 백업 +sudo -u postgres pg_dump guardia_db > "$BACKUP_DIR/guardia_db.sql" +sudo -u postgres pg_dump gitea_db > "$BACKUP_DIR/gitea_db.sql" + +# SQLite 백업 +cp /opt/zioinfo/app/data/zioinfo.db "$BACKUP_DIR/zioinfo.db" + +# 설정 파일 백업 +cp -r /etc/nginx/sites-available "$BACKUP_DIR/nginx/" +cp -r /etc/gitea "$BACKUP_DIR/gitea/" +cp /opt/guardia/app/.env "$BACKUP_DIR/guardia.env" + +# 30일 이전 백업 삭제 +find /opt/backup -maxdepth 1 -type d -mtime +30 -exec rm -rf {} \; + +echo "백업 완료: $BACKUP_DIR" +``` + +```bash +# cron 등록 (매일 새벽 2시) +crontab -e +# 추가: +0 2 * * * /opt/backup/daily_backup.sh >> /var/log/backup.log 2>&1 +``` + +--- + +## 16. 자주 쓰는 명령어 모음 + +```bash +# ── 전체 서비스 상태 ────────────────────────────── +for s in nginx zioinfo guardia gitea jenkins postgresql ollama; do + printf "%-15s %s\n" $s $(systemctl is-active $s) +done + +# ── 포트 사용 현황 ──────────────────────────────── +ss -tlnp | awk '{print $4}' | sort + +# ── 디스크 / 메모리 ────────────────────────────── +df -h / && free -h + +# ── 홈페이지 API 테스트 ─────────────────────────── +curl -s http://localhost/api/company | python3 -m json.tool + +# ── 홈페이지 관리자 로그인 테스트 ──────────────── +curl -s -X POST http://localhost/api/admin/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"Admin@2026!"}' \ + | python3 -c "import json,sys; d=json.load(sys.stdin); print('Token:', d.get('token','FAIL')[:20]+'...')" + +# ── Gitea 저장소 목록 ───────────────────────────── +curl -s http://localhost:3000/api/v1/repos/search \ + -u "zio:Zio@Admin2026!" | python3 -c \ + "import json,sys; [print(r['full_name']) for r in json.load(sys.stdin)['data']]" + +# ── 배포 수동 트리거 ───────────────────────────── +curl -s -X POST http://localhost:9999/ \ + -H "Content-Type: application/json" -d '{}' + +# ── Ollama 모델 확인 ───────────────────────────── +ollama list + +# ── 최근 에러 로그 ─────────────────────────────── +journalctl -p err --since "1 hour ago" --no-pager +``` + +--- + +## 17. 업데이트 및 유지보수 + +### 17-1. 시스템 패키지 업데이트 + +```bash +apt-get update && apt-get upgrade -y +# 주의: 업데이트 전 서비스 상태 확인 필수 +# 커널 업데이트 시 재부팅 필요 +``` + +### 17-2. 애플리케이션 업데이트 + +```bash +# Gitea 업데이트 +wget -q https://dl.gitea.com/gitea/X.X.X/gitea-X.X.X-linux-amd64 \ + -O /opt/gitea/bin/gitea +chmod +x /opt/gitea/bin/gitea +systemctl restart gitea + +# Ollama 업데이트 +curl -fsSL https://ollama.com/install.sh | sh +systemctl restart ollama +``` + +### 17-3. LLM 모델 업데이트 + +```bash +# 새 버전 모델 다운로드 +ollama pull llama3:8b + +# 구 버전 삭제 +ollama rm llama3:7b +``` + +--- + +*문서 버전: 1.0 | 최종 수정: 2026-05-30* diff --git a/20_zio서버_CICD_가이드.md b/20_zio서버_CICD_가이드.md new file mode 100644 index 0000000..8b81d64 --- /dev/null +++ b/20_zio서버_CICD_가이드.md @@ -0,0 +1,346 @@ +# zio-server CI/CD 파이프라인 가이드 + +> **서버**: zio-server | **IP**: zioinfo.co.kr +> **작성일**: 2026-05-30 + +--- + +## 1. CI/CD 아키텍처 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 개발자 로컬 PC │ +│ 코드 수정 → git commit → git push gitea main:main │ +└──────────────────────┬──────────────────────────────────┘ + │ HTTP push (포트 3000) + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Gitea (zioinfo.co.kr:3000) │ +│ zio/zioinfo-web 저장소 → 웹훅 트리거 │ +└──────────────────────┬──────────────────────────────────┘ + │ POST http://localhost:9999/ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ ZioInfo Deploy Server (포트 9999) │ +│ /opt/zioinfo/deploy_server.py │ +│ │ +│ ① git pull (소스 최신화) │ +│ ② npm ci && npm run build (React SPA 빌드) │ +│ ③ mvn clean package (Spring Boot JAR 빌드) │ +│ ④ cp app.jar /opt/zioinfo/app/ │ +│ ⑤ cp static/* /var/www/zioinfo/ │ +│ ⑥ systemctl restart zioinfo │ +└──────────────────────┬──────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Spring Boot (포트 8082) │ +│ /opt/zioinfo/app/app.jar │ +│ DB: /opt/zioinfo/app/data/zioinfo.db (SQLite) │ +└──────────────────────┬──────────────────────────────────┘ + │ proxy_pass + ▼ +┌─────────────────────────────────────────────────────────┐ +│ Nginx (포트 80) │ +│ /var/www/zioinfo/ → React SPA 정적 파일 │ +│ /api/ → localhost:8082 프록시 │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ + http://zioinfo.co.kr (서비스 엔드포인트) +``` + +--- + +## 2. 로컬 개발 환경 설정 + +### 2-1. Git remote 설정 (최초 1회) + +```bash +# 현재 remote 확인 +git remote -v + +# Gitea remote 추가 +cd C:\GUARDiA\workspace\zioinfo-web +git remote add gitea http://zio:Zio%40Admin2026%21@zioinfo.co.kr:3000/zio/zioinfo-web.git + +# 설정 확인 +git remote -v +# gitea http://zio:...@zioinfo.co.kr:3000/zio/zioinfo-web.git (fetch) +# gitea http://zio:...@zioinfo.co.kr:3000/zio/zioinfo-web.git (push) +# origin https://github.com/... (fetch) ← GitHub (기존) +``` + +### 2-2. 코드 배포 + +```bash +# 1. 변경 사항 커밋 +git add . +git commit -m "feat: 기능 설명" + +# 2. Gitea에 push → 자동 빌드·배포 시작 +git push gitea main:main + +# 3. 배포 진행 상황 확인 (SSH 접속 후) +ssh root@zioinfo.co.kr "tail -f /var/log/zioinfo/deploy.log" +``` + +--- + +## 3. 배포 서버 구성 + +### 3-1. 파일 위치 + +| 파일 | 경로 | 설명 | +|------|------|------| +| 배포 스크립트 | `/opt/zioinfo/deploy_server.py` | 웹훅 수신 + 빌드·배포 실행 | +| 소스 코드 | `/opt/zioinfo/src/` | Gitea 클론 경로 | +| 실행 JAR | `/opt/zioinfo/app/app.jar` | 배포된 Spring Boot JAR | +| 정적 파일 | `/var/www/zioinfo/` | 배포된 React 빌드 결과물 | +| 배포 로그 | `/var/log/zioinfo/deploy.log` | 빌드·배포 과정 기록 | + +### 3-2. systemd 서비스 + +```ini +# /etc/systemd/system/zioinfo-deploy.service +[Unit] +Description=ZioInfo CI/CD Webhook Server +After=network.target + +[Service] +User=root +ExecStart=/usr/bin/python3 /opt/zioinfo/deploy_server.py +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +```bash +# 서비스 관리 +systemctl status zioinfo-deploy +systemctl restart zioinfo-deploy +journalctl -u zioinfo-deploy -n 20 --no-pager +``` + +### 3-3. 웹훅 보안 + +배포 서버는 HMAC-SHA256으로 요청을 검증합니다. + +```python +# 검증 키: zioinfo-deploy-2026 +# Gitea 웹훅 설정의 Secret 값과 동일 +``` + +--- + +## 4. Gitea 웹훅 설정 + +### 4-1. 현재 웹훅 확인 + +```bash +# API로 웹훅 목록 조회 +curl -s http://localhost:3000/api/v1/repos/zio/zioinfo-web/hooks \ + -u "zio:Zio@Admin2026!" | python3 -m json.tool + +# 브라우저에서 확인 +# http://zioinfo.co.kr:3000/zio/zioinfo-web/settings/hooks +``` + +### 4-2. 웹훅 재등록 (필요 시) + +```bash +# 기존 웹훅 삭제 +curl -s -X DELETE http://localhost:3000/api/v1/repos/zio/zioinfo-web/hooks/1 \ + -u "zio:Zio@Admin2026!" + +# 새 웹훅 등록 (Python) +python3 << 'EOF' +import urllib.request, json, base64 + +data = json.dumps({ + "type": "gitea", + "active": True, + "config": { + "url": "http://localhost:9999/", + "content_type": "json", + "secret": "zioinfo-deploy-2026" + }, + "events": ["push"] +}).encode() + +cred = base64.b64encode(b"zio:Zio@Admin2026!").decode() +req = urllib.request.Request( + "http://localhost:3000/api/v1/repos/zio/zioinfo-web/hooks", + data=data, + headers={"Content-Type": "application/json", "Authorization": f"Basic {cred}"}) +resp = urllib.request.urlopen(req) +d = json.loads(resp.read()) +print(f"Webhook ID: {d.get('id')}") +EOF +``` + +--- + +## 5. 배포 이력 및 롤백 + +### 5-1. 배포 이력 확인 + +```bash +# Gitea 커밋 이력 +curl -s "http://localhost:3000/api/v1/repos/zio/zioinfo-web/commits?limit=10" \ + -u "zio:Zio@Admin2026!" | python3 -c " +import json, sys +commits = json.load(sys.stdin) +for c in commits: + print(c['sha'][:8], c['commit']['message'][:60], '|', c['commit']['author']['date'][:10]) +" + +# 서버 소스 로컬 이력 +ssh root@zioinfo.co.kr "git -C /opt/zioinfo/src log --oneline -10" +``` + +### 5-2. 특정 버전으로 롤백 + +```bash +ssh root@zioinfo.co.kr + +# 이전 커밋으로 체크아웃 +cd /opt/zioinfo/src +git log --oneline -5 # 커밋 해시 확인 +git checkout <커밋해시> # 원하는 버전으로 변경 + +# 빌드 및 재배포 +cd frontend && npm ci && npm run build +cd ../backend && mvn clean package -DskipTests -q +cp target/zioinfo-web-*.jar /opt/zioinfo/app/app.jar +cp -r src/main/resources/static/. /var/www/zioinfo/ +systemctl restart zioinfo + +# 최신 버전으로 복구 +git checkout main +``` + +--- + +## 6. Jenkins 파이프라인 (심화 설정) + +> Jenkins 초기 설정 완료 후 사용 가능 + +### 6-1. Jenkinsfile 위치 + +``` +workspace/zioinfo-web/Jenkinsfile (홈페이지 파이프라인) +itsm/Jenkinsfile (GUARDiA ITSM 파이프라인) +``` + +### 6-2. 홈페이지 Jenkinsfile 주요 단계 + +```groovy +pipeline { + agent any + stages { + stage('Frontend Build') { /* npm ci + npm run build */ } + stage('Backend Build') { /* mvn clean package */ } + stage('Test') { /* mvn test */ } + stage('Deploy') { + when { branch 'main' } + /* JAR 복사 + 정적파일 배포 + systemctl restart */ + } + } +} +``` + +### 6-3. Jenkins Job 수동 트리거 + +```bash +# Jenkins API로 빌드 트리거 (초기 설정 완료 후) +CRUMB=$(curl -s -u admin:Admin@2026! \ + http://localhost:8080/crumbIssuer/api/json | python3 -c \ + "import json,sys; print(json.load(sys.stdin)['crumb'])") + +curl -u admin:Admin@2026! -X POST \ + http://localhost:8080/job/zioinfo-web/build \ + -H "Jenkins-Crumb: $CRUMB" +``` + +--- + +## 7. 트러블슈팅 + +### 7-1. push 후 자동 배포가 안 될 때 + +```bash +# 1. 웹훅 서버 상태 확인 +systemctl is-active zioinfo-deploy + +# 2. 웹훅 서버 로그 +journalctl -u zioinfo-deploy -n 20 --no-pager + +# 3. 수동 트리거 테스트 +curl -s -X POST http://localhost:9999/ \ + -H "Content-Type: application/json" -d '{}' + +# 4. 배포 로그 실시간 확인 +tail -f /var/log/zioinfo/deploy.log +``` + +### 7-2. 빌드 실패 시 + +```bash +# 빌드 로그에서 오류 확인 +grep -i "error\|failed\|fail" /var/log/zioinfo/deploy.log | tail -20 + +# npm 빌드 오류 재현 +cd /opt/zioinfo/src/frontend +npm ci --legacy-peer-deps +npm run build + +# Maven 빌드 오류 재현 +cd /opt/zioinfo/src/backend +mvn clean package -DskipTests +``` + +### 7-3. Spring Boot 재시작 실패 + +```bash +# 상세 로그 확인 +journalctl -u zioinfo --since "5 minutes ago" --no-pager +tail -50 /var/log/zioinfo/spring.log + +# 포트 충돌 확인 (8082) +ss -tlnp | grep 8082 +kill -9 # 기존 프로세스 강제 종료 + +# 수동 실행 (디버깅) +java -jar /opt/zioinfo/app/app.jar --server.port=8082 +``` + +--- + +## 8. GUARDiA ITSM 배포 + +> GUARDiA는 별도 Jenkinsfile 기반 파이프라인 사용 + +### 8-1. 수동 배포 + +```bash +# Gitea에서 최신 코드 반영 +cd /opt/guardia/app +git pull http://zio:Zio%40Admin2026%21@localhost:3000/zio/guardia-itsm.git main + +# 패키지 업데이트 +/opt/guardia/venv/bin/pip install -r requirements.txt -q + +# 서비스 재시작 +systemctl restart guardia +sleep 3 && systemctl is-active guardia + +# 헬스체크 +curl -s http://localhost:8001/docs -o /dev/null -w "HTTP %{http_code}" +``` + +--- + +*문서 버전: 1.0 | 최종 수정: 2026-05-30* diff --git a/21_zio서버_장애대응_가이드.md b/21_zio서버_장애대응_가이드.md new file mode 100644 index 0000000..1a738b8 --- /dev/null +++ b/21_zio서버_장애대응_가이드.md @@ -0,0 +1,409 @@ +# zio-server 장애 대응 가이드 (Runbook) + +> **서버**: zio-server | **IP**: zioinfo.co.kr +> **작성일**: 2026-05-30 +> **목적**: 장애 발생 시 빠른 진단과 복구를 위한 단계별 절차서 + +--- + +## 긴급 연락 및 접속 + +```bash +# 즉시 서버 접속 +ssh -i "zio-server-key.pem" root@zioinfo.co.kr +# 또는 +ssh root@zioinfo.co.kr # 비밀번호: 1q2w3e!Q +``` + +--- + +## 1. 빠른 현황 진단 (첫 번째로 실행) + +```bash +#!/bin/bash +echo "=== 서비스 상태 ===" +for s in nginx zioinfo zioinfo-deploy guardia gitea jenkins postgresql ollama; do + status=$(systemctl is-active $s 2>/dev/null) + icon="✅"; [ "$status" != "active" ] && icon="❌" + printf "%s %-22s %s\n" "$icon" "$s" "$status" +done + +echo "" +echo "=== 리소스 ===" +free -h | grep Mem +df -h / | tail -1 + +echo "" +echo "=== 최근 에러 ===" +journalctl -p err --since "30 minutes ago" --no-pager | tail -10 +``` + +--- + +## 2. 장애 유형별 대응 + +--- + +### CASE 1: 홈페이지(http://zioinfo.co.kr) 접속 불가 + +**증상**: 브라우저에서 접속 시 연결 거부 또는 502 오류 + +```bash +# Step 1: Nginx 확인 +systemctl is-active nginx || { + nginx -t && systemctl start nginx + echo "Nginx 재시작" +} + +# Step 2: Nginx 502 (백엔드 응답 없음) +curl -s -o /dev/null -w "%{http_code}" http://localhost:8082/api/company +# → 000 또는 연결 실패 시: Spring Boot 재시작 +systemctl restart zioinfo +sleep 8 +systemctl is-active zioinfo + +# Step 3: Spring Boot 로그 확인 +tail -30 /var/log/zioinfo/spring.log + +# Step 4: 포트 확인 +ss -tlnp | grep -E ":(80|8082)" + +# 완료 확인 +curl -s -o /dev/null -w "홈페이지: HTTP %{http_code}\n" http://localhost/ +``` + +**예상 복구 시간**: 1~3분 + +--- + +### CASE 2: 관리자 페이지(/admin) 로그인 불가 + +**증상**: admin/Admin@2026! 로그인 실패 또는 JWT 오류 + +```bash +# Step 1: Spring Boot API 응답 확인 +curl -s -X POST http://localhost/api/admin/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"Admin@2026!"}' | python3 -m json.tool + +# Step 2: 401 Unauthorized → 계정 확인 +# SQLite DB 접속 +sqlite3 /opt/zioinfo/app/data/zioinfo.db "SELECT username, enabled FROM admin_user;" + +# Step 3: 계정 비활성화 시 활성화 +sqlite3 /opt/zioinfo/app/data/zioinfo.db \ + "UPDATE admin_user SET enabled=1 WHERE username='admin';" + +# Step 4: Spring Boot 재시작 +systemctl restart zioinfo +``` + +--- + +### CASE 3: GUARDiA ITSM(포트 8001) 응답 없음 + +**증상**: `http://zioinfo.co.kr:8001` 접속 불가 + +```bash +# Step 1: 서비스 상태 +systemctl status guardia + +# Step 2: 로그 확인 +tail -30 /opt/guardia/logs/error.log + +# Step 3: PostgreSQL 연결 확인 +systemctl is-active postgresql || systemctl start postgresql +psql -h 127.0.0.1 -U guardia -d guardia_db -c "SELECT 1" 2>&1 + +# Step 4: GUARDiA 재시작 +systemctl restart guardia +sleep 5 + +# Step 5: FastAPI 응답 확인 +curl -s -o /dev/null -w "GUARDiA: HTTP %{http_code}\n" http://localhost:8001/docs +``` + +**예상 복구 시간**: 2~5분 + +--- + +### CASE 4: PostgreSQL 연결 오류 + +**증상**: GUARDiA 또는 Gitea에서 DB 연결 실패 로그 + +```bash +# Step 1: 상태 확인 +systemctl status postgresql + +# Step 2: 재시작 +systemctl restart postgresql +sleep 3 + +# Step 3: 연결 테스트 +sudo -u postgres psql -c "\l" 2>&1 + +# Step 4: 로그 확인 +tail -20 /var/log/postgresql/postgresql-16-main.log + +# Step 5: DB 접속 정상화 확인 +psql -h 127.0.0.1 -U guardia -d guardia_db -c "SELECT count(*) FROM pg_tables" +``` + +--- + +### CASE 5: CI/CD 배포 실패 + +**증상**: `git push` 후 변경 내용이 적용되지 않음 + +```bash +# Step 1: 웹훅 서버 상태 +systemctl is-active zioinfo-deploy +journalctl -u zioinfo-deploy -n 20 --no-pager + +# Step 2: 배포 로그 확인 +tail -50 /var/log/zioinfo/deploy.log + +# 오류 유형별 대응: +# [git pull] 실패 → 네트워크 또는 Gitea 확인 +systemctl is-active gitea +curl -s http://localhost:3000/api/v1/version -u "zio:Zio@Admin2026!" + +# [npm build] 실패 → node_modules 재설치 +cd /opt/zioinfo/src/frontend && rm -rf node_modules && npm install --legacy-peer-deps + +# [mvn package] 실패 → Maven 캐시 클리어 +cd /opt/zioinfo/src/backend && mvn clean -q + +# Step 3: 수동 배포 실행 +cd /opt/zioinfo/src +git pull origin main +cd frontend && npm ci && npm run build +cd ../backend && mvn clean package -DskipTests -q +cp target/zioinfo-web-*.jar /opt/zioinfo/app/app.jar +cp -r src/main/resources/static/. /var/www/zioinfo/ +systemctl restart zioinfo +``` + +--- + +### CASE 6: Gitea 접속 불가 (포트 3000) + +**증상**: `http://zioinfo.co.kr:3000` 접속 불가 + +```bash +# Step 1: 서비스 상태 +systemctl status gitea +journalctl -u gitea -n 20 --no-pager + +# Step 2: PostgreSQL 확인 (Gitea DB 의존) +systemctl is-active postgresql + +# Step 3: 재시작 +systemctl restart gitea +sleep 5 +curl -s -o /dev/null -w "Gitea: HTTP %{http_code}\n" http://localhost:3000/ +``` + +--- + +### CASE 7: 서버 메모리 부족 (OOM) + +**증상**: 서비스 갑자기 종료, journalctl에서 OOM killer 메시지 + +```bash +# Step 1: 메모리 현황 +free -h +dmesg | grep -i "oom" | tail -10 + +# Step 2: 메모리 사용량 상위 프로세스 +ps aux --sort=-%mem | head -10 + +# Step 3: Ollama가 주요 원인 (4.7GB 점유) +# 사용 안 할 때 중지 +systemctl stop ollama + +# Step 4: 필수 서비스 재시작 +for s in nginx zioinfo guardia postgresql gitea; do + systemctl is-active $s || systemctl start $s +done + +# Step 5: 임시 스왑 설정 (재부팅 시 사라짐) +[ ! -f /swapfile ] && { + fallocate -l 2G /swapfile + chmod 600 /swapfile + mkswap /swapfile + swapon /swapfile + echo "2GB 스왑 설정 완료" +} +free -h +``` + +--- + +### CASE 8: 디스크 용량 부족 + +**증상**: 로그 쓰기 실패, DB 오류 + +```bash +# Step 1: 디스크 현황 +df -h +du -sh /var/log/* 2>/dev/null | sort -rh | head -10 +du -sh /opt/* 2>/dev/null | sort -rh | head -10 + +# Step 2: 로그 정리 +# journald 로그 정리 +journalctl --vacuum-size=500M + +# Nginx 로그 압축 +gzip /var/log/nginx/access.log.1 2>/dev/null +gzip /var/log/nginx/error.log.1 2>/dev/null + +# 오래된 배포 로그 정리 +find /var/log/zioinfo -name "*.log" -mtime +30 -delete + +# Maven 캐시 정리 (~/.m2) +rm -rf /var/lib/jenkins/.m2/repository/*/ + +# npm 캐시 정리 +npm cache clean --force 2>/dev/null + +# Step 3: PostgreSQL 로그 정리 +find /var/log/postgresql -name "*.log" -mtime +7 -delete + +# Step 4: 확인 +df -h +``` + +--- + +### CASE 9: Jenkins 응답 없음 (포트 8080) + +```bash +# Step 1: 상태 확인 +systemctl status jenkins +curl -s -o /dev/null -w "Jenkins: HTTP %{http_code}\n" http://localhost:8080/ + +# Step 2: 재시작 +systemctl restart jenkins +sleep 10 +systemctl is-active jenkins + +# Step 3: Java 힙 부족 시 메모리 조정 +# /etc/default/jenkins 편집 +echo 'JAVA_ARGS="-Xmx512m -Xms256m"' >> /etc/default/jenkins +systemctl restart jenkins +``` + +--- + +### CASE 10: SSH 접속 불가 + +```bash +# 다른 방법으로 접속 (NCloud 콘솔에서 시리얼 콘솔 사용) +# 또는 NCloud 포털 → 서버 → 콘솔 접속 + +# SSH 서비스 재시작 (서버 콘솔에서) +systemctl restart sshd +systemctl is-active sshd + +# 방화벽 SSH 허용 확인 +ufw allow 22/tcp +ufw status | grep 22 +``` + +--- + +## 3. 서버 재부팅 후 복구 절차 + +```bash +# 재부팅 후 전체 서비스 자동 시작 확인 +systemctl list-units --type=service --state=failed + +# 실패한 서비스가 있을 경우 개별 재시작 +systemctl start <서비스명> + +# 모든 서비스 한번에 시작 +for s in postgresql gitea nginx zioinfo zioinfo-deploy guardia ollama jenkins; do + systemctl is-active $s || { + echo "Starting $s..." + systemctl start $s + sleep 2 + } +done + +# 전체 헬스체크 +echo "=== 재부팅 후 헬스체크 ===" +curl -s -o /dev/null -w "홈페이지: HTTP %{http_code}\n" http://localhost/ +curl -s -o /dev/null -w "API: HTTP %{http_code}\n" http://localhost/api/company +curl -s -o /dev/null -w "GUARDiA: HTTP %{http_code}\n" http://localhost:8001/docs +curl -s -o /dev/null -w "Gitea: HTTP %{http_code}\n" http://localhost:3000/ +curl -s -o /dev/null -w "Jenkins: HTTP %{http_code}\n" http://localhost:8080/ +``` + +--- + +## 4. 정기 점검 체크리스트 + +```bash +#!/bin/bash +# 주 1회 실행 권장 +echo "===== zio-server 정기 점검 =====" +echo "점검일: $(date)" +echo "" + +echo "[ 서비스 상태 ]" +for s in nginx zioinfo zioinfo-deploy guardia gitea jenkins postgresql ollama; do + status=$(systemctl is-active $s) + printf " %-20s %s\n" $s $status +done + +echo "" +echo "[ 리소스 현황 ]" +echo " 디스크: $(df -h / | tail -1 | awk '{print $3"/"$2" ("$5")"}')" +echo " 메모리: $(free -h | grep Mem | awk '{print $3"/"$2}')" +echo " 스왑: $(free -h | grep Swap | awk '{print $3"/"$2}')" + +echo "" +echo "[ 응답 테스트 ]" +for url in \ + "http://localhost/:홈페이지" \ + "http://localhost/api/company:API" \ + "http://localhost:8001/docs:GUARDiA" \ + "http://localhost:3000/:Gitea" \ + "http://localhost:8080/:Jenkins"; do + url_part="${url%%:*}" + name="${url##*:}" + code=$(curl -s -o /dev/null -w "%{http_code}" "$url_part" 2>/dev/null) + icon="✅"; [[ "$code" != "200" && "$code" != "302" ]] && icon="❌" + printf " %s %-12s HTTP %s\n" "$icon" "$name" "$code" +done + +echo "" +echo "[ 최근 에러 로그 (1시간) ]" +journalctl -p err --since "1 hour ago" --no-pager 2>/dev/null | grep -v "^--" | tail -5 || echo " 없음" + +echo "" +echo "[ Gitea 저장소 ]" +curl -s http://localhost:3000/api/v1/repos/search -u "zio:Zio@Admin2026!" 2>/dev/null | \ + python3 -c "import json,sys; [print(' '+r['full_name']) for r in json.load(sys.stdin)['data']]" 2>/dev/null + +echo "" +echo "[ PostgreSQL DB 상태 ]" +sudo -u postgres psql -c "\l" 2>/dev/null | grep -E "(guardia|gitea|zioinfo)" | awk '{print " "$1}' + +echo "=============================" +``` + +--- + +## 5. 긴급 연락처 및 에스컬레이션 + +| 단계 | 담당 | 연락처 | 조건 | +|------|------|--------|------| +| L1 | 운영팀 | — | 서비스 재시작으로 해결 가능 | +| L2 | 개발팀 | — | 코드/설정 변경 필요 | +| L3 | NCloud 지원 | 1544-5117 | 서버 하드웨어/네트워크 장애 | + +--- + +*문서 버전: 1.0 | 최종 수정: 2026-05-30* diff --git a/22_GUARDiA_개방망_운영가이드.md b/22_GUARDiA_개방망_운영가이드.md new file mode 100644 index 0000000..5a3871c --- /dev/null +++ b/22_GUARDiA_개방망_운영가이드.md @@ -0,0 +1,454 @@ +# GUARDiA ITSM 개방망 운영 가이드 + +> **버전**: 2.0.0 | **작성일**: 2026-05-30 +> **서버**: zio-server (zioinfo.co.kr) + +--- + +## 1. 개요 + +GUARDiA ITSM은 기본적으로 **폐쇄망(Closed Network)** 환경에서 동작하도록 설계되어 있습니다. +본 가이드는 GUARDiA를 **개방망(Open Network)** 에서도 안전하게 서비스하기 위한 구성 방법을 설명합니다. + +### 1-1. 폐쇄망 vs 개방망 비교 + +| 항목 | 폐쇄망 (기본) | 개방망 (이 가이드) | +|------|-------------|----------------| +| 접근 범위 | 내부망 only | 인터넷 외부 접근 허용 | +| CORS | localhost 만 허용 | 지정 외부 도메인 허용 | +| HTTPS | 선택 | **필수** | +| API 인증 | JWT | JWT + **API Key** 추가 | +| 외부 AI 호출 | 금지 (Ollama only) | 금지 유지 (변경 불가) | +| Rate Limiting | 기본 | **강화** (30req/min) | +| 보안 헤더 | 기본 | HSTS 포함 강화 | + +### 1-2. 개방망 지원 아키텍처 + +``` +외부 클라이언트 (브라우저, 메신저봇, 외부시스템) + │ + │ HTTPS (443 / 8443) + ▼ +┌─────────────────────────────────────────────────┐ +│ Nginx (TLS 종료 프록시) │ +│ ├── SSL/TLS (자체서명 or Let's Encrypt) │ +│ ├── Rate Limiting (30 req/min) │ +│ ├── 보안 헤더 (HSTS, X-Frame, X-XSS) │ +│ └── CORS 정책 적용 │ +└─────────────────┬───────────────────────────────┘ + │ HTTP (내부) + ▼ +┌─────────────────────────────────────────────────┐ +│ GUARDiA ITSM FastAPI (포트 8001) │ +│ ├── GUARDIA_NETWORK_MODE=open │ +│ ├── CORS: 지정 외부 도메인 허용 │ +│ ├── 보안 미들웨어 (HSTS, X-Frame, CSP) │ +│ └── /api/external/* (API Key 인증) │ +└─────────────────┬───────────────────────────────┘ + │ + ┌───────┴────────┐ + ▼ ▼ + PostgreSQL Ollama LLM + (내부 전용) (내부 전용) + localhost:5432 localhost:11434 +``` + +> **핵심 원칙**: LLM(Ollama)과 DB는 외부에서 직접 접근 불가. API 서버만 노출. + +--- + +## 2. 설치 및 구성 + +### 2-1. 사전 요구사항 + +| 항목 | 요구 사항 | +|------|---------| +| OS | Ubuntu 20.04+ | +| Nginx | 1.18+ | +| SSL 인증서 | 자체서명 or Let's Encrypt | +| GUARDiA | 2.0.0+ | +| Python | 3.11+ | + +### 2-2. SSL 인증서 생성 + +**자체 서명 인증서 (테스트/내부망):** +```bash +mkdir -p /etc/ssl/guardia +openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout /etc/ssl/guardia/server.key \ + -out /etc/ssl/guardia/server.crt \ + -subj "/C=KR/ST=Seoul/O=YourOrg/CN=your-server-ip" \ + -addext "subjectAltName=IP:zioinfo.co.kr" +chmod 600 /etc/ssl/guardia/server.key +``` + +**Let's Encrypt (도메인 보유 시 — 권장):** +```bash +apt install certbot python3-certbot-nginx +certbot --nginx -d itsm.zioinfo.co.kr +# 자동 갱신 확인 +certbot renew --dry-run +``` + +### 2-3. 환경변수 설정 (.env) + +```bash +cp /opt/guardia/app/.env.open /opt/guardia/app/.env +nano /opt/guardia/app/.env +``` + +**개방망 필수 설정:** +```env +# 개방망 모드 활성화 +GUARDIA_NETWORK_MODE=open + +# 허용할 외부 출처 (쉼표 구분) +GUARDIA_ALLOWED_ORIGINS=https://itsm.zioinfo.co.kr,https://portal.myorg.go.kr + +# 웹훅 HMAC 시크릿 (반드시 변경) +GUARDIA_WEBHOOK_SECRET=your-strong-secret-here + +# DB (특수문자 URL 인코딩 필수: @ → %40, ! → %21) +DATABASE_URL=postgresql+asyncpg://guardia:G%40urd1a_2026%21@localhost:5432/guardia_db + +# LLM (내부 전용 — 절대 변경 금지) +OLLAMA_BASE_URL=http://localhost:11434 +LLM_MODEL=llama3:8b +``` + +### 2-4. Nginx 개방망 설정 + +```bash +# Nginx http 블록에 rate limit zone 추가 +nano /etc/nginx/nginx.conf +``` + +```nginx +http { + limit_req_zone $binary_remote_addr zone=guardia_api:10m rate=30r/m; + ... +} +``` + +**`/etc/nginx/sites-available/guardia-https`** 생성: +```nginx +server { + listen 8443 ssl; + server_name _; + + ssl_certificate /etc/ssl/guardia/server.crt; + ssl_certificate_key /etc/ssl/guardia/server.key; + ssl_protocols TLSv1.2 TLSv1.3; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Frame-Options DENY always; + add_header X-Content-Type-Options nosniff always; + + location /api/ { + limit_req zone=guardia_api burst=10 nodelay; + proxy_pass http://127.0.0.1:8001; + proxy_set_header X-Forwarded-Proto https; + } + location / { + proxy_pass http://127.0.0.1:8001; + } +} +``` + +```bash +ln -sf /etc/nginx/sites-available/guardia-https /etc/nginx/sites-enabled/ +nginx -t && systemctl reload nginx +``` + +### 2-5. 서비스 재시작 + +```bash +systemctl restart guardia +systemctl is-active guardia +``` + +--- + +## 3. 외부 API 사용법 + +### 3-1. API 엔드포인트 목록 + +| 엔드포인트 | 메서드 | 인증 | 설명 | +|-----------|--------|------|------| +| `/api/external/health` | GET | 불필요 | 헬스체크 | +| `/api/external/status` | GET | 불필요 | 시스템 공개 상태 | +| `/api/external/keys` | GET | JWT (관리자) | API Key 목록 | +| `/api/external/keys` | POST | JWT (관리자) | API Key 발급 | +| `/api/external/keys/{id}` | DELETE | JWT (관리자) | API Key 비활성화 | +| `/api/external/sr` | GET | API Key (read) | SR 목록 조회 | +| `/api/external/sr` | POST | API Key (write) | SR 등록 | +| `/api/external/webhook` | POST | HMAC (선택) | 외부 메신저 웹훅 | +| `/docs` | GET | 불필요 | OpenAPI 문서 | + +### 3-2. API Key 발급 + +**1단계: 관리자 로그인 (JWT 획득)** +```bash +curl -X POST https://zioinfo.co.kr:8443/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"Admin@zioinfo2026!"}' \ + -k +# → {"access_token": "eyJ...", "token_type": "bearer"} +``` + +**2단계: API Key 발급** +```bash +TOKEN="eyJ..." +curl -X POST https://zioinfo.co.kr:8443/api/external/keys \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "카카오워크 봇", + "scopes": "read,write,webhook", + "expires_days": 365, + "allowed_ips": "" + }' -k +``` + +**응답 (발급 후 1회만 노출):** +```json +{ + "id": 1, + "name": "카카오워크 봇", + "api_key": "grd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "scopes": "read,write,webhook", + "expires_at": "2027-05-30T10:00:00", + "warning": "이 키는 다시 조회할 수 없습니다. 안전한 곳에 저장하세요." +} +``` + +### 3-3. API Key로 SR 등록 + +```bash +API_KEY="grd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +# SR 등록 +curl -X POST https://zioinfo.co.kr:8443/api/external/sr \ + -H "X-API-Key: $API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "웹서버 재시작 요청", + "description": "nginx가 502 오류를 반환하고 있습니다.", + "priority": "HIGH", + "requester_name": "홍길동", + "requester_email": "hong@example.go.kr" + }' -k +``` + +### 3-4. 외부 메신저 웹훅 연동 + +```bash +# Gitea/Slack/카카오워크 웹훅 URL 설정 +WEBHOOK_URL="https://zioinfo.co.kr:8443/api/external/webhook" +SECRET="guardia-webhook-secret-change-me-2026" + +# 서명 생성 (Python) +python3 -c " +import hmac, hashlib, json +body = json.dumps({'command': '서버 상태 확인', 'user_id': 'user01'}) +sig = 'sha256=' + hmac.new(b'$SECRET', body.encode(), hashlib.sha256).hexdigest() +print(sig) +" + +# 웹훅 전송 +curl -X POST $WEBHOOK_URL \ + -H "Content-Type: application/json" \ + -H "X-GUARDiA-Signature: sha256=<서명값>" \ + -H "X-Source: kakaotalk" \ + -d '{"command": "서버 상태 확인", "user_id": "홍길동"}' \ + -k +``` + +--- + +## 4. API Key 권한 스코프 + +| 스코프 | 설명 | 허용 API | +|--------|------|---------| +| `read` | 읽기 전용 | SR 목록 조회 | +| `write` | 쓰기 | SR 등록, 상태 변경 | +| `admin` | 전체 권한 | 모든 외부 API | +| `webhook` | 웹훅 전용 | `/api/external/webhook` | + +**스코프 조합 예시:** +```json +"scopes": "read,write" // 조회 + 등록 +"scopes": "webhook" // 웹훅만 +"scopes": "admin" // 전체 (주의) +``` + +--- + +## 5. 보안 설정 + +### 5-1. 적용된 보안 헤더 + +| 헤더 | 값 | 효과 | +|------|-----|------| +| `Strict-Transport-Security` | `max-age=31536000` | 브라우저가 HTTPS만 사용 | +| `X-Frame-Options` | `DENY` | iframe 삽입 차단 | +| `X-Content-Type-Options` | `nosniff` | MIME 타입 스니핑 방지 | +| `X-XSS-Protection` | `1; mode=block` | XSS 공격 차단 | +| `Referrer-Policy` | `strict-origin-when-cross-origin` | Referrer 정보 제한 | + +### 5-2. Rate Limiting 설정 + +| 위치 | 제한 | +|------|------| +| Nginx (기본) | 30 req/min per IP | +| Nginx (burst) | 최대 10 req 버스트 허용 | +| FastAPI (ratelimit.py) | 별도 설정 가능 | + +### 5-3. IP 화이트리스트 (API Key) + +특정 외부 시스템만 API Key를 사용할 수 있도록 IP를 제한할 수 있습니다: + +```json +{ + "name": "공공기관 포털", + "scopes": "read,write", + "allowed_ips": "203.10.20.30,203.10.20.31" +} +``` + +빈 문자열(`""`)로 설정하면 모든 IP에서 접근 가능합니다. + +### 5-4. 변경 불가 보안 정책 + +> 개방망 모드에서도 다음 정책은 절대 변경 불가합니다. + +| 정책 | 내용 | +|------|------| +| **외부 LLM 금지** | Ollama(localhost:11434)만 사용. OpenAI, Claude 등 외부 API 완전 금지 | +| **SSH 자격증명 보호** | IP, 비밀번호, SSH 계정을 API 응답에 포함 금지 | +| **AES-256 암호화** | 서버 자격증명은 암호화 저장 | +| **root SSH 금지** | opsagent 전용 계정 사용 | + +--- + +## 6. 모드 전환 + +### 6-1. 폐쇄망 → 개방망 + +```bash +# .env 수정 +echo "GUARDIA_NETWORK_MODE=open" >> /opt/guardia/app/.env +echo "GUARDIA_ALLOWED_ORIGINS=https://your-domain.go.kr" >> /opt/guardia/app/.env + +# Nginx HTTPS 활성화 +ln -sf /etc/nginx/sites-available/guardia-https /etc/nginx/sites-enabled/ +nginx -t && systemctl reload nginx + +# 서비스 재시작 +systemctl restart guardia +``` + +### 6-2. 개방망 → 폐쇄망 (롤백) + +```bash +# .env 수정 +sed -i 's/GUARDIA_NETWORK_MODE=open/GUARDIA_NETWORK_MODE=closed/' /opt/guardia/app/.env + +# HTTPS 비활성화 +rm /etc/nginx/sites-enabled/guardia-https +systemctl reload nginx + +# 서비스 재시작 +systemctl restart guardia +``` + +--- + +## 7. 테스트 결과 + +### 7-1. 테스트 환경 + +| 항목 | 값 | +|------|-----| +| 서버 | Ubuntu 24.04 LTS (zioinfo.co.kr) | +| GUARDiA 버전 | 2.0.0 | +| Nginx 버전 | 1.24.0 | +| 테스트 일자 | 2026-05-30 | + +### 7-2. 테스트 결과 + +| 테스트 | 항목 | 결과 | +|--------|------|------| +| T1 | HTTP 헬스체크 (8001) | ✅ HTTP 200 | +| T2 | HTTPS 헬스체크 (8443) | ✅ HTTP 200 | +| T3 | 홈페이지 HTTPS (443) | ✅ HTTP 200 | +| T4 | 미인증 API 접근 | ✅ HTTP 401 반환 | +| T5 | CORS 외부 출처 허용 | ✅ `Access-Control-Allow-Origin` 헤더 | +| T6 | HSTS 헤더 | ✅ `Strict-Transport-Security` 적용 | +| T7 | X-Frame-Options | ✅ `DENY` 설정 | +| T8 | Rate Limiting | ✅ Nginx rate limit zone 설정 | +| T9 | 시스템 상태 공개 | ✅ `operational` | +| T10 | 개방망 모드 활성 | ✅ `NETWORK_MODE=open` | + +--- + +## 8. 트러블슈팅 + +### 8-1. CORS 오류 + +**증상**: 브라우저에서 `Access-Control-Allow-Origin` 오류 + +```bash +# .env에 도메인 추가 +GUARDIA_ALLOWED_ORIGINS=https://your-domain.com,https://other-domain.go.kr +systemctl restart guardia +``` + +### 8-2. HTTPS 인증서 오류 + +**증상**: `SSL: CERTIFICATE_VERIFY_FAILED` + +```bash +# 자체서명 인증서인 경우 curl에 -k 플래그 사용 +curl -k https://zioinfo.co.kr:8443/api/external/health + +# 브라우저에서는 예외 추가 또는 Let's Encrypt 인증서 사용 +``` + +### 8-3. Rate Limit 초과 + +**증상**: HTTP 429 Too Many Requests + +```bash +# Nginx rate limit 완화 +nano /etc/nginx/nginx.conf +# rate=60r/m 으로 변경 +nginx -t && systemctl reload nginx +``` + +### 8-4. DATABASE_URL 연결 오류 + +**증상**: `Name or service not known` + +```bash +# @ 특수문자 URL 인코딩 확인 +# G@urd1a_2026! → G%40urd1a_2026%21 +sed -i 's|G@urd1a_2026!|G%40urd1a_2026%21|g' /opt/guardia/app/.env +systemctl restart guardia +``` + +--- + +## 9. 접속 정보 요약 + +| 서비스 | URL | 비고 | +|--------|-----|------| +| GUARDiA ITSM (HTTP) | `http://zioinfo.co.kr:8001` | 내부망 권장 | +| GUARDiA ITSM (HTTPS) | `https://zioinfo.co.kr:8443` | 개방망 사용 | +| 외부 API | `https://zioinfo.co.kr:8443/api/external/` | API Key 인증 | +| OpenAPI 문서 | `https://zioinfo.co.kr:8443/docs` | 무인증 | +| 홈페이지 HTTPS | `https://zioinfo.co.kr` | 포트 443 | + +--- + +*GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30* diff --git a/23_GUARDiA_개방망_가이드.pdf b/23_GUARDiA_개방망_가이드.pdf new file mode 100644 index 0000000..256d2f7 Binary files /dev/null and b/23_GUARDiA_개방망_가이드.pdf differ diff --git a/24_GUARDiA_개방망_발표자료.pptx b/24_GUARDiA_개방망_발표자료.pptx new file mode 100644 index 0000000..e632963 Binary files /dev/null and b/24_GUARDiA_개방망_발표자료.pptx differ diff --git a/25_GUARDiA_Manager_라이선스_관리_가이드.md b/25_GUARDiA_Manager_라이선스_관리_가이드.md new file mode 100644 index 0000000..9e7255b --- /dev/null +++ b/25_GUARDiA_Manager_라이선스_관리_가이드.md @@ -0,0 +1,296 @@ +# GUARDiA Manager — 라이선스 키 등록 및 관리 가이드 + +> **버전**: 2.0.0 | **작성일**: 2026-05-30 +> **접속 URL**: http://zioinfo.co.kr:8090/licenses +> **대상**: 시스템 관리자 (admin 역할) + +--- + +## 1. 개요 + +GUARDiA Manager의 **라이선스 관리** 페이지는 GUARDiA ITSM 플랫폼의 라이선스를 +통합 관제하는 화면입니다. + +### 주요 기능 + +| 기능 | 설명 | +|------|------| +| **현재 상태 확인** | 에디션, 만료일, 남은 기간, 허용 한도 실시간 표시 | +| **라이선스 키 등록** | 발급받은 라이선스 키를 붙여 넣고 즉시 활성화 | +| **무료 체험 시작** | 7/14/30일 체험 라이선스 즉시 발급 (설치당 1회) | +| **키 검증** | 등록 없이 키 유효성만 검증 (발급 전 사전 확인) | +| **라이선스 비활성화** | 현재 라이선스 비활성화 (서비스 제한 발생) | +| **이력 조회** | 과거 등록된 모든 라이선스 이력 테이블 | +| **에디션 비교** | TRIAL/COMMUNITY/STANDARD/ENTERPRISE 기능 비교 | + +--- + +## 2. 라이선스 에디션 + +### 2-1. 에디션 비교표 + +| 구분 | TRIAL | COMMUNITY | STANDARD | ENTERPRISE | +|------|-------|-----------|----------|------------| +| 가격 | 무료 (7일) | 무료 | 협의 | 협의 | +| 기관 수 | 1 | 1 | 50 | 무제한 | +| 사용자 수 | 10명 | 10명 | 200명 | 무제한 | +| 서버 수 | 20대 | 50대 | 500대 | 무제한 | +| AI 에이전트 | ❌ | ❌ | ✅ | ✅ | +| LDAP/MFA | ❌ | ❌ | ✅ | ✅ | +| SLA 관리 | ✅ 기본 | ✅ 기본 | ✅ 고급 | ✅ 고급 | +| 취약점 스캔 | ❌ | ❌ | ❌ | ✅ | +| FinOps | ❌ | ❌ | ❌ | ✅ | +| Scouter APM | ❌ | ❌ | ❌ | ✅ | +| 기술 지원 | ❌ | 커뮤니티 | 이메일 | 전담 지원 | + +### 2-2. 라이선스 키 형식 + +``` +TRIAL: GRD-{Base64URL 인코딩 페이로드} (자동 생성) +COMMUNITY/ grd_lic_{발급기관코드}_{서명} +STANDARD/ +ENTERPRISE: +``` + +--- + +## 3. 화면 구성 (NCloud 콘솔 스타일) + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 업그레이드 배너 (만료 3일 전 표시) │ +│ ⚠️ 체험판이 X일 후 만료됩니다. [라이선스 등록] 버튼 │ +├─────────────────────────────────────────────────────────────────┤ +│ 현재 라이선스 상태 카드 │ +│ [에디션 배지] [체험판/만료 배지] │ +│ 고객명: 지오정보기술 체험판 │ +│ 메시지: TRIAL 라이선스 활성 (6일 남음) │ +│ [🔑 등록] [🎁 체험] [🔍 검증] [비활성화] │ +│ ───────────────────────────────────────────────────────────── │ +│ [만료 게이지 바] [라이선스 ID] [허용 한도] │ +│ ████████░░ 6일 남음 TRL-290EA0FB 기관:1/사용자:10/서버:20 │ +├─────────────────────────────────────────────────────────────────┤ +│ 액션 패널 (선택한 액션에 따라 표시) │ +│ 예) 라이선스 키 등록: [textarea] [활성화 버튼] │ +├─────────────────────────────────────────────────────────────────┤ +│ 에디션 비교 (4개 카드 — 현재 에디션 강조) │ +│ [TRIAL] [COMMUNITY] [STANDARD ★] [ENTERPRISE] │ +├─────────────────────────────────────────────────────────────────┤ +│ 라이선스 이력 테이블 │ +│ ID | 라이선스ID | 에디션 | 고객명 | 체험판 | 만료일 | 상태 | 등록자│ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. 기능별 사용 방법 + +### 4-1. 라이선스 키 등록 + +> **사전 조건**: admin 역할로 로그인, GUARDiA ITSM 서버에 `GUARDIA_LICENSE_KEY` 환경변수 설정 + +1. [🔑 라이선스 등록] 버튼 클릭 +2. 발급받은 라이선스 키를 텍스트 영역에 붙여 넣기 +3. [활성화] 버튼 클릭 +4. 성공 시 현재 상태 카드가 즉시 갱신됨 + +```bash +# 라이선스 키 발급 (서버 관리자용 — Python 직접 실행) +ssh root@zioinfo.co.kr + +source /opt/guardia/venv/bin/activate +cd /opt/guardia/app +python -m core.license \ + --customer "서울특별시 정보화부" \ + --edition STANDARD \ + --days 365 \ + --key $GUARDIA_LICENSE_KEY +``` + +### 4-2. 무료 체험 시작 + +> **제한**: 설치당 1회만 가능 + +1. [🎁 무료 체험] 버튼 클릭 +2. 고객/기관명 입력 (기본값: "GUARDiA 체험판") +3. 체험 기간 선택: 7일 / 14일 / 30일 +4. [🎁 체험 시작] 버튼 클릭 +5. **발급된 체험 키가 팝업으로 1회만 표시** → 반드시 복사 보관 +6. 즉시 TRIAL 에디션으로 활성화 + +> ⚠️ 체험 라이선스 키는 발급 시 화면에 1회만 표시됩니다. 화면을 닫으면 다시 확인할 수 없습니다. + +### 4-3. 라이선스 키 검증 + +등록 전 키가 유효한지 먼저 확인할 때 사용합니다. + +1. [🔍 키 검증] 버튼 클릭 +2. 확인할 키 입력 +3. [검증] 버튼 클릭 +4. 결과 확인: 에디션, 고객명, 발급일, 만료일 표시 +5. 유효한 경우 [이 키로 활성화] 버튼으로 즉시 등록 가능 + +### 4-4. 라이선스 비활성화 + +> **주의**: 비활성화 시 서비스 제한이 발생합니다. + +1. [비활성화] 버튼 클릭 (빨간 버튼) +2. 확인 다이얼로그에서 [확인] +3. 상태가 "활성 라이선스가 없습니다."로 변경됨 + +--- + +## 5. API 명세 + +GUARDiA ITSM API를 직접 호출하는 엔드포인트입니다. + +### 기본 정보 + +| 항목 | 값 | +|------|-----| +| Base URL | `http://zioinfo.co.kr:8001` | +| 인증 | `Authorization: Bearer {JWT Token}` | +| 로그인 | `POST /api/auth/login` (JSON, admin 역할 필요) | + +### 엔드포인트 목록 + +| 메서드 | 경로 | 인증 | 설명 | +|--------|------|------|------| +| `GET` | `/api/license/status` | 로그인 | 현재 라이선스 상태 | +| `POST` | `/api/license/trial` | admin | 체험 라이선스 발급 | +| `POST` | `/api/license/activate` | admin | 라이선스 키 활성화 | +| `POST` | `/api/license/verify` | admin | 라이선스 키 검증만 | +| `DELETE` | `/api/license` | admin | 라이선스 비활성화 | +| `GET` | `/api/license/history` | admin | 등록 이력 조회 | + +### 요청/응답 예시 + +**체험 라이선스 발급** +```bash +curl -X POST http://zioinfo.co.kr:8001/api/license/trial \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"customer":"지오정보기술","days":7}' +``` +```json +{ + "message": "🎁 7일 무료 체험이 시작되었습니다!", + "license_id": "TRL-XXXXXXXXXX", + "edition": "TRIAL", + "customer": "지오정보기술", + "expires_at": "2026-06-06T11:37:25", + "days_remaining": 7, + "is_trial": true, + "license_key": "GRD-..." +} +``` + +**라이선스 상태 조회** +```bash +curl http://zioinfo.co.kr:8001/api/license/status \ + -H "Authorization: Bearer $TOKEN" +``` +```json +{ + "activated": true, + "valid": true, + "expired": false, + "is_trial": true, + "edition": "TRIAL", + "customer": "지오정보기술", + "days_remaining": 6, + "limits": { + "max_institutions": 1, + "max_users": 10, + "max_servers": 20 + }, + "message": "TRIAL [체험판] 라이선스 활성 (6일 남음)" +} +``` + +--- + +## 6. 테스트 결과 + +### 6-1. 테스트 환경 + +| 항목 | 값 | +|------|-----| +| 서버 | Ubuntu 24.04 (zioinfo.co.kr) | +| GUARDiA ITSM | v2.0.0 | +| GUARDiA Manager | v1.0.0 | +| 테스트 일자 | 2026-05-30 | + +### 6-2. 테스트 결과 (7/7 PASS) + +| 테스트 | 항목 | 결과 | +|--------|------|------| +| T1 | admin 로그인 (JSON) | ✅ PASS | +| T2 | 라이선스 현재 상태 조회 | ✅ PASS | +| T3 | 체험 라이선스 발급 (7일) | ✅ PASS | +| T4 | 활성화 후 상태 확인 (TRIAL, 6일) | ✅ PASS | +| T5 | 라이선스 이력 조회 (1건) | ✅ PASS | +| T6 | 잘못된 키 검증 (에러 처리) | ✅ PASS | +| T7 | Manager UI 접속 | ✅ PASS | +| T8 | Manager Backend API | ✅ PASS | + +### 6-3. 버그 수정 이력 + +| 날짜 | 파일 | 버그 | 수정 내용 | +|------|------|------|---------| +| 2026-05-30 | `routers/license.py` | `datetime` timezone-aware/naive 충돌 | `datetime.fromisoformat(...).replace(tzinfo=None)` 적용 | + +--- + +## 7. 운영 절차 + +### 7-1. 정기 만료일 모니터링 + +```bash +# 라이선스 만료일 확인 (서버 직접 확인) +ssh root@zioinfo.co.kr +curl -s -X POST http://localhost:8001/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"1111"}' \ + | python3 -c "import json,sys; print(json.load(sys.stdin).get('access_token')[:20])" + +TOKEN=$(...) +curl -s http://localhost:8001/api/license/status \ + -H "Authorization: Bearer $TOKEN" \ + | python3 -m json.tool | grep -E "edition|days_remaining|expires_at" +``` + +### 7-2. 라이선스 갱신 절차 + +1. 새 라이선스 키 발급 (담당자에게 요청) +2. GUARDiA Manager → 라이선스 관리 → [🔍 키 검증]으로 사전 확인 +3. [🔑 라이선스 등록] → 새 키 입력 → 활성화 +4. 기존 라이선스는 자동 비활성화됨 + +### 7-3. 라이선스 환경변수 설정 + +STANDARD/ENTERPRISE 라이선스 사용 시 서버에 마스터 키 설정이 필요합니다: + +```bash +# /opt/guardia/app/.env 에 추가 +GUARDIA_LICENSE_KEY=<64자리 hex 마스터 키> + +# 서비스 재시작 +systemctl restart guardia +``` + +--- + +## 8. 트러블슈팅 + +| 증상 | 원인 | 해결 | +|------|------|------| +| 체험판 발급 실패 (409) | 이미 체험 이력 존재 | 체험은 1회 한정, 정식 키 필요 | +| 키 검증/등록 500 에러 | `GUARDIA_LICENSE_KEY` 미설정 | .env에 마스터 키 설정 | +| 이력 조회 403 | admin 역할 아님 | admin 계정으로 로그인 | +| 만료 배너 표시 | 만료 3일 이내 | 새 라이선스 등록 | +| `datetime` 오류 | timezone aware/naive 충돌 | `license.py` 패치 적용 (완료) | + +--- + +*GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30* diff --git a/26_GUARDiA_Manager_라이선스_가이드.pdf b/26_GUARDiA_Manager_라이선스_가이드.pdf new file mode 100644 index 0000000..9bc557d Binary files /dev/null and b/26_GUARDiA_Manager_라이선스_가이드.pdf differ diff --git a/27_GUARDiA_Manager_라이선스_발표자료.pptx b/27_GUARDiA_Manager_라이선스_발표자료.pptx new file mode 100644 index 0000000..605e60f Binary files /dev/null and b/27_GUARDiA_Manager_라이선스_발표자료.pptx differ diff --git a/28_GUARDiA_폐쇄망_데이터연동_가이드.md b/28_GUARDiA_폐쇄망_데이터연동_가이드.md new file mode 100644 index 0000000..1a731ef --- /dev/null +++ b/28_GUARDiA_폐쇄망_데이터연동_가이드.md @@ -0,0 +1,76 @@ +# GUARDiA 폐쇄망 ↔ 개방망 데이터 연동 가이드 + +> **버전**: 1.0.0 | **작성일**: 2026-05-30 +> **서버**: zioinfo.co.kr | **대상**: 시스템 관리자 (admin) + +--- + +## 1. 개요 + +폐쇄망에 설치된 GUARDiA ITSM의 데이터(SR, CMDB, 기관, 감사로그)를 +개방망 GUARDiA Manager로 안전하게 이관하는 Export/Import 인터페이스입니다. + +### 보안 특징 +- **HMAC-SHA256 서명**: 번들 파일 위변조 방지 +- **민감 정보 자동 마스킹**: IP 주소, SSH 비밀번호는 `****`로 처리 +- **Dry Run 모드**: 실제 저장 전 사전 검증 +- **중복 방지**: sr_id 기준 중복 SKIP + +--- + +## 2. API 엔드포인트 + +| 메서드 | 경로 | 설명 | +|--------|------|------| +| `GET` | `/api/export-import/export/bundle` | 전체 번들 ZIP (권장) | +| `GET` | `/api/export-import/export/sr` | SR 목록 JSON | +| `GET` | `/api/export-import/export/cmdb` | CMDB 서버 자산 JSON | +| `GET` | `/api/export-import/export/institutions` | 기관 목록 JSON | +| `GET` | `/api/export-import/export/audit` | 감사 로그 JSON | +| `POST` | `/api/export-import/import/bundle` | 번들 ZIP Import | +| `POST` | `/api/export-import/import/sr` | SR JSON Import | + +--- + +## 3. 사용 방법 + +### 3-1. 폐쇄망에서 Export + +```bash +# 전체 번들 다운로드 (권장) +TOKEN=$(curl -s -X POST http://폐쇄망-IP:8001/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"비밀번호"}' \ + | python3 -c "import json,sys; print(json.load(sys.stdin).get('access_token'))") + +curl -O http://폐쇄망-IP:8001/api/export-import/export/bundle \ + -H "Authorization: Bearer $TOKEN" +# → guardia_export_20260530_HHMMSS.zip 저장 +``` + +### 3-2. 개방망 Manager에서 Import + +1. `http://zioinfo.co.kr:8090/export-import` 접속 +2. 번들 ZIP 파일 드래그 & 드롭 +3. **[🔍 검증 실행]** — Dry Run으로 내용 확인 +4. 이상 없으면 **Dry Run 체크 해제** → **[📥 Import 실행]** + +--- + +## 4. 테스트 결과 (7/7 PASS) + +| 테스트 | 결과 | +|--------|------| +| SR Export | ✅ PASS | +| CMDB Export | ✅ PASS | +| 기관 Export | ✅ PASS | +| 감사 로그 Export | ✅ PASS | +| 번들 ZIP Export (HMAC) | ✅ PASS | +| SR Import dry_run | ✅ PASS | +| Manager UI 접속 | ✅ PASS | + +**버그 수정**: `date` 타입 JSON 직렬화 오류 → `isoformat()` 처리 완료 + +--- + +*GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30* diff --git a/29_GUARDiA_폐쇄망_데이터연동_가이드.pdf b/29_GUARDiA_폐쇄망_데이터연동_가이드.pdf new file mode 100644 index 0000000..444d69d Binary files /dev/null and b/29_GUARDiA_폐쇄망_데이터연동_가이드.pdf differ diff --git a/30_GUARDiA_폐쇄망_데이터연동_발표자료.pptx b/30_GUARDiA_폐쇄망_데이터연동_발표자료.pptx new file mode 100644 index 0000000..c3679c3 Binary files /dev/null and b/30_GUARDiA_폐쇄망_데이터연동_발표자료.pptx differ diff --git a/31_AI_플랫폼_제안서.md b/31_AI_플랫폼_제안서.md new file mode 100644 index 0000000..22bb5b0 --- /dev/null +++ b/31_AI_플랫폼_제안서.md @@ -0,0 +1,134 @@ +# GUARDiA AI 플랫폼 최적 오픈소스 제안서 + +> **서버**: zio-server (zioinfo.co.kr) | **작성일**: 2026-05-30 +> **현재 사양**: 2 vCPU / 7.8GB RAM / 99GB Disk / Ubuntu 24.04 +> **기설치**: Ollama 0.24.0 + Llama3:8b (4.7GB) + +--- + +## 1. 제안 아키텍처 + +``` +사용자/메신저 명령 + ↓ +[GUARDiA ITSM FastAPI] + ↓ +[LangChain Orchestration Layer] ← 에이전트 체인, 툴 호출 + ↓ +┌─────────────────────────────────────┐ +│ RAG 파이프라인 │ +│ ChromaDB (벡터 검색) │ +│ ← GUARDiA KB, 코드베이스, 문서 임베딩 │ +└─────────────────────────────────────┘ + ↓ +[Ollama] → Llama3:8b (온프레미스 LLM) +``` + +--- + +## 2. LLM / sLLM 추천 + +| 순위 | 모델 | RAM 요구 | 특징 | GUARDiA 적합성 | +|------|------|---------|------|--------------| +| ⭐1 | **llama3:8b** (기설치) | ~5GB | 범용 추론, 한국어 양호 | ✅ 현재 운영 중 | +| 2 | **qwen2.5:7b** | ~5GB | 한국어 성능 우수, 코드 생성 | ✅ 권장 추가 | +| 3 | **codellama:7b** | ~4GB | 코드 리뷰, SSH 명령 생성 | ✅ 코드 분석용 | +| 4 | **mistral:7b** | ~5GB | 빠른 응답, 영어 최적 | 보조 모델 | +| 5 | llama3.2:3b | ~2.5GB | 경량, 빠름 | 빠른 응답 전용 | + +**현재 서버 RAM 7.8GB 제한으로 동시 2개 모델 한계 → llama3:8b + codellama:7b 조합 권장** + +--- + +## 3. AI 프레임워크 추천 + +### 3-1. LangChain (핵심 추천 ⭐) + +| 항목 | 내용 | +|------|------| +| 용도 | 에이전트 체인, 툴 호출, RAG 파이프라인 | +| GUARDiA 연동 | FastAPI 내부에서 `ChatOllama` 사용 | +| 패키지 | `langchain`, `langchain-community`, `langchain-ollama` | + +```python +from langchain_ollama import ChatOllama +from langchain_core.messages import HumanMessage + +llm = ChatOllama(model="llama3:8b", base_url="http://localhost:11434") +response = llm.invoke([HumanMessage(content="서버 재시작 명령 생성")]) +``` + +### 3-2. LangGraph (에이전트 워크플로우) + +| 항목 | 내용 | +|------|------| +| 용도 | 복잡한 멀티 에이전트 상태 머신 | +| GUARDiA 연동 | SR 처리 → 승인 → 배포 플로우 구현 | +| 패키지 | `langgraph` | + +### 3-3. Haystack (RAG 파이프라인) + +| 항목 | 내용 | +|------|------| +| 용도 | 문서 검색, Q&A, KB 기반 응답 | +| GUARDiA 연동 | KB, 운영 메뉴얼 RAG 검색 | +| 패키지 | `haystack-ai`, `farm-haystack` | + +--- + +## 4. 벡터 DB 추천 (별도 문서: 32_벡터DB_제안서.md) + +| 순위 | DB | 특징 | 설치 방식 | +|------|-----|------|---------| +| ⭐1 | **ChromaDB** | 경량, Python native, 로컬 파일 | pip install | +| 2 | **Qdrant** | 고성능, REST API, Docker | Docker 컨테이너 | +| 3 | Weaviate | 풍부한 기능, 무거움 | Docker 컨테이너 | +| 4 | FAISS | Meta제작, 초고속, 메모리 전용 | pip install | + +**서버 사양(7.8GB RAM) 고려 → ChromaDB (로컬 파일 기반) 1차 권장** + +--- + +## 5. 임베딩 모델 추천 + +| 모델 | 크기 | 한국어 | 용도 | +|------|------|--------|------| +| **nomic-embed-text** | 274MB | 양호 | Ollama 내장, 온프레미스 | +| all-MiniLM-L6-v2 | 80MB | 미흡 | 영어 최적, 빠름 | +| bge-m3 | 570MB | 우수 | 다국어 최고 성능 | + +**GUARDiA 권장: `nomic-embed-text` (Ollama 내장, 외부 API 불필요)** + +--- + +## 6. 설치 계획 (zio 서버) + +### Phase 1: LangChain + RAG 기반 +```bash +pip install langchain langchain-community langchain-ollama \ + langchain-chroma chromadb sentence-transformers +ollama pull nomic-embed-text +``` + +### Phase 2: 추가 모델 +```bash +ollama pull qwen2.5:7b # 한국어 강화 +ollama pull codellama:7b # 코드 리뷰 +``` + +### Phase 3: GUARDiA 코드베이스 학습 +```python +from langchain_community.document_loaders import DirectoryLoader +from langchain_chroma import Chroma +from langchain_ollama import OllamaEmbeddings + +loader = DirectoryLoader("/opt/guardia/app", glob="**/*.py") +docs = loader.load() +embeddings = OllamaEmbeddings(model="nomic-embed-text") +vectordb = Chroma.from_documents(docs, embeddings, + persist_directory="/opt/guardia/vectordb") +``` + +--- + +*GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30* diff --git a/32_벡터DB_제안서.md b/32_벡터DB_제안서.md new file mode 100644 index 0000000..84e2f9c --- /dev/null +++ b/32_벡터DB_제안서.md @@ -0,0 +1,124 @@ +# GUARDiA 벡터 데이터베이스 제안서 + +> **서버**: zio-server (zioinfo.co.kr) | **작성일**: 2026-05-30 + +--- + +## 1. 벡터 DB 비교 + +| DB | 설치 | 메모리 | 영속성 | REST API | 한국어 | GUARDiA 적합성 | +|----|------|--------|--------|---------|--------|--------------| +| **ChromaDB** | pip | 낮음 | 파일 기반 | ❌ (Python) | ✅ | ⭐ 1순위 | +| **Qdrant** | Docker | 중간 | 볼륨 | ✅ | ✅ | ⭐ 2순위 | +| FAISS | pip | 낮음 | 없음(메모리) | ❌ | ✅ | 빠른 검색 | +| Weaviate | Docker | 높음 | 볼륨 | ✅ | ✅ | 대규모 용 | +| Milvus | Docker | 높음 | 볼륨 | ✅ | ✅ | 엔터프라이즈 | + +--- + +## 2. 최종 선택: ChromaDB + +### 선택 이유 +- **설치 간편**: `pip install chromadb` 한 줄 +- **서버 RAM 절약**: 7.8GB RAM 환경에 최적 +- **Python 직접 통합**: GUARDiA FastAPI와 완벽 연동 +- **로컬 파일 영속**: 별도 서비스 불필요 +- **Ollama 임베딩 연동**: `nomic-embed-text` 온프레미스 임베딩 + +### 설치 및 설정 +```bash +# 설치 +/opt/guardia/venv/bin/pip install chromadb langchain-chroma + +# 디렉터리 +mkdir -p /opt/guardia/vectordb +``` + +### GUARDiA 연동 코드 +```python +import chromadb +from chromadb.config import Settings + +# 클라이언트 초기화 +client = chromadb.PersistentClient( + path="/opt/guardia/vectordb", + settings=Settings(anonymized_telemetry=False) +) + +# 컬렉션 생성 +collection = client.get_or_create_collection( + name="guardia_kb", + metadata={"hnsw:space": "cosine"} +) + +# 문서 추가 +collection.add( + documents=["서버 재시작 절차: systemctl restart guardia"], + metadatas=[{"type": "kb", "category": "deployment"}], + ids=["kb-001"] +) + +# 유사도 검색 +results = collection.query( + query_texts=["guardia 서비스 재시작하는 방법"], + n_results=3 +) +``` + +--- + +## 3. 2순위: Qdrant (Docker) + +RAM이 충분하거나 REST API가 필요한 경우 추천합니다. + +```bash +docker run -d --name qdrant \ + -p 6333:6333 \ + -v /opt/guardia/qdrant:/qdrant/storage \ + qdrant/qdrant +``` + +--- + +## 4. GUARDiA 코드베이스 학습 계획 + +### 학습 데이터 소스 +| 소스 | 내용 | 문서 수 예상 | +|------|------|------------| +| `/opt/guardia/app/routers/*.py` | API 라우터 코드 | ~70개 | +| `/opt/guardia/app/core/*.py` | 비즈니스 로직 | ~38개 | +| `/opt/guardia/app/models.py` | 데이터 모델 | 1개 (대용량) | +| `/opt/guardia/app/static/` | UI 코드 | ~20개 | +| `C:/GUARDiA/manual/*.md` | 운영 문서 | ~32개 | + +### 임베딩 스크립트 +```python +# /opt/guardia/app/scripts/embed_codebase.py +from langchain_community.document_loaders import DirectoryLoader, TextLoader +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_ollama import OllamaEmbeddings +from langchain_chroma import Chroma + +loader = DirectoryLoader("/opt/guardia/app", glob="**/*.py", + loader_cls=TextLoader, loader_kwargs={"encoding": "utf-8"}) +docs = loader.load() + +splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) +chunks = splitter.split_documents(docs) + +embeddings = OllamaEmbeddings( + model="nomic-embed-text", + base_url="http://localhost:11434" +) + +vectordb = Chroma.from_documents( + chunks, embeddings, + persist_directory="/opt/guardia/vectordb", + collection_name="guardia_codebase" +) +print(f"임베딩 완료: {len(chunks)}개 청크") +``` + +--- + +*GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30* diff --git a/33_GUARDiA_Messenger_앱_스토어_등록_가이드.md b/33_GUARDiA_Messenger_앱_스토어_등록_가이드.md new file mode 100644 index 0000000..5a756a3 --- /dev/null +++ b/33_GUARDiA_Messenger_앱_스토어_등록_가이드.md @@ -0,0 +1,244 @@ +# GUARDiA Messenger 앱 스토어 등록 가이드 + +> **앱명**: GUARDiA Messenger +> **패키지**: kr.co.zioinfo.guardia +> **버전**: 1.0.0 +> **작성일**: 2026-05-31 + +--- + +## 1. 사전 준비 + +### 1-1. 필요 계정 + +| 스토어 | 계정 | 비용 | 등록 링크 | +|--------|------|------|----------| +| **Google Play** | Google Play Developer | $25 (1회) | play.google.com/console | +| **Apple App Store** | Apple Developer Program | $99/년 | developer.apple.com | +| **Expo EAS** | Expo 계정 (무료) | 무료 | expo.dev | + +### 1-2. 환경 준비 + +```bash +# EAS CLI 설치 +npm install -g eas-cli + +# Expo 로그인 +eas login + +# 프로젝트 초기화 +cd C:\GUARDiA\app +eas init --id guardia-messenger-zioinfo +``` + +--- + +## 2. EAS Build — 클라우드 빌드 (Mac 불필요) + +### 2-1. Android APK/AAB 빌드 + +```bash +cd C:\GUARDiA\app + +# 테스트용 APK (내부 배포) +eas build --platform android --profile preview + +# 스토어 배포용 AAB +eas build --platform android --profile production +``` + +빌드 완료 후 `.aab` 파일 다운로드 → Google Play Console에 업로드 + +### 2-2. iOS IPA 빌드 + +```bash +# App Store Connect용 IPA (Mac 없이 클라우드 빌드) +eas build --platform ios --profile production +``` + +> Apple Developer 계정이 필요하며 EAS가 자동으로 인증서와 프로파일을 관리합니다. + +--- + +## 3. Google Play Store 등록 + +### 3-1. 앱 생성 + +1. **play.google.com/console** 접속 → Google Play Developer 로그인 +2. [앱 만들기] 클릭 +3. 정보 입력: + +| 항목 | 값 | +|------|-----| +| 앱 이름 | GUARDiA Messenger | +| 기본 언어 | 한국어 | +| 앱 유형 | 앱 | +| 무료/유료 | 무료 | + +### 3-2. 스토어 등록 정보 + +**앱 이름**: GUARDiA Messenger + +**간략한 설명** (80자 이내): +``` +AI 기반 IT 인프라 자율 운영 플랫폼 — GUARDiA ITSM 모바일 앱 +``` + +**자세한 설명** (4000자 이내): +``` +GUARDiA Messenger는 (주)지오정보기술의 AI 기반 레거시 인프라 자율 운영 플랫폼 +GUARDiA ITSM과 연동하는 공식 모바일 앱입니다. + +주요 기능: +• 서비스 요청(SR) 실시간 조회 및 등록 +• AI 챗봇을 통한 자연어 인프라 명령 +• 인시던트 및 긴급 알림 즉시 수신 +• SLA 위반 경고 및 배포 상태 모니터링 +• 라이선스 현황 확인 + +GUARDiA ITSM 서버 주소 입력 후 사용 가능합니다. + +[지원]: guardia@zioinfo.co.kr +[회사]: (주)지오정보기술 +``` + +**카테고리**: 비즈니스 + +**키워드**: ITSM, 인프라관리, IT운영, 서버관리, ChatOps, 모니터링 + +### 3-3. 콘텐츠 등급 설문 + +- 폭력: 없음 +- 성인 콘텐츠: 없음 +- 도박: 없음 +- → **모든 연령** 등급 + +### 3-4. AAB 업로드 + +``` +프로덕션 트랙 → 새 버전 만들기 +→ eas build에서 다운로드한 .aab 업로드 +→ 버전 노트 (한국어): + "GUARDiA Messenger 첫 번째 릴리즈 + - 대시보드, SR 관리, AI 챗봇, 알림" +→ 검토 시작 (2~7일 소요) +``` + +--- + +## 4. Apple App Store 등록 + +### 4-1. App Store Connect 앱 생성 + +1. **appstoreconnect.apple.com** 접속 +2. [나의 앱] → [+] → [새 앱] +3. 정보 입력: + +| 항목 | 값 | +|------|-----| +| 플랫폼 | iOS | +| 이름 | GUARDiA Messenger | +| 기본 언어 | 한국어 | +| 번들 ID | kr.co.zioinfo.guardia | +| SKU | guardia-messenger-kr | + +### 4-2. 앱 정보 + +**카테고리**: 비즈니스 (Business) + +**부제**: AI 기반 IT 인프라 운영 플랫폼 + +**프라이버시 정책 URL**: +``` +https://zioinfo.co.kr/privacy +``` + +> ⚠️ App Store는 Privacy Policy URL이 필수입니다. 홈페이지에 개인정보처리방침 페이지를 추가해야 합니다. + +### 4-3. 스크린샷 + +각 기기별 최소 1개 스크린샷 필요: +- iPhone 6.7인치 (1290×2796): 필수 +- iPhone 6.5인치 (1242×2688): 필수 +- iPad Pro 12.9인치: 선택 + +```bash +# EAS로 시뮬레이터 스크린샷 자동 생성 (권장) +eas metadata:pull +``` + +### 4-4. IPA 업로드 및 심사 + +```bash +# EAS Submit으로 자동 업로드 +eas submit --platform ios --profile production + +# 또는 Transporter 앱 사용 (Mac에서) +``` + +심사 기간: **1~3일** (영업일 기준) + +--- + +## 5. 전체 진행 순서 + +``` +1. Expo 계정 생성 (expo.dev) + ↓ +2. eas login / eas init + ↓ +3. Google Play Console 개발자 등록 ($25) + ↓ +4. Apple Developer Program 등록 ($99/년) + ↓ +5. eas build --platform android --profile production + ↓ +6. eas build --platform ios --profile production + ↓ +7. 각 스토어에 메타데이터 등록 + ↓ +8. 빌드 업로드 (eas submit 또는 수동) + ↓ +9. 심사 대기 (Android 2~7일, iOS 1~3일) + ↓ +10. 출시 🎉 +``` + +--- + +## 6. 개인정보처리방침 (홈페이지 추가 필요) + +App Store 필수 요구사항: + +``` +zioinfo.co.kr/privacy 페이지에 아래 내용 추가: + +1. 수집 정보: 이메일, 사용자명 (JWT 인증 목적) +2. 보관 기간: 앱 삭제 시 즉시 삭제 (기기 내 보관) +3. 제3자 제공: 없음 +4. 문의: guardia@zioinfo.co.kr +``` + +--- + +## 7. 빠른 시작 명령어 + +```bash +# 1. EAS 설정 +cd C:\GUARDiA\app +npm install -g eas-cli +eas login +eas init + +# 2. Android 빌드 (바로 가능) +eas build --platform android --profile preview +# → APK 다운로드 URL 생성 (테스트 설치 가능) + +# 3. 정식 빌드 +eas build --platform android --profile production +eas build --platform ios --profile production +``` + +--- + +*GUARDiA Messenger v1.0.0 | (주)지오정보기술 | 2026-05-31* diff --git a/34_GUARDiA_Messenger_개발_배포_가이드.md b/34_GUARDiA_Messenger_개발_배포_가이드.md new file mode 100644 index 0000000..f0b1cd9 --- /dev/null +++ b/34_GUARDiA_Messenger_개발_배포_가이드.md @@ -0,0 +1,288 @@ +# GUARDiA Messenger 개발 및 배포 가이드 + +> **버전**: 1.0.0 | **작성일**: 2026-05-31 +> **기술 스택**: React Native + Expo SDK 51 + EAS Build +> **EAS 계정**: zioinfo | **성공 빌드**: 51096ada + +--- + +## 1. 앱 개요 + +GUARDiA Messenger는 (주)지오정보기술의 GUARDiA ITSM 플랫폼과 연동하는 공식 모바일 앱입니다. + +### 주요 기능 + +| 화면 | 기능 | +|------|------| +| 로그인 | GUARDiA ITSM JWT 인증 | +| 대시보드 | SR 통계, 서비스 상태, 배포 이력 | +| SR 관리 | 서비스 요청 목록 조회 및 신규 등록 | +| AI 챗봇 | Ollama LLM 기반 자연어 인프라 명령 | +| 알림 | 인시던트·SLA·배포 알림 수신 | +| 설정 | 프로필, 알림 설정, 로그아웃 | + +### 기술 스택 + +| 항목 | 기술 | +|------|------| +| 프레임워크 | React Native 0.74.5 + Expo SDK 51 | +| 언어 | TypeScript (strict) | +| 라우터 | Expo Router 3.5.x | +| 인증 저장소 | expo-secure-store (보안 키체인) | +| HTTP 클라이언트 | Axios | +| 빌드 시스템 | EAS Build (Expo Application Services) | +| 서버 연결 | https://zioinfo.co.kr:8443 | + +--- + +## 2. 개발 환경 설정 + +### 2-1. 필수 설치 + +```bash +# Node.js 20 LTS +node --version # v20 이상 + +# Expo CLI +npm install -g expo-cli eas-cli + +# 프로젝트 설치 +cd C:\GUARDiA\app +npm install --legacy-peer-deps +``` + +### 2-2. 로컬 개발 서버 + +```bash +cd C:\GUARDiA\app +npx expo start + +# QR 코드 스캔 → Expo Go 앱에서 실행 (개발 전용) +# 또는 npx expo start --android (에뮬레이터) +``` + +### 2-3. 프로젝트 구조 + +``` +C:\GUARDiA\app\ +├── app/ +│ ├── _layout.tsx ← 루트 레이아웃 (인증 라우팅) +│ ├── (auth)/login.tsx ← 로그인 +│ └── (tabs)/ +│ ├── _layout.tsx ← 탭 네비게이터 +│ ├── index.tsx ← 대시보드 +│ ├── sr.tsx ← SR 관리 +│ ├── chat.tsx ← AI 챗봇 +│ ├── notifications.tsx← 알림 +│ └── settings.tsx ← 설정 +├── components/ ← 공통 컴포넌트 +├── constants/Config.ts ← 서버 URL, 컬러 시스템 +├── hooks/useAuth.ts ← JWT 인증 훅 +├── services/api.ts ← GUARDiA ITSM API 클라이언트 +├── plugins/ +│ └── withGradleProps.js ← Gradle 빌드 최적화 +├── assets/ ← 아이콘, 스플래시 이미지 +├── app.json ← Expo 앱 설정 +├── eas.json ← EAS 빌드 프로필 +├── babel.config.js ← Babel 설정 +└── .easignore ← EAS 업로드 제외 목록 +``` + +--- + +## 3. EAS Build 가이드 + +### 3-1. EAS 초기 설정 (최초 1회) + +```bash +cd C:\GUARDiA\app + +# EAS 로그인 +npx eas-cli login +# → expo.dev 계정 입력 + +# 프로젝트 초기화 +npx eas-cli init +# → 프로젝트 ID 자동 등록 +``` + +### 3-2. 빌드 명령어 + +```bash +# Android APK (테스트 배포 — 약 10~15분) +npx eas-cli build --platform android --profile preview + +# Android AAB (Play Store 제출용) +npx eas-cli build --platform android --profile production + +# iOS IPA (App Store 제출용, Apple Developer $99/년 필요) +npx eas-cli build --platform ios --profile production +``` + +### 3-3. 빌드 전 필수 체크리스트 + +| 항목 | 확인 방법 | 올바른 상태 | +|------|---------|-----------| +| android/ 폴더 없음 | `ls android/` | 없어야 함 | +| .easignore 설정 | `cat .easignore` | `android/`, `ios/` 포함 | +| PNG Crunching 비활성화 | `cat plugins/withGradleProps.js` | `false` 확인 | +| babel.config.js | `cat babel.config.js` | `babel-preset-expo`만 | +| EAS 로그인 | `npx eas-cli whoami` | `zioinfo` 표시 | + +### 3-4. APK 폰 설치 방법 + +1. 안드로이드 폰에서 **Expo 빌드 URL** 열기 +2. **Download** 버튼 탭 +3. 설정 → 보안 → 알 수 없는 앱 설치 허용 (최초 1회) +4. APK 설치 완료 + +> **주의**: `adb` / Android Studio 불필요 — 브라우저 직접 다운로드로 설치 + +--- + +## 4. 빌드 이슈 이력 및 해결책 + +> 이 섹션은 실제 빌드 과정에서 발생한 이슈와 해결책입니다. 동일한 이슈 재발 시 이 섹션을 참조하세요. + +### 이슈 1: android/ 폴더 → EAS Bare Workflow 오인 + +**증상**: `EAS_BUILD_UNKNOWN_GRADLE_ERROR` (반복 실패) + +**원인 흐름**: +``` +로컬 expo prebuild 실행 + → android/ 폴더 생성 + → EAS 업로드에 포함 + → EAS: "Bare Workflow 프로젝트" 인식 + → 로컬 android/ 그대로 사용 (EAS 환경과 불일치) + → Gradle 빌드 실패 +``` + +**해결**: `.easignore` 파일에 추가 +``` +android/ +ios/ +``` + +### 이슈 2: PIL PNG + AAPT2 PNG Crunching 충돌 + +**증상**: `expo-splash-screen:packageReleaseResources` 단계 실패 + +**원인**: Python PIL 생성 PNG + `enablePngCrunchInReleaseBuilds=true` → AAPT2 처리 불가 + +**해결**: `plugins/withGradleProps.js` 생성 +```javascript +const { withGradleProperties } = require('@expo/config-plugins') +module.exports = function(config) { + return withGradleProperties(config, (cfg) => { + const set = (key, value) => { + const idx = cfg.modResults.findIndex(p => p.key === key) + idx !== -1 ? cfg.modResults[idx].value = value + : cfg.modResults.push({ type:'property', key, value }) + } + set('android.enablePngCrunchInReleaseBuilds', 'false') + set('reactNativeArchitectures', 'arm64-v8a') + set('org.gradle.jvmargs', '-Xmx4096m -XX:MaxMetaspaceSize=1024m') + return cfg + }) +} +``` + +### 이슈 3: expo-notifications 플러그인 빌드 실패 + +**증상**: Firebase 관련 Gradle 오류 + +**원인**: `expo-notifications` 플러그인이 `google-services.json` 파일 필요 + +**해결**: `app.json` plugins에서 `expo-notifications` 제거. 푸시 알림 추가 시 Firebase 설정 후 재등록. + +### 이슈 4: expo-router/babel deprecated + +**증상**: Babel 경고, 일부 환경에서 빌드 오류 + +**해결**: `babel.config.js` +```javascript +module.exports = function(api) { + api.cache(true) + return { presets: ['babel-preset-expo'] } +} +``` + +### 최종 성공한 빌드 + +| 항목 | 값 | +|------|-----| +| 빌드 ID | 51096ada-9735-4ea8-9e81-5f5991731ea8 | +| 플랫폼 | Android (APK) | +| EAS 계정 | zioinfo | +| 소요 시간 | 약 12분 | + +--- + +## 5. 스토어 등록 절차 + +### 5-1. 사전 준비 + +| 항목 | Google Play | Apple App Store | +|------|------------|-----------------| +| 개발자 계정 | $25 (1회) | $99/년 | +| Privacy Policy | 필수 | 필수 | +| 스크린샷 | 최소 2개 | 최소 3개 (6.7인치) | +| APK/IPA | AAB 파일 | IPA 파일 | + +### 5-2. Google Play Store 등록 + +``` +1. play.google.com/console 접속 +2. [앱 만들기] → 패키지명: kr.co.zioinfo.guardia +3. 스토어 등록정보 → 메타데이터 입력 +4. 콘텐츠 등급 → 모든 연령 (비즈니스 앱) +5. 프로덕션 트랙 → AAB 업로드 +6. 검토 제출 (2~7 영업일) +``` + +### 5-3. Apple App Store 등록 + +``` +1. appstoreconnect.apple.com 접속 +2. [새 앱] → 번들 ID: kr.co.zioinfo.guardia +3. 앱 정보 + 스크린샷 업로드 +4. Privacy Policy URL 등록 (필수!) +5. npx eas-cli submit --platform ios --profile production +6. 심사 제출 (1~3 영업일) +``` + +--- + +## 6. 앱 로그인 정보 + +| 항목 | 값 | +|------|-----| +| 서버 URL | https://zioinfo.co.kr:8443 | +| 관리자 계정 | admin | +| 비밀번호 | Admin@zioinfo2026! | + +--- + +## 7. 하네스 사용법 + +이 프로젝트는 `.claude/` 하네스가 구성되어 있습니다. + +``` +기능 개발 요청: "SR 상세 화면 추가해줘" + → messenger-orchestrator 스킬 트리거 + → rn-developer가 화면 구현 + → eas-engineer가 빌드 검증 + +빌드 요청: "Android APK 빌드해줘" + → eas-build-deploy 스킬 참조 + → 체크리스트 확인 후 빌드 실행 + +스토어 등록: "Play Store 등록 도와줘" + → store-publish 스킬 참조 + → 메타데이터 + 절차 가이드 +``` + +--- + +*GUARDiA Messenger v1.0.0 | (주)지오정보기술 | 2026-05-31* diff --git a/35_GUARDiA_Messenger_개발가이드.pdf b/35_GUARDiA_Messenger_개발가이드.pdf new file mode 100644 index 0000000..d76cdac Binary files /dev/null and b/35_GUARDiA_Messenger_개발가이드.pdf differ diff --git a/36_GUARDiA_Messenger_발표자료.pptx b/36_GUARDiA_Messenger_발표자료.pptx new file mode 100644 index 0000000..9230d76 Binary files /dev/null and b/36_GUARDiA_Messenger_발표자료.pptx differ diff --git a/37_세션_전체_작업_요약.md b/37_세션_전체_작업_요약.md new file mode 100644 index 0000000..61160e4 --- /dev/null +++ b/37_세션_전체_작업_요약.md @@ -0,0 +1,206 @@ +# GUARDiA 프로젝트 세션 전체 작업 요약 + +> **세션 날짜**: 2026-05-30 ~ 2026-05-31 +> **작성 목적**: clear 전 중요 내용 및 스킬 보존 + +--- + +## 1. 서버 구성 (zioinfo.co.kr) + +### 설치 완료 SW + +| 카테고리 | SW | 포트 | +|----------|-----|------| +| 웹 서버 | Nginx 1.24 | 80/443 | +| 홈페이지 | Spring Boot 3.2.5 | 8082 | +| ITSM | GUARDiA FastAPI v2.0 | 8001/8443 | +| 관리자 | GUARDiA Manager | 8090/8002 | +| DB | PostgreSQL 16.11 | 5432 | +| AI | Ollama + Llama3:8b + nomic-embed-text | 11434 | +| AI 프레임워크 | LangChain 1.3.2 + ChromaDB 1.5.9 | - | +| Git | Gitea 1.22.3 | 3000 | +| CI/CD | Jenkins 2.555 + Deploy Webhook | 8080/9999 | +| 메일 | Postfix + Dovecot | 25/587/143/993 | + +### Gitea 저장소 + +``` +http://zioinfo.co.kr:3000 +├── zio/zioinfo-web ← 홈페이지 (Spring Boot + React) +├── zio/guardia-itsm ← GUARDiA ITSM (FastAPI) +└── zio/guardia-messenger ← GUARDiA Messenger (React Native) +``` + +### CI/CD 흐름 + +``` +git push gitea main + → Gitea Webhook → Deploy Server(9999) + → git pull → 빌드 → systemctl restart +``` + +--- + +## 2. 개발 완료 시스템 + +### 2-1. GUARDiA ITSM (기존 + 추가) + +- **개방망 지원**: HTTPS 8443, API Key 인증, CORS 외부 허용 +- **라이선스**: TRIAL/COMMUNITY/STANDARD/ENTERPRISE 관리 +- **Export/Import**: 폐쇄망 데이터 번들 (HMAC-SHA256 서명) +- **AI 프레임워크**: LangChain + ChromaDB + 코드베이스 임베딩 +- **datetime 버그 수정**: timezone-aware/naive 충돌 → .replace(tzinfo=None) + +### 2-2. GUARDiA Manager (신규) + +``` +http://zioinfo.co.kr:8090 +``` + +| 화면 | 기능 | +|------|------| +| 대시보드 | SR 통계, 리소스 게이지, 배포 타임라인 | +| 서버/CMDB | 자산 목록, 서비스 재시작 | +| 배포/CI-CD | 배포 이력, 수동 트리거 | +| 보안 | API Key 발급/관리, 감사 로그 | +| 라이선스 | 에디션 관리, 체험판 발급, 이력 | +| 데이터 연동 | Export/Import (폐쇄망 연동) | +| LLM | Ollama 모델 현황 | +| 설정 | .env 뷰어, Nginx 리로드 | + +### 2-3. GUARDiA Messenger (신규) + +``` +패키지: kr.co.zioinfo.guardia +EAS 계정: zioinfo / 프로젝트: ca2f72d6-... +성공 빌드: 51096ada (Android APK) +``` + +**⚠️ EAS 빌드 필수 원칙**: +1. `.easignore`: android/, ios/ 반드시 포함 +2. `plugins/withGradleProps.js`: `enablePngCrunchInReleaseBuilds=false` +3. `expo-notifications`: app.json plugins 등록 금지 +4. `babel.config.js`: `babel-preset-expo`만 사용 + +**알림 연동**: +- WebSocket: `wss://zioinfo.co.kr:8443/ws/events?token={jwt}` +- 채널: sr, deploy, sla, incident +- `hooks/useWebSocket.ts` 구현 완료 + +### 2-4. 홈페이지 관리자시스템 (신규) + +``` +http://zioinfo.co.kr/admin +계정: admin / Admin@2026! +``` + +- JWT 인증 (GUARDiA ITSM 공유) +- 뉴스/채용/문의 CRUD +- Spring Security + jjwt 0.12.3 + +--- + +## 3. SMTP 메일 서버 + +``` +도메인: @zioinfo.co.kr +서버: mail.zioinfo.co.kr (zioinfo.co.kr) +``` + +| 항목 | 값 | +|------|-----| +| 계정 | info, admin, ythong, choyounbun, guardia | +| 비밀번호 | 1q2w3e!Q (모두 동일) | +| SMTP | :25 / :587 (STARTTLS) | +| IMAP | :143 / :993 (SSL) | +| SPF | v=spf1 ip4:zioinfo.co.kr ~all (가비아) | +| DKIM | mail._domainkey.zioinfo.co.kr (가비아) | +| DMARC | _dmarc.zioinfo.co.kr (가비아) | + +**스팸 방지 중요사항**: +- 이메일 본문에 `http://zioinfo.co.kr:8001` 같은 IP 직접 링크 금지 → 피싱 경고 발생 +- PTR 역방향 DNS 없음 → Gmail 스팸 가능성 → NCloud 지원에 PTR 설정 요청 필요 + +--- + +## 4. 하네스 구조 + +### C:\GUARDiA\itsm (GUARDiA ITSM) + +``` +.claude/agents/: sr-manager, deploy-engineer, incident-responder, sla-guardian +.claude/skills/: guardia-orchestrator, deploy-pipeline, sr-lifecycle, code-review +``` + +### C:\GUARDiA\manager (GUARDiA Manager) + +``` +.claude/agents/: ux-architect, backend-engineer, integration-specialist, security-engineer, devops-engineer +.claude/skills/: manager-orchestrator, manager-ui, manager-api, manager-integration, manager-security, manager-deploy +``` + +### C:\GUARDiA\app (GUARDiA Messenger) + +``` +.claude/agents/: rn-developer, eas-engineer, store-publisher, doc-writer +.claude/skills/: messenger-orchestrator, rn-screen-dev, eas-build-deploy, store-publish, doc-generator + └── rn-screen-dev/references/build-issues.md ← EAS 빌드 이슈 이력 (매우 중요) +``` + +### C:\GUARDiA\workspace\zioinfo-web (홈페이지) + +``` +트리거: workspace-analyzer 스킬 +``` + +--- + +## 5. 매뉴얼 파일 목록 (C:\GUARDiA\manual) + +| 번호 | 파일 | 내용 | +|------|------|------| +| 18 | zio서버_설치SW_목록.md | 전체 SW 목록 | +| 19 | zio서버_운영가이드.md | 운영 절차 | +| 20 | zio서버_CICD_가이드.md | CI/CD 흐름 | +| 21 | zio서버_장애대응_가이드.md | Runbook | +| 22 | GUARDiA_개방망_운영가이드.md | 개방망 설정 | +| 23-24 | 개방망 PDF/PPTX | - | +| 25 | Manager 라이선스 가이드.md | 라이선스 관리 | +| 26-27 | 라이선스 PDF/PPTX | - | +| 28 | 폐쇄망_데이터연동_가이드.md | Export/Import | +| 29-30 | 데이터연동 PDF/PPTX | - | +| 31 | AI_플랫폼_제안서.md | LangChain/ChromaDB 제안 | +| 32 | 벡터DB_제안서.md | ChromaDB 상세 | +| 33 | Messenger_스토어_등록_가이드.md | Play Store/App Store | +| 34 | Messenger_개발_배포_가이드.md | EAS 빌드 가이드 | +| 35-36 | Messenger PDF/PPTX | - | + +--- + +## 6. 주요 계정 정보 + +| 서비스 | ID | PW | +|--------|----|----| +| SSH | root | 1q2w3e!Q | +| GUARDiA ITSM | admin | 1111 | +| GUARDiA Manager | admin | Admin@zioinfo2026! | +| 홈페이지 관리자 | admin | Admin@2026! | +| Gitea | zio | Zio@Admin2026! | +| PostgreSQL | guardia | G@urd1a_2026! (URL: G%40urd1a_2026%21) | +| SMTP 계정 | *@zioinfo.co.kr | 1q2w3e!Q | +| Expo EAS | zioinfo | expo.dev 로그인 | + +--- + +## 7. 알림 테스트 결과 + +### ITSM → Messenger (8/8 PASS) +JWT → WebSocket 연결 → SR 이벤트 브로드캐스트 → 앱 알림탭 수신 + +### Messenger → ITSM (10/10 PASS) +앱 SR탭 등록 → POST /api/tasks → ITSM SR 생성 확인 +AI챗봇 → "nginx 재시작해줘" → AI가 SR 자동 생성 응답 + +--- + +*GUARDiA 프로젝트 세션 정리 | 2026-05-31* diff --git a/38_스킬_트리거_빠른참조.md b/38_스킬_트리거_빠른참조.md new file mode 100644 index 0000000..c9df3ea --- /dev/null +++ b/38_스킬_트리거_빠른참조.md @@ -0,0 +1,81 @@ +# GUARDiA 하네스 스킬 빠른 참조 + +> 새 세션에서 각 스킬을 트리거하는 키워드 모음 + +--- + +## GUARDiA ITSM (C:\GUARDiA\itsm) + +| 스킬 | 트리거 키워드 | +|------|------------| +| `guardia-orchestrator` | SR 처리, 배포 진행, 코드 리뷰, SLA 현황, 인시던트, RCA, 보안 패치, Jira 연동, 다시 실행 | +| `deploy-pipeline` | 배포, 빌드, 릴리즈, Jenkins, VibeSession | +| `sr-lifecycle` | SR 접수, SR 조회, 티켓 관리 | + +--- + +## GUARDiA Manager (C:\GUARDiA\manager) + +| 스킬 | 트리거 키워드 | +|------|------------| +| `manager-orchestrator` | 관리자 화면, M-01~M-08, 대시보드 수정, 서버 관리, 배포 관리, 가이드 작성, 다시 실행 | +| `manager-ui` | 화면 구현, 컴포넌트, NCloud 스타일, 대시보드 차트 | +| `manager-api` | FastAPI 백엔드, 시스템 API, 서비스 제어 | +| `manager-integration` | GUARDiA API 연동, Gitea 연동, Ollama 조회 | +| `manager-security` | JWT 인증, Route Guard, API Key 보안 | +| `manager-deploy` | Nginx 설정, systemd, 배포 파이프라인 | + +--- + +## GUARDiA Messenger (C:\GUARDiA\app) + +| 스킬 | 트리거 키워드 | +|------|------------| +| `messenger-orchestrator` | 화면 추가, 기능 구현, EAS 빌드, APK, Play Store, App Store, 가이드 작성, 다시 실행 | +| `rn-screen-dev` | 화면 구현, 컴포넌트, UI 수정, API 연동 | +| `eas-build-deploy` | EAS 빌드, APK 빌드, Gradle 오류, 빌드 실패 | +| `store-publish` | Play Store, App Store, 스크린샷, Privacy Policy | +| `doc-generator` | 가이드 작성, PDF 생성, PPTX 생성, 문서화 | + +--- + +## 홈페이지 (C:\GUARDiA\workspace\zioinfo-web) + +| 스킬 | 트리거 키워드 | +|------|------------| +| `workspace-analyzer` | 소스 분석, 개발환경 가이드, 하네스 적용, workspace | + +--- + +## 주요 빠른 명령 + +```bash +# GUARDiA ITSM 재시작 +ssh root@zioinfo.co.kr "systemctl restart guardia" + +# EAS Android 빌드 (C:\GUARDiA\app에서) +npx eas-cli build --platform android --profile preview + +# Gitea Push (홈페이지) +cd C:\GUARDiA\workspace\zioinfo-web +git push gitea main:main + +# 서버 전체 상태 확인 +ssh root@zioinfo.co.kr "for s in nginx zioinfo guardia guardia-manager gitea jenkins postgresql; do printf '%-22s %s\n' $s $(systemctl is-active $s); done" +``` + +--- + +## ⚠️ 자주 발생하는 오류와 해결 + +| 오류 | 원인 | 해결 | +|------|------|------| +| GUARDiA 500 시작 실패 | DATABASE_URL의 @를 %40으로 인코딩 안함 | .env에서 G@urd1a → G%40urd1a%21 | +| EAS Gradle 실패 | android/ 폴더가 업로드됨 | .easignore에 android/, ios/ 추가 | +| EAS packageReleaseResources 실패 | PIL PNG + AAPT2 충돌 | plugins/withGradleProps.js 확인 | +| Gmail 피싱 경고 | 본문에 IP 직접 링크 포함 | http://zioinfo.co.kr:8001 URL 제거 | +| Gmail 스팸 분류 | PTR 레코드 없음, DMARC 없음 | NCloud PTR 신청, 가비아 DMARC 등록 | +| Nginx IPv6 오류 | NCloud가 IPv4 only | sed로 [::]:80 제거 후 nginx -t | +| Dovecot 시작 실패 | IPv6 listen 시도 | dovecot.conf에 listen = 0.0.0.0 | + +*GUARDiA 프로젝트 | 2026-05-31* diff --git a/gen_export_docs.py b/gen_export_docs.py new file mode 100644 index 0000000..9443816 --- /dev/null +++ b/gen_export_docs.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +"""Export/Import 가이드 PDF + PPTX 생성""" +from pathlib import Path +OUT_DIR = Path(__file__).parent + +def gen_pdf(out): + from reportlab.lib.pagesizes import A4 + from reportlab.lib import colors + from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + from reportlab.lib.units import mm + from reportlab.lib.enums import TA_CENTER + from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, PageBreak + from reportlab.pdfbase import pdfmetrics + from reportlab.pdfbase.ttfonts import TTFont + import os + + FONT_DIRS = ["C:/Windows/Fonts","/usr/share/fonts/truetype/noto","/usr/share/fonts/truetype/dejavu"] + font = "Helvetica" + for fn,alias in [("malgun.ttf","Malgun"),("NanumGothic.ttf","NanumGothic"),("DejaVuSans.ttf","DejaVuSans")]: + for d in FONT_DIRS: + fp = os.path.join(d,fn) + if os.path.exists(fp): + try: pdfmetrics.registerFont(TTFont(alias,fp)); font=alias; break + except: pass + if font != "Helvetica": break + + BRAND=colors.HexColor("#1a3a6b"); ACCENT=colors.HexColor("#4f6ef7") + GREEN=colors.HexColor("#22c55e"); GRAY=colors.HexColor("#f0f2f5") + MUTED=colors.HexColor("#64748b") + + doc = SimpleDocTemplate(out,pagesize=A4,leftMargin=20*mm,rightMargin=20*mm,topMargin=20*mm,bottomMargin=20*mm) + W = A4[0]-40*mm + styles = getSampleStyleSheet() + def sty(name,**kw): + kw.setdefault("fontName",font) + return ParagraphStyle(name,parent=styles["Normal"],**kw) + def hr(c=None): return HRFlowable(width="100%",thickness=1,color=c or ACCENT,spaceAfter=4,spaceBefore=4) + def tbl(data,cws=None,hdr=True): + t=Table(data,colWidths=cws) + base=[("FONTNAME",(0,0),(-1,-1),font),("FONTSIZE",(0,0),(-1,-1),9), + ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,GRAY]), + ("GRID",(0,0),(-1,-1),.5,colors.HexColor("#e2e8f0")), + ("VALIGN",(0,0),(-1,-1),"MIDDLE"), + ("LEFTPADDING",(0,0),(-1,-1),6),("RIGHTPADDING",(0,0),(-1,-1),6), + ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5)] + if hdr:base+=[("BACKGROUND",(0,0),(-1,0),BRAND),("TEXTCOLOR",(0,0),(-1,0),colors.white), + ("FONTNAME",(0,0),(-1,0),font)] + t.setStyle(TableStyle(base)); return t + + S={"h1":sty("h1",fontSize=18,textColor=BRAND,spaceBefore=16,spaceAfter=8,leading=24), + "h2":sty("h2",fontSize=13,textColor=ACCENT,spaceBefore=10,spaceAfter=5,leading=18), + "body":sty("body",fontSize=10,textColor=colors.HexColor("#1e293b"),leading=16,spaceAfter=4), + "code":sty("code",fontName="Courier",fontSize=9,backColor=GRAY, + textColor=colors.HexColor("#1d4ed8"),leading=14,leftIndent=10,spaceBefore=3,spaceAfter=3), + "note":sty("note",fontSize=9,textColor=MUTED,leftIndent=10,leading=14,spaceAfter=3)} + + story=[] + + # 표지 + cover=Table([[Paragraph("GUARDiA 폐쇄망 ↔ 개방망",sty("ct",fontSize=26,textColor=colors.white,alignment=TA_CENTER,leading=34))], + [Paragraph("데이터 Export / Import 가이드",sty("cs",fontSize=16,textColor=colors.HexColor("#aac4e8"),alignment=TA_CENTER))], + [Paragraph("v1.0.0 | 2026-05-30 | (주)지오정보기술",sty("cm",fontSize=10,textColor=colors.HexColor("#7c85a8"),alignment=TA_CENTER))]], + colWidths=[W]) + cover.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),BRAND), + ("TOPPADDING",(0,0),(-1,-1),35),("BOTTOMPADDING",(0,0),(-1,-1),35), + ("LEFTPADDING",(0,0),(-1,-1),20),("RIGHTPADDING",(0,0),(-1,-1),20)])) + story+=[Spacer(1,30*mm),cover,PageBreak()] + + story+=[Paragraph("1. 개요",S["h1"]),hr(), + Paragraph("폐쇄망 GUARDiA ITSM의 데이터를 개방망 GUARDiA Manager로 안전하게 이관합니다. " + "번들 파일에 HMAC-SHA256 서명을 포함하여 위변조를 방지하며, 민감 정보는 자동 마스킹됩니다.",S["body"]), + Spacer(1,6),Paragraph("1-1. 보안 특징",S["h2"]), + tbl([["특징","내용"],["HMAC-SHA256 서명","번들 ZIP 위변조 방지"], + ["민감 정보 마스킹","IP 주소, SSH 비밀번호 → '****' 처리"], + ["Dry Run 모드","실제 저장 전 사전 검증"], + ["중복 방지","sr_id 기준 중복 SKIP"]],cws=[55*mm,110*mm]), + Spacer(1,10),Paragraph("2. API 엔드포인트",S["h1"]),hr(), + tbl([["메서드","경로","설명"], + ["GET","/api/export-import/export/bundle","전체 번들 ZIP (권장)"], + ["GET","/api/export-import/export/sr","SR 목록 JSON"], + ["GET","/api/export-import/export/cmdb","CMDB 서버 자산 JSON"], + ["GET","/api/export-import/export/institutions","기관 목록 JSON"], + ["GET","/api/export-import/export/audit","감사 로그 JSON"], + ["POST","/api/export-import/import/bundle","번들 ZIP Import"], + ["POST","/api/export-import/import/sr","SR JSON Import"]], + cws=[18*mm,75*mm,72*mm]), + PageBreak(), + Paragraph("3. 연동 흐름",S["h1"]),hr(), + tbl([["단계","작업","비고"], + ["1","폐쇄망 서버에서 번들 Export","GET /export/bundle → ZIP 다운로드"], + ["2","USB/보안매체로 ZIP 이동","Air Gap 환경 고려"], + ["3","Manager UI에서 파일 업로드","http://zioinfo.co.kr:8090/export-import"], + ["4","Dry Run 검증 실행","HMAC 서명 + 데이터 카운트 확인"], + ["5","Import 실행 (dry_run=false)","중복 sr_id 자동 SKIP"]],cws=[12*mm,75*mm,78*mm]), + Spacer(1,10),Paragraph("4. 테스트 결과 (7/7 PASS)",S["h1"]),hr(), + tbl([["#","테스트 항목","결과"], + ["T1","SR Export (5건)","PASS"], + ["T2","CMDB Export (6건)","PASS"], + ["T3","기관 Export (3건)","PASS"], + ["T4","감사 로그 Export (5건)","PASS"], + ["T5","번들 ZIP Export (HMAC 서명)","PASS"], + ["T6","SR Import dry_run","PASS"], + ["T7","Manager UI /export-import","PASS"]],cws=[12*mm,90*mm,63*mm]), + Spacer(1,6), + Paragraph("버그 수정: date 타입 JSON 직렬화 오류 → isoformat() 처리 완료", + sty("fix",fontSize=10,textColor=GREEN,fontName=font)), + Spacer(1,12), + hr(colors.HexColor("#e2e8f0")), + Paragraph("GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30", + sty("foot",fontSize=8,textColor=MUTED,alignment=TA_CENTER,fontName=font))] + + doc.build(story) + print(f"PDF: {out}") + +def gen_pptx(out): + from pptx import Presentation + from pptx.util import Inches, Pt + from pptx.dml.color import RGBColor + from pptx.enum.text import PP_ALIGN + + W,H=Inches(13.33),Inches(7.5) + prs=Presentation(); prs.slide_width=W; prs.slide_height=H + + BRAND=RGBColor(0x1a,0x3a,0x6b); ACCENT=RGBColor(0x4f,0x6e,0xf7) + WHITE=RGBColor(0xff,0xff,0xff); GRAY=RGBColor(0xf0,0xf2,0xf5) + GREEN=RGBColor(0x22,0xc5,0x5e); MUTED=RGBColor(0x64,0x74,0x8b) + DARK=RGBColor(0x1e,0x29,0x3b) + + def blank(): return prs.slides.add_slide(prs.slide_layouts[6]) + def rect(sl,x,y,w,h,fill=None): + s=sl.shapes.add_shape(1,x,y,w,h) + if fill: s.fill.solid(); s.fill.fore_color.rgb=fill + else: s.fill.background() + s.line.fill.background(); return s + def text(sl,t,x,y,w,h,sz=14,bold=False,color=DARK,align=PP_ALIGN.LEFT): + tb=sl.shapes.add_textbox(x,y,w,h); tf=tb.text_frame; tf.word_wrap=True + p=tf.paragraphs[0]; p.alignment=align; r=p.add_run(); r.text=t + r.font.size=Pt(sz); r.font.bold=bold; r.font.color.rgb=color + def tbl_sl(sl,hdrs,rows,x,y,w,h,cws=None): + t=sl.shapes.add_table(len(rows)+1,len(hdrs),x,y,w,h).table + if cws: + for i,cw in enumerate(cws): t.columns[i].width=cw + def cell(c,v,bg=None,clr=DARK,bold=False): + c.text=str(v); c.text_frame.paragraphs[0].font.size=Pt(9) + c.text_frame.paragraphs[0].font.bold=bold + c.text_frame.paragraphs[0].font.color.rgb=clr + if bg: c.fill.solid(); c.fill.fore_color.rgb=bg + for j,h in enumerate(hdrs): cell(t.cell(0,j),h,bg=BRAND,clr=WHITE,bold=True) + for i,row in enumerate(rows): + bg=GRAY if i%2==0 else WHITE + for j,v in enumerate(row): cell(t.cell(i+1,j),v,bg=bg) + + # S1 표지 + s=blank() + rect(s,0,0,W,H,fill=BRAND) + rect(s,Inches(.5),Inches(1.2),Inches(12.33),Inches(4.0),fill=RGBColor(0x25,0x4a,0x80)) + text(s,"GUARDiA 폐쇄망 ↔ 개방망",Inches(.8),Inches(1.5),Inches(11.73),Inches(1.0),sz=38,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + text(s,"데이터 Export / Import 연동 가이드",Inches(.8),Inches(2.7),Inches(11.73),Inches(.7),sz=20,color=RGBColor(0xaa,0xc4,0xe8),align=PP_ALIGN.CENTER) + text(s,"v1.0.0 | 2026-05-30 | (주)지오정보기술",Inches(.8),Inches(3.5),Inches(11.73),Inches(.5),sz=13,color=MUTED,align=PP_ALIGN.CENTER) + for i,(t_,c) in enumerate([("🔒 HMAC 서명",RGBColor(0x22,0xc5,0x5e)),("📦 번들 ZIP",ACCENT), + ("🔍 Dry Run",RGBColor(0xf5,0x9e,0x0b)),("🚫 민감 마스킹",RGBColor(0xef,0x44,0x44))]): + bx=Inches(2.3+i*2.2) + rect(s,bx,Inches(4.9),Inches(2.0),Inches(.5),fill=c) + text(s,t_,bx+Inches(.1),Inches(4.95),Inches(1.8),Inches(.4),sz=11,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + + # S2 연동 흐름 + s=blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"데이터 연동 흐름",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + boxes=[("🖥️","폐쇄망\nGUARDiA","폐쇄망 서버"),("📦","번들 Export\n(ZIP+HMAC)","GET /export/bundle"), + ("🔌","물리적 이동","USB/보안매체"),("☁️","개방망\nManager","Import UI")] + for i,(icon,lbl,sub) in enumerate(boxes): + bx=Inches(.4+i*3.1) + rect(s,bx,Inches(1.5),Inches(2.7),Inches(2.5),fill=GRAY) + text(s,icon,bx+Inches(.9),Inches(1.7),Inches(.9),Inches(.9),sz=28,align=PP_ALIGN.CENTER,color=DARK) + text(s,lbl,bx+Inches(.2),Inches(2.7),Inches(2.3),Inches(.7),sz=13,bold=True,color=BRAND,align=PP_ALIGN.CENTER) + text(s,sub,bx+Inches(.2),Inches(3.4),Inches(2.3),Inches(.4),sz=10,color=MUTED,align=PP_ALIGN.CENTER) + if i<3: + text(s,"→",Inches(.4+i*3.1+2.75),Inches(2.4),Inches(.35),Inches(.5),sz=22,color=MUTED,align=PP_ALIGN.CENTER) + tbl_sl(s,["단계","작업","비고"], + [["1","폐쇄망 Export","GET /export/bundle → ZIP"],["2","물리적 이동","USB/보안매체"], + ["3","Manager 업로드","http://zioinfo.co.kr:8090/export-import"], + ["4","Dry Run 검증","HMAC + 데이터 카운트"],["5","Import 실행","중복 SKIP"]], + Inches(.4),Inches(4.2),Inches(12.5),Inches(2.8),cws=[Inches(1.0),Inches(5.5),Inches(6.0)]) + + # S3 API 명세 + s=blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"API 명세",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_sl(s,["메서드","경로","설명"], + [["GET","/api/export-import/export/bundle","전체 번들 ZIP (HMAC 서명 포함)"], + ["GET","/api/export-import/export/sr","SR 목록 JSON (최대 5000건)"], + ["GET","/api/export-import/export/cmdb","CMDB 서버 자산 JSON"], + ["GET","/api/export-import/export/institutions","기관 목록 JSON"], + ["GET","/api/export-import/export/audit","감사 로그 JSON (최대 2000건)"], + ["POST","/api/export-import/import/bundle","번들 ZIP Import (dry_run 지원)"], + ["POST","/api/export-import/import/sr","SR JSON Import (중복 SKIP)"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(3.5),cws=[Inches(1.5),Inches(5.5),Inches(5.5)]) + rect(s,Inches(.4),Inches(5.2),Inches(12.5),Inches(.8),fill=RGBColor(0xff,0xf3,0xcd)) + text(s,"📌 민감 필드 자동 마스킹: ip_addr, os_pw_enc, ssh_user, ssh_pw → '****'", + Inches(.6),Inches(5.3),Inches(12.1),Inches(.6),sz=12,color=RGBColor(0x85,0x4d,0x0e)) + + # S4 테스트 결과 + s=blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"테스트 결과 — 7/7 PASS ✅",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_sl(s,["#","테스트 항목","내용","결과"], + [["T1","SR Export","5건 JSON","PASS"],["T2","CMDB Export","6건 JSON","PASS"], + ["T3","기관 Export","3건 JSON","PASS"],["T4","감사 로그 Export","5건 JSON","PASS"], + ["T5","번들 ZIP Export","HMAC 서명 포함 5KB ZIP","PASS"], + ["T6","SR Import dry_run","1건 검증, 저장 없음","PASS"], + ["T7","Manager UI","HTTP 200","PASS"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(4.0),cws=[Inches(.7),Inches(3.5),Inches(5.5),Inches(2.3)]) + rect(s,Inches(.4),Inches(5.8),Inches(12.5),Inches(.8),fill=GREEN) + text(s,"✅ 전체 7개 테스트 모두 통과 | 버그 수정: date JSON 직렬화 오류 → isoformat() 처리", + Inches(.6),Inches(5.9),Inches(12.1),Inches(.6),sz=13,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + + # S5 마지막 + s=blank() + rect(s,0,0,W,H,fill=BRAND) + text(s,"GUARDiA 폐쇄망 ↔ 개방망 데이터 연동",Inches(1),Inches(2.3),Inches(11.33),Inches(1.2),sz=28,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + text(s,"안전한 HMAC 서명으로 데이터 무결성을 보장합니다",Inches(1),Inches(3.7),Inches(11.33),Inches(.6),sz=14,color=RGBColor(0xaa,0xc4,0xe8),align=PP_ALIGN.CENTER) + text(s,"(주)지오정보기술 | GUARDiA v2.0.0 | 2026-05-30",Inches(1),Inches(5.5),Inches(11.33),Inches(.5),sz=12,color=MUTED,align=PP_ALIGN.CENTER) + + prs.save(out); print(f"PPTX: {out}") + +if __name__ == "__main__": + gen_pdf(str(OUT_DIR/"29_GUARDiA_폐쇄망_데이터연동_가이드.pdf")) + gen_pptx(str(OUT_DIR/"30_GUARDiA_폐쇄망_데이터연동_발표자료.pptx")) + print("완료") diff --git a/gen_license_docs.py b/gen_license_docs.py new file mode 100644 index 0000000..d06e396 --- /dev/null +++ b/gen_license_docs.py @@ -0,0 +1,460 @@ +#!/usr/bin/env python3 +""" +GUARDiA Manager 라이선스 관리 가이드 — PDF + PPTX 자동 생성 +출력: + manual/26_GUARDiA_Manager_라이선스_가이드.pdf + manual/27_GUARDiA_Manager_라이선스_발표자료.pptx +""" +from pathlib import Path +OUT_DIR = Path(__file__).parent + +# ═══════════════════════════════════════════════════════ +# PDF +# ═══════════════════════════════════════════════════════ +def gen_pdf(out: str): + from reportlab.lib.pagesizes import A4 + from reportlab.lib import colors + from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + from reportlab.lib.units import mm + from reportlab.lib.enums import TA_CENTER, TA_LEFT + from reportlab.platypus import ( + SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, + HRFlowable, PageBreak + ) + from reportlab.pdfbase import pdfmetrics + from reportlab.pdfbase.ttfonts import TTFont + import os + + FONT_DIRS = ["C:/Windows/Fonts", "/usr/share/fonts/truetype/noto", + "/usr/share/fonts/truetype/dejavu"] + FONT_CANDS = [("malgun.ttf","Malgun"),("NanumGothic.ttf","NanumGothic"), + ("DejaVuSans.ttf","DejaVuSans")] + font = "Helvetica" + for fn, alias in FONT_CANDS: + for d in FONT_DIRS: + fp = os.path.join(d, fn) + if os.path.exists(fp): + try: pdfmetrics.registerFont(TTFont(alias, fp)); font = alias; break + except: pass + if font != "Helvetica": break + + BRAND = colors.HexColor("#1a3a6b") + ACCENT = colors.HexColor("#4f6ef7") + LIGHT = colors.HexColor("#e8ecff") + GRAY_BG = colors.HexColor("#f0f2f5") + GREEN = colors.HexColor("#22c55e") + ORANGE = colors.HexColor("#f59e0b") + RED = colors.HexColor("#ef4444") + MUTED = colors.HexColor("#64748b") + + doc = SimpleDocTemplate(out, pagesize=A4, + leftMargin=20*mm, rightMargin=20*mm, topMargin=20*mm, bottomMargin=20*mm) + W = A4[0] - 40*mm + styles = getSampleStyleSheet() + + def sty(name, **kw): + kw.setdefault("fontName", font) + return ParagraphStyle(name, parent=styles["Normal"], **kw) + + S = { + "h1": sty("h1", fontSize=18, textColor=BRAND, spaceBefore=16, spaceAfter=8, leading=24), + "h2": sty("h2", fontSize=13, textColor=ACCENT, spaceBefore=10, spaceAfter=5, leading=18), + "body": sty("body", fontSize=10, textColor=colors.HexColor("#1e293b"), leading=16, spaceAfter=4), + "code": sty("code", fontName="Courier", fontSize=9, backColor=GRAY_BG, + textColor=colors.HexColor("#1d4ed8"), leading=14, leftIndent=10, + spaceBefore=4, spaceAfter=4), + "note": sty("note", fontSize=9, textColor=MUTED, leftIndent=10, leading=14, spaceAfter=3), + "cover_title": sty("ct", fontSize=30, textColor=colors.white, + alignment=TA_CENTER, spaceAfter=6, leading=38), + "cover_sub": sty("cs", fontSize=15, textColor=colors.HexColor("#aac4e8"), + alignment=TA_CENTER, spaceAfter=4), + "cover_meta": sty("cm", fontSize=10, textColor=colors.HexColor("#7c85a8"), + alignment=TA_CENTER), + } + + def hr(c=ACCENT, w=1): + return HRFlowable(width="100%", thickness=w, color=c, spaceAfter=4, spaceBefore=4) + + def tbl(data, col_widths=None, header=True): + t = Table(data, colWidths=col_widths) + base = [ + ("FONTNAME", (0,0),(-1,-1), font), + ("FONTSIZE", (0,0),(-1,-1), 9), + ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white, GRAY_BG]), + ("GRID", (0,0),(-1,-1), 0.5, colors.HexColor("#e2e8f0")), + ("VALIGN", (0,0),(-1,-1), "MIDDLE"), + ("LEFTPADDING",(0,0),(-1,-1), 6), + ("RIGHTPADDING",(0,0),(-1,-1), 6), + ("TOPPADDING",(0,0),(-1,-1), 5), + ("BOTTOMPADDING",(0,0),(-1,-1), 5), + ] + if header: + base += [("BACKGROUND",(0,0),(-1,0),BRAND), + ("TEXTCOLOR",(0,0),(-1,0),colors.white), + ("FONTNAME",(0,0),(-1,0),font)] + t.setStyle(TableStyle(base)) + return t + + story = [] + + # ── 표지 ────────────────────────────────────────────── + cover = Table([ + [Paragraph("GUARDiA Manager", S["cover_title"])], + [Paragraph("라이선스 키 등록 및 관리", S["cover_sub"])], + [Paragraph(" ", S["cover_sub"])], + [Paragraph("v2.0.0 | 2026-05-30 | (주)지오정보기술", S["cover_meta"])], + [Paragraph("http://zioinfo.co.kr:8090/licenses", S["cover_meta"])], + ], colWidths=[W]) + cover.setStyle(TableStyle([ + ("BACKGROUND",(0,0),(-1,-1),BRAND), + ("TOPPADDING",(0,0),(-1,-1),35), ("BOTTOMPADDING",(0,0),(-1,-1),35), + ("LEFTPADDING",(0,0),(-1,-1),20), ("RIGHTPADDING",(0,0),(-1,-1),20), + ])) + story += [Spacer(1,28*mm), cover, Spacer(1,8*mm)] + + # 기능 배지 + badges = [("라이선스 등록","#22c55e"),("무료 체험","#4f6ef7"), + ("키 검증","#f59e0b"),("이력 조회","#ef4444")] + badge_tbl = Table([[Paragraph(f"{t}", + sty(f"badge_{idx}", fontSize=10, textColor=colors.white, + fontName=font, alignment=TA_CENTER)) + for idx,(t,c) in enumerate(badges)]], colWidths=[W/4]*4) + badge_tbl.setStyle(TableStyle([( + "BACKGROUND",(i,0),(i,0),colors.HexColor(c)) + for i,(t,c) in enumerate(badges)] + [ + ("TOPPADDING",(0,0),(-1,-1),8),("BOTTOMPADDING",(0,0),(-1,-1),8)])) + story += [badge_tbl, PageBreak()] + + # ── 섹션 1: 개요 ────────────────────────────────────── + story += [ + Paragraph("1. 개요", S["h1"]), hr(), + Paragraph( + "GUARDiA Manager 라이선스 관리 페이지는 GUARDiA ITSM 플랫폼의 라이선스를 " + "통합 관제합니다. admin 계정 로그인 후 체험 발급, 정식 키 등록, 만료일 모니터링, " + "이력 조회를 단일 화면에서 처리할 수 있습니다.", S["body"]), + Spacer(1,4), + Paragraph("1-1. 주요 기능", S["h2"]), + tbl([ + ["기능","설명"], + ["라이선스 키 등록","발급받은 키를 붙여 넣고 즉시 활성화"], + ["무료 체험 시작","7/14/30일 체험 라이선스 즉시 발급 (설치당 1회)"], + ["키 검증","등록 없이 유효성만 확인 (에디션·만료일 사전 조회)"], + ["라이선스 비활성화","현재 라이선스 비활성화 (서비스 제한 주의)"], + ["이력 조회","과거 등록된 모든 라이선스 이력 테이블 확인"], + ["에디션 비교","TRIAL/COMMUNITY/STANDARD/ENTERPRISE 기능 비교"], + ], col_widths=[55*mm, 110*mm]), + ] + + # ── 섹션 2: 에디션 비교 ──────────────────────────────── + story += [ + Spacer(1,8), Paragraph("2. 라이선스 에디션 비교", S["h1"]), hr(), + tbl([ + ["구분","TRIAL","COMMUNITY","STANDARD","ENTERPRISE"], + ["가격","무료(7일)","무료","협의","협의"], + ["기관 수","1","1","50","무제한"], + ["사용자 수","10명","10명","200명","무제한"], + ["서버 수","20대","50대","500대","무제한"], + ["AI 에이전트","❌","❌","✅","✅"], + ["LDAP/MFA","❌","❌","✅","✅"], + ["취약점 스캔","❌","❌","❌","✅"], + ["FinOps","❌","❌","❌","✅"], + ["기술 지원","없음","커뮤니티","이메일","전담"], + ], col_widths=[38*mm,33*mm,33*mm,33*mm,28*mm]), + Spacer(1,6), + Paragraph("* STANDARD/ENTERPRISE 등록 시 서버에 GUARDIA_LICENSE_KEY 환경변수 필수.", S["note"]), + ] + + # ── 섹션 3: 화면 구성 ───────────────────────────────── + story += [ + PageBreak(), + Paragraph("3. 화면 구성 (NCloud 콘솔 스타일)", S["h1"]), hr(), + tbl([ + ["구성 요소","설명"], + ["업그레이드 배너","만료 3일 전 자동 표시 (긴급/경고 색상 구분)"], + ["현재 상태 카드","에디션 배지, 고객명, 만료 게이지, 라이선스 ID, 허용 한도"], + ["액션 버튼 그룹","🔑등록 / 🎁체험 / 🔍검증 / 비활성화 / ↺새로고침"], + ["액션 패널","선택한 액션에 따라 동적 렌더링 (텍스트 입력·폼)"], + ["에디션 비교 카드","4개 에디션 비교, 현재 에디션 강조 표시"], + ["라이선스 이력 테이블","DataTable 컴포넌트, ID/에디션/고객/만료일/등록자 컬럼"], + ], col_widths=[55*mm, 110*mm]), + Spacer(1,8), + Paragraph("3-1. 체험판 키 1회 노출 팝업", S["h2"]), + Paragraph( + "무료 체험 라이선스 발급 성공 시 발급된 키가 팝업으로 1회만 표시됩니다. " + "팝업을 닫으면 다시 확인할 수 없으므로 반드시 클립보드로 복사하여 안전한 곳에 보관하세요.", + S["body"]), + ] + + # ── 섹션 4: API 명세 ────────────────────────────────── + story += [ + Spacer(1,8), Paragraph("4. API 명세", S["h1"]), hr(), + tbl([ + ["메서드","경로","인증","설명"], + ["GET", "/api/license/status", "JWT","현재 라이선스 상태"], + ["POST", "/api/license/trial", "admin","체험 라이선스 발급"], + ["POST", "/api/license/activate","admin","라이선스 키 활성화"], + ["POST", "/api/license/verify", "admin","라이선스 키 검증만"], + ["DELETE","/api/license", "admin","라이선스 비활성화"], + ["GET", "/api/license/history", "admin","등록 이력 조회"], + ], col_widths=[18*mm, 62*mm, 22*mm, 63*mm]), + Spacer(1,6), + Paragraph("curl 예시 (체험 발급):", S["h2"]), + Paragraph('curl -X POST http://zioinfo.co.kr:8001/api/license/trial', S["code"]), + Paragraph(' -H "Authorization: Bearer $TOKEN"', S["code"]), + Paragraph(" -d '{\"customer\":\"지오정보기술\",\"days\":7}'", S["code"]), + ] + + # ── 섹션 5: 테스트 결과 ──────────────────────────────── + story += [ + PageBreak(), + Paragraph("5. 테스트 결과", S["h1"]), hr(), + Paragraph("테스트 환경: Ubuntu 24.04, GUARDiA ITSM v2.0.0 | 2026-05-30", S["note"]), + Spacer(1,4), + tbl([ + ["#","테스트 항목","기대값","실제값","결과"], + ["T1","admin 로그인 (JSON)","JWT 토큰","발급 성공","PASS"], + ["T2","라이선스 현재 상태","message 필드","활성 없음","PASS"], + ["T3","체험 라이선스 발급 (7일)","HTTP 200","🎁 7일 시작","PASS"], + ["T4","활성화 후 상태","valid=True","TRIAL 6일","PASS"], + ["T5","라이선스 이력 조회","HTTP 200","1건","PASS"], + ["T6","잘못된 키 검증","에러 반환","HTTP 500","PASS"], + ["T7","Manager UI 접속","HTTP 200","HTTP 200","PASS"], + ["T8","Manager Backend","ok","ok","PASS"], + ], col_widths=[12*mm,65*mm,35*mm,35*mm,18*mm]), + Spacer(1,6), + Paragraph("버그 수정: datetime timezone-aware/naive 충돌 → replace(tzinfo=None) 적용 완료", + sty("fix", fontSize=10, textColor=GREEN, fontName=font)), + Spacer(1,4), + Paragraph("전체 8개 테스트 모두 통과 (8/8 PASS)", sty( + "tr", fontSize=12, textColor=GREEN, alignment=TA_CENTER, fontName=font)), + ] + + # ── 섹션 6: 운영 절차 ───────────────────────────────── + story += [ + Spacer(1,8), Paragraph("6. 운영 절차", S["h1"]), hr(), + tbl([ + ["작업","절차"], + ["라이선스 갱신","키 검증 → 라이선스 등록 → 자동 교체"], + ["체험 시작","[🎁 무료 체험] → 고객명 입력 → 기간 선택 → 시작"], + ["만료일 모니터링","상태 카드 만료 게이지 / 3일 전 배너 자동 표시"], + ["비활성화","[비활성화] 버튼 → 확인 → 서비스 제한 발생 주의"], + ["환경변수 설정","GUARDIA_LICENSE_KEY= .env 추가 → systemctl restart guardia"], + ], col_widths=[45*mm, 120*mm]), + Spacer(1,12), + hr(colors.HexColor("#e2e8f0")), + Paragraph("GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30", + sty("foot", fontSize=8, textColor=MUTED, alignment=TA_CENTER, fontName=font)), + ] + + doc.build(story) + print(f"PDF 생성: {out}") + + +# ═══════════════════════════════════════════════════════ +# PPTX +# ═══════════════════════════════════════════════════════ +def gen_pptx(out: str): + from pptx import Presentation + from pptx.util import Inches, Pt + from pptx.dml.color import RGBColor + from pptx.enum.text import PP_ALIGN + + W, H = Inches(13.33), Inches(7.5) + prs = Presentation(); prs.slide_width = W; prs.slide_height = H + + BRAND = RGBColor(0x1a,0x3a,0x6b) + ACCENT = RGBColor(0x4f,0x6e,0xf7) + WHITE = RGBColor(0xff,0xff,0xff) + GRAY = RGBColor(0xf0,0xf2,0xf5) + GREEN = RGBColor(0x22,0xc5,0x5e) + ORANGE = RGBColor(0xf5,0x9e,0x0b) + RED = RGBColor(0xef,0x44,0x44) + DARK = RGBColor(0x1e,0x29,0x3b) + MUTED = RGBColor(0x64,0x74,0x8b) + + def blank(): + return prs.slides.add_slide(prs.slide_layouts[6]) + + def rect(sl, x, y, w, h, fill=None): + s = sl.shapes.add_shape(1, x, y, w, h) + if fill: s.fill.solid(); s.fill.fore_color.rgb = fill + else: s.fill.background() + s.line.fill.background() + return s + + def text(sl, t, x, y, w, h, sz=14, bold=False, color=DARK, + align=PP_ALIGN.LEFT, italic=False): + tb = sl.shapes.add_textbox(x, y, w, h) + tf = tb.text_frame; tf.word_wrap = True + p = tf.paragraphs[0]; p.alignment = align + r = p.add_run(); r.text = t + r.font.size = Pt(sz); r.font.bold = bold + r.font.italic = italic; r.font.color.rgb = color + return tb + + def tbl_slide(sl, headers, rows, x, y, w, h, cws=None): + t = sl.shapes.add_table(len(rows)+1, len(headers), x, y, w, h).table + if cws: + for i,cw in enumerate(cws): t.columns[i].width = cw + def cell(c, v, bg=None, clr=DARK, bold=False, sz=9): + c.text = v + c.text_frame.paragraphs[0].font.size = Pt(sz) + c.text_frame.paragraphs[0].font.bold = bold + c.text_frame.paragraphs[0].font.color.rgb = clr + if bg: c.fill.solid(); c.fill.fore_color.rgb = bg + for j,h in enumerate(headers): + cell(t.cell(0,j), h, bg=BRAND, clr=WHITE, bold=True) + for i,row in enumerate(rows): + bg = GRAY if i%2==0 else WHITE + for j,v in enumerate(row): cell(t.cell(i+1,j), str(v), bg=bg) + + # ── S1 표지 ───────────────────────────────────────── + s = blank() + rect(s, 0, 0, W, H, fill=BRAND) + rect(s, Inches(.5), Inches(1.2), Inches(12.33), Inches(4.2), fill=RGBColor(0x25,0x4a,0x80)) + text(s,"GUARDiA Manager",Inches(.8),Inches(1.5),Inches(11.73),Inches(1.2), + sz=44,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + text(s,"라이선스 키 등록 및 관리 시스템",Inches(.8),Inches(2.8),Inches(11.73),Inches(.7), + sz=20,color=RGBColor(0xaa,0xc4,0xe8),align=PP_ALIGN.CENTER) + text(s,"v2.0.0 | 2026-05-30 | (주)지오정보기술",Inches(.8),Inches(3.6),Inches(11.73),Inches(.5), + sz=13,color=MUTED,align=PP_ALIGN.CENTER) + for i,(t_,c) in enumerate([("라이선스 등록",GREEN),("무료 체험",ACCENT), + ("키 검증",ORANGE),("이력 관리",RED)]): + bx = Inches(2.5+i*2.1) + rect(s,bx,Inches(4.9),Inches(1.8),Inches(.5),fill=c) + text(s,t_,bx+Inches(.1),Inches(4.95),Inches(1.6),Inches(.4), + sz=11,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + + # ── S2 기능 개요 ───────────────────────────────────── + s = blank() + rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + text(s,"기능 개요",Inches(.4),Inches(.28),Inches(12),Inches(.7), + sz=24,bold=True,color=WHITE) + items = [ + ("🔑","라이선스 키 등록","발급받은 키를 붙여 넣고 즉시 활성화"), + ("🎁","무료 체험 시작","7/14/30일 체험, 설치당 1회 한정"), + ("🔍","키 검증","등록 없이 유효성·에디션·만료일 사전 확인"), + ("❌","라이선스 비활성화","현재 라이선스 비활성화 (서비스 제한 주의)"), + ("📋","이력 조회","모든 등록 이력 테이블 조회"), + ("📊","에디션 비교","TRIAL/COMMUNITY/STANDARD/ENTERPRISE 비교"), + ] + for i,(icon,title,desc) in enumerate(items): + r,c = i//2, i%2 + bx = Inches(.4+c*6.4); by = Inches(1.4+r*1.6) + rect(s,bx,by,Inches(5.9),Inches(1.3),fill=GRAY) + text(s,icon,bx+Inches(.1),by+Inches(.2),Inches(.6),Inches(.9),sz=22,align=PP_ALIGN.CENTER) + text(s,title,bx+Inches(.8),by+Inches(.15),Inches(5),Inches(.45),sz=14,bold=True,color=BRAND) + text(s,desc,bx+Inches(.8),by+Inches(.6),Inches(5),Inches(.45),sz=11,color=MUTED) + + # ── S3 에디션 비교 ─────────────────────────────────── + s = blank() + rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + text(s,"라이선스 에디션 비교",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_slide(s, + ["구분","TRIAL","COMMUNITY","STANDARD","ENTERPRISE"], + [["가격","무료(7일)","무료","협의","협의"], + ["기관","1","1","50","무제한"], + ["사용자","10명","10명","200명","무제한"], + ["서버","20대","50대","500대","무제한"], + ["AI 에이전트","❌","❌","✅","✅"], + ["LDAP/MFA","❌","❌","✅","✅"], + ["취약점 스캔","❌","❌","❌","✅"], + ["기술 지원","없음","커뮤니티","이메일","전담"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(4.8), + cws=[Inches(2.0)]+[Inches(2.6)]*4) + + # ── S4 화면 구성 ───────────────────────────────────── + s = blank() + rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + text(s,"화면 구성 (NCloud 콘솔 스타일)",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + components = [ + ("업그레이드 배너","만료 3일 전 자동 표시, 긴급(빨강)/경고(노랑) 색상"), + ("현재 상태 카드","에디션 배지·고객명·만료 게이지·라이선스 ID·허용 한도"), + ("액션 버튼","🔑등록 / 🎁체험 / 🔍검증 / 비활성화 버튼 그룹"), + ("액션 패널","선택 동작에 따라 동적 렌더링 (텍스트 입력, 체험 폼)"), + ("에디션 비교","4개 에디션 카드, 현재 에디션 테두리 강조"), + ("라이선스 이력","DataTable: ID/에디션/고객/체험판여부/만료일/등록자"), + ] + for i,(title,desc) in enumerate(components): + r,c = i//2, i%2 + bx = Inches(.4+c*6.5); by = Inches(1.4+r*1.6) + rect(s,bx,by,Inches(6),Inches(1.3),fill=RGBColor(0xef,0xf2,0xff)) + text(s,title,bx+Inches(.2),by+Inches(.15),Inches(5.6),Inches(.45),sz=13,bold=True,color=ACCENT) + text(s,desc,bx+Inches(.2),by+Inches(.6),Inches(5.6),Inches(.5),sz=11,color=DARK) + + # ── S5 테스트 결과 ─────────────────────────────────── + s = blank() + rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + text(s,"테스트 결과 — 8/8 PASS ✅",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_slide(s, + ["#","테스트 항목","기대값","실제값","결과"], + [["T1","admin 로그인","JWT 토큰","발급 성공","PASS"], + ["T2","라이선스 현재 상태","message","활성 없음","PASS"], + ["T3","체험 라이선스 발급(7일)","HTTP 200","🎁 7일","PASS"], + ["T4","활성화 후 상태","valid=True","TRIAL 6일","PASS"], + ["T5","라이선스 이력 조회","HTTP 200","1건","PASS"], + ["T6","잘못된 키 검증","에러 반환","HTTP 500","PASS"], + ["T7","Manager UI 접속","HTTP 200","HTTP 200","PASS"], + ["T8","Manager Backend","ok","ok","PASS"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(4.5), + cws=[Inches(.7),Inches(4.5),Inches(2.2),Inches(2.2),Inches(1.5)]) + rect(s,Inches(.4),Inches(6.2),Inches(12.5),Inches(.8),fill=GREEN) + text(s,"✅ 전체 8개 테스트 모두 통과 (8/8 PASS) | 버그 수정: datetime timezone 패치 완료", + Inches(.6),Inches(6.3),Inches(12.1),Inches(.6), + sz=13,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + + # ── S6 API 명세 ────────────────────────────────────── + s = blank() + rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + text(s,"API 명세 (GUARDiA ITSM REST API)",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_slide(s, + ["메서드","경로","인증","설명"], + [["GET","/api/license/status","JWT","현재 라이선스 상태"], + ["POST","/api/license/trial","admin","체험 라이선스 발급"], + ["POST","/api/license/activate","admin","라이선스 키 활성화"], + ["POST","/api/license/verify","admin","라이선스 키 검증만"], + ["DELETE","/api/license","admin","라이선스 비활성화"], + ["GET","/api/license/history","admin","등록 이력 조회"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(3.2), + cws=[Inches(1.5),Inches(4.5),Inches(1.8),Inches(5.2)]) + text(s,"Base URL: http://zioinfo.co.kr:8001 | 인증: Authorization: Bearer {JWT}", + Inches(.4),Inches(4.8),Inches(12.5),Inches(.5),sz=12,color=MUTED) + + # ── S7 운영 절차 ───────────────────────────────────── + s = blank() + rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + text(s,"운영 절차",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + steps = [ + ("🔑 라이선스 갱신","① 키 검증 확인\n② 라이선스 등록\n③ 자동 교체"), + ("🎁 체험 시작","① [무료 체험] 클릭\n② 고객명 입력\n③ 기간 선택 → 시작"), + ("📈 만료 모니터링","① 상태 카드 게이지\n② 3일 전 배너 자동\n③ 만료 전 갱신"), + ("⚙️ 환경변수 설정","① .env 파일 편집\n② GUARDIA_LICENSE_KEY=\n③ systemctl restart"), + ] + for i,(title,desc) in enumerate(steps): + bx = Inches(.4+i*3.2); by = Inches(1.4) + rect(s,bx,by,Inches(3.0),Inches(4.5),fill=GRAY) + text(s,title,bx+Inches(.2),by+Inches(.2),Inches(2.7),Inches(.6),sz=13,bold=True,color=BRAND) + text(s,desc,bx+Inches(.2),by+Inches(.9),Inches(2.7),Inches(3.2),sz=11,color=DARK) + + # ── S8 마지막 ──────────────────────────────────────── + s = blank() + rect(s, 0, 0, W, H, fill=BRAND) + text(s,"GUARDiA Manager 라이선스 관리",Inches(1),Inches(2.0),Inches(11.33),Inches(1.2), + sz=30,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + text(s,"안정적인 라이선스 운영으로 GUARDiA ITSM을 최대한 활용하세요", + Inches(1),Inches(3.3),Inches(11.33),Inches(.6),sz=14,color=RGBColor(0xaa,0xc4,0xe8), + align=PP_ALIGN.CENTER) + text(s,"(주)지오정보기술 | GUARDiA v2.0.0 | 2026-05-30", + Inches(1),Inches(5.5),Inches(11.33),Inches(.5),sz=12,color=MUTED,align=PP_ALIGN.CENTER) + + prs.save(out); print(f"PPTX 생성: {out}") + + +if __name__ == "__main__": + pdf_out = str(OUT_DIR / "26_GUARDiA_Manager_라이선스_가이드.pdf") + pptx_out = str(OUT_DIR / "27_GUARDiA_Manager_라이선스_발표자료.pptx") + gen_pdf(pdf_out) + gen_pptx(pptx_out) + print("\n=== 생성 완료 ===") + print(f"PDF : {pdf_out}") + print(f"PPTX: {pptx_out}") diff --git a/gen_messenger_docs.py b/gen_messenger_docs.py new file mode 100644 index 0000000..321fc8a --- /dev/null +++ b/gen_messenger_docs.py @@ -0,0 +1,411 @@ +#!/usr/bin/env python3 +"""GUARDiA Messenger 개발·배포 가이드 PDF + PPTX 자동 생성""" +from pathlib import Path +OUT = Path(__file__).parent + +# ═══════════════════════════════════ +# 공통 상수 +# ═══════════════════════════════════ +BRAND_HEX = "#1a3a6b" +ACCENT_HEX = "#4f6ef7" +GREEN_HEX = "#22c55e" +ORANGE_HEX = "#f59e0b" +RED_HEX = "#ef4444" +GRAY_HEX = "#f0f2f5" + +# ═══════════════════════════════════ +# PDF +# ═══════════════════════════════════ +def gen_pdf(out: str): + from reportlab.lib.pagesizes import A4 + from reportlab.lib import colors + from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + from reportlab.lib.units import mm + from reportlab.lib.enums import TA_CENTER, TA_LEFT + from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, + Table, TableStyle, HRFlowable, PageBreak) + from reportlab.pdfbase import pdfmetrics + from reportlab.pdfbase.ttfonts import TTFont + import os + + FONT_DIRS = ["C:/Windows/Fonts", "/usr/share/fonts/truetype/noto", + "/usr/share/fonts/truetype/dejavu"] + font = "Helvetica" + for fn, alias in [("malgun.ttf","Malgun"),("NanumGothic.ttf","NanumGothic"), + ("DejaVuSans.ttf","DejaVuSans")]: + for d in FONT_DIRS: + fp = os.path.join(d, fn) + if os.path.exists(fp): + try: pdfmetrics.registerFont(TTFont(alias, fp)); font = alias; break + except: pass + if font != "Helvetica": break + + BRAND = colors.HexColor(BRAND_HEX) + ACCENT = colors.HexColor(ACCENT_HEX) + GREEN = colors.HexColor(GREEN_HEX) + GRAY = colors.HexColor(GRAY_HEX) + MUTED = colors.HexColor("#64748b") + W = A4[0] - 40*mm + + doc = SimpleDocTemplate(out, pagesize=A4, + leftMargin=20*mm, rightMargin=20*mm, topMargin=20*mm, bottomMargin=20*mm) + styles = getSampleStyleSheet() + + def sty(name, **kw): + kw.setdefault("fontName", font) + return ParagraphStyle(name, parent=styles["Normal"], **kw) + + def hr(c=None): + return HRFlowable(width="100%", thickness=1, color=c or ACCENT, + spaceAfter=4, spaceBefore=4) + + def tbl(data, cws=None, hdr=True): + t = Table(data, colWidths=cws) + base = [("FONTNAME",(0,0),(-1,-1),font),("FONTSIZE",(0,0),(-1,-1),9), + ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white, GRAY]), + ("GRID",(0,0),(-1,-1),.5,colors.HexColor("#e2e8f0")), + ("VALIGN",(0,0),(-1,-1),"MIDDLE"), + ("LEFTPADDING",(0,0),(-1,-1),6),("RIGHTPADDING",(0,0),(-1,-1),6), + ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5)] + if hdr: + base += [("BACKGROUND",(0,0),(-1,0),BRAND), + ("TEXTCOLOR",(0,0),(-1,0),colors.white), + ("FONTNAME",(0,0),(-1,0),font)] + t.setStyle(TableStyle(base)); return t + + S = { + "h1": sty("h1",fontSize=18,textColor=BRAND,spaceBefore=16,spaceAfter=8,leading=24), + "h2": sty("h2",fontSize=13,textColor=ACCENT,spaceBefore=10,spaceAfter=5,leading=18), + "body": sty("body",fontSize=10,textColor=colors.HexColor("#1e293b"),leading=16,spaceAfter=4), + "code": sty("code",fontName="Courier",fontSize=9,backColor=GRAY, + textColor=colors.HexColor("#1d4ed8"),leading=14, + leftIndent=10,spaceBefore=4,spaceAfter=4), + "note": sty("note",fontSize=9,textColor=MUTED,leftIndent=10,leading=14,spaceAfter=3), + "ok": sty("ok",fontSize=10,textColor=GREEN,fontName=font), + } + + story = [] + + # 표지 + cover = Table([ + [Paragraph("GUARDiA Messenger",sty("ct",fontSize=28,textColor=colors.white,alignment=TA_CENTER,leading=36))], + [Paragraph("모바일 앱 개발 · EAS 빌드 · 스토어 배포 가이드",sty("cs",fontSize=14,textColor=colors.HexColor("#aac4e8"),alignment=TA_CENTER))], + [Paragraph(" ",sty("sp",fontSize=8,textColor=colors.white,alignment=TA_CENTER))], + [Paragraph("v1.0.0 | 2026-05-31 | (주)지오정보기술",sty("cm",fontSize=10,textColor=colors.HexColor("#7c85a8"),alignment=TA_CENTER))], + [Paragraph("React Native + Expo SDK 51 + EAS Build",sty("cm2",fontSize=10,textColor=colors.HexColor("#7c85a8"),alignment=TA_CENTER))], + ], colWidths=[W]) + cover.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),BRAND), + ("TOPPADDING",(0,0),(-1,-1),32),("BOTTOMPADDING",(0,0),(-1,-1),32), + ("LEFTPADDING",(0,0),(-1,-1),20),("RIGHTPADDING",(0,0),(-1,-1),20)])) + story += [Spacer(1,26*mm), cover, PageBreak()] + + # 섹션 1: 앱 개요 + story += [ + Paragraph("1. 앱 개요", S["h1"]), hr(), + Paragraph("GUARDiA Messenger는 GUARDiA ITSM과 연동하는 모바일 앱입니다.", S["body"]), + Paragraph("1-1. 구현 화면", S["h2"]), + tbl([["화면","경로","주요 기능"], + ["로그인","(auth)/login.tsx","JWT 인증 · SecureStore 저장"], + ["대시보드","(tabs)/index.tsx","SR 통계 · 서비스 상태 · 배포 이력"], + ["SR 관리","(tabs)/sr.tsx","서비스 요청 목록 조회 및 신규 등록"], + ["AI 챗봇","(tabs)/chat.tsx","Ollama LLM 자연어 인프라 명령"], + ["알림","(tabs)/notifications.tsx","인시던트·SLA·배포 알림 수신"], + ["설정","(tabs)/settings.tsx","프로필·알림설정·로그아웃"]], + cws=[28*mm,50*mm,87*mm]), + Spacer(1,6), + Paragraph("1-2. 기술 스택", S["h2"]), + tbl([["항목","기술"], + ["프레임워크","React Native 0.74.5 + Expo SDK 51"], + ["언어","TypeScript (strict)"], + ["라우터","Expo Router 3.5.x (파일 기반)"], + ["인증 저장소","expo-secure-store (보안 키체인)"], + ["HTTP 클라이언트","Axios (서버: https://zioinfo.co.kr:8443)"], + ["빌드 시스템","EAS Build (Expo Application Services)"]], + cws=[40*mm,125*mm]), + ] + + # 섹션 2: EAS 빌드 + story += [ + PageBreak(), + Paragraph("2. EAS 빌드 가이드", S["h1"]), hr(), + Paragraph("2-1. 빌드 명령어", S["h2"]), + tbl([["명령어","용도","소요 시간"], + ["eas build --platform android --profile preview","테스트 APK","~10분"], + ["eas build --platform android --profile production","Play Store AAB","~15분"], + ["eas build --platform ios --profile production","App Store IPA","~20분"]], + cws=[105*mm,45*mm,15*mm]), + Spacer(1,6), + Paragraph("2-2. 빌드 전 필수 체크리스트", S["h2"]), + tbl([["항목","올바른 상태","확인 명령"], + ["android/ 폴더","없어야 함","ls android/ → 오류가 정상"], + [".easignore","android/, ios/ 포함","cat .easignore"], + ["PNG Crunching","false","cat plugins/withGradleProps.js"], + ["babel.config","babel-preset-expo만","cat babel.config.js"], + ["EAS 로그인","zioinfo 표시","npx eas-cli whoami"]], + cws=[40*mm,50*mm,75*mm]), + Spacer(1,6), + Paragraph("2-3. APK 폰 설치 (Android Studio 불필요)", S["h2"]), + Paragraph("안드로이드 폰 브라우저에서 Expo 빌드 URL 열기 → Download → 설치", S["body"]), + Paragraph("설정 → 보안 → 알 수 없는 앱 설치 허용 (최초 1회 설정 필요)", S["note"]), + ] + + # 섹션 3: 빌드 이슈 이력 + story += [ + PageBreak(), + Paragraph("3. 빌드 이슈 이력 및 해결책", S["h1"]), hr(), + Paragraph("실제 빌드 과정에서 발생한 4개 이슈와 검증된 해결책입니다.", S["note"]), + Spacer(1,6), + tbl([["이슈","원인","해결"], + ["Gradle UNKNOWN ERROR","android/ 폴더 → EAS Bare Workflow 오인",".easignore에 android/, ios/ 추가"], + ["packageReleaseResources 실패","PIL PNG + AAPT2 PNG Crunching 충돌","withGradleProps.js enablePngCrunchInReleaseBuilds=false"], + ["Firebase Gradle 오류","expo-notifications 플러그인 (google-services.json 없음)","app.json plugins에서 expo-notifications 제거"], + ["Babel 경고","expo-router/babel deprecated (SDK 51)","babel.config.js에서 제거, babel-preset-expo만 사용"]], + cws=[55*mm,60*mm,50*mm]), + Spacer(1,6), + Paragraph("3-1. 핵심 수정 파일: plugins/withGradleProps.js", S["h2"]), + Paragraph("android.enablePngCrunchInReleaseBuilds=false", S["code"]), + Paragraph("reactNativeArchitectures=arm64-v8a", S["code"]), + Paragraph("org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m", S["code"]), + Spacer(1,4), + Paragraph("최종 성공 빌드: 51096ada (Android APK, EAS zioinfo 계정)", S["ok"]), + ] + + # 섹션 4: 스토어 등록 + story += [ + PageBreak(), + Paragraph("4. 스토어 등록 절차", S["h1"]), hr(), + tbl([["항목","Google Play","Apple App Store"], + ["계정 비용","$25 1회","$99/년"], + ["패키지/번들 ID","kr.co.zioinfo.guardia","kr.co.zioinfo.guardia"], + ["Privacy Policy","필수","필수"], + ["스크린샷","최소 2개","최소 3개 (6.7인치)"], + ["제출 방법","AAB 업로드","eas submit --platform ios"], + ["심사 기간","2~7 영업일","1~3 영업일"]], + cws=[45*mm,70*mm,50*mm]), + Spacer(1,6), + Paragraph("4-1. Privacy Policy URL (App Store 필수)", S["h2"]), + Paragraph("https://zioinfo.co.kr/privacy 페이지에 개인정보처리방침 등록 필요", S["body"]), + Paragraph("수집 정보: 이메일, 사용자명 (JWT 인증 목적) / 제3자 제공 없음", S["note"]), + ] + + # 섹션 5: 하네스 구조 + story += [ + Spacer(1,8), Paragraph("5. 하네스 (.claude/) 구조", S["h1"]), hr(), + tbl([["에이전트","역할"], + ["rn-developer","React Native 화면 구현, 컴포넌트 개발"], + ["eas-engineer","EAS Build 실행, 빌드 실패 진단"], + ["store-publisher","Play Store / App Store 등록 메타데이터"], + ["doc-writer","개발 가이드 작성, PDF/PPTX 생성"]], + cws=[45*mm,120*mm]), + Spacer(1,6), + tbl([["스킬","트리거 키워드"], + ["messenger-orchestrator","화면 구현, EAS 빌드, 스토어 등록, 가이드 작성 등 모든 요청"], + ["rn-screen-dev","화면 추가, 컴포넌트 작성, UI 수정, API 연동"], + ["eas-build-deploy","APK 빌드, Gradle 오류, EAS 설정, 빌드 실패"], + ["store-publish","Play Store, App Store, 스크린샷, Privacy Policy"], + ["doc-generator","가이드 작성, PDF 생성, PPTX 생성, 문서화"]], + cws=[50*mm,115*mm]), + Spacer(1,10), + hr(colors.HexColor("#e2e8f0")), + Paragraph("GUARDiA Messenger v1.0.0 | (주)지오정보기술 | 2026-05-31", + sty("foot",fontSize=8,textColor=MUTED,alignment=TA_CENTER,fontName=font)), + ] + + doc.build(story) + print(f"PDF: {out}") + + +# ═══════════════════════════════════ +# PPTX +# ═══════════════════════════════════ +def gen_pptx(out: str): + from pptx import Presentation + from pptx.util import Inches, Pt + from pptx.dml.color import RGBColor + from pptx.enum.text import PP_ALIGN + + W, H = Inches(13.33), Inches(7.5) + prs = Presentation(); prs.slide_width = W; prs.slide_height = H + + BRAND = RGBColor(0x1a,0x3a,0x6b) + ACCENT = RGBColor(0x4f,0x6e,0xf7) + WHITE = RGBColor(0xff,0xff,0xff) + GRAY = RGBColor(0xf0,0xf2,0xf5) + GREEN = RGBColor(0x22,0xc5,0x5e) + ORANGE = RGBColor(0xf5,0x9e,0x0b) + RED = RGBColor(0xef,0x44,0x44) + DARK = RGBColor(0x1e,0x29,0x3b) + MUTED = RGBColor(0x64,0x74,0x8b) + + def blank(): return prs.slides.add_slide(prs.slide_layouts[6]) + + def rect(sl, x, y, w, h, fill=None): + s = sl.shapes.add_shape(1, x, y, w, h) + if fill: s.fill.solid(); s.fill.fore_color.rgb = fill + else: s.fill.background() + s.line.fill.background(); return s + + def text(sl, t, x, y, w, h, sz=14, bold=False, color=DARK, align=PP_ALIGN.LEFT): + tb = sl.shapes.add_textbox(x, y, w, h) + tf = tb.text_frame; tf.word_wrap = True + p = tf.paragraphs[0]; p.alignment = align + r = p.add_run(); r.text = t + r.font.size = Pt(sz); r.font.bold = bold; r.font.color.rgb = color + + def tbl_sl(sl, hdrs, rows, x, y, w, h, cws=None): + t = sl.shapes.add_table(len(rows)+1, len(hdrs), x, y, w, h).table + if cws: + for i,cw in enumerate(cws): t.columns[i].width = cw + def cell(c, v, bg=None, clr=DARK, bold=False, sz=9): + c.text = str(v) + c.text_frame.paragraphs[0].font.size = Pt(sz) + c.text_frame.paragraphs[0].font.bold = bold + c.text_frame.paragraphs[0].font.color.rgb = clr + if bg: c.fill.solid(); c.fill.fore_color.rgb = bg + for j,h in enumerate(hdrs): cell(t.cell(0,j), h, bg=BRAND, clr=WHITE, bold=True) + for i,row in enumerate(rows): + bg = GRAY if i%2==0 else RGBColor(0xff,0xff,0xff) + for j,v in enumerate(row): cell(t.cell(i+1,j), v, bg=bg) + + # S1: 표지 + s = blank() + rect(s, 0, 0, W, H, fill=BRAND) + rect(s, Inches(.5), Inches(1.2), Inches(12.33), Inches(4.0), fill=RGBColor(0x25,0x4a,0x80)) + text(s,"GUARDiA Messenger",Inches(.8),Inches(1.5),Inches(11.73),Inches(1.2), + sz=42,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + text(s,"모바일 앱 개발 · EAS 빌드 · 스토어 배포 가이드",Inches(.8),Inches(2.8),Inches(11.73),Inches(.7), + sz=18,color=RGBColor(0xaa,0xc4,0xe8),align=PP_ALIGN.CENTER) + text(s,"v1.0.0 | 2026-05-31 | (주)지오정보기술 | React Native + Expo SDK 51", + Inches(.8),Inches(3.6),Inches(11.73),Inches(.5),sz=13,color=MUTED,align=PP_ALIGN.CENTER) + for i,(t_,c) in enumerate([("📱 6개 화면",GREEN),("🔨 EAS Build",ACCENT), + ("🏪 스토어 등록",ORANGE),("🐛 4개 이슈 해결",RED)]): + bx = Inches(2.0+i*2.3) + rect(s,bx,Inches(4.9),Inches(2.1),Inches(.5),fill=c) + text(s,t_,bx+Inches(.1),Inches(4.95),Inches(1.9),Inches(.4), + sz=12,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + + # S2: 앱 개요 + s = blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"앱 개요 — 구현된 6개 화면",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_sl(s,["화면","경로","기능"], + [["로그인","(auth)/login.tsx","JWT 인증 · SecureStore 저장"], + ["대시보드","(tabs)/index.tsx","SR 통계 · 서비스 상태 · 배포 이력"], + ["SR 관리","(tabs)/sr.tsx","서비스 요청 목록 조회 및 신규 등록"], + ["AI 챗봇","(tabs)/chat.tsx","Ollama LLM 자연어 인프라 명령"], + ["알림","(tabs)/notifications.tsx","인시던트·SLA·배포 알림 수신"], + ["설정","(tabs)/settings.tsx","프로필·알림설정·로그아웃"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(3.8), + cws=[Inches(2.0),Inches(3.5),Inches(7.0)]) + + # S3: EAS 빌드 가이드 + s = blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"EAS Build 가이드",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_sl(s,["명령어","용도","시간"], + [["eas build --platform android --profile preview","테스트 APK","~10분"], + ["eas build --platform android --profile production","Play Store AAB","~15분"], + ["eas build --platform ios --profile production","App Store IPA","~20분"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(2.0), + cws=[Inches(6.5),Inches(4.0),Inches(2.0)]) + text(s,"필수 체크리스트",Inches(.4),Inches(3.6),Inches(12),Inches(.4),sz=14,bold=True,color=BRAND) + items = [("android/ 폴더 없음",GREEN),("PNG Crunching=false",GREEN), + (".easignore 설정",GREEN),("babel-preset-expo만",GREEN),("EAS 로그인",GREEN)] + for i,(t_,c) in enumerate(items): + bx=Inches(.4+i*2.5); by=Inches(4.1) + rect(s,bx,by,Inches(2.3),Inches(.6),fill=RGBColor(0xf0,0xfd,0xf4)) + text(s,"✅ "+t_,bx+Inches(.1),by+Inches(.1),Inches(2.1),Inches(.4),sz=10,color=GREEN) + + # S4: 빌드 이슈 이력 + s = blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"빌드 이슈 이력 — 4개 이슈 모두 해결",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_sl(s,["이슈","원인","해결"], + [["Gradle UNKNOWN ERROR","android/ 폴더 → Bare Workflow 오인",".easignore android/, ios/ 추가"], + ["packageReleaseResources 실패","PIL PNG + AAPT2 Crunching 충돌","withGradleProps.js PNG Crunching=false"], + ["Firebase Gradle 오류","expo-notifications (google-services.json 없음)","app.json plugins에서 제거"], + ["Babel 경고","expo-router/babel deprecated (SDK 51)","babel-preset-expo만 사용"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(3.2), + cws=[Inches(3.5),Inches(4.5),Inches(4.5)]) + rect(s,Inches(.4),Inches(4.8),Inches(12.5),Inches(.8),fill=GREEN) + text(s,"✅ 최종 성공 빌드: 51096ada (Android APK) — EAS 계정: zioinfo", + Inches(.6),Inches(4.9),Inches(12.1),Inches(.6),sz=14,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + + # S5: 스토어 등록 + s = blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"스토어 등록 절차",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_sl(s,["항목","Google Play","Apple App Store"], + [["계정 비용","$25 (1회)","$99/년"], + ["패키지/번들 ID","kr.co.zioinfo.guardia","kr.co.zioinfo.guardia"], + ["Privacy Policy","필수","필수 (URL 필요)"], + ["스크린샷","최소 2개","최소 3개 (6.7인치)"], + ["제출 방법","AAB 업로드","eas submit --platform ios"], + ["심사 기간","2~7 영업일","1~3 영업일"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(3.5), + cws=[Inches(3.0),Inches(4.75),Inches(4.75)]) + rect(s,Inches(.4),Inches(5.2),Inches(12.5),Inches(.8),fill=RGBColor(0xff,0xf3,0xcd)) + text(s,"📌 App Store는 Privacy Policy URL이 필수입니다: https://zioinfo.co.kr/privacy 페이지 등록 필요", + Inches(.6),Inches(5.3),Inches(12.1),Inches(.6),sz=12,color=RGBColor(0x85,0x4d,0x0e)) + + # S6: 하네스 구조 + s = blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"하네스 (.claude/) 구조 — 4 에이전트 + 5 스킬",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + agents = [("👨‍💻","rn-developer","RN 화면 구현"), + ("🔨","eas-engineer","EAS 빌드 관리"), + ("🏪","store-publisher","스토어 등록"), + ("📝","doc-writer","가이드 + PDF/PPTX")] + for i,(icon,name,desc) in enumerate(agents): + bx=Inches(.4+i*3.2); by=Inches(1.4) + rect(s,bx,by,Inches(3.0),Inches(2.5),fill=GRAY) + text(s,icon,bx+Inches(.9),by+Inches(.2),Inches(1.2),Inches(.7),sz=28,align=PP_ALIGN.CENTER,color=DARK) + text(s,name,bx+Inches(.2),by+Inches(1.0),Inches(2.6),Inches(.5),sz=13,bold=True,color=BRAND,align=PP_ALIGN.CENTER) + text(s,desc,bx+Inches(.2),by+Inches(1.5),Inches(2.6),Inches(.5),sz=11,color=MUTED,align=PP_ALIGN.CENTER) + tbl_sl(s,["스킬","트리거 키워드"], + [["messenger-orchestrator","화면 구현, EAS 빌드, 스토어 등록, 가이드 작성 등 모든 요청"], + ["rn-screen-dev","화면 추가, 컴포넌트, UI 수정, API 연동"], + ["eas-build-deploy","APK 빌드, Gradle 오류, EAS 설정"], + ["store-publish","Play Store, App Store, 스크린샷"], + ["doc-generator","가이드 작성, PDF, PPTX 생성"]], + Inches(.4),Inches(4.1),Inches(12.5),Inches(2.8), + cws=[Inches(3.0),Inches(9.5)]) + + # S7: 접속 정보 + s = blank() + rect(s,0,0,W,Inches(1.2),fill=BRAND) + text(s,"접속 정보 및 빠른 참조",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE) + tbl_sl(s,["항목","값"], + [["서버 URL","https://zioinfo.co.kr:8443"], + ["관리자 계정","admin / Admin@zioinfo2026!"], + ["EAS 계정","zioinfo (expo.dev)"], + ["EAS 프로젝트 ID","ca2f72d6-7dda-4491-9590-7ace34b10a88"], + ["성공 빌드 ID","51096ada-9735-4ea8-9e81-5f5991731ea8"], + ["패키지명","kr.co.zioinfo.guardia"], + ["개발 서버","npx expo start (로컬)"]], + Inches(.4),Inches(1.4),Inches(12.5),Inches(4.0), + cws=[Inches(4.0),Inches(8.5)]) + + # S8: 마지막 + s = blank() + rect(s,0,0,W,H,fill=BRAND) + text(s,"GUARDiA Messenger",Inches(1),Inches(2.3),Inches(11.33),Inches(1.2), + sz=34,bold=True,color=WHITE,align=PP_ALIGN.CENTER) + text(s,"📱 APK 빌드 성공 · 🏪 스토어 등록 준비 완료",Inches(1),Inches(3.6),Inches(11.33),Inches(.7), + sz=16,color=RGBColor(0xaa,0xc4,0xe8),align=PP_ALIGN.CENTER) + text(s,"(주)지오정보기술 | GUARDiA Messenger v1.0.0 | 2026-05-31", + Inches(1),Inches(5.5),Inches(11.33),Inches(.5),sz=13,color=MUTED,align=PP_ALIGN.CENTER) + + prs.save(out) + print(f"PPTX: {out}") + + +if __name__ == "__main__": + pdf = str(OUT / "35_GUARDiA_Messenger_개발가이드.pdf") + pptx = str(OUT / "36_GUARDiA_Messenger_발표자료.pptx") + gen_pdf(pdf) + gen_pptx(pptx) + print("\n=== 완료 ===") + print(f"MD : {OUT}/34_GUARDiA_Messenger_개발_배포_가이드.md") + print(f"PDF : {pdf}") + print(f"PPTX: {pptx}") diff --git a/gen_opennet_docs.py b/gen_opennet_docs.py new file mode 100644 index 0000000..57565fd --- /dev/null +++ b/gen_opennet_docs.py @@ -0,0 +1,701 @@ +#!/usr/bin/env python3 +""" +GUARDiA 개방망 가이드 — PDF + PPTX 자동 생성 +출력: manual/23_GUARDiA_개방망_가이드.pdf + manual/24_GUARDiA_개방망_발표자료.pptx +""" +import os, sys +from pathlib import Path + +OUT_DIR = Path(__file__).parent +FONT_PATH = None # None이면 기본 폰트 사용 + +# ══════════════════════════════════════════════════════════════ +# PDF 생성 +# ══════════════════════════════════════════════════════════════ +def gen_pdf(output_path: str): + from reportlab.lib.pagesizes import A4 + from reportlab.lib import colors + from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + from reportlab.lib.units import mm + from reportlab.platypus import ( + SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, + HRFlowable, PageBreak + ) + from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT + + # 폰트 등록 (한글 지원) + from reportlab.pdfbase import pdfmetrics + from reportlab.pdfbase.ttfonts import TTFont + + FONT_DIRS = [ + "C:/Windows/Fonts", + "/usr/share/fonts/truetype/noto", + "/usr/share/fonts/truetype/dejavu", + "/System/Library/Fonts", + ] + FONT_CANDIDATES = [ + ("malgun.ttf", "Malgun"), + ("NanumGothic.ttf", "NanumGothic"), + ("DejaVuSans.ttf", "DejaVuSans"), + ] + + font_name = "Helvetica" + for fname, alias in FONT_CANDIDATES: + for d in FONT_DIRS: + fp = os.path.join(d, fname) + if os.path.exists(fp): + try: + pdfmetrics.registerFont(TTFont(alias, fp)) + font_name = alias + break + except Exception: + pass + if font_name != "Helvetica": + break + + # ── 색상 정의 ────────────────────────────────────────────── + BRAND_BLUE = colors.HexColor("#1a3a6b") + ACCENT_BLUE = colors.HexColor("#4f6ef7") + LIGHT_BLUE = colors.HexColor("#e8ecff") + GRAY_BG = colors.HexColor("#f0f2f5") + SUCCESS_GRN = colors.HexColor("#22c55e") + WARNING_ORG = colors.HexColor("#f59e0b") + DANGER_RED = colors.HexColor("#ef4444") + TEXT_DARK = colors.HexColor("#1e293b") + TEXT_MUTED = colors.HexColor("#64748b") + + doc = SimpleDocTemplate( + output_path, pagesize=A4, + leftMargin=20*mm, rightMargin=20*mm, + topMargin=20*mm, bottomMargin=20*mm + ) + + styles = getSampleStyleSheet() + def sty(name, **kw): + base = kw.pop("base", "Normal") + kw.setdefault("fontName", font_name) + s = ParagraphStyle(name, parent=styles[base], **kw) + return s + + S = { + "cover_title": sty("ct", fontSize=28, textColor=colors.white, + alignment=TA_CENTER, spaceAfter=6, leading=36), + "cover_sub": sty("cs", fontSize=14, textColor=LIGHT_BLUE, + alignment=TA_CENTER, spaceAfter=4), + "cover_meta": sty("cm", fontSize=10, textColor=colors.HexColor("#aab4c8"), + alignment=TA_CENTER), + "h1": sty("h1", fontSize=18, textColor=BRAND_BLUE, + spaceAfter=8, spaceBefore=16, leading=24, fontName=font_name), + "h2": sty("h2", fontSize=13, textColor=ACCENT_BLUE, + spaceAfter=5, spaceBefore=10, leading=18), + "h3": sty("h3", fontSize=11, textColor=TEXT_DARK, + spaceAfter=4, spaceBefore=8, leading=16), + "body": sty("body", fontSize=10, textColor=TEXT_DARK, leading=16, spaceAfter=4), + "code": sty("code", fontName="Courier", fontSize=9, + backColor=GRAY_BG, textColor=colors.HexColor("#1d4ed8"), + leading=14, leftIndent=10, spaceBefore=4, spaceAfter=4), + "note": sty("note", fontSize=9, textColor=TEXT_MUTED, + leftIndent=10, leading=14, spaceAfter=3), + "badge_ok": sty("bok", fontSize=9, textColor=SUCCESS_GRN, fontName=font_name), + "badge_warn": sty("bwrn", fontSize=9, textColor=WARNING_ORG, fontName=font_name), + } + + story = [] + W = A4[0] - 40*mm + + def hr(color=ACCENT_BLUE, w=1): + return HRFlowable(width="100%", thickness=w, color=color, spaceAfter=4, spaceBefore=4) + + def table(data, col_widths=None, header=True): + t = Table(data, colWidths=col_widths) + base_style = [ + ("FONTNAME", (0,0), (-1,-1), font_name), + ("FONTSIZE", (0,0), (-1,-1), 9), + ("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, GRAY_BG]), + ("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#e2e8f0")), + ("VALIGN", (0,0), (-1,-1), "MIDDLE"), + ("LEFTPADDING", (0,0), (-1,-1), 6), + ("RIGHTPADDING", (0,0), (-1,-1), 6), + ("TOPPADDING", (0,0), (-1,-1), 5), + ("BOTTOMPADDING",(0,0), (-1,-1), 5), + ] + if header: + base_style += [ + ("BACKGROUND", (0,0), (-1,0), BRAND_BLUE), + ("TEXTCOLOR", (0,0), (-1,0), colors.white), + ("FONTSIZE", (0,0), (-1,0), 9), + ("FONTNAME", (0,0), (-1,0), font_name), + ] + t.setStyle(TableStyle(base_style)) + return t + + # ── 표지 ──────────────────────────────────────────────────── + from reportlab.platypus import KeepTogether + cover_bg = Table( + [[Paragraph("GUARDiA ITSM", S["cover_title"]),], + [Paragraph("개방망(Open Network) 구현 가이드", S["cover_sub"])], + [Paragraph(" ", S["cover_sub"])], + [Paragraph("v2.0.0 | 2026-05-30 | (주)지오정보기술", S["cover_meta"])], + [Paragraph("서버: zioinfo.co.kr | AI 기반 레거시 인프라 자율 운영 플랫폼", S["cover_meta"])], + ], + colWidths=[W] + ) + cover_bg.setStyle(TableStyle([ + ("BACKGROUND", (0,0), (-1,-1), BRAND_BLUE), + ("TOPPADDING", (0,0), (-1,-1), 30), + ("BOTTOMPADDING",(0,0),(-1,-1), 30), + ("LEFTPADDING", (0,0), (-1,-1), 20), + ("RIGHTPADDING",(0,0), (-1,-1), 20), + ("ROUNDEDCORNERS", [8]), + ])) + story += [Spacer(1, 30*mm), cover_bg, Spacer(1, 10*mm)] + + # 목차 카드 + toc_data = [ + ["순서", "섹션", "페이지"], + ["1", "개요 및 배경", "2"], + ["2", "아키텍처", "2"], + ["3", "구현 내용", "3"], + ["4", "설치 및 설정", "4"], + ["5", "API 사용법", "5"], + ["6", "보안 설정", "6"], + ["7", "테스트 결과", "7"], + ["8", "운영 절차", "7"], + ] + story += [ + Paragraph("목 차", sty("toc_h", fontSize=13, textColor=BRAND_BLUE, + alignment=TA_CENTER, spaceAfter=8)), + table(toc_data, col_widths=[15*mm, 120*mm, 20*mm]), + PageBreak(), + ] + + # ── 섹션 1: 개요 ─────────────────────────────────────────── + story += [ + Paragraph("1. 개요 및 배경", S["h1"]), hr(), + Paragraph( + "GUARDiA ITSM은 기본적으로 폐쇄망(Closed Network) 환경에서 운영됩니다. " + "그러나 외부 메신저(카카오워크, 네이버웍스, Slack)와의 연동, 공공기관 포털 연계, " + "재택/원격 관리 등의 요구사항이 증가함에 따라 개방망 지원 기능을 추가하였습니다.", + S["body"]), + Spacer(1, 4), + Paragraph("1-1. 폐쇄망 vs 개방망 비교", S["h2"]), + table([ + ["항목", "폐쇄망 (기본)", "개방망 (이 가이드)"], + ["접근 범위", "내부망 only", "인터넷 외부 접근 허용"], + ["CORS 정책", "localhost 만 허용", "지정 외부 도메인 허용"], + ["HTTPS", "선택", "필수 (TLS 1.2/1.3)"], + ["API 인증", "JWT", "JWT + API Key"], + ["외부 AI 호출", "금지 (Ollama only)", "금지 유지 (변경 불가)"], + ["Rate Limiting", "기본", "강화 (30 req/min)"], + ["보안 헤더", "기본", "HSTS 포함 강화"], + ], col_widths=[45*mm, 60*mm, 60*mm]), + Spacer(1, 4), + Paragraph( + "⚠ 핵심 원칙 유지: 개방망 모드에서도 Ollama(LLM)는 내부 전용 유지. " + "외부 AI API(OpenAI, Anthropic 등) 절대 사용 금지.", + S["note"]), + ] + + # ── 섹션 2: 아키텍처 ─────────────────────────────────────── + story += [ + Spacer(1, 8), Paragraph("2. 개방망 아키텍처", S["h1"]), hr(), + Paragraph("2-1. 시스템 구성도", S["h2"]), + Paragraph( + "외부 클라이언트는 Nginx를 통해 TLS 암호화된 채널로 GUARDiA API에 접근합니다. " + "LLM(Ollama)과 데이터베이스(PostgreSQL)는 외부 직접 접근이 불가하며, " + "API 서버를 통해서만 간접 이용 가능합니다.", + S["body"]), + table([ + ["구성 요소", "역할", "외부 접근"], + ["Nginx (443, 8443)", "TLS 종료 + Rate Limit + 보안헤더", "허용"], + ["GUARDiA FastAPI (8001)", "비즈니스 로직 + CORS + 보안 미들웨어", "Nginx 통해서만"], + ["PostgreSQL (5432)", "데이터 저장", "금지 (127.0.0.1만)"], + ["Ollama LLM (11434)", "온프레미스 AI 추론", "금지 (127.0.0.1만)"], + ], col_widths=[55*mm, 85*mm, 25*mm]), + Spacer(1, 4), + Paragraph("2-2. 포트 구성", S["h2"]), + table([ + ["포트", "프로토콜", "서비스", "외부 접근"], + ["80", "HTTP", "홈페이지 (HTTPS 리다이렉트)", "허용"], + ["443", "HTTPS", "홈페이지 SSL", "허용"], + ["8001", "HTTP", "GUARDiA API (내부 직접)", "권장하지 않음"], + ["8443", "HTTPS", "GUARDiA API (외부 접근 권장)", "허용"], + ["5432", "TCP", "PostgreSQL", "차단"], + ["11434", "HTTP", "Ollama LLM", "차단"], + ], col_widths=[15*mm, 25*mm, 90*mm, 35*mm]), + ] + + # ── 섹션 3: 구현 내용 ──────────────────────────────────────── + story += [ + PageBreak(), + Paragraph("3. 구현 내용", S["h1"]), hr(), + Paragraph("3-1. 신규 추가 파일", S["h2"]), + table([ + ["파일", "내용"], + ["core/external_security.py", "API Key 생성/검증/감사 유틸리티"], + ["routers/external_api.py", "외부 API 라우터 (헬스체크, SR, 웹훅, API Key 관리)"], + [".env.open", "개방망 운영 환경변수 템플릿"], + ["deploy/nginx_opennet.py", "Nginx HTTPS 설정 배포 스크립트"], + ], col_widths=[70*mm, 95*mm]), + Spacer(1, 4), + Paragraph("3-2. 수정된 파일", S["h2"]), + table([ + ["파일", "변경 내용"], + ["main.py", "CORS 환경변수 기반 동적 설정, 보안 헤더 미들웨어, external_api 라우터 등록"], + ["models.py", "APIKey ORM 모델 추가 (tb_api_key 테이블)"], + ], col_widths=[70*mm, 95*mm]), + Spacer(1, 4), + Paragraph("3-3. 개방망 모드 CORS 동작 방식", S["h2"]), + Paragraph( + "환경변수 GUARDIA_NETWORK_MODE에 따라 CORS 정책이 자동 전환됩니다:", + S["body"]), + Paragraph("• closed (기본): localhost만 허용", S["note"]), + Paragraph("• open: GUARDIA_ALLOWED_ORIGINS에 지정된 외부 도메인도 허용", S["note"]), + Paragraph("• 정규식 패턴 허용으로 서브도메인 일괄 허용 가능", S["note"]), + ] + + # ── 섹션 4: 설치 및 설정 ──────────────────────────────────── + story += [ + Spacer(1, 8), + Paragraph("4. 설치 및 설정", S["h1"]), hr(), + Paragraph("4-1. .env 개방망 설정", S["h2"]), + Paragraph("다음 환경변수를 /opt/guardia/app/.env 에 설정합니다:", S["body"]), + table([ + ["환경변수", "값 예시", "설명"], + ["GUARDIA_NETWORK_MODE", "open", "개방망 모드 활성화"], + ["GUARDIA_ALLOWED_ORIGINS", "https://itsm.zioinfo.co.kr", "허용 외부 출처"], + ["GUARDIA_WEBHOOK_SECRET", "<강력한 랜덤 값>", "웹훅 HMAC 서명 키"], + ["DATABASE_URL", "postgresql+asyncpg://...", "@ 포함 시 %40으로 인코딩"], + ], col_widths=[60*mm, 55*mm, 50*mm]), + Spacer(1, 4), + Paragraph("4-2. SSL 인증서", S["h2"]), + Paragraph( + "도메인이 있는 경우 Let's Encrypt 인증서 사용을 권장합니다. " + "IP만 있는 경우 자체 서명 인증서를 생성합니다.", + S["body"]), + Paragraph("도메인 보유: certbot --nginx -d itsm.zioinfo.co.kr", S["code"]), + Paragraph( + "IP 전용: openssl req -x509 -nodes -days 3650 -newkey rsa:2048 ...", + S["code"]), + ] + + # ── 섹션 5: API 사용법 ──────────────────────────────────────── + story += [ + PageBreak(), + Paragraph("5. 외부 API 사용법", S["h1"]), hr(), + Paragraph("5-1. API 엔드포인트 목록", S["h2"]), + table([ + ["엔드포인트", "메서드", "인증", "설명"], + ["/api/external/health", "GET", "없음", "헬스체크"], + ["/api/external/status", "GET", "없음", "시스템 공개 상태"], + ["/api/external/keys", "POST", "JWT (관리자)", "API Key 발급"], + ["/api/external/keys/{id}", "DELETE", "JWT (관리자)", "API Key 비활성화"], + ["/api/external/sr", "GET", "API Key (read)", "SR 목록 조회"], + ["/api/external/sr", "POST", "API Key (write)", "SR 등록"], + ["/api/external/webhook", "POST", "HMAC (선택)", "외부 메신저 웹훅"], + ["/docs", "GET", "없음", "OpenAPI 문서"], + ], col_widths=[60*mm, 20*mm, 40*mm, 45*mm]), + Spacer(1, 4), + Paragraph("5-2. API Key 권한 스코프", S["h2"]), + table([ + ["스코프", "허용 API", "사용 예시"], + ["read", "SR 목록 조회", "모니터링 시스템"], + ["write", "SR 등록, 상태 변경", "외부 티켓 시스템"], + ["admin", "모든 외부 API", "통합 관리 도구"], + ["webhook", "웹훅 수신", "카카오워크, Slack 봇"], + ], col_widths=[30*mm, 70*mm, 65*mm]), + Spacer(1, 4), + Paragraph("5-3. 외부 메신저 웹훅 연동 구조", S["h2"]), + Paragraph( + "외부 메신저(카카오워크, 네이버웍스, Slack 등)는 GUARDiA 웹훅 엔드포인트로 " + "자연어 명령을 전송합니다. GUARDiA는 Ollama LLM으로 명령을 파싱하여 처리합니다.", + S["body"]), + table([ + ["메신저", "웹훅 URL", "인증 방식"], + ["카카오워크", "POST /api/external/webhook", "X-GUARDiA-Signature (HMAC)"], + ["네이버웍스", "POST /api/external/webhook", "X-GUARDiA-Signature (HMAC)"], + ["Slack", "POST /api/external/webhook", "X-Source: slack"], + ["Teams", "POST /api/external/webhook", "X-Source: teams"], + ["사용자 정의", "POST /api/external/webhook", "선택 (HMAC 권장)"], + ], col_widths=[35*mm, 80*mm, 50*mm]), + ] + + # ── 섹션 6: 보안 ────────────────────────────────────────────── + story += [ + Spacer(1, 8), + Paragraph("6. 보안 설정", S["h1"]), hr(), + Paragraph("6-1. 적용된 보안 헤더", S["h2"]), + table([ + ["헤더", "값", "효과"], + ["Strict-Transport-Security", "max-age=31536000; includeSubDomains", + "브라우저가 HTTPS만 사용"], + ["X-Frame-Options", "DENY", "Clickjacking 방지"], + ["X-Content-Type-Options", "nosniff", "MIME 스니핑 방지"], + ["X-XSS-Protection", "1; mode=block", "XSS 차단"], + ["Referrer-Policy", "strict-origin-when-cross-origin", "Referrer 정보 제한"], + ], col_widths=[55*mm, 70*mm, 40*mm]), + Spacer(1, 4), + Paragraph("6-2. 변경 불가 보안 정책", S["h2"]), + Paragraph( + "개방망 모드에서도 다음 핵심 보안 정책은 절대 변경 불가합니다:", + S["body"]), + table([ + ["정책", "내용"], + ["외부 LLM 금지", "Ollama(localhost) 전용. OpenAI/Claude 등 외부 API 완전 금지"], + ["SSH 자격증명 보호", "IP, 비밀번호, SSH 계정을 API 응답에 절대 포함 금지"], + ["AES-256-GCM 암호화", "서버 자격증명은 암호화 저장 (os_pw_enc 컬럼)"], + ["root SSH 금지", "opsagent 전용 계정만 사용"], + ["감사 로그", "모든 외부 API 호출 TB_AUDIT_LOG에 기록"], + ], col_widths=[50*mm, 115*mm]), + ] + + # ── 섹션 7: 테스트 결과 ─────────────────────────────────────── + story += [ + PageBreak(), + Paragraph("7. 테스트 결과", S["h1"]), hr(), + Paragraph("테스트 환경: Ubuntu 24.04, GUARDiA 2.0.0, Nginx 1.24 | 2026-05-30", S["note"]), + Spacer(1, 4), + table([ + ["#", "테스트 항목", "기대값", "실제값", "결과"], + ["T1", "HTTP 헬스체크 (8001)", "200 OK", "200 OK", "PASS"], + ["T2", "HTTPS 헬스체크 (8443)", "200 OK", "200 OK", "PASS"], + ["T3", "홈페이지 HTTPS (443)", "200 OK", "200 OK", "PASS"], + ["T4", "미인증 API 접근 차단", "401", "401", "PASS"], + ["T5", "CORS 외부 출처 허용", "Allow-Origin 헤더", "헤더 포함", "PASS"], + ["T6", "HSTS 헤더 적용", "max-age=31536000", "적용됨", "PASS"], + ["T7", "X-Frame-Options", "DENY", "DENY", "PASS"], + ["T8", "Rate Limiting 설정", "zone 설정 확인", "1개 zone", "PASS"], + ["T9", "공개 시스템 상태", "operational", "operational", "PASS"], + ["T10", "개방망 모드 활성", "open", "open", "PASS"], + ], col_widths=[10*mm, 60*mm, 40*mm, 40*mm, 15*mm]), + Spacer(1, 4), + Paragraph("전체 10개 테스트 모두 통과 (10/10 PASS)", sty( + "tr", fontSize=11, textColor=SUCCESS_GRN, alignment=TA_CENTER)), + ] + + # ── 섹션 8: 운영 절차 ────────────────────────────────────────── + story += [ + Spacer(1, 8), + Paragraph("8. 운영 절차", S["h1"]), hr(), + Paragraph("8-1. 모드 전환 명령", S["h2"]), + table([ + ["작업", "명령어"], + ["폐쇄망→개방망", + "echo GUARDIA_NETWORK_MODE=open >> .env && systemctl restart guardia"], + ["개방망→폐쇄망", + "sed -i 's/open/closed/' .env && systemctl restart guardia"], + ["HTTPS 활성화", + "ln -sf sites-available/guardia-https sites-enabled/ && nginx -t && systemctl reload nginx"], + ["HTTPS 비활성화", + "rm sites-enabled/guardia-https && systemctl reload nginx"], + ], col_widths=[35*mm, 130*mm]), + Spacer(1, 4), + Paragraph("8-2. 서비스 접속 정보", S["h2"]), + table([ + ["서비스", "URL", "용도"], + ["GUARDiA ITSM HTTP", "http://zioinfo.co.kr:8001", "내부망 직접 접근"], + ["GUARDiA ITSM HTTPS", "https://zioinfo.co.kr:8443", "개방망 외부 접근 (권장)"], + ["외부 API", "https://zioinfo.co.kr:8443/api/external/", "API Key 인증"], + ["OpenAPI 문서", "https://zioinfo.co.kr:8443/docs", "API 명세서 (공개)"], + ["홈페이지 HTTPS", "https://zioinfo.co.kr", "지오정보기술 홈페이지"], + ], col_widths=[40*mm, 75*mm, 50*mm]), + Spacer(1, 8), + hr(colors.HexColor("#e2e8f0")), + Paragraph( + "GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30", + sty("footer", fontSize=8, textColor=TEXT_MUTED, alignment=TA_CENTER)), + ] + + doc.build(story) + print(f"PDF 생성 완료: {output_path}") + + +# ══════════════════════════════════════════════════════════════ +# PPTX 생성 +# ══════════════════════════════════════════════════════════════ +def gen_pptx(output_path: str): + from pptx import Presentation + from pptx.util import Inches, Pt, Emu + from pptx.dml.color import RGBColor + from pptx.enum.text import PP_ALIGN + from pptx.util import Inches, Pt + + W, H = Inches(13.33), Inches(7.5) # 16:9 와이드 + prs = Presentation() + prs.slide_width = W + prs.slide_height = H + + # ── 색상 팔레트 ───────────────────────────────────────────── + BRAND = RGBColor(0x1a, 0x3a, 0x6b) + ACCENT = RGBColor(0x4f, 0x6e, 0xf7) + WHITE = RGBColor(0xff, 0xff, 0xff) + GRAY_LT = RGBColor(0xf0, 0xf2, 0xf5) + GREEN = RGBColor(0x22, 0xc5, 0x5e) + ORANGE = RGBColor(0xf5, 0x9e, 0x0b) + RED = RGBColor(0xef, 0x44, 0x44) + DARK = RGBColor(0x1e, 0x29, 0x3b) + MUTED = RGBColor(0x64, 0x74, 0x8b) + + def blank_slide(): + layout = prs.slide_layouts[6] # blank + return prs.slides.add_slide(layout) + + def add_rect(slide, x, y, w, h, fill=None, line=None, radius=0): + from pptx.util import Emu + shape = slide.shapes.add_shape(1, x, y, w, h) # MSO_SHAPE_TYPE.RECTANGLE + if fill: + shape.fill.solid() + shape.fill.fore_color.rgb = fill + else: + shape.fill.background() + if line: + shape.line.color.rgb = line + shape.line.width = Pt(0.75) + else: + shape.line.fill.background() + return shape + + def add_text(slide, text, x, y, w, h, size=18, bold=False, + color=DARK, align=PP_ALIGN.LEFT, italic=False): + tb = slide.shapes.add_textbox(x, y, w, h) + tf = tb.text_frame + tf.word_wrap = True + p = tf.paragraphs[0] + p.alignment = align + run = p.add_run() + run.text = text + run.font.size = Pt(size) + run.font.bold = bold + run.font.italic = italic + run.font.color.rgb = color + return tb + + def add_table_slide(slide, headers, rows, x, y, w, h, col_widths=None): + from pptx.util import Pt + cols = len(headers) + tbl = slide.shapes.add_table(len(rows)+1, cols, x, y, w, h).table + if col_widths: + for i, cw in enumerate(col_widths): + tbl.columns[i].width = cw + + def cell_style(cell, text, bg=None, txt_color=DARK, bold=False, sz=10): + cell.text = text + cell.text_frame.paragraphs[0].font.size = Pt(sz) + cell.text_frame.paragraphs[0].font.bold = bold + cell.text_frame.paragraphs[0].font.color.rgb = txt_color + if bg: + cell.fill.solid() + cell.fill.fore_color.rgb = bg + + for j, h_text in enumerate(headers): + cell_style(tbl.cell(0, j), h_text, bg=BRAND, txt_color=WHITE, bold=True, sz=10) + for i, row in enumerate(rows): + bg = GRAY_LT if i % 2 == 0 else WHITE + for j, val in enumerate(row): + cell_style(tbl.cell(i+1, j), str(val), bg=bg) + + # ── 슬라이드 1: 표지 ─────────────────────────────────────── + s = blank_slide() + add_rect(s, 0, 0, W, H, fill=BRAND) + add_rect(s, Inches(0.5), Inches(1.2), Inches(12.33), Inches(4.5), + fill=RGBColor(0x25, 0x4a, 0x80)) + + add_text(s, "GUARDiA ITSM", Inches(0.8), Inches(1.5), Inches(11.73), Inches(1.2), + size=44, bold=True, color=WHITE, align=PP_ALIGN.CENTER) + add_text(s, "개방망(Open Network) 구현 가이드", + Inches(0.8), Inches(2.7), Inches(11.73), Inches(0.8), + size=22, color=RGBColor(0xaa, 0xc4, 0xe8), align=PP_ALIGN.CENTER) + add_text(s, "v2.0.0 | 2026-05-30 | (주)지오정보기술", + Inches(0.8), Inches(3.5), Inches(11.73), Inches(0.5), + size=13, color=MUTED, align=PP_ALIGN.CENTER) + + # 배지들 + badges = [("HTTPS", GREEN), ("API Key", ACCENT), ("CORS", ORANGE), ("Rate Limit", RED)] + for i, (txt, col) in enumerate(badges): + bx = Inches(3.2 + i * 1.8) + add_rect(s, bx, Inches(5.0), Inches(1.5), Inches(0.5), fill=col) + add_text(s, txt, bx + Inches(0.1), Inches(5.05), Inches(1.3), Inches(0.4), + size=12, bold=True, color=WHITE, align=PP_ALIGN.CENTER) + + add_text(s, "AI 기반 레거시 인프라 자율 운영 플랫폼", + Inches(0.8), Inches(6.5), Inches(11.73), Inches(0.5), + size=11, color=MUTED, italic=True, align=PP_ALIGN.CENTER) + + # ── 슬라이드 2: 목차 ─────────────────────────────────────── + s = blank_slide() + add_rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + add_text(s, "목차 (Agenda)", Inches(0.4), Inches(0.25), Inches(12), Inches(0.7), + size=24, bold=True, color=WHITE) + + items = [ + ("1", "개요 및 배경", "폐쇄망 vs 개방망 비교, 지원 필요성"), + ("2", "아키텍처", "개방망 시스템 구성도, 포트 구성"), + ("3", "구현 내용", "신규/수정 파일, CORS 동작 방식"), + ("4", "설치 및 설정", ".env 설정, SSL 인증서, Nginx"), + ("5", "외부 API", "엔드포인트, API Key 권한, 웹훅 연동"), + ("6", "보안 설정", "보안 헤더, 불변 보안 정책"), + ("7", "테스트 결과", "10개 항목 전체 통과 (10/10 PASS)"), + ("8", "운영 절차", "모드 전환, 접속 정보"), + ] + for i, (num, title, desc) in enumerate(items): + row = i // 2; col = i % 2 + bx = Inches(0.4 + col * 6.4); by = Inches(1.4 + row * 1.35) + add_rect(s, bx, by, Inches(5.9), Inches(1.1), fill=GRAY_LT, line=ACCENT) + add_rect(s, bx, by, Inches(0.6), Inches(1.1), fill=ACCENT) + add_text(s, num, bx + Inches(0.05), by + Inches(0.2), + Inches(0.5), Inches(0.6), size=18, bold=True, color=WHITE, align=PP_ALIGN.CENTER) + add_text(s, title, bx + Inches(0.7), by + Inches(0.1), + Inches(5.1), Inches(0.45), size=14, bold=True, color=BRAND) + add_text(s, desc, bx + Inches(0.7), by + Inches(0.55), + Inches(5.1), Inches(0.45), size=10, color=MUTED) + + # ── 슬라이드 3: 개요 ──────────────────────────────────────── + s = blank_slide() + add_rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + add_text(s, "1. 개요 — 개방망 지원 필요성", Inches(0.4), Inches(0.25), + Inches(12), Inches(0.7), size=24, bold=True, color=WHITE) + + add_table_slide(s, + ["항목", "폐쇄망 (기본)", "개방망 (이 가이드)"], + [ + ["접근 범위", "내부망 only", "인터넷 외부 접근 허용"], + ["CORS 정책", "localhost 만 허용", "지정 외부 도메인 허용"], + ["HTTPS", "선택", "필수 (TLS 1.2/1.3)"], + ["API 인증", "JWT only", "JWT + API Key 추가"], + ["외부 LLM", "금지 (Ollama only)", "금지 유지 (변경 불가)"], + ["Rate Limiting", "기본", "강화 (30 req/min)"], + ], + Inches(0.4), Inches(1.4), Inches(12.5), Inches(4.0), + col_widths=[Inches(2.5), Inches(4.5), Inches(5.5)] + ) + + add_rect(s, Inches(0.4), Inches(5.8), Inches(12.5), Inches(0.7), + fill=RGBColor(0xff, 0xf1, 0xf2)) + add_text(s, "⚠ 핵심 원칙 유지: 개방망 모드에서도 Ollama(LLM)는 내부 전용. " + "외부 AI API 절대 사용 금지.", + Inches(0.6), Inches(5.9), Inches(12.2), Inches(0.5), + size=11, bold=True, color=RED) + + # ── 슬라이드 4: 아키텍처 ──────────────────────────────────── + s = blank_slide() + add_rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + add_text(s, "2. 개방망 아키텍처", Inches(0.4), Inches(0.25), + Inches(12), Inches(0.7), size=24, bold=True, color=WHITE) + + # 아키텍처 다이어그램 (박스들) + boxes = [ + ("외부 클라이언트\n(브라우저/메신저봇)", Inches(0.3), Inches(1.3), Inches(2.8), Inches(1.0), ACCENT, WHITE), + ("Nginx\n(TLS + Rate Limit + 보안헤더)", Inches(4.0), Inches(1.3), Inches(2.8), Inches(1.0), BRAND, WHITE), + ("GUARDiA ITSM\n(FastAPI 8001)", Inches(7.7), Inches(1.3), Inches(2.8), Inches(1.0), ACCENT, WHITE), + ("PostgreSQL\n(내부 전용 5432)", Inches(7.7), Inches(3.2), Inches(2.8), Inches(0.9), DARK, WHITE), + ("Ollama LLM\n(내부 전용 11434)", Inches(11.0), Inches(3.2), Inches(2.0), Inches(0.9), DARK, WHITE), + ] + for txt, bx, by, bw, bh, fill, txt_col in boxes: + add_rect(s, bx, by, bw, bh, fill=fill) + add_text(s, txt, bx + Inches(0.1), by + Inches(0.1), bw - Inches(0.2), + bh - Inches(0.2), size=11, bold=True, color=txt_col, align=PP_ALIGN.CENTER) + + # 화살표 텍스트 + add_text(s, "→ HTTPS (443/8443)", Inches(3.2), Inches(1.6), Inches(0.7), Inches(0.5), + size=8, color=MUTED, align=PP_ALIGN.CENTER) + add_text(s, "→ HTTP 내부", Inches(6.9), Inches(1.6), Inches(0.7), Inches(0.5), + size=8, color=MUTED, align=PP_ALIGN.CENTER) + + # 포트 구성 테이블 + add_table_slide(s, + ["포트", "서비스", "외부 접근"], + [ + ["80/443", "홈페이지 Nginx (HTTPS)", "허용"], + ["8001", "GUARDiA FastAPI (직접)", "권장 안 함"], + ["8443", "GUARDiA Nginx (HTTPS, 권장)", "허용"], + ["5432", "PostgreSQL", "차단"], + ["11434", "Ollama LLM", "차단"], + ], + Inches(0.4), Inches(4.3), Inches(7.5), Inches(2.7), + col_widths=[Inches(1.5), Inches(3.5), Inches(2.5)] + ) + + # ── 슬라이드 5: 외부 API ──────────────────────────────────── + s = blank_slide() + add_rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + add_text(s, "5. 외부 API 엔드포인트", Inches(0.4), Inches(0.25), + Inches(12), Inches(0.7), size=24, bold=True, color=WHITE) + + add_table_slide(s, + ["엔드포인트", "메서드", "인증", "설명"], + [ + ["/api/external/health", "GET", "없음", "헬스체크 (공개)"], + ["/api/external/status", "GET", "없음", "시스템 공개 상태"], + ["/api/external/keys", "POST", "JWT (관리자)", "API Key 발급"], + ["/api/external/sr", "GET", "API Key (read)", "SR 목록 조회"], + ["/api/external/sr", "POST", "API Key (write)", "SR 등록"], + ["/api/external/webhook", "POST", "HMAC (선택)", "외부 메신저 웹훅 수신"], + ], + Inches(0.4), Inches(1.4), Inches(12.5), Inches(3.2), + col_widths=[Inches(3.5), Inches(1.5), Inches(2.5), Inches(5.0)] + ) + + add_text(s, "API Key 권한 스코프:", Inches(0.4), Inches(4.9), + Inches(4), Inches(0.4), size=12, bold=True, color=BRAND) + scopes = [("read", "조회", GREEN), ("write", "등록/수정", ACCENT), + ("webhook", "웹훅", ORANGE), ("admin", "전체", RED)] + for i, (sc, desc, col) in enumerate(scopes): + bx = Inches(0.4 + i * 3.1) + add_rect(s, bx, Inches(5.4), Inches(2.8), Inches(0.8), fill=col) + add_text(s, f"{sc}\n{desc}", bx + Inches(0.1), Inches(5.45), + Inches(2.6), Inches(0.7), size=11, bold=True, color=WHITE, align=PP_ALIGN.CENTER) + + # ── 슬라이드 6: 테스트 결과 ───────────────────────────────── + s = blank_slide() + add_rect(s, 0, 0, W, Inches(1.2), fill=BRAND) + add_text(s, "7. 테스트 결과 — 10/10 PASS ✅", Inches(0.4), Inches(0.25), + Inches(12), Inches(0.7), size=24, bold=True, color=WHITE) + + add_table_slide(s, + ["#", "테스트 항목", "기대값", "실제값", "결과"], + [ + ["T1", "HTTP 헬스체크 (8001)", "200 OK", "200 OK", "PASS"], + ["T2", "HTTPS 헬스체크 (8443)", "200 OK", "200 OK", "PASS"], + ["T3", "홈페이지 HTTPS (443)", "200 OK", "200 OK", "PASS"], + ["T4", "미인증 API 접근 차단", "401", "401", "PASS"], + ["T5", "CORS 외부 출처 허용", "Allow-Origin", "헤더 포함", "PASS"], + ["T6", "HSTS 헤더 적용", "max-age=31536000", "적용됨", "PASS"], + ["T7", "X-Frame-Options DENY", "DENY", "DENY", "PASS"], + ["T8", "Rate Limiting 설정", "zone 확인", "1개 zone", "PASS"], + ["T9", "공개 시스템 상태", "operational", "operational", "PASS"], + ["T10", "개방망 모드 활성", "NETWORK_MODE=open", "open", "PASS"], + ], + Inches(0.4), Inches(1.4), Inches(12.5), Inches(4.5), + col_widths=[Inches(0.8), Inches(3.8), Inches(2.5), Inches(2.5), Inches(1.5)] + ) + + add_rect(s, Inches(0.4), Inches(6.2), Inches(12.5), Inches(0.8), fill=GREEN) + add_text(s, "✅ 전체 10개 테스트 모두 통과 (10/10 PASS) — 2026-05-30", + Inches(0.6), Inches(6.3), Inches(12.1), Inches(0.6), + size=14, bold=True, color=WHITE, align=PP_ALIGN.CENTER) + + # ── 슬라이드 7: 마지막 ────────────────────────────────────── + s = blank_slide() + add_rect(s, 0, 0, W, H, fill=BRAND) + add_text(s, "GUARDiA ITSM 개방망 지원 완료", Inches(1), Inches(2.0), + Inches(11.33), Inches(1.2), size=32, bold=True, color=WHITE, align=PP_ALIGN.CENTER) + add_text(s, "HTTP → HTTPS 전환 | API Key 인증 | CORS 외부 허용 | 보안 헤더 강화", + Inches(1), Inches(3.3), Inches(11.33), Inches(0.6), + size=14, color=RGBColor(0xaa, 0xc4, 0xe8), align=PP_ALIGN.CENTER) + add_text(s, "(주)지오정보기술 | GUARDiA v2.0.0 | 2026-05-30", + Inches(1), Inches(5.5), Inches(11.33), Inches(0.5), + size=12, color=MUTED, align=PP_ALIGN.CENTER) + + prs.save(output_path) + print(f"PPTX 생성 완료: {output_path}") + + +if __name__ == "__main__": + pdf_out = str(OUT_DIR / "23_GUARDiA_개방망_가이드.pdf") + pptx_out = str(OUT_DIR / "24_GUARDiA_개방망_발표자료.pptx") + gen_pdf(pdf_out) + gen_pptx(pptx_out) + print("\n=== 생성 완료 ===") + print(f"PDF : {pdf_out}") + print(f"PPTX: {pptx_out}")