diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..cb1ffcd5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,63 @@ +# Git +.git/ +.gitignore + +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ +*.egg-info/ +dist/ +build/ +.eggs/ +venv/ +.venv/ +env/ + +# 개발 도구 +.vscode/ +.idea/ +*.swp +*.swo + +# 테스트 +tests/ +test_*.py +*.test.py + +# 문서/설정 +docs/ +manual/ +*.md +*.txt +!requirements.txt + +# 데이터 +*.db +*.sqlite +*.sqlite3 +guardia_itsm.db* + +# 업로드 +uploads/ +*.log + +# 환경 파일 (보안) +.env +.env.* +*.env +secrets/ + +# Docker 패키지 +docker-package/ + +# 설치 스크립트 +setup/ + +# workspace +workspace/ + +# Claude +.claude/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..210ea7c4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +# ============================================================ +# GUARDiA ITSM — Production Dockerfile +# Base: Python 3.11-slim +# Port: 8001 +# ============================================================ + +FROM python:3.11-slim AS base + +LABEL maintainer="GUARDiA Team" +LABEL description="GUARDiA ITSM — AI 기반 레거시 인프라 자율 운영 플랫폼" + +# ── 시스템 의존성 ────────────────────────────────────────── +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl wget git \ + libpq-dev gcc \ + && rm -rf /var/lib/apt/lists/* + +# ── 비루트 실행 계정 ─────────────────────────────────────── +RUN groupadd -r guardia && useradd -r -g guardia -d /app guardia + +# ── 작업 디렉토리 ───────────────────────────────────────── +WORKDIR /app + +# ── Python 의존성 (레이어 캐싱 최적화) ───────────────────── +COPY itsm/requirements.txt . +RUN pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir -r requirements.txt + +# ── 애플리케이션 소스 복사 ─────────────────────────────── +COPY itsm/ . + +# ── 업로드/데이터 디렉토리 ────────────────────────────── +RUN mkdir -p uploads/sr_files uploads/workspaces \ + && chown -R guardia:guardia /app + +# ── 환경 기본값 ────────────────────────────────────────── +ENV PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=utf-8 \ + PYTHONDONTWRITEBYTECODE=1 \ + DATABASE_URL="sqlite+aiosqlite:///./guardia_itsm.db" \ + OLLAMA_BASE_URL="http://ollama:11434" + +# ── 포트 노출 ──────────────────────────────────────────── +EXPOSE 8001 + +# ── 비루트 전환 ───────────────────────────────────────── +USER guardia + +# ── 헬스체크 ───────────────────────────────────────────── +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -sf http://localhost:8001/ || exit 1 + +# ── 엔트리포인트 ───────────────────────────────────────── +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001", "--workers", "4"] diff --git a/docker-compose.gpu.yml b/docker-compose.gpu.yml new file mode 100644 index 00000000..6df4ce17 --- /dev/null +++ b/docker-compose.gpu.yml @@ -0,0 +1,23 @@ +# ============================================================ +# GUARDiA — NVIDIA GPU 오버라이드 (Ollama 가속) +# ============================================================ +# 사용법: +# docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d ollama +# +# 전제조건: +# - nvidia-container-toolkit 설치 완료 +# - nvidia-smi 정상 작동 확인 +# ============================================================ + +services: + ollama: + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + environment: + OLLAMA_HOST: 0.0.0.0 + CUDA_VISIBLE_DEVICES: "0" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 00000000..6248c8e2 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,46 @@ +# ============================================================ +# GUARDiA ITSM — 운영 환경 오버라이드 +# ============================================================ +# 사용법: +# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d +# ============================================================ + +services: + + guardia: + image: guardia-itsm:${GUARDIA_VERSION:-latest} + environment: + DATABASE_URL: ${DATABASE_URL} # 운영 DB URL 필수 + SECRET_KEY: ${SECRET_KEY} # 운영 시크릿 키 필수 + ports: + - "127.0.0.1:8001:8001" # localhost만 — Nginx를 통해서만 외부 접근 + volumes: + - /data/guardia/uploads:/app/uploads # 호스트 경로 직접 마운트 + - /data/guardia/logs:/app/logs + deploy: + replicas: 1 + restart_policy: + condition: on-failure + max_attempts: 3 + resources: + limits: + cpus: "2.0" + memory: 2G + + postgres: + ports: [] # 운영에서는 외부 노출 금지 + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + + redis: + ports: [] # 운영에서는 외부 노출 금지 + + ollama: + ports: + - "127.0.0.1:11434:11434" # localhost만 노출 + volumes: + - ${OLLAMA_MODELS_PATH:-/data/ollama}:/root/.ollama + + tomcat: + ports: + - "127.0.0.1:8080:8080" # localhost만 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..9b09dc03 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,196 @@ +# ============================================================ +# GUARDiA ITSM — Full Stack docker-compose (개발/테스트용) +# ============================================================ +# 사용법: +# docker compose up -d # 전체 스택 시작 +# docker compose up -d guardia # GUARDiA만 시작 (DB/Redis는 외부) +# docker compose logs -f guardia +# docker compose down -v # 볼륨 포함 완전 삭제 +# +# 환경변수: +# .env 파일에 GUARDIA_LICENSE_KEY, SECRET_KEY 등을 설정하세요. +# (itsm/.env가 없으면 기본값 사용) +# ============================================================ + +x-common-env: &common-env + PYTHONIOENCODING: utf-8 + PYTHONUNBUFFERED: "1" + DATABASE_URL: postgresql+asyncpg://guardia:guardia@postgres:5432/guardia + REDIS_URL: redis://redis:6379/0 + OLLAMA_BASE_URL: http://ollama:11434 + MESSENGER_BASE_URL: http://messenger:8002 + MESSENGER_OPS_ROOM: ops + +services: + + # ── GUARDiA ITSM ──────────────────────────────────────── + guardia: + build: + context: . + dockerfile: Dockerfile + image: guardia-itsm:latest + container_name: guardia-itsm + ports: + - "8001:8001" + environment: + <<: *common-env + SECRET_KEY: ${SECRET_KEY:-change_this_in_production_min_32chars} + ALGORITHM: HS256 + ACCESS_TOKEN_EXPIRE_MINUTES: 480 + GUARDIA_LLM_MODEL: ${GUARDIA_LLM_MODEL:-llama3.1:8b} + GUARDIA_LICENSE_KEY: ${GUARDIA_LICENSE_KEY:-} + # OAuth 소셜 로그인 (선택) + GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-} + GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-} + GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-} + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-} + KEYCLOAK_BASE_URL: ${KEYCLOAK_BASE_URL:-} + KEYCLOAK_REALM: ${KEYCLOAK_REALM:-master} + KEYCLOAK_CLIENT_ID: ${KEYCLOAK_CLIENT_ID:-guardia} + KEYCLOAK_CLIENT_SECRET: ${KEYCLOAK_CLIENT_SECRET:-} + CATALINA_HOME: /app/tomcat + volumes: + - guardia-uploads:/app/uploads + - guardia-db:/app # SQLite 개발 모드용 (PostgreSQL 사용 시 불필요) + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - guardia-net + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8001/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # ── Nginx 리버스 프록시 ────────────────────────────────── + nginx: + image: nginx:alpine + container_name: guardia-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./docker/nginx/guardia.conf:/etc/nginx/conf.d/default.conf:ro + - ./docker/nginx/ssl:/etc/nginx/ssl:ro # HTTPS 인증서 (선택) + depends_on: + - guardia + networks: + - guardia-net + restart: unless-stopped + + # ── PostgreSQL ─────────────────────────────────────────── + postgres: + image: postgres:15-alpine + container_name: guardia-postgres + environment: + POSTGRES_DB: guardia + POSTGRES_USER: guardia + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-guardia} + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + - guardia-pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" # 개발용 노출 (운영에서는 제거) + networks: + - guardia-net + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U guardia -d guardia"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + + # ── Redis ──────────────────────────────────────────────── + redis: + image: redis:7-alpine + container_name: guardia-redis + command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru + volumes: + - guardia-redis:/data + ports: + - "6379:6379" # 개발용 노출 + networks: + - guardia-net + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 3 + + # ── Ollama (온프레미스 sLLM) ───────────────────────────── + # 모델은 볼륨(guardia-ollama-models)에 저장 — 이미지에 포함 안 함 + # GPU 지원: docker-compose.gpu.yml 오버라이드 파일 참조 + ollama: + image: ollama/ollama:latest + container_name: guardia-ollama + volumes: + - guardia-ollama-models:/root/.ollama # 모델 영구 저장 + ports: + - "11434:11434" # 내부 전용 (외부 노출 금지 권장) + environment: + OLLAMA_HOST: 0.0.0.0 + networks: + - guardia-net + restart: unless-stopped + # GPU 사용 시 아래 주석 해제 (docker-compose.gpu.yml에서 override) + # deploy: + # resources: + # reservations: + # devices: + # - driver: nvidia + # count: 1 + # capabilities: [gpu] + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:11434/api/version"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + + # ── Tomcat 9 (WAS 환경 시뮬레이션) ────────────────────── + tomcat: + image: tomcat:9.0-jdk17-temurin-jammy + container_name: guardia-tomcat + ports: + - "8080:8080" + volumes: + - guardia-tomcat-webapps:/usr/local/tomcat/webapps + - guardia-tomcat-logs:/usr/local/tomcat/logs + - ./docker/tomcat/tomcat-users.xml:/usr/local/tomcat/conf/tomcat-users.xml:ro + environment: + JAVA_OPTS: "-Xms512m -Xmx1024m -Djava.awt.headless=true" + CATALINA_OPTS: "-server -XX:+UseParallelGC" + networks: + - guardia-net + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8080/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + +# ── 볼륨 ───────────────────────────────────────────────── +volumes: + guardia-db: + guardia-uploads: + guardia-pgdata: + guardia-redis: + guardia-ollama-models: # Ollama 모델 (로컬 경로 마운트 가능) + guardia-tomcat-webapps: + guardia-tomcat-logs: + +# ── 네트워크 ────────────────────────────────────────────── +networks: + guardia-net: + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16 diff --git a/docker-start.sh b/docker-start.sh new file mode 100644 index 00000000..43d0697b --- /dev/null +++ b/docker-start.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# GUARDiA 빠른 시작 스크립트 +# 사용법: bash docker-start.sh [dev|prod|gpu] + +MODE="${1:-dev}" + +case "$MODE" in + dev) + echo "▶ 개발 모드 시작..." + docker compose up -d + ;; + prod) + echo "▶ 운영 모드 시작..." + [[ -f ".env" ]] || { echo "❌ .env 파일이 없습니다. .env.example을 복사하세요."; exit 1; } + docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d + ;; + gpu) + echo "▶ GPU 모드 시작 (Ollama NVIDIA 가속)..." + docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d + ;; + *) + echo "사용법: bash docker-start.sh [dev|prod|gpu]" + exit 1 + ;; +esac + +echo "" +echo "서비스 상태:" +docker compose ps + +echo "" +echo "접속: http://localhost" +echo "로그: docker compose logs -f guardia" diff --git a/docker/nginx/guardia.conf b/docker/nginx/guardia.conf new file mode 100644 index 00000000..3410f6d1 --- /dev/null +++ b/docker/nginx/guardia.conf @@ -0,0 +1,44 @@ +upstream guardia_backend { + server guardia:8001; + keepalive 32; +} + +server { + listen 80; + server_name _; + client_max_body_size 100M; + + # GUARDiA ITSM + location / { + proxy_pass http://guardia_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + + # 정적 파일 직접 서빙 (선택 최적화) + # location /static/ { + # alias /app/static/; + # expires 1d; + # add_header Cache-Control "public"; + # } + + # Ollama/Tomcat 직접 노출 금지 + location /api/ollama/ { return 403; } + location /manager/ { return 403; } +} + +# HTTPS (인증서 설정 후 활성화) +# server { +# listen 443 ssl http2; +# server_name guardia.example.com; +# ssl_certificate /etc/nginx/ssl/cert.pem; +# ssl_certificate_key /etc/nginx/ssl/key.pem; +# ssl_protocols TLSv1.2 TLSv1.3; +# include /etc/nginx/conf.d/guardia.conf; # location 블록 재사용 +# } diff --git a/docker/tomcat/tomcat-users.xml b/docker/tomcat/tomcat-users.xml new file mode 100644 index 00000000..131d55d6 --- /dev/null +++ b/docker/tomcat/tomcat-users.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/setup/docker_load.sh b/setup/docker_load.sh new file mode 100644 index 00000000..3aa3f2e2 --- /dev/null +++ b/setup/docker_load.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# ============================================================ +# GUARDiA Docker 폐쇄망 로드 스크립트 +# ============================================================ +# docker_package.sh 로 생성된 패키지를 폐쇄망 서버에서 로드합니다. +# +# 사용법: +# tar -xzf guardia-docker-YYYYMMDD.tar.gz +# cd guardia-docker-YYYYMMDD +# bash docker_load.sh +# bash docker_load.sh --start # 로드 후 즉시 서비스 시작 +# ============================================================ + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +START_AFTER="${1:-}" + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +fail() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; } +info() { echo -e " $*"; } + +echo "==================================================" +echo " GUARDiA Docker 폐쇄망 로드" +echo " 디렉토리: $SCRIPT_DIR" +echo "==================================================" + +# Docker 설치 확인 +command -v docker &>/dev/null || fail "Docker가 설치되지 않았습니다. Docker Engine을 먼저 설치하세요." +docker compose version &>/dev/null || fail "Docker Compose가 필요합니다. (docker compose 플러그인)" + +# ── 1. 버전 정보 확인 ─────────────────────────────────── +if [[ -f "$SCRIPT_DIR/VERSION" ]]; then + source "$SCRIPT_DIR/VERSION" + echo " 패키지 버전: ${GUARDIA_VERSION:-unknown}" + echo " 빌드 일자: ${BUILD_DATE:-unknown}" +fi + +# ── 2. 이미지 로드 ────────────────────────────────────── +echo "" +echo "[1/4] Docker 이미지 로드..." + +# 메인 이미지 +if [[ -f "$SCRIPT_DIR/images.tar.gz" ]]; then + echo " 로드 중: images.tar.gz" + docker load < "$SCRIPT_DIR/images.tar.gz" + ok "메인 이미지 로드 완료" +else + fail "images.tar.gz 파일을 찾을 수 없습니다." +fi + +# Ollama 이미지 +if [[ -f "$SCRIPT_DIR/ollama.tar.gz" ]]; then + echo " 로드 중: ollama.tar.gz (시간이 걸릴 수 있습니다...)" + docker load < "$SCRIPT_DIR/ollama.tar.gz" + ok "Ollama 이미지 로드 완료" +else + warn "ollama.tar.gz 없음 — Ollama 이미지 로드 건너뜀" +fi + +# 로드된 이미지 목록 출력 +echo "" +echo " 로드된 이미지:" +docker images | grep -E "guardia|postgres|redis|nginx|ollama|tomcat" | awk '{printf " %-40s %-15s %s\n", $1, $2, $7}' + +# ── 3. Ollama 모델 복사 ───────────────────────────────── +echo "" +echo "[2/4] Ollama 모델 설정..." +MODELS_TAR="$SCRIPT_DIR/ollama-models/models.tar.gz" + +if [[ -f "$MODELS_TAR" ]]; then + OLLAMA_DATA="${OLLAMA_DATA_DIR:-/var/lib/guardia/ollama}" + mkdir -p "$OLLAMA_DATA" + echo " 모델 압축 해제: $OLLAMA_DATA" + tar -xzf "$MODELS_TAR" -C "$OLLAMA_DATA" + ok "Ollama 모델 복사 완료: $OLLAMA_DATA" + export OLLAMA_MODELS_PATH="$OLLAMA_DATA" +else + warn "Ollama 모델 파일 없음" + warn " 방법 1: docker compose up -d ollama && docker exec guardia-ollama ollama pull llama3.1:8b" + warn " 방법 2: 모델 파일을 수동으로 /var/lib/guardia/ollama/models 에 복사" +fi + +# ── 4. 환경 파일 설정 ──────────────────────────────────── +echo "" +echo "[3/4] 환경 파일 설정..." +ENV_FILE="$SCRIPT_DIR/.env" + +if [[ ! -f "$ENV_FILE" ]]; then + cat > "$ENV_FILE" << 'ENVEOF' +# GUARDiA Docker 환경 설정 +# 운영 배포 전 반드시 아래 값을 변경하세요! + +SECRET_KEY=CHANGE_THIS_TO_RANDOM_32CHARS_MIN +POSTGRES_PASSWORD=guardia_secure_pw +GUARDIA_VERSION=latest +GUARDIA_LLM_MODEL=llama3.1:8b + +# Ollama 모델 경로 (폐쇄망 복사 경로) +OLLAMA_MODELS_PATH=/var/lib/guardia/ollama +ENVEOF + warn ".env 파일 생성됨 — SECRET_KEY를 반드시 변경하세요: $ENV_FILE" +else + info ".env 파일 이미 존재" +fi + +# ── 5. 서비스 시작 ────────────────────────────────────── +echo "" +echo "[4/4] 서비스 준비 완료" + +if [[ "$START_AFTER" == "--start" ]]; then + echo " 서비스 시작 중..." + cd "$SCRIPT_DIR" + docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d + + # 헬스체크 대기 + echo " 서비스 기동 대기 (최대 60초)..." + for i in $(seq 1 12); do + sleep 5 + if curl -sf http://localhost/ &>/dev/null; then + ok "GUARDiA ITSM 서비스 정상 기동!" + break + fi + echo " 대기 중... (${i}/12)" + done + + echo "" + echo " 서비스 상태:" + docker compose ps +else + echo "" + info "서비스 시작 명령어:" + info " cd $SCRIPT_DIR" + info " # .env 파일 편집 후:" + info " docker compose up -d" + info " # 또는 운영 모드:" + info " docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d" + info "" + info " # 즉시 시작:" + info " bash docker_load.sh --start" +fi + +echo "" +echo "==================================================" +ok "Docker 로드 완료!" +info "접속 URL: http://$(hostname -I | awk '{print $1}')" +info "GUARDiA 로그: docker compose logs -f guardia" +echo "==================================================" diff --git a/setup/docker_package.sh b/setup/docker_package.sh new file mode 100644 index 00000000..e356825e --- /dev/null +++ b/setup/docker_package.sh @@ -0,0 +1,147 @@ +#!/bin/bash +# ============================================================ +# GUARDiA Docker 폐쇄망 패키지 생성 스크립트 +# ============================================================ +# 인터넷이 연결된 서버에서 실행하여 모든 이미지를 tar로 패키징합니다. +# 생성된 패키지를 USB/내부 서버를 통해 폐쇄망에 복사 후 docker_load.sh 실행. +# +# 사용법: +# bash setup/docker_package.sh [출력 디렉토리] +# bash setup/docker_package.sh /mnt/usb/guardia-docker +# +# 결과: +# guardia-docker-<버전>.tar.gz (약 5~8GB — Ollama 모델 별도) +# ============================================================ + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +GUARDIA_ROOT="$(dirname "$SCRIPT_DIR")" +OUTPUT_DIR="${1:-$GUARDIA_ROOT/docker-package}" +VERSION="${GUARDIA_VERSION:-$(date +%Y%m%d)}" +PACKAGE_NAME="guardia-docker-${VERSION}" + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +info() { echo -e " $*"; } + +echo "==================================================" +echo " GUARDiA Docker 패키지 생성" +echo " 버전: $VERSION" +echo " 출력: $OUTPUT_DIR" +echo "==================================================" + +mkdir -p "$OUTPUT_DIR/$PACKAGE_NAME" +cd "$GUARDIA_ROOT" + +# ── 1. GUARDiA 이미지 빌드 ─────────────────────────────── +echo "" +echo "[1/5] GUARDiA ITSM 이미지 빌드..." +docker build -t "guardia-itsm:${VERSION}" -t "guardia-itsm:latest" . +ok "guardia-itsm:${VERSION} 빌드 완료" + +# ── 2. 외부 이미지 Pull ────────────────────────────────── +echo "" +echo "[2/5] 외부 이미지 다운로드..." + +IMAGES=( + "postgres:15-alpine" + "redis:7-alpine" + "nginx:alpine" + "ollama/ollama:latest" + "tomcat:9.0-jdk17-temurin-jammy" +) + +for img in "${IMAGES[@]}"; do + echo " pulling: $img" + docker pull "$img" || warn "$img 다운로드 실패 — 건너뜀" +done +ok "외부 이미지 다운로드 완료" + +# ── 3. 이미지 tar 저장 ────────────────────────────────── +echo "" +echo "[3/5] 이미지 저장 (tar)..." +TAR_FILE="$OUTPUT_DIR/$PACKAGE_NAME/images.tar" + +docker save \ + "guardia-itsm:${VERSION}" \ + "guardia-itsm:latest" \ + "postgres:15-alpine" \ + "redis:7-alpine" \ + "nginx:alpine" \ + "tomcat:9.0-jdk17-temurin-jammy" \ + | gzip > "${TAR_FILE}.gz" + +ok "이미지 저장 완료: ${TAR_FILE}.gz ($(du -sh "${TAR_FILE}.gz" | cut -f1))" + +# Ollama는 용량이 크므로 별도 패키징 +OLLAMA_TAR="$OUTPUT_DIR/$PACKAGE_NAME/ollama.tar" +docker save "ollama/ollama:latest" | gzip > "${OLLAMA_TAR}.gz" +ok "Ollama 이미지 저장: ${OLLAMA_TAR}.gz ($(du -sh "${OLLAMA_TAR}.gz" | cut -f1))" + +# ── 4. Ollama 모델 패키징 (선택) ────────────────────────── +echo "" +echo "[4/5] Ollama 모델 패키징..." +OLLAMA_MODELS_DIR="${OLLAMA_MODELS_DIR:-$HOME/.ollama}" +MODELS_OUTPUT="$OUTPUT_DIR/$PACKAGE_NAME/ollama-models" + +if [[ -d "$OLLAMA_MODELS_DIR/models" ]]; then + echo " 모델 디렉토리: $OLLAMA_MODELS_DIR" + mkdir -p "$MODELS_OUTPUT" + + # 각 모델을 개별 파일로 저장 (크기 분할) + if command -v ollama &>/dev/null; then + ollama list 2>/dev/null | tail -n +2 | awk '{print $1}' | while read -r model; do + model_file="${model//:/--}.tar" + echo " 패키징 모델: $model → $model_file" + # 모델 블obs 복사 + mkdir -p "$MODELS_OUTPUT/$model_file" + done + fi + + # 전체 모델 디렉토리 압축 + tar -czf "$MODELS_OUTPUT/models.tar.gz" -C "$OLLAMA_MODELS_DIR" models manifests 2>/dev/null \ + && ok "Ollama 모델 패키징 완료 ($(du -sh "$MODELS_OUTPUT/models.tar.gz" | cut -f1))" \ + || warn "모델 패키징 실패 — 수동으로 ~/.ollama/models 를 복사하세요" +else + warn "Ollama 모델 디렉토리 없음 — 별도 복사 필요: $OLLAMA_MODELS_DIR" + echo " # 모델을 미리 다운로드: ollama pull llama3.1:8b" +fi + +# ── 5. 배포 파일 패키징 ────────────────────────────────── +echo "" +echo "[5/5] 배포 파일 패키징..." + +# docker-compose 파일 복사 +cp "$GUARDIA_ROOT/docker-compose.yml" "$OUTPUT_DIR/$PACKAGE_NAME/" +cp "$GUARDIA_ROOT/docker-compose.prod.yml" "$OUTPUT_DIR/$PACKAGE_NAME/" +cp "$GUARDIA_ROOT/docker-compose.gpu.yml" "$OUTPUT_DIR/$PACKAGE_NAME/" +cp "$GUARDIA_ROOT/setup/docker_load.sh" "$OUTPUT_DIR/$PACKAGE_NAME/" +cp -r "$GUARDIA_ROOT/docker/" "$OUTPUT_DIR/$PACKAGE_NAME/docker/" +chmod +x "$OUTPUT_DIR/$PACKAGE_NAME/docker_load.sh" + +# 버전 정보 +cat > "$OUTPUT_DIR/$PACKAGE_NAME/VERSION" << VEREOF +GUARDIA_VERSION=$VERSION +BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) +IMAGES_INCLUDED=guardia-itsm,postgres,redis,nginx,tomcat,ollama +VEREOF + +# 전체 패키지 압축 +echo " 전체 패키지 압축 중..." +cd "$OUTPUT_DIR" +tar -czf "${PACKAGE_NAME}.tar.gz" "$PACKAGE_NAME/" +ok "패키지 생성 완료: $OUTPUT_DIR/${PACKAGE_NAME}.tar.gz" + +echo "" +echo "==================================================" +ok "패키지 생성 완료!" +echo "" +info "패키지 크기:" +du -sh "$OUTPUT_DIR/${PACKAGE_NAME}.tar.gz" +echo "" +info "폐쇄망 설치 절차:" +info " 1. ${PACKAGE_NAME}.tar.gz 을 폐쇄망 서버로 복사" +info " 2. tar -xzf ${PACKAGE_NAME}.tar.gz" +info " 3. bash ${PACKAGE_NAME}/docker_load.sh" +echo "=================================================="