mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-12 01:33:36 +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 @@
|
||||
# Wdrożenie z Apache
|
||||
|
||||
Hostuj BentoPDF za pomocą serwera Apache HTTP Server.
|
||||
|
||||
## Wymagania wstępne
|
||||
|
||||
- Apache 2.4+
|
||||
- Włączony mod_rewrite
|
||||
- Certyfikat SSL (zalecany)
|
||||
|
||||
## Krok 1: Zbuduj projekt
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Aby dostosować branding, ustaw zmienne środowiskowe przed budowaniem:
|
||||
|
||||
```bash
|
||||
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
```
|
||||
|
||||
## Krok 2: Skopiuj pliki
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/bentopdf
|
||||
sudo cp -r dist/* /var/www/bentopdf/
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
```
|
||||
|
||||
## Krok 3: Konfiguracja Apache
|
||||
|
||||
Utwórz `/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>
|
||||
```
|
||||
|
||||
## Krok 4: .htaccess do routingu
|
||||
|
||||
Utwórz `/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
|
||||
```
|
||||
|
||||
## Krok 5: Włącz wymagane moduły
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
sudo a2enmod ssl
|
||||
sudo a2enmod headers
|
||||
sudo a2enmod expires
|
||||
sudo a2enmod deflate
|
||||
```
|
||||
|
||||
## Krok 6: Włącz witrynę
|
||||
|
||||
```bash
|
||||
sudo a2ensite bentopdf.conf
|
||||
sudo apache2ctl configtest
|
||||
sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
## Wdrożenie w podkatalogu
|
||||
|
||||
Aby hostować pod adresem `/pdf/`:
|
||||
|
||||
1. Zbuduj z bazowym adresem URL:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf/ npm run build
|
||||
```
|
||||
|
||||
2. Zaktualizuj `.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
|
||||
```
|
||||
|
||||
## Rozwiązywanie problemów
|
||||
|
||||
### Błędy 404 dla WASM
|
||||
|
||||
Upewnij się, że typ MIME jest skonfigurowany:
|
||||
|
||||
```apache
|
||||
AddType application/wasm .wasm
|
||||
```
|
||||
|
||||
### Podpisywanie PDF lub wypełnianie formularzy pokazuje pustą przeglądarkę (błąd MIME `.mjs`)
|
||||
|
||||
Jeśli konsola przeglądarki pokazuje:
|
||||
|
||||
```
|
||||
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.
|
||||
```
|
||||
|
||||
…a żądanie, które się nie powiodło, kończy się na `.mjs`, w konfiguracji Apache brakuje mapowania `.mjs → application/javascript`. Dołączona przeglądarka PDF zawiera moduły ES z rozszerzeniem `.mjs`, a standardowy plik `mime.types` w Apache domyślnie ich nie obejmuje.
|
||||
|
||||
Dodaj do VirtualHost lub `.htaccess`:
|
||||
|
||||
```apache
|
||||
AddType application/javascript .mjs
|
||||
```
|
||||
|
||||
Następnie przeładuj Apache:
|
||||
|
||||
```bash
|
||||
sudo apache2ctl configtest && sudo systemctl reload apache2
|
||||
```
|
||||
|
||||
Aby to potwierdzić: w narzędziach deweloperskich przeglądarki → karta Network znajdź nieudane żądanie `.mjs` i sprawdź nagłówek odpowiedzi `Content-Type`. Powinien mieć wartość `application/javascript`, a nie `application/octet-stream`.
|
||||
|
||||
### Rewrite nie działa
|
||||
|
||||
Sprawdź, czy mod_rewrite jest włączony:
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
```
|
||||
|
||||
### Konwersja Word/ODT/Excel do PDF nie działa
|
||||
|
||||
LibreOffice WASM wymaga `SharedArrayBuffer`, a do tego potrzebne są te nagłówki:
|
||||
|
||||
```apache
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
```
|
||||
|
||||
Potrzebny jest też bezpieczny kontekst. `http://localhost` działa do testów lokalnych, ale `http://192.168.x.x` i inne adresy LAN zwykle wymagają HTTPS. Jeśli nagłówki są obecne, a `window.crossOriginIsolated` nadal ma wartość `false`, sprawdź, czy strona nie jest otwierana przez zwykłe HTTP z adresu innego niż pętla zwrotna.
|
||||
|
||||
Wstępnie skompresowane pliki `.wasm.gz` i `.data.gz` wymagają też poprawnego `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>
|
||||
```
|
||||
|
||||
Upewnij się, że `mod_headers` jest włączony: `sudo a2enmod headers`
|
||||
|
||||
### Odmowa dostępu
|
||||
|
||||
```bash
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
sudo chmod -R 755 /var/www/bentopdf
|
||||
```
|
||||
@@ -0,0 +1,200 @@
|
||||
# Wdrożenie na AWS S3 + CloudFront
|
||||
|
||||
Hostuj BentoPDF na AWS, aby uzyskać maksymalną kontrolę i skalowalność.
|
||||
|
||||
## Architektura
|
||||
|
||||
```
|
||||
User → CloudFront (CDN) → S3 (Static Files)
|
||||
```
|
||||
|
||||
## Krok 1: Utwórz bucket 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
|
||||
```
|
||||
|
||||
## Krok 2: Zbuduj i wyślij
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Krok 3: Utwórz dystrybucję CloudFront
|
||||
|
||||
```bash
|
||||
aws cloudfront create-distribution \
|
||||
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
|
||||
--default-root-object index.html
|
||||
```
|
||||
|
||||
Albo użyj konsoli AWS:
|
||||
|
||||
1. Przejdź do CloudFront → Create distribution
|
||||
2. Origin domain: wybierz swój bucket S3
|
||||
3. Włącz „Origin Access Control”
|
||||
4. Default root object: `index.html`
|
||||
5. Utwórz dystrybucję
|
||||
|
||||
## Krok 3b: Polityka nagłówków odpowiedzi (wymagana dla LibreOffice WASM)
|
||||
|
||||
Konwersje oparte na LibreOffice (Word, Excel, PowerPoint do PDF) wymagają `SharedArrayBuffer`, a to wymaga określonych nagłówków odpowiedzi. Utwórz politykę nagłówków odpowiedzi CloudFront (Response Headers Policy):
|
||||
|
||||
1. Przejdź do CloudFront → Policies → Response headers
|
||||
2. Utwórz własną politykę z tymi nagłówkami:
|
||||
|
||||
| Nagłówek | Wartość |
|
||||
| ------------------------------ | -------------- |
|
||||
| `Cross-Origin-Embedder-Policy` | `require-corp` |
|
||||
| `Cross-Origin-Opener-Policy` | `same-origin` |
|
||||
| `Cross-Origin-Resource-Policy` | `cross-origin` |
|
||||
|
||||
3. Dołącz politykę do domyślnego zachowania pamięci podręcznej (default cache behavior) swojej dystrybucji
|
||||
|
||||
Albo przez 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}
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Krok 3c: Metadane S3 dla wstępnie skompresowanych plików WASM
|
||||
|
||||
Pliki LibreOffice WASM są wstępnie skompresowane (`.wasm.gz`, `.data.gz`). Ustaw poprawny Content-Type i Content-Encoding, aby przeglądarki je rozpakowywały:
|
||||
|
||||
```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 Ważne
|
||||
Bez polityki nagłówków odpowiedzi `SharedArrayBuffer` jest niedostępny, a konwersje LibreOffice WASM zawieszą się na ok. 55%. Bez poprawnego Content-Encoding dla plików `.gz` przeglądarka otrzymuje surowe bajty gzip i kompilacja WASM kończy się błędem.
|
||||
:::
|
||||
|
||||
## Krok 4: Polityka bucketu S3
|
||||
|
||||
Zezwól CloudFront na dostęp do bucketu:
|
||||
|
||||
```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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Krok 5: Własne strony błędów
|
||||
|
||||
Skonfiguruj 404 tak, aby zwracał `index.html` na potrzeby routingu SPA:
|
||||
|
||||
1. CloudFront → Error pages
|
||||
2. Utwórz własną odpowiedź błędu:
|
||||
- HTTP error code: 404
|
||||
- Response page path: `/index.html`
|
||||
- HTTP response code: 200
|
||||
|
||||
## Szacunkowe koszty
|
||||
|
||||
| Zasób | Szacunkowy koszt |
|
||||
| --------------------------- | ---------------- |
|
||||
| Magazyn S3 (~500MB) | ~$0.01/mies. |
|
||||
| CloudFront (1TB transferu) | ~$85/mies. |
|
||||
| CloudFront (10GB transferu) | ~$0.85/mies. |
|
||||
|
||||
::: tip
|
||||
Użyj S3 Intelligent Tiering, aby obniżyć koszty plików rzadko używanych.
|
||||
:::
|
||||
|
||||
## Automatyzacja z 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 @@
|
||||
# Wdrożenie na Cloudflare Pages
|
||||
|
||||
[Cloudflare Pages](https://pages.cloudflare.com) oferuje szybki, globalny hosting stron statycznych z nielimitowaną przepustowością.
|
||||
|
||||
## Szybkie wdrożenie
|
||||
|
||||
1. Przejdź do [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
|
||||
2. Kliknij „Create a project”
|
||||
3. Połącz swoje repozytorium GitHub
|
||||
|
||||
## Konfiguracja budowania
|
||||
|
||||
| Ustawienie | Wartość |
|
||||
| ------------------- | --------------- |
|
||||
| Framework preset | Brak |
|
||||
| Polecenie budowania | `npm run build` |
|
||||
| Katalog wyjściowy | `dist` |
|
||||
| Katalog główny | `/` |
|
||||
|
||||
## Zmienne środowiskowe
|
||||
|
||||
Dodaj je w Settings → Environment variables:
|
||||
|
||||
| Zmienna | Wartość |
|
||||
| ----------------------- | ------------------------------------------------- |
|
||||
| `NODE_VERSION` | `18` |
|
||||
| `SIMPLE_MODE` | `false` (opcjonalnie) |
|
||||
| `VITE_BRAND_NAME` | Własna nazwa marki (opcjonalnie) |
|
||||
| `VITE_BRAND_LOGO` | Ścieżka logo względem `public/` (opcjonalnie) |
|
||||
| `VITE_FOOTER_TEXT` | Własny tekst stopki/praw autorskich (opcjonalnie) |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Domyślny język interfejsu, np. `fr` (opcjonalnie) |
|
||||
|
||||
## Plik konfiguracji
|
||||
|
||||
Utwórz `_headers` w folderze `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 Ważne
|
||||
Nagłówki `Cross-Origin-Embedder-Policy` i `Cross-Origin-Opener-Policy` są wymagane do konwersji Word/ODT/Excel/PowerPoint do PDF. Bez nich `SharedArrayBuffer` jest niedostępny, a silnik LibreOffice WASM nie zainicjuje się.
|
||||
:::
|
||||
|
||||
Utwórz `_redirects` na potrzeby routingu SPA:
|
||||
|
||||
```
|
||||
/* /index.html 200
|
||||
```
|
||||
|
||||
## Własna domena
|
||||
|
||||
1. Przejdź do swojego projektu Pages
|
||||
2. Kliknij „Custom domains”
|
||||
3. Dodaj swoją domenę
|
||||
4. Cloudflare automatycznie skonfiguruje DNS, jeśli domena jest w Cloudflare
|
||||
|
||||
## Zalety
|
||||
|
||||
- **Darmowa, nielimitowana przepustowość**
|
||||
- **Globalny CDN** z ponad 300 lokalizacjami brzegowymi
|
||||
- **Automatyczne HTTPS**
|
||||
- **Wdrożenia podglądowe** dla pull requestów
|
||||
- **Szybkie budowanie**
|
||||
|
||||
## Rozwiązywanie problemów
|
||||
|
||||
### Wysyłanie dużych plików
|
||||
|
||||
Cloudflare Pages obsługuje pliki do 25 MB. Moduły WASM powinny się zmieścić, ale jeśli natrafisz na limity, rozważ:
|
||||
|
||||
```bash
|
||||
# Split large files during build
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Limity rozmiaru Workera
|
||||
|
||||
Jeśli używasz Cloudflare Workers do zaawansowanego routingu, pamiętaj o limicie 1 MB w planach darmowych.
|
||||
|
||||
## Worker proxy CORS (dla podpisów cyfrowych)
|
||||
|
||||
Narzędzie podpisu cyfrowego wymaga proxy CORS do pobierania łańcuchów certyfikatów. Wdróż dołączony worker:
|
||||
|
||||
```bash
|
||||
cd cloudflare
|
||||
npx wrangler login
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### Funkcje bezpieczeństwa
|
||||
|
||||
| Funkcja | Opis |
|
||||
| ------------------------- | ------------------------------------- |
|
||||
| **Ograniczenia URL** | Dozwolone tylko adresy certyfikatów |
|
||||
| **Limit rozmiaru pliku** | Maks. 10MB na żądanie |
|
||||
| **Limit żądań** | 60 żądań/IP/min (wymaga KV) |
|
||||
| **Blokada prywatnych IP** | Blokuje localhost i adresy wewnętrzne |
|
||||
|
||||
### Włącz limit żądań
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Zbuduj z adresem URL proxy
|
||||
|
||||
```bash
|
||||
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
|
||||
```
|
||||
|
||||
Albo z Dockerem:
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
> **Uwaga:** Konfigurację podpisu HMAC znajdziesz w [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required).
|
||||
@@ -0,0 +1,180 @@
|
||||
# Proxy CORS do pobierania certyfikatów
|
||||
|
||||
Narzędzie podpisu cyfrowego używa proxy CORS do pobierania certyfikatów wystawców z zewnętrznych urzędów certyfikacji (CA). Jest to konieczne, ponieważ wiele serwerów CA nie dołącza nagłówków CORS do odpowiedzi, co uniemożliwia bezpośrednie pobieranie z poziomu przeglądarki.
|
||||
|
||||
Ponadto wiele serwerów CA udostępnia certyfikaty przez zwykłe HTTP. Gdy Twoja instancja BentoPDF jest hostowana przez HTTPS, przeglądarki blokują takie żądania HTTP (zasada mixed content). Proxy CORS rozwiązuje oba problemy, kierując żądania przez punkt końcowy HTTPS z odpowiednimi nagłówkami.
|
||||
|
||||
## Jak to działa
|
||||
|
||||
Podczas podpisywania pliku PDF certyfikatem:
|
||||
|
||||
1. Biblioteka `zgapdfsigner` próbuje zbudować pełny łańcuch certyfikatów
|
||||
2. Pobiera certyfikaty wystawców z adresów URL zapisanych w rozszerzeniu AIA (Authority Information Access) Twojego certyfikatu
|
||||
3. Te żądania są kierowane przez proxy CORS, które dodaje wymagane nagłówki `Access-Control-Allow-Origin`
|
||||
4. Proxy zwraca dane certyfikatu do przeglądarki
|
||||
|
||||
## Samodzielny hosting proxy CORS
|
||||
|
||||
Jeśli hostujesz BentoPDF samodzielnie, musisz wdrożyć własne proxy CORS, aby podpisy cyfrowe działały z certyfikatami wymagającymi pobrania łańcucha.
|
||||
|
||||
### Opcja 1: Cloudflare Workers (zalecana)
|
||||
|
||||
1. **Zainstaluj Wrangler CLI**:
|
||||
|
||||
```bash
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
2. **Zaloguj się do Cloudflare**:
|
||||
|
||||
```bash
|
||||
wrangler login
|
||||
```
|
||||
|
||||
3. **Sklonuj BentoPDF i zaktualizuj dozwolone źródła**:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf/cloudflare
|
||||
```
|
||||
|
||||
Otwórz `cors-proxy-worker.js` i zmień tablicę `ALLOWED_ORIGINS` na swoją domenę:
|
||||
|
||||
```js
|
||||
const ALLOWED_ORIGINS = [
|
||||
'https://your-domain.com',
|
||||
'https://www.your-domain.com',
|
||||
];
|
||||
```
|
||||
|
||||
::: warning Ważne
|
||||
Bez tej zmiany proxy odrzuci wszystkie żądania z Twojej witryny z błędem **403 Forbidden**. Domyślnie dozwolone są tylko żądania z `bentopdf.com`.
|
||||
:::
|
||||
|
||||
4. **Wdróż proxy**:
|
||||
|
||||
```bash
|
||||
wrangler deploy
|
||||
```
|
||||
|
||||
Zapisz adres URL swojego workera (np. `https://bentopdf-cors-proxy.your-subdomain.workers.dev`).
|
||||
|
||||
5. **Zbuduj BentoPDF ponownie z adresem URL proxy**:
|
||||
|
||||
Jeśli używasz Dockera:
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
Jeśli budujesz ze źródeł:
|
||||
|
||||
```bash
|
||||
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
|
||||
```
|
||||
|
||||
### Opcja 2: Własne proxy backendowe
|
||||
|
||||
Możesz też utworzyć własny punkt końcowy proxy. Wymagania:
|
||||
|
||||
1. Przyjmuj żądania GET z parametrem zapytania `url`
|
||||
2. Pobieraj adres URL ze swojego serwera (po stronie serwera nie ma ograniczeń CORS)
|
||||
3. Zwracaj odpowiedź z tymi nagłówkami:
|
||||
- `Access-Control-Allow-Origin: https://your-domain.com`
|
||||
- `Access-Control-Allow-Methods: GET, OPTIONS`
|
||||
- `X-Content-Type-Options: nosniff`
|
||||
|
||||
Przykładowa implementacja w 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' });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Kwestie bezpieczeństwa
|
||||
|
||||
Dołączony Cloudflare Worker ma kilka zabezpieczeń:
|
||||
|
||||
| Funkcja | Opis |
|
||||
| ------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| **Walidacja źródła** | Zezwala tylko na żądania z domen z listy `ALLOWED_ORIGINS` |
|
||||
| **Ograniczenia URL** | Zezwala tylko na adresy certyfikatów (`.crt`, `.cer`, `.pem`, `/certs/`, `/ocsp`, `/crl`) |
|
||||
| **Blokada prywatnych IP** | Blokuje prywatne zakresy IPv4/IPv6, link-local, loopback, adresy dziesiętne i metadane chmury |
|
||||
| **Bezpieczeństwo Content-Type** | Zwraca tylko bezpieczne typy MIME certyfikatów, blokuje wstrzykiwanie content-type z upstreamu |
|
||||
| **Limit rozmiaru pliku** | Strumieniuje odpowiedź z limitem 10MB, przerywa pobieranie w trakcie po jego przekroczeniu |
|
||||
| **Limit żądań** | 60 żądań na minutę na IP lub IPv6 /64 (wymaga KV, przy błędach KV przepuszcza ruch) |
|
||||
| **Podpisy HMAC** | Opcjonalne podpisywanie po stronie klienta (zniechęca do przypadkowych nadużyć) |
|
||||
|
||||
### DNS rebinding (hosting poza Cloudflare)
|
||||
|
||||
Worker odrzuca każdy adres URL, który wskazuje na prywatny lub wewnętrzny adres IP. Nie może użyć stałej listy dozwolonych hostów, ponieważ adresy łańcucha certyfikatów wskazują na dowolne CA, które wystawiło certyfikat (FNMT, korporacyjne CA itd.), więc miejsca docelowe nie są znane z góry. Zostaje małe okno na rebinding: osoba zarządzająca DNS własnej domeny mogłaby podać publiczny adres IP przy sprawdzaniu bezpieczeństwa, a chwilę później prywatny przy właściwym żądaniu.
|
||||
|
||||
W Cloudflare nie da się tego wykorzystać: Workers nie mają dostępu do prywatnych adresów IP ani do punktu metadanych `169.254.169.254`, więc rebinding trafia w pustkę. Nie dawaj tylko workerowi drogi do środka: nie podłączaj Tunnel, Service ani powiązania z siecią prywatną (potrzebuje wyłącznie `RATE_LIMIT_KV`).
|
||||
|
||||
Jeśli uruchamiasz to poza Cloudflare (workerd, Node itp.) wewnątrz VPC, środowisko _może_ docierać do adresów wewnętrznych. Postaw przed nim zaporę ruchu wychodzącego i zablokuj zakresy prywatne: `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` oraz ich odpowiedniki IPv6. Kod aplikacji nie wymusi tego poza Cloudflare.
|
||||
|
||||
## Wyłączanie proxy
|
||||
|
||||
Jeśli nie chcesz używać proxy CORS, ustaw zmienną środowiskową na pusty ciąg:
|
||||
|
||||
```
|
||||
VITE_CORS_PROXY_URL=
|
||||
```
|
||||
|
||||
**Uwaga**: Bez proxy podpisywanie certyfikatami wymagającymi zewnętrznego pobrania łańcucha (jak FNMT lub niektóre korporacyjne CA) zakończy się błędem „Failed to fetch”.
|
||||
|
||||
## Rozwiązywanie problemów
|
||||
|
||||
### "Signing error: TypeError: Failed to fetch"
|
||||
|
||||
Zwykle oznacza to jedno z poniższych:
|
||||
|
||||
1. **Nie skonfigurowano proxy CORS**: ustaw `VITE_CORS_PROXY_URL` i zbuduj ponownie
|
||||
2. **Zablokowana zawartość mieszana**: Twoja witryna używa HTTPS, a adres wystawcy certyfikatu to HTTP. Proxy CORS to rozwiązuje.
|
||||
3. **Proxy CORS odrzuca Twoje źródło**: sprawdź, czy Twoja domena jest w tablicy `ALLOWED_ORIGINS` w `cors-proxy-worker.js`
|
||||
|
||||
### Błąd "403 Forbidden" z proxy
|
||||
|
||||
Twojej domeny nie ma na liście `ALLOWED_ORIGINS`. Edytuj `cors-proxy-worker.js`:
|
||||
|
||||
```js
|
||||
const ALLOWED_ORIGINS = ['https://your-domain.com'];
|
||||
```
|
||||
|
||||
Następnie wdróż ponownie: `npx wrangler deploy`
|
||||
|
||||
### Testowanie proxy
|
||||
|
||||
```bash
|
||||
curl -H "Origin: https://your-domain.com" \
|
||||
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
|
||||
```
|
||||
|
||||
### Certyfikaty działające bez proxy
|
||||
|
||||
Niektóre certyfikaty zawierają pełny łańcuch w pliku P12/PFX i nie wymagają zewnętrznego pobierania:
|
||||
|
||||
- Certyfikaty z podpisem własnym
|
||||
- Niektóre komercyjne CA dołączające certyfikaty pośrednie
|
||||
- Certyfikaty, w których ręcznie złożyłeś pełny łańcuch
|
||||
@@ -0,0 +1,622 @@
|
||||
# Wdrożenie z Dockerem / Podmanem
|
||||
|
||||
Najłatwiejszy sposób samodzielnego hostingu BentoPDF w środowisku produkcyjnym.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Wymagane nagłówki do konwersji plików Office**
|
||||
>
|
||||
> Narzędzia oparte na LibreOffice (konwersja Word, Excel, PowerPoint) wymagają tych nagłówków HTTP do obsługi `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> Strona musi być też serwowana z bezpiecznego kontekstu. `http://localhost` działa do testów lokalnych, ale `http://192.168.x.x` i inne adresy LAN zwykle go nie spełniają, więc konwersja Office przez zwykłe HTTP nie zadziała, nawet jeśli nagłówki są obecne.
|
||||
>
|
||||
> Oficjalne obrazy kontenerów zawierają te nagłówki. Jeśli używasz reverse proxy (Traefik, Caddy itp.), upewnij się, że nagłówki są zachowywane lub dodawane, i używaj HTTPS przy dostępie spoza pętli zwrotnej.
|
||||
|
||||
> [!TIP]
|
||||
> **Użytkownicy Podmana:** Wszystkie polecenia `docker` działają z Podmanem po zastąpieniu `docker` przez `podman` i `docker-compose` przez `podman-compose`.
|
||||
|
||||
## Szybki start
|
||||
|
||||
> [!TIP]
|
||||
> BentoPDF jest dostępny w dwóch wersjach:
|
||||
>
|
||||
> - **Wersja do samodzielnego hostingu**: `ghcr.io/alam00000/bentopdf-simple:latest`. Wszystkie narzędzia PDF, **bez** warstwy marketingowej (bez hero, FAQ, opinii i stopki). Użyj jej do wdrożeń wewnętrznych, zespołowych i organizacyjnych. To **nie** jest okrojona wersja „lite”.
|
||||
> - **Wersja komercyjna**: `ghcr.io/alam00000/bentopdf:latest`. Pełna witryna marketingowa, używana przez samo bentopdf.com i przez posiadaczy licencji komercyjnej z publicznymi wdrożeniami.
|
||||
>
|
||||
> W razie wątpliwości pobierz wersję do samodzielnego hostingu.
|
||||
|
||||
```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
|
||||
|
||||
Utwórz `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
|
||||
```
|
||||
|
||||
Uruchom:
|
||||
|
||||
```bash
|
||||
# Docker Compose
|
||||
docker compose up -d
|
||||
|
||||
# Podman Compose
|
||||
podman-compose up -d
|
||||
```
|
||||
|
||||
## Zbuduj własny obraz
|
||||
|
||||
```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;"]
|
||||
```
|
||||
|
||||
Zbuduj i uruchom:
|
||||
|
||||
```bash
|
||||
docker build -t bentopdf:custom .
|
||||
docker run -d -p 3000:8080 bentopdf:custom
|
||||
```
|
||||
|
||||
## Zmienne środowiskowe
|
||||
|
||||
| Zmienna | Opis | Domyślnie |
|
||||
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Zbuduj wariant do samodzielnego hostingu (ukrywa warstwę marketingową bentopdf.com: hero, FAQ, opinie, stopkę). Wszystkie narzędzia PDF pozostają. | `false` |
|
||||
| `BASE_URL` | Wdrożenie w podkatalogu | `/` |
|
||||
| `VITE_WASM_PYMUPDF_URL` | Adres URL modułu PyMuPDF WASM | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
|
||||
| `VITE_WASM_GS_URL` | Adres URL modułu Ghostscript WASM | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
|
||||
| `VITE_WASM_CPDF_URL` | Adres URL modułu CoherentPDF WASM | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
|
||||
| `VITE_TESSERACT_WORKER_URL` | Adres URL skryptu workera OCR | _(puste; domyślny CDN Tesseract.js)_ |
|
||||
| `VITE_TESSERACT_CORE_URL` | Katalog środowiska uruchomieniowego OCR | _(puste; domyślny CDN Tesseract.js)_ |
|
||||
| `VITE_TESSERACT_LANG_URL` | Katalog traineddata OCR | _(puste; domyślny CDN Tesseract.js)_ |
|
||||
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | Języki OCR rozdzielone przecinkami, widoczne w interfejsie | _(puste; pełny katalog)_ |
|
||||
| `VITE_OCR_FONT_BASE_URL` | Katalog czcionek warstwy tekstowej OCR | _(puste; zdalne adresy czcionek Noto)_ |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Domyślny język interfejsu | `en` |
|
||||
| `VITE_BRAND_NAME` | Własna nazwa marki | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | Ścieżka logo względem `public/` | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | Własny tekst stopki/praw autorskich | `© 2026 BentoPDF. All rights reserved.` |
|
||||
| `DISABLE_TOOLS` | Identyfikatory narzędzi do ukrycia, rozdzielone przecinkami | _(puste; wszystkie narzędzia włączone)_ |
|
||||
| `DISABLE_GITHUB_STARS` | Pomija pobieranie liczby gwiazdek GitHub przy ładowaniu strony i usuwa `api.github.com` z CSP. Buildy Simple Mode zawsze to pomijają. | `false` |
|
||||
| `SITE_URL` | Publiczny adres URL zapisywany w linkach canonical, hreflang i mapy witryny. Zostaw domyślny, chyba że chcesz, aby wyszukiwarki traktowały Twoje wdrożenie jako osobną witrynę. | `https://www.bentopdf.com` |
|
||||
| `ALLOW_INDEXING` | Przy własnym `SITE_URL` każda strona jest oznaczana jako `noindex`, aby Twoja kopia nie konkurowała z bentopdf.com w wynikach wyszukiwania. Ustaw `true`, aby pozwolić wyszukiwarkom ją indeksować. | `false` |
|
||||
|
||||
Adresy URL modułów WASM są wstępnie skonfigurowane z domyślnymi CDN, więc wszystkie zaawansowane funkcje działają od razu. Zmień je w środowiskach odciętych od sieci lub przy samodzielnym hostingu.
|
||||
|
||||
### Wyszukiwarki
|
||||
|
||||
Domyślnie każda strona wskazuje wyszukiwarkom bentopdf.com jako oryginał, więc Twoje wdrożenie nie jest widoczne jako duplikat. W prywatnej lub wewnętrznej instancji nie musisz niczego zmieniać.
|
||||
|
||||
- **Całkowicie ukryj swoją instancję w wynikach wyszukiwania:** uruchom kontener z `-e ROBOTS_NOINDEX=true`. Wystarczy restart, bez ponownego budowania.
|
||||
- **Uczyń swoją instancję osobną witryną w wynikach wyszukiwania:** zbuduj z `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true`. Bez `ALLOW_INDEXING=true` własny `SITE_URL` oznacza każdą stronę jako `noindex`.
|
||||
|
||||
### Content-Security-Policy
|
||||
|
||||
Obraz nginx zawiera egzekwowany nagłówek `Content-Security-Policy`. Dyrektywy CSP `script-src`, `connect-src` i `font-src` są **generowane podczas budowania** na podstawie powyższych zmiennych URL `VITE_*`: wszystkie hosty przekazane przez `--build-arg` są automatycznie dodawane do polityki.
|
||||
|
||||
W rezultacie:
|
||||
|
||||
- Jeśli podczas budowania zmienisz `VITE_CORS_PROXY_URL` lub `VITE_WASM_*_URL`, CSP automatycznie zezwoli na te źródła, bez dodatkowej konfiguracji.
|
||||
- Dla adresów na `cdn.jsdelivr.net` `script-src` zezwala tylko na dokładny folder pakietu z wersją, na przykład `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`, a nie na cały CDN. jsDelivr udostępnia każdy publiczny pakiet npm i repozytorium GitHub, więc zezwolenie na cały host pozwoliłoby uruchomić którykolwiek z nich w Twojej instancji. Adresy na każdym innym hoście są dozwolone według źródła (origin).
|
||||
- Jeśli własne adresy WASM ustawisz _w czasie działania_ na stronie Ustawienia zaawansowane w aplikacji, te źródła **nie** są w CSP i przeglądarka zablokuje pobieranie z nich. Dotyczy to także innej wersji tego samego pakietu na `cdn.jsdelivr.net`, ponieważ dozwolona jest tylko wersja ustawiona podczas budowania. Konfiguracja w czasie działania służy do eksperymentów; dla trwałych własnych adresów ustaw odpowiedni argument budowania `VITE_*`.
|
||||
- We wdrożeniach odciętych od sieci, które zmieniają wszystkie trzy wartości `VITE_WASM_*_URL`, foldery pakietów `cdn.jsdelivr.net` są też usuwane z CSP (każda wartość domyślna jest zastępowana, a nie uzupełniana). Domyślne adresy Tesseract OCR na `cdn.jsdelivr.net` zostają w polityce, dopóki nie ustawisz także trzech wartości `VITE_TESSERACT_*`. Podobnie ustawienie `VITE_CORS_PROXY_URL` zastępuje publiczną wartość domyślną `bentopdf-cors-proxy.bentopdf.workers.dev`.
|
||||
|
||||
CSP zawiera `'unsafe-eval'` w `script-src`, ponieważ środowisko LibreOffice WASM (używane przez narzędzia konwersji Word/Excel/PowerPoint) i biblioteka obrazów wasm-vips (używana przez PDF do TIFF) kompilują wewnętrzny kod dispatch przez `new Function()`. Usunięcie tej wartości zepsułoby te narzędzia. Jeśli budujesz w `SIMPLE_MODE` (bez LibreOffice) i nie potrzebujesz PDF do TIFF, możesz ręcznie edytować wygenerowany `security-headers.conf`, aby usunąć `'unsafe-eval'` i uzyskać ostrzejszą politykę.
|
||||
|
||||
W przypadku OCR zostaw zmienne `VITE_TESSERACT_*` puste, aby używać domyślnych zasobów online, albo ustaw wszystkie trzy razem dla OCR hostowanego samodzielnie lub offline. Częściowe nadpisania OCR są odrzucane, ponieważ worker, środowisko uruchomieniowe i katalog traineddata muszą do siebie pasować. Aby uzyskać w pełni offline przeszukiwalny plik PDF, ustaw też `VITE_OCR_FONT_BASE_URL`, aby czcionki warstwy tekstowej OCR były ładowane z Twojego wewnętrznego serwera, a nie z publicznych adresów czcionek Noto.
|
||||
|
||||
`VITE_DEFAULT_LANGUAGE` ustawia język interfejsu dla nowych odwiedzających. Obsługiwane wartości: `en`, `ar`, `be`, `fr`, `de`, `es`, `zh`, `zh-TW`, `vi`, `tr`, `id`, `it`, `pt`, `nl`, `da`. Użytkownicy nadal mogą zmienić język: to zmienia tylko wartość domyślną.
|
||||
|
||||
Przykład:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Własny branding
|
||||
|
||||
Zastąp domyślne logo, nazwę i tekst stopki BentoPDF własnymi. Umieść plik logo w folderze `public/` (lub użyj istniejącego obrazu), a następnie przekaż zmienne brandingu podczas budowania:
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
Branding działa zarówno w trybie pełnym, jak i w Simple Mode i można go łączyć ze wszystkimi innymi opcjami czasu budowania.
|
||||
|
||||
### Wyłączanie wybranych narzędzi
|
||||
|
||||
Ukryj narzędzia w interfejsie ze względu na wymogi zgodności lub bezpieczeństwa. Wyłączone narzędzia są usuwane ze strony głównej, wyników wyszukiwania, skrótów klawiszowych i kreatora przepływów pracy. Bezpośredni dostęp przez adres URL pokazuje stronę „narzędzie niedostępne”.
|
||||
|
||||
Identyfikatory narzędzi to adres URL strony bez `.html`. Na przykład jeśli narzędzie znajduje się pod `bentopdf.com/edit-pdf.html`, jego identyfikator to `edit-pdf`.
|
||||
|
||||
#### Jak znaleźć identyfikatory narzędzi
|
||||
|
||||
Najłatwiej: otwórz dowolne narzędzie w BentoPDF i spójrz na adres URL. Ostatnia część ścieżki (bez `.html`) to identyfikator narzędzia.
|
||||
|
||||
<details>
|
||||
<summary>Pełna lista identyfikatorów narzędzi</summary>
|
||||
|
||||
**Edycja i adnotacje:** `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`
|
||||
|
||||
**Konwersja do 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`
|
||||
|
||||
**Konwersja z 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`
|
||||
|
||||
**Organizacja i zarządzanie:** `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`
|
||||
|
||||
**Optymalizacja i naprawa:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
|
||||
|
||||
**Bezpieczeństwo:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
|
||||
|
||||
</details>
|
||||
|
||||
#### Opcja 1: Podczas budowania (argument budowania Docker)
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
|
||||
-t bentopdf .
|
||||
```
|
||||
|
||||
Lista wyłączonych narzędzi jest wbudowywana w pakiet JavaScript. Zmiana wymaga ponownego zbudowania.
|
||||
|
||||
#### Opcja 2: W czasie działania (config.json)
|
||||
|
||||
Zamontuj plik `config.json` w serwowanym katalogu, bez ponownego budowania:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Albo z 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
|
||||
```
|
||||
|
||||
Obie metody można łączyć: listy są scalane. Jeśli narzędzie występuje w którejkolwiek, jest wyłączone.
|
||||
|
||||
#### Wyłączanie funkcji edytora
|
||||
|
||||
Możesz też wyłączyć wybrane funkcje wewnątrz edytora PDF (np. redagowanie, adnotacje, formularze) bez wyłączania całego edytora. Dodaj `editorDisabledCategories` do swojego `config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"editorDisabledCategories": ["redaction", "annotation-stamp"]
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Pełna lista kategorii edytora</summary>
|
||||
|
||||
**Powiększenie:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
|
||||
|
||||
**Adnotacje:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
|
||||
|
||||
**Kształty:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
|
||||
|
||||
**Formularze:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
|
||||
|
||||
**Redagowanie:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
|
||||
|
||||
**Dokument:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
|
||||
|
||||
**Strona:** `page`, `spread`, `rotate`, `scroll`, `navigation`
|
||||
|
||||
**Panel:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
|
||||
|
||||
**Narzędzia:** `tools`, `pan`, `pointer`, `capture`
|
||||
|
||||
**Zaznaczenie:** `selection`, `selection-copy`
|
||||
|
||||
**Historia:** `history`, `history-undo`, `history-redo`
|
||||
|
||||
</details>
|
||||
|
||||
Kategorie są hierarchiczne: wyłączenie kategorii nadrzędnej (np. `annotation`) wyłącza wszystkie jej podkategorie.
|
||||
|
||||
### Wyłączanie licznika gwiazdek GitHub
|
||||
|
||||
Pasek nawigacji pokazuje aktualną liczbę gwiazdek GitHub, pobieraną z `api.github.com` przy ładowaniu strony. To jedyne zewnętrzne żądanie, które nie obsługuje funkcji PDF. Wyłącz je podczas budowania, aby usunąć kod pobierania z pakietu i `api.github.com` z Content-Security-Policy:
|
||||
|
||||
```bash
|
||||
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
|
||||
```
|
||||
|
||||
Buildy Simple Mode (`bentopdf-simple`) zawsze pomijają licznik gwiazdek: ta flaga jest potrzebna tylko w wersji komercyjnej lub własnych pełnych buildach.
|
||||
|
||||
### Własne adresy WASM (środowiska odcięte od sieci / samodzielny hosting)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Adresy WASM są wbudowywane w JavaScript **podczas budowania**. Pliki WASM pobiera **przeglądarka użytkownika** w czasie działania: Docker nie pobiera ich podczas budowania. W sieciach odciętych od internetu musisz hostować pliki WASM na wewnętrznym serwerze dostępnym dla przeglądarek.
|
||||
|
||||
**Pełny proces dla środowiska odciętego od sieci:**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Użyj kodów wypisanych przez `bash scripts/prepare-airgap.sh --list-ocr-languages` lub wyszukaj po nazwie za pomocą `bash scripts/prepare-airgap.sh --search-ocr-language <term>` dla `--ocr-languages`. Gdy budujesz z ograniczonym podzbiorem OCR, przekaż te same kody do `VITE_TESSERACT_AVAILABLE_LANGUAGES`, aby aplikacja pokazywała tylko dołączone języki. Dla pełnego OCR offline hostuj też dołączony katalog `ocr-fonts/` i wskaż go w `VITE_OCR_FONT_BASE_URL`.
|
||||
|
||||
Ustaw zmienną na pusty ciąg, aby wyłączyć dany moduł (użytkownicy muszą wtedy skonfigurować go ręcznie w Ustawieniach zaawansowanych).
|
||||
|
||||
## Własny port
|
||||
|
||||
Domyślnie BentoPDF nasłuchuje na porcie `8080` wewnątrz kontenera. Aby to zmienić, ustaw zmienną środowiskową `PORT` w czasie uruchamiania:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
| Zmienna | Opis | Domyślnie |
|
||||
| ------- | ------------------------------------- | --------- |
|
||||
| `PORT` | Port nasłuchiwania Nginx w kontenerze | `8080` |
|
||||
|
||||
Działa to zarówno ze standardowym Dockerfile, jak i z wersją nonroot.
|
||||
|
||||
## Własny identyfikator użytkownika (PUID/PGID)
|
||||
|
||||
Dla środowisk wymagających uruchamiania jako konkretny użytkownik inny niż root (urządzenia NAS, Kubernetes z kontekstami bezpieczeństwa, zasady organizacji) BentoPDF udostępnia osobny Dockerfile z obsługą PUID/PGID w stylu LSIO.
|
||||
|
||||
### Budowanie i uruchamianie
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Zmienne środowiskowe
|
||||
|
||||
| Zmienna | Opis | Domyślnie |
|
||||
| -------------- | -------------------------------------------- | --------- |
|
||||
| `PUID` | Identyfikator użytkownika, jako który działa | `1000` |
|
||||
| `PGID` | Identyfikator grupy, jako która działa | `1000` |
|
||||
| `PORT` | Port nasłuchiwania Nginx | `8080` |
|
||||
| `DISABLE_IPV6` | Wyłącz nasłuchiwanie 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
|
||||
```
|
||||
|
||||
### Jak to działa
|
||||
|
||||
Kontener startuje jako root, tworzy użytkownika o podanym PUID/PGID, dostosowuje własność wszystkich katalogów z możliwością zapisu, a następnie obniża uprawnienia za pomocą `su-exec`. Proces nginx działa w całości jako wskazany użytkownik.
|
||||
|
||||
> [!NOTE]
|
||||
> Standardowy `Dockerfile` używa `nginx-unprivileged` (UID 101) i jest zalecany w większości wdrożeń. Używaj `Dockerfile.nonroot` tylko wtedy, gdy potrzebujesz konkretnego UID/GID.
|
||||
|
||||
> [!WARNING]
|
||||
> PUID/PGID nie mogą mieć wartości `0` (root). Punkt wejścia waliduje dane i kończy się błędem przy nieprawidłowych wartościach.
|
||||
|
||||
## Z Traefik (reverse proxy)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Z Caddy (reverse proxy)
|
||||
|
||||
```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:
|
||||
```
|
||||
|
||||
Plik Caddyfile:
|
||||
|
||||
```
|
||||
pdf.example.com {
|
||||
reverse_proxy bentopdf:8080
|
||||
header Cross-Origin-Opener-Policy "same-origin"
|
||||
header Cross-Origin-Embedder-Policy "require-corp"
|
||||
}
|
||||
```
|
||||
|
||||
## Limity zasobów
|
||||
|
||||
```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 (integracja z systemd)
|
||||
|
||||
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) pozwala uruchamiać kontenery Podman jako usługi systemd. To idealne rozwiązanie dla wdrożeń produkcyjnych w systemach Linux.
|
||||
|
||||
### Podstawowa konfiguracja Quadlet
|
||||
|
||||
Utwórz plik jednostki kontenera w `~/.config/containers/systemd/bentopdf.container` (użytkownik) lub `/etc/containers/systemd/bentopdf.container` (system):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Włączanie i uruchamianie
|
||||
|
||||
```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]
|
||||
> Dla wdrożenia ogólnosystemowego użyj `systemctl` bez flagi `--user` i umieść plik w `/etc/containers/systemd/`.
|
||||
|
||||
### Quadlet z kontrolą stanu
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Automatyczna aktualizacja z Quadlet
|
||||
|
||||
Podman może automatycznie aktualizować kontenery, gdy pojawią się nowe obrazy:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Konfiguracja sieci Quadlet
|
||||
|
||||
Aby skonfigurować własną sieć, utwórz plik sieci `bentopdf.network`:
|
||||
|
||||
```ini
|
||||
[Network]
|
||||
Subnet=10.89.0.0/24
|
||||
Gateway=10.89.0.1
|
||||
```
|
||||
|
||||
Następnie odwołaj się do niego w pliku kontenera:
|
||||
|
||||
```ini
|
||||
[Container]
|
||||
Image=ghcr.io/alam00000/bentopdf-simple:latest
|
||||
ContainerName=bentopdf
|
||||
PublishPort=3000:8080
|
||||
Network=bentopdf.network
|
||||
```
|
||||
|
||||
## Aktualizacja
|
||||
|
||||
```bash
|
||||
# Pull latest image
|
||||
docker compose pull
|
||||
|
||||
# Recreate container
|
||||
docker compose up -d
|
||||
```
|
||||
@@ -0,0 +1,266 @@
|
||||
# Wdrożenie na Hostinger
|
||||
|
||||
[Hostinger](https://hostinger.com) to popularny dostawca hostingu współdzielonego. Ten przewodnik opisuje wdrożenie BentoPDF na hostingu współdzielonym Hostinger.
|
||||
|
||||
## Wymagania wstępne
|
||||
|
||||
- Plan hostingowy Hostinger z dostępem do menedżera plików
|
||||
- Node.js zainstalowany lokalnie do budowania
|
||||
|
||||
## Krok 1: Zbuduj projekt
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Zbudowane pliki znajdą się w folderze `dist`.
|
||||
|
||||
## Krok 2: Wyślij na Hostinger
|
||||
|
||||
### Wdrożenie w domenie głównej
|
||||
|
||||
1. Zaloguj się na swoje konto Hostinger
|
||||
2. Przejdź do **File Manager** → **public_html**
|
||||
3. **Usuń** istniejące pliki (w razie potrzeby zrób kopię zapasową)
|
||||
4. **Wyślij** całą zawartość lokalnego folderu `dist` do `public_html`
|
||||
|
||||
### Wdrożenie w podkatalogu
|
||||
|
||||
Jeśli wdrażasz do podkatalogu (np. `yourdomain.com/pdf-tools/`):
|
||||
|
||||
1. Zbuduj z poprawnym bazowym adresem URL:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
2. Utwórz folder w Hostinger:
|
||||
- Przejdź do **File Manager** → **public_html**
|
||||
- Utwórz nowy folder: `pdf-tools`
|
||||
3. Wyślij całą zawartość `dist` do `public_html/pdf-tools/`
|
||||
|
||||
## Krok 3: Utwórz plik .htaccess
|
||||
|
||||
Utwórz plik `.htaccess` w katalogu głównym folderu wdrożenia (`public_html` lub `public_html/pdf-tools/`):
|
||||
|
||||
::: warning Ważne
|
||||
Przy wdrożeniu w podkatalogu zmień `RewriteBase /` na `RewriteBase /pdf-tools/` (lub nazwę swojego folderu).
|
||||
:::
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Przykład .htaccess dla podkatalogu
|
||||
|
||||
Dla `yourdomain.com/pdf-tools/` zaktualizuj te linie:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Rozwiązywanie problemów
|
||||
|
||||
### Pliki WASM się nie ładują
|
||||
|
||||
Upewnij się, że typy MIME są poprawnie ustawione w `.htaccess`:
|
||||
|
||||
```apache
|
||||
AddType application/wasm .wasm
|
||||
```
|
||||
|
||||
### Narzędzia LibreOffice nie działają
|
||||
|
||||
Nagłówki bezpieczeństwa są kluczowe dla SharedArrayBuffer:
|
||||
|
||||
```apache
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
```
|
||||
|
||||
Jeśli nagłówki nie są stosowane, skontaktuj się z pomocą techniczną Hostinger, aby włączyła `mod_headers`.
|
||||
|
||||
### Błędy 404 po odświeżeniu strony
|
||||
|
||||
Upewnij się, że reguła rozszerzenia `.html` i trasy językowe są poprawnie skonfigurowane. BentoPDF używa statycznych plików HTML, a nie routingu 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]
|
||||
```
|
||||
|
||||
### Limity wysyłania plików
|
||||
|
||||
Hostinger może mieć limity rozmiaru plików. Jeśli wysyłanie zbiorcze zawodzi, wyślij pliki WASM osobno.
|
||||
@@ -0,0 +1,538 @@
|
||||
# Przewodnik self-hostingu
|
||||
|
||||
BentoPDF można hostować na własnej infrastrukturze. Ten przewodnik omawia różne opcje wdrożenia.
|
||||
|
||||
## Szybki start z Docker / Podman
|
||||
|
||||
Najszybszy sposób na self-hosting BentoPDF:
|
||||
|
||||
> [!TIP]
|
||||
> BentoPDF jest dostępny w dwóch wersjach:
|
||||
>
|
||||
> - **Wersja Self-Hosted**: `ghcr.io/alam00000/bentopdf-simple:latest`. Wszystkie narzędzia PDF, **bez** elementów marketingowych (bez hero, FAQ, opinii, stopki). Użyj jej do wdrożeń wewnętrznych, zespołowych i w organizacjach. To **nie** jest okrojona wersja "lite".
|
||||
> - **Wersja komercyjna**: `ghcr.io/alam00000/bentopdf:latest`. Pełna strona marketingowa, używana przez samo bentopdf.com i przez posiadaczy licencji komercyjnej uruchamiających publiczne wdrożenia.
|
||||
>
|
||||
> W razie wątpliwości: pobierz wersję Self-Hosted.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Konwersja plików Office wymaga `SharedArrayBuffer`, co oznacza, że aplikacja musi być jednocześnie odizolowana cross-origin i serwowana z bezpiecznego kontekstu. Oficjalny obraz już wysyła wymagane nagłówki COOP/COEP, ale przeglądarki nadal wyłączają `SharedArrayBuffer` na zwykłych adresach HTTP w sieci lokalnej, takich jak `http://192.168.x.x`.
|
||||
>
|
||||
> Używaj `http://localhost` tylko do testów na tym samym urządzeniu. Jeśli użytkownicy łączą się z BentoPDF przez adres IP w sieci LAN lub nazwę hosta, zakończ połączenie przez 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
|
||||
```
|
||||
|
||||
Lub z 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)
|
||||
|
||||
Uruchom BentoPDF jako usługę systemd. Utwórz `~/.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
|
||||
```
|
||||
|
||||
Pełną dokumentację Quadlet znajdziesz w [przewodniku wdrożenia Docker](/pl/self-hosting/docker).
|
||||
|
||||
## Budowanie ze źródeł
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## Opcje konfiguracji
|
||||
|
||||
### Wersja Self-Hosted (Simple Mode)
|
||||
|
||||
Wersja Self-Hosted (obraz `bentopdf-simple`, zwana też Simple Mode) jest **funkcjonalnie identyczna** z wersją komercyjną: każde narzędzie PDF jest obecne i działa tak samo. Ukrywa tylko elementy marketingowe, które mają sens wyłącznie na samym bentopdf.com lub w komercyjnym, publicznym wdrożeniu. **To nie jest okrojona wersja "lite".**
|
||||
|
||||
**Co ukrywa wersja Self-Hosted** (tylko kosmetycznie, żadne funkcje PDF nie są usuwane):
|
||||
|
||||
- Pasek nawigacji, sekcja hero, sekcja funkcji, FAQ, opinie, stopka
|
||||
- Zmienia tytuł strony na "Narzędzia PDF"
|
||||
|
||||
**Co zachowuje wersja Self-Hosted** (wszystko, co faktycznie pracuje na PDF):
|
||||
|
||||
- Każde narzędzie PDF (łączenie, dzielenie, edycja, podpisywanie, OCR, konwersja Office itd.)
|
||||
- Obsługa własnego brandingu, cała konfiguracja czasu budowania i działania
|
||||
|
||||
Wersja komercyjna (`ghcr.io/alam00000/bentopdf:latest`) napędza samo bentopdf.com i jest używana przez posiadaczy licencji komercyjnej uruchamiających publiczne wdrożenia. Dodaje hero, FAQ, opinie i stopkę, które nie miałyby sensu w narzędziu wewnętrznym.
|
||||
|
||||
Jeśli hostujesz BentoPDF dla swojego zespołu, organizacji lub jako narzędzie wewnętrzne, pobierz wersję Self-Hosted:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Szczegóły znajdziesz w [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md).
|
||||
|
||||
### Wersja komercyjna (publiczne wdrożenia z własną marką)
|
||||
|
||||
Wersja komercyjna (obraz `bentopdf`, bez sufiksu `-simple`) napędza samo bentopdf.com. Zawiera pełną stronę marketingową (hero, funkcje, FAQ, opinie, stopka) oraz każde narzędzie PDF. Użyj tej wersji, gdy chcesz **wdrożyć BentoPDF jako publiczną usługę PDF pod własną marką**, na przykład:
|
||||
|
||||
- Uruchamiasz BentoPDF jako hostowaną usługę SaaS dla użytkowników końcowych na własnej domenie
|
||||
- Chcesz doświadczenia strony docelowej (sekcje marketingowe + narzędzia), a nie samą powierzchnię narzędzi
|
||||
- Jesteś posiadaczem licencji komercyjnej i osadzasz BentoPDF w komercyjnym produkcie
|
||||
|
||||
**Uruchom bez zmian** (z brandingiem BentoPDF, przydatne do podglądu buildu):
|
||||
|
||||
```bash
|
||||
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
|
||||
```
|
||||
|
||||
**Zbuduj z własną marką** (typowy przepływ komercyjny):
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Każda inna opcja czasu budowania (`BASE_URL`, `VITE_DEFAULT_LANGUAGE`, `DISABLE_TOOLS`, nadpisania adresów WASM itd.) działa tak samo jak w wersji Self-Hosted.
|
||||
|
||||
::: warning Licencjonowanie
|
||||
Uruchamianie wersji komercyjnej jest dozwolone w ramach jednej z dwóch opcji licencyjnych BentoPDF:
|
||||
|
||||
- **AGPL-3.0** (darmowa): dozwolone, jeśli Twoje wdrożenie publikuje cały kod źródłowy na licencji AGPL, w tym modyfikacje brandingu i otaczającą logikę biznesową.
|
||||
- **Licencja komercyjna** (79 USD dożywotnio): wymagana dla wdrożeń z zamkniętym kodem / własnościowych, w których nie udostępniasz otwarcie forka brandingu ani kodu biznesowego.
|
||||
|
||||
Pełne porównanie znajdziesz na [stronie Licencjonowanie](https://www.bentopdf.com/licensing). Moduły WASM na licencji AGPL (PyMuPDF, Ghostscript, CoherentPDF) ładują się z CDN w czasie działania, więc nie trafiają do Twojego obrazu i nie zmieniają Twojej sytuacji licencyjnej.
|
||||
:::
|
||||
|
||||
### Base URL
|
||||
|
||||
Wdrożenie w podkatalogu:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
### Własny branding
|
||||
|
||||
Zastąp domyślne logo BentoPDF, nazwę i tekst stopki własnymi podczas budowania:
|
||||
|
||||
| Zmienna | Opis | Domyślnie |
|
||||
| ------------------ | ----------------------------------- | --------------------------------------- |
|
||||
| `VITE_BRAND_NAME` | Nazwa marki w nagłówku i stopce | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | Ścieżka do logo względem `public/` | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | Własny tekst stopki/praw autorskich | `© 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
|
||||
```
|
||||
|
||||
Lub przez 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 .
|
||||
```
|
||||
|
||||
Branding działa zarówno w trybie pełnym, jak i Simple Mode, i można go łączyć ze wszystkimi innymi opcjami czasu budowania (`BASE_URL`, `SIMPLE_MODE`, `VITE_DEFAULT_LANGUAGE`).
|
||||
|
||||
### Wyłączanie wybranych narzędzi
|
||||
|
||||
Ukryj wybrane narzędzia ze względów zgodności lub bezpieczeństwa. Wyłączone narzędzia są usuwane ze strony głównej, wyszukiwarki, skrótów, kreatora przepływów i bezpośredniego dostępu przez URL.
|
||||
|
||||
Identyfikatory narzędzi to adres strony bez `.html`. Otwórz dowolne narzędzie i spójrz na adres URL (np. `edit-pdf`, `sign-pdf`, `encrypt-pdf`).
|
||||
|
||||
**Czas budowania** (wbudowane w bundle):
|
||||
|
||||
```bash
|
||||
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
|
||||
```
|
||||
|
||||
**Czas działania** (bez przebudowywania, zamontuj `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
|
||||
```
|
||||
|
||||
Obie metody można łączyć, a listy są scalane.
|
||||
|
||||
Możesz też wyłączyć wybrane funkcje w Edytorze PDF (np. redakcję) bez wyłączania całego narzędzia, dodając `editorDisabledCategories` do `config.json`. Pełną listę kategorii znajdziesz w [przewodniku Docker](/pl/self-hosting/docker#wyłaczanie-funkcji-edytora).
|
||||
|
||||
## Przewodniki wdrożenia
|
||||
|
||||
Wybierz swoją platformę:
|
||||
|
||||
- [Vercel](/pl/self-hosting/vercel)
|
||||
- [Netlify](/pl/self-hosting/netlify)
|
||||
- [Cloudflare Pages](/pl/self-hosting/cloudflare)
|
||||
- [AWS S3 + CloudFront](/pl/self-hosting/aws)
|
||||
- [Hostinger](/pl/self-hosting/hostinger)
|
||||
- [Nginx](/pl/self-hosting/nginx)
|
||||
- [Apache](/pl/self-hosting/apache)
|
||||
- [Docker](/pl/self-hosting/docker)
|
||||
- [Kubernetes](/pl/self-hosting/kubernetes)
|
||||
- [CORS Proxy](/pl/self-hosting/cors-proxy) - wymagany dla podpisów cyfrowych
|
||||
|
||||
## Typowe problemy
|
||||
|
||||
### Konwersja Word / ODT / Excel / PowerPoint na PDF zawiesza się (SharedArrayBuffer niedostępny)
|
||||
|
||||
**Objaw**: konwersje dokumentów oparte na LibreOffice (Word, ODT, Excel, PowerPoint na PDF) zawieszają się na ok. 55% lub nie startują. Konsola przeglądarki może pokazywać:
|
||||
|
||||
> `ReferenceError: SharedArrayBuffer is not defined`
|
||||
|
||||
…albo `window.crossOriginIsolated` zwraca `false`, albo kompilacja WASM zgłasza `expected magic word 00 61 73 6d, found 1f 8b 08 08`.
|
||||
|
||||
**Przyczyna**: LibreOffice WASM wymaga `SharedArrayBuffer`, który przeglądarka włącza tylko wtedy, gdy strona jest **odizolowana cross-origin** ORAZ serwowana z **bezpiecznego kontekstu**. Oznacza to, że muszą być spełnione dwa warunki:
|
||||
|
||||
1. Każda odpowiedź zawiera oba nagłówki:
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
2. Strona jest serwowana z `https://...` lub `http://localhost`. Zwykły HTTP na adresie IP w sieci LAN (np. `http://192.168.x.x`) NIE jest uznawany za bezpieczny, a przeglądarki wyłączają tam `SharedArrayBuffer`.
|
||||
|
||||
Niezgodność `00 61 73 6d / 1f 8b 08 08` to osobny podobjaw: wstępnie skompresowane pliki `.wasm.gz` / `.data.gz` nie mają nagłówka odpowiedzi `Content-Encoding: gzip`, więc przeglądarka dostaje surowe bajty gzip zamiast rozpakowanego WASM.
|
||||
|
||||
**Rozwiązanie**: dokładną konfigurację znajdziesz w przewodniku dla swojej platformy:
|
||||
|
||||
- [Nginx →](/pl/self-hosting/nginx#podpisz-pdf-lub-wypełnianie-formularzy-pdf-pokazuje-pusty-podglad-bład-mime-mjs)
|
||||
- [Apache →](/pl/self-hosting/apache#podpisywanie-pdf-lub-wypełnianie-formularzy-pokazuje-pusta-przegladarke-bład-mime-mjs)
|
||||
- [AWS S3 + CloudFront →](/pl/self-hosting/aws#krok-3b-polityka-nagłowkow-odpowiedzi-wymagana-dla-libreoffice-wasm)
|
||||
- [Cloudflare Pages →](/pl/self-hosting/cloudflare#plik-konfiguracji) (plik `_headers`)
|
||||
- [Netlify →](/pl/self-hosting/netlify#konwersja-word-odt-excel-na-pdf-zawiesza-sie-na-55) (`netlify.toml`)
|
||||
- [Vercel →](/pl/self-hosting/vercel#konwersja-word-odt-excel-na-pdf-nie-działa) (`vercel.json`)
|
||||
- [Hostinger →](/pl/self-hosting/hostinger#narzedzia-libreoffice-nie-działaja) (`.htaccess`)
|
||||
- [Kubernetes →](/pl/self-hosting/kubernetes#błedy-typu-mime-dla-mjs-pusty-iframe-podpisz-pdf-wypełnianie-formularzy-pdf)
|
||||
- **Docker**: obsługiwane automatycznie przez wbudowaną konfigurację nginx, nie trzeba nic robić.
|
||||
|
||||
**Weryfikacja**: otwórz konsolę DevTools na dowolnej stronie BentoPDF i uruchom:
|
||||
|
||||
```js
|
||||
console.log(window.crossOriginIsolated); // should be true
|
||||
console.log(typeof SharedArrayBuffer); // should be "function"
|
||||
```
|
||||
|
||||
Jeśli strona działa przez HTTPS lub `http://localhost` ORAZ oba nagłówki COEP/COOP są obecne w każdej odpowiedzi, oba testy przejdą. Jeśli jesteś na `http://192.168.x.x` lub innym adresie HTTP spoza pętli zwrotnej, zakończ połączenie przez HTTPS. Nie ma rozwiązania opartego wyłącznie na nagłówkach.
|
||||
|
||||
### Pliki `.mjs` serwowane jako `application/octet-stream`
|
||||
|
||||
**Objaw**: Podpisz PDF / Wypełnianie formularzy PDF / niektóre inne narzędzia pokazują pusty podgląd lub nie ładują się. Konsola przeglądarki zgłasza:
|
||||
|
||||
> `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.`
|
||||
|
||||
**Przyczyna**: Twój serwer WWW lub reverse proxy nie ma mapowania typu MIME dla plików `.mjs` (wbudowana przeglądarka PDF dostarcza moduły ES z tym rozszerzeniem). Wiele standardowych konfiguracji serwerów domyślnie używa `application/octet-stream` dla nierozpoznanych rozszerzeń, a przeglądarki odmawiają wykonania takich plików jako modułów ES.
|
||||
|
||||
**Rozwiązanie**: dokładny fragment znajdziesz w przewodniku dla swojej platformy:
|
||||
|
||||
- [Nginx →](/pl/self-hosting/nginx#podpisz-pdf-lub-wypełnianie-formularzy-pdf-pokazuje-pusty-podglad-bład-mime-mjs)
|
||||
- [Apache →](/pl/self-hosting/apache#podpisywanie-pdf-lub-wypełnianie-formularzy-pokazuje-pusta-przegladarke-bład-mime-mjs)
|
||||
- [AWS S3 + CloudFront →](/pl/self-hosting/aws#krok-2-zbuduj-i-wyslij) (zobacz `aws s3 cp ... --include "*.mjs"`)
|
||||
- [Kubernetes →](/pl/self-hosting/kubernetes#błedy-typu-mime-dla-mjs-pusty-iframe-podpisz-pdf-wypełnianie-formularzy-pdf)
|
||||
- **Docker, Vercel, Netlify, Cloudflare Pages, Hostinger**: obsługiwane automatycznie, nie trzeba nic robić.
|
||||
|
||||
**Weryfikacja**: otwórz DevTools → karta Network, znajdź nieudane żądanie `.mjs` i sprawdź nagłówek odpowiedzi `Content-Type`. Powinien mieć wartość `application/javascript`. Jeśli nadal jest `application/octet-stream`, zewnętrzny reverse proxy lub CDN może ponownie wykrywać typ, więc sprawdź każdą warstwę w łańcuchu serwowania.
|
||||
|
||||
## Konfiguracja WASM (komponenty AGPL)
|
||||
|
||||
BentoPDF **nie dołącza** bibliotek przetwarzania na licencji AGPL do swojego kodu źródłowego, ale **wstępnie konfiguruje adresy CDN**, więc wszystkie funkcje działają od razu, bez ręcznej konfiguracji.
|
||||
|
||||
::: tip Domyślnie bez konfiguracji
|
||||
Od wersji v2.0.0 moduły WASM są wstępnie skonfigurowane do ładowania z CDN jsDelivr przez zmienne środowiskowe. Wszystkie zaawansowane funkcje działają od razu, bez żadnej konfiguracji użytkownika.
|
||||
:::
|
||||
|
||||
| Komponent | Licencja | Funkcje |
|
||||
| --------------- | -------- | --------------------------------------------------------------- |
|
||||
| **PyMuPDF** | AGPL-3.0 | Konwersja EPUB/MOBI/FB2/XPS, wyodrębnianie obrazów i tabel |
|
||||
| **Ghostscript** | AGPL-3.0 | Konwersja do PDF/A, kompresja, prostowanie skanów, rasteryzacja |
|
||||
| **CoherentPDF** | AGPL-3.0 | Spis treści, załączniki, łączenie PDF z zakładkami |
|
||||
|
||||
### Domyślne zmienne środowiskowe
|
||||
|
||||
Są ustawione w `.env.production` i wbudowane w build:
|
||||
|
||||
```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=
|
||||
```
|
||||
|
||||
### Nadpisywanie adresów WASM
|
||||
|
||||
Możesz nadpisać wartości domyślne podczas budowania dla własnych wdrożeń:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Aby całkowicie wyłączyć moduł (wymagając ręcznej konfiguracji przez użytkownika w Ustawieniach zaawansowanych), ustaw jego zmienną na pusty ciąg.
|
||||
|
||||
Dla OCR albo zostaw wszystkie zmienne `VITE_TESSERACT_*` puste i korzystaj z domyślnych zasobów online, albo ustaw razem adresy worker/core/lang dla self-hostowanego/offline OCR. Jeśli dołączasz tylko wybrane języki OCR, ustaw też `VITE_TESSERACT_AVAILABLE_LANGUAGES` na te same kody oddzielone przecinkami, aby interfejs oferował tylko zainstalowane języki, a nieobsługiwane wybory kończyły się opisowym błędem. Dla w pełni offline'owego wyniku przeszukiwalnego PDF ustaw też `VITE_OCR_FONT_BASE_URL` na wewnętrzny katalog serwujący dołączone czcionki OCR.
|
||||
|
||||
Użytkownicy mogą też w dowolnej chwili nadpisać te wartości w **Ustawieniach zaawansowanych** w interfejsie. Nadpisania zapisane w przeglądarce mają pierwszeństwo przed domyślnymi wartościami środowiska.
|
||||
|
||||
### Wdrożenie w sieci izolowanej / offline
|
||||
|
||||
Dla sieci bez dostępu do internetu (administracja, ochrona zdrowia, finanse itd.). Adresy WASM są wbudowane w JavaScript podczas **budowania**, a same pliki WASM pobiera **przeglądarka użytkownika** w czasie działania. Dlatego wszystko trzeba przygotować na maszynie z internetem, a potem przenieść do izolowanej sieci.
|
||||
|
||||
#### Skrypt automatyczny (zalecany)
|
||||
|
||||
Dołączony skrypt `prepare-airgap.sh` automatyzuje cały proces: pobiera pakiety WASM, buduje obraz Docker i tworzy samodzielny pakiet ze skryptem instalacyjnym.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Powstaje katalog pakietu:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
Przenieś pakiet do izolowanej sieci przez USB, wewnętrzne repozytorium artefaktów lub zatwierdzoną metodę. Następnie uruchom dołączony skrypt instalacyjny:
|
||||
|
||||
```bash
|
||||
cd bentopdf-airgap-bundle
|
||||
bash setup.sh
|
||||
```
|
||||
|
||||
Skrypt instalacyjny wczytuje obraz Docker, wypakowuje pliki WASM i opcjonalnie uruchamia kontener.
|
||||
|
||||
**Opcje skryptu:**
|
||||
|
||||
| Flaga | Opis | Domyślnie |
|
||||
| ------------------------------ | -------------------------------------------------------- | -------------------------------- |
|
||||
| `--wasm-base-url <url>` | Gdzie pliki WASM będą hostowane wewnętrznie | _(wymagane, pytanie jeśli brak)_ |
|
||||
| `--image-name <name>` | Tag obrazu Docker | `bentopdf` |
|
||||
| `--output-dir <path>` | Katalog wyjściowy pakietu | `./bentopdf-airgap-bundle` |
|
||||
| `--simple-mode` | Włącz Simple Mode | wyłączone |
|
||||
| `--base-url <path>` | Bazowy URL podkatalogu (np. `/pdf/`) | `/` |
|
||||
| `--language <code>` | Domyślny język interfejsu (np. `fr`, `de`) | _(brak)_ |
|
||||
| `--brand-name <name>` | Własna nazwa marki | _(brak)_ |
|
||||
| `--brand-logo <path>` | Ścieżka do logo względem `public/` | _(brak)_ |
|
||||
| `--footer-text <text>` | Własny tekst stopki | _(brak)_ |
|
||||
| `--ocr-languages <list>` | Języki OCR do dołączenia, oddzielone przecinkami | `eng` |
|
||||
| `--list-ocr-languages` | Wypisz obsługiwane kody i nazwy OCR, a następnie zakończ | wyłączone |
|
||||
| `--search-ocr-language <term>` | Szukaj kodów OCR po nazwie lub skrócie | wyłączone |
|
||||
| `--dockerfile <path>` | Używany Dockerfile | `Dockerfile` |
|
||||
| `--skip-docker` | Pomiń budowanie i eksport Docker | wyłączone |
|
||||
| `--skip-wasm` | Pomiń pobieranie WASM (użyj istniejących plików `.tgz`) | wyłączone |
|
||||
|
||||
Interaktywny monit akceptuje też `list`, aby wypisać pełną listę obsługiwanych kodów Tesseract, oraz `search <term>`, aby znaleźć dopasowania, np. `search german` lub `search chi`.
|
||||
|
||||
::: warning Wymóg tego samego źródła
|
||||
Pliki WASM muszą być serwowane z **tego samego źródła (origin)** co aplikacja BentoPDF. Web Workery używają `importScripts()`, które nie może ładować skryptów cross-origin. Na przykład, jeśli BentoPDF działa pod `https://internal.example.com`, bazowy adres WASM powinien być również `https://internal.example.com/wasm`.
|
||||
:::
|
||||
|
||||
#### Kroki ręczne
|
||||
|
||||
<details>
|
||||
<summary>Jeśli wolisz zrobić to ręcznie, bez skryptu</summary>
|
||||
|
||||
**Krok 1: Pobierz pakiety WASM i OCR** (na maszynie z internetem)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
**Krok 2: Zbuduj obraz Docker z wewnętrznymi adresami**
|
||||
|
||||
```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 .
|
||||
```
|
||||
|
||||
**Krok 3: Wyeksportuj obraz Docker**
|
||||
|
||||
```bash
|
||||
docker save bentopdf -o bentopdf.tar
|
||||
```
|
||||
|
||||
**Krok 4: Przenieś do izolowanej sieci**
|
||||
|
||||
Skopiuj przez USB, wewnętrzne repozytorium artefaktów lub zatwierdzoną metodę transferu:
|
||||
|
||||
- `bentopdf.tar`: obraz Docker
|
||||
- Pięć pakietów `.tgz` WASM/OCR z kroku 1
|
||||
- Katalog `tesseract-langdata/` z kroku 1
|
||||
- Katalog `ocr-fonts/` z kroku 1
|
||||
|
||||
**Krok 5: Skonfiguruj wewnątrz izolowanej sieci**
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Upewnij się, że pliki są dostępne pod adresami skonfigurowanymi w kroku 2, w tym `.../ocr/worker.min.js`, `.../ocr/core`, `.../ocr/lang-data` i `.../ocr/fonts`.
|
||||
|
||||
</details>
|
||||
|
||||
::: info Budujesz ze źródeł zamiast w Dockerze?
|
||||
Ustaw zmienne w `.env.production` przed uruchomieniem `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
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Hostowanie własnego proxy WASM
|
||||
|
||||
Jeśli musisz serwować pliki WASM na licencji AGPL z odpowiednimi nagłówkami CORS, możesz wdrożyć proste proxy. Przykładową implementację znajdziesz w [przewodniku Cloudflare WASM Proxy](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md).
|
||||
|
||||
::: tip Dlaczego osobno?
|
||||
To rozdzielenie zapewnia:
|
||||
|
||||
- Jasną zgodność prawną dla użytkowników komercyjnych
|
||||
- Rdzeń BentoPDF pozostaje na podwójnej licencji (AGPL-3.0 / komercyjna)
|
||||
- Pliki WASM są ładowane w czasie działania, a nie dołączane do źródeł
|
||||
:::
|
||||
|
||||
## Wymagania systemowe
|
||||
|
||||
| Wymaganie | Minimum |
|
||||
| --------- | ----------------------------------- |
|
||||
| Miejsce | ok. 100 MB (rdzeń bez modułów AGPL) |
|
||||
| RAM | 512 MB |
|
||||
| CPU | Dowolny nowoczesny procesor |
|
||||
|
||||
::: tip
|
||||
BentoPDF to statyczna strona, więc nie jest potrzebna baza danych ani serwer backendowy!
|
||||
:::
|
||||
@@ -0,0 +1,211 @@
|
||||
# Wdrożenie na Kubernetes
|
||||
|
||||
Kubernetes może być przesadą dla statycznej strony, ale świetnie sprawdzi się, jeśli standaryzujesz wszystko na Helm + GitOps.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Wymagane nagłówki dla konwersji plików Office**
|
||||
>
|
||||
> Narzędzia oparte na LibreOffice (konwersja Word, Excel, PowerPoint) wymagają tych nagłówków HTTP dla obsługi `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> Oficjalne obrazy nginx BentoPDF zawierają te nagłówki. W Kubernetes **kontrolery Ingress/Gateway także są reverse proxy**, więc upewnij się, że nagłówki są zachowywane (lub dodaj je na brzegu).
|
||||
|
||||
## Wymagania wstępne
|
||||
|
||||
- Klaster Kubernetes
|
||||
- Helm v3
|
||||
- Obraz nginx BentoPDF (np. `ghcr.io/alam00000/bentopdf:<tag>`) serwujący na **porcie 8080**
|
||||
|
||||
## Wdrożenie z Helm
|
||||
|
||||
### Instalacja z tego repozytorium (lokalny chart)
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Instalacja z GHCR (chart OCI)
|
||||
|
||||
Jeśli chart jest opublikowany w GHCR jako artefakt 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
|
||||
```
|
||||
|
||||
## Udostępnianie
|
||||
|
||||
### Port-forward (szybki test)
|
||||
|
||||
```bash
|
||||
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
|
||||
```
|
||||
|
||||
### Ingress (opcjonalnie)
|
||||
|
||||
Włącz Ingress (przykład dla nginx-ingress):
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: pdf.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
```
|
||||
|
||||
### Gateway API (opcjonalnie)
|
||||
|
||||
Ten chart obsługuje Gateway API `Gateway` + `HTTPRoute`.
|
||||
|
||||
Przykład (operator 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
|
||||
```
|
||||
|
||||
## Upewnianie się, że nagłówki SharedArrayBuffer nadal działają (Ingress/Gateway)
|
||||
|
||||
### Co "powinno" się dziać
|
||||
|
||||
Konfiguracja nginx BentoPDF ustawia wymagane nagłówki odpowiedzi. Większość kontrolerów Ingress/Gateway **przekazuje nagłówki odpowiedzi z upstreamu bez zmian**.
|
||||
|
||||
### Co może to zepsuć
|
||||
|
||||
- Polityka kontrolera lub brzegu, która **nadpisuje** lub **usuwa** nagłówki odpowiedzi
|
||||
- Middleware "security headers", które ustawia inne wartości COOP/COEP
|
||||
|
||||
### Jak sprawdzić
|
||||
|
||||
Uruchom to na swoim publicznym adresie:
|
||||
|
||||
```bash
|
||||
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
|
||||
```
|
||||
|
||||
Powinieneś zobaczyć:
|
||||
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
|
||||
### Jeśli Twój kontroler Ingress ich nie zachowuje
|
||||
|
||||
Dodaj nagłówki na brzegu (zależnie od kontrolera). Przykład dla **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;
|
||||
```
|
||||
|
||||
## Błędy typu MIME dla `.mjs` (pusty iframe Podpisz PDF / Wypełnianie formularzy PDF)
|
||||
|
||||
Jeśli konsola przeglądarki pokazuje `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` przy żądaniu `.mjs`, kontroler Ingress/Gateway przed nginx BentoPDF zastępuje nagłówek `Content-Type` z upstreamu wartością `application/octet-stream`.
|
||||
|
||||
Nginx w obrazie BentoPDF już serwuje `.mjs` jako `application/javascript`. Rozwiązaniem jest powstrzymanie kontrolera przed usuwaniem lub zastępowaniem tego nagłówka. Dla nginx-ingress:
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
location ~* \.mjs$ {
|
||||
types {} default_type application/javascript;
|
||||
}
|
||||
```
|
||||
|
||||
Możesz też użyć filtra `ResponseHeaderModifier` w Gateway API, aby ustawić `Content-Type: application/javascript` dla ścieżek `.mjs`. Zweryfikuj w DevTools → karta Network: nieudane żądanie `.mjs` powinno teraz pokazywać `content-type: application/javascript`.
|
||||
|
||||
### Jeśli używasz Gateway API i chcesz wymusić dodanie nagłówków
|
||||
|
||||
Gateway API obsługuje filtr `ResponseHeaderModifier`. Możesz go dołączyć w `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
|
||||
```
|
||||
|
||||
Obsługa konkretnych filtrów zależy od Twojego kontrolera Gateway. Jeśli filtr jest ignorowany, dodaj nagłówki na warstwie brzegu/kontrolera.
|
||||
|
||||
## Wyłączanie wybranych narzędzi
|
||||
|
||||
Użyj ConfigMap, aby wyłączyć narzędzia w czasie działania, bez przebudowywania obrazu:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bentopdf-config
|
||||
namespace: bentopdf
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
|
||||
}
|
||||
```
|
||||
|
||||
Zamontuj go w serwowanym katalogu:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Identyfikatory narzędzi to adres strony bez `.html`. Otwórz dowolne narzędzie i spójrz na adres URL (np. `edit-pdf`, `merge-pdf`, `compress-pdf`). Wyłączone narzędzia są ukryte na stronie głównej, w wyszukiwarce, skrótach, kreatorze przepływów i przy bezpośrednim dostępie przez URL. Pełną listę opcji znajdziesz w [przewodniku Docker](/pl/self-hosting/docker#wyłaczanie-wybranych-narzedzi).
|
||||
@@ -0,0 +1,139 @@
|
||||
# Wdrożenie na Netlify
|
||||
|
||||
[Netlify](https://netlify.com) zapewnia świetny hosting statycznych stron z hojnym darmowym planem.
|
||||
|
||||
## Wdrożenie jednym kliknięciem
|
||||
|
||||
[](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
|
||||
|
||||
## Wdrożenie ręczne
|
||||
|
||||
### Krok 1: Połącz repozytorium
|
||||
|
||||
1. Zaloguj się do [Netlify](https://app.netlify.com)
|
||||
2. Kliknij "Add new site" → "Import an existing project"
|
||||
3. Połącz swoje konto GitHub
|
||||
4. Wybierz swój fork BentoPDF
|
||||
|
||||
### Krok 2: Skonfiguruj ustawienia budowania
|
||||
|
||||
| Ustawienie | Wartość |
|
||||
| ----------------- | --------------- |
|
||||
| Build command | `npm run build` |
|
||||
| Publish directory | `dist` |
|
||||
| Node version | 18+ |
|
||||
|
||||
### Krok 3: Wdróż
|
||||
|
||||
Kliknij "Deploy site" i poczekaj na zakończenie budowania.
|
||||
|
||||
## Plik konfiguracyjny
|
||||
|
||||
Utwórz `netlify.toml` w katalogu głównym projektu:
|
||||
|
||||
```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 Ważne
|
||||
Nagłówki `Cross-Origin-Embedder-Policy` i `Cross-Origin-Opener-Policy` są wymagane do konwersji Word/ODT/Excel/PowerPoint na PDF. Bez nich `SharedArrayBuffer` jest niedostępny, a silnik LibreOffice WASM nie uruchomi się.
|
||||
|
||||
Nagłówki `Content-Encoding: gzip` przy plikach `.wasm.gz` i `.data.gz` informują przeglądarkę, że ma je automatycznie rozpakować. Bez nich przeglądarka otrzymuje surowe bajty gzip, a kompilacja WASM kończy się błędem.
|
||||
:::
|
||||
|
||||
## Zmienne środowiskowe
|
||||
|
||||
Ustaw je w Site settings → Environment variables:
|
||||
|
||||
| Zmienna | Opis |
|
||||
| ----------------------- | ------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Ustaw na `true` dla minimalnego builda |
|
||||
| `VITE_BRAND_NAME` | Własna nazwa marki (zastępuje "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | Ścieżka do logo względem `public/` (np. `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Własny tekst stopki/praw autorskich |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Domyślny język interfejsu (np. `fr`, `de`, `es`) |
|
||||
|
||||
## Własna domena
|
||||
|
||||
1. Przejdź do Site settings → Domain management
|
||||
2. Kliknij "Add custom domain"
|
||||
3. Postępuj zgodnie z instrukcjami konfiguracji DNS
|
||||
|
||||
## Duże pliki multimedialne
|
||||
|
||||
Dla dużych plików WASM rozważ włączenie [Netlify Large Media](https://docs.netlify.com/large-media/overview/):
|
||||
|
||||
```bash
|
||||
netlify lm:setup
|
||||
git lfs track "*.wasm"
|
||||
```
|
||||
|
||||
## Rozwiązywanie problemów
|
||||
|
||||
### Konwersja Word/ODT/Excel na PDF zawiesza się na 55%
|
||||
|
||||
Jeśli konwersje dokumentów zawieszają się na 55%, otwórz konsolę DevTools i sprawdź:
|
||||
|
||||
```js
|
||||
console.log(window.crossOriginIsolated); // should be true
|
||||
console.log(typeof SharedArrayBuffer); // should be "function"
|
||||
```
|
||||
|
||||
Jeśli `crossOriginIsolated` ma wartość `false`, nagłówki COEP/COOP z Twojego `netlify.toml` nie są stosowane. Upewnij się, że plik jest w katalogu głównym projektu, i wdróż ponownie.
|
||||
|
||||
Jeśli w konsoli widzisz `expected magic word 00 61 73 6d, found 1f 8b 08 08`, pliki `.wasm.gz` nie mają nagłówków `Content-Encoding: gzip`. Upewnij się, że bloki `[[headers]]` dla `soffice.wasm.gz` i `soffice.data.gz` znajdują się w Twoim `netlify.toml`.
|
||||
|
||||
### Budowanie kończy się błędem
|
||||
|
||||
Sprawdź zgodność wersji Node:
|
||||
|
||||
```toml
|
||||
[build.environment]
|
||||
NODE_VERSION = "20"
|
||||
```
|
||||
|
||||
### Wolne pierwsze ładowanie
|
||||
|
||||
Włącz optymalizację zasobów w Site settings → Build & deploy → Asset optimization.
|
||||
@@ -0,0 +1,232 @@
|
||||
# Wdrożenie z Nginx
|
||||
|
||||
Hostuj BentoPDF na własnym serwerze za pomocą Nginx.
|
||||
|
||||
## Wymagania wstępne
|
||||
|
||||
- Serwer Ubuntu/Debian
|
||||
- Zainstalowany Nginx
|
||||
- Certyfikat SSL (zalecany: Let's Encrypt)
|
||||
|
||||
## Krok 1: Zbuduj projekt
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
Aby dostosować branding, ustaw zmienne środowiskowe przed budowaniem:
|
||||
|
||||
```bash
|
||||
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
|
||||
```
|
||||
|
||||
## Krok 2: Skopiuj pliki
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/www/bentopdf
|
||||
sudo cp -r dist/* /var/www/bentopdf/
|
||||
sudo chown -R www-data:www-data /var/www/bentopdf
|
||||
```
|
||||
|
||||
## Krok 3: Konfiguracja Nginx
|
||||
|
||||
Utwórz `/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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Krok 4: Włącz witrynę
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## Krok 5: SSL z Let's Encrypt
|
||||
|
||||
```bash
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
sudo certbot --nginx -d your-domain.com
|
||||
```
|
||||
|
||||
## Wdrożenie w podkatalogu
|
||||
|
||||
Aby hostować pod `/pdf/`:
|
||||
|
||||
```nginx
|
||||
location /pdf/ {
|
||||
alias /var/www/bentopdf/;
|
||||
try_files $uri $uri/ /pdf/index.html;
|
||||
}
|
||||
```
|
||||
|
||||
Zbuduj z:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf/ npm run build
|
||||
```
|
||||
|
||||
## Dostrajanie wydajności
|
||||
|
||||
Dodaj do `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;
|
||||
}
|
||||
```
|
||||
|
||||
## Rozwiązywanie problemów
|
||||
|
||||
### WASM się nie ładuje
|
||||
|
||||
Upewnij się, że typ MIME jest ustawiony:
|
||||
|
||||
```nginx
|
||||
types {
|
||||
application/wasm wasm;
|
||||
}
|
||||
```
|
||||
|
||||
### Podpisz PDF lub Wypełnianie formularzy PDF pokazuje pusty podgląd (błąd MIME `.mjs`)
|
||||
|
||||
Jeśli konsola przeglądarki pokazuje:
|
||||
|
||||
```
|
||||
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.
|
||||
```
|
||||
|
||||
…a nieudane żądanie kończy się na `.mjs`, w Twoim nginx brakuje mapowania `.mjs → application/javascript`. Wbudowana przeglądarka PDF (używana przez Podpisz PDF, Wypełnianie formularzy PDF i kilka innych narzędzi) dostarcza moduły ES z rozszerzeniem `.mjs`, a standardowy `mime.types` w nginx domyślnie ich nie zawiera.
|
||||
|
||||
Napraw to, dodając `mjs` do istniejącej linii `application/javascript` w `/etc/nginx/mime.types`:
|
||||
|
||||
```nginx
|
||||
application/javascript js mjs;
|
||||
```
|
||||
|
||||
> NIE dodawaj _drugiej_ linii `application/javascript`. Nginx parsuje `mime.types` linia po linii i działa tylko jedno mapowanie typu na rozszerzenie, więc druga linia jest w niektórych wersjach po cichu ignorowana. Edytuj istniejącą linię, dodając `mjs` obok `js`.
|
||||
|
||||
Możesz też nadpisać to lokalnie w bloku server (bez edycji pliku globalnego):
|
||||
|
||||
```nginx
|
||||
types {
|
||||
application/javascript js mjs;
|
||||
application/wasm wasm;
|
||||
}
|
||||
```
|
||||
|
||||
Po zmianie przeładuj nginx:
|
||||
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
Aby potwierdzić: w DevTools → karta Network znajdź nieudane żądanie `.mjs` i sprawdź nagłówek odpowiedzi `Content-Type`. Powinien mieć teraz wartość `application/javascript`, a nie `application/octet-stream`.
|
||||
|
||||
### Konwersja Word/ODT/Excel na PDF nie działa
|
||||
|
||||
LibreOffice WASM wymaga `SharedArrayBuffer`, a to wymaga nagłówków `Cross-Origin-Embedder-Policy` i `Cross-Origin-Opener-Policy`. Potrzebny jest też bezpieczny kontekst, więc `http://localhost` działa w lokalnych testach, ale `http://192.168.x.x` i inne adresy IP w sieci LAN zwykle wymagają HTTPS. Pamiętaj, że dyrektywy nginx `add_header` w bloku `location` **nadpisują** dyrektywy `add_header` z poziomu serwera, a nie łączą się z nimi. Każdy blok `location` z własnym `add_header` musi zawierać nagłówki COEP/COOP.
|
||||
|
||||
Zweryfikuj poleceniem:
|
||||
|
||||
```bash
|
||||
curl -I https://your-domain.com/ | grep -i cross-origin
|
||||
```
|
||||
|
||||
Jeśli przed nginx stoi reverse proxy, upewnij się, że zachowuje te nagłówki.
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
Sprawdź logi błędów Nginx:
|
||||
|
||||
```bash
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
```
|
||||
@@ -0,0 +1,122 @@
|
||||
# Wdrożenie na Vercel
|
||||
|
||||
[Vercel](https://vercel.com) oferuje najprostszy sposób wdrażania statycznych stron.
|
||||
|
||||
## Wdrożenie jednym kliknięciem
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
|
||||
|
||||
## Wdrożenie ręczne
|
||||
|
||||
### Krok 1: Zrób fork repozytorium
|
||||
|
||||
Zrób fork [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) na swoje konto GitHub.
|
||||
|
||||
### Krok 2: Zaimportuj do Vercel
|
||||
|
||||
1. Przejdź do [vercel.com/new](https://vercel.com/new)
|
||||
2. Wybierz swój fork repozytorium
|
||||
3. Skonfiguruj projekt:
|
||||
|
||||
| Ustawienie | Wartość |
|
||||
| ---------------- | --------------- |
|
||||
| Framework Preset | Vite |
|
||||
| Build Command | `npm run build` |
|
||||
| Output Directory | `dist` |
|
||||
| Install Command | `npm install` |
|
||||
|
||||
### Krok 3: Zmienne środowiskowe (opcjonalnie)
|
||||
|
||||
Dodaj je, jeśli są potrzebne:
|
||||
|
||||
| Zmienna | Opis |
|
||||
| ----------------------- | ------------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | Ustaw na `true` dla minimalnego interfejsu |
|
||||
| `VITE_BRAND_NAME` | Własna nazwa marki (zastępuje "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | Ścieżka do logo względem `public/` (np. `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | Własny tekst stopki/praw autorskich |
|
||||
| `VITE_DEFAULT_LANGUAGE` | Domyślny język interfejsu (np. `fr`, `de`, `es`) |
|
||||
|
||||
### Krok 4: Wdróż
|
||||
|
||||
Kliknij "Deploy" i poczekaj na zakończenie budowania.
|
||||
|
||||
## Własna domena
|
||||
|
||||
1. Przejdź do ustawień projektu
|
||||
2. Otwórz "Domains"
|
||||
3. Dodaj swoją domenę
|
||||
4. Skonfiguruj DNS zgodnie z instrukcjami
|
||||
|
||||
## Ograniczenia
|
||||
|
||||
::: warning Duże pliki
|
||||
Funkcje serverless Vercel mają limit 50 MB. Ponieważ BentoPDF jest statyczną stroną, nie powinno to na Ciebie wpłynąć, ale moduły WASM są duże (łącznie ok. 100 MB). Upewnij się, że są serwowane z folderu `/public`.
|
||||
:::
|
||||
|
||||
## Rozwiązywanie problemów
|
||||
|
||||
### Przekroczenie czasu budowania
|
||||
|
||||
Jeśli budowanie przekracza limit czasu, spróbuj:
|
||||
|
||||
```json
|
||||
// vercel.json
|
||||
{
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "dist"
|
||||
}
|
||||
```
|
||||
|
||||
### Błąd 404 po odświeżeniu
|
||||
|
||||
Dodaj plik `vercel.json` dla routingu SPA:
|
||||
|
||||
```json
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
```
|
||||
|
||||
### Konwersja Word/ODT/Excel na PDF nie działa
|
||||
|
||||
Konwersje LibreOffice WASM wymagają `SharedArrayBuffer`, a to wymaga tych nagłówków odpowiedzi na wszystkich stronach:
|
||||
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
|
||||
Wstępnie skompresowane pliki `.wasm.gz` i `.data.gz` potrzebują też nagłówka `Content-Encoding: gzip`, aby przeglądarka je rozpakowała.
|
||||
|
||||
Dodaj je do swojego `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" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Aby to zweryfikować, otwórz konsolę DevTools i uruchom `console.log(window.crossOriginIsolated)`. Powinno zwrócić `true`.
|
||||
Reference in new issue
Block a user