| | |
| | | #!/usr/bin/env bash
|
| | | set -euo pipefail
|
| | |
|
| | | #!/usr/bin/env bash |
| | | set -euo pipefail |
| | | |
| | | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| | | WORKSPACE_ROOT="${COMBRABO_VOICE_RUNTIME_HELPER_WORKDIR:-${SCRIPT_DIR}}" |
| | | 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}"
|
| | | }
|
| | |
|
| | | 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}"
|
| | | 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
|
| | | -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_ROLE_ID |
| | | -e CV_DEVICE_OUTPUT_SMOKE_ENABLED |
| | | -e CV_DEVICE_OUTPUT_DESTINATION_IDENTITIES |
| | | -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 [[ -n "${livekit_url}" ]]; then
|
| | | export CV_LIVEKIT_URL="$(rewrite_livekit_url_for_docker "${livekit_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
|
| | | 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
|
| | | 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 |