first
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# audio-agent-engine — start / stop / restart helper.
|
||||
#
|
||||
# Usage:
|
||||
# ./server.sh start [--build] Start the server (optionally build first).
|
||||
# ./server.sh stop Stop the running server.
|
||||
# ./server.sh restart [--build] Stop then start (optionally build first).
|
||||
# ./server.sh status Show process / port / HTTP status.
|
||||
# ./server.sh logs [N] Tail the last N log lines (default 50, -f).
|
||||
# ./server.sh build Build the release binary only.
|
||||
#
|
||||
# Environment:
|
||||
# PROFILE=release (default) | debug Which Cargo profile to run.
|
||||
# RUST_LOG=info (default) Log verbosity; `audio_agent_engine::tts=debug`
|
||||
# and `audio_agent_engine::vad=debug` are always
|
||||
# appended (unless already present).
|
||||
# PORT=8787 (default) Must match SERVER_PORT in .env.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# --- Resolve project layout (script may be called from anywhere) ------------
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
PROFILE="${PROFILE:-release}"
|
||||
# INFO globally, DEBUG for the TTS and VAD subsystems (per-chunk streaming /
|
||||
# per-window VAD decisions). Each directive is appended even when RUST_LOG is
|
||||
# already exported (e.g. by an IDE or the parent shell as `RUST_LOG=info`),
|
||||
# otherwise those debug logs would be silently filtered out.
|
||||
RUST_LOG="${RUST_LOG:-info}"
|
||||
case "$RUST_LOG" in
|
||||
*audio_agent_engine::tts*) ;;
|
||||
*) RUST_LOG="$RUST_LOG,audio_agent_engine::tts=debug" ;;
|
||||
esac
|
||||
case "$RUST_LOG" in
|
||||
*audio_agent_engine::vad*) ;;
|
||||
*) RUST_LOG="$RUST_LOG,audio_agent_engine::vad=debug" ;;
|
||||
esac
|
||||
PORT="${PORT:-8787}"
|
||||
|
||||
RUN_DIR="$SCRIPT_DIR/run"
|
||||
PID_FILE="$RUN_DIR/server.pid"
|
||||
LOG_FILE="$RUN_DIR/server.log"
|
||||
BIN="$SCRIPT_DIR/target/$PROFILE/audio-agent-engine"
|
||||
|
||||
CERT_DIR="$SCRIPT_DIR/certs"
|
||||
CERT_FILE="$CERT_DIR/cert.pem"
|
||||
KEY_FILE="$CERT_DIR/key.pem"
|
||||
|
||||
# --- Colors -----------------------------------------------------------------
|
||||
if [ -t 1 ]; then
|
||||
C_RED='\033[31m'; C_GRN='\033[32m'; C_YLW='\033[33m'; C_DIM='\033[2m'; C_RST='\033[0m'
|
||||
else
|
||||
C_RED=''; C_GRN=''; C_YLW=''; C_DIM=''; C_RST=''
|
||||
fi
|
||||
info() { echo -e "${C_DIM}[server]${C_RST} $*"; }
|
||||
ok() { echo -e "${C_GRN}[server]${C_RST} $*"; }
|
||||
warn() { echo -e "${C_YLW}[server]${C_RST} $*"; }
|
||||
err() { echo -e "${C_RED}[server]${C_RST} $*" >&2; }
|
||||
|
||||
# --- Helpers ----------------------------------------------------------------
|
||||
# PID currently listening on $PORT (empty if none).
|
||||
listening_pid() {
|
||||
ss -ltnp 2>/dev/null | awk -v p=":$PORT" '$4 ~ p {print $NF}' \
|
||||
| grep -oE 'pid=[0-9]+' | head -n1 | cut -d= -f2
|
||||
}
|
||||
|
||||
# PID recorded in the pid file, if the process is still alive.
|
||||
pidfile_pid() {
|
||||
[ -f "$PID_FILE" ] || return 0
|
||||
local pid
|
||||
pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
echo "$pid"
|
||||
fi
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[ -n "$(pidfile_pid)" ] || [ -n "$(listening_pid)" ]
|
||||
}
|
||||
|
||||
# True when .env enables HTTPS (both TLS_CERT and TLS_KEY set, non-empty).
|
||||
tls_enabled() {
|
||||
[ -f "$SCRIPT_DIR/.env" ] || return 1
|
||||
grep -qE '^[[:space:]]*TLS_CERT=.+' "$SCRIPT_DIR/.env" &&
|
||||
grep -qE '^[[:space:]]*TLS_KEY=.+' "$SCRIPT_DIR/.env"
|
||||
}
|
||||
|
||||
# URL scheme the server will speak, based on .env.
|
||||
scheme() {
|
||||
if tls_enabled; then echo "https"; else echo "http"; fi
|
||||
}
|
||||
|
||||
build() {
|
||||
info "building ($PROFILE)…"
|
||||
cargo build "--$PROFILE"
|
||||
ok "build complete → $BIN"
|
||||
}
|
||||
|
||||
# Generate a self-signed certificate (SAN: localhost + this host's IPs).
|
||||
# Usage: cert [--force]
|
||||
cert() {
|
||||
local force="${1:-}"
|
||||
if [ -s "$CERT_FILE" ] && [ -s "$KEY_FILE" ] && [ "$force" != "--force" ]; then
|
||||
ok "certificate already present: $CERT_FILE (use 'cert --force' to regenerate)"
|
||||
return 0
|
||||
fi
|
||||
if ! command -v openssl >/dev/null 2>&1; then
|
||||
err "openssl not found — install it or provide TLS_CERT/TLS_KEY manually"
|
||||
return 1
|
||||
fi
|
||||
mkdir -p "$CERT_DIR"
|
||||
local san="DNS:localhost,IP:127.0.0.1,IP:::1"
|
||||
for ip in $(hostname -I 2>/dev/null); do san="$san,IP:$ip"; done
|
||||
[ -n "${EXTRA_SAN:-}" ] && san="$san,$EXTRA_SAN"
|
||||
|
||||
info "generating self-signed certificate (SAN: $san)…"
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -days 825 \
|
||||
-keyout "$KEY_FILE" -out "$CERT_FILE" \
|
||||
-subj "/CN=audio-agent-engine" -addext "subjectAltName=$san" >/dev/null 2>&1
|
||||
chmod 600 "$KEY_FILE" 2>/dev/null || true
|
||||
ok "certificate → $CERT_FILE / $KEY_FILE"
|
||||
}
|
||||
|
||||
start() {
|
||||
if is_running; then
|
||||
warn "already running (pid $(pidfile_pid || listening_pid)) — use 'restart'"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -x "$BIN" ]; then
|
||||
warn "binary not found: $BIN"
|
||||
build
|
||||
fi
|
||||
|
||||
# HTTPS requested but certificate missing → generate one on the fly.
|
||||
if tls_enabled && { [ ! -s "$CERT_FILE" ] || [ ! -s "$KEY_FILE" ]; }; then
|
||||
cert
|
||||
fi
|
||||
|
||||
mkdir -p "$RUN_DIR"
|
||||
local sch; sch="$(scheme)"
|
||||
info "starting on :$PORT ($sch, RUST_LOG=$RUST_LOG, profile=$PROFILE)…"
|
||||
# The server reads .env + models/ with paths relative to this directory,
|
||||
# so we launch it from $SCRIPT_DIR.
|
||||
nohup env RUST_LOG="$RUST_LOG" "$BIN" >> "$LOG_FILE" 2>&1 &
|
||||
echo $! > "$PID_FILE"
|
||||
local pid=$!
|
||||
|
||||
# Wait for the port to accept connections (model load ~1.5 s).
|
||||
for _ in $(seq 1 40); do
|
||||
if [ -n "$(listening_pid)" ]; then
|
||||
ok "started (pid $pid) — $sch://localhost:$PORT/"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
err "failed to start within ~20 s. Last log lines:"
|
||||
tail -n 20 "$LOG_FILE" || true
|
||||
rm -f "$PID_FILE"
|
||||
return 1
|
||||
}
|
||||
|
||||
stop() {
|
||||
local pid
|
||||
pid="$(pidfile_pid)"
|
||||
[ -z "$pid" ] && pid="$(listening_pid)"
|
||||
|
||||
if [ -z "$pid" ]; then
|
||||
warn "not running"
|
||||
rm -f "$PID_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
info "stopping pid $pid…"
|
||||
kill "$pid" 2>/dev/null || true
|
||||
|
||||
# Wait up to 10 s for a graceful exit, then force-kill.
|
||||
for _ in $(seq 1 20); do
|
||||
kill -0 "$pid" 2>/dev/null || break
|
||||
sleep 0.5
|
||||
done
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
warn "still alive — sending SIGKILL"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
ok "stopped"
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
status() {
|
||||
local pid; pid="$(pidfile_pid)"
|
||||
local lpid; lpid="$(listening_pid)"
|
||||
local sch; sch="$(scheme)"
|
||||
echo "profile : $PROFILE"
|
||||
echo "binary : $BIN $([ -x "$BIN" ] && echo '(ok)' || echo '(missing)')"
|
||||
echo "pid : ${pid:-${lpid:-none}}"
|
||||
echo "port : $PORT"
|
||||
echo "scheme : $sch"
|
||||
if tls_enabled; then
|
||||
echo "cert : $CERT_FILE $([ -s "$CERT_FILE" ] && echo '(ok)' || echo '(missing — run ./server.sh cert)')"
|
||||
fi
|
||||
if [ -n "$lpid" ]; then
|
||||
local code
|
||||
# -k: accept the self-signed certificate when probing.
|
||||
code="$(curl -sk -o /dev/null -w '%{http_code}' --max-time 5 \
|
||||
"$sch://localhost:$PORT/health" || echo 000)"
|
||||
echo -e "http : ${C_GRN}$code${C_RST} ($sch://localhost:$PORT/)"
|
||||
else
|
||||
echo -e "http : ${C_RED}not listening${C_RST}"
|
||||
fi
|
||||
}
|
||||
|
||||
logs() {
|
||||
local n="${1:-50}"
|
||||
[ -f "$LOG_FILE" ] || { warn "no log file yet ($LOG_FILE)"; return 0; }
|
||||
info "tailing $LOG_FILE (Ctrl-C to stop)…"
|
||||
tail -n "$n" -f "$LOG_FILE"
|
||||
}
|
||||
|
||||
# --- Dispatch ---------------------------------------------------------------
|
||||
cmd="${1:-}"; shift || true
|
||||
# Allow `--build` as a flag for start/restart in any position.
|
||||
DO_BUILD=0
|
||||
for arg in "$@"; do [ "$arg" = "--build" ] && DO_BUILD=1; done
|
||||
|
||||
case "$cmd" in
|
||||
start)
|
||||
[ "$DO_BUILD" = 1 ] && build
|
||||
start
|
||||
;;
|
||||
stop) stop ;;
|
||||
restart)
|
||||
[ "$DO_BUILD" = 1 ] && build
|
||||
restart
|
||||
;;
|
||||
status) status ;;
|
||||
logs) logs "${1:-50}" ;;
|
||||
build) build ;;
|
||||
cert) cert "${1:-}" ;;
|
||||
*)
|
||||
cat <<EOF
|
||||
audio-agent-engine — control script
|
||||
|
||||
Usage: ./server.sh <command> [args]
|
||||
|
||||
start [--build] Start the server (--build recompiles first)
|
||||
stop Stop the running server
|
||||
restart [--build] Restart (--build recompiles first)
|
||||
status Show process / port / scheme / HTTP status
|
||||
logs [N] Tail the log file (default 50 lines, follow)
|
||||
build Build the release binary only
|
||||
cert [--force] Generate the self-signed HTTPS certificate
|
||||
|
||||
HTTPS is served when .env sets both TLS_CERT and TLS_KEY (see .env.example).
|
||||
Browsers only grant microphone access on a secure context: use HTTPS, or open
|
||||
the page on localhost.
|
||||
|
||||
Environment: PROFILE=$PROFILE RUST_LOG=$RUST_LOG PORT=$PORT
|
||||
EXTRA_SAN=<extra SAN entries for 'cert'>
|
||||
EOF
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user