2026-02-28 22:12:04 -05:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
cd "$SCRIPT_DIR"
|
|
|
|
|
|
|
|
|
|
if [[ ! -d ".venv" ]]; then
|
|
|
|
|
python3 -m venv .venv
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
source .venv/bin/activate
|
|
|
|
|
pip install -r requirements.txt >/dev/null
|
|
|
|
|
|
|
|
|
|
# Optional local voice settings. Example file: .env.voice
|
|
|
|
|
if [[ -f ".env.voice" ]]; then
|
|
|
|
|
set -a
|
|
|
|
|
# shellcheck disable=SC1091
|
|
|
|
|
source ".env.voice"
|
|
|
|
|
set +a
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Nanobot command defaults (prefer Nanobot's own virtualenv interpreter).
|
|
|
|
|
: "${NANOBOT_WORKDIR:=${HOME}/nanobot}"
|
|
|
|
|
if [[ -z "${NANOBOT_COMMAND:-}" ]]; then
|
|
|
|
|
if [[ -x "${NANOBOT_WORKDIR}/.venv/bin/python" ]]; then
|
|
|
|
|
NANOBOT_COMMAND="${NANOBOT_WORKDIR}/.venv/bin/python -m nanobot agent --no-markdown"
|
|
|
|
|
elif [[ -x "${NANOBOT_WORKDIR}/venv/bin/python" ]]; then
|
|
|
|
|
NANOBOT_COMMAND="${NANOBOT_WORKDIR}/venv/bin/python -m nanobot agent --no-markdown"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
export NANOBOT_WORKDIR NANOBOT_COMMAND
|
|
|
|
|
: "${NANOBOT_SUPPRESS_NOISY_UI:=1}"
|
|
|
|
|
: "${NANOBOT_OUTPUT_DEDUP_WINDOW_S:=1.5}"
|
|
|
|
|
export NANOBOT_SUPPRESS_NOISY_UI NANOBOT_OUTPUT_DEDUP_WINDOW_S
|
|
|
|
|
|
|
|
|
|
# Host voice pipeline env vars (safe defaults).
|
|
|
|
|
: "${HOST_STT_PROVIDER:=faster-whisper}"
|
|
|
|
|
: "${HOST_STT_COMMAND:=}"
|
2026-03-04 08:20:42 -05:00
|
|
|
: "${HOST_STT_MODEL:=distil-large-v3}"
|
2026-02-28 22:12:04 -05:00
|
|
|
: "${HOST_STT_DEVICE:=auto}"
|
|
|
|
|
: "${HOST_STT_COMPUTE_TYPE:=int8}"
|
|
|
|
|
: "${HOST_STT_LANGUAGE:=en}"
|
|
|
|
|
: "${HOST_STT_BEAM_SIZE:=2}"
|
|
|
|
|
: "${HOST_STT_BEST_OF:=2}"
|
|
|
|
|
: "${HOST_STT_VAD_FILTER:=0}"
|
|
|
|
|
: "${HOST_STT_TEMPERATURE:=0.0}"
|
|
|
|
|
: "${HOST_STT_LOG_PROB_THRESHOLD:=-1.0}"
|
|
|
|
|
: "${HOST_STT_NO_SPEECH_THRESHOLD:=0.6}"
|
|
|
|
|
: "${HOST_STT_COMPRESSION_RATIO_THRESHOLD:=2.4}"
|
|
|
|
|
: "${HOST_STT_INITIAL_PROMPT:=Transcribe brief spoken English precisely. Prefer common words over sound effects.}"
|
2026-03-04 08:20:42 -05:00
|
|
|
: "${HOST_STT_REPETITION_PENALTY:=1.0}"
|
|
|
|
|
: "${HOST_STT_HALLUCINATION_SILENCE_THRESHOLD:=}"
|
2026-02-28 22:12:04 -05:00
|
|
|
: "${HOST_TTS_PROVIDER:=supertonic}"
|
|
|
|
|
: "${HOST_TTS_COMMAND:=}"
|
|
|
|
|
: "${SUPERTONIC_MODEL:=supertonic-2}"
|
2026-03-04 08:20:42 -05:00
|
|
|
: "${SUPERTONIC_VOICE_STYLE:=F1}"
|
2026-02-28 22:12:04 -05:00
|
|
|
: "${SUPERTONIC_LANG:=en}"
|
2026-03-04 08:20:42 -05:00
|
|
|
: "${SUPERTONIC_TOTAL_STEPS:=8}"
|
|
|
|
|
: "${SUPERTONIC_SPEED:=1.5}"
|
2026-02-28 22:12:04 -05:00
|
|
|
: "${SUPERTONIC_INTRA_OP_THREADS:=1}"
|
|
|
|
|
: "${SUPERTONIC_INTER_OP_THREADS:=1}"
|
|
|
|
|
: "${SUPERTONIC_AUTO_DOWNLOAD:=1}"
|
|
|
|
|
: "${HOST_STT_MIN_PTT_MS:=220}"
|
|
|
|
|
: "${HOST_STT_SEGMENT_QUEUE_SIZE:=2}"
|
|
|
|
|
: "${HOST_STT_BACKLOG_NOTICE_INTERVAL_S:=6.0}"
|
|
|
|
|
: "${HOST_STT_SUPPRESS_DURING_TTS:=1}"
|
|
|
|
|
: "${HOST_STT_SUPPRESS_MS_AFTER_TTS:=300}"
|
|
|
|
|
: "${HOST_RTC_OUTBOUND_LEAD_IN_MS:=120}"
|
|
|
|
|
: "${HOST_RTC_OUTBOUND_IDLE_S:=0.6}"
|
|
|
|
|
|
|
|
|
|
export HOST_STT_PROVIDER HOST_STT_COMMAND HOST_STT_MODEL HOST_STT_DEVICE
|
|
|
|
|
export HOST_STT_COMPUTE_TYPE HOST_STT_LANGUAGE HOST_STT_BEAM_SIZE HOST_STT_BEST_OF HOST_STT_VAD_FILTER
|
|
|
|
|
export HOST_STT_TEMPERATURE HOST_STT_LOG_PROB_THRESHOLD HOST_STT_NO_SPEECH_THRESHOLD
|
|
|
|
|
export HOST_STT_COMPRESSION_RATIO_THRESHOLD
|
2026-03-04 08:20:42 -05:00
|
|
|
export HOST_STT_INITIAL_PROMPT HOST_STT_REPETITION_PENALTY HOST_STT_HALLUCINATION_SILENCE_THRESHOLD
|
2026-02-28 22:12:04 -05:00
|
|
|
export HOST_TTS_PROVIDER HOST_TTS_COMMAND
|
|
|
|
|
export SUPERTONIC_MODEL SUPERTONIC_VOICE_STYLE SUPERTONIC_LANG
|
|
|
|
|
export SUPERTONIC_TOTAL_STEPS SUPERTONIC_SPEED
|
|
|
|
|
export SUPERTONIC_INTRA_OP_THREADS SUPERTONIC_INTER_OP_THREADS SUPERTONIC_AUTO_DOWNLOAD
|
2026-03-04 08:20:42 -05:00
|
|
|
export HOST_STT_MIN_PTT_MS HOST_STT_SEGMENT_QUEUE_SIZE
|
2026-02-28 22:12:04 -05:00
|
|
|
export HOST_STT_BACKLOG_NOTICE_INTERVAL_S
|
|
|
|
|
export HOST_STT_SUPPRESS_DURING_TTS HOST_STT_SUPPRESS_MS_AFTER_TTS
|
|
|
|
|
export HOST_RTC_OUTBOUND_LEAD_IN_MS HOST_RTC_OUTBOUND_IDLE_S
|
|
|
|
|
|
|
|
|
|
: "${UVICORN_HOST:=0.0.0.0}"
|
|
|
|
|
: "${UVICORN_PORT:=8000}"
|
|
|
|
|
: "${ENABLE_HTTPS:=1}"
|
|
|
|
|
: "${SSL_DAYS:=365}"
|
|
|
|
|
: "${SSL_CERT_FILE:=.certs/local-cert.pem}"
|
|
|
|
|
: "${SSL_KEY_FILE:=.certs/local-key.pem}"
|
|
|
|
|
|
|
|
|
|
if [[ "$ENABLE_HTTPS" == "1" ]]; then
|
|
|
|
|
mkdir -p "$(dirname "$SSL_CERT_FILE")"
|
|
|
|
|
mkdir -p "$(dirname "$SSL_KEY_FILE")"
|
|
|
|
|
|
|
|
|
|
if [[ ! -f "$SSL_CERT_FILE" || ! -f "$SSL_KEY_FILE" ]]; then
|
|
|
|
|
LOCAL_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
|
|
|
|
SAN_ENTRIES="DNS:localhost,IP:127.0.0.1"
|
|
|
|
|
if [[ -n "${LOCAL_IP:-}" ]]; then
|
|
|
|
|
SAN_ENTRIES="${SAN_ENTRIES},IP:${LOCAL_IP}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "Generating local TLS certificate at '$SSL_CERT_FILE' (SAN: ${SAN_ENTRIES})"
|
|
|
|
|
openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days "$SSL_DAYS" \
|
|
|
|
|
-keyout "$SSL_KEY_FILE" \
|
|
|
|
|
-out "$SSL_CERT_FILE" \
|
|
|
|
|
-subj "/CN=localhost" \
|
|
|
|
|
-addext "subjectAltName=${SAN_ENTRIES}" \
|
|
|
|
|
-addext "keyUsage=digitalSignature,keyEncipherment" \
|
|
|
|
|
-addext "extendedKeyUsage=serverAuth"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "Starting HTTPS server on https://localhost:${UVICORN_PORT}"
|
|
|
|
|
exec uvicorn app:app --host "$UVICORN_HOST" --port "$UVICORN_PORT" \
|
|
|
|
|
--ssl-certfile "$SSL_CERT_FILE" \
|
|
|
|
|
--ssl-keyfile "$SSL_KEY_FILE"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "Starting HTTP server on http://localhost:${UVICORN_PORT}"
|
|
|
|
|
exec uvicorn app:app --host "$UVICORN_HOST" --port "$UVICORN_PORT"
|