# ============================================================== # GUARDiA ITSM 설치 스크립트 — Windows Server 2019/2022 # ============================================================== # 전제조건: 순수 Windows Server OS (PowerShell 5.1+) # 실행 방법: PowerShell -ExecutionPolicy Bypass -File setup_windows.ps1 # 설치 테스트: .\setup_windows.ps1 -Test # ============================================================== param( [switch]$Test = $false ) $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $GuardiaRoot = Split-Path -Parent $ScriptDir $LogFile = "C:\guardia_install.log" function Write-OK { param($msg) Write-Host "[OK] $msg" -ForegroundColor Green } function Write-Warn { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow } function Write-Fail { param($msg) Write-Host "[FAIL] $msg" -ForegroundColor Red; exit 1 } function Write-Info { param($msg) Write-Host " $msg" } Start-Transcript -Path $LogFile -Append | Out-Null Write-Host "==================================================" Write-Host " GUARDiA ITSM 설치 — Windows Server" Write-Host " 시작: $(Get-Date)" Write-Host "==================================================" # ── 테스트 모드 ───────────────────────────────────────────── if ($Test) { Write-Host "=== 설치 검증 모드 ===" $pass = 0; $fail = 0 function Check-Item { param($desc, [scriptblock]$cmd) try { & $cmd | Out-Null Write-OK $desc; $script:pass++ } catch { Write-Host "[FAIL] $desc" -ForegroundColor Red; $script:fail++ } } Check-Item "Python 3.11+" { python --version 2>&1 | Select-String "3\.(1[1-9]|[2-9]\d)" } Check-Item "PostgreSQL" { pg_isready -q } Check-Item "Redis" { redis-cli ping } Check-Item "GUARDiA 포트 8001" { $r = Invoke-WebRequest "http://localhost:8001/api/dashboard/overview" -UseBasicParsing -TimeoutSec 5 if ($r.StatusCode -ne 200) { throw "API 응답 오류" } } Check-Item "NSSM 서비스" { Get-Service "guardia-itsm" -ErrorAction Stop } Check-Item "Nginx" { nginx -t 2>&1 } Write-Host "" Write-Host "검증 결과: 성공 $pass / 실패 $fail" if ($fail -eq 0) { Write-OK "모든 검사 통과 — GUARDiA ITSM 정상 설치됨" } else { Write-Fail "일부 검사 실패 — 로그 확인: $LogFile" } Stop-Transcript | Out-Null exit 0 } # ── 관리자 권한 확인 ──────────────────────────────────────── $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ) if (-not $isAdmin) { Write-Fail "관리자 권한으로 실행하세요 (우클릭 → 관리자로 실행)" } # ── Chocolatey 패키지 관리자 ──────────────────────────────── Write-Host "" Write-Host "[1/8] Chocolatey 패키지 관리자 설치..." if (-not (Get-Command choco -ErrorAction SilentlyContinue)) { Set-ExecutionPolicy Bypass -Scope Process -Force [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") } Write-OK "Chocolatey 준비 완료" # ── 1. 필수 패키지 설치 ────────────────────────────────────── Write-Host "" Write-Host "[2/8] 필수 패키지 설치 (Python, PostgreSQL, Redis, Nginx, NSSM)..." $packages = @( "python --version=3.11.9", "postgresql", "redis-64", "nginx-winssl", "nssm", "git" ) foreach ($pkg in $packages) { $name = $pkg.Split(" ")[0] Write-Host " 설치 중: $name" choco install $pkg -y --no-progress 2>&1 | Out-Null } # PATH 갱신 $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") Write-OK "필수 패키지 설치 완료" # ── 2. Python 가상환경 ────────────────────────────────────── Write-Host "" Write-Host "[3/8] Python 가상환경 설정..." $venvPath = "C:\guardia\venv" if (-not (Test-Path $venvPath)) { python -m venv $venvPath } $pip = "$venvPath\Scripts\pip.exe" & $pip install --upgrade pip -q & $pip install -r "$GuardiaRoot\itsm\requirements.txt" -q Write-OK "Python 패키지 설치 완료" # ── 3. PostgreSQL 초기화 ──────────────────────────────────── Write-Host "" Write-Host "[4/8] PostgreSQL 초기화..." $pgBin = "C:\Program Files\PostgreSQL\16\bin" if (-not (Test-Path $pgBin)) { $pgBin = Get-ChildItem "C:\Program Files\PostgreSQL" -Directory | Sort-Object Name -Descending | Select-Object -First 1 | ForEach-Object { "$($_.FullName)\bin" } } $env:PGPASSWORD = "postgres" # DB/사용자 생성 & "$pgBin\psql.exe" -U postgres -c "CREATE USER guardia WITH PASSWORD 'guardia_secure_pw';" 2>$null & "$pgBin\psql.exe" -U postgres -c "CREATE DATABASE guardia OWNER guardia;" 2>$null Write-OK "PostgreSQL 설정 완료" # ── 4. Redis 서비스 등록 ──────────────────────────────────── Write-Host "" Write-Host "[5/8] Redis 서비스 등록..." $redisExe = (Get-Command redis-server -ErrorAction SilentlyContinue)?.Source if ($redisExe) { nssm install redis-server $redisExe 2>$null nssm start redis-server 2>$null } Write-OK "Redis 완료" # ── 5. 환경 설정 파일 ─────────────────────────────────────── Write-Host "" Write-Host "[6/8] 환경 설정 파일 생성..." $envFile = "$GuardiaRoot\itsm\.env" if (-not (Test-Path $envFile)) { @" 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 "@ | Out-File -FilePath $envFile -Encoding utf8 Write-Warn ".env 생성됨 — SECRET_KEY를 변경하세요: $envFile" } # ── 6. DB 초기화 ──────────────────────────────────────────── Write-Host "" Write-Host "[7/8] DB 초기화..." Push-Location "$GuardiaRoot\itsm" & "$venvPath\Scripts\python.exe" -c @" import asyncio, sys, os sys.path.insert(0, '.') os.chdir(r'$GuardiaRoot\itsm') from dotenv import load_dotenv; load_dotenv('.env') from database import init_db asyncio.run(init_db()) print('DB OK') "@ Pop-Location Write-OK "DB 초기화 완료" # ── 7. NSSM Windows 서비스 등록 ───────────────────────────── Write-Host "" Write-Host "[8/8] Windows 서비스 등록 (NSSM)..." $uvicorn = "$venvPath\Scripts\uvicorn.exe" $svcName = "guardia-itsm" # 기존 서비스 제거 후 재등록 nssm stop $svcName 2>$null nssm remove $svcName confirm 2>$null nssm install $svcName $uvicorn nssm set $svcName AppParameters "main:app --host 0.0.0.0 --port 8001 --workers 4" nssm set $svcName AppDirectory "$GuardiaRoot\itsm" nssm set $svcName AppEnvironmentExtra "PATH=$venvPath\Scripts;$env:PATH" nssm set $svcName Start SERVICE_AUTO_START nssm set $svcName AppStdout "C:\guardia\logs\guardia-itsm.log" nssm set $svcName AppStderr "C:\guardia\logs\guardia-itsm-err.log" New-Item -ItemType Directory -Force "C:\guardia\logs" | Out-Null nssm start $svcName Write-OK "Windows 서비스 등록 완료" # ── Nginx 리버스 프록시 ────────────────────────────────────── $nginxConf = "C:\tools\nginx-winssl\conf\conf.d\guardia.conf" New-Item -ItemType Directory -Force (Split-Path $nginxConf) | Out-Null @" 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_read_timeout 300s; } } "@ | Out-File -FilePath $nginxConf -Encoding utf8 # ── 방화벽 규칙 ────────────────────────────────────────────── New-NetFirewallRule -DisplayName "GUARDiA HTTP" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow 2>$null New-NetFirewallRule -DisplayName "GUARDiA HTTPS" -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow 2>$null Write-OK "방화벽 규칙 추가 완료" Write-Host "" Write-Host "==================================================" Write-OK "GUARDiA ITSM 설치 완료 — Windows Server" Write-Host "" Write-Info "접속 URL: http://$(Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias 'Ethernet*' | Select-Object -First 1 | ForEach-Object { $_.IPAddress })" Write-Info "설치 로그: $LogFile" Write-Info "서비스 상태: Get-Service guardia-itsm" Write-Info "검증: .\setup_windows.ps1 -Test" Write-Host "" Write-Warn "보안 필수 조치:" Write-Warn " 1. $envFile 의 SECRET_KEY 변경" Write-Warn " 2. PostgreSQL postgres 계정 비밀번호 변경" Write-Host "==================================================" Stop-Transcript | Out-Null