mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-13 09:54:19 +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-сертификат (рекомендуется)
|
||||
|
||||
## Шаг 1: соберите проект
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Шаг 2: скопируйте файлы
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/bentopdf
|
||||
sudo cp -r dist/* /var/www/bentopdf/
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
```
|
||||
|
||||
## Шаг 3: конфигурация 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>
|
||||
```
|
||||
|
||||
## Шаг 4: .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
|
||||
```
|
||||
|
||||
## Шаг 5: включите нужные модули
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
sudo a2enmod ssl
|
||||
sudo a2enmod headers
|
||||
sudo a2enmod expires
|
||||
sudo a2enmod deflate
|
||||
```
|
||||
|
||||
## Шаг 6: включите сайт
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Sign PDF или Form Filler показывают пустой просмотрщик (ошибка 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`.
|
||||
|
||||
### Rewrite не работает
|
||||
|
||||
Проверьте, что 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"
|
||||
```
|
||||
|
||||
Также нужен безопасный контекст. `http://localhost` подходит для локального тестирования, а для `http://192.168.x.x` и других адресов локальной сети обычно требуется 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`
|
||||
|
||||
### Отказано в доступе
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
## Шаг 1: создайте S3-бакет
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Шаг 2: сборка и загрузка
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Шаг 3: создайте дистрибутив CloudFront
|
||||
|
||||
```bash
|
||||
aws cloudfront create-distribution \
|
||||
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
|
||||
--default-root-object index.html
|
||||
```
|
||||
|
||||
Или воспользуйтесь консолью AWS:
|
||||
|
||||
1. Откройте CloudFront → Create distribution
|
||||
2. Origin domain: выберите свой S3-бакет
|
||||
3. Включите "Origin Access Control"
|
||||
4. Default root object: `index.html`
|
||||
5. Создайте дистрибутив
|
||||
|
||||
## Шаг 3b: политика заголовков ответа (обязательна для 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 вашего дистрибутива
|
||||
|
||||
Или через 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}
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Шаг 3c: метаданные 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 зависнут примерно на 55%. Без правильного Content-Encoding у файлов `.gz` браузер получает необработанные байты gzip, и компиляция WASM завершается ошибкой.
|
||||
:::
|
||||
|
||||
## Шаг 4: политика S3-бакета
|
||||
|
||||
Разрешите CloudFront доступ к бакету:
|
||||
|
||||
```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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Шаг 5: собственные страницы ошибок
|
||||
|
||||
Настройте возврат `index.html` при 404 для маршрутизации SPA:
|
||||
|
||||
1. CloudFront → Error pages
|
||||
2. Создайте собственный ответ на ошибку:
|
||||
- HTTP error code: 404
|
||||
- Response page path: `/index.html`
|
||||
- HTTP response code: 200
|
||||
|
||||
## Оценка стоимости
|
||||
|
||||
| Ресурс | Ориентировочная стоимость |
|
||||
| -------------------------- | ------------------------- |
|
||||
| S3 Storage (~500MB) | ~$0.01/мес. |
|
||||
| CloudFront (1TB transfer) | ~$85/мес. |
|
||||
| CloudFront (10GB transfer) | ~$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 |
|
||||
| Build command | `npm run build` |
|
||||
| Build output directory | `dist` |
|
||||
| Root directory | `/` |
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
Добавьте их в 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 автоматически настроит DNS, если домен находится в Cloudflare
|
||||
|
||||
## Преимущества
|
||||
|
||||
- **Бесплатный неограниченный трафик**
|
||||
- **Глобальная CDN** с более чем 300 узлами
|
||||
- **Автоматический HTTPS**
|
||||
- **Предпросмотр развёртываний** для pull request
|
||||
- **Быстрые сборки**
|
||||
|
||||
## Устранение неполадок
|
||||
|
||||
### Загрузка больших файлов
|
||||
|
||||
Cloudflare Pages поддерживает файлы размером до 25 МБ. Модули WASM должны проходить, но если вы упрётесь в лимит, попробуйте:
|
||||
|
||||
```bash
|
||||
# Split large files during build
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Ограничения размера Worker
|
||||
|
||||
Если вы используете Cloudflare Workers для сложной маршрутизации, учтите лимит 1 МБ на бесплатных тарифах.
|
||||
|
||||
## CORS-прокси Worker (для цифровых подписей)
|
||||
|
||||
Инструменту цифровой подписи нужен CORS-прокси для получения цепочек сертификатов. Разверните входящий в комплект worker:
|
||||
|
||||
```bash
|
||||
cd cloudflare
|
||||
npx wrangler login
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### Функции безопасности
|
||||
|
||||
| Функция | Описание |
|
||||
| ------------------------- | ----------------------------------- |
|
||||
| **Ограничение URL** | Разрешены только URL сертификатов |
|
||||
| **Лимит размера файла** | Максимум 10 МБ на запрос |
|
||||
| **Ограничение частоты** | 60 запросов/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-прокси решает обе проблемы: запросы идут через HTTPS-endpoint с нужными заголовками.
|
||||
|
||||
## Как это работает
|
||||
|
||||
При подписании PDF сертификатом:
|
||||
|
||||
1. Библиотека `zgapdfsigner` пытается построить полную цепочку сертификатов
|
||||
2. Она запрашивает сертификаты издателей по URL из расширения AIA (Authority Information Access) вашего сертификата
|
||||
3. Эти запросы идут через CORS-прокси, который добавляет нужные заголовки `Access-Control-Allow-Origin`
|
||||
4. Прокси возвращает данные сертификата в браузер
|
||||
|
||||
## Собственный CORS-прокси
|
||||
|
||||
Если вы размещаете BentoPDF у себя, для работы цифровых подписей с сертификатами, которым нужна загрузка цепочки, потребуется развернуть собственный CORS-прокси.
|
||||
|
||||
### Вариант 1: 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
|
||||
```
|
||||
|
||||
### Вариант 2: собственный серверный прокси
|
||||
|
||||
Можно создать и собственный 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 источником |
|
||||
| **Лимит размера файла** | Передаёт ответ потоком с лимитом 10 МБ и прерывает загрузку при превышении |
|
||||
| **Ограничение частоты** | 60 запросов в минуту на IP или IPv6 /64 (нужен KV, при ошибках KV пропускает запросы) |
|
||||
| **Подписи HMAC** | Необязательная подпись на стороне клиента (сдерживает случайные злоупотребления) |
|
||||
|
||||
### DNS rebinding (хостинг вне Cloudflare)
|
||||
|
||||
Worker отклоняет любой URL, который разрешается в частный или внутренний IP. Фиксированный список разрешённых хостов использовать нельзя, так как URL цепочек сертификатов указывают на тот CA, который выдал сертификат (FNMT, корпоративные CA и так далее), и адреса заранее неизвестны. Поэтому остаётся небольшое окно для rebinding: тот, кто управляет DNS своего домена, может отдать проверке безопасности публичный IP, а настоящему запросу чуть позже частный.
|
||||
|
||||
В Cloudflare это не эксплуатируется: Workers не могут обращаться к частным IP и endpoint метаданных `169.254.169.254`, поэтому rebind ни к чему не приведёт. Только не давайте worker доступа внутрь: не подключайте Tunnel, Service или привязку к частной сети (ему нужен лишь `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 у себя в рабочей среде.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Обязательные заголовки для конвертации файлов Office**
|
||||
>
|
||||
> Инструментам на основе LibreOffice (конвертация Word, Excel, PowerPoint) для поддержки `SharedArrayBuffer` нужны такие HTTP-заголовки:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> Страница также должна отдаваться из безопасного контекста. `http://localhost` подходит для локального тестирования, а `http://192.168.x.x` и другие адреса локальной сети обычно под это не подпадают, поэтому конвертация Office по обычному HTTP не сработает, даже если заголовки есть.
|
||||
>
|
||||
> Официальные образы контейнеров включают эти заголовки. Если вы используете обратный прокси (Traefik, Caddy и т. д.), убедитесь, что эти заголовки сохраняются или добавляются, и используйте HTTPS для доступа не через loopback.
|
||||
|
||||
> [!TIP]
|
||||
> **Пользователям Podman:** все команды `docker` работают и с Podman, если заменить `docker` на `podman`, а `docker-compose` на `podman-compose`.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
> [!TIP]
|
||||
> BentoPDF выпускается в двух сборках:
|
||||
>
|
||||
> - **Сборка Self-Hosted**: `ghcr.io/alam00000/bentopdf-simple:latest`. Все PDF-инструменты **без** маркетинговой обвязки (без главного экрана, FAQ, отзывов и подвала). Используйте её для внутренних, командных и корпоративных развёртываний. Это **не** урезанная «лёгкая» версия.
|
||||
> - **Сборка Commercial**: `ghcr.io/alam00000/bentopdf:latest`. Полный маркетинговый сайт, который использует сам bentopdf.com и владельцы коммерческой лицензии для публичных развёртываний.
|
||||
>
|
||||
> Если сомневаетесь, берите сборку Self-Hosted.
|
||||
|
||||
```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` | Собрать вариант Self-Hosted (скрывает маркетинговую обвязку bentopdf.com: главный экран, FAQ, отзывы, подвал). Все 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 скрипта воркера 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` | ID инструментов через запятую, которые нужно скрыть | _(пусто; все инструменты включены)_ |
|
||||
| `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 по умолчанию, поэтому все расширенные функции работают сразу. Переопределите их для изолированных (air-gapped) или самостоятельно размещаемых развёртываний.
|
||||
|
||||
### Поисковые системы
|
||||
|
||||
По умолчанию каждая страница указывает поисковым системам на 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`. Директивы CSP `script-src`, `connect-src` и `font-src` **генерируются при сборке** из перечисленных выше переменных 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 на любых других хостах разрешаются по источнику (origin).
|
||||
- Если вы задаёте собственные URL WASM во _время работы_ на странице расширенных настроек приложения, эти источники **не** попадают в CSP, и браузер заблокирует запросы к ним. Это относится и к другой версии того же пакета на `cdn.jsdelivr.net`, так как разрешена только версия, заданная при сборке. Настройка во время работы предназначена для экспериментов; для постоянных собственных URL задайте соответствующий аргумент сборки `VITE_*`.
|
||||
- В изолированных развёртываниях, где переопределены все три значения `VITE_WASM_*_URL`, из CSP также удаляются эти папки пакетов `cdn.jsdelivr.net` (каждое значение по умолчанию заменяется, а не дополняется). Значения 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 на своём сервере или офлайн. Частичное переопределение отклоняется, потому что воркер, основной рантайм и каталог 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 показывается страница «инструмент недоступен».
|
||||
|
||||
ID инструмента: URL страницы без `.html`. Например, если инструмент находится по адресу `bentopdf.com/edit-pdf.html`, его ID равен `edit-pdf`.
|
||||
|
||||
#### Как найти ID инструментов
|
||||
|
||||
Проще всего открыть любой инструмент в BentoPDF и посмотреть на URL. Последняя часть пути (без `.html`) и есть ID инструмента.
|
||||
|
||||
<details>
|
||||
<summary>Полный список ID инструментов</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>
|
||||
|
||||
#### Вариант 1: при сборке (аргумент сборки Docker)
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
|
||||
-t bentopdf .
|
||||
```
|
||||
|
||||
Список отключённых инструментов встраивается в JavaScript-бандл. Для изменения нужна пересборка.
|
||||
|
||||
#### Вариант 2: во время работы (config.json)
|
||||
|
||||
Смонтируйте файл `config.json` в раздаваемый каталог, пересборка не нужна:
|
||||
|
||||
```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`) всегда пропускают счётчик звёзд: этот флаг нужен только для сборки Commercial или собственных полных сборок.
|
||||
|
||||
### Собственные 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
|
||||
```
|
||||
|
||||
Используйте коды, которые выводит `bash scripts/prepare-airgap.sh --list-ocr-languages`, или ищите по названию через `bash scripts/prepare-airgap.sh --search-ocr-language <term>`, для `--ocr-languages`. Когда вы собираете с ограниченным набором языков 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` |
|
||||
|
||||
Это работает и со стандартным, и с nonroot-Dockerfile.
|
||||
|
||||
## Свой ID пользователя (PUID/PGID)
|
||||
|
||||
Для сред, где нужен запуск от конкретного непривилегированного пользователя (NAS-устройства, Kubernetes с контекстами безопасности, организационные политики), 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` | ID пользователя для запуска | `1000` |
|
||||
| `PGID` | ID группы для запуска | `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) и рекомендуется для большинства развёртываний. Используйте `Dockerfile.nonroot` только когда нужен конкретный UID/GID.
|
||||
|
||||
> [!WARNING]
|
||||
> PUID/PGID не могут быть равны `0` (root). Точка входа проверяет значения и завершится с ошибкой при недопустимых.
|
||||
|
||||
## С 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. Это идеально подходит для рабочих развёртываний в 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,266 @@
|
||||
# Развёртывание на Hostinger
|
||||
|
||||
[Hostinger](https://hostinger.com) популярный провайдер виртуального хостинга. Это руководство описывает развёртывание BentoPDF на виртуальном хостинге Hostinger.
|
||||
|
||||
## Требования
|
||||
|
||||
- Тариф хостинга Hostinger с доступом к файловому менеджеру
|
||||
- Node.js, установленный локально для сборки
|
||||
|
||||
## Шаг 1: соберите проект
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Готовые файлы будут в папке `dist`.
|
||||
|
||||
## Шаг 2: загрузите на Hostinger
|
||||
|
||||
### Развёртывание в корне домена
|
||||
|
||||
1. Войдите в свой аккаунт Hostinger
|
||||
2. Откройте **File Manager** → **public_html**
|
||||
3. **Удалите** все существующие файлы (при необходимости сделайте резервную копию)
|
||||
4. **Загрузите** всё содержимое локальной папки `dist` в `public_html`
|
||||
|
||||
### Развёртывание в подкаталоге
|
||||
|
||||
Если вы развёртываете в подкаталог (например, `yourdomain.com/pdf-tools/`):
|
||||
|
||||
1. Соберите проект с правильным базовым URL:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
2. Создайте папку на Hostinger:
|
||||
- Откройте **File Manager** → **public_html**
|
||||
- Создайте новую папку: `pdf-tools`
|
||||
3. Загрузите всё содержимое `dist` в `public_html/pdf-tools/`
|
||||
|
||||
## Шаг 3: создайте файл .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, **без** маркетингового оформления (без главного экрана, FAQ, отзывов и подвала). Используйте ее для внутренних развертываний в команде или организации. Это **не** урезанная «облегченная» версия.
|
||||
> - **Коммерческая сборка**: `ghcr.io/alam00000/bentopdf:latest`. Полный маркетинговый сайт, который использует сам bentopdf.com и держатели коммерческих лицензий для публичных развертываний.
|
||||
>
|
||||
> Если сомневаетесь, берите сборку для самостоятельного размещения.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Для конвертации офисных файлов нужен `SharedArrayBuffer`, а значит, приложение должно быть изолировано между источниками (cross-origin isolated) и раздаваться из защищенного контекста. Официальный образ уже отправляет нужные заголовки COOP/COEP, но браузеры все равно отключают `SharedArrayBuffer` на обычных HTTP-адресах в локальной сети, например `http://192.168.x.x`.
|
||||
>
|
||||
> Используйте `http://localhost` только для проверки на одном устройстве. Если пользователи открывают BentoPDF по IP-адресу или имени хоста в локальной сети, настройте 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](/ru/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 не удаляются):
|
||||
|
||||
- Панель навигации, главный экран, раздел возможностей, FAQ, отзывы, подвал
|
||||
- Заголовок страницы меняется на «Инструменты PDF»
|
||||
|
||||
**Что сохраняет сборка для самостоятельного размещения** (все, что реально работает с PDF):
|
||||
|
||||
- Все инструменты PDF (объединение, разделение, редактирование, подпись, OCR, конвертация офисных файлов и т. д.)
|
||||
- Поддержку собственного брендинга, всю конфигурацию на этапе сборки и во время работы
|
||||
|
||||
Коммерческая сборка (`ghcr.io/alam00000/bentopdf:latest`) лежит в основе самого bentopdf.com, и ее используют держатели коммерческих лицензий для публичных развертываний. Она добавляет главный экран, FAQ, отзывы и подвал, которые не нужны во внутреннем инструменте.
|
||||
|
||||
Если вы размещаете 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. Она включает полный маркетинговый сайт (главный экран, возможности, FAQ, отзывы, подвал) в дополнение ко всем инструментам PDF. Используйте эту сборку, когда хотите **развернуть 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, включая любые изменения брендинга и окружающую бизнес-логику.
|
||||
- **Коммерческая лицензия** (79 $ навсегда): требуется для закрытых и проприетарных развертываний, где вы не публикуете исходный код своего форка с брендингом или бизнес-код.
|
||||
|
||||
Полное сравнение смотрите на [странице лицензирования](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`: откройте любой инструмент и посмотрите на адрес (например, `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
|
||||
|
||||
**Во время сборки** (вшивается в бандл):
|
||||
|
||||
```bash
|
||||
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
|
||||
```
|
||||
|
||||
**Во время работы** (пересборка не нужна, смонтируйте `config.json`):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Оба способа можно сочетать: списки объединяются.
|
||||
|
||||
Вы также можете отключить отдельные функции внутри редактора PDF (например, редактирование с удалением данных), не отключая инструмент целиком, добавив `editorDisabledCategories` в `config.json`. Полный список категорий смотрите в [руководстве по Docker](/ru/self-hosting/docker#отключение-функции-редактора).
|
||||
|
||||
## Руководства по развертыванию
|
||||
|
||||
Выберите платформу:
|
||||
|
||||
- [Vercel](/ru/self-hosting/vercel)
|
||||
- [Netlify](/ru/self-hosting/netlify)
|
||||
- [Cloudflare Pages](/ru/self-hosting/cloudflare)
|
||||
- [AWS S3 + CloudFront](/ru/self-hosting/aws)
|
||||
- [Hostinger](/ru/self-hosting/hostinger)
|
||||
- [Nginx](/ru/self-hosting/nginx)
|
||||
- [Apache](/ru/self-hosting/apache)
|
||||
- [Docker](/ru/self-hosting/docker)
|
||||
- [Kubernetes](/ru/self-hosting/kubernetes)
|
||||
- [CORS-прокси](/ru/self-hosting/cors-proxy) - необходим для цифровых подписей
|
||||
|
||||
## Частые проблемы
|
||||
|
||||
### Конвертация Word / ODT / Excel / PowerPoint в PDF зависает (SharedArrayBuffer недоступен)
|
||||
|
||||
**Симптом**: конвертация документов через LibreOffice (Word, ODT, Excel, PowerPoint в PDF) зависает примерно на 55% или не запускается. В консоли браузера может быть:
|
||||
|
||||
> `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`, поэтому браузер получает необработанные байты gzip вместо распакованного WASM.
|
||||
|
||||
**Решение**: точную конфигурацию смотрите в руководстве для вашей платформы:
|
||||
|
||||
- [Nginx →](/ru/self-hosting/nginx#конвертация-word-odt-excel-в-pdf-не-работает)
|
||||
- [Apache →](/ru/self-hosting/apache#конвертация-word-odt-excel-в-pdf-не-работает)
|
||||
- [AWS S3 + CloudFront →](/ru/self-hosting/aws#шаг-3b-политика-заголовков-ответа-обязательна-для-libreoffice-wasm)
|
||||
- [Cloudflare Pages →](/ru/self-hosting/cloudflare#фаил-конфигурации) (файл `_headers`)
|
||||
- [Netlify →](/ru/self-hosting/netlify#конвертация-word-odt-excel-в-pdf-зависает-на-55) (`netlify.toml`)
|
||||
- [Vercel →](/ru/self-hosting/vercel#конвертация-word-odt-excel-в-pdf-не-работает) (`vercel.json`)
|
||||
- [Hostinger →](/ru/self-hosting/hostinger#инструменты-libreoffice-не-работают) (`.htaccess`)
|
||||
- [Kubernetes →](/ru/self-hosting/kubernetes#сохранение-заголовков-sharedarraybuffer-ingress-gateway)
|
||||
- **Docker**: обрабатывается автоматически встроенной конфигурацией nginx, никаких действий не требуется.
|
||||
|
||||
**Проверка**: откройте консоль 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`
|
||||
|
||||
**Симптом**: Sign PDF / Form Filler и некоторые другие инструменты показывают пустой просмотрщик или не загружаются. В консоли браузера:
|
||||
|
||||
> `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 →](/ru/self-hosting/nginx#sign-pdf-или-form-filler-показывает-пустои-просмотрщик-ошибка-mime-для-mjs)
|
||||
- [Apache →](/ru/self-hosting/apache#sign-pdf-или-form-filler-показывают-пустои-просмотрщик-ошибка-mime-для-mjs)
|
||||
- [AWS S3 + CloudFront →](/ru/self-hosting/aws#шаг-2-сборка-и-загрузка) (см. `aws s3 cp ... --include "*.mjs"`)
|
||||
- [Kubernetes →](/ru/self-hosting/kubernetes#ошибки-mime-типа-mjs-пустои-iframe-в-sign-pdf-form-filler)
|
||||
- **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 воркера, ядра и языковых данных вместе для автономного 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 должны раздаваться с **того же источника** (origin), что и приложение BentoPDF. Веб-воркеры используют `importScripts()`, который не может загружать скрипты с другого источника. Например, если BentoPDF работает на `https://internal.example.com`, базовый URL для WASM тоже должен быть `https://internal.example.com/wasm`.
|
||||
:::
|
||||
|
||||
#### Ручные шаги
|
||||
|
||||
<details>
|
||||
<summary>Если вы предпочитаете выполнить все вручную, без скрипта</summary>
|
||||
|
||||
**Шаг 1: Скачайте пакеты 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
|
||||
```
|
||||
|
||||
**Шаг 2: Соберите образ 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 .
|
||||
```
|
||||
|
||||
**Шаг 3: Экспортируйте образ Docker**
|
||||
|
||||
```bash
|
||||
docker save bentopdf -o bentopdf.tar
|
||||
```
|
||||
|
||||
**Шаг 4: Перенесите во внутреннюю изолированную сеть**
|
||||
|
||||
Скопируйте через USB, внутренний репозиторий артефактов или другой разрешенный способ передачи:
|
||||
|
||||
- `bentopdf.tar`: образ Docker
|
||||
- Пять пакетов `.tgz` WASM/OCR из шага 1
|
||||
- Директорию `tesseract-langdata/` из шага 1
|
||||
- Директорию `ocr-fonts/` из шага 1
|
||||
|
||||
**Шаг 5: Настройте внутри изолированной сети**
|
||||
|
||||
```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, настроенным на шаге 2, включая `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data` и `.../ocr/fonts`.
|
||||
|
||||
</details>
|
||||
|
||||
::: info Собираете из исходного кода, а не через Docker?
|
||||
Задайте переменные в `.env.production` перед запуском `npm run build`:
|
||||
|
||||
```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 загружаются во время работы и не входят в исходный код
|
||||
:::
|
||||
|
||||
## Системные требования
|
||||
|
||||
| Требование | Минимум |
|
||||
| ---------- | ------------------------------- |
|
||||
| Диск | ~100 МБ (ядро без модулей AGPL) |
|
||||
| ОЗУ | 512 МБ |
|
||||
| Процессор | Любой современный |
|
||||
|
||||
::: tip
|
||||
BentoPDF является статическим сайтом: база данных и серверная часть не нужны!
|
||||
:::
|
||||
@@ -0,0 +1,211 @@
|
||||
# Развертывание в Kubernetes
|
||||
|
||||
Kubernetes может быть избыточен для статического сайта, но он хорошо подходит, если вы уже стандартизировали процессы на Helm и GitOps.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Обязательные заголовки для конвертации офисных файлов**
|
||||
>
|
||||
> Инструментам на основе LibreOffice (конвертация Word, Excel, PowerPoint) для поддержки `SharedArrayBuffer` нужны следующие HTTP-заголовки:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> Официальные образы BentoPDF на nginx уже отправляют эти заголовки. В Kubernetes **контроллеры Ingress и Gateway тоже являются обратными прокси**, поэтому убедитесь, что заголовки сохраняются (или добавьте их на границе).
|
||||
|
||||
## Требования
|
||||
|
||||
- Кластер Kubernetes
|
||||
- Helm v3
|
||||
- Образ BentoPDF на nginx (например, `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)
|
||||
|
||||
Если чарт опубликован в GHCR как артефакт OCI:
|
||||
|
||||
```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 в Sign PDF / Form Filler)
|
||||
|
||||
Если в консоли браузера появляется `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` для запроса `.mjs`, значит контроллер 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`. Проверьте на вкладке Network в DevTools: у неудавшегося запроса `.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"]
|
||||
}
|
||||
```
|
||||
|
||||
Смонтируйте его в раздаваемую директорию:
|
||||
|
||||
```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`: откройте любой инструмент и посмотрите на адрес (например, `edit-pdf`, `merge-pdf`, `compress-pdf`). Отключенные инструменты скрыты с главной страницы, из поиска, быстрых клавиш, конструктора рабочих процессов и недоступны по прямому URL. Полный список параметров смотрите в [руководстве по Docker](/ru/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: Подключите репозиторий
|
||||
|
||||
1. Войдите в [Netlify](https://app.netlify.com)
|
||||
2. Нажмите «Add new site» → «Import an existing project»
|
||||
3. Подключите свой аккаунт GitHub
|
||||
4. Выберите свой форк BentoPDF
|
||||
|
||||
### Шаг 2: Настройте параметры сборки
|
||||
|
||||
| Параметр | Значение |
|
||||
| ----------------- | --------------- |
|
||||
| Build command | `npm run build` |
|
||||
| Publish directory | `dist` |
|
||||
| Node version | 18+ |
|
||||
|
||||
### Шаг 3: Разверните
|
||||
|
||||
Нажмите «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 зависает на 55%
|
||||
|
||||
Если конвертация документов зависает на 55%, откройте консоль 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`, у файлов `.wasm.gz` нет заголовков `Content-Encoding: gzip`. Убедитесь, что блоки `[[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)
|
||||
|
||||
## Шаг 1: Соберите проект
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Шаг 2: Скопируйте файлы
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/bentopdf
|
||||
sudo cp -r dist/* /var/www/bentopdf/
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
```
|
||||
|
||||
## Шаг 3: Конфигурация 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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Шаг 4: Включите сайт
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## Шаг 5: 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;
|
||||
}
|
||||
```
|
||||
|
||||
### Sign PDF или Form Filler показывает пустой просмотрщик (ошибка 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 (его используют Sign PDF, Form Filler и несколько других инструментов) содержит файлы 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` и других адресов в локальной сети обычно требуется HTTPS. Учтите, что директивы `add_header` в блоке `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)
|
||||
|
||||
## Ручное развертывание
|
||||
|
||||
### Шаг 1: Сделайте форк репозитория
|
||||
|
||||
Сделайте форк [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) в свой аккаунт GitHub.
|
||||
|
||||
### Шаг 2: Импортируйте в 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` |
|
||||
|
||||
### Шаг 3: Переменные окружения (необязательно)
|
||||
|
||||
Добавьте их при необходимости:
|
||||
|
||||
| Переменная | Описание |
|
||||
| ----------------------- | ----------------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Укажите `true` для минимального интерфейса |
|
||||
| `VITE_BRAND_NAME` | Свое название бренда (заменяет «BentoPDF») |
|
||||
| `VITE_BRAND_LOGO` | Путь к логотипу относительно `public/` (например, `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Свой текст подвала и авторских прав |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Язык интерфейса по умолчанию (например, `fr`, `de`, `es`) |
|
||||
|
||||
### Шаг 4: Разверните
|
||||
|
||||
Нажмите «Deploy» и дождитесь завершения сборки.
|
||||
|
||||
## Собственный домен
|
||||
|
||||
1. Откройте настройки проекта
|
||||
2. Перейдите в раздел «Domains»
|
||||
3. Добавьте свой домен
|
||||
4. Настройте DNS по инструкции
|
||||
|
||||
## Ограничения
|
||||
|
||||
::: warning Большие файлы
|
||||
У serverless-функций Vercel есть ограничение в 50 МБ. Поскольку BentoPDF является статическим сайтом, это не должно вас затронуть, но модули WASM велики (около 100 МБ в сумме). Убедитесь, что они раздаются из папки `/public`.
|
||||
:::
|
||||
|
||||
## Устранение неполадок
|
||||
|
||||
### Тайм-аут сборки
|
||||
|
||||
Если сборка завершается по тайм-ауту, попробуйте:
|
||||
|
||||
```json
|
||||
// vercel.json
|
||||
{
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "dist"
|
||||
}
|
||||
```
|
||||
|
||||
### Ошибка 404 при обновлении страницы
|
||||
|
||||
Добавьте `vercel.json` для маршрутизации SPA:
|
||||
|
||||
```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" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Для проверки откройте консоль DevTools и выполните `console.log(window.crossOriginIsolated)`: должно вернуться `true`.
|
||||
Reference in new issue
Block a user