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

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

No files matched your search

+275
View File
@@ -0,0 +1,275 @@
# 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
```
+200
View File
@@ -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"
}
}
```
+154
View File
@@ -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).
+180
View File
@@ -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
+622
View File
@@ -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
```
+266
View File
@@ -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.
+538
View File
@@ -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!
:::
+211
View File
@@ -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).
+139
View File
@@ -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
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](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.
+232
View File
@@ -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
```
+122
View File
@@ -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
[![Deploy with Vercel](https://vercel.com/button)](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`.