如何自行托管 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 命名空间中。每次转换只能看到两个目录:一个以只读方式挂载、用于存放输入文件的目录,以及一个用于接收输出文件的目录。网络处于非共享状态;按照项目的说法,closes every URL handler in every dependency at once。这一点比听起来更重要。ImageMagick 和 Ghostscript 都接受会获取 URL 的引用,因此转换器可能变成服务器端请求伪造(SSRF)工具,从网络内部访问云元数据端点。命名空间没有网络后,就无法执行此类获取操作。
拒绝访问是另一半: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 的内存映射。视频转换会使用所有可用 CPU 核心,因为 FFmpeg 处理视频时就是如此。PHP 自身的 memory_limit 在项目配置中为 512M。这些数值会在单个任务期间叠加,还要加上操作系统和 Web 服务器的开销。
因此,1 GB VPS 在处理第一个真正的文档时就会开始使用交换空间,随后持续抖动。当内存耗尽时,内核的内存不足杀手会终止驻留集最大的进程。通常是 soffice.bin,用户看到的只是转换失败,且没有有用的错误信息。有时被终止的是 apache2,整个站点就会停止运行。事后可使用 dmesg -T | grep -i "killed process" 进行确认。
这属于容量规划建议,不是基准测试:对于小型团队,4 GB RAM 和 2 个核心比较充足;如果负载主要是文档和图像,并且可以接受等待,2 GB RAM 加交换文件也能工作。交换文件不会让转换更快。它只能让突发负载变慢而不是直接失败,这决定了结果是页面暂时卡住,还是服务中断。磁盘空间应预留得比直觉所需更多,因为 3 GB 图像、较大的上传限制和转换后的输出文件加在一起,通常会比其他资源更早占满磁盘。
转换任务本身具有突发性。两个人同时上传视频时,会占用所有 CPU 核心,后续请求只能等待。前面没有任务队列,因此你唯一可用的控制手段就是限制。
设置限制,防止单次上传占满磁盘
先收紧 PHP 的配置值。对于共享的 4 GB 服务器,upload_max_filesize = 512M、post_max_size = 512M 和 max_file_uploads = 20 可以作为合理的起点。请注意,max_execution_time = 1200 允许单个 PHP 请求运行 20 分钟。长视频转换确实可能需要这么长时间,但这也意味着一个缓慢的上传会占用一个 worker 长达 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 隧道访问。这样,公共互联网中的任何人都无法向其发送文件,整个攻击面会被移除,而不只是进行过滤。如果这些同事需要在浏览器中打开它,而您又不想发布主机名或开放端口,可以将其作为 v3 onion 服务提供。这样转换器仍绑定到 loopback,同时会提供一个可访问的地址。
如果必须通过浏览器访问,请在代理前添加身份验证。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 服务器可以在没有内置登录功能的应用前提供转发身份验证。
如果目标确实是公开提供转换服务,请接受相应风险并做好规划。假设攻击者会探测沙箱。固定 image tag,严格限制 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 服务器用户的路径无法访问它。
一种格式失败,其他格式正常。 这是缺少二进制文件,提示信息会直接指出这一点: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 个限制,其中 1 个在 nginx 中,另外 2 个在 PHP 中,最终以最小值为准。
转换停止,但没有明显的配置变化。 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 编写 AppArmor 配置文件,并使用 flags=(unconfined),再使用 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 内存和 2 个 CPU 核心;如果转换无提示退出,请检查 dmesg -T | grep -i "killed process"。
转换后的文件存放在哪里,何时会删除?
文件会存放在 Resources/config.php 中由 $ConvertLoc 指定的工作目录内,其默认值为 /DATA/HRConvert2。$DeleteThreshold 设置会话过期前的时间,单位为分钟,默认值为 60。清理任务通过命令行运行:php convertCore.php -c 会清除已过期的会话,-c=now 会立即清除所有会话,包括活动会话。将 -c 添加到 cron 条目或 systemd 定时器中,使文件删除不依赖用户访问网站。