| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325 |
- #!/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}"
- }
- 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[@]}")"
- vector_storage="$(prompt_choice "Vector storage" "$vector_default" "${VECTOR_STORAGE_OPTIONS[@]}")"
- graph_storage="$(prompt_choice "Graph storage" "$graph_default" "${GRAPH_STORAGE_OPTIONS[@]}")"
- doc_storage="$(prompt_choice "Doc status storage" "$doc_default" "${DOC_STATUS_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"
- 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
- report_security_issue \
- "No API protection is configured." \
- "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
|