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` та інші LAN-адреси зазвичай вимагають HTTPS. Якщо заголовки є, але `window.crossOriginIsolated` усе ще `false`, перевірте, чи сторінка не відкривається через звичайний HTTP з origin, що не є 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 (~500MB) | ~$0.01/міс. |
|
||||
| CloudFront (1TB трафіку) | ~$85/міс. |
|
||||
| CloudFront (10GB трафіку) | ~$0.85/міс. |
|
||||
|
||||
::: tip
|
||||
Використовуйте S3 Intelligent Tiering, щоб оптимізувати витрати на файли, до яких звертаються нечасто.
|
||||
:::
|
||||
|
||||
## Автоматизація за допомогою Terraform
|
||||
|
||||
```hcl
|
||||
# main.tf
|
||||
resource "aws_s3_bucket" "bentopdf" {
|
||||
bucket = "your-bentopdf-bucket"
|
||||
}
|
||||
|
||||
resource "aws_cloudfront_distribution" "bentopdf" {
|
||||
origin {
|
||||
domain_name = aws_s3_bucket.bentopdf.bucket_regional_domain_name
|
||||
origin_id = "S3Origin"
|
||||
}
|
||||
|
||||
enabled = true
|
||||
default_root_object = "index.html"
|
||||
|
||||
default_cache_behavior {
|
||||
allowed_methods = ["GET", "HEAD"]
|
||||
cached_methods = ["GET", "HEAD"]
|
||||
target_origin_id = "S3Origin"
|
||||
|
||||
viewer_protocol_policy = "redirect-to-https"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,154 @@
|
||||
# Розгортання на Cloudflare Pages
|
||||
|
||||
[Cloudflare Pages](https://pages.cloudflare.com) пропонує швидкий глобальний хостинг статичних сайтів з необмеженим трафіком.
|
||||
|
||||
## Швидке розгортання
|
||||
|
||||
1. Перейдіть до [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
|
||||
2. Натисніть "Create a project"
|
||||
3. Підключіть свій репозиторій GitHub
|
||||
|
||||
## Конфігурація збірки
|
||||
|
||||
| Параметр | Значення |
|
||||
| ---------------------- | --------------- |
|
||||
| Framework preset | None |
|
||||
| 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 MB. Модулі WASM мають вміститися, але якщо ви впираєтеся в ліміти, розгляньте:
|
||||
|
||||
```bash
|
||||
# Split large files during build
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Ліміти розміру Worker
|
||||
|
||||
Якщо ви використовуєте Cloudflare Workers для складної маршрутизації, зверніть увагу на ліміт 1 MB для безкоштовних планів.
|
||||
|
||||
## Worker CORS-проксі (для цифрових підписів)
|
||||
|
||||
Інструмент цифрового підпису потребує CORS-проксі для отримання ланцюжків сертифікатів. Розгорніть вбудований worker:
|
||||
|
||||
```bash
|
||||
cd cloudflare
|
||||
npx wrangler login
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### Функції безпеки
|
||||
|
||||
| Функція | Опис |
|
||||
| --------------------------- | ------------------------------- |
|
||||
| **Обмеження URL** | Дозволено лише URL сертифікатів |
|
||||
| **Ліміт розміру файлу** | Макс. 10MB на запит |
|
||||
| **Обмеження частоти** | 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-ендпоінт із потрібними заголовками.
|
||||
|
||||
## Як це працює
|
||||
|
||||
Під час підписання 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 і змініть дозволені origin**:
|
||||
|
||||
```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: власний бекенд-проксі
|
||||
|
||||
Ви також можете створити власний ендпоінт проксі. Вимоги такі:
|
||||
|
||||
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 має кілька заходів безпеки:
|
||||
|
||||
| Функція | Опис |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| **Перевірка origin** | Дозволяє запити лише з доменів зі списку `ALLOWED_ORIGINS` |
|
||||
| **Обмеження URL** | Дозволяє лише URL сертифікатів (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
|
||||
| **Блокування приватних IP** | Блокує приватні діапазони IPv4/IPv6, link-local, loopback, десяткові IP та метадані хмари |
|
||||
| **Безпечний Content-Type** | Повертає лише безпечні MIME-типи сертифікатів, блокує підміну content-type від джерела |
|
||||
| **Ліміт розміру файлу** | Передає відповідь потоком з лімітом 10MB, перериває завантаження, якщо його перевищено |
|
||||
| **Обмеження частоти** | 60 запитів на хвилину на IP або IPv6 /64 (потрібен KV, у разі помилок KV пропускає запити) |
|
||||
| **Підписи HMAC** | Необов'язкове підписування на стороні клієнта (стримує випадкові зловживання) |
|
||||
|
||||
### DNS rebinding (самостійне розгортання поза Cloudflare)
|
||||
|
||||
Worker відхиляє будь-який URL, що вказує на приватну або внутрішню IP-адресу. Він не може використовувати фіксований список дозволених хостів, бо URL ланцюжків сертифікатів вказують на будь-який CA, що видав сертифікат (FNMT, корпоративні CA тощо), тож призначення наперед невідомі. Через це залишається невелике вікно для rebinding: той, хто керує DNS власного домену, може віддати публічну IP під час перевірки безпеки, а приватну в реальному запиті за мить.
|
||||
|
||||
На Cloudflare це не експлуатується: Workers не мають доступу до приватних IP чи ендпоінта метаданих `169.254.169.254`, тож rebind нікуди не веде. Просто не давайте worker шляху всередину: не підключайте Tunnel, Service чи binding приватної мережі (йому потрібен лише `RATE_LIMIT_KV`).
|
||||
|
||||
Якщо ви запускаєте це поза Cloudflare (workerd, Node тощо) усередині VPC, середовище _може_ звертатися до внутрішніх IP. Поставте перед ним egress-фаєрвол і заблокуйте приватні діапазони: `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-проксі відхиляє ваш origin**: перевірте, чи ваш домен є в масиві `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]
|
||||
> **Обов'язкові заголовки для конвертації офісних файлів**
|
||||
>
|
||||
> Інструменти на основі LibreOffice (конвертація Word, Excel, PowerPoint) потребують таких HTTP-заголовків для підтримки `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> Сторінка також має віддаватися з безпечного контексту. `http://localhost` підходить для локального тестування, але `http://192.168.x.x` та інші LAN-адреси зазвичай не відповідають вимогам, тому конвертація Office через звичайний HTTP не працюватиме, навіть якщо заголовки присутні.
|
||||
>
|
||||
> Офіційні образи контейнерів містять ці заголовки. Якщо ви використовуєте зворотний проксі (Traefik, Caddy тощо), переконайтеся, що ці заголовки зберігаються або додаються, і використовуйте HTTPS для доступу не з loopback.
|
||||
|
||||
> [!TIP]
|
||||
> **Користувачам Podman:** усі команди `docker` працюють з Podman, якщо замінити `docker` на `podman`, а `docker-compose` на `podman-compose`.
|
||||
|
||||
## Швидкий старт
|
||||
|
||||
> [!TIP]
|
||||
> BentoPDF постачається у двох збірках:
|
||||
>
|
||||
> - **Self-Hosted build**: `ghcr.io/alam00000/bentopdf-simple:latest`. Усі PDF-інструменти, **без** маркетингових елементів (без головного екрана, FAQ, відгуків і футера). Використовуйте для внутрішніх розгортань у командах і організаціях. Це **не** урізана "lite"-версія.
|
||||
> - **Commercial build**: `ghcr.io/alam00000/bentopdf:latest`. Повний маркетинговий сайт, який використовує сам bentopdf.com і власники комерційної ліцензії для публічних розгортань.
|
||||
>
|
||||
> Якщо вагаєтеся, беріть Self-Hosted build.
|
||||
|
||||
```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 worker | _(порожньо; CDN Tesseract.js за замовчуванням)_ |
|
||||
| `VITE_TESSERACT_CORE_URL` | Каталог основного runtime 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 автоматично дозволяє ці origin, додаткове налаштування не потрібне.
|
||||
- Для 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 під час _роботи_ через сторінку «Додаткові налаштування» у застосунку, ці origin **не** потрапляють до CSP, і браузер блокуватиме запити до них. Це також стосується іншої версії того самого пакета на `cdn.jsdelivr.net`, бо дозволено лише версію, задану під час збірки. Налаштування під час роботи призначене для експериментів; для постійних власних URL задайте відповідний build arg `VITE_*`.
|
||||
- Ізольовані розгортання, що перевизначають усі три значення `VITE_WASM_*_URL`, також позбуваються цих папок пакетів `cdn.jsdelivr.net` у CSP (кожне типове значення замінюється, а не доповнюється). Типові значення Tesseract OCR на `cdn.jsdelivr.net` залишаються в політиці, доки ви також не задасте три значення `VITE_TESSERACT_*`. Так само задання `VITE_CORS_PROXY_URL` замінює публічне типове значення `bentopdf-cors-proxy.bentopdf.workers.dev`.
|
||||
|
||||
CSP містить `'unsafe-eval'` у `script-src`, тому що runtime LibreOffice WASM (використовується інструментами конвертації Word/Excel/PowerPoint) і бібліотека зображень wasm-vips (використовується для PDF у TIFF) компілюють внутрішній диспетчерський код через `new Function()`. Його видалення зламало б ці інструменти. Якщо ви збираєте в `SIMPLE_MODE` (без LibreOffice) і не потребуєте PDF у TIFF, можна вручну відредагувати згенерований `security-headers.conf`, прибравши `'unsafe-eval'` для суворішої політики.
|
||||
|
||||
Для OCR залиште змінні `VITE_TESSERACT_*` порожніми, щоб використовувати типові онлайн-ресурси, або задайте всі три разом для самостійного/офлайн OCR. Часткові перевизначення OCR відхиляються, бо worker, основний runtime і каталог 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: під час збірки (build arg 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-редактора (наприклад, редагування (redaction), анотації, форми), не вимикаючи весь інструмент редактора. Додайте `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 build або власних повних збірок.
|
||||
|
||||
### Власні URL WASM (ізольовані / самостійно розгорнуті середовища)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> URL WASM вшиваються в JavaScript **під час збірки**. Файли WASM завантажує **браузер користувача** під час роботи: Docker не завантажує їх під час збірки. Для ізольованих мереж потрібно розмістити файли WASM на внутрішньому сервері, доступному для браузерів.
|
||||
|
||||
**Повний процес для ізольованого середовища:**
|
||||
|
||||
```bash
|
||||
# 1. On a machine WITH internet — download WASM packages
|
||||
bash scripts/prepare-airgap.sh --list-ocr-languages
|
||||
bash scripts/prepare-airgap.sh --search-ocr-language german
|
||||
|
||||
# 2. Download WASM/OCR packages
|
||||
npm pack @bentopdf/pymupdf-wasm@0.11.14
|
||||
npm pack @bentopdf/gs-wasm
|
||||
npm pack coherentpdf
|
||||
npm pack tesseract.js@7.0.0
|
||||
npm pack tesseract.js-core@7.0.0
|
||||
mkdir -p tesseract-langdata
|
||||
curl -fsSL https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz -o tesseract-langdata/eng.traineddata.gz
|
||||
mkdir -p ocr-fonts
|
||||
curl -fsSL https://raw.githack.com/googlefonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf -o ocr-fonts/NotoSans-Regular.ttf
|
||||
|
||||
# 3. Build the image with your internal server URLs
|
||||
docker build \
|
||||
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
|
||||
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
|
||||
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
|
||||
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
|
||||
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
|
||||
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
|
||||
--build-arg VITE_TESSERACT_AVAILABLE_LANGUAGES=eng,deu \
|
||||
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
|
||||
-t bentopdf .
|
||||
|
||||
# 4. Export the image
|
||||
docker save bentopdf -o bentopdf.tar
|
||||
|
||||
# 5. Transfer bentopdf.tar + the .tgz packages + tesseract-langdata/ + ocr-fonts/ into the air-gapped network
|
||||
|
||||
# 6. Inside the air-gapped network — load and run
|
||||
docker load -i bentopdf.tar
|
||||
|
||||
# Extract WASM packages to your internal web server
|
||||
mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf /var/www/wasm/ocr/core /var/www/wasm/ocr/lang-data /var/www/wasm/ocr/fonts
|
||||
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1
|
||||
tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1
|
||||
tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1
|
||||
TEMP_TESS=$(mktemp -d)
|
||||
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
|
||||
cp "$TEMP_TESS/package/dist/worker.min.js" /var/www/wasm/ocr/worker.min.js
|
||||
rm -rf "$TEMP_TESS"
|
||||
tar xzf tesseract.js-core-7.0.0.tgz -C /var/www/wasm/ocr/core --strip-components=1
|
||||
cp ./tesseract-langdata/*.traineddata.gz /var/www/wasm/ocr/lang-data/
|
||||
cp ./ocr-fonts/* /var/www/wasm/ocr/fonts/
|
||||
|
||||
# Run BentoPDF
|
||||
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
|
||||
```
|
||||
|
||||
Для `--ocr-languages` використовуйте коди, які виводить `bash scripts/prepare-airgap.sh --list-ocr-languages`, або шукайте за назвою через `bash scripts/prepare-airgap.sh --search-ocr-language <term>`. Коли ви збираєте з обмеженим набором мов OCR, передайте ті самі коди в `VITE_TESSERACT_AVAILABLE_LANGUAGES`, щоб застосунок показував лише вбудовані мови. Для повного офлайн-виводу OCR також розмістіть вбудований каталог `ocr-fonts/` і вкажіть на нього `VITE_OCR_FONT_BASE_URL`.
|
||||
|
||||
Задайте змінній порожній рядок, щоб вимкнути відповідний модуль (користувачі мають налаштувати його вручну через «Додаткові налаштування»).
|
||||
|
||||
## Власний порт
|
||||
|
||||
За замовчуванням BentoPDF слухає порт `8080` усередині контейнера. Щоб його змінити, задайте змінну середовища `PORT` під час запуску:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
| Змінна | Опис | Типове значення |
|
||||
| ------ | ------------------------------------ | --------------- |
|
||||
| `PORT` | Порт, який слухає Nginx у контейнері | `8080` |
|
||||
|
||||
Це працює і зі стандартним, і з nonroot Dockerfile.
|
||||
|
||||
## Власний ID користувача (PUID/PGID)
|
||||
|
||||
Для середовищ, які вимагають запуску від конкретного користувача без прав root (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 постачається у двох збірках:
|
||||
>
|
||||
> - **Збірка для самостійного розміщення** (Self-Hosted): `ghcr.io/alam00000/bentopdf-simple:latest`. Усі PDF-інструменти, **без** маркетингового оформлення (без hero-блоку, FAQ, відгуків і футера). Використовуйте її для внутрішніх розгортань у команді чи організації. Це **не** урізана "lite"-версія.
|
||||
> - **Комерційна збірка** (Commercial): `ghcr.io/alam00000/bentopdf:latest`. Повний маркетинговий сайт, який використовує сам bentopdf.com і власники комерційної ліцензії з публічними розгортаннями.
|
||||
>
|
||||
> Якщо вагаєтеся, беріть збірку для самостійного розміщення.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Перетворення офісних файлів потребує `SharedArrayBuffer`, а отже застосунок має бути і ізольованим між походженнями (cross-origin isolated), і обслуговуватися з безпечного контексту. Офіційний образ уже надсилає потрібні заголовки COOP/COEP, але браузери все одно вимикають `SharedArrayBuffer` на звичайних HTTP-адресах локальної мережі, як-от `http://192.168.x.x`.
|
||||
>
|
||||
> Використовуйте `http://localhost` лише для тестування на одному пристрої. Якщо користувачі відкривають BentoPDF через LAN 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](/uk/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 або в комерційному публічному розгортанні. **Це не урізана чи "lite"-версія.**
|
||||
|
||||
**Що приховує збірка для самостійного розміщення** (лише косметика, жодні функції PDF не вилучаються):
|
||||
|
||||
- Панель навігації, hero-секцію, секцію переваг, FAQ, відгуки, футер
|
||||
- Змінює заголовок сторінки на "PDF інструменти"
|
||||
|
||||
**Що зберігає збірка для самостійного розміщення** (усе, що справді працює з PDF):
|
||||
|
||||
- Усі PDF-інструменти (об’єднання, розділення, редагування, підпис, OCR, перетворення офісних файлів тощо)
|
||||
- Підтримку власного брендингу, усі налаштування часу збірки та виконання
|
||||
|
||||
Комерційна збірка (`ghcr.io/alam00000/bentopdf:latest`) живить сам bentopdf.com і використовується власниками комерційної ліцензії з публічними розгортаннями. Вона додає hero-блок, 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. Вона містить повний маркетинговий сайт (hero, переваги, 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`: відкрийте будь-який інструмент і подивіться на URL (наприклад, `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](/uk/self-hosting/docker#вимкнення-функціи-редактора).
|
||||
|
||||
## Посібники з розгортання
|
||||
|
||||
Виберіть свою платформу:
|
||||
|
||||
- [Vercel](/uk/self-hosting/vercel)
|
||||
- [Netlify](/uk/self-hosting/netlify)
|
||||
- [Cloudflare Pages](/uk/self-hosting/cloudflare)
|
||||
- [AWS S3 + CloudFront](/uk/self-hosting/aws)
|
||||
- [Hostinger](/uk/self-hosting/hostinger)
|
||||
- [Nginx](/uk/self-hosting/nginx)
|
||||
- [Apache](/uk/self-hosting/apache)
|
||||
- [Docker](/uk/self-hosting/docker)
|
||||
- [Kubernetes](/uk/self-hosting/kubernetes)
|
||||
- [CORS-проксі](/uk/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`, який браузер вмикає лише тоді, коли сторінка **ізольована між походженнями** І обслуговується з **безпечного контексту**. Тобто мають виконуватися дві умови:
|
||||
|
||||
1. Кожна відповідь містить обидва заголовки:
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
2. Сторінка обслуговується через `https://...` або `http://localhost`. Звичайний HTTP на LAN 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 →](/uk/self-hosting/nginx#не-працює-перетворення-word-odt-excel-у-pdf)
|
||||
- [Apache →](/uk/self-hosting/apache#конвертація-word-odt-excel-у-pdf-не-працює)
|
||||
- [AWS S3 + CloudFront →](/uk/self-hosting/aws#крок-3b-політика-заголовків-відповіді-обов-язково-для-libreoffice-wasm)
|
||||
- [Cloudflare Pages →](/uk/self-hosting/cloudflare#фаил-конфігураціі) (файл `_headers`)
|
||||
- [Netlify →](/uk/self-hosting/netlify#перетворення-word-odt-excel-у-pdf-зависає-на-55) (`netlify.toml`)
|
||||
- [Vercel →](/uk/self-hosting/vercel#не-працює-перетворення-word-odt-excel-у-pdf) (`vercel.json`)
|
||||
- [Hostinger →](/uk/self-hosting/hostinger#інструменти-libreoffice-не-працюють) (`.htaccess`)
|
||||
- [Kubernetes →](/uk/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` чи іншому не-loopback HTTP-походженні, налаштуйте 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 →](/uk/self-hosting/nginx#sign-pdf-або-form-filler-показує-порожніи-переглядач-помилка-mime-для-mjs)
|
||||
- [Apache →](/uk/self-hosting/apache#sign-pdf-або-form-filler-показує-порожніи-переглядач-помилка-mime-для-mjs)
|
||||
- [AWS S3 + CloudFront →](/uk/self-hosting/aws#крок-2-зберіть-і-завантажте) (дивіться `aws s3 cp ... --include "*.mjs"`)
|
||||
- [Kubernetes →](/uk/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 мають обслуговуватися з **того самого походження**, що й застосунок 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 / Commercial)
|
||||
- Завантаження файлів WASM під час виконання, а не вбудовування у вихідний код
|
||||
:::
|
||||
|
||||
## Системні вимоги
|
||||
|
||||
| Вимога | Мінімум |
|
||||
| ------- | ------------------------------- |
|
||||
| Сховище | ~100 МБ (ядро без модулів AGPL) |
|
||||
| RAM | 512 МБ |
|
||||
| CPU | Будь-який сучасний процесор |
|
||||
|
||||
::: tip
|
||||
BentoPDF є статичним сайтом: база даних чи серверна частина не потрібні!
|
||||
:::
|
||||
@@ -0,0 +1,211 @@
|
||||
# Розгортання в Kubernetes
|
||||
|
||||
Kubernetes може бути надмірним для статичного сайту, але він чудово підходить, якщо ви вже стандартизувалися на Helm + GitOps.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Обов’язкові заголовки для перетворення офісних файлів**
|
||||
>
|
||||
> Інструменти на основі LibreOffice (перетворення Word, Excel, PowerPoint) потребують таких HTTP-заголовків для підтримки `SharedArrayBuffer`:
|
||||
>
|
||||
> - `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 API `Gateway` + `HTTPRoute`.
|
||||
|
||||
Приклад (оператор 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 **передають заголовки відповіді вихідного сервера без змін**.
|
||||
|
||||
### Що може це зламати
|
||||
|
||||
- Політика контролера/межі, що **перевизначає** або **видаляє** заголовки відповіді
|
||||
- Проміжне ПЗ "security headers", яке встановлює інші значення COOP/COEP
|
||||
|
||||
### Як перевірити
|
||||
|
||||
Виконайте це для свого публічного endpoint:
|
||||
|
||||
```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`. Перевірте у DevTools → вкладка Network: невдалий запит `.mjs` тепер має показувати `content-type: application/javascript`.
|
||||
|
||||
### Якщо ви використовуєте Gateway API і хочете примусово додати заголовки
|
||||
|
||||
Gateway API підтримує фільтр `ResponseHeaderModifier`. Його можна підключити в `httpRoute.rules[*].filters`:
|
||||
|
||||
```yaml
|
||||
httpRoute:
|
||||
enabled: true
|
||||
hostnames: [pdf.example.com]
|
||||
parentRefs:
|
||||
- name: bento-tunnel
|
||||
namespace: misc
|
||||
sectionName: http
|
||||
rules:
|
||||
- matches:
|
||||
- path: { type: PathPrefix, value: / }
|
||||
filters:
|
||||
- type: ResponseHeaderModifier
|
||||
responseHeaderModifier:
|
||||
set:
|
||||
- name: Cross-Origin-Opener-Policy
|
||||
value: same-origin
|
||||
- name: Cross-Origin-Embedder-Policy
|
||||
value: require-corp
|
||||
```
|
||||
|
||||
Підтримка окремих фільтрів залежить від вашого контролера Gateway; якщо фільтр ігнорується, додайте заголовки на рівні межі/контролера.
|
||||
|
||||
## Вимкнення окремих інструментів
|
||||
|
||||
Використайте ConfigMap, щоб вимкнути інструменти під час виконання без перебудови образу:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bentopdf-config
|
||||
namespace: bentopdf
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
|
||||
}
|
||||
```
|
||||
|
||||
Змонтуйте його в каталог, що обслуговується:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
containers:
|
||||
- name: bentopdf
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /usr/share/nginx/html/config.json
|
||||
subPath: config.json
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: bentopdf-config
|
||||
```
|
||||
|
||||
Ідентифікатори інструментів є URL сторінки без `.html`: відкрийте будь-який інструмент і подивіться на URL (наприклад, `edit-pdf`, `merge-pdf`, `compress-pdf`). Вимкнені інструменти приховуються на головній сторінці, у пошуку, ярликах, конструкторі робочих процесів і при прямому доступі за URL. Повний перелік параметрів дивіться в [посібнику з Docker](/uk/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` та інші LAN-адреси зазвичай потребують HTTPS. Зверніть увагу, що директиви nginx `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 Великі файли
|
||||
Серверні функції 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