| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027 |
- # Regression tests for interactive setup wizard.
- # Classification: keep tests here when they exercise env_* top-level wizard flows and their end-to-end env/compose rewrite outcomes.
- from __future__ import annotations
- from pathlib import Path
- import pytest
- from tests.setup._helpers import (
- REPO_ROOT,
- assert_single_compose_backup,
- parse_lines,
- run_bash,
- run_bash_process,
- run_bash_lines,
- write_storage_setup_files,
- write_text_lines,
- )
- pytestmark = pytest.mark.offline
- def test_env_base_flow_preserves_non_inference_env_values(tmp_path: Path) -> None:
- """env-base wizard should leave server, security, and observability values untouched."""
- env_file = tmp_path / ".env"
- env_file.write_text(
- "\n".join(
- [
- "HOST=127.0.0.1",
- "PORT=9999",
- "WEBUI_TITLE=Existing Title",
- "WEBUI_DESCRIPTION=Existing Description",
- "SSL=true",
- "SSL_CERTFILE=/some/cert.pem",
- "SSL_KEYFILE=/some/key.pem",
- "AUTH_ACCOUNTS=admin:secret",
- "TOKEN_SECRET=jwt-secret",
- "LIGHTRAG_API_KEY=api-key",
- "LANGFUSE_ENABLE_TRACE=true",
- "LANGFUSE_SECRET_KEY=langfuse-secret",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING_API_KEY=sk-existing",
- ]
- )
- + "\n",
- encoding="utf-8",
- )
- output = run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{ return 1; }}
- finalize_base_setup() {{
- printf 'HOST=%s\\n' "${{ENV_VALUES[HOST]}}"
- printf 'PORT=%s\\n' "${{ENV_VALUES[PORT]}}"
- printf 'WEBUI_TITLE=%s\\n' "${{ENV_VALUES[WEBUI_TITLE]}}"
- printf 'WEBUI_DESCRIPTION=%s\\n' "${{ENV_VALUES[WEBUI_DESCRIPTION]}}"
- printf 'LLM_BINDING=%s\\n' "${{ENV_VALUES[LLM_BINDING]}}"
- printf 'LLM_BINDING_API_KEY=%s\\n' "${{ENV_VALUES[LLM_BINDING_API_KEY]}}"
- printf 'EMBEDDING_BINDING_API_KEY=%s\\n' "${{ENV_VALUES[EMBEDDING_BINDING_API_KEY]}}"
- printf 'SSL_SET=%s\\n' "${{ENV_VALUES[SSL]+set}}"
- printf 'AUTH_ACCOUNTS_SET=%s\\n' "${{ENV_VALUES[AUTH_ACCOUNTS]+set}}"
- printf 'TOKEN_SECRET_SET=%s\\n' "${{ENV_VALUES[TOKEN_SECRET]+set}}"
- printf 'LIGHTRAG_API_KEY_SET=%s\\n' "${{ENV_VALUES[LIGHTRAG_API_KEY]+set}}"
- printf 'LANGFUSE_ENABLE_TRACE_SET=%s\\n' "${{ENV_VALUES[LANGFUSE_ENABLE_TRACE]+set}}"
- printf 'LANGFUSE_SECRET_KEY_SET=%s\\n' "${{ENV_VALUES[LANGFUSE_SECRET_KEY]+set}}"
- }}
- env_base_flow
- """)
- values = parse_lines(output)
- assert values["HOST"] == "127.0.0.1"
- assert values["PORT"] == "9999"
- assert values["WEBUI_TITLE"] == "Existing Title"
- assert values["WEBUI_DESCRIPTION"] == "Existing Description"
- assert values["LLM_BINDING"] == "openai"
- assert values["LLM_BINDING_API_KEY"] == "sk-existing"
- assert values["EMBEDDING_BINDING_API_KEY"] == "sk-existing"
- assert values["SSL_SET"] == "set"
- assert values["AUTH_ACCOUNTS_SET"] == "set"
- assert values["TOKEN_SECRET_SET"] == "set"
- assert values["LIGHTRAG_API_KEY_SET"] == "set"
- assert values["LANGFUSE_ENABLE_TRACE_SET"] == "set"
- assert values["LANGFUSE_SECRET_KEY_SET"] == "set"
- def test_env_base_flow_preserves_existing_provider_bindings_on_rerun(
- tmp_path: Path,
- ) -> None:
- """Rerunning env-base should keep prior LLM and embedding provider settings."""
- env_file = tmp_path / ".env"
- env_file.write_text(
- "\n".join(
- [
- "LLM_BINDING=ollama",
- "LLM_MODEL=llama3.2:latest",
- "LLM_BINDING_HOST=http://localhost:11434",
- "EMBEDDING_BINDING=ollama",
- "EMBEDDING_MODEL=nomic-embed-text:latest",
- "EMBEDDING_DIM=768",
- "EMBEDDING_BINDING_HOST=http://localhost:11434",
- ]
- )
- + "\n",
- encoding="utf-8",
- )
- output = run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{ return 1; }}
- finalize_base_setup() {{
- printf 'LLM_BINDING=%s\\n' "${{ENV_VALUES[LLM_BINDING]}}"
- printf 'LLM_MODEL=%s\\n' "${{ENV_VALUES[LLM_MODEL]}}"
- printf 'LLM_BINDING_HOST=%s\\n' "${{ENV_VALUES[LLM_BINDING_HOST]}}"
- printf 'EMBEDDING_BINDING=%s\\n' "${{ENV_VALUES[EMBEDDING_BINDING]}}"
- printf 'EMBEDDING_MODEL=%s\\n' "${{ENV_VALUES[EMBEDDING_MODEL]}}"
- printf 'EMBEDDING_DIM=%s\\n' "${{ENV_VALUES[EMBEDDING_DIM]}}"
- printf 'EMBEDDING_BINDING_HOST=%s\\n' "${{ENV_VALUES[EMBEDDING_BINDING_HOST]}}"
- }}
- env_base_flow
- """)
- values = parse_lines(output)
- assert values["LLM_BINDING"] == "ollama"
- assert values["LLM_MODEL"] == "llama3.2:latest"
- assert values["LLM_BINDING_HOST"] == "http://localhost:11434"
- assert values["EMBEDDING_BINDING"] == "ollama"
- assert values["EMBEDDING_MODEL"] == "nomic-embed-text:latest"
- assert values["EMBEDDING_DIM"] == "768"
- assert values["EMBEDDING_BINDING_HOST"] == "http://localhost:11434"
- def test_env_base_flow_preserves_existing_vllm_embedding_settings_on_rerun(
- tmp_path: Path,
- ) -> None:
- """Rerunning env-base should keep saved local vLLM embedding settings."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING=openai",
- "EMBEDDING_MODEL=BAAI/custom-embed",
- "EMBEDDING_DIM=768",
- "EMBEDDING_BINDING_HOST=http://localhost:9101/v1",
- "EMBEDDING_BINDING_API_KEY=embed-key",
- "LIGHTRAG_SETUP_EMBEDDING_PROVIDER=vllm",
- "VLLM_EMBED_MODEL=BAAI/custom-embed",
- "VLLM_EMBED_PORT=9101",
- "VLLM_EMBED_DEVICE=cpu",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- finalize_base_setup() {{
- printf 'EMBEDDING_MODEL=%s\\n' "${{ENV_VALUES[EMBEDDING_MODEL]}}"
- printf 'EMBEDDING_DIM=%s\\n' "${{ENV_VALUES[EMBEDDING_DIM]}}"
- printf 'EMBEDDING_BINDING_HOST=%s\\n' "${{ENV_VALUES[EMBEDDING_BINDING_HOST]}}"
- printf 'VLLM_EMBED_MODEL=%s\\n' "${{ENV_VALUES[VLLM_EMBED_MODEL]}}"
- printf 'VLLM_EMBED_PORT=%s\\n' "${{ENV_VALUES[VLLM_EMBED_PORT]}}"
- }}
- env_base_flow
- """)
- assert values["EMBEDDING_MODEL"] == "BAAI/custom-embed"
- assert values["EMBEDDING_DIM"] == "768"
- assert values["EMBEDDING_BINDING_HOST"] == "http://localhost:9101/v1"
- assert values["VLLM_EMBED_MODEL"] == "BAAI/custom-embed"
- assert values["VLLM_EMBED_PORT"] == "9101"
- def test_env_base_flow_resets_remote_embedding_host_when_switching_to_vllm(
- tmp_path: Path,
- ) -> None:
- """Switching a remote embedding provider to local vLLM should restore localhost."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING=jina",
- "EMBEDDING_MODEL=jina-embeddings-v4",
- "EMBEDDING_DIM=2048",
- "EMBEDDING_BINDING_HOST=https://api.jina.ai/v1/embeddings",
- "EMBEDDING_BINDING_API_KEY=jina-key",
- "VLLM_EMBED_PORT=9101",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 0 ;;
- "Enable reranking?") return 1 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{ return 1; }}
- finalize_base_setup() {{
- printf 'EMBEDDING_BINDING_HOST=%s\\n' "${{ENV_VALUES[EMBEDDING_BINDING_HOST]}}"
- printf 'LIGHTRAG_SETUP_EMBEDDING_PROVIDER=%s\\n' "${{ENV_VALUES[LIGHTRAG_SETUP_EMBEDDING_PROVIDER]}}"
- }}
- env_base_flow
- """)
- assert values["EMBEDDING_BINDING_HOST"] == "http://localhost:9101/v1"
- assert values["LIGHTRAG_SETUP_EMBEDDING_PROVIDER"] == "vllm"
- def test_env_base_flow_preserves_existing_vllm_embedding_device_on_gpu_host(
- tmp_path: Path,
- ) -> None:
- """Saved vLLM embedding CPU/GPU mode should win over auto-detected GPU defaults."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING=openai",
- "EMBEDDING_MODEL=BAAI/custom-embed",
- "EMBEDDING_DIM=1024",
- "EMBEDDING_BINDING_HOST=http://localhost:9101/v1",
- "EMBEDDING_BINDING_API_KEY=embed-key",
- "LIGHTRAG_SETUP_EMBEDDING_PROVIDER=vllm",
- "VLLM_EMBED_MODEL=BAAI/custom-embed",
- "VLLM_EMBED_PORT=9101",
- "VLLM_EMBED_DEVICE=cpu",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- nvidia-smi() {{ return 0; }}
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- finalize_base_setup() {{
- printf 'VLLM_EMBED_DEVICE=%s\\n' "${{ENV_VALUES[VLLM_EMBED_DEVICE]}}"
- }}
- env_base_flow
- """)
- assert values["VLLM_EMBED_DEVICE"] == "cpu"
- def test_env_base_flow_preserves_existing_vllm_embedding_cuda_device_on_rerun(
- tmp_path: Path,
- ) -> None:
- """Saved vLLM embedding CUDA mode should survive env-base reruns."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING=openai",
- "EMBEDDING_MODEL=BAAI/custom-embed",
- "EMBEDDING_DIM=1024",
- "EMBEDDING_BINDING_HOST=http://localhost:9101/v1",
- "EMBEDDING_BINDING_API_KEY=embed-key",
- "LIGHTRAG_SETUP_EMBEDDING_PROVIDER=vllm",
- "VLLM_EMBED_MODEL=BAAI/custom-embed",
- "VLLM_EMBED_PORT=9101",
- "VLLM_EMBED_DEVICE=cuda",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- nvidia-smi() {{ return 0; }}
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- finalize_base_setup() {{
- printf 'VLLM_EMBED_DEVICE=%s\\n' "${{ENV_VALUES[VLLM_EMBED_DEVICE]}}"
- }}
- env_base_flow
- """)
- assert values["VLLM_EMBED_DEVICE"] == "cuda"
- def test_env_base_flow_defaults_new_vllm_embedding_to_cuda_on_gpu_host(
- tmp_path: Path,
- ) -> None:
- """Fresh local vLLM embedding setup should honor GPU auto-detection."""
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- nvidia-smi() {{ return 0; }}
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 0 ;;
- "Enable reranking?") return 1 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{
- return 1
- }}
- finalize_base_setup() {{
- printf 'VLLM_EMBED_DEVICE=%s\\n' "${{ENV_VALUES[VLLM_EMBED_DEVICE]}}"
- }}
- env_base_flow
- """)
- assert values["VLLM_EMBED_DEVICE"] == "cuda"
- def test_env_base_flow_forced_vllm_cuda_selection_writes_cuda_devices_to_env(
- tmp_path: Path,
- ) -> None:
- """Forced CUDA selection should drive both .env devices and GPU compose templates."""
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- tmp_path / "docker-compose.yml",
- (REPO_ROOT / "docker-compose.yml").read_text(encoding="utf-8").splitlines(),
- )
- result = run_bash_process(
- f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- host_cuda_available() {{ return 1; }}
- prompt_choice() {{
- case "$1" in
- "LLM provider") printf 'ollama' ;;
- "Embedding device"|"Rerank device") printf 'cuda' ;;
- *) printf '%s' "$2" ;;
- esac
- }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 0 ;;
- "Enable reranking?") return 0 ;;
- "Run rerank service locally via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{
- case "$1" in
- *"The compose file will be created/updated. Continue?"*) return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_base_flow
- """,
- cwd=tmp_path,
- )
- assert result.returncode == 0, result.stderr
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- generated_compose = (tmp_path / "docker-compose.final.yml").read_text(
- encoding="utf-8"
- )
- assert "VLLM_EMBED_DEVICE=cuda" in generated_env
- assert "VLLM_RERANK_DEVICE=cuda" in generated_env
- assert generated_compose.count("capabilities: [gpu]") >= 2
- assert (
- "CUDA device selected for vLLM embedding but no NVIDIA driver detected on host."
- in result.stdout
- )
- assert (
- "CUDA device selected for vLLM rerank but no NVIDIA driver detected on host."
- in result.stdout
- )
- def test_env_base_flow_vllm_defaults_prefer_original_env_values_on_rerun(
- tmp_path: Path,
- ) -> None:
- """vLLM prompt defaults should prefer the loaded `.env` snapshot over later mutations."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING=openai",
- "EMBEDDING_MODEL=BAAI/original-embed",
- "EMBEDDING_DIM=1024",
- "EMBEDDING_BINDING_HOST=http://localhost:9101/v1",
- "EMBEDDING_BINDING_API_KEY=embed-key",
- "LIGHTRAG_SETUP_EMBEDDING_PROVIDER=vllm",
- "VLLM_EMBED_MODEL=BAAI/original-embed",
- "VLLM_EMBED_PORT=9101",
- "VLLM_EMBED_DEVICE=cpu",
- "RERANK_BINDING=cohere",
- "RERANK_MODEL=BAAI/original-rerank",
- "RERANK_BINDING_HOST=http://localhost:9200/rerank",
- "RERANK_BINDING_API_KEY=rerank-key",
- "LIGHTRAG_SETUP_RERANK_PROVIDER=vllm",
- "VLLM_RERANK_MODEL=BAAI/original-rerank",
- "VLLM_RERANK_PORT=9200",
- "VLLM_RERANK_DEVICE=cpu",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- nvidia-smi() {{ return 0; }}
- collect_llm_config() {{
- ENV_VALUES[VLLM_EMBED_MODEL]="BAAI/mutated-embed"
- ENV_VALUES[EMBEDDING_DIM]="2048"
- ENV_VALUES[VLLM_EMBED_PORT]="9991"
- ENV_VALUES[EMBEDDING_BINDING_HOST]="http://localhost:9991/v1"
- ENV_VALUES[VLLM_EMBED_DEVICE]="cuda"
- ENV_VALUES[VLLM_RERANK_MODEL]="BAAI/mutated-rerank"
- ENV_VALUES[VLLM_RERANK_PORT]="9990"
- ENV_VALUES[RERANK_BINDING_HOST]="http://localhost:9990/rerank"
- ENV_VALUES[VLLM_RERANK_DEVICE]="cuda"
- }}
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- "Enable reranking?") return 0 ;;
- "Run embedding model locally via Docker (vLLM)?") return 0 ;;
- "Run rerank service locally via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- finalize_base_setup() {{
- printf 'VLLM_EMBED_MODEL=%s\\n' "${{ENV_VALUES[VLLM_EMBED_MODEL]}}"
- printf 'EMBEDDING_DIM=%s\\n' "${{ENV_VALUES[EMBEDDING_DIM]}}"
- printf 'VLLM_EMBED_PORT=%s\\n' "${{ENV_VALUES[VLLM_EMBED_PORT]}}"
- printf 'EMBEDDING_BINDING_HOST=%s\\n' "${{ENV_VALUES[EMBEDDING_BINDING_HOST]}}"
- printf 'VLLM_EMBED_DEVICE=%s\\n' "${{ENV_VALUES[VLLM_EMBED_DEVICE]}}"
- printf 'VLLM_RERANK_MODEL=%s\\n' "${{ENV_VALUES[VLLM_RERANK_MODEL]}}"
- printf 'VLLM_RERANK_PORT=%s\\n' "${{ENV_VALUES[VLLM_RERANK_PORT]}}"
- printf 'RERANK_BINDING_HOST=%s\\n' "${{ENV_VALUES[RERANK_BINDING_HOST]}}"
- printf 'VLLM_RERANK_DEVICE=%s\\n' "${{ENV_VALUES[VLLM_RERANK_DEVICE]}}"
- }}
- env_base_flow
- """)
- assert values["VLLM_EMBED_MODEL"] == "BAAI/original-embed"
- assert values["EMBEDDING_DIM"] == "1024"
- assert values["VLLM_EMBED_PORT"] == "9101"
- assert values["EMBEDDING_BINDING_HOST"] == "http://localhost:9101/v1"
- assert values["VLLM_EMBED_DEVICE"] == "cpu"
- assert values["VLLM_RERANK_MODEL"] == "BAAI/original-rerank"
- assert values["VLLM_RERANK_PORT"] == "9200"
- assert values["RERANK_BINDING_HOST"] == "http://localhost:9200/rerank"
- assert values["VLLM_RERANK_DEVICE"] == "cpu"
- def test_env_base_flow_vllm_device_prompt_is_first_after_docker_choice(
- tmp_path: Path,
- ) -> None:
- """vLLM should ask for device before model-specific prompts once Docker is selected."""
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- PROMPT_LOG_FILE="$(mktemp)"
- : > "$PROMPT_LOG_FILE"
- prompt_choice() {{
- printf '%s\\n' "$1" >> "$PROMPT_LOG_FILE"
- printf '%s' "$2"
- }}
- prompt_with_default() {{
- printf '%s\\n' "$1" >> "$PROMPT_LOG_FILE"
- printf '%s' "$2"
- }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 0 ;;
- "Enable reranking?") return 0 ;;
- "Run rerank service locally via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{
- return 1
- }}
- finalize_base_setup() {{ :; }}
- env_base_flow
- printf 'PROMPT_LOG=%s\\n' "$(paste -sd '|' "$PROMPT_LOG_FILE")"
- """)
- prompt_log = values["PROMPT_LOG"]
- assert prompt_log.index("Embedding device") < prompt_log.index("Embedding model")
- assert prompt_log.index("Rerank device") < prompt_log.index("Rerank model")
- def test_env_base_flow_preserves_ssl_config_on_rerun(tmp_path: Path) -> None:
- """env-base should preserve SSL config on rerun, even when old paths are stale."""
- cases = {
- "stale-paths": [
- "SSL=true",
- "SSL_CERTFILE=/missing/cert.pem",
- "SSL_KEYFILE=/missing/key.pem",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING_API_KEY=sk-existing",
- ],
- "existing-paths": [
- "SSL=true",
- "SSL_CERTFILE=/some/cert.pem",
- "SSL_KEYFILE=/some/key.pem",
- ],
- }
- for case_name, env_lines in cases.items():
- case_dir = tmp_path / case_name
- case_dir.mkdir()
- write_text_lines(
- case_dir / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(case_dir / ".env", env_lines)
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{case_dir}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_base_flow
- """)
- generated_lines = (case_dir / ".env").read_text(encoding="utf-8").splitlines()
- for line in env_lines:
- assert line in generated_lines
- def test_env_base_flow_preserves_existing_compose_ssl_when_env_paths_are_stale(
- tmp_path: Path,
- ) -> None:
- """env-base should keep compose SSL wiring when inherited source paths no longer exist."""
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- tmp_path / ".env",
- [
- "SSL=true",
- "SSL_CERTFILE=/missing/cert.pem",
- "SSL_KEYFILE=/missing/key.pem",
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING=openai",
- "EMBEDDING_MODEL=text-embedding-3-small",
- "EMBEDDING_DIM=1536",
- "EMBEDDING_BINDING_HOST=https://api.openai.com/v1",
- "EMBEDDING_BINDING_API_KEY=sk-existing",
- ],
- )
- write_text_lines(
- tmp_path / "docker-compose.final.yml",
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " environment:",
- ' SSL_CERTFILE: "/app/data/certs/cert.pem"',
- ' SSL_KEYFILE: "/app/data/certs/key.pem"',
- " volumes:",
- ' - "./data/certs/cert.pem:/app/data/certs/cert.pem:ro"',
- ' - "./data/certs/key.pem:/app/data/certs/key.pem:ro"',
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- "Run LightRAG Server via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_base_flow
- """)
- generated_compose = (tmp_path / "docker-compose.final.yml").read_text(
- encoding="utf-8"
- )
- assert 'SSL_CERTFILE: "/app/data/certs/cert.pem"' in generated_compose
- assert 'SSL_KEYFILE: "/app/data/certs/key.pem"' in generated_compose
- assert "./data/certs/cert.pem:/app/data/certs/cert.pem:ro" in generated_compose
- assert "./data/certs/key.pem:/app/data/certs/key.pem:ro" in generated_compose
- def test_env_base_flow_preserves_existing_storage_images_on_rerun(
- tmp_path: Path,
- ) -> None:
- """env-base should preserve postgres and neo4j images from an existing compose rerun."""
- write_storage_setup_files(
- tmp_path,
- [
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=docker",
- "LIGHTRAG_SETUP_NEO4J_DEPLOYMENT=docker",
- ],
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " postgres:",
- " image: registry.example.com/postgres-for-rag:patched",
- " neo4j:",
- " image: registry.example.com/neo4j:custom",
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- host_cuda_available() {{ return 1; }}
- collect_llm_config() {{ :; }}
- collect_embedding_config() {{ :; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- *"The compose file will be created/updated. Continue?"*) return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- env_base_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- assert "image: registry.example.com/postgres-for-rag:patched" in result
- assert "image: registry.example.com/neo4j:custom" in result
- def test_env_base_flow_backs_up_legacy_generated_compose_before_rewrite(
- tmp_path: Path,
- ) -> None:
- """env-base should back up the active legacy compose file before regenerating final output."""
- legacy_compose = (
- "\n".join(["services:", " lightrag:", " image: prod/lightrag"]) + "\n"
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING=openai",
- "EMBEDDING_MODEL=text-embedding-3-small",
- "EMBEDDING_DIM=1536",
- "EMBEDDING_BINDING_HOST=https://api.openai.com/v1",
- "EMBEDDING_BINDING_API_KEY=sk-existing",
- ],
- )
- (tmp_path / "docker-compose.production.yml").write_text(
- legacy_compose, encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{
- case "$1" in
- "LLM API key: "|"Embedding API key: ") printf 'sk-test-key' ;;
- *) printf '%s' "$2" ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- "Run LightRAG Server via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_base_flow
- """)
- assert_single_compose_backup(tmp_path, legacy_compose)
- assert (tmp_path / "docker-compose.final.yml").exists()
- assert (tmp_path / "docker-compose.production.yml").read_text(
- encoding="utf-8"
- ) == legacy_compose
- def test_env_base_flow_deletes_compose_when_switching_lightrag_to_host(
- tmp_path: Path,
- ) -> None:
- """env-base should back up and delete compose when no Docker services remain."""
- existing_compose = (
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " redis:",
- " image: redis:latest",
- ]
- )
- + "\n"
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_RUNTIME_TARGET=compose",
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "EMBEDDING_BINDING=openai",
- "EMBEDDING_MODEL=text-embedding-3-small",
- "EMBEDDING_DIM=1536",
- "EMBEDDING_BINDING_HOST=https://api.openai.com/v1",
- "EMBEDDING_BINDING_API_KEY=sk-existing",
- ],
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- existing_compose, encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{
- case "$1" in
- "LLM API key: "|"Embedding API key: ") printf 'sk-test-key' ;;
- *) printf '%s' "$2" ;;
- esac
- }}
- confirm_default_no() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_base_flow
- """)
- assert_single_compose_backup(tmp_path, existing_compose)
- assert not (tmp_path / "docker-compose.final.yml").exists()
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert "LIGHTRAG_RUNTIME_TARGET=host" in generated_env
- def test_env_base_flow_generates_env_and_compose_files(tmp_path: Path) -> None:
- """env-base should generate `.env` and docker-compose output for hosted and local providers."""
- cases = {
- "openai": {
- "prompt_choice": "prompt_choice() { printf '%s' \"$2\"; }",
- "prompt_secret": """
- prompt_secret_until_valid_with_default() {
- case "$1" in
- "LLM API key: "|"Embedding API key: ") printf 'sk-test-key' ;;
- *) printf '%s' "$2" ;;
- esac
- }
- """,
- "env_assertions": [
- "LLM_BINDING=openai",
- "LLM_BINDING_API_KEY=sk-test-key",
- "EMBEDDING_BINDING_API_KEY=sk-test-key",
- ],
- },
- "ollama": {
- "prompt_choice": """
- prompt_choice() {
- case "$1" in
- "LLM provider") printf 'ollama' ;;
- "Embedding provider") printf 'ollama' ;;
- *) printf '%s' "$2" ;;
- esac
- }
- """,
- "prompt_secret": "prompt_secret_until_valid_with_default() { printf '%s' \"$2\"; }",
- "env_assertions": ["LLM_BINDING=ollama", "EMBEDDING_BINDING=ollama"],
- },
- }
- for case_name, case in cases.items():
- case_dir = tmp_path / case_name
- case_dir.mkdir()
- write_text_lines(
- case_dir / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- case_dir / "docker-compose.yml",
- (REPO_ROOT / "docker-compose.yml").read_text(encoding="utf-8").splitlines(),
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{case_dir}"
- {case['prompt_choice']}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- {case['prompt_secret']}
- confirm_default_no() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 1 ;;
- "Enable reranking?") return 1 ;;
- "Run LightRAG Server via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{
- case "$1" in
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_base_flow
- """)
- generated_env = (case_dir / ".env").read_text(encoding="utf-8")
- generated_compose = (case_dir / "docker-compose.final.yml").read_text(
- encoding="utf-8"
- )
- assert "LIGHTRAG_RUNTIME_TARGET=compose" in generated_env
- assert "LIGHTRAG_KV_STORAGE=JsonKVStorage" in generated_env
- assert "LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage" in generated_env
- assert "LIGHTRAG_GRAPH_STORAGE=NetworkXStorage" in generated_env
- assert "LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage" in generated_env
- for expected_line in case["env_assertions"]:
- assert expected_line in generated_env
- assert "services:" in generated_compose
- assert " lightrag:" in generated_compose
- assert "env_file:" not in generated_compose
- def test_env_base_flow_generates_validatable_env_on_clean_checkout(
- tmp_path: Path,
- ) -> None:
- """Fresh env-base output should include default storage selections and pass validation."""
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{
- case "$1" in
- "LLM API key: "|"Embedding API key: ") printf 'sk-test-key' ;;
- *) printf '%s' "$2" ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_base_flow
- validate_env_file
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert "LIGHTRAG_KV_STORAGE=JsonKVStorage" in generated_env
- assert "LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage" in generated_env
- assert "LIGHTRAG_GRAPH_STORAGE=NetworkXStorage" in generated_env
- assert "LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage" in generated_env
- assert "LIGHTRAG_RUNTIME_TARGET=host" in generated_env
- assert "LIGHTRAG_SETUP_PROFILE=" not in generated_env
- def test_env_storage_flow_drops_legacy_setup_profile_on_write(tmp_path: Path) -> None:
- """Modular flows should not persist LIGHTRAG_SETUP_PROFILE into regenerated .env files."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_SETUP_PROFILE=production",
- "LIGHTRAG_KV_STORAGE=JsonKVStorage",
- "LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage",
- "LIGHTRAG_GRAPH_STORAGE=NetworkXStorage",
- "LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="NetworkXStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- validate_required_variables() {{ return 0; }}
- confirm_default_yes() {{ return 0; }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert "LIGHTRAG_RUNTIME_TARGET=host" in generated_env
- assert "LIGHTRAG_SETUP_PROFILE=" not in generated_env
- def test_env_base_flow_registers_vllm_rerank_service_for_docker_deployment(
- tmp_path: Path,
- ) -> None:
- """Choosing docker rerank in env-base should add vllm-rerank to DOCKER_SERVICE_SET."""
- output = run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- reset_state
- collect_llm_config() {{ :; }}
- collect_embedding_config() {{ :; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 1 ;;
- "Enable reranking?") return 0 ;;
- "Run rerank service locally via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{ return 1; }}
- finalize_base_setup() {{
- if [[ -n "${{DOCKER_SERVICE_SET[vllm-rerank]+set}}" ]]; then
- printf 'HAS_VLLM_SERVICE=yes\\n'
- else
- printf 'HAS_VLLM_SERVICE=no\\n'
- fi
- }}
- env_base_flow
- """)
- values = parse_lines(output)
- assert values["HAS_VLLM_SERVICE"] == "yes"
- def test_env_base_flow_preserves_existing_vllm_rerank_settings_on_rerun(
- tmp_path: Path,
- ) -> None:
- """Rerunning env-base should keep saved local vLLM rerank model and port."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "RERANK_BINDING=cohere",
- "RERANK_MODEL=BAAI/custom-rerank",
- "RERANK_BINDING_HOST=http://localhost:9200/rerank",
- "RERANK_BINDING_API_KEY=rerank-key",
- "LIGHTRAG_SETUP_RERANK_PROVIDER=vllm",
- "VLLM_RERANK_MODEL=BAAI/custom-rerank",
- "VLLM_RERANK_PORT=9200",
- "VLLM_RERANK_DEVICE=cpu",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Enable reranking?") return 0 ;;
- "Run rerank service locally via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{ return 1; }}
- collect_embedding_config() {{ :; }}
- finalize_base_setup() {{
- printf 'RERANK_MODEL=%s\\n' "${{ENV_VALUES[RERANK_MODEL]}}"
- printf 'RERANK_BINDING_HOST=%s\\n' "${{ENV_VALUES[RERANK_BINDING_HOST]}}"
- printf 'VLLM_RERANK_MODEL=%s\\n' "${{ENV_VALUES[VLLM_RERANK_MODEL]}}"
- printf 'VLLM_RERANK_PORT=%s\\n' "${{ENV_VALUES[VLLM_RERANK_PORT]}}"
- }}
- env_base_flow
- """)
- assert values["RERANK_MODEL"] == "BAAI/custom-rerank"
- assert values["RERANK_BINDING_HOST"] == "http://localhost:9200/rerank"
- assert values["VLLM_RERANK_MODEL"] == "BAAI/custom-rerank"
- assert values["VLLM_RERANK_PORT"] == "9200"
- def test_env_base_flow_does_not_repeat_rerank_docker_prompt_when_declined(
- tmp_path: Path,
- ) -> None:
- """Declining rerank Docker at the outer prompt should switch to endpoint-based config."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "RERANK_BINDING=cohere",
- "RERANK_MODEL=BAAI/custom-rerank",
- "RERANK_BINDING_HOST=http://localhost:9200/rerank",
- "RERANK_BINDING_API_KEY=rerank-key",
- "LIGHTRAG_SETUP_RERANK_PROVIDER=vllm",
- "VLLM_RERANK_MODEL=BAAI/custom-rerank",
- "VLLM_RERANK_PORT=9200",
- "VLLM_RERANK_DEVICE=cpu",
- ],
- )
- output = run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- DOCKER_PROMPT_COUNT=0
- RERANK_MODEL_PROMPT_LOG="$REPO_ROOT/rerank-model-prompts.log"
- : > "$RERANK_MODEL_PROMPT_LOG"
- prompt_choice() {{
- case "$1" in
- "vLLM device")
- echo "unexpected vLLM device prompt" >&2
- return 91
- ;;
- *)
- printf '%s' "$2"
- ;;
- esac
- }}
- prompt_with_default() {{
- case "$1" in
- "vLLM rerank model")
- echo "unexpected vLLM rerank model prompt" >&2
- return 93
- ;;
- "Rerank model")
- printf 'hit
- ' >> "$RERANK_MODEL_PROMPT_LOG"
- printf '%s' "$2"
- return 0
- ;;
- "Rerank endpoint")
- printf '%s' "https://rerank.example.internal/rerank"
- return 0
- ;;
- esac
- printf '%s' "$2"
- }}
- prompt_until_valid() {{
- case "$1" in
- "vLLM rerank port")
- echo "unexpected vLLM rerank port prompt" >&2
- return 92
- ;;
- esac
- printf '%s' "$2"
- }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{ return 1; }}
- confirm_default_yes() {{
- case "$1" in
- "Enable reranking?") return 0 ;;
- "Run rerank service locally via Docker?")
- DOCKER_PROMPT_COUNT=$((DOCKER_PROMPT_COUNT + 1))
- return 1
- ;;
- *) return 1 ;;
- esac
- }}
- collect_embedding_config() {{ :; }}
- finalize_base_setup() {{
- local rerank_model_prompt_count
- rerank_model_prompt_count="$(wc -l < "$RERANK_MODEL_PROMPT_LOG" | tr -d '[:space:]')"
- printf 'DOCKER_PROMPT_COUNT=%s\\n' "$DOCKER_PROMPT_COUNT"
- printf 'RERANK_MODEL_PROMPT_COUNT=%s\\n' "$rerank_model_prompt_count"
- printf 'RERANK_BINDING_HOST=%s\\n' "${{ENV_VALUES[RERANK_BINDING_HOST]}}"
- printf 'LIGHTRAG_SETUP_RERANK_PROVIDER=%s\\n' "${{ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]:-}}"
- }}
- env_base_flow
- """)
- values = parse_lines(output)
- assert values["DOCKER_PROMPT_COUNT"] == "1"
- assert values["RERANK_MODEL_PROMPT_COUNT"] == "1"
- assert values["RERANK_BINDING_HOST"] == "https://rerank.example.internal/rerank"
- assert values["LIGHTRAG_SETUP_RERANK_PROVIDER"] == ""
- assert "vLLM uses the Cohere-compatible rerank API." not in output
- def test_env_base_flow_comments_rerank_setup_marker_when_switching_off_docker(
- tmp_path: Path,
- ) -> None:
- """Switching rerank from Docker to a non-Docker provider should drop the setup marker."""
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "RERANK_BINDING=cohere",
- "RERANK_MODEL=BAAI/custom-rerank",
- "RERANK_BINDING_HOST=http://localhost:9200/rerank",
- "RERANK_BINDING_API_KEY=rerank-key",
- "LIGHTRAG_SETUP_RERANK_PROVIDER=vllm",
- "VLLM_RERANK_MODEL=BAAI/custom-rerank",
- "VLLM_RERANK_PORT=9200",
- "VLLM_RERANK_DEVICE=cpu",
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{
- case "$1" in
- "Rerank provider") printf 'cohere' ;;
- *) printf '%s' "$2" ;;
- esac
- }}
- prompt_with_default() {{
- case "$1" in
- "Rerank endpoint") printf '%s' "https://api.cohere.com/v2/rerank" ;;
- *) printf '%s' "$2" ;;
- esac
- }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Run embedding model locally via Docker (vLLM)?") return 1 ;;
- "Run rerank service locally via Docker?") return 1 ;;
- "Run LightRAG Server via Docker?") return 1 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{
- case "$1" in
- "Enable reranking?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_base_flow
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- active_marker_lines = [
- line
- for line in generated_env.splitlines()
- if line.startswith("LIGHTRAG_SETUP_RERANK_PROVIDER=")
- ]
- assert "RERANK_BINDING=cohere" in generated_env
- assert active_marker_lines == []
- def test_env_base_flow_resets_remote_rerank_host_when_switching_to_vllm(
- tmp_path: Path,
- ) -> None:
- """Switching a remote reranker to local vLLM should restore localhost."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "RERANK_BINDING=jina",
- "RERANK_MODEL=jina-reranker-v2-base-multilingual",
- "RERANK_BINDING_HOST=https://api.jina.ai/v1/rerank",
- "RERANK_BINDING_API_KEY=jina-key",
- "VLLM_RERANK_PORT=9200",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Run rerank service locally via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{
- case "$1" in
- "Enable reranking?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- collect_embedding_config() {{ :; }}
- finalize_base_setup() {{
- printf 'RERANK_BINDING=%s\\n' "${{ENV_VALUES[RERANK_BINDING]}}"
- printf 'RERANK_BINDING_HOST=%s\\n' "${{ENV_VALUES[RERANK_BINDING_HOST]}}"
- printf 'LIGHTRAG_SETUP_RERANK_PROVIDER=%s\\n' "${{ENV_VALUES[LIGHTRAG_SETUP_RERANK_PROVIDER]}}"
- }}
- env_base_flow
- """)
- assert values["RERANK_BINDING"] == "cohere"
- assert values["RERANK_BINDING_HOST"] == "http://localhost:9200/rerank"
- assert values["LIGHTRAG_SETUP_RERANK_PROVIDER"] == "vllm"
- def test_env_base_flow_preserves_existing_vllm_rerank_device_on_gpu_host(
- tmp_path: Path,
- ) -> None:
- """Saved vLLM rerank CPU/GPU mode should win over auto-detected GPU defaults."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "RERANK_BINDING=cohere",
- "RERANK_MODEL=BAAI/custom-rerank",
- "RERANK_BINDING_HOST=http://localhost:9200/rerank",
- "RERANK_BINDING_API_KEY=rerank-key",
- "LIGHTRAG_SETUP_RERANK_PROVIDER=vllm",
- "VLLM_RERANK_MODEL=BAAI/custom-rerank",
- "VLLM_RERANK_PORT=9200",
- "VLLM_RERANK_DEVICE=cpu",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- nvidia-smi() {{ return 0; }}
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Enable reranking?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{
- case "$1" in
- "Run rerank service locally via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- collect_embedding_config() {{ :; }}
- finalize_base_setup() {{
- printf 'VLLM_RERANK_DEVICE=%s\\n' "${{ENV_VALUES[VLLM_RERANK_DEVICE]}}"
- }}
- env_base_flow
- """)
- assert values["VLLM_RERANK_DEVICE"] == "cpu"
- def test_env_base_flow_preserves_existing_vllm_rerank_cuda_device_on_rerun(
- tmp_path: Path,
- ) -> None:
- """Saved vLLM rerank CUDA mode should survive env-base reruns."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LLM_BINDING=openai",
- "LLM_MODEL=gpt-4o-mini",
- "LLM_BINDING_HOST=https://api.openai.com/v1",
- "LLM_BINDING_API_KEY=sk-existing",
- "RERANK_BINDING=cohere",
- "RERANK_MODEL=BAAI/custom-rerank",
- "RERANK_BINDING_HOST=http://localhost:9200/rerank",
- "RERANK_BINDING_API_KEY=rerank-key",
- "LIGHTRAG_SETUP_RERANK_PROVIDER=vllm",
- "VLLM_RERANK_MODEL=BAAI/custom-rerank",
- "VLLM_RERANK_PORT=9200",
- "VLLM_RERANK_DEVICE=cuda",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- nvidia-smi() {{ return 0; }}
- prompt_choice() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_no() {{
- case "$1" in
- "Enable reranking?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_default_yes() {{
- case "$1" in
- "Run rerank service locally via Docker?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- collect_embedding_config() {{ :; }}
- finalize_base_setup() {{
- printf 'VLLM_RERANK_DEVICE=%s\\n' "${{ENV_VALUES[VLLM_RERANK_DEVICE]}}"
- }}
- env_base_flow
- """)
- assert values["VLLM_RERANK_DEVICE"] == "cuda"
- def test_env_storage_flow_applies_selected_storage_backends(tmp_path: Path) -> None:
- """env-storage should honor the selected backends without auto-applying a preset."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_KV_STORAGE=JsonKVStorage",
- "LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage",
- "LIGHTRAG_GRAPH_STORAGE=NetworkXStorage",
- "LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage",
- "LLM_BINDING=ollama",
- "LLM_MODEL=llama3.2:latest",
- "LLM_BINDING_HOST=http://localhost:11434",
- "EMBEDDING_BINDING=ollama",
- "EMBEDDING_MODEL=nomic-embed-text:latest",
- "EMBEDDING_DIM=768",
- "EMBEDDING_BINDING_HOST=http://localhost:11434",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="RedisKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="MilvusVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="Neo4JStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="RedisDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- collect_docker_image_tags() {{ :; }}
- finalize_storage_setup() {{
- printf 'LIGHTRAG_KV_STORAGE=%s\\n' "${{ENV_VALUES[LIGHTRAG_KV_STORAGE]}}"
- printf 'LIGHTRAG_VECTOR_STORAGE=%s\\n' "${{ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]}}"
- printf 'LIGHTRAG_GRAPH_STORAGE=%s\\n' "${{ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]}}"
- printf 'LIGHTRAG_DOC_STATUS_STORAGE=%s\\n' "${{ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]}}"
- printf 'LLM_BINDING=%s\\n' "${{ENV_VALUES[LLM_BINDING]}}"
- printf 'EMBEDDING_BINDING=%s\\n' "${{ENV_VALUES[EMBEDDING_BINDING]}}"
- }}
- env_storage_flow
- """)
- assert values["LIGHTRAG_KV_STORAGE"] == "RedisKVStorage"
- assert values["LIGHTRAG_VECTOR_STORAGE"] == "MilvusVectorDBStorage"
- assert values["LIGHTRAG_GRAPH_STORAGE"] == "Neo4JStorage"
- assert values["LIGHTRAG_DOC_STATUS_STORAGE"] == "RedisDocStatusStorage"
- assert values["LLM_BINDING"] == "ollama"
- assert values["EMBEDDING_BINDING"] == "ollama"
- def test_env_storage_flow_reuses_saved_storage_docker_default(tmp_path: Path) -> None:
- """Saved storage deployment metadata should drive the next Docker prompt default."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=docker",
- "LIGHTRAG_KV_STORAGE=PGKVStorage",
- "LIGHTRAG_VECTOR_STORAGE=PGVectorStorage",
- "LIGHTRAG_GRAPH_STORAGE=PGGraphStorage",
- "LIGHTRAG_DOC_STATUS_STORAGE=PGDocStatusStorage",
- ],
- )
- values = run_bash_lines(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="PGVectorStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="PGGraphStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- REQUIRED_DB_TYPES[postgresql]=1
- }}
- collect_postgres_config() {{
- printf 'POSTGRES_DEFAULT_DOCKER=%s\\n' "$1"
- }}
- finalize_storage_setup() {{ :; }}
- env_storage_flow
- """)
- assert values["POSTGRES_DEFAULT_DOCKER"] == "yes"
- def test_env_storage_flow_writes_storage_docker_marker_for_selected_service(
- tmp_path: Path,
- ) -> None:
- """Choosing a bundled storage service should persist its deployment marker in `.env`."""
- write_text_lines(
- tmp_path / ".env", ["LLM_BINDING=ollama", "EMBEDDING_BINDING=ollama"]
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.yml").write_text(
- (REPO_ROOT / "docker-compose.yml").read_text(encoding="utf-8"), encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="PGVectorStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="PGGraphStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- REQUIRED_DB_TYPES[postgresql]=1
- }}
- collect_postgres_config() {{
- add_docker_service "postgres"
- ENV_VALUES[POSTGRES_HOST]="localhost"
- ENV_VALUES[POSTGRES_PORT]="5432"
- ENV_VALUES[POSTGRES_USER]="lightrag"
- ENV_VALUES[POSTGRES_PASSWORD]="secret"
- ENV_VALUES[POSTGRES_DATABASE]="lightrag"
- }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert any(
- line == "LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=docker"
- for line in generated_env.splitlines()
- )
- assert "LIGHTRAG_RUNTIME_TARGET=compose" in generated_env
- def test_env_storage_flow_writes_opensearch_docker_marker_for_selected_service(
- tmp_path: Path,
- ) -> None:
- """Choosing bundled OpenSearch should persist its deployment marker in `.env`."""
- write_text_lines(
- tmp_path / ".env", ["LLM_BINDING=ollama", "EMBEDDING_BINDING=ollama"]
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.yml").write_text(
- (REPO_ROOT / "docker-compose.yml").read_text(encoding="utf-8"), encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="OpenSearchKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="OpenSearchVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="OpenSearchGraphStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="OpenSearchDocStatusStorage"
- REQUIRED_DB_TYPES[opensearch]=1
- }}
- collect_opensearch_config() {{
- add_docker_service "opensearch"
- ENV_VALUES[OPENSEARCH_HOSTS]="localhost:9200"
- ENV_VALUES[OPENSEARCH_USER]="admin"
- ENV_VALUES[OPENSEARCH_PASSWORD]="secret"
- ENV_VALUES[OPENSEARCH_USE_SSL]="true"
- ENV_VALUES[OPENSEARCH_VERIFY_CERTS]="false"
- }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert any(
- line == "LIGHTRAG_SETUP_OPENSEARCH_DEPLOYMENT=docker"
- for line in generated_env.splitlines()
- )
- assert "LIGHTRAG_RUNTIME_TARGET=compose" in generated_env
- def test_env_storage_flow_removes_storage_docker_marker_when_switching_to_host(
- tmp_path: Path,
- ) -> None:
- """Choosing a host-managed storage backend should clear a previously saved Docker marker."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=docker",
- "LIGHTRAG_KV_STORAGE=PGKVStorage",
- "LIGHTRAG_VECTOR_STORAGE=PGVectorStorage",
- "LIGHTRAG_GRAPH_STORAGE=PGGraphStorage",
- "LIGHTRAG_DOC_STATUS_STORAGE=PGDocStatusStorage",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="PGVectorStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="PGGraphStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- REQUIRED_DB_TYPES[postgresql]=1
- }}
- collect_postgres_config() {{
- ENV_VALUES[POSTGRES_HOST]="localhost"
- ENV_VALUES[POSTGRES_PORT]="5432"
- ENV_VALUES[POSTGRES_USER]="lightrag"
- ENV_VALUES[POSTGRES_PASSWORD]="secret"
- ENV_VALUES[POSTGRES_DATABASE]="lightrag"
- }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert not any(
- line.startswith("LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=")
- for line in generated_env.splitlines()
- )
- assert "LIGHTRAG_RUNTIME_TARGET=host" in generated_env
- def test_env_storage_flow_clears_unused_storage_docker_markers(tmp_path: Path) -> None:
- """Markers for databases no longer required by the selected backends should be removed."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=docker",
- "LIGHTRAG_KV_STORAGE=PGKVStorage",
- "LIGHTRAG_VECTOR_STORAGE=PGVectorStorage",
- "LIGHTRAG_GRAPH_STORAGE=PGGraphStorage",
- "LIGHTRAG_DOC_STATUS_STORAGE=PGDocStatusStorage",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="NetworkXStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert not any(
- line.startswith("LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=")
- for line in generated_env.splitlines()
- )
- assert "LIGHTRAG_KV_STORAGE=JsonKVStorage" in generated_env
- def test_env_storage_flow_generates_env_and_compose_files(tmp_path: Path) -> None:
- """env-storage should write updated .env and a docker-compose.final.yml."""
- env_file = tmp_path / ".env"
- env_file.write_text(
- "\n".join(
- [
- "LLM_BINDING=ollama",
- "EMBEDDING_BINDING=ollama",
- "AUTH_ACCOUNTS=admin:secret",
- "TOKEN_SECRET=jwt-secret",
- "WHITELIST_PATHS=/health",
- ]
- )
- + "\n",
- encoding="utf-8",
- )
- (tmp_path / "env.example").write_text(
- (REPO_ROOT / "env.example").read_text(encoding="utf-8"), encoding="utf-8"
- )
- (tmp_path / "docker-compose.yml").write_text(
- (REPO_ROOT / "docker-compose.yml").read_text(encoding="utf-8"), encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="MilvusVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="Neo4JStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- add_docker_service "postgres"
- add_docker_service "neo4j"
- }}
- collect_database_config() {{ :; }}
- collect_docker_image_tags() {{ :; }}
- validate_required_variables() {{ return 0; }}
- prompt_secret_with_default() {{ printf '%s' "$2"; }}
- prompt_secret_until_valid_with_default() {{ printf '%s' "$2"; }}
- confirm_default_yes() {{
- case "$1" in
- *) return 1 ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- generated_compose = (tmp_path / "docker-compose.final.yml").read_text(
- encoding="utf-8"
- )
- assert "LIGHTRAG_KV_STORAGE=PGKVStorage" in generated_env
- assert "LIGHTRAG_GRAPH_STORAGE=Neo4JStorage" in generated_env
- assert "LLM_BINDING=ollama" in generated_env
- assert "services:" in generated_compose
- assert " lightrag:" in generated_compose
- assert "env_file:" not in generated_compose
- def test_env_storage_flow_uses_host_defaults_for_empty_postgres_docker_credentials(
- tmp_path: Path,
- ) -> None:
- """env-storage should write the host-mode postgres defaults when old `.env` creds are empty."""
- env_file = tmp_path / ".env"
- env_file.write_text(
- "\n".join(
- [
- "LLM_BINDING=ollama",
- "EMBEDDING_BINDING=ollama",
- "AUTH_ACCOUNTS=admin:secret",
- "TOKEN_SECRET=jwt-secret",
- "WHITELIST_PATHS=/health",
- "POSTGRES_USER=",
- "POSTGRES_PASSWORD=",
- "POSTGRES_DATABASE=",
- ]
- )
- + "\n",
- encoding="utf-8",
- )
- (tmp_path / "env.example").write_text(
- (REPO_ROOT / "env.example").read_text(encoding="utf-8"), encoding="utf-8"
- )
- (tmp_path / "docker-compose.yml").write_text(
- (REPO_ROOT / "docker-compose.yml").read_text(encoding="utf-8"), encoding="utf-8"
- )
- run_bash(
- f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- PROMPT_LOG_FILE="$(mktemp)"
- : > "$PROMPT_LOG_FILE"
- select_storage_backends() {{
- REQUIRED_DB_TYPES[postgresql]=1
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="PGVectorStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="PGGraphStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- }}
- confirm_default_no() {{
- if [[ "$1" == "Run PostgreSQL locally via Docker?" ]]; then
- return 0
- fi
- return 1
- }}
- confirm_default_yes() {{ return 0; }}
- confirm_required_yes_no() {{ return 0; }}
- prompt_with_default() {{
- printf '%s\\n' "$1" >> "$PROMPT_LOG_FILE"
- case "$1" in
- "PostgreSQL host") printf 'localhost' ;;
- *) printf '%s' "$2" ;;
- esac
- }}
- prompt_secret_with_default() {{
- printf 'secret:%s\\n' "$1" >> "$PROMPT_LOG_FILE"
- printf '%s' "$2"
- }}
- env_storage_flow
- printf 'PROMPT_LOG=%s\\n' "$(paste -sd '|' "$PROMPT_LOG_FILE")\"
- """,
- cwd=tmp_path,
- )
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- generated_compose = (tmp_path / "docker-compose.final.yml").read_text(
- encoding="utf-8"
- )
- assert "POSTGRES_USER=rag" in generated_env
- assert "POSTGRES_PASSWORD=rag" in generated_env
- assert "POSTGRES_DATABASE=lightrag" in generated_env
- assert 'POSTGRES_USER: "rag"' in generated_compose
- assert 'POSTGRES_PASSWORD: "rag"' in generated_compose
- assert 'POSTGRES_DB: "lightrag"' in generated_compose
- def test_env_storage_flow_preserves_existing_postgres_image_during_rewrite(
- tmp_path: Path,
- ) -> None:
- """Postgres env rewrites should keep an existing custom image."""
- write_storage_setup_files(
- tmp_path,
- [
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "POSTGRES_USER=rag",
- "POSTGRES_PASSWORD=rag",
- "POSTGRES_DATABASE=rag",
- ],
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " postgres:",
- " image: registry.example.com/postgres-for-rag:patched",
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- REQUIRED_DB_TYPES[postgresql]=1
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="PGVectorStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="PGGraphStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- }}
- collect_database_config() {{
- if [[ "$1" == "postgresql" ]]; then
- add_docker_service "postgres"
- ENV_VALUES[POSTGRES_USER]="updated-user"
- fi
- }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- assert "image: registry.example.com/postgres-for-rag:patched" in result
- assert 'POSTGRES_USER: "updated-user"' in result
- assert 'POSTGRES_DB: "rag"' in result
- def test_env_storage_flow_preserves_existing_neo4j_image_during_rewrite(
- tmp_path: Path,
- ) -> None:
- """Neo4j database rewrites should keep an existing custom image."""
- write_storage_setup_files(
- tmp_path,
- [
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "NEO4J_USERNAME=neo4j",
- "NEO4J_PASSWORD=neo4j-password",
- "NEO4J_DATABASE=neo4j",
- ],
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " neo4j:",
- " image: registry.example.com/neo4j:custom",
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- REQUIRED_DB_TYPES[neo4j]=1
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="Neo4JStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{
- if [[ "$1" == "neo4j" ]]; then
- add_docker_service "neo4j"
- ENV_VALUES[NEO4J_DATABASE]="updated-database"
- fi
- }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- assert "image: registry.example.com/neo4j:custom" in result
- assert 'NEO4J_dbms_default__database: "updated-database"' in result
- def test_env_storage_flow_preserves_existing_postgres_and_neo4j_images_on_rewrite(
- tmp_path: Path,
- ) -> None:
- """Concurrent postgres and neo4j rewrites should preserve both custom images."""
- write_storage_setup_files(
- tmp_path,
- [
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "POSTGRES_USER=rag",
- "POSTGRES_PASSWORD=rag",
- "POSTGRES_DATABASE=rag",
- "NEO4J_USERNAME=neo4j",
- "NEO4J_PASSWORD=neo4j-password",
- "NEO4J_DATABASE=neo4j",
- ],
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " postgres:",
- " image: registry.example.com/postgres-for-rag:patched",
- " neo4j:",
- " image: registry.example.com/neo4j:custom",
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- REQUIRED_DB_TYPES[postgresql]=1
- REQUIRED_DB_TYPES[neo4j]=1
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="PGVectorStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="Neo4JStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- }}
- collect_database_config() {{
- case "$1" in
- postgresql)
- add_docker_service "postgres"
- ENV_VALUES[POSTGRES_USER]="updated-user"
- ;;
- neo4j)
- add_docker_service "neo4j"
- ENV_VALUES[NEO4J_DATABASE]="updated-database"
- ;;
- esac
- }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- assert "image: registry.example.com/postgres-for-rag:patched" in result
- assert "image: registry.example.com/neo4j:custom" in result
- assert 'POSTGRES_USER: "updated-user"' in result
- assert 'NEO4J_dbms_default__database: "updated-database"' in result
- def test_env_storage_flow_uses_template_image_when_existing_service_has_no_image(
- tmp_path: Path,
- ) -> None:
- """A rewritten service without an existing image should fall back to the template image."""
- write_storage_setup_files(
- tmp_path,
- [
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "POSTGRES_USER=rag",
- "POSTGRES_PASSWORD=rag",
- "POSTGRES_DATABASE=rag",
- ],
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " postgres:",
- " environment:",
- ' LEGACY_SETTING: "1"',
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- REQUIRED_DB_TYPES[postgresql]=1
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="PGVectorStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="PGGraphStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- }}
- collect_database_config() {{
- if [[ "$1" == "postgresql" ]]; then
- add_docker_service "postgres"
- ENV_VALUES[POSTGRES_USER]="updated-user"
- fi
- }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- assert "image: gzdaniel/postgres-for-rag:pg18-age-pgvector" in result
- assert 'POSTGRES_USER: "updated-user"' in result
- def test_env_storage_flow_force_rewrite_drops_preserved_storage_images(
- tmp_path: Path,
- ) -> None:
- """FORCE_REWRITE_COMPOSE should bypass preserved postgres and neo4j images."""
- write_storage_setup_files(
- tmp_path,
- [
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "POSTGRES_USER=rag",
- "POSTGRES_PASSWORD=rag",
- "POSTGRES_DATABASE=rag",
- "NEO4J_USERNAME=neo4j",
- "NEO4J_PASSWORD=neo4j-password",
- "NEO4J_DATABASE=neo4j",
- ],
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " postgres:",
- " image: registry.example.com/postgres-for-rag:patched",
- " neo4j:",
- " image: registry.example.com/neo4j:custom",
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- FORCE_REWRITE_COMPOSE="yes"
- select_storage_backends() {{
- REQUIRED_DB_TYPES[postgresql]=1
- REQUIRED_DB_TYPES[neo4j]=1
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="PGKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="PGVectorStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="Neo4JStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="PGDocStatusStorage"
- }}
- collect_database_config() {{
- case "$1" in
- postgresql)
- add_docker_service "postgres"
- ENV_VALUES[POSTGRES_USER]="updated-user"
- ;;
- neo4j)
- add_docker_service "neo4j"
- ENV_VALUES[NEO4J_DATABASE]="updated-database"
- ;;
- esac
- }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- assert "image: gzdaniel/postgres-for-rag:pg18-age-pgvector" in result
- assert "image: neo4j:5-community" in result
- assert "registry.example.com/postgres-for-rag:patched" not in result
- assert "registry.example.com/neo4j:custom" not in result
- def test_env_storage_flow_backs_up_existing_compose_before_rewrite(
- tmp_path: Path,
- ) -> None:
- """env-storage should back up the current compose file before rewriting it."""
- existing_compose = (
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " environment:",
- ' LEGACY_SETTING: "1"',
- " postgres:",
- " image: gzdaniel/postgres-for-rag:pg18-age-pgvector",
- ]
- )
- + "\n"
- )
- write_text_lines(
- tmp_path / ".env", ["LLM_BINDING=openai", "EMBEDDING_BINDING=openai"]
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- existing_compose, encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="NetworkXStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- assert_single_compose_backup(tmp_path, existing_compose)
- assert (tmp_path / "docker-compose.final.yml").exists()
- def test_env_storage_flow_keeps_compose_mode_for_user_sidecars(tmp_path: Path) -> None:
- """env-storage should keep LightRAG in Docker when user sidecars are present."""
- existing_compose = (
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " environment:",
- ' LEGACY_SETTING: "1"',
- " sidecar:",
- " image: busybox",
- ]
- )
- + "\n"
- )
- write_text_lines(
- tmp_path / ".env", ["LLM_BINDING=openai", "EMBEDDING_BINDING=openai"]
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- existing_compose, encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="NetworkXStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{ return 0; }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert_single_compose_backup(tmp_path, existing_compose)
- assert " lightrag:" in result
- assert " sidecar:" in result
- assert "LIGHTRAG_RUNTIME_TARGET=compose" in generated_env
- def test_env_storage_flow_preserves_mongodb_docker_marker_for_atlas_local_vector_storage(
- tmp_path: Path,
- ) -> None:
- """MongoDB Atlas Local vector storage should preserve the bundled Docker deployment marker."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_SETUP_MONGODB_DEPLOYMENT=docker",
- "LIGHTRAG_KV_STORAGE=MongoKVStorage",
- "LIGHTRAG_VECTOR_STORAGE=MongoVectorDBStorage",
- "LIGHTRAG_GRAPH_STORAGE=MongoGraphStorage",
- "LIGHTRAG_DOC_STATUS_STORAGE=MongoDocStatusStorage",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="MongoKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="MongoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="MongoGraphStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="MongoDocStatusStorage"
- REQUIRED_DB_TYPES[mongodb]=1
- }}
- prompt_until_valid() {{ printf '%s' "$2"; }}
- prompt_with_default() {{ printf '%s' "$2"; }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{ return 0; }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert "LIGHTRAG_SETUP_MONGODB_DEPLOYMENT=docker" in generated_env
- assert "MONGO_URI=mongodb://localhost:27017/?directConnection=true" in generated_env
- def test_env_storage_flow_preserves_existing_compose_ssl_when_env_paths_are_stale(
- tmp_path: Path,
- ) -> None:
- """env-storage should keep compose SSL wiring when inherited source paths no longer exist."""
- write_text_lines(
- tmp_path / ".env",
- [
- "SSL=true",
- "SSL_CERTFILE=/missing/cert.pem",
- "SSL_KEYFILE=/missing/key.pem",
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "LIGHTRAG_KV_STORAGE=JsonKVStorage",
- "LIGHTRAG_VECTOR_STORAGE=NanoVectorDBStorage",
- "LIGHTRAG_GRAPH_STORAGE=NetworkXStorage",
- "LIGHTRAG_DOC_STATUS_STORAGE=JsonDocStatusStorage",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- tmp_path / "docker-compose.final.yml",
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " environment:",
- ' SSL_CERTFILE: "/app/data/certs/cert.pem"',
- ' SSL_KEYFILE: "/app/data/certs/key.pem"',
- " volumes:",
- ' - "./data/certs/cert.pem:/app/data/certs/cert.pem:ro"',
- ' - "./data/certs/key.pem:/app/data/certs/key.pem:ro"',
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="NetworkXStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- validate_required_variables() {{ return 0; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- generated_compose = (tmp_path / "docker-compose.final.yml").read_text(
- encoding="utf-8"
- )
- assert 'SSL_CERTFILE: "/app/data/certs/cert.pem"' in generated_compose
- assert 'SSL_KEYFILE: "/app/data/certs/key.pem"' in generated_compose
- assert "./data/certs/cert.pem:/app/data/certs/cert.pem:ro" in generated_compose
- assert "./data/certs/key.pem:/app/data/certs/key.pem:ro" in generated_compose
- def test_env_server_flow_preserves_existing_compose_ssl_when_env_paths_are_stale(
- tmp_path: Path,
- ) -> None:
- """env-server should keep compose SSL wiring and variable-based port publishing."""
- write_text_lines(
- tmp_path / ".env",
- [
- "SSL=true",
- "SSL_CERTFILE=/missing/cert.pem",
- "SSL_KEYFILE=/missing/key.pem",
- "HOST=0.0.0.0",
- "PORT=9621",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- tmp_path / "docker-compose.final.yml",
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " environment:",
- ' SSL_CERTFILE: "/app/data/certs/cert.pem"',
- ' SSL_KEYFILE: "/app/data/certs/key.pem"',
- " volumes:",
- ' - "./data/certs/cert.pem:/app/data/certs/cert.pem:ro"',
- ' - "./data/certs/key.pem:/app/data/certs/key.pem:ro"',
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- collect_server_config() {{
- ENV_VALUES[HOST]="0.0.0.0"
- ENV_VALUES[PORT]="8080"
- }}
- collect_security_config() {{ :; }}
- collect_ssl_config() {{ :; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_server_flow
- """)
- generated_compose = (tmp_path / "docker-compose.final.yml").read_text(
- encoding="utf-8"
- )
- assert 'SSL_CERTFILE: "/app/data/certs/cert.pem"' in generated_compose
- assert 'SSL_KEYFILE: "/app/data/certs/key.pem"' in generated_compose
- assert "./data/certs/cert.pem:/app/data/certs/cert.pem:ro" in generated_compose
- assert "./data/certs/key.pem:/app/data/certs/key.pem:ro" in generated_compose
- assert 'PORT: "9621"' in generated_compose
- assert ' - "${HOST:-0.0.0.0}:${PORT:-9621}:9621"' in generated_compose
- def test_env_server_flow_backs_up_existing_compose_before_rewrite(
- tmp_path: Path,
- ) -> None:
- """env-server should back up the current compose file before rewriting it."""
- existing_compose = (
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " environment:",
- ' PORT: "9621"',
- ]
- )
- + "\n"
- )
- write_text_lines(tmp_path / ".env", ["HOST=0.0.0.0", "PORT=9621"])
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- existing_compose, encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- collect_server_config() {{
- ENV_VALUES[HOST]="0.0.0.0"
- ENV_VALUES[PORT]="8080"
- }}
- collect_security_config() {{ :; }}
- collect_ssl_config() {{ :; }}
- validate_sensitive_env_literals() {{ return 0; }}
- validate_security_config() {{ return 0; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_server_flow
- """)
- assert_single_compose_backup(tmp_path, existing_compose)
- assert (tmp_path / "docker-compose.final.yml").read_text(
- encoding="utf-8"
- ) != existing_compose
- def test_env_storage_flow_drops_stale_vllm_services_missing_from_env_markers(
- tmp_path: Path,
- ) -> None:
- """env-storage should remove stale vLLM services unless `.env` still marks them as Docker-managed."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_RUNTIME_TARGET=compose",
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "RERANK_BINDING=cohere",
- "LIGHTRAG_SETUP_RERANK_PROVIDER=cohere",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " vllm-embed:",
- " image: vllm/vllm-openai:latest",
- " vllm-rerank:",
- " image: vllm/vllm-openai:latest",
- "volumes:",
- " vllm_embed_cache:",
- " vllm_rerank_cache:",
- ]
- )
- + "\n",
- encoding="utf-8",
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="NetworkXStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{ return 1; }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert " vllm-embed:" not in result
- assert " vllm-rerank:" not in result
- assert "vllm_embed_cache:" not in result
- assert "vllm_rerank_cache:" not in result
- assert "LIGHTRAG_RUNTIME_TARGET=compose" in generated_env
- def test_env_storage_flow_preserves_vllm_services_marked_in_env(tmp_path: Path) -> None:
- """env-storage should restore vLLM services from `.env` markers even without old compose entries."""
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_RUNTIME_TARGET=compose",
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "EMBEDDING_BINDING_HOST=http://localhost:8001/v1",
- "LIGHTRAG_SETUP_EMBEDDING_PROVIDER=vllm",
- "VLLM_EMBED_MODEL=BAAI/bge-m3",
- "VLLM_EMBED_PORT=8001",
- "VLLM_EMBED_DEVICE=cpu",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- write_text_lines(
- tmp_path / "docker-compose.final.yml",
- ["services:", " lightrag:", " image: example/lightrag:test"],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="NetworkXStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{ return 1; }}
- confirm_default_no() {{ return 1; }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert " vllm-embed:" in result
- assert "LIGHTRAG_RUNTIME_TARGET=compose" in generated_env
- def test_env_storage_flow_deletes_compose_when_switching_lightrag_to_host(
- tmp_path: Path,
- ) -> None:
- """env-storage should back up and delete compose when no Docker services remain."""
- existing_compose = (
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " redis:",
- " image: redis:latest",
- ]
- )
- + "\n"
- )
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_RUNTIME_TARGET=compose",
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- existing_compose, encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- select_storage_backends() {{
- ENV_VALUES[LIGHTRAG_KV_STORAGE]="JsonKVStorage"
- ENV_VALUES[LIGHTRAG_VECTOR_STORAGE]="NanoVectorDBStorage"
- ENV_VALUES[LIGHTRAG_GRAPH_STORAGE]="NetworkXStorage"
- ENV_VALUES[LIGHTRAG_DOC_STATUS_STORAGE]="JsonDocStatusStorage"
- }}
- collect_database_config() {{ :; }}
- validate_required_variables() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- confirm_default_yes() {{ return 1; }}
- confirm_default_no() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_storage_flow
- """)
- assert_single_compose_backup(tmp_path, existing_compose)
- assert not (tmp_path / "docker-compose.final.yml").exists()
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert "LIGHTRAG_RUNTIME_TARGET=host" in generated_env
- def test_env_server_flow_preserves_existing_storage_images_on_compose_rewrite(
- tmp_path: Path,
- ) -> None:
- """env-server should preserve postgres and neo4j images when a compose rewrite is triggered."""
- original_compose_lines = [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " environment:",
- ' PORT: "9621"',
- " postgres:",
- " image: registry.example.com/postgres-for-rag:patched",
- " neo4j:",
- " image: registry.example.com/neo4j:custom",
- ]
- original_compose_content = "\n".join(original_compose_lines) + "\n"
- write_storage_setup_files(
- tmp_path,
- [
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "HOST=0.0.0.0",
- "PORT=9621",
- "LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=docker",
- "LIGHTRAG_SETUP_NEO4J_DEPLOYMENT=docker",
- ],
- original_compose_lines,
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- collect_server_config() {{
- ENV_VALUES[HOST]="0.0.0.0"
- ENV_VALUES[PORT]="8080"
- }}
- collect_security_config() {{ :; }}
- collect_ssl_config() {{ :; }}
- confirm_default_yes() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 1 ;;
- *) return 0 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- validate_auth_accounts_runtime_config() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- env_server_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- assert_single_compose_backup(tmp_path, expected_content=original_compose_content)
- assert "image: registry.example.com/postgres-for-rag:patched" in result
- assert "image: registry.example.com/neo4j:custom" in result
- def test_env_server_flow_preserves_existing_storage_images_on_env_only_rerun(
- tmp_path: Path,
- ) -> None:
- """env-server write_env_only path should leave custom storage images untouched."""
- write_storage_setup_files(
- tmp_path,
- [
- "LLM_BINDING=openai",
- "EMBEDDING_BINDING=openai",
- "LIGHTRAG_SETUP_POSTGRES_DEPLOYMENT=docker",
- "LIGHTRAG_SETUP_NEO4J_DEPLOYMENT=docker",
- ],
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " postgres:",
- " image: registry.example.com/postgres-for-rag:patched",
- " neo4j:",
- " image: registry.example.com/neo4j:custom",
- ],
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- collect_server_config() {{ :; }}
- collect_security_config() {{ :; }}
- collect_ssl_config() {{ :; }}
- confirm_required_yes_no() {{ return 0; }}
- validate_sensitive_env_literals() {{ return 0; }}
- validate_auth_accounts_runtime_config() {{ return 0; }}
- validate_mongo_vector_storage_config() {{ return 0; }}
- env_server_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- assert "image: registry.example.com/postgres-for-rag:patched" in result
- assert "image: registry.example.com/neo4j:custom" in result
- def test_env_server_flow_deletes_compose_when_switching_lightrag_to_host(
- tmp_path: Path,
- ) -> None:
- """env-server should back up and delete compose when no managed or sidecar services remain."""
- existing_compose = (
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " redis:",
- " image: redis:latest",
- ]
- )
- + "\n"
- )
- write_text_lines(
- tmp_path / ".env",
- ["LIGHTRAG_RUNTIME_TARGET=compose", "HOST=0.0.0.0", "PORT=9621"],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- existing_compose, encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- collect_server_config() {{
- ENV_VALUES[HOST]="0.0.0.0"
- ENV_VALUES[PORT]="8080"
- }}
- collect_security_config() {{ :; }}
- collect_ssl_config() {{ :; }}
- validate_sensitive_env_literals() {{ return 0; }}
- validate_security_config() {{ return 0; }}
- confirm_default_yes() {{ return 1; }}
- confirm_default_no() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_server_flow
- """)
- assert_single_compose_backup(tmp_path, existing_compose)
- assert not (tmp_path / "docker-compose.final.yml").exists()
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert "LIGHTRAG_RUNTIME_TARGET=host" in generated_env
- def test_env_server_flow_keeps_compose_mode_for_user_sidecars(tmp_path: Path) -> None:
- """env-server should keep LightRAG in Docker when compose still carries user sidecars."""
- existing_compose = (
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " sidecar:",
- " image: busybox",
- ]
- )
- + "\n"
- )
- write_text_lines(
- tmp_path / ".env",
- ["LIGHTRAG_RUNTIME_TARGET=compose", "HOST=0.0.0.0", "PORT=9621"],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- existing_compose, encoding="utf-8"
- )
- run_bash(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- collect_server_config() {{
- ENV_VALUES[HOST]="0.0.0.0"
- ENV_VALUES[PORT]="8080"
- }}
- collect_security_config() {{ :; }}
- collect_ssl_config() {{ :; }}
- validate_sensitive_env_literals() {{ return 0; }}
- validate_security_config() {{ return 0; }}
- confirm_default_yes() {{ return 0; }}
- confirm_required_yes_no() {{ return 0; }}
- env_server_flow
- """)
- result = (tmp_path / "docker-compose.final.yml").read_text(encoding="utf-8")
- generated_env = (tmp_path / ".env").read_text(encoding="utf-8")
- assert " sidecar:" in result
- assert " lightrag:" in result
- assert "LIGHTRAG_RUNTIME_TARGET=compose" in generated_env
- def test_env_server_flow_rejects_invalid_ssl_cert_when_switching_to_host(
- tmp_path: Path,
- ) -> None:
- """finalize_server_setup should reject a missing SSL cert even when switching to host mode."""
- existing_compose = (
- "\n".join(
- [
- "services:",
- " lightrag:",
- " image: example/lightrag:test",
- " redis:",
- " image: redis:latest",
- ]
- )
- + "\n"
- )
- write_text_lines(
- tmp_path / ".env",
- [
- "LIGHTRAG_RUNTIME_TARGET=compose",
- "HOST=0.0.0.0",
- "PORT=9621",
- "SSL=true",
- "SSL_CERTFILE=/nonexistent/cert.pem",
- "SSL_KEYFILE=/nonexistent/key.pem",
- ],
- )
- write_text_lines(
- tmp_path / "env.example",
- (REPO_ROOT / "env.example").read_text(encoding="utf-8").splitlines(),
- )
- (tmp_path / "docker-compose.final.yml").write_text(
- existing_compose, encoding="utf-8"
- )
- result = run_bash_process(f"""
- set -euo pipefail
- source "{REPO_ROOT}/scripts/setup/setup.sh"
- REPO_ROOT="{tmp_path}"
- collect_server_config() {{ :; }}
- collect_security_config() {{ :; }}
- collect_ssl_config() {{
- ENV_VALUES[SSL]="true"
- SSL_CERT_SOURCE_PATH="/nonexistent/cert.pem"
- SSL_KEY_SOURCE_PATH="/nonexistent/key.pem"
- }}
- validate_sensitive_env_literals() {{ return 0; }}
- validate_security_config() {{ return 0; }}
- confirm_default_yes() {{ return 1; }}
- confirm_default_no() {{
- case "$1" in
- "All wizard-managed services have been removed. Remove LightRAG from Docker and switch to host mode?") return 0 ;;
- *) return 1 ;;
- esac
- }}
- confirm_required_yes_no() {{ return 0; }}
- env_server_flow
- """)
- assert result.returncode != 0
- assert (
- "Invalid SSL_CERTFILE" in result.stderr
- or "Invalid SSL_CERTFILE" in result.stdout
- )
- assert (tmp_path / "docker-compose.final.yml").exists()
- assert "LIGHTRAG_RUNTIME_TARGET=compose" in (tmp_path / ".env").read_text(
- encoding="utf-8"
- )
|