跳到主要内容
版本:2.6

管理环境变量与密钥

Shine 可以保存预设模板变量,也可以使用 GPG 或 age 封存项目环境中的敏感值。封存后的 密钥既可以提供给本机子进程,也可以通过 SSH Secret Broker 由远端命令按需请求本机解密。 不要把真实密钥写进公开仓库或文档示例。

密钥操作统一位于 shine env secret 下;workspace 形式的 shine env run 和 env run --with 用于按需向子进程注入变量。若目标命令在 SSH 远端,先阅读下文“向远端命令提供变量与密钥”, 选择直接转发或按需解密。

查看和设置变量​

shine env list
shine env get HTTP_PROXY_PORT
shine env explain HTTP_PROXY_PORT
shine env set HTTP_PROXY_PORT 6152
shine env delete HTTP_PROXY_PORT

PROXY_NO_PROXY 控制 setproxy 设置的 NO_PROXY 和 no_proxy,默认为 localhost,127.0.0.1,::1。修改它或其他代理变量后,shine update 会把已安装的 proxy shell 预设标记为可更新;运行 shine upgrade 应用新值。

内置图片命令默认使用 IMAGE_QUALITY=80、IMAGE_MAX_WIDTH=1920、 IMAGE_MAX_HEIGHT=1080。可用 --quality、--width、--height 只覆盖当次运行,也可用 shine env set 为当前机器保留不同默认值。

shine env list 默认隐藏敏感值;--reveal 可取消脱敏,应只在安全终端中使用。 列表中的换行、制表符、其他控制字符及反斜杠会转义显示,并按终端显示宽度截断。 这些显示调整不会改变存储值或 env get 的输出。 输出按 builtin、overlay、全局配置、全局 env 文件、项目配置、项目 env 文件分组,显示实际路径 及当前项目根。env explain KEY 从低到高展示同一存储键的定义、胜出层和描述来源,默认隐藏 所有值;--reveal 不会自动解密。显式注入仍先查找 KEY_SECRET,再查找 KEY;来源说明和 写入输出会提示已有的密文键。 list/explain 的路径及环境变量写入、代理启停回执会转义终端控制字符,存储路径保持原值。 键名包含控制字符时,explain 会要求使用原键名,不生成可能指向另一种拼写的可复制命令。

默认值只在运行时兜底,查询不会创建配置。保存显式值时,即使与内置值相同也会记录该项。

配置 TOML 解析失败时,会报告文件或字段位置,不回显配置值或源码行。环境变量读写命令 以退出码 1 结束;修正输入后重试。解析失败不会应用本次环境变量修改。工作区定义与个人 shine.config.local.toml 偏好的 TOML 解析错误同样不回显文档内容。

全局 ~/.shine/config.toml 和项目 shine.config.toml 的 [env] 支持简写字符串,也支持 同时记录值和说明:

[env]
HTTP_PROXY_PORT = "6152"
MY_API_TOKEN = { value = "<令牌>", description = "内部 API 的访问令牌" }

value 的使用方式与简写字符串完全相同;description 会显示在 shine env list 中。 对已有详细条目执行 shine env set MY_API_TOKEN <新值> 时,Shine 会更新 value 并保留 说明。

写入目标由参数和当前目录确定,与有效值来自哪里无关:

参数目标
无范围参数当前项目 shine.config.toml 的 [env],无项目时为全局配置
--global全局 config.toml 的 [env]
--project最近项目的 [env],无项目时报错
--env-file所选或默认范围的 shine.env.toml
--overlay当前手动 overlay 的 shine.env.toml

范围参数互斥。--overlay --env-file 使用同一目标。脚本应同时指定范围和文件选项, 避免依赖执行目录。未传任何目标参数时,同键被更高层定义就拒绝写入,即使值相同;显式选择 目标允许维护被遮蔽项,只修改指定文件并分别报告保存位置、当前有效来源和值是否改变。

shine env set HTTP_PROXY_PORT 7890 --global
shine env set HTTP_PROXY_PORT 8080 --project
shine env set HTTP_PROXY_PORT 8081 --project --env-file
shine env set HTTP_PROXY_PORT 6152 --overlay
shine env delete HTTP_PROXY_PORT --global --env-file
shine env secret encrypt --from MY_TOKEN --set MY_TOKEN_SECRET --project --env-file

delete 只删除目标文件实际存在的项,不删除其他层或同时删除 KEY_SECRET;不存在则报错。 删除恢复继承,不表示禁用变量,空字符串仍是合法值。Git 托管 overlay 镜像禁止直接写入; 应修改上游、使用手动维护目录,或选择本地覆盖层。上述三个写入入口的旧 --force 已停用, 请求会失败且零写入,并提示准确的替代参数。

写入预检在加密工具启动前完成。提交前会核对目标、配置来源和 --from 输入;期间发生变化 则要求重试。Shine 对同一目标的最终核对和原子替换使用文件锁串行化。它不锁定外部编辑器 或 Git 的所有操作。若提示“文件已保存,但有效状态核对失败”,命令返回失败,但文件已保存, 请检查配置后重新查询;不要假定没有写入。

Unix 配置与 env 文件及其临时文件使用仅所有者可读写的 0600 权限。原子替换失败保留原文件, 拒绝符号链接及特殊文件目标,保留注释、无关项和详细条目的说明。明文值仍然是明文。

不带 [env] 表头的全局、overlay 和项目 shine.env.toml 覆盖文件也支持这两种格式:

HTTP_PROXY_PORT = "7890"
PROXY_HOST = { value = "127.0.0.1", description = "本地代理主机" }

字符串与仅含 value 的详细项继承低层说明;显式 description 才替换说明,空字符串清空 说明且不再使用 preset catalog 兜底。数字、数组、缺少 value 等无效条目会直接报错,不会被静默忽略。

修改用于模板渲染的值后,运行:

shine upgrade

Env 分层变更的人工升级与回退​

新顺序统一为:内置默认值 < overlay 基线 < 全局 [env] < 全局 env 文件 < 项目 [env] < 项目 env 文件。旧版 overlay 高于全局覆盖文件,所有独立 env 文件高于两层 [env]; 升级可能改变有效值,不提供旧顺序模式或自动迁移。

  1. 升级前记录实际 runtime root、重要项目及关键变量来源。对准备编辑的配置做私有备份, 不把秘密提交仓库或复制进公开日志。
  2. 更新旧 --force 脚本:全局 env 文件改用 --global --env-file,项目 env 文件改用 --project --env-file,手动 overlay 改用 --overlay。托管镜像改为上游维护或本地覆盖。
  3. 升级后在项目外和各重要项目内分别运行 env list、env explain KEY。项目可选择不同的 overlay,一个项目的结果不能代表其他项目。
  4. 历史项(包括过去自动补写、等于旧默认值的项)全部保留为显式值。确认不再需要时,用明确 目标的 env delete 逐项清理并再次检查来源;不能只凭“等于默认值”判断删除。只有没有显式 定义的键才跟随新版默认值。希望 overlay 胜出的用户须人工删除不再需要的本机或项目同键项。
  5. 核对 KEY_SECRET;调整或删除明文键不取消密文键优先。运行 shine update 检查安装产物, 审阅 shine upgrade 的 Plan 后再更新。修改配置或升级二进制不会自动重渲染、执行或批准预设。

回退二进制会恢复旧排序,但不能恢复手动删除的项或已更新的安装产物。按需核对并恢复私有备份, 避免覆盖期间的无关编辑;产物仍需通过既有 lifecycle 审阅处理。没有专用 env 迁移或恢复命令。 旧二进制还可能重新把缺失的内置默认值补写到全局配置中;这不能恢复已删除的自定义值。

使用 GPG 加密值​

Shine 在内部完成 GPG 和 age 密文的 Base64 编解码,这些操作无需外部 base64 命令。 仍需安装所选的加密后端(gpg 或 age)及其身份所需的插件。已有 Shine 密文无需迁移。 允许 Base64 换行和 ASCII 空白;缺失或错误 padding 等畸形编码会被拒绝。

先确认本机的 gpg 可以使用对应公钥;私钥保存在 YubiKey 时,可参考 在 macOS 和 Windows 使用 YubiKey OpenPGP完成接入。然后在 ~/.shine/config.toml 中指定默认接收者(可同时加密给多把 GPG 公钥):

gpg_recipients = ["user@example.com", "team-backup@example.com"]

将已有明文变量加密并保存为另一个 key:

shine env secret encrypt --from MY_TOKEN --set MY_TOKEN_SECRET
shine env secret decrypt MY_TOKEN_SECRET

加密只需要接收者公钥;解密时才需要连接持有对应私钥的 YubiKey,并按提示输入 PIN 或触摸设备。

旧版的单值 gpg_key_id 已废弃。先用 shine state migrate --dry-run 查看,再运行 shine state migrate 将它转换为 gpg_recipients;工作区中旧的 [env.encryption].recipient 也会在 env run 或 env secret seal 需要使用时提示迁移。

需要导出到当前 shell 时:

export_code="$(shine env secret export MY_TOKEN)" && eval "$export_code"
export_code="$(shine env secret export MY_TOKEN --as API_TOKEN)" && eval "$export_code"

在 PowerShell 中,仅在导出成功后执行生成的代码:

$exportCode = shine env secret export MY_TOKEN --as API_TOKEN
if ($LASTEXITCODE -eq 0) { Invoke-Expression $exportCode }

PowerShell 导出会保留 Unicode 和多行值,包括原始换行符。 安装 utils shell 预设后,也可以使用 shine-env-export MY_TOKEN --as API_TOKEN。 环境变量无法表示含 NUL 的值。env secret export 会以退出码 1 拒绝该值,stdout 不输出导出代码; env get 和 env secret decrypt 仍保留原始输出。

使用 age identity​

Shine 支持 age 作为第二种密钥后端。它适合把密文提交到团队仓库中,并加密给多个成员各自的 recipient。已有 GPG 密文不需要迁移:不带标签的旧密文继续按 GPG 解密,age 后端生成的新密文会带有 age: 标签。

先确认 age 1.3 或更高版本已安装并位于 PATH 中。生成一个普通软件 identity,并记录输出中的 recipient:

shine env secret identity init
shine env secret identity list

普通 identity 使用 age-keygen,默认写入 ~/.shine/age/identity.txt。 已有文件需要 --force 才能替换。强制替换会先在私有临时目录生成并检查新 identity,再替换旧文件; 生成或检查失败时保留旧 identity。Unix 上,生成的 identity 文件使用仅所有者可读写的 0600 权限。

自定义 --output 时,输出的 age_identity 配置提示使用绝对路径和 TOML 引用, 因此切换目录后仍能找到该文件。提示本身不会修改配置。 Unix 上,--output 保留原生文件名的字节。若绝对路径不是有效 UTF-8,Shine 会省略可复制的 age_identity 赋值提示;要通过 TOML 配置该文件,请使用 UTF-8 路径。 identity list 显示每个已配置 identity 的路径和 recipient。Unix 文件名中的字面反斜杠会 原样保留;Windows 路径使用正斜杠显示。 identity 列表及软件/Touch ID 生成结果行会转义路径和 recipient 中的终端控制字符。 可复制的配置提示保留 TOML 引用格式,包括多行值。

在 macOS 上使用 Touch ID​

如果希望每次解密都需要本机用户授权,macOS 上可以改用由 Secure Enclave 托管的 identity。 这种方式还需要安装 age-plugin-se;Homebrew 可以同时安装两个依赖:

brew install age age-plugin-se

不要再执行上面的普通 identity 初始化,而是运行 Touch ID 形式,然后记录它的 recipient:

shine env secret identity init --touch-id
shine env secret identity list

--touch-id 只适用于 macOS。解密时需要本机 Secure Enclave,并会触发 Touch ID 或系统密码 授权;即使 identity 文件被复制到另一台机器,通常也不能直接解密。看到意外的授权提示时, 应取消授权并检查触发它的命令。如果 AI Agent 可以在本机运行命令,请继续阅读 在 AI Agent 参与开发时保护环境密钥,了解这种方式的安全边界。

新建 Touch ID identity 会输出 age1tag... recipient。age 1.3 可以在 macOS、Linux 或 Windows 上直接向这个公开 recipient 加密,无需在加密端安装 age-plugin-se;只有解密端需要 插件和原来的 Mac。Tagged recipient 以可识别性换取这种跨平台能力:已经知道 recipient 的人 可以判断某份密文是否发给它。

如需在本机所有项目中使用同一默认后端和 recipient,将它们写入 ~/.shine/config.toml:

secret_backend = "age"
age_recipients = ["age1tag1qexample...", "age1qteammate..."]
age_identity = "~/.shine/age/identity.txt"
age_identities = ["C:/Users/<user>/AppData/Local/age-plugin-phone/identity-....txt"]

旧的 age_identity 单路径会与新增的 age_identities 路径列表按顺序合并并去重。这样普通 identity、Secure Enclave identity 和硬件 plugin stub 可以共存,不需要复制任何文件。

如果 recipient 是某个项目团队共享的名单,应将它写入项目根目录的 shine.workspace.toml 的 [env.encryption];这样可以随项目提交,而不会影响本机的其他 项目。该配置会优先于全局默认值,完整格式见下文“使用分层项目环境”。不要将私有 age_identity 提交到仓库。

也可以只在单次命令中选择后端和 recipient:

shine env secret encrypt --backend age -r age1tag1qexample... -r age1qteammate... --from MY_TOKEN
shine env secret seal --backend age -r age1tag1qexample... -r age1qteammate...

-r/--recipient 对 GPG 和 age 都可以重复使用。

旧版 Secure Enclave identity 可能使用 age1se... recipient,导致仅执行加密的电脑也必须 安装 age-plugin-se。在 workspace 所在项目中预览并应用公开 recipient 转换:

shine state migrate --dry-run
shine state migrate

迁移只会把配置中的 age1se... 改为等价的 age1tag...,不会读取 identity 或解密 secret。 它会一并检查全局配置、当前项目配置和最近的 workspace;dry-run 按文件报告转换数量,应用时 保留数组顺序、注释和重复项,并在写入任何文件前校验全部旧 recipient。迁移后再次 seal,才会 生成使用原生 tagged recipient 的新密文;已有密文不会改变,仍可照常解密。

为已有 workspace 密钥添加接收者​

仅修改 age_recipients 不会更新已有密文。在 shine.workspace.toml 中保留原有接收者, 追加新成员的公开 recipient:

[env.encryption]
backend = "age"
age_recipients = ["age1existing...", "age1newmember...", "age1backup..."]

将所有占位符替换为实际 recipient,然后在本机 identity 仍能解密旧 payload 的设备上运行:

shine env secret seal

seal 会解密旧 payload,再按当前完整接收者名单重新加密。即使 [secret] 中所有项都是 true,也会保留原值并重新加密;保持这些项为 true 即可,无需导出明文或重新输入值。 新增成员只需提供公开 recipient,不需要提供私有 identity。解密旧 payload 时,仍可能 要求 Touch ID、手机授权或对应 identity 的其他确认。

不指定文件时,seal 会处理 workspace 在已配置的各个 mode(包括 default_mode)下 引用的已存在源文件。使用 shine env secret seal <FILE> 可只处理一个源文件。如果封存 因错误中止,部分文件可能已更新;应解决报告的错误并重新运行,再共享结果。

将更新后的 workspace 配置和重新封存的共享源文件一起提交。名单中的每个接收者都能 独立解密新密文;请新成员在自己的设备上验证访问。新接收者无法解密 Git 历史中的旧密文。 同样,移除接收者并重新封存也不会撤销其对历史密文的访问,或轮换上游服务中的真实凭据; 必要时应在上游服务中轮换已泄露的凭据。

在 Windows 和 macOS 上实验手机授权​

age-plugin-phone 已提供面向技术用户的有限 Beta,仍只能用于合成或可丢弃数据,不能保护真实或生产 secret。安装、桌面与手机制品匹配、 配对和恢复验证请参照项目的 Beta 发布说明 和 Windows Beta 快速入门。 始终保留独立验证过的恢复 recipient。

Windows 要求 Windows 11 x64、TPM 2.0、Microsoft Platform Crypto Provider,以及能力检查 合格的 Android StrongBox 手机。Windows 和 macOS 均支持通过 Cargo 从源码安装,构建前提 以插件文档为准。可选的 Windows ZIP 使用测试签名,不要将其私有签名根证书导入系统信任库。 macOS 没有安装器包。

Shine 也开放了实验性 macOS 配对入口。请按照插件的 macOS 源码快速入门 搭配匹配的 Android StrongBox 应用。插件要求在已登录用户会话中使用真正的 Secure Enclave; Intel/T2、其它硬件和系统版本尚未普遍验证。编译部署下限不代表已经验证的最低 macOS 支持版本。 iPhone 验证仅限现有开发设备,Beta 不提供可供外部用户安装的 iOS 应用。硬件检查由插件负责。

Beta 已验证的传输范围是 Android Developer USB 和记录中设备的前台 Wi-Fi;tagged recipient 的 QR 流程、BLE 和后台唤醒不在该范围内。macOS 的 auto 在没有 Wi-Fi listener 响应时可能 选择 QR,因此请先准备好手机 Wi-Fi listener,或在此流程中显式使用 Android ADB。 操作失败后,不要通过恢复旧的插件 replay 状态来修复;请遵循插件的恢复说明。

例如,在已授权 Android ADB 设备的 Mac 上运行:

shine env secret identity init --phone --label "Work Mac" --transport adb --adb-serial SERIAL

安装匹配的桌面 plugin 和 Android 应用后,可以通过 Shine 启动 plugin 自己的事务式配对:

shine env secret identity init --phone --label "NUC WiFi Pair" --transport auto

如果希望通过局域网完成配对,先在手机端打开显式的 Pair · Wi-Fi 操作,再运行上述 命令。Windows 和 macOS 上的 auto 会先执行一次有界的 Wi-Fi discovery:只有一个匹配且位于前台的手机 listener 响应时选择 Wi-Fi;没有 listener 响应时,会在创建 pairing offer 之前选择 Windows 的 Developer USB/ADB 或 macOS 的 QR。QR 需要受支持的摄像头,并按插件提示完成 扫码。多个响应或本机 discovery 错误会安全失败;协议处理开始后不会再切换 transport。auto 是默认值,因此省略 --transport auto 时策略不变。

Developer USB 的顺序相反:先启动桌面命令;plugin 选择 ADB 并开始等待手机连接后,再在手机端 点击 Pair · USB。使用 --transport adb 时,plugin 会在预检完成后直接选择 ADB。手机只会 立即尝试连接一次,因此如果在桌面建立 adb reverse 规则前点击 Pair · USB,手机会报告 usb_transport_failed。

配对标签默认使用 Windows 或 macOS 计算机名;无法取得有效名称时使用 Shine desktop。 显式标签不能为空白,且最多为 64 个 UTF-8 字节。也可以显式指定标签、固定使用 Developer USB 或 QR, 以及在存在多台 ADB 设备时指定序列号:

shine env secret identity init --phone --label "Work laptop"
shine env secret identity init --phone --transport adb
shine env secret identity init --phone --transport qr
shine env secret identity init --phone --adb-serial SERIAL

配对、硬件密钥、replay、locator、中断恢复和清理状态仍完全由 plugin 管理。完整指纹确认成功后, Shine 只会把公开 identity stub 路径加入当前用户的全局 age_identities。如果当前项目显式 覆盖了 age_identity 或 age_identities,命令会在配对前退出,避免创建一个随后被项目忽略 的 identity。对应的手工配置形式如下,请使用插件在当前平台上实际返回的绝对 identity 路径:

age_identities = ["/absolute/path/returned/by/plugin/identity.txt"]

配置多个 phone identity stub 是有效用法:age 应把与当前密文不匹配但格式有效的 stub 视为 普通未命中,并继续尝试下一个 identity。旧版 age-plugin-phone 可能反而在第一个不匹配的 stub 处终止。如果 hybrid unwrap 只在配置多个 phone identity 时失败,可设置 AGE_PLUGIN_PHONE_MESSAGES=1 确认插件诊断并升级插件。临时规避时只配置匹配的 identity 路径即可;不要仅为了缩小这个列表而删除配对或恢复状态。

这个快捷命令不会修改 secret_backend,也不会自动添加 recipient。plugin setup 如果中断, 必须按照其文档使用 age-plugin-phone setup --resume 或 age-plugin-phone setup --cleanup; 不要把重新发起一次配对当作恢复手段。

在可提交到仓库的项目 shine.workspace.toml 中,同时加入配对输出的 age1tag... recipient 和一个已经独立验证过的恢复 recipient:

[env.encryption]
backend = "age"
age_recipients = ["age1tag...", "age1..."]

Shine 默认请求 --recipient-type tag,配对前要求 age 1.3+,并且桌面插件和手机应用都必须 支持 tagged recipient。该能力从 0.1.0-beta.1 开始提供,旧 alpha.5 版本不包含它。 Shine 不会重新发起配对,也不会自动退回其它 recipient 类型。向 age1tag... 加密的电脑只需 age 1.3+,无需 phone 插件,也不会弹出手机授权。配对和手机解密仍需插件及匹配的手机应用。

如需保留插件的 phone recipient 格式,请显式选择:

shine env secret identity init --phone --recipient-type phone

每台向 age1phone... 加密的电脑仍需安装 age-plugin-phone。插件自身仍默认 phone, Shine 会显式传入所选类型。与 phone v2 的私有 recipient 选择不同,知道 tagged recipient 的人 可以判断密文是否发给它。

对于已有配对,先升级并验证桌面插件和手机应用的 tag 支持,再导出公开 tag recipient,无需重新配对:

age-plugin-phone recipients -i <IDENTITY_STUB> --recipient-type tag

替换全局或项目 age_recipients,或 workspace [env.encryption].age_recipients 中对应的 recipient,保留已经独立验证的恢复 recipient,再重新 seal。shine state migrate 不转换 phone recipient。导出不会改写 stub 或已有密文;identity list 仍显示 stub 中记录的 recipient。 重新封存已有 payload 需要先解密,因此可能要求手机授权;参见 为已有 workspace 密钥添加接收者。修改 recipient 不会撤销对历史密文的访问。

使用 auto 配对后,如果希望后续 解密也优先走 Wi-Fi,请开启 Wi-Fi auto-listen 并保持手机应用在前台。plugin 会在创建 unwrap request 前发现匹配的 listener;未发现时在 Windows 上选择 Developer USB/ADB,在 macOS 上选择 QR,不会并行竞速或 在请求开始后自动重试其它路径。解密由手机保护的 secret(包括通过 shine env run 使用它)时, 标准 age plugin 会为每次 file key 解包要求一次新的强生物验证。Developer USB 和 Wi-Fi 的 plugin 提示默认 静默;设置 AGE_PLUGIN_PHONE_MESSAGES=1 可显式开启。QR 请求必须由手机扫描,因此仍会显示在终端中。 shine env secret decrypt 成功时只写入解密值,同时屏蔽 age 客户端自身的等待提示,并且不会额外添加换行。 Shell 主题仍可能主动把下一条 prompt 放到新行。对于需要保留的数据,绝不能只配置这个实验性手机 recipient;恢复路径不能依赖同一部手机的 StrongBox 密钥、同一台电脑的 TPM/Secure Enclave 密钥或该 plugin 的本地状态。

如果 AI Agent 会参与开发,先阅读在 AI Agent 参与开发时保护环境密钥,确认 identity 文件、Touch ID、手机授权提示和命令执行权限的安全边界。

只向一个命令提供变量​

不修改当前终端、也不创建 workspace 文件时,使用可重复的 --with:

shine env run --with MY_TOKEN -- bun run build
shine env run --with MY_TOKEN=API_TOKEN -- bun run build
shine env run --with TOKEN_A --with TOKEN_B=OTHER_TOKEN -- bun run build
shine env run --no-workspace --with MY_TOKEN -- bun run build

每个 KEY 都优先解密 <KEY>_SECRET,不存在时才读取明文 <KEY>。等号右侧是子进程中 的变量名。显式 --with 值优先于当前进程和 workspace 的同名变量。

--no-workspace 会完全跳过 shine.workspace.toml 查找,只合并当前进程环境和显式 --with;它不能与 --workspace 或 --mode 同时使用。这个模式也用于需要固定读取 Shine 配置环境、但不应受当前工作目录影响的受管 Bun 命令入口。

选择单次注入还是透明代理​

偶尔执行一次敏感操作时,优先使用单次注入。例如,Cargo 的 cargo:token credential provider 启用时,可以通过 CARGO_REGISTRY_TOKEN 读取 crates.io token,因此执行 yank 时 不必把 token 留在 Shell 中,也不必长期启用命令代理:

shine env run --no-workspace \
--with CARGO_REGISTRY_TOKEN \
-- cargo yank my-crate@1.2.3

--with CARGO_REGISTRY_TOKEN 会优先解密 CARGO_REGISTRY_TOKEN_SECRET,只在本次运行中 注入 Cargo。Cargo 及其启动的后代进程仍然可以读取该值。日常持久使用 Cargo 认证时,Cargo 官方更推荐操作系统 credential provider;只有明确希望把 token 加密保存在 Shine 中时,才选择 Shine 注入。详见 Cargo registry authentication 和 cargo yank。

为固定凭据变量安装透明代理​

如果一个 CLI 每次调用都需要同一个固定凭据变量,可以安装透明代理。有些 CLI(例如 GitHub CLI)不会接受从命令行传入的 token,而是读取 GH_TOKEN 这类环境变量:

shine env proxy install gh --with GH_TOKEN
gh pr list

Shine 会在 ~/.shine/bin/ 创建同名 shim,并记录当前 PATH 中找到的真实命令。运行 gh 时,shim 只在它的子进程中解析 GH_TOKEN_SECRET;若不存在密文,才读取明文 GH_TOKEN。 该值不会写回或导出到父 Shell。--with 可重复使用,也可写成 KEY=ALIAS,以不同的变量名 传给目标命令。

代理转发子进程的 stdout/stderr,并保留其数字退出码。在 Unix 上,子进程被信号终止时返回 128 + 信号编号(例如 SIGTERM 返回 143);禁用注入或没有匹配规则时也遵循这一规则。

shim 会保留安装时选定的绝对运行时目录,包括自定义 --config-dir。之后即使调用者的 SHINE_CONFIG_DIR 不同,仍会使用安装时的目录;项目规则仍按调用时的工作目录发现。 重新运行 env proxy install 可更新旧 shim,或将其安装到另一个运行时目录。 代理安装要求运行时和目标路径为有效 UTF-8;无法表示的路径会在写入启动器、配置或回执之前被拒绝。

代理规则作用于整个命令,不能只匹配某个子命令。如果明确要代理 Cargo,应在不需要 token 时 停止注入:

shine env proxy install cargo --with CARGO_REGISTRY_TOKEN
shine env proxy disable cargo

# 之后只在需要凭据的操作前启用:
shine env proxy enable cargo
cargo yank my-crate@1.2.3
shine env proxy disable cargo

启用期间,每个 Cargo 子命令及其后代进程都可能继承 token。disable 会保留 shim,只停止 解密和注入,并直接转发到真实 Cargo。偶尔执行 yank 时,应优先采用上面的单次 env run。

只代理你明确允许的裸命令名;命令名只能包含 ASCII 字母、数字、-、_ 或 .。安装前请确认 ~/.shine/bin/ 已在 PATH 的靠前位置,且目标命令不是另一个 Shine 代理。若同名入口已存在 但并非 Shine 创建,安装会拒绝覆盖它。在 Windows 上,Shine 会在写入任何文件前检查整组启动器, 包括 .cmd 和 .ps1;冲突文件或符号链接都会保留。查找真实命令时,会跳过实际指向 Shine bin 目录的 PATH 项,包括符号链接别名。在 Unix 上,修改代理规则也会保持配置文件 仅所有者可读写的权限。 在 Unix 上,查找目标命令也会跳过没有执行权限的文件,并接受指向可执行文件的符号链接, 同时保留命令的原始调用名称。 在 Windows 上,请使用不带可执行扩展名的命令,例如 gh,不要使用 gh.exe。 安装会拒绝 .exe、.com、.bat、.cmd 和 .ps1 后缀(不区分大小写)。 此前安装的带后缀名称仍可通过 shine env proxy uninstall <COMMAND> 清理。 Shine 拒绝代理 shine 或 shine.exe(不区分大小写),以防止启动器递归调用自身。 卸载会在修改规则或安装记录前检查整组启动器;发生归属冲突时,启动器和配置保持原样。 如果卸载过程中启动器发生变化,Shine 会保留变化后的文件并报错。

安装会在写入前校验规则文件、安装记录和整组启动器。写入失败时,Shine 会尝试恢复原文件 内容和 Unix 权限,并移除新建文件。并发编辑会保留;回滚不完整时会明确报错。这一回滚处理 可报告的写入失败,不涵盖进程突然终止。已捕获代理规则或代理安装记录的 TOML 解析错误不会回显文档内容; 无效输入仍会在修改启动器、规则或安装记录前阻止操作。

默认规则保存在全局 ~/.shine/config.toml。在含有 shine.config.toml 的项目内加入 --project,可将该命令的规则限定到项目;同一命令的项目规则会覆盖全局规则。 离开项目后,shim 会使用已有的全局规则;若没有适用规则,则直接执行原命令,不注入变量:

shine env proxy install gh --with GH_TOKEN --project
shine env proxy list

代理列表字段、安装目标提示和保存后警告的详情会转义终端控制字符;已保存的规则和实际程序目标保持原值。

需要临时保留 shim、但禁止任何解密或注入时,可禁用规则。禁用后命令会直接转发给真实程序:

shine env proxy disable gh
shine env proxy enable gh
shine env proxy disable gh --project

启用/禁用命令会分别显示已保存的配置文件和当前生效状态。不加 --project 时,即使在项目中 也只修改全局规则,因此项目覆盖规则可能保持不同的状态。如果保存后的状态检查失败,Shine 会警告规则已保存但无法确认生效状态;成功保存仍返回退出码 0。

不再需要代理时,移除 Shine 管理的 shim 及其用户级规则:

shine env proxy uninstall gh

如果真实命令被升级、移动或删除,重新执行安装命令以记录新的目标路径。

向远端命令提供变量与密钥​

通过 shine ssh 运行远端命令时,根据远端需要看到明文的范围选择方式:

目标本机命令明文可见范围
转发普通变量shine ssh --with API_URL dev远端登录 shell 或指定命令
解密并直接转发一个密钥shine ssh --with-secret API_TOKEN dev远端登录 shell 或指定命令
由远端子命令按需请求本机解密shine ssh --secret-broker ... dev仅获准启动的远端子进程

直接转发密钥适合可信远端上的临时操作,但远端登录 Shell 和同账号进程可能读取明文。若解密私钥 必须保留在本机,而且只有指定远端子进程应接收明文,请使用 SSH Secret Broker;远端管理员或 同账号进程仍可能检查该子进程。

设置命令、本机策略、登记流程和完整安全边界见 SSH 会话:密钥代理与文件传输。

从 dotenv 初始化工作区​

已有 Vite 风格的 .env 文件时,可在项目根目录生成 Shine workspace 和对应的 TOML 环境源:

shine env workspace init --from-dotenv --dry-run
shine env workspace init --from-dotenv

它读取 .env、.env.local、.env.<mode> 与 .env.<mode>.local,自动发现 mode,并保持该覆盖顺序。原 dotenv 文件不会被修改;生成目标已存在时命令会拒绝覆盖,包括悬空符号链接和并发导入创建的文件,确认后才添加 --force。只导入指定 mode 时可重复使用 --mode:

shine env workspace init --from-dotenv --mode development --mode production

未使用 --force 时,预览显示 Would create,写入成功后显示 Created。 使用 --force 时,消息说明会写入并在目标已存在时替换,不会声称所有目标都是新文件。 --dry-run 始终不会写入或替换文件。

导入时可将明确知道的敏感键放进 [secret],之后配置 recipient 并封存。未标记的值会作为明文导入;不要把实际凭据误当作普通配置提交。 导入的 Secret 在封存前也仍是明文。Unix 上,生成的 workspace 与环境源文件使用仅所有者可读写的 0600 权限,使用 --force 替换时同样如此。

shine env workspace init --from-dotenv --secret DATABASE_URL
shine env secret seal

为避免改变 dotenv 语义,含 $(包括 $BASE_URL 和 ${BASE_URL})或带转义的双引号内容的值会被拒绝,即使它们位于引号内。先解析插值再导入;需要字面量美元符号时,请手动编写 Shine 环境源。单行反引号值会保留其中的 #、引号、反斜杠和空白;跨行引号值会被拒绝。未加引号的 # 表示注释开始;值本身含 # 时请加引号。即使没有选择 --secret,生成文件仍会保留带说明的空 [secret] 表。

将 workspace 导出为 dotenv​

当其他工具需要普通 dotenv 文件,或者准备停止使用 Shine env 时,可导出某个 mode 合并后的最终结果:

shine env workspace export \
--format dotenv \
--mode production \
--output .env.production.local

--format 为必填项,以便明确导出格式。命令按 workspace 声明的顺序合并环境源,但不会混入当前进程变量或 --with 值。默认只导出最终生效的 [plain] 项,不会解密 payload;若后层 secret 覆盖了前层 plain,同名旧明文也不会被导出。

只有目标确实需要完整可运行环境时,才显式包含已经封存的 secret:

shine env workspace export \
--format dotenv \
--mode production \
--output .env.production.local \
--include-secrets

这会把 secret 以明文写入文件。在 Unix 上,含 secret 的新输出文件权限为仅所有者可读写的 0600;无论使用什么平台,都应将它排除在版本控制之外。 未指定 --force 时,已有目标(包括悬空符号链接)会被保留,即使它是在等待解密期间出现的。 多个导出同时写入同一目标时,只允许一个成功。确认需要替换已有输出后,再添加 --force。 --dry-run 只报告 mode、目标路径和最终生效的非机密变量数量,不显示值,也不写文件。 即使指定 --include-secrets,预览也不会解密或统计机密;该选项仅在实际导出时生效。 被机密覆盖的普通变量也不计入预览数量。预览、成功消息和目标已存在的错误提示会为目标路径 加引号并转义展示:换行和制表符显示为 \n 和 \t。这只改变消息,实际目标路径和导出字节不变。

导出文件不含 Shine 元数据,也不依赖 Shine 运行。若要停用 Shine env,请逐个导出并验证所需 mode,将含 secret 的输出加入 .gitignore,移除 shine env run 包装,最后再自行归档或删除 shine.workspace.toml 与对应的 *.shine.toml 环境源。导出命令不会删除这些源文件。

导出使用 dotenv 引号保留反斜杠、引号和换行原值。含 $、回车、NUL 字节或无法安全引用的值会在写入或替换输出前被拒绝。遇到这些值时,请继续使用 shine env run,或为目标工具单独准备输出文件。切换前务必用该工具验证导出结果。

使用分层项目环境​

在项目根目录创建 shine.workspace.toml,声明可用 mode、按顺序合并的文件和项目共享的 GPG recipient:

version = 2

[env]
modes = ["development", "production"]
default_mode = "development"
files = [
".env.shine.toml",
".env.local.shine.toml",
".env.{mode}.shine.toml",
".env.{mode}.local.shine.toml",
]

[env.encryption]
gpg_recipients = ["user@example.com", "team-backup@example.com"]
# 团队使用 age 时,取消以下两行注释,并填入每位成员的 recipient
# backend = "age"
# age_recipients = ["age1tag1qexample...", "age1qteammate..."]

后面的环境文件覆盖前面的文件。{mode} 会替换为 --mode 指定的值;省略 --mode 时使用 default_mode。环境源文件可以同时包含明文值和待封存的 secret:

version = 1

[plain]
VITE_APP_NAME = "Example App"

[secret]
DATABASE_URL = true
API_TOKEN = false
SENTRY_TOKEN = "<待封存的值>"

[payload]
data = "<由 Shine 管理的 GPG 密文>"
  • true 保留 payload 中已有的密文值。
  • false 会在下次 seal 时安全提示输入。
  • 字符串会在封存后替换为 true,避免明文继续留在文件中。

封存待处理的 secret,再用合并后的环境启动命令:

shine env secret seal
shine env run --mode production -- bun run build

seal 默认处理 workspace 引用的环境文件。可用 shine env secret seal <FILE> 只处理一个文件, 或通过 --workspace <FILE> 指定其他 workspace;-r/--recipient 可临时覆盖接收者。 普通 seal 的完成消息会将来源路径中的终端控制字符显示为转义文本。

默认情况下,当前进程已经存在的环境变量优先于 workspace。设置 env.override_process_env = true 后改由 workspace 值覆盖;显式 --with 始终具有最高 优先级。

策略和源均为单后端且配置了可用 recipient 时,env run 会在系统缓存目录按 mode 使用所选后端保存加密缓存。 workspace、源文件内容或文件顺序变化后缓存会自动重建;无需手工编译或删除缓存。 非 UTF-8 项目目录不会与替换字符拼写的另一个目录共用缓存。 缓存文档或解密后的内容格式损坏时,警告不会回显缓存值或文档内容;Shine 会从工作区源文件 重新编译。缓存读写警告中的路径和错误文本会转义控制字符;子进程输出和退出码保持不变。

个人覆盖文件应加入 .gitignore:

.env.local.shine.toml
.env.*.local.shine.toml

不要提交含有尚未封存字符串的环境文件。可在提交前搜索 [secret] 项并确认它们都已变为 true。

环境文件结构和覆盖顺序见配置参考。

让 GPG 与 age 成员共享一份 payload​

从 Shine 2.0.x 转换工作区前,先将所有读取端(包括 SSH broker 所在本机)升级到支持 hybrid 的版本。旧读取端无法读取 hybrid: 密文。封存需要 GnuPG 2.2–2.5 和 age 1.3+, 读取端只需所选后端。既有未标记 GPG 和 age: 密文继续 可读,普通读取不迁移文件。显式配置工作区,替换以下公钥占位符:

[env.encryption]
backend = "hybrid"
gpg_recipients = ["<full 40-hex primary fingerprint>"]
age_recipients = ["age1..."]

运行 shine env secret seal --workspace shine.workspace.toml。读取旧密文需要其对应 identity;重新封存需要两种加密工具及完整公钥名单。任意一组均可独立读取同一份数据密文。 混合 GPG 密钥封装排除本机选项文件与隐式额外收件人;group 和模糊用户 ID 会在解密已有 秘密前被拒绝。

工作区策略为 hybrid,或当前 mode 消费的源含混合 payload 时,env run 使用本机选定的 GPG 或 age 后端保存加密编译缓存。建立缓存只需要该后端;收件人取工作区对应名单, 不回退到全局名单。名单缺失时跳过缓存;缓存写入失败只警告,不阻止使用成功编译的环境运行。

命中缓存后只解密一次合并结果。不设置 TTL,也不保存解锁状态:每次读取仍遵循后端的 授权要求。工作区或源内容、源顺序、所选后端、收件人变化会使缓存失效。GPG 和 age 缓存 分开保存,旧缓存不能替代 hybrid 源。畸形混合封装在缓存解密前失败;缓存解密开始后, 失败或取消都会终止命令,不再重试解密源文件。导出不使用此缓存,默认省略秘密; --include-secrets 才释放秘密,dry-run 不解密秘密。

可在选定的 shine.workspace.toml 旁创建个人配置 shine.config.local.toml, 使用 --workspace 时也以该工作区所在目录为准:

hybrid_decrypt_backend = "age"

将 /shine.config.local.toml 加入该项目的 .gitignore。本机 env run、 env secret seal 和 env workspace export 使用此偏好。文件或字段缺失时继承全局 config.toml;两层都未设置时沿用能力检查和交互选择。配置无效会明确报错。 此文件目前只接受 hybrid_decrypt_backend = "gpg" | "age"。它属于受信任的本机项目配置, 能够修改它的程序也能改变所选授权路径。SSH broker 请求不加载或传输此文件;实际解密 机器仍使用自己的配置并执行原有释放检查。

封存要求源文件为普通文件,拒绝符号链接;Unix 上还会拒绝具有多个硬链接的文件,避免 原子替换后明文仍留在其他链接文件中。GPG 加密和解密显式使用标准输出,不受本机 output 设置影响;加密输出为空时会报错,不会替换源文件。

封存会锁定其他遵守协议的 Shine 封存进程,并在替换前复核工作区与源文件捕获字节。 硬件批准期间编辑将终止当前文件;此前完成的文件保留,剩余文件不再处理。封存期间请勿 编辑:不遵守锁的编辑器可能在最后复核到替换之间产生竞态。失败后明文待封存项可能仍在, Shine 不会声称已清除。.shine-seal.lock 锁文件可能保留,不含秘密;封存仍在进行时不要删除。

临时机密文件​

使用 workspace version 3,可将签名 keystore、密码文件和服务凭据加密后保存在 Git 中。 文件组是命名的映射集合,不是目录。每条映射包含密文 source 和明文 target;一个组可覆盖 多个目录,重复使用 --file-group 可选择多个组。应用代码读取普通文件,不需要 Shine API; 使用自动解密的构建入口才需要安装 Shine CLI。

将接收者占位符替换为实际公钥,解密身份保存在仓库外:

version = 3

[env]
default_mode = "staging"
modes = ["staging", "production"]

[env.encryption]
backend = "age"
age_recipients = ["<actual age recipient>"]

[[file_groups.android-signing.files]]
source = ".secrets/{mode}/release.jks.enc"
target = ".android-signing/release.jks"

[[file_groups.android-signing.files]]
source = ".secrets/{mode}/signing.properties.enc"
target = ".android-signing/signing.properties"

[[file_groups.service-account.files]]
source = ".secrets/{mode}/service-account.json.enc"
target = "app/.credentials/service-account.json"

两个路径均相对 workspace 所在目录解析,并支持 {mode}。--mode 优先于 env.default_mode;所选环境必须属于 env.modes 或 env.default_mode。缺失文件时失败, 不回退到其他环境。所有环境使用相同映射清单。Version 2 workspace 仍受支持,旧客户端会拒绝 version 3。Version 3 配置了文件组时可省略 env.files;此类 workspace 的 env run 必须选择文件组。

在 Git 仓库使用前,先忽略所有明文目标,例如 .android-signing/ 和 app/.credentials/。 已跟踪或未被忽略的目标会被拒绝,包括工作区文件已缺失的已跟踪路径的大小写或 Unicode 等价名称。 Shine 不修改 .gitignore。 检查以每个目标所属的仓库为准,包括嵌套仓库。Git 检查失败会在释放明文前终止操作。 准备好明文后执行:

shine env secret seal --mode production --file-group android-signing --file-group service-account --dry-run
shine env secret seal --mode production --file-group android-signing --file-group service-account --remove-plaintext
shine env run --mode production --file-group android-signing --file-group service-account -- pnpm android:build

seal --file-group 读取 target 并创建或更新 source,复用现有 GPG、age 和 hybrid 策略, 包括 CLI 覆盖与 hybrid 收件人约束。默认保留明文。--remove-plaintext 会先解密并逐字节验证 全部写入的密文,再开始删除明文,因此需要可用身份,并可能触发授权。删除前验证失败或取消时, 保留全部原始明文。多文件写入和删除不是整体事务;失败会报告已完成项及剩余项。 首版不提供仅从密文更新收件人的专用流程。

如果希望长期保留解密后的普通文件,让工具直接使用而无需每次通过 Shine 启动,可将选中文件组 导出到配置中的 target:

shine env workspace export --format files --mode production --file-group android-signing --file-group service-account --dry-run
shine env workspace export --format files --mode production --file-group android-signing --file-group service-account

--mode 必填。此操作只导出选中的文件组,不导出环境变量。全部文件解密成功后才开始创建目标。 已有目标一律拒绝覆盖,包括并发写入时出现的目标;重试前应先检查并移走这些文件。 files 格式不接受 --force、--output 或 --include-secrets。Workspace 配置和密文保持不变, 路径、Git 忽略、大小限制和私有权限要求与临时解密相同。

成功导出的文件会持续保留,由你管理或删除。env run --file-group 会拒绝这些已有目标, 不会接管或清理它们。导出失败或受控中断时,Shine 尝试移除本次创建的文件和空目录,保留被替换 的对象,并报告清理失败及可能残留。多文件导出不是原子事务;强制杀死 Shine 或断电仍可能留下 文件。不要将明文提交到 Git。

env run --file-group 先解密全部选中文件,再准备目标;全部目标就绪后才启动命令, 在成功、失败或受控中断后清理本次文件。已有目标不会被覆盖;被替换的目标会被保留并报告。 如果子命令已删除目标文件或其父目录,该目标视为已完成清理;Shine 仍会清理其余映射, 并保留子命令退出码。替换后的文件、目录和悬空链接仍会被保留并报告。 在 Unix 上,子命令移除文件读取权限后,清理仍可删除身份未变的本次文件;不会为了清理而 恢复权限或删除替换后的文件。使用手机 identity 的 age 解密会保留二维码授权提示,以及 显式开启的终端消息;普通的已捕获后端诊断仍保持隐藏。 env run 和文件导出的映射预览写入 stderr,子命令的 stdout 保持逐字节原样;dry-run 预览仍写入 stdout。 映射预览会为每个路径加引号,并转义换行等控制字符,使一条映射保持在同一行;这些显示转义 不会改变文件操作使用的实际路径。封存、删除明文、导出的结果路径及清理警告也会转义终端控制字符。 Unix 新建明文文件为 0600、目录为 0700;Windows 要求文件系统支持当前用户私有 ACL。 清理失败返回错误,子命令失败时保留其退出码。包装器在 Unix 管理进程组,在 Windows 使用 Job Object。不要启动需要在被包装命令结束后继续运行的脱离后台任务;剩余受管子进程会被终止。

--dry-run 只检查配置和元数据,不读取明文、不解密、不写文件、不启动命令,也无法验证密文 完整性或私钥可用性。--file-group 分别与 seal <FILE>、--no-workspace 和 --secret-broker 互斥。普通 seal 和 env run 不会隐式选择文件组。

路径必须位于 workspace 内;拒绝绝对路径、父目录穿越、符号链接/重解析点、硬链接和映射重叠。 映射不能与 workspace 定义文件重叠,包括仅大小写或 Unicode 规范化形式不同的名称。各平台均保守拒绝这些路径别名。 单个明文文件上限为 16 MiB,单次总量上限为 64 MiB。 编码后的密文单文件上限为 32 MiB、单次总量上限为 128 MiB。文件密文使用 Shine 现有带标签的存储编码,不是原生 .age 或 .gpg 文件。

子命令及拥有相应本机权限的其他进程可以读取临时明文。强制杀死 Shine 或断电可能留下文件: 重试前应人工检查已有目标。删除文件不代表安全擦除。构建不会自动把改动后的目标重新加密回仓库。 删除收件人也无法撤销其对 Git 历史密文的访问能力。