← 返回文章列表

Dify 插件开发实验(11):打包分发与离线安装——插件如何打包签名、分发与离线安装?

1. 业务场景

先讲一个我们实际遇到的场景。

客服工单 SaaS 的插件集开发完了:01 的时间工具、02 的工单查询、04 的企业对接……九个插件各司其职。但交付的客户环境是离线内网——不能访问 marketplace,不能访问外网 PyPI,安装升级全靠本地 .difypkg 文件。交付物不再是一堆代码,而是一组签名包 + 安装顺序说明 + 升级方案,客户环境装完要能正常跑,升级不能破坏已有应用,出了问题还得能回滚。

我们第一次做这种交付时,第一反应也是「打包上传,客户自己装不就行了」。真正动手才发现——「插件能跑」和「插件能交付」,是两回事:离线环境装不上、升级把客户已配置的工作流弄坏了、升级失败没法回滚,任何一环出问题,前面十篇的开发成果都交付不出去——可靠性不在开发环境,而在客户环境。

这不是个例。任何 ToB 交付场景都是这个模式:客户内网环境、不能访问公网市场、安装升级的可靠性直接决定交付质量——「插件能跑」只是第一步,「插件能交付」才是分水岭。

2. 场景痛点

这个流程的痛点,在离线交付时体现得最直接:

本质上,交付的可靠性不在开发环境,而在客户环境——离线安装、升级兼容、可回滚,这三件事是插件交付的及格线。

3. 方案:为什么是 difypkg 生命周期管理

选 difypkg 生命周期管理,我们实际对比过:

这篇文章我们就用它把 106-01~10 的插件整理为「客服工单插件集」,模拟交付到离线客户环境,跑通 打包 → 签名 → 离线安装 → 升级 0.0.1→0.1.0 → 回滚 的完整生命周期。

4. 整体架构

graph TD subgraph dev["【开发环境】"] pkgs["插件集(9 插件)"] --> sign["打包 + 签名(dify106_key)"] --> pkg[".signed.difypkg"] end subgraph cust["【客户环境(离线)】"] console["控制台「插件」"] --> imp["本地导入 .difypkg"] --> verify["签名校验(白名单公钥)"] --> install["install"] --> poll["tasks 轮询 success"] poll --> smoke["回归冒烟(106-01 应用作回归基线)"] poll --> upgrade["升级 0.0.1 → 0.1.0(卸载旧 + 装新)"] --> compat["已有应用自动兼容"] poll --> rollback["回滚(装回 0.0.1 原包)"] --> restore["回归恢复"] end

链路很清晰:开发环境打包签名 → 客户环境本地导入 → 签名校验安装 → 回归冒烟 → 升级/回滚闭环。关键设计是「回归基线」——106-01 应用作为每次安装/升级/回滚后的冒烟基线,任何一步破坏功能都能立刻暴露。

5. 模块设计

5.1 打包元数据三处同步

manifest.yaml 的 version、meta.version、pyproject.toml 的 version 三处必须一致,客户控制台才显示清晰信息:

# manifest.yaml —— 打包产物元数据(客户控制台可见)

name: dify106_01_time_tool

version: 0.1.0          # ① manifest version

meta:

  version: 0.1.0        # ② meta.version(升级 0.0.1 → 0.1.0 时与 ① 同步)

③ pyproject.toml 的 version = "0.1.0" 与上面两处保持一致——三处任一遗漏,安装/升级时版本信息错乱(实测坑)。

5.2 版本切换流程(升级/回滚实测路径)

# 升级 0.0.1 → 0.1.0(同一 plugin_id 同时只能有一个版本 → 升级 = 卸载旧 + 装新)

dify plugin uninstall --installation_id <旧实例 id>   # 卸载旧(用 installation_id,不是 plugin_installation_id)

# 上传 dify106_01_time_tool-0.1.0.signed.difypkg(新 sha/新版本号)

# 控制台插件 → 本地导入 → install → tasks 轮询 success

# 106-01 应用回归冒烟(回归基线)

# 回滚:保留 0.0.1 原包 → 装回即回滚 → 回归冒烟验证

5.3 升级兼容性(本实验最重要结论)

应用依赖按 plugin_id 匹配(非严格 sha)——插件升级后,已配置旧工具的工作流自动兼容无需重配(实测:依赖 0.0.1 旧 uid 的应用在 0.1.0 下运行正常)。但同一 plugin_id 同时只能有一个版本(装 0.1.0 时 0.0.1 被替换/需先卸载)。

6. 运行验证

验证项 场景 预期 结果
打包 插件集 9 个 .difypkg 命名/版本语义化规范 ✅ 0.0.1/0.1.0
干净安装 卸载 01/02/04 → 按清单装回 全部 success
回归冒烟 106-01 应用 装回后功能正常(回归基线)
升级 0.0.1 → 0.1.0(新增 format 参数) 升级成功且已有应用不破坏 ✅ 旧应用自动兼容(plugin_id 匹配)
回滚 装回 0.0.1 回归恢复基线
离线安装 控制台本地导入 .difypkg upload → 签名校验 → install → tasks 全链路

在线 vs 离线差异表:来源——marketplace 官方市场(需外网)/ 本地 .difypkg;校验——官方签名 / 第三方签名(白名单公钥);更新——市场检查更新 / 手动升级(卸载+装新);依赖——daemon 自动处理 / 同上(需 PyPI 可达或预置缓存);适用——公网环境 / 内网客户环境。

7. 实战坑

现象 修复
依赖声明缺失 插件依赖 sdk 版本(pyproject 锁 dify_plugin>=0.7.4,<0.8),客户环境由 daemon uv sync 处理 完全离线环境需预置依赖(daemon uv-cache 或内网 PyPI 镜像)——记录为离线边界
升级破坏已有应用 担心升级后旧工作流失配 实测应用依赖按 plugin_id 匹配(非严格 sha),升级后旧应用自动兼容无需重配;同一 plugin_id 同时只能一个版本(升级=卸载旧+装新)
回滚无预案 升级失败不知如何恢复 保留旧版本原包即可回滚(装回旧包)→ 回归冒烟验证(0.1.0→0.0.1 实测恢复)
打包元数据不全 manifest/pyproject 版本号不同步,控制台版本信息错乱 版本号三处同步(manifest version + meta.version + pyproject version),0.1.0 升级时三处一致
离线校验失败 本地导入 .difypkg 报 bad signature 拒绝 白名单公钥前置配置(dify106_key.public.pem 随交付包提供)

8. 实验文档及源码获取

文章聚焦核心配置与采坑点,完整分步操作与升级/回滚全流程验证记录见实验文档原文。

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过