uapb/ai
0
1#!/usr/bin/env bash2#3# run-jem.sh — bring the whole Jem stack up on localhost.4#5# ollama :11434 serves llama3.2:3b, the model that actually answers6# jem-agent :5000 Flask; persona, session memory, SSE streaming7# site :8002 FastAPI; serves web/index.html and proxies /chat*8#9# Ollama is a rootless install under ~/.local/ollama (this box has no10# passwordless sudo, so there is no systemd unit to start). Everything here is11# a plain user process, so a WSL restart takes the whole stack down — just run12# this again. Logs land in logs/ at the repo root (already gitignored).13#14# Usage: ./scripts/run-jem.sh start anything not already running15# ./scripts/run-jem.sh stop stop the agent and the site16# ./scripts/run-jem.sh restart stop them, then start again — use this17# after editing jem_agent/app.py, since a18# running agent holds the old persona in19# memory20#21set -uo pipefail22# Guarded: everything below (pkill, log writes) assumes the repo root, so a23# failed cd must stop the script rather than act on some other directory.24cd "$(dirname "$0")/.." || exit 125mkdir -p logs26 27export PATH="$HOME/.local/bin:$PATH"28OLLAMA_BIN="$HOME/.local/ollama/bin/ollama"29PORT="${PORT:-8002}"30AGENT_PORT="${JEM_AGENT_PORT:-5000}"31 32stop_servers () {33 # Match on the listening port rather than the command line: the servers are34 # started with setsid and `pkill -f` has proved unreliable at matching them.35 for p in "$PORT" "$AGENT_PORT"; do36 local pids37 pids=$(ss -ltnpH "sport = :$p" 2>/dev/null | grep -oP 'pid=\K[0-9]+' | sort -u)38 [[ -n "$pids" ]] && kill $pids 2>/dev/null && echo " stopped whatever held :$p"39 done40 pkill -f "uvicorn server.main:app" 2>/dev/null41 pkill -f "flask --app jem_agent.app" 2>/dev/null42 pkill -f "flask --app app run" 2>/dev/null43 sleep 144}45 46if [[ "${1:-}" == "stop" ]]; then47 stop_servers48 echo "(ollama left running — pkill -f 'ollama serve' to stop it too)"49 exit 050fi51 52if [[ "${1:-}" == "restart" ]]; then53 echo "▶ stopping"54 stop_servers55fi56 57# setsid detaches each server from this shell's session and process group, so58# closing the terminal does not take them with it.59#60# It is NOT what keeps the stack alive, and an earlier version of this comment61# wrongly claimed WSL tears the distro down when the launching command returns.62# It does not — the distro was measured up for 4h58m with every server dead. The63# real killers were the OOM killer and the absence of any supervisor. Use64# ./install-services.sh for anything long-lived; this script is for quick tests.65spawn () { # spawn <logfile> <command...>66 local log=$1; shift67 setsid "$@" > "$log" 2>&1 < /dev/null &68 disown 2>/dev/null || true69}70 71wait_for () { # wait_for <url> <label> <tries>72 local url=$1 label=$2 tries=${3:-60}73 for ((i = 0; i < tries; i++)); do74 curl -sf -m 2 "$url" > /dev/null 2>&1 && { echo " ✅ $label"; return 0; }75 sleep 176 done77 echo " ❌ $label did not come up"78 return 179}80 81# ── 1. ollama ────────────────────────────────────────────────────────────────82echo "▶ ollama"83if curl -sf -m 2 http://localhost:11434/api/tags > /dev/null 2>&1; then84 echo " ✅ already serving"85else86 [[ -x "$OLLAMA_BIN" ]] || { echo " ❌ $OLLAMA_BIN missing — reinstall ollama"; exit 1; }87 spawn logs/ollama.log "$OLLAMA_BIN" serve88 wait_for http://localhost:11434/api/tags "serving on :11434" || { tail -20 logs/ollama.log; exit 1; }89fi90MODEL="${OLLAMA_MODEL:-llama3.2:3b}"91"$OLLAMA_BIN" list 2>/dev/null | grep -q "${MODEL%%:*}" \92 && echo " ✅ $MODEL present" \93 || echo " ⚠️ $MODEL not pulled — run: $OLLAMA_BIN pull $MODEL"94 95# ── 2. jem-agent ──────────────────────────────────────────────────────────96echo "▶ jem-agent"97if curl -sf -m 2 "http://localhost:$AGENT_PORT/health" > /dev/null 2>&1; then98 echo " ✅ already up"99else100 # From the repo root, not inside the package: app.py imports its persona as a101 # package sibling (from .persona import …), which only resolves with the root102 # on sys.path. Same reason install-services.sh sets WorkingDirectory=$REPO.103 spawn logs/jem-agent.log \104 python3 -m flask --app jem_agent.app run --host 127.0.0.1 --port "$AGENT_PORT"105 # /health is deliberately cheap — probing /chat would queue a real generation106 # job per attempt and bury the model under abandoned work.107 wait_for "http://localhost:$AGENT_PORT/health" "up on :$AGENT_PORT" \108 || { tail -20 logs/jem-agent.log; exit 1; }109fi110 111# ── 3. site ──────────────────────────────────────────────────────────────────112echo "▶ site"113if curl -sf -m 2 "http://localhost:$PORT/health" > /dev/null 2>&1; then114 echo " ✅ already up"115else116 spawn logs/server.log uvicorn server.main:app --host 0.0.0.0 --port "$PORT"117 wait_for "http://localhost:$PORT/health" "up on :$PORT" 150 \118 || { tail -20 logs/server.log; exit 1; }119fi120 121echo122echo "════════════════════════════════════════════════════════════"123echo " Jem is live: http://localhost:$PORT"124echo " agent health: $(curl -s -m 3 http://localhost:$AGENT_PORT/health)"125echo "════════════════════════════════════════════════════════════"126 