setup.sh 108 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. if [[ -z "${BASH_VERSINFO+x}" || "${BASH_VERSINFO[0]}" -lt 4 ]]; then
  4. echo "Error: scripts/setup/setup.sh requires Bash 4 or newer." >&2
  5. echo "Hint: install a newer bash and run it via 'bash scripts/setup/setup.sh ...'." >&2
  6. exit 1
  7. fi
  8. SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  9. LIB_DIR="$SCRIPT_DIR/lib"
  10. # shellcheck disable=SC2034
  11. TEMPLATES_DIR="$SCRIPT_DIR/templates"
  12. REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
  13. declare -A ENV_VALUES
  14. declare -A ORIGINAL_ENV_VALUES
  15. declare -A COMPOSE_ENV_OVERRIDES
  16. declare -A COMPOSE_REWRITE_SERVICE_SET
  17. declare -A COMPOSE_SERVICE_IMAGE_OVERRIDES
  18. declare -A REQUIRED_DB_TYPES
  19. declare -A DOCKER_SERVICE_SET
  20. declare -A EXISTING_MANAGED_ROOT_SERVICE_SET
  21. declare -a DOCKER_SERVICES
  22. SSL_CERT_SOURCE_PATH=""
  23. SSL_KEY_SOURCE_PATH=""
  24. LIGHTRAG_COMPOSE_SERVER_PORT_MAPPING=""
  25. NORMALIZED_SERVER_HOST_FOR_COMPOSE=""
  26. FORCE_REWRITE_COMPOSE="no"
  27. DEBUG="${DEBUG:-false}"
  28. PRESET_VLLM_EMBEDDING=(
  29. "EMBEDDING_BINDING=openai"
  30. "EMBEDDING_BINDING_HOST=http://localhost:8001/v1"
  31. "EMBEDDING_MODEL=BAAI/bge-m3"
  32. "EMBEDDING_DIM=1024"
  33. "VLLM_EMBED_MODEL=BAAI/bge-m3"
  34. "VLLM_EMBED_PORT=8001"
  35. "VLLM_EMBED_DEVICE=cpu"
  36. )
  37. PRESET_VLLM_RERANKER=(
  38. "RERANK_BINDING=cohere"
  39. "LIGHTRAG_SETUP_RERANK_PROVIDER=vllm"
  40. "RERANK_MODEL=BAAI/bge-reranker-v2-m3"
  41. "RERANK_BINDING_HOST=http://localhost:8000/rerank"
  42. "VLLM_RERANK_MODEL=BAAI/bge-reranker-v2-m3"
  43. "VLLM_RERANK_PORT=8000"
  44. "VLLM_RERANK_DEVICE=cpu"
  45. )
  46. VLLM_SERVICES=(
  47. "vllm-embed"
  48. "vllm-rerank"
  49. )
  50. STORAGE_SERVICES=(
  51. "postgres"
  52. "neo4j"
  53. "mongodb"
  54. "redis"
  55. "milvus"
  56. "qdrant"
  57. "memgraph"
  58. "opensearch"
  59. )
  60. DEFAULT_RUNTIME_TARGET="host"
  61. COMPOSE_LIGHTRAG_WORKING_DIR="/app/data/rag_storage"
  62. COMPOSE_LIGHTRAG_INPUT_DIR="/app/data/inputs"
  63. COMPOSE_LIGHTRAG_PROMPT_DIR="/app/data/prompts"
  64. # shellcheck disable=SC2034
  65. COLOR_RESET=""
  66. COLOR_BOLD=""
  67. COLOR_BLUE=""
  68. COLOR_GREEN=""
  69. COLOR_YELLOW=""
  70. # shellcheck disable=SC2034
  71. COLOR_RED=""
  72. # shellcheck disable=SC1091
  73. source "$LIB_DIR/storage_requirements.sh"
  74. # shellcheck disable=SC1091
  75. source "$LIB_DIR/validation.sh"
  76. # shellcheck disable=SC1091
  77. source "$LIB_DIR/prompts.sh"
  78. # shellcheck disable=SC1091
  79. source "$LIB_DIR/file_ops.sh"
  80. # shellcheck disable=SC1091
  81. source "$LIB_DIR/presets.sh"
  82. init_colors() {
  83. if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
  84. COLOR_RESET=$'\033[0m'
  85. COLOR_BOLD=$'\033[1m'
  86. COLOR_BLUE=$'\033[34m'
  87. COLOR_GREEN=$'\033[32m'
  88. COLOR_YELLOW=$'\033[33m'
  89. # shellcheck disable=SC2034
  90. COLOR_RED=$'\033[31m'
  91. fi
  92. }
  93. reset_state() {
  94. ENV_VALUES=()
  95. ORIGINAL_ENV_VALUES=()
  96. COMPOSE_ENV_OVERRIDES=()
  97. COMPOSE_REWRITE_SERVICE_SET=()
  98. COMPOSE_SERVICE_IMAGE_OVERRIDES=()
  99. REQUIRED_DB_TYPES=()
  100. DOCKER_SERVICE_SET=()
  101. EXISTING_MANAGED_ROOT_SERVICE_SET=()
  102. DOCKER_SERVICES=()
  103. SSL_CERT_SOURCE_PATH=""
  104. SSL_KEY_SOURCE_PATH=""
  105. LIGHTRAG_COMPOSE_SERVER_PORT_MAPPING=""
  106. NORMALIZED_SERVER_HOST_FOR_COMPOSE=""
  107. }
  108. validate_runtime_target() {
  109. local runtime_target="${1:-$DEFAULT_RUNTIME_TARGET}"
  110. case "$runtime_target" in
  111. host|compose)
  112. return 0
  113. ;;
  114. *)
  115. format_error \
  116. "Invalid LIGHTRAG_RUNTIME_TARGET: ${runtime_target}" \
  117. "Use 'host' or 'compose', or rerun the setup wizard to regenerate .env."
  118. return 1
  119. ;;
  120. esac
  121. }
  122. set_runtime_target() {
  123. local runtime_target="${1:-$DEFAULT_RUNTIME_TARGET}"
  124. if ! validate_runtime_target "$runtime_target"; then
  125. return 1
  126. fi
  127. ENV_VALUES["LIGHTRAG_RUNTIME_TARGET"]="$runtime_target"
  128. }
  129. clear_deprecated_vllm_dtype_state() {
  130. unset 'ENV_VALUES[VLLM_EMBED_DTYPE]'
  131. unset 'ENV_VALUES[VLLM_RERANK_DTYPE]'
  132. }
  133. normalize_vllm_rerank_binding_state() {
  134. if [[ "${ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}" == "vllm" ]]; then
  135. ENV_VALUES["RERANK_BINDING"]="cohere"
  136. fi
  137. }
  138. # Backfill sentinel hosts for bedrock/gemini bindings when LLM_BINDING_HOST or
  139. # EMBEDDING_BINDING_HOST is missing or empty. Flows that skip collect_llm_config /
  140. # collect_embedding_config (--server, --storage) would otherwise let the openai URL
  141. # hardcoded in env.example leak into the regenerated .env.
  142. normalize_provider_binding_hosts() {
  143. local llm_sentinel="" embedding_sentinel=""
  144. case "${ENV_VALUES[LLM_BINDING]:-}" in
  145. bedrock) llm_sentinel="DEFAULT_BEDROCK_ENDPOINT" ;;
  146. gemini) llm_sentinel="DEFAULT_GEMINI_ENDPOINT" ;;
  147. esac
  148. case "${ENV_VALUES[EMBEDDING_BINDING]:-}" in
  149. bedrock) embedding_sentinel="DEFAULT_BEDROCK_ENDPOINT" ;;
  150. gemini) embedding_sentinel="DEFAULT_GEMINI_ENDPOINT" ;;
  151. esac
  152. if [[ -n "$llm_sentinel" && -z "${ENV_VALUES[LLM_BINDING_HOST]:-}" ]]; then
  153. ENV_VALUES["LLM_BINDING_HOST"]="$llm_sentinel"
  154. fi
  155. if [[ -n "$embedding_sentinel" && -z "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" ]]; then
  156. ENV_VALUES["EMBEDDING_BINDING_HOST"]="$embedding_sentinel"
  157. fi
  158. }
  159. load_existing_env_if_present() {
  160. local env_file="${REPO_ROOT}/.env"
  161. if [[ -f "$env_file" ]]; then
  162. log_debug "Loading existing .env defaults from $env_file"
  163. load_env_file "$env_file"
  164. clear_deprecated_vllm_dtype_state
  165. normalize_vllm_rerank_binding_state
  166. normalize_provider_binding_hosts
  167. if [[ "${ENV_VALUES[SSL]:-false}" == "true" ]]; then
  168. SSL_CERT_SOURCE_PATH="${ENV_VALUES[SSL_CERTFILE]:-}"
  169. SSL_KEY_SOURCE_PATH="${ENV_VALUES[SSL_KEYFILE]:-}"
  170. fi
  171. snapshot_original_env_values
  172. fi
  173. }
  174. snapshot_original_env_values() {
  175. local key
  176. ORIGINAL_ENV_VALUES=()
  177. for key in "${!ENV_VALUES[@]}"; do
  178. ORIGINAL_ENV_VALUES["$key"]="${ENV_VALUES[$key]}"
  179. done
  180. }
  181. prepare_compose_output_from_existing() {
  182. local output_file="$1"
  183. local existing_file="$2"
  184. if [[ -z "$existing_file" || "$existing_file" == "$output_file" || -f "$output_file" ]]; then
  185. return 0
  186. fi
  187. if ! cp "$existing_file" "$output_file"; then
  188. format_error "Failed to prepare compose output at ${output_file}" \
  189. "Check file permissions and available disk space, then rerun setup."
  190. return 1
  191. fi
  192. log_success "Using ${existing_file} as merge input for ${output_file}"
  193. }
  194. log_debug() {
  195. if [[ "$DEBUG" == "true" ]]; then
  196. echo "${COLOR_YELLOW}[debug]${COLOR_RESET} $*"
  197. fi
  198. }
  199. log_info() {
  200. echo "${COLOR_BLUE}${COLOR_BOLD}$*${COLOR_RESET}"
  201. }
  202. log_warn() {
  203. echo "${COLOR_YELLOW}$*${COLOR_RESET}"
  204. }
  205. log_success() {
  206. echo "${COLOR_GREEN}$*${COLOR_RESET}"
  207. }
  208. log_step() {
  209. echo "${COLOR_BLUE}${COLOR_BOLD}$*${COLOR_RESET}"
  210. }
  211. normalize_loopback_uri_for_compose() {
  212. local uri="$1"
  213. if [[ "$uri" =~ ^([a-zA-Z][a-zA-Z0-9+.-]*://)([^/?#]+@)?(localhost|127\.0\.0\.1|0\.0\.0\.0)([/:?].*)?$ ]]; then
  214. printf '%s%shost.docker.internal%s' \
  215. "${BASH_REMATCH[1]}" \
  216. "${BASH_REMATCH[2]}" \
  217. "${BASH_REMATCH[4]}"
  218. return 0
  219. fi
  220. printf '%s' "$uri"
  221. }
  222. ensure_mongodb_direct_connection_suffix() {
  223. local suffix="${1:-}"
  224. local path query fragment filtered_query=""
  225. local part
  226. local -a query_parts=()
  227. if [[ "$suffix" == *"#"* ]]; then
  228. fragment="#${suffix#*#}"
  229. suffix="${suffix%%#*}"
  230. else
  231. fragment=""
  232. fi
  233. if [[ "$suffix" == *"?"* ]]; then
  234. path="${suffix%%\?*}"
  235. query="${suffix#*\?}"
  236. else
  237. path="$suffix"
  238. query=""
  239. fi
  240. if [[ -z "$path" ]]; then
  241. path="/"
  242. fi
  243. if [[ -n "$query" ]]; then
  244. IFS='&' read -r -a query_parts <<< "$query"
  245. for part in "${query_parts[@]}"; do
  246. if [[ -z "$part" || "$part" == directConnection=* ]]; then
  247. continue
  248. fi
  249. if [[ -n "$filtered_query" ]]; then
  250. filtered_query="${filtered_query}&${part}"
  251. else
  252. filtered_query="$part"
  253. fi
  254. done
  255. fi
  256. if [[ -n "$filtered_query" ]]; then
  257. printf '%s?%s&directConnection=true%s' "$path" "$filtered_query" "$fragment"
  258. else
  259. printf '%s?directConnection=true%s' "$path" "$fragment"
  260. fi
  261. }
  262. mongodb_uri_has_direct_connection_true() {
  263. local uri="$1"
  264. local direct_connection_pattern='[?&]directConnection=true([&#]|$)'
  265. [[ "$uri" =~ ^mongodb:// ]] && [[ "$uri" =~ $direct_connection_pattern ]]
  266. }
  267. is_wizard_managed_local_mongodb_uri() {
  268. local uri="$1"
  269. [[ "$uri" =~ ^mongodb://([^/?#]+@)?(mongodb|localhost|127\.0\.0\.1|0\.0\.0\.0):27017([/?#].*)?$ ]] && \
  270. mongodb_uri_has_direct_connection_true "$uri"
  271. }
  272. normalize_mongodb_uri_for_local_service() {
  273. local uri="$1"
  274. local suffix
  275. if [[ "$uri" =~ ^mongodb://([^/?#]+@)?(mongodb|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
  276. suffix="$(ensure_mongodb_direct_connection_suffix "${BASH_REMATCH[4]:-/}")"
  277. printf 'mongodb://localhost:27017%s' "$suffix"
  278. return 0
  279. fi
  280. printf '%s' "$uri"
  281. }
  282. normalize_neo4j_uri_for_local_service() {
  283. local uri="$1"
  284. if [[ "$uri" =~ ^([a-zA-Z][a-zA-Z0-9+.-]*://)([^/?#]+@)?(neo4j|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
  285. printf '%s%slocalhost:7687%s' \
  286. "${BASH_REMATCH[1]}" \
  287. "${BASH_REMATCH[2]}" \
  288. "${BASH_REMATCH[5]}"
  289. return 0
  290. fi
  291. printf '%s' "$uri"
  292. }
  293. normalize_redis_uri_for_local_service() {
  294. local uri="$1"
  295. if [[ "$uri" =~ ^rediss?://([^/?#]+@)?(redis|localhost|127\.0\.0\.1|0\.0\.0\.0)(:([0-9]+))?(/.*)?$ ]]; then
  296. printf 'redis://localhost:6379%s' "${BASH_REMATCH[5]}"
  297. return 0
  298. fi
  299. printf '%s' "$uri"
  300. }
  301. normalize_milvus_uri_for_local_service() {
  302. local uri="$1"
  303. if [[ "$uri" =~ ^(https?://)([^/?#]+@)?(milvus|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
  304. printf '%slocalhost:19530%s' \
  305. "${BASH_REMATCH[1]}" \
  306. "${BASH_REMATCH[5]}"
  307. return 0
  308. fi
  309. printf '%s' "$uri"
  310. }
  311. normalize_qdrant_uri_for_local_service() {
  312. local uri="$1"
  313. if [[ "$uri" =~ ^(https?://)([^/?#]+@)?(qdrant|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
  314. printf '%slocalhost:6333%s' \
  315. "${BASH_REMATCH[1]}" \
  316. "${BASH_REMATCH[5]}"
  317. return 0
  318. fi
  319. printf '%s' "$uri"
  320. }
  321. normalize_memgraph_uri_for_local_service() {
  322. local uri="$1"
  323. if [[ "$uri" =~ ^(bolt://)([^/?#]+@)?(memgraph|localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?([/?#].*)?$ ]]; then
  324. printf 'bolt://localhost:7687%s' "${BASH_REMATCH[5]}"
  325. return 0
  326. fi
  327. printf '%s' "$uri"
  328. }
  329. normalize_loopback_host_for_compose() {
  330. local host="$1"
  331. if [[ "$host" == "localhost" || "$host" == "127.0.0.1" || "$host" == "0.0.0.0" ]]; then
  332. printf 'host.docker.internal'
  333. return 0
  334. fi
  335. printf '%s' "$host"
  336. }
  337. normalize_opensearch_hosts_for_compose() {
  338. local hosts="$1"
  339. local entry=""
  340. local trimmed=""
  341. local normalized_entry=""
  342. local -a raw_entries=()
  343. local -a normalized_entries=()
  344. IFS=',' read -r -a raw_entries <<< "$hosts"
  345. for entry in "${raw_entries[@]}"; do
  346. trimmed="${entry#"${entry%%[![:space:]]*}"}"
  347. trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
  348. # OPENSEARCH_HOSTS is intentionally limited to bare host[:port] entries.
  349. # TLS is configured separately via OPENSEARCH_USE_SSL, so scheme-bearing
  350. # URLs are rejected during validation rather than normalized here.
  351. normalized_entry="$trimmed"
  352. if [[ "$trimmed" =~ ^(localhost|127\.0\.0\.1|0\.0\.0\.0)(:[0-9]+)?$ ]]; then
  353. normalized_entry="host.docker.internal${BASH_REMATCH[2]}"
  354. fi
  355. normalized_entries+=("$normalized_entry")
  356. done
  357. (
  358. IFS=','
  359. printf '%s' "${normalized_entries[*]}"
  360. )
  361. }
  362. env_value_is_true() {
  363. local value="${1:-}"
  364. case "${value,,}" in
  365. true|1|yes)
  366. return 0
  367. ;;
  368. *)
  369. return 1
  370. ;;
  371. esac
  372. }
  373. normalize_server_host_for_compose() {
  374. # Keep the published bind address/port configurable through compose-time
  375. # variable expansion, while forcing the container itself to listen on the
  376. # internal service defaults.
  377. LIGHTRAG_COMPOSE_SERVER_PORT_MAPPING='${HOST:-0.0.0.0}:${PORT:-9621}:9621'
  378. NORMALIZED_SERVER_HOST_FOR_COMPOSE="0.0.0.0"
  379. }
  380. host_cuda_available() {
  381. command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi >/dev/null 2>&1
  382. }
  383. resolve_local_device_default() {
  384. local configured_device="${1:-}"
  385. if [[ "$configured_device" == "cpu" || "$configured_device" == "cuda" ]]; then
  386. printf '%s' "$configured_device"
  387. return 0
  388. fi
  389. if host_cuda_available; then
  390. printf 'cuda'
  391. else
  392. printf 'cpu'
  393. fi
  394. }
  395. default_loopback_url() {
  396. local port="$1"
  397. local path="${2:-}"
  398. printf 'http://localhost:%s%s' "$port" "$path"
  399. }
  400. uri_points_to_host() {
  401. local uri="$1"
  402. shift
  403. local host=""
  404. local allowed_host
  405. if [[ "$uri" =~ ^[a-zA-Z][a-zA-Z0-9+.-]*://([^/?#]+@)?(\[[^]]+\]|[^/:?#]+) ]]; then
  406. host="${BASH_REMATCH[2]}"
  407. for allowed_host in "$@"; do
  408. if [[ "$host" == "$allowed_host" ]]; then
  409. return 0
  410. fi
  411. done
  412. fi
  413. return 1
  414. }
  415. prefer_local_service_uri() {
  416. local current_uri="$1"
  417. local default_uri="$2"
  418. shift 2
  419. if [[ -z "$current_uri" ]]; then
  420. printf '%s' "$default_uri"
  421. return 0
  422. fi
  423. if uri_points_to_host "$current_uri" "$@"; then
  424. printf '%s' "$current_uri"
  425. return 0
  426. fi
  427. printf '%s' "$default_uri"
  428. }
  429. set_compose_override() {
  430. local key="$1"
  431. local value="${2:-}"
  432. if [[ -n "$value" ]]; then
  433. COMPOSE_ENV_OVERRIDES["$key"]="$value"
  434. else
  435. unset "COMPOSE_ENV_OVERRIDES[$key]"
  436. fi
  437. }
  438. set_managed_service_compose_overrides() {
  439. local root_service="$1"
  440. case "$root_service" in
  441. postgres)
  442. if [[ -z "${COMPOSE_ENV_OVERRIDES[POSTGRES_HOST]+set}" ]]; then
  443. set_compose_override "POSTGRES_HOST" "postgres"
  444. fi
  445. # The bundled postgres compose service always listens on 5432 internally.
  446. if [[ -z "${COMPOSE_ENV_OVERRIDES[POSTGRES_PORT]+set}" ]]; then
  447. set_compose_override "POSTGRES_PORT" "5432"
  448. fi
  449. ;;
  450. neo4j)
  451. if [[ -z "${COMPOSE_ENV_OVERRIDES[NEO4J_URI]+set}" ]]; then
  452. set_compose_override "NEO4J_URI" "neo4j://neo4j:7687"
  453. fi
  454. ;;
  455. mongodb)
  456. if [[ -z "${COMPOSE_ENV_OVERRIDES[MONGO_URI]+set}" ]]; then
  457. set_compose_override "MONGO_URI" "mongodb://mongodb:27017/?directConnection=true"
  458. fi
  459. ;;
  460. redis)
  461. if [[ -z "${COMPOSE_ENV_OVERRIDES[REDIS_URI]+set}" ]]; then
  462. set_compose_override "REDIS_URI" "redis://redis:6379"
  463. fi
  464. ;;
  465. milvus)
  466. if [[ -z "${COMPOSE_ENV_OVERRIDES[MILVUS_URI]+set}" ]]; then
  467. set_compose_override "MILVUS_URI" "http://milvus:19530"
  468. fi
  469. ;;
  470. qdrant)
  471. if [[ -z "${COMPOSE_ENV_OVERRIDES[QDRANT_URL]+set}" ]]; then
  472. set_compose_override "QDRANT_URL" "http://qdrant:6333"
  473. fi
  474. ;;
  475. memgraph)
  476. if [[ -z "${COMPOSE_ENV_OVERRIDES[MEMGRAPH_URI]+set}" ]]; then
  477. set_compose_override "MEMGRAPH_URI" "bolt://memgraph:7687"
  478. fi
  479. ;;
  480. opensearch)
  481. if [[ -z "${COMPOSE_ENV_OVERRIDES[OPENSEARCH_HOSTS]+set}" ]]; then
  482. set_compose_override "OPENSEARCH_HOSTS" "opensearch:9200"
  483. fi
  484. ;;
  485. esac
  486. }
  487. prepare_compose_runtime_overrides() {
  488. local normalized_value
  489. local key
  490. local root_service
  491. # EMBEDDING_BINDING_HOST: when vllm-embed is part of this compose, the LightRAG
  492. # container must reach it by Docker service name, not by a loopback address.
  493. # This applies even when the wizard did not visit the embedding step (e.g.
  494. # env_server_flow), because vllm-embed is detected and added to DOCKER_SERVICE_SET
  495. # before prepare_compose_env_overrides is called.
  496. if [[ -z "${COMPOSE_ENV_OVERRIDES[EMBEDDING_BINDING_HOST]+set}" ]]; then
  497. if [[ -n "${DOCKER_SERVICE_SET[vllm-embed]+set}" ]]; then
  498. set_compose_override "EMBEDDING_BINDING_HOST" \
  499. "http://vllm-embed:${ENV_VALUES[VLLM_EMBED_PORT]:-8001}/v1"
  500. elif [[ -n "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" ]]; then
  501. normalized_value="$(normalize_loopback_uri_for_compose "${ENV_VALUES[EMBEDDING_BINDING_HOST]}")"
  502. if [[ "$normalized_value" != "${ENV_VALUES[EMBEDDING_BINDING_HOST]}" ]]; then
  503. set_compose_override "EMBEDDING_BINDING_HOST" "$normalized_value"
  504. fi
  505. fi
  506. fi
  507. # RERANK_BINDING_HOST: same pattern for vllm-rerank.
  508. if [[ -z "${COMPOSE_ENV_OVERRIDES[RERANK_BINDING_HOST]+set}" ]]; then
  509. if [[ -n "${DOCKER_SERVICE_SET[vllm-rerank]+set}" ]]; then
  510. set_compose_override "RERANK_BINDING_HOST" \
  511. "http://vllm-rerank:${ENV_VALUES[VLLM_RERANK_PORT]:-8000}/rerank"
  512. elif [[ -n "${ENV_VALUES[RERANK_BINDING_HOST]:-}" ]]; then
  513. normalized_value="$(normalize_loopback_uri_for_compose "${ENV_VALUES[RERANK_BINDING_HOST]}")"
  514. if [[ "$normalized_value" != "${ENV_VALUES[RERANK_BINDING_HOST]}" ]]; then
  515. set_compose_override "RERANK_BINDING_HOST" "$normalized_value"
  516. fi
  517. fi
  518. fi
  519. for root_service in postgres neo4j mongodb redis milvus qdrant memgraph opensearch; do
  520. if [[ -n "${DOCKER_SERVICE_SET[$root_service]+set}" ]]; then
  521. set_managed_service_compose_overrides "$root_service"
  522. fi
  523. done
  524. for key in \
  525. "LLM_BINDING_HOST" \
  526. "REDIS_URI" \
  527. "MONGO_URI" \
  528. "NEO4J_URI" \
  529. "MILVUS_URI" \
  530. "QDRANT_URL" \
  531. "MEMGRAPH_URI"; do
  532. if [[ -n "${COMPOSE_ENV_OVERRIDES[$key]+set}" ]]; then
  533. continue
  534. fi
  535. if [[ -n "${ENV_VALUES[$key]:-}" ]]; then
  536. normalized_value="$(normalize_loopback_uri_for_compose "${ENV_VALUES[$key]}")"
  537. if [[ "$normalized_value" != "${ENV_VALUES[$key]}" ]]; then
  538. set_compose_override "$key" "$normalized_value"
  539. fi
  540. fi
  541. done
  542. for key in "OPENSEARCH_HOSTS"; do
  543. if [[ -n "${COMPOSE_ENV_OVERRIDES[$key]+set}" ]]; then
  544. continue
  545. fi
  546. if [[ -n "${ENV_VALUES[$key]:-}" ]]; then
  547. normalized_value="$(normalize_opensearch_hosts_for_compose "${ENV_VALUES[$key]}")"
  548. if [[ "$normalized_value" != "${ENV_VALUES[$key]}" ]]; then
  549. set_compose_override "$key" "$normalized_value"
  550. fi
  551. fi
  552. done
  553. for key in "POSTGRES_HOST"; do
  554. if [[ -n "${COMPOSE_ENV_OVERRIDES[$key]+set}" ]]; then
  555. continue
  556. fi
  557. if [[ -n "${ENV_VALUES[$key]:-}" ]]; then
  558. normalized_value="$(normalize_loopback_host_for_compose "${ENV_VALUES[$key]}")"
  559. if [[ "$normalized_value" != "${ENV_VALUES[$key]}" ]]; then
  560. set_compose_override "$key" "$normalized_value"
  561. fi
  562. fi
  563. done
  564. normalize_server_host_for_compose "${ENV_VALUES[HOST]:-0.0.0.0}"
  565. normalized_value="$NORMALIZED_SERVER_HOST_FOR_COMPOSE"
  566. set_compose_override "HOST" "$normalized_value"
  567. set_compose_override "PORT" "9621"
  568. }
  569. prepare_compose_ssl_overrides() {
  570. local cert_name=""
  571. local key_name=""
  572. if [[ -n "$SSL_CERT_SOURCE_PATH" ]]; then
  573. cert_name="$(resolve_staged_ssl_basename "cert" "$SSL_CERT_SOURCE_PATH" "$SSL_KEY_SOURCE_PATH")"
  574. set_compose_override "SSL_CERTFILE" "/app/data/certs/${cert_name}"
  575. fi
  576. if [[ -n "$SSL_KEY_SOURCE_PATH" ]]; then
  577. key_name="$(resolve_staged_ssl_basename "key" "$SSL_KEY_SOURCE_PATH" "$SSL_CERT_SOURCE_PATH")"
  578. set_compose_override "SSL_KEYFILE" "/app/data/certs/${key_name}"
  579. fi
  580. }
  581. prepare_compose_data_path_overrides() {
  582. # Compose mounts always bind the data directories into these container paths.
  583. # Force lightrag to use them so values from the mounted .env cannot redirect
  584. # storage into a different location.
  585. set_compose_override "WORKING_DIR" "$COMPOSE_LIGHTRAG_WORKING_DIR"
  586. set_compose_override "INPUT_DIR" "$COMPOSE_LIGHTRAG_INPUT_DIR"
  587. set_compose_override "PROMPT_DIR" "$COMPOSE_LIGHTRAG_PROMPT_DIR"
  588. }
  589. prepare_compose_env_overrides() {
  590. prepare_compose_data_path_overrides
  591. prepare_compose_runtime_overrides
  592. prepare_compose_ssl_overrides
  593. }
  594. add_docker_service() {
  595. local service="$1"
  596. if [[ -z "${DOCKER_SERVICE_SET[$service]+set}" ]]; then
  597. DOCKER_SERVICE_SET["$service"]=1
  598. DOCKER_SERVICES+=("$service")
  599. fi
  600. }
  601. restore_storage_docker_services_from_env() {
  602. local db_type
  603. local marker_key=""
  604. local service_name=""
  605. local db_types=("postgresql" "neo4j" "mongodb" "redis" "milvus" "qdrant" "memgraph" "opensearch")
  606. for db_type in "${db_types[@]}"; do
  607. marker_key="$(storage_deployment_marker_key "$db_type")"
  608. if [[ -n "$marker_key" && "${ENV_VALUES[$marker_key]:-}" == "docker" ]]; then
  609. service_name="$(storage_service_name_for_db_type "$db_type")"
  610. if [[ -n "$service_name" ]]; then
  611. add_docker_service "$service_name"
  612. fi
  613. fi
  614. done
  615. }
  616. restore_vllm_docker_services_from_env() {
  617. if [[ "${ENV_VALUES[LIGHTRAG_SETUP_EMBEDDING_PROVIDER]:-}" == "vllm" ]]; then
  618. add_docker_service "vllm-embed"
  619. fi
  620. if [[ "${ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}" == "vllm" ]]; then
  621. add_docker_service "vllm-rerank"
  622. fi
  623. }
  624. compose_has_non_wizard_services() {
  625. local compose_file="$1"
  626. local service_name=""
  627. if [[ -z "$compose_file" || ! -f "$compose_file" ]]; then
  628. return 1
  629. fi
  630. while IFS= read -r service_name; do
  631. if [[ -z "$(_managed_service_root_name "$service_name")" ]]; then
  632. return 0
  633. fi
  634. done < <(detect_compose_services "$compose_file")
  635. return 1
  636. }
  637. resolve_compose_output_action() {
  638. local existing_compose="$1"
  639. local -n action_ref="$2"
  640. local -n runtime_target_ref="$3"
  641. local -n host_hint_ref="$4"
  642. local current_target="${ENV_VALUES[LIGHTRAG_RUNTIME_TARGET]:-$DEFAULT_RUNTIME_TARGET}"
  643. action_ref="write_env_only"
  644. runtime_target_ref="$DEFAULT_RUNTIME_TARGET"
  645. host_hint_ref="no"
  646. if ((${#DOCKER_SERVICES[@]} > 0)); then
  647. action_ref="rewrite_compose"
  648. runtime_target_ref="compose"
  649. return 0
  650. fi
  651. if compose_has_non_wizard_services "$existing_compose"; then
  652. action_ref="rewrite_compose"
  653. runtime_target_ref="compose"
  654. return 0
  655. fi
  656. if [[ -n "$existing_compose" ]]; then
  657. if confirm_default_no "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?"; then
  658. action_ref="delete_compose_and_switch_host"
  659. runtime_target_ref="host"
  660. host_hint_ref="yes"
  661. else
  662. action_ref="rewrite_compose"
  663. runtime_target_ref="compose"
  664. fi
  665. return 0
  666. fi
  667. if [[ "$current_target" == "compose" ]]; then
  668. if confirm_default_yes "Run LightRAG Server via Docker?"; then
  669. action_ref="rewrite_compose"
  670. runtime_target_ref="compose"
  671. else
  672. host_hint_ref="yes"
  673. fi
  674. else
  675. if confirm_default_no "Run LightRAG Server via Docker?"; then
  676. action_ref="rewrite_compose"
  677. runtime_target_ref="compose"
  678. else
  679. host_hint_ref="yes"
  680. fi
  681. fi
  682. }
  683. mark_compose_service_for_rewrite() {
  684. local service="$1"
  685. local root_service=""
  686. root_service="$(_managed_service_root_name "$service")"
  687. if [[ -n "$root_service" ]]; then
  688. COMPOSE_REWRITE_SERVICE_SET["$root_service"]=1
  689. fi
  690. }
  691. record_existing_managed_root_services() {
  692. local compose_file="$1"
  693. local service_name
  694. local root_service
  695. EXISTING_MANAGED_ROOT_SERVICE_SET=()
  696. if [[ -z "$compose_file" || ! -f "$compose_file" ]]; then
  697. return 0
  698. fi
  699. while IFS= read -r service_name; do
  700. root_service="$(_managed_service_root_name "$service_name")"
  701. if [[ -n "$root_service" ]]; then
  702. EXISTING_MANAGED_ROOT_SERVICE_SET["$root_service"]=1
  703. fi
  704. done < <(detect_managed_root_services "$compose_file")
  705. }
  706. collect_preserved_storage_service_images() {
  707. local compose_file="${1:-}"
  708. local service_name=""
  709. local image_value=""
  710. COMPOSE_SERVICE_IMAGE_OVERRIDES=()
  711. if [[ "$FORCE_REWRITE_COMPOSE" == "yes" || -z "$compose_file" || ! -f "$compose_file" ]]; then
  712. return 0
  713. fi
  714. # Only postgres and neo4j are wizard-managed Docker storage services that users
  715. # commonly pin to custom registry images. If new storage backends are added as
  716. # wizard-managed Docker services, extend this list accordingly.
  717. for service_name in postgres neo4j; do
  718. if [[ -z "${COMPOSE_REWRITE_SERVICE_SET[$service_name]+set}" ]] || \
  719. [[ -z "${DOCKER_SERVICE_SET[$service_name]+set}" ]] || \
  720. ! existing_managed_root_service_present "$service_name"; then
  721. continue
  722. fi
  723. image_value="$(read_service_image_value "$compose_file" "$service_name" || true)"
  724. if [[ -n "$image_value" ]]; then
  725. COMPOSE_SERVICE_IMAGE_OVERRIDES["$service_name"]="$image_value"
  726. fi
  727. done
  728. }
  729. backup_existing_compose_if_generating() {
  730. local generate_compose="${1:-no}"
  731. local existing_compose="${2:-}"
  732. local compose_backup_path=""
  733. if [[ "$generate_compose" != "yes" ]]; then
  734. return 0
  735. fi
  736. compose_backup_path="$(backup_compose_file "$existing_compose")" || return 1
  737. if [[ -n "$compose_backup_path" ]]; then
  738. log_success "Backed up existing compose file to $compose_backup_path"
  739. fi
  740. }
  741. backup_existing_compose_for_action() {
  742. local compose_action="${1:-write_env_only}"
  743. local existing_compose="${2:-}"
  744. local compose_backup_path=""
  745. case "$compose_action" in
  746. rewrite_compose|delete_compose_and_switch_host)
  747. ;;
  748. *)
  749. return 0
  750. ;;
  751. esac
  752. compose_backup_path="$(backup_compose_file "$existing_compose")" || return 1
  753. if [[ -n "$compose_backup_path" ]]; then
  754. log_success "Backed up existing compose file to $compose_backup_path"
  755. fi
  756. }
  757. remove_existing_compose_file() {
  758. local compose_file="${1:-}"
  759. if [[ -z "$compose_file" || ! -f "$compose_file" ]]; then
  760. return 0
  761. fi
  762. if ! rm "$compose_file"; then
  763. format_error "Failed to remove ${compose_file}" \
  764. "Check file permissions, then remove the compose file manually or rerun setup."
  765. return 1
  766. fi
  767. log_success "Removed ${compose_file}"
  768. }
  769. existing_managed_root_service_present() {
  770. local root_service="$1"
  771. [[ -n "${EXISTING_MANAGED_ROOT_SERVICE_SET[$root_service]+set}" ]]
  772. }
  773. compose_service_block_contains_literal() {
  774. local compose_file="$1"
  775. local service_name="$2"
  776. local literal="$3"
  777. if [[ -z "$compose_file" || ! -f "$compose_file" ]]; then
  778. return 1
  779. fi
  780. awk -v header=" ${service_name}:" -v literal="$literal" '
  781. $0 == header { in_service = 1; next }
  782. in_service && $0 ~ /^ [^[:space:]]/ { exit found ? 0 : 1 }
  783. in_service && index($0, literal) { found = 1; exit 0 }
  784. END { exit found ? 0 : 1 }
  785. ' "$compose_file"
  786. }
  787. mongodb_service_requires_atlas_local_rewrite() {
  788. local compose_file="${1:-}"
  789. if [[ -z "$compose_file" ]]; then
  790. return 1
  791. fi
  792. if [[ "${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" != "MongoVectorDBStorage" ]]; then
  793. return 1
  794. fi
  795. if [[ "${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}" != "docker" ]]; then
  796. return 1
  797. fi
  798. if [[ -z "${DOCKER_SERVICE_SET[mongodb]+set}" ]] || \
  799. ! existing_managed_root_service_present "mongodb"; then
  800. return 1
  801. fi
  802. if ! compose_service_block_contains_literal "$compose_file" "mongodb" "image: mongodb/mongodb-atlas-local:"; then
  803. return 0
  804. fi
  805. if ! compose_service_block_contains_literal "$compose_file" "mongodb" "mongo_config_data:/data/configdb"; then
  806. return 0
  807. fi
  808. if ! compose_service_block_contains_literal "$compose_file" "mongodb" "mongo_mongot_data:/data/mongot"; then
  809. return 0
  810. fi
  811. return 1
  812. }
  813. configure_mongodb_compose_migration_rewrite() {
  814. local existing_compose="${1:-}"
  815. if [[ "$FORCE_REWRITE_COMPOSE" == "yes" ]]; then
  816. return 0
  817. fi
  818. if mongodb_service_requires_atlas_local_rewrite "$existing_compose"; then
  819. mark_compose_service_for_rewrite "mongodb"
  820. fi
  821. }
  822. env_value_changed_from_original() {
  823. local key="$1"
  824. local missing_marker="__LIGHTRAG_MISSING__"
  825. local current_value="${ENV_VALUES[$key]-$missing_marker}"
  826. local original_value="${ORIGINAL_ENV_VALUES[$key]-$missing_marker}"
  827. [[ "$current_value" != "$original_value" ]]
  828. }
  829. any_env_value_changed_from_original() {
  830. local key
  831. for key in "$@"; do
  832. if env_value_changed_from_original "$key"; then
  833. return 0
  834. fi
  835. done
  836. return 1
  837. }
  838. compose_template_variant_for_service() {
  839. local service="$1"
  840. local snapshot="${2:-current}"
  841. local device=""
  842. case "$service" in
  843. milvus)
  844. if [[ "$snapshot" == "original" ]]; then
  845. device="${ORIGINAL_ENV_VALUES[MILVUS_DEVICE]:-cpu}"
  846. else
  847. device="${ENV_VALUES[MILVUS_DEVICE]:-cpu}"
  848. fi
  849. ;;
  850. qdrant)
  851. if [[ "$snapshot" == "original" ]]; then
  852. device="${ORIGINAL_ENV_VALUES[QDRANT_DEVICE]:-cpu}"
  853. else
  854. device="${ENV_VALUES[QDRANT_DEVICE]:-cpu}"
  855. fi
  856. ;;
  857. vllm-embed)
  858. if [[ "$snapshot" == "original" ]]; then
  859. device="${ORIGINAL_ENV_VALUES[VLLM_EMBED_DEVICE]:-cpu}"
  860. else
  861. device="${ENV_VALUES[VLLM_EMBED_DEVICE]:-cpu}"
  862. fi
  863. ;;
  864. vllm-rerank)
  865. if [[ "$snapshot" == "original" ]]; then
  866. device="${ORIGINAL_ENV_VALUES[VLLM_RERANK_DEVICE]:-cpu}"
  867. else
  868. device="${ENV_VALUES[VLLM_RERANK_DEVICE]:-cpu}"
  869. fi
  870. ;;
  871. *)
  872. printf 'default'
  873. return 0
  874. ;;
  875. esac
  876. if [[ "$device" == "cuda" ]]; then
  877. printf 'gpu'
  878. else
  879. printf 'cpu'
  880. fi
  881. }
  882. configure_base_compose_rewrites() {
  883. if [[ "$FORCE_REWRITE_COMPOSE" == "yes" ]]; then
  884. return 0
  885. fi
  886. if existing_managed_root_service_present "vllm-embed" && \
  887. [[ -n "${DOCKER_SERVICE_SET[vllm-embed]+set}" ]] && \
  888. [[ "$(compose_template_variant_for_service "vllm-embed" "current")" != \
  889. "$(compose_template_variant_for_service "vllm-embed" "original")" ]]; then
  890. mark_compose_service_for_rewrite "vllm-embed"
  891. fi
  892. if existing_managed_root_service_present "vllm-rerank" && \
  893. [[ -n "${DOCKER_SERVICE_SET[vllm-rerank]+set}" ]] && \
  894. [[ "$(compose_template_variant_for_service "vllm-rerank" "current")" != \
  895. "$(compose_template_variant_for_service "vllm-rerank" "original")" ]]; then
  896. mark_compose_service_for_rewrite "vllm-rerank"
  897. fi
  898. }
  899. configure_storage_compose_rewrites() {
  900. if [[ "$FORCE_REWRITE_COMPOSE" == "yes" ]]; then
  901. return 0
  902. fi
  903. if existing_managed_root_service_present "postgres" && \
  904. [[ -n "${DOCKER_SERVICE_SET[postgres]+set}" ]] && \
  905. any_env_value_changed_from_original "POSTGRES_USER" "POSTGRES_PASSWORD" "POSTGRES_DATABASE"; then
  906. mark_compose_service_for_rewrite "postgres"
  907. fi
  908. if existing_managed_root_service_present "neo4j" && \
  909. [[ -n "${DOCKER_SERVICE_SET[neo4j]+set}" ]] && \
  910. any_env_value_changed_from_original "NEO4J_DATABASE"; then
  911. mark_compose_service_for_rewrite "neo4j"
  912. fi
  913. if existing_managed_root_service_present "milvus" && \
  914. [[ -n "${DOCKER_SERVICE_SET[milvus]+set}" ]] && \
  915. [[ "$(compose_template_variant_for_service "milvus" "current")" != \
  916. "$(compose_template_variant_for_service "milvus" "original")" ]]; then
  917. mark_compose_service_for_rewrite "milvus"
  918. fi
  919. if existing_managed_root_service_present "qdrant" && \
  920. [[ -n "${DOCKER_SERVICE_SET[qdrant]+set}" ]] && \
  921. [[ "$(compose_template_variant_for_service "qdrant" "current")" != \
  922. "$(compose_template_variant_for_service "qdrant" "original")" ]]; then
  923. mark_compose_service_for_rewrite "qdrant"
  924. fi
  925. }
  926. select_storage_backends() {
  927. local deployment_type="$1"
  928. local kv_default="JsonKVStorage"
  929. local vector_default="NanoVectorDBStorage"
  930. local graph_default="NetworkXStorage"
  931. local doc_default="JsonDocStatusStorage"
  932. local kv_storage vector_storage graph_storage doc_storage
  933. if [[ "$deployment_type" == "production" ]]; then
  934. kv_default="PGKVStorage"
  935. vector_default="MilvusVectorDBStorage"
  936. graph_default="Neo4JStorage"
  937. doc_default="PGDocStatusStorage"
  938. fi
  939. kv_default="${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-$kv_default}"
  940. vector_default="${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-$vector_default}"
  941. graph_default="${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-$graph_default}"
  942. doc_default="${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-$doc_default}"
  943. while true; do
  944. kv_storage="$(prompt_choice "KV storage" "$kv_default" "${KV_STORAGE_OPTIONS[@]}")"
  945. vector_storage="$(prompt_choice "Vector storage" "$vector_default" "${VECTOR_STORAGE_OPTIONS[@]}")"
  946. graph_storage="$(prompt_choice "Graph storage" "$graph_default" "${GRAPH_STORAGE_OPTIONS[@]}")"
  947. doc_storage="$(prompt_choice "Doc status storage" "$doc_default" "${DOC_STATUS_STORAGE_OPTIONS[@]}")"
  948. if check_storage_compatibility "$kv_storage" "$vector_storage" "$graph_storage" "$doc_storage"; then
  949. break
  950. fi
  951. if confirm_default_no "Proceed with these storage selections anyway?"; then
  952. break
  953. fi
  954. done
  955. ENV_VALUES["LIGHTRAG_KV_STORAGE"]="$kv_storage"
  956. ENV_VALUES["LIGHTRAG_VECTOR_STORAGE"]="$vector_storage"
  957. ENV_VALUES["LIGHTRAG_GRAPH_STORAGE"]="$graph_storage"
  958. ENV_VALUES["LIGHTRAG_DOC_STATUS_STORAGE"]="$doc_storage"
  959. for storage in "$kv_storage" "$vector_storage" "$graph_storage" "$doc_storage"; do
  960. if [[ -n "${STORAGE_DB_TYPES[$storage]:-}" ]]; then
  961. REQUIRED_DB_TYPES["${STORAGE_DB_TYPES[$storage]}"]=1
  962. fi
  963. done
  964. }
  965. initialize_default_storage_backends() {
  966. # env-base does not prompt for storage, but its generated .env must remain
  967. # self-consistent for first-run users who have not run env-storage yet.
  968. ENV_VALUES["LIGHTRAG_KV_STORAGE"]="${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-JsonKVStorage}"
  969. ENV_VALUES["LIGHTRAG_VECTOR_STORAGE"]="${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-NanoVectorDBStorage}"
  970. ENV_VALUES["LIGHTRAG_GRAPH_STORAGE"]="${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-NetworkXStorage}"
  971. ENV_VALUES["LIGHTRAG_DOC_STATUS_STORAGE"]="${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-JsonDocStatusStorage}"
  972. }
  973. storage_service_name_for_db_type() {
  974. local db_type="$1"
  975. case "$db_type" in
  976. postgresql)
  977. printf 'postgres'
  978. ;;
  979. neo4j|mongodb|redis|milvus|qdrant|memgraph|opensearch)
  980. printf '%s' "$db_type"
  981. ;;
  982. *)
  983. printf ''
  984. ;;
  985. esac
  986. }
  987. storage_deployment_marker_key() {
  988. local db_type="$1"
  989. case "$db_type" in
  990. postgresql)
  991. printf 'LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT'
  992. ;;
  993. neo4j)
  994. printf 'LIGHTRAG_SETUP_NEO4J_DEPLOYMENT'
  995. ;;
  996. mongodb)
  997. printf 'LIGHTRAG_SETUP_MONGODB_DEPLOYMENT'
  998. ;;
  999. redis)
  1000. printf 'LIGHTRAG_SETUP_REDIS_DEPLOYMENT'
  1001. ;;
  1002. milvus)
  1003. printf 'LIGHTRAG_SETUP_MILVUS_DEPLOYMENT'
  1004. ;;
  1005. qdrant)
  1006. printf 'LIGHTRAG_SETUP_QDRANT_DEPLOYMENT'
  1007. ;;
  1008. memgraph)
  1009. printf 'LIGHTRAG_SETUP_MEMGRAPH_DEPLOYMENT'
  1010. ;;
  1011. opensearch)
  1012. printf 'LIGHTRAG_SETUP_OPENSEARCH_DEPLOYMENT'
  1013. ;;
  1014. *)
  1015. printf ''
  1016. ;;
  1017. esac
  1018. }
  1019. storage_default_docker_for_db_type() {
  1020. local db_type="$1"
  1021. local marker_key
  1022. marker_key="$(storage_deployment_marker_key "$db_type")"
  1023. if [[ -n "$marker_key" && "${ENV_VALUES[$marker_key]:-}" == "docker" ]]; then
  1024. printf 'yes'
  1025. else
  1026. printf 'no'
  1027. fi
  1028. }
  1029. persist_storage_deployment_choice() {
  1030. local db_type="$1"
  1031. local deployment_mode="${2:-no}"
  1032. local marker_key
  1033. marker_key="$(storage_deployment_marker_key "$db_type")"
  1034. if [[ -z "$marker_key" ]]; then
  1035. return 0
  1036. fi
  1037. case "$deployment_mode" in
  1038. yes|docker)
  1039. ENV_VALUES["$marker_key"]="docker"
  1040. ;;
  1041. no|'')
  1042. unset "ENV_VALUES[$marker_key]"
  1043. ;;
  1044. *)
  1045. ENV_VALUES["$marker_key"]="$deployment_mode"
  1046. ;;
  1047. esac
  1048. }
  1049. clear_unused_storage_deployment_markers() {
  1050. local db_type
  1051. for db_type in postgresql neo4j mongodb redis milvus qdrant memgraph opensearch; do
  1052. if [[ -z "${REQUIRED_DB_TYPES[$db_type]+set}" ]]; then
  1053. persist_storage_deployment_choice "$db_type" "no"
  1054. fi
  1055. done
  1056. }
  1057. collect_database_config() {
  1058. local db_type="$1"
  1059. local default_docker="${2:-no}"
  1060. local service_name=""
  1061. local deployment_mode="no"
  1062. # Storage collector rule for this wizard:
  1063. # - Existing ENV_VALUES loaded from .env are user-owned configuration.
  1064. # - Collectors should use those values as defaults and preserve them when they
  1065. # are already set, even for Docker-managed services.
  1066. # - A collector may normalize the stored form, or write a hard default only
  1067. # when the key is absent.
  1068. # Keep future storage collectors aligned with this behavior so rerunning the
  1069. # wizard does not silently erase explicit .env overrides.
  1070. case "$db_type" in
  1071. postgresql)
  1072. collect_postgres_config "$default_docker"
  1073. ;;
  1074. neo4j)
  1075. collect_neo4j_config "$default_docker"
  1076. ;;
  1077. mongodb)
  1078. collect_mongodb_config "$default_docker"
  1079. ;;
  1080. redis)
  1081. collect_redis_config "$default_docker"
  1082. ;;
  1083. milvus)
  1084. collect_milvus_config "$default_docker"
  1085. ;;
  1086. qdrant)
  1087. collect_qdrant_config "$default_docker"
  1088. ;;
  1089. memgraph)
  1090. collect_memgraph_config "$default_docker"
  1091. ;;
  1092. opensearch)
  1093. collect_opensearch_config "$default_docker"
  1094. ;;
  1095. *)
  1096. echo "Unknown database type: $db_type" >&2
  1097. return 1
  1098. ;;
  1099. esac
  1100. service_name="$(storage_service_name_for_db_type "$db_type")"
  1101. if [[ -n "$service_name" && -n "${DOCKER_SERVICE_SET[$service_name]+set}" ]]; then
  1102. deployment_mode="docker"
  1103. fi
  1104. persist_storage_deployment_choice "$db_type" "$deployment_mode"
  1105. }
  1106. collect_postgres_config() {
  1107. local default_docker="${1:-no}"
  1108. local use_docker="no"
  1109. local host port user password database
  1110. local existing_user="" existing_password="" existing_database=""
  1111. if [[ "$default_docker" == "yes" ]]; then
  1112. if confirm_default_yes "Run PostgreSQL locally via Docker?"; then
  1113. use_docker="yes"
  1114. fi
  1115. else
  1116. if confirm_default_no "Run PostgreSQL locally via Docker?"; then
  1117. use_docker="yes"
  1118. fi
  1119. fi
  1120. if [[ "$use_docker" == "yes" ]]; then
  1121. add_docker_service "postgres"
  1122. host="${ENV_VALUES[POSTGRES_HOST]:-localhost}"
  1123. if [[ "$host" != "localhost" && "$host" != "127.0.0.1" && "$host" != "0.0.0.0" && "$host" != "postgres" ]]; then
  1124. host="localhost"
  1125. elif [[ "$host" == "postgres" ]]; then
  1126. host="localhost"
  1127. fi
  1128. else
  1129. host="${ENV_VALUES[POSTGRES_HOST]:-localhost}"
  1130. fi
  1131. host="$(prompt_with_default "PostgreSQL host" "$host")"
  1132. if [[ "$use_docker" == "yes" ]]; then
  1133. port="5432"
  1134. set_compose_override "POSTGRES_HOST" "postgres"
  1135. set_compose_override "POSTGRES_PORT" "5432"
  1136. else
  1137. port="$(prompt_until_valid "PostgreSQL port" "${ENV_VALUES[POSTGRES_PORT]:-5432}" validate_port)"
  1138. set_compose_override "POSTGRES_HOST" ""
  1139. set_compose_override "POSTGRES_PORT" ""
  1140. fi
  1141. # The bundled postgres image creates its user/password/database from the
  1142. # POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB env vars on first start, so docker
  1143. # and host deployments share the same prompts and defaults (rag/rag/lightrag).
  1144. existing_user="${ORIGINAL_ENV_VALUES[POSTGRES_USER]-${ENV_VALUES[POSTGRES_USER]:-}}"
  1145. existing_password="${ORIGINAL_ENV_VALUES[POSTGRES_PASSWORD]-${ENV_VALUES[POSTGRES_PASSWORD]:-}}"
  1146. existing_database="${ORIGINAL_ENV_VALUES[POSTGRES_DATABASE]-${ENV_VALUES[POSTGRES_DATABASE]:-}}"
  1147. user="$(prompt_with_default "PostgreSQL user" "${existing_user:-rag}")"
  1148. password="$(prompt_secret_with_default "PostgreSQL password: " "${existing_password:-rag}")"
  1149. database="$(prompt_with_default "PostgreSQL database" "${existing_database:-lightrag}")"
  1150. ENV_VALUES["POSTGRES_HOST"]="$host"
  1151. ENV_VALUES["POSTGRES_PORT"]="$port"
  1152. ENV_VALUES["POSTGRES_USER"]="$user"
  1153. ENV_VALUES["POSTGRES_PASSWORD"]="$password"
  1154. ENV_VALUES["POSTGRES_DATABASE"]="$database"
  1155. }
  1156. collect_neo4j_config() {
  1157. local default_docker="${1:-no}"
  1158. local use_docker="no"
  1159. local uri username password database
  1160. local existing_username="" existing_password="" existing_database=""
  1161. if [[ "$default_docker" == "yes" ]]; then
  1162. if confirm_default_yes "Run Neo4j locally via Docker?"; then
  1163. use_docker="yes"
  1164. fi
  1165. else
  1166. if confirm_default_no "Run Neo4j locally via Docker?"; then
  1167. use_docker="yes"
  1168. fi
  1169. fi
  1170. if [[ "$use_docker" == "yes" ]]; then
  1171. add_docker_service "neo4j"
  1172. uri="$(prefer_local_service_uri "${ENV_VALUES[NEO4J_URI]:-}" "neo4j://localhost:7687" "neo4j" "localhost" "127.0.0.1" "0.0.0.0")"
  1173. else
  1174. uri="${ENV_VALUES[NEO4J_URI]:-neo4j://localhost:7687}"
  1175. fi
  1176. uri="$(prompt_until_valid "Neo4j URI" "$uri" validate_uri neo4j)"
  1177. if [[ "$use_docker" == "yes" ]]; then
  1178. uri="$(normalize_neo4j_uri_for_local_service "$uri")"
  1179. fi
  1180. existing_username="${ORIGINAL_ENV_VALUES[NEO4J_USERNAME]-${ENV_VALUES[NEO4J_USERNAME]:-}}"
  1181. existing_password="${ORIGINAL_ENV_VALUES[NEO4J_PASSWORD]-${ENV_VALUES[NEO4J_PASSWORD]:-}}"
  1182. existing_database="${ORIGINAL_ENV_VALUES[NEO4J_DATABASE]-${ENV_VALUES[NEO4J_DATABASE]:-}}"
  1183. if [[ "$use_docker" == "yes" ]]; then
  1184. username="$(prompt_until_valid "Neo4j username" "${existing_username:-neo4j}" validate_non_empty)"
  1185. password="$(prompt_secret_until_valid_with_default "Neo4j password: " "${existing_password:-neo4j_password}" validate_non_empty)"
  1186. if [[ -n "$existing_database" ]]; then
  1187. database="$(prompt_with_default "Neo4j database" "$existing_database")"
  1188. else
  1189. database="neo4j"
  1190. fi
  1191. else
  1192. username="$(prompt_with_default "Neo4j username" "${existing_username:-neo4j}")"
  1193. password="$(prompt_secret_with_default "Neo4j password: " "${existing_password:-neo4j_password}")"
  1194. database="$(prompt_with_default "Neo4j database" "${existing_database:-neo4j}")"
  1195. fi
  1196. ENV_VALUES["NEO4J_URI"]="$uri"
  1197. ENV_VALUES["NEO4J_USERNAME"]="$username"
  1198. ENV_VALUES["NEO4J_PASSWORD"]="$password"
  1199. ENV_VALUES["NEO4J_DATABASE"]="$database"
  1200. if [[ "$use_docker" == "yes" ]]; then
  1201. set_compose_override "NEO4J_URI" "neo4j://neo4j:7687"
  1202. else
  1203. set_compose_override "NEO4J_URI" ""
  1204. fi
  1205. }
  1206. collect_mongodb_config() {
  1207. local default_docker="${1:-no}"
  1208. local use_docker="no"
  1209. local uri database
  1210. local existing_database=""
  1211. local vector_search_required="no"
  1212. if [[ "${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" == "MongoVectorDBStorage" ]]; then
  1213. vector_search_required="yes"
  1214. fi
  1215. if [[ "$default_docker" == "yes" ]]; then
  1216. if confirm_default_yes "Run MongoDB locally via Docker?"; then
  1217. use_docker="yes"
  1218. fi
  1219. else
  1220. if confirm_default_no "Run MongoDB locally via Docker?"; then
  1221. use_docker="yes"
  1222. fi
  1223. fi
  1224. if [[ "$use_docker" == "yes" ]]; then
  1225. if [[ "$vector_search_required" == "yes" ]]; then
  1226. log_info "Docker MongoDB uses Atlas Local, so MongoVectorDBStorage can use Atlas Search / Vector Search locally."
  1227. fi
  1228. add_docker_service "mongodb"
  1229. uri="$(prefer_local_service_uri "${ENV_VALUES[MONGO_URI]:-}" "mongodb://localhost:27017/?directConnection=true" "mongodb" "localhost" "127.0.0.1" "0.0.0.0")"
  1230. elif [[ "$vector_search_required" == "yes" ]]; then
  1231. uri="${ENV_VALUES[MONGO_URI]:-}"
  1232. if [[ -z "$uri" ]] || is_wizard_managed_local_mongodb_uri "$uri"; then
  1233. uri="mongodb+srv://cluster.example.mongodb.net/"
  1234. fi
  1235. else
  1236. uri="${ENV_VALUES[MONGO_URI]:-mongodb://localhost:27017/}"
  1237. fi
  1238. if [[ "$vector_search_required" == "yes" ]]; then
  1239. uri="$(prompt_until_valid "MongoDB URI (must support Atlas Search / Vector Search)" "$uri" validate_uri mongodb)"
  1240. else
  1241. uri="$(prompt_until_valid "MongoDB URI" "$uri" validate_uri mongodb)"
  1242. fi
  1243. if [[ "$use_docker" == "yes" ]]; then
  1244. uri="$(normalize_mongodb_uri_for_local_service "$uri")"
  1245. fi
  1246. existing_database="${ORIGINAL_ENV_VALUES[MONGO_DATABASE]-${ENV_VALUES[MONGO_DATABASE]:-}}"
  1247. database="$(prompt_with_default "MongoDB database" "${existing_database:-LightRAG}")"
  1248. ENV_VALUES["MONGO_URI"]="$uri"
  1249. ENV_VALUES["MONGO_DATABASE"]="$database"
  1250. if [[ "$use_docker" == "yes" ]]; then
  1251. set_compose_override "MONGO_URI" "mongodb://mongodb:27017/?directConnection=true"
  1252. else
  1253. set_compose_override "MONGO_URI" ""
  1254. fi
  1255. }
  1256. collect_redis_config() {
  1257. local default_docker="${1:-no}"
  1258. local use_docker="no"
  1259. local uri
  1260. if [[ "$default_docker" == "yes" ]]; then
  1261. if confirm_default_yes "Run Redis locally via Docker?"; then
  1262. use_docker="yes"
  1263. fi
  1264. else
  1265. if confirm_default_no "Run Redis locally via Docker?"; then
  1266. use_docker="yes"
  1267. fi
  1268. fi
  1269. if [[ "$use_docker" == "yes" ]]; then
  1270. add_docker_service "redis"
  1271. uri="$(prefer_local_service_uri "${ENV_VALUES[REDIS_URI]:-}" "redis://localhost:6379" "redis" "localhost" "127.0.0.1" "0.0.0.0")"
  1272. else
  1273. uri="${ENV_VALUES[REDIS_URI]:-redis://localhost:6379}"
  1274. fi
  1275. uri="$(prompt_until_valid "Redis URI" "$uri" validate_uri redis)"
  1276. if [[ "$use_docker" == "yes" ]]; then
  1277. uri="$(normalize_redis_uri_for_local_service "$uri")"
  1278. fi
  1279. ENV_VALUES["REDIS_URI"]="$uri"
  1280. if [[ "$use_docker" == "yes" ]]; then
  1281. set_compose_override "REDIS_URI" "redis://redis:6379"
  1282. else
  1283. set_compose_override "REDIS_URI" ""
  1284. fi
  1285. }
  1286. collect_milvus_config() {
  1287. local default_docker="${1:-no}"
  1288. local use_docker="no"
  1289. local uri db_name milvus_device=""
  1290. local existing_db_name="" existing_device=""
  1291. if [[ "$default_docker" == "yes" ]]; then
  1292. if confirm_default_yes "Run Milvus locally via Docker?"; then
  1293. use_docker="yes"
  1294. fi
  1295. else
  1296. if confirm_default_no "Run Milvus locally via Docker?"; then
  1297. use_docker="yes"
  1298. fi
  1299. fi
  1300. if [[ "$use_docker" == "yes" ]]; then
  1301. add_docker_service "milvus"
  1302. uri="$(prefer_local_service_uri "${ENV_VALUES[MILVUS_URI]:-}" "http://localhost:19530" "milvus" "localhost" "127.0.0.1" "0.0.0.0")"
  1303. else
  1304. uri="${ENV_VALUES[MILVUS_URI]:-http://localhost:19530}"
  1305. fi
  1306. existing_db_name="${ORIGINAL_ENV_VALUES[MILVUS_DB_NAME]-${ENV_VALUES[MILVUS_DB_NAME]:-}}"
  1307. existing_device="${ORIGINAL_ENV_VALUES[MILVUS_DEVICE]-${ENV_VALUES[MILVUS_DEVICE]:-}}"
  1308. if [[ "$use_docker" == "yes" ]]; then
  1309. milvus_device="$(resolve_local_device_default "$existing_device")"
  1310. milvus_device="$(prompt_choice "Milvus device" "$milvus_device" "cpu" "cuda")"
  1311. if [[ "$milvus_device" == "cuda" ]] && ! host_cuda_available; then
  1312. log_warn "CUDA device selected for Milvus but no NVIDIA driver detected on host."
  1313. fi
  1314. uri="$(prompt_until_valid "Milvus URI" "$uri" validate_uri milvus)"
  1315. uri="$(normalize_milvus_uri_for_local_service "$uri")"
  1316. if [[ -z "${ENV_VALUES[MINIO_ACCESS_KEY_ID]:-}" ]]; then
  1317. ENV_VALUES["MINIO_ACCESS_KEY_ID"]="minioadmin"
  1318. fi
  1319. if [[ -z "${ENV_VALUES[MINIO_SECRET_ACCESS_KEY]:-}" ]]; then
  1320. ENV_VALUES["MINIO_SECRET_ACCESS_KEY"]="minioadmin"
  1321. fi
  1322. else
  1323. uri="$(prompt_until_valid "Milvus URI" "$uri" validate_uri milvus)"
  1324. fi
  1325. db_name="$(prompt_with_default "Milvus database name" "${existing_db_name:-lightrag}")"
  1326. ENV_VALUES["MILVUS_URI"]="$uri"
  1327. ENV_VALUES["MILVUS_DB_NAME"]="$db_name"
  1328. if [[ -n "$milvus_device" ]]; then
  1329. ENV_VALUES["MILVUS_DEVICE"]="$milvus_device"
  1330. fi
  1331. if [[ "$use_docker" == "yes" ]]; then
  1332. set_compose_override "MILVUS_URI" "http://milvus:19530"
  1333. else
  1334. set_compose_override "MILVUS_URI" ""
  1335. fi
  1336. }
  1337. collect_qdrant_config() {
  1338. local default_docker="${1:-no}"
  1339. local use_docker="no"
  1340. local url qdrant_device=""
  1341. local existing_device=""
  1342. if [[ "$default_docker" == "yes" ]]; then
  1343. if confirm_default_yes "Run Qdrant locally via Docker?"; then
  1344. use_docker="yes"
  1345. fi
  1346. else
  1347. if confirm_default_no "Run Qdrant locally via Docker?"; then
  1348. use_docker="yes"
  1349. fi
  1350. fi
  1351. if [[ "$use_docker" == "yes" ]]; then
  1352. add_docker_service "qdrant"
  1353. url="$(prefer_local_service_uri "${ENV_VALUES[QDRANT_URL]:-}" "http://localhost:6333" "qdrant" "localhost" "127.0.0.1" "0.0.0.0")"
  1354. else
  1355. url="${ENV_VALUES[QDRANT_URL]:-http://localhost:6333}"
  1356. fi
  1357. existing_device="${ORIGINAL_ENV_VALUES[QDRANT_DEVICE]-${ENV_VALUES[QDRANT_DEVICE]:-}}"
  1358. if [[ "$use_docker" == "yes" ]]; then
  1359. qdrant_device="$(resolve_local_device_default "$existing_device")"
  1360. qdrant_device="$(prompt_choice "Qdrant device" "$qdrant_device" "cpu" "cuda")"
  1361. if [[ "$qdrant_device" == "cuda" ]] && ! host_cuda_available; then
  1362. log_warn "CUDA device selected for Qdrant but no NVIDIA driver detected on host."
  1363. fi
  1364. url="$(prompt_until_valid "Qdrant URL" "$url" validate_uri qdrant)"
  1365. url="$(normalize_qdrant_uri_for_local_service "$url")"
  1366. else
  1367. url="$(prompt_until_valid "Qdrant URL" "$url" validate_uri qdrant)"
  1368. fi
  1369. ENV_VALUES["QDRANT_URL"]="$url"
  1370. if [[ -n "$qdrant_device" ]]; then
  1371. ENV_VALUES["QDRANT_DEVICE"]="$qdrant_device"
  1372. fi
  1373. if [[ "$use_docker" == "yes" ]]; then
  1374. set_compose_override "QDRANT_URL" "http://qdrant:6333"
  1375. else
  1376. set_compose_override "QDRANT_URL" ""
  1377. fi
  1378. }
  1379. collect_memgraph_config() {
  1380. local default_docker="${1:-no}"
  1381. local use_docker="no"
  1382. local uri
  1383. if [[ "$default_docker" == "yes" ]]; then
  1384. if confirm_default_yes "Run Memgraph locally via Docker?"; then
  1385. use_docker="yes"
  1386. fi
  1387. else
  1388. if confirm_default_no "Run Memgraph locally via Docker?"; then
  1389. use_docker="yes"
  1390. fi
  1391. fi
  1392. if [[ "$use_docker" == "yes" ]]; then
  1393. add_docker_service "memgraph"
  1394. uri="$(prefer_local_service_uri "${ENV_VALUES[MEMGRAPH_URI]:-}" "bolt://localhost:7687" "memgraph" "localhost" "127.0.0.1" "0.0.0.0")"
  1395. else
  1396. uri="${ENV_VALUES[MEMGRAPH_URI]:-bolt://localhost:7687}"
  1397. fi
  1398. uri="$(prompt_until_valid "Memgraph URI" "$uri" validate_uri memgraph)"
  1399. if [[ "$use_docker" == "yes" ]]; then
  1400. uri="$(normalize_memgraph_uri_for_local_service "$uri")"
  1401. fi
  1402. ENV_VALUES["MEMGRAPH_URI"]="$uri"
  1403. if [[ "$use_docker" == "yes" ]]; then
  1404. set_compose_override "MEMGRAPH_URI" "bolt://memgraph:7687"
  1405. else
  1406. set_compose_override "MEMGRAPH_URI" ""
  1407. fi
  1408. }
  1409. collect_opensearch_config() {
  1410. local default_docker="${1:-no}"
  1411. local use_docker="no"
  1412. local hosts user password
  1413. local existing_user="" existing_password=""
  1414. local existing_use_ssl="" existing_verify_certs=""
  1415. local existing_num_shards="" existing_num_replicas=""
  1416. local use_ssl="true"
  1417. local verify_certs="false"
  1418. local use_ssl_default="yes"
  1419. local verify_certs_default="no"
  1420. if [[ "$default_docker" == "yes" ]]; then
  1421. if confirm_default_yes "Run OpenSearch locally via Docker?"; then
  1422. use_docker="yes"
  1423. fi
  1424. else
  1425. if confirm_default_no "Run OpenSearch locally via Docker?"; then
  1426. use_docker="yes"
  1427. fi
  1428. fi
  1429. if [[ "$use_docker" == "yes" ]]; then
  1430. add_docker_service "opensearch"
  1431. hosts="$(prefer_local_service_uri "${ENV_VALUES[OPENSEARCH_HOSTS]:-}" "localhost:9200" "opensearch" "localhost" "127.0.0.1" "0.0.0.0")"
  1432. else
  1433. hosts="${ENV_VALUES[OPENSEARCH_HOSTS]:-localhost:9200}"
  1434. fi
  1435. existing_user="${ORIGINAL_ENV_VALUES[OPENSEARCH_USER]-${ENV_VALUES[OPENSEARCH_USER]:-}}"
  1436. existing_password="${ORIGINAL_ENV_VALUES[OPENSEARCH_PASSWORD]-${ENV_VALUES[OPENSEARCH_PASSWORD]:-}}"
  1437. existing_use_ssl="${ORIGINAL_ENV_VALUES[OPENSEARCH_USE_SSL]-${ENV_VALUES[OPENSEARCH_USE_SSL]:-}}"
  1438. existing_verify_certs="${ORIGINAL_ENV_VALUES[OPENSEARCH_VERIFY_CERTS]-${ENV_VALUES[OPENSEARCH_VERIFY_CERTS]:-}}"
  1439. existing_num_shards="${ORIGINAL_ENV_VALUES[OPENSEARCH_NUMBER_OF_SHARDS]-${ENV_VALUES[OPENSEARCH_NUMBER_OF_SHARDS]:-}}"
  1440. existing_num_replicas="${ORIGINAL_ENV_VALUES[OPENSEARCH_NUMBER_OF_REPLICAS]-${ENV_VALUES[OPENSEARCH_NUMBER_OF_REPLICAS]:-}}"
  1441. hosts="$(prompt_until_valid "OpenSearch hosts (host:port, comma-separated)" "$hosts" validate_opensearch_hosts_format)"
  1442. user="$(prompt_with_default "OpenSearch user" "${existing_user:-admin}")"
  1443. password="$(prompt_secret_until_valid_with_default "OpenSearch password: " "${existing_password:-LightRAG2026_!@}" validate_opensearch_password_strength)"
  1444. if [[ "$use_docker" == "yes" ]]; then
  1445. if [[ -n "$existing_use_ssl" ]]; then
  1446. env_value_is_true "$existing_use_ssl" && use_ssl="true" || use_ssl="false"
  1447. else
  1448. use_ssl="true"
  1449. fi
  1450. verify_certs="false"
  1451. else
  1452. if [[ -n "$existing_use_ssl" ]] && ! env_value_is_true "$existing_use_ssl"; then
  1453. use_ssl_default="no"
  1454. fi
  1455. if [[ "$use_ssl_default" == "yes" ]]; then
  1456. confirm_default_yes "Use SSL for OpenSearch?" && use_ssl="true" || use_ssl="false"
  1457. else
  1458. confirm_default_no "Use SSL for OpenSearch?" && use_ssl="true" || use_ssl="false"
  1459. fi
  1460. if [[ "$use_ssl" == "true" ]]; then
  1461. if [[ -n "$existing_verify_certs" ]] && env_value_is_true "$existing_verify_certs"; then
  1462. verify_certs_default="yes"
  1463. fi
  1464. if [[ "$verify_certs_default" == "yes" ]]; then
  1465. confirm_default_yes "Verify OpenSearch TLS certificates?" && verify_certs="true" || verify_certs="false"
  1466. else
  1467. confirm_default_no "Verify OpenSearch TLS certificates?" && verify_certs="true" || verify_certs="false"
  1468. fi
  1469. fi
  1470. fi
  1471. local num_shards num_replicas
  1472. if [[ "$use_docker" == "yes" ]]; then
  1473. num_shards="1"
  1474. num_replicas="0"
  1475. else
  1476. num_shards="$(prompt_until_valid "Number of index shards" "${existing_num_shards:-1}" validate_positive_integer)"
  1477. num_replicas="$(prompt_until_valid "Number of index replicas (use 2 for 3-AZ clusters)" "${existing_num_replicas:-0}" validate_non_negative_integer)"
  1478. fi
  1479. ENV_VALUES["OPENSEARCH_HOSTS"]="$hosts"
  1480. ENV_VALUES["OPENSEARCH_USER"]="$user"
  1481. ENV_VALUES["OPENSEARCH_PASSWORD"]="$password"
  1482. ENV_VALUES["OPENSEARCH_USE_SSL"]="$use_ssl"
  1483. ENV_VALUES["OPENSEARCH_VERIFY_CERTS"]="$verify_certs"
  1484. ENV_VALUES["OPENSEARCH_NUMBER_OF_SHARDS"]="$num_shards"
  1485. ENV_VALUES["OPENSEARCH_NUMBER_OF_REPLICAS"]="$num_replicas"
  1486. if [[ "$use_docker" == "yes" ]]; then
  1487. set_compose_override "OPENSEARCH_HOSTS" "opensearch:9200"
  1488. else
  1489. set_compose_override "OPENSEARCH_HOSTS" ""
  1490. fi
  1491. }
  1492. clear_bedrock_credentials() {
  1493. unset 'ENV_VALUES[AWS_ACCESS_KEY_ID]'
  1494. unset 'ENV_VALUES[AWS_SECRET_ACCESS_KEY]'
  1495. unset 'ENV_VALUES[AWS_SESSION_TOKEN]'
  1496. unset 'ENV_VALUES[AWS_REGION]'
  1497. }
  1498. collect_bedrock_credentials() {
  1499. local access_key secret_key session_token region
  1500. log_info "Bedrock ignores LLM_BINDING_API_KEY/EMBEDDING_BINDING_API_KEY; use SigV4 credentials or AWS_BEARER_TOKEN_BEDROCK."
  1501. if [[ -n "${ENV_VALUES[AWS_ACCESS_KEY_ID]:-}" && -n "${ENV_VALUES[AWS_SECRET_ACCESS_KEY]:-}" ]]; then
  1502. if confirm_default_yes "Reuse existing AWS Bedrock credentials?"; then
  1503. region="$(prompt_with_default "AWS region" "${ENV_VALUES[AWS_REGION]:-us-east-1}")"
  1504. ENV_VALUES["AWS_REGION"]="$region"
  1505. return 0
  1506. fi
  1507. fi
  1508. if confirm_default_no "Store explicit AWS Bedrock credentials in .env?"; then
  1509. access_key="$(prompt_required_secret "AWS access key ID: ")"
  1510. secret_key="$(prompt_required_secret "AWS secret access key: ")"
  1511. session_token="$(mask_sensitive_input "AWS session token (optional): ")"
  1512. region="$(prompt_with_default "AWS region" "${ENV_VALUES[AWS_REGION]:-us-east-1}")"
  1513. ENV_VALUES["AWS_ACCESS_KEY_ID"]="$access_key"
  1514. ENV_VALUES["AWS_SECRET_ACCESS_KEY"]="$secret_key"
  1515. ENV_VALUES["AWS_REGION"]="$region"
  1516. if [[ -n "$session_token" ]]; then
  1517. ENV_VALUES["AWS_SESSION_TOKEN"]="$session_token"
  1518. else
  1519. unset 'ENV_VALUES[AWS_SESSION_TOKEN]'
  1520. fi
  1521. return 0
  1522. fi
  1523. log_info "Using the ambient AWS credential chain (for example IAM roles, AWS profiles, or aws sso login)."
  1524. clear_bedrock_credentials
  1525. region="$(prompt_clearable_with_default "AWS region (optional)" "${ENV_VALUES[AWS_REGION]:-}")"
  1526. apply_clearable_env_value "AWS_REGION" "$region"
  1527. }
  1528. store_optional_env_value() {
  1529. local key="$1"
  1530. local value="${2:-}"
  1531. if [[ -n "$value" ]]; then
  1532. ENV_VALUES["$key"]="$value"
  1533. else
  1534. unset "ENV_VALUES[$key]"
  1535. fi
  1536. }
  1537. provider_default_or_existing() {
  1538. local selected_binding="$1"
  1539. local existing_binding="${2:-}"
  1540. local existing_value="${3:-}"
  1541. local default_value="${4:-}"
  1542. if [[ "$selected_binding" == "$existing_binding" && -n "$existing_value" ]]; then
  1543. printf '%s' "$existing_value"
  1544. return 0
  1545. fi
  1546. printf '%s' "$default_value"
  1547. }
  1548. default_llm_model_for_binding() {
  1549. local binding="$1"
  1550. case "$binding" in
  1551. openai|azure_openai)
  1552. printf 'gpt-5-mini'
  1553. ;;
  1554. ollama|lollms|openai-ollama)
  1555. printf 'mistral-nemo:latest'
  1556. ;;
  1557. gemini)
  1558. printf 'gemini-flash-latest'
  1559. ;;
  1560. bedrock)
  1561. printf 'anthropic.claude-3-5-sonnet-20241022-v2:0'
  1562. ;;
  1563. *)
  1564. printf 'gpt-5-mini'
  1565. ;;
  1566. esac
  1567. }
  1568. default_embedding_model_for_binding() {
  1569. local binding="$1"
  1570. case "$binding" in
  1571. openai|azure_openai)
  1572. printf 'text-embedding-3-large'
  1573. ;;
  1574. ollama)
  1575. printf 'bge-m3:latest'
  1576. ;;
  1577. jina)
  1578. printf 'jina-embeddings-v4'
  1579. ;;
  1580. gemini)
  1581. printf 'gemini-embedding-001'
  1582. ;;
  1583. bedrock)
  1584. printf 'amazon.titan-embed-text-v2:0'
  1585. ;;
  1586. lollms)
  1587. printf 'lollms_embedding_model'
  1588. ;;
  1589. *)
  1590. printf 'text-embedding-3-large'
  1591. ;;
  1592. esac
  1593. }
  1594. default_embedding_dim_for_binding() {
  1595. local binding="$1"
  1596. case "$binding" in
  1597. openai|azure_openai)
  1598. printf '3072'
  1599. ;;
  1600. ollama|bedrock|lollms)
  1601. printf '1024'
  1602. ;;
  1603. jina)
  1604. printf '2048'
  1605. ;;
  1606. gemini)
  1607. printf '1536'
  1608. ;;
  1609. *)
  1610. printf '3072'
  1611. ;;
  1612. esac
  1613. }
  1614. collect_llm_config() {
  1615. local options=("openai" "azure_openai" "ollama" "openai-ollama" "lollms" "gemini" "bedrock")
  1616. local current_binding="${ENV_VALUES[LLM_BINDING]:-openai}"
  1617. local binding model model_default host host_default api_key
  1618. binding="$(prompt_choice "LLM provider" "$current_binding" "${options[@]}")"
  1619. model_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_MODEL]:-}" "$(default_llm_model_for_binding "$binding")")"
  1620. model="$(prompt_with_default "LLM model" "$model_default")"
  1621. case "$binding" in
  1622. ollama)
  1623. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "$(default_loopback_url 11434)")"
  1624. host="$(prompt_with_default "Ollama host" "$host_default")"
  1625. api_key=""
  1626. ;;
  1627. openai-ollama)
  1628. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "$(default_loopback_url 11434 "/v1")")"
  1629. host="$(prompt_with_default "OpenAI-compatible Ollama endpoint" "$host_default")"
  1630. api_key="$(prompt_secret_until_valid_with_default "LLM API key: " "${ENV_VALUES[LLM_BINDING_API_KEY]:-}" validate_api_key openai)"
  1631. ;;
  1632. lollms)
  1633. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "http://localhost:9600")"
  1634. host="$(prompt_with_default "LoLLMs host" "$host_default")"
  1635. api_key=""
  1636. ;;
  1637. azure_openai)
  1638. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "https://example.openai.azure.com/")"
  1639. host="$(prompt_with_default "Azure OpenAI endpoint" "$host_default")"
  1640. api_key="$(prompt_secret_until_valid_with_default "Azure OpenAI API key: " "${ENV_VALUES[LLM_BINDING_API_KEY]:-}" validate_api_key azure_openai)"
  1641. ;;
  1642. gemini)
  1643. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "DEFAULT_GEMINI_ENDPOINT")"
  1644. host="$(prompt_with_default "Gemini endpoint" "$host_default")"
  1645. api_key="$(prompt_secret_until_valid_with_default "Gemini API key: " "${ENV_VALUES[LLM_BINDING_API_KEY]:-}" validate_api_key gemini)"
  1646. ;;
  1647. bedrock)
  1648. host="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "DEFAULT_BEDROCK_ENDPOINT")"
  1649. api_key=""
  1650. collect_bedrock_credentials
  1651. ;;
  1652. *)
  1653. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[LLM_BINDING_HOST]:-}" "https://api.openai.com/v1")"
  1654. host="$(prompt_with_default "LLM endpoint" "$host_default")"
  1655. api_key="$(prompt_secret_until_valid_with_default "LLM API key: " "${ENV_VALUES[LLM_BINDING_API_KEY]:-}" validate_api_key "$binding")"
  1656. ;;
  1657. esac
  1658. ENV_VALUES["LLM_BINDING"]="$binding"
  1659. ENV_VALUES["LLM_MODEL"]="$model"
  1660. ENV_VALUES["LLM_BINDING_HOST"]="$host"
  1661. store_optional_env_value "LLM_BINDING_API_KEY" "$api_key"
  1662. # Role-specific LLM models — default to the base LLM_MODEL when unset in .env.
  1663. local keyword_default query_default keyword_model query_model
  1664. keyword_default="${ENV_VALUES[KEYWORD_LLM_MODEL]:-$model}"
  1665. query_default="${ENV_VALUES[QUERY_LLM_MODEL]:-$model}"
  1666. keyword_model="$(prompt_with_default "Keyword LLM model" "$keyword_default")"
  1667. query_model="$(prompt_with_default "Query LLM model" "$query_default")"
  1668. ENV_VALUES["KEYWORD_LLM_MODEL"]="$keyword_model"
  1669. ENV_VALUES["QUERY_LLM_MODEL"]="$query_model"
  1670. }
  1671. collect_embedding_config() {
  1672. local options=("openai" "azure_openai" "ollama" "jina" "lollms" "gemini" "bedrock")
  1673. local current_binding="${ENV_VALUES[EMBEDDING_BINDING]:-openai}"
  1674. local binding model model_default host host_default api_key dim dim_default
  1675. if [[ "${ENV_VALUES[LLM_BINDING]:-}" == "openai-ollama" ]]; then
  1676. binding="ollama"
  1677. if [[ "$current_binding" != "ollama" ]]; then
  1678. log_info "openai-ollama uses Ollama embeddings. Forcing embedding provider to ollama."
  1679. fi
  1680. else
  1681. binding="$(prompt_choice "Embedding provider" "$current_binding" "${options[@]}")"
  1682. fi
  1683. model_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_MODEL]:-}" "$(default_embedding_model_for_binding "$binding")")"
  1684. dim_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_DIM]:-}" "$(default_embedding_dim_for_binding "$binding")")"
  1685. model="$(prompt_with_default "Embedding model" "$model_default")"
  1686. dim="$(prompt_with_default "Embedding dimension" "$dim_default")"
  1687. local llm_host_fallback="" llm_api_key_default=""
  1688. if [[ "$binding" == "${ENV_VALUES[LLM_BINDING]:-}" ]]; then
  1689. llm_host_fallback="${ENV_VALUES[LLM_BINDING_HOST]:-}"
  1690. llm_api_key_default="${ENV_VALUES[LLM_BINDING_API_KEY]:-}"
  1691. fi
  1692. case "$binding" in
  1693. ollama)
  1694. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-$(default_loopback_url 11434)}")"
  1695. host="$(prompt_with_default "Ollama embedding host" "$host_default")"
  1696. api_key=""
  1697. ;;
  1698. lollms)
  1699. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-http://localhost:9600}")"
  1700. host="$(prompt_with_default "LoLLMs embedding host" "$host_default")"
  1701. api_key=""
  1702. ;;
  1703. azure_openai)
  1704. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-https://example.openai.azure.com/}")"
  1705. host="$(prompt_with_default "Azure OpenAI endpoint" "$host_default")"
  1706. 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)"
  1707. ;;
  1708. gemini)
  1709. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-DEFAULT_GEMINI_ENDPOINT}")"
  1710. host="$(prompt_with_default "Gemini endpoint" "$host_default")"
  1711. 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)"
  1712. ;;
  1713. bedrock)
  1714. host="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-DEFAULT_BEDROCK_ENDPOINT}")"
  1715. api_key=""
  1716. collect_bedrock_credentials
  1717. ;;
  1718. jina)
  1719. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-https://api.jina.ai/v1/embeddings}")"
  1720. host="$(prompt_with_default "Jina endpoint" "$host_default")"
  1721. 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)"
  1722. ;;
  1723. *)
  1724. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}" "${llm_host_fallback:-https://api.openai.com/v1}")"
  1725. host="$(prompt_with_default "Embedding endpoint" "$host_default")"
  1726. 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")"
  1727. ;;
  1728. esac
  1729. ENV_VALUES["EMBEDDING_BINDING"]="$binding"
  1730. ENV_VALUES["EMBEDDING_MODEL"]="$model"
  1731. ENV_VALUES["EMBEDDING_DIM"]="$dim"
  1732. ENV_VALUES["EMBEDDING_BINDING_HOST"]="$host"
  1733. store_optional_env_value "EMBEDDING_BINDING_API_KEY" "$api_key"
  1734. # User chose a remote provider — clear the Docker deployment marker.
  1735. unset 'ENV_VALUES[LIGHTRAG_SETUP_EMBEDDING_PROVIDER]'
  1736. }
  1737. collect_rerank_config() {
  1738. # Pass "yes" to skip the "Enable reranking?" prompt (caller already asked it).
  1739. # The optional second argument is retained for caller compatibility.
  1740. local skip_enable_check="${1:-no}"
  1741. local _docker_choice_override="${2:-prompt}"
  1742. local options=("cohere" "jina" "aliyun")
  1743. local current_binding="${ENV_VALUES[RERANK_BINDING]:-cohere}"
  1744. local binding model host api_key
  1745. local default_model="" default_host="" model_default="" host_default=""
  1746. local previous_provider="${ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}"
  1747. unset 'ENV_VALUES[VLLM_RERANK_DTYPE]'
  1748. if [[ "$skip_enable_check" != "yes" ]]; then
  1749. local rerank_was_enabled="no"
  1750. if [[ -n "${ENV_VALUES[RERANK_BINDING]:-}" && "${ENV_VALUES[RERANK_BINDING]}" != "null" ]]; then
  1751. rerank_was_enabled="yes"
  1752. fi
  1753. local rerank_enabled="no"
  1754. if [[ "$rerank_was_enabled" == "yes" ]]; then
  1755. confirm_default_yes "Enable reranking?" && rerank_enabled="yes"
  1756. else
  1757. confirm_default_no "Enable reranking?" && rerank_enabled="yes"
  1758. fi
  1759. if [[ "$rerank_enabled" != "yes" ]]; then
  1760. ENV_VALUES["RERANK_BINDING"]="null"
  1761. unset 'ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]'
  1762. return
  1763. fi
  1764. fi
  1765. if [[ "$current_binding" == "null" ]]; then
  1766. current_binding="cohere"
  1767. fi
  1768. binding="$(prompt_choice "Rerank provider" "$current_binding" "${options[@]}")"
  1769. case "$binding" in
  1770. cohere)
  1771. default_model="rerank-v3.5"
  1772. default_host="https://api.cohere.com/v2/rerank"
  1773. ;;
  1774. jina)
  1775. default_model="jina-reranker-v2-base-multilingual"
  1776. default_host="https://api.jina.ai/v1/rerank"
  1777. ;;
  1778. aliyun)
  1779. default_model="gte-rerank-v2"
  1780. default_host="https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank"
  1781. ;;
  1782. *)
  1783. default_model=""
  1784. default_host=""
  1785. ;;
  1786. esac
  1787. if [[ "$previous_provider" == "vllm" ]]; then
  1788. # Switching away from local vLLM should replace stale localhost/model values.
  1789. model_default="$default_model"
  1790. host_default="$default_host"
  1791. else
  1792. model_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[RERANK_MODEL]:-}" "$default_model")"
  1793. host_default="$(provider_default_or_existing "$binding" "$current_binding" "${ENV_VALUES[RERANK_BINDING_HOST]:-}" "$default_host")"
  1794. fi
  1795. model="$(prompt_with_default "Rerank model" "$model_default")"
  1796. host="$(prompt_with_default "Rerank endpoint" "$host_default")"
  1797. api_key="$(prompt_secret_until_valid_with_default "Rerank API key: " "${ENV_VALUES[RERANK_BINDING_API_KEY]:-}" validate_api_key "$binding")"
  1798. ENV_VALUES["RERANK_BINDING"]="$binding"
  1799. # Only env_base_flow's Docker branch should keep the local vLLM setup marker.
  1800. unset 'ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]'
  1801. if [[ -n "$model" ]]; then
  1802. ENV_VALUES["RERANK_MODEL"]="$model"
  1803. fi
  1804. if [[ -n "$host" ]]; then
  1805. ENV_VALUES["RERANK_BINDING_HOST"]="$host"
  1806. fi
  1807. store_optional_env_value "RERANK_BINDING_API_KEY" "$api_key"
  1808. }
  1809. collect_server_config() {
  1810. local host port title description summary_language
  1811. host="$(prompt_with_default "Server host" "${ENV_VALUES[HOST]:-0.0.0.0}")"
  1812. port="$(prompt_until_valid "Server port" "${ENV_VALUES[PORT]:-9621}" validate_port)"
  1813. title="$(prompt_with_default "WebUI title" "${ENV_VALUES[WEBUI_TITLE]:-My Graph KB}")"
  1814. description="$(prompt_with_default "WebUI description" "${ENV_VALUES[WEBUI_DESCRIPTION]:-Simple and Fast Graph Based RAG System}")"
  1815. summary_language="$(prompt_with_default "Summary language" "${ENV_VALUES[SUMMARY_LANGUAGE]:-English}")"
  1816. ENV_VALUES["HOST"]="$host"
  1817. ENV_VALUES["PORT"]="$port"
  1818. ENV_VALUES["WEBUI_TITLE"]="$title"
  1819. ENV_VALUES["WEBUI_DESCRIPTION"]="$description"
  1820. ENV_VALUES["SUMMARY_LANGUAGE"]="$summary_language"
  1821. }
  1822. collect_ssl_config() {
  1823. local cert key
  1824. local ssl_enabled_default="no"
  1825. case "${ENV_VALUES[SSL]:-}" in
  1826. true|TRUE|True|1|yes|YES|Yes|y|Y|on|ON|On|t|T)
  1827. ssl_enabled_default="yes"
  1828. ;;
  1829. esac
  1830. if [[ "$ssl_enabled_default" == "yes" ]]; then
  1831. if ! confirm_default_yes "Enable SSL/TLS for the API server?"; then
  1832. unset 'ENV_VALUES[SSL]'
  1833. unset 'ENV_VALUES[SSL_CERTFILE]'
  1834. unset 'ENV_VALUES[SSL_KEYFILE]'
  1835. SSL_CERT_SOURCE_PATH=""
  1836. SSL_KEY_SOURCE_PATH=""
  1837. return
  1838. fi
  1839. else
  1840. if ! confirm_default_no "Enable SSL/TLS for the API server?"; then
  1841. unset 'ENV_VALUES[SSL]'
  1842. unset 'ENV_VALUES[SSL_CERTFILE]'
  1843. unset 'ENV_VALUES[SSL_KEYFILE]'
  1844. SSL_CERT_SOURCE_PATH=""
  1845. SSL_KEY_SOURCE_PATH=""
  1846. return
  1847. fi
  1848. fi
  1849. cert="$(prompt_until_valid "SSL certificate file" "${ENV_VALUES[SSL_CERTFILE]:-}" validate_existing_file)"
  1850. key="$(prompt_until_valid "SSL key file" "${ENV_VALUES[SSL_KEYFILE]:-}" validate_existing_file)"
  1851. ENV_VALUES["SSL"]="true"
  1852. ENV_VALUES["SSL_CERTFILE"]="$cert"
  1853. ENV_VALUES["SSL_KEYFILE"]="$key"
  1854. SSL_CERT_SOURCE_PATH="$cert"
  1855. SSL_KEY_SOURCE_PATH="$key"
  1856. }
  1857. collect_security_config() {
  1858. local required="${1:-no}"
  1859. local default_yes="${2:-no}"
  1860. local auth_accounts token_secret token_expire api_key whitelist
  1861. local confirm_result=1
  1862. local whitelist_default=""
  1863. local whitelist_is_set="no"
  1864. if [[ -n "${ENV_VALUES[WHITELIST_PATHS]+set}" ]]; then
  1865. whitelist_default="${ENV_VALUES[WHITELIST_PATHS]}"
  1866. whitelist_is_set="yes"
  1867. fi
  1868. if [[ "$default_yes" == "yes" ]]; then
  1869. if confirm_default_yes "Configure authentication and API key settings?"; then
  1870. confirm_result=0
  1871. fi
  1872. else
  1873. if confirm_default_no "Configure authentication and API key settings?"; then
  1874. confirm_result=0
  1875. fi
  1876. fi
  1877. if ((confirm_result != 0)); then
  1878. if [[ "$required" == "yes" ]]; then
  1879. echo "Warning: production deployments should configure AUTH_ACCOUNTS; API keys are optional on top." >&2
  1880. fi
  1881. return
  1882. fi
  1883. echo "Press Enter to keep an existing value. Type 'clear' to remove it." >&2
  1884. if [[ "$whitelist_is_set" == "no" ]]; then
  1885. whitelist_default="/health"
  1886. elif [[ "$required" == "yes" && "$whitelist_default" == "/health,/api/*" ]]; then
  1887. whitelist_default="/health"
  1888. fi
  1889. auth_accounts="$(prompt_clearable_with_default "Auth accounts (user:pass,comma-separated)" "${ENV_VALUES[AUTH_ACCOUNTS]:-}")"
  1890. token_secret="$(prompt_clearable_secret_with_default "JWT token secret: " "${ENV_VALUES[TOKEN_SECRET]:-}")"
  1891. token_expire="$(prompt_clearable_with_default "Token expire hours" "${ENV_VALUES[TOKEN_EXPIRE_HOURS]:-48}")"
  1892. api_key="$(prompt_clearable_secret_with_default "LightRAG API key: " "${ENV_VALUES[LIGHTRAG_API_KEY]:-}")"
  1893. whitelist="$(prompt_clearable_with_default "Whitelist paths (comma-separated)" "$whitelist_default")"
  1894. if [[ "$whitelist_is_set" == "yes" && -z "$whitelist_default" && -z "$whitelist" ]]; then
  1895. whitelist="$CLEAR_INPUT_SENTINEL"
  1896. fi
  1897. if [[ -z "$token_secret" ]]; then
  1898. token_secret="$(openssl rand -hex 32 2>/dev/null || LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 64)"
  1899. log_info "Generated TOKEN_SECRET and saved to .env."
  1900. fi
  1901. apply_clearable_env_value "AUTH_ACCOUNTS" "$auth_accounts"
  1902. apply_clearable_env_value "TOKEN_SECRET" "$token_secret"
  1903. apply_clearable_env_value "TOKEN_EXPIRE_HOURS" "$token_expire"
  1904. apply_clearable_env_value "LIGHTRAG_API_KEY" "$api_key"
  1905. apply_clearable_env_value "WHITELIST_PATHS" "$whitelist" "empty"
  1906. }
  1907. apply_clearable_env_value() {
  1908. local key="$1"
  1909. local value="${2:-}"
  1910. local clear_mode="${3:-unset}"
  1911. if [[ "$clear_mode" == "empty" && "$value" == "$CLEAR_INPUT_SENTINEL" ]]; then
  1912. ENV_VALUES["$key"]=""
  1913. return 0
  1914. fi
  1915. if [[ "$value" == "$CLEAR_INPUT_SENTINEL" || -z "$value" ]]; then
  1916. unset "ENV_VALUES[$key]"
  1917. return 0
  1918. fi
  1919. ENV_VALUES["$key"]="$value"
  1920. }
  1921. collect_observability_config() {
  1922. local secret_key public_key host
  1923. if ! confirm_default_no "Enable Langfuse observability?"; then
  1924. unset 'ENV_VALUES[LANGFUSE_ENABLE_TRACE]'
  1925. unset 'ENV_VALUES[LANGFUSE_SECRET_KEY]'
  1926. unset 'ENV_VALUES[LANGFUSE_PUBLIC_KEY]'
  1927. unset 'ENV_VALUES[LANGFUSE_HOST]'
  1928. return
  1929. fi
  1930. secret_key="$(prompt_secret_until_valid_with_default "Langfuse secret key: " "${ENV_VALUES[LANGFUSE_SECRET_KEY]:-}" validate_api_key langfuse)"
  1931. public_key="$(prompt_secret_until_valid_with_default "Langfuse public key: " "${ENV_VALUES[LANGFUSE_PUBLIC_KEY]:-}" validate_api_key langfuse)"
  1932. host="$(prompt_with_default "Langfuse host" "${ENV_VALUES[LANGFUSE_HOST]:-https://cloud.langfuse.com}")"
  1933. if [[ -n "$secret_key" ]]; then
  1934. ENV_VALUES["LANGFUSE_SECRET_KEY"]="$secret_key"
  1935. fi
  1936. if [[ -n "$public_key" ]]; then
  1937. ENV_VALUES["LANGFUSE_PUBLIC_KEY"]="$public_key"
  1938. fi
  1939. if [[ -n "$host" ]]; then
  1940. ENV_VALUES["LANGFUSE_HOST"]="$host"
  1941. fi
  1942. ENV_VALUES["LANGFUSE_ENABLE_TRACE"]="true"
  1943. }
  1944. show_summary() {
  1945. local key
  1946. local value
  1947. echo
  1948. log_info "Configuration summary:"
  1949. if ((${#ENV_VALUES[@]} > 0)); then
  1950. local -a sorted_keys
  1951. mapfile -t sorted_keys < <(printf '%s\n' "${!ENV_VALUES[@]}" | sort)
  1952. for key in "${sorted_keys[@]}"; do
  1953. value="${ENV_VALUES[$key]}"
  1954. if is_sensitive_env_key "$key"; then
  1955. value="***"
  1956. fi
  1957. printf ' %s=%s\n' "$key" "$value"
  1958. done
  1959. fi
  1960. if ((${#DOCKER_SERVICES[@]} > 0)); then
  1961. echo
  1962. log_info "Docker services to include:"
  1963. for service in "${DOCKER_SERVICES[@]}"; do
  1964. echo " - $service"
  1965. done
  1966. echo " Compose file: docker-compose.final.yml"
  1967. fi
  1968. }
  1969. # Preserve already-staged SSL mounts when regenerating compose output. The
  1970. # setup wizards treat .env as the configuration for the current target runtime,
  1971. # not as a single file guaranteed to work for both host and Docker Compose at
  1972. # the same time. A later wizard run may rewrite .env again when the operator
  1973. # switches between host and compose workflows.
  1974. prepare_inherited_ssl_assets_for_compose() {
  1975. local existing_compose="${1:-}"
  1976. local staged_cert_source="$SSL_CERT_SOURCE_PATH"
  1977. local staged_key_source="$SSL_KEY_SOURCE_PATH"
  1978. local preserved_cert_path=""
  1979. local preserved_key_path=""
  1980. if [[ -n "$SSL_CERT_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_CERT_SOURCE_PATH"; then
  1981. if [[ -n "$existing_compose" ]]; then
  1982. preserved_cert_path="$(read_service_environment_value "$existing_compose" "lightrag" "SSL_CERTFILE" || true)"
  1983. fi
  1984. if [[ "$preserved_cert_path" == /app/data/certs/* ]]; then
  1985. log_warn "SSL_CERTFILE source is missing; preserving the existing compose SSL certificate mount."
  1986. staged_cert_source=""
  1987. ENV_VALUES["SSL_CERTFILE"]="$preserved_cert_path"
  1988. set_compose_override "SSL_CERTFILE" "$preserved_cert_path"
  1989. else
  1990. format_error "Invalid SSL_CERTFILE" \
  1991. "Set it to an existing certificate file, disable SSL, or rerun the wizard to choose a new certificate."
  1992. return 1
  1993. fi
  1994. fi
  1995. if [[ -n "$SSL_KEY_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_KEY_SOURCE_PATH"; then
  1996. if [[ -n "$existing_compose" ]]; then
  1997. preserved_key_path="$(read_service_environment_value "$existing_compose" "lightrag" "SSL_KEYFILE" || true)"
  1998. fi
  1999. if [[ "$preserved_key_path" == /app/data/certs/* ]]; then
  2000. log_warn "SSL_KEYFILE source is missing; preserving the existing compose SSL key mount."
  2001. staged_key_source=""
  2002. ENV_VALUES["SSL_KEYFILE"]="$preserved_key_path"
  2003. set_compose_override "SSL_KEYFILE" "$preserved_key_path"
  2004. else
  2005. format_error "Invalid SSL_KEYFILE" \
  2006. "Set it to an existing private key file, disable SSL, or rerun the wizard to choose a new key."
  2007. return 1
  2008. fi
  2009. fi
  2010. SSL_CERT_SOURCE_PATH="$staged_cert_source"
  2011. SSL_KEY_SOURCE_PATH="$staged_key_source"
  2012. if [[ -n "$SSL_CERT_SOURCE_PATH" || -n "$SSL_KEY_SOURCE_PATH" ]]; then
  2013. stage_ssl_assets "$SSL_CERT_SOURCE_PATH" "$SSL_KEY_SOURCE_PATH"
  2014. fi
  2015. }
  2016. prepare_managed_service_assets_for_compose() {
  2017. local existing_compose="${1:-}"
  2018. if ! prepare_inherited_ssl_assets_for_compose "$existing_compose"; then
  2019. return 1
  2020. fi
  2021. if [[ -n "${DOCKER_SERVICE_SET[redis]:-}" ]]; then
  2022. stage_redis_config_asset || return 1
  2023. fi
  2024. }
  2025. env_base_flow() {
  2026. local vllm_embed_api_key=""
  2027. local vllm_rerank_api_key=""
  2028. local existing_vllm_embed_model=""
  2029. local existing_embedding_dim=""
  2030. local existing_vllm_embed_port=""
  2031. local existing_vllm_embed_host=""
  2032. local existing_vllm_embed_device=""
  2033. local previous_embedding_provider=""
  2034. local existing_vllm_rerank_model=""
  2035. local existing_vllm_rerank_port=""
  2036. local existing_vllm_rerank_host=""
  2037. local existing_vllm_rerank_device=""
  2038. local previous_rerank_provider=""
  2039. if host_cuda_available; then
  2040. log_info "GPU detected: NVIDIA GPU found. New local vLLM services default to CUDA (GPU image + float16)."
  2041. else
  2042. log_info "GPU detection: no NVIDIA GPU found. New local vLLM services default to CPU image + float32."
  2043. fi
  2044. reset_state
  2045. load_existing_env_if_present
  2046. initialize_default_storage_backends
  2047. log_info "Base configuration wizard (LLM / Embedding / Reranker)"
  2048. echo "This wizard only modifies LLM, embedding, and reranker settings."
  2049. echo "Storage, server, and security settings are preserved."
  2050. echo ""
  2051. log_step "LLM configuration"
  2052. collect_llm_config
  2053. echo ""
  2054. # ── Embedding ────────────────────────────────────────────────────────────────
  2055. log_step "Embedding configuration"
  2056. local docker_embed_default="no"
  2057. previous_embedding_provider="${ENV_VALUES[LIGHTRAG_SETUP_EMBEDDING_PROVIDER]:-}"
  2058. if [[ "$previous_embedding_provider" == "vllm" ]]; then
  2059. docker_embed_default="yes"
  2060. fi
  2061. local use_docker_embed="no"
  2062. if [[ "$docker_embed_default" == "yes" ]]; then
  2063. confirm_default_yes "Run embedding model locally via Docker (vLLM)?" && use_docker_embed="yes" || use_docker_embed="no"
  2064. else
  2065. confirm_default_no "Run embedding model locally via Docker (vLLM)?" && use_docker_embed="yes" || use_docker_embed="no"
  2066. fi
  2067. if [[ "$use_docker_embed" == "yes" ]]; then
  2068. existing_vllm_embed_model="${ORIGINAL_ENV_VALUES[VLLM_EMBED_MODEL]-${ENV_VALUES[VLLM_EMBED_MODEL]:-}}"
  2069. existing_embedding_dim="${ORIGINAL_ENV_VALUES[EMBEDDING_DIM]-${ENV_VALUES[EMBEDDING_DIM]:-}}"
  2070. existing_vllm_embed_port="${ORIGINAL_ENV_VALUES[VLLM_EMBED_PORT]-${ENV_VALUES[VLLM_EMBED_PORT]:-}}"
  2071. existing_vllm_embed_host="${ORIGINAL_ENV_VALUES[EMBEDDING_BINDING_HOST]-${ENV_VALUES[EMBEDDING_BINDING_HOST]:-}}"
  2072. existing_vllm_embed_device="${ORIGINAL_ENV_VALUES[VLLM_EMBED_DEVICE]-${ENV_VALUES[VLLM_EMBED_DEVICE]:-}}"
  2073. apply_preset_overwrite "${PRESET_VLLM_EMBEDDING[@]}"
  2074. local vllm_embed_device
  2075. vllm_embed_device="$(resolve_local_device_default "$existing_vllm_embed_device")"
  2076. vllm_embed_device="$(prompt_choice "Embedding device" "$vllm_embed_device" "cpu" "cuda")"
  2077. if [[ "$vllm_embed_device" == "cuda" ]] && ! host_cuda_available; then
  2078. log_warn "CUDA device selected for vLLM embedding but no NVIDIA driver detected on host."
  2079. fi
  2080. if [[ -n "$existing_vllm_embed_port" ]]; then
  2081. ENV_VALUES["VLLM_EMBED_PORT"]="$existing_vllm_embed_port"
  2082. fi
  2083. if [[ -n "$existing_embedding_dim" ]]; then
  2084. ENV_VALUES["EMBEDDING_DIM"]="$existing_embedding_dim"
  2085. fi
  2086. if [[ "$previous_embedding_provider" == "vllm" && -n "$existing_vllm_embed_host" ]]; then
  2087. ENV_VALUES["EMBEDDING_BINDING_HOST"]="$existing_vllm_embed_host"
  2088. else
  2089. ENV_VALUES["EMBEDDING_BINDING_HOST"]="http://localhost:${ENV_VALUES[VLLM_EMBED_PORT]:-8001}/v1"
  2090. fi
  2091. local embed_model
  2092. embed_model="$(prompt_with_default "Embedding model" "${existing_vllm_embed_model:-${ENV_VALUES[VLLM_EMBED_MODEL]:-BAAI/bge-m3}}")"
  2093. ENV_VALUES["VLLM_EMBED_MODEL"]="$embed_model"
  2094. ENV_VALUES["EMBEDDING_MODEL"]="$embed_model"
  2095. ENV_VALUES["VLLM_EMBED_DEVICE"]="$vllm_embed_device"
  2096. ENV_VALUES["LIGHTRAG_SETUP_EMBEDDING_PROVIDER"]="vllm"
  2097. vllm_embed_api_key="${ENV_VALUES[VLLM_EMBED_API_KEY]:-${ENV_VALUES[EMBEDDING_BINDING_API_KEY]:-}}"
  2098. if [[ -z "$vllm_embed_api_key" ]]; then
  2099. 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)"
  2100. fi
  2101. ENV_VALUES["VLLM_EMBED_API_KEY"]="$vllm_embed_api_key"
  2102. ENV_VALUES["EMBEDDING_BINDING_API_KEY"]="$vllm_embed_api_key"
  2103. add_docker_service "vllm-embed"
  2104. set_compose_override "EMBEDDING_BINDING_HOST" \
  2105. "http://vllm-embed:${ENV_VALUES[VLLM_EMBED_PORT]:-8001}/v1"
  2106. else
  2107. collect_embedding_config
  2108. fi
  2109. echo ""
  2110. # ── Reranker ─────────────────────────────────────────────────────────────────
  2111. log_step "Reranker configuration"
  2112. local rerank_enabled_default="no"
  2113. if [[ -n "${ENV_VALUES[RERANK_BINDING]:-}" && "${ENV_VALUES[RERANK_BINDING]}" != "null" ]]; then
  2114. rerank_enabled_default="yes"
  2115. fi
  2116. previous_rerank_provider="${ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}"
  2117. local enable_reranking="no"
  2118. if [[ "$rerank_enabled_default" == "yes" ]]; then
  2119. confirm_default_yes "Enable reranking?" && enable_reranking="yes" || enable_reranking="no"
  2120. else
  2121. confirm_default_no "Enable reranking?" && enable_reranking="yes" || enable_reranking="no"
  2122. fi
  2123. if [[ "$enable_reranking" == "yes" ]]; then
  2124. local docker_rerank_default="no"
  2125. if [[ "$previous_rerank_provider" == "vllm" ]]; then
  2126. docker_rerank_default="yes"
  2127. fi
  2128. local use_docker_rerank="no"
  2129. if [[ "$docker_rerank_default" == "yes" ]]; then
  2130. confirm_default_yes "Run rerank service locally via Docker?" && use_docker_rerank="yes" || use_docker_rerank="no"
  2131. else
  2132. confirm_default_no "Run rerank service locally via Docker?" && use_docker_rerank="yes" || use_docker_rerank="no"
  2133. fi
  2134. if [[ "$use_docker_rerank" == "yes" ]]; then
  2135. existing_vllm_rerank_model="${ORIGINAL_ENV_VALUES[VLLM_RERANK_MODEL]-${ENV_VALUES[VLLM_RERANK_MODEL]:-}}"
  2136. existing_vllm_rerank_port="${ORIGINAL_ENV_VALUES[VLLM_RERANK_PORT]-${ENV_VALUES[VLLM_RERANK_PORT]:-}}"
  2137. existing_vllm_rerank_host="${ORIGINAL_ENV_VALUES[RERANK_BINDING_HOST]-${ENV_VALUES[RERANK_BINDING_HOST]:-}}"
  2138. existing_vllm_rerank_device="${ORIGINAL_ENV_VALUES[VLLM_RERANK_DEVICE]-${ENV_VALUES[VLLM_RERANK_DEVICE]:-}}"
  2139. apply_preset_overwrite "${PRESET_VLLM_RERANKER[@]}"
  2140. local vllm_rerank_device
  2141. vllm_rerank_device="$(resolve_local_device_default "$existing_vllm_rerank_device")"
  2142. vllm_rerank_device="$(prompt_choice "Rerank device" "$vllm_rerank_device" "cpu" "cuda")"
  2143. if [[ "$vllm_rerank_device" == "cuda" ]] && ! host_cuda_available; then
  2144. log_warn "CUDA device selected for vLLM rerank but no NVIDIA driver detected on host."
  2145. fi
  2146. local rerank_model rerank_port
  2147. if [[ -n "$existing_vllm_rerank_port" ]]; then
  2148. ENV_VALUES["VLLM_RERANK_PORT"]="$existing_vllm_rerank_port"
  2149. fi
  2150. if [[ "$previous_rerank_provider" == "vllm" && -n "$existing_vllm_rerank_host" ]]; then
  2151. ENV_VALUES["RERANK_BINDING_HOST"]="$existing_vllm_rerank_host"
  2152. else
  2153. ENV_VALUES["RERANK_BINDING_HOST"]="http://localhost:${ENV_VALUES[VLLM_RERANK_PORT]:-8000}/rerank"
  2154. fi
  2155. rerank_model="$(prompt_with_default "Rerank model" "${existing_vllm_rerank_model:-${ENV_VALUES[VLLM_RERANK_MODEL]:-BAAI/bge-reranker-v2-m3}}")"
  2156. rerank_port="${ENV_VALUES[VLLM_RERANK_PORT]:-8000}"
  2157. ENV_VALUES["VLLM_RERANK_MODEL"]="$rerank_model"
  2158. ENV_VALUES["RERANK_MODEL"]="$rerank_model"
  2159. ENV_VALUES["VLLM_RERANK_PORT"]="$rerank_port"
  2160. ENV_VALUES["VLLM_RERANK_DEVICE"]="$vllm_rerank_device"
  2161. ENV_VALUES["LIGHTRAG_SETUP_RERANK_PROVIDER"]="vllm"
  2162. vllm_rerank_api_key="${ENV_VALUES[VLLM_RERANK_API_KEY]:-${ENV_VALUES[RERANK_BINDING_API_KEY]:-}}"
  2163. if [[ -z "$vllm_rerank_api_key" ]]; then
  2164. 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)"
  2165. fi
  2166. ENV_VALUES["VLLM_RERANK_API_KEY"]="$vllm_rerank_api_key"
  2167. ENV_VALUES["RERANK_BINDING_API_KEY"]="$vllm_rerank_api_key"
  2168. add_docker_service "vllm-rerank"
  2169. set_compose_override "RERANK_BINDING_HOST" \
  2170. "http://vllm-rerank:${rerank_port}/rerank"
  2171. else
  2172. # Reranking enabled but not via Docker — ask provider/host/model/api_key
  2173. collect_rerank_config "yes" "no"
  2174. fi
  2175. else
  2176. ENV_VALUES["RERANK_BINDING"]="null"
  2177. unset 'ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]'
  2178. fi
  2179. echo ""
  2180. finalize_base_setup
  2181. }
  2182. finalize_base_setup() {
  2183. local backup_path
  2184. local compose_file
  2185. local existing_compose
  2186. local compose_action="write_env_only"
  2187. local runtime_target="$DEFAULT_RUNTIME_TARGET"
  2188. local show_host_start_hint="no"
  2189. local svc_names=""
  2190. if [[ ! -f "${REPO_ROOT}/env.example" ]]; then
  2191. format_error "env.example is missing in $REPO_ROOT" "Restore env.example before running setup."
  2192. return 1
  2193. fi
  2194. if [[ ! -w "$REPO_ROOT" ]]; then
  2195. format_error "No write permission in $REPO_ROOT" "Run the setup from a writable directory."
  2196. return 1
  2197. fi
  2198. if ! validate_sensitive_env_literals; then
  2199. return 1
  2200. fi
  2201. if ! validate_mongo_vector_storage_config \
  2202. "${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" \
  2203. "${ENV_VALUES[MONGO_URI]:-}" \
  2204. "${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}"; then
  2205. return 1
  2206. fi
  2207. show_summary
  2208. if ! confirm_required_yes_no "${COLOR_YELLOW}Ready to proceed and write .env${COLOR_RESET}"; then
  2209. log_warn "Setup cancelled."
  2210. return 1
  2211. fi
  2212. existing_compose="$(find_generated_compose_file)"
  2213. compose_file="${REPO_ROOT}/docker-compose.final.yml"
  2214. record_existing_managed_root_services "$existing_compose"
  2215. restore_storage_docker_services_from_env
  2216. configure_mongodb_compose_migration_rewrite "$existing_compose"
  2217. configure_base_compose_rewrites
  2218. if ((${#DOCKER_SERVICES[@]} > 0)); then
  2219. # LightRAG depends on managed Docker services; it must run via Docker.
  2220. svc_names="$(printf '%s ' "${DOCKER_SERVICES[@]}")"
  2221. svc_names="${svc_names% }"
  2222. echo "LightRAG requires Docker services: ${svc_names}"
  2223. if ! confirm_default_yes "${COLOR_YELLOW}The compose file will be created/updated. Continue?${COLOR_RESET}"; then
  2224. log_warn "Setup cancelled."
  2225. return 1
  2226. fi
  2227. compose_action="rewrite_compose"
  2228. runtime_target="compose"
  2229. else
  2230. resolve_compose_output_action \
  2231. "$existing_compose" \
  2232. compose_action \
  2233. runtime_target \
  2234. show_host_start_hint
  2235. fi
  2236. if [[ "$compose_action" == "rewrite_compose" ]]; then
  2237. backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
  2238. if ! prepare_managed_service_assets_for_compose "$existing_compose"; then
  2239. return 1
  2240. fi
  2241. collect_preserved_storage_service_images "$existing_compose"
  2242. prepare_compose_env_overrides
  2243. elif [[ "$compose_action" == "delete_compose_and_switch_host" ]]; then
  2244. backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
  2245. fi
  2246. backup_path="$(backup_env_file)"
  2247. if [[ -n "$backup_path" ]]; then
  2248. log_success "Backed up existing .env to $backup_path"
  2249. fi
  2250. clear_deprecated_vllm_dtype_state
  2251. set_runtime_target "$runtime_target" || return 1
  2252. generate_env_file "${REPO_ROOT}/env.example" "${REPO_ROOT}/.env"
  2253. log_success "Wrote .env"
  2254. case "$compose_action" in
  2255. rewrite_compose)
  2256. prepare_compose_output_from_existing "$compose_file" "$existing_compose" || return 1
  2257. generate_docker_compose "$compose_file"
  2258. log_success "Wrote ${compose_file}"
  2259. echo " To start: docker compose -f ${compose_file} up -d"
  2260. ;;
  2261. delete_compose_and_switch_host)
  2262. remove_existing_compose_file "$existing_compose" || return 1
  2263. echo " To start: lightrag-server"
  2264. ;;
  2265. *)
  2266. if [[ "$show_host_start_hint" == "yes" ]]; then
  2267. echo " To start: lightrag-server"
  2268. fi
  2269. ;;
  2270. esac
  2271. }
  2272. env_storage_flow() {
  2273. local env_file="${REPO_ROOT}/.env"
  2274. local db_type
  2275. local db_order=("postgresql" "neo4j" "mongodb" "redis" "milvus" "qdrant" "memgraph" "opensearch")
  2276. if [[ ! -f "$env_file" ]]; then
  2277. format_error "No .env file found." "Run 'make env-base' first to configure LLM and embedding."
  2278. return 1
  2279. fi
  2280. reset_state
  2281. load_existing_env_if_present
  2282. log_info "Storage configuration wizard"
  2283. echo "This wizard only modifies storage backend settings."
  2284. echo "LLM, embedding, reranker, server, and security settings are preserved."
  2285. echo ""
  2286. log_step "Storage backend selection"
  2287. select_storage_backends "custom"
  2288. 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]:-}"
  2289. clear_unused_storage_deployment_markers
  2290. log_step "Database configuration"
  2291. for db_type in "${db_order[@]}"; do
  2292. if [[ -n "${REQUIRED_DB_TYPES[$db_type]+set}" ]]; then
  2293. collect_database_config "$db_type" "$(storage_default_docker_for_db_type "$db_type")"
  2294. echo ""
  2295. fi
  2296. done
  2297. finalize_storage_setup
  2298. }
  2299. finalize_storage_setup() {
  2300. local backup_path
  2301. local compose_file
  2302. local existing_compose
  2303. local compose_action="write_env_only"
  2304. local runtime_target="$DEFAULT_RUNTIME_TARGET"
  2305. local show_host_start_hint="no"
  2306. if [[ ! -f "${REPO_ROOT}/env.example" ]]; then
  2307. format_error "env.example is missing in $REPO_ROOT" "Restore env.example before running setup."
  2308. return 1
  2309. fi
  2310. if [[ ! -w "$REPO_ROOT" ]]; then
  2311. format_error "No write permission in $REPO_ROOT" "Run the setup from a writable directory."
  2312. return 1
  2313. fi
  2314. if [[ -n "${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-}" ]]; then
  2315. if ! validate_required_variables \
  2316. "${ENV_VALUES[LIGHTRAG_KV_STORAGE]}" \
  2317. "${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]}" \
  2318. "${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]}" \
  2319. "${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]}"; then
  2320. return 1
  2321. fi
  2322. fi
  2323. if ! validate_mongo_vector_storage_config \
  2324. "${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" \
  2325. "${ENV_VALUES[MONGO_URI]:-}" \
  2326. "${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}"; then
  2327. return 1
  2328. fi
  2329. if ! validate_sensitive_env_literals; then
  2330. return 1
  2331. fi
  2332. show_summary
  2333. if ! confirm_required_yes_no "${COLOR_YELLOW}Ready to proceed and write .env${COLOR_RESET}"; then
  2334. log_warn "Setup cancelled."
  2335. return 1
  2336. fi
  2337. existing_compose="$(find_generated_compose_file)"
  2338. compose_file="${REPO_ROOT}/docker-compose.final.yml"
  2339. record_existing_managed_root_services "$existing_compose"
  2340. restore_vllm_docker_services_from_env
  2341. configure_storage_compose_rewrites
  2342. configure_mongodb_compose_migration_rewrite "$existing_compose"
  2343. resolve_compose_output_action \
  2344. "$existing_compose" \
  2345. compose_action \
  2346. runtime_target \
  2347. show_host_start_hint
  2348. if [[ "$compose_action" == "rewrite_compose" ]]; then
  2349. backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
  2350. if ! prepare_managed_service_assets_for_compose "$existing_compose"; then
  2351. return 1
  2352. fi
  2353. collect_preserved_storage_service_images "$existing_compose"
  2354. prepare_compose_env_overrides
  2355. elif [[ "$compose_action" == "delete_compose_and_switch_host" ]]; then
  2356. backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
  2357. fi
  2358. backup_path="$(backup_env_file)"
  2359. if [[ -n "$backup_path" ]]; then
  2360. log_success "Backed up existing .env to $backup_path"
  2361. fi
  2362. clear_deprecated_vllm_dtype_state
  2363. set_runtime_target "$runtime_target" || return 1
  2364. generate_env_file "${REPO_ROOT}/env.example" "${REPO_ROOT}/.env"
  2365. log_success "Wrote .env"
  2366. case "$compose_action" in
  2367. rewrite_compose)
  2368. prepare_compose_output_from_existing "$compose_file" "$existing_compose" || return 1
  2369. generate_docker_compose "$compose_file"
  2370. log_success "Wrote ${compose_file}"
  2371. echo " To start: docker compose -f ${compose_file} up -d"
  2372. ;;
  2373. delete_compose_and_switch_host)
  2374. remove_existing_compose_file "$existing_compose" || return 1
  2375. echo " To start: lightrag-server"
  2376. ;;
  2377. *)
  2378. if [[ "$show_host_start_hint" == "yes" ]]; then
  2379. echo " To start: lightrag-server"
  2380. fi
  2381. ;;
  2382. esac
  2383. }
  2384. env_server_flow() {
  2385. local env_file="${REPO_ROOT}/.env"
  2386. if [[ ! -f "$env_file" ]]; then
  2387. format_error "No .env file found." "Run 'make env-base' first to configure LLM and embedding."
  2388. return 1
  2389. fi
  2390. reset_state
  2391. load_existing_env_if_present
  2392. log_info "Server configuration wizard"
  2393. echo "This wizard only modifies server, security, and SSL settings."
  2394. echo "LLM, embedding, reranker, and storage settings are preserved."
  2395. echo ""
  2396. log_step "Server configuration"
  2397. collect_server_config
  2398. echo ""
  2399. log_step "Security configuration"
  2400. collect_security_config "no" "no"
  2401. echo ""
  2402. log_step "SSL configuration"
  2403. collect_ssl_config
  2404. echo ""
  2405. finalize_server_setup
  2406. }
  2407. finalize_server_setup() {
  2408. local backup_path
  2409. local compose_file
  2410. local existing_compose
  2411. local compose_action="write_env_only"
  2412. local runtime_target="$DEFAULT_RUNTIME_TARGET"
  2413. local show_host_start_hint="no"
  2414. if [[ ! -f "${REPO_ROOT}/env.example" ]]; then
  2415. format_error "env.example is missing in $REPO_ROOT" "Restore env.example before running setup."
  2416. return 1
  2417. fi
  2418. if [[ ! -w "$REPO_ROOT" ]]; then
  2419. format_error "No write permission in $REPO_ROOT" "Run the setup from a writable directory."
  2420. return 1
  2421. fi
  2422. if ! validate_sensitive_env_literals; then
  2423. return 1
  2424. fi
  2425. if ! validate_auth_accounts_runtime_config \
  2426. "${ENV_VALUES[AUTH_ACCOUNTS]:-}"; then
  2427. return 1
  2428. fi
  2429. if ! validate_mongo_vector_storage_config \
  2430. "${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}" \
  2431. "${ENV_VALUES[MONGO_URI]:-}" \
  2432. "${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}"; then
  2433. return 1
  2434. fi
  2435. show_summary
  2436. if ! confirm_required_yes_no "${COLOR_YELLOW}Ready to proceed and write .env${COLOR_RESET}"; then
  2437. log_warn "Setup cancelled."
  2438. return 1
  2439. fi
  2440. existing_compose="$(find_generated_compose_file)"
  2441. compose_file="${REPO_ROOT}/docker-compose.final.yml"
  2442. record_existing_managed_root_services "$existing_compose"
  2443. restore_storage_docker_services_from_env
  2444. restore_vllm_docker_services_from_env
  2445. configure_mongodb_compose_migration_rewrite "$existing_compose"
  2446. resolve_compose_output_action \
  2447. "$existing_compose" \
  2448. compose_action \
  2449. runtime_target \
  2450. show_host_start_hint
  2451. if [[ "$compose_action" == "rewrite_compose" ]]; then
  2452. backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
  2453. if ! prepare_managed_service_assets_for_compose "$existing_compose"; then
  2454. return 1
  2455. fi
  2456. collect_preserved_storage_service_images "$existing_compose"
  2457. prepare_compose_env_overrides
  2458. elif [[ "$compose_action" == "delete_compose_and_switch_host" ]]; then
  2459. backup_existing_compose_for_action "$compose_action" "$existing_compose" || return 1
  2460. if [[ -n "$SSL_CERT_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_CERT_SOURCE_PATH"; then
  2461. format_error "Invalid SSL_CERTFILE" \
  2462. "Set it to an existing certificate file, disable SSL, or rerun the wizard to choose a new certificate."
  2463. return 1
  2464. fi
  2465. if [[ -n "$SSL_KEY_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_KEY_SOURCE_PATH"; then
  2466. format_error "Invalid SSL_KEYFILE" \
  2467. "Set it to an existing private key file, disable SSL, or rerun the wizard to choose a new key."
  2468. return 1
  2469. fi
  2470. else
  2471. if [[ -n "$SSL_CERT_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_CERT_SOURCE_PATH"; then
  2472. format_error "Invalid SSL_CERTFILE" \
  2473. "Set it to an existing certificate file, disable SSL, or rerun the wizard to choose a new certificate."
  2474. return 1
  2475. fi
  2476. if [[ -n "$SSL_KEY_SOURCE_PATH" ]] && ! validate_existing_file "$SSL_KEY_SOURCE_PATH"; then
  2477. format_error "Invalid SSL_KEYFILE" \
  2478. "Set it to an existing private key file, disable SSL, or rerun the wizard to choose a new key."
  2479. return 1
  2480. fi
  2481. fi
  2482. backup_path="$(backup_env_file)"
  2483. if [[ -n "$backup_path" ]]; then
  2484. log_success "Backed up existing .env to $backup_path"
  2485. fi
  2486. clear_deprecated_vllm_dtype_state
  2487. set_runtime_target "$runtime_target" || return 1
  2488. generate_env_file "${REPO_ROOT}/env.example" "${REPO_ROOT}/.env"
  2489. log_success "Wrote .env"
  2490. case "$compose_action" in
  2491. rewrite_compose)
  2492. prepare_compose_output_from_existing "$compose_file" "$existing_compose" || return 1
  2493. generate_docker_compose "$compose_file"
  2494. log_success "Wrote ${compose_file}"
  2495. log_success "Server port and security settings updated in compose."
  2496. echo " To restart: docker compose -f ${compose_file} up -d --force-recreate lightrag"
  2497. ;;
  2498. delete_compose_and_switch_host)
  2499. remove_existing_compose_file "$existing_compose" || return 1
  2500. echo " To start: lightrag-server"
  2501. ;;
  2502. *)
  2503. if [[ "$show_host_start_hint" == "yes" ]]; then
  2504. echo " To start: lightrag-server"
  2505. fi
  2506. ;;
  2507. esac
  2508. }
  2509. load_env_file() {
  2510. local env_file="$1"
  2511. local line key value
  2512. if [[ ! -f "$env_file" ]]; then
  2513. format_error ".env file not found at $env_file" "Run make env-base to generate it."
  2514. return 1
  2515. fi
  2516. while IFS= read -r line || [[ -n "$line" ]]; do
  2517. if [[ "$line" =~ ^[A-Za-z0-9_]+= ]]; then
  2518. key="${line%%=*}"
  2519. value="${line#*=}"
  2520. if [[ "$value" =~ ^\".*\"$ ]]; then
  2521. value="${value:1:${#value}-2}"
  2522. value="${value//\\\$/\$}"
  2523. value="${value//\\\"/\"}"
  2524. value="${value//\\\\/\\}"
  2525. elif [[ "$value" =~ ^\'.*\'$ ]]; then
  2526. value="${value:1:${#value}-2}"
  2527. fi
  2528. ENV_VALUES["$key"]="$value"
  2529. fi
  2530. done < "$env_file"
  2531. }
  2532. validate_ssl_runtime_path() {
  2533. local path="$1"
  2534. local runtime_target="${ENV_VALUES[LIGHTRAG_RUNTIME_TARGET]:-$DEFAULT_RUNTIME_TARGET}"
  2535. local staged_path=""
  2536. if validate_existing_file "$path"; then
  2537. return 0
  2538. fi
  2539. if [[ "$runtime_target" == "compose" && "$path" == /app/data/certs/* ]]; then
  2540. staged_path="${REPO_ROOT}/data/certs/${path#/app/data/certs/}"
  2541. validate_existing_file "$staged_path"
  2542. return $?
  2543. fi
  2544. return 1
  2545. }
  2546. validate_env_file() {
  2547. local env_file="${REPO_ROOT}/.env"
  2548. local errors=0
  2549. local kv vector graph doc_status
  2550. local runtime_target
  2551. local storage db_type
  2552. local -A referenced_db_types=()
  2553. reset_state
  2554. if ! load_env_file "$env_file"; then
  2555. return 1
  2556. fi
  2557. kv="${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-}"
  2558. vector="${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}"
  2559. graph="${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-}"
  2560. doc_status="${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-}"
  2561. runtime_target="${ENV_VALUES[LIGHTRAG_RUNTIME_TARGET]:-$DEFAULT_RUNTIME_TARGET}"
  2562. for storage in "$kv" "$vector" "$graph" "$doc_status"; do
  2563. if [[ -z "$storage" ]]; then
  2564. continue
  2565. fi
  2566. db_type="${STORAGE_DB_TYPES[$storage]:-}"
  2567. if [[ -n "$db_type" ]]; then
  2568. referenced_db_types["$db_type"]=1
  2569. fi
  2570. done
  2571. if ! validate_runtime_target "$runtime_target"; then
  2572. errors=1
  2573. fi
  2574. if [[ -z "$kv" || -z "$vector" || -z "$graph" || -z "$doc_status" ]]; then
  2575. format_error "Storage selections are missing in .env" "Set LIGHTRAG_*_STORAGE variables."
  2576. return 1
  2577. fi
  2578. if ! validate_mongo_vector_storage_config \
  2579. "$vector" \
  2580. "${ENV_VALUES[MONGO_URI]:-}" \
  2581. "${ENV_VALUES[LIGHTRAG_SETUP_MONGODB_DEPLOYMENT]:-}"; then
  2582. errors=1
  2583. fi
  2584. if ! validate_required_variables "$kv" "$vector" "$graph" "$doc_status"; then
  2585. errors=1
  2586. fi
  2587. if ! validate_auth_accounts_runtime_config \
  2588. "${ENV_VALUES[AUTH_ACCOUNTS]:-}"; then
  2589. errors=1
  2590. fi
  2591. if ! validate_sensitive_env_literals; then
  2592. errors=1
  2593. fi
  2594. if [[ "${ENV_VALUES[SSL]:-false}" == "true" ]]; then
  2595. if ! validate_ssl_runtime_path "${ENV_VALUES[SSL_CERTFILE]:-}"; then
  2596. format_error "Invalid SSL_CERTFILE" "Set it to an existing certificate file when SSL=true."
  2597. errors=1
  2598. fi
  2599. if ! validate_ssl_runtime_path "${ENV_VALUES[SSL_KEYFILE]:-}"; then
  2600. format_error "Invalid SSL_KEYFILE" "Set it to an existing private key file when SSL=true."
  2601. errors=1
  2602. fi
  2603. fi
  2604. if [[ -n "${referenced_db_types[neo4j]+set}" ]] && [[ -n "${ENV_VALUES[NEO4J_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[NEO4J_URI]}" neo4j; then
  2605. format_error "Invalid NEO4J_URI" "Use neo4j:// or bolt:// format."
  2606. errors=1
  2607. fi
  2608. if [[ -n "${referenced_db_types[mongodb]+set}" ]] && [[ -n "${ENV_VALUES[MONGO_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[MONGO_URI]}" mongodb; then
  2609. format_error "Invalid MONGO_URI" "Use mongodb:// or mongodb+srv:// format."
  2610. errors=1
  2611. fi
  2612. if [[ -n "${referenced_db_types[redis]+set}" ]] && [[ -n "${ENV_VALUES[REDIS_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[REDIS_URI]}" redis; then
  2613. format_error "Invalid REDIS_URI" "Use redis:// or rediss:// format."
  2614. errors=1
  2615. fi
  2616. if [[ -n "${referenced_db_types[milvus]+set}" ]] && [[ -n "${ENV_VALUES[MILVUS_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[MILVUS_URI]}" milvus; then
  2617. format_error "Invalid MILVUS_URI" "Use http://host:port format."
  2618. errors=1
  2619. fi
  2620. if [[ -n "${referenced_db_types[qdrant]+set}" ]] && [[ -n "${ENV_VALUES[QDRANT_URL]:-}" ]] && ! validate_uri "${ENV_VALUES[QDRANT_URL]}" qdrant; then
  2621. format_error "Invalid QDRANT_URL" "Use http://host:port format."
  2622. errors=1
  2623. fi
  2624. if [[ -n "${referenced_db_types[memgraph]+set}" ]] && [[ -n "${ENV_VALUES[MEMGRAPH_URI]:-}" ]] && ! validate_uri "${ENV_VALUES[MEMGRAPH_URI]}" memgraph; then
  2625. format_error "Invalid MEMGRAPH_URI" "Use bolt://host:port format."
  2626. errors=1
  2627. fi
  2628. if [[ -n "${referenced_db_types[postgresql]+set}" ]] && [[ -n "${ENV_VALUES[POSTGRES_PORT]:-}" ]] && ! validate_port "${ENV_VALUES[POSTGRES_PORT]}"; then
  2629. format_error "Invalid POSTGRES_PORT" "Use a port between 1 and 65535."
  2630. errors=1
  2631. fi
  2632. if [[ -n "${referenced_db_types[opensearch]+set}" ]] && [[ -v 'ENV_VALUES[OPENSEARCH_HOSTS]' ]] && [[ -z "${ENV_VALUES[OPENSEARCH_HOSTS]}" ]]; then
  2633. format_error "Empty OPENSEARCH_HOSTS" "Set it to host:port (e.g. localhost:9200)."
  2634. errors=1
  2635. fi
  2636. if [[ -n "${referenced_db_types[opensearch]+set}" ]]; then
  2637. if ! validate_opensearch_config \
  2638. "${ENV_VALUES[LIGHTRAG_SETUP_OPENSEARCH_DEPLOYMENT]:-}" \
  2639. "${ENV_VALUES[OPENSEARCH_HOSTS]:-}" \
  2640. "${ENV_VALUES[OPENSEARCH_USER]:-}" \
  2641. "${ENV_VALUES[OPENSEARCH_PASSWORD]:-}" \
  2642. "${ENV_VALUES[OPENSEARCH_NUMBER_OF_SHARDS]-1}" \
  2643. "${ENV_VALUES[OPENSEARCH_NUMBER_OF_REPLICAS]-0}"; then
  2644. errors=1
  2645. fi
  2646. fi
  2647. if ((errors != 0)); then
  2648. return 1
  2649. fi
  2650. log_success "Validation passed."
  2651. }
  2652. report_security_issue() {
  2653. local message="$1"
  2654. local suggestion="${2:-}"
  2655. echo "${COLOR_YELLOW:-}Security issue:${COLOR_RESET:-} $message"
  2656. if [[ -n "$suggestion" ]]; then
  2657. echo " Suggestion: $suggestion"
  2658. fi
  2659. }
  2660. security_check_env_file() {
  2661. local env_file="${REPO_ROOT}/.env"
  2662. local findings=0
  2663. local auth_accounts=""
  2664. local token_secret=""
  2665. local api_key=""
  2666. local whitelist_paths=""
  2667. local whitelist_is_set="no"
  2668. local effective_whitelist=""
  2669. local kv=""
  2670. local vector=""
  2671. local graph=""
  2672. local doc_status=""
  2673. local storage=""
  2674. local db_type=""
  2675. local opensearch_in_use="no"
  2676. local key value
  2677. local invalid_sensitive_keys=()
  2678. local -A referenced_db_types=()
  2679. reset_state
  2680. if ! load_env_file "$env_file"; then
  2681. return 1
  2682. fi
  2683. auth_accounts="${ENV_VALUES[AUTH_ACCOUNTS]:-}"
  2684. token_secret="${ENV_VALUES[TOKEN_SECRET]:-}"
  2685. api_key="${ENV_VALUES[LIGHTRAG_API_KEY]:-}"
  2686. kv="${ENV_VALUES[LIGHTRAG_KV_STORAGE]:-}"
  2687. vector="${ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]:-}"
  2688. graph="${ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]:-}"
  2689. doc_status="${ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]:-}"
  2690. if [[ -n "${ENV_VALUES[WHITELIST_PATHS]+set}" ]]; then
  2691. whitelist_paths="${ENV_VALUES[WHITELIST_PATHS]}"
  2692. whitelist_is_set="yes"
  2693. fi
  2694. for storage in "$kv" "$vector" "$graph" "$doc_status"; do
  2695. if [[ -z "$storage" ]]; then
  2696. continue
  2697. fi
  2698. if [[ ! -v "STORAGE_DB_TYPES[$storage]" ]]; then
  2699. continue
  2700. fi
  2701. db_type="${STORAGE_DB_TYPES[$storage]}"
  2702. if [[ -n "$db_type" ]]; then
  2703. referenced_db_types["$db_type"]=1
  2704. fi
  2705. done
  2706. if [[ -n "${referenced_db_types[opensearch]+set}" || "${ENV_VALUES[LIGHTRAG_SETUP_OPENSEARCH_DEPLOYMENT]:-}" == "docker" ]]; then
  2707. opensearch_in_use="yes"
  2708. fi
  2709. for key in "${!ENV_VALUES[@]}"; do
  2710. if ! is_sensitive_env_key "$key"; then
  2711. continue
  2712. fi
  2713. value="${ENV_VALUES[$key]:-}"
  2714. if [[ -n "$value" ]] && contains_env_interpolation_syntax "$value"; then
  2715. invalid_sensitive_keys+=("$key")
  2716. fi
  2717. done
  2718. if ((${#invalid_sensitive_keys[@]} > 0)); then
  2719. report_security_issue \
  2720. "Sensitive values still contain \${...} interpolation syntax: ${invalid_sensitive_keys[*]}" \
  2721. "Replace them with literal values or inject those secrets at runtime."
  2722. findings=$((findings + 1))
  2723. fi
  2724. if [[ -z "$auth_accounts" && -z "$api_key" ]]; then
  2725. report_security_issue \
  2726. "No API protection is configured." \
  2727. "Set AUTH_ACCOUNTS and TOKEN_SECRET, add LIGHTRAG_API_KEY, or put the service behind a trusted reverse proxy."
  2728. findings=$((findings + 1))
  2729. fi
  2730. if [[ -n "$auth_accounts" ]]; then
  2731. if ! validate_auth_accounts_format "$auth_accounts"; then
  2732. report_security_issue \
  2733. "AUTH_ACCOUNTS is malformed." \
  2734. "Use comma-separated user:password pairs such as admin:{bcrypt}<hash> or admin:secret,reader:another-secret."
  2735. findings=$((findings + 1))
  2736. elif ! validate_auth_accounts_password_safety "$auth_accounts"; then
  2737. report_security_issue \
  2738. "AUTH_ACCOUNTS uses a predictable password prefix." \
  2739. "Passwords must not start with 'admin' or 'pass'. Choose a stronger password or use lightrag-hash-password."
  2740. findings=$((findings + 1))
  2741. fi
  2742. if [[ -z "$token_secret" ]]; then
  2743. report_security_issue \
  2744. "AUTH_ACCOUNTS is set but TOKEN_SECRET is missing." \
  2745. "Set a non-empty JWT signing secret before enabling account-based authentication."
  2746. findings=$((findings + 1))
  2747. elif [[ "$token_secret" == "lightrag-jwt-default-secret" ]]; then
  2748. report_security_issue \
  2749. "TOKEN_SECRET still uses the built-in default value." \
  2750. "Generate a unique JWT signing secret and update TOKEN_SECRET."
  2751. findings=$((findings + 1))
  2752. fi
  2753. effective_whitelist="$whitelist_paths"
  2754. if [[ "$whitelist_is_set" != "yes" ]]; then
  2755. effective_whitelist="/health,/api/*"
  2756. fi
  2757. if whitelist_exposes_api_routes "$effective_whitelist"; then
  2758. report_security_issue \
  2759. "WHITELIST_PATHS exposes /api routes while AUTH_ACCOUNTS is enabled." \
  2760. "Use a minimal whitelist such as /health,/docs and keep /api routes authenticated."
  2761. findings=$((findings + 1))
  2762. fi
  2763. fi
  2764. if [[ -z "$auth_accounts" && -n "$api_key" ]]; then
  2765. effective_whitelist="$whitelist_paths"
  2766. if [[ "$whitelist_is_set" != "yes" ]]; then
  2767. effective_whitelist="/health,/api/*"
  2768. fi
  2769. if whitelist_exposes_api_routes "$effective_whitelist"; then
  2770. report_security_issue \
  2771. "WHITELIST_PATHS exposes /api routes while LIGHTRAG_API_KEY is the only active auth mechanism." \
  2772. "Use a minimal whitelist such as /health,/docs and keep /api routes protected by the API key."
  2773. findings=$((findings + 1))
  2774. fi
  2775. fi
  2776. if [[ "$opensearch_in_use" == "yes" ]] && [[ -n "${ENV_VALUES[OPENSEARCH_PASSWORD]:-}" ]]; then
  2777. local os_pass="${ENV_VALUES[OPENSEARCH_PASSWORD]}"
  2778. if [[ "$os_pass" == "admin" || "$os_pass" == "LightRAG2026_!@" ]]; then
  2779. report_security_issue \
  2780. "OPENSEARCH_PASSWORD uses a well-known default value." \
  2781. "Set a unique, strong password for the OpenSearch admin account."
  2782. findings=$((findings + 1))
  2783. fi
  2784. fi
  2785. if ((findings == 0)); then
  2786. log_success "No obvious security issues found in ${env_file}."
  2787. return 0
  2788. fi
  2789. log_warn "Security check found ${findings} issue(s) in ${env_file}."
  2790. return 1
  2791. }
  2792. backup_only() {
  2793. local backup_path
  2794. local compose_backup_path
  2795. backup_path="$(backup_env_file)"
  2796. if [[ -z "$backup_path" ]]; then
  2797. format_error "No .env file found to back up." "Create one with make env-base first."
  2798. return 1
  2799. fi
  2800. echo "Backed up .env to $backup_path"
  2801. compose_backup_path="$(backup_compose_file)" || return 1
  2802. if [[ -n "$compose_backup_path" ]]; then
  2803. echo "Backed up compose file to $compose_backup_path"
  2804. fi
  2805. }
  2806. print_help() {
  2807. cat <<'HELP'
  2808. Usage: scripts/setup/setup.sh [--base|--storage|--server|--validate|--security-check|--backup] [--rewrite-compose]
  2809. Options:
  2810. --base Configure LLM, embedding, and reranker (run first)
  2811. --storage Configure storage backends and databases (requires .env)
  2812. --server Configure server, security, and SSL (requires .env)
  2813. --validate Validate an existing .env file
  2814. --security-check Audit an existing .env for security risks
  2815. --backup Backup the current .env and generated compose file when present
  2816. --rewrite-compose Force regeneration of all wizard-managed compose services
  2817. --debug Enable debug logging
  2818. --help Show this help message
  2819. HELP
  2820. }
  2821. _sigint_handler() {
  2822. echo ""
  2823. echo "Setup interrupted."
  2824. exit 130
  2825. }
  2826. main() {
  2827. trap '_sigint_handler' INT
  2828. init_colors
  2829. local mode="help"
  2830. while [[ $# -gt 0 ]]; do
  2831. case "$1" in
  2832. --base)
  2833. mode="base"
  2834. ;;
  2835. --storage)
  2836. mode="storage"
  2837. ;;
  2838. --server)
  2839. mode="server"
  2840. ;;
  2841. --validate)
  2842. mode="validate"
  2843. ;;
  2844. --security-check)
  2845. mode="security-check"
  2846. ;;
  2847. --backup)
  2848. mode="backup"
  2849. ;;
  2850. --debug)
  2851. DEBUG="true"
  2852. ;;
  2853. --rewrite-compose)
  2854. FORCE_REWRITE_COMPOSE="yes"
  2855. ;;
  2856. --help|-h)
  2857. mode="help"
  2858. ;;
  2859. *)
  2860. echo "Unknown option: $1" >&2
  2861. print_help
  2862. return 1
  2863. ;;
  2864. esac
  2865. shift
  2866. done
  2867. case "$mode" in
  2868. base)
  2869. env_base_flow
  2870. ;;
  2871. storage)
  2872. env_storage_flow
  2873. ;;
  2874. server)
  2875. env_server_flow
  2876. ;;
  2877. validate)
  2878. validate_env_file
  2879. ;;
  2880. security-check)
  2881. security_check_env_file
  2882. ;;
  2883. backup)
  2884. backup_only
  2885. ;;
  2886. *)
  2887. print_help
  2888. ;;
  2889. esac
  2890. }
  2891. if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  2892. main "$@"
  2893. fi