Skip to content

Commit 07bd6c5

Browse files
authored
implement $ENV:[env-var]:[fallback] config replacements (#2293)
1 parent 56f0389 commit 07bd6c5

2 files changed

Lines changed: 101 additions & 25 deletions

File tree

docs/docs/config.mdx

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,34 @@ wsh editconfig
9595
| 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. |
9696
| telemetry:enabled | bool | set to enable/disable telemetry |
9797

98-
For reference, this is the current default configuration (v0.10.4):
98+
For reference, this is the current default configuration (v0.11.5):
9999

100100
```json
101101
{
102-
"ai:preset": "ai@global",
103-
"ai:model": "gpt-4o-mini",
104-
"ai:maxtokens": 2048,
105-
"ai:timeoutms": 60000,
106-
"app:defaultnewblock": "term",
107-
"autoupdate:enabled": true,
108-
"autoupdate:installonquit": true,
109-
"autoupdate:intervalms": 3600000,
110-
"conn:askbeforewshinstall": true,
111-
"conn:wshenabled": true,
112-
"editor:minimapenabled": true,
113-
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
114-
"web:defaultsearch": "https://www.google.com/search?q={query}",
115-
"window:tilegapsize": 3,
116-
"window:maxtabcachesize": 10,
117-
"window:nativetitlebar": true,
118-
"window:magnifiedblockopacity": 0.6,
119-
"window:magnifiedblocksize": 0.9,
120-
"window:magnifiedblockblurprimarypx": 10,
121-
"window:magnifiedblockblursecondarypx": 2,
122-
"window:confirmclose": true,
123-
"window:savelastwindow": true,
124-
"telemetry:enabled": true,
125-
"term:copyonselect": true
102+
"ai:preset": "ai@global",
103+
"ai:model": "gpt-4o-mini",
104+
"ai:maxtokens": 2048,
105+
"ai:timeoutms": 60000,
106+
"app:defaultnewblock": "term",
107+
"autoupdate:enabled": true,
108+
"autoupdate:installonquit": true,
109+
"autoupdate:intervalms": 3600000,
110+
"conn:askbeforewshinstall": true,
111+
"conn:wshenabled": true,
112+
"editor:minimapenabled": true,
113+
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
114+
"web:defaultsearch": "https://www.google.com/search?q={query}",
115+
"window:tilegapsize": 3,
116+
"window:maxtabcachesize": 10,
117+
"window:nativetitlebar": true,
118+
"window:magnifiedblockopacity": 0.6,
119+
"window:magnifiedblocksize": 0.9,
120+
"window:magnifiedblockblurprimarypx": 10,
121+
"window:magnifiedblockblursecondarypx": 2,
122+
"window:confirmclose": true,
123+
"window:savelastwindow": true,
124+
"telemetry:enabled": true,
125+
"term:copyonselect": true
126126
}
127127
```
128128

@@ -134,6 +134,17 @@ files as well: `termthemes.json`, `presets.json`, and `widgets.json`.
134134

135135
:::
136136

137+
## Environment Variable Resolution
138+
139+
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.).
140+
141+
```json
142+
{
143+
"ai:apitoken": "$ENV:OPENAI_APIKEY",
144+
"ai:baseurl": "$ENV:AI_BASEURL:https://api.openai.com/v1"
145+
}
146+
```
147+
137148
## WebBookmarks Configuration
138149

139150
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 <Kbd k="Cmd:o"/>

pkg/wconfig/settingsconfig.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,65 @@ func isTrailingCommaError(barr []byte, offset int) bool {
327327
return false
328328
}
329329

330+
func resolveEnvReplacements(m waveobj.MetaMapType) {
331+
if m == nil {
332+
return
333+
}
334+
335+
for key, value := range m {
336+
switch v := value.(type) {
337+
case string:
338+
if resolved, ok := resolveEnvValue(v); ok {
339+
m[key] = resolved
340+
}
341+
case map[string]interface{}:
342+
resolveEnvReplacements(waveobj.MetaMapType(v))
343+
case []interface{}:
344+
resolveEnvArray(v)
345+
}
346+
}
347+
}
348+
349+
func resolveEnvArray(arr []interface{}) {
350+
for i, value := range arr {
351+
switch v := value.(type) {
352+
case string:
353+
if resolved, ok := resolveEnvValue(v); ok {
354+
arr[i] = resolved
355+
}
356+
case map[string]interface{}:
357+
resolveEnvReplacements(waveobj.MetaMapType(v))
358+
case []interface{}:
359+
resolveEnvArray(v)
360+
}
361+
}
362+
}
363+
364+
func resolveEnvValue(value string) (string, bool) {
365+
if !strings.HasPrefix(value, "$ENV:") {
366+
return "", false
367+
}
368+
369+
envSpec := value[5:] // Remove "$ENV:" prefix
370+
parts := strings.SplitN(envSpec, ":", 2)
371+
envVar := parts[0]
372+
var fallback string
373+
if len(parts) > 1 {
374+
fallback = parts[1]
375+
}
376+
377+
// Get the environment variable value
378+
if envValue, exists := os.LookupEnv(envVar); exists {
379+
return envValue, true
380+
}
381+
382+
// Return fallback if provided, otherwise return empty string
383+
if fallback != "" {
384+
return fallback, true
385+
}
386+
return "", true
387+
}
388+
330389
func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.MetaMapType, []ConfigError) {
331390
var cerrs []ConfigError
332391
if readErr != nil && !os.IsNotExist(readErr) {
@@ -353,6 +412,12 @@ func readConfigHelper(fileName string, barr []byte, readErr error) (waveobj.Meta
353412
}
354413
cerrs = append(cerrs, ConfigError{File: fileName, Err: err.Error()})
355414
}
415+
416+
// Resolve environment variable replacements
417+
if rtn != nil {
418+
resolveEnvReplacements(rtn)
419+
}
420+
356421
return rtn, cerrs
357422
}
358423

0 commit comments

Comments
 (0)