HOME

Veeam Backup & Replication 13 Linux Appliance 中文化实战:从版本不兼容到提交上游 PR

文章目录15 节

一、先说最终结果

这次我没有把一个版本不匹配的中文包强行装进 Veeam,而是以实际运行的 Veeam Backup & Replication Linux Appliance 为基准,重新完成了一套 13.0.2.29 专用 Web UI 简体中文包。

最终成果包括:

  • 为 VBR Web UI build 13.0.2.29 建立独立适配分支。
  • 从已经审校的 13.1.0.411 中文资源迁移可复用翻译。
  • 人工补齐旧版专有资源,最终覆盖 6100 / 6102 条,覆盖率为 99.97%
  • 对目标版本、原始文件和安装结果实施 SHA-256 校验。
  • 验证首次安装、重复安装、卸载还原、版本不匹配拒绝和文件篡改保护。
  • 修复 macOS 打包元数据导致的 Linux 解压警告。
  • 修复切换中文后浏览器持续转圈、主线程卡死的问题。
  • 修复“移除代理/Worker”弹窗的变量占位符错误,并把占位符检查扩展到整个翻译目录。
  • 发布最终 v13.0.2.29-linux-r6 Release。
  • 向上游项目提交 PR #1

截至本文整理时,PR 状态仍是 Open,还没有合并到上游 main。最终可用包已经在我的 Fork 中发布,但它仍然是非官方本地化包,不是 Veeam 官方补丁。

二、为什么不能直接套用现有 13.1 中文包

最初参考的是上游项目 Coku2015/Veeam-Chinese-UI-Packages。项目已经提供 VBR 13.1.0.411 中文包和使用步骤,但我当时实际运行的 Web UI build 是 13.0.2.29

真正阻止安装的不是脚本形式,而是两个版本的 Web UI 结构并不相同:

对比项VBR 13.0.2.29VBR 13.1.0.411 中文包预期
Web UI 根目录/opt/veeam/vbr/GatewayApiService/app新版 Web UI 目录结构
主模块assets/index-e21aa407.jsassets/index-BJPSD5I9.js
插件文件plugin/plugin.js还涉及 vdpPlugin.js
旧版附加资源assets/assets-9c8c6fd9.js资源组织方式已经变化
登录语言选择器/opt/veeam/vbr/wwwroot/oauth/static/lib/languageSelector.js需要按新版结构处理

如果跳过版本检查,把 13.1 的文件名、补丁位置和哈希直接套到 13.0,轻则安装脚本找不到文件,重则把错误内容写入生产 Web UI。这里最重要的决定,是不使用类似 ALLOW_UNVERIFIED=1 的方式绕过兼容性保护,而是从服务器实际文件重新建立适配基线。

风险等级:INFO(只读)

作用: 在 Veeam Appliance 的 root Shell 中查看 Web UI 软件包版本,并计算目标静态文件的 SHA-256,用于确认环境是否与本文的 13.0.2.29 基线一致。 注意事项: 这些命令不修改系统。路径、文件名或哈希不一致时,应停止安装并寻找匹配当前 build 的中文包,不要凭产品大版本相同就继续。

rpm -q veeam-vbr-webui
sha256sum \
  /opt/veeam/vbr/GatewayApiService/app/index.html \
  /opt/veeam/vbr/GatewayApiService/app/assets/index-e21aa407.js \
  /opt/veeam/vbr/GatewayApiService/app/plugin/plugin.js \
  /opt/veeam/vbr/wwwroot/oauth/static/lib/languageSelector.js

本次适配记录的四个原始文件哈希如下:

文件SHA-256
index.html7d5d30326a648dea90e1fa16f818e1d46c9d5bb2ea596131b273b4a246bba7e8
index-e21aa407.jsa56354fa06e4857d87ddb50b29ab53702896a85334ec0a20bfa4103ca53324bb
plugin.jse1af0c38752ae7669a577bfa70e8119b80d0ac906bdb3548ed76a21c83ab8ec2
languageSelector.jsae389601d582ea5ea6ca20bb77204493b77837ee79e5ba4166f2fe00fcec078d

这些哈希不是“所有 13.0 都应该一样”的通用答案,而是安装脚本用于识别本次目标 build 原始状态的保护边界。只要 Veeam 发布了补丁、重构了前端或文件被其他工具修改,校验就可能不再通过。

三、从 Fork 和适配分支开始

我保留了上游历史,并在自己的 Fork 中创建了专用分支:

上游仓库:Coku2015/Veeam-Chinese-UI-Packages
我的 Fork:MrXJG/Veeam-Chinese-UI-Packages
适配分支:adapt-vbr-13.0.2.29-linux
目标 build:13.0.2.29

没有直接修改上游 main 的原因很简单:13.0 与 13.1 必须长期保持各自的目录、安装脚本、校验哈希和发布包。版本专用分支既方便审阅,也能让每次故障修复通过独立提交和 Release 追踪。

整个适配过程最终形成六个主要提交:

提交作用对应阶段
9d8bc84新增 VBR 13.0.2.29 Linux 中文包初始适配
197592c提升翻译覆盖率13.1 文案变化审校
a692660翻译剩余旧版专有资源覆盖率达到 99.97%
bdca77f清理 macOS 元数据r4 打包修复
1b259a1修复中文界面监听器循环r5 运行时修复
27ed366校验翻译占位符r6 最终修复

这条提交历史后来完整进入了上游 PR,而不是只提交一个无法解释来源的二进制压缩包。

四、把 13.1 翻译迁移到 13.0,而不是机械覆盖

13.0 原始英文目录共有 6102 条资源。迁移时,生成器按不同证据层匹配 13.1 中已经审校的中文内容:

  1. 同命名空间、同资源键。
  2. 全局唯一的同名资源键。
  3. 英文原文完全一致。
  4. 对 13.1 与 13.0 文案轻微变化的资源进行人工审校映射。
  5. 对旧版独有文本进行人工翻译。

最后的目录统计是:

项目数量
13.0 英文资源总数6102
已覆盖中文资源6100
13.1 文案变化审校映射77
人工翻译的旧版专有资源602
未加入目录的资源2

剩余两条的英文原文本身就是空字符串,不会在页面中显示。我没有为了把数字写成 100% 而加入无意义空翻译。

迁移过程中还必须保护 {{hostName}}%JobName%{url} 等变量。翻译文字看起来正确,并不代表运行时一定安全;只要中文比英文多一个变量,或者少一个变量,前端格式化函数就可能在用户点击按钮时直接抛错。

五、安装和卸载必须有明确的回退边界

这个中文包不会替换整个 Veeam RPM,也不会覆盖 13.0.2.29 原有的 English 和日本語;安装脚本只在旧版语言注册表中追加 zh-CN。它主要完成以下动作:

  • 在旧版主模块和插件语言注册表中增加 zh-CN
  • 加载中文目录和旧版兼容脚本。
  • 在登录语言菜单中加入“简体中文”。
  • 修改前先校验四个原始文件的 SHA-256。
  • 把原始文件备份到 /var/lib/veeam-webui-zh-cn/13.0.2.29/
  • 记录原始哈希、安装后哈希和新增资源哈希。

安装脚本还处理了一个很容易被忽略的状态:如果清单存在且当前文件完全等于本包的安装结果,重复运行会提示“已经安装”,不会再次叠加补丁;如果清单存在但文件已经被升级或人工修改,脚本会拒绝继续。

卸载脚本同样不会无条件覆盖文件。它先确认当前文件仍然等于本包安装后的哈希,才使用备份恢复原始文件。这样可以避免 Veeam 已经升级后,卸载旧中文包又把旧版文件覆盖回去。

离线副本测试验证了以下场景:

  • 第一次安装成功。
  • 第二次安装保持幂等,不重复修改。
  • 卸载后四个原始文件哈希全部恢复。
  • 原始文件哈希不匹配时拒绝安装。
  • 安装后文件被篡改时拒绝卸载覆盖。

六、从 r1 到 r6:真正耗时的是故障复现和保护补齐

1. r1 到 r3:先让版本适配完整可用

初始包完成了 13.0.2.29 的目录、安装脚本、卸载脚本和翻译生成器。后续继续对照 13.1 文案变化,并人工翻译旧版专有资源,r3 时覆盖率达到 6100 / 6102

这一步解决的是“有没有中文”和“覆盖是否足够”,但还没有覆盖不同操作系统打包、浏览器动态 DOM 和运行时变量这些边界。

2. r4:修复 macOS 打包元数据

在 macOS 上直接制作 Linux tar.gz 后,Appliance 解压时出现了 ._* AppleDouble 文件以及类似下面的警告:

Ignoring unknown extended header keyword
LIBARCHIVE.xattr.com.apple.provenance

这些内容不属于 Veeam 中文资源,而是 macOS 扩展属性被写进了归档。r4 增加专用构建脚本,使用 COPYFILE_DISABLE=1、禁用 xattr、排除 ._*,并在发布前扫描残留元数据。

3. r5:切换中文后持续转圈

安装后,英文界面可以正常进入;一切换到简体中文,浏览器主线程就被占满,页面一直转圈。服务器端的 NGINX、Backup Service、Web Service、REST API 和后端端口都正常,故障最终定位到旧版兼容脚本的 DOM 翻译逻辑。

当时的实现同时存在三个问题:

  • 遍历整页所有元素和文本节点。
  • 监听整棵 DOM 的 characterDatachildList 变化。
  • 每 750 毫秒再次扫描整个页面。

翻译目录中还有一些原文与译文相同的产品名,例如 Veeam ONE。脚本把相同文本再次写回节点后,会触发 MutationObserver;Observer 再次写入相同文本,于是形成自触发循环。

r5 的修复包括:

  • 只有译文与当前文本不同时才写入节点。
  • 过滤原文与译文相同的映射。
  • 删除 750 毫秒整页轮询。
  • 只处理真正新增或发生变化的节点。
  • 新增 NATIVE_GUARD_TEST 回归测试,确保相同文本不会产生写操作,真实翻译只写入一次。

同一提交还把构建流程从 tar -czf 调整为 tar -cf - | gzip -ngzip -n 不记录原文件名和时间戳,使同一源码重复构建时能够得到一致的 SHA-256。

修复包重新安装后,中文概览页能够稳定加载,这才证明问题确实来自中文兼容脚本,而不是后端服务。

4. r6:代理按钮暴露占位符错误

r5 修复转圈后,我继续操作不同页面。点击“代理和 Worker”中的移除按钮时,确认框还没有调用后端接口就报错:

Missing prop: 'name' in '是否移除代理/Worker {{name}}?'

VBR 13.0 原文是 Remove proxy?,没有 {{name}} 参数;错误中文却从 13.1 文案带入了这个变量。调用方没有传递 name,所以前端在渲染弹窗时直接中断。

全目录审计后共发现 5 处占位符或语义不匹配,包括文件级恢复标题、主机搜索提示、代理确认框和邮件主题提示。r6 不只修正这 5 条,还调整了生成器规则:

  • 资源级人工审校覆盖优先于自动匹配。
  • 无论翻译来自自动迁移还是人工覆盖,都必须统一校验占位符。
  • 资源目录和源文本映射任何一处占位符不一致,构建立即失败。
  • 新增回归检查,固定验证这 5 个容易回归的资源。

最终验证结果为:

resourceMismatchCount: 0
sourceMismatchCount: 0
regressionTranslationsChecked: 5

“移除代理/Worker”确认框随后在实际 VBR 13.0.2.29 页面中恢复正常。验证只确认弹窗能够正确显示,没有把测试动作扩展为真正删除代理。

七、不是所有 502 都是中文包造成的

汉化验证期间,我还排查过一次 Appliance 升级到 13.1.0.411 后出现的 NGINX 502。那次 NGINX 本身正常,但上游 127.0.0.1:34813 没有进程监听,多个 Veeam 服务都因为下面的程序集错误退出:

Could not load file or assembly
'Veeam.Backup.LicenseLib, Version=13.1.0.0'

继续核对文件版本、时间和哈希后发现,升级后的 13.1 目录被复制回了旧版许可证组件,其中 DLL 产品版本为 13.0.1.180。旧进程在重启前可能仍然使用已经加载的程序集,重启后 13.1 服务重新加载文件才集中失败,最终表现为 NGINX 502。

中文包只修改 Web UI 的 HTML、JavaScript 和语言资源,并不触碰 Veeam.Backup.LicenseLib.dlllibVeeamLicense.so。这次故障提醒我:看到“安装中文包后出现异常”只能建立时间相关性,不能直接证明因果关系。必须继续检查服务、监听端口、反向代理上游、应用日志和实际被修改的文件范围。

八、最终 Release 与上游 PR

最终版本为 v13.0.2.29-linux-r6

项目结果
文件VeeamWebUiZhCN-13.0.2.29-linux.tar.gz
大小143560 字节
SHA-25670bd47a9c19d2a5c97bc1557dbd33bd3fde2cba756680118e1439f180693110a
翻译覆盖6100 / 6102,99.97%
资源占位符不匹配0
源文本映射占位符不匹配0
现场验证中文概览稳定加载,代理确认框正常

2026 年 8 月 8 日,我把完整适配分支提交到上游,PR 为:

feat: 新增 VBR 13.0.2.29 Linux Appliance 中文界面包(PR #1)

PR 包含安装和卸载脚本、翻译生成工具、人工审校映射、构建脚本、占位符测试、运行时回归测试、发布包及使用说明,共涉及 14 个文件。当前状态为 Open,因此在上游合并前,使用者应从我的 Fork 的 r6 Release 下载,不应假设上游 main 已经包含 13.0.2.29 包。

九、r6 安装与回退方法

安装前先从 r6 Release 页面 下载压缩包和 SHA256SUMS-13.0.2.29.txt,再上传到 Appliance 的 /tmp/。本文命令仅适用于 VBR Web UI build 13.0.2.29

风险等级:INFO(只读)

作用: 在 Appliance root Shell 中确认软件包版本,并校验已经上传到 /tmp/ 的 r6 压缩包。 需要调整: 如果文件不在 /tmp/,请把路径替换为实际上传位置。 完成验证: 版本应为 13.0.2.29,压缩包 SHA-256 应为 70bd47a9c19d2a5c97bc1557dbd33bd3fde2cba756680118e1439f180693110a

rpm -q veeam-vbr-webui
cd /tmp
sha256sum VeeamWebUiZhCN-13.0.2.29-linux.tar.gz

风险等级:CAUTION(修改 Veeam Web UI 静态文件)

作用:/tmp/ 解压 r6,并执行版本专用安装脚本。 执行对象: VBR 13.0.2.29 Linux Appliance 的 Web UI 静态文件;需要 Appliance root Shell。 执行前检查: 必须先完成版本与压缩包 SHA-256 校验。安装脚本还会校验四个原始文件,任一哈希不匹配都会停止,不应绕过。 预期影响: 增加简体中文语言选项,刷新页面时可能短暂重新加载前端;不需要为了安装主动重启整台 Appliance。 回退方法: 使用同一目录中的卸载脚本恢复安装前备份。如果当前文件已被升级或其他工具修改,卸载脚本会拒绝覆盖,应先保留现场并重新判断版本。 完成验证: 安装脚本应显示目标 build 安装成功;浏览器强制刷新后可选择“简体中文”,概览页应能稳定加载。

cd /tmp
tar xvf VeeamWebUiZhCN-13.0.2.29-linux.tar.gz
bash VeeamWebUiZhCN-13.0.2.29/linux/install-veeam-webui-zh-cn.sh

风险等级:CAUTION(恢复原始 Web UI 文件)

作用: 卸载中文包并恢复安装前的四个原始文件。 执行前检查: 确认仍在同一 VBR build,且 /var/lib/veeam-webui-zh-cn/13.0.2.29/ 中的安装清单和备份完整。Veeam 升级前应优先执行本步骤。 预期影响: 简体中文选项被移除,Web UI 恢复到安装前状态。 回退与异常处理: 如果脚本提示当前文件哈希不属于本包,不要手工覆盖;保留备份目录,先判断是否发生 Veeam 升级或其他修改。 完成验证: 脚本应提示原始文件哈希验证通过,浏览器刷新后中文选项消失,原有语言正常使用。

cd /tmp
bash VeeamWebUiZhCN-13.0.2.29/linux/uninstall-veeam-webui-zh-cn.sh

十、给后续版本适配留下的测试基线

这次最有价值的结果,不只是多了一个中文包,而是把后续适配需要守住的边界固定了下来。

风险等级:INFO(本地只读测试)

作用: 在中文包仓库根目录运行两个 Node.js 回归测试,检查运行时监听器和全目录占位符。 执行环境: 已拉取 adapt-vbr-13.0.2.29-linux 分支、安装可用 Node.js,并保留生成器所需的 13.0 原始资源基线。 完成验证: 第一项输出 NATIVE_GUARD_TEST=ok;第二项的资源和源文本不匹配计数都应为 0。

node tools/test-vbr-13.0.2.29-native-guard.js
node tools/test-vbr-13.0.2.29-catalog-placeholders.js

以后适配新 build 时,至少应重新完成:

  1. 识别实际 RPM/Web UI build 和静态文件布局。
  2. 保存原始文件并记录 SHA-256。
  3. 从已审校目录迁移翻译,但不跨版本硬套文件路径。
  4. 全量检查变量占位符,而不是只检查人工翻译。
  5. 验证登录页、概览、作业、存储库、代理和弹窗等动态页面。
  6. 验证安装、重复安装、卸载、版本不匹配与篡改保护。
  7. 在 Linux 上检查归档元数据,并验证构建可复现。
  8. 现场故障先检查后端服务和网络链路,再判断是否属于前端中文资源。

十一、这次工作的结论

Veeam Web UI 中文化并不是简单的字符串替换。真正决定一个版本包能否长期使用的,是版本边界、资源映射、变量约束、动态 DOM 行为、安装回退和现场故障归因。

这次从“不适配”走到 r6,最关键的三个修复分别来自三个不同层面:

  • 操作系统层:清理 macOS 写入 Linux 压缩包的扩展属性。
  • 浏览器运行时层:阻止 MutationObserver 因相同文本写回而自循环。
  • 国际化资源层:保证英文与中文占位符严格一致。

最终包已经在 VBR 13.0.2.29 Linux Appliance 上完成中文页面和代理确认框验证,上游 PR 也已提交。后续如果 PR 收到审阅意见或 Veeam 发布新的 Web UI build,我会继续沿用这套“先识别真实版本、再适配、可回退、可验证”的方式维护,而不会把现有 r6 跨版本复用。

Veeam Linux Appliance Web UI 中文化 JavaScript GitHub 开源贡献 故障排查