Compare commits
10 Commits
d99be57814
...
c256fface1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c256fface1 | ||
|
|
e25eefbb52 | ||
|
|
820820ae1c | ||
|
|
6c9796d7c3 | ||
|
|
e2ea2016d2 | ||
|
|
ac0329d852 | ||
|
|
57addb7177 | ||
|
|
a4aea36716 | ||
|
|
2bd7d876cc | ||
|
|
0b35ab54eb |
@ -1,6 +1,6 @@
|
||||
# GUARDiA ITSM — 전체 기능 목록 및 API 명세서
|
||||
|
||||
> **버전:** 2.1.0 | **총 라우트:** 617개 | **기준일:** 2026-05-31
|
||||
> **버전:** 2.2.0 | **총 라우트:** 617개 | **기준일:** 2026-05-31 | Manager/Messenger UI 연동 완료
|
||||
> **Base URL:** `http://localhost:8001`
|
||||
> **인증:** JWT Bearer Token (`POST /api/auth/login` → `access_token`)
|
||||
|
||||
|
||||
248
18_zio서버_설치SW_목록.md
Normal file
248
18_zio서버_설치SW_목록.md
Normal file
@ -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)
|
||||
```
|
||||
732
19_zio서버_운영가이드.md
Normal file
732
19_zio서버_운영가이드.md
Normal file
@ -0,0 +1,732 @@
|
||||
# zio-server 운영 가이드
|
||||
|
||||
> **서버**: zio-server | **IP**: zioinfo.co.kr
|
||||
> **작성일**: 2026-05-30 | **대상**: 서버 운영자 / 개발자
|
||||
|
||||
---
|
||||
|
||||
## 1. 서비스 접속 주소
|
||||
|
||||
| 서비스 | URL | 계정 |
|
||||
|--------|-----|------|
|
||||
| 지오정보기술 홈페이지 | **https://zioinfo.co.kr** | — |
|
||||
| 홈페이지 관리자 | https://zioinfo.co.kr/admin | admin / Admin@2026! |
|
||||
| GUARDiA ITSM | **https://zioinfo.co.kr:8001** | admin / 1111 |
|
||||
| GUARDiA Manager | **https://zioinfo.co.kr:8090** | admin / Admin@zioinfo2026! |
|
||||
| GUARDiA 개방망 | **https://zioinfo.co.kr:8443** | API Key 인증 |
|
||||
| Gitea (Git 서버) | **https://zioinfo.co.kr:3000** | zio / Zio@Admin2026! |
|
||||
| Jenkins (CI/CD) | **https://zioinfo.co.kr:8080** | admin / `c753461ad51f4b85901e90bff6612f84` | 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=<JWT 시크릿>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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. 브라우저에서 `https://zioinfo.co.kr:8080` 접속
|
||||
2. 초기 비밀번호 입력 (아래 값 복사):
|
||||
```
|
||||
c753461ad51f4b85901e90bff6612f84
|
||||
```
|
||||
서버에서 재확인:
|
||||
```bash
|
||||
cat /var/lib/jenkins/secrets/initialAdminPassword
|
||||
```
|
||||
3. **"Install suggested plugins"** 선택
|
||||
4. 추가 플러그인 설치:
|
||||
- **Pipeline** (워크플로우 파이프라인)
|
||||
- **Git** (Git 연동)
|
||||
- **Gitea** (Gitea 웹훅 연동)
|
||||
5. 관리자 계정 생성: `admin / Admin@2026!`
|
||||
6. Jenkins URL 설정: `https://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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 방화벽 (ufw + NCloud ACG) — 2026-05-31 정리 완료
|
||||
|
||||
### 현재 ufw 규칙 (12개)
|
||||
|
||||
| # | 포트 | 서비스 | 목적 |
|
||||
|---|------|--------|------|
|
||||
| 1 | 22/tcp | SSH | 서버 관리 |
|
||||
| 2 | 80/tcp | Nginx | HTTP → HTTPS 자동 리다이렉트 |
|
||||
| 3 | 443/tcp | Nginx + Let's Encrypt | HTTPS 홈페이지 |
|
||||
| 4 | 8080/tcp | Jenkins | CI/CD 파이프라인 |
|
||||
| 5 | 8001/tcp | GUARDiA ITSM | ITSM API 서버 |
|
||||
| 6 | 3000/tcp | Gitea | Git 저장소 |
|
||||
| 7 | 9999/tcp | CI/CD Webhook | 자동 배포 수신 |
|
||||
| 8 | 8443/tcp | Nginx 개방망 | GUARDiA 개방망 HTTPS |
|
||||
| 9 | 8090/tcp | GUARDiA Manager | 관리자 포털 |
|
||||
| 10 | 25/tcp | Postfix | SMTP 수신 |
|
||||
| 11 | 587/tcp | Postfix | SMTP 인증 발신 |
|
||||
| 12 | 993/tcp | Dovecot | IMAPS |
|
||||
|
||||
### NCloud ACG 동일 규칙 적용 필요
|
||||
|
||||
NCloud Console → Server → Network ACG → 인바운드 규칙에 위 12개 포트(TCP, 0.0.0.0/0) 등록.
|
||||
|
||||
### HTTPS 전환 구조 (2026-05-31)
|
||||
|
||||
모든 서비스가 Let's Encrypt 인증서로 HTTPS 서빙. Nginx가 SSL 종료 후 내부 포트로 프록시.
|
||||
|
||||
| 공개 포트 | 프로토콜 | Nginx → 내부 포트 | 서비스 |
|
||||
|----------|---------|------------------|--------|
|
||||
| 80 | HTTP | → HTTPS 301 | 홈페이지 리다이렉트 |
|
||||
| 443 | HTTPS | 127.0.0.1:8082 | 지오정보기술 홈페이지 |
|
||||
| 8001 | HTTPS | 127.0.0.1:9001 | GUARDiA ITSM |
|
||||
| 8090 | HTTPS | 127.0.0.1:8002 + /var/www/manager | GUARDiA Manager |
|
||||
| 8443 | HTTPS | 127.0.0.1:9001 | GUARDiA 개방망 |
|
||||
| 3000 | HTTPS | 127.0.0.1:9003 | Gitea |
|
||||
| 8080 | HTTPS | 127.0.0.1:9080 | Jenkins |
|
||||
|
||||
### 내부 전용 포트 (외부 차단)
|
||||
|
||||
| 포트 | 서비스 | 비고 |
|
||||
|------|--------|------|
|
||||
| 5432 | PostgreSQL | 127.0.0.1 only |
|
||||
| 11434 | Ollama | 127.0.0.1 only |
|
||||
| 8002 | GUARDiA Manager Backend | 127.0.0.1 only |
|
||||
| 8082 | Spring Boot | Nginx 뒤 내부 |
|
||||
|
||||
### 제거된 규칙 (2026-05-31)
|
||||
|
||||
`8088, 9001, 8082, 8002, 995, 143, 110, 3001` — 중복·평문·미사용
|
||||
|
||||
### ufw 상태 확인
|
||||
|
||||
```bash
|
||||
ufw status numbered
|
||||
```
|
||||
|
||||
### SSL 인증서 (Let's Encrypt)
|
||||
|
||||
```bash
|
||||
# 인증서 목록 확인
|
||||
certbot certificates
|
||||
|
||||
# 갱신 테스트
|
||||
certbot renew --dry-run
|
||||
|
||||
# 수동 갱신
|
||||
certbot renew
|
||||
```
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 도메인 | zioinfo.co.kr |
|
||||
| 만료일 | 2026-08-29 |
|
||||
| 자동갱신 | certbot.timer (하루 2회) |
|
||||
|
||||
---
|
||||
|
||||
*문서 버전: 1.0 | 최종 수정: 2026-05-30*
|
||||
346
20_zio서버_CICD_가이드.md
Normal file
346
20_zio서버_CICD_가이드.md
Normal file
@ -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 <PID> # 기존 프로세스 강제 종료
|
||||
|
||||
# 수동 실행 (디버깅)
|
||||
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*
|
||||
409
21_zio서버_장애대응_가이드.md
Normal file
409
21_zio서버_장애대응_가이드.md
Normal file
@ -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*
|
||||
454
22_GUARDiA_개방망_운영가이드.md
Normal file
454
22_GUARDiA_개방망_운영가이드.md
Normal file
@ -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*
|
||||
BIN
23_GUARDiA_개방망_가이드.pdf
Normal file
BIN
23_GUARDiA_개방망_가이드.pdf
Normal file
Binary file not shown.
BIN
24_GUARDiA_개방망_발표자료.pptx
Normal file
BIN
24_GUARDiA_개방망_발표자료.pptx
Normal file
Binary file not shown.
296
25_GUARDiA_Manager_라이선스_관리_가이드.md
Normal file
296
25_GUARDiA_Manager_라이선스_관리_가이드.md
Normal file
@ -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*
|
||||
BIN
26_GUARDiA_Manager_라이선스_가이드.pdf
Normal file
BIN
26_GUARDiA_Manager_라이선스_가이드.pdf
Normal file
Binary file not shown.
BIN
27_GUARDiA_Manager_라이선스_발표자료.pptx
Normal file
BIN
27_GUARDiA_Manager_라이선스_발표자료.pptx
Normal file
Binary file not shown.
76
28_GUARDiA_폐쇄망_데이터연동_가이드.md
Normal file
76
28_GUARDiA_폐쇄망_데이터연동_가이드.md
Normal file
@ -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*
|
||||
BIN
29_GUARDiA_폐쇄망_데이터연동_가이드.pdf
Normal file
BIN
29_GUARDiA_폐쇄망_데이터연동_가이드.pdf
Normal file
Binary file not shown.
BIN
30_GUARDiA_폐쇄망_데이터연동_발표자료.pptx
Normal file
BIN
30_GUARDiA_폐쇄망_데이터연동_발표자료.pptx
Normal file
Binary file not shown.
134
31_AI_플랫폼_제안서.md
Normal file
134
31_AI_플랫폼_제안서.md
Normal file
@ -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*
|
||||
124
32_벡터DB_제안서.md
Normal file
124
32_벡터DB_제안서.md
Normal file
@ -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*
|
||||
277
33_GUARDiA_Messenger_앱_스토어_등록_가이드.md
Normal file
277
33_GUARDiA_Messenger_앱_스토어_등록_가이드.md
Normal file
@ -0,0 +1,277 @@
|
||||
# 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-2-1. Play Store 스크린샷
|
||||
|
||||
**생성된 스크린샷 위치:**
|
||||
```
|
||||
C:\GUARDiA\app\store_screenshots\
|
||||
├── play_01_login.png (1080×1920) — 로그인 화면
|
||||
├── play_02_dashboard.png (1080×1920) — 대시보드
|
||||
├── play_03_sr.png (1080×1920) — SR 관리
|
||||
└── play_04_chat.png (1080×1920) — AI 챗봇
|
||||
```
|
||||
|
||||
> ⚠️ Play Store 요구사항:
|
||||
> - 최소 2장, 최대 8장
|
||||
> - 크기: 320~3840px (16:9 또는 9:16)
|
||||
> - JPEG 또는 24비트 PNG (알파 없음)
|
||||
|
||||
**스크린샷 재생성:**
|
||||
```bash
|
||||
python C:\GUARDiA\capture_store_screenshots.py
|
||||
```
|
||||
|
||||
### 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인치: 선택
|
||||
|
||||
**생성된 스크린샷 위치:**
|
||||
```
|
||||
C:\GUARDiA\app\store_screenshots\
|
||||
├── ios67_01_login.png (1290×2796) — 로그인 화면
|
||||
├── ios67_02_dashboard.png (1290×2796) — 대시보드
|
||||
├── ios67_03_sr.png (1290×2796) — SR 관리
|
||||
└── ios67_04_chat.png (1290×2796) — AI 챗봇
|
||||
```
|
||||
|
||||
> ⚠️ 스크린샷은 실제 앱 화면과 일치해야 합니다. 앱 디자인 변경 시 `python C:\GUARDiA\capture_store_screenshots.py`로 재생성하세요.
|
||||
|
||||
**iPhone 6.5" 추가 생성 필요 시 (1242×2688):**
|
||||
```python
|
||||
# capture_store_screenshots.py 수정
|
||||
IOS_W, IOS_H = 1242, 2688 # 6.7" 대신 6.5"
|
||||
```
|
||||
|
||||
### 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*
|
||||
288
34_GUARDiA_Messenger_개발_배포_가이드.md
Normal file
288
34_GUARDiA_Messenger_개발_배포_가이드.md
Normal file
@ -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*
|
||||
BIN
35_GUARDiA_Messenger_개발가이드.pdf
Normal file
BIN
35_GUARDiA_Messenger_개발가이드.pdf
Normal file
Binary file not shown.
BIN
36_GUARDiA_Messenger_발표자료.pptx
Normal file
BIN
36_GUARDiA_Messenger_발표자료.pptx
Normal file
Binary file not shown.
206
37_세션_전체_작업_요약.md
Normal file
206
37_세션_전체_작업_요약.md
Normal file
@ -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*
|
||||
81
38_스킬_트리거_빠른참조.md
Normal file
81
38_스킬_트리거_빠른참조.md
Normal file
@ -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*
|
||||
@ -537,4 +537,31 @@ Authorization: Bearer {token}
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 5. Manager / Messenger UI 연동 현황 (2026-05-31 추가)
|
||||
|
||||
### GUARDiA Manager (포트 8090)
|
||||
|
||||
| 메뉴 경로 | URL | 기능 |
|
||||
|----------|-----|------|
|
||||
| 운영 관제 → DR 재해복구 | `/dr` | 시나리오 현황, RTO/RPO, 복구 테스트 실행 |
|
||||
| 운영 관제 → 네트워크 장비 | `/network` | 장비 목록, 백업 실행, 설정 diff |
|
||||
| 운영 관제 → CSAP 점검 | `/csap` | 준수율 대시보드, 점검 실행, Excel 다운로드 |
|
||||
|
||||
### GUARDiA Messenger (모바일 앱)
|
||||
|
||||
| 탭 | 화면 | 기능 |
|
||||
|----|------|------|
|
||||
| 🛡️ DR | `app/(tabs)/dr.tsx` | 시나리오 상태, RTO 실적, 복구 테스트 |
|
||||
| 🔀 네트워크 | `app/(tabs)/network.tsx` | 장비 목록, 백업 실행, 상태 확인 |
|
||||
|
||||
### 신규 hooks
|
||||
|
||||
| 파일 | 기능 |
|
||||
|------|------|
|
||||
| `hooks/useBiometric.ts` | 지문/Face ID 생체인증 |
|
||||
| `hooks/useOfflineCache.ts` | 오프라인 캐시 (SecureStore 기반) |
|
||||
|
||||
---
|
||||
*Copyright © 2026 GUARDiA All Rights Reserved.*
|
||||
|
||||
180
40_바이브코딩_운영가이드.md
Normal file
180
40_바이브코딩_운영가이드.md
Normal file
@ -0,0 +1,180 @@
|
||||
# GUARDiA 바이브 코딩(Vibe Coding) 운영 가이드
|
||||
|
||||
> 최종 업데이트: 2026-05-31
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
**바이브 코딩(VibeSession)**은 GUARDiA ITSM의 AI 보조 개발 세션 기능입니다.
|
||||
메신저 명령 한 줄로 SR(서비스 요청)에 연결된 코딩 세션을 시작하고, 빌드·배포까지 자동화합니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 폐쇄망 환경에서의 바이브 코딩
|
||||
|
||||
### 2-1. 폐쇄망 환경 정의
|
||||
|
||||
| 항목 | 설명 |
|
||||
|------|------|
|
||||
| 정의 | 인터넷 차단, 내부망 전용 운영 환경 |
|
||||
| 해당 고객 | 공공기관, 금융, 국방 등 보안 규정 적용 기관 |
|
||||
| LLM | Ollama 로컬 모델 (llama3:8b, codellama:7b) |
|
||||
| 소스 저장소 | 내부 Gitea (Git 호환 오픈소스) |
|
||||
| CI/CD | 내부 Jenkins |
|
||||
|
||||
### 2-2. 폐쇄망 바이브 코딩 흐름
|
||||
|
||||
```
|
||||
[메신저 채널]
|
||||
↓ !vibe SR-20260531-XXXX
|
||||
[GUARDiA ITSM - 내부망]
|
||||
↓ SR 조회 → VibeSession 생성
|
||||
↓ 워크스페이스 생성: /opt/guardia/workspaces/{session_id}/
|
||||
↓ 내부 Gitea에서 소스 클론 (git clone http://gitea.내부망/...)
|
||||
[AI 코딩 보조 - Ollama 로컬]
|
||||
↓ codellama:7b 코드 분석 (외부 API 불필요)
|
||||
↓ 변경 파일 목록 생성
|
||||
↓ !build {session_id}
|
||||
[Jenkins CI - 내부망]
|
||||
↓ 빌드 실행 (Maven/Gradle/npm)
|
||||
↓ 테스트 실행
|
||||
↓ !deploy {session_id}
|
||||
[SSH 배포 - paramiko]
|
||||
↓ 대상 서버에 SSH 접속 (에이전트리스)
|
||||
↓ 백업 → 배포 → 헬스체크 → 롤백(실패 시)
|
||||
[메신저 채널]
|
||||
← 배포 성공/실패 알림
|
||||
```
|
||||
|
||||
### 2-3. 폐쇄망 전제 조건
|
||||
|
||||
| 항목 | 설치 위치 | 확인 명령 |
|
||||
|------|----------|---------|
|
||||
| Ollama | GUARDiA 서버 로컬 | `curl http://localhost:11434/api/tags` |
|
||||
| codellama:7b | Ollama 모델 | `ollama list` |
|
||||
| Gitea | 내부 서버 (3000번 포트) | `curl http://내부IP:3000` |
|
||||
| Jenkins | 내부 서버 (8080번 포트) | `curl http://내부IP:8080` |
|
||||
| SSH opsagent | 각 대상 서버 계정 | `ssh opsagent@대상IP` |
|
||||
|
||||
### 2-4. 폐쇄망 설정 (ITSM .env)
|
||||
|
||||
```bash
|
||||
# 폐쇄망 환경 필수 설정
|
||||
OLLAMA_BASE_URL=http://127.0.0.1:11434 # 로컬 Ollama
|
||||
GITEA_URL=http://내부Gitea서버:3000 # 내부 Git 서버
|
||||
JENKINS_URL=http://내부Jenkins서버:8080 # 내부 CI/CD
|
||||
JENKINS_USER=admin
|
||||
JENKINS_TOKEN=<Jenkins API Token>
|
||||
EXTERNAL_API_ALLOWED=false # 외부 API 차단
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 개방망 환경에서의 바이브 코딩
|
||||
|
||||
### 3-1. 개방망 환경 정의
|
||||
|
||||
| 항목 | 설명 |
|
||||
|------|------|
|
||||
| 정의 | 인터넷 접근 가능, 클라우드 연동 허용 환경 |
|
||||
| 해당 고객 | 민간 기업, SaaS 서비스, 개발 환경 |
|
||||
| LLM | Ollama 로컬 (온프레미스 원칙) 또는 대체 모델 |
|
||||
| 소스 저장소 | GitHub, GitLab, 내부 Gitea 선택 |
|
||||
| CI/CD | Jenkins, GitHub Actions, GitLab CI 선택 |
|
||||
|
||||
### 3-2. 개방망 바이브 코딩 흐름
|
||||
|
||||
```
|
||||
[메신저 채널]
|
||||
↓ !vibe SR-20260531-XXXX
|
||||
[GUARDiA ITSM - 개방망 포트]
|
||||
↓ SR 조회 → VibeSession 생성
|
||||
↓ 워크스페이스 생성
|
||||
↓ GitHub/GitLab에서 소스 클론 (HTTPS)
|
||||
[AI 코딩 보조 - Ollama 로컬 (원칙)]
|
||||
↓ 코드 분석 + 수정 제안
|
||||
[CI/CD - GitHub Actions / Jenkins]
|
||||
↓ 빌드 → 테스트 → 배포
|
||||
[외부 알림]
|
||||
← Slack/카카오워크/Teams로 결과 전송
|
||||
```
|
||||
|
||||
### 3-3. 개방망 설정 (ITSM .env)
|
||||
|
||||
```bash
|
||||
# 개방망 환경 설정 (GUARDiA OpenNet 사용)
|
||||
GUARDIA_OPENNET=true
|
||||
OPENNET_API_KEY=goa-xxxx # API 키 (헤더 X-API-Key)
|
||||
OPENNET_BASE_URL=https://zioinfo.co.kr # GUARDiA 개방망 주소
|
||||
GITEA_URL=https://github.com # 또는 내부 Gitea
|
||||
EXTERNAL_API_ALLOWED=false # 외부 LLM API는 여전히 차단
|
||||
```
|
||||
|
||||
### 3-4. 개방망 vs 폐쇄망 차이점
|
||||
|
||||
| 항목 | 폐쇄망 | 개방망 |
|
||||
|------|--------|--------|
|
||||
| LLM | localhost:11434 (Ollama 필수) | localhost:11434 (Ollama 권장) |
|
||||
| Git 서버 | 내부 Gitea 전용 | GitHub/GitLab/Gitea 모두 가능 |
|
||||
| CI/CD | 내부 Jenkins 전용 | Jenkins, GitHub Actions 가능 |
|
||||
| 메신저 | 내부 메신저 또는 GUARDiA Messenger | 카카오워크, Slack, Teams 연동 가능 |
|
||||
| 배포 대상 | 내부 서버만 | 내부 + 클라우드(AWS/NCloud) 가능 |
|
||||
| 외부 API | 완전 차단 | 내부 LLM만 허용 (외부 LLM 불가) |
|
||||
|
||||
---
|
||||
|
||||
## 4. 바이브 코딩 메신저 명령어
|
||||
|
||||
```
|
||||
!vibe <SR-ID> [project_id] → 바이브 세션 시작
|
||||
!build <session_id> → 빌드 실행
|
||||
!deploy <session_id> → 배포 실행
|
||||
!status <SR-ID> → 세션 상태 조회
|
||||
!cancel <session_id> → 세션 취소
|
||||
/rollback <session_id> → 긴급 롤백
|
||||
```
|
||||
|
||||
### 4-1. 세션 상태 흐름
|
||||
|
||||
```
|
||||
CREATED → CODING → BUILT → DEPLOYED
|
||||
↘ FAILED (빌드 실패 → 자동 롤백 없음)
|
||||
↘ FAILED_ROLLBACK (배포 실패 → 자동 롤백)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 워크스페이스 구조
|
||||
|
||||
```
|
||||
/opt/guardia/workspaces/{session_id}/
|
||||
├── source/ # Git 클론된 소스
|
||||
├── build/ # 빌드 결과물
|
||||
├── backup/ # 배포 전 백업 (롤백용)
|
||||
└── logs/ # 세션 로그
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 보안 원칙
|
||||
|
||||
| 원칙 | 설명 |
|
||||
|------|------|
|
||||
| 에이전트리스 | 대상 서버에 어떤 소프트웨어도 설치 안 함 |
|
||||
| 최소 권한 | opsagent 계정 사용, root SSH 금지 |
|
||||
| 자격증명 암호화 | AES-256-GCM, API 응답에 노출 절대 금지 |
|
||||
| 외부 API 차단 | 폐쇄망/개방망 모두 외부 LLM API 사용 금지 |
|
||||
| 감사 추적 | 모든 명령·배포는 tb_audit_log에 기록 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 자주 발생하는 오류와 해결 방법
|
||||
|
||||
| 오류 | 원인 | 해결 |
|
||||
|------|------|------|
|
||||
| `Ollama 연결 실패` | Ollama 미설치 또는 중단 | `systemctl restart ollama` |
|
||||
| `Git 클론 실패 (폐쇄망)` | Gitea URL 설정 오류 | `.env`의 `GITEA_URL` 확인 |
|
||||
| `SSH 접속 거부` | opsagent 계정 미생성 또는 키 문제 | 대상 서버에서 `useradd opsagent` |
|
||||
| `빌드 실패` | 의존성 문제 | 빌드 로그 확인: `!status <session_id>` |
|
||||
| `배포 후 헬스체크 실패` | 서비스 기동 지연 | 자동 롤백 → 수동 확인 필요 |
|
||||
229
41_zio서버_DB_연결정보.md
Normal file
229
41_zio서버_DB_연결정보.md
Normal file
@ -0,0 +1,229 @@
|
||||
# zio 서버 DB 및 시스템 연결 정보
|
||||
|
||||
> **서버**: 101.79.17.164 (zioinfo.co.kr)
|
||||
> **OS**: Ubuntu 24.04 LTS
|
||||
> **최종 업데이트**: 2026-05-31
|
||||
|
||||
> ⚠️ **보안 주의**: 이 파일은 내부 운영용입니다. 외부 공개 금지.
|
||||
|
||||
---
|
||||
|
||||
## 1. PostgreSQL 16
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 호스트 | localhost (127.0.0.1) |
|
||||
| 포트 | 5432 |
|
||||
| 버전 | 16.11 |
|
||||
|
||||
### 데이터베이스 목록
|
||||
|
||||
| DB명 | 사용자 | 비밀번호 | 용도 |
|
||||
|------|--------|---------|------|
|
||||
| `guardia_db` | `guardia` | `G@urd1a_2026!` | GUARDiA ITSM 메인 DB |
|
||||
| `gitea_db` | `gitea` | (Gitea 내부 관리) | Gitea 저장소 메타데이터 |
|
||||
| `zioinfo_db` | `postgres` | (시스템 관리) | 홈페이지 (Spring Boot) |
|
||||
|
||||
### pgvector 확장
|
||||
```sql
|
||||
-- guardia_db에 설치됨
|
||||
SELECT extname, extversion FROM pg_extension WHERE extname='vector';
|
||||
-- vector | 0.6.0
|
||||
|
||||
-- 벡터 테이블 목록
|
||||
\dt tb_vector_*
|
||||
-- tb_vector_sr, tb_vector_kb, tb_vector_scrap
|
||||
```
|
||||
|
||||
### 연결 방법
|
||||
```bash
|
||||
# psql 직접 접속
|
||||
psql -h localhost -U guardia -d guardia_db
|
||||
# 비밀번호: G@urd1a_2026!
|
||||
|
||||
# Python SQLAlchemy (GUARDiA ITSM)
|
||||
DATABASE_URL=postgresql+asyncpg://guardia:G%40urd1a_2026%21@localhost:5432/guardia_db
|
||||
# 주의: @ 문자는 URL 인코딩 시 %40 사용
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Gitea Git 서버
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| URL | http://101.79.17.164:3000 |
|
||||
| HTTPS | https://zioinfo.co.kr:3000 (도메인) |
|
||||
| 포트 | 3000 |
|
||||
|
||||
### 계정 정보
|
||||
|
||||
| 계정 | 비밀번호 | 권한 |
|
||||
|------|---------|------|
|
||||
| `zio` | `Zio@Admin2026!` | 관리자 |
|
||||
|
||||
### 저장소 목록
|
||||
|
||||
| 저장소 | URL | 용도 |
|
||||
|--------|-----|------|
|
||||
| `zio/zioinfo-web` | http://localhost:3000/zio/zioinfo-web | 지오정보기술 홈페이지 |
|
||||
| `zio/guardia-itsm` | http://localhost:3000/zio/guardia-itsm | GUARDiA ITSM |
|
||||
|
||||
### Git 원격 연결
|
||||
```bash
|
||||
# 인증 포함 URL 형식
|
||||
https://zio:Zio%40Admin2026%21@zioinfo.co.kr:3000/zio/zioinfo-web.git
|
||||
http://zio:Zio%40Admin2026%21@101.79.17.164:3000/zio/guardia-itsm.git
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Ollama (로컬 LLM)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| URL | http://localhost:11434 |
|
||||
| 포트 | 11434 |
|
||||
| 바인딩 | 127.0.0.1 (외부 접근 불가) |
|
||||
|
||||
### 설치된 모델
|
||||
```bash
|
||||
ollama list
|
||||
# NAME ID SIZE
|
||||
# llama3:8b ... 4.7 GB
|
||||
# codellama:7b ... 3.8 GB
|
||||
# nomic-embed-text ... 274 MB
|
||||
```
|
||||
|
||||
### API 사용
|
||||
```bash
|
||||
# 텍스트 생성
|
||||
curl http://localhost:11434/api/generate \
|
||||
-d '{"model":"llama3","prompt":"안녕","stream":false}'
|
||||
|
||||
# 임베딩 (pgvector 연동)
|
||||
curl http://localhost:11434/api/embeddings \
|
||||
-d '{"model":"nomic-embed-text","prompt":"텍스트"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. ChromaDB (벡터 DB)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 경로 | `/opt/guardia/chroma/` |
|
||||
| 모드 | 파일 기반 (서버리스) |
|
||||
| 연동 | Python `chromadb` 라이브러리 |
|
||||
|
||||
```python
|
||||
import chromadb
|
||||
client = chromadb.PersistentClient(path="/opt/guardia/chroma")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 웹 서비스 포트 및 로그인
|
||||
|
||||
| 서비스 | 내부 포트 | 외부 URL | 계정 |
|
||||
|--------|----------|---------|------|
|
||||
| 홈페이지 | 8082 | https://zioinfo.co.kr | - |
|
||||
| GUARDiA ITSM | 9001 | https://zioinfo.co.kr:8443 | admin / Admin@2026! |
|
||||
| GUARDiA Manager | 8002 | https://zioinfo.co.kr:8090 | admin / Admin@zioinfo2026! |
|
||||
| Gitea | 3000 | https://zioinfo.co.kr:3000 | zio / Zio@Admin2026! |
|
||||
| Jenkins | 8080 | http://zioinfo.co.kr:8080 | admin / (초기키 확인) |
|
||||
| Nginx (HTTP) | 80 | http://zioinfo.co.kr | - |
|
||||
| Nginx (HTTPS) | 443/8443 | https://zioinfo.co.kr | - |
|
||||
|
||||
---
|
||||
|
||||
## 6. Deploy Webhook (자동 배포)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 포트 | 9999 |
|
||||
| 시크릿 | `zioinfo-deploy-2026` |
|
||||
| 로그 | `/var/log/zioinfo/deploy.log` |
|
||||
|
||||
### 배포 흐름
|
||||
```
|
||||
git push gitea → Gitea Webhook → http://localhost:9999
|
||||
→ zioinfo-web: git pull → npm install → npm run build → mvn package → restart
|
||||
→ guardia-itsm: git pull → pip install → rsync → restart guardia
|
||||
```
|
||||
|
||||
### 수동 배포 트리거
|
||||
```bash
|
||||
curl -X POST http://localhost:9999 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"repository":{"name":"zioinfo-web"}}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. SSH 서버 접속
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 호스트 | 101.79.17.164 |
|
||||
| 포트 | 22 |
|
||||
| root 계정 | root / 1q2w3e!Q |
|
||||
| 키 파일 | `ssh -i "zio-server-key.pem" root@zioinfo.co.kr` |
|
||||
|
||||
### 주요 서비스 경로
|
||||
```bash
|
||||
/opt/zioinfo/ # 홈페이지 (Spring Boot)
|
||||
├── app/app.jar # 실행 JAR
|
||||
└── src/ # 소스 코드
|
||||
|
||||
/opt/guardia/ # GUARDiA ITSM
|
||||
├── app/ # FastAPI 소스
|
||||
├── venv/ # Python 가상환경
|
||||
└── chroma/ # ChromaDB 데이터
|
||||
|
||||
/opt/manager/ # GUARDiA Manager
|
||||
├── backend/ # FastAPI 백엔드
|
||||
└── frontend/ # React 소스
|
||||
|
||||
/var/www/
|
||||
├── zioinfo/ # 홈페이지 정적 파일
|
||||
└── manager/ # Manager 정적 파일
|
||||
|
||||
/var/log/zioinfo/ # 배포·서비스 로그
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. SMTP (이메일)
|
||||
|
||||
| 항목 | 값 |
|
||||
|------|-----|
|
||||
| 서버 | 101.79.17.164 |
|
||||
| SMTP 포트 | 25 / 587 |
|
||||
| IMAP 포트 | 143 / 993 |
|
||||
| 도메인 | zioinfo.co.kr |
|
||||
|
||||
| 계정 | 비밀번호 | 용도 |
|
||||
|------|---------|------|
|
||||
| info@zioinfo.co.kr | 1q2w3e!Q | 일반 발신 |
|
||||
| admin@zioinfo.co.kr | 1q2w3e!Q | 시스템 알림 |
|
||||
| ythong@zioinfo.co.kr | 1q2w3e!Q | 담당자 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 서비스 관리 명령어
|
||||
|
||||
```bash
|
||||
# 서비스 상태 확인
|
||||
systemctl status zioinfo guardia guardia-manager gitea zioinfo-deploy
|
||||
|
||||
# 재시작
|
||||
systemctl restart zioinfo # 홈페이지
|
||||
systemctl restart guardia # ITSM
|
||||
systemctl restart guardia-manager # Manager
|
||||
|
||||
# 로그 확인
|
||||
tail -f /var/log/zioinfo/spring.log # 홈페이지
|
||||
journalctl -u guardia -f # ITSM
|
||||
tail -f /var/log/zioinfo/deploy.log # 배포 로그
|
||||
```
|
||||
212
42_zio서버_소프트웨어_구성도.md
Normal file
212
42_zio서버_소프트웨어_구성도.md
Normal file
@ -0,0 +1,212 @@
|
||||
# zio 서버 소프트웨어 구성도
|
||||
|
||||
> **서버**: 101.79.17.164 (zioinfo.co.kr)
|
||||
> **OS**: Ubuntu 24.04 LTS | RAM: 7.8GB | Disk: 99GB
|
||||
> **최종 업데이트**: 2026-05-31
|
||||
|
||||
---
|
||||
|
||||
## 전체 아키텍처
|
||||
|
||||
```
|
||||
인터넷
|
||||
│
|
||||
├─ :80/:443 ─── Nginx 1.24 (리버스 프록시 + SSL)
|
||||
│ │
|
||||
│ ├─ / → :8082 홈페이지 (Spring Boot)
|
||||
│ ├─ :8443 → :9001 GUARDiA ITSM (FastAPI) [HTTPS]
|
||||
│ ├─ :8001 → :8082 홈페이지 API [HTTP]
|
||||
│ ├─ :8090 → :8002 GUARDiA Manager (FastAPI)
|
||||
│ └─ :3000 → :3000 Gitea
|
||||
│
|
||||
└─ :22 ─── SSH (root 접속)
|
||||
|
||||
내부 서비스
|
||||
├─ :9001 GUARDiA ITSM (uvicorn, 127.0.0.1)
|
||||
├─ :8002 GUARDiA Manager (uvicorn, 127.0.0.1)
|
||||
├─ :8082 홈페이지 (Spring Boot JAR)
|
||||
├─ :5432 PostgreSQL 16
|
||||
├─ :11434 Ollama (LLM 서버)
|
||||
├─ :9999 Deploy Webhook (자동 배포)
|
||||
├─ :3000 Gitea (Git 서버)
|
||||
└─ :8080 Jenkins (CI/CD)
|
||||
|
||||
메일 서버
|
||||
├─ :25/:587 Postfix (SMTP)
|
||||
└─ :143/:993 Dovecot (IMAP)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 설치 소프트웨어 전체 목록
|
||||
|
||||
| 분류 | SW | 버전 | 포트 | 경로 | 용도 |
|
||||
|------|-----|------|------|------|------|
|
||||
| **웹서버** | Nginx | 1.24 | 80/443/8001/8090/8443 | `/etc/nginx/` | 리버스 프록시, SSL |
|
||||
| **홈페이지** | Spring Boot | 3.2.5 | 8082 | `/opt/zioinfo/app/app.jar` | 지오정보기술 홈페이지 |
|
||||
| **ITSM** | GUARDiA ITSM | v2.0 | 9001 | `/opt/guardia/app/` | FastAPI ITSM 플랫폼 |
|
||||
| **Manager** | GUARDiA Manager | v1.0 | 8002 | `/opt/manager/` | 관리자 포털 |
|
||||
| **DB** | PostgreSQL | 16.11 | 5432 | `/var/lib/postgresql/` | 메인 RDBMS |
|
||||
| **벡터DB** | pgvector | 0.6.0 | 5432 (확장) | PostgreSQL 확장 | 벡터 검색 |
|
||||
| **벡터DB** | ChromaDB | 1.5.9 | - (파일) | `/opt/guardia/chroma/` | RAG·임베딩 저장 |
|
||||
| **AI/LLM** | Ollama | 0.24 | 11434 | `/usr/local/bin/ollama` | 온프레미스 LLM |
|
||||
| **AI 모델** | llama3:8b | - | - | Ollama 관리 | 텍스트 생성 |
|
||||
| **AI 모델** | codellama:7b | - | - | Ollama 관리 | 코드 생성·리뷰 |
|
||||
| **AI 모델** | nomic-embed-text | - | - | Ollama 관리 | 768차원 임베딩 |
|
||||
| **AI 프레임워크** | LangChain | 1.3.2 | - | Python 패키지 | RAG 파이프라인 |
|
||||
| **형상관리** | Gitea | 1.22.3 | 3000 | `/opt/gitea/` | Git 서버 |
|
||||
| **CI/CD** | Jenkins | 2.555 | 8080 | `/var/lib/jenkins/` | 빌드·배포 자동화 |
|
||||
| **배포** | Deploy Webhook | - | 9999 | `/opt/zioinfo/deploy_server.py` | Gitea→자동배포 |
|
||||
| **메일** | Postfix | 3.8.6 | 25/587 | `/etc/postfix/` | SMTP 발신 |
|
||||
| **메일** | Dovecot | 2.3.21 | 143/993 | `/etc/dovecot/` | IMAP 수신 |
|
||||
| **Python** | Python | 3.11 | - | `/opt/guardia/venv/` | ITSM·Manager 런타임 |
|
||||
| **Java** | OpenJDK | 17 | - | `/usr/lib/jvm/` | Spring Boot 런타임 |
|
||||
| **Node.js** | Node.js | 20 | - | `/usr/bin/node` | 빌드 도구 |
|
||||
| **SSL** | Let's Encrypt | - | - | `/etc/letsencrypt/` | HTTPS 인증서 |
|
||||
|
||||
---
|
||||
|
||||
## GUARDiA ITSM 상세 구성
|
||||
|
||||
```
|
||||
/opt/guardia/app/
|
||||
├── main.py ← FastAPI 진입점 (포트 9001)
|
||||
├── models.py ← ORM 모델 (50+ 테이블)
|
||||
├── database.py ← DB 연결
|
||||
├── core/
|
||||
│ ├── auth.py ← JWT 인증
|
||||
│ ├── rpa_engine.py ← RPA 봇 엔진
|
||||
│ ├── scraping_engine.py ← 스크랩핑 엔진
|
||||
│ ├── nl_command.py ← 자연어 명령 파서
|
||||
│ ├── scheduler.py ← APScheduler 크론
|
||||
│ └── ...
|
||||
├── routers/ ← 80개+ API 라우터
|
||||
│ ├── rpa.py ← RPA 봇
|
||||
│ ├── scraping.py ← 스크랩핑
|
||||
│ ├── messenger.py ← 봇 명령어 (45개+)
|
||||
│ └── ...
|
||||
├── static/ ← HTML5 프론트엔드
|
||||
│ ├── index.html
|
||||
│ ├── app.js
|
||||
│ └── style.css
|
||||
└── rpa_rules.json ← Validation 학습 규칙 (영속)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Python 가상환경 패키지 (주요)
|
||||
|
||||
```bash
|
||||
# /opt/guardia/venv/bin/pip list | grep -E "fastapi|sqlalchemy|paramiko|langchain|chroma|pgvector|httpx|playwright"
|
||||
|
||||
fastapi 0.115.x
|
||||
sqlalchemy 2.0.x
|
||||
alembic 1.13.x
|
||||
paramiko 3.x
|
||||
httpx 0.27.0
|
||||
uvicorn 0.29.x
|
||||
langchain 1.3.2
|
||||
langchain-core 0.3.x
|
||||
chromadb 1.5.9
|
||||
pgvector 0.3.x
|
||||
psycopg2-binary 2.9.x
|
||||
beautifulsoup4 4.12.x
|
||||
lxml 6.1.1
|
||||
apscheduler 3.10.x
|
||||
python-jose 3.3.x
|
||||
passlib 1.7.x
|
||||
pyotp 2.9.x
|
||||
pandas 2.2.x
|
||||
openpyxl 3.1.x
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 홈페이지 (Spring Boot) 구조
|
||||
|
||||
```
|
||||
/opt/zioinfo/
|
||||
├── app/app.jar ← 실행 JAR
|
||||
└── src/ ← 소스 (Gitea에서 pull)
|
||||
├── frontend/ ← React 18 + Vite
|
||||
│ ├── src/pages/ ← 페이지 컴포넌트
|
||||
│ ├── src/styles/tokens.css ← Variant 디자인 토큰
|
||||
│ └── public/
|
||||
│ ├── screenshots/ ← 스크린샷 이미지
|
||||
│ ├── zioinfo-logo.png ← 로고 (라이트)
|
||||
│ ├── zioinfo-logo-dark.png ← 로고 (다크)
|
||||
│ └── CI.jpg ← CI 심볼
|
||||
└── backend/ ← Spring Boot 3.2.5
|
||||
└── src/main/java/kr/co/zioinfo/web/
|
||||
├── model/ ← JPA 엔티티
|
||||
│ ├── CompanyHistory.java ← 연혁 DB
|
||||
│ └── ...
|
||||
├── repository/ ← JPA 레포지토리
|
||||
├── controller/ ← REST API
|
||||
└── config/
|
||||
└── DataInitializer.java ← 초기 데이터
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gitea 저장소 구성
|
||||
|
||||
```
|
||||
http://localhost:3000/zio/
|
||||
├── zioinfo-web ← 홈페이지 (frontend + backend)
|
||||
└── guardia-itsm ← GUARDiA ITSM
|
||||
```
|
||||
|
||||
### Webhook 설정
|
||||
- `zioinfo-web` → http://localhost:9999 (SECRET: `zioinfo-deploy-2026`)
|
||||
- `guardia-itsm` → http://localhost:9999
|
||||
|
||||
---
|
||||
|
||||
## systemd 서비스 파일
|
||||
|
||||
| 서비스명 | 파일 위치 | 시작 명령 |
|
||||
|---------|----------|---------|
|
||||
| `zioinfo` | `/etc/systemd/system/zioinfo.service` | `java -jar /opt/zioinfo/app/app.jar --server.port=8082` |
|
||||
| `guardia` | `/etc/systemd/system/guardia.service` | `uvicorn main:app --host 127.0.0.1 --port 9001` |
|
||||
| `guardia-manager` | `/etc/systemd/system/guardia-manager.service` | `uvicorn main:app --host 127.0.0.1 --port 8002` |
|
||||
| `zioinfo-deploy` | `/etc/systemd/system/zioinfo-deploy.service` | `python3 /opt/zioinfo/deploy_server.py` |
|
||||
| `gitea` | `/etc/systemd/system/gitea.service` | Gitea 공식 |
|
||||
| `jenkins` | `/etc/systemd/system/jenkins.service` | Jenkins 공식 |
|
||||
| `postgresql` | `/lib/systemd/system/postgresql.service` | PG 공식 |
|
||||
| `ollama` | `/etc/systemd/system/ollama.service` | `ollama serve` |
|
||||
| `postfix` | 시스템 기본 | Postfix 공식 |
|
||||
| `dovecot` | 시스템 기본 | Dovecot 공식 |
|
||||
|
||||
---
|
||||
|
||||
## Nginx 가상 호스트 구성
|
||||
|
||||
```
|
||||
/etc/nginx/sites-enabled/zioinfo
|
||||
├── :80 → 301 redirect to HTTPS
|
||||
├── :443 → SSL + /api/ → :8082 (Spring Boot)
|
||||
│ → / → /var/www/zioinfo/ (정적 파일)
|
||||
├── :8001 → /api/ → :8082
|
||||
├── :8443 → SSL + / → :9001 (ITSM FastAPI)
|
||||
│ → /api/ → :9001
|
||||
├── :8090 → / → :8002 (Manager FastAPI)
|
||||
└── :3000 → Gitea
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SSL 인증서
|
||||
|
||||
| 도메인 | 만료일 | 방식 |
|
||||
|--------|--------|------|
|
||||
| zioinfo.co.kr | (Let's Encrypt 90일 자동 갱신) | Certbot |
|
||||
| *.zioinfo.co.kr | - | 와일드카드 미설정 |
|
||||
|
||||
```bash
|
||||
# 인증서 확인
|
||||
certbot certificates
|
||||
|
||||
# 수동 갱신
|
||||
certbot renew --nginx
|
||||
```
|
||||
230
gen_export_docs.py
Normal file
230
gen_export_docs.py
Normal file
@ -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("완료")
|
||||
460
gen_license_docs.py
Normal file
460
gen_license_docs.py
Normal file
@ -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"<b>{t}</b>",
|
||||
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}")
|
||||
411
gen_messenger_docs.py
Normal file
411
gen_messenger_docs.py
Normal file
@ -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}")
|
||||
701
gen_opennet_docs.py
Normal file
701
gen_opennet_docs.py
Normal file
@ -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은 기본적으로 <b>폐쇄망(Closed Network)</b> 환경에서 운영됩니다. "
|
||||
"그러나 외부 메신저(카카오워크, 네이버웍스, Slack)와의 연동, 공공기관 포털 연계, "
|
||||
"재택/원격 관리 등의 요구사항이 증가함에 따라 <b>개방망 지원 기능</b>을 추가하였습니다.",
|
||||
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(
|
||||
"⚠ <b>핵심 원칙 유지</b>: 개방망 모드에서도 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(
|
||||
"환경변수 <b>GUARDIA_NETWORK_MODE</b>에 따라 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(
|
||||
"도메인이 있는 경우 <b>Let's Encrypt</b> 인증서 사용을 권장합니다. "
|
||||
"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}")
|
||||
89
mockups/m_chat.html
Normal file
89
mockups/m_chat.html
Normal file
@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html><html><head><meta charset="UTF-8">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,sans-serif}
|
||||
body{background:#0f172a;width:390px;height:844px;display:flex;flex-direction:column;overflow:hidden}
|
||||
.sb{height:44px;background:#0f172a;display:flex;align-items:center;justify-content:space-between;padding:0 20px;flex-shrink:0}
|
||||
.sb .t{color:#fff;font-size:15px;font-weight:600}
|
||||
.sb .i{color:#fff;font-size:13px}
|
||||
.hd{background:linear-gradient(135deg,#1a3a6b,#1a5fd8);padding:14px 20px;display:flex;align-items:center;gap:12px;flex-shrink:0}
|
||||
.ba{width:42px;height:42px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0}
|
||||
.hi h1{color:#fff;font-size:17px;font-weight:700}
|
||||
.hi p{color:rgba(255,255,255,.7);font-size:12px}
|
||||
.ol{display:inline-block;width:8px;height:8px;background:#22c55e;border-radius:50%;margin-right:4px}
|
||||
.msgs{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:14px}
|
||||
.msg{display:flex;gap:10px;max-width:85%}
|
||||
.mu{align-self:flex-end;flex-direction:row-reverse}
|
||||
.mb{padding:12px 14px;border-radius:18px;font-size:13px;line-height:1.6}
|
||||
.bot .mb{background:#1e293b;color:#e2e8f0;border-bottom-left-radius:4px}
|
||||
.mu .mb{background:#1a5fd8;color:#fff;border-bottom-right-radius:4px}
|
||||
.ba2{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;align-self:flex-end}
|
||||
.mt{font-size:10px;color:#475569;align-self:flex-end}
|
||||
.qr{display:flex;gap:8px;flex-wrap:wrap;padding:0 16px 12px;flex-shrink:0}
|
||||
.qc{padding:7px 13px;background:#1e293b;border-radius:20px;font-size:12px;color:#94a3b8;border:1px solid rgba(255,255,255,.08)}
|
||||
.ib{background:#1e293b;padding:12px 16px;display:flex;gap:10px;align-items:center;flex-shrink:0;border-top:1px solid rgba(255,255,255,.06)}
|
||||
.inp{flex:1;background:#0f172a;border:1px solid rgba(255,255,255,.1);border-radius:24px;padding:10px 16px;color:#e2e8f0;font-size:14px}
|
||||
.snd{width:40px;height:40px;background:#1a5fd8;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;color:#fff;flex-shrink:0}
|
||||
.tb{height:60px;background:#1e293b;border-top:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-around;flex-shrink:0}
|
||||
.tab{display:flex;flex-direction:column;align-items:center;gap:3px}
|
||||
.tab .ic{font-size:20px}
|
||||
.tab .lb{font-size:9px;color:#64748b}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="sb"><span class="t">9:41</span><span class="i">● ●● ▐▌</span></div>
|
||||
<div class="hd">
|
||||
<div class="ba">🤖</div>
|
||||
<div class="hi"><h1>GUARDiA AI</h1><p><span class="ol"></span>Ollama llama3:8b 연결됨</p></div>
|
||||
</div>
|
||||
<div class="msgs">
|
||||
<div class="msg bot">
|
||||
<div class="ba2">🤖</div>
|
||||
<div>
|
||||
<div class="mb">안녕하세요 홍 관리자님! 👋<br>GUARDiA AI 어시스턴트입니다.<br>무엇을 도와드릴까요?</div>
|
||||
<div class="mt">9:35</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg mu">
|
||||
<div>
|
||||
<div class="mb">서울시청 WAS-01 서비스 상태 확인해줘</div>
|
||||
<div class="mt" style="text-align:right">9:40</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg bot">
|
||||
<div class="ba2">🤖</div>
|
||||
<div>
|
||||
<div class="mb">📡 <strong>WAS-01 상태 확인 중...</strong><br><br>✅ <strong>서비스 정상 운영 중</strong><br>• CPU: 34% | 메모리: 2.1/4GB<br>• Tomcat 8.5.99 — UP (14일)<br>• 최근 에러로그: 없음<br><br>⚡ 응답시간: 12ms (정상 범위)</div>
|
||||
<div class="mt">9:41</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg mu">
|
||||
<div>
|
||||
<div class="mb">DR 테스트 실행해줘</div>
|
||||
<div class="mt" style="text-align:right">9:41</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msg bot">
|
||||
<div class="ba2">🤖</div>
|
||||
<div>
|
||||
<div class="mb">🛡️ <strong>DR 복구 테스트 시작합니다</strong><br><br>시나리오: WAS-01 장애<br>단계 1/4 ✅ 서비스 중단 확인<br>단계 2/4 🔄 대기 서버 활성화 중...</div>
|
||||
<div class="mt">9:41</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="qr">
|
||||
<span class="qc">서버 상태 확인</span>
|
||||
<span class="qc">SR 목록 보여줘</span>
|
||||
<span class="qc">최근 인시던트</span>
|
||||
<span class="qc">DR 테스트 실행</span>
|
||||
</div>
|
||||
<div class="ib">
|
||||
<div class="inp">메시지 입력...</div>
|
||||
<div class="snd">➤</div>
|
||||
</div>
|
||||
<div class="tb">
|
||||
<div class="tab"><div class="ic">📊</div><div class="lb">홈</div></div>
|
||||
<div class="tab"><div class="ic">📋</div><div class="lb">SR</div></div>
|
||||
<div class="tab"><div class="ic">🛡️</div><div class="lb">DR</div></div>
|
||||
<div class="tab"><div class="ic">🤖</div><div class="lb" style="color:#60a5fa">AI</div></div>
|
||||
<div class="tab"><div class="ic">⚙️</div><div class="lb">설정</div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
79
mockups/m_dashboard.html
Normal file
79
mockups/m_dashboard.html
Normal file
@ -0,0 +1,79 @@
|
||||
<!DOCTYPE html><html><head><meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=390">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,sans-serif}
|
||||
body{background:#0f172a;width:390px;height:844px;overflow:hidden;display:flex;flex-direction:column}
|
||||
.sb{height:44px;background:#0f172a;display:flex;align-items:center;justify-content:space-between;padding:0 20px;flex-shrink:0}
|
||||
.sb .t{color:#fff;font-size:15px;font-weight:600}
|
||||
.sb .i{color:#fff;font-size:13px}
|
||||
.gnb{background:linear-gradient(135deg,#1a3a6b,#1a5fd8);padding:16px 20px 20px;flex-shrink:0}
|
||||
.gnb .g{color:rgba(255,255,255,.7);font-size:12px;margin-bottom:4px}
|
||||
.gnb .n{color:#fff;font-size:20px;font-weight:700}
|
||||
.gnb .b{display:inline-flex;align-items:center;gap:4px;background:rgba(255,255,255,.15);border-radius:20px;padding:4px 10px;font-size:11px;color:#fff;margin-top:8px}
|
||||
.sc{flex:1;overflow-y:auto;padding:16px}
|
||||
.sr{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px}
|
||||
.st{background:#1e293b;border-radius:14px;padding:16px;border:1px solid rgba(255,255,255,.06)}
|
||||
.st .num{color:#60a5fa;font-size:28px;font-weight:700}
|
||||
.st .lab{color:#94a3b8;font-size:11px;margin-top:4px}
|
||||
.st .sub{color:#22c55e;font-size:10px;margin-top:2px}
|
||||
.sec{background:#1e293b;border-radius:14px;padding:16px;margin-bottom:14px;border:1px solid rgba(255,255,255,.06)}
|
||||
.sec h3{color:#94a3b8;font-size:11px;text-transform:uppercase;letter-spacing:.5px;margin-bottom:12px}
|
||||
.si{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid rgba(255,255,255,.05)}
|
||||
.si:last-child{border-bottom:none}
|
||||
.sd{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||||
.st2{flex:1}
|
||||
.tt{color:#e2e8f0;font-size:13px;font-weight:500}
|
||||
.tm{color:#64748b;font-size:11px;margin-top:2px}
|
||||
.sb2{font-size:10px;padding:2px 8px;border-radius:10px;font-weight:600}
|
||||
.bp{background:rgba(59,130,246,.2);color:#60a5fa}
|
||||
.bw{background:rgba(245,158,11,.2);color:#fbbf24}
|
||||
.bc{background:rgba(34,197,94,.2);color:#4ade80}
|
||||
.svr{display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,.05)}
|
||||
.svr:last-child{border-bottom:none}
|
||||
.sn{color:#e2e8f0;font-size:13px}
|
||||
.sp{color:#94a3b8;font-size:11px}
|
||||
.sok{color:#22c55e;font-size:12px}
|
||||
.swn{color:#f59e0b;font-size:12px}
|
||||
.tb{height:60px;background:#1e293b;border-top:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-around;flex-shrink:0}
|
||||
.tab{display:flex;flex-direction:column;align-items:center;gap:3px}
|
||||
.tab .ic{font-size:20px}
|
||||
.tab .lb{font-size:9px;color:#64748b}
|
||||
.tab.act .lb{color:#60a5fa}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="sb"><span class="t">9:41</span><span class="i">● ●● ▐▌</span></div>
|
||||
<div class="gnb">
|
||||
<div class="g">안녕하세요</div>
|
||||
<div class="n">홍 관리자님 👋</div>
|
||||
<span class="b">🟢 ITSM 운영 중 | Enterprise</span>
|
||||
</div>
|
||||
<div class="sc">
|
||||
<div class="sr">
|
||||
<div class="st"><div class="num">23</div><div class="lab">오늘 SR 접수</div><div class="sub">▲ 3 증가</div></div>
|
||||
<div class="st"><div class="num" style="color:#f59e0b">7</div><div class="lab">처리 대기</div><div class="sub" style="color:#ef4444">SLA 위험 2건</div></div>
|
||||
</div>
|
||||
<div class="sr">
|
||||
<div class="st"><div class="num" style="color:#22c55e">98.2%</div><div class="lab">서버 가용률</div><div class="sub">24대 중 24대 정상</div></div>
|
||||
<div class="st"><div class="num" style="color:#a78bfa">14</div><div class="lab">오늘 배포</div><div class="sub">성공 13 / 실패 1</div></div>
|
||||
</div>
|
||||
<div class="sec">
|
||||
<h3>최근 서비스 요청</h3>
|
||||
<div class="si"><div class="sd" style="background:#ef4444"></div><div class="st2"><div class="tt">행정망 WAS 서비스 장애</div><div class="tm">서울시청 · 10분 전</div></div><span class="sb2 bp">처리중</span></div>
|
||||
<div class="si"><div class="sd" style="background:#f59e0b"></div><div class="st2"><div class="tt">Tomcat 재기동 요청</div><div class="tm">안산시청 · 23분 전</div></div><span class="sb2 bw">대기</span></div>
|
||||
<div class="si"><div class="sd" style="background:#3b82f6"></div><div class="st2"><div class="tt">DB 백업 스케줄 변경</div><div class="tm">수원시청 · 1시간 전</div></div><span class="sb2 bc">완료</span></div>
|
||||
</div>
|
||||
<div class="sec">
|
||||
<h3>서버 상태</h3>
|
||||
<div class="svr"><div><div class="sn">WAS-서울시청-01</div><div class="sp">응답 12ms</div></div><span class="sok">● 정상</span></div>
|
||||
<div class="svr"><div><div class="sn">DB-안산시청-01</div><div class="sp">응답 45ms</div></div><span class="swn">⚠ 주의</span></div>
|
||||
<div class="svr"><div><div class="sn">WEB-수원시청-01</div><div class="sp">응답 8ms</div></div><span class="sok">● 정상</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb">
|
||||
<div class="tab act"><div class="ic">📊</div><div class="lb" style="color:#60a5fa">홈</div></div>
|
||||
<div class="tab"><div class="ic">📋</div><div class="lb">SR</div></div>
|
||||
<div class="tab"><div class="ic">🛡️</div><div class="lb">DR</div></div>
|
||||
<div class="tab"><div class="ic">🤖</div><div class="lb">AI</div></div>
|
||||
<div class="tab"><div class="ic">⚙️</div><div class="lb">설정</div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
80
mockups/m_dr.html
Normal file
80
mockups/m_dr.html
Normal file
@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html><html><head><meta charset="UTF-8">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,sans-serif}
|
||||
body{background:#0f172a;width:390px;height:844px;display:flex;flex-direction:column;overflow:hidden}
|
||||
.sb{height:44px;background:#0f172a;display:flex;align-items:center;justify-content:space-between;padding:0 20px;flex-shrink:0}
|
||||
.sb .t{color:#fff;font-size:15px;font-weight:600}
|
||||
.sb .i{color:#fff;font-size:13px}
|
||||
.hd{background:linear-gradient(135deg,#1a3a6b,#1a5fd8);padding:16px 20px;flex-shrink:0}
|
||||
.hd h1{color:#fff;font-size:20px;font-weight:700}
|
||||
.hd p{color:rgba(255,255,255,.7);font-size:12px;margin-top:2px}
|
||||
.sc{flex:1;overflow-y:auto;padding:16px}
|
||||
.sr{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:14px}
|
||||
.su{background:#1e293b;border-radius:12px;padding:10px 6px;text-align:center;border:1px solid rgba(255,255,255,.06)}
|
||||
.su .n{font-size:20px;font-weight:700;color:#e2e8f0}
|
||||
.su .l{font-size:9px;color:#94a3b8;margin-top:2px}
|
||||
.sec{background:#1e293b;border-radius:14px;padding:16px;margin-bottom:14px;border:1px solid rgba(255,255,255,.06)}
|
||||
.sec h3{color:#94a3b8;font-size:11px;text-transform:uppercase;letter-spacing:.5px;margin-bottom:12px}
|
||||
.sc2{padding:12px 0;border-bottom:1px solid rgba(255,255,255,.05)}
|
||||
.sc2:last-child{border-bottom:none}
|
||||
.st{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
|
||||
.sn{color:#e2e8f0;font-size:13px;font-weight:600}
|
||||
.sbg{font-size:10px;padding:2px 8px;border-radius:10px;font-weight:700}
|
||||
.bps{background:rgba(34,197,94,.2);color:#4ade80}
|
||||
.bfl{background:rgba(239,68,68,.2);color:#f87171}
|
||||
.bpd{background:rgba(148,163,184,.2);color:#94a3b8}
|
||||
.rb{display:flex;align-items:center;gap:8px;margin-top:5px}
|
||||
.rl{font-size:10px;color:#64748b;width:55px}
|
||||
.rt{flex:1;height:6px;background:rgba(255,255,255,.08);border-radius:3px;overflow:hidden}
|
||||
.rf{height:100%;border-radius:3px}
|
||||
.rv{font-size:10px;color:#94a3b8;width:30px;text-align:right}
|
||||
.sm{font-size:10px;color:#475569;margin-top:4px}
|
||||
.tb{height:60px;background:#1e293b;border-top:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-around;flex-shrink:0}
|
||||
.tab{display:flex;flex-direction:column;align-items:center;gap:3px}
|
||||
.tab .ic{font-size:20px}
|
||||
.tab .lb{font-size:9px;color:#64748b}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="sb"><span class="t">9:41</span><span class="i">● ●● ▐▌</span></div>
|
||||
<div class="hd"><h1>🛡️ DR 재해복구</h1><p>RTO/RPO 목표 대비 실적 모니터링</p></div>
|
||||
<div class="sc">
|
||||
<div class="sr">
|
||||
<div class="su"><div class="n">5</div><div class="l">전체</div></div>
|
||||
<div class="su"><div class="n" style="color:#22c55e">3</div><div class="l">PASS</div></div>
|
||||
<div class="su"><div class="n" style="color:#ef4444">1</div><div class="l">FAIL</div></div>
|
||||
<div class="su"><div class="n" style="color:#f59e0b">1</div><div class="l">미테스트</div></div>
|
||||
</div>
|
||||
<div class="sec">
|
||||
<h3>시나리오 목록</h3>
|
||||
<div class="sc2">
|
||||
<div class="st"><span class="sn">서울시청 WAS 장애</span><span class="sbg bps">PASS</span></div>
|
||||
<div class="rb"><span class="rl">RTO 목표</span><div class="rt"><div class="rf" style="width:15%;background:#22c55e"></div></div><span class="rv">120분</span></div>
|
||||
<div class="rb"><span class="rl">RTO 실적</span><div class="rt"><div class="rf" style="width:10%;background:#60a5fa"></div></div><span class="rv">18분</span></div>
|
||||
<div class="sm">최근 테스트: 2026-05-30 ✔ 충족</div>
|
||||
</div>
|
||||
<div class="sc2">
|
||||
<div class="st"><span class="sn">안산시청 DB 페일오버</span><span class="sbg bps">PASS</span></div>
|
||||
<div class="rb"><span class="rl">RTO 목표</span><div class="rt"><div class="rf" style="width:30%;background:#22c55e"></div></div><span class="rv">240분</span></div>
|
||||
<div class="rb"><span class="rl">RTO 실적</span><div class="rt"><div class="rf" style="width:18%;background:#60a5fa"></div></div><span class="rv">45분</span></div>
|
||||
<div class="sm">최근 테스트: 2026-05-29 ✔ 충족</div>
|
||||
</div>
|
||||
<div class="sc2">
|
||||
<div class="st"><span class="sn">수원시청 사이트 장애</span><span class="sbg bfl">FAIL</span></div>
|
||||
<div class="rb"><span class="rl">RTO 목표</span><div class="rt"><div class="rf" style="width:25%;background:#22c55e"></div></div><span class="rv">200분</span></div>
|
||||
<div class="rb"><span class="rl">RTO 실적</span><div class="rt"><div class="rf" style="width:80%;background:#ef4444"></div></div><span class="rv">320분</span></div>
|
||||
<div class="sm" style="color:#ef4444">최근 테스트: 2026-05-28 ✘ 목표 초과</div>
|
||||
</div>
|
||||
<div class="sc2">
|
||||
<div class="st"><span class="sn">화성시청 네트워크 장애</span><span class="sbg bpd">미테스트</span></div>
|
||||
<div class="sm" style="margin-top:4px">테스트 예정: 2026-06-15</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb">
|
||||
<div class="tab"><div class="ic">📊</div><div class="lb">홈</div></div>
|
||||
<div class="tab"><div class="ic">📋</div><div class="lb">SR</div></div>
|
||||
<div class="tab"><div class="ic">🛡️</div><div class="lb" style="color:#60a5fa">DR</div></div>
|
||||
<div class="tab"><div class="ic">🤖</div><div class="lb">AI</div></div>
|
||||
<div class="tab"><div class="ic">⚙️</div><div class="lb">설정</div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
81
mockups/m_network.html
Normal file
81
mockups/m_network.html
Normal file
@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html><html><head><meta charset="UTF-8">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,sans-serif}
|
||||
body{background:#0f172a;width:390px;height:844px;display:flex;flex-direction:column;overflow:hidden}
|
||||
.sb{height:44px;background:#0f172a;display:flex;align-items:center;justify-content:space-between;padding:0 20px;flex-shrink:0}
|
||||
.sb .t{color:#fff;font-size:15px;font-weight:600}
|
||||
.sb .i{color:#fff;font-size:13px}
|
||||
.hd{background:linear-gradient(135deg,#1a3a6b,#1a5fd8);padding:16px 20px;flex-shrink:0}
|
||||
.hd h1{color:#fff;font-size:20px;font-weight:700}
|
||||
.hd p{color:rgba(255,255,255,.7);font-size:12px;margin-top:2px}
|
||||
.se{padding:10px 16px;background:#0f172a;flex-shrink:0}
|
||||
.se input{width:100%;background:#1e293b;border:1px solid rgba(255,255,255,.1);border-radius:12px;padding:10px 14px;color:#94a3b8;font-size:13px}
|
||||
.sc{flex:1;overflow-y:auto;padding:12px 16px}
|
||||
.sr{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:12px}
|
||||
.su{background:#1e293b;border-radius:10px;padding:10px 8px;text-align:center;border:1px solid rgba(255,255,255,.06)}
|
||||
.su .n{font-size:18px;font-weight:700;color:#e2e8f0}
|
||||
.su .l{font-size:9px;color:#94a3b8;margin-top:2px}
|
||||
.cd{background:#1e293b;border-radius:14px;padding:16px;margin-bottom:10px;border:1px solid rgba(255,255,255,.06)}
|
||||
.dt{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
||||
.di{font-size:28px}
|
||||
.dn{flex:1}
|
||||
.dna{color:#e2e8f0;font-size:14px;font-weight:600}
|
||||
.dm{font-size:11px;color:#64748b;margin-top:2px}
|
||||
.dst{font-size:20px}
|
||||
.dd{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px}
|
||||
.tg{background:rgba(255,255,255,.06);border-radius:8px;padding:4px 10px;font-size:11px;color:#94a3b8}
|
||||
.bk{display:flex;align-items:center;justify-content:space-between}
|
||||
.bi{font-size:11px;color:#64748b}
|
||||
.bb{border-radius:8px;padding:5px 12px;font-size:11px;font-weight:600;color:#fff;background:#1a5fd8}
|
||||
.bw{background:rgba(245,158,11,.2);color:#fbbf24}
|
||||
.tb{height:60px;background:#1e293b;border-top:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-around;flex-shrink:0}
|
||||
.tab{display:flex;flex-direction:column;align-items:center;gap:3px}
|
||||
.tab .ic{font-size:20px}
|
||||
.tab .lb{font-size:9px;color:#64748b}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="sb"><span class="t">9:41</span><span class="i">● ●● ▐▌</span></div>
|
||||
<div class="hd"><h1>🔀 네트워크 장비</h1><p>스위치·라우터·방화벽 관제</p></div>
|
||||
<div class="se"><input placeholder="장비명 / 벤더 검색..." /></div>
|
||||
<div class="sc">
|
||||
<div class="sr">
|
||||
<div class="su"><div class="n">12</div><div class="l">전체</div></div>
|
||||
<div class="su"><div class="n" style="color:#22c55e">10</div><div class="l">정상</div></div>
|
||||
<div class="su"><div class="n" style="color:#ef4444">2</div><div class="l">미백업</div></div>
|
||||
</div>
|
||||
<div class="cd">
|
||||
<div class="dt">
|
||||
<div class="di">🔀</div>
|
||||
<div class="dn"><div class="dna">Core-Switch-서울-01</div><div class="dm">CISCO Catalyst 9300 · 서울시청 IDC</div></div>
|
||||
<div class="dst">✅</div>
|
||||
</div>
|
||||
<div class="dd"><span class="tg">SWITCH</span><span class="tg">cisco_ios</span><span class="tg">백업 2일 전</span></div>
|
||||
<div class="bk"><span class="bi">최근 백업: 2026-05-29 · 변경 0줄</span><span class="bb">백업</span></div>
|
||||
</div>
|
||||
<div class="cd">
|
||||
<div class="dt">
|
||||
<div class="di">🛡️</div>
|
||||
<div class="dn"><div class="dna">FW-안산-01</div><div class="dm">PIOLINK TPLINK · 안산시청</div></div>
|
||||
<div class="dst">⚠️</div>
|
||||
</div>
|
||||
<div class="dd"><span class="tg">FIREWALL</span><span class="tg">linux</span><span class="tg" style="color:#ef4444">미백업 8일</span></div>
|
||||
<div class="bk"><span class="bi" style="color:#f59e0b">갱신 필요 — 8일 초과</span><span class="bb bw">백업 필요</span></div>
|
||||
</div>
|
||||
<div class="cd">
|
||||
<div class="dt">
|
||||
<div class="di">🔗</div>
|
||||
<div class="dn"><div class="dna">Router-수원-01</div><div class="dm">HUAWEI AR3200 · 수원시청</div></div>
|
||||
<div class="dst">✅</div>
|
||||
</div>
|
||||
<div class="dd"><span class="tg">ROUTER</span><span class="tg">huawei_vrp</span><span class="tg">백업 1일 전</span></div>
|
||||
<div class="bk"><span class="bi">최근 백업: 2026-05-30 · 변경 3줄</span><span class="bb">백업</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb">
|
||||
<div class="tab"><div class="ic">📊</div><div class="lb">홈</div></div>
|
||||
<div class="tab"><div class="ic">📋</div><div class="lb">SR</div></div>
|
||||
<div class="tab"><div class="ic">🛡️</div><div class="lb">DR</div></div>
|
||||
<div class="tab"><div class="ic">🤖</div><div class="lb">AI</div></div>
|
||||
<div class="tab"><div class="ic">⚙️</div><div class="lb">설정</div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
86
mockups/m_sr.html
Normal file
86
mockups/m_sr.html
Normal file
@ -0,0 +1,86 @@
|
||||
<!DOCTYPE html><html><head><meta charset="UTF-8">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,sans-serif}
|
||||
body{background:#0f172a;width:390px;height:844px;display:flex;flex-direction:column;overflow:hidden}
|
||||
.sb{height:44px;background:#0f172a;display:flex;align-items:center;justify-content:space-between;padding:0 20px;flex-shrink:0}
|
||||
.sb .t{color:#fff;font-size:15px;font-weight:600}
|
||||
.sb .i{color:#fff;font-size:13px}
|
||||
.hd{background:linear-gradient(135deg,#1a3a6b,#1a5fd8);padding:16px 20px;flex-shrink:0}
|
||||
.hd h1{color:#fff;font-size:20px;font-weight:700}
|
||||
.hd p{color:rgba(255,255,255,.7);font-size:12px;margin-top:2px}
|
||||
.fr{display:flex;gap:8px;padding:12px 16px;background:#0f172a;flex-shrink:0;overflow-x:auto}
|
||||
.ch{padding:6px 14px;border-radius:20px;font-size:12px;font-weight:600;white-space:nowrap;flex-shrink:0}
|
||||
.cha{background:#1a5fd8;color:#fff}
|
||||
.chi{background:#1e293b;color:#94a3b8}
|
||||
.ls{flex:1;overflow-y:auto;padding:12px 16px}
|
||||
.cd{background:#1e293b;border-radius:14px;padding:16px;margin-bottom:10px;border:1px solid rgba(255,255,255,.06)}
|
||||
.ct{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:8px}
|
||||
.si{font-size:11px;color:#64748b;font-family:monospace}
|
||||
.pr{font-size:10px;padding:2px 8px;border-radius:8px;font-weight:700}
|
||||
.pc{background:rgba(239,68,68,.2);color:#f87171}
|
||||
.ph{background:rgba(245,158,11,.2);color:#fbbf24}
|
||||
.pm{background:rgba(59,130,246,.2);color:#60a5fa}
|
||||
.cn{color:#e2e8f0;font-size:14px;font-weight:600;margin-bottom:4px}
|
||||
.cm{display:flex;gap:8px;align-items:center}
|
||||
.cm span{font-size:11px;color:#64748b}
|
||||
.dt{width:3px;height:3px;background:#475569;border-radius:50%}
|
||||
.sr2{display:flex;justify-content:space-between;align-items:center;margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,.05)}
|
||||
.av2{display:flex;align-items:center;gap:6px}
|
||||
.av{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;color:#fff}
|
||||
.an{font-size:11px;color:#94a3b8}
|
||||
.stb{font-size:10px;padding:3px 10px;border-radius:10px;font-weight:600}
|
||||
.sp{background:rgba(59,130,246,.2);color:#60a5fa}
|
||||
.sw{background:rgba(245,158,11,.2);color:#fbbf24}
|
||||
.sc{background:rgba(34,197,94,.2);color:#4ade80}
|
||||
.sr3{background:rgba(148,163,184,.2);color:#94a3b8}
|
||||
.tb{height:60px;background:#1e293b;border-top:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-around;flex-shrink:0}
|
||||
.tab{display:flex;flex-direction:column;align-items:center;gap:3px}
|
||||
.tab .ic{font-size:20px}
|
||||
.tab .lb{font-size:9px;color:#64748b}
|
||||
.fab{position:fixed;right:20px;bottom:78px;width:52px;height:52px;border-radius:50%;background:#1a5fd8;display:flex;align-items:center;justify-content:center;font-size:26px;color:#fff;box-shadow:0 4px 16px rgba(26,95,216,.5)}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="sb"><span class="t">9:41</span><span class="i">● ●● ▐▌</span></div>
|
||||
<div class="hd"><h1>서비스 요청</h1><p>전체 23건 · 대기 7건</p></div>
|
||||
<div class="fr">
|
||||
<span class="ch cha">전체</span>
|
||||
<span class="ch chi">처리중</span>
|
||||
<span class="ch chi">대기</span>
|
||||
<span class="ch chi">완료</span>
|
||||
<span class="ch chi">CRITICAL</span>
|
||||
</div>
|
||||
<div class="ls">
|
||||
<div class="cd">
|
||||
<div class="ct"><span class="si">SR-20260531-0042</span><span class="pr pc">CRITICAL</span></div>
|
||||
<div class="cn">행정망 WAS 서비스 장애 — 즉시 대응 요망</div>
|
||||
<div class="cm"><span>서울시청</span><div class="dt"></div><span>WAS-01</span><div class="dt"></div><span>10분 전</span></div>
|
||||
<div class="sr2"><div class="av2"><div class="av" style="background:linear-gradient(135deg,#6366f1,#8b5cf6)">김</div><span class="an">김엔지니어</span></div><span class="stb sp">처리중</span></div>
|
||||
</div>
|
||||
<div class="cd">
|
||||
<div class="ct"><span class="si">SR-20260531-0041</span><span class="pr ph">HIGH</span></div>
|
||||
<div class="cn">Tomcat 8.5 재기동 요청</div>
|
||||
<div class="cm"><span>안산시청</span><div class="dt"></div><span>WAS-02</span><div class="dt"></div><span>23분 전</span></div>
|
||||
<div class="sr2"><div class="av2"><div class="av" style="background:linear-gradient(135deg,#0ea5e9,#06b6d4)">박</div><span class="an">박엔지니어</span></div><span class="stb sw">대기</span></div>
|
||||
</div>
|
||||
<div class="cd">
|
||||
<div class="ct"><span class="si">SR-20260531-0040</span><span class="pr pm">MEDIUM</span></div>
|
||||
<div class="cn">DB 백업 스케줄 매일 02:00 변경</div>
|
||||
<div class="cm"><span>수원시청</span><div class="dt"></div><span>DB-01</span><div class="dt"></div><span>1시간 전</span></div>
|
||||
<div class="sr2"><div class="av2"><div class="av" style="background:linear-gradient(135deg,#10b981,#059669)">이</div><span class="an">이엔지니어</span></div><span class="stb sc">완료</span></div>
|
||||
</div>
|
||||
<div class="cd">
|
||||
<div class="ct"><span class="si">SR-20260531-0039</span><span class="pr pm">MEDIUM</span></div>
|
||||
<div class="cn">SSL 인증서 갱신 — 만료 7일 전</div>
|
||||
<div class="cm"><span>화성시청</span><div class="dt"></div><span>WEB-01</span><div class="dt"></div><span>2시간 전</span></div>
|
||||
<div class="sr2"><div class="av2"><div class="av" style="background:linear-gradient(135deg,#f59e0b,#d97706)">최</div><span class="an">최엔지니어</span></div><span class="stb sr3">접수됨</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fab">+</div>
|
||||
<div class="tb">
|
||||
<div class="tab"><div class="ic">📊</div><div class="lb">홈</div></div>
|
||||
<div class="tab"><div class="ic">📋</div><div class="lb" style="color:#60a5fa">SR</div></div>
|
||||
<div class="tab"><div class="ic">🛡️</div><div class="lb">DR</div></div>
|
||||
<div class="tab"><div class="ic">🤖</div><div class="lb">AI</div></div>
|
||||
<div class="tab"><div class="ic">⚙️</div><div class="lb">설정</div></div>
|
||||
</div>
|
||||
</body></html>
|
||||
Loading…
Reference in New Issue
Block a user