210 lines
4.8 KiB
Bash
Executable File
210 lines
4.8 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
SESSION_NAME="${SESSION_NAME:-eryao-dev}"
|
|
ENV_FILE="$ROOT_DIR/.env"
|
|
|
|
usage() {
|
|
echo "Usage: $0 {start|stop|restart}"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " start Start local web process in tmux"
|
|
echo " stop Stop tmux session and orphaned local processes"
|
|
echo " restart Stop then start all app processes"
|
|
exit 1
|
|
}
|
|
|
|
load_env_if_exists() {
|
|
if [ -f "$ENV_FILE" ]; then
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
. "$ENV_FILE"
|
|
set +a
|
|
fi
|
|
}
|
|
|
|
is_port_in_use() {
|
|
local port="$1"
|
|
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
lsof -iTCP:"$port" -sTCP:LISTEN -t >/dev/null 2>&1
|
|
return $?
|
|
fi
|
|
|
|
if command -v ss >/dev/null 2>&1; then
|
|
ss -ltn "sport = :$port" | awk 'NR > 1 {exit 0} END {exit 1}'
|
|
return $?
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
collect_listening_pids() {
|
|
local port="$1"
|
|
|
|
if command -v lsof >/dev/null 2>&1; then
|
|
lsof -iTCP:"$port" -sTCP:LISTEN -t | sort -u
|
|
return
|
|
fi
|
|
|
|
if command -v ss >/dev/null 2>&1; then
|
|
ss -lptn "sport = :$port" | awk -F 'pid=' 'NF > 1 {split($2, tmp, ","); print tmp[1]}' | sort -u
|
|
fi
|
|
}
|
|
|
|
kill_pids_gracefully() {
|
|
local label="$1"
|
|
shift
|
|
local pids=("$@")
|
|
local alive=()
|
|
|
|
if [ "${#pids[@]}" -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
echo "Stopping ${label}: ${pids[*]}"
|
|
kill -TERM "${pids[@]}" 2>/dev/null || true
|
|
|
|
for _ in {1..10}; do
|
|
alive=()
|
|
for pid in "${pids[@]}"; do
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
alive+=("$pid")
|
|
fi
|
|
done
|
|
|
|
if [ "${#alive[@]}" -eq 0 ]; then
|
|
return
|
|
fi
|
|
|
|
sleep 1
|
|
done
|
|
|
|
echo "Force killing ${label}: ${alive[*]}"
|
|
kill -KILL "${alive[@]}" 2>/dev/null || true
|
|
}
|
|
|
|
kill_matching_processes() {
|
|
local label="$1"
|
|
local pattern="$2"
|
|
local pids
|
|
|
|
pids="$(pgrep -f "$pattern" || true)"
|
|
if [ -z "$pids" ]; then
|
|
return
|
|
fi
|
|
|
|
# shellcheck disable=SC2086
|
|
kill_pids_gracefully "$label" $pids
|
|
}
|
|
|
|
kill_listening_processes() {
|
|
local label="$1"
|
|
local port="$2"
|
|
local pids
|
|
|
|
pids="$(collect_listening_pids "$port" || true)"
|
|
if [ -z "$pids" ]; then
|
|
return
|
|
fi
|
|
|
|
# shellcheck disable=SC2086
|
|
kill_pids_gracefully "$label" $pids
|
|
}
|
|
|
|
start() {
|
|
echo "=== Eryao App Up ==="
|
|
echo "This script starts local web process in tmux."
|
|
echo "Redis should be managed separately via docker-compose."
|
|
echo "NOTE: Database migration must be run separately."
|
|
echo ""
|
|
|
|
if ! command -v tmux >/dev/null 2>&1; then
|
|
echo "Error: tmux is required." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
echo "Error: env file not found at $ENV_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
load_env_if_exists
|
|
|
|
UVICORN_LOG_LEVEL="${ERYAO_RUNTIME__LOG_LEVEL:-info}"
|
|
UVICORN_LOG_LEVEL="$(echo "$UVICORN_LOG_LEVEL" | tr '[:upper:]' '[:lower:]')"
|
|
WEB_PORT="${ERYAO_WEB__PORT:-8000}"
|
|
|
|
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
echo "Error: tmux session '$SESSION_NAME' already exists." >&2
|
|
echo "Hint: tmux kill-session -t $SESSION_NAME" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if is_port_in_use "$WEB_PORT"; then
|
|
echo "Error: web port ${WEB_PORT} is already in use." >&2
|
|
echo "Hint: run '$0 stop' or change ERYAO_WEB__PORT in .env" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${ERYAO_DEEPSEEK__API_KEY:-}" ]; then
|
|
echo "Warning: ERYAO_DEEPSEEK__API_KEY is empty; deepseek calls may fail." >&2
|
|
fi
|
|
|
|
WEB_CMD="cd '$ROOT_DIR' && PYTHONPATH=backend/src ERYAO_RUNTIME__SERVICE_NAME=web uv run uvicorn backend.src.app:app --host ${ERYAO_WEB__HOST:-0.0.0.0} --port ${WEB_PORT} --workers ${ERYAO_WEB__WORKERS:-2} --log-level ${UVICORN_LOG_LEVEL}"
|
|
|
|
echo "Starting tmux web process in session '$SESSION_NAME'..."
|
|
|
|
tmux new-session -d -s "$SESSION_NAME" -n web "bash -lc \"$WEB_CMD; echo '[web] exited'; exec bash\""
|
|
|
|
echo ""
|
|
echo "=== App Started ==="
|
|
echo "Log files will be created in logs/ directory:"
|
|
echo " - web.log, web.error.log"
|
|
echo ""
|
|
echo "tmux attach -t $SESSION_NAME"
|
|
echo "tmux list-windows -t $SESSION_NAME"
|
|
}
|
|
|
|
stop() {
|
|
echo "=== Eryao App Down ==="
|
|
echo "Stopping tmux app processes (docker redis is not managed here)."
|
|
load_env_if_exists
|
|
WEB_PORT="${ERYAO_WEB__PORT:-8000}"
|
|
|
|
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
|
|
echo "Stopping tmux session '$SESSION_NAME'..."
|
|
tmux kill-session -t "$SESSION_NAME"
|
|
else
|
|
echo "No tmux session '$SESSION_NAME' found."
|
|
fi
|
|
|
|
echo "Checking for orphaned processes..."
|
|
|
|
kill_matching_processes "uvicorn" "uv run uvicorn backend.src.app:app"
|
|
|
|
kill_listening_processes "port ${WEB_PORT} listeners" "$WEB_PORT"
|
|
|
|
if is_port_in_use "$WEB_PORT"; then
|
|
echo "Warning: port ${WEB_PORT} is still in use after cleanup." >&2
|
|
echo "Hint: check process with 'lsof -iTCP:${WEB_PORT} -sTCP:LISTEN'" >&2
|
|
return 1
|
|
fi
|
|
|
|
echo "Session stopped and cleaned up."
|
|
}
|
|
|
|
restart() {
|
|
stop
|
|
echo ""
|
|
start
|
|
}
|
|
|
|
case "${1:-}" in
|
|
start) start ;;
|
|
stop) stop ;;
|
|
restart) restart ;;
|
|
*) usage ;;
|
|
esac
|