mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-11 01:14:35 +01:00
feat: translate all pages, UI messages and docs and fix SEO redirects and indexing
This commit is contained in:
1 parent
5e019de5ae
commit
4edb1718cc
3344 files changed
+415643
-4759
No files matched your search
@@ -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
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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) را ببینید.
|
||||
@@ -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های تجاری که گواهیهای میانی را همراه میکنند
|
||||
- گواهیهایی که خودتان دستی با زنجیرهٔ کامل سرهم کردهاید
|
||||
@@ -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
|
||||
```
|
||||
@@ -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 را جداگانه بارگذاری کنید.
|
||||
@@ -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 یک سایت ایستاست؛ به هیچ پایگاه داده یا سرور بکاندی نیاز نیست!
|
||||
:::
|
||||
@@ -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#غیرفعال-کردن-ابزارهای-مشخص) را ببینید.
|
||||
@@ -0,0 +1,139 @@
|
||||
# استقرار روی Netlify
|
||||
|
||||
[Netlify](https://netlify.com) میزبانی عالی برای سایتهای ایستا با یک طرح رایگان سخاوتمندانه ارائه میدهد.
|
||||
|
||||
## استقرار با یک کلیک
|
||||
|
||||
[](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 فعال کنید.
|
||||
@@ -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
|
||||
```
|
||||
@@ -0,0 +1,122 @@
|
||||
# استقرار روی Vercel
|
||||
|
||||
[Vercel](https://vercel.com) سادهترین تجربهٔ استقرار را برای سایتهای ایستا ارائه میدهد.
|
||||
|
||||
## استقرار با یک کلیک
|
||||
|
||||
[](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` برگرداند.
|
||||
Reference in new issue
Block a user