From c239434b10f8a4efd263e82b0cae7ff79f82bc70 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Mon, 4 May 2026 04:15:21 +0000 Subject: [PATCH] fix(config): bind all defaulted keys to env; doc GOFLAGS=-tags=sqlite_fts5 for go install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two product bugs reported against v0.1.3 from a corporate-firewall install. 1. DOCSIQ_LLM_* env vars silently ignored without a config file. Viper's AutomaticEnv() + Unmarshal() only reads env for keys that are either explicitly bound via BindEnv or already present in the merged config tree (Viper #761). Of all the LLM keys, only llm.call_timeout was explicitly bound, so DOCSIQ_LLM_PROVIDER, DOCSIQ_LLM_OLLAMA_*, DOCSIQ_LLM_AZURE_*, DOCSIQ_LLM_OPENAI_*, etc. were dropped on the floor for users without ~/.docsiq/config.yaml. Fix: after all SetDefault calls, iterate v.AllKeys() and BindEnv every one. The existing explicit BindEnv list stays — it documents the DOCSIQ_API_KEY shortcut alias for server.api_key and double-binds harmlessly. Adds TestLoad_EnvOverridesLLM as a regression test covering DOCSIQ_LLM_PROVIDER, the Ollama and Azure subtrees, and DOCSIQ_DATA_DIR — all without writing a config file. 2. go install github.com/RandomCodeSpace/docsiq@ produces a binary that fails at runtime with "no such module: fts5" when opening any project store. mattn/go-sqlite3 only enables FTS5 when compiled with -tags=sqlite_fts5; the Makefile passes that, but go install does not pick up Makefile tags. Fix (docs only): docs/getting-started.md updated to document `GOFLAGS='-tags=sqlite_fts5' go install ...@latest` with an explanatory line on why the tag is non-negotiable. README's "Build from source" already shows the tag on `go build` and does not reference `go install`, so it stays. CONTRIBUTING.md does not reference `go install` either. Verification: - make vet, make build, make test all green. - internal/config new test passes. - End-to-end smoke: fresh binary on :37789 with DOCSIQ_LLM_PROVIDER=ollama DOCSIQ_LLM_OLLAMA_EMBED_MODEL=bge-m3 logs "LLM provider initialised provider=ollama model=bge-m3" without a config file — confirms env override path. - Production server on :37777 untouched. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/getting-started.md | 10 ++++++-- internal/config/config.go | 11 +++++++++ internal/config/config_test.go | 42 ++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) 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) + } +}