变量与替换
配置文本在被解析之前会展开两种 token:{env:NAME} 读取环境变量,{file:PATH} 读取文件。二者合起来,就是让密钥或长值不必写进 JSON 文档的方式。
{
"provider": {
"myopenai": {
"options": {
"baseURL": "{env:MYOPENAI_BASE_URL}"
}
}
}
}2
3
4
5
6
7
8
9
替换作用于文本,而不是值
这条规则解释了下面所有边界情况。展开在文件的字节上运行,结果再交给 JSONC 解析器。因此 token 出现在哪里就在哪里被展开:键里面、字符串里面、跨越一个引号,甚至跨越一次换行。
实际后果是:被替换进来的值必须在它落地的位置合法。一个包含双引号的值被插入 JSON 字符串后,会在文档层面产生解析错误,而不是在那个字段上产生校验错误。
{env:NAME}
对整份文本做一遍无条件扫描。
| 文本 | 结果 |
|---|---|
{env:PRESENT} | 该变量的值 |
{env:ABSENT} | 空字符串。变量缺失不是错误 |
{env:} | 保持不变。名称是必需的,所以这不是一个 token |
{env:FOO | 保持不变。没有闭合花括号,就没有 token |
{"a":"{env:FOO"} | 匹配会越过引号,一直到下一个 } |
「变量缺失会展开为空」这一点值得记牢:变量名拼错会产生一个空值,而不是一条诊断。请用 zuno debug config 验证,而不是靠假设。
出现在 // 注释行里的 {env:...} token 会被替换,因为环境变量这一遍扫描并不识别注释。
{file:PATH}
文件扫描在已经完成环境展开的文本上运行,因此一个值中包含 {file:...} token 的环境变量,确实会导致那个文件被读取。文件内容不会被再次扫描这两种 token。
路径解析有三种形态:
| 写法 | 解析方式 |
|---|---|
~/x | 拼接到 home 目录并归一化 |
| 绝对路径 | 原样使用,包括 .. 与 //,由内核解析 |
| 其他任何形式 | 相对于配置文件所在目录解析,而不是进程工作目录 |
内容会被 trim,结果会做转义以便插入 JSON 字符串。不是有效 UTF-8 的文件会得到替换字符,而不是导致失败。
{
"provider": {
"myopenai": {
"options": {
"apiKey": "{file:~/.secrets/myopenai.key}"
}
}
}
}2
3
4
5
6
7
8
9
文件缺失
对 zuno.json 而言,一个无法读取的 {file:...} 目标会让加载失败,错误信息会同时指出该 token 和解析后的路径。这覆盖所有读取失败,不只是文件不存在:指向一个目录的 token 会以同样的方式失败。
对 tui.json 而言,缺失的目标什么都不替换,加载继续。这种不对称是刻意的。一个错误的 provider 端点应当让进程停下;而一个缺失的装饰性取值不该让你失去界面。
注释跳过规则
{file:...} 这一遍扫描会跳过位于 // 注释行上的 token。精确地说:取该 token 所在行从行首到 token 之前的文本,去掉前导空白,如果剩下的内容以 // 开头,就跳过这个 token。
| 文本 | 是否替换? |
|---|---|
// {file:x} | 否 |
/// {file:x} | 否 |
{"a":1} // {file:x} | 是。行尾注释不是注释行 |
{"a":"// {file:x}"} | 是。字符串值内部的 // 不是注释 |
/* {file:x} */ | 是。块注释不被识别 |
// {file:a} and {file:b} | 两者都否。该行上的每个 token 都被跳过 |
被跳过的 token 绝不会被读取,因此注释里的一个缺失文件不会让加载失败。这也让把某一行 {file:...} 注释掉成为一种安全的禁用方式。
密钥:应当优先用什么
按优先次序,最好的在前:
- 由
zuno providers login保存的 provider 登录凭据。 - 在
provider.<id>.env下声明、被直接使用的环境变量。 - 指向一个权限受限文件的
{file:...}。 - 在配置中内联的
{env:...}。 zuno.json中字面量形式的apiKey字符串。
最后一种是受支持的,但会把密钥暴露给配置备份与源码管理。凭据优先级记录在认证。
ZUNO_AUTH_CONTENT 可以用一个 JSON 对象完全取代凭据读取,这在什么都不应写入磁盘的受管或临时环境中是正确形态。
验证一次展开
zuno debug config它打印替换之后合并出的配置,因此这里就是确认某个 token 是否解析成你预期值的地方。请注意,解析后的密钥在那份输出中是可见的;请相应对待。