On Windows the venv Python binary lives at .venv/Scripts/python.exe, not .venv/bin/python. Fall back to the Windows path when the Unix path does not exist so the script works cross-platform.
254 lines
7.2 KiB
Bash
254 lines
7.2 KiB
Bash
#!/usr/bin/env bash
|
|
# server.sh — Non-blocking server management script for local-mcp.
|
|
#
|
|
# Usage:
|
|
# ./server.sh start # Start (no-op if already running)
|
|
# ./server.sh stop # Kill the running server
|
|
# ./server.sh restart # Stop then start
|
|
# ./server.sh status # Show PID, memory, and tail 20 log lines
|
|
# ./server.sh logs [N] # Tail last N lines of stdout log (default 40)
|
|
# ./server.sh logs -f # Follow log live (Ctrl-C to quit)
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Paths ─────────────────────────────────────────────────────────────────
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PYTHON="$ROOT/.venv/bin/python"
|
|
if [ ! -f "$PYTHON" ]; then
|
|
PYTHON="$ROOT/.venv/Scripts/python.exe"
|
|
fi
|
|
ENTRY="$ROOT/main.py"
|
|
LOG_DIR="$ROOT/logs"
|
|
LOG_OUT="$LOG_DIR/server.log"
|
|
LOG_ERR="$LOG_DIR/server.err.log"
|
|
PID_FILE="$LOG_DIR/server.pid"
|
|
PORT=8000
|
|
|
|
# ── Colours ───────────────────────────────────────────────────────────────
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
GRAY='\033[0;90m'
|
|
NC='\033[0m' # No Colour
|
|
|
|
cecho() { printf "${1}${2}${NC}\n"; }
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
ensure_log_dir() {
|
|
mkdir -p "$LOG_DIR"
|
|
}
|
|
|
|
get_server_pid() {
|
|
# Trust the PID file first; verify the process is alive
|
|
if [[ -f "$PID_FILE" ]]; then
|
|
local stored
|
|
stored=$(cat "$PID_FILE" 2>/dev/null || true)
|
|
if [[ "$stored" =~ ^[0-9]+$ ]]; then
|
|
if kill -0 "$stored" 2>/dev/null; then
|
|
echo "$stored"
|
|
return
|
|
fi
|
|
fi
|
|
rm -f "$PID_FILE"
|
|
fi
|
|
|
|
# Fallback: find a process listening on the port
|
|
local pid
|
|
pid=$(lsof -ti "TCP:$PORT" -s "TCP:LISTEN" 2>/dev/null | head -1 || true)
|
|
if [[ -n "$pid" ]]; then
|
|
echo "$pid"
|
|
return
|
|
fi
|
|
|
|
echo ""
|
|
}
|
|
|
|
is_running() {
|
|
local pid
|
|
pid=$(get_server_pid)
|
|
[[ -n "$pid" ]]
|
|
}
|
|
|
|
# ── Commands ──────────────────────────────────────────────────────────────
|
|
|
|
cmd_start() {
|
|
local pid
|
|
pid=$(get_server_pid)
|
|
if [[ -n "$pid" ]]; then
|
|
cecho "$GREEN" "Server already running (PID $pid http://localhost:$PORT)"
|
|
return
|
|
fi
|
|
|
|
if [[ ! -x "$PYTHON" ]]; then
|
|
cecho "$RED" "ERROR: Python venv not found at $PYTHON"
|
|
cecho "$YELLOW" "Run: python -m venv .venv && .venv/bin/pip install -r requirements.txt"
|
|
exit 1
|
|
fi
|
|
|
|
ensure_log_dir
|
|
|
|
# Stamp the log files
|
|
local stamp
|
|
stamp="$(date '+%Y-%m-%d %H:%M:%S') [server.sh] ---- starting ----"
|
|
echo "$stamp" >> "$LOG_OUT"
|
|
echo "$stamp" >> "$LOG_ERR"
|
|
|
|
# Start detached: stdout → LOG_OUT, stderr → LOG_ERR
|
|
nohup "$PYTHON" -u "$ENTRY" >> "$LOG_OUT" 2>> "$LOG_ERR" &
|
|
local new_pid=$!
|
|
echo "$new_pid" > "$PID_FILE"
|
|
|
|
# Wait up to 6 s for the port to open
|
|
local deadline=$(( $(date +%s) + 6 ))
|
|
local ready=false
|
|
while [[ $(date +%s) -lt $deadline ]]; do
|
|
sleep 0.4
|
|
if lsof -ti "TCP:$PORT" -s "TCP:LISTEN" &>/dev/null; then
|
|
ready=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if $ready; then
|
|
cecho "$GREEN" "Server started (PID $new_pid http://localhost:$PORT)"
|
|
else
|
|
cecho "$YELLOW" "Server process launched (PID $new_pid) but port $PORT not yet open."
|
|
cecho "$YELLOW" "Check logs: ./server.sh logs"
|
|
fi
|
|
}
|
|
|
|
cmd_stop() {
|
|
local pid
|
|
pid=$(get_server_pid)
|
|
if [[ -z "$pid" ]]; then
|
|
cecho "$YELLOW" "Server is not running."
|
|
return
|
|
fi
|
|
|
|
kill "$pid" 2>/dev/null || true
|
|
rm -f "$PID_FILE"
|
|
|
|
# Wait up to 4 s for the port to free
|
|
local deadline=$(( $(date +%s) + 4 ))
|
|
while [[ $(date +%s) -lt $deadline ]]; do
|
|
sleep 0.3
|
|
if ! lsof -ti "TCP:$PORT" -s "TCP:LISTEN" &>/dev/null; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
cecho "$YELLOW" "Server stopped (was PID $pid)"
|
|
}
|
|
|
|
cmd_restart() {
|
|
cmd_stop
|
|
sleep 0.5
|
|
cmd_start
|
|
}
|
|
|
|
cmd_status() {
|
|
local pid
|
|
pid=$(get_server_pid)
|
|
|
|
echo ""
|
|
if [[ -n "$pid" ]]; then
|
|
local mem="?"
|
|
# Try to get RSS memory in MB (Linux: /proc, macOS: ps)
|
|
if [[ -f "/proc/$pid/status" ]]; then
|
|
local kb
|
|
kb=$(grep -i VmRSS "/proc/$pid/status" 2>/dev/null | awk '{print $2}' || echo "0")
|
|
mem="$(( kb / 1024 )) MB"
|
|
elif command -v ps &>/dev/null; then
|
|
local rss
|
|
rss=$(ps -o rss= -p "$pid" 2>/dev/null | tr -d ' ' || echo "0")
|
|
mem="$(( rss / 1024 )) MB"
|
|
fi
|
|
|
|
cecho "$GREEN" " Status : RUNNING"
|
|
printf " PID : %s\n" "$pid"
|
|
printf " Memory : %s\n" "$mem"
|
|
printf " URL : http://localhost:%s\n" "$PORT"
|
|
printf " Logs : %s\n" "$LOG_OUT"
|
|
printf " %s\n" "$LOG_ERR"
|
|
else
|
|
cecho "$RED" " Status : STOPPED"
|
|
fi
|
|
|
|
echo ""
|
|
cecho "$GRAY" "--- Last 20 log lines (stdout) ---"
|
|
if [[ -f "$LOG_OUT" ]]; then
|
|
tail -n 20 "$LOG_OUT" | while IFS= read -r line; do
|
|
cecho "$GRAY" "$line"
|
|
done
|
|
else
|
|
cecho "$GRAY" " (no log file yet)"
|
|
fi
|
|
|
|
if [[ -f "$LOG_ERR" ]]; then
|
|
local err_lines
|
|
err_lines=$(grep -E "ERROR|Exception|Traceback" "$LOG_ERR" 2>/dev/null | tail -5 || true)
|
|
if [[ -n "$err_lines" ]]; then
|
|
echo ""
|
|
cecho "$RED" "--- Recent errors (stderr) ---"
|
|
echo "$err_lines" | while IFS= read -r line; do
|
|
cecho "$RED" "$line"
|
|
done
|
|
fi
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
cmd_logs() {
|
|
local arg="${1:-}"
|
|
ensure_log_dir
|
|
|
|
if [[ "$arg" == "-f" ]]; then
|
|
if [[ ! -f "$LOG_OUT" ]]; then
|
|
cecho "$YELLOW" "No log file yet. Start the server first."
|
|
return
|
|
fi
|
|
cecho "$CYAN" "Following $LOG_OUT (Ctrl-C to stop)"
|
|
tail -f -n 30 "$LOG_OUT"
|
|
else
|
|
local n=40
|
|
if [[ "$arg" =~ ^[0-9]+$ ]]; then
|
|
n="$arg"
|
|
fi
|
|
cecho "$CYAN" "--- stdout (last $n lines) ---"
|
|
if [[ -f "$LOG_OUT" ]]; then
|
|
tail -n "$n" "$LOG_OUT"
|
|
else
|
|
cecho "$GRAY" " (empty)"
|
|
fi
|
|
echo ""
|
|
cecho "$YELLOW" "--- stderr (last 20 lines) ---"
|
|
if [[ -f "$LOG_ERR" ]]; then
|
|
tail -n 20 "$LOG_ERR"
|
|
else
|
|
cecho "$GRAY" " (empty)"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ── Dispatch ──────────────────────────────────────────────────────────────
|
|
|
|
COMMAND="${1:-status}"
|
|
ARG="${2:-}"
|
|
|
|
case "$COMMAND" in
|
|
start) cmd_start ;;
|
|
stop) cmd_stop ;;
|
|
restart) cmd_restart ;;
|
|
status) cmd_status ;;
|
|
logs) cmd_logs "$ARG" ;;
|
|
*)
|
|
echo "Usage: $0 {start|stop|restart|status|logs [N|-f]}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|