feat: translate all pages, UI messages and docs and fix SEO redirects and indexing

This commit is contained in:
alam00000 committed 2026-10-25 12:55:21 +05:30
1 parent 5e019de5ae
commit 4edb1718cc
3344 files changed
+415643 -4759

No files matched your search

+275
View File
@@ -0,0 +1,275 @@
# استقرار با Apache
BentoPDF را با Apache HTTP Server میزبانی کنید.
## پیش‌نیازها
- Apache 2.4+
- فعال بودن mod_rewrite
- گواهی SSL (توصیه‌شده)
## گام ۱: ساخت پروژه
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
برای سفارشی‌سازی برند، پیش از ساخت متغیرهای محیطی را تنظیم کنید:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## گام ۲: کپی فایل‌ها
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## گام ۳: پیکربندی Apache
فایل `/etc/apache2/sites-available/bentopdf.conf` را بسازید:
```apache
<VirtualHost *:80>
ServerName your-domain.com
Redirect permanent / https://your-domain.com/
</VirtualHost>
<VirtualHost *:443>
ServerName your-domain.com
DocumentRoot /var/www/bentopdf
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/your-domain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.com/privkey.pem
<Directory /var/www/bentopdf>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# WASM MIME type
AddType application/wasm .wasm
# Prevent double-compression of pre-compressed files
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
# Compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json application/wasm
</IfModule>
# Cache headers
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>
# Required headers for SharedArrayBuffer (LibreOffice WASM)
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "cross-origin"
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
# Pre-compressed LibreOffice WASM files
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
</VirtualHost>
```
## گام ۴: فایل .htaccess برای مسیریابی
فایل `/var/www/bentopdf/.htaccess` را بسازید:
```apache
RewriteEngine On
RewriteBase /
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# ADD .HTML EXTENSION (ROOT LEVEL)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /404.html
```
## گام ۵: فعال‌سازی ماژول‌های لازم
```bash
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod expires
sudo a2enmod deflate
```
## گام ۶: فعال‌سازی سایت
```bash
sudo a2ensite bentopdf.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```
## استقرار در زیرپوشه
برای میزبانی در `/pdf/`:
1. با URL پایه بسازید:
```bash
BASE_URL=/pdf/ npm run build
```
2. فایل `.htaccess` را به‌روز کنید:
```apache
RewriteEngine On
RewriteBase /pdf/
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Language routes
RewriteRule ^en/?$ /pdf/ [R=301,L]
RewriteRule ^en/(.+)$ /pdf/$1 [R=301,L]
RewriteCond %{DOCUMENT_ROOT}/pdf/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf/$1/index.html [L]
RewriteCond %{DOCUMENT_ROOT}/pdf/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf/$1/$2.html [L]
# Root level .html extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /pdf/404.html
```
## عیب‌یابی
### خطاهای 404 برای WASM
مطمئن شوید نوع MIME پیکربندی شده است:
```apache
AddType application/wasm .wasm
```
### امضای PDF یا پرکنندهٔ فرم نمایشگر خالی نشان می‌دهد (خطای MIME برای `.mjs`)
اگر کنسول مرورگر این را نشان می‌دهد:
```
Failed to load module script: The server responded with a non-JavaScript
MIME type of "application/octet-stream". Strict MIME type checking is
enforced for module scripts per HTML spec.
```
…و درخواست ناموفق به `.mjs` ختم می‌شود، پیکربندی Apache شما نگاشت `.mjs → application/javascript` را ندارد. نمایشگر PDF همراه، فایل‌های ماژول ES با پسوند `.mjs` دارد و `mime.types` پیش‌فرض Apache به‌طور پیش‌فرض آن‌ها را شامل نمی‌شود.
به VirtualHost یا `.htaccess` خود اضافه کنید:
```apache
AddType application/javascript .mjs
```
سپس Apache را دوباره بارگذاری کنید:
```bash
sudo apache2ctl configtest && sudo systemctl reload apache2
```
برای تأیید: در DevTools مرورگر ← زبانهٔ Network، درخواست ناموفق `.mjs` را پیدا کنید و سرآیند پاسخ `Content-Type` را بررسی کنید. اکنون باید `application/javascript` باشد، نه `application/octet-stream`.
### بازنویسی کار نمی‌کند
بررسی کنید که mod_rewrite فعال باشد:
```bash
sudo a2enmod rewrite
```
### تبدیل Word/ODT/Excel به PDF کار نمی‌کند
LibreOffice WASM به `SharedArrayBuffer` نیاز دارد که این سرآیندها را لازم دارد:
```apache
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
```
همچنین به یک بستر امن (secure context) نیاز دارد. `http://localhost` برای آزمایش محلی کار می‌کند، اما `http://192.168.x.x` یا دیگر IPهای شبکهٔ محلی معمولاً به HTTPS نیاز دارند. اگر سرآیندها وجود دارند اما `window.crossOriginIsolated` همچنان `false` است، بررسی کنید که آیا صفحه با HTTP ساده روی مبدأیی غیر از loopback باز می‌شود.
فایل‌های از پیش فشرده‌شدهٔ `.wasm.gz` و `.data.gz` نیز به `Content-Encoding` درست نیاز دارند:
```apache
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
```
مطمئن شوید `mod_headers` فعال است: `sudo a2enmod headers`
### دسترسی رد شد (Permission Denied)
```bash
sudo chown -R www-data:www-data /var/www/bentopdf
sudo chmod -R 755 /var/www/bentopdf
```
+200
View File
@@ -0,0 +1,200 @@
# استقرار روی AWS S3 + CloudFront
BentoPDF را برای حداکثر کنترل و مقیاس‌پذیری روی AWS میزبانی کنید.
## معماری
```
User → CloudFront (CDN) → S3 (Static Files)
```
## گام ۱: ساخت S3 Bucket
```bash
# Create bucket
aws s3 mb s3://your-bentopdf-bucket --region us-east-1
# Enable static website hosting
aws s3 website s3://your-bentopdf-bucket \
--index-document index.html \
--error-document index.html
```
## گام ۲: ساخت و بارگذاری
```bash
# Build the project (optionally with custom branding)
# VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
npm run build
# Sync to S3
aws s3 sync dist/ s3://your-bentopdf-bucket \
--delete \
--cache-control "max-age=31536000"
# Set correct MIME types for WASM
aws s3 cp s3://your-bentopdf-bucket/ s3://your-bentopdf-bucket/ \
--recursive \
--exclude "*" \
--include "*.wasm" \
--content-type "application/wasm" \
--metadata-directive REPLACE
# Set correct MIME type for ES module files (.mjs)
# S3 defaults .mjs to application/octet-stream, which browsers refuse to
# execute as ES modules. The bundled PDF viewer (Sign PDF, Form Filler)
# ships ES modules with .mjs extensions and will fail without this step.
aws s3 cp s3://your-bentopdf-bucket/ s3://your-bentopdf-bucket/ \
--recursive \
--exclude "*" \
--include "*.mjs" \
--content-type "application/javascript" \
--metadata-directive REPLACE
```
## گام ۳: ساخت CloudFront Distribution
```bash
aws cloudfront create-distribution \
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
--default-root-object index.html
```
یا از AWS Console استفاده کنید:
1. به CloudFront ← Create distribution بروید
2. Origin domain: S3 bucket خود را انتخاب کنید
3. گزینهٔ "Origin Access Control" را فعال کنید
4. Default root object: `index.html`
5. distribution را بسازید
## گام ۳b: سیاست سرآیندهای پاسخ (لازم برای LibreOffice WASM)
تبدیل‌های مبتنی بر LibreOffice (Word، Excel، PowerPoint به PDF) به `SharedArrayBuffer` نیاز دارند که سرآیندهای پاسخ مشخصی را لازم دارد. یک CloudFront Response Headers Policy بسازید:
1. به CloudFront ← Policies ← Response headers بروید
2. یک سیاست سفارشی با این سرآیندها بسازید:
| سرآیند | مقدار |
| ------------------------------ | -------------- |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Resource-Policy` | `cross-origin` |
3. سیاست را به default cache behavior مربوط به distribution خود متصل کنید
یا از طریق CLI:
```bash
aws cloudfront create-response-headers-policy \
--response-headers-policy-config '{
"Name": "BentoPDF-COEP-COOP",
"CustomHeadersConfig": {
"Quantity": 3,
"Items": [
{"Header": "Cross-Origin-Embedder-Policy", "Value": "require-corp", "Override": true},
{"Header": "Cross-Origin-Opener-Policy", "Value": "same-origin", "Override": true},
{"Header": "Cross-Origin-Resource-Policy", "Value": "cross-origin", "Override": true}
]
}
}'
```
## گام ۳c: فرادادهٔ S3 برای فایل‌های WASM از پیش فشرده‌شده
فایل‌های LibreOffice WASM از پیش فشرده شده‌اند (`.wasm.gz`، `.data.gz`). Content-Type و Content-Encoding درست را تنظیم کنید تا مرورگرها آن‌ها را از حالت فشرده خارج کنند:
```bash
# Set correct headers for soffice.wasm.gz
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
--content-type "application/wasm" \
--content-encoding "gzip" \
--metadata-directive REPLACE
# Set correct headers for soffice.data.gz
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
--content-type "application/octet-stream" \
--content-encoding "gzip" \
--metadata-directive REPLACE
```
::: warning مهم
بدون سیاست سرآیندهای پاسخ، `SharedArrayBuffer` در دسترس نیست و تبدیل‌های LibreOffice WASM در حدود ۵۵٪ متوقف می‌شوند. بدون Content-Encoding درست روی فایل‌های `.gz`، مرورگر بایت‌های خام gzip را دریافت می‌کند و کامپایل WASM شکست می‌خورد.
:::
## گام ۴: سیاست S3 Bucket
به CloudFront اجازهٔ دسترسی به bucket را بدهید:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontAccess",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bentopdf-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
}
}
}
]
}
```
## گام ۵: صفحات خطای سفارشی
خطای 404 را طوری پیکربندی کنید که برای مسیریابی SPA فایل `index.html` را برگرداند:
1. CloudFront ← Error pages
2. یک پاسخ خطای سفارشی بسازید:
- HTTP error code: 404
- Response page path: `/index.html`
- HTTP response code: 200
## برآورد هزینه
| منبع | هزینهٔ تقریبی |
| --------------------------- | ------------- |
| فضای ذخیره‌سازی S3 (~500MB) | ~$0.01 در ماه |
| CloudFront (انتقال 1TB) | ~$85 در ماه |
| CloudFront (انتقال 10GB) | ~$0.85 در ماه |
::: tip
برای بهینه‌سازی هزینهٔ فایل‌هایی که کمتر به آن‌ها دسترسی می‌شود، از S3 Intelligent Tiering استفاده کنید.
:::
## خودکارسازی با Terraform
```hcl
# main.tf
resource "aws_s3_bucket" "bentopdf" {
bucket = "your-bentopdf-bucket"
}
resource "aws_cloudfront_distribution" "bentopdf" {
origin {
domain_name = aws_s3_bucket.bentopdf.bucket_regional_domain_name
origin_id = "S3Origin"
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin"
viewer_protocol_policy = "redirect-to-https"
}
}
```
+154
View File
@@ -0,0 +1,154 @@
# استقرار روی Cloudflare Pages
[Cloudflare Pages](https://pages.cloudflare.com) میزبانی سریع و جهانی سایت‌های ایستا با پهنای باند نامحدود ارائه می‌دهد.
## استقرار سریع
1. به [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages) بروید
2. روی "Create a project" کلیک کنید
3. مخزن GitHub خود را متصل کنید
## پیکربندی ساخت
| تنظیم | مقدار |
| ---------------- | --------------- |
| Framework preset | None |
| فرمان ساخت | `npm run build` |
| پوشهٔ خروجی ساخت | `dist` |
| پوشهٔ ریشه | `/` |
## متغیرهای محیطی
این‌ها را در Settings ← Environment variables اضافه کنید:
| متغیر | مقدار |
| ----------------------- | ---------------------------------------------- |
| `NODE_VERSION` | `18` |
| `SIMPLE_MODE` | `false` (اختیاری) |
| `VITE_BRAND_NAME` | نام برند سفارشی (اختیاری) |
| `VITE_BRAND_LOGO` | مسیر لوگو نسبت به `public/` (اختیاری) |
| `VITE_FOOTER_TEXT` | متن سفارشی پاصفحه یا کپی‌رایت (اختیاری) |
| `VITE_DEFAULT_LANGUAGE` | زبان پیش‌فرض رابط کاربری، مثلاً `fr` (اختیاری) |
## فایل پیکربندی
فایل `_headers` را در پوشهٔ `public` خود بسازید:
```
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
/*
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: cross-origin
# Pre-compressed LibreOffice WASM binary
/libreoffice-wasm/soffice.wasm.gz
Content-Type: application/wasm
Content-Encoding: gzip
Cache-Control: public, max-age=31536000, immutable
# Pre-compressed LibreOffice WASM data
/libreoffice-wasm/soffice.data.gz
Content-Type: application/octet-stream
Content-Encoding: gzip
Cache-Control: public, max-age=31536000, immutable
# Cache WASM files aggressively
/*.wasm
Cache-Control: public, max-age=31536000, immutable
Content-Type: application/wasm
# Service worker
/sw.js
Cache-Control: no-cache
```
::: warning مهم
سرآیندهای `Cross-Origin-Embedder-Policy` و `Cross-Origin-Opener-Policy` برای تبدیل Word/ODT/Excel/PowerPoint به PDF لازم هستند. بدون آن‌ها، `SharedArrayBuffer` در دسترس نیست و موتور LibreOffice WASM راه‌اندازی نمی‌شود.
:::
فایل `_redirects` را برای مسیریابی SPA بسازید:
```
/* /index.html 200
```
## دامنهٔ سفارشی
1. به پروژهٔ Pages خود بروید
2. روی "Custom domains" کلیک کنید
3. دامنهٔ خود را اضافه کنید
4. اگر دامنه روی Cloudflare باشد، Cloudflare به‌طور خودکار DNS را پیکربندی می‌کند
## مزایا
- **پهنای باند نامحدود رایگان**
- **CDN جهانی** با بیش از ۳۰۰ نقطهٔ لبه
- **HTTPS خودکار**
- **استقرارهای پیش‌نمایش** برای pull requestها
- **ساخت سریع**
## عیب‌یابی
### بارگذاری فایل‌های بزرگ
Cloudflare Pages از فایل‌هایی تا ۲۵ مگابایت پشتیبانی می‌کند. ماژول‌های WASM باید مشکلی نداشته باشند، اما اگر به محدودیت برخوردید، این را در نظر بگیرید:
```bash
# Split large files during build
npm run build
```
### محدودیت اندازهٔ Worker
اگر از Cloudflare Workers برای مسیریابی پیشرفته استفاده می‌کنید، به محدودیت ۱ مگابایت در طرح‌های رایگان توجه کنید.
## Worker پراکسی CORS (برای امضاهای دیجیتال)
ابزار امضای دیجیتال برای دریافت زنجیرهٔ گواهی‌ها به یک پراکسی CORS نیاز دارد. worker همراه را مستقر کنید:
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy
```
### ویژگی‌های امنیتی
| ویژگی | توضیح |
| ------------------------ | ------------------------------------------- |
| **محدودیت URL** | فقط URLهای گواهی مجاز هستند |
| **محدودیت اندازهٔ فایل** | حداکثر 10MB برای هر درخواست |
| **محدودسازی نرخ** | ۶۰ درخواست برای هر IP در دقیقه (نیازمند KV) |
| **مسدودسازی IP خصوصی** | مسدود کردن localhost و IPهای داخلی |
### فعال‌سازی محدودسازی نرخ
```bash
# Create KV namespace
npx wrangler kv namespace create "RATE_LIMIT_KV"
# Add to wrangler.toml with returned ID:
# [[kv_namespaces]]
# binding = "RATE_LIMIT_KV"
# id = "YOUR_ID"
npx wrangler deploy
```
### ساخت با URL پراکسی
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
یا با Docker:
```bash
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
DOCKER_BUILDKIT=1 docker build \
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
-t your-bentopdf .
```
> **نکته:** برای راه‌اندازی امضای HMAC، [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required) را ببینید.
+180
View File
@@ -0,0 +1,180 @@
# پراکسی CORS برای دریافت گواهی‌ها
ابزار امضای دیجیتال از یک پراکسی CORS برای دریافت گواهی‌های صادرکننده از مراجع صدور گواهی (CA) خارجی استفاده می‌کند. این کار لازم است، چون بسیاری از سرورهای CA سرآیندهای CORS را در پاسخ‌های خود قرار نمی‌دهند و این مانع دریافت مستقیم از مرورگر می‌شود.
علاوه بر این، بسیاری از سرورهای CA گواهی‌ها را روی HTTP ساده ارائه می‌کنند. وقتی نمونهٔ BentoPDF شما روی HTTPS میزبانی می‌شود، مرورگرها این درخواست‌های HTTP را مسدود می‌کنند (سیاست محتوای ترکیبی). پراکسی CORS هر دو مشکل را با هدایت درخواست‌ها از طریق یک endpoint با HTTPS و سرآیندهای مناسب حل می‌کند.
## نحوهٔ کار
هنگام امضای PDF با یک گواهی:
1. کتابخانهٔ `zgapdfsigner` تلاش می‌کند یک زنجیرهٔ کامل گواهی بسازد
2. گواهی‌های صادرکننده را از URLهایی که در افزونهٔ AIA (Authority Information Access) گواهی شما قرار دارند دریافت می‌کند
3. این درخواست‌ها از طریق یک پراکسی CORS هدایت می‌شوند که سرآیندهای لازم `Access-Control-Allow-Origin` را اضافه می‌کند
4. پراکسی دادهٔ گواهی را به مرورگر برمی‌گرداند
## میزبانی شخصی پراکسی CORS
اگر BentoPDF را خودتان میزبانی می‌کنید، باید پراکسی CORS خود را مستقر کنید تا امضای دیجیتال با گواهی‌هایی که به دریافت زنجیره نیاز دارند کار کند.
### گزینهٔ ۱: Cloudflare Workers (توصیه‌شده)
1. **نصب Wrangler CLI**:
```bash
npm install -g wrangler
```
2. **ورود به Cloudflare**:
```bash
wrangler login
```
3. **کلون کردن BentoPDF و به‌روزرسانی مبدأهای مجاز**:
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf/cloudflare
```
فایل `cors-proxy-worker.js` را باز کنید و آرایهٔ `ALLOWED_ORIGINS` را به دامنهٔ خود تغییر دهید:
```js
const ALLOWED_ORIGINS = [
'https://your-domain.com',
'https://www.your-domain.com',
];
```
::: warning مهم
بدون این تغییر، پراکسی همهٔ درخواست‌های سایت شما را با خطای **403 Forbidden** رد می‌کند. پیش‌فرض فقط درخواست‌های `bentopdf.com` را مجاز می‌کند.
:::
4. **استقرار پراکسی**:
```bash
wrangler deploy
```
URL worker خود را یادداشت کنید (مثلاً `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
5. **ساخت دوبارهٔ BentoPDF با URL پراکسی**:
اگر از Docker استفاده می‌کنید:
```bash
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
DOCKER_BUILDKIT=1 docker build \
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
-t your-bentopdf .
```
اگر از کد منبع می‌سازید:
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
### گزینهٔ ۲: پراکسی بک‌اند سفارشی
همچنین می‌توانید endpoint پراکسی خود را بسازید. الزامات عبارت‌اند از:
1. پذیرش درخواست‌های GET با پارامتر پرس‌وجوی `url`
2. دریافت URL از سرور شما (در سمت سرور محدودیت CORS وجود ندارد)
3. بازگرداندن پاسخ با این سرآیندها:
- `Access-Control-Allow-Origin: https://your-domain.com`
- `Access-Control-Allow-Methods: GET, OPTIONS`
- `X-Content-Type-Options: nosniff`
نمونهٔ پیاده‌سازی با Express.js:
```javascript
app.get('/api/cert-proxy', async (req, res) => {
const targetUrl = req.query.url;
// Validate it's a certificate URL
if (!isValidCertUrl(targetUrl)) {
return res.status(400).json({ error: 'Invalid URL' });
}
try {
const response = await fetch(targetUrl);
const data = await response.arrayBuffer();
res.set('Access-Control-Allow-Origin', 'https://your-domain.com');
res.set('Content-Type', 'application/octet-stream');
res.set('X-Content-Type-Options', 'nosniff');
res.send(Buffer.from(data));
} catch (error) {
res.status(500).json({ error: 'Proxy error' });
}
});
```
## ملاحظات امنیتی
Cloudflare Worker همراه چندین تدبیر امنیتی دارد:
| ویژگی | توضیح |
| ------------------------ | --------------------------------------------------------------------------------------------------------- |
| **اعتبارسنجی مبدأ** | فقط درخواست‌های دامنه‌های فهرست‌شده در `ALLOWED_ORIGINS` را مجاز می‌کند |
| **محدودیت URL** | فقط URLهای گواهی را مجاز می‌کند (`.crt`، `.cer`، `.pem`، `/certs/`، `/ocsp`، `/crl`) |
| **مسدودسازی IP خصوصی** | محدوده‌های خصوصی IPv4/IPv6، link-local، loopback، IPهای دهدهی و فرادادهٔ ابری را مسدود می‌کند |
| **ایمنی Content-Type** | فقط انواع MIME امن گواهی را برمی‌گرداند و تزریق content-type از بالادست را مسدود می‌کند |
| **محدودیت اندازهٔ فایل** | پاسخ را با محدودیت 10MB به‌صورت جریانی ارسال می‌کند و در صورت عبور از آن، دانلود را در میانه متوقف می‌کند |
| **محدودسازی نرخ** | ۶۰ درخواست در دقیقه برای هر IP یا IPv6 /64 (نیازمند KV، در خطاهای KV باز می‌ماند) |
| **امضاهای HMAC** | امضای اختیاری سمت کاربر (بازدارندهٔ سوءاستفادهٔ معمولی) |
### DNS rebinding (میزبانی شخصی خارج از Cloudflare)
worker هر URL را که به یک IP خصوصی یا داخلی resolve شود رد می‌کند. نمی‌تواند از فهرست ثابتی از میزبان‌های مجاز استفاده کند، چون URLهای زنجیرهٔ گواهی به هر CA که گواهی را صادر کرده اشاره می‌کنند (FNMT، CAهای سازمانی و غیره)، پس مقصدها از پیش معلوم نیستند. این یک پنجرهٔ کوچک rebinding باقی می‌گذارد: کسی که DNS دامنهٔ خودش را اداره می‌کند می‌تواند یک IP عمومی به بررسی ایمنی و لحظه‌ای بعد یک IP خصوصی به درخواست واقعی بدهد.
روی Cloudflare این قابل بهره‌برداری نیست: Workers نمی‌توانند به IPهای خصوصی یا endpoint فرادادهٔ `169.254.169.254` دسترسی پیدا کنند، پس rebind به جایی نمی‌رسد. فقط راه ورودی به worker ندهید: Tunnel، Service یا binding شبکهٔ خصوصی متصل نکنید (فقط به `RATE_LIMIT_KV` نیاز دارد).
اگر این را خارج از Cloudflare (workerd، Node و مانند آن‌ها) درون یک VPC اجرا می‌کنید، محیط اجرا _می‌تواند_ به IPهای داخلی دسترسی پیدا کند. یک فایروال خروجی جلوی آن قرار دهید و محدوده‌های خصوصی را مسدود کنید: `10.0.0.0/8`، `172.16.0.0/12`، `192.168.0.0/16`، `127.0.0.0/8`، `169.254.0.0/16` و معادل‌های IPv6 آن‌ها. وقتی خارج از Cloudflare هستید، کد برنامه نمی‌تواند این را اعمال کند.
## غیرفعال کردن پراکسی
اگر نمی‌خواهید از پراکسی CORS استفاده کنید، متغیر محیطی را با رشتهٔ خالی تنظیم کنید:
```
VITE_CORS_PROXY_URL=
```
**نکته**: بدون پراکسی، امضا با گواهی‌هایی که به دریافت خارجی زنجیره نیاز دارند (مانند FNMT یا برخی CAهای سازمانی) با خطای "Failed to fetch" شکست می‌خورد.
## عیب‌یابی
### "Signing error: TypeError: Failed to fetch"
این معمولاً یعنی یکی از این موارد:
1. **پراکسی CORS پیکربندی نشده است**: `VITE_CORS_PROXY_URL` را تنظیم کنید و دوباره بسازید
2. **محتوای ترکیبی مسدود شده است**: سایت شما HTTPS است، اما URL صادرکنندهٔ گواهی HTTP است. پراکسی CORS این را حل می‌کند.
3. **پراکسی CORS مبدأ شما را رد می‌کند**: بررسی کنید دامنهٔ شما در آرایهٔ `ALLOWED_ORIGINS` در `cors-proxy-worker.js` باشد
### خطای "403 Forbidden" از پراکسی
دامنهٔ شما در فهرست `ALLOWED_ORIGINS` نیست. `cors-proxy-worker.js` را ویرایش کنید:
```js
const ALLOWED_ORIGINS = ['https://your-domain.com'];
```
سپس دوباره مستقر کنید: `npx wrangler deploy`
### آزمایش پراکسی
```bash
curl -H "Origin: https://your-domain.com" \
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
```
### گواهی‌هایی که بدون پراکسی کار می‌کنند
برخی گواهی‌ها زنجیرهٔ کامل را در فایل P12/PFX دارند و به دریافت خارجی نیاز ندارند:
- گواهی‌های خودامضا
- برخی CAهای تجاری که گواهی‌های میانی را همراه می‌کنند
- گواهی‌هایی که خودتان دستی با زنجیرهٔ کامل سرهم کرده‌اید
+622
View File
@@ -0,0 +1,622 @@
# استقرار با Docker / Podman
ساده‌ترین راه برای میزبانی شخصی BentoPDF در محیط production.
> [!IMPORTANT]
> **سرآیندهای لازم برای تبدیل فایل‌های Office**
>
> ابزارهای مبتنی بر LibreOffice (تبدیل Word، Excel، PowerPoint) برای پشتیبانی از `SharedArrayBuffer` به این سرآیندهای HTTP نیاز دارند:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> صفحه همچنین باید از یک بستر امن (secure context) ارائه شود. `http://localhost` برای آزمایش محلی کار می‌کند، اما `http://192.168.x.x` یا دیگر IPهای شبکهٔ محلی معمولاً واجد شرایط نیستند، بنابراین تبدیل Office روی HTTP ساده حتی با وجود سرآیندها شکست می‌خورد.
>
> ایمیج‌های رسمی کانتینر این سرآیندها را دارند. اگر از پراکسی معکوس (مانند Traefik، Caddy و غیره) استفاده می‌کنید، مطمئن شوید این سرآیندها حفظ یا اضافه می‌شوند و برای دسترسی غیر loopback از HTTPS استفاده کنید.
> [!TIP]
> **کاربران Podman:** همهٔ فرمان‌های `docker` با Podman کار می‌کنند؛ کافی است `docker` را با `podman` و `docker-compose` را با `podman-compose` جایگزین کنید.
## شروع سریع
> [!TIP]
> BentoPDF در دو نسخهٔ ساخت عرضه می‌شود:
>
> - **نسخهٔ میزبانی شخصی**: `ghcr.io/alam00000/bentopdf-simple:latest`. همهٔ ابزارهای PDF، **بدون** بخش‌های بازاریابی (بدون hero، پرسش‌های متداول، نظرات کاربران و پاصفحه). از این نسخه برای استقرارهای داخلی، تیمی و سازمانی استفاده کنید. این نسخه یک نسخهٔ «سبک» با قابلیت‌های کمتر **نیست**.
> - **نسخهٔ تجاری**: `ghcr.io/alam00000/bentopdf:latest`. سایت کامل بازاریابی که خود bentopdf.com و دارندگان مجوز تجاری برای استقرارهای عمومی از آن استفاده می‌کنند.
>
> اگر مطمئن نیستید: نسخهٔ میزبانی شخصی را دریافت کنید.
```bash
# Docker
docker run -d \
--name bentopdf \
-p 3000:8080 \
--restart unless-stopped \
ghcr.io/alam00000/bentopdf-simple:latest
# Podman
podman run -d \
--name bentopdf \
-p 3000:8080 \
ghcr.io/alam00000/bentopdf-simple:latest
```
## Docker Compose / Podman Compose
فایل `docker-compose.yml` را بسازید:
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
container_name: bentopdf
ports:
- '3000:8080'
restart: unless-stopped
healthcheck:
test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8080']
interval: 30s
timeout: 10s
retries: 3
```
اجرا:
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## ساخت ایمیج شخصی
```dockerfile
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginxinc/nginx-unprivileged:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
```
ساخت و اجرا:
```bash
docker build -t bentopdf:custom .
docker run -d -p 3000:8080 bentopdf:custom
```
## متغیرهای محیطی
| متغیر | توضیح | پیش‌فرض |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `SIMPLE_MODE` | ساخت نسخهٔ میزبانی شخصی (بخش‌های بازاریابی bentopdf.com، یعنی hero، پرسش‌های متداول، نظرات کاربران و پاصفحه را پنهان می‌کند). همهٔ ابزارهای PDF باقی می‌مانند. | `false` |
| `BASE_URL` | استقرار در زیرپوشه | `/` |
| `VITE_WASM_PYMUPDF_URL` | URL ماژول PyMuPDF WASM | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
| `VITE_WASM_GS_URL` | URL ماژول Ghostscript WASM | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
| `VITE_WASM_CPDF_URL` | URL ماژول CoherentPDF WASM | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
| `VITE_TESSERACT_WORKER_URL` | URL اسکریپت worker برای OCR | _(خالی؛ استفاده از CDN پیش‌فرض Tesseract.js)_ |
| `VITE_TESSERACT_CORE_URL` | پوشهٔ هستهٔ اجرایی OCR | _(خالی؛ استفاده از CDN پیش‌فرض Tesseract.js)_ |
| `VITE_TESSERACT_LANG_URL` | پوشهٔ traineddata برای OCR | _(خالی؛ استفاده از CDN پیش‌فرض Tesseract.js)_ |
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | زبان‌های OCR نمایش‌داده‌شده در رابط کاربری، جداشده با کاما | _(خالی؛ نمایش فهرست کامل)_ |
| `VITE_OCR_FONT_BASE_URL` | پوشهٔ فونت‌های لایهٔ متنی OCR | _(خالی؛ استفاده از URLهای راه دور فونت Noto)_ |
| `VITE_DEFAULT_LANGUAGE` | زبان پیش‌فرض رابط کاربری | `en` |
| `VITE_BRAND_NAME` | نام برند سفارشی | `BentoPDF` |
| `VITE_BRAND_LOGO` | مسیر لوگو نسبت به `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | متن سفارشی پاصفحه یا کپی‌رایت | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | شناسه‌های ابزارهایی که پنهان می‌شوند، جداشده با کاما | _(خالی؛ همهٔ ابزارها فعال)_ |
| `DISABLE_GITHUB_STARS` | از دریافت تعداد ستاره‌های GitHub هنگام بارگذاری صفحه صرف‌نظر می‌کند و `api.github.com` را از CSP حذف می‌کند. ساخت‌های Simple Mode همیشه از آن صرف‌نظر می‌کنند. | `false` |
| `SITE_URL` | URL عمومی که در پیوندهای canonical، hreflang و sitemap نوشته می‌شود. آن را روی پیش‌فرض نگه دارید، مگر اینکه بخواهید موتورهای جستجو استقرار شما را سایتی مستقل بدانند. | `https://www.bentopdf.com` |
| `ALLOW_INDEXING` | با `SITE_URL` سفارشی، همهٔ صفحات `noindex` علامت‌گذاری می‌شوند تا نسخهٔ شما در نتایج جستجو با bentopdf.com رقابت نکند. برای اجازهٔ نمایه‌سازی به موتورهای جستجو، آن را `true` کنید. | `false` |
URLهای ماژول‌های WASM با پیش‌فرض‌های CDN از پیش پیکربندی شده‌اند؛ همهٔ قابلیت‌های پیشرفته بلافاصله کار می‌کنند. برای استقرارهای ایزوله یا میزبانی شخصی، این‌ها را بازنویسی کنید.
### موتورهای جستجو
به‌طور پیش‌فرض، همهٔ صفحات bentopdf.com را به‌عنوان نسخهٔ اصلی به موتورهای جستجو معرفی می‌کنند تا استقرار شما به‌صورت تکراری نمایش داده نشود. برای یک نمونهٔ خصوصی یا داخلی نیازی به تغییر چیزی نیست.
- **کاملاً بیرون نگه داشتن نمونهٔ شما از نتایج جستجو:** کانتینر را با `-e ROBOTS_NOINDEX=true` اجرا کنید. این فقط به راه‌اندازی مجدد نیاز دارد، نه ساخت دوباره.
- **تبدیل نمونهٔ شما به سایتی مستقل در نتایج جستجو:** با `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true` بسازید. بدون `ALLOW_INDEXING=true`، یک `SITE_URL` سفارشی همهٔ صفحات را `noindex` علامت‌گذاری می‌کند.
### Content-Security-Policy
ایمیج nginx با یک سرآیند `Content-Security-Policy` اجباری عرضه می‌شود. دستورهای `script-src`، `connect-src` و `font-src` در CSP **هنگام ساخت** از متغیرهای URL `VITE_*` بالا تولید می‌شوند؛ هر میزبانی که از طریق `--build-arg` بدهید به‌طور خودکار به سیاست افزوده می‌شود.
در نتیجه:
- اگر `VITE_CORS_PROXY_URL` یا `VITE_WASM_*_URL` را هنگام ساخت بازنویسی کنید، CSP به‌طور خودکار آن مبدأها را مجاز می‌کند؛ نیازی به پیکربندی اضافه نیست.
- برای URLهای روی `cdn.jsdelivr.net`، `script-src` فقط پوشهٔ دقیق بسته با نسخهٔ مشخص را مجاز می‌کند، برای مثال `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`، نه کل CDN را. jsDelivr هر بستهٔ عمومی npm یا مخزن GitHub را ارائه می‌کند، پس مجاز کردن کل میزبان به هر یک از آن‌ها اجازه می‌دهد روی نمونهٔ شما اجرا شوند. URLهای روی هر میزبان دیگر بر اساس مبدأ مجاز می‌شوند.
- اگر URLهای سفارشی WASM را در _زمان اجرا_ از طریق صفحهٔ «تنظیمات پیشرفته» درون برنامه پیکربندی کنید، آن مبدأها در CSP **نیستند** و مرورگر درخواست‌ها به آن‌ها را مسدود می‌کند. این برای نسخهٔ دیگری از همان بسته روی `cdn.jsdelivr.net` نیز صدق می‌کند، چون فقط نسخهٔ تنظیم‌شده هنگام ساخت مجاز است. پیکربندی زمان اجرا برای آزمایش در نظر گرفته شده است؛ برای URLهای سفارشی دائمی، build arg متناظر `VITE_*` را تنظیم کنید.
- در استقرارهای ایزوله‌ای که هر سه مقدار `VITE_WASM_*_URL` را بازنویسی می‌کنند، آن پوشه‌های بسته روی `cdn.jsdelivr.net` نیز از CSP حذف می‌شوند (هر پیش‌فرض جایگزین می‌شود، نه اینکه به آن افزوده شود). پیش‌فرض‌های Tesseract OCR روی `cdn.jsdelivr.net` تا زمانی که سه مقدار `VITE_TESSERACT_*` را نیز تنظیم نکنید در سیاست باقی می‌مانند. به همین ترتیب، تنظیم `VITE_CORS_PROXY_URL` جایگزین پیش‌فرض عمومی `bentopdf-cors-proxy.bentopdf.workers.dev` می‌شود.
CSP شامل `'unsafe-eval'` در `script-src` است، زیرا محیط اجرایی LibreOffice WASM (که ابزارهای تبدیل Word/Excel/PowerPoint از آن استفاده می‌کنند) و کتابخانهٔ تصویر wasm-vips (که PDF به TIFF از آن استفاده می‌کند) کد توزیع داخلی را از طریق `new Function()` کامپایل می‌کنند. حذف آن این ابزارها را از کار می‌اندازد. اگر در `SIMPLE_MODE` (بدون LibreOffice) می‌سازید و به PDF به TIFF نیاز ندارید، می‌توانید فایل تولیدشدهٔ `security-headers.conf` را دستی ویرایش کنید و برای سیاستی سخت‌گیرانه‌تر `'unsafe-eval'` را حذف کنید.
برای OCR، متغیرهای `VITE_TESSERACT_*` را خالی بگذارید تا از منابع آنلاین پیش‌فرض استفاده شود، یا برای OCR میزبانی‌شده یا آفلاین هر سه را با هم تنظیم کنید. بازنویسی‌های ناقص OCR رد می‌شوند، چون worker، هستهٔ اجرایی و پوشهٔ traineddata باید با هم سازگار باشند. برای خروجی PDF قابل‌جستجوی کاملاً آفلاین، `VITE_OCR_FONT_BASE_URL` را نیز تنظیم کنید تا فونت‌های لایهٔ متنی OCR به‌جای URLهای عمومی فونت Noto از سرور داخلی شما بارگذاری شوند.
`VITE_DEFAULT_LANGUAGE` زبان رابط کاربری را برای بازدیدکنندگان بار اول تعیین می‌کند. مقادیر پشتیبانی‌شده: `en`، `ar`، `be`، `fr`، `de`، `es`، `zh`، `zh-TW`، `vi`، `tr`، `id`، `it`، `pt`، `nl`، `da`. کاربران همچنان می‌توانند زبان را تغییر دهند؛ این فقط پیش‌فرض را تغییر می‌دهد.
مثال:
```bash
# Build with French as the default language
docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf .
docker run -d -p 3000:8080 bentopdf
```
### برندسازی سفارشی
لوگو، نام و متن پاصفحهٔ پیش‌فرض BentoPDF را با موارد خودتان جایگزین کنید. فایل لوگوی خود را در پوشهٔ `public/` قرار دهید (یا از یک تصویر موجود استفاده کنید)، سپس متغیرهای برندسازی را هنگام ساخت ارسال کنید:
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
-t acmepdf .
```
برندسازی هم در حالت کامل و هم در Simple Mode کار می‌کند و با همهٔ گزینه‌های دیگر زمان ساخت قابل ترکیب است.
### غیرفعال کردن ابزارهای مشخص
ابزارها را برای الزامات انطباق یا امنیتی از رابط کاربری پنهان کنید. ابزارهای غیرفعال از صفحهٔ اصلی، نتایج جستجو، میان‌برهای صفحه‌کلید و سازندهٔ گردش کار حذف می‌شوند. دسترسی مستقیم از طریق URL صفحهٔ «ابزار در دسترس نیست» را نشان می‌دهد.
شناسهٔ ابزار همان URL صفحه بدون `.html` است. برای مثال، اگر ابزار در `bentopdf.com/edit-pdf.html` باشد، شناسه `edit-pdf` است.
#### یافتن شناسهٔ ابزارها
ساده‌ترین راه: هر ابزاری را در BentoPDF باز کنید و به URL نگاه کنید. بخش آخر مسیر (بدون `.html`) شناسهٔ ابزار است.
<details>
<summary>فهرست کامل شناسه‌های ابزارها</summary>
**ویرایش و حاشیه‌نویسی:** `edit-pdf`, `bookmark`, `table-of-contents`, `page-numbers`, `add-page-labels`, `bates-numbering`, `add-watermark`, `header-footer`, `invert-colors`, `scanner-effect`, `adjust-colors`, `background-color`, `text-color`, `sign-pdf`, `add-stamps`, `remove-annotations`, `crop-pdf`, `form-filler`, `form-creator`, `remove-blank-pages`
**تبدیل به PDF:** `image-to-pdf`, `jpg-to-pdf`, `png-to-pdf`, `webp-to-pdf`, `svg-to-pdf`, `bmp-to-pdf`, `heic-to-pdf`, `tiff-to-pdf`, `txt-to-pdf`, `markdown-to-pdf`, `json-to-pdf`, `csv-to-pdf`, `rtf-to-pdf`, `odt-to-pdf`, `word-to-pdf`, `excel-to-pdf`, `powerpoint-to-pdf`, `xps-to-pdf`, `mobi-to-pdf`, `epub-to-pdf`, `fb2-to-pdf`, `cbz-to-pdf`, `wpd-to-pdf`, `wps-to-pdf`, `xml-to-pdf`, `pages-to-pdf`, `odg-to-pdf`, `ods-to-pdf`, `odp-to-pdf`, `pub-to-pdf`, `vsd-to-pdf`, `psd-to-pdf`, `email-to-pdf`
**تبدیل از PDF:** `pdf-to-jpg`, `pdf-to-png`, `pdf-to-webp`, `pdf-to-bmp`, `pdf-to-tiff`, `pdf-to-cbz`, `pdf-to-svg`, `pdf-to-csv`, `pdf-to-excel`, `pdf-to-greyscale`, `pdf-to-json`, `pdf-to-docx`, `extract-images`, `pdf-to-markdown`, `prepare-pdf-for-ai`, `pdf-to-text`
**سازماندهی و مدیریت:** `ocr-pdf`, `merge-pdf`, `alternate-merge`, `organize-pdf`, `add-attachments`, `extract-attachments`, `edit-attachments`, `pdf-multi-tool`, `pdf-layers`, `extract-tables`, `split-pdf`, `divide-pages`, `extract-pages`, `delete-pages`, `add-blank-page`, `reverse-pages`, `rotate-pdf`, `rotate-custom`, `n-up-pdf`, `pdf-booklet`, `combine-single-page`, `view-metadata`, `edit-metadata`, `pdf-to-zip`, `compare-pdfs`, `posterize-pdf`, `page-dimensions`
**بهینه‌سازی و تعمیر:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**امنیت:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### گزینهٔ ۱: زمان ساخت (Docker build arg)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
این کار فهرست غیرفعال‌ها را در بستهٔ JavaScript قرار می‌دهد. تغییر آن به ساخت دوباره نیاز دارد.
#### گزینهٔ ۲: زمان اجرا (config.json)
یک فایل `config.json` را در پوشهٔ ارائه‌شده mount کنید؛ نیازی به ساخت دوباره نیست:
```json
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
```bash
docker run -d \
-p 3000:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
ghcr.io/alam00000/bentopdf-simple:latest
```
یا با Docker Compose:
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
ports:
- '3000:8080'
volumes:
- ./config.json:/usr/share/nginx/html/config.json:ro
```
هر دو روش قابل ترکیب هستند؛ فهرست‌ها ادغام می‌شوند. اگر ابزاری در هر یک از آن‌ها باشد، غیرفعال می‌شود.
#### غیرفعال کردن قابلیت‌های ویرایشگر
همچنین می‌توانید قابلیت‌های مشخصی را در ویرایشگر PDF (مثلاً حذف محتوای حساس، حاشیه‌نویسی‌ها، فرم‌ها) بدون غیرفعال کردن کل ابزار ویرایشگر غیرفعال کنید. `editorDisabledCategories` را به `config.json` خود اضافه کنید:
```json
{
"editorDisabledCategories": ["redaction", "annotation-stamp"]
}
```
<details>
<summary>فهرست کامل دسته‌های ویرایشگر</summary>
**بزرگنمایی:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
**حاشیه‌نویسی:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
**اشکال:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
**فرم:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
**حذف محتوای حساس:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
**سند:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
**صفحه:** `page`, `spread`, `rotate`, `scroll`, `navigation`
**پنل:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
**ابزارها:** `tools`, `pan`, `pointer`, `capture`
**انتخاب:** `selection`, `selection-copy`
**تاریخچه:** `history`, `history-undo`, `history-redo`
</details>
دسته‌ها سلسله‌مراتبی هستند؛ غیرفعال کردن یک والد (مثلاً `annotation`) همهٔ فرزندان آن را غیرفعال می‌کند.
### غیرفعال کردن شمارندهٔ ستاره‌های GitHub
نوار ناوبری تعداد زندهٔ ستاره‌های GitHub را نشان می‌دهد که هنگام بارگذاری صفحه از `api.github.com` دریافت می‌شود. این تنها درخواست خارجی است که به یک قابلیت PDF مربوط نیست. آن را هنگام ساخت غیرفعال کنید تا کد دریافت از بسته حذف شود و `api.github.com` از Content-Security-Policy کنار گذاشته شود:
```bash
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
```
ساخت‌های Simple Mode (`bentopdf-simple`) همیشه از شمارندهٔ ستاره صرف‌نظر می‌کنند؛ این پرچم فقط در نسخهٔ تجاری یا ساخت‌های کامل سفارشی لازم است.
### URLهای سفارشی WASM (ایزوله / میزبانی شخصی)
> [!IMPORTANT]
> URLهای WASM **هنگام ساخت** در JavaScript قرار می‌گیرند. فایل‌های WASM در زمان اجرا توسط **مرورگر کاربر** دانلود می‌شوند؛ Docker آن‌ها را هنگام ساخت دانلود نمی‌کند. برای شبکه‌های ایزوله، باید فایل‌های WASM را روی یک سرور داخلی که مرورگرها به آن دسترسی دارند میزبانی کنید.
**روند کامل محیط ایزوله:**
```bash
# 1. On a machine WITH internet — download WASM packages
bash scripts/prepare-airgap.sh --list-ocr-languages
bash scripts/prepare-airgap.sh --search-ocr-language german
# 2. Download WASM/OCR packages
npm pack @bentopdf/pymupdf-wasm@0.11.14
npm pack @bentopdf/gs-wasm
npm pack coherentpdf
npm pack tesseract.js@7.0.0
npm pack tesseract.js-core@7.0.0
mkdir -p tesseract-langdata
curl -fsSL https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz -o tesseract-langdata/eng.traineddata.gz
mkdir -p ocr-fonts
curl -fsSL https://raw.githack.com/googlefonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf -o ocr-fonts/NotoSans-Regular.ttf
# 3. Build the image with your internal server URLs
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
--build-arg VITE_TESSERACT_AVAILABLE_LANGUAGES=eng,deu \
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
-t bentopdf .
# 4. Export the image
docker save bentopdf -o bentopdf.tar
# 5. Transfer bentopdf.tar + the .tgz packages + tesseract-langdata/ + ocr-fonts/ into the air-gapped network
# 6. Inside the air-gapped network — load and run
docker load -i bentopdf.tar
# Extract WASM packages to your internal web server
mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf /var/www/wasm/ocr/core /var/www/wasm/ocr/lang-data /var/www/wasm/ocr/fonts
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1
tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1
tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1
TEMP_TESS=$(mktemp -d)
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
cp "$TEMP_TESS/package/dist/worker.min.js" /var/www/wasm/ocr/worker.min.js
rm -rf "$TEMP_TESS"
tar xzf tesseract.js-core-7.0.0.tgz -C /var/www/wasm/ocr/core --strip-components=1
cp ./tesseract-langdata/*.traineddata.gz /var/www/wasm/ocr/lang-data/
cp ./ocr-fonts/* /var/www/wasm/ocr/fonts/
# Run BentoPDF
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
```
برای `--ocr-languages` از کدهایی استفاده کنید که `bash scripts/prepare-airgap.sh --list-ocr-languages` چاپ می‌کند، یا با `bash scripts/prepare-airgap.sh --search-ocr-language <term>` بر اساس نام جستجو کنید. وقتی با زیرمجموعهٔ محدودی از زبان‌های OCR می‌سازید، همان کدها را به `VITE_TESSERACT_AVAILABLE_LANGUAGES` بدهید تا برنامه فقط زبان‌های همراه را نشان دهد. برای خروجی OCR کاملاً آفلاین، پوشهٔ همراه `ocr-fonts/` را نیز میزبانی کنید و `VITE_OCR_FONT_BASE_URL` را به آن اشاره دهید.
یک متغیر را با رشتهٔ خالی تنظیم کنید تا آن ماژول غیرفعال شود (کاربران باید آن را دستی از طریق «تنظیمات پیشرفته» پیکربندی کنند).
## پورت سفارشی
به‌طور پیش‌فرض، BentoPDF درون کانتینر روی پورت `8080` گوش می‌دهد. برای تغییر آن، متغیر محیطی `PORT` را در زمان اجرا تنظیم کنید:
```bash
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
```
| متغیر | توضیح | پیش‌فرض |
| ------ | -------------------------- | ------- |
| `PORT` | پورت شنود Nginx در کانتینر | `8080` |
این با هر دو Dockerfile استاندارد و nonroot کار می‌کند.
## شناسهٔ کاربر سفارشی (PUID/PGID)
برای محیط‌هایی که اجرا با یک کاربر غیر root مشخص را لازم دارند (دستگاه‌های NAS، Kubernetes با security context، سیاست‌های سازمانی)، BentoPDF یک Dockerfile جداگانه با پشتیبانی PUID/PGID به سبک LSIO ارائه می‌دهد.
### ساخت و اجرا
```bash
# Build the non-root image
docker build -f Dockerfile.nonroot -t bentopdf-nonroot .
# Run with custom UID/GID
docker run -d \
--name bentopdf \
-p 3000:8080 \
-e PUID=1000 \
-e PGID=1000 \
--restart unless-stopped \
bentopdf-nonroot
```
### متغیرهای محیطی
| متغیر | توضیح | پیش‌فرض |
| -------------- | ---------------------- | ------- |
| `PUID` | شناسهٔ کاربر برای اجرا | `1000` |
| `PGID` | شناسهٔ گروه برای اجرا | `1000` |
| `PORT` | پورت شنود Nginx | `8080` |
| `DISABLE_IPV6` | غیرفعال کردن شنود IPv6 | `false` |
### Docker Compose
```yaml
services:
bentopdf:
build:
context: .
dockerfile: Dockerfile.nonroot
container_name: bentopdf
ports:
- '3000:8080'
environment:
- PUID=1000
- PGID=1000
restart: unless-stopped
```
### نحوهٔ کار
کانتینر با root شروع می‌شود، کاربری با PUID/PGID مشخص‌شده می‌سازد، مالکیت همهٔ پوشه‌های قابل‌نوشتن را تنظیم می‌کند و سپس با `su-exec` از دسترسی‌ها صرف‌نظر می‌کند. فرایند nginx کاملاً با کاربر مشخص‌شدهٔ شما اجرا می‌شود.
> [!NOTE]
> `Dockerfile` استاندارد از `nginx-unprivileged` (UID 101) استفاده می‌کند و برای بیشتر استقرارها توصیه می‌شود. فقط وقتی به UID/GID مشخصی نیاز دارید از `Dockerfile.nonroot` استفاده کنید.
> [!WARNING]
> PUID/PGID نمی‌تواند `0` (root) باشد. entrypoint ورودی‌ها را اعتبارسنجی می‌کند و برای مقادیر نامعتبر با خطا خارج می‌شود.
## با Traefik (پراکسی معکوس)
```yaml
services:
traefik:
image: traefik:v2.10
command:
- '--providers.docker=true'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--certificatesresolvers.letsencrypt.acme.email=you@example.com'
- '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
ports:
- '80:80'
- '443:443'
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.bentopdf.rule=Host(`pdf.example.com`)'
- 'traefik.http.routers.bentopdf.entrypoints=websecure'
- 'traefik.http.routers.bentopdf.tls.certresolver=letsencrypt'
- 'traefik.http.services.bentopdf.loadbalancer.server.port=8080'
# Required headers for SharedArrayBuffer (LibreOffice WASM)
- 'traefik.http.routers.bentopdf.middlewares=bentopdf-headers'
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Opener-Policy=same-origin'
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Embedder-Policy=require-corp'
restart: unless-stopped
```
## با Caddy (پراکسی معکوس)
```yaml
services:
caddy:
image: caddy:2
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
restart: unless-stopped
volumes:
caddy_data:
```
Caddyfile:
```
pdf.example.com {
reverse_proxy bentopdf:8080
header Cross-Origin-Opener-Policy "same-origin"
header Cross-Origin-Embedder-Policy "require-corp"
}
```
## محدودیت منابع
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
```
## Podman Quadlet (یکپارچگی با Systemd)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) به شما امکان می‌دهد کانتینرهای Podman را به‌صورت سرویس‌های systemd اجرا کنید. این برای استقرارهای production روی سیستم‌های Linux ایدئال است.
### راه‌اندازی پایهٔ Quadlet
یک فایل واحد کانتینر در `~/.config/containers/systemd/bentopdf.container` (کاربر) یا `/etc/containers/systemd/bentopdf.container` (سیستم) بسازید:
```ini
[Unit]
Description=BentoPDF - Privacy-first PDF toolkit
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### فعال‌سازی و شروع
```bash
# Reload systemd to detect new unit
systemctl --user daemon-reload
# Start the service
systemctl --user start bentopdf
# Enable on boot
systemctl --user enable bentopdf
# Check status
systemctl --user status bentopdf
# View logs
journalctl --user -u bentopdf -f
```
> [!TIP]
> برای استقرار در سطح سیستم، از `systemctl` بدون پرچم `--user` استفاده کنید و فایل را در `/etc/containers/systemd/` قرار دهید.
### Quadlet با بررسی سلامت
```ini
[Unit]
Description=BentoPDF with health monitoring
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
HealthCmd=wget --spider -q http://localhost:8080 || exit 1
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### به‌روزرسانی خودکار با Quadlet
Podman می‌تواند هنگام در دسترس بودن ایمیج‌های جدید، کانتینرها را به‌طور خودکار به‌روز کند:
```bash
# Enable auto-update timer
systemctl --user enable --now podman-auto-update.timer
# Check for updates manually
podman auto-update
# Dry run (check without updating)
podman auto-update --dry-run
```
### پیکربندی شبکهٔ Quadlet
برای پیکربندی شبکهٔ سفارشی، یک فایل شبکه به نام `bentopdf.network` بسازید:
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
سپس در فایل کانتینر خود به آن ارجاع دهید:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## به‌روزرسانی
```bash
# Pull latest image
docker compose pull
# Recreate container
docker compose up -d
```
+267
View File
@@ -0,0 +1,267 @@
# استقرار روی Hostinger
[Hostinger](https://hostinger.com) یک ارائه‌دهندهٔ محبوب میزبانی اشتراکی است. این راهنما استقرار BentoPDF روی میزبانی اشتراکی Hostinger را پوشش می‌دهد.
## پیش‌نیازها
- طرح میزبانی Hostinger با دسترسی به File Manager
- Node.js نصب‌شده به‌صورت محلی برای ساخت
## گام ۱: ساخت پروژه
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
فایل‌های ساخته‌شده در پوشهٔ `dist` قرار می‌گیرند.
## گام ۲: بارگذاری روی Hostinger
### استقرار در دامنهٔ اصلی
1. وارد حساب Hostinger خود شوید
2. به **File Manager** ← **public_html** بروید
3. همهٔ فایل‌های موجود را **حذف** کنید (در صورت نیاز پشتیبان بگیرید)
4. همهٔ محتوای پوشهٔ محلی `dist` را در `public_html` **بارگذاری** کنید
### استقرار در زیرپوشه
اگر در یک زیرپوشه مستقر می‌کنید (مثلاً `yourdomain.com/pdf-tools/`):
1. با base URL درست بسازید:
```bash
BASE_URL=/pdf-tools/ npm run build
```
2. پوشه را در Hostinger بسازید:
- به **File Manager** ← **public_html** بروید
- یک پوشهٔ جدید بسازید: `pdf-tools`
3. همهٔ محتوای `dist` را در `public_html/pdf-tools/` بارگذاری کنید
## گام ۳: ساخت فایل .htaccess
یک فایل `.htaccess` در ریشهٔ پوشهٔ استقرار خود (`public_html` یا `public_html/pdf-tools/`) بسازید:
::: warning مهم
برای استقرار در زیرپوشه، `RewriteBase /` را به `RewriteBase /pdf-tools/` (یا نام پوشهٔ خود) تغییر دهید.
:::
```apache
RewriteEngine On
RewriteBase /
# ============================================
# 1. SECURITY HEADERS (CRITICAL FOR WASM)
# ============================================
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# REQUIRED for soffice.js (SharedArrayBuffer)
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
</IfModule>
# ============================================
# 2. BROWSER CACHING
# ============================================
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType application/gzip "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
# ============================================
# 3. COMPRESSION (STANDARD)
# ============================================
# Prevent server from double-compressing files
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
SetEnvIfNoCase Request_URI "\.br$" no-gzip
SetEnvIfNoCase Request_URI "\.wasm$" no-gzip
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE font/woff
AddOutputFilterByType DEFLATE font/ttf
</IfModule>
# ============================================
# 4. MIME TYPES & SPECIAL FILE HANDLING
# ============================================
AddType application/javascript .js .mjs
AddType application/wasm .wasm
AddType font/woff2 .woff2
AddType font/woff .woff
AddType image/webp .webp
# Handle soffice.wasm.gz correctly
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
Header set Cross-Origin-Resource-Policy "cross-origin"
Header append Vary Accept-Encoding
</FilesMatch>
# Handle data.gz
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
Header append Vary Accept-Encoding
</FilesMatch>
# ============================================
# 5. REDIRECTS & ROUTING
# ============================================
# Canonical WWW (update domain as needed)
RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC]
RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301]
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Remove trailing slash (except for language root directories)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/$
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# 5.1. LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root (for SEO consistency)
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# 5.5. DOCS ROUTING (VitePress)
# ============================================
RewriteCond %{REQUEST_URI} ^/docs
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html [L]
# ============================================
# 6. ADD .HTML EXTENSION IF FILE EXISTS (ROOT LEVEL ONLY)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
# ============================================
# 7. ERROR PAGES
# ============================================
ErrorDocument 404 /404.html
```
## نمونهٔ .htaccess برای زیرپوشه
برای `yourdomain.com/pdf-tools/`، این خطوط را به‌روز کنید:
```apache
RewriteBase /pdf-tools/
# ... (same content as above, but update paths) ...
# Language prefix root
RewriteCond %{DOCUMENT_ROOT}/pdf-tools/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf-tools/$1/index.html [L]
# Language prefix with path
RewriteCond %{DOCUMENT_ROOT}/pdf-tools/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf-tools/$1/$2.html [L]
# Root level .html extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /pdf-tools/404.html
```
## عیب‌یابی
### فایل‌های WASM بارگذاری نمی‌شوند
مطمئن شوید انواع MIME در `.htaccess` درست تنظیم شده‌اند:
```apache
AddType application/wasm .wasm
```
### ابزارهای LibreOffice کار نمی‌کنند
سرآیندهای امنیتی برای SharedArrayBuffer حیاتی هستند:
```apache
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
اگر سرآیندها اعمال نمی‌شوند، با پشتیبانی Hostinger تماس بگیرید تا `mod_headers` را فعال کنند.
### خطاهای 404 هنگام بازخوانی صفحه
مطمئن شوید قاعدهٔ پسوند `.html` و مسیرهای زبان درست پیکربندی شده‌اند. BentoPDF از فایل‌های HTML ایستا استفاده می‌کند، نه مسیریابی SPA:
```apache
# Language routes serve actual files from language directories
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# Root level pages
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
```
### محدودیت‌های بارگذاری فایل
Hostinger ممکن است محدودیت اندازهٔ فایل داشته باشد. اگر بارگذاری گروهی شکست خورد، فایل‌های WASM را جداگانه بارگذاری کنید.
+538
View File
@@ -0,0 +1,538 @@
# راهنمای میزبانی شخصی
BentoPDF را می‌توان روی زیرساخت خودتان میزبانی کرد. این راهنما گزینه‌های مختلف استقرار را پوشش می‌دهد.
## شروع سریع با Docker / Podman
سریع‌ترین راه برای میزبانی شخصی BentoPDF:
> [!TIP]
> BentoPDF در دو نسخهٔ ساخت عرضه می‌شود:
>
> - **نسخهٔ میزبانی شخصی**: `ghcr.io/alam00000/bentopdf-simple:latest`. همهٔ ابزارهای PDF، **بدون** بخش‌های تبلیغاتی (بدون hero، پرسش‌های متداول، نظرات کاربران، پاورقی). از این نسخه برای استقرارهای داخلی، تیمی یا سازمانی استفاده کنید. این نسخه یک نسخهٔ «سبک» با امکانات کمتر **نیست**.
> - **نسخهٔ تجاری**: `ghcr.io/alam00000/bentopdf:latest`. سایت کامل تبلیغاتی که خود bentopdf.com و دارندگان مجوز تجاری برای استقرارهای عمومی از آن استفاده می‌کنند.
>
> اگر مطمئن نیستید: نسخهٔ میزبانی شخصی را دریافت کنید.
> [!IMPORTANT]
> تبدیل فایل‌های Office به `SharedArrayBuffer` نیاز دارد، یعنی برنامه باید هم جداسازی‌شده بین مبدأها (cross-origin isolated) باشد و هم از یک بستر امن ارائه شود. ایمیج رسمی از قبل هدرهای لازم COOP/COEP را ارسال می‌کند، اما مرورگرها همچنان `SharedArrayBuffer` را روی مبداهای HTTP ساده در شبکهٔ محلی مانند `http://192.168.x.x` غیرفعال می‌کنند.
>
> از `http://localhost` فقط برای آزمایش روی همان دستگاه استفاده کنید. اگر کاربران از طریق IP یا نام میزبان شبکهٔ محلی به BentoPDF دسترسی دارند، آن را با HTTPS ارائه کنید.
```bash
# Docker
docker run -d -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Podman
podman run -d -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
```
یا با Docker Compose / Podman Compose:
```yaml
# docker-compose.yml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
ports:
- '3000:8080'
restart: unless-stopped
```
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Podman Quadlet (Linux Systemd)
BentoPDF را به‌عنوان یک سرویس systemd اجرا کنید. فایل `~/.config/containers/systemd/bentopdf.container` را بسازید:
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now bentopdf
```
برای مستندات کامل Quadlet، [راهنمای استقرار با Docker](/fa/self-hosting/docker) را ببینید.
## ساخت از کد منبع
```bash
# Clone and build
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
# The built files are in the `dist` folder
```
## گزینه‌های پیکربندی
### نسخهٔ میزبانی شخصی (Simple Mode)
نسخهٔ میزبانی شخصی (ایمیج `bentopdf-simple` که Simple Mode هم نامیده می‌شود) از نظر عملکرد **کاملاً یکسان** با نسخهٔ تجاری است؛ همهٔ ابزارهای PDF وجود دارند و یکسان رفتار می‌کنند. این نسخه فقط بخش‌های تبلیغاتی را پنهان می‌کند که تنها روی خود bentopdf.com یا یک استقرار عمومی تجاری معنا دارند. **این نسخه یک نسخهٔ «سبک» یا با امکانات کمتر نیست.**
**آنچه نسخهٔ میزبانی شخصی پنهان می‌کند** (فقط ظاهری؛ هیچ قابلیت PDF حذف نمی‌شود):
- نوار پیمایش، بخش hero، بخش ویژگی‌ها، پرسش‌های متداول، نظرات کاربران، پاورقی
- عنوان صفحه را به «ابزارهای PDF» تغییر می‌دهد
**آنچه نسخهٔ میزبانی شخصی نگه می‌دارد** (هر چیزی که واقعاً کار PDF انجام می‌دهد):
- همهٔ ابزارهای PDF (ادغام، تقسیم، ویرایش، امضا، OCR، تبدیل Office و غیره)
- پشتیبانی از برندسازی سفارشی، همهٔ پیکربندی‌های زمان ساخت و زمان اجرا
نسخهٔ تجاری (`ghcr.io/alam00000/bentopdf:latest`) همان چیزی است که خود bentopdf.com را اجرا می‌کند و دارندگان مجوز تجاری برای استقرارهای عمومی از آن استفاده می‌کنند؛ این نسخه hero، پرسش‌های متداول، نظرات کاربران و پاورقی را اضافه می‌کند که برای یک ابزار داخلی معنایی ندارند.
اگر BentoPDF را برای تیم یا سازمان خود یا به‌عنوان یک ابزار داخلی میزبانی می‌کنید، نسخهٔ میزبانی شخصی را دریافت کنید:
```bash
# Use the pre-built image (recommended)
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Or build it yourself
SIMPLE_MODE=true npm run build
```
برای جزئیات کامل [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) را ببینید.
### نسخهٔ تجاری (استقرارهای عمومی با برند خودتان)
نسخهٔ تجاری (ایمیج `bentopdf`، بدون پسوند `-simple`) همان چیزی است که خود bentopdf.com را اجرا می‌کند. این نسخه علاوه بر همهٔ ابزارهای PDF، سایت کامل تبلیغاتی (hero، ویژگی‌ها، پرسش‌های متداول، نظرات کاربران، پاورقی) را دارد. وقتی می‌خواهید **BentoPDF را به‌عنوان یک سرویس عمومی PDF با برند خودتان مستقر کنید** از این نسخه استفاده کنید، برای مثال:
- BentoPDF را به‌عنوان یک SaaS میزبانی‌شده برای کاربران نهایی روی دامنهٔ خودتان اجرا می‌کنید
- تجربهٔ صفحهٔ فرود (بخش‌های تبلیغاتی + ابزارها) را می‌خواهید، نه فقط ابزارهای خالی
- دارندهٔ مجوز تجاری هستید و BentoPDF را در یک محصول تجاری جاسازی می‌کنید
**اجرای بدون تغییر** (با برند BentoPDF؛ برای پیش‌نمایش نسخه مفید است):
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**ساخت با برند خودتان** (گردش کار معمول تجاری):
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. All rights reserved." \
-t acmepdf .
docker run -p 3000:8080 acmepdf
```
همهٔ گزینه‌های دیگر زمان ساخت (`BASE_URL`، `VITE_DEFAULT_LANGUAGE`، `DISABLE_TOOLS`، بازنویسی URLهای WASM و غیره) دقیقاً مانند نسخهٔ میزبانی شخصی کار می‌کنند.
::: warning مجوز
اجرای نسخهٔ تجاری تحت هر یک از دو گزینهٔ مجوز BentoPDF مجاز است:
- **AGPL-3.0** (رایگان): مجاز است اگر استقرار شما کد منبع کامل خود را تحت AGPL منتشر کند، از جمله هرگونه تغییر برندسازی و منطق تجاری پیرامون آن.
- **مجوز تجاری** (۷۹ دلار مادام‌العمر): برای استقرارهای بسته یا اختصاصی لازم است که فورک برندسازی یا کد تجاری خود را متن‌باز نمی‌کنید.
برای مقایسهٔ کامل [صفحهٔ مجوزها](https://www.bentopdf.com/licensing) را ببینید. ماژول‌های WASM با مجوز AGPL (PyMuPDF، Ghostscript، CoherentPDF) در زمان اجرا از یک CDN بارگذاری می‌شوند، بنابراین وارد ایمیج شما نمی‌شوند و وضعیت مجوز شما را تغییر نمی‌دهند.
:::
### URL پایه
استقرار در یک زیرپوشه:
```bash
BASE_URL=/pdf-tools/ npm run build
```
### برندسازی سفارشی
لوگو، نام و متن پاورقی پیش‌فرض BentoPDF را در زمان ساخت با موارد خودتان جایگزین کنید:
| متغیر | توضیح | پیش‌فرض |
| ------------------ | ------------------------------------------ | --------------------------------------- |
| `VITE_BRAND_NAME` | نام برند نمایش‌داده‌شده در سرصفحه و پاورقی | `BentoPDF` |
| `VITE_BRAND_LOGO` | مسیر لوگو نسبت به `public/` | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | متن سفارشی پاورقی/حق نشر | `© 2026 BentoPDF. All rights reserved.` |
```bash
# Place your logo in public/, then build
VITE_BRAND_NAME="AcmePDF" \
VITE_BRAND_LOGO="images/acme-logo.svg" \
VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
npm run build
```
یا از طریق Docker:
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
-t acmepdf .
```
برندسازی هم در حالت کامل و هم در Simple Mode کار می‌کند و می‌توان آن را با همهٔ گزینه‌های دیگر زمان ساخت (`BASE_URL`، `SIMPLE_MODE`، `VITE_DEFAULT_LANGUAGE`) ترکیب کرد.
### غیرفعال کردن ابزارهای خاص
ابزارهای جداگانه را برای انطباق یا امنیت پنهان کنید. ابزارهای غیرفعال از صفحهٔ اصلی، جستجو، میان‌برها، سازندهٔ گردش کار و دسترسی مستقیم با URL حذف می‌شوند.
شناسهٔ ابزارها همان URL صفحه بدون `.html` است؛ هر ابزاری را باز کنید و به URL نگاه کنید (مثلاً `edit-pdf`، `sign-pdf`، `encrypt-pdf`).
**زمان ساخت** (درون بسته قرار می‌گیرد):
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**زمان اجرا** (بدون نیاز به ساخت دوباره؛ یک `config.json` را mount کنید):
```json
{
"disabledTools": ["edit-pdf", "sign-pdf"]
}
```
```bash
docker run -d -p 3000:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
ghcr.io/alam00000/bentopdf-simple:latest
```
هر دو روش را می‌توان ترکیب کرد؛ فهرست‌ها با هم ادغام می‌شوند.
همچنین می‌توانید با افزودن `editorDisabledCategories` به `config.json`، قابلیت‌های خاصی را درون ویرایشگر PDF (مثلاً سانسور) غیرفعال کنید، بدون اینکه کل ابزار غیرفعال شود. برای فهرست کامل دسته‌ها [راهنمای Docker](/fa/self-hosting/docker#غیرفعال-کردن-قابلیت‌های-ویرایشگر) را ببینید.
## راهنماهای استقرار
پلتفرم خود را انتخاب کنید:
- [Vercel](/fa/self-hosting/vercel)
- [Netlify](/fa/self-hosting/netlify)
- [Cloudflare Pages](/fa/self-hosting/cloudflare)
- [AWS S3 + CloudFront](/fa/self-hosting/aws)
- [Hostinger](/fa/self-hosting/hostinger)
- [Nginx](/fa/self-hosting/nginx)
- [Apache](/fa/self-hosting/apache)
- [Docker](/fa/self-hosting/docker)
- [Kubernetes](/fa/self-hosting/kubernetes)
- [پراکسی CORS](/fa/self-hosting/cors-proxy) - برای امضاهای دیجیتال لازم است
## مشکلات رایج
### تبدیل Word / ODT / Excel / PowerPoint به PDF متوقف می‌شود (SharedArrayBuffer در دسترس نیست)
**نشانه**: تبدیل‌های اسناد مبتنی بر LibreOffice (Word، ODT، Excel، PowerPoint به PDF) در حدود ۵۵٪ متوقف می‌شوند یا شروع نمی‌شوند. کنسول مرورگر ممکن است این را نشان دهد:
> `ReferenceError: SharedArrayBuffer is not defined`
…یا `window.crossOriginIsolated` مقدار `false` را گزارش می‌کند، یا کامپایل WASM خطای `expected magic word 00 61 73 6d, found 1f 8b 08 08` را می‌دهد.
**علت**: LibreOffice WASM به `SharedArrayBuffer` نیاز دارد که مرورگر فقط وقتی آن را فعال می‌کند که صفحه **جداسازی‌شده بین مبدأها (cross-origin isolated)** باشد و از یک **بستر امن** ارائه شود. یعنی دو شرط باید برقرار باشد:
1. هر پاسخ هر دو هدر را داشته باشد:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
2. صفحه از `https://...` یا `http://localhost` ارائه شود. HTTP ساده روی IP شبکهٔ محلی (مثلاً `http://192.168.x.x`) امن به حساب نمی‌آید و مرورگرها `SharedArrayBuffer` را در آنجا غیرفعال می‌کنند.
ناسازگاری `00 61 73 6d / 1f 8b 08 08` یک نشانهٔ فرعی جداگانه است: فایل‌های از پیش فشرده‌شدهٔ `.wasm.gz` / `.data.gz` هدر پاسخ `Content-Encoding: gzip` را ندارند، بنابراین مرورگر به‌جای WASM از حالت فشرده خارج‌شده، بایت‌های خام gzip را دریافت می‌کند.
**راه‌حل**: برای پیکربندی دقیق، راهنمای استقرار پلتفرم خود را ببینید:
- [Nginx ←](/fa/self-hosting/nginx#تبدیل-word-odt-excel-به-pdf-کار-نمی‌کند)
- [Apache ←](/fa/self-hosting/apache#تبدیل-word-odt-excel-به-pdf-کار-نمی‌کند)
- [AWS S3 + CloudFront ←](/fa/self-hosting/aws#گام-۳b-سیاست-سرآیندهای-پاسخ-لازم-برای-libreoffice-wasm)
- [Cloudflare Pages ←](/fa/self-hosting/cloudflare#فایل-پیکربندی) (فایل `_headers`)
- [Netlify ←](/fa/self-hosting/netlify#تبدیل-word-odt-excel-به-pdf-در-۵۵٪-متوقف-می‌شود) (`netlify.toml`)
- [Vercel ←](/fa/self-hosting/vercel#تبدیل-word-odt-excel-به-pdf-کار-نمی‌کند) (`vercel.json`)
- [Hostinger ←](/fa/self-hosting/hostinger#ابزارهای-libreoffice-کار-نمی‌کنند) (`.htaccess`)
- [Kubernetes ←](/fa/self-hosting/kubernetes#اطمینان-از-کارکرد-هدرهای-sharedarraybuffer-ingress-gateway)
- **Docker**: به‌طور خودکار توسط پیکربندی nginx همراه انجام می‌شود؛ نیازی به اقدام نیست.
**بررسی**: Console در DevTools را روی هر صفحهٔ BentoPDF باز کنید و اجرا کنید:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
اگر صفحه HTTPS یا `http://localhost` باشد و هر دو هدر COEP/COOP روی همهٔ پاسخ‌ها وجود داشته باشند، هر دو بررسی موفق می‌شوند. اگر روی `http://192.168.x.x` یا مبدأ HTTP دیگری غیر از loopback هستید، آن را با HTTPS ارائه کنید؛ هیچ راه‌حلی فقط با هدر وجود ندارد.
### فایل‌های `.mjs` با `application/octet-stream` ارائه می‌شوند
**نشانه**: امضای PDF / پرکنندهٔ فرم / برخی ابزارهای دیگر نمایشگر خالی نشان می‌دهند یا بارگذاری نمی‌شوند. کنسول مرورگر گزارش می‌دهد:
> `Failed to load module script: The server responded with a non-JavaScript MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.`
**علت**: وب‌سرور یا پراکسی معکوس شما نگاشت نوع MIME برای فایل‌های `.mjs` ندارد (نمایشگر PDF همراه، ماژول‌های ES را با این پسوند ارائه می‌دهد). بسیاری از پیکربندی‌های پیش‌فرض سرور برای پسوندهای ناشناخته از `application/octet-stream` استفاده می‌کنند که مرورگرها از اجرای آن به‌عنوان ماژول ES خودداری می‌کنند.
**راه‌حل**: برای قطعه‌کد دقیق، راهنمای استقرار پلتفرم خود را ببینید:
- [Nginx ←](/fa/self-hosting/nginx#امضای-pdf-یا-پرکنندهٔ-فرم-نمایشگر-خالی-نشان-می‌دهد-خطای-mime-برای-mjs)
- [Apache ←](/fa/self-hosting/apache#امضای-pdf-یا-پرکنندهٔ-فرم-نمایشگر-خالی-نشان-می‌دهد-خطای-mime-برای-mjs)
- [AWS S3 + CloudFront ←](/fa/self-hosting/aws#گام-۲-ساخت-و-بارگذاری) (`aws s3 cp ... --include "*.mjs"` را ببینید)
- [Kubernetes ←](/fa/self-hosting/kubernetes#خطاهای-نوع-mime-برای-mjs-خالی-بودن-iframe-امضای-pdf-پرکنندهٔ-فرم)
- **Docker، Vercel، Netlify، Cloudflare Pages، Hostinger**: به‌طور خودکار انجام می‌شود؛ نیازی به اقدام نیست.
**بررسی**: DevTools ← زبانهٔ Network را باز کنید، درخواست ناموفق `.mjs` را پیدا کنید و هدر پاسخ `Content-Type` را بررسی کنید. باید `application/javascript` باشد. اگر هنوز `application/octet-stream` است، ممکن است یک پراکسی معکوس بیرونی یا CDN نوع را دوباره تشخیص دهد؛ هر لایه از زنجیرهٔ ارائهٔ خود را بررسی کنید.
## پیکربندی WASM (اجزای AGPL)
BentoPDF کتابخانه‌های پردازشی با مجوز AGPL را در کد منبع خود **همراه نمی‌کند**، اما **URLهای CDN را از پیش پیکربندی می‌کند** تا همهٔ قابلیت‌ها بی‌درنگ کار کنند؛ نیازی به راه‌اندازی دستی نیست.
::: tip بدون پیکربندی به‌طور پیش‌فرض
از نسخهٔ v2.0.0، ماژول‌های WASM از طریق متغیرهای محیطی برای بارگذاری از CDN jsDelivr از پیش پیکربندی شده‌اند. همهٔ قابلیت‌های پیشرفته بدون هیچ پیکربندی از سوی کاربر بی‌درنگ کار می‌کنند.
:::
| جزء | مجوز | قابلیت‌ها |
| --------------- | -------- | ----------------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | تبدیل EPUB/MOBI/FB2/XPS، استخراج تصویر، استخراج جدول |
| **Ghostscript** | AGPL-3.0 | تبدیل PDF/A، فشرده‌سازی، صاف‌کردن کجی، تبدیل به تصویر |
| **CoherentPDF** | AGPL-3.0 | فهرست مطالب، پیوست‌ها، ادغام PDF با نشانک‌ها |
### متغیرهای محیطی پیش‌فرض
این‌ها در `.env.production` تنظیم شده‌اند و درون نسخهٔ ساخت قرار می‌گیرند:
```bash
VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/
VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/
VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/
VITE_TESSERACT_WORKER_URL=
VITE_TESSERACT_CORE_URL=
VITE_TESSERACT_LANG_URL=
VITE_TESSERACT_AVAILABLE_LANGUAGES=
VITE_OCR_FONT_BASE_URL=
```
### بازنویسی URLهای WASM
می‌توانید پیش‌فرض‌ها را در زمان ساخت برای استقرارهای سفارشی بازنویسی کنید:
```bash
# Via Docker build args
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://your-server.com/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://your-server.com/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://your-server.com/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://your-server.com/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://your-server.com/ocr/lang-data \
--build-arg VITE_TESSERACT_AVAILABLE_LANGUAGES=eng,deu \
--build-arg VITE_OCR_FONT_BASE_URL=https://your-server.com/ocr/fonts \
-t bentopdf .
# Or via .env.production before building from source
VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ npm run build
```
برای غیرفعال کردن کامل یک ماژول (نیاز به پیکربندی دستی کاربر از طریق تنظیمات پیشرفته)، متغیر آن را روی رشتهٔ خالی تنظیم کنید.
برای OCR، یا همهٔ متغیرهای `VITE_TESSERACT_*` را خالی بگذارید و از دارایی‌های آنلاین پیش‌فرض استفاده کنید، یا URLهای worker/core/lang را با هم برای OCR میزبانی‌شده یا آفلاین تنظیم کنید. اگر فقط زبان‌های OCR خاصی را همراه می‌کنید، `VITE_TESSERACT_AVAILABLE_LANGUAGES` را نیز روی همان کدهای جداشده با ویرگول تنظیم کنید تا رابط کاربری فقط زبان‌های نصب‌شده را پیشنهاد دهد و انتخاب‌های پشتیبانی‌نشده با خطای توصیفی شکست بخورند. برای خروجی PDF قابل جستجوی کاملاً آفلاین، `VITE_OCR_FONT_BASE_URL` را نیز روی پوشهٔ داخلی که قلم‌های OCR همراه را ارائه می‌دهد تنظیم کنید.
کاربران همچنین می‌توانند هر زمان این پیش‌فرض‌ها را از طریق **تنظیمات پیشرفته** در رابط کاربری بازنویسی کنند؛ بازنویسی‌های کاربر که در مرورگر ذخیره می‌شوند بر پیش‌فرض‌های محیطی اولویت دارند.
### استقرار ایزوله / آفلاین
برای شبکه‌های بدون دسترسی به اینترنت (دولتی، درمانی، مالی و غیره). URLهای WASM در **زمان ساخت** درون JavaScript قرار می‌گیرند؛ فایل‌های واقعی WASM در زمان اجرا توسط **مرورگر کاربر** دانلود می‌شوند. بنابراین باید همه چیز را روی دستگاهی با اینترنت آماده کنید و سپس به شبکهٔ ایزوله منتقل کنید.
#### اسکریپت خودکار (پیشنهادی)
اسکریپت همراه `prepare-airgap.sh` کل فرایند را خودکار می‌کند: دانلود بسته‌های WASM، ساخت ایمیج Docker و تولید یک بستهٔ مستقل همراه با اسکریپت راه‌اندازی.
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
# Show supported OCR language codes (for --ocr-languages)
bash scripts/prepare-airgap.sh --list-ocr-languages
# Search OCR language codes by name or abbreviation
bash scripts/prepare-airgap.sh --search-ocr-language german
# Interactive mode — prompts for all options
bash scripts/prepare-airgap.sh
# Or fully automated
bash scripts/prepare-airgap.sh --wasm-base-url https://internal.example.com/wasm
```
این یک پوشهٔ بسته تولید می‌کند:
```
bentopdf-airgap-bundle/
bentopdf.tar # Docker image
*.tgz # WASM packages (PyMuPDF, Ghostscript, CoherentPDF, Tesseract)
tesseract-langdata/ # OCR traineddata files
ocr-fonts/ # OCR text-layer font files
setup.sh # Setup script for the air-gapped side
README.md # Instructions
```
بسته را از طریق USB، مخزن داخلی آرتیفکت یا روش تأییدشده به شبکهٔ ایزوله منتقل کنید. سپس اسکریپت راه‌اندازی همراه را اجرا کنید:
```bash
cd bentopdf-airgap-bundle
bash setup.sh
```
اسکریپت راه‌اندازی ایمیج Docker را بارگذاری می‌کند، فایل‌های WASM را استخراج می‌کند و در صورت تمایل کانتینر را اجرا می‌کند.
**گزینه‌های اسکریپت:**
| پرچم | توضیح | پیش‌فرض |
| ------------------------------ | ---------------------------------------------------------------- | -------------------------------------- |
| `--wasm-base-url <url>` | محل میزبانی داخلی WASMها | _(الزامی، در صورت نبود پرسیده می‌شود)_ |
| `--image-name <name>` | برچسب ایمیج Docker | `bentopdf` |
| `--output-dir <path>` | پوشهٔ خروجی بسته | `./bentopdf-airgap-bundle` |
| `--simple-mode` | فعال کردن Simple Mode | خاموش |
| `--base-url <path>` | URL پایهٔ زیرپوشه (مثلاً `/pdf/`) | `/` |
| `--language <code>` | زبان پیش‌فرض رابط کاربری (مثلاً `fr`، `de`) | _(هیچ)_ |
| `--brand-name <name>` | نام برند سفارشی | _(هیچ)_ |
| `--brand-logo <path>` | مسیر لوگو نسبت به `public/` | _(هیچ)_ |
| `--footer-text <text>` | متن سفارشی پاورقی | _(هیچ)_ |
| `--ocr-languages <list>` | زبان‌های OCR برای همراه کردن، جداشده با ویرگول | `eng` |
| `--list-ocr-languages` | چاپ کدها و نام‌های OCR پشتیبانی‌شده و سپس خروج | خاموش |
| `--search-ocr-language <term>` | جستجوی کدهای OCR بر اساس نام یا اختصار | خاموش |
| `--dockerfile <path>` | Dockerfile مورد استفاده | `Dockerfile` |
| `--skip-docker` | رد شدن از ساخت و خروجی گرفتن Docker | خاموش |
| `--skip-wasm` | رد شدن از دانلود WASM (استفادهٔ دوباره از فایل‌های `.tgz` موجود) | خاموش |
ورودی تعاملی همچنین `list` را برای چاپ فهرست کامل کدهای پشتیبانی‌شدهٔ Tesseract و `search <term>` را برای یافتن موارد منطبق مانند `search german` یا `search chi` می‌پذیرد.
::: warning الزام هم‌مبدأ بودن
فایل‌های WASM باید از **همان مبدأ** برنامهٔ BentoPDF ارائه شوند. Web Workerها از `importScripts()` استفاده می‌کنند که نمی‌تواند اسکریپت‌ها را از مبدأ دیگری بارگذاری کند. برای مثال، اگر BentoPDF روی `https://internal.example.com` اجرا می‌شود، URL پایه برای WASM نیز باید `https://internal.example.com/wasm` باشد.
:::
#### گام‌های دستی
<details>
<summary>اگر ترجیح می‌دهید بدون اسکریپت و به‌صورت دستی انجام دهید</summary>
**گام ۱: دانلود بسته‌های WASM و OCR** (روی دستگاهی با اینترنت)
```bash
npm pack @bentopdf/pymupdf-wasm@0.11.14
npm pack @bentopdf/gs-wasm
npm pack coherentpdf
npm pack tesseract.js@7.0.0
npm pack tesseract.js-core@7.0.0
mkdir -p tesseract-langdata
curl -fsSL https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz -o tesseract-langdata/eng.traineddata.gz
mkdir -p ocr-fonts
curl -fsSL https://raw.githack.com/googlefonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf -o ocr-fonts/NotoSans-Regular.ttf
```
**گام ۲: ساخت ایمیج Docker با URLهای داخلی**
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
-t bentopdf .
```
**گام ۳: خروجی گرفتن از ایمیج Docker**
```bash
docker save bentopdf -o bentopdf.tar
```
**گام ۴: انتقال به شبکهٔ ایزوله**
از طریق USB، مخزن داخلی آرتیفکت یا روش انتقال تأییدشده کپی کنید:
- `bentopdf.tar`: ایمیج Docker
- پنج بستهٔ `.tgz` مربوط به WASM/OCR از گام ۱
- پوشهٔ `tesseract-langdata/` از گام ۱
- پوشهٔ `ocr-fonts/` از گام ۱
**گام ۵: راه‌اندازی درون شبکهٔ ایزوله**
```bash
# Load the Docker image
docker load -i bentopdf.tar
# Extract WASM packages
mkdir -p ./wasm/pymupdf ./wasm/gs ./wasm/cpdf ./wasm/ocr/core ./wasm/ocr/lang-data ./wasm/ocr/fonts
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C ./wasm/pymupdf --strip-components=1
tar xzf bentopdf-gs-wasm-*.tgz -C ./wasm/gs --strip-components=1
tar xzf coherentpdf-*.tgz -C ./wasm/cpdf --strip-components=1
TEMP_TESS=$(mktemp -d)
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
cp "$TEMP_TESS/package/dist/worker.min.js" ./wasm/ocr/worker.min.js
rm -rf "$TEMP_TESS"
tar xzf tesseract.js-core-7.0.0.tgz -C ./wasm/ocr/core --strip-components=1
cp ./tesseract-langdata/*.traineddata.gz ./wasm/ocr/lang-data/
cp ./ocr-fonts/* ./wasm/ocr/fonts/
# Run BentoPDF
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
```
مطمئن شوید فایل‌ها در URLهایی که در گام ۲ پیکربندی کردید در دسترس هستند، از جمله `.../ocr/worker.min.js`، `.../ocr/core`، `.../ocr/lang-data` و `.../ocr/fonts`.
</details>
::: info به‌جای Docker از کد منبع می‌سازید؟
پیش از اجرای `npm run build`، متغیرها را در `.env.production` تنظیم کنید:
```bash
VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/
VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/
VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/
VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js
VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core
VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data
VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts
```
:::
### میزبانی پراکسی WASM خودتان
اگر لازم است فایل‌های WASM با مجوز AGPL را با هدرهای CORS مناسب ارائه کنید، می‌توانید یک پراکسی ساده مستقر کنید. برای نمونه پیاده‌سازی، [راهنمای Cloudflare WASM Proxy](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md) را ببینید.
::: tip چرا جداگانه؟
این جداسازی تضمین می‌کند:
- انطباق حقوقی روشن برای کاربران تجاری
- هستهٔ BentoPDF تحت مجوز دوگانهٔ خود (AGPL-3.0 / تجاری) باقی می‌ماند
- فایل‌های WASM در زمان اجرا بارگذاری می‌شوند، نه اینکه در کد منبع همراه شوند
:::
## نیازمندی‌های سیستم
| نیازمندی | حداقل |
| --------------- | ------------------------------------------- |
| فضای ذخیره‌سازی | حدود ۱۰۰ مگابایت (هسته بدون ماژول‌های AGPL) |
| RAM | ۵۱۲ مگابایت |
| CPU | هر پردازندهٔ مدرن |
::: tip
BentoPDF یک سایت ایستاست؛ به هیچ پایگاه داده یا سرور بک‌اندی نیاز نیست!
:::
+211
View File
@@ -0,0 +1,211 @@
# استقرار با Kubernetes
Kubernetes ممکن است برای یک سایت ایستا زیاده از حد باشد، اما اگر از قبل روی Helm و GitOps استانداردسازی کرده‌اید، گزینهٔ بسیار مناسبی است.
> [!IMPORTANT]
> **هدرهای لازم برای تبدیل فایل‌های Office**
>
> ابزارهای مبتنی بر LibreOffice (تبدیل Word، Excel، PowerPoint) برای پشتیبانی از `SharedArrayBuffer` به این هدرهای HTTP نیاز دارند:
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> ایمیج‌های رسمی nginx در BentoPDF این هدرها را دارند. در Kubernetes، **کنترلرهای Ingress/Gateway نیز پراکسی معکوس هستند**، پس مطمئن شوید این هدرها حفظ می‌شوند (یا آن‌ها را در لبه اضافه کنید).
## پیش‌نیازها
- یک کلاستر Kubernetes
- Helm v3
- یک ایمیج nginx از BentoPDF (مثلاً `ghcr.io/alam00000/bentopdf:<tag>`) که روی **پورت 8080** سرویس می‌دهد
## استقرار با Helm
### نصب از این مخزن (چارت محلی)
```bash
kubectl create namespace bentopdf
helm upgrade --install bentopdf /path/to/bentopdf/chart \
--namespace bentopdf \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
### نصب از GHCR (چارت OCI)
اگر چارت به‌صورت یک آرتیفکت OCI در GHCR منتشر شده باشد:
```bash
export GHCR_USERNAME="<github-org-or-user>"
helm upgrade --install bentopdf oci://ghcr.io/$GHCR_USERNAME/charts/bentopdf \
--namespace bentopdf \
--create-namespace \
--version 0.1.0 \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
## در دسترس قرار دادن
### Port-forward (آزمایش سریع)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress (اختیاری)
Ingress را فعال کنید (نمونه برای nginx-ingress):
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API (اختیاری)
این چارت از `Gateway` و `HTTPRoute` در Gateway API پشتیبانی می‌کند.
نمونه (اپراتور Cloudflare Gateway API):
```yaml
gateway:
enabled: true
name: bento-tunnel
namespace: bentopdf
gatewayClassName: cloudflare
httpRoute:
enabled: true
parentRefs:
- name: bento-tunnel
namespace: bentopdf
sectionName: http
hostnames:
- pdfs.example.com
```
## اطمینان از کارکرد هدرهای SharedArrayBuffer (Ingress/Gateway)
### چه چیزی «باید» اتفاق بیفتد
پیکربندی nginx در BentoPDF هدرهای پاسخ لازم را تنظیم می‌کند. بیشتر کنترلرهای Ingress/Gateway **هدرهای پاسخ بالادستی را بدون تغییر عبور می‌دهند**.
### چه چیزی می‌تواند آن را خراب کند
- سیاست کنترلر/لبه‌ای که هدرهای پاسخ را **بازنویسی** یا **حذف** می‌کند
- یک میان‌افزار «هدرهای امنیتی» که مقادیر متفاوتی برای COOP/COEP تنظیم می‌کند
### نحوهٔ بررسی
این را روی نقطهٔ پایانی عمومی خود اجرا کنید:
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
باید این‌ها را ببینید:
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### اگر کنترلر Ingress شما آن‌ها را حفظ نمی‌کند
هدرها را در لبه اضافه کنید (وابسته به کنترلر). نمونه برای **nginx-ingress**:
```yaml
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
```
## خطاهای نوع MIME برای `.mjs` (خالی بودن iframe امضای PDF / پرکنندهٔ فرم)
اگر کنسول مرورگر برای یک درخواست `.mjs` خطای `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` را نشان می‌دهد، یک کنترلر Ingress/Gateway جلوی nginx در BentoPDF هدر `Content-Type` بالادستی را با `application/octet-stream` جایگزین می‌کند.
nginx در ایمیج BentoPDF از قبل `.mjs` را با `application/javascript` ارائه می‌دهد. راه‌حل این است که جلوی حذف یا جایگزینی آن توسط کنترلر را بگیرید. برای nginx-ingress:
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
location ~* \.mjs$ {
types {} default_type application/javascript;
}
```
یا از فیلتر `ResponseHeaderModifier` در Gateway API برای تنظیم `Content-Type: application/javascript` روی مسیرهای `.mjs` استفاده کنید. با DevTools ← زبانهٔ Network بررسی کنید: درخواست ناموفق `.mjs` اکنون باید `content-type: application/javascript` را نشان دهد.
### اگر از Gateway API استفاده می‌کنید و می‌خواهید هدرها را اجباری اضافه کنید
Gateway API از فیلتر `ResponseHeaderModifier` پشتیبانی می‌کند. می‌توانید آن را در `httpRoute.rules[*].filters` قرار دهید:
```yaml
httpRoute:
enabled: true
hostnames: [pdf.example.com]
parentRefs:
- name: bento-tunnel
namespace: misc
sectionName: http
rules:
- matches:
- path: { type: PathPrefix, value: / }
filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
set:
- name: Cross-Origin-Opener-Policy
value: same-origin
- name: Cross-Origin-Embedder-Policy
value: require-corp
```
پشتیبانی از فیلترهای خاص به کنترلر Gateway شما بستگی دارد؛ اگر فیلتری نادیده گرفته شد، هدرها را به‌جای آن در لایهٔ لبه/کنترلر اضافه کنید.
## غیرفعال کردن ابزارهای خاص
از یک ConfigMap برای غیرفعال کردن ابزارها در زمان اجرا، بدون ساخت دوبارهٔ ایمیج، استفاده کنید:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
آن را در پوشهٔ ارائه‌شده mount کنید:
```yaml
spec:
containers:
- name: bentopdf
volumeMounts:
- name: config
mountPath: /usr/share/nginx/html/config.json
subPath: config.json
readOnly: true
volumes:
- name: config
configMap:
name: bentopdf-config
```
شناسهٔ ابزارها همان URL صفحه بدون `.html` است؛ هر ابزاری را باز کنید و به URL نگاه کنید (مثلاً `edit-pdf`، `merge-pdf`، `compress-pdf`). ابزارهای غیرفعال از صفحهٔ اصلی، جستجو، میان‌برها، سازندهٔ گردش کار و دسترسی مستقیم با URL پنهان می‌شوند. برای فهرست کامل گزینه‌ها [راهنمای Docker](/fa/self-hosting/docker#غیرفعال-کردن-ابزارهای-مشخص) را ببینید.
+139
View File
@@ -0,0 +1,139 @@
# استقرار روی Netlify
[Netlify](https://netlify.com) میزبانی عالی برای سایت‌های ایستا با یک طرح رایگان سخاوتمندانه ارائه می‌دهد.
## استقرار با یک کلیک
[![استقرار روی Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
## استقرار دستی
### گام ۱: اتصال مخزن
1. وارد [Netlify](https://app.netlify.com) شوید
2. روی «Add new site» ← «Import an existing project» کلیک کنید
3. حساب GitHub خود را متصل کنید
4. فورک BentoPDF خود را انتخاب کنید
### گام ۲: پیکربندی تنظیمات ساخت
| تنظیم | مقدار |
| ------------ | --------------- |
| فرمان ساخت | `npm run build` |
| پوشهٔ انتشار | `dist` |
| نسخهٔ Node | 18+ |
### گام ۳: استقرار
روی «Deploy site» کلیک کنید و منتظر ساخت بمانید.
## فایل پیکربندی
فایل `netlify.toml` را در ریشهٔ پروژه بسازید:
```toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "18"
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
[[headers]]
for = "/*"
[headers.values]
Cross-Origin-Embedder-Policy = "require-corp"
Cross-Origin-Opener-Policy = "same-origin"
Cross-Origin-Resource-Policy = "cross-origin"
# Pre-compressed LibreOffice WASM binary - must be served with Content-Encoding
[[headers]]
for = "/libreoffice-wasm/soffice.wasm.gz"
[headers.values]
Content-Type = "application/wasm"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
# Pre-compressed LibreOffice WASM data - must be served with Content-Encoding
[[headers]]
for = "/libreoffice-wasm/soffice.data.gz"
[headers.values]
Content-Type = "application/octet-stream"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
# Cache other WASM files
[[headers]]
for = "*.wasm"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Content-Type = "application/wasm"
# SPA routing
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
```
::: warning مهم
هدرهای `Cross-Origin-Embedder-Policy` و `Cross-Origin-Opener-Policy` برای تبدیل Word/ODT/Excel/PowerPoint به PDF لازم هستند. بدون آن‌ها، `SharedArrayBuffer` در دسترس نیست و موتور LibreOffice WASM راه‌اندازی نمی‌شود.
هدرهای `Content-Encoding: gzip` روی فایل‌های `.wasm.gz` و `.data.gz` به مرورگر می‌گویند آن‌ها را به‌طور خودکار از حالت فشرده خارج کند. بدون این هدرها، مرورگر بایت‌های خام gzip را دریافت می‌کند و کامپایل WASM شکست می‌خورد.
:::
## متغیرهای محیطی
این موارد را در Site settings ← Environment variables تنظیم کنید:
| متغیر | توضیح |
| ----------------------- | -------------------------------------------------------- |
| `SIMPLE_MODE` | برای ساخت حداقلی روی `true` تنظیم کنید |
| `VITE_BRAND_NAME` | نام برند سفارشی (جایگزین «BentoPDF») |
| `VITE_BRAND_LOGO` | مسیر لوگو نسبت به `public/` (مثلاً `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | متن سفارشی پاورقی/حق نشر |
| `VITE_DEFAULT_LANGUAGE` | زبان پیش‌فرض رابط کاربری (مثلاً `fr`، `de`، `es`) |
## دامنهٔ سفارشی
1. به Site settings ← Domain management بروید
2. روی «Add custom domain» کلیک کنید
3. دستورالعمل‌های پیکربندی DNS را دنبال کنید
## رسانه‌های بزرگ
برای فایل‌های بزرگ WASM، فعال کردن [Netlify Large Media](https://docs.netlify.com/large-media/overview/) را در نظر بگیرید:
```bash
netlify lm:setup
git lfs track "*.wasm"
```
## عیب‌یابی
### تبدیل Word/ODT/Excel به PDF در ۵۵٪ متوقف می‌شود
اگر تبدیل اسناد در ۵۵٪ متوقف می‌شود، Console در DevTools را باز کنید و بررسی کنید:
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
اگر `crossOriginIsolated` برابر `false` باشد، هدرهای COEP/COOP از `netlify.toml` شما اعمال نمی‌شوند. مطمئن شوید فایل در ریشهٔ پروژه است و دوباره مستقر کنید.
اگر در کنسول `expected magic word 00 61 73 6d, found 1f 8b 08 08` را می‌بینید، هدرهای `Content-Encoding: gzip` برای فایل‌های `.wasm.gz` وجود ندارد. مطمئن شوید بلوک‌های `[[headers]]` برای `soffice.wasm.gz` و `soffice.data.gz` در `netlify.toml` شما هستند.
### ساخت شکست می‌خورد
سازگاری نسخهٔ Node را بررسی کنید:
```toml
[build.environment]
NODE_VERSION = "20"
```
### بارگذاری اولیهٔ کند
بهینه‌سازی دارایی‌ها را در Site settings ← Build & deploy ← Asset optimization فعال کنید.
+232
View File
@@ -0,0 +1,232 @@
# استقرار با Nginx
BentoPDF را با Nginx روی سرور خودتان میزبانی کنید.
## پیش‌نیازها
- سرور Ubuntu/Debian
- Nginx نصب‌شده
- گواهی SSL (پیشنهادی: Let's Encrypt)
## گام ۱: ساخت پروژه
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
برای سفارشی‌سازی برند، پیش از ساخت متغیرهای محیطی را تنظیم کنید:
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## گام ۲: کپی فایل‌ها
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## گام ۳: پیکربندی Nginx
فایل `/etc/nginx/sites-available/bentopdf` را بسازید:
```nginx
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
root /var/www/bentopdf;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/wasm;
gzip_min_length 1000;
# WASM MIME type
types {
application/wasm wasm;
}
# Required headers for SharedArrayBuffer (LibreOffice WASM)
# These must be on every response - especially HTML pages
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "cross-origin" always;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Pre-compressed LibreOffice WASM binary
location ~* /libreoffice-wasm/soffice\.wasm\.gz$ {
gzip off;
types {} default_type application/wasm;
add_header Content-Encoding gzip;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# Pre-compressed LibreOffice WASM data
location ~* /libreoffice-wasm/soffice\.data\.gz$ {
gzip off;
types {} default_type application/octet-stream;
add_header Content-Encoding gzip;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|wasm)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# SPA routing - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
}
```
## گام ۴: فعال کردن سایت
```bash
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## گام ۵: SSL با Let's Encrypt
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## استقرار در زیرپوشه
برای میزبانی در `/pdf/`:
```nginx
location /pdf/ {
alias /var/www/bentopdf/;
try_files $uri $uri/ /pdf/index.html;
}
```
با این دستور بسازید:
```bash
BASE_URL=/pdf/ npm run build
```
## بهینه‌سازی کارایی
به `nginx.conf` اضافه کنید:
```nginx
http {
# Enable sendfile
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Increase buffer sizes
client_max_body_size 100M;
# Worker connections
worker_connections 2048;
}
```
## عیب‌یابی
### WASM بارگذاری نمی‌شود
مطمئن شوید نوع MIME تنظیم شده است:
```nginx
types {
application/wasm wasm;
}
```
### امضای PDF یا پرکنندهٔ فرم نمایشگر خالی نشان می‌دهد (خطای MIME برای `.mjs`)
اگر کنسول مرورگر این را نشان می‌دهد:
```
Failed to load module script: The server responded with a non-JavaScript
MIME type of "application/octet-stream". Strict MIME type checking is
enforced for module scripts per HTML spec.
```
…و درخواست ناموفق به `.mjs` ختم می‌شود، nginx شما نگاشت `.mjs → application/javascript` را ندارد. نمایشگر PDF همراه (که امضای PDF، پرکنندهٔ فرم و چند ابزار دیگر از آن استفاده می‌کنند) فایل‌های ماژول ES با پسوند `.mjs` دارد و `mime.types` پیش‌فرض nginx به‌طور پیش‌فرض آن‌ها را شامل نمی‌شود.
برای رفع آن، `mjs` را به خط موجود `application/javascript` در `/etc/nginx/mime.types` اضافه کنید:
```nginx
application/javascript js mjs;
```
> خط _دومی_ برای `application/javascript` اضافه نکنید؛ nginx فایل `mime.types` را خط به خط تجزیه می‌کند و فقط یک نگاشت نوع به پسوند اعمال می‌شود، پس در برخی نسخه‌ها خط دوم بی‌صدا نادیده گرفته می‌شود. خط موجود را ویرایش کنید و `mjs` را کنار `js` اضافه کنید.
یا به‌صورت محلی درون بلوک server بازنویسی کنید (بدون ویرایش فایل سراسری):
```nginx
types {
application/javascript js mjs;
application/wasm wasm;
}
```
پس از تغییر، nginx را دوباره بارگذاری کنید:
```bash
sudo nginx -t && sudo systemctl reload nginx
```
برای تأیید: در DevTools مرورگر ← زبانهٔ Network، درخواست ناموفق `.mjs` را پیدا کنید و هدر پاسخ `Content-Type` را بررسی کنید. اکنون باید `application/javascript` باشد، نه `application/octet-stream`.
### تبدیل Word/ODT/Excel به PDF کار نمی‌کند
LibreOffice WASM به `SharedArrayBuffer` نیاز دارد که آن هم به هدرهای `Cross-Origin-Embedder-Policy` و `Cross-Origin-Opener-Policy` نیاز دارد. همچنین به یک بستر امن نیاز دارد، بنابراین `http://localhost` برای آزمایش محلی کار می‌کند اما `http://192.168.x.x` یا دیگر IPهای شبکهٔ محلی معمولاً به HTTPS نیاز دارند. توجه کنید که دستورهای `add_header` در nginx درون یک بلوک `location` دستورهای `add_header` سطح server را **بازنویسی** می‌کنند و با آن‌ها ادغام نمی‌شوند. هر بلوک `location` که `add_header` خودش را دارد باید هدرهای COEP/COOP را نیز شامل شود.
با این دستور بررسی کنید:
```bash
curl -I https://your-domain.com/ | grep -i cross-origin
```
اگر جلوی nginx از پراکسی معکوس استفاده می‌کنید، مطمئن شوید این هدرها را حفظ می‌کند.
### 502 Bad Gateway
لاگ‌های خطای Nginx را بررسی کنید:
```bash
sudo tail -f /var/log/nginx/error.log
```
+122
View File
@@ -0,0 +1,122 @@
# استقرار روی Vercel
[Vercel](https://vercel.com) ساده‌ترین تجربهٔ استقرار را برای سایت‌های ایستا ارائه می‌دهد.
## استقرار با یک کلیک
[![استقرار با Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
## استقرار دستی
### گام ۱: فورک کردن مخزن
مخزن [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) را در حساب GitHub خود فورک کنید.
### گام ۲: وارد کردن به Vercel
1. به [vercel.com/new](https://vercel.com/new) بروید
2. مخزن فورک‌شدهٔ خود را انتخاب کنید
3. پروژه را پیکربندی کنید:
| تنظیم | مقدار |
| ---------------- | --------------- |
| Framework Preset | Vite |
| Build Command | `npm run build` |
| Output Directory | `dist` |
| Install Command | `npm install` |
### گام ۳: متغیرهای محیطی (اختیاری)
در صورت نیاز این موارد را اضافه کنید:
| متغیر | توضیح |
| ----------------------- | -------------------------------------------------------- |
| `SIMPLE_MODE` | برای رابط کاربری حداقلی روی `true` تنظیم کنید |
| `VITE_BRAND_NAME` | نام برند سفارشی (جایگزین «BentoPDF») |
| `VITE_BRAND_LOGO` | مسیر لوگو نسبت به `public/` (مثلاً `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | متن سفارشی پاورقی/حق نشر |
| `VITE_DEFAULT_LANGUAGE` | زبان پیش‌فرض رابط کاربری (مثلاً `fr`، `de`، `es`) |
### گام ۴: استقرار
روی «Deploy» کلیک کنید و منتظر بمانید تا ساخت کامل شود.
## دامنهٔ سفارشی
1. به تنظیمات پروژه بروید
2. به بخش «Domains» بروید
3. دامنهٔ سفارشی خود را اضافه کنید
4. DNS را طبق دستورالعمل پیکربندی کنید
## محدودیت‌ها
::: warning فایل‌های بزرگ
توابع serverless در Vercel محدودیت ۵۰ مگابایت دارند. از آنجا که BentoPDF یک سایت ایستاست، این موضوع نباید بر شما اثر بگذارد، اما ماژول‌های WASM بزرگ هستند (در مجموع حدود ۱۰۰ مگابایت). مطمئن شوید که از پوشهٔ `/public` ارائه می‌شوند.
:::
## عیب‌یابی
### پایان زمان ساخت
اگر زمان ساخت به پایان می‌رسد، این را امتحان کنید:
```json
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
```
### خطای 404 هنگام تازه‌سازی
برای مسیریابی SPA یک فایل `vercel.json` اضافه کنید:
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```
### تبدیل Word/ODT/Excel به PDF کار نمی‌کند
تبدیل‌های LibreOffice WASM به `SharedArrayBuffer` نیاز دارند که آن هم به این هدرهای پاسخ در همهٔ صفحات نیاز دارد:
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
فایل‌های از پیش فشرده‌شدهٔ `.wasm.gz` و `.data.gz` نیز به `Content-Encoding: gzip` نیاز دارند تا مرورگر آن‌ها را از حالت فشرده خارج کند.
این موارد را به `vercel.json` خود اضافه کنید:
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" },
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Resource-Policy", "value": "cross-origin" }
]
},
{
"source": "/libreoffice-wasm/soffice.wasm.gz",
"headers": [
{ "key": "Content-Type", "value": "application/wasm" },
{ "key": "Content-Encoding", "value": "gzip" }
]
},
{
"source": "/libreoffice-wasm/soffice.data.gz",
"headers": [
{ "key": "Content-Type", "value": "application/octet-stream" },
{ "key": "Content-Encoding", "value": "gzip" }
]
}
]
}
```
برای بررسی، Console در DevTools را باز کنید و `console.log(window.crossOriginIsolated)` را اجرا کنید؛ باید `true` برگرداند.