3358 lines
109 KiB
Bash
3358 lines
109 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [[ -z "${BASH_VERSINFO+x}" || "${BASH_VERSINFO[0]}" -lt 4 ]]; then
|
|
echo "Error: scripts/setup/setup.sh requires Bash 4 or newer." >&2
|
|
echo "Hint: install a newer bash and run it via 'bash scripts/setup/setup.sh ...'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
LIB_DIR="$SCRIPT_DIR/lib"
|
|
# shellcheck disable=SC2034
|
|
TEMPLATES_DIR="$SCRIPT_DIR/templates"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
declare -A ENV_VALUES
|
|
declare -A ORIGINAL_ENV_VALUES
|
|
declare -A COMPOSE_ENV_OVERRIDES
|
|
declare -A COMPOSE_REWRITE_SERVICE_SET
|
|
declare -A COMPOSE_SERVICE_IMAGE_OVERRIDES
|
|
declare -A REQUIRED_DB_TYPES
|
|
declare -A DOCKER_SERVICE_SET
|
|
declare -A EXISTING_MANAGED_ROOT_SERVICE_SET
|
|
declare -a DOCKER_SERVICES
|
|
SSL_CERT_SOURCE_PATH=""
|
|
SSL_KEY_SOURCE_PATH=""
|
|
LIGHTRAG_COMPOSE_SERVER_PORT_MAPPING=""
|
|
NORMALIZED_SERVER_HOST_FOR_COMPOSE=""
|
|
FORCE_REWRITE_COMPOSE="no"
|
|
DEBUG="${DEBUG:-false}"
|
|
|
|
PRESET_VLLM_EMBEDDING=(
|
|
"EMBEDDING_BINDING=openai"
|
|
"EMBEDDING_BINDING_HOST=http://localhost:8001/v1"
|
|
"EMBEDDING_MODEL=BAAI/bge-m3"
|
|
"EMBEDDING_DIM=1024"
|
|
"VLLM_EMBED_MODEL=BAAI/bge-m3"
|
|
"VLLM_EMBED_PORT=8001"
|
|
"VLLM_EMBED_DEVICE=cpu"
|
|
)
|
|
|
|
PRESET_VLLM_RERANKER=(
|
|
"RERANK_BINDING=cohere"
|
|
"LIGHTRAG_SETUP_RERANK_PROVIDER=vllm"
|
|
"RERANK_MODEL=BAAI/bge-reranker-v2-m3"
|
|
"RERANK_BINDING_HOST=http://localhost:8000/rerank"
|
|
"VLLM_RERANK_MODEL=BAAI/bge-reranker-v2-m3"
|
|
"VLLM_RERANK_PORT=8000"
|
|
"VLLM_RERANK_DEVICE=cpu"
|
|
)
|
|
VLLM_SERVICES=(
|
|
"vllm-embed"
|
|
"vllm-rerank"
|
|
)
|
|
|
|
STORAGE_SERVICES=(
|
|
"postgres"
|
|
"neo4j"
|
|
"mongodb"
|
|
"redis"
|
|
"milvus"
|
|
"qdrant"
|
|
"memgraph"
|
|
"opensearch"
|
|
)
|
|
DEFAULT_RUNTIME_TARGET="host"
|
|
COMPOSE_LIGHTRAG_WORKING_DIR="/app/data/rag_storage"
|
|
COMPOSE_LIGHTRAG_INPUT_DIR="/app/data/inputs"
|
|
COMPOSE_LIGHTRAG_PROMPT_DIR="/app/data/prompts"
|
|
# shellcheck disable=SC2034
|
|
COLOR_RESET=""
|
|
COLOR_BOLD=""
|
|
COLOR_BLUE=""
|
|
COLOR_GREEN=""
|
|
COLOR_YELLOW=""
|
|
# shellcheck disable=SC2034
|
|
COLOR_RED=""
|
|
|
|
# shellcheck disable=SC1091
|
|
source "$LIB_DIR/storage_requirements.sh"
|
|
# shellcheck disable=SC1091
|
|
source "$LIB_DIR/validation.sh"
|
|
# shellcheck disable=SC1091
|
|
source "$LIB_DIR/prompts.sh"
|
|
# shellcheck disable=SC1091
|
|
source "$LIB_DIR/file_ops.sh"
|
|
# shellcheck disable=SC1091
|
|
source "$LIB_DIR/presets.sh"
|
|
|
|
init_colors() {
|
|
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
|
|
COLOR_RESET=$'\033[0m'
|
|
COLOR_BOLD=$'\033[1m'
|
|
COLOR_BLUE=$'\033[34m'
|
|
COLOR_GREEN=$'\033[32m'
|
|
COLOR_YELLOW=$'\033[33m'
|
|
# shellcheck disable=SC2034
|
|
COLOR_RED=$'\033[31m'
|
|
fi
|
|
}
|
|
|
|
reset_state() {
|
|
ENV_VALUES=()
|
|
ORIGINAL_ENV_VALUES=()
|
|
COMPOSE_ENV_OVERRIDES=()
|
|
COMPOSE_REWRITE_SERVICE_SET=()
|
|
COMPOSE_SERVICE_IMAGE_OVERRIDES=()
|
|
REQUIRED_DB_TYPES=()
|
|
DOCKER_SERVICE_SET=()
|
|
EXISTING_MANAGED_ROOT_SERVICE_SET=()
|
|
DOCKER_SERVICES=()
|
|
SSL_CERT_SOURCE_PATH=""
|
|
SSL_KEY_SOURCE_PATH=""
|
|
LIGHTRAG_COMPOSE_SERVER_PORT_MAPPING=""
|
|
NORMALIZED_SERVER_HOST_FOR_COMPOSE=""
|
|
}
|
|
|
|
validate_runtime_target() {
|
|
local runtime_target="${1:-$DEFAULT_RUNTIME_TARGET}"
|
|
|
|
case "$runtime_target" in
|
|
host|compose)
|
|
return 0
|
|
;;
|
|
*)
|
|
format_error \
|
|
"Invalid LIGHTRAG_RUNTIME_TARGET: ${runtime_target}" \
|
|
"Use 'host' or 'compose', or rerun the setup wizard to regenerate .env."
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
set_runtime_target() {
|
|
local runtime_target="${1:-$DEFAULT_RUNTIME_TARGET}"
|
|
|
|
if ! validate_runtime_target "$runtime_target"; then
|
|
return 1
|
|
fi
|
|
|
|
ENV_VALUES["LIGHTRAG_RUNTIME_TARGET"]="$runtime_target"
|
|
}
|
|
|
|
clear_deprecated_vllm_dtype_state() {
|
|
unset 'ENV_VALUES[VLLM_EMBED_DTYPE]'
|
|
unset 'ENV_VALUES[VLLM_RERANK_DTYPE]'
|
|
}
|
|
|
|
normalize_vllm_rerank_binding_state() {
|
|
if [[ "${ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}" == "vllm" ]]; then
|
|
ENV_VALUES["RERANK_BINDING"]="cohere"
|
|
fi
|
|
}
|
|
|
|
# Backfill sentinel hosts for bedrock/gemini bindings when LLM_BINDING_HOST or
|
|
# EMBEDDING_BINDING_HOST is missing or empty. Flows that skip collect_llm_config /
|
|
# collect_embedding_config (--server, --storage) would otherwise let the openai URL
|
|
# hardcoded in env.example leak into the regenerated .env.
|
|
normalize_provider_binding_hosts() {
|
|
local llm_sentinel="" embedding_sentinel=""
|
|
case "${ENV_VALUES[LLM_BINDING]:-}" in
|
|
bedrock) llm_sentinel="DEFAULT_BEDROCK_ENDPOINT" ;;
|
|
gemini) llm_sentinel="DEFAULT_GEMINI_ENDPOINT" ;;
|
|
esac
|
|
case "${ENV_VALUES[EMBEDDING_BINDING]:-}" in
|
|
bedrock) embedding_sentinel="DEFAULT_BEDROCK_ENDPOINT" ;;
|
|
gemini) embedding_sentinel="DEFAULT_GEMINI_ENDPOINT" ;;
|
|
esac
|
|
if [[ -n "$llm_sentinel" && -z "${ENV_VALUES[LLM_BINDING_HOST]:-}" ]]; then
|
|
ENV_VALUES["LLM_BINDING_HOST"]="$llm_sentinel"
|
|
fi
|
|
if [[ -n "$embedding_sentinel" && -z "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" ]]; then
|
|
ENV_VALUES["EMBEDDING_BINDING_HOST"]="$embedding_sentinel"
|
|
fi
|
|
}
|
|
|
|
load_existing_env_if_present() {
|
|
local env_file="${REPO_ROOT}/.env"
|
|
|
|
if [[ -f "$env_file" ]]; then
|
|
log_debug "Loading existing .env defaults from $env_file"
|
|
load_env_file "$env_file"
|
|
clear_deprecated_vllm_dtype_state
|
|
normalize_vllm_rerank_binding_state
|
|
normalize_provider_binding_hosts
|
|
if [[ "${ENV_VALUES[SSL]:-false}" == "true" ]]; then
|
|
SSL_CERT_SOURCE_PATH="${ENV_VALUES[SSL_CERTFILE]:-}"
|
|
SSL_KEY_SOURCE_PATH="${ENV_VALUES[SSL_KEYFILE]:-}"
|
|
fi
|
|
|
|
snapshot_original_env_values
|
|
fi
|
|
}
|
|
|
|
snapshot_original_env_values() {
|
|
local key
|
|
|
|
ORIGINAL_ENV_VALUES=()
|
|
for key in "${!ENV_VALUES[@]}"; do
|
|
ORIGINAL_ENV_VALUES["$key"]="${ENV_VALUES[$key]}"
|
|
done
|
|
}
|
|
|
|
prepare_compose_output_from_existing() {
|
|
local output_file="$1"
|
|
local existing_file="$2"
|
|
|
|
if [[ -z "$existing_file" || "$existing_file" == "$output_file" || -f "$output_file" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if ! cp "$existing_file" "$output_file"; then
|
|
format_error "Failed to prepare compose output at ${output_file}" \
|
|
"Check file permissions and available disk space, then rerun setup."
|
|
return 1
|
|
fi
|
|
|
|
log_success "Using ${existing_file} as merge input for ${output_file}"
|
|
}
|
|
|
|
log_debug() {
|
|
if [[ "$DEBUG" == "true" ]]; then
|
|
echo "${COLOR_YELLOW}[debug]${COLOR_RESET} $*"
|
|
fi
|
|
}
|
|
|
|
log_info() {
|
|
echo "${COLOR_BLUE}${COLOR_BOLD}$*${COLOR_RESET}"
|
|
}
|
|
|
|
log_warn() {
|
|
echo "${COLOR_YELLOW}$*${COLOR_RESET}"
|
|
}
|
|
|
|
log_success() {
|
|
echo "${COLOR_GREEN}$*${COLOR_RESET}"
|
|
}
|
|
|
|
log_step() {
|
|
echo "${COLOR_BLUE}${COLOR_BOLD}$*${COLOR_RESET}"
|
|
}
|
|
|
|
# Return success when the given host binds to a loopback interface only.
|
|
# An empty host means "use the server default" (0.0.0.0), i.e. not loopback.
|
|
host_is_loopback() {
|
|
local host="${1:-}"
|
|
case "$host" in
|
|
localhost|127.0.0.1|::1|127.*) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
# Print a suggestion (informational only — no logic change) when the server is
|
|
# configured to bind to a non-loopback address without any authentication.
|
|
warn_if_network_exposed_without_auth() {
|
|
local host="${ENV_VALUES[HOST]:-0.0.0.0}"
|
|
|
|
if host_is_loopback "$host"; then
|
|
return 0
|
|
fi
|
|
if [[ -n "${ENV_VALUES[AUTH_ACCOUNTS]:-}" || -n "${ENV_VALUES[LIGHTRAG_API_KEY]:-}" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
log_warn "HOST=$host is reachable from the network but no authentication is configured."
|
|
echo " Suggestion: set AUTH_ACCOUNTS (with TOKEN_SECRET) or LIGHTRAG_API_KEY before"
|
|
echo " exposing the server, or bind to a loopback address (HOST=127.0.0.1)."
|
|
}
|
|
|
|
normalize_loopback_uri_for_compose() {
|
|
local uri="$1"
|
|
|
|
if [[ "$uri" =~ ^([a-zA-Z][a-zA-Z0-9+.-]*://)([^/?#]+@)?(localhost|127\.0\.0\.1|0\.0\.0\.0)([/:?].*)?$ ]]; then
|
|
printf '%s%shost.docker.internal%s' \
|
|
"${BASH_REMATCH[1]}" \
|
|
"${BASH_REMATCH[2]}" \
|
|
"${BASH_REMATCH[4]}"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$uri"
|
|
}
|
|
|
|
ensure_mongodb_direct_connection_suffix() {
|
|
local suffix="${1:-}"
|
|
local path query fragment filtered_query=""
|
|
local part
|
|
local -a query_parts=()
|
|
|
|
if [[ "$suffix" == *"#"* ]]; then
|
|
fragment="#${suffix#*#}"
|
|
suffix="${suffix%%#*}"
|
|
else
|
|
fragment=""
|
|
fi
|
|
|
|
if [[ "$suffix" == *"?"* ]]; then
|
|
path="${suffix%%\?*}"
|
|
query="${suffix#*\?}"
|
|
else
|
|
path="$suffix"
|
|
query=""
|
|
fi
|
|
|
|
if [[ -z "$path" ]]; then
|
|
path="/"
|
|
fi
|
|
|
|
if [[ -n "$query" ]]; then
|
|
IFS='&' read -r -a query_parts <<< "$query"
|
|
for part in "${query_parts[@]}"; do
|
|
if [[ -z "$part" || "$part" == directConnection=* ]]; then
|
|
continue
|
|
fi
|
|
if [[ -n "$filtered_query" ]]; then
|
|
filtered_query="${filtered_query}&${part}"
|
|
else
|
|
filtered_query="$part"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [[ -n "$filtered_query" ]]; then
|
|
printf '%s?%s&directConnection=true%s' "$path" "$filtered_query" "$fragment"
|
|
else
|
|
printf '%s?directConnection=true%s' "$path" "$fragment"
|
|
fi
|
|
}
|
|
|
|
mongodb_uri_has_direct_connection_true() {
|
|
local uri="$1"
|
|
local direct_connection_pattern='[?&]directConnection=true([&#]|$)'
|
|
|
|
[[ "$uri" =~ ^mongodb:// ]] && [[ "$uri" =~ $direct_connection_pattern ]]
|
|
}
|
|
|
|
is_wizard_managed_local_mongodb_uri() {
|
|
local uri="$1"
|
|
|
|
[[ "$uri" =~ ^mongodb://([^/?#]+@)?(mongodb|localhost|127\.0\.0\.1|0\.0\.0\.0):27017([/?#].*)?$ ]] && \
|
|
mongodb_uri_has_direct_connection_true "$uri"
|
|
}
|
|
|
|
normalize_mongodb_uri_for_local_service() {
|
|
local uri="$1"
|
|
local suffix
|
|
|
|
if [[ "$uri" =~ ^mongodb://([^/?#]+@)?(mongodb|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
|
|
suffix="$(ensure_mongodb_direct_connection_suffix "${BASH_REMATCH[4]:-/}")"
|
|
printf 'mongodb://localhost:27017%s' "$suffix"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$uri"
|
|
}
|
|
|
|
normalize_neo4j_uri_for_local_service() {
|
|
local uri="$1"
|
|
|
|
if [[ "$uri" =~ ^([a-zA-Z][a-zA-Z0-9+.-]*://)([^/?#]+@)?(neo4j|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
|
|
printf '%s%slocalhost:7687%s' \
|
|
"${BASH_REMATCH[1]}" \
|
|
"${BASH_REMATCH[2]}" \
|
|
"${BASH_REMATCH[5]}"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$uri"
|
|
}
|
|
|
|
normalize_redis_uri_for_local_service() {
|
|
local uri="$1"
|
|
|
|
if [[ "$uri" =~ ^rediss?://([^/?#]+@)?(redis|localhost|127\.0\.0\.1|0\.0\.0\.0)(:([0-9]+))?(/.*)?$ ]]; then
|
|
printf 'redis://localhost:6379%s' "${BASH_REMATCH[5]}"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$uri"
|
|
}
|
|
|
|
normalize_milvus_uri_for_local_service() {
|
|
local uri="$1"
|
|
|
|
if [[ "$uri" =~ ^(https?://)([^/?#]+@)?(milvus|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
|
|
printf '%slocalhost:19530%s' \
|
|
"${BASH_REMATCH[1]}" \
|
|
"${BASH_REMATCH[5]}"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$uri"
|
|
}
|
|
|
|
normalize_qdrant_uri_for_local_service() {
|
|
local uri="$1"
|
|
|
|
if [[ "$uri" =~ ^(https?://)([^/?#]+@)?(qdrant|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
|
|
printf '%slocalhost:6333%s' \
|
|
"${BASH_REMATCH[1]}" \
|
|
"${BASH_REMATCH[5]}"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$uri"
|
|
}
|
|
|
|
normalize_memgraph_uri_for_local_service() {
|
|
local uri="$1"
|
|
|
|
if [[ "$uri" =~ ^(bolt://)([^/?#]+@)?(memgraph|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
|
|
printf 'bolt://localhost:7687%s' "${BASH_REMATCH[5]}"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$uri"
|
|
}
|
|
|
|
normalize_loopback_host_for_compose() {
|
|
local host="$1"
|
|
|
|
if [[ "$host" == "localhost" || "$host" == "127.0.0.1" || "$host" == "0.0.0.0" ]]; then
|
|
printf 'host.docker.internal'
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$host"
|
|
}
|
|
|
|
normalize_opensearch_hosts_for_compose() {
|
|
local hosts="$1"
|
|
local entry=""
|
|
local trimmed=""
|
|
local normalized_entry=""
|
|
local -a raw_entries=()
|
|
local -a normalized_entries=()
|
|
|
|
IFS=',' read -r -a raw_entries <<< "$hosts"
|
|
for entry in "${raw_entries[@]}"; do
|
|
trimmed="${entry#"${entry%%[![:space:]]*}"}"
|
|
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
|
|
# OPENSEARCH_HOSTS is intentionally limited to bare host[:port] entries.
|
|
# TLS is configured separately via OPENSEARCH_USE_SSL, so scheme-bearing
|
|
# URLs are rejected during validation rather than normalized here.
|
|
normalized_entry="$trimmed"
|
|
|
|
if [[ "$trimmed" =~ ^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?$ ]]; then
|
|
normalized_entry="host.docker.internal${BASH_REMATCH[2]}"
|
|
fi
|
|
|
|
normalized_entries+=("$normalized_entry")
|
|
done
|
|
|
|
(
|
|
IFS=','
|
|
printf '%s' "${normalized_entries[*]}"
|
|
)
|
|
}
|
|
|
|
env_value_is_true() {
|
|
local value="${1:-}"
|
|
|
|
case "${value,,}" in
|
|
true|1|yes)
|
|
return 0
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
normalize_server_host_for_compose() {
|
|
# Keep the published bind address/port configurable through compose-time
|
|
# variable expansion, while forcing the container itself to listen on the
|
|
# internal service defaults.
|
|
LIGHTRAG_COMPOSE_SERVER_PORT_MAPPING='${HOST:-0.0.0.0}:${PORT:-9621}:9621'
|
|
NORMALIZED_SERVER_HOST_FOR_COMPOSE="0.0.0.0"
|
|
}
|
|
|
|
host_cuda_available() {
|
|
command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi >/dev/null 2>&1
|
|
}
|
|
|
|
resolve_local_device_default() {
|
|
local configured_device="${1:-}"
|
|
|
|
if [[ "$configured_device" == "cpu" || "$configured_device" == "cuda" ]]; then
|
|
printf '%s' "$configured_device"
|
|
return 0
|
|
fi
|
|
|
|
if host_cuda_available; then
|
|
printf 'cuda'
|
|
else
|
|
printf 'cpu'
|
|
fi
|
|
}
|
|
|
|
default_loopback_url() {
|
|
local port="$1"
|
|
local path="${2:-}"
|
|
printf 'http://localhost:%s%s' "$port" "$path"
|
|
}
|
|
|
|
uri_points_to_host() {
|
|
local uri="$1"
|
|
shift
|
|
local host=""
|
|
local allowed_host
|
|
|
|
if [[ "$uri" =~ ^[a-zA-Z][a-zA-Z0-9+.-]*://([^/?#]+@)?(\[[^]]+\]|[^/:?#]+) ]]; then
|
|
host="${BASH_REMATCH[2]}"
|
|
for allowed_host in "$@"; do
|
|
if [[ "$host" == "$allowed_host" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
prefer_local_service_uri() {
|
|
local current_uri="$1"
|
|
local default_uri="$2"
|
|
shift 2
|
|
|
|
if [[ -z "$current_uri" ]]; then
|
|
printf '%s' "$default_uri"
|
|
return 0
|
|
fi
|
|
|
|
if uri_points_to_host "$current_uri" "$@"; then
|
|
printf '%s' "$current_uri"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$default_uri"
|
|
}
|
|
|
|
set_compose_override() {
|
|
local key="$1"
|
|
local value="${2:-}"
|
|
|
|
if [[ -n "$value" ]]; then
|
|
COMPOSE_ENV_OVERRIDES["$key"]="$value"
|
|
else
|
|
unset "COMPOSE_ENV_OVERRIDES[$key]"
|
|
fi
|
|
}
|
|
|
|
set_managed_service_compose_overrides() {
|
|
local root_service="$1"
|
|
|
|
case "$root_service" in
|
|
postgres)
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[POSTGRES_HOST]+set}" ]]; then
|
|
set_compose_override "POSTGRES_HOST" "postgres"
|
|
fi
|
|
# The bundled postgres compose service always listens on 5432 internally.
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[POSTGRES_PORT]+set}" ]]; then
|
|
set_compose_override "POSTGRES_PORT" "5432"
|
|
fi
|
|
;;
|
|
neo4j)
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[NEO4J_URI]+set}" ]]; then
|
|
set_compose_override "NEO4J_URI" "neo4j://neo4j:7687"
|
|
fi
|
|
;;
|
|
mongodb)
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[MONGO_URI]+set}" ]]; then
|
|
set_compose_override "MONGO_URI" "mongodb://mongodb:27017/?directConnection=true"
|
|
fi
|
|
;;
|
|
redis)
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[REDIS_URI]+set}" ]]; then
|
|
set_compose_override "REDIS_URI" "redis://redis:6379"
|
|
fi
|
|
;;
|
|
milvus)
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[MILVUS_URI]+set}" ]]; then
|
|
set_compose_override "MILVUS_URI" "http://milvus:19530"
|
|
fi
|
|
;;
|
|
qdrant)
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[QDRANT_URL]+set}" ]]; then
|
|
set_compose_override "QDRANT_URL" "http://qdrant:6333"
|
|
fi
|
|
;;
|
|
memgraph)
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[MEMGRAPH_URI]+set}" ]]; then
|
|
set_compose_override "MEMGRAPH_URI" "bolt://memgraph:7687"
|
|
fi
|
|
;;
|
|
opensearch)
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[OPENSEARCH_HOSTS]+set}" ]]; then
|
|
set_compose_override "OPENSEARCH_HOSTS" "opensearch:9200"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
prepare_compose_runtime_overrides() {
|
|
local normalized_value
|
|
local key
|
|
local root_service
|
|
|
|
# EMBEDDING_BINDING_HOST: when vllm-embed is part of this compose, the LightRAG
|
|
# container must reach it by Docker service name, not by a loopback address.
|
|
# This applies even when the wizard did not visit the embedding step (e.g.
|
|
# env_server_flow), because vllm-embed is detected and added to DOCKER_SERVICE_SET
|
|
# before prepare_compose_env_overrides is called.
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[EMBEDDING_BINDING_HOST]+set}" ]]; then
|
|
if [[ -n "${DOCKER_SERVICE_SET[vllm-embed]+set}" ]]; then
|
|
set_compose_override "EMBEDDING_BINDING_HOST" \
|
|
"http://vllm-embed:${ENV_VALUES[VLLM_EMBED_PORT]:-8001}/v1"
|
|
elif [[ -n "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" ]]; then
|
|
normalized_value="$(normalize_loopback_uri_for_compose "${ENV_VALUES[EMBEDDING_BINDING_HOST]}")"
|
|
if [[ "$normalized_value" != "${ENV_VALUES[EMBEDDING_BINDING_HOST]}" ]]; then
|
|
set_compose_override "EMBEDDING_BINDING_HOST" "$normalized_value"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# RERANK_BINDING_HOST: same pattern for vllm-rerank.
|
|
if [[ -z "${COMPOSE_ENV_OVERRIDES[RERANK_BINDING_HOST]+set}" ]]; then
|
|
if [[ -n "${DOCKER_SERVICE_SET[vllm-rerank]+set}" ]]; then
|
|
set_compose_override "RERANK_BINDING_HOST" \
|
|
"http://vllm-rerank:${ENV_VALUES[VLLM_RERANK_PORT]:-8000}/rerank"
|
|
elif [[ -n "${ENV_VALUES[RERANK_BINDING_HOST]:-}" ]]; then
|
|
normalized_value="$(normalize_loopback_uri_for_compose "${ENV_VALUES[RERANK_BINDING_HOST]}")"
|
|
if [[ "$normalized_value" != "${ENV_VALUES[RERANK_BINDING_HOST]}" ]]; then
|
|
set_compose_override "RERANK_BINDING_HOST" "$normalized_value"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
for root_service in postgres neo4j mongodb redis milvus qdrant memgraph opensearch; do
|
|
if [[ -n "${DOCKER_SERVICE_SET[$root_service]+set}" ]]; then
|
|
set_managed_service_compose_overrides "$root_service"
|
|
fi
|
|
done
|
|
|
|
for key in \
|
|
"LLM_BINDING_HOST" \
|
|
"REDIS_URI" \
|
|
"MONGO_URI" \
|
|
"NEO4J_URI" \
|
|
"MILVUS_URI" \
|
|
"QDRANT_URL" \
|
|
"MEMGRAPH_URI"; do
|
|
if [[ -n "${COMPOSE_ENV_OVERRIDES[$key]+set}" ]]; then
|
|
continue
|
|
fi
|
|
if [[ -n "${ENV_VALUES[$key]:-}" ]]; then
|
|
normalized_value="$(normalize_loopback_uri_for_compose "${ENV_VALUES[$key]}")"
|
|
if [[ "$normalized_value" != "${ENV_VALUES[$key]}" ]]; then
|
|
set_compose_override "$key" "$normalized_value"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
for key in "OPENSEARCH_HOSTS"; do
|
|
if [[ -n "${COMPOSE_ENV_OVERRIDES[$key]+set}" ]]; then
|
|
continue
|
|
fi
|
|
if [[ -n "${ENV_VALUES[$key]:-}" ]]; then
|
|
normalized_value="$(normalize_opensearch_hosts_for_compose "${ENV_VALUES[$key]}")"
|
|
if [[ "$normalized_value" != "${ENV_VALUES[$key]}" ]]; then
|
|
set_compose_override "$key" "$normalized_value"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
for key in "POSTGRES_HOST"; do
|
|
if [[ -n "${COMPOSE_ENV_OVERRIDES[$key]+set}" ]]; then
|
|
continue
|
|
fi
|
|
if [[ -n "${ENV_VALUES[$key]:-}" ]]; then
|
|
normalized_value="$(normalize_loopback_host_for_compose "${ENV_VALUES[$key]}")"
|
|
if [[ "$normalized_value" != "${ENV_VALUES[$key]}" ]]; then
|
|
set_compose_override "$key" "$normalized_value"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
normalize_server_host_for_compose "${ENV_VALUES[HOST]:-0.0.0.0}"
|
|
normalized_value="$NORMALIZED_SERVER_HOST_FOR_COMPOSE"
|
|
set_compose_override "HOST" "$normalized_value"
|
|
set_compose_override "PORT" "9621"
|
|
}
|
|
|
|
prepare_compose_ssl_overrides() {
|
|
local cert_name=""
|
|
local key_name=""
|
|
|
|
if [[ -n "$SSL_CERT_SOURCE_PATH" ]]; then
|
|
cert_name="$(resolve_staged_ssl_basename "cert" "$SSL_CERT_SOURCE_PATH" "$SSL_KEY_SOURCE_PATH")"
|
|
set_compose_override "SSL_CERTFILE" "/app/data/certs/${cert_name}"
|
|
fi
|
|
|
|
if [[ -n "$SSL_KEY_SOURCE_PATH" ]]; then
|
|
key_name="$(resolve_staged_ssl_basename "key" "$SSL_KEY_SOURCE_PATH" "$SSL_CERT_SOURCE_PATH")"
|
|
set_compose_override "SSL_KEYFILE" "/app/data/certs/${key_name}"
|
|
fi
|
|
}
|
|
|
|
prepare_compose_data_path_overrides() {
|
|
# Compose mounts always bind the data directories into these container paths.
|
|
# Force lightrag to use them so values from the mounted .env cannot redirect
|
|
# storage into a different location.
|
|
set_compose_override "WORKING_DIR" "$COMPOSE_LIGHTRAG_WORKING_DIR"
|
|
set_compose_override "INPUT_DIR" "$COMPOSE_LIGHTRAG_INPUT_DIR"
|
|
set_compose_override "PROMPT_DIR" "$COMPOSE_LIGHTRAG_PROMPT_DIR"
|
|
}
|
|
|
|
prepare_compose_env_overrides() {
|
|
prepare_compose_data_path_overrides
|
|
prepare_compose_runtime_overrides
|
|
prepare_compose_ssl_overrides
|
|
}
|
|
|
|
add_docker_service() {
|
|
local service="$1"
|
|
|
|
if [[ -z "${DOCKER_SERVICE_SET[$service]+set}" ]]; then
|
|
DOCKER_SERVICE_SET["$service"]=1
|
|
DOCKER_SERVICES+=("$service")
|
|
fi
|
|
}
|
|
|
|
restore_storage_docker_services_from_env() {
|
|
local db_type
|
|
local marker_key=""
|
|
local service_name=""
|
|
local db_types=("postgresql" "neo4j" "mongodb" "redis" "milvus" "qdrant" "memgraph" "opensearch")
|
|
|
|
for db_type in "${db_types[@]}"; do
|
|
marker_key="$(storage_deployment_marker_key "$db_type")"
|
|
if [[ -n "$marker_key" && "${ENV_VALUES[$marker_key]:-}" == "docker" ]]; then
|
|
service_name="$(storage_service_name_for_db_type "$db_type")"
|
|
if [[ -n "$service_name" ]]; then
|
|
add_docker_service "$service_name"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
restore_vllm_docker_services_from_env() {
|
|
if [[ "${ENV_VALUES[LIGHTRAG_SETUP_EMBEDDING_PROVIDER]:-}" == "vllm" ]]; then
|
|
add_docker_service "vllm-embed"
|
|
fi
|
|
|
|
if [[ "${ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}" == "vllm" ]]; then
|
|
add_docker_service "vllm-rerank"
|
|
fi
|
|
}
|
|
|
|
compose_has_non_wizard_services() {
|
|
local compose_file="$1"
|
|
local service_name=""
|
|
|
|
if [[ -z "$compose_file" || ! -f "$compose_file" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
while IFS= read -r service_name; do
|
|
if [[ -z "$(_managed_service_root_name "$service_name")" ]]; then
|
|
return 0
|
|
fi
|
|
done < <(detect_compose_services "$compose_file")
|
|
|
|
return 1
|
|
}
|
|
|
|
resolve_compose_output_action() {
|
|
local existing_compose="$1"
|
|
local -n action_ref="$2"
|
|
local -n runtime_target_ref="$3"
|
|
local -n host_hint_ref="$4"
|
|
local current_target="${ENV_VALUES[LIGHTRAG_RUNTIME_TARGET]:-$DEFAULT_RUNTIME_TARGET}"
|
|
|
|
action_ref="write_env_only"
|
|
runtime_target_ref="$DEFAULT_RUNTIME_TARGET"
|
|
host_hint_ref="no"
|
|
|
|
if ((${#DOCKER_SERVICES[@]} > 0)); then
|
|
action_ref="rewrite_compose"
|
|
runtime_target_ref="compose"
|
|
return 0
|
|
fi
|
|
|
|
if compose_has_non_wizard_services "$existing_compose"; then
|
|
action_ref="rewrite_compose"
|
|
runtime_target_ref="compose"
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "$existing_compose" ]]; then
|
|
if confirm_default_no "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?"; then
|
|
action_ref="delete_compose_and_switch_host"
|
|
runtime_target_ref="host"
|
|
host_hint_ref="yes"
|
|
else
|
|
action_ref="rewrite_compose"
|
|
runtime_target_ref="compose"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$current_target" == "compose" ]]; then
|
|
if confirm_default_yes "Run LightRAG Server via Docker?"; then
|
|
action_ref="rewrite_compose"
|
|
runtime_target_ref="compose"
|
|
else
|
|
host_hint_ref="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run LightRAG Server via Docker?"; then
|
|
action_ref="rewrite_compose"
|
|
runtime_target_ref="compose"
|
|
else
|
|
host_hint_ref="yes"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
mark_compose_service_for_rewrite() {
|
|
local service="$1"
|
|
local root_service=""
|
|
|
|
root_service="$(_managed_service_root_name "$service")"
|
|
if [[ -n "$root_service" ]]; then
|
|
COMPOSE_REWRITE_SERVICE_SET["$root_service"]=1
|
|
fi
|
|
}
|
|
|
|
record_existing_managed_root_services() {
|
|
local compose_file="$1"
|
|
local service_name
|
|
local root_service
|
|
|
|
EXISTING_MANAGED_ROOT_SERVICE_SET=()
|
|
|
|
if [[ -z "$compose_file" || ! -f "$compose_file" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
while IFS= read -r service_name; do
|
|
root_service="$(_managed_service_root_name "$service_name")"
|
|
if [[ -n "$root_service" ]]; then
|
|
EXISTING_MANAGED_ROOT_SERVICE_SET["$root_service"]=1
|
|
fi
|
|
done < <(detect_managed_root_services "$compose_file")
|
|
}
|
|
|
|
collect_preserved_storage_service_images() {
|
|
local compose_file="${1:-}"
|
|
local service_name=""
|
|
local image_value=""
|
|
|
|
COMPOSE_SERVICE_IMAGE_OVERRIDES=()
|
|
|
|
if [[ "$FORCE_REWRITE_COMPOSE" == "yes" || -z "$compose_file" || ! -f "$compose_file" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Only postgres and neo4j are wizard-managed Docker storage services that users
|
|
# commonly pin to custom registry images. If new storage backends are added as
|
|
# wizard-managed Docker services, extend this list accordingly.
|
|
for service_name in postgres neo4j; do
|
|
if [[ -z "${COMPOSE_REWRITE_SERVICE_SET[$service_name]+set}" ]] || \
|
|
[[ -z "${DOCKER_SERVICE_SET[$service_name]+set}" ]] || \
|
|
! existing_managed_root_service_present "$service_name"; then
|
|
continue
|
|
fi
|
|
|
|
image_value="$(read_service_image_value "$compose_file" "$service_name" || true)"
|
|
if [[ -n "$image_value" ]]; then
|
|
COMPOSE_SERVICE_IMAGE_OVERRIDES["$service_name"]="$image_value"
|
|
fi
|
|
done
|
|
}
|
|
|
|
backup_existing_compose_if_generating() {
|
|
local generate_compose="${1:-no}"
|
|
local existing_compose="${2:-}"
|
|
local compose_backup_path=""
|
|
|
|
if [[ "$generate_compose" != "yes" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
compose_backup_path="$(backup_compose_file "$existing_compose")" || return 1
|
|
if [[ -n "$compose_backup_path" ]]; then
|
|
log_success "Backed up existing compose file to $compose_backup_path"
|
|
fi
|
|
}
|
|
|
|
backup_existing_compose_for_action() {
|
|
local compose_action="${1:-write_env_only}"
|
|
local existing_compose="${2:-}"
|
|
local compose_backup_path=""
|
|
|
|
case "$compose_action" in
|
|
rewrite_compose|delete_compose_and_switch_host)
|
|
;;
|
|
*)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
compose_backup_path="$(backup_compose_file "$existing_compose")" || return 1
|
|
if [[ -n "$compose_backup_path" ]]; then
|
|
log_success "Backed up existing compose file to $compose_backup_path"
|
|
fi
|
|
}
|
|
|
|
remove_existing_compose_file() {
|
|
local compose_file="${1:-}"
|
|
|
|
if [[ -z "$compose_file" || ! -f "$compose_file" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if ! rm "$compose_file"; then
|
|
format_error "Failed to remove ${compose_file}" \
|
|
"Check file permissions, then remove the compose file manually or rerun setup."
|
|
return 1
|
|
fi
|
|
|
|
log_success "Removed ${compose_file}"
|
|
}
|
|
|
|
existing_managed_root_service_present() {
|
|
local root_service="$1"
|
|
|
|
[[ -n "${EXISTING_MANAGED_ROOT_SERVICE_SET[$root_service]+set}" ]]
|
|
}
|
|
|
|
compose_service_block_contains_literal() {
|
|
local compose_file="$1"
|
|
local service_name="$2"
|
|
local literal="$3"
|
|
|
|
if [[ -z "$compose_file" || ! -f "$compose_file" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
awk -v header=" ${service_name}:" -v literal="$literal" '
|
|
$0 == header { in_service = 1; next }
|
|
in_service && $0 ~ /^ [^[:space:]]/ { exit found ? 0 : 1 }
|
|
in_service && index($0, literal) { found = 1; exit 0 }
|
|
END { exit found ? 0 : 1 }
|
|
' "$compose_file"
|
|
}
|
|
|
|
mongodb_service_requires_atlas_local_rewrite() {
|
|
local compose_file="${1:-}"
|
|
|
|
if [[ -z "$compose_file" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ "${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" != "MongoVectorDBStorage" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ "${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}" != "docker" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ -z "${DOCKER_SERVICE_SET[mongodb]+set}" ]] || \
|
|
! existing_managed_root_service_present "mongodb"; then
|
|
return 1
|
|
fi
|
|
|
|
if ! compose_service_block_contains_literal "$compose_file" "mongodb" "image: mongodb/mongodb-atlas-local:"; then
|
|
return 0
|
|
fi
|
|
|
|
if ! compose_service_block_contains_literal "$compose_file" "mongodb" "mongo_config_data:/data/configdb"; then
|
|
return 0
|
|
fi
|
|
|
|
if ! compose_service_block_contains_literal "$compose_file" "mongodb" "mongo_mongot_data:/data/mongot"; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
configure_mongodb_compose_migration_rewrite() {
|
|
local existing_compose="${1:-}"
|
|
|
|
if [[ "$FORCE_REWRITE_COMPOSE" == "yes" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if mongodb_service_requires_atlas_local_rewrite "$existing_compose"; then
|
|
mark_compose_service_for_rewrite "mongodb"
|
|
fi
|
|
}
|
|
|
|
env_value_changed_from_original() {
|
|
local key="$1"
|
|
local missing_marker="__LIGHTRAG_MISSING__"
|
|
local current_value="${ENV_VALUES[$key]-$missing_marker}"
|
|
local original_value="${ORIGINAL_ENV_VALUES[$key]-$missing_marker}"
|
|
|
|
[[ "$current_value" != "$original_value" ]]
|
|
}
|
|
|
|
any_env_value_changed_from_original() {
|
|
local key
|
|
|
|
for key in "$@"; do
|
|
if env_value_changed_from_original "$key"; then
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
compose_template_variant_for_service() {
|
|
local service="$1"
|
|
local snapshot="${2:-current}"
|
|
local device=""
|
|
|
|
case "$service" in
|
|
milvus)
|
|
if [[ "$snapshot" == "original" ]]; then
|
|
device="${ORIGINAL_ENV_VALUES[MILVUS_DEVICE]:-cpu}"
|
|
else
|
|
device="${ENV_VALUES[MILVUS_DEVICE]:-cpu}"
|
|
fi
|
|
;;
|
|
qdrant)
|
|
if [[ "$snapshot" == "original" ]]; then
|
|
device="${ORIGINAL_ENV_VALUES[QDRANT_DEVICE]:-cpu}"
|
|
else
|
|
device="${ENV_VALUES[QDRANT_DEVICE]:-cpu}"
|
|
fi
|
|
;;
|
|
vllm-embed)
|
|
if [[ "$snapshot" == "original" ]]; then
|
|
device="${ORIGINAL_ENV_VALUES[VLLM_EMBED_DEVICE]:-cpu}"
|
|
else
|
|
device="${ENV_VALUES[VLLM_EMBED_DEVICE]:-cpu}"
|
|
fi
|
|
;;
|
|
vllm-rerank)
|
|
if [[ "$snapshot" == "original" ]]; then
|
|
device="${ORIGINAL_ENV_VALUES[VLLM_RERANK_DEVICE]:-cpu}"
|
|
else
|
|
device="${ENV_VALUES[VLLM_RERANK_DEVICE]:-cpu}"
|
|
fi
|
|
;;
|
|
*)
|
|
printf 'default'
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
if [[ "$device" == "cuda" ]]; then
|
|
printf 'gpu'
|
|
else
|
|
printf 'cpu'
|
|
fi
|
|
}
|
|
|
|
configure_base_compose_rewrites() {
|
|
if [[ "$FORCE_REWRITE_COMPOSE" == "yes" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if existing_managed_root_service_present "vllm-embed" && \
|
|
[[ -n "${DOCKER_SERVICE_SET[vllm-embed]+set}" ]] && \
|
|
[[ "$(compose_template_variant_for_service "vllm-embed" "current")" != \
|
|
"$(compose_template_variant_for_service "vllm-embed" "original")" ]]; then
|
|
mark_compose_service_for_rewrite "vllm-embed"
|
|
fi
|
|
|
|
if existing_managed_root_service_present "vllm-rerank" && \
|
|
[[ -n "${DOCKER_SERVICE_SET[vllm-rerank]+set}" ]] && \
|
|
[[ "$(compose_template_variant_for_service "vllm-rerank" "current")" != \
|
|
"$(compose_template_variant_for_service "vllm-rerank" "original")" ]]; then
|
|
mark_compose_service_for_rewrite "vllm-rerank"
|
|
fi
|
|
}
|
|
|
|
configure_storage_compose_rewrites() {
|
|
if [[ "$FORCE_REWRITE_COMPOSE" == "yes" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if existing_managed_root_service_present "postgres" && \
|
|
[[ -n "${DOCKER_SERVICE_SET[postgres]+set}" ]] && \
|
|
any_env_value_changed_from_original "POSTGRES_USER" "POSTGRES_PASSWORD" "POSTGRES_DATABASE"; then
|
|
mark_compose_service_for_rewrite "postgres"
|
|
fi
|
|
|
|
if existing_managed_root_service_present "neo4j" && \
|
|
[[ -n "${DOCKER_SERVICE_SET[neo4j]+set}" ]] && \
|
|
any_env_value_changed_from_original "NEO4J_DATABASE"; then
|
|
mark_compose_service_for_rewrite "neo4j"
|
|
fi
|
|
|
|
if existing_managed_root_service_present "milvus" && \
|
|
[[ -n "${DOCKER_SERVICE_SET[milvus]+set}" ]] && \
|
|
[[ "$(compose_template_variant_for_service "milvus" "current")" != \
|
|
"$(compose_template_variant_for_service "milvus" "original")" ]]; then
|
|
mark_compose_service_for_rewrite "milvus"
|
|
fi
|
|
|
|
if existing_managed_root_service_present "qdrant" && \
|
|
[[ -n "${DOCKER_SERVICE_SET[qdrant]+set}" ]] && \
|
|
[[ "$(compose_template_variant_for_service "qdrant" "current")" != \
|
|
"$(compose_template_variant_for_service "qdrant" "original")" ]]; then
|
|
mark_compose_service_for_rewrite "qdrant"
|
|
fi
|
|
}
|
|
|
|
select_storage_backends() {
|
|
local deployment_type="$1"
|
|
local kv_default="JsonKVStorage"
|
|
local vector_default="NanoVectorDBStorage"
|
|
local graph_default="NetworkXStorage"
|
|
local doc_default="JsonDocStatusStorage"
|
|
local kv_storage vector_storage graph_storage doc_storage
|
|
|
|
if [[ "$deployment_type" == "production" ]]; then
|
|
kv_default="PGKVStorage"
|
|
vector_default="MilvusVectorDBStorage"
|
|
graph_default="Neo4JStorage"
|
|
doc_default="PGDocStatusStorage"
|
|
fi
|
|
|
|
kv_default="${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-$kv_default}"
|
|
vector_default="${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-$vector_default}"
|
|
graph_default="${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-$graph_default}"
|
|
doc_default="${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-$doc_default}"
|
|
|
|
while true; do
|
|
kv_storage="$(prompt_choice "KV storage" "$kv_default" "${KV_STORAGE_OPTIONS[@]}")"
|
|
doc_storage="$(prompt_choice "Doc status storage" "$doc_default" "${DOC_STATUS_STORAGE_OPTIONS[@]}")"
|
|
vector_storage="$(prompt_choice "Vector storage" "$vector_default" "${VECTOR_STORAGE_OPTIONS[@]}")"
|
|
graph_storage="$(prompt_choice "Graph storage" "$graph_default" "${GRAPH_STORAGE_OPTIONS[@]}")"
|
|
|
|
if check_storage_compatibility "$kv_storage" "$vector_storage" "$graph_storage" "$doc_storage"; then
|
|
break
|
|
fi
|
|
|
|
if confirm_default_no "Proceed with these storage selections anyway?"; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
ENV_VALUES["LIGHTRAG_KV_STORAGE"]="$kv_storage"
|
|
ENV_VALUES["LIGHTRAG_VECTOR_STORAGE"]="$vector_storage"
|
|
ENV_VALUES["LIGHTRAG_GRAPH_STORAGE"]="$graph_storage"
|
|
ENV_VALUES["LIGHTRAG_DOC_STATUS_STORAGE"]="$doc_storage"
|
|
|
|
for storage in "$kv_storage" "$vector_storage" "$graph_storage" "$doc_storage"; do
|
|
if [[ -n "${STORAGE_DB_TYPES[$storage]:-}" ]]; then
|
|
REQUIRED_DB_TYPES["${STORAGE_DB_TYPES[$storage]}"]=1
|
|
fi
|
|
done
|
|
}
|
|
|
|
initialize_default_storage_backends() {
|
|
# env-base does not prompt for storage, but its generated .env must remain
|
|
# self-consistent for first-run users who have not run env-storage yet.
|
|
ENV_VALUES["LIGHTRAG_KV_STORAGE"]="${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-JsonKVStorage}"
|
|
ENV_VALUES["LIGHTRAG_VECTOR_STORAGE"]="${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-NanoVectorDBStorage}"
|
|
ENV_VALUES["LIGHTRAG_GRAPH_STORAGE"]="${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-NetworkXStorage}"
|
|
ENV_VALUES["LIGHTRAG_DOC_STATUS_STORAGE"]="${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-JsonDocStatusStorage}"
|
|
}
|
|
|
|
storage_service_name_for_db_type() {
|
|
local db_type="$1"
|
|
|
|
case "$db_type" in
|
|
postgresql)
|
|
printf 'postgres'
|
|
;;
|
|
neo4j|mongodb|redis|milvus|qdrant|memgraph|opensearch)
|
|
printf '%s' "$db_type"
|
|
;;
|
|
*)
|
|
printf ''
|
|
;;
|
|
esac
|
|
}
|
|
|
|
storage_deployment_marker_key() {
|
|
local db_type="$1"
|
|
|
|
case "$db_type" in
|
|
postgresql)
|
|
printf 'LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT'
|
|
;;
|
|
neo4j)
|
|
printf 'LIGHTRAG_SETUP_NEO4J_DEPLOYMENT'
|
|
;;
|
|
mongodb)
|
|
printf 'LIGHTRAG_SETUP_MONGODB_DEPLOYMENT'
|
|
;;
|
|
redis)
|
|
printf 'LIGHTRAG_SETUP_REDIS_DEPLOYMENT'
|
|
;;
|
|
milvus)
|
|
printf 'LIGHTRAG_SETUP_MILVUS_DEPLOYMENT'
|
|
;;
|
|
qdrant)
|
|
printf 'LIGHTRAG_SETUP_QDRANT_DEPLOYMENT'
|
|
;;
|
|
memgraph)
|
|
printf 'LIGHTRAG_SETUP_MEMGRAPH_DEPLOYMENT'
|
|
;;
|
|
opensearch)
|
|
printf 'LIGHTRAG_SETUP_OPENSEARCH_DEPLOYMENT'
|
|
;;
|
|
*)
|
|
printf ''
|
|
;;
|
|
esac
|
|
}
|
|
|
|
storage_default_docker_for_db_type() {
|
|
local db_type="$1"
|
|
local marker_key
|
|
|
|
marker_key="$(storage_deployment_marker_key "$db_type")"
|
|
if [[ -n "$marker_key" && "${ENV_VALUES[$marker_key]:-}" == "docker" ]]; then
|
|
printf 'yes'
|
|
else
|
|
printf 'no'
|
|
fi
|
|
}
|
|
|
|
persist_storage_deployment_choice() {
|
|
local db_type="$1"
|
|
local deployment_mode="${2:-no}"
|
|
local marker_key
|
|
|
|
marker_key="$(storage_deployment_marker_key "$db_type")"
|
|
if [[ -z "$marker_key" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
case "$deployment_mode" in
|
|
yes|docker)
|
|
ENV_VALUES["$marker_key"]="docker"
|
|
;;
|
|
no|'')
|
|
unset "ENV_VALUES[$marker_key]"
|
|
;;
|
|
*)
|
|
ENV_VALUES["$marker_key"]="$deployment_mode"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
clear_unused_storage_deployment_markers() {
|
|
local db_type
|
|
|
|
for db_type in postgresql neo4j mongodb redis milvus qdrant memgraph opensearch; do
|
|
if [[ -z "${REQUIRED_DB_TYPES[$db_type]+set}" ]]; then
|
|
persist_storage_deployment_choice "$db_type" "no"
|
|
fi
|
|
done
|
|
}
|
|
|
|
collect_database_config() {
|
|
local db_type="$1"
|
|
local default_docker="${2:-no}"
|
|
local service_name=""
|
|
local deployment_mode="no"
|
|
|
|
# Storage collector rule for this wizard:
|
|
# - Existing ENV_VALUES loaded from .env are user-owned configuration.
|
|
# - Collectors should use those values as defaults and preserve them when they
|
|
# are already set, even for Docker-managed services.
|
|
# - A collector may normalize the stored form, or write a hard default only
|
|
# when the key is absent.
|
|
# Keep future storage collectors aligned with this behavior so rerunning the
|
|
# wizard does not silently erase explicit .env overrides.
|
|
|
|
case "$db_type" in
|
|
postgresql)
|
|
collect_postgres_config "$default_docker"
|
|
;;
|
|
neo4j)
|
|
collect_neo4j_config "$default_docker"
|
|
;;
|
|
mongodb)
|
|
collect_mongodb_config "$default_docker"
|
|
;;
|
|
redis)
|
|
collect_redis_config "$default_docker"
|
|
;;
|
|
milvus)
|
|
collect_milvus_config "$default_docker"
|
|
;;
|
|
qdrant)
|
|
collect_qdrant_config "$default_docker"
|
|
;;
|
|
memgraph)
|
|
collect_memgraph_config "$default_docker"
|
|
;;
|
|
opensearch)
|
|
collect_opensearch_config "$default_docker"
|
|
;;
|
|
*)
|
|
echo "Unknown database type: $db_type" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
service_name="$(storage_service_name_for_db_type "$db_type")"
|
|
if [[ -n "$service_name" && -n "${DOCKER_SERVICE_SET[$service_name]+set}" ]]; then
|
|
deployment_mode="docker"
|
|
fi
|
|
persist_storage_deployment_choice "$db_type" "$deployment_mode"
|
|
}
|
|
|
|
collect_postgres_config() {
|
|
local default_docker="${1:-no}"
|
|
local use_docker="no"
|
|
local host port user password database
|
|
local existing_user="" existing_password="" existing_database=""
|
|
|
|
if [[ "$default_docker" == "yes" ]]; then
|
|
if confirm_default_yes "Run PostgreSQL locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run PostgreSQL locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
add_docker_service "postgres"
|
|
host="${ENV_VALUES[POSTGRES_HOST]:-localhost}"
|
|
if [[ "$host" != "localhost" && "$host" != "127.0.0.1" && "$host" != "0.0.0.0" && "$host" != "postgres" ]]; then
|
|
host="localhost"
|
|
elif [[ "$host" == "postgres" ]]; then
|
|
host="localhost"
|
|
fi
|
|
else
|
|
host="${ENV_VALUES[POSTGRES_HOST]:-localhost}"
|
|
fi
|
|
|
|
host="$(prompt_with_default "PostgreSQL host" "$host")"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
port="5432"
|
|
set_compose_override "POSTGRES_HOST" "postgres"
|
|
set_compose_override "POSTGRES_PORT" "5432"
|
|
else
|
|
port="$(prompt_until_valid "PostgreSQL port" "${ENV_VALUES[POSTGRES_PORT]:-5432}" validate_port)"
|
|
set_compose_override "POSTGRES_HOST" ""
|
|
set_compose_override "POSTGRES_PORT" ""
|
|
fi
|
|
|
|
# The bundled postgres image creates its user/password/database from the
|
|
# POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB env vars on first start, so docker
|
|
# and host deployments share the same prompts and defaults (rag/rag/lightrag).
|
|
existing_user="${ORIGINAL_ENV_VALUES[POSTGRES_USER]-${ENV_VALUES[POSTGRES_USER]:-}}"
|
|
existing_password="${ORIGINAL_ENV_VALUES[POSTGRES_PASSWORD]-${ENV_VALUES[POSTGRES_PASSWORD]:-}}"
|
|
existing_database="${ORIGINAL_ENV_VALUES[POSTGRES_DATABASE]-${ENV_VALUES[POSTGRES_DATABASE]:-}}"
|
|
user="$(prompt_with_default "PostgreSQL user" "${existing_user:-rag}")"
|
|
password="$(prompt_secret_with_default "PostgreSQL password: " "${existing_password:-rag}")"
|
|
database="$(prompt_with_default "PostgreSQL database" "${existing_database:-lightrag}")"
|
|
|
|
ENV_VALUES["POSTGRES_HOST"]="$host"
|
|
ENV_VALUES["POSTGRES_PORT"]="$port"
|
|
ENV_VALUES["POSTGRES_USER"]="$user"
|
|
ENV_VALUES["POSTGRES_PASSWORD"]="$password"
|
|
ENV_VALUES["POSTGRES_DATABASE"]="$database"
|
|
}
|
|
|
|
collect_neo4j_config() {
|
|
local default_docker="${1:-no}"
|
|
local use_docker="no"
|
|
local uri username password database
|
|
local existing_username="" existing_password="" existing_database=""
|
|
|
|
if [[ "$default_docker" == "yes" ]]; then
|
|
if confirm_default_yes "Run Neo4j locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run Neo4j locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
add_docker_service "neo4j"
|
|
uri="$(prefer_local_service_uri "${ENV_VALUES[NEO4J_URI]:-}" "neo4j://localhost:7687" "neo4j" "localhost" "127.0.0.1" "0.0.0.0")"
|
|
else
|
|
uri="${ENV_VALUES[NEO4J_URI]:-neo4j://localhost:7687}"
|
|
fi
|
|
|
|
uri="$(prompt_until_valid "Neo4j URI" "$uri" validate_uri neo4j)"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
uri="$(normalize_neo4j_uri_for_local_service "$uri")"
|
|
fi
|
|
existing_username="${ORIGINAL_ENV_VALUES[NEO4J_USERNAME]-${ENV_VALUES[NEO4J_USERNAME]:-}}"
|
|
existing_password="${ORIGINAL_ENV_VALUES[NEO4J_PASSWORD]-${ENV_VALUES[NEO4J_PASSWORD]:-}}"
|
|
existing_database="${ORIGINAL_ENV_VALUES[NEO4J_DATABASE]-${ENV_VALUES[NEO4J_DATABASE]:-}}"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
username="$(prompt_until_valid "Neo4j username" "${existing_username:-neo4j}" validate_non_empty)"
|
|
password="$(prompt_secret_until_valid_with_default "Neo4j password: " "${existing_password:-neo4j_password}" validate_non_empty)"
|
|
if [[ -n "$existing_database" ]]; then
|
|
database="$(prompt_with_default "Neo4j database" "$existing_database")"
|
|
else
|
|
database="neo4j"
|
|
fi
|
|
else
|
|
username="$(prompt_with_default "Neo4j username" "${existing_username:-neo4j}")"
|
|
password="$(prompt_secret_with_default "Neo4j password: " "${existing_password:-neo4j_password}")"
|
|
database="$(prompt_with_default "Neo4j database" "${existing_database:-neo4j}")"
|
|
fi
|
|
|
|
ENV_VALUES["NEO4J_URI"]="$uri"
|
|
ENV_VALUES["NEO4J_USERNAME"]="$username"
|
|
ENV_VALUES["NEO4J_PASSWORD"]="$password"
|
|
ENV_VALUES["NEO4J_DATABASE"]="$database"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
set_compose_override "NEO4J_URI" "neo4j://neo4j:7687"
|
|
else
|
|
set_compose_override "NEO4J_URI" ""
|
|
fi
|
|
}
|
|
|
|
collect_mongodb_config() {
|
|
local default_docker="${1:-no}"
|
|
local use_docker="no"
|
|
local uri database
|
|
local existing_database=""
|
|
local vector_search_required="no"
|
|
|
|
if [[ "${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" == "MongoVectorDBStorage" ]]; then
|
|
vector_search_required="yes"
|
|
fi
|
|
|
|
if [[ "$default_docker" == "yes" ]]; then
|
|
if confirm_default_yes "Run MongoDB locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run MongoDB locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
if [[ "$vector_search_required" == "yes" ]]; then
|
|
log_info "Docker MongoDB uses Atlas Local, so MongoVectorDBStorage can use Atlas Search / Vector Search locally."
|
|
fi
|
|
add_docker_service "mongodb"
|
|
uri="$(prefer_local_service_uri "${ENV_VALUES[MONGO_URI]:-}" "mongodb://localhost:27017/?directConnection=true" "mongodb" "localhost" "127.0.0.1" "0.0.0.0")"
|
|
elif [[ "$vector_search_required" == "yes" ]]; then
|
|
uri="${ENV_VALUES[MONGO_URI]:-}"
|
|
if [[ -z "$uri" ]] || is_wizard_managed_local_mongodb_uri "$uri"; then
|
|
uri="mongodb+srv://cluster.example.mongodb.net/"
|
|
fi
|
|
else
|
|
uri="${ENV_VALUES[MONGO_URI]:-mongodb://localhost:27017/}"
|
|
fi
|
|
|
|
if [[ "$vector_search_required" == "yes" ]]; then
|
|
uri="$(prompt_until_valid "MongoDB URI (must support Atlas Search / Vector Search)" "$uri" validate_uri mongodb)"
|
|
else
|
|
uri="$(prompt_until_valid "MongoDB URI" "$uri" validate_uri mongodb)"
|
|
fi
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
uri="$(normalize_mongodb_uri_for_local_service "$uri")"
|
|
fi
|
|
existing_database="${ORIGINAL_ENV_VALUES[MONGO_DATABASE]-${ENV_VALUES[MONGO_DATABASE]:-}}"
|
|
database="$(prompt_with_default "MongoDB database" "${existing_database:-LightRAG}")"
|
|
|
|
ENV_VALUES["MONGO_URI"]="$uri"
|
|
ENV_VALUES["MONGO_DATABASE"]="$database"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
set_compose_override "MONGO_URI" "mongodb://mongodb:27017/?directConnection=true"
|
|
else
|
|
set_compose_override "MONGO_URI" ""
|
|
fi
|
|
}
|
|
|
|
collect_redis_config() {
|
|
local default_docker="${1:-no}"
|
|
local use_docker="no"
|
|
local uri
|
|
|
|
if [[ "$default_docker" == "yes" ]]; then
|
|
if confirm_default_yes "Run Redis locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run Redis locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
add_docker_service "redis"
|
|
uri="$(prefer_local_service_uri "${ENV_VALUES[REDIS_URI]:-}" "redis://localhost:6379" "redis" "localhost" "127.0.0.1" "0.0.0.0")"
|
|
else
|
|
uri="${ENV_VALUES[REDIS_URI]:-redis://localhost:6379}"
|
|
fi
|
|
|
|
uri="$(prompt_until_valid "Redis URI" "$uri" validate_uri redis)"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
uri="$(normalize_redis_uri_for_local_service "$uri")"
|
|
fi
|
|
ENV_VALUES["REDIS_URI"]="$uri"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
set_compose_override "REDIS_URI" "redis://redis:6379"
|
|
else
|
|
set_compose_override "REDIS_URI" ""
|
|
fi
|
|
}
|
|
|
|
collect_milvus_config() {
|
|
local default_docker="${1:-no}"
|
|
local use_docker="no"
|
|
local uri db_name milvus_device=""
|
|
local existing_db_name="" existing_device=""
|
|
|
|
if [[ "$default_docker" == "yes" ]]; then
|
|
if confirm_default_yes "Run Milvus locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run Milvus locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
add_docker_service "milvus"
|
|
uri="$(prefer_local_service_uri "${ENV_VALUES[MILVUS_URI]:-}" "http://localhost:19530" "milvus" "localhost" "127.0.0.1" "0.0.0.0")"
|
|
else
|
|
uri="${ENV_VALUES[MILVUS_URI]:-http://localhost:19530}"
|
|
fi
|
|
|
|
existing_db_name="${ORIGINAL_ENV_VALUES[MILVUS_DB_NAME]-${ENV_VALUES[MILVUS_DB_NAME]:-}}"
|
|
existing_device="${ORIGINAL_ENV_VALUES[MILVUS_DEVICE]-${ENV_VALUES[MILVUS_DEVICE]:-}}"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
milvus_device="$(resolve_local_device_default "$existing_device")"
|
|
milvus_device="$(prompt_choice "Milvus device" "$milvus_device" "cpu" "cuda")"
|
|
if [[ "$milvus_device" == "cuda" ]] && ! host_cuda_available; then
|
|
log_warn "CUDA device selected for Milvus but no NVIDIA driver detected on host."
|
|
fi
|
|
uri="$(prompt_until_valid "Milvus URI" "$uri" validate_uri milvus)"
|
|
uri="$(normalize_milvus_uri_for_local_service "$uri")"
|
|
if [[ -z "${ENV_VALUES[MINIO_ACCESS_KEY_ID]:-}" ]]; then
|
|
ENV_VALUES["MINIO_ACCESS_KEY_ID"]="minioadmin"
|
|
fi
|
|
if [[ -z "${ENV_VALUES[MINIO_SECRET_ACCESS_KEY]:-}" ]]; then
|
|
ENV_VALUES["MINIO_SECRET_ACCESS_KEY"]="minioadmin"
|
|
fi
|
|
else
|
|
uri="$(prompt_until_valid "Milvus URI" "$uri" validate_uri milvus)"
|
|
fi
|
|
db_name="$(prompt_with_default "Milvus database name" "${existing_db_name:-lightrag}")"
|
|
|
|
ENV_VALUES["MILVUS_URI"]="$uri"
|
|
ENV_VALUES["MILVUS_DB_NAME"]="$db_name"
|
|
if [[ -n "$milvus_device" ]]; then
|
|
ENV_VALUES["MILVUS_DEVICE"]="$milvus_device"
|
|
fi
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
set_compose_override "MILVUS_URI" "http://milvus:19530"
|
|
else
|
|
set_compose_override "MILVUS_URI" ""
|
|
fi
|
|
}
|
|
|
|
collect_qdrant_config() {
|
|
local default_docker="${1:-no}"
|
|
local use_docker="no"
|
|
local url qdrant_device=""
|
|
local existing_device=""
|
|
|
|
if [[ "$default_docker" == "yes" ]]; then
|
|
if confirm_default_yes "Run Qdrant locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run Qdrant locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
add_docker_service "qdrant"
|
|
url="$(prefer_local_service_uri "${ENV_VALUES[QDRANT_URL]:-}" "http://localhost:6333" "qdrant" "localhost" "127.0.0.1" "0.0.0.0")"
|
|
else
|
|
url="${ENV_VALUES[QDRANT_URL]:-http://localhost:6333}"
|
|
fi
|
|
|
|
existing_device="${ORIGINAL_ENV_VALUES[QDRANT_DEVICE]-${ENV_VALUES[QDRANT_DEVICE]:-}}"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
qdrant_device="$(resolve_local_device_default "$existing_device")"
|
|
qdrant_device="$(prompt_choice "Qdrant device" "$qdrant_device" "cpu" "cuda")"
|
|
if [[ "$qdrant_device" == "cuda" ]] && ! host_cuda_available; then
|
|
log_warn "CUDA device selected for Qdrant but no NVIDIA driver detected on host."
|
|
fi
|
|
url="$(prompt_until_valid "Qdrant URL" "$url" validate_uri qdrant)"
|
|
url="$(normalize_qdrant_uri_for_local_service "$url")"
|
|
else
|
|
url="$(prompt_until_valid "Qdrant URL" "$url" validate_uri qdrant)"
|
|
fi
|
|
ENV_VALUES["QDRANT_URL"]="$url"
|
|
if [[ -n "$qdrant_device" ]]; then
|
|
ENV_VALUES["QDRANT_DEVICE"]="$qdrant_device"
|
|
fi
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
set_compose_override "QDRANT_URL" "http://qdrant:6333"
|
|
else
|
|
set_compose_override "QDRANT_URL" ""
|
|
fi
|
|
}
|
|
|
|
collect_memgraph_config() {
|
|
local default_docker="${1:-no}"
|
|
local use_docker="no"
|
|
local uri
|
|
|
|
if [[ "$default_docker" == "yes" ]]; then
|
|
if confirm_default_yes "Run Memgraph locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run Memgraph locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
add_docker_service "memgraph"
|
|
uri="$(prefer_local_service_uri "${ENV_VALUES[MEMGRAPH_URI]:-}" "bolt://localhost:7687" "memgraph" "localhost" "127.0.0.1" "0.0.0.0")"
|
|
else
|
|
uri="${ENV_VALUES[MEMGRAPH_URI]:-bolt://localhost:7687}"
|
|
fi
|
|
|
|
uri="$(prompt_until_valid "Memgraph URI" "$uri" validate_uri memgraph)"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
uri="$(normalize_memgraph_uri_for_local_service "$uri")"
|
|
fi
|
|
ENV_VALUES["MEMGRAPH_URI"]="$uri"
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
set_compose_override "MEMGRAPH_URI" "bolt://memgraph:7687"
|
|
else
|
|
set_compose_override "MEMGRAPH_URI" ""
|
|
fi
|
|
}
|
|
|
|
collect_opensearch_config() {
|
|
local default_docker="${1:-no}"
|
|
local use_docker="no"
|
|
local hosts user password
|
|
local existing_user="" existing_password=""
|
|
local existing_use_ssl="" existing_verify_certs=""
|
|
local existing_num_shards="" existing_num_replicas=""
|
|
local use_ssl="true"
|
|
local verify_certs="false"
|
|
local use_ssl_default="yes"
|
|
local verify_certs_default="no"
|
|
|
|
if [[ "$default_docker" == "yes" ]]; then
|
|
if confirm_default_yes "Run OpenSearch locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
else
|
|
if confirm_default_no "Run OpenSearch locally via Docker?"; then
|
|
use_docker="yes"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
add_docker_service "opensearch"
|
|
hosts="$(prefer_local_service_uri "${ENV_VALUES[OPENSEARCH_HOSTS]:-}" "localhost:9200" "opensearch" "localhost" "127.0.0.1" "0.0.0.0")"
|
|
else
|
|
hosts="${ENV_VALUES[OPENSEARCH_HOSTS]:-localhost:9200}"
|
|
fi
|
|
|
|
existing_user="${ORIGINAL_ENV_VALUES[OPENSEARCH_USER]-${ENV_VALUES[OPENSEARCH_USER]:-}}"
|
|
existing_password="${ORIGINAL_ENV_VALUES[OPENSEARCH_PASSWORD]-${ENV_VALUES[OPENSEARCH_PASSWORD]:-}}"
|
|
existing_use_ssl="${ORIGINAL_ENV_VALUES[OPENSEARCH_USE_SSL]-${ENV_VALUES[OPENSEARCH_USE_SSL]:-}}"
|
|
existing_verify_certs="${ORIGINAL_ENV_VALUES[OPENSEARCH_VERIFY_CERTS]-${ENV_VALUES[OPENSEARCH_VERIFY_CERTS]:-}}"
|
|
existing_num_shards="${ORIGINAL_ENV_VALUES[OPENSEARCH_NUMBER_OF_SHARDS]-${ENV_VALUES[OPENSEARCH_NUMBER_OF_SHARDS]:-}}"
|
|
existing_num_replicas="${ORIGINAL_ENV_VALUES[OPENSEARCH_NUMBER_OF_REPLICAS]-${ENV_VALUES[OPENSEARCH_NUMBER_OF_REPLICAS]:-}}"
|
|
|
|
hosts="$(prompt_until_valid "OpenSearch hosts (host:port, comma-separated)" "$hosts" validate_opensearch_hosts_format)"
|
|
user="$(prompt_with_default "OpenSearch user" "${existing_user:-admin}")"
|
|
password="$(prompt_secret_until_valid_with_default "OpenSearch password: " "${existing_password:-LightRAG2026_!@}" validate_opensearch_password_strength)"
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
if [[ -n "$existing_use_ssl" ]]; then
|
|
env_value_is_true "$existing_use_ssl" && use_ssl="true" || use_ssl="false"
|
|
else
|
|
use_ssl="true"
|
|
fi
|
|
verify_certs="false"
|
|
else
|
|
if [[ -n "$existing_use_ssl" ]] && ! env_value_is_true "$existing_use_ssl"; then
|
|
use_ssl_default="no"
|
|
fi
|
|
|
|
if [[ "$use_ssl_default" == "yes" ]]; then
|
|
confirm_default_yes "Use SSL for OpenSearch?" && use_ssl="true" || use_ssl="false"
|
|
else
|
|
confirm_default_no "Use SSL for OpenSearch?" && use_ssl="true" || use_ssl="false"
|
|
fi
|
|
|
|
if [[ "$use_ssl" == "true" ]]; then
|
|
if [[ -n "$existing_verify_certs" ]] && env_value_is_true "$existing_verify_certs"; then
|
|
verify_certs_default="yes"
|
|
fi
|
|
|
|
if [[ "$verify_certs_default" == "yes" ]]; then
|
|
confirm_default_yes "Verify OpenSearch TLS certificates?" && verify_certs="true" || verify_certs="false"
|
|
else
|
|
confirm_default_no "Verify OpenSearch TLS certificates?" && verify_certs="true" || verify_certs="false"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
local num_shards num_replicas
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
num_shards="1"
|
|
num_replicas="0"
|
|
else
|
|
num_shards="$(prompt_until_valid "Number of index shards" "${existing_num_shards:-1}" validate_positive_integer)"
|
|
num_replicas="$(prompt_until_valid "Number of index replicas (use 2 for 3-AZ clusters)" "${existing_num_replicas:-0}" validate_non_negative_integer)"
|
|
fi
|
|
|
|
ENV_VALUES["OPENSEARCH_HOSTS"]="$hosts"
|
|
ENV_VALUES["OPENSEARCH_USER"]="$user"
|
|
ENV_VALUES["OPENSEARCH_PASSWORD"]="$password"
|
|
ENV_VALUES["OPENSEARCH_USE_SSL"]="$use_ssl"
|
|
ENV_VALUES["OPENSEARCH_VERIFY_CERTS"]="$verify_certs"
|
|
ENV_VALUES["OPENSEARCH_NUMBER_OF_SHARDS"]="$num_shards"
|
|
ENV_VALUES["OPENSEARCH_NUMBER_OF_REPLICAS"]="$num_replicas"
|
|
|
|
if [[ "$use_docker" == "yes" ]]; then
|
|
set_compose_override "OPENSEARCH_HOSTS" "opensearch:9200"
|
|
else
|
|
set_compose_override "OPENSEARCH_HOSTS" ""
|
|
fi
|
|
}
|
|
|
|
clear_bedrock_credentials() {
|
|
unset 'ENV_VALUES[AWS_ACCESS_KEY_ID]'
|
|
unset 'ENV_VALUES[AWS_SECRET_ACCESS_KEY]'
|
|
unset 'ENV_VALUES[AWS_SESSION_TOKEN]'
|
|
unset 'ENV_VALUES[AWS_REGION]'
|
|
}
|
|
|
|
collect_bedrock_credentials() {
|
|
local access_key secret_key session_token region
|
|
|
|
log_info "Bedrock ignores LLM_BINDING_API_KEY/EMBEDDING_BINDING_API_KEY; use SigV4 credentials or AWS_BEARER_TOKEN_BEDROCK."
|
|
if [[ -n "${ENV_VALUES[AWS_ACCESS_KEY_ID]:-}" && -n "${ENV_VALUES[AWS_SECRET_ACCESS_KEY]:-}" ]]; then
|
|
if confirm_default_yes "Reuse existing AWS Bedrock credentials?"; then
|
|
region="$(prompt_with_default "AWS region" "${ENV_VALUES[AWS_REGION]:-us-east-1}")"
|
|
ENV_VALUES["AWS_REGION"]="$region"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if confirm_default_no "Store explicit AWS Bedrock credentials in .env?"; then
|
|
access_key="$(prompt_required_secret "AWS access key ID: ")"
|
|
secret_key="$(prompt_required_secret "AWS secret access key: ")"
|
|
session_token="$(mask_sensitive_input "AWS session token (optional): ")"
|
|
region="$(prompt_with_default "AWS region" "${ENV_VALUES[AWS_REGION]:-us-east-1}")"
|
|
|
|
ENV_VALUES["AWS_ACCESS_KEY_ID"]="$access_key"
|
|
ENV_VALUES["AWS_SECRET_ACCESS_KEY"]="$secret_key"
|
|
ENV_VALUES["AWS_REGION"]="$region"
|
|
if [[ -n "$session_token" ]]; then
|
|
ENV_VALUES["AWS_SESSION_TOKEN"]="$session_token"
|
|
else
|
|
unset 'ENV_VALUES[AWS_SESSION_TOKEN]'
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
log_info "Using the ambient AWS credential chain (for example IAM roles, AWS profiles, or aws sso login)."
|
|
clear_bedrock_credentials
|
|
region="$(prompt_clearable_with_default "AWS region (optional)" "${ENV_VALUES[AWS_REGION]:-}")"
|
|
apply_clearable_env_value "AWS_REGION" "$region"
|
|
}
|
|
|
|
store_optional_env_value() {
|
|
local key="$1"
|
|
local value="${2:-}"
|
|
|
|
if [[ -n "$value" ]]; then
|
|
ENV_VALUES["$key"]="$value"
|
|
else
|
|
unset "ENV_VALUES[$key]"
|
|
fi
|
|
}
|
|
|
|
provider_default_or_existing() {
|
|
local selected_binding="$1"
|
|
local existing_binding="${2:-}"
|
|
local existing_value="${3:-}"
|
|
local default_value="${4:-}"
|
|
|
|
if [[ "$selected_binding" == "$existing_binding" && -n "$existing_value" ]]; then
|
|
printf '%s' "$existing_value"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' "$default_value"
|
|
}
|
|
|
|
default_llm_model_for_binding() {
|
|
local binding="$1"
|
|
|
|
case "$binding" in
|
|
openai|azure_openai)
|
|
printf 'gpt-5-mini'
|
|
;;
|
|
ollama|lollms|openai-ollama)
|
|
printf 'mistral-nemo:latest'
|
|
;;
|
|
gemini)
|
|
printf 'gemini-flash-latest'
|
|
;;
|
|
bedrock)
|
|
printf 'anthropic.claude-3-5-sonnet-20241022-v2:0'
|
|
;;
|
|
*)
|
|
printf 'gpt-5-mini'
|
|
;;
|
|
esac
|
|
}
|
|
|
|
default_embedding_model_for_binding() {
|
|
local binding="$1"
|
|
|
|
case "$binding" in
|
|
openai|azure_openai)
|
|
printf 'text-embedding-3-large'
|
|
;;
|
|
ollama)
|
|
printf 'bge-m3:latest'
|
|
;;
|
|
jina)
|
|
printf 'jina-embeddings-v4'
|
|
;;
|
|
gemini)
|
|
printf 'gemini-embedding-001'
|
|
;;
|
|
bedrock)
|
|
printf 'amazon.titan-embed-text-v2:0'
|
|
;;
|
|
lollms)
|
|
printf 'lollms_embedding_model'
|
|
;;
|
|
*)
|
|
printf 'text-embedding-3-large'
|
|
;;
|
|
esac
|
|
}
|
|
|
|
default_embedding_dim_for_binding() {
|
|
local binding="$1"
|
|
|
|
case "$binding" in
|
|
openai|azure_openai)
|
|
printf '3072'
|
|
;;
|
|
ollama|bedrock|lollms)
|
|
printf '1024'
|
|
;;
|
|
jina)
|
|
printf '2048'
|
|
;;
|
|
gemini)
|
|
printf '1536'
|
|
;;
|
|
*)
|
|
printf '3072'
|
|
;;
|
|
esac
|
|
}
|
|
|
|
collect_llm_config() {
|
|
local options=("openai" "azure_openai" "ollama" "openai-ollama" "lollms" "gemini" "bedrock")
|
|
local current_binding="${ENV_VALUES[LLM_BINDING]:-openai}"
|
|
local binding model model_default host host_default api_key
|
|
|
|
binding="$(prompt_choice "LLM provider" "$current_binding" "${options[@]}")"
|
|
model_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_MODEL]:-}" "$(default_llm_model_for_binding "$binding")")"
|
|
model="$(prompt_with_default "LLM model" "$model_default")"
|
|
|
|
case "$binding" in
|
|
ollama)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "$(default_loopback_url 11434)")"
|
|
host="$(prompt_with_default "Ollama host" "$host_default")"
|
|
api_key=""
|
|
;;
|
|
openai-ollama)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "$(default_loopback_url 11434 "/v1")")"
|
|
host="$(prompt_with_default "OpenAI-compatible Ollama endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "LLM API key: " "${ENV_VALUES[LLM_BINDING_API_KEY]:-}" validate_api_key openai)"
|
|
;;
|
|
lollms)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "http://localhost:9600")"
|
|
host="$(prompt_with_default "LoLLMs host" "$host_default")"
|
|
api_key=""
|
|
;;
|
|
azure_openai)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "https://example.openai.azure.com/")"
|
|
host="$(prompt_with_default "Azure OpenAI endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "Azure OpenAI API key: " "${ENV_VALUES[LLM_BINDING_API_KEY]:-}" validate_api_key azure_openai)"
|
|
;;
|
|
gemini)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "DEFAULT_GEMINI_ENDPOINT")"
|
|
host="$(prompt_with_default "Gemini endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "Gemini API key: " "${ENV_VALUES[LLM_BINDING_API_KEY]:-}" validate_api_key gemini)"
|
|
;;
|
|
bedrock)
|
|
host="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "DEFAULT_BEDROCK_ENDPOINT")"
|
|
api_key=""
|
|
collect_bedrock_credentials
|
|
;;
|
|
*)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "https://api.openai.com/v1")"
|
|
host="$(prompt_with_default "LLM endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "LLM API key: " "${ENV_VALUES[LLM_BINDING_API_KEY]:-}" validate_api_key "$binding")"
|
|
;;
|
|
esac
|
|
|
|
ENV_VALUES["LLM_BINDING"]="$binding"
|
|
ENV_VALUES["LLM_MODEL"]="$model"
|
|
ENV_VALUES["LLM_BINDING_HOST"]="$host"
|
|
store_optional_env_value "LLM_BINDING_API_KEY" "$api_key"
|
|
|
|
# Role-specific LLM models — default to the base LLM_MODEL when unset in .env.
|
|
local keyword_default query_default keyword_model query_model
|
|
keyword_default="${ENV_VALUES[KEYWORD_LLM_MODEL]:-$model}"
|
|
query_default="${ENV_VALUES[QUERY_LLM_MODEL]:-$model}"
|
|
keyword_model="$(prompt_with_default "Keyword LLM model" "$keyword_default")"
|
|
query_model="$(prompt_with_default "Query LLM model" "$query_default")"
|
|
ENV_VALUES["KEYWORD_LLM_MODEL"]="$keyword_model"
|
|
ENV_VALUES["QUERY_LLM_MODEL"]="$query_model"
|
|
}
|
|
|
|
collect_embedding_config() {
|
|
local options=("openai" "azure_openai" "ollama" "jina" "lollms" "gemini" "bedrock")
|
|
local current_binding="${ENV_VALUES[EMBEDDING_BINDING]:-openai}"
|
|
local binding model model_default host host_default api_key dim dim_default
|
|
|
|
if [[ "${ENV_VALUES[LLM_BINDING]:-}" == "openai-ollama" ]]; then
|
|
binding="ollama"
|
|
if [[ "$current_binding" != "ollama" ]]; then
|
|
log_info "openai-ollama uses Ollama embeddings. Forcing embedding provider to ollama."
|
|
fi
|
|
else
|
|
binding="$(prompt_choice "Embedding provider" "$current_binding" "${options[@]}")"
|
|
fi
|
|
model_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_MODEL]:-}" "$(default_embedding_model_for_binding "$binding")")"
|
|
dim_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_DIM]:-}" "$(default_embedding_dim_for_binding "$binding")")"
|
|
model="$(prompt_with_default "Embedding model" "$model_default")"
|
|
dim="$(prompt_with_default "Embedding dimension" "$dim_default")"
|
|
|
|
local llm_host_fallback="" llm_api_key_default=""
|
|
if [[ "$binding" == "${ENV_VALUES[LLM_BINDING]:-}" ]]; then
|
|
llm_host_fallback="${ENV_VALUES[LLM_BINDING_HOST]:-}"
|
|
llm_api_key_default="${ENV_VALUES[LLM_BINDING_API_KEY]:-}"
|
|
fi
|
|
|
|
case "$binding" in
|
|
ollama)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-$(default_loopback_url 11434)}")"
|
|
host="$(prompt_with_default "Ollama embedding host" "$host_default")"
|
|
api_key=""
|
|
;;
|
|
lollms)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-http://localhost:9600}")"
|
|
host="$(prompt_with_default "LoLLMs embedding host" "$host_default")"
|
|
api_key=""
|
|
;;
|
|
azure_openai)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-https://example.openai.azure.com/}")"
|
|
host="$(prompt_with_default "Azure OpenAI endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "Azure OpenAI API key: " "${ENV_VALUES[EMBEDDING_BINDING_API_KEY]:-$llm_api_key_default}" validate_api_key azure_openai)"
|
|
;;
|
|
gemini)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-DEFAULT_GEMINI_ENDPOINT}")"
|
|
host="$(prompt_with_default "Gemini endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "Gemini API key: " "${ENV_VALUES[EMBEDDING_BINDING_API_KEY]:-$llm_api_key_default}" validate_api_key gemini)"
|
|
;;
|
|
bedrock)
|
|
host="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-DEFAULT_BEDROCK_ENDPOINT}")"
|
|
api_key=""
|
|
collect_bedrock_credentials
|
|
;;
|
|
jina)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-https://api.jina.ai/v1/embeddings}")"
|
|
host="$(prompt_with_default "Jina endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "Jina API key: " "${ENV_VALUES[EMBEDDING_BINDING_API_KEY]:-$llm_api_key_default}" validate_api_key jina)"
|
|
;;
|
|
*)
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-https://api.openai.com/v1}")"
|
|
host="$(prompt_with_default "Embedding endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "Embedding API key: " "${ENV_VALUES[EMBEDDING_BINDING_API_KEY]:-$llm_api_key_default}" validate_api_key "$binding")"
|
|
;;
|
|
esac
|
|
|
|
ENV_VALUES["EMBEDDING_BINDING"]="$binding"
|
|
ENV_VALUES["EMBEDDING_MODEL"]="$model"
|
|
ENV_VALUES["EMBEDDING_DIM"]="$dim"
|
|
ENV_VALUES["EMBEDDING_BINDING_HOST"]="$host"
|
|
store_optional_env_value "EMBEDDING_BINDING_API_KEY" "$api_key"
|
|
# User chose a remote provider — clear the Docker deployment marker.
|
|
unset 'ENV_VALUES[LIGHTRAG_SETUP_EMBEDDING_PROVIDER]'
|
|
}
|
|
|
|
collect_rerank_config() {
|
|
# Pass "yes" to skip the "Enable reranking?" prompt (caller already asked it).
|
|
# The optional second argument is retained for caller compatibility.
|
|
local skip_enable_check="${1:-no}"
|
|
local _docker_choice_override="${2:-prompt}"
|
|
local options=("cohere" "jina" "aliyun")
|
|
local current_binding="${ENV_VALUES[RERANK_BINDING]:-cohere}"
|
|
local binding model host api_key
|
|
local default_model="" default_host="" model_default="" host_default=""
|
|
local previous_provider="${ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}"
|
|
|
|
unset 'ENV_VALUES[VLLM_RERANK_DTYPE]'
|
|
|
|
if [[ "$skip_enable_check" != "yes" ]]; then
|
|
local rerank_was_enabled="no"
|
|
if [[ -n "${ENV_VALUES[RERANK_BINDING]:-}" && "${ENV_VALUES[RERANK_BINDING]}" != "null" ]]; then
|
|
rerank_was_enabled="yes"
|
|
fi
|
|
|
|
local rerank_enabled="no"
|
|
if [[ "$rerank_was_enabled" == "yes" ]]; then
|
|
confirm_default_yes "Enable reranking?" && rerank_enabled="yes"
|
|
else
|
|
confirm_default_no "Enable reranking?" && rerank_enabled="yes"
|
|
fi
|
|
|
|
if [[ "$rerank_enabled" != "yes" ]]; then
|
|
ENV_VALUES["RERANK_BINDING"]="null"
|
|
unset 'ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]'
|
|
return
|
|
fi
|
|
fi
|
|
|
|
if [[ "$current_binding" == "null" ]]; then
|
|
current_binding="cohere"
|
|
fi
|
|
|
|
binding="$(prompt_choice "Rerank provider" "$current_binding" "${options[@]}")"
|
|
case "$binding" in
|
|
cohere)
|
|
default_model="rerank-v3.5"
|
|
default_host="https://api.cohere.com/v2/rerank"
|
|
;;
|
|
jina)
|
|
default_model="jina-reranker-v2-base-multilingual"
|
|
default_host="https://api.jina.ai/v1/rerank"
|
|
;;
|
|
aliyun)
|
|
default_model="gte-rerank-v2"
|
|
default_host="https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank"
|
|
;;
|
|
*)
|
|
default_model=""
|
|
default_host=""
|
|
;;
|
|
esac
|
|
|
|
if [[ "$previous_provider" == "vllm" ]]; then
|
|
# Switching away from local vLLM should replace stale localhost/model values.
|
|
model_default="$default_model"
|
|
host_default="$default_host"
|
|
else
|
|
model_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[RERANK_MODEL]:-}" "$default_model")"
|
|
host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[RERANK_BINDING_HOST]:-}" "$default_host")"
|
|
fi
|
|
|
|
model="$(prompt_with_default "Rerank model" "$model_default")"
|
|
host="$(prompt_with_default "Rerank endpoint" "$host_default")"
|
|
api_key="$(prompt_secret_until_valid_with_default "Rerank API key: " "${ENV_VALUES[RERANK_BINDING_API_KEY]:-}" validate_api_key "$binding")"
|
|
|
|
ENV_VALUES["RERANK_BINDING"]="$binding"
|
|
# Only env_base_flow's Docker branch should keep the local vLLM setup marker.
|
|
unset 'ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]'
|
|
if [[ -n "$model" ]]; then
|
|
ENV_VALUES["RERANK_MODEL"]="$model"
|
|
fi
|
|
if [[ -n "$host" ]]; then
|
|
ENV_VALUES["RERANK_BINDING_HOST"]="$host"
|
|
fi
|
|
store_optional_env_value "RERANK_BINDING_API_KEY" "$api_key"
|
|
}
|
|
|
|
collect_server_config() {
|
|
local host port title description summary_language
|
|
|
|
host="$(prompt_with_default "Server host" "${ENV_VALUES[HOST]:-0.0.0.0}")"
|
|
port="$(prompt_until_valid "Server port" "${ENV_VALUES[PORT]:-9621}" validate_port)"
|
|
title="$(prompt_with_default "WebUI title" "${ENV_VALUES[WEBUI_TITLE]:-My Graph KB}")"
|
|
description="$(prompt_with_default "WebUI description" "${ENV_VALUES[WEBUI_DESCRIPTION]:-Simple and Fast Graph Based RAG System}")"
|
|
summary_language="$(prompt_with_default "Summary language" "${ENV_VALUES[SUMMARY_LANGUAGE]:-English}")"
|
|
|
|
ENV_VALUES["HOST"]="$host"
|
|
ENV_VALUES["PORT"]="$port"
|
|
ENV_VALUES["WEBUI_TITLE"]="$title"
|
|
ENV_VALUES["WEBUI_DESCRIPTION"]="$description"
|
|
ENV_VALUES["SUMMARY_LANGUAGE"]="$summary_language"
|
|
}
|
|
|
|
collect_ssl_config() {
|
|
local cert key
|
|
local ssl_enabled_default="no"
|
|
|
|
case "${ENV_VALUES[SSL]:-}" in
|
|
true|TRUE|True|1|yes|YES|Yes|y|Y|on|ON|On|t|T)
|
|
ssl_enabled_default="yes"
|
|
;;
|
|
esac
|
|
|
|
if [[ "$ssl_enabled_default" == "yes" ]]; then
|
|
if ! confirm_default_yes "Enable SSL/TLS for the API server?"; then
|
|
unset 'ENV_VALUES[SSL]'
|
|
unset 'ENV_VALUES[SSL_CERTFILE]'
|
|
unset 'ENV_VALUES[SSL_KEYFILE]'
|
|
SSL_CERT_SOURCE_PATH=""
|
|
SSL_KEY_SOURCE_PATH=""
|
|
return
|
|
fi
|
|
else
|
|
if ! confirm_default_no "Enable SSL/TLS for the API server?"; then
|
|
unset 'ENV_VALUES[SSL]'
|
|
unset 'ENV_VALUES[SSL_CERTFILE]'
|
|
unset 'ENV_VALUES[SSL_KEYFILE]'
|
|
SSL_CERT_SOURCE_PATH=""
|
|
SSL_KEY_SOURCE_PATH=""
|
|
return
|
|
fi
|
|
fi
|
|
|
|
cert="$(prompt_until_valid "SSL certificate file" "${ENV_VALUES[SSL_CERTFILE]:-}" validate_existing_file)"
|
|
key="$(prompt_until_valid "SSL key file" "${ENV_VALUES[SSL_KEYFILE]:-}" validate_existing_file)"
|
|
|
|
ENV_VALUES["SSL"]="true"
|
|
ENV_VALUES["SSL_CERTFILE"]="$cert"
|
|
ENV_VALUES["SSL_KEYFILE"]="$key"
|
|
SSL_CERT_SOURCE_PATH="$cert"
|
|
SSL_KEY_SOURCE_PATH="$key"
|
|
}
|
|
|
|
collect_security_config() {
|
|
local required="${1:-no}"
|
|
local default_yes="${2:-no}"
|
|
local auth_accounts token_secret token_expire api_key whitelist
|
|
local confirm_result=1
|
|
local whitelist_default=""
|
|
local whitelist_is_set="no"
|
|
|
|
if [[ -n "${ENV_VALUES[WHITELIST_PATHS]+set}" ]]; then
|
|
whitelist_default="${ENV_VALUES[WHITELIST_PATHS]}"
|
|
whitelist_is_set="yes"
|
|
fi
|
|
|
|
if [[ "$default_yes" == "yes" ]]; then
|
|
if confirm_default_yes "Configure authentication and API key settings?"; then
|
|
confirm_result=0
|
|
fi
|
|
else
|
|
if confirm_default_no "Configure authentication and API key settings?"; then
|
|
confirm_result=0
|
|
fi
|
|
fi
|
|
|
|
if ((confirm_result != 0)); then
|
|
if [[ "$required" == "yes" ]]; then
|
|
echo "Warning: production deployments should configure AUTH_ACCOUNTS; API keys are optional on top." >&2
|
|
fi
|
|
return
|
|
fi
|
|
|
|
echo "Press Enter to keep an existing value. Type 'clear' to remove it." >&2
|
|
|
|
if [[ "$whitelist_is_set" == "no" ]]; then
|
|
whitelist_default="/health"
|
|
elif [[ "$required" == "yes" && "$whitelist_default" == "/health,/api/*" ]]; then
|
|
whitelist_default="/health"
|
|
fi
|
|
|
|
auth_accounts="$(prompt_clearable_with_default "Auth accounts (user:pass,comma-separated)" "${ENV_VALUES[AUTH_ACCOUNTS]:-}")"
|
|
token_secret="$(prompt_clearable_secret_with_default "JWT token secret: " "${ENV_VALUES[TOKEN_SECRET]:-}")"
|
|
token_expire="$(prompt_clearable_with_default "Token expire hours" "${ENV_VALUES[TOKEN_EXPIRE_HOURS]:-48}")"
|
|
api_key="$(prompt_clearable_secret_with_default "LightRAG API key: " "${ENV_VALUES[LIGHTRAG_API_KEY]:-}")"
|
|
whitelist="$(prompt_clearable_with_default "Whitelist paths (comma-separated)" "$whitelist_default")"
|
|
if [[ "$whitelist_is_set" == "yes" && -z "$whitelist_default" && -z "$whitelist" ]]; then
|
|
whitelist="$CLEAR_INPUT_SENTINEL"
|
|
fi
|
|
|
|
if [[ -z "$token_secret" ]]; then
|
|
token_secret="$(openssl rand -hex 32 2>/dev/null || LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 64)"
|
|
log_info "Generated TOKEN_SECRET and saved to .env."
|
|
fi
|
|
|
|
apply_clearable_env_value "AUTH_ACCOUNTS" "$auth_accounts"
|
|
apply_clearable_env_value "TOKEN_SECRET" "$token_secret"
|
|
apply_clearable_env_value "TOKEN_EXPIRE_HOURS" "$token_expire"
|
|
apply_clearable_env_value "LIGHTRAG_API_KEY" "$api_key"
|
|
apply_clearable_env_value "WHITELIST_PATHS" "$whitelist" "empty"
|
|
}
|
|
|
|
apply_clearable_env_value() {
|
|
local key="$1"
|
|
local value="${2:-}"
|
|
local clear_mode="${3:-unset}"
|
|
|
|
if [[ "$clear_mode" == "empty" && "$value" == "$CLEAR_INPUT_SENTINEL" ]]; then
|
|
ENV_VALUES["$key"]=""
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$value" == "$CLEAR_INPUT_SENTINEL" || -z "$value" ]]; then
|
|
unset "ENV_VALUES[$key]"
|
|
return 0
|
|
fi
|
|
|
|
ENV_VALUES["$key"]="$value"
|
|
}
|
|
|
|
collect_observability_config() {
|
|
local secret_key public_key host
|
|
|
|
if ! confirm_default_no "Enable Langfuse observability?"; then
|
|
unset 'ENV_VALUES[LANGFUSE_ENABLE_TRACE]'
|
|
unset 'ENV_VALUES[LANGFUSE_SECRET_KEY]'
|
|
unset 'ENV_VALUES[LANGFUSE_PUBLIC_KEY]'
|
|
unset 'ENV_VALUES[LANGFUSE_HOST]'
|
|
return
|
|
fi
|
|
|
|
secret_key="$(prompt_secret_until_valid_with_default "Langfuse secret key: " "${ENV_VALUES[LANGFUSE_SECRET_KEY]:-}" validate_api_key langfuse)"
|
|
public_key="$(prompt_secret_until_valid_with_default "Langfuse public key: " "${ENV_VALUES[LANGFUSE_PUBLIC_KEY]:-}" validate_api_key langfuse)"
|
|
host="$(prompt_with_default "Langfuse host" "${ENV_VALUES[LANGFUSE_HOST]:-https://cloud.langfuse.com}")"
|
|
|
|
if [[ -n "$secret_key" ]]; then
|
|
ENV_VALUES["LANGFUSE_SECRET_KEY"]="$secret_key"
|
|
fi
|
|
if [[ -n "$public_key" ]]; then
|
|
ENV_VALUES["LANGFUSE_PUBLIC_KEY"]="$public_key"
|
|
fi
|
|
if [[ -n "$host" ]]; then
|
|
ENV_VALUES["LANGFUSE_HOST"]="$host"
|
|
fi
|
|
ENV_VALUES["LANGFUSE_ENABLE_TRACE"]="true"
|
|
}
|
|
|
|
|
|
show_summary() {
|
|
local key
|
|
local value
|
|
|
|
echo
|
|
log_info "Configuration summary:"
|
|
if ((${#ENV_VALUES[@]} > 0)); then
|
|
local -a sorted_keys
|
|
mapfile -t sorted_keys < <(printf '%s\n' "${!ENV_VALUES[@]}" | sort)
|
|
for key in "${sorted_keys[@]}"; do
|
|
value="${ENV_VALUES[$key]}"
|
|
if is_sensitive_env_key "$key"; then
|
|
value="***"
|
|
fi
|
|
printf ' %s=%s\n' "$key" "$value"
|
|
done
|
|
fi
|
|
|
|
if ((${#DOCKER_SERVICES[@]} > 0)); then
|
|
echo
|
|
log_info "Docker services to include:"
|
|
for service in "${DOCKER_SERVICES[@]}"; do
|
|
echo " - $service"
|
|
done
|
|
echo " Compose file: docker-compose.final.yml"
|
|
fi
|
|
}
|
|
|
|
# Preserve already-staged SSL mounts when regenerating compose output. The
|
|
# setup wizards treat .env as the configuration for the current target runtime,
|
|
# not as a single file guaranteed to work for both host and Docker Compose at
|
|
# the same time. A later wizard run may rewrite .env again when the operator
|
|
# switches between host and compose workflows.
|
|
prepare_inherited_ssl_assets_for_compose() {
|
|
local existing_compose="${1:-}"
|
|
local staged_cert_source="$SSL_CERT_SOURCE_PATH"
|
|
local staged_key_source="$SSL_KEY_SOURCE_PATH"
|
|
local preserved_cert_path=""
|
|
local preserved_key_path=""
|
|
|
|
if [[ -n "$SSL_CERT_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_CERT_SOURCE_PATH"; then
|
|
if [[ -n "$existing_compose" ]]; then
|
|
preserved_cert_path="$(read_service_environment_value "$existing_compose" "lightrag" "SSL_CERTFILE" || true)"
|
|
fi
|
|
if [[ "$preserved_cert_path" == /app/data/certs/* ]]; then
|
|
log_warn "SSL_CERTFILE source is missing; preserving the existing compose SSL certificate mount."
|
|
staged_cert_source=""
|
|
ENV_VALUES["SSL_CERTFILE"]="$preserved_cert_path"
|
|
set_compose_override "SSL_CERTFILE" "$preserved_cert_path"
|
|
else
|
|
format_error "Invalid SSL_CERTFILE" \
|
|
"Set it to an existing certificate file, disable SSL, or rerun the wizard to choose a new certificate."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$SSL_KEY_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_KEY_SOURCE_PATH"; then
|
|
if [[ -n "$existing_compose" ]]; then
|
|
preserved_key_path="$(read_service_environment_value "$existing_compose" "lightrag" "SSL_KEYFILE" || true)"
|
|
fi
|
|
if [[ "$preserved_key_path" == /app/data/certs/* ]]; then
|
|
log_warn "SSL_KEYFILE source is missing; preserving the existing compose SSL key mount."
|
|
staged_key_source=""
|
|
ENV_VALUES["SSL_KEYFILE"]="$preserved_key_path"
|
|
set_compose_override "SSL_KEYFILE" "$preserved_key_path"
|
|
else
|
|
format_error "Invalid SSL_KEYFILE" \
|
|
"Set it to an existing private key file, disable SSL, or rerun the wizard to choose a new key."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
SSL_CERT_SOURCE_PATH="$staged_cert_source"
|
|
SSL_KEY_SOURCE_PATH="$staged_key_source"
|
|
|
|
if [[ -n "$SSL_CERT_SOURCE_PATH" || -n "$SSL_KEY_SOURCE_PATH" ]]; then
|
|
stage_ssl_assets "$SSL_CERT_SOURCE_PATH" "$SSL_KEY_SOURCE_PATH"
|
|
fi
|
|
}
|
|
|
|
prepare_managed_service_assets_for_compose() {
|
|
local existing_compose="${1:-}"
|
|
|
|
if ! prepare_inherited_ssl_assets_for_compose "$existing_compose"; then
|
|
return 1
|
|
fi
|
|
|
|
if [[ -n "${DOCKER_SERVICE_SET[redis]:-}" ]]; then
|
|
stage_redis_config_asset || return 1
|
|
fi
|
|
}
|
|
|
|
env_base_flow() {
|
|
local vllm_embed_api_key=""
|
|
local vllm_rerank_api_key=""
|
|
local existing_vllm_embed_model=""
|
|
local existing_embedding_dim=""
|
|
local existing_vllm_embed_port=""
|
|
local existing_vllm_embed_host=""
|
|
local existing_vllm_embed_device=""
|
|
local previous_embedding_provider=""
|
|
local existing_vllm_rerank_model=""
|
|
local existing_vllm_rerank_port=""
|
|
local existing_vllm_rerank_host=""
|
|
local existing_vllm_rerank_device=""
|
|
local previous_rerank_provider=""
|
|
if host_cuda_available; then
|
|
log_info "GPU detected: NVIDIA GPU found. New local vLLM services default to CUDA (GPU image + float16)."
|
|
else
|
|
log_info "GPU detection: no NVIDIA GPU found. New local vLLM services default to CPU image + float32."
|
|
fi
|
|
|
|
reset_state
|
|
load_existing_env_if_present
|
|
initialize_default_storage_backends
|
|
|
|
log_info "Base configuration wizard (LLM / Embedding / Reranker)"
|
|
echo "This wizard only modifies LLM, embedding, and reranker settings."
|
|
echo "Storage, server, and security settings are preserved."
|
|
echo ""
|
|
|
|
log_step "LLM configuration"
|
|
collect_llm_config
|
|
echo ""
|
|
|
|
# ── Embedding ────────────────────────────────────────────────────────────────
|
|
log_step "Embedding configuration"
|
|
local docker_embed_default="no"
|
|
previous_embedding_provider="${ENV_VALUES[LIGHTRAG_SETUP_EMBEDDING_PROVIDER]:-}"
|
|
if [[ "$previous_embedding_provider" == "vllm" ]]; then
|
|
docker_embed_default="yes"
|
|
fi
|
|
|
|
local use_docker_embed="no"
|
|
if [[ "$docker_embed_default" == "yes" ]]; then
|
|
confirm_default_yes "Run embedding model locally via Docker (vLLM)?" && use_docker_embed="yes" || use_docker_embed="no"
|
|
else
|
|
confirm_default_no "Run embedding model locally via Docker (vLLM)?" && use_docker_embed="yes" || use_docker_embed="no"
|
|
fi
|
|
|
|
if [[ "$use_docker_embed" == "yes" ]]; then
|
|
existing_vllm_embed_model="${ORIGINAL_ENV_VALUES[VLLM_EMBED_MODEL]-${ENV_VALUES[VLLM_EMBED_MODEL]:-}}"
|
|
existing_embedding_dim="${ORIGINAL_ENV_VALUES[EMBEDDING_DIM]-${ENV_VALUES[EMBEDDING_DIM]:-}}"
|
|
existing_vllm_embed_port="${ORIGINAL_ENV_VALUES[VLLM_EMBED_PORT]-${ENV_VALUES[VLLM_EMBED_PORT]:-}}"
|
|
existing_vllm_embed_host="${ORIGINAL_ENV_VALUES[EMBEDDING_BINDING_HOST]-${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}}"
|
|
existing_vllm_embed_device="${ORIGINAL_ENV_VALUES[VLLM_EMBED_DEVICE]-${ENV_VALUES[VLLM_EMBED_DEVICE]:-}}"
|
|
apply_preset_overwrite "${PRESET_VLLM_EMBEDDING[@]}"
|
|
local vllm_embed_device
|
|
vllm_embed_device="$(resolve_local_device_default "$existing_vllm_embed_device")"
|
|
vllm_embed_device="$(prompt_choice "Embedding device" "$vllm_embed_device" "cpu" "cuda")"
|
|
if [[ "$vllm_embed_device" == "cuda" ]] && ! host_cuda_available; then
|
|
log_warn "CUDA device selected for vLLM embedding but no NVIDIA driver detected on host."
|
|
fi
|
|
if [[ -n "$existing_vllm_embed_port" ]]; then
|
|
ENV_VALUES["VLLM_EMBED_PORT"]="$existing_vllm_embed_port"
|
|
fi
|
|
if [[ -n "$existing_embedding_dim" ]]; then
|
|
ENV_VALUES["EMBEDDING_DIM"]="$existing_embedding_dim"
|
|
fi
|
|
if [[ "$previous_embedding_provider" == "vllm" && -n "$existing_vllm_embed_host" ]]; then
|
|
ENV_VALUES["EMBEDDING_BINDING_HOST"]="$existing_vllm_embed_host"
|
|
else
|
|
ENV_VALUES["EMBEDDING_BINDING_HOST"]="http://localhost:${ENV_VALUES[VLLM_EMBED_PORT]:-8001}/v1"
|
|
fi
|
|
local embed_model
|
|
embed_model="$(prompt_with_default "Embedding model" "${existing_vllm_embed_model:-${ENV_VALUES[VLLM_EMBED_MODEL]:-BAAI/bge-m3}}")"
|
|
ENV_VALUES["VLLM_EMBED_MODEL"]="$embed_model"
|
|
ENV_VALUES["EMBEDDING_MODEL"]="$embed_model"
|
|
ENV_VALUES["VLLM_EMBED_DEVICE"]="$vllm_embed_device"
|
|
ENV_VALUES["LIGHTRAG_SETUP_EMBEDDING_PROVIDER"]="vllm"
|
|
|
|
vllm_embed_api_key="${ENV_VALUES[VLLM_EMBED_API_KEY]:-${ENV_VALUES[EMBEDDING_BINDING_API_KEY]:-}}"
|
|
if [[ -z "$vllm_embed_api_key" ]]; then
|
|
vllm_embed_api_key="$(openssl rand -hex 16 2>/dev/null || LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32)"
|
|
fi
|
|
ENV_VALUES["VLLM_EMBED_API_KEY"]="$vllm_embed_api_key"
|
|
ENV_VALUES["EMBEDDING_BINDING_API_KEY"]="$vllm_embed_api_key"
|
|
add_docker_service "vllm-embed"
|
|
set_compose_override "EMBEDDING_BINDING_HOST" \
|
|
"http://vllm-embed:${ENV_VALUES[VLLM_EMBED_PORT]:-8001}/v1"
|
|
else
|
|
collect_embedding_config
|
|
fi
|
|
echo ""
|
|
|
|
# ── Reranker ─────────────────────────────────────────────────────────────────
|
|
log_step "Reranker configuration"
|
|
local rerank_enabled_default="no"
|
|
if [[ -n "${ENV_VALUES[RERANK_BINDING]:-}" && "${ENV_VALUES[RERANK_BINDING]}" != "null" ]]; then
|
|
rerank_enabled_default="yes"
|
|
fi
|
|
previous_rerank_provider="${ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}"
|
|
|
|
local enable_reranking="no"
|
|
if [[ "$rerank_enabled_default" == "yes" ]]; then
|
|
confirm_default_yes "Enable reranking?" && enable_reranking="yes" || enable_reranking="no"
|
|
else
|
|
confirm_default_no "Enable reranking?" && enable_reranking="yes" || enable_reranking="no"
|
|
fi
|
|
|
|
if [[ "$enable_reranking" == "yes" ]]; then
|
|
local docker_rerank_default="no"
|
|
if [[ "$previous_rerank_provider" == "vllm" ]]; then
|
|
docker_rerank_default="yes"
|
|
fi
|
|
|
|
local use_docker_rerank="no"
|
|
if [[ "$docker_rerank_default" == "yes" ]]; then
|
|
confirm_default_yes "Run rerank service locally via Docker?" && use_docker_rerank="yes" || use_docker_rerank="no"
|
|
else
|
|
confirm_default_no "Run rerank service locally via Docker?" && use_docker_rerank="yes" || use_docker_rerank="no"
|
|
fi
|
|
|
|
if [[ "$use_docker_rerank" == "yes" ]]; then
|
|
existing_vllm_rerank_model="${ORIGINAL_ENV_VALUES[VLLM_RERANK_MODEL]-${ENV_VALUES[VLLM_RERANK_MODEL]:-}}"
|
|
existing_vllm_rerank_port="${ORIGINAL_ENV_VALUES[VLLM_RERANK_PORT]-${ENV_VALUES[VLLM_RERANK_PORT]:-}}"
|
|
existing_vllm_rerank_host="${ORIGINAL_ENV_VALUES[RERANK_BINDING_HOST]-${ENV_VALUES[RERANK_BINDING_HOST]:-}}"
|
|
existing_vllm_rerank_device="${ORIGINAL_ENV_VALUES[VLLM_RERANK_DEVICE]-${ENV_VALUES[VLLM_RERANK_DEVICE]:-}}"
|
|
apply_preset_overwrite "${PRESET_VLLM_RERANKER[@]}"
|
|
local vllm_rerank_device
|
|
vllm_rerank_device="$(resolve_local_device_default "$existing_vllm_rerank_device")"
|
|
vllm_rerank_device="$(prompt_choice "Rerank device" "$vllm_rerank_device" "cpu" "cuda")"
|
|
if [[ "$vllm_rerank_device" == "cuda" ]] && ! host_cuda_available; then
|
|
log_warn "CUDA device selected for vLLM rerank but no NVIDIA driver detected on host."
|
|
fi
|
|
local rerank_model rerank_port
|
|
if [[ -n "$existing_vllm_rerank_port" ]]; then
|
|
ENV_VALUES["VLLM_RERANK_PORT"]="$existing_vllm_rerank_port"
|
|
fi
|
|
if [[ "$previous_rerank_provider" == "vllm" && -n "$existing_vllm_rerank_host" ]]; then
|
|
ENV_VALUES["RERANK_BINDING_HOST"]="$existing_vllm_rerank_host"
|
|
else
|
|
ENV_VALUES["RERANK_BINDING_HOST"]="http://localhost:${ENV_VALUES[VLLM_RERANK_PORT]:-8000}/rerank"
|
|
fi
|
|
rerank_model="$(prompt_with_default "Rerank model" "${existing_vllm_rerank_model:-${ENV_VALUES[VLLM_RERANK_MODEL]:-BAAI/bge-reranker-v2-m3}}")"
|
|
rerank_port="${ENV_VALUES[VLLM_RERANK_PORT]:-8000}"
|
|
ENV_VALUES["VLLM_RERANK_MODEL"]="$rerank_model"
|
|
ENV_VALUES["RERANK_MODEL"]="$rerank_model"
|
|
ENV_VALUES["VLLM_RERANK_PORT"]="$rerank_port"
|
|
ENV_VALUES["VLLM_RERANK_DEVICE"]="$vllm_rerank_device"
|
|
ENV_VALUES["LIGHTRAG_SETUP_RERANK_PROVIDER"]="vllm"
|
|
|
|
vllm_rerank_api_key="${ENV_VALUES[VLLM_RERANK_API_KEY]:-${ENV_VALUES[RERANK_BINDING_API_KEY]:-}}"
|
|
if [[ -z "$vllm_rerank_api_key" ]]; then
|
|
vllm_rerank_api_key="$(openssl rand -hex 16 2>/dev/null || LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32)"
|
|
fi
|
|
ENV_VALUES["VLLM_RERANK_API_KEY"]="$vllm_rerank_api_key"
|
|
ENV_VALUES["RERANK_BINDING_API_KEY"]="$vllm_rerank_api_key"
|
|
add_docker_service "vllm-rerank"
|
|
set_compose_override "RERANK_BINDING_HOST" \
|
|
"http://vllm-rerank:${rerank_port}/rerank"
|
|
else
|
|
# Reranking enabled but not via Docker — ask provider/host/model/api_key
|
|
collect_rerank_config "yes" "no"
|
|
fi
|
|
else
|
|
ENV_VALUES["RERANK_BINDING"]="null"
|
|
unset 'ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]'
|
|
fi
|
|
echo ""
|
|
|
|
finalize_base_setup
|
|
}
|
|
|
|
finalize_base_setup() {
|
|
local backup_path
|
|
local compose_file
|
|
local existing_compose
|
|
local compose_action="write_env_only"
|
|
local runtime_target="$DEFAULT_RUNTIME_TARGET"
|
|
local show_host_start_hint="no"
|
|
local svc_names=""
|
|
|
|
if [[ ! -f "${REPO_ROOT}/env.example" ]]; then
|
|
format_error "env.example is missing in $REPO_ROOT" "Restore env.example before running setup."
|
|
return 1
|
|
fi
|
|
if [[ ! -w "$REPO_ROOT" ]]; then
|
|
format_error "No write permission in $REPO_ROOT" "Run the setup from a writable directory."
|
|
return 1
|
|
fi
|
|
|
|
if ! validate_sensitive_env_literals; then
|
|
return 1
|
|
fi
|
|
|
|
if ! validate_mongo_vector_storage_config \
|
|
"${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" \
|
|
"${ENV_VALUES[MONGO_URI]:-}" \
|
|
"${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}"; then
|
|
return 1
|
|
fi
|
|
|
|
show_summary
|
|
|
|
if ! confirm_required_yes_no "${COLOR_YELLOW}Ready to proceed and write .env${COLOR_RESET}"; then
|
|
log_warn "Setup cancelled."
|
|
return 1
|
|
fi
|
|
|
|
existing_compose="$(find_generated_compose_file)"
|
|
compose_file="${REPO_ROOT}/docker-compose.final.yml"
|
|
record_existing_managed_root_services "$existing_compose"
|
|
restore_storage_docker_services_from_env
|
|
configure_mongodb_compose_migration_rewrite "$existing_compose"
|
|
|
|
configure_base_compose_rewrites
|
|
|
|
if ((${#DOCKER_SERVICES[@]} > 0)); then
|
|
# LightRAG depends on managed Docker services; it must run via Docker.
|
|
svc_names="$(printf '%s ' "${DOCKER_SERVICES[@]}")"
|
|
svc_names="${svc_names% }"
|
|
echo "LightRAG requires Docker services: ${svc_names}"
|
|
if ! confirm_default_yes "${COLOR_YELLOW}The compose file will be created/updated. Continue?${COLOR_RESET}"; then
|
|
log_warn "Setup cancelled."
|
|
return 1
|
|
fi
|
|
compose_action="rewrite_compose"
|
|
runtime_target="compose"
|
|
else
|
|
resolve_compose_output_action \
|
|
"$existing_compose" \
|
|
compose_action \
|
|
runtime_target \
|
|
show_host_start_hint
|
|
fi
|
|
|
|
if [[ "$compose_action" == "rewrite_compose" ]]; then
|
|
backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
|
|
if ! prepare_managed_service_assets_for_compose "$existing_compose"; then
|
|
return 1
|
|
fi
|
|
collect_preserved_storage_service_images "$existing_compose"
|
|
prepare_compose_env_overrides
|
|
elif [[ "$compose_action" == "delete_compose_and_switch_host" ]]; then
|
|
backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
|
|
fi
|
|
|
|
backup_path="$(backup_env_file)"
|
|
if [[ -n "$backup_path" ]]; then
|
|
log_success "Backed up existing .env to $backup_path"
|
|
fi
|
|
|
|
clear_deprecated_vllm_dtype_state
|
|
set_runtime_target "$runtime_target" || return 1
|
|
generate_env_file "${REPO_ROOT}/env.example" "${REPO_ROOT}/.env"
|
|
log_success "Wrote .env"
|
|
|
|
case "$compose_action" in
|
|
rewrite_compose)
|
|
prepare_compose_output_from_existing "$compose_file" "$existing_compose" || return 1
|
|
generate_docker_compose "$compose_file"
|
|
log_success "Wrote ${compose_file}"
|
|
echo " To start: docker compose -f ${compose_file} up -d"
|
|
;;
|
|
delete_compose_and_switch_host)
|
|
remove_existing_compose_file "$existing_compose" || return 1
|
|
echo " To start: lightrag-server"
|
|
;;
|
|
*)
|
|
if [[ "$show_host_start_hint" == "yes" ]]; then
|
|
echo " To start: lightrag-server"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
env_storage_flow() {
|
|
local env_file="${REPO_ROOT}/.env"
|
|
local db_type
|
|
local db_order=("postgresql" "neo4j" "mongodb" "redis" "milvus" "qdrant" "memgraph" "opensearch")
|
|
|
|
if [[ ! -f "$env_file" ]]; then
|
|
format_error "No .env file found." "Run 'make env-base' first to configure LLM and embedding."
|
|
return 1
|
|
fi
|
|
|
|
reset_state
|
|
load_existing_env_if_present
|
|
|
|
log_info "Storage configuration wizard"
|
|
echo "This wizard only modifies storage backend settings."
|
|
echo "LLM, embedding, reranker, server, and security settings are preserved."
|
|
echo ""
|
|
|
|
log_step "Storage backend selection"
|
|
select_storage_backends "custom"
|
|
log_debug "Storage selections: kv=${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-} vector=${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-} graph=${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-} doc=${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-}"
|
|
clear_unused_storage_deployment_markers
|
|
|
|
log_step "Database configuration"
|
|
for db_type in "${db_order[@]}"; do
|
|
if [[ -n "${REQUIRED_DB_TYPES[$db_type]+set}" ]]; then
|
|
collect_database_config "$db_type" "$(storage_default_docker_for_db_type "$db_type")"
|
|
echo ""
|
|
fi
|
|
done
|
|
|
|
finalize_storage_setup
|
|
}
|
|
|
|
finalize_storage_setup() {
|
|
local backup_path
|
|
local compose_file
|
|
local existing_compose
|
|
local compose_action="write_env_only"
|
|
local runtime_target="$DEFAULT_RUNTIME_TARGET"
|
|
local show_host_start_hint="no"
|
|
|
|
if [[ ! -f "${REPO_ROOT}/env.example" ]]; then
|
|
format_error "env.example is missing in $REPO_ROOT" "Restore env.example before running setup."
|
|
return 1
|
|
fi
|
|
if [[ ! -w "$REPO_ROOT" ]]; then
|
|
format_error "No write permission in $REPO_ROOT" "Run the setup from a writable directory."
|
|
return 1
|
|
fi
|
|
|
|
if [[ -n "${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-}" ]]; then
|
|
if ! validate_required_variables \
|
|
"${ENV_VALUES[LIGHTRAG_KV_STORAGE]}" \
|
|
"${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]}" \
|
|
"${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]}" \
|
|
"${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]}"; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if ! validate_mongo_vector_storage_config \
|
|
"${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" \
|
|
"${ENV_VALUES[MONGO_URI]:-}" \
|
|
"${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}"; then
|
|
return 1
|
|
fi
|
|
|
|
if ! validate_sensitive_env_literals; then
|
|
return 1
|
|
fi
|
|
|
|
show_summary
|
|
|
|
if ! confirm_required_yes_no "${COLOR_YELLOW}Ready to proceed and write .env${COLOR_RESET}"; then
|
|
log_warn "Setup cancelled."
|
|
return 1
|
|
fi
|
|
|
|
existing_compose="$(find_generated_compose_file)"
|
|
compose_file="${REPO_ROOT}/docker-compose.final.yml"
|
|
record_existing_managed_root_services "$existing_compose"
|
|
restore_vllm_docker_services_from_env
|
|
configure_storage_compose_rewrites
|
|
configure_mongodb_compose_migration_rewrite "$existing_compose"
|
|
resolve_compose_output_action \
|
|
"$existing_compose" \
|
|
compose_action \
|
|
runtime_target \
|
|
show_host_start_hint
|
|
|
|
if [[ "$compose_action" == "rewrite_compose" ]]; then
|
|
backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
|
|
if ! prepare_managed_service_assets_for_compose "$existing_compose"; then
|
|
return 1
|
|
fi
|
|
collect_preserved_storage_service_images "$existing_compose"
|
|
prepare_compose_env_overrides
|
|
elif [[ "$compose_action" == "delete_compose_and_switch_host" ]]; then
|
|
backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
|
|
fi
|
|
|
|
backup_path="$(backup_env_file)"
|
|
if [[ -n "$backup_path" ]]; then
|
|
log_success "Backed up existing .env to $backup_path"
|
|
fi
|
|
|
|
clear_deprecated_vllm_dtype_state
|
|
set_runtime_target "$runtime_target" || return 1
|
|
generate_env_file "${REPO_ROOT}/env.example" "${REPO_ROOT}/.env"
|
|
log_success "Wrote .env"
|
|
|
|
case "$compose_action" in
|
|
rewrite_compose)
|
|
prepare_compose_output_from_existing "$compose_file" "$existing_compose" || return 1
|
|
generate_docker_compose "$compose_file"
|
|
log_success "Wrote ${compose_file}"
|
|
echo " To start: docker compose -f ${compose_file} up -d"
|
|
;;
|
|
delete_compose_and_switch_host)
|
|
remove_existing_compose_file "$existing_compose" || return 1
|
|
echo " To start: lightrag-server"
|
|
;;
|
|
*)
|
|
if [[ "$show_host_start_hint" == "yes" ]]; then
|
|
echo " To start: lightrag-server"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
env_server_flow() {
|
|
local env_file="${REPO_ROOT}/.env"
|
|
|
|
if [[ ! -f "$env_file" ]]; then
|
|
format_error "No .env file found." "Run 'make env-base' first to configure LLM and embedding."
|
|
return 1
|
|
fi
|
|
|
|
reset_state
|
|
load_existing_env_if_present
|
|
|
|
log_info "Server configuration wizard"
|
|
echo "This wizard only modifies server, security, and SSL settings."
|
|
echo "LLM, embedding, reranker, and storage settings are preserved."
|
|
echo ""
|
|
|
|
log_step "Server configuration"
|
|
collect_server_config
|
|
echo ""
|
|
log_step "Security configuration"
|
|
collect_security_config "no" "no"
|
|
warn_if_network_exposed_without_auth
|
|
echo ""
|
|
log_step "SSL configuration"
|
|
collect_ssl_config
|
|
echo ""
|
|
|
|
finalize_server_setup
|
|
}
|
|
|
|
finalize_server_setup() {
|
|
local backup_path
|
|
local compose_file
|
|
local existing_compose
|
|
local compose_action="write_env_only"
|
|
local runtime_target="$DEFAULT_RUNTIME_TARGET"
|
|
local show_host_start_hint="no"
|
|
|
|
if [[ ! -f "${REPO_ROOT}/env.example" ]]; then
|
|
format_error "env.example is missing in $REPO_ROOT" "Restore env.example before running setup."
|
|
return 1
|
|
fi
|
|
if [[ ! -w "$REPO_ROOT" ]]; then
|
|
format_error "No write permission in $REPO_ROOT" "Run the setup from a writable directory."
|
|
return 1
|
|
fi
|
|
|
|
if ! validate_sensitive_env_literals; then
|
|
return 1
|
|
fi
|
|
|
|
if ! validate_auth_accounts_runtime_config \
|
|
"${ENV_VALUES[AUTH_ACCOUNTS]:-}"; then
|
|
return 1
|
|
fi
|
|
|
|
if ! validate_mongo_vector_storage_config \
|
|
"${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" \
|
|
"${ENV_VALUES[MONGO_URI]:-}" \
|
|
"${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}"; then
|
|
return 1
|
|
fi
|
|
|
|
show_summary
|
|
|
|
if ! confirm_required_yes_no "${COLOR_YELLOW}Ready to proceed and write .env${COLOR_RESET}"; then
|
|
log_warn "Setup cancelled."
|
|
return 1
|
|
fi
|
|
|
|
existing_compose="$(find_generated_compose_file)"
|
|
compose_file="${REPO_ROOT}/docker-compose.final.yml"
|
|
record_existing_managed_root_services "$existing_compose"
|
|
restore_storage_docker_services_from_env
|
|
restore_vllm_docker_services_from_env
|
|
configure_mongodb_compose_migration_rewrite "$existing_compose"
|
|
resolve_compose_output_action \
|
|
"$existing_compose" \
|
|
compose_action \
|
|
runtime_target \
|
|
show_host_start_hint
|
|
|
|
if [[ "$compose_action" == "rewrite_compose" ]]; then
|
|
backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
|
|
if ! prepare_managed_service_assets_for_compose "$existing_compose"; then
|
|
return 1
|
|
fi
|
|
collect_preserved_storage_service_images "$existing_compose"
|
|
prepare_compose_env_overrides
|
|
elif [[ "$compose_action" == "delete_compose_and_switch_host" ]]; then
|
|
backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
|
|
if [[ -n "$SSL_CERT_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_CERT_SOURCE_PATH"; then
|
|
format_error "Invalid SSL_CERTFILE" \
|
|
"Set it to an existing certificate file, disable SSL, or rerun the wizard to choose a new certificate."
|
|
return 1
|
|
fi
|
|
|
|
if [[ -n "$SSL_KEY_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_KEY_SOURCE_PATH"; then
|
|
format_error "Invalid SSL_KEYFILE" \
|
|
"Set it to an existing private key file, disable SSL, or rerun the wizard to choose a new key."
|
|
return 1
|
|
fi
|
|
else
|
|
if [[ -n "$SSL_CERT_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_CERT_SOURCE_PATH"; then
|
|
format_error "Invalid SSL_CERTFILE" \
|
|
"Set it to an existing certificate file, disable SSL, or rerun the wizard to choose a new certificate."
|
|
return 1
|
|
fi
|
|
|
|
if [[ -n "$SSL_KEY_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_KEY_SOURCE_PATH"; then
|
|
format_error "Invalid SSL_KEYFILE" \
|
|
"Set it to an existing private key file, disable SSL, or rerun the wizard to choose a new key."
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
backup_path="$(backup_env_file)"
|
|
if [[ -n "$backup_path" ]]; then
|
|
log_success "Backed up existing .env to $backup_path"
|
|
fi
|
|
|
|
clear_deprecated_vllm_dtype_state
|
|
set_runtime_target "$runtime_target" || return 1
|
|
generate_env_file "${REPO_ROOT}/env.example" "${REPO_ROOT}/.env"
|
|
log_success "Wrote .env"
|
|
|
|
case "$compose_action" in
|
|
rewrite_compose)
|
|
prepare_compose_output_from_existing "$compose_file" "$existing_compose" || return 1
|
|
generate_docker_compose "$compose_file"
|
|
log_success "Wrote ${compose_file}"
|
|
log_success "Server port and security settings updated in compose."
|
|
echo " To restart: docker compose -f ${compose_file} up -d --force-recreate lightrag"
|
|
;;
|
|
delete_compose_and_switch_host)
|
|
remove_existing_compose_file "$existing_compose" || return 1
|
|
echo " To start: lightrag-server"
|
|
;;
|
|
*)
|
|
if [[ "$show_host_start_hint" == "yes" ]]; then
|
|
echo " To start: lightrag-server"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
load_env_file() {
|
|
local env_file="$1"
|
|
local line key value
|
|
|
|
if [[ ! -f "$env_file" ]]; then
|
|
format_error ".env file not found at $env_file" "Run make env-base to generate it."
|
|
return 1
|
|
fi
|
|
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
if [[ "$line" =~ ^[A-Za-z0-9_]+= ]]; then
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
if [[ "$value" =~ ^\".*\"$ ]]; then
|
|
value="${value:1:${#value}-2}"
|
|
value="${value//\\\$/\$}"
|
|
value="${value//\\\"/\"}"
|
|
value="${value//\\\\/\\}"
|
|
elif [[ "$value" =~ ^\'.*\'$ ]]; then
|
|
value="${value:1:${#value}-2}"
|
|
fi
|
|
ENV_VALUES["$key"]="$value"
|
|
fi
|
|
done < "$env_file"
|
|
}
|
|
|
|
validate_ssl_runtime_path() {
|
|
local path="$1"
|
|
local runtime_target="${ENV_VALUES[LIGHTRAG_RUNTIME_TARGET]:-$DEFAULT_RUNTIME_TARGET}"
|
|
local staged_path=""
|
|
|
|
if validate_existing_file "$path"; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$runtime_target" == "compose" && "$path" == /app/data/certs/* ]]; then
|
|
staged_path="${REPO_ROOT}/data/certs/${path#/app/data/certs/}"
|
|
validate_existing_file "$staged_path"
|
|
return $?
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
validate_env_file() {
|
|
local env_file="${REPO_ROOT}/.env"
|
|
local errors=0
|
|
local kv vector graph doc_status
|
|
local runtime_target
|
|
local storage db_type
|
|
local -A referenced_db_types=()
|
|
|
|
reset_state
|
|
|
|
if ! load_env_file "$env_file"; then
|
|
return 1
|
|
fi
|
|
|
|
kv="${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-}"
|
|
vector="${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}"
|
|
graph="${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-}"
|
|
doc_status="${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-}"
|
|
runtime_target="${ENV_VALUES[LIGHTRAG_RUNTIME_TARGET]:-$DEFAULT_RUNTIME_TARGET}"
|
|
|
|
for storage in "$kv" "$vector" "$graph" "$doc_status"; do
|
|
if [[ -z "$storage" ]]; then
|
|
continue
|
|
fi
|
|
db_type="${STORAGE_DB_TYPES[$storage]:-}"
|
|
if [[ -n "$db_type" ]]; then
|
|
referenced_db_types["$db_type"]=1
|
|
fi
|
|
done
|
|
|
|
if ! validate_runtime_target "$runtime_target"; then
|
|
errors=1
|
|
fi
|
|
|
|
if [[ -z "$kv" || -z "$vector" || -z "$graph" || -z "$doc_status" ]]; then
|
|
format_error "Storage selections are missing in .env" "Set LIGHTRAG_*_STORAGE variables."
|
|
return 1
|
|
fi
|
|
|
|
if ! validate_mongo_vector_storage_config \
|
|
"$vector" \
|
|
"${ENV_VALUES[MONGO_URI]:-}" \
|
|
"${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}"; then
|
|
errors=1
|
|
fi
|
|
|
|
if ! validate_required_variables "$kv" "$vector" "$graph" "$doc_status"; then
|
|
errors=1
|
|
fi
|
|
|
|
if ! validate_auth_accounts_runtime_config \
|
|
"${ENV_VALUES[AUTH_ACCOUNTS]:-}"; then
|
|
errors=1
|
|
fi
|
|
|
|
if ! validate_sensitive_env_literals; then
|
|
errors=1
|
|
fi
|
|
|
|
if [[ "${ENV_VALUES[SSL]:-false}" == "true" ]]; then
|
|
if ! validate_ssl_runtime_path "${ENV_VALUES[SSL_CERTFILE]:-}"; then
|
|
format_error "Invalid SSL_CERTFILE" "Set it to an existing certificate file when SSL=true."
|
|
errors=1
|
|
fi
|
|
if ! validate_ssl_runtime_path "${ENV_VALUES[SSL_KEYFILE]:-}"; then
|
|
format_error "Invalid SSL_KEYFILE" "Set it to an existing private key file when SSL=true."
|
|
errors=1
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${referenced_db_types[neo4j]+set}" ]] && [[ -n "${ENV_VALUES[NEO4J_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[NEO4J_URI]}" neo4j; then
|
|
format_error "Invalid NEO4J_URI" "Use neo4j:// or bolt:// format."
|
|
errors=1
|
|
fi
|
|
if [[ -n "${referenced_db_types[mongodb]+set}" ]] && [[ -n "${ENV_VALUES[MONGO_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[MONGO_URI]}" mongodb; then
|
|
format_error "Invalid MONGO_URI" "Use mongodb:// or mongodb+srv:// format."
|
|
errors=1
|
|
fi
|
|
if [[ -n "${referenced_db_types[redis]+set}" ]] && [[ -n "${ENV_VALUES[REDIS_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[REDIS_URI]}" redis; then
|
|
format_error "Invalid REDIS_URI" "Use redis:// or rediss:// format."
|
|
errors=1
|
|
fi
|
|
if [[ -n "${referenced_db_types[milvus]+set}" ]] && [[ -n "${ENV_VALUES[MILVUS_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[MILVUS_URI]}" milvus; then
|
|
format_error "Invalid MILVUS_URI" "Use http://host:port format."
|
|
errors=1
|
|
fi
|
|
if [[ -n "${referenced_db_types[qdrant]+set}" ]] && [[ -n "${ENV_VALUES[QDRANT_URL]:-}" ]] && ! validate_uri "${ENV_VALUES[QDRANT_URL]}" qdrant; then
|
|
format_error "Invalid QDRANT_URL" "Use http://host:port format."
|
|
errors=1
|
|
fi
|
|
if [[ -n "${referenced_db_types[memgraph]+set}" ]] && [[ -n "${ENV_VALUES[MEMGRAPH_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[MEMGRAPH_URI]}" memgraph; then
|
|
format_error "Invalid MEMGRAPH_URI" "Use bolt://host:port format."
|
|
errors=1
|
|
fi
|
|
if [[ -n "${referenced_db_types[postgresql]+set}" ]] && [[ -n "${ENV_VALUES[POSTGRES_PORT]:-}" ]] && ! validate_port "${ENV_VALUES[POSTGRES_PORT]}"; then
|
|
format_error "Invalid POSTGRES_PORT" "Use a port between 1 and 65535."
|
|
errors=1
|
|
fi
|
|
if [[ -n "${referenced_db_types[opensearch]+set}" ]] && [[ -v 'ENV_VALUES[OPENSEARCH_HOSTS]' ]] && [[ -z "${ENV_VALUES[OPENSEARCH_HOSTS]}" ]]; then
|
|
format_error "Empty OPENSEARCH_HOSTS" "Set it to host:port (e.g. localhost:9200)."
|
|
errors=1
|
|
fi
|
|
if [[ -n "${referenced_db_types[opensearch]+set}" ]]; then
|
|
if ! validate_opensearch_config \
|
|
"${ENV_VALUES[LIGHTRAG_SETUP_OPENSEARCH_DEPLOYMENT]:-}" \
|
|
"${ENV_VALUES[OPENSEARCH_HOSTS]:-}" \
|
|
"${ENV_VALUES[OPENSEARCH_USER]:-}" \
|
|
"${ENV_VALUES[OPENSEARCH_PASSWORD]:-}" \
|
|
"${ENV_VALUES[OPENSEARCH_NUMBER_OF_SHARDS]-1}" \
|
|
"${ENV_VALUES[OPENSEARCH_NUMBER_OF_REPLICAS]-0}"; then
|
|
errors=1
|
|
fi
|
|
fi
|
|
if ((errors != 0)); then
|
|
return 1
|
|
fi
|
|
|
|
log_success "Validation passed."
|
|
}
|
|
|
|
report_security_issue() {
|
|
local message="$1"
|
|
local suggestion="${2:-}"
|
|
|
|
echo "${COLOR_YELLOW:-}Security issue:${COLOR_RESET:-} $message"
|
|
if [[ -n "$suggestion" ]]; then
|
|
echo " Suggestion: $suggestion"
|
|
fi
|
|
}
|
|
|
|
security_check_env_file() {
|
|
local env_file="${REPO_ROOT}/.env"
|
|
local findings=0
|
|
local auth_accounts=""
|
|
local token_secret=""
|
|
local api_key=""
|
|
local whitelist_paths=""
|
|
local whitelist_is_set="no"
|
|
local effective_whitelist=""
|
|
local kv=""
|
|
local vector=""
|
|
local graph=""
|
|
local doc_status=""
|
|
local storage=""
|
|
local db_type=""
|
|
local opensearch_in_use="no"
|
|
local key value
|
|
local invalid_sensitive_keys=()
|
|
local -A referenced_db_types=()
|
|
|
|
reset_state
|
|
|
|
if ! load_env_file "$env_file"; then
|
|
return 1
|
|
fi
|
|
|
|
auth_accounts="${ENV_VALUES[AUTH_ACCOUNTS]:-}"
|
|
token_secret="${ENV_VALUES[TOKEN_SECRET]:-}"
|
|
api_key="${ENV_VALUES[LIGHTRAG_API_KEY]:-}"
|
|
kv="${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-}"
|
|
vector="${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}"
|
|
graph="${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-}"
|
|
doc_status="${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-}"
|
|
if [[ -n "${ENV_VALUES[WHITELIST_PATHS]+set}" ]]; then
|
|
whitelist_paths="${ENV_VALUES[WHITELIST_PATHS]}"
|
|
whitelist_is_set="yes"
|
|
fi
|
|
|
|
for storage in "$kv" "$vector" "$graph" "$doc_status"; do
|
|
if [[ -z "$storage" ]]; then
|
|
continue
|
|
fi
|
|
if [[ ! -v "STORAGE_DB_TYPES[$storage]" ]]; then
|
|
continue
|
|
fi
|
|
db_type="${STORAGE_DB_TYPES[$storage]}"
|
|
if [[ -n "$db_type" ]]; then
|
|
referenced_db_types["$db_type"]=1
|
|
fi
|
|
done
|
|
|
|
if [[ -n "${referenced_db_types[opensearch]+set}" || "${ENV_VALUES[LIGHTRAG_SETUP_OPENSEARCH_DEPLOYMENT]:-}" == "docker" ]]; then
|
|
opensearch_in_use="yes"
|
|
fi
|
|
|
|
for key in "${!ENV_VALUES[@]}"; do
|
|
if ! is_sensitive_env_key "$key"; then
|
|
continue
|
|
fi
|
|
value="${ENV_VALUES[$key]:-}"
|
|
if [[ -n "$value" ]] && contains_env_interpolation_syntax "$value"; then
|
|
invalid_sensitive_keys+=("$key")
|
|
fi
|
|
done
|
|
|
|
if ((${#invalid_sensitive_keys[@]} > 0)); then
|
|
report_security_issue \
|
|
"Sensitive values still contain \${...} interpolation syntax: ${invalid_sensitive_keys[*]}" \
|
|
"Replace them with literal values or inject those secrets at runtime."
|
|
findings=$((findings + 1))
|
|
fi
|
|
|
|
if [[ -z "$auth_accounts" && -z "$api_key" ]]; then
|
|
local no_auth_message="No API protection is configured."
|
|
if ! host_is_loopback "${ENV_VALUES[HOST]:-0.0.0.0}"; then
|
|
no_auth_message="No API protection is configured while HOST=${ENV_VALUES[HOST]:-0.0.0.0} is reachable from the network."
|
|
fi
|
|
report_security_issue \
|
|
"$no_auth_message" \
|
|
"Set AUTH_ACCOUNTS and TOKEN_SECRET, add LIGHTRAG_API_KEY, or put the service behind a trusted reverse proxy."
|
|
findings=$((findings + 1))
|
|
fi
|
|
|
|
if [[ -n "$auth_accounts" ]]; then
|
|
if ! validate_auth_accounts_format "$auth_accounts"; then
|
|
report_security_issue \
|
|
"AUTH_ACCOUNTS is malformed." \
|
|
"Use comma-separated user:password pairs such as admin:{bcrypt}<hash> or admin:secret,reader:another-secret."
|
|
findings=$((findings + 1))
|
|
elif ! validate_auth_accounts_password_safety "$auth_accounts"; then
|
|
report_security_issue \
|
|
"AUTH_ACCOUNTS uses a predictable password prefix." \
|
|
"Passwords must not start with 'admin' or 'pass'. Choose a stronger password or use lightrag-hash-password."
|
|
findings=$((findings + 1))
|
|
fi
|
|
|
|
if [[ -z "$token_secret" ]]; then
|
|
report_security_issue \
|
|
"AUTH_ACCOUNTS is set but TOKEN_SECRET is missing." \
|
|
"Set a non-empty JWT signing secret before enabling account-based authentication."
|
|
findings=$((findings + 1))
|
|
elif [[ "$token_secret" == "lightrag-jwt-default-secret" ]]; then
|
|
report_security_issue \
|
|
"TOKEN_SECRET still uses the built-in default value." \
|
|
"Generate a unique JWT signing secret and update TOKEN_SECRET."
|
|
findings=$((findings + 1))
|
|
fi
|
|
|
|
effective_whitelist="$whitelist_paths"
|
|
if [[ "$whitelist_is_set" != "yes" ]]; then
|
|
effective_whitelist="/health,/api/*"
|
|
fi
|
|
if whitelist_exposes_api_routes "$effective_whitelist"; then
|
|
report_security_issue \
|
|
"WHITELIST_PATHS exposes /api routes while AUTH_ACCOUNTS is enabled." \
|
|
"Use a minimal whitelist such as /health,/docs and keep /api routes authenticated."
|
|
findings=$((findings + 1))
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "$auth_accounts" && -n "$api_key" ]]; then
|
|
effective_whitelist="$whitelist_paths"
|
|
if [[ "$whitelist_is_set" != "yes" ]]; then
|
|
effective_whitelist="/health,/api/*"
|
|
fi
|
|
if whitelist_exposes_api_routes "$effective_whitelist"; then
|
|
report_security_issue \
|
|
"WHITELIST_PATHS exposes /api routes while LIGHTRAG_API_KEY is the only active auth mechanism." \
|
|
"Use a minimal whitelist such as /health,/docs and keep /api routes protected by the API key."
|
|
findings=$((findings + 1))
|
|
fi
|
|
fi
|
|
|
|
if [[ "$opensearch_in_use" == "yes" ]] && [[ -n "${ENV_VALUES[OPENSEARCH_PASSWORD]:-}" ]]; then
|
|
local os_pass="${ENV_VALUES[OPENSEARCH_PASSWORD]}"
|
|
if [[ "$os_pass" == "admin" || "$os_pass" == "LightRAG2026_!@" ]]; then
|
|
report_security_issue \
|
|
"OPENSEARCH_PASSWORD uses a well-known default value." \
|
|
"Set a unique, strong password for the OpenSearch admin account."
|
|
findings=$((findings + 1))
|
|
fi
|
|
fi
|
|
|
|
if ((findings == 0)); then
|
|
log_success "No obvious security issues found in ${env_file}."
|
|
return 0
|
|
fi
|
|
|
|
log_warn "Security check found ${findings} issue(s) in ${env_file}."
|
|
return 1
|
|
}
|
|
|
|
backup_only() {
|
|
local backup_path
|
|
local compose_backup_path
|
|
|
|
backup_path="$(backup_env_file)"
|
|
if [[ -z "$backup_path" ]]; then
|
|
format_error "No .env file found to back up." "Create one with make env-base first."
|
|
return 1
|
|
fi
|
|
echo "Backed up .env to $backup_path"
|
|
|
|
compose_backup_path="$(backup_compose_file)" || return 1
|
|
if [[ -n "$compose_backup_path" ]]; then
|
|
echo "Backed up compose file to $compose_backup_path"
|
|
fi
|
|
}
|
|
|
|
print_help() {
|
|
cat <<'HELP'
|
|
Usage: scripts/setup/setup.sh [--base|--storage|--server|--validate|--security-check|--backup] [--rewrite-compose]
|
|
|
|
Options:
|
|
--base Configure LLM, embedding, and reranker (run first)
|
|
--storage Configure storage backends and databases (requires .env)
|
|
--server Configure server, security, and SSL (requires .env)
|
|
--validate Validate an existing .env file
|
|
--security-check Audit an existing .env for security risks
|
|
--backup Backup the current .env and generated compose file when present
|
|
--rewrite-compose Force regeneration of all wizard-managed compose services
|
|
--debug Enable debug logging
|
|
--help Show this help message
|
|
HELP
|
|
}
|
|
|
|
_sigint_handler() {
|
|
echo ""
|
|
echo "Setup interrupted."
|
|
exit 130
|
|
}
|
|
|
|
main() {
|
|
trap '_sigint_handler' INT
|
|
init_colors
|
|
local mode="help"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--base)
|
|
mode="base"
|
|
;;
|
|
--storage)
|
|
mode="storage"
|
|
;;
|
|
--server)
|
|
mode="server"
|
|
;;
|
|
--validate)
|
|
mode="validate"
|
|
;;
|
|
--security-check)
|
|
mode="security-check"
|
|
;;
|
|
--backup)
|
|
mode="backup"
|
|
;;
|
|
--debug)
|
|
DEBUG="true"
|
|
;;
|
|
--rewrite-compose)
|
|
FORCE_REWRITE_COMPOSE="yes"
|
|
;;
|
|
--help|-h)
|
|
mode="help"
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1" >&2
|
|
print_help
|
|
return 1
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
case "$mode" in
|
|
base)
|
|
env_base_flow
|
|
;;
|
|
storage)
|
|
env_storage_flow
|
|
;;
|
|
server)
|
|
env_server_flow
|
|
;;
|
|
validate)
|
|
validate_env_file
|
|
;;
|
|
security-check)
|
|
security_check_env_file
|
|
;;
|
|
backup)
|
|
backup_only
|
|
;;
|
|
*)
|
|
print_help
|
|
;;
|
|
esac
|
|
}
|
|
|
|
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
main "$@"
|
|
fi
|