TREK 插件安装与管理实战指南:从审查安装到隔离边界的完整避坑手册
【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK
你的自托管 TREK 实例运行得很稳,现在想加一个仪表盘 widget 或者通知渠道——要不要装第三方插件?TREK 的插件系统允许任何人在不改动源码的前提下扩展实例能力:仪表盘 widget、独立页面、行程页标签,乃至照片/日历/通知渠道等集成。装与不装之间,真正的分水岭是审查:装之前你同意的权限清单,装之后就是真实的边界。下文按"能不能装、怎么装、怎么养、边界在哪"的顺序,走完 Admin → Plugins 面板上的完整运维流程。
装之前先确认:运行时总开关、管理员权限与版本兼容
Admin → Plugins整个面板只对管理员开放,且要求插件运行时处于开启状态。面板里有两个视图(Installed 已安装 / Discover 发现)共享一套工具栏:搜索框、Type 类型筛选(Widget / Page / Integration / Trip page)、Sort 排序、Upload plugin 上传按钮、Rescan 重扫描按钮;Installed 视图额外多一个 Status 状态筛选(Active / Off / Update available / Error)。
插件运行时开关是怎么判定开没开的
运行时由环境变量TREK_PLUGINS_ENABLED控制,默认开启——但"运行时开着"和"某个插件在运行"是两回事:所有已安装插件必须逐个手动激活,激活之前不会执行任何第三方代码。
判定逻辑非常直接,见 server/src/nest/plugins/kill-switch.ts#L12-L14:只要取值不是false、0、off、no(大小写不敏感),就算开启;并且每次调用时实时读取,改掉环境变量、重启服务就立即生效。运行时关闭时,面板会显示提示"插件运行时已关闭(TREK_PLUGINS_ENABLED),在管理员于服务端配置开启之前,没有任何插件可以运行";开启时面板头部则出现绿色的Runtime on标识。
关闭后已安装插件仍保留在磁盘上(停用状态、无害),重新打开总开关即可恢复。其余三个相关环境变量在此一并交代:
| 环境变量 | 作用 | 默认值 |
|---|---|---|
TREK_PLUGINS_ENABLED | 插件系统总开关 | 开启 |
TREK_PLUGINS_DEV_LINK | 开发专用:允许从本地构建目录注册插件并热重载(仅当值恰为1) | 关闭 |
TREK_PLUGIN_ALLOW_PRIVATE_EGRESS | 设为on时允许插件出口访问私网/内网地址 | 关闭(默认拒绝私网出口) |
TREK_PLUGIN_REGISTRY_URL | 覆盖 Discover 页浏览的注册表索引地址(可指向自己的 fork/镜像) | TREK 官方注册表 |
TREK 版本不兼容时安装按钮会变成什么样
安装流程分三步:切到Discover视图(卡片展示图标、名称、作者、描述、类型、Reviewed徽章、Signed / Unsigned徽章、最新版本号和下载量)→ 点卡片打开预安装审查对话框 → 点Install。
其中有一个值得注意的版本兼容逻辑:如果最新版要求比当前实例更新的 TREK,按钮会变成Install {version}——安装那个仍兼容的旧版本;如果所有版本都不兼容,按钮变成Incompatible并禁用。无论哪种情况,对话框都会用琥珀色提示条直接解释原因,而不是把原因藏在 tooltip 里。服务端通过 server/src/nest/plugins/install/host-compat.ts 中的assertHostCompatible/hostSatisfies校验插件声明的trek版本范围,注册表、上传旁路、dev-link 三条安装入口加上激活闸门都走这同一处检查,保证插件永远不会跑在它声明不支持的宿主上。
顺带说清Rescan按钮:它重新发现磁盘上本地安装的插件,并且强制拉取远程注册表,绕过 30 分钟的服务端缓存(server/src/nest/plugins/registry/registry.service.ts 中CACHE_TTL为 30 分钟)和 GitHub CDN——刚发布的插件可以立即出现,而不必最长等约 35 分钟。注册表拉的是聚合的dist/index.json(而非逐个插件调 GitHub API,避免速率限制),拉取失败时软降级,不影响面板使用。
三条安装路径怎么选:注册表安装、上传旁路、本地开发链接
注册表安装是默认路径,前面已述。装下来的插件默认是off状态——安装流程只做下载、校验、安全解压、重新校验 manifest、注册(inactive),不执行任何内容(见 registry.service.ts 顶部注释)。在手动启用之前,它不会运行任何代码。
上传旁路(Sideload):点工具栏 Upload plugin,或直接把.zip拖到面板上。服务端sideload()先解压到 staging 区,套用与注册表安装相同的硬性防护——防 zip-slip/zip 炸弹的安全解压、严格 manifest 校验、拒绝原生二进制,唯一不适用的是 SHA-256/签名校验(因为没有注册表条目可对)。上传上限 50 MB(50 * 1024 * 1024 + 4096字节,registry.service.ts 与 plugins.controller.ts 中一致)。归档保持 inactive,激活时仍需同意权限。该行标记为Sideloaded:手动上传,不来自注册表,无签名、未审查。若用相同 id 覆盖上传,旧代码会先被强制停止并停用,替换进来的代码绝不可能是"未经重新激活仍在运行"的状态。
本地开发链接(Dev-Link):一个路径输入框,从本地构建目录注册插件,并针对真实数据热重载。仅限开发环境,且仅当服务端设置TREK_PLUGINS_DEV_LINK=1时入口才出现。服务端link()对代码目录创建符号链接(不复制),校验 manifest、拒绝原生二进制,注册为 inactive,再用fs.watch监听构建输出,重建后防抖 400ms 自动重新 fork(server/src/nest/plugins/dev-link.ts 说明了为什么这条通道必须显式开关)。该行标记为Dev-Link。
对自己诚实一点:Sideloaded 与 Dev-Link 都只是卡片上的标签,记录代码来源,但不做额外检查、不同沙箱、不加限制——它们以声明的权限原样运行,与注册表插件完全一样,且没有经过恶意软件扫描、没有签名。徽章的意义是让你一眼看出:除了你,没有人为这段代码背书。(source 徽章会替换这些行上的 Signed/Unsigned 徽章,因为它本身就是更强的声明。)
安装前审查:权限、出口主机与签名徽章一次看懂
点 Discover 卡片后弹出的审查对话框,是你做决定前唯一完整的信息窗口。
审查对话框四个区块各看什么
- What it can access(它能访问什么)——插件请求的权限,以平实语言逐条渲染(如"读取你的行程……""创建和编辑地点……")。未知权限代码原样显示;若不请求任何权限,则显示Needs no special access.
- Connects to(连接目标)——manifest 声明的所有可访问主机,等宽字体 chip 展示。
- Setup(配置)——插件将要求你(或每个用户)填写的设置项,标注Instance-wide(实例级)或Per user(用户级),必要时标注Required。
- Details(详情)——版本、体积、所需 TREK 版本范围、审查时间、总下载量。
对话框底部附Source repository、Report an issue与插件Homepage链接。关键一点:展示前服务端会拉取插件在审查提交点的实时 manifest 生成预览(ManifestPreview,见 registry.service.ts),包含权限列表、出口主机、operatorEgress标记、设置字段(key/label/inputType/scope/required)、许可证、图标、所需 addon 与插件依赖——对话框里的信息来自这份预览,而不是作者自己写的介绍文字。
自托管服务的出口主机(Allowed hosts)怎么配
有一类插件要访问只有你才知道地址的服务——自托管的 Gotify、ntfy 之类。manifest 无法预知这些主机名,因此会声明operatorEgress,审查对话框随之多出一个+ hosts you addchip 和提示:"此插件访问的服务只有你能命名(自托管服务器)。安装后在 ⋯ → Allowed hosts 下添加它可到达的主机,其余主机一律不可达。"
安装完成后,在该插件行⋯ → Allowed hosts里逐个添加主机。注意两点:
- 添加第一个主机前,插件行显示琥珀色Add allowed hostchip——因为此时它一个主机也够不到,不提示会像"静默故障";有了主机后 chip 变蓝并显示数量。
- 保存后会重启插件加载新白名单。运行中插件的出口守卫在子进程初始化时只安装一次、且拒绝二次
init(见 server/src/nest/plugins/plugin-runtime.service.ts#L557-L561 的注释),白名单永远无法在原进程内热扩容;删除某个主机后插件立即失去该出口并再次重启。
主机格式与 manifest 声明出口的规则一致:不允许裸*、不允许整 TLD 通配、不允许带协议前缀(校验正则同样定义在 plugin-runtime.service.ts 中,因为该字符串会直接插入出口守卫和 CSP)。卸载插件时这些主机一并删除,防止后续复用同一 id 的插件继承前人的出口权限。
三条硬边界记牢:未声明operatorEgress的插件永远无法被授予主机,安装时你同意的范围是硬边界;只有管理员能添加主机,普通用户即使自带凭据也不能扩大出口;服务若在 TREK 同机或同一局域网(localhost、192.168.x.x),还需TREK_PLUGIN_ALLOW_PRIVATE_EGRESS=on——默认插件不得访问私网,且该变量会放宽所有已安装插件的私网出口策略,只有当你信任全部插件时才开。
Reviewed / Signed / Unsigned 徽章各保证什么、不保证什么
- Reviewed(已审查):TREK 维护者对每一个版本做了恶意软件扫描——但不承诺质量、可用性,也不是"无害"担保。
- Signed(已签名):安装时文件已对照作者签名密钥校验,且密钥被钉住(TOFU)。校验和证明文件是注册表担保的字节,签名则进一步证明这些字节来自作者。
- Unsigned(未签名):目前注册表大多数插件未签名,所以是琥珀色提示而非警报。原文只有一句:The files match what the registry vouches for, but nothing ties them to the author. One guarantee fewer — not unsafe.
要反复强调:两个徽章都不说明代码做了什么。面板底部有可折叠的How plugins are contained — and the limits面板,完整阐述隔离模型的承诺与局限(下文第六节展开)。
启用、更新与停用插件:依赖级联和权限再同意机制
每个插件行有一个Enable plugin开关与Active / Off状态;图标块上的圆点反映健康状态:active(绿)、starting(蓝闪烁)、error(红)、inactive(淡)、disabled/incompatible(琥珀)。
启用插件被拒的三类原因与补救步骤
每次启用都是一个只读预检,从最严重到最不严重依次检查:TREK 版本兼容 → 权限再同意 → 必需 addon → 插件依赖。任何一项不满足都不会留下半激活状态,且以结构化错误码返回(见 server/src/nest/plugins/plugin-runtime.service.ts 中PluginDependencyError与assertActivatable):
- 必需的 addon 未启用(
ADDON_DISABLED)——toast 提示 addon 名称,到 Admin → Addons 开启后重试。 - 插件依赖缺失或版本过旧(
DEPENDENCY_MISSING)——对话框逐条列出依赖,提供一键Download / Update,装好最新兼容版本后自动重试启用。 - TREK 版本不兼容(
TREK_VERSION_INCOMPATIBLE/TREK_VERSION_UNKNOWN)——宿主版本不在插件声明范围内,或插件压根没声明范围;这类情况排在最前,不会先给你弹权限对话框。
依赖是双向级联的:启用一个插件时,已安装但停用的依赖会被自动级联启用(activate()先按依赖图算出enableOrder,先拉起依赖再拉起目标),并有 toast 告知哪些依赖被顺带打开。反过来,若某 addon 被关闭,依赖它的插件及所有传递依赖者会被自动停用(deactivateForDisabledAddon);停用一个被依赖的插件,也会停用所有依赖它的插件——没有依赖的插件不应该继续运行(deactivateWithDependents)。
每行的⋯菜单提供:Restart(仅活动插件)、View error log、Allowed hosts、Source repository与Report an issue(仅注册表插件)、Delete。
更新时权限扩大、签名密钥变更怎么处理
有新版时插件行出现Update → v{version},列表上方出现 "{count} 个插件有可用更新" 提示条与Update all批量按钮。更新目标由resolveUpdateTarget挑选当前 TREK 能运行的最新版本,而不是简单取最新——避免新版放弃对当前宿主的支持,把好好的插件更新坏。
新版若请求了尚未授予的权限,服务端update()会对比声明权限与已授予权限的差集(newGrants):没有新增则透明重启到新代码;有任何新增(权限或出口主机)则新代码装好但保持插件关闭,行上显示"该插件新版本在请求你尚未授予的权限,新版本已安装,但批准前保持关闭"。对话框列出Newly requested permissions与New outbound connections,由你选择Approve & turn on或Keep off for now;批量更新时这些同意提示排队依次出现,不会跳过任何一个。被同意的新版本若未签名,对话框会额外说明:没有任何机制把该版本与其作者绑定。
如果作者的签名密钥与安装时钉住的密钥不再匹配,更新会被拒绝,行上显示Update blocked — {reason}加Review链接,对话框并排展示钉住的密钥指纹与当前提供的密钥指纹,并提醒:TREK 无法区分合法的密钥轮换与劫持——两者从这里看完全一样,接受前请先通过你信任的渠道向作者确认新密钥。覆盖范围被严格限定:只有"密钥变更"可以覆盖(Trust the new key & update按钮);签名无效、缺失或半声明的情况只给解释,没有任何覆盖按钮,服务端同样拒绝。/retrust端点在服务端强制了这一范围(assertRetrustable,plugins.controller.ts 与 plugin-runtime.service.ts):只允许SIGNATURE_KEY_CHANGED错误码,且调用方必须回显对话框中展示的完整公钥,防止对话框渲染后注册表条目又被换钥。重信任与更新在同一调用内完成:要么新密钥通过校验、插件落到新版本并钉住新密钥,要么什么都不变——杜绝"钉住未验证密钥"的窗口。
卸载插件与数据善后:哪些数据会一起删
⋯ → Delete弹出确认框:Uninstall plugin?——"这将停止插件、移除其代码,并删除它的全部数据,不可撤销。"
uninstall()(plugin-runtime.service.ts)依次执行:停止插件进程、移除代码目录、删除plugins注册表行与设置字段。若勾选deleteData=true,还会清除插件自己的数据目录、错误日志、实体元数据、每用户配置(含加密的密钥)、OAuth 令牌与状态、迁移台账、能力审计日志以及待处理的 GDPR 擦除队列。出口主机与定时任务无条件删除——否则复用同一 id 的插件会悄悄继承这些权限;若选择保留数据,则待处理的用户数据擦除义务也会保留,等同 id 插件重装后继续兑现。
安全边界:插件隔离模型承诺什么、不承诺什么
每个插件的进程边界与 RPC 通道
隔离模型的核心事实(wiki/Plugins.md 中总结,由 server/src/nest/plugins/runtime/ 与 server/src/nest/plugins/supervisor/ 实现):
- 每个活动插件运行在独立 OS 子进程中,由 Node 权限模型(
--permission)启动,文件系统读取限定在其自身代码目录。 - 插件无法访问
JWT_SECRET、数据库连接或任何 TREK 机密——对其进程物理不可达。 - 插件不能打开
trek.db、不能写文件、不能派生子进程、不能用 worker 线程、不能加载原生模块;自有数据存放在独立 SQLite 文件中,且只能通过 TREK 访问。 - 插件只通过内部 RPC 通道与 TREK 通信,TREK 只应答 manifest声明且你批准的能力——未授予的调用被拒绝而非忽略。
- RPC 通道对插件代码本身是封死的:即便插件跑在 fork 进程中,其原始 IPC 原语(
process.send、process.on('message'))也在代码加载前被吊销——既不能伪造宿主消息,也不能窃听在途请求,一切交互被迫经过能力校验的 SDK。 - 页面/组件运行在密封的浏览器 frame中,读不到会话 cookie,碰不到外围 TREK 页面。
- 插件崩溃、挂起或内存耗尽时,只有它自己的进程死掉——TREK 继续运行,可随时重启或停用它。
由此得出最实际的一条:你批准的权限列表是真实边界而非标签。它界定了插件能触及什么,但不约束它在授权范围内的意图——一个被允许读取行程且连接某主机的插件,完全可能把这些数据发过去。所以务必先读权限清单和出口主机,再决定安装。
插件活动日志与审计:谁能看到它做了什么
面板所有端点要求管理员账户,叠加TREK_PLUGINS_ENABLED总开关,dev-link 再叠加TREK_PLUGINS_DEV_LINK;服务端路由统一挂在api/admin/plugins下并同时使用JwtAuthGuard与AdminGuard(server/src/nest/plugins/plugins.controller.ts),运行时关闭时install、upload、activate、update、rescan等操作统一返回 503。
每条权限的确切授予范围记录在 wiki/Plugin-Permissions.md:只读的db:read:trips对每次调用都做 membership 校验;写入类的db:write:places叠加place_edit权限与写入审计;oauth:client由宿主持有令牌,插件只拿到短期 access token;notify:send的收件人被强制限定为操作者本人或所属行程成员。
插件做了什么也是可审计的:每个用户都能在Settings → Plugins的活动日志中查看插件以其名义执行的全部操作——读取的行程/费用、写入的地点、TREK 代发的每次出站调用。该视图不设管理员门槛——这是 TREK 基于哈希链的防篡改插件审计的用户侧;管理员则在Admin → Plugins看到按插件维度的视图。
装之前先花三分钟做三件事:读完权限清单、核对出口主机、看清徽章含义。做到这三点,Admin → Plugins 面板剩下的操作就只是执行而已。
延伸阅读
- wiki/Plugins.md——插件系统全貌:类型、隔离模型、依赖、活动日志
- wiki/Plugin-Permissions.md——每条权限的确切授予范围与
http:outbound细节 - wiki/Plugin-Development.md——SDK 与 manifest 编写
- wiki/Plugin-Publishing.md——注册表提交流程与
trek-pluginCLI - wiki/Admin-Addons.md——插件可能依赖的 addon 管理
【免费下载链接】TREKA self-hosted travel/trip planner with real-time collaboration, interactive maps, PWA support, SSO, budgets, packing lists, and more.项目地址: https://gitcode.com/GitHub_Trending/nomad22/TREK
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考