#!/usr/bin/env bash set -euo pipefail ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) STATE_DIR="$ROOT_DIR/state" RUNTIME_ENV="$STATE_DIR/runtime.env" load_runtime_env() { if [ -f "$RUNTIME_ENV" ]; then # shellcheck disable=SC1090 . "$RUNTIME_ENV" fi : "${COURSEWARE_STATE_DIR:=$STATE_DIR}" : "${COURSEWARE_BIND_HOST:=127.0.0.1}" : "${COURSEWARE_URL_HOST:=127.0.0.1}" : "${COURSEWARE_NETRON_PORT:=8338}" : "${COURSEWARE_PROMPTFOO_PORT:=15500}" : "${COURSEWARE_WIKI_PORT:=80}" : "${COURSEWARE_WETTY_PORT:=7681}" : "${COURSEWARE_OLLAMA_MIN_VERSION:=0.12.11}" : "${COURSEWARE_WETTY_BASE_PATH:=/wetty}" : "${NODE_RUNTIME_BIN_DIR:=$COURSEWARE_STATE_DIR/tools/node-runtime/node_modules/node/bin}" : "${NETRON_VENV:=$COURSEWARE_STATE_DIR/venvs/netron}" : "${WETTY_BIN:=$COURSEWARE_STATE_DIR/tools/wetty/node_modules/.bin/wetty}" : "${PROMPTFOO_DIR:=$COURSEWARE_STATE_DIR/lab6}" : "${WIKI_DIR:=$COURSEWARE_STATE_DIR/repos/LLM-Labs}" : "${WIKI_RUNTIME_CONFIG_PATH:=$WIKI_DIR/public/courseware-runtime.json}" : "${COURSEWARE_OLLAMA_BASE_URL:=http://$COURSEWARE_URL_HOST:$COURSEWARE_OLLAMA_PORT}" : "${COURSEWARE_LAB1_LLAMA_MODEL_PATH:=$COURSEWARE_STATE_DIR/models/lab1/Llama-3.2-1B.Q4_K_M.gguf}" : "${COURSEWARE_LAB1_OLLAMA_MODEL_ALIAS:=batiai/gemma4-e2b:q4}" : "${LLAMA_CPP_BIN_DIR:=$COURSEWARE_STATE_DIR/repos/llama.cpp/build/bin}" if [ -n "${OLLAMA_BIN:-}" ] && [[ "$OLLAMA_BIN" != */* ]] && command -v "$OLLAMA_BIN" >/dev/null 2>&1; then OLLAMA_BIN=$(command -v "$OLLAMA_BIN") fi } ensure_runtime_env() { if [ ! -f "$RUNTIME_ENV" ]; then echo "Missing $RUNTIME_ENV. Run ./labctl up first." >&2 exit 1 fi } service_list() { printf '%s\n' \ "ollama" \ "open-webui" \ "netron" \ "chunkviz" \ "embedding-atlas" \ "unsloth" \ "promptfoo" \ "wiki" \ "wetty" } service_pid_file() { printf '%s/run/%s.pid\n' "$STATE_DIR" "${1//-/_}" } service_log_file() { printf '%s/logs/%s.log\n' "$STATE_DIR" "${1//-/_}" } service_port() { case "$1" in ollama) printf '%s\n' "${COURSEWARE_OLLAMA_PORT}" ;; open-webui) printf '%s\n' "${COURSEWARE_OPEN_WEBUI_PORT}" ;; netron) printf '%s\n' "${COURSEWARE_NETRON_PORT}" ;; chunkviz) printf '%s\n' "${COURSEWARE_CHUNKVIZ_PORT}" ;; embedding-atlas) printf '%s\n' "${COURSEWARE_EMBEDDING_ATLAS_PORT}" ;; unsloth) printf '%s\n' "${COURSEWARE_UNSLOTH_PORT}" ;; promptfoo) printf '%s\n' "${COURSEWARE_PROMPTFOO_PORT}" ;; wiki) printf '%s\n' "${COURSEWARE_WIKI_PORT}" ;; wetty) printf '%s\n' "${COURSEWARE_WETTY_PORT}" ;; *) return 1 ;; esac } service_url() { case "$1" in ollama) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_OLLAMA_PORT" ;; open-webui) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_OPEN_WEBUI_PORT" ;; netron) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_NETRON_PORT" ;; chunkviz) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_CHUNKVIZ_PORT" ;; embedding-atlas) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_EMBEDDING_ATLAS_PORT" ;; unsloth) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_UNSLOTH_PORT" ;; promptfoo) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_PROMPTFOO_PORT" ;; wiki) printf 'http://%s:%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_WIKI_PORT" ;; wetty) printf 'http://%s:%s%s\n' "$COURSEWARE_URL_HOST" "$COURSEWARE_WETTY_PORT" "$COURSEWARE_WETTY_BASE_PATH" ;; *) return 1 ;; esac } service_command() { case "$1" in ollama) printf 'exec env OLLAMA_HOST=%s:%s OLLAMA_MODELS="%s" "%s" serve' \ "$COURSEWARE_BIND_HOST" \ "$COURSEWARE_OLLAMA_PORT" \ "$OLLAMA_MODELS_DIR" \ "$OLLAMA_BIN" ;; open-webui) printf 'exec env DATA_DIR="%s" OLLAMA_BASE_URL=http://%s:%s "%s/bin/open-webui" serve --host %s --port %s' \ "$OPEN_WEBUI_DATA_DIR" \ "$COURSEWARE_URL_HOST" \ "$COURSEWARE_OLLAMA_PORT" \ "$OPEN_WEBUI_VENV" \ "$COURSEWARE_BIND_HOST" \ "$COURSEWARE_OPEN_WEBUI_PORT" ;; netron) printf 'exec "%s/bin/netron" --host %s --port %s --verbosity quiet' \ "$NETRON_VENV" \ "$COURSEWARE_BIND_HOST" \ "$COURSEWARE_NETRON_PORT" ;; chunkviz) printf 'cd "%s" && PATH="%s:$PATH" exec "./node_modules/.bin/serve" build -s -n -L -l tcp://%s:%s' \ "$CHUNKVIZ_DIR" \ "$NODE_RUNTIME_BIN_DIR" \ "$COURSEWARE_BIND_HOST" \ "$COURSEWARE_CHUNKVIZ_PORT" ;; embedding-atlas) printf 'exec "%s/bin/embedding-atlas" "%s" --text "Scenario" --host %s --port %s --no-auto-port' \ "$EMBEDDING_ATLAS_VENV" \ "$TTPS_DATASET_PATH" \ "$COURSEWARE_BIND_HOST" \ "$COURSEWARE_EMBEDDING_ATLAS_PORT" ;; unsloth) printf 'exec "%s" studio -H %s -p %s' \ "$UNSLOTH_BIN" \ "$COURSEWARE_BIND_HOST" \ "$COURSEWARE_UNSLOTH_PORT" ;; promptfoo) printf 'cd "%s" && PATH="%s:$PATH" COURSEWARE_BIND_HOST=%s "%s" view . --port %s --no' \ "$PROMPTFOO_DIR" \ "$NODE_RUNTIME_BIN_DIR" \ "$COURSEWARE_BIND_HOST" \ "$PROMPTFOO_BIN" \ "$COURSEWARE_PROMPTFOO_PORT" ;; wiki) printf 'cd "%s" && PATH="%s:$PATH" exec env COURSEWARE_OLLAMA_BASE_URL="%s" COURSEWARE_LAB1_LLAMA_MODEL_PATH="%s" COURSEWARE_LAB1_OLLAMA_MODEL_ALIAS="%s" "./node_modules/.bin/next" start --hostname %s --port %s' \ "$WIKI_DIR" \ "$NODE_RUNTIME_BIN_DIR" \ "$COURSEWARE_OLLAMA_BASE_URL" \ "$COURSEWARE_LAB1_LLAMA_MODEL_PATH" \ "$COURSEWARE_LAB1_OLLAMA_MODEL_ALIAS" \ "$COURSEWARE_BIND_HOST" \ "$COURSEWARE_WIKI_PORT" ;; wetty) printf 'cd "%s" && PATH="%s:$PATH" exec "%s" --host %s --port %s --base %s --allow-iframe --ssh-host 127.0.0.1 --ssh-port 22 --ssh-auth password' \ "$COURSEWARE_ROOT" \ "$NODE_RUNTIME_BIN_DIR" \ "$WETTY_BIN" \ "$COURSEWARE_BIND_HOST" \ "$COURSEWARE_WETTY_PORT" \ "$COURSEWARE_WETTY_BASE_PATH" ;; *) return 1 ;; esac }