kpaa / manage.sh
scvcoder's picture
Cleanup: dead code, route deletion (/info, /chat, /api/*), comment polish, auth mode docs, URL rename
9344f01 verified
#!/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