File size: 12,274 Bytes
9344f01 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | #!/usr/bin/env bash
# KPAA 백엔드 + Open WebUI 통합 관리 스크립트.
#
# 사용법:
# ./manage.sh start KPAA + Open WebUI 백그라운드 기동, ready 대기까지
# ./manage.sh stop 양쪽 모두 정확한 종료 (PID 파일 기반)
# ./manage.sh restart stop → start
# ./manage.sh status 실행 여부 + PID + 포트 (양쪽)
# ./manage.sh logs KPAA 로그 tail
# ./manage.sh logs-owui Open WebUI 로그 tail
#
# 환경변수 (KPAA):
# KPAA_HOST 기본 127.0.0.1
# KPAA_PORT 기본 8000
# KPAA_LOG_FILE 기본 /tmp/kpaa_serve.log
# KPAA_PID_FILE 기본 ./.run/kpaa.pid
# KPAA_READY_TIMEOUT 기본 90 (초)
#
# 환경변수 (Open WebUI):
# KPAA_OPENWEBUI_ENABLED 기본 1 (0 으로 비활성화)
# KPAA_OPENWEBUI_BIN 기본 ~/.kpaa-owui/bin/open-webui (PATH 폴백)
# KPAA_OPENWEBUI_HOST 기본 127.0.0.1
# KPAA_OPENWEBUI_PORT 기본 8080
# KPAA_OPENWEBUI_LOG_FILE 기본 /tmp/kpaa_openwebui.log
# KPAA_OPENWEBUI_READY_TIMEOUT 기본 120 (초, 첫 부팅 시 모델 다운로드 등 변수)
# KPAA_OPENWEBUI_OPENAI_BASE_URLS 기본 http://${KPAA_HOST}:${KPAA_PORT}/v1
# (`;` 구분으로 다중 endpoint 가능)
# KPAA_OPENWEBUI_OPENAI_KEYS 기본 local
# KPAA_OPENWEBUI_DEFAULT_MODELS 기본 kpaa-privacy-ko (UI 첫 default 모델)
# KPAA_OPENWEBUI_NAME 기본 "KPAA — 개인정보보호법 상담"
# KPAA_OPENWEBUI_WEBUI_AUTH 기본 false — true 로 두면 본인 이메일/비밀번호 가입·로그인.
# 기본은 admin@localhost/admin 자동 생성·로그인 (로컬 전용).
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR"
# ── KPAA ─────────────────────────────────────────────────────────────
HOST="${KPAA_HOST:-127.0.0.1}"
PORT="${KPAA_PORT:-8000}"
LOG_FILE="${KPAA_LOG_FILE:-/tmp/kpaa_serve.log}"
PID_DIR="${ROOT_DIR}/.run"
PID_FILE="${KPAA_PID_FILE:-${PID_DIR}/kpaa.pid}"
VENV_BIN="${ROOT_DIR}/.venv/bin"
HEALTH_URL="http://${HOST}:${PORT}/v1/models"
READY_TIMEOUT_S="${KPAA_READY_TIMEOUT:-90}"
# ── Open WebUI ────────────────────────────────────────────────────────
OWUI_ENABLED="${KPAA_OPENWEBUI_ENABLED:-1}"
OWUI_BIN_DEFAULT="${HOME}/.kpaa-owui/bin/open-webui"
OWUI_BIN="${KPAA_OPENWEBUI_BIN:-$OWUI_BIN_DEFAULT}"
if [[ ! -x "$OWUI_BIN" ]] && command -v open-webui >/dev/null 2>&1; then
OWUI_BIN="$(command -v open-webui)"
fi
OWUI_HOST="${KPAA_OPENWEBUI_HOST:-127.0.0.1}"
OWUI_PORT="${KPAA_OPENWEBUI_PORT:-8080}"
OWUI_LOG_FILE="${KPAA_OPENWEBUI_LOG_FILE:-/tmp/kpaa_openwebui.log}"
OWUI_PID_FILE="${PID_DIR}/openwebui.pid"
OWUI_HEALTH_URL="http://${OWUI_HOST}:${OWUI_PORT}/health"
OWUI_READY_TIMEOUT_S="${KPAA_OPENWEBUI_READY_TIMEOUT:-120}"
C_GREEN='\033[0;32m'; C_YELLOW='\033[0;33m'; C_RED='\033[0;31m'; C_RESET='\033[0m'
ok() { printf "${C_GREEN}✓${C_RESET} %s\n" "$*"; }
warn() { printf "${C_YELLOW}!${C_RESET} %s\n" "$*"; }
err() { printf "${C_RED}✗${C_RESET} %s\n" "$*" >&2; }
# ── 공통 PID 헬퍼 ────────────────────────────────────────────────────
is_alive() {
local pid="$1"
[[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null
}
read_pid() {
local f="$1"
[[ -f "$f" ]] && cat "$f" 2>/dev/null || true
}
current_pid() {
# PID 파일 우선. 없거나 죽었으면 빈값. 포트로 *추론하지 않음* — Chrome 등
# 무관한 클라이언트 프로세스를 잘못 죽이는 사고 방지.
local pid; pid="$(read_pid "$1")"
if is_alive "$pid"; then
echo "$pid"
else
echo ""
fi
}
wait_health() {
local url="$1" timeout="$2" label="$3"
local deadline=$(( $(date +%s) + timeout ))
while (( $(date +%s) < deadline )); do
if curl -fsS --max-time 2 "$url" > /dev/null 2>&1; then
return 0
fi
sleep 1
done
return 1
}
# ── KPAA 시작 ────────────────────────────────────────────────────────
start_kpaa() {
local existing; existing="$(current_pid "$PID_FILE")"
if [[ -n "$existing" ]]; then
warn "[kpaa] 이미 기동 중 (pid=$existing)"
return 0
fi
if [[ ! -x "${VENV_BIN}/kpaa" ]]; then
err "${VENV_BIN}/kpaa 가 없습니다. 가상환경을 먼저 활성화/설치하세요."
return 1
fi
if lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN >/dev/null 2>&1; then
err "[kpaa] 포트 ${PORT} 이미 점유."
lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN >&2
return 1
fi
mkdir -p "$PID_DIR"
: > "$LOG_FILE"
nohup "${VENV_BIN}/kpaa" serve --host "$HOST" --port "$PORT" \
>> "$LOG_FILE" 2>&1 &
local pid=$!
echo "$pid" > "$PID_FILE"
ok "[kpaa] 기동 시도 (pid=$pid, log=$LOG_FILE)"
if wait_health "$HEALTH_URL" "$READY_TIMEOUT_S" "kpaa"; then
ok "[kpaa] 준비 완료 — ${HEALTH_URL}"
return 0
fi
err "[kpaa] ${READY_TIMEOUT_S}초 내 ready 응답 없음. tail -n 80 $LOG_FILE"
return 1
}
# ── Open WebUI 시작 ──────────────────────────────────────────────────
start_owui() {
if [[ "$OWUI_ENABLED" != "1" ]]; then
warn "[owui] 비활성화 (KPAA_OPENWEBUI_ENABLED=$OWUI_ENABLED) — 건너뜀"
return 0
fi
if [[ ! -x "$OWUI_BIN" ]]; then
warn "[owui] 실행 파일을 찾지 못함: $OWUI_BIN"
warn " 설치: pip install open-webui (전용 venv 권장: ${OWUI_BIN_DEFAULT%/bin/*})"
warn " 이 기능 영구 비활성화: export KPAA_OPENWEBUI_ENABLED=0"
return 0
fi
local existing; existing="$(current_pid "$OWUI_PID_FILE")"
if [[ -n "$existing" ]]; then
warn "[owui] 이미 기동 중 (pid=$existing)"
return 0
fi
if lsof -nP -iTCP:"${OWUI_PORT}" -sTCP:LISTEN >/dev/null 2>&1; then
err "[owui] 포트 ${OWUI_PORT} 이미 점유."
lsof -nP -iTCP:"${OWUI_PORT}" -sTCP:LISTEN >&2
return 1
fi
mkdir -p "$PID_DIR"
: > "$OWUI_LOG_FILE"
# OpenWebUI 가 KPAA backend 의 OpenAI 호환 endpoint 를 자동으로 부르도록
# 환경변수 주입 — docker-compose.yml 의 동일 패턴.
#
# KPAA 는 llama-cpp-python 임베드 단일 경로라, OpenWebUI 의 외부 추론 데몬
# 자동 감지(`ENABLE_OLLAMA_API`)는 항상 차단 — KPAA 백엔드만 사용.
local owui_envs=()
owui_envs+=("OPENAI_API_BASE_URLS=${KPAA_OPENWEBUI_OPENAI_BASE_URLS:-http://${HOST}:${PORT}/v1}")
owui_envs+=("OPENAI_API_KEYS=${KPAA_OPENWEBUI_OPENAI_KEYS:-local}")
owui_envs+=("DEFAULT_MODELS=${KPAA_OPENWEBUI_DEFAULT_MODELS:-kpaa-privacy-ko}")
owui_envs+=("WEBUI_NAME=${KPAA_OPENWEBUI_NAME:-KPAA — 개인정보보호법 상담}")
owui_envs+=("ENABLE_OLLAMA_API=false")
# 로컬 단일 사용자 — 기본 false 시 OpenWebUI 가 admin@localhost/admin 자동 생성·로그인.
# 본인 이메일/비밀번호로 운영하려면 export KPAA_OPENWEBUI_WEBUI_AUTH=true 후 재시작.
# 자세한 내용 README "🔐 인증 모드" 참고.
owui_envs+=("WEBUI_AUTH=${KPAA_OPENWEBUI_WEBUI_AUTH:-false}")
# `open-webui serve` 인자: --host / --port 지원. 0.5+ 기준.
# `env -S` 대신 nohup 앞에 환경변수 prefix — bash 표준 호환.
nohup env "${owui_envs[@]}" "$OWUI_BIN" serve --host "$OWUI_HOST" --port "$OWUI_PORT" \
>> "$OWUI_LOG_FILE" 2>&1 &
local pid=$!
echo "$pid" > "$OWUI_PID_FILE"
ok "[owui] 기동 시도 (pid=$pid, log=$OWUI_LOG_FILE)"
ok " OPENAI_API_BASE_URLS=${KPAA_OPENWEBUI_OPENAI_BASE_URLS:-http://${HOST}:${PORT}/v1}"
if wait_health "$OWUI_HEALTH_URL" "$OWUI_READY_TIMEOUT_S" "owui"; then
ok "[owui] 준비 완료 — ${OWUI_HEALTH_URL}"
return 0
fi
warn "[owui] ${OWUI_READY_TIMEOUT_S}초 내 ready 응답 없음. tail -n 80 $OWUI_LOG_FILE"
warn " KPAA backend 는 정상 동작 — OpenAI-호환 API 직접 호출 가능: http://${HOST}:${PORT}/v1"
return 0 # owui 실패는 *전체 start 실패* 로 보지 않음 — KPAA 는 동작
}
# ── 종료 헬퍼 ────────────────────────────────────────────────────────
stop_one() {
local label="$1" pid_file="$2"
local pid; pid="$(current_pid "$pid_file")"
if [[ -z "$pid" ]]; then
warn "[$label] 실행 중 아님"
[[ -f "$pid_file" ]] && rm -f "$pid_file"
return 0
fi
ok "[$label] SIGTERM (pid=$pid)"
kill "$pid" 2>/dev/null || true
local deadline=$(( $(date +%s) + 15 ))
while is_alive "$pid" && (( $(date +%s) < deadline )); do
sleep 1
done
if is_alive "$pid"; then
warn "[$label] SIGTERM 무응답. SIGKILL."
kill -9 "$pid" 2>/dev/null || true
sleep 1
fi
if is_alive "$pid"; then
err "[$label] 여전히 살아있음 (pid=$pid). 수동 점검 필요."
return 1
fi
rm -f "$pid_file"
ok "[$label] 종료 완료"
}
# ── 통합 명령 ────────────────────────────────────────────────────────
cmd_start() {
start_kpaa || return 1
start_owui || true # owui 실패가 KPAA 부팅을 막지 않게
}
cmd_stop() {
# 종료 순서: owui 먼저 (자식 클라이언트 → 백엔드 순으로 깨끗)
stop_one owui "$OWUI_PID_FILE" || true
stop_one kpaa "$PID_FILE" || true
}
cmd_restart() {
cmd_stop || true
cmd_start
}
cmd_status() {
local kpid opid
kpid="$(current_pid "$PID_FILE")"
opid="$(current_pid "$OWUI_PID_FILE")"
if [[ -n "$kpid" ]]; then
ok "[kpaa] RUNNING (pid=$kpid, port=$PORT)"
if curl -fsS --max-time 2 "$HEALTH_URL" >/dev/null 2>&1; then
ok "[kpaa] 헬스체크 OK — $HEALTH_URL"
else
warn "[kpaa] 프로세스 alive 이나 헬스체크 미응답 (부팅 중일 수 있음)"
fi
else
warn "[kpaa] STOPPED"
if lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN >/dev/null 2>&1; then
warn " 다만 포트 ${PORT} 다른 프로세스가 점유:"
lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN
fi
fi
if [[ "$OWUI_ENABLED" != "1" ]]; then
warn "[owui] 비활성화 (KPAA_OPENWEBUI_ENABLED=$OWUI_ENABLED)"
elif [[ -n "$opid" ]]; then
ok "[owui] RUNNING (pid=$opid, port=$OWUI_PORT)"
if curl -fsS --max-time 2 "$OWUI_HEALTH_URL" >/dev/null 2>&1; then
ok "[owui] 헬스체크 OK — $OWUI_HEALTH_URL"
else
warn "[owui] 프로세스 alive 이나 헬스체크 미응답 (부팅 중일 수 있음)"
fi
else
warn "[owui] STOPPED"
if lsof -nP -iTCP:"${OWUI_PORT}" -sTCP:LISTEN >/dev/null 2>&1; then
warn " 다만 포트 ${OWUI_PORT} 다른 프로세스가 점유:"
lsof -nP -iTCP:"${OWUI_PORT}" -sTCP:LISTEN
fi
fi
echo "logs:"
echo " kpaa : $LOG_FILE"
echo " owui : $OWUI_LOG_FILE"
}
cmd_logs() {
if [[ ! -f "$LOG_FILE" ]]; then
err "로그 파일 없음: $LOG_FILE"
return 1
fi
exec tail -n 200 -f "$LOG_FILE"
}
cmd_logs_owui() {
if [[ ! -f "$OWUI_LOG_FILE" ]]; then
err "로그 파일 없음: $OWUI_LOG_FILE"
return 1
fi
exec tail -n 200 -f "$OWUI_LOG_FILE"
}
usage() {
cat <<EOF
사용법: $(basename "$0") {start|stop|restart|status|logs|logs-owui}
KPAA 백엔드 + Open WebUI 를 함께 관리합니다.
환경변수 (요약):
KPAA_HOST / KPAA_PORT 백엔드 (기본 127.0.0.1:8000)
KPAA_OPENWEBUI_ENABLED 0 으로 두면 OpenWebUI 미동반
KPAA_OPENWEBUI_HOST / KPAA_OPENWEBUI_PORT (기본 127.0.0.1:8080)
KPAA_OPENWEBUI_BIN open-webui 실행 경로 override
EOF
}
case "${1:-}" in
start) cmd_start ;;
stop) cmd_stop ;;
restart) cmd_restart ;;
status) cmd_status ;;
logs) cmd_logs ;;
logs-owui) cmd_logs_owui ;;
""|-h|--help|help) usage ;;
*) usage; exit 2 ;;
esac
|