| #!/usr/bin/env bash |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| set -euo pipefail |
|
|
| ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| cd "$ROOT_DIR" |
|
|
| |
| 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}" |
|
|
| |
| 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; } |
|
|
| |
| 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() { |
| |
| |
| 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 |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| 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" |
|
|
| |
| |
| |
| |
| |
| 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") |
| |
| |
| |
| owui_envs+=("WEBUI_AUTH=${KPAA_OPENWEBUI_WEBUI_AUTH:-false}") |
|
|
| |
| |
| 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 |
| } |
|
|
| |
| 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 |
| } |
|
|
| cmd_stop() { |
| |
| 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 |
|
|