185 lines
5.5 KiB
Bash
Executable File
185 lines
5.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
PID_FILE="$PROJECT_DIR/.server.pid"
|
|
LOG_FILE="$PROJECT_DIR/logs/server.log"
|
|
|
|
# Optional project-root .env (gitignored; setup.sh writes credentials here,
|
|
# operators can add e.g. HOST=0.0.0.0 for reverse-proxy deployments so a bare
|
|
# `server.sh restart` keeps the bind address). Lines are `KEY=value` or
|
|
# `export KEY='value'`. Precedence: explicit environment > .env > defaults,
|
|
# so `HOST=127.0.0.1 scripts/server.sh restart` still overrides the file.
|
|
if [[ -f "$PROJECT_DIR/.env" ]]; then
|
|
# Values are parsed LITERALLY (no eval/source): a value is either bare, or
|
|
# single-quoted in setup.sh's format ('\'' encodes a literal quote), or
|
|
# double-quoted (quotes stripped, contents kept literal — no $ expansion).
|
|
# One KEY=value per line; full-line comments only.
|
|
#
|
|
# Keys this loader itself set from .env are tracked space-delimited (keys are
|
|
# validated identifiers so this is unambiguous — no Bash-4 associative
|
|
# arrays, macOS ships bash 3.2). Only EXTERNAL pre-set environment is
|
|
# protected; duplicate keys within .env keep normal source semantics (last
|
|
# line wins) because earlier lines are recorded here and may be overridden.
|
|
_envfile_set=" "
|
|
_q=\'
|
|
_esc=$'\x01'
|
|
while IFS= read -r _line || [[ -n "$_line" ]]; do
|
|
_line="${_line#"${_line%%[![:space:]]*}"}" # ltrim
|
|
[[ -z "$_line" || "$_line" == \#* ]] && continue
|
|
_kv="${_line#export }"
|
|
_key="${_kv%%=*}"
|
|
[[ "$_key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
|
|
[[ "$_kv" == *=* ]] || continue
|
|
_val="${_kv#*=}"
|
|
case "$_val" in
|
|
"$_q"*)
|
|
_val="${_val//${_q}\\${_q}${_q}/${_esc}}" # '\'' -> literal-quote marker
|
|
_val="${_val//${_q}/}" # drop quoting quotes
|
|
_val="${_val//${_esc}/${_q}}" # restore literal quotes
|
|
;;
|
|
\"*\")
|
|
_val="${_val#\"}"
|
|
_val="${_val%\"}"
|
|
;;
|
|
esac
|
|
if [[ "$_envfile_set" == *" $_key "* || -z "${!_key+x}" ]]; then
|
|
export "$_key=$_val"
|
|
_envfile_set="${_envfile_set}${_key} "
|
|
fi
|
|
done < "$PROJECT_DIR/.env"
|
|
unset _line _kv _key _val _envfile_set _q _esc
|
|
fi
|
|
|
|
PORT="${PORT:-9876}"
|
|
|
|
cd "$PROJECT_DIR"
|
|
|
|
usage() {
|
|
echo "Usage: $0 {start|stop|restart|status|logs}"
|
|
exit 1
|
|
}
|
|
|
|
is_running() {
|
|
if [[ -f "$PID_FILE" ]]; then
|
|
local pid
|
|
pid=$(cat "$PID_FILE")
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
rm -f "$PID_FILE"
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
do_start() {
|
|
if is_running; then
|
|
echo "Server already running (PID $(cat "$PID_FILE"))"
|
|
return 0
|
|
fi
|
|
|
|
# Surface stray maestro processes the pidfile doesn't track (e.g. an orphaned
|
|
# run on another port/mode that a previous restart never stopped). The
|
|
# app-level worker lock (src/instance-lock.ts) will refuse to double-start
|
|
# worker mode against the same DB, but warn here so the operator notices.
|
|
local strays
|
|
strays=$(pgrep -f "node dist/main.js" 2>/dev/null | grep -vx "$$" || true)
|
|
if [[ -n "$strays" ]]; then
|
|
echo "WARNING: other 'node dist/main.js' process(es) detected (PID: $(echo "$strays" | tr '\n' ' '))."
|
|
echo " If a previous run was not stopped cleanly, stop it first to avoid port/DB conflicts."
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
|
|
echo "Checking runtime dependencies..."
|
|
"$SCRIPT_DIR/prepare.sh"
|
|
|
|
echo "Building..."
|
|
npm run build --silent 2>&1 | tail -1
|
|
|
|
echo "Starting server on port $PORT..."
|
|
# AAO is a single binary with two modes (dist/main.js dispatches):
|
|
# AAO_MODE=worker (default) — full orchestrator (DB + bridge API + workers)
|
|
# AAO_MODE=gateway — OpenAI-compatible LLM gateway only
|
|
# To launch as a gateway: `AAO_MODE=gateway scripts/server.sh start` and
|
|
# set `gateway.listen_port` in config.yaml (default 4000). dist/index.js
|
|
# is preserved as a worker-mode shim for legacy paths.
|
|
PORT="$PORT" AAO_MODE="${AAO_MODE:-worker}" nohup node dist/main.js >> "$LOG_FILE" 2>&1 &
|
|
local pid=$!
|
|
echo "$pid" > "$PID_FILE"
|
|
|
|
# Wait briefly and verify it started
|
|
sleep 2
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
echo "Server started (PID $pid, log: $LOG_FILE)"
|
|
else
|
|
rm -f "$PID_FILE"
|
|
echo "Server failed to start. Check $LOG_FILE"
|
|
tail -5 "$LOG_FILE"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
do_stop() {
|
|
if ! is_running; then
|
|
echo "Server not running"
|
|
# Also kill any stray process on the port
|
|
local stray
|
|
stray=$(lsof -ti:"$PORT" 2>/dev/null || true)
|
|
if [[ -n "$stray" ]]; then
|
|
echo "Found stray process on port $PORT (PID $stray), killing..."
|
|
kill "$stray" 2>/dev/null || true
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
local pid
|
|
pid=$(cat "$PID_FILE")
|
|
echo "Stopping server (PID $pid)..."
|
|
kill "$pid" 2>/dev/null || true
|
|
|
|
# Wait for graceful shutdown
|
|
for i in {1..10}; do
|
|
if ! kill -0 "$pid" 2>/dev/null; then
|
|
rm -f "$PID_FILE"
|
|
echo "Server stopped"
|
|
return 0
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
|
|
# Force kill
|
|
echo "Force killing..."
|
|
kill -9 "$pid" 2>/dev/null || true
|
|
rm -f "$PID_FILE"
|
|
echo "Server stopped (forced)"
|
|
}
|
|
|
|
do_status() {
|
|
if is_running; then
|
|
local pid
|
|
pid=$(cat "$PID_FILE")
|
|
echo "Server running (PID $pid, port $PORT)"
|
|
else
|
|
echo "Server not running"
|
|
fi
|
|
}
|
|
|
|
do_logs() {
|
|
if [[ -f "$LOG_FILE" ]]; then
|
|
tail -f "$LOG_FILE"
|
|
else
|
|
echo "No log file found at $LOG_FILE"
|
|
fi
|
|
}
|
|
|
|
case "${1:-}" in
|
|
start) do_start ;;
|
|
stop) do_stop ;;
|
|
restart) do_stop; do_start ;;
|
|
status) do_status ;;
|
|
logs) do_logs ;;
|
|
*) usage ;;
|
|
esac
|