From d75d5fc3212403dab3b6520113abe257c75003f1 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 26 Aug 2025 16:41:19 -0700 Subject: [PATCH 1/4] implement $ENV:[env-var]:[fallback] config replacements --- pkg/wconfig/settingsconfig.go | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index a6822a61da..8f95319c4a 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -327,6 +327,65 @@ func isTrailingCommaError(barr []byte, offset int) bool { return false } +func resolveEnvReplacements(m waveobj.MetaMapType) { + if m == nil { + return + } + + for key, value := range m { + switch v := value.(type) { + case string: + if resolved, ok := resolveEnvValue(v); ok { + m[key] = resolved + } + case map[string]interface{}: + resolveEnvReplacements(waveobj.MetaMapType(v)) + case []interface{}: + resolveEnvArray(v) + } + } +} + +func resolveEnvArray(arr []interface{}) { + for i, value := range arr { + switch v := value.(type) { + case string: + if resolved, ok := resolveEnvValue(v); ok { + arr[i] = resolved + } + case map[string]interface{}: + resolveEnvReplacements(waveobj.MetaMapType(v)) + case []interface{}: + resolveEnvArray(v) + } + } +} + +func resolveEnvValue(value string) (string, bool) { + if !strings.HasPrefix(value, "$ENV:") { + return "", false + } + + envSpec := value[5:] // Remove "$ENV:" prefix + parts := strings.SplitN(envSpec, ":", 2) + envVar := parts[0] + var fallback string + if len(parts) > 1 { + fallback = parts[1] + } + + // Get the environment variable value + if envValue := os.Getenv(envVar); envValue != "" { + return envValue, true + } + + // Return fallback if provided, otherwise return empty string + if fallback != "" { + return fallback, true + } + return "", true +} + func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.MetaMapType, []ConfigError) { var cerrs []ConfigError if readErr != nil && !os.IsNotExist(readErr) { @@ -353,6 +412,12 @@ func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.Meta } cerrs = append(cerrs, ConfigError{File: fileName, Err: err.Error()}) } + + // Resolve environment variable replacements + if rtn != nil { + resolveEnvReplacements(rtn) + } + return rtn, cerrs } From 4121da2c63656f8b905f132f91690472c356efee Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 26 Aug 2025 16:49:33 -0700 Subject: [PATCH 2/4] update config for $ENV feature --- docs/docs/config.mdx | 61 ++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx index 1b27b5cc96..aa18aff3ca 100644 --- a/docs/docs/config.mdx +++ b/docs/docs/config.mdx @@ -95,34 +95,34 @@ wsh editconfig | window:dimensions | string | set the default dimensions for new windows using the format "WIDTHxHEIGHT" (e.g. "1920x1080"). when a new window is created, these dimensions will be automatically applied. The width and height values should be specified in pixels. | | telemetry:enabled | bool | set to enable/disable telemetry | -For reference, this is the current default configuration (v0.10.4): +For reference, this is the current default configuration (v0.11.5): ```json { - "ai:preset": "ai@global", - "ai:model": "gpt-4o-mini", - "ai:maxtokens": 2048, - "ai:timeoutms": 60000, - "app:defaultnewblock": "term", - "autoupdate:enabled": true, - "autoupdate:installonquit": true, - "autoupdate:intervalms": 3600000, - "conn:askbeforewshinstall": true, - "conn:wshenabled": true, - "editor:minimapenabled": true, - "web:defaulturl": "https://github.com/wavetermdev/waveterm", - "web:defaultsearch": "https://www.google.com/search?q={query}", - "window:tilegapsize": 3, - "window:maxtabcachesize": 10, - "window:nativetitlebar": true, - "window:magnifiedblockopacity": 0.6, - "window:magnifiedblocksize": 0.9, - "window:magnifiedblockblurprimarypx": 10, - "window:magnifiedblockblursecondarypx": 2, - "window:confirmclose": true, - "window:savelastwindow": true, - "telemetry:enabled": true, - "term:copyonselect": true + "ai:preset": "ai@global", + "ai:model": "gpt-4o-mini", + "ai:maxtokens": 2048, + "ai:timeoutms": 60000, + "app:defaultnewblock": "term", + "autoupdate:enabled": true, + "autoupdate:installonquit": true, + "autoupdate:intervalms": 3600000, + "conn:askbeforewshinstall": true, + "conn:wshenabled": true, + "editor:minimapenabled": true, + "web:defaulturl": "https://github.com/wavetermdev/waveterm", + "web:defaultsearch": "https://www.google.com/search?q={query}", + "window:tilegapsize": 3, + "window:maxtabcachesize": 10, + "window:nativetitlebar": true, + "window:magnifiedblockopacity": 0.6, + "window:magnifiedblocksize": 0.9, + "window:magnifiedblockblurprimarypx": 10, + "window:magnifiedblockblursecondarypx": 2, + "window:confirmclose": true, + "window:savelastwindow": true, + "telemetry:enabled": true, + "term:copyonselect": true } ``` @@ -134,6 +134,17 @@ files as well: `termthemes.json`, `presets.json`, and `widgets.json`. ::: +## Environment Variable Resolution + +To avoid putting secrets directly in config files, Wave supports environment variable resolution using `$ENV:VARIABLE_NAME` or `$ENV:VARIABLE_NAME:fallback` syntax. + +```json +{ + "ai:apitoken": "$ENV:OPENAI_APIKEY", + "ai:baseurl": "$ENV:AI_BASEURL:https://api.openai.com/v1" +} +``` + ## WebBookmarks Configuration WebBookmarks allows you to store and manage web links with customizable display preferences. The bookmarks are stored in a JSON file (`bookmarks.json`) as a key-value map where the key (`id`) is an arbitrary identifier for the bookmark. By convention, you should start your ids with "bookmark@". In the web widget, you can pull up your bookmarks using From ffe62456a81867b48decd272c1c252dc75831cec Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 26 Aug 2025 16:51:14 -0700 Subject: [PATCH 3/4] minor --- docs/docs/config.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx index aa18aff3ca..2622ddc52a 100644 --- a/docs/docs/config.mdx +++ b/docs/docs/config.mdx @@ -136,7 +136,7 @@ files as well: `termthemes.json`, `presets.json`, and `widgets.json`. ## Environment Variable Resolution -To avoid putting secrets directly in config files, Wave supports environment variable resolution using `$ENV:VARIABLE_NAME` or `$ENV:VARIABLE_NAME:fallback` syntax. +To avoid putting secrets directly in config files, Wave supports environment variable resolution using `$ENV:VARIABLE_NAME` or `$ENV:VARIABLE_NAME:fallback` syntax. This works for any string value in any config file (settings.json, presets.json, ai.json, etc.). ```json { From 2ede0ec4211e804e548041fdc29688b0ac93c224 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 26 Aug 2025 16:55:17 -0700 Subject: [PATCH 4/4] use lookupenv and only use fallback if env var doesn't exist --- pkg/wconfig/settingsconfig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 8f95319c4a..5c33e69fe9 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -375,7 +375,7 @@ func resolveEnvValue(value string) (string, bool) { } // Get the environment variable value - if envValue := os.Getenv(envVar); envValue != "" { + if envValue, exists := os.LookupEnv(envVar); exists { return envValue, true }