#!/bin/bash # ============================================================= # GUARDiA ITSM 설치 스크립트 — Ubuntu 20.04 / 22.04 / 24.04 # ============================================================= # 전제조건: 순수 Ubuntu OS (최소 설치) # 실행 방법: sudo bash setup_ubuntu.sh # 설치 테스트: bash setup_ubuntu.sh --test # 환경변수 (오프라인/폐쇄망 환경): # TOMCAT_VER=9.0.98 : Tomcat 버전 (기본 9.0.98) # TOMCAT_MIRROR=http://... : 내부 미러 URL (기본 apache.org) # TOMCAT_MIRROR=file://$(pwd)/setup/offline/common : 로컬 파일 사용 # OLLAMA_INSTALL=offline : 오프라인 바이너리 설치 # OLLAMA_BIN_PATH=./setup/offline/common/ollama-linux-amd64 # OFFLINE_PKG_DIR=./setup/offline/ubuntu : .deb 패키지 디렉토리 # ============================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" GUARDIA_ROOT="$(dirname "$SCRIPT_DIR")" LOG_FILE="/var/log/guardia_install.log" TEST_MODE="${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 " $*"; } exec > >(tee -a "$LOG_FILE") 2>&1 echo "==================================================" echo " GUARDiA ITSM 설치 — Ubuntu" echo " 시작: $(date)" echo "==================================================" [[ $EUID -eq 0 ]] || fail "root 권한으로 실행하세요: sudo bash $0" # 공통 라이브러리 로드 LIB_DIR="$SCRIPT_DIR/lib" # shellcheck source=setup/lib/db_select.sh source "$LIB_DIR/db_select.sh" # shellcheck source=setup/lib/gitea_setup.sh source "$LIB_DIR/gitea_setup.sh" # ── DB 선택 (설치 전 먼저 물어봄) ──────────────────────────── select_database # ── 테스트 모드 ────────────────────────────────────────────── if [[ "$TEST_MODE" == "--test" ]]; then echo "=== 설치 검증 모드 ===" PASS=0; FAIL=0 check() { local desc="$1"; shift if "$@" &>/dev/null; then ok "$desc"; ((PASS++)) else echo -e "${RED}[FAIL]${NC} $desc"; ((FAIL++)) fi } check "Java 17 (OpenJDK)" java -version check "Python 3.11+" python3.11 --version check "PostgreSQL" pg_isready -q check "Redis" redis-cli ping check "Tomcat 9 서비스" systemctl is-active tomcat9 check "Tomcat HTTP" bash -c 'curl -sf http://localhost:8080/ -o /dev/null' check "Ollama 서비스" systemctl is-active ollama check "Ollama API" bash -c 'curl -sf http://localhost:11434/api/version -o /dev/null' check "Ollama 모델 존재" bash -c 'ollama list 2>/dev/null | grep -v NAME | grep -q .' check "GUARDiA 서비스" systemctl is-active guardia-itsm check "GUARDiA HTTP" bash -c 'curl -sf http://localhost:8001/ -o /dev/null' check "GUARDiA 로그인 API" bash -c 'curl -sf -X POST http://localhost:8001/api/auth/login \ -H "Content-Type: application/json" -d "{\"username\":\"admin\",\"password\":\"1111\"}" -o /dev/null' check "Fail2ban 실행" systemctl is-active fail2ban check "Chrony NTP" chronyc tracking check "Scouter APM API" bash -c 'curl -sf http://localhost:6180/scouter/v1/info/version -o /dev/null' check "Gitea 서비스" systemctl is-active gitea check "Gitea HTTP" bash -c 'curl -sf http://localhost:3000/api/v1/version -o /dev/null' check "Nginx 설정" nginx -t check "Python UTF-8 인코딩" bash -c 'PYTHONIOENCODING=utf-8 python3.11 -c "print(\"OK\")" > /dev/null' echo "" echo "검증 결과: 성공 $PASS / 실패 $FAIL" [[ $FAIL -eq 0 ]] && ok "모든 검사 통과 — GUARDiA ITSM 정상 설치됨" \ || fail "일부 검사 실패 — 로그: $LOG_FILE" exit 0 fi # ── 1. 시스템 패키지 업데이트 ──────────────────────────────── echo "" echo "[1/10] 시스템 패키지 업데이트..." # 오프라인 패키지 디렉토리 사용 여부 확인 OFFLINE_PKG_DIR="${OFFLINE_PKG_DIR:-}" if [[ -n "$OFFLINE_PKG_DIR" && -d "$OFFLINE_PKG_DIR" ]]; then info "오프라인 모드: $OFFLINE_PKG_DIR" dpkg -i "$OFFLINE_PKG_DIR"/*.deb 2>/dev/null || true apt-get install -f -y -qq 2>/dev/null || true ok "오프라인 패키지 설치 완료" else apt-get update -qq fi apt-get install -y -qq \ curl wget git build-essential libssl-dev libffi-dev \ python3.11 python3.11-venv python3.11-dev python3-pip \ openjdk-17-jdk \ postgresql postgresql-contrib \ redis-server nginx supervisor \ unzip jq lsof # JAVA_HOME 설정 JAVA_HOME_PATH=$(update-java-alternatives -l 2>/dev/null | grep java-17 | awk '{print $3}' | head -1) [ -z "$JAVA_HOME_PATH" ] && JAVA_HOME_PATH=$(dirname $(dirname $(readlink -f $(which java)))) cat > /etc/profile.d/java.sh << JHEOF export JAVA_HOME=$JAVA_HOME_PATH export PATH=\$JAVA_HOME/bin:\$PATH JHEOF source /etc/profile.d/java.sh ok "시스템 패키지 + OpenJDK 17 설치 완료" # ── 2. Tomcat 9 설치 ───────────────────────────────────────── echo "" echo "[2/10] Tomcat 9 + OpenJDK 17 설정..." TOMCAT_VER="${TOMCAT_VER:-9.0.98}" TOMCAT_HOME="/app/tomcat" TOMCAT_USER="tomcat" # tomcat 전용 계정 id $TOMCAT_USER &>/dev/null || useradd -r -s /bin/false -d "$TOMCAT_HOME" $TOMCAT_USER if apt-cache show tomcat9 &>/dev/null 2>&1; then # 패키지 관리자 설치 (온라인 환경) apt-get install -y -qq tomcat9 tomcat9-admin # shell_scripts_guide의 /app/tomcat 경로로 심링크 ln -sfn /usr/share/tomcat9 "$TOMCAT_HOME" 2>/dev/null || true ln -sfn /var/log/tomcat9 "$TOMCAT_HOME/logs" 2>/dev/null || true TOMCAT_CONF="/etc/tomcat9" ok "Tomcat 9 패키지 설치 완료" else # 수동 설치 — 오프라인 또는 버전 고정 환경 warn "apt 저장소에 tomcat9 없음 — Tomcat ${TOMCAT_VER} 수동 설치..." TOMCAT_TAR="apache-tomcat-${TOMCAT_VER}.tar.gz" MIRROR="${TOMCAT_MIRROR:-https://archive.apache.org/dist/tomcat/tomcat-9/v${TOMCAT_VER}/bin}" wget -q "$MIRROR/$TOMCAT_TAR" -O /tmp/$TOMCAT_TAR \ || fail "Tomcat 다운로드 실패. 오프라인 환경이면 TOMCAT_MIRROR=http://내부미러/... 로 설정하세요." mkdir -p "$TOMCAT_HOME" tar -xzf /tmp/$TOMCAT_TAR -C "$TOMCAT_HOME" --strip-components=1 chmod +x "$TOMCAT_HOME/bin/"*.sh TOMCAT_CONF="$TOMCAT_HOME/conf" ok "Tomcat ${TOMCAT_VER} 수동 설치 완료: $TOMCAT_HOME" fi # 로그 디렉토리 보장 mkdir -p "$TOMCAT_HOME/logs" "$TOMCAT_HOME/temp" chown -R $TOMCAT_USER:$TOMCAT_USER "$TOMCAT_HOME" 2>/dev/null || true # opsagent 계정 — GUARDiA Manager 원격 제어용 for XML in "${TOMCAT_CONF}/tomcat-users.xml" "$TOMCAT_HOME/conf/tomcat-users.xml"; do if [[ -f "$XML" ]] && ! grep -q "opsagent" "$XML"; then sed -i 's|| \n \n \n|' "$XML" info "opsagent 계정 추가: $XML" break fi done # systemd 서비스 cat > /etc/systemd/system/tomcat9.service << TCEOF [Unit] Description=Apache Tomcat 9 After=network.target [Service] Type=forking User=$TOMCAT_USER Group=$TOMCAT_USER Environment="JAVA_HOME=$JAVA_HOME_PATH" Environment="CATALINA_HOME=$TOMCAT_HOME" Environment="CATALINA_BASE=$TOMCAT_HOME" Environment="CATALINA_PID=$TOMCAT_HOME/temp/tomcat.pid" Environment="CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC" Environment="JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom" ExecStart=$TOMCAT_HOME/bin/startup.sh ExecStop=$TOMCAT_HOME/bin/shutdown.sh Restart=on-failure RestartSec=10 SuccessExitStatus=143 [Install] WantedBy=multi-user.target TCEOF systemctl daemon-reload systemctl enable tomcat9 systemctl start tomcat9 ok "Tomcat 9 서비스 등록 완료 (포트 8080)" # ── 3. Python 가상환경 ────────────────────────────────────── echo "" echo "[3/10] Python 가상환경 설정..." mkdir -p /opt/guardia python3.11 -m venv /opt/guardia/venv source /opt/guardia/venv/bin/activate pip install --upgrade pip -q pip install -r "$GUARDIA_ROOT/itsm/requirements.txt" -q ok "Python 패키지 설치 완료" # ── 4. PostgreSQL 설정 (DB 선택에 따라 조건부 실행) ───────── echo "" echo "[4/10] 데이터베이스 설정..." if [[ "$INSTALL_POSTGRES" == "true" ]]; then systemctl start postgresql systemctl enable postgresql setup_postgres else ok "SQLite 사용 — PostgreSQL 설치 건너뜀" fi # ── 5. 환경 설정 파일 ───────────────────────────────────── echo "" echo "[5/10] 환경 설정 파일 생성..." ENV_FILE="$GUARDIA_ROOT/itsm/.env" if [[ ! -f "$ENV_FILE" ]]; then cat > "$ENV_FILE" << ENVEOF DATABASE_URL=${DATABASE_URL} SECRET_KEY=change_this_secret_key_in_production_min_32chars ALGORITHM=HS256 ACCESS_TOKEN_EXPIRE_MINUTES=480 REDIS_URL=redis://localhost:6379/0 OLLAMA_BASE_URL=http://localhost:11434 GUARDIA_LLM_MODEL=llama3.1:8b MESSENGER_BASE_URL=http://localhost:8002 MESSENGER_OPS_ROOM=ops CATALINA_HOME=/app/tomcat ENABLE_VECTOR=${INSTALL_PGVECTOR:-false} ENVEOF warn ".env 생성됨 — SECRET_KEY를 변경하세요: $ENV_FILE" else # 기존 .env에 DATABASE_URL 업데이트 write_db_env "$ENV_FILE" fi # ── 6. DB 초기화 (스키마 불일치 자동 감지·복구) ───────────────────────── echo "" echo "[6/10] DB 초기화..." cd "$GUARDIA_ROOT/itsm" source /opt/guardia/venv/bin/activate if ss -tlnp 2>/dev/null | grep -q ':8001'; then warn "포트 8001 사용 중 — 기존 프로세스 종료..." fuser -k 8001/tcp 2>/dev/null || true sleep 2 fi PYTHONIOENCODING=utf-8 python tools/db_init.py --force \ && ok "DB 초기화 완료" || fail "DB 초기화 실패 — 로그: $LOG_FILE" # ── 7. systemd 서비스 등록 ─────────────────────────────── echo "" echo "[7/10] GUARDiA ITSM 서비스 등록..." systemctl stop guardia-itsm 2>/dev/null || true cat > /etc/systemd/system/guardia-itsm.service << SVCEOF [Unit] Description=GUARDiA ITSM Server After=network.target postgresql.service redis.service tomcat9.service [Service] Type=exec User=www-data WorkingDirectory=$GUARDIA_ROOT/itsm Environment="PATH=/opt/guardia/venv/bin" Environment="PYTHONIOENCODING=utf-8" Environment="PYTHONUNBUFFERED=1" EnvironmentFile=$GUARDIA_ROOT/itsm/.env ExecStart=/opt/guardia/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8001 --workers 4 Restart=always RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target SVCEOF chown -R www-data:www-data "$GUARDIA_ROOT/itsm" 2>/dev/null || true systemctl daemon-reload systemctl enable guardia-itsm systemctl start guardia-itsm ok "GUARDiA ITSM 서비스 등록 완료" # ── 8. Nginx 리버스 프록시 ─────────────────────────────── echo "" echo "[8/10] Nginx 리버스 프록시 설정..." cat > /etc/nginx/sites-available/guardia << 'NGXEOF' server { listen 80; server_name _; client_max_body_size 100M; location / { proxy_pass http://127.0.0.1:8001; 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; } location /static/ { alias /opt/guardia/static/; expires 1d; } # Tomcat 직접 노출 금지 — GUARDiA를 통해서만 접근 # location /tomcat/ { proxy_pass http://127.0.0.1:8080/; } } NGXEOF ln -sf /etc/nginx/sites-available/guardia /etc/nginx/sites-enabled/guardia rm -f /etc/nginx/sites-enabled/default nginx -t && systemctl reload nginx ok "Nginx 설정 완료" # ── 9. 방화벽 ──────────────────────────────────────────── echo "" echo "[9/14] Scouter APM 서버 설치..." INSTALL_SCOUTER="${INSTALL_SCOUTER:-true}" if [[ "$INSTALL_SCOUTER" == "true" ]]; then SCOUTER_VER="${SCOUTER_VER:-2.20.0}" SCOUTER_HOME="/opt/scouter-server" SCOUTER_PORT="${SCOUTER_PORT:-6100}" # Scouter 서버 다운로드 if [[ ! -d "$SCOUTER_HOME" ]]; then SCOUTER_URL="${SCOUTER_MIRROR:-https://github.com/scouter-project/scouter/releases/download/v${SCOUTER_VER}/scouter-all-${SCOUTER_VER}.tar.gz}" wget -q "$SCOUTER_URL" -O /tmp/scouter-all.tar.gz 2>/dev/null \ || warn "Scouter 다운로드 실패 — Docker 이미지 사용 권장: docker compose up -d scouter-server" if [[ -f /tmp/scouter-all.tar.gz ]]; then mkdir -p "$SCOUTER_HOME" tar -xzf /tmp/scouter-all.tar.gz -C "$SCOUTER_HOME" --strip-components=1 2>/dev/null || true rm -f /tmp/scouter-all.tar.gz fi fi # Scouter 서버 systemd 등록 if [[ -d "$SCOUTER_HOME" ]]; then cat > /etc/systemd/system/scouter-server.service << SCEOF [Unit] Description=Scouter APM Server After=network.target [Service] Type=simple WorkingDirectory=$SCOUTER_HOME ExecStart=/bin/bash $SCOUTER_HOME/startup.sh Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target SCEOF systemctl daemon-reload systemctl enable scouter-server 2>/dev/null systemctl start scouter-server 2>/dev/null || true ok "Scouter APM 서버 등록 완료 (HTTP API: 포트 6180)" else warn "Scouter 미설치 — docker compose up -d scouter-server 사용" fi # 에이전트 JAR 다운로드 SCOUTER_AGENT_DIR="$SCRIPT_DIR/scouter" mkdir -p "$SCOUTER_AGENT_DIR" bash "$SCRIPT_DIR/scouter/download_scouter.sh" 2>/dev/null \ && ok "Scouter 에이전트 다운로드 완료" \ || warn "Scouter 에이전트 다운로드 실패 — 수동 다운로드 필요" else warn "Scouter 설치 건너뜀 (INSTALL_SCOUTER=false)" fi echo "[10/14] Ollama (온프레미스 sLLM 서버) 설치..." # Ollama: GUARDiA AI 기능의 핵심 — 없으면 챗봇/RCA/티켓분류 모두 비작동 OLLAMA_INSTALL="${OLLAMA_INSTALL:-online}" # online | offline | skip OLLAMA_MODELS="${OLLAMA_MODELS:-llama3.1:8b}" if [[ "$OLLAMA_INSTALL" == "skip" ]]; then warn "Ollama 설치 건너뜀 (OLLAMA_INSTALL=skip) — 나중에 수동 설치 필요" else if [[ "$OLLAMA_INSTALL" == "offline" ]]; then # 오프라인: OLLAMA_BIN_PATH 에서 바이너리 복사 BIN_PATH="${OLLAMA_BIN_PATH:-/tmp/ollama}" [[ -f "$BIN_PATH" ]] || fail "오프라인 설치: OLLAMA_BIN_PATH에 바이너리를 놓으세요." cp "$BIN_PATH" /usr/local/bin/ollama && chmod +x /usr/local/bin/ollama ok "Ollama 오프라인 바이너리 설치 완료" else # 온라인: 공식 설치 스크립트 curl -fsSL https://ollama.com/install.sh | sh \ || { warn "Ollama 온라인 설치 실패 — 나중에 수동으로 설치하세요"; OLLAMA_INSTALL=failed; } fi if [[ "$OLLAMA_INSTALL" != "failed" ]]; then # systemd 서비스 활성화 (설치 시 자동 생성됨) systemctl enable ollama 2>/dev/null || true systemctl start ollama 2>/dev/null || true sleep 3 # 모델 다운로드 (온라인 환경만 — 오프라인은 수동으로 ollama pull) if [[ "$OLLAMA_INSTALL" == "online" ]]; then for model in $OLLAMA_MODELS; do info "모델 다운로드: $model (시간이 걸릴 수 있습니다...)" ollama pull "$model" 2>&1 | tail -3 || warn "모델 $model 다운로드 실패 — 나중에 수동 실행: ollama pull $model" done fi ok "Ollama 설치 완료 (http://localhost:11434)" fi fi # ── 10. 보안·운영 도구 설치 ────────────────────────────── echo "" echo "[11/14] 보안·운영 도구 (Fail2ban / NTP / JDK 다중 버전 / Logrotate)..." # Fail2ban — SSH 무차별 대입 방지 apt-get install -y -qq fail2ban cat > /etc/fail2ban/jail.local << 'F2BEOF' [sshd] enabled = true maxretry = 5 bantime = 3600 findtime = 600 F2BEOF systemctl enable fail2ban systemctl start fail2ban ok "Fail2ban 설치 완료 (SSH 5회 실패 시 1시간 차단)" # NTP/Chrony — 감사 로그 타임스탬프 정합성 apt-get install -y -qq chrony systemctl enable chrony systemctl start chrony chronyc makestep 2>/dev/null || true ok "Chrony NTP 설정 완료" # JDK 8 추가 설치 — 레거시 WAS(Tomcat 7/8) 지원용 apt-get install -y -qq openjdk-8-jdk 2>/dev/null || warn "JDK 8 설치 실패 (저장소 없음 — 무시)" apt-get install -y -qq openjdk-11-jdk 2>/dev/null || warn "JDK 11 설치 실패 (저장소 없음 — 무시)" ok "JDK 다중 버전 설치 완료 (java -version 으로 JDK 17 기본 확인, alternatives --config java 로 전환)" # Logrotate — Tomcat catalina.out 무제한 증가 방지 cat > /etc/logrotate.d/tomcat9 << 'LREOF' /app/tomcat/logs/catalina.out { daily rotate 14 compress delaycompress missingok notifempty sharedscripts postrotate /bin/kill -HUP $(cat /app/tomcat/temp/tomcat.pid 2>/dev/null) 2>/dev/null || true endscript } /app/tomcat/logs/*.log { weekly rotate 8 compress missingok notifempty } LREOF ok "Logrotate 설정 완료 (catalina.out 14일 보관)" # ── 11. 방화벽 설정 ────────────────────────────────────── echo "" echo "[12/14] 방화벽 설정..." if command -v ufw &>/dev/null; then ufw allow 22/tcp 2>/dev/null || true ufw allow 80/tcp 2>/dev/null || true ufw allow 443/tcp 2>/dev/null || true # 8080(Tomcat), 11434(Ollama) 외부 직접 노출 차단 ufw --force enable 2>/dev/null || true ok "UFW 방화벽 설정 완료 (22/80/443 허용, 8080/11434 내부 전용)" fi # ── 12. 최종 상태 확인 ─────────────────────────────────── echo "" echo "[13/14] Gitea 설치 및 초기화..." INSTALL_GITEA="${INSTALL_GITEA:-true}" if [[ "$INSTALL_GITEA" == "true" ]]; then install_gitea # 개발자 계정 생성 (환경변수로 지정 가능: GITEA_DEV_USERS="user1 user2") for devuser in ${GITEA_DEV_USERS:-engineer1 engineer2 pm1}; do create_dev_user "$devuser" "Dev@guardia!" "${devuser}@guardia.local" done init_gitea_repos ok "Gitea 설치 + 초기화 완료" else warn "Gitea 설치 건너뜀 (INSTALL_GITEA=false)" fi echo "[14/14] 서비스 상태 확인..." for svc in tomcat9 ollama guardia-itsm nginx postgresql redis-server fail2ban chrony gitea; do if systemctl is-active "$svc" &>/dev/null; then ok "$svc 실행 중" else warn "$svc 미실행 — 수동 확인: systemctl status $svc" fi done # Ollama 모델 목록 출력 if command -v ollama &>/dev/null && systemctl is-active ollama &>/dev/null; then info "Ollama 모델 목록: $(ollama list 2>/dev/null | tail -n +2 | awk '{print $1}' | tr '\n' ' ')" fi echo "" echo "==================================================" ok "GUARDiA ITSM 설치 완료! [13/13 단계]" echo "" info "GUARDiA URL: http://$(hostname -I | awk '{print $1}')" info "Tomcat URL: http://$(hostname -I | awk '{print $1}'):8080 (내부 전용)" info "Ollama URL: http://localhost:11434 (내부 전용)" info "설치 로그: $LOG_FILE" echo "" warn "보안 필수 조치:" warn " 1. $ENV_FILE 의 SECRET_KEY 변경" warn " 2. PostgreSQL 비밀번호 변경" warn " 3. Tomcat opsagent 비밀번호 변경" warn " 4. HTTPS(Let's Encrypt) 설정 권장" echo "" info "Ollama 모델 추가: ollama pull codellama:7b" info "설치 검증: sudo bash $0 --test" echo "=================================================="