跳到主要内容
版本:2.0

管理应用配置

应用预设把配置文件安装到目标应用使用的位置,并通过 ~/.shine/app-manifest.toml 记录受管文件。安装前遇到已有的非受管文件时,Shine 会先创建 *.shine.bak 备份。

它们只管理配置文件,不安装、下载或启动对应应用。所有内置类别的目标路径、平台限制、权限与重启要求见内置预设

查看与预览

shine app list
shine app info starship
shine app install starship --dry-run

涉及系统目录的预设可能需要额外权限。先使用 --dry-run 确认目标路径和变更范围。

安装与更新

shine app install starship
shine install app/starship
shine update
shine upgrade

shine update 比较当前安装结果与预设,只报告状态;shine upgrade 将受管 shell 和应用配置更新到当前预设内容。

如果只需覆盖一个类别的受管文件:

shine app install starship --replace-managed

将旧版 App metadata 迁移到 Shine 2

当前 App metadata 会在 shine.toml 根级声明自身语法版本:

metadata_schema_version = 2

它不同于 [permissions].schema_version,后者声明的是权限语法版本。若 overlay 覆盖了 app/<category>/shine.toml,但没有这个根级字段,它就是旧版 v1 metadata。先运行 shine preset migrate --dry-run 审阅,再运行 shine preset migrate,在默认 No 的确认后应用所显示的 metadata-only diff;若它不是当前激活来源,可传入仓库、类别或 manifest 路径。迁移器只会移除精确 指向同一类别的递归 shine app artifact apply hook;merge.yamlrules/ 等 payload 覆盖会保留, 报告也会提醒 artifact 仍需显式执行。写入前会创建私有备份。不要通过授予外部代码信任来绕过 不兼容。

opaque hook、generator 或 artifact 仍需作者编写 target-local 权限,并通过 shine preset validateshine preset plan 验证。shine state migrate 仍只迁移 Shine 自己拥有的运行状态,绝不会重写 Preset source 或 overlay。

卸载与恢复

shine app uninstall starship --dry-run
shine app uninstall starship
shine app uninstall starship --purge

install、upgrade、uninstall、generator refresh 和 artifact apply/remove 会在 mutation 前显示 绑定快照的 Plan,确认默认是 No;非交互执行使用命令级 --yes。该参数仍会显示并重新校验 Plan,不能绕过缺失权限、被阻塞的 teardown 或外部代码 gate。upgrade 仅在审阅命令包含 --prune-stale 时移除 App stale 文件。未修改的静态 Copy 与 JSON stale 条目会复用卸载所用的 receipt-gated journal;用户修改过的 stale 内容仍会保留。

当 metadata 把静态 Copy 文件迁移到新的 effective destination 时,upgrade 会把旧 receipt 与 destination、可选固定 backup、rollback 路径和必须为空的新 destination 纳入同一个 relocation 事务。旧受管文件必须未修改(或者在没有 backup 时已经缺失),新路径也必须空闲;新路径被占用或旧 文件被修改时会保留现状并报告冲突。

默认情况下,安装后被用户修改过的文件会保留并标记为用户修改。若安装时创建过备份,安全卸载会恢复原文件。在受支持、已 journal 的静态 Copy 替换不受管 regular-file destination 前,Shine 要求固定的 <name>.shine.bak 路径不存在;已有 backup 会阻塞 Plan,并保留两个文件。--purge 还会删除相应预设目录;卸载全部类别时也会删除 manifest。

app uninstall --force 会显式授权删除被用户修改过的受管内容。对于符合条件的静态 Copy,审阅的 Plan 会标明该 override;事务会先把修改后的文件暂存到 <name>.shine.rollback,直至 receipt commit,并在同一事务中还原可选的固定 backup。管理员静态 Copy 的创建、原地更新和移除使用同一 journal 与 recovery contract;受保护路径的 write、move、mode 还原与 cleanup 会在管理员权限下 执行。JSON merge 的 install、原地 update、普通 uninstall 和强制 uninstall 也会写入 journal。 其他安装策略仍使用原有 lifecycle 路径。执行破坏性操作前请使用 --dry-run 预览。

恢复中断的 App 操作

在受支持的 App 文件 mutation 之前,Shine 会先写入 operation journal。如果进程在这之后中断, App install、upgrade、uninstall、refresh 和 artifact 等 mutation 命令会保持阻塞,避免静默 丢弃恢复状态;只读 status/update 检查不会恢复或删除 journal。使用以下命令审阅并应用独立的 recovery Plan:

shine app recover
# 仅在已经审阅同一 Plan 的非交互环境中使用:
shine app recover --yes

恢复只接受与事务日志所记录的文件类型、模式、哈希、回执和路径布局完全一致的状态。相关替换回执或 receipt-committed 标记持久化之前,恢复会回退尚未提交的操作;提交后则保留已经完成的结果,并且 只清理未修改的事务文件。任何受保护路径在中断后发生变化,都会阻塞恢复并保留现场,等待显式处理。

创建与原地替换

  • 目标路径原本不存在。 只有事务创建的文件仍与 Shine 写入的内容逐字节相同,恢复才会将其删除。
  • 创建时保留了固定备份。 只有固定备份仍匹配原始内容,且目标路径缺失或仍匹配受管内容时,恢复 才会还原备份。若备份移动尚未开始,恢复会保留原始目标文件。
  • 原地替换已有回执的静态 Copy Shine 会先把上一个受管文件移到同目录的 <name>.shine.rollback。替换回执持久化前,只有目标文件和回滚文件仍分别匹配记录的目标指纹和原有 指纹时,恢复才会继续。回执持久化后,恢复会保留目标文件,只清理未修改的回滚文件和过期事务日志。

卸载静态 Copy 文件

  • 没有固定备份的普通卸载。 Shine 会先把未修改的受管文件移到 <name>.shine.rollback,并保留到 回执移除持久化完成。精确的旧回执仍存在时,恢复会还原该文件。如果回执已经消失,但缺少匹配的 receipt-committed 状态,恢复会采用保守回滚:重建旧回执并还原未修改的文件。只有回执移除和事务 日志中对应的提交状态都已持久化,恢复才会清理回滚文件,而且仅在其类型、模式和内容均未变化时执行。
  • 带固定备份的卸载。 事务日志会记录两次移动:先把受管文件移到 .shine.rollback,再把 .shine.bak 还原到目标路径。回执提交前,恢复只接受这两次移动之前、之间或之后产生的精确三路径 状态;必要时会先把已经还原的用户文件移回 .shine.bak,再恢复受管文件与旧回执。提交后,恢复会 保留目标路径中未修改的用户文件,只清理未修改的受管回滚文件。这两个文件的模式与内容指纹都必须 匹配。
  • 强制卸载被用户修改的文件。 恢复会分别校验旧回执的哈希,以及修改后文件的模式和哈希。回执 提交前,如果回执已经消失但没有 receipt-committed 状态,恢复会先重建旧回执,再还原这个精确的 修改后文件并反转可选的备份还原。提交后,恢复会保留已完成的卸载,只移除与修改后文件精确匹配的 回滚文件。

JSON merge 与 stale 清理

  • JSON merge。 声明的顶层键是所有权边界。Shine 会把已有的完整 JSON 对象移到 .shine.rollback,但恢复只从中读取并还原这些键,同时保留中断后发生变化的其它当前值。如果目标 路径原本不存在,只有当前对象不含其它键时,恢复才会删除整个文件。卸载回执提交后,当前 JSON 对象 已归用户所有;即使用户重新加入曾受管的键,恢复也只会清理未修改的回滚文件。
  • upgrade --prune-stale 未修改的静态 Copy 和 JSON 条目使用相同的移除恢复规则。如果回执 已经消失,但对应的 receipt-committed 标记尚未持久化,恢复会重建旧回执,并且只还原精确匹配的 回滚状态。目标路径已经缺失时只清理回执;此路径绝不会强制移除用户修改过的 stale 内容。

迁移目标路径

  • 静态 Copy 迁移。 新回执持久化之前,恢复只会移除未修改的新文件;必要时会把已经还原到旧 目标路径的用户文件放回固定备份,再恢复精确的旧受管文件。新回执持久化后,恢复会保留两端的最终 状态,只清理未修改的旧回滚文件。
  • JSON merge 迁移。 此场景使用独立的键所有权事务。新回执持久化前,恢复只移除新目标路径中的 目标键,并只还原旧目标路径中的原有键,同时保留两端其它当前设置。回执提交后,旧 JSON 对象已归 用户所有;只要新对象的受管键集合未修改,恢复就会保留两端,只清理精确匹配的旧回滚文件。

管理员路径与被阻塞的恢复

  • 管理员路径。 当创建、更新、目标路径迁移或移除的恢复需要修改管理员路径时,恢复 Plan 会包含 管理员权限。Shine 只在该 Plan 获得批准后请求授权。仅重建回执或清理事务日志的恢复不会请求管理员 权限。
  • 敏感回滚文件与冲突。 回滚文件可能包含之前的受管配置,应按敏感内容处理。如果任一受保护路径 在中断后被修改,恢复命令会返回非零,并保留这些路径和事务日志。把普通文件替换为符号链接或目录也 视为修改。不要手动编辑或删除事务日志或回滚文件。

配置变换

部分预设会在安装前处理源文件,例如:

  • jsonc-to-json:移除 JSONC 注释和尾随逗号,再写入标准 JSON。
  • template:用当前 [env] 值替换 @@VAR_NAME@@
  • json-merge 安装模式:只维护目标 JSON 中声明的顶层键,保留其它用户设置。

shine update 比较的是变换后的最终结果,而不是原始预设文件。

生成式文件与 Surge URI 订阅

App 预设可以为 [[files]] 声明 generator,把命令的 UTF-8 stdout 作为该受管文件的预期内容。生成结果仍经过正常的变换、hash、manifest、用户修改保护和卸载流程,不应由脚本绕过 Shine 直接改写目标文件。

生成器可分为自动和手动两类。普通 listinfoupdate 都不会运行它们;无法在不执行代码的 情况下计算动态预期内容时,info/update 会醒目显示 generator not evaluated,不会声称已安装文件 是最新状态。使用 --run-generators 可以显式执行所选 generator、在内存中应用 transform,并检查 状态或最终 diff,而不会写入目标文件或 manifest:

shine app info surge --run-generators
shine info app/surge --run-generators --diff
shine update app/surge --run-generators --diff
shine update --run-generators

全局形式会评估所有已安装 App 类别;定向形式只评估选中的 App。由于该参数已经表达明确意图,自动 generator 和 auto = false 的手动 generator 都会参与。外部或 overlay generator 仍需要匹配的 scoped trust。单项失败不会阻止其余 generator 继续评估,但命令会在报告不完整结果后返回非零状态。

自动 generator 也可以在获批的安装或升级中运行;auto = false 的手动 generator 可在安装、显式 评估或显式 refresh 中运行:

shine app refresh <CATEGORY>
shine app refresh <CATEGORY> <SOURCE_FILE>

指定文件时,SOURCE_FILE 是预设 [[files]].source 的相对路径。刷新失败会保留上次成功内容; 目标已被用户修改时也会保留,只有确认要覆盖时才添加 --force。安装和带 --replace-managed 的修复安装会运行已由 when_env 启用的生成器,不受 auto 设置影响。 refresh 会显示并重新校验安全 Plan;自动化调用必须添加 --yes

外部预设或 overlay 提供的 generator 属于可执行代码,需要审阅后运行 shine trust grant app/<CATEGORY>。Shine 只向它传入预设显式声明的 env 值及固定的 SHINE_APP_* 路径变量,并限制执行时间和输出大小;仍应只运行自己审阅和信任的预设。

类别根部的 [permissions] 会另外声明 generator、hook、artifact 的 command、network scope 和 环境变量敏感度,供静态校验与后续安全 Plan 使用。该声明不会启用或信任外部代码;其中不得写入 URL token、环境变量值、命令参数或密文。

Surge URI 订阅

内置 surge 预设可把 HTTPS Base64 URI 订阅转换为受管的 subscription-proxies.conf。此功能需要 Bun,支持兼容的 ss://vmess:// 记录;VLESS、不支持的 transport、插件、坏记录与重复项会被跳过,并只输出不含凭据的摘要。用户维护的 local-proxies.conf 不会被改写。

要定制 Surge 的本地代理、策略组或规则文件,先将完整内置预设复制到自己的局部 overlay:

mkdir -p ~/dotfiles/shine-overlay
cd ~/dotfiles/shine-overlay
shine preset copy app/surge
shine preset overlay link .

编辑复制出的 app/surge/local-proxies.conflocal-proxy-groups.conflocal-rules.conf,再安装预设。只打算定制其中部分文件时,可以删除其余复制出的文件:overlay 按相对路径覆盖,缺失的文件会继续使用内置版本并随 Shine 更新。不要直接修改 Surge Profiles 目录中的受管副本。

先配置 URL 并安装:

shine env set SURGE_SUBSCRIPTION_URL 'https://provider.example/subscription?...'
shine app install surge

该生成器为手动模式,日常 shine updateshine upgrade 不会访问订阅。需要刷新时,先打开 provider 的访问窗口,再运行:

shine app refresh surge subscription-proxies.conf

刷新成功且内容变化后会通过现有 post_upgrade 钩子 reload Surge;失败时保留上次成功文件。local-proxy-groups.conf 中的 Subscription 组通过 policy-path=subscription-proxies.conf 读取节点,其它策略组可用 include-other-group=Subscription 纳入这些节点。

构建辅助资源

部分 app 预设会在 shine.toml[artifact] 中声明脚本。需要生成或刷新这类资源时,手动运行:

shine app artifact apply surge

Shine 不会隐式运行 artifact 操作。生命周期可以通过纳入父 Plan 的脚本型钩子复用同一个脚本, 但不要在钩子中调用 app artifact apply:artifact 有独立的 快照绑定 Plan,不能继承父 App 操作的批准。手动 apply/remove 会显示并重新校验安全 Plan;自动化 调用必须添加 --yes,执行失败会让命令直接失败。 当某个声明 artifact 的类别在安装或升级中确实改动了受管文件时,Shine 会打印显式 apply 命令;没有受管文件 发生变化时则不提示。脚本只会收到 [artifact].env 列出且已配置的 source,并且 这些 source 还必须在类别 [permissions].environment 中声明;此外会加入 SHINE_APP_HTTP_DIRSHINE_CACHE_DIRSHINE_STATE_DIR 等固定路径变量,适合生成放在 ~/.shine/http/app/<APP_ID>/ 下的本地资源。完整变量说明见任务与本地服务

内置 surge app 预设会把 local-proxies.conflocal-proxy-groups.conflocal-rules.conf 和可选的订阅生成文件安装到 Surge Profiles 目录。设置 [env] 中的 SURGE_PROFILE 后,shine app artifact apply surge 使用内置 Bun artifact 幂等修补活动配置文件的 [Proxy][Proxy Group][Rule] #!include 行。Overlay 只需覆盖自己的策略文件,无需提供构建脚本。

预设还安装默认注释、不立即生效的 LAN NetworkLAN PROXYOther Direct 规则示例。每类规则在 local-rules.conf 中提供三种互斥来源:随 Profile 安装的相对 rules/*.list、同设备 loopback HTTP 地址,或自行替换域名的远程 HTTPS 地址。每类只启用一种;相对文件通常最简单。localhost 始终指运行 Surge 的设备,在 iOS 上不会指向另一台局域网主机。

需要撤销这项修补时运行:

shine app artifact remove surge

artifact remove 只运行预设声明的 teardown 脚本。卸载带有 teardown 的 app 时,Shine 也会尽力执行清理;清理失败只会警告,仍会继续安全卸载受管文件。

Clash Verge Rev

内置 clash-verge 预设提供一个默认无效果的 merge.yaml 示例。要叠加自己的代理、策略组、rule-provider 和前置规则,先将完整内置预设复制到自己的局部 overlay:

mkdir -p ~/dotfiles/shine-overlay
cd ~/dotfiles/shine-overlay
shine preset copy app/clash-verge
shine preset overlay link .

这会创建 app/clash-verge/,其中包含当前 Shine 版本附带的 merge.yaml、元数据和构建脚本。编辑其中的 app/clash-verge/merge.yaml,填入实际配置;不要直接修改 ~/.shine/clash-verge/,该目录是 Shine 安装后的受管副本。只打算定制 merge.yaml 时,可以删除复制出的其它文件:overlay 按相对路径覆盖,缺失的文件会继续使用内置版本并随 Shine 更新。

确认内容后安装预设:

shine app install clash-verge

首次使用时,在 Clash Verge Rev 当前订阅中依次打开并保存 Extend ConfigEdit RulesEdit ProxiesEdit Groups 四个订阅级编辑器,然后运行:

shine app artifact apply clash-verge

Shine 只读取 profiles.yaml 定位这些绑定文件,不会修改订阅、创建绑定或写入远端订阅 YAML。构建写入新内容后,在 Clash Verge Rev 中重新选择一次订阅,再运行构建即可请求立即刷新 rule-provider。

示例沿用上述三类流量,并为 rule-provider 提供三套互斥布局:mihomo HomeDir 内的 type: file、同设备的 loopback HTTP 服务,或远程 HTTPS 服务。Shine 会通过普通受管 app 文件把三份默认不生效的参考规则安装到 HomeDir/ruleset/shine-source/;只有选择 file provider 时才需要在 overlay 中覆盖这些文件。loopback 和远程 HTTP 布局不会引用它们,因此 URL、interval 与 provider 缓存路径保持不变。shine upgrade 实际更新该类别后,会自动运行已审批的 post-upgrade 脚本。若订阅绑定内容已经生效,本地参考规则更新会立即刷新全部 provider 并关闭旧连接;若渲染结果改变了绑定文件,脚本只写入文件,不请求 mihomo 尚未加载的 provider。此时请重新选择订阅,再运行 shine app artifact apply clash-verge。选择一整套 provider 后,还需同步启用对应策略组与 prepend-rules。loopback 或私有服务的 proxy: DIRECT 只控制 provider 下载,如服务器只能经代理访问,应删除或调整它。私有域名依赖系统 split DNS 时,还需配置 mihomo 自己的 dns.nameserver-policy

artifact 和 post-upgrade 脚本都使用 Bun,运行机器必须已安装 Bun。自动钩子仅在 shine upgrade 确实改动 clash-verge 受管文件时运行;首次设置、重选变化后的绑定或刷新失败重试仍使用显式命令。外部脚本型钩子需要当前 target-scoped trust grant。即时刷新还可使用 [env] 中的 CLASH_CONTROLLER_URLCLASH_CONTROLLER_TOKEN;未配置 URL 时只跳过立即刷新,provider 仍按自身 interval 更新。artifact 会刷新最终生效的 merge.yamlrule-providers 映射声明的全部名称,自定义 provider 名称无需同步修改脚本。该映射缺失、为 null 或为空时跳过刷新;存在但不是映射时报告配置错误。所有已声明 provider 都刷新成功后,artifact 还会关闭当前全部 mihomo 连接,使浏览器和其它应用自动重连并立即按新规则匹配,无需重启应用;正在进行的下载或其它长连接可能会短暂中断。控制器令牌不要写入 overlay 或文档。

shine app artifact remove clash-verge 不会清除 Clash Verge Rev 自己保存的订阅绑定;完全移除时还需在应用中手动清空上述四个编辑器。

生命周期钩子

预设作者可以声明 post_installpost_upgrade 钩子:前者在安装实际写入文件后运行,后者只在 shine upgrade 实际更新该类别至少一个文件后运行;未变化的类别不会触发。

每个钩子必须且只能声明一种动作。command 直接运行 argv;script 从已审查的 Preset 快照解析 native 或 Bun 脚本,将自身 env 声明的值与固定 SHINE_APP_* 环境一起注入,并作为父生命周期 Plan 的一部分执行。runtime 只能与 script 同用;Bun 脚本沿用 artifact 和 generator 的扩展名及锁定依赖规则。

钩子读取的每个环境输入都必须列入钩子的 env,并在类别权限声明中声明同名变量。Plan 审阅会对 plain 值取 hash,并以 opaque revision 绑定 secret 值;Plan 不会序列化任何原值。command hook 输入缺失或 secret identity 不可用时不能批准。脚本型 hook 的输入与 artifact 一样可选:缺失状态 会绑定进快照,但不会注入子进程环境。

post_upgrade = [
{ command = "my-reloader", env = ["API_URL", "API_TOKEN"] },
]

[permissions]
schema_version = 1
environment = [
{ name = "API_URL", sensitivity = "plain" },
{ name = "API_TOKEN", sensitivity = "secret" },
]
commands = ["my-reloader"]
post_upgrade = [{
script = "refresh.ts",
runtime = "bun",
env = ["API_URL", "API_TOKEN"],
}]

[permissions]
schema_version = 1
filesystem = [{ access = ["execute"], base = "preset", path = "refresh.ts" }]
network = [{ scope = "any" }]
commands = ["bun"]
environment = [
{ name = "API_URL", sensitivity = "plain" },
{ name = "API_TOKEN", sensitivity = "secret" },
]

外部预设中的钩子和 generator 需要 target-scoped trust:

shine trust inspect app/<CATEGORY>
shine trust grant app/<CATEGORY>

trust inspect 只读,不会授予信任。授予信任前,先处理 Plan 显示的所有缺失权限声明。若启用的 overlay 中 app/<CATEGORY>/shine.toml 是覆盖内置 metadata 的旧版完整副本,应先删除或迁移该 metadata 文件; merge.yamlrules/ 等 overlay payload 文件仍可保留。

钩子默认不显示 stdout。预设将 show_output 设为 true 后,安装和 refresh 会显示成功输出;shine upgrade 仅在 --verbose 下显示成功完成信息和输出。钩子失败或权限拦截始终可见,但不会中断其它类别的安装或升级。