scripts/preflight.py (stdlib-only, no psutil):
- Port probing: owned services auto-reassign to next free port; external
services (Ollama) show ✓ reachable / ⚠ not responding
- System resources: CPU cores, RAM (total + available), GPU VRAM via
nvidia-smi; works on Linux + macOS
- Profile recommendation: remote / cpu / single-gpu / dual-gpu
- vLLM KV cache offload: calculates CPU_OFFLOAD_GB when VRAM < 10 GB
free and RAM headroom > 4 GB (uses up to 25% of available headroom)
- Writes resolved values to .env for docker compose; single-service mode
(--service streamlit) for scripted port queries
- Exit 0 unless an owned port genuinely can't be resolved
scripts/manage-ui.sh:
- Calls preflight.py --service streamlit before bind; falls back to
pure-bash port scan if Python/yaml unavailable
compose.yml:
- vllm command: adds --cpu-offload-gb ${CPU_OFFLOAD_GB:-0}
Makefile:
- start / restart depend on preflight target
- PYTHON variable for env portability
- test target uses PYTHON variable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
145 lines
3.8 KiB
Bash
Executable file
145 lines
3.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# scripts/manage-ui.sh — manage the Streamlit job-seeker web UI
|
|
# Usage: bash scripts/manage-ui.sh [start|stop|restart|status|logs]
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
STREAMLIT_BIN="${STREAMLIT_BIN:-streamlit}"
|
|
# Resolve: conda env bin, system PATH, or explicit override
|
|
if [[ "$STREAMLIT_BIN" == "streamlit" ]]; then
|
|
for _candidate in \
|
|
"$(conda run -n job-seeker which streamlit 2>/dev/null)" \
|
|
"$(which streamlit 2>/dev/null)"; do
|
|
if [[ -n "$_candidate" && -x "$_candidate" ]]; then
|
|
STREAMLIT_BIN="$_candidate"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
APP_ENTRY="$REPO_DIR/app/app.py"
|
|
PID_FILE="$REPO_DIR/.streamlit.pid"
|
|
LOG_FILE="$REPO_DIR/.streamlit.log"
|
|
PORT="${STREAMLIT_PORT:-8501}"
|
|
|
|
_resolve_port() {
|
|
# Ask preflight.py for the next free port near the configured port.
|
|
# Falls back to a pure-bash scan if Python/yaml is not available.
|
|
local python_bin
|
|
for python_bin in python3 python; do
|
|
if command -v "$python_bin" &>/dev/null && \
|
|
"$python_bin" -c "import yaml" &>/dev/null 2>&1; then
|
|
local resolved
|
|
resolved=$("$python_bin" "$REPO_DIR/scripts/preflight.py" --service streamlit 2>/dev/null)
|
|
if [[ -n "$resolved" && "$resolved" =~ ^[0-9]+$ ]]; then
|
|
echo "$resolved"; return
|
|
fi
|
|
fi
|
|
done
|
|
# Pure-bash fallback: scan for a free port
|
|
local p="$PORT"
|
|
while (echo >/dev/tcp/127.0.0.1/"$p") 2>/dev/null; do
|
|
((p++))
|
|
[[ $p -gt $((PORT + 20)) ]] && break
|
|
done
|
|
echo "$p"
|
|
}
|
|
|
|
start() {
|
|
if is_running; then
|
|
echo "Already running (PID $(cat "$PID_FILE")). Use 'restart' to reload."
|
|
return 0
|
|
fi
|
|
|
|
PORT=$(_resolve_port)
|
|
if [[ "$PORT" != "${STREAMLIT_PORT:-8501}" ]]; then
|
|
echo "Port ${STREAMLIT_PORT:-8501} in use — using $PORT instead."
|
|
fi
|
|
|
|
echo "Starting Streamlit on http://localhost:$PORT …"
|
|
"$STREAMLIT_BIN" run "$APP_ENTRY" \
|
|
--server.port "$PORT" \
|
|
--server.headless true \
|
|
--server.fileWatcherType none \
|
|
> "$LOG_FILE" 2>&1 &
|
|
echo $! > "$PID_FILE"
|
|
sleep 2
|
|
|
|
if is_running; then
|
|
echo "Started (PID $(cat "$PID_FILE")). Logs: $LOG_FILE"
|
|
else
|
|
echo "Failed to start. Check logs: $LOG_FILE"
|
|
tail -20 "$LOG_FILE"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
stop() {
|
|
if ! is_running; then
|
|
echo "Not running."
|
|
rm -f "$PID_FILE"
|
|
return 0
|
|
fi
|
|
|
|
PID=$(cat "$PID_FILE")
|
|
echo "Stopping PID $PID …"
|
|
kill "$PID" 2>/dev/null || true
|
|
sleep 1
|
|
if kill -0 "$PID" 2>/dev/null; then
|
|
kill -9 "$PID" 2>/dev/null || true
|
|
fi
|
|
rm -f "$PID_FILE"
|
|
echo "Stopped."
|
|
}
|
|
|
|
restart() {
|
|
stop
|
|
sleep 1
|
|
start
|
|
}
|
|
|
|
status() {
|
|
if is_running; then
|
|
echo "Running (PID $(cat "$PID_FILE")) on http://localhost:$PORT"
|
|
else
|
|
echo "Not running."
|
|
fi
|
|
}
|
|
|
|
logs() {
|
|
if [[ -f "$LOG_FILE" ]]; then
|
|
tail -50 "$LOG_FILE"
|
|
else
|
|
echo "No log file found at $LOG_FILE"
|
|
fi
|
|
}
|
|
|
|
is_running() {
|
|
if [[ -f "$PID_FILE" ]]; then
|
|
PID=$(cat "$PID_FILE")
|
|
if kill -0 "$PID" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
CMD="${1:-help}"
|
|
case "$CMD" in
|
|
start) start ;;
|
|
stop) stop ;;
|
|
restart) restart ;;
|
|
status) status ;;
|
|
logs) logs ;;
|
|
*)
|
|
echo "Usage: bash scripts/manage-ui.sh [start|stop|restart|status|logs]"
|
|
echo ""
|
|
echo " start Start the Streamlit UI (default port: $PORT)"
|
|
echo " stop Stop the running UI"
|
|
echo " restart Stop then start"
|
|
echo " status Show whether it's running"
|
|
echo " logs Tail the last 50 lines of the log"
|
|
echo ""
|
|
echo " STREAMLIT_PORT=8502 bash scripts/manage-ui.sh start (custom port)"
|
|
;;
|
|
esac
|