如何用HRConvert2自建文件转换器
在自己的VPS上部署HRConvert2,避免客户文件上传到免费转换站。本文涵盖Docker或Apache安装、bubblewrap沙箱、上传限制与自动清理,并提醒固定版本。
为何自行托管文件转换器
自行托管的文件转换器会将文件保存在您自己的磁盘上。这就是运行它的全部原因。免费转换网站会接收您上传的文件,但不会让您知道之后如何处理这些文件。如果文件是已签署的客户合同或扫描的医疗记录,那么上传行为本身就构成安全事件。HRConvert2 是一个使用 PHP 编写、采用 GPLv3 许可证的拖放式文件转换服务器。3.7.4 版本于 18 August 2026 发布,项目方声称支持 488 种格式。
它不使用数据库,也没有账户和 cookie。每个用户对应一个临时目录。每次转换都由本地命令行工具完成:文档使用 LibreOffice,音频和视频使用 FFmpeg,图像使用 ImageMagick,光学字符识别(OCR)使用 Tesseract,其他格式则由许多规模较小的工具处理。HRConvert2 负责上传页面、转换流程以及相关的清理工作。
它可以将文件从一种格式转换为另一种格式。它不是浏览器办公套件。如果您需要的是让用户在浏览器标签页中编辑文档,请改为比较 自行托管的 OnlyOffice 和 Collabora。它也不是存储系统。转换后的输出文件应当删除,因此如果文件需要长期保存,应由 自行托管的文件管理器负责。
所需环境
Debian 或 Ubuntu、Apache 2.4、PHP 8 或更高版本,以及 bubblewrap。Bubblewrap(bwrap)用于提供沙箱,不能省略:无法创建沙箱的服务器会拒绝转换,而不会在没有沙箱的情况下继续运行。上游 README 表示 Raspberry Pi Model B+ 已经足够,这对 PHP 部分确实如此。转换器二进制文件决定实际的硬件需求,后文会进一步说明。
有两种使用方式。Docker 镜像可以立即运行。安装 Apache 和 PHP 需要一个晚上,但能让您清楚了解服务器上的具体内容。
今晚使用 Docker 运行
该镜像包含所有转换器二进制文件,因此体积较大:截至 August 2026,约为 3 GB。拉取前检查可用磁盘空间。
这里的标签很重要。截至 17 August 2026,Docker Hub 上发布的最新标签是 v3.7.2,而最新的 GitHub 版本是 v3.7.4。latest 标签会自动指向新版本。由于此应用包含大量解析代码,应固定版本,并按计划升级。
docker pull zelon88/hrconvert2:v3.7.2
docker run -d --name hrconvert2 \
-p 127.0.0.1:8080:80 \
--security-opt seccomp=unconfined \
zelon88/hrconvert2:v3.7.2docker ps
curl -I http://127.0.0.1:8080/健康容器会保持在 Up 状态,curl 返回 HTTP/1.1 200 OK。持续重启的容器存在启动问题,因此在进行其他更改前,先读取 docker logs hrconvert2。
有两个标志需要特别注意。-p 127.0.0.1:8080:80 仅在 loopback 地址上发布端口,因此在有意将代理置于其前面之前,外部无法访问转换器。项目自己的示例会映射 -p 8080:80 -p 8443:443,该端口监听所有网络接口,包括公网接口。之所以需要 --security-opt seccomp=unconfined,是因为 bubblewrap 使用 user namespace 和 mount 系统调用构建沙箱,而 Docker 的默认 seccomp 配置文件会阻止这些调用。不加此标志时,转换会失败,应用会明确说明原因:A sandbox blocks the required syscalls unless it was started with the correct options.
这个标志确实会带来取舍。你会放宽容器的系统调用过滤器,使应用能够在容器内构建更严格的沙箱。Resources/config.php 中决定此行为的两个设置是 $RequireSandbox 和 $RequireSandboxOnDocker,默认值分别为 TRUE 和 FALSE。由于 Docker 要求默认关闭,未使用 seccomp 标志的容器可能在完全没有沙箱的情况下执行转换。加上该标志后,设置 $RequireSandboxOnDocker = TRUE;,即可在容器内恢复拒绝行为。
如果这台机器刚开始使用 Docker,请先配置 Docker daemon。在 VPS 上运行 Docker介绍安装过程、存储驱动,以及 Docker 写入自身防火墙规则的方式。
改为在 Apache 和 PHP 上安装
仓库中的 Documentation/INSTALLATION_INSTRUCTIONS.txt 是权威版本,共包含 9 个步骤。整体流程如下。先安装 Web 服务器、语言运行时和沙盒:
sudo apt update
sudo apt install -y apache2 php libapache2-mod-php php-all-dev php8.3-zip php8.3-gd bubblewrapphp8.3-* 中的软件包名称适用于 Ubuntu 24.04。运行 php -v,并使用与版本匹配的前缀,因为这些软件包名称会随每个 PHP 版本变化。使用错误的名称会得到 Unable to locate package。
然后安装转换器。这里涵盖文档、图像、音频、视频和 OCR,这些是实际最常用的转换类型:
sudo apt install -y imagemagick ffmpeg libreoffice-common libreoffice-java-common \
default-jre ghostscript poppler-utils libgxps-utils tesseract-ocr inkscape \
xvfb clamav curl tar libxcb-cursor0归档格式、3D 模型、电子书和可引导 ISO 镜像需要额外的软件包,其中部分软件包位于 Ubuntu 的 multiverse 组件中。官方说明的第 3 步和第 5 步按顺序列出了完整的软件包清单。还有两个依赖项并不是 apt 软件包:仓库为需要编码器或 Ubuntu 未打包的 ImageMagick 7 的用户提供 Documentation/Build/ffmpeg-build.sh 和 Documentation/Build/build-imagemagick-v7.sh。电子书支持来自 calibre 自带的安装程序,说明中将其写成一行命令:
sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin这是一个通过管道传给 shell、并以 root 身份运行的供应商脚本。这是上游提供的方法,但不是必需的:跳过它后,只有电子书转换功能不可用。
接下来设置 PHP 限制。转换过程较慢,文件也较大,因此默认值过小。项目在 php.ini 中设置以下值:
max_execution_time = 1200
max_input_time = 90
memory_limit = 512M
post_max_size = 5000M
upload_max_filesize = 5000M
max_file_uploads = 100
display_errors = Off
zlib.output_compression = On这些数值假定机器有足够的资源。在接近小型 VPS 前先降低这些值,因为 upload_max_filesize = 5000M 配合 max_file_uploads = 100 表示单个请求可能写入远超 40 GB 磁盘容量的数据。重启 Apache,并确认 PHP 实际加载的配置:
sudo service apache2 restart
php -i | grep -E "upload_max_filesize|post_max_size|memory_limit"现在设置工作目录。Resources/config.php 中的 $ConvertLoc 用于指定该目录,默认值为 /DATA/HRConvert2。Web 服务器用户必须拥有该目录:
sudo mkdir -p /DATA/HRConvert2
sudo chmod -R 0755 /DATA/HRConvert2
sudo chown -R www-data:www-data /DATA/HRConvert2将发行版解压到 Apache 文档根目录下。默认布局会将其放入一个 HRProprietary/HRConvert2 文件夹,Resources/config.php 中的 $InstLoc 必须指向实际使用的目录。然后运行内置诊断程序。这是发现缺失依赖项的最快方式,可以在用户发现问题前完成检查:
sudo php /path/to/HRConvert2/convertCore.php -v-v 会检查整个安装,包括核心版本、依赖项、沙盒状态和语言包。命令行不支持文件转换,因此这组参数仅用于管理。
为什么在全新的 Ubuntu 24.04 安装中每次转换都会失败?
原因是沙箱问题,这是首次部署时最常见的问题。Ubuntu 24.04 和 Debian 12 默认限制非特权用户命名空间。Bubblewrap 需要用户命名空间来构建沙箱,因此 bwrap 无法启动;而应用在没有沙箱时会拒绝执行转换,所以每个任务都会失败。
直接检查:
bwrap --ro-bind / / --dev /dev /bin/true && echo sandbox ok如果出现 permission denied 错误,说明用户命名空间被阻止。解决方法是为 bwrap 二进制文件创建 AppArmor 配置文件。先列出 ABI 文件,并记下当前存在的最大编号:
ls /etc/apparmor.d/abi/然后写入 /etc/apparmor.d/bwrap,将 4.0 替换为该最大编号:
abi <abi/4.0>,
include <tunables/global>
profile bwrap /usr/bin/bwrap flags=(unconfined) {
userns,
include if exists <local/bwrap>
}加载配置:
sudo apparmor_parser -r /etc/apparmor.d/bwrap没有输出表示配置文件已加载。再次运行 bwrap 检查命令,此时应输出 sandbox ok。之后即可正常执行转换。
公开转换器就是暴露给陌生用户的解析器
本文其余内容都是为本节服务的。互联网上可访问的文件转换器会接收匿名用户提供的任意文件,并将其交给 LibreOffice、ImageMagick、FFmpeg 或 Ghostscript。这些软件都包含规模庞大的 C 和 C++ 代码库,并且长期存在解析器漏洞。上传者可以选择文件格式,也就决定了运行哪个解析器,以及解析器内部执行哪条代码路径。
HRConvert2 的做法是将每个依赖项都运行在 bubblewrap namespace 中。每次转换只能看到两个目录:存放输入文件且以只读方式挂载的目录,以及接收输出文件的目录。网络处于未共享状态,用项目的话说,closes every URL handler in every dependency at once。这一点的重要性超出直觉。ImageMagick 和 Ghostscript 都接受用于获取 URL 的引用,因此转换器可能变成服务器端请求伪造(SSRF)工具,从网络内部访问云元数据端点。namespace 中没有网络连接后,就无法执行此类获取操作。
拒绝访问是另一半:A server that cannot build a sandbox refuses the conversion rather than quietly running without one.能够安全失败的工具,比只在无人查看的日志中发出警告的工具更可靠。这也是上文的 AppArmor 步骤不可省略的原因,同时也说明在公开容器前应查看 $RequireSandboxOnDocker。
使用 policy.xml 强化 ImageMagick
ImageMagick 自带的策略文件是沙箱之外的第二层防护,值得配置。在 Ubuntu 24.04 上使用 ImageMagick 6 时,该文件位于 /etc/ImageMagick-6/policy.xml。先查看当前生效的策略:
identify -list policy项目在 Documentation/Build/policy.xml 提供了一个策略文件,可作为参考。该文件拒绝 PS、PS2、PS3、EPS、XPS 和 MVG 编码器,也拒绝 URL、HTTPS、HTTP 和 gs 代理,同时允许 PDF:
<policy domain="coder" rights="none" pattern="PS" />
<policy domain="coder" rights="none" pattern="MVG" />
<policy domain="delegate" rights="none" pattern="URL" />
<policy domain="delegate" rights="none" pattern="gs" />
<policy domain="coder" rights="read|write" pattern="PDF" />gs 这一行很重要。ImageMagick 不会自行解析 PostScript,而是调用 Ghostscript。ImageMagick 的远程代码执行漏洞通常就存在于这个代理中。拒绝该代理后,无论上传文件声称自己是什么类型,ImageMagick 都不会将其交给 gs 处理。
同一策略还会设置资源上限,以防止单个恶意构造的图像耗尽整台机器的资源:
<policy domain="resource" name="memory" value="256MiB"/>
<policy domain="resource" name="map" value="512MiB"/>
<policy domain="resource" name="disk" value="1GiB"/>
<policy domain="resource" name="width" value="16KP"/>
<policy domain="resource" name="height" value="16KP"/>
<policy domain="resource" name="area" value="128MP"/>解压缩炸弹是一个声明了超大尺寸的小文件。width、height 和 area 限制会在分配资源前拒绝该文件,因此进程会退出,而不是让内核终止其他进程。
反方向还存在一个陷阱。Ubuntu 的默认策略会直接拒绝 PDF 编码器,因此在未修改的系统上处理 PDF 时会出现 attempt to perform an operation not allowed by the security policy 'PDF'。该字符串表示策略正在正常工作。是否重新允许该编码器由您谨慎决定;即使重新允许,也应继续拒绝 gs 代理。
小型 VPS 上依赖链的资源成本
空闲时,这些组件都不昂贵。Apache 和 PHP 只占用几十 MB,转换器二进制文件也不会运行。全部成本会在文件到达时一次性产生。
文档转换会启动 LibreOffice,LibreOffice 又会启动 Java 运行时。根据上述策略,图像转换会为 ImageMagick 提供 256 MiB 内存和 512 MiB 内存映射。视频转换会让 FFmpeg 使用所有可用核心,因为 FFmpeg 处理视频时就是这样运行的。项目配置中,PHP 自身的 memory_limit 为 512M。这些数值会在单个任务期间叠加,还要加上操作系统和 Web 服务器的资源占用。
因此,1 GB VPS 在处理第一个真正的文档时就会开始使用 swap,随后发生严重抖动。内存耗尽时,内核的 OOM killer 会终止常驻内存占用最大的进程。通常这个进程是 soffice.bin,用户只会看到转换失败,且没有有用的错误信息。有时被终止的是 apache2,整个站点就会停止运行。事后可使用 dmesg -T | grep -i "killed process" 进行确认。
这只是容量规划建议,不是基准测试:4 GB RAM 和 2 个核心足以让小型团队舒适运行;如果负载主要是文档和图像,并且可以接受等待,2 GB RAM 加 swap 文件也能工作。swap 文件不会让转换更快。它只能让突发负载变慢而不是直接失败,而这决定了结果是页面停滞还是服务中断。为磁盘预留比直觉所需更多的空间,因为 3 GB 图像、较大的上传限制和转换后的输出文件加在一起,通常会在其他资源耗尽之前填满磁盘。
转换任务本身具有突发性。两个人同时上传视频时,会占用所有核心,后续请求只能排队等待。前面没有任务队列,因此你唯一可用的控制手段就是限制。
设置限制,防止单次上传占满磁盘
先调低 PHP 参数。对于一台共用的 4 GB 服务器,upload_max_filesize = 512M、post_max_size = 512M 和 max_file_uploads = 20 是合理的起点。请注意,max_execution_time = 1200 允许单个 PHP 请求运行 20 分钟。长视频转换确实可能需要这么长时间,但这也意味着一次缓慢的上传会占用一个工作进程 20 分钟。
然后在请求到达 PHP 之前,先在代理层限制请求大小和速率:
limit_req_zone $binary_remote_addr zone=convert:10m rate=6r/m;
server {
listen 443 ssl;
server_name convert.example.com;
client_max_body_size 512M;
client_body_timeout 300s;
location / {
limit_req zone=convert burst=4 nodelay;
proxy_pass http://127.0.0.1:8080;
proxy_read_timeout 1200s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}client_max_body_size 必须至少等于要转换的最大文件大小,否则 nginx 会返回 413 Request Entity Too Large,PHP 根本不会收到上传内容。proxy_read_timeout 必须长于最长转换时间,否则即使任务在代理后正常运行,浏览器仍会收到 504 Gateway Time-out。该 server block 的其余配置(包括 TLS(传输层安全)终止)详见逐行讲解的 nginx 反向代理配置。
删除转换后的文件
每次转换都会在 Web 服务器可读取的目录中保留一份敏感文件副本。清理机制决定了转换器是一个工具,还是一份记录所有历史转换内容的归档。
$DeleteThreshold 中的 Resources/config.php 表示会话过期时间,单位为分钟,默认值为 60。处理敏感内容时,将其缩短为 15。清理操作本身通过核心程序的命令行参数执行:
sudo -u www-data php /path/to/HRConvert2/convertCore.php -c
sudo -u www-data php /path/to/HRConvert2/convertCore.php -c=15-c 使用已配置的阈值,从两个数据位置清除已过期的会话。-c=15 仅对本次运行使用 15 分钟。-c=now 会删除所有会话,不论其年龄,包括用户当时正在使用的会话,因此仅应在维护时使用。通过 docker exec 在容器内执行时,也可以使用相同的参数。
将清理操作配置为定时任务,避免清理依赖有人加载页面。在 /etc/cron.d/hrconvert2 中添加一行即可:
*/10 * * * * www-data php /path/to/HRConvert2/convertCore.php -c几分钟后使用 ls /DATA/HRConvert2 检查,并监控旧会话目录是否消失。由于 Web 服务器用户拥有该目录,因此解析器遭到利用后,攻击者获得的正是这个账户的权限。该账户不应拥有其他有价值的资源。VPS 上的最小权限用户账户介绍了一般做法,这里的适用性尤其高。
除非目标是公开提供,否则应将其置于身份验证之后
默认安装没有账户,这是设计如此。任何能访问该页面的人都可以上传文件并运行您的转换器二进制文件,速率限制只能减慢攻击速度。因此,请先确定您属于哪种情况。
如果只有您和少数同事使用,请不要将其暴露到公网。按上文所示将容器绑定到 loopback,并通过专用网络或 SSH 隧道访问。这样,公网上的任何对象都无法向其发送文件,从而彻底消除攻击面,而不是仅对其进行过滤。
如果必须让浏览器访问,请在代理前配置身份验证。Basic auth 只需执行两条命令,即可避免陌生人看到上传表单:
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd alicelocation / {
auth_basic "Converter";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:8080;
}重新加载 nginx,然后打开页面。如果出现提示,说明配置已生效;如果没有提示,说明您编辑的 location 块并不是处理该请求的配置块。若需要使用独立账户,而不是共享密码,请在单点登录提供商处终止身份验证:自托管的 Authentik SSO 服务器可以在没有内置登录功能的应用前提供 forward authentication。
如果目标确实是公开提供转换服务,请接受这一前提并做好规划。假设攻击者会探测 sandbox。固定镜像标签,严格限制 ImageMagick policy,设置较小的上传限制,并将其运行在不存放其他重要数据的 VPS 上。
故障模式及您将看到的字符串
每次转换都会立即失败。 沙箱无法构建。在普通安装中,原因是 AppArmor 配置文件。在 Docker 中,原因是缺少 --security-opt seccomp=unconfined。应用会明确指出这一点:A sandbox blocks the required syscalls unless it was started with the correct options.,并指向 See --Require Sandbox-- & --Require Sandbox On Docker-- in config.php.
只有图像转换失败。 Bubblewrap is missing or non functional, so this image conversion cannot be isolated! 表示 bwrap 不存在,或 Web 服务器用户的 PATH 无法访问它。
一种格式失败,其他格式正常。 这是缺少二进制文件的明确提示:ImageMagick may not be installed, or may not be reachable on the system path used by the web server user.。FFmpeg 和 LibreOffice 也会显示相同类型的消息。运行 convertCore.php -v,查看安装中可找到哪些程序。请注意,Apache worker 的 PATH 与您的登录 shell 的 PATH 不同。
PDF 处理因策略错误失败。 attempt to perform an operation not allowed by the security policy 'PDF' 来自 ImageMagick 的 policy.xml,而不是 HRConvert2。
大文件上传返回 413。 nginx 的 client_max_body_size 小于文件大小。整个链路中有 3 个限制:nginx 中的 1 个限制和 PHP 中的 2 个限制。最终以最小的限制为准。
转换停止,但没有明显的配置变更。 The device where data is stored has an insufficient amount of storage space available.。检查可用磁盘空间,并确认清理任务确实在运行。
日志中出现清理错误。 Could not clean the temporary location! 和 Could not clean the convert location! 表示所有权问题。Web 服务器用户必须拥有 $ConvertLoc 指定的目录。
FAQ
将自行托管的文件转换器暴露到互联网是否安全?
只有将其视为暴露给陌生人的解析器时,安全性才足够高。每个上传文件都会交给 LibreOffice、ImageMagick、FFmpeg 或 Ghostscript 处理,而上传者可以选择使用其中哪个工具。HRConvert2 会在无网络连接且输入目录只读的 bubblewrap 命名空间中运行这些工具,并拒绝执行无法在沙箱中运行的转换,这是较稳妥的默认设置。仍建议要求身份验证、限制上传文件大小,并将其运行在不存放其他重要数据的 VPS 上。
为什么在全新安装的 Ubuntu 24.04 上每次转换都会失败?
Ubuntu 24.04 和 Debian 12 会限制非特权用户命名空间,而 bubblewrap 需要使用该功能来创建沙箱。由于应用在没有沙箱时会拒绝转换,因此所有任务都会失败,而不是只有部分任务失败。为 /usr/bin/bwrap 编写包含 flags=(unconfined) 的 AppArmor 配置文件,使用 sudo apparmor_parser -r /etc/apparmor.d/bwrap 加载,然后使用 bwrap --ro-bind / / --dev /dev /bin/true 确认。
为什么转换在 Docker 中失败,但在普通安装中可以正常运行?
Docker 的默认 seccomp 配置文件会阻止 bubblewrap 使用的系统调用,因此容器内无法创建沙箱。使用 --security-opt seccomp=unconfined 启动容器;项目自身的运行命令也使用该参数。请注意,$RequireSandboxOnDocker 的默认值为 FALSE,因此未指定该标志的容器可能会在完全没有沙箱的情况下执行转换。设置 seccomp 标志后,将其设为 TRUE。
文件转换服务器需要多少 RAM?
空闲时占用很少,但正在执行转换时并非如此。LibreOffice 会启动 Java 运行时,ImageMagick 在随附策略下会占用 256 MiB 内存和 512 MiB 映射,而 PHP 自身的限制为 512M。在 1 GB VPS 上,这种组合会触发交换,内存不足终止程序会结束 soffice.bin 或 apache2。小型团队建议使用 4 GB RAM 和 2 个核心;如果转换在没有消息的情况下终止,请检查 dmesg -T | grep -i "killed process"。
转换后的文件存放在哪里,何时会删除?
文件会存放在 Resources/config.php 中由 $ConvertLoc 指定的工作目录,默认值为 /DATA/HRConvert2。$DeleteThreshold 设置会话过期的时间,单位为分钟,默认值为 60。清理任务从命令行运行:php convertCore.php -c 会清除已过期的会话,-c=now 会立即清除所有会话,包括活动会话。将 -c 加入 cron 条目或 systemd 定时器,确保删除操作不依赖用户访问网站。