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