#!/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)
# =============================================================
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"
# ── 테스트 모드 ──────────────────────────────────────────────
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 "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] 시스템 패키지 업데이트..."
apt-get update -qq
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 설정 ─────────────────────────────────────
echo ""
echo "[4/10] PostgreSQL 설정..."
systemctl start postgresql
systemctl enable postgresql
sudo -u postgres psql -tc "SELECT 1 FROM pg_user WHERE usename='guardia'" | grep -q 1 || \
sudo -u postgres psql -c "CREATE USER guardia WITH PASSWORD 'guardia_secure_pw';"
sudo -u postgres psql -tc "SELECT 1 FROM pg_database WHERE datname='guardia'" | grep -q 1 || \
sudo -u postgres psql -c "CREATE DATABASE guardia OWNER guardia;"
ok "PostgreSQL 설정 완료"
# ── 5. 환경 설정 파일 ─────────────────────────────────────
echo ""
echo "[5/10] 환경 설정 파일 생성..."
ENV_FILE="$GUARDIA_ROOT/itsm/.env"
if [[ ! -f "$ENV_FILE" ]]; then
cat > "$ENV_FILE" << 'ENVEOF'
DATABASE_URL=postgresql+asyncpg://guardia:guardia_secure_pw@localhost:5432/guardia
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
ENVEOF
warn ".env 생성됨 — SECRET_KEY를 변경하세요: $ENV_FILE"
else
info ".env 파일 이미 존재 — 건너뜀"
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/13] 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 "[10/13] 보안·운영 도구 (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 "[11/13] 방화벽 설정..."
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 "[12/13] 서비스 상태 확인..."
for svc in tomcat9 ollama guardia-itsm nginx postgresql redis-server fail2ban chrony; 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 "=================================================="