SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor

آموزش نصب و راه‌اندازی مبدل فایل HRConvert2 روی سرور شخصی

با نصب HRConvert2 روی سرور شخصی، فایل‌های حساس خود را به سایت‌های واسطه ارسال نکنید. این راهنما نحوه پیکربندی Docker، تنظیم محدودیت آپلود و پاک‌سازی خودکار را شرح می‌دهد.

چرا باید یک مبدل فایل را به‌صورت self-hosted اجرا کرد

یک مبدل فایل self-hosted، فایل را روی دیسک خودتان نگه می‌دارد. این تمام دلیل اجرای چنین سرویسی است. سایت‌های مبدل رایگان، فایل آپلودشده را دریافت می‌کنند و هیچ راهی برای اطلاع از سرنوشت آن پس از پردازش به شما نمی‌دهند؛ وقتی فایل مورد نظر یک قرارداد امضاشده یا یک پرونده پزشکی اسکن‌شده باشد، خودِ عمل آپلود یک حادثه امنیتی محسوب می‌شود. HRConvert2 یک سرور تبدیل فایل با قابلیت کشیدن و رها کردن (drag and drop) است که با PHP نوشته شده و تحت مجوز GPLv3 منتشر می‌شود. این راهنما بر اساس نسخه 3.7.4 نوشته شده که در 18 اوت 2026 منتشر شد. پروژه از آن زمان به‌سرعت پیشرفت کرده است: جدیدترین نسخه تا تاریخ 13 اکتبر 2026، نسخه v3.9.1 است که در 6 سپتامبر 2026 منتشر شده و پروژه ادعای پشتیبانی از 488 فرمت مختلف را دارد.

این ابزار فاقد پایگاه داده، سیستم حساب کاربری و کوکی است. هر کاربر در واقع یک دایرکتوری موقت است. هر تبدیل توسط یک ابزار خط فرمان محلی انجام می‌شود: LibreOffice برای اسناد، FFmpeg برای صوت و ویدیو، ImageMagick برای تصاویر، Tesseract برای تشخیص نوری کاراکترها (OCR) و مجموعه‌ای از ابزارهای کوچک‌تر برای سایر موارد. HRConvert2 در واقع صفحه آپلود، خط لوله پردازش و عملیات پاک‌سازی پیرامون آن‌هاست.

این ابزار فایل را از یک فرمت به فرمت دیگر تبدیل می‌کند. این یک مجموعه اداری تحت مرورگر نیست، بنابراین اگر هدف شما این است که کاربران اسناد را در یک تب مرورگر ویرایش کنند، OnlyOffice و Collabora به صورت self-hosted را بررسی کنید. همچنین این ابزار یک فضای ذخیره‌سازی نیست. خروجی‌های تبدیل‌شده باید حذف شوند، بنابراین اگر فایل‌ها نیاز به نگهداری در جایی دارند، این وظیفه بر عهده یک مدیریت فایل self-hosted است.

پیش‌نیازها

سیستم‌عامل Debian یا Ubuntu، وب‌سرور Apache 2.4، نسخه PHP 8 یا بالاتر، و ابزار bubblewrap. ابزار bubblewrap (bwrap) نقش sandbox را ایفا می‌کند و استفاده از آن اختیاری نیست: سروری که نتواند sandbox ایجاد کند، به‌جای اجرا بدون آن، از انجام عملیات تبدیل خودداری می‌کند. طبق مستندات README اصلی، یک Raspberry Pi Model B+ برای بخش PHP کافی است. با این حال، باینری‌های مبدل، محدودیت‌های سخت‌افزاری واقعی شما را تعیین می‌کنند که در ادامه به آن پرداخته شده است.

دو روش برای راه‌اندازی وجود دارد. استفاده از image داکر همین امشب قابل انجام است. نصب Apache و PHP یک عصر زمان می‌برد و به شما نشان می‌دهد دقیقاً چه چیزی روی سرور قرار دارد.

اجرای آن امشب با Docker

این image شامل تمام فایل‌های باینری مبدل است، بنابراین حجم آن زیاد است: حدود 3 GB تا اوت 2026. پیش از pull کردن، فضای آزاد دیسک را بررسی کنید.

در اینجا تگ‌ها اهمیت دارند. این راهنما نسخه v3.7.2 را ثابت کرده است که جدیدترین تگ در Docker Hub در تاریخ 17 اوت 2026 و همچنین جدیدترین release در GitHub در همان روز بود. نسخه v3.7.4 در 18 اوت منتشر شد. تا تاریخ 13 اکتبر 2026، جدیدترین release در GitHub نسخه v3.9.1 (6 سپتامبر 2026) است و جدیدترین تگ‌های Docker Hub عبارتند از edge (22 سپتامبر 2026) و latest که همان v3.8.4 (31 اوت 2026) است. تگ latest به‌طور مداوم تغییر می‌کند و از آنجا که این برنامه یک parser بزرگ است، حتماً یک نسخه خاص را ثابت (pin) کنید و ارتقا را به‌صورت آگاهانه انجام دهید.

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.2
docker ps
curl -I http://127.0.0.1:8080/

یک container سالم در وضعیت Up باقی می‌ماند و دستور curl مقدار HTTP/1.1 200 OK را برمی‌گرداند. containerای که مدام restart می‌شود، مشکل راه‌اندازی دارد؛ بنابراین پیش از تغییر هر چیز دیگری، docker logs hrconvert2 را مطالعه کنید.

دو flag اهمیت ویژه‌ای دارند. -p 127.0.0.1:8080:80 پورت را فقط روی loopback منتشر می‌کند، بنابراین تا زمانی که عمداً یک proxy در مقابل آن قرار ندهید، هیچ‌چیز به مبدل دسترسی نخواهد داشت. مثال فعلی پروژه، پورت -p 8080:80 را map می‌کند که روی تمام interfaceها، از جمله interface عمومی، گوش می‌دهد. --security-opt seccomp=unconfined به این دلیل وجود دارد که bubblewrap محیط sandbox خود را با استفاده از user namespace و فراخوان‌های سیستمی mount می‌سازد که پروفایل پیش‌فرض seccomp در Docker آن‌ها را مسدود می‌کند. توضیحات نسخه 3.7 در config.php می‌افزاید که برخی میزبان‌ها به --security-opt apparmor=unconfined نیز نیاز دارند. بدون این flag، تبدیل‌ها با شکست مواجه می‌شوند و برنامه دلیل آن را به شما می‌گوید: A sandbox blocks the required syscalls unless it was started with the correct options.

این flag یک مصالحه واقعی است. شما فیلتر فراخوان‌های سیستمی container را شل می‌کنید تا برنامه بتواند sandbox محدودتر خود را در داخل آن بسازد. دو تنظیمی که رفتار را تعیین می‌کنند، $RequireSandbox و $RequireSandboxOnDocker در فایل Resources/config.php هستند. نسخه v3.7.4 آن‌ها را به‌صورت TRUE و FALSE عرضه می‌کند. تگ v3.7.2 که در اینجا ثابت شده، هر دو را به‌صورت FALSE عرضه می‌کند، زیرا توضیحات آن می‌گوید container باید $RequireSandbox را خاموش نگه دارد و اجازه دهد $RequireSandboxOnDocker تصمیم بگیرد. از آنجا که نیاز Docker به‌صورت پیش‌فرض غیرفعال است، container بدون flag seccomp می‌تواند بدون هیچ sandboxای تبدیل را انجام دهد. هنگامی که flag اعمال شد، $RequireSandboxOnDocker = TRUE; را تنظیم کنید تا رفتار امتناع در داخل container بازگردد.

imageهای جدیدتر متفاوت شروع می‌شوند. این پروژه فرآیند Docker خود را در نسخه v3.8.0 (25 اوت 2026) تغییر داد. README فعلی و صفحه Docker Hub آن، container را با --cap-add SYS_ADMIN به‌جای گزینه seccomp اجرا می‌کنند و بیان می‌کنند که بدون آن، تبدیل‌ها رد می‌شوند. اگر نسخه ثابت خود را به تگ 3.8 یا 3.9 تغییر دادید، از flagهایی که در مستندات آن image آمده استفاده کنید. آن قابلیت بسیار گسترده است، بنابراین اتصال پورت 127.0.0.1 را حفظ کنید.

اگر Docker روی این ماشین جدید است، ابتدا daemon را راه‌اندازی کنید. اجرای Docker روی یک VPS شامل نصب، درایور ذخیره‌سازی و نحوه نوشتن قوانین فایروال توسط خود Docker است.

نصب آن روی Apache و PHP

تا نسخه 3.7.4، فایل Documentation/INSTALLATION_INSTRUCTIONS.txt در مخزن، این فرآیند را به صورت نصب دستی در نه مرحله شرح می‌داد و این بخش از همان دستورالعمل پیروی می‌کند. از نسخه 3.8.0 به بعد، همان فایل به جای آن، یک اسکریپت نصب را معرفی می‌کند. با اجرای sudo bash Documentation/Build/hrconvert2-setup.sh از داخل مخزن کلون‌شده، وابستگی‌ها نصب می‌شوند، تنظیمات PHP به جای ویرایش مستقیم php.ini در یک فایل مجزا (drop-in) نوشته می‌شوند و پروفایل AppArmor برای bubblewrap گسترش می‌یابد. سپس php convertCore.php -v و --status نتیجه را تایید می‌کنند. روش دستی زمان‌برتر است، اما دقیقاً می‌دانید چه چیزی روی سیستم قرار گرفته است. ساختار آن به این صورت است. با وب‌سرور، زبان و محیط sandbox شروع کنید:

sudo apt update
sudo apt install -y apache2 php libapache2-mod-php php-all-dev php8.3-zip php8.3-gd bubblewrap

نام‌های php8.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

فرمت‌های آرشیو، مدل‌های سه‌بعدی، کتاب‌های الکترونیکی و ایمیج‌های ISO قابل بوت به بسته‌های بیشتری نیاز دارند و برخی از آن‌ها در بخش multiverse مخازن Ubuntu قرار دارند. در نسخه 3.7.4، مرحله 3 از دستورالعمل‌های رسمی شامل لیست کامل apt و مرحله 5 شامل بیلد از سورس بود. راهنمای فعلی فاقد لیست بسته‌ها است. اسکریپت نصب، مانیفست Resources/Engine/Contract/depends.php را می‌خواند و php convertCore.php --setup --check-depends لیست فعلی و وضعیت آن را چاپ می‌کند. دو وابستگی را نیز می‌توان از سورس بیلد کرد. مخزن شامل Documentation/Build/ffmpeg-build.sh برای انکودرهایی است که FFmpeg بسته‌بندی‌شده فاقد آن است و Documentation/Build/build-imagemagick-v7.sh برای ImageMagick 7 روی Ubuntu 22.04 یا 24.04 که همچنان شاخه 6.9 را ارائه می‌دهند. نسخه‌های Ubuntu 25.10 و 26.04 LTS شامل ImageMagick 7.1 هستند. پشتیبانی از کتاب‌های الکترونیکی از طریق اسکریپت نصب خودِ calibre فراهم می‌شود که در دستورالعمل‌های 3.7 به صورت یک خط دستور ارائه شده بود:

sudo -v && wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sudo sh /dev/stdin

این یک اسکریپت از سمت تولیدکننده است که با دسترسی root به shell ارسال می‌شود. این روش رسمی (upstream) است و اختیاری می‌باشد: اگر از آن صرف‌نظر کنید، تنها قابلیت تبدیل کتاب‌های الکترونیکی را از دست خواهید داد.

در مرحله بعد، محدودیت‌های PHP را تنظیم کنید. فرآیند تبدیل کند است و فایل‌ها حجیم هستند، بنابراین مقادیر پیش‌فرض بسیار کوچک‌اند. دستورالعمل‌های 3.7 این مقادیر را در 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 گیگابایتی بنویسد. Apache را مجدداً راه‌اندازی کنید و تأیید کنید که PHP واقعاً چه مقادیری را بارگذاری کرده است:

sudo service apache2 restart
php -i | grep -E "upload_max_filesize|post_max_size|memory_limit"

اکنون دایرکتوری کاری را تنظیم کنید. $ConvertLoc در Resources/config.php نام آن را مشخص می‌کند و مقدار پیش‌فرض /DATA/HRConvert2 است. کاربر وب‌سرور باید مالک آن باشد:

sudo mkdir -p /DATA/HRConvert2
sudo chmod -R 0755 /DATA/HRConvert2
sudo chown -R www-data:www-data /DATA/HRConvert2

نسخه منتشرشده را در دایرکتوری ریشه Apache (document root) استخراج کنید. ساختار پیش‌فرض آن را در پوشه HRProprietary/HRConvert2 قرار می‌دهد و $InstLoc در Resources/config.php باید مسیر دقیق محل استقرار فایل‌ها را مشخص کند. سپس ابزار عیب‌یابی داخلی را اجرا کنید؛ این سریع‌ترین راه برای یافتن وابستگی‌های مفقود پیش از مواجهه کاربر با آن‌هاست:

sudo php /path/to/HRConvert2/convertCore.php -v

-v کل فرآیند نصب را بررسی می‌کند: نسخه‌های اصلی، بررسی وابستگی‌ها، وضعیت sandbox و بسته‌های زبانی. تبدیل فایل‌ها از طریق خط فرمان پشتیبانی نمی‌شود، بنابراین این مجموعه آرگومان‌ها صرفاً برای مدیریت سیستم است.

چرا تمام تبدیل‌ها در یک نصب تازه Ubuntu 24.04 با شکست مواجه می‌شوند؟

دلیل این امر sandbox است و این رایج‌ترین مشکلی است که در روز اول با آن مواجه می‌شوید. Ubuntu 24.04 به‌صورت پیش‌فرض فضای نام (namespace) کاربران غیرمجاز را از طریق AppArmor محدود می‌کند. Bubblewrap برای ساخت sandbox خود به یک فضای نام کاربری نیاز دارد، بنابراین bwrap نمی‌تواند اجرا شود و از آنجا که برنامه بدون sandbox از انجام تبدیل خودداری می‌کند، تمام عملیات‌ها با شکست مواجه می‌شوند.

آن را مستقیماً بررسی کنید:

bwrap --ro-bind / / --dev /dev /bin/true && echo sandbox ok

خطای permission denied به این معنی است که فضای نام مسدود شده است. راه‌حل، ایجاد یک پروفایل AppArmor برای باینری bwrap است. ابتدا فایل‌های 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 را چاپ کند. از این لحظه به بعد، تبدیل‌ها به‌درستی کار می‌کنند.

Debian 12 این مشکل را ندارد. هسته آن اجازه استفاده از فضای نام کاربران غیرمجاز را می‌دهد و بسته bubblewrap مقدار kernel.unprivileged_userns_clone را در زمان بوت روی 1 تنظیم می‌کند، بنابراین بررسی فوق در آنجا بدون نیاز به پروفایل با موفقیت انجام می‌شود. در نسخه v3.8.0 و بالاتر، اسکریپت راه‌اندازی، پروفایل AppArmor مربوط به bubblewrap توزیع شما را به‌طور خودکار گسترش می‌دهد.

یک مبدل عمومی، پارسری است که در دسترس غریبه‌ها قرار دارد

این بخشی است که کل این مطلب برای آن نوشته شده است. یک مبدل فایل که از طریق اینترنت قابل دسترسی باشد، فایلی دلخواه را از یک فرد ناشناس می‌پذیرد و آن را به LibreOffice، ImageMagick، FFmpeg یا Ghostscript تحویل می‌دهد. این‌ها کدهای بزرگ C و C++ با تاریخچه‌ای طولانی از باگ‌های پارسر هستند. شخصی که فایل را آپلود می‌کند، فرمت را انتخاب می‌کند؛ این یعنی او انتخاب می‌کند که کدام پارسر اجرا شود و کدام مسیر کد در داخل آن فعال گردد.

پاسخ HRConvert2 این است که هر وابستگی را در یک namespace از نوع bubblewrap اجرا کند. هر تبدیل، دو دایرکتوری را می‌بیند: یکی که ورودی را در خود دارد و به‌صورت read-only mount شده است، و دیگری که خروجی را دریافت می‌کند. شبکه در این حالت unshared است که به گفتهٔ پروژه، closes every URL handler in every dependency at once. این موضوع از آنچه به نظر می‌رسد اهمیت بیشتری دارد. هم ImageMagick و هم Ghostscript ارجاعاتی را می‌پذیرند که یک URL را فراخوانی می‌کنند؛ این همان روشی است که یک مبدل را به ابزاری برای Server Side Request Forgery (SSRF) جهت دسترسی به endpoint متادیتای ابری از داخل شبکهٔ شما تبدیل می‌کند. بدون وجود شبکه در namespace، این فراخوانی نمی‌تواند انجام شود.

بخش دیگر، امتناع از پردازش است: A server that cannot build a sandbox refuses the conversion rather than quietly running without one. ابزاری که در صورت خطا متوقف می‌شود (fails closed)، ارزشمندتر از ابزاری است که فقط در لاگی که کسی نمی‌خواند، هشدار می‌دهد. به همین دلیل است که مرحلهٔ AppArmor در بالا اختیاری نیست و چرا $RequireSandboxOnDocker پیش از آنکه container را در معرض دید قرار دهید، ارزش بررسی دارد.

ایمن‌سازی ImageMagick با استفاده از policy.xml

فایل سیاست (policy) خودِ ImageMagick، لایهٔ دومی در زیر sandbox محسوب می‌شود و تنظیم آن توصیه می‌شود. در Ubuntu 24.04 با نسخهٔ ImageMagick 6، این فایل در مسیر /etc/ImageMagick-6/policy.xml قرار دارد. وضعیت فعال فعلی را با دستور زیر مشاهده کنید:

identify -list policy

این پروژه یک فایل سیاست در Documentation/Build/policy.xml ارائه می‌دهد که الگوی مناسبی است. این فایل کدک‌های PS، PS2، PS3، EPS، XPS و MVG و همچنین نمایندگان (delegates) 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 را تجزیه (parse) نمی‌کند. این برنامه درخواست‌ها را به Ghostscript ارجاع می‌دهد و همان نماینده، محل اصلی آسیب‌پذیری‌های شناخته‌شدهٔ اجرای کد از راه دور (RCE) در 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"/>

بمب‌های فشرده‌سازی (decompression bomb)، فایل‌های کوچکی هستند که ابعاد بسیار بزرگی را اعلام می‌کنند. محدودیت‌های width، height و area پیش از تخصیص حافظه، این فایل‌ها را رد می‌کنند تا پردازش متوقف شود و از کشته شدن پروسه‌ها توسط هسته (kernel) جلوگیری شود.

یک نکتهٔ انحرافی در جهت مخالف وجود دارد. سیاست پیش‌فرض Ubuntu، کدک PDF را به‌طور کامل مسدود می‌کند؛ بنابراین در یک سیستم دست‌نخورده، پردازش فایل‌های PDF با خطای attempt to perform an operation not allowed by the security policy 'PDF' مواجه می‌شود. این رشته متنی نشان‌دهندهٔ عملکرد صحیح سیاست امنیتی است. بازگرداندن دسترسی به این کدک یک تصمیم آگاهانه است که شما می‌گیرید، اما در این حالت نیز باید نمایندهٔ gs را همچنان مسدود نگه دارید.

هزینه زنجیره وابستگی‌ها در یک VPS کوچک

در حالت بیکار (Idle)، هیچ‌کدام از این موارد هزینه‌بر نیستند. Apache و PHP تنها چند ده مگابایت اشغال می‌کنند و فایل‌های اجرایی مبدل‌ها اصلاً در حال اجرا نیستند. تمام هزینه به‌یک‌باره و هنگام ورود یک فایل تحمیل می‌شود.

تبدیل یک سند، LibreOffice را اجرا می‌کند که خود باعث راه‌اندازی یک Java runtime می‌شود. تبدیل یک تصویر، 256 MiB حافظه به ImageMagick اختصاص می‌دهد، به‌علاوه یک memory map به حجم 512 MiB طبق سیاست ذکر شده در بالا. تبدیل ویدیو، تمام هسته‌های پردازشی شما را در اختیار FFmpeg قرار می‌دهد، زیرا این همان کاری است که FFmpeg با ویدیو انجام می‌دهد. مقدار memory_limit خودِ PHP در پیکربندی پروژه برابر با 512M است. این اعداد در طول یک پردازش واحد، روی مصرف سیستم‌عامل و وب‌سرور انباشته می‌شوند.

بنابراین، یک VPS با 1 GB رم در اولین سند واقعی شروع به استفاده از swap می‌کند و سپس دچار thrashing می‌شود. وقتی حافظه تمام شود، مکانیزم out of memory killer هسته سیستم‌عامل، فرآیندی را که بیشترین حافظه مقیم (resident size) را دارد، خاتمه می‌دهد. معمولاً این فرآیند soffice.bin است و کاربر با یک تبدیل ناموفق بدون هیچ پیام مفیدی مواجه می‌شود. گاهی اوقات این فرآیند apache2 است و کل سایت از دسترس خارج می‌شود. این موضوع را پس از وقوع با دستور dmesg -T | grep -i "killed process" تأیید کنید.

این موارد راهنمای تعیین اندازه هستند، نه یک بنچمارک: 4 GB رم و دو هسته پردازشی برای یک تیم کوچک مناسب است، و 2 GB رم به همراه یک swap file نیز اگر بار کاری شامل اسناد و تصاویر باشد و تأخیر را بپذیرید، کار می‌کند. یک swap file باعث سریع‌تر شدن تبدیل نمی‌شود. بلکه باعث می‌شود یک جهش ناگهانی در مصرف منابع، به‌جای آنکه مرگبار باشد، کند شود؛ که این تفاوت بین یک صفحه متوقف‌شده و یک قطعی کامل است. به دیسک فضای بیشتری از آنچه فکر می‌کنید نیاز است اختصاص دهید، زیرا یک تصویر 3 GB، محدودیت آپلود بالا و خروجی تبدیل‌شده، همگی خیلی زودتر از تمام شدن سایر منابع، دیسک را پر می‌کنند.

فرآیندهای تبدیل ذاتاً دارای جهش‌های ناگهانی (bursty) هستند. دو نفر که هم‌زمان ویدیو آپلود کنند، تمام هسته‌ها را اشغال می‌کنند و درخواست بعدی پشت سر آن‌ها منتظر می‌ماند. هیچ صف کاری (job queue) در جلوی این فرآیند وجود ندارد، بنابراین تنها کنترلی که در اختیار دارید، اعمال محدودیت‌ها است.

تنظیم محدودیت‌هایی برای جلوگیری از پر شدن دیسک توسط یک آپلود

ابتدا مقادیر PHP را کاهش دهید. مقادیری مانند upload_max_filesize = 512M، post_max_size = 512M و max_file_uploads = 20 نقطه شروع معقولی برای یک سرور اشتراکی با 4 گیگابایت رم هستند. به یاد داشته باشید که max_execution_time = 1200 به یک درخواست PHP اجازه می‌دهد تا بیست دقیقه اجرا شود؛ این زمان برای تبدیل ویدیوهای طولانی واقعاً لازم است، اما به این معنی است که یک آپلود کند، یک worker را برای بیست دقیقه اشغال می‌کند.

سپس اندازه و نرخ انتقال را در سطح پروکسی اعمال کنید، پیش از آنکه درخواست به 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 را به مرورگر بازمی‌گرداند. مابقی آن بلوک سرور، شامل خاتمه TLS (امنیت لایه انتقال)، در توضیح خط‌به‌خط پیکربندی reverse proxy در nginx پوشش داده شده است.

حذف فایل‌های تبدیل‌شده

هر عملیات تبدیل، یک کپی از فایل حساس را در دایرکتوری قابل خواندن توسط وب‌سرور باقی می‌گذارد. پاکسازی همان چیزی است که یک مبدل را از آرشیوی از تمام فایل‌هایی که تاکنون روی آن تبدیل شده‌اند، متمایز می‌کند.

$DeleteThreshold در Resources/config.php نشان‌دهنده عمر نشست (session) به دقیقه است که پس از آن منقضی می‌شود و مقدار پیش‌فرض آن 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 برای همان اجرا از مقدار پانزده دقیقه استفاده می‌کند. -c=now تمام نشست‌ها را بدون در نظر گرفتن عمر آن‌ها حذف می‌کند، از جمله نشستی که کاربر در همان لحظه در حال تبدیل فایل با آن است؛ بنابراین از این دستور فقط برای نگهداری (maintenance) استفاده کنید. همین آرگومان‌ها در داخل کانتینر از طریق docker exec نیز کار می‌کنند.

عملیات پاکسازی را روی یک تایمر قرار دهید تا پاکسازی هرگز به بارگذاری صفحه توسط کاربر وابسته نباشد. یک خط در /etc/cron.d/hrconvert2 کافی است:

*/10 * * * * www-data php /path/to/HRConvert2/convertCore.php -c

چند دقیقه بعد آن را با ls /DATA/HRConvert2 بررسی کنید و ناپدید شدن دایرکتوری‌های نشست قدیمی را مشاهده کنید. از آنجا که کاربر وب‌سرور مالک آن دایرکتوری است، این دقیقاً همان حسابی است که یک پارسر (parser) آسیب‌دیده به آن دسترسی پیدا می‌کند؛ بنابراین آن کاربر نباید مالک هیچ چیز ارزشمند دیگری باشد. حساب‌های کاربری با حداقل دسترسی روی VPS الگوی کلی این کار است و در اینجا بیش از موارد معمول کاربرد دارد.

اگر هدف عمومی نیست، آن را پشت لایه احراز هویت قرار دهید

نصب پیش‌فرض به‌صورت طراحی‌شده فاقد حساب کاربری است. هر کسی که به صفحه دسترسی داشته باشد می‌تواند فایلی آپلود کرده و باینری‌های مبدل شما را اجرا کند، و محدودیت‌های نرخ (rate limits) تنها این روند را کند می‌کنند. بنابراین مشخص کنید در چه وضعیتی قرار دارید.

اگر این سرویس برای شما و چند همکار است، آن را به‌هیچ‌وجه در معرض اینترنت قرار ندهید. کانتینر را مطابق آنچه در بالا نشان داده شد به loopback متصل کنید و از طریق یک شبکه خصوصی یا SSH tunnel به آن دسترسی پیدا کنید. در این صورت هیچ‌چیز در اینترنت عمومی نمی‌تواند فایلی به آن ارسال کند، که این کار به‌جای فیلتر کردن، کل سطح حمله را حذف می‌کند. اگر آن همکاران نیاز دارند بدون انتشار نام دامنه یا باز کردن پورت، آن را در مرورگر باز کنند، سرو کردن آن به عنوان یک v3 onion service باعث می‌شود مبدل همچنان به loopback متصل بماند و در عین حال آدرسی برای بازدید در اختیار آن‌ها قرار دهد.

اگر سرویس باید از طریق مرورگر قابل دسترسی باشد، یک لایه احراز هویت جلوی پروکسی قرار دهید. Basic auth تنها با دو دستور انجام می‌شود و فرم آپلود را از دسترس غریبه‌ها دور نگه می‌دارد:

sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd alice
location / {
    auth_basic "Converter";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_pass http://127.0.0.1:8080;
}

سرویس nginx را reload کنید و صفحه را بارگذاری نمایید. نمایش پنجره درخواست رمز به معنای کارکرد صحیح است، و اگر پنجره‌ای ظاهر نشد، یعنی بلاک location که ویرایش کرده‌اید، مسئول رسیدگی به این درخواست نیست. برای داشتن حساب‌های کاربری واقعی به‌جای یک رمز عبور مشترک، از یک سرویس Single Sign-On استفاده کنید: یک سرور Authentik SSO خودمیزبان به شما امکان می‌دهد احراز هویت را پیش از رسیدن به برنامه‌ای که خودش سیستم ورود ندارد، انجام دهید.

اگر هدف، داشتن یک مبدل کاملاً عمومی است، پیامدهای آن را بپذیرید و برای آن برنامه‌ریزی کنید. فرض را بر این بگیرید که sandbox مورد بررسی و نفوذ قرار خواهد گرفت. تگ image را ثابت (pinned) نگه دارید، سیاست‌های ImageMagick را سخت‌گیرانه تنظیم کنید، محدودیت‌های آپلود را کوچک نگه دارید و آن را روی یک VPS اجرا کنید که هیچ داده مهم دیگری در آن ندارید.

حالت‌های شکست و رشته‌هایی که مشاهده خواهید کرد

تمام تبدیل‌ها بلافاصله با شکست مواجه می‌شوند. محیط sandbox ساخته نمی‌شود. در یک نصب استاندارد Ubuntu 24.04، علت آن پروفایل AppArmor است. در image پین‌شده 3.7 داکر، علت آن نبود --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 وجود ندارد یا در مسیری که کاربر وب‌سرور به آن دسترسی دارد، قابل‌دستیابی نیست.

یک فرمت خاص شکست می‌خورد و بقیه کار می‌کنند. یک فایل باینری گم شده است که به‌وضوح با پیام 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 را اجرا کنید تا ببینید سیستم نصب‌شده چه مواردی را پیدا می‌کند؛ به یاد داشته باشید که PATH کاربر worker آپاچی با PATH شل ورود شما متفاوت است.

عملیات PDF با خطای policy شکست می‌خورد. خطای attempt to perform an operation not allowed by the security policy 'PDF' از فایل policy.xml در ImageMagick ناشی می‌شود، نه از HRConvert2.

آپلودهای حجیم خطای 413 برمی‌گردانند. مقدار client_max_body_size در nginx از اندازه فایل کوچک‌تر است. سه محدودیت در این زنجیره وجود دارد: یکی در nginx و دو مورد در PHP؛ و کوچک‌ترین مقدار تعیین‌کننده است.

تبدیل‌ها متوقف می‌شوند و هیچ تغییر واضحی رخ نداده است. The device where data is stored has an insufficient amount of storage space available. فضای آزاد را بررسی کنید و مطمئن شوید که فرآیند پاک‌سازی (cleanup sweep) در حال اجراست.

پاک‌سازی در لاگ خطا می‌دهد. Could not clean the temporary location! و Could not clean the convert location! مشکلات مالکیت فایل هستند. کاربر وب‌سرور باید مالک دایرکتوری مشخص‌شده توسط $ConvertLoc باشد.

FAQ

آیا قرار دادن یک مبدل فایل self-hosted در معرض اینترنت امن است؟

این کار تنها در صورتی به اندازه کافی امن است که با آن به عنوان یک پارسر (parser) که در دسترس غریبه‌ها قرار گرفته برخورد کنید. هر فایل آپلود شده به LibreOffice، ImageMagick، FFmpeg یا Ghostscript سپرده می‌شود و شخصی که فایل را آپلود می‌کند، تعیین می‌کند کدام‌یک از این ابزارها استفاده شود. برنامه HRConvert2 این ابزارها را درون یک namespace از نوع bubblewrap بدون دسترسی به شبکه و با دایرکتوری ورودی فقط‌خواندنی (read-only) اجرا می‌کند و هر تبدیلی را که نتواند در sandbox ایزوله کند، رد می‌کند که این یک پیش‌فرض امنیتی قوی است. با این حال، همچنان بهتر است احراز هویت را اجباری کنید، محدودیت‌های آپلود را کوچک نگه دارید و آن را روی یک VPS اجرا کنید که هیچ دادهٔ ارزشمند دیگری در آن وجود ندارد.

چرا تمام تبدیل‌ها در یک نصب تازه Ubuntu 24.04 با شکست مواجه می‌شوند؟

سیستم‌عامل Ubuntu 24.04 دسترسی به user namespaceهای غیرمجاز (unprivileged) را از طریق AppArmor محدود کرده است و bubblewrap برای ساخت sandbox خود به یکی از آن‌ها نیاز دارد. از آنجا که برنامه بدون sandbox از انجام تبدیل خودداری می‌کند، تمام عملیات‌ها با شکست مواجه می‌شوند. یک پروفایل AppArmor برای /usr/bin/bwrap با flags=(unconfined) بنویسید، آن را با sudo apparmor_parser -r /etc/apparmor.d/bwrap بارگذاری کنید و سپس با bwrap --ro-bind / / --dev /dev /bin/true تأیید کنید. سیستم‌عامل Debian 12 به‌طور پیش‌فرض به user namespaceهای غیرمجاز اجازه فعالیت می‌دهد و نیازی به این مرحله ندارد.

چرا تبدیل‌ها در Docker با شکست مواجه می‌شوند اما در یک نصب معمولی کار می‌کنند؟

پروفایل پیش‌فرض seccomp در Docker، فراخوانی‌های سیستمی (system calls) مورد استفاده توسط bubblewrap را مسدود می‌کند، بنابراین sandbox نمی‌تواند درون کانتینر ایجاد شود. برای image نسخه 3.7 که در اینجا مشخص شده، آن را با --security-opt seccomp=unconfined اجرا کنید که همان چیزی است که در کامنت‌های config.php آن نسخه توصیه شده است. از نسخه v3.8.0 به بعد، پروژه استفاده از --cap-add SYS_ADMIN را مستند کرده است. توجه داشته باشید که $RequireSandboxOnDocker به‌طور پیش‌فرض FALSE است، بنابراین کانتینری که بدون فلگ اجرا شود ممکن است بدون هیچ‌گونه sandbox عملیات تبدیل را انجام دهد. پس از اعمال فلگ seccomp، آن را روی TRUE تنظیم کنید.

یک سرور تبدیل فایل به چه مقدار RAM نیاز دارد؟

در حالت بیکار (idle) مصرف رم کم است، اما در حین تبدیل فایل این‌طور نیست. LibreOffice یک Java runtime را اجرا می‌کند، ImageMagick طبق سیاست‌های پیش‌فرض 256 MiB حافظه و 512 MiB مپ (map) اشغال می‌کند و محدودیت خودِ PHP نیز 512M است. روی یک VPS با 1 GB رم، این ترکیب باعث ایجاد swap شده و out of memory killer باعث توقف soffice.bin یا apache2 می‌شود. برای یک تیم کوچک، 4 GB رم و دو هسته پردازنده در نظر بگیرید و هر زمان که یک تبدیل بدون نمایش پیام خطا متوقف شد، dmesg -T | grep -i "killed process" را بررسی کنید.

فایل‌های تبدیل‌شده کجا می‌روند و چه زمانی حذف می‌شوند؟

آن‌ها به دایرکتوری کاری که توسط $ConvertLoc در Resources/config.php تعیین شده می‌روند که پیش‌فرض آن /DATA/HRConvert2 است. $DeleteThreshold عمر نشست (session) را به دقیقه تعیین می‌کند که پیش‌فرض آن 60 است. عملیات پاکسازی از طریق خط فرمان اجرا می‌شود: php convertCore.php -c نشست‌های منقضی‌شده را پاک می‌کند و -c=now تمام نشست‌ها را بلافاصله، از جمله نشست‌های فعال، حذف می‌کند. دستور -c را در یک cron entry یا یک systemd timer قرار دهید تا حذف فایل‌ها به بازدید کاربران از سایت وابسته نباشد.

آیا این مقاله مفید بود؟