#!/usr/bin/env bash
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
WORKSPACE_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
MANIFEST_PATH="${SCRIPT_DIR}/Cargo.toml"
|
BINARY_PATH="${SCRIPT_DIR}/target/release/combrabo-voice-runtime-helper"
|
DOCKERFILE_PATH="${SCRIPT_DIR}/Dockerfile"
|
IMAGE_NAME="${COMBRABO_VOICE_RUNTIME_HELPER_IMAGE:-combrabo-voice-runtime-helper:local}"
|
MODE="${COMBRABO_VOICE_RUNTIME_HELPER_MODE:-auto}"
|
|
usage() {
|
cat <<'EOF'
|
Usage:
|
run-local.sh [--prepare] [--rebuild]
|
|
Options:
|
--prepare Prepare the current helper runtime mode without starting the helper.
|
--rebuild Force docker image rebuild in docker mode.
|
EOF
|
}
|
|
PREPARE_ONLY=false
|
FORCE_REBUILD=false
|
|
while [[ $# -gt 0 ]]; do
|
case "$1" in
|
--prepare)
|
PREPARE_ONLY=true
|
shift
|
;;
|
--rebuild)
|
FORCE_REBUILD=true
|
shift
|
;;
|
-h|--help)
|
usage
|
exit 0
|
;;
|
*)
|
echo "Unknown argument: $1" >&2
|
usage >&2
|
exit 1
|
;;
|
esac
|
done
|
|
resolve_mode() {
|
local requested="$1"
|
if [[ "${requested}" != "auto" ]]; then
|
printf '%s' "${requested}"
|
return 0
|
fi
|
if [[ "$(uname -s)" == "Darwin" ]] && command -v docker >/dev/null 2>&1; then
|
printf '%s' "docker"
|
return 0
|
fi
|
printf '%s' "host"
|
}
|
|
prepare_host() {
|
(cd "${SCRIPT_DIR}" && cargo build --release)
|
}
|
|
prepare_docker() {
|
if ! command -v docker >/dev/null 2>&1; then
|
echo "Docker is required for docker helper mode, but docker is not installed." >&2
|
exit 1
|
fi
|
if [[ "${FORCE_REBUILD}" == "true" ]] || ! docker image inspect "${IMAGE_NAME}" >/dev/null 2>&1; then
|
docker build -t "${IMAGE_NAME}" "${SCRIPT_DIR}"
|
fi
|
}
|
|
rewrite_livekit_url_for_docker() {
|
local url="$1"
|
if [[ "${url}" =~ ^(ws|wss)://(127\.0\.0\.1|localhost)([:/].*)?$ ]]; then
|
printf '%s://host.docker.internal%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[3]:-}"
|
return 0
|
fi
|
printf '%s' "${url}"
|
}
|
|
rewrite_http_url_for_docker() {
|
local url="$1"
|
if [[ "${url}" =~ ^(http|https)://(127\.0\.0\.1|localhost)([:/].*)?$ ]]; then
|
printf '%s://host.docker.internal%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[3]:-}"
|
return 0
|
fi
|
printf '%s' "${url}"
|
}
|
|
is_truthy() {
|
case "${1:-}" in
|
true|TRUE|1|yes|YES|on|ON)
|
return 0
|
;;
|
*)
|
return 1
|
;;
|
esac
|
}
|
|
docker_service_host() {
|
local bind_addr="${1:-127.0.0.1:18080}"
|
if [[ "${bind_addr}" =~ ^([^:]+):([0-9]+)$ ]]; then
|
printf '%s' "${BASH_REMATCH[1]}"
|
return 0
|
fi
|
printf '%s' "127.0.0.1"
|
}
|
|
docker_service_port() {
|
local bind_addr="${1:-127.0.0.1:18080}"
|
if [[ "${bind_addr}" =~ ^([^:]+):([0-9]+)$ ]]; then
|
printf '%s' "${BASH_REMATCH[2]}"
|
return 0
|
fi
|
printf '%s' "18080"
|
}
|
|
run_host() {
|
if [[ ! -x "${BINARY_PATH}" ]]; then
|
prepare_host
|
fi
|
exec "${BINARY_PATH}"
|
}
|
|
run_docker() {
|
prepare_docker
|
local livekit_url="${CV_LIVEKIT_URL:-}"
|
local -a docker_args
|
docker_args=(
|
--rm
|
--name "cv-helper-${CV_CALL_ID:-manual}"
|
-v "${WORKSPACE_ROOT}:${WORKSPACE_ROOT}"
|
-w "${WORKSPACE_ROOT}"
|
-e CV_CALL_ID
|
-e CV_TRACE_ID
|
-e CV_GAME_MATCH_ID
|
-e CV_LIVEKIT_URL
|
-e CV_LIVEKIT_ROOM_ID
|
-e CV_LIVEKIT_BOT_TOKEN
|
-e CV_LIVEKIT_BOT_PARTICIPANT_IDENTITY
|
-e CV_LIVEKIT_USER_PARTICIPANT_IDENTITY
|
-e CV_FIRST_AUDIO_SOURCE
|
-e CV_GREETING_SOURCE
|
-e CV_GREETING_AUDIO_FILE
|
-e CV_GREETING_AUDIO_URL
|
-e CV_GREETING_TEXT
|
-e CV_AUDIO_DEBUG_DUMP_DIR
|
-e CV_ENABLE_USER_AUDIO_OBSERVER
|
-e CV_ENABLE_SIMPLE_VAD
|
-e CV_VAD_RMS_THRESHOLD
|
-e CV_VAD_PEAK_THRESHOLD
|
-e CV_VAD_START_FRAMES
|
-e CV_VAD_END_SILENCE_MS
|
-e CV_VAD_MIN_SPEECH_MS
|
-e CV_VAD_MAX_TURN_MS
|
-e CV_VAD_INITIAL_IGNORE_MS
|
-e CV_VAD_GATE_UNTIL_GREETING_DONE
|
-e CV_VAD_POST_GREETING_DELAY_MS
|
-e CV_RUNTIME_TURN_BRIDGE_URL
|
-e CV_RUNTIME_TURN_BRIDGE_TOKEN
|
-e CV_RUNTIME_TURN_ARTIFACT_DIR
|
-e CV_RUNTIME_SESSION_NONCE
|
-e CV_HELPER_SERVICE_ENABLED
|
-e CV_HELPER_SERVICE_BIND
|
-e CV_HELPER_AUTH_TOKEN
|
-e CV_HELPER_AUTH_TOKEN_LOCAL_DEV
|
-e CV_TURN_BRIDGE_TOKEN_LOCAL_DEV
|
-e CV_HELPER_AUTH_TOKEN_DEV
|
-e CV_TURN_BRIDGE_TOKEN_DEV
|
)
|
if is_truthy "${CV_HELPER_SERVICE_ENABLED:-}"; then
|
local service_bind service_host service_port
|
service_bind="${CV_HELPER_SERVICE_BIND:-127.0.0.1:18080}"
|
service_host="$(docker_service_host "${service_bind}")"
|
service_port="$(docker_service_port "${service_bind}")"
|
export CV_HELPER_SERVICE_BIND="0.0.0.0:${service_port}"
|
docker_args+=(-p "${service_host}:${service_port}:${service_port}")
|
fi
|
if [[ -n "${livekit_url}" ]]; then
|
export CV_LIVEKIT_URL="$(rewrite_livekit_url_for_docker "${livekit_url}")"
|
fi
|
if [[ -n "${CV_RUNTIME_TURN_BRIDGE_URL:-}" ]]; then
|
export CV_RUNTIME_TURN_BRIDGE_URL="$(rewrite_http_url_for_docker "${CV_RUNTIME_TURN_BRIDGE_URL}")"
|
fi
|
if [[ -n "${CV_GREETING_AUDIO_FILE:-}" && -f "${CV_GREETING_AUDIO_FILE}" ]]; then
|
local audio_parent
|
audio_parent="$(cd "$(dirname "${CV_GREETING_AUDIO_FILE}")" && pwd)"
|
if [[ "${audio_parent}" != "${WORKSPACE_ROOT}" && "${audio_parent}" != "${WORKSPACE_ROOT}/"* ]]; then
|
docker_args+=(-v "${audio_parent}:${audio_parent}:ro")
|
fi
|
fi
|
if [[ -n "${CV_AUDIO_DEBUG_DUMP_DIR:-}" ]]; then
|
mkdir -p "${CV_AUDIO_DEBUG_DUMP_DIR}"
|
local debug_dump_dir
|
debug_dump_dir="$(cd "${CV_AUDIO_DEBUG_DUMP_DIR}" && pwd)"
|
export CV_AUDIO_DEBUG_DUMP_DIR="${debug_dump_dir}"
|
if [[ "${debug_dump_dir}" != "${WORKSPACE_ROOT}" && "${debug_dump_dir}" != "${WORKSPACE_ROOT}/"* ]]; then
|
docker_args+=(-v "${debug_dump_dir}:${debug_dump_dir}")
|
fi
|
fi
|
if [[ -n "${CV_RUNTIME_TURN_ARTIFACT_DIR:-}" ]]; then
|
mkdir -p "${CV_RUNTIME_TURN_ARTIFACT_DIR}"
|
local turn_artifact_dir
|
turn_artifact_dir="$(cd "${CV_RUNTIME_TURN_ARTIFACT_DIR}" && pwd)"
|
export CV_RUNTIME_TURN_ARTIFACT_DIR="${turn_artifact_dir}"
|
if [[ "${turn_artifact_dir}" != "${WORKSPACE_ROOT}" && "${turn_artifact_dir}" != "${WORKSPACE_ROOT}/"* ]]; then
|
docker_args+=(-v "${turn_artifact_dir}:${turn_artifact_dir}")
|
fi
|
fi
|
exec docker run "${docker_args[@]}" "${IMAGE_NAME}"
|
}
|
|
RUNTIME_MODE="$(resolve_mode "${MODE}")"
|
|
if [[ "${PREPARE_ONLY}" == "true" ]]; then
|
case "${RUNTIME_MODE}" in
|
host)
|
prepare_host
|
;;
|
docker)
|
prepare_docker
|
;;
|
*)
|
echo "Unsupported helper mode: ${RUNTIME_MODE}" >&2
|
exit 1
|
;;
|
esac
|
exit 0
|
fi
|
|
case "${RUNTIME_MODE}" in
|
host)
|
run_host
|
;;
|
docker)
|
run_docker
|
;;
|
*)
|
echo "Unsupported helper mode: ${RUNTIME_MODE}" >&2
|
exit 1
|
;;
|
esac
|