diff --git a/docs/getting-started.md b/docs/getting-started.md index ade223b..fc3905d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -24,9 +24,15 @@ project indexed and the MCP endpoint answering queries. ## Install ```bash -go install github.com/RandomCodeSpace/docsiq@latest +GOFLAGS='-tags=sqlite_fts5' go install github.com/RandomCodeSpace/docsiq@latest ``` +The `sqlite_fts5` build tag is required — docsiq uses SQLite's FTS5 +full-text-search extension. Without it the binary fails at runtime with +`no such module: fts5` when opening a project store. `go install` does +**not** pick up Makefile tags, so the flag has to come from `GOFLAGS` +(or `-tags`) on the install command itself. + This drops a `docsiq` binary into `$(go env GOPATH)/bin`. Make sure that path is on your `PATH`. @@ -35,7 +41,7 @@ Alternatively, build from a checkout: ```bash git clone https://github.com/RandomCodeSpace/docsiq.git cd docsiq -CGO_ENABLED=1 go build -o docsiq . +CGO_ENABLED=1 go build -tags sqlite_fts5 -o docsiq . ``` ## First project diff --git a/internal/config/config.go b/internal/config/config.go index 6924028..e917b91 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -273,6 +273,17 @@ func Load(cfgFile string) (*Config, error) { v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AutomaticEnv() + // Bind every defaulted key to its DOCSIQ_ env variant so + // v.Unmarshal honours overrides without requiring a config file. + // Without this, only keys explicitly listed via BindEnv are read + // from env when nothing in the config tree already mentions them + // (Viper #761). The explicit BindEnv calls below stay because they + // document the secondary DOCSIQ_API_KEY alias for server.api_key + // and harmlessly double-bind the rest. + for _, key := range v.AllKeys() { + _ = v.BindEnv(key) + } + // Explicit alias: DOCSIQ_API_KEY is a convenience shortcut for // DOCSIQ_SERVER_API_KEY (the auth-middleware API key). Bind both so // either form populates server.api_key. BindEnv names are matched diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 856892b..5ab0744 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -719,3 +719,45 @@ func TestLoad_LogFormatFromEnv(t *testing.T) { t.Errorf("env Log.Format=%q want json", cfg.Log.Format) } } + +// TestLoad_EnvOverridesLLM is the regression test for the bug where +// DOCSIQ_LLM_* env vars were silently ignored without a config file +// because only a handful of keys were explicitly bound via BindEnv +// (Viper #761). After the fix, every defaulted key — including the +// nested LLM provider keys — must be reachable via env. +func TestLoad_EnvOverridesLLM(t *testing.T) { + home := t.TempDir() + isolateEnv(t, home) + + t.Setenv("DOCSIQ_LLM_OLLAMA_CHAT_MODEL", "envtest-chat") + t.Setenv("DOCSIQ_LLM_OLLAMA_EMBED_MODEL", "envtest-embed") + t.Setenv("DOCSIQ_LLM_PROVIDER", "azure") + t.Setenv("DOCSIQ_LLM_AZURE_API_KEY", "envtest-azure-key") + t.Setenv("DOCSIQ_LLM_AZURE_ENDPOINT", "https://envtest.openai.azure.com/") + t.Setenv("DOCSIQ_LLM_AZURE_CHAT_MODEL", "envtest-azure-chat") + t.Setenv("DOCSIQ_LLM_AZURE_EMBED_MODEL", "envtest-azure-embed") + t.Setenv("DOCSIQ_DATA_DIR", filepath.Join(home, "envtest-data")) + + cfg, err := Load("") + if err != nil { + t.Fatalf("Load: %v", err) + } + + // Ollama keys must round-trip even when provider is azure — the + // fix is about *reachability* of the keys, not which one is active. + if got, want := cfg.LLM.Ollama.ChatModel, "envtest-chat"; got != want { + t.Errorf("LLM.Ollama.ChatModel = %q, want %q", got, want) + } + if got, want := cfg.LLM.Ollama.EmbedModel, "envtest-embed"; got != want { + t.Errorf("LLM.Ollama.EmbedModel = %q, want %q", got, want) + } + if got, want := cfg.LLM.Provider, "azure"; got != want { + t.Errorf("LLM.Provider = %q, want %q", got, want) + } + if got, want := cfg.LLM.Azure.APIKey, "envtest-azure-key"; got != want { + t.Errorf("LLM.Azure.APIKey = %q, want %q", got, want) + } + if got, want := cfg.DataDir, filepath.Join(home, "envtest-data"); got != want { + t.Errorf("DataDir = %q, want %q", got, want) + } +}