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 @@
# Apacheでデプロイする
Apache HTTP Serverを使ってBentoPDFをホストします。
## 前提条件
- Apache 2.4+
- mod_rewriteが有効
- SSL証明書(推奨)
## ステップ1:プロジェクトをビルドする
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
ブランディングをカスタマイズするには、ビルド前に環境変数を設定します。
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## ステップ2:ファイルをコピーする
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## ステップ3:Apacheの設定
`/etc/apache2/sites-available/bentopdf.conf` を作成します。
```apache
<VirtualHost *:80>
ServerName your-domain.com
Redirect permanent / https://your-domain.com/
</VirtualHost>
<VirtualHost *:443>
ServerName your-domain.com
DocumentRoot /var/www/bentopdf
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/your-domain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.com/privkey.pem
<Directory /var/www/bentopdf>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# WASM MIME type
AddType application/wasm .wasm
# Prevent double-compression of pre-compressed files
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
# Compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json application/wasm
</IfModule>
# Cache headers
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>
# Required headers for SharedArrayBuffer (LibreOffice WASM)
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Resource-Policy "cross-origin"
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
# Pre-compressed LibreOffice WASM files
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
</VirtualHost>
```
## ステップ4:ルーティング用の.htaccess
`/var/www/bentopdf/.htaccess` を作成します。
```apache
RewriteEngine On
RewriteBase /
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# ADD .HTML EXTENSION (ROOT LEVEL)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /404.html
```
## ステップ5:必要なモジュールを有効にする
```bash
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod expires
sudo a2enmod deflate
```
## ステップ6:サイトを有効にする
```bash
sudo a2ensite bentopdf.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
```
## サブディレクトリへのデプロイ
`/pdf/` でホストするには:
1. ベースURLを指定してビルドします。
```bash
BASE_URL=/pdf/ npm run build
```
2. `.htaccess` を更新します。
```apache
RewriteEngine On
RewriteBase /pdf/
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Language routes
RewriteRule ^en/?$ /pdf/ [R=301,L]
RewriteRule ^en/(.+)$ /pdf/$1 [R=301,L]
RewriteCond %{DOCUMENT_ROOT}/pdf/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf/$1/index.html [L]
RewriteCond %{DOCUMENT_ROOT}/pdf/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf/$1/$2.html [L]
# Root level .html extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /pdf/404.html
```
## トラブルシューティング
### WASMの404エラー
MIMEタイプが設定されていることを確認してください。
```apache
AddType application/wasm .wasm
```
### PDFへ署名またはフォーム入力で空白のビューアが表示される(`.mjs` のMIMEエラー)
ブラウザのコンソールに次のように表示される場合:
```
Failed to load module script: The server responded with a non-JavaScript
MIME type of "application/octet-stream". Strict MIME type checking is
enforced for module scripts per HTML spec.
```
…かつ失敗しているリクエストが `.mjs` で終わる場合、Apacheの設定に `.mjs → application/javascript` の対応付けがありません。同梱のPDFビューアは拡張子 `.mjs` のESモジュールファイルを配布していますが、Apache標準の `mime.types` にはデフォルトで含まれていません。
VirtualHostまたは `.htaccess` に追加します。
```apache
AddType application/javascript .mjs
```
続いてApacheをリロードします。
```bash
sudo apache2ctl configtest && sudo systemctl reload apache2
```
確認するには、ブラウザのDevToolsの「Network」タブで失敗している `.mjs` のリクエストを探し、レスポンスヘッダー `Content-Type` を確認します。`application/octet-stream` ではなく `application/javascript` になっているはずです。
### リライトが動作しない
mod_rewriteが有効になっているか確認してください。
```bash
sudo a2enmod rewrite
```
### Word/ODT/ExcelからPDFへの変換が動作しない
LibreOffice WASMには `SharedArrayBuffer` が必要で、次のヘッダーが求められます。
```apache
Header always set Cross-Origin-Embedder-Policy "require-corp"
Header always set Cross-Origin-Opener-Policy "same-origin"
```
セキュアコンテキストも必要です。ローカルでのテストでは `http://localhost` が使えますが、`http://192.168.x.x` などのLAN IPでは通常HTTPSが必要です。ヘッダーが設定されているのに `window.crossOriginIsolated` が `false` のままの場合は、ループバック以外のオリジンでページがプレーンなHTTPで開かれていないか確認してください。
事前圧縮された `.wasm.gz` と `.data.gz` ファイルにも、正しい `Content-Encoding` が必要です。
```apache
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
</FilesMatch>
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
</FilesMatch>
```
`mod_headers` が有効になっていることを確認してください:`sudo a2enmod headers`
### パーミッションエラー
```bash
sudo chown -R www-data:www-data /var/www/bentopdf
sudo chmod -R 755 /var/www/bentopdf
```
+200
View File
@@ -0,0 +1,200 @@
# AWS S3 + CloudFrontにデプロイする
最大限の制御とスケーラビリティを求めて、AWS上にBentoPDFをホストします。
## アーキテクチャ
```
User → CloudFront (CDN) → S3 (Static Files)
```
## ステップ1:S3バケットを作成する
```bash
# Create bucket
aws s3 mb s3://your-bentopdf-bucket --region us-east-1
# Enable static website hosting
aws s3 website s3://your-bentopdf-bucket \
--index-document index.html \
--error-document index.html
```
## ステップ2:ビルドしてアップロードする
```bash
# Build the project (optionally with custom branding)
# VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
npm run build
# Sync to S3
aws s3 sync dist/ s3://your-bentopdf-bucket \
--delete \
--cache-control "max-age=31536000"
# Set correct MIME types for WASM
aws s3 cp s3://your-bentopdf-bucket/ s3://your-bentopdf-bucket/ \
--recursive \
--exclude "*" \
--include "*.wasm" \
--content-type "application/wasm" \
--metadata-directive REPLACE
# Set correct MIME type for ES module files (.mjs)
# S3 defaults .mjs to application/octet-stream, which browsers refuse to
# execute as ES modules. The bundled PDF viewer (Sign PDF, Form Filler)
# ships ES modules with .mjs extensions and will fail without this step.
aws s3 cp s3://your-bentopdf-bucket/ s3://your-bentopdf-bucket/ \
--recursive \
--exclude "*" \
--include "*.mjs" \
--content-type "application/javascript" \
--metadata-directive REPLACE
```
## ステップ3:CloudFrontディストリビューションを作成する
```bash
aws cloudfront create-distribution \
--origin-domain-name your-bentopdf-bucket.s3.amazonaws.com \
--default-root-object index.html
```
またはAWSコンソールを使います。
1. CloudFront → ディストリビューションを作成 に移動します
2. オリジンドメイン:S3バケットを選択します
3. 「Origin Access Control」を有効にします
4. デフォルトルートオブジェクト:`index.html`
5. ディストリビューションを作成します
## ステップ3b:レスポンスヘッダーポリシー(LibreOffice WASMに必須)
LibreOfficeベースの変換(Word、Excel、PowerPointからPDFへ)には `SharedArrayBuffer` が必要で、特定のレスポンスヘッダーが求められます。CloudFrontのレスポンスヘッダーポリシーを作成します。
1. CloudFront → ポリシー → レスポンスヘッダー に移動します
2. 次のヘッダーでカスタムポリシーを作成します。
| ヘッダー | 値 |
| ------------------------------ | -------------- |
| `Cross-Origin-Embedder-Policy` | `require-corp` |
| `Cross-Origin-Opener-Policy` | `same-origin` |
| `Cross-Origin-Resource-Policy` | `cross-origin` |
3. ポリシーをディストリビューションのデフォルトキャッシュビヘイビアにアタッチします
CLIを使う場合:
```bash
aws cloudfront create-response-headers-policy \
--response-headers-policy-config '{
"Name": "BentoPDF-COEP-COOP",
"CustomHeadersConfig": {
"Quantity": 3,
"Items": [
{"Header": "Cross-Origin-Embedder-Policy", "Value": "require-corp", "Override": true},
{"Header": "Cross-Origin-Opener-Policy", "Value": "same-origin", "Override": true},
{"Header": "Cross-Origin-Resource-Policy", "Value": "cross-origin", "Override": true}
]
}
}'
```
## ステップ3c:事前圧縮されたWASMファイルのS3メタデータ
LibreOffice WASMファイルは事前圧縮されています(`.wasm.gz`、`.data.gz`)。ブラウザが展開できるように、正しいContent-TypeとContent-Encodingを設定します。
```bash
# Set correct headers for soffice.wasm.gz
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.wasm.gz \
--content-type "application/wasm" \
--content-encoding "gzip" \
--metadata-directive REPLACE
# Set correct headers for soffice.data.gz
aws s3 cp s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
s3://your-bentopdf-bucket/libreoffice-wasm/soffice.data.gz \
--content-type "application/octet-stream" \
--content-encoding "gzip" \
--metadata-directive REPLACE
```
::: warning 重要
レスポンスヘッダーポリシーがないと `SharedArrayBuffer` が使えず、LibreOffice WASMの変換は約55%で止まります。`.gz` ファイルに正しいContent-Encodingがないと、ブラウザは生のgzipバイトを受け取り、WASMのコンパイルに失敗します。
:::
## ステップ4:S3バケットポリシー
CloudFrontがバケットにアクセスできるようにします。
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontAccess",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bentopdf-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
}
}
}
]
}
```
## ステップ5:カスタムエラーページ
SPAのルーティングのため、404で `index.html` を返すように設定します。
1. CloudFront → エラーページ
2. カスタムエラーレスポンスを作成します。
- HTTPエラーコード:404
- レスポンスページのパス:`/index.html`
- HTTPレスポンスコード:200
## コストの目安
| リソース | 概算コスト |
| ----------------------- | ---------- |
| S3ストレージ(約500MB) | 約$0.01/月 |
| CloudFront(1TB転送) | 約$85/月 |
| CloudFront(10GB転送) | 約$0.85/月 |
::: tip
アクセス頻度の低いファイルには、S3 Intelligent-Tieringを使うとコストを最適化できます。
:::
## Terraformによる自動化
```hcl
# main.tf
resource "aws_s3_bucket" "bentopdf" {
bucket = "your-bentopdf-bucket"
}
resource "aws_cloudfront_distribution" "bentopdf" {
origin {
domain_name = aws_s3_bucket.bentopdf.bucket_regional_domain_name
origin_id = "S3Origin"
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin"
viewer_protocol_policy = "redirect-to-https"
}
}
```
+154
View File
@@ -0,0 +1,154 @@
# Cloudflare Pagesにデプロイする
[Cloudflare Pages](https://pages.cloudflare.com)は、帯域幅無制限で高速なグローバル静的サイトホスティングを提供します。
## クイックデプロイ
1. [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)に移動します
2. 「Create a project」をクリックします
3. GitHubリポジトリを接続します
## ビルド設定
| 設定 | 値 |
| ------------------------ | --------------- |
| フレームワークプリセット | None |
| ビルドコマンド | `npm run build` |
| ビルド出力ディレクトリ | `dist` |
| ルートディレクトリ | `/` |
## 環境変数
設定 → 環境変数 で次を追加します。
| 変数 | 値 |
| ----------------------- | ------------------------------------------ |
| `NODE_VERSION` | `18` |
| `SIMPLE_MODE` | `false`(任意) |
| `VITE_BRAND_NAME` | カスタムブランド名(任意) |
| `VITE_BRAND_LOGO` | `public/` からの相対ロゴパス(任意) |
| `VITE_FOOTER_TEXT` | カスタムのフッター/著作権テキスト(任意) |
| `VITE_DEFAULT_LANGUAGE` | デフォルトのUI言語、例:`fr`(任意) |
## 設定ファイル
`public` フォルダに `_headers` を作成します。
```
# 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 重要
Word/ODT/Excel/PowerPointからPDFへの変換には、`Cross-Origin-Embedder-Policy` ヘッダーと `Cross-Origin-Opener-Policy` ヘッダーが必要です。これらがないと `SharedArrayBuffer` が使えず、LibreOffice WASMエンジンの初期化に失敗します。
:::
SPAのルーティング用に `_redirects` を作成します。
```
/* /index.html 200
```
## カスタムドメイン
1. Pagesプロジェクトに移動します
2. 「Custom domains」をクリックします
3. ドメインを追加します
4. ドメインがCloudflareにある場合、CloudflareがDNSを自動設定します
## メリット
- **帯域幅無制限で無料**
- 300以上のエッジロケーションを持つ**グローバルCDN**
- **自動HTTPS**
- プルリクエストの**プレビューデプロイ**
- **高速なビルド**
## トラブルシューティング
### 大きなファイルのアップロード
Cloudflare Pagesは最大25 MBのファイルに対応しています。WASMモジュールは問題ないはずですが、上限に達した場合は次を検討してください。
```bash
# Split large files during build
npm run build
```
### Workerのサイズ制限
高度なルーティングにCloudflare Workersを使う場合は、無料プランでは1 MBの制限がある点に注意してください。
## CORSプロキシWorker(デジタル署名用)
デジタル署名ツールが証明書チェーンを取得するには、CORSプロキシが必要です。同梱のWorkerをデプロイします。
```bash
cd cloudflare
npx wrangler login
npx wrangler deploy
```
### セキュリティ機能
| 機能 | 説明 |
| ---------------------------- | ------------------------------ |
| **URL制限** | 証明書のURLのみ許可 |
| **ファイルサイズ制限** | 1リクエストあたり最大10MB |
| **レート制限** | 60リクエスト/IP/分(KVが必要) |
| **プライベートIPのブロック** | localhostと内部IPをブロック |
### レート制限を有効にする
```bash
# Create KV namespace
npx wrangler kv namespace create "RATE_LIMIT_KV"
# Add to wrangler.toml with returned ID:
# [[kv_namespaces]]
# binding = "RATE_LIMIT_KV"
# id = "YOUR_ID"
npx wrangler deploy
```
### プロキシURLを指定してビルドする
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
Dockerを使う場合:
```bash
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
DOCKER_BUILDKIT=1 docker build \
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
-t your-bentopdf .
```
> **注:** HMAC署名の設定については[README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required)を参照してください。
+180
View File
@@ -0,0 +1,180 @@
# 証明書取得用のCORSプロキシ
デジタル署名ツールは、外部の認証局(CA)から発行者証明書を取得するためにCORSプロキシを使います。多くのCAサーバーはレスポンスにCORSヘッダーを含めないため、ブラウザから直接取得することができません。
さらに、多くのCAサーバーはプレーンなHTTPで証明書を提供しています。BentoPDFのインスタンスをHTTPSでホストしている場合、ブラウザはこれらのHTTPリクエストをブロックします(混合コンテンツポリシー)。CORSプロキシは、適切なヘッダーを付けたHTTPSエンドポイント経由でリクエストを中継することで、この2つの問題を解決します。
## 仕組み
証明書でPDFに署名するとき:
1. `zgapdfsigner` ライブラリが完全な証明書チェーンの構築を試みます
2. 証明書のAIA(Authority Information Access)拡張に埋め込まれたURLから、発行者証明書を取得します
3. これらのリクエストは、必要な `Access-Control-Allow-Origin` ヘッダーを追加するCORSプロキシ経由で送られます
4. プロキシが証明書データをブラウザに返します
## CORSプロキシのセルフホスト
BentoPDFをセルフホストしている場合、チェーンの取得が必要な証明書でデジタル署名を使うには、独自のCORSプロキシをデプロイする必要があります。
### オプション1:Cloudflare Workers(推奨)
1. **Wrangler CLIをインストールする**:
```bash
npm install -g wrangler
```
2. **Cloudflareにログインする**:
```bash
wrangler login
```
3. **BentoPDFをクローンし、許可するオリジンを更新する**:
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf/cloudflare
```
`cors-proxy-worker.js` を開き、`ALLOWED_ORIGINS` 配列を自分のドメインに変更します。
```js
const ALLOWED_ORIGINS = [
'https://your-domain.com',
'https://www.your-domain.com',
];
```
::: warning 重要
この変更をしないと、プロキシはサイトからのすべてのリクエストを **403 Forbidden** エラーで拒否します。デフォルトでは `bentopdf.com` からのリクエストのみ許可されています。
:::
4. **プロキシをデプロイする**:
```bash
wrangler deploy
```
WorkerのURL(例:`https://bentopdf-cors-proxy.your-subdomain.workers.dev`)を控えておきます。
5. **プロキシURLを指定してBentoPDFを再ビルドする**:
Dockerを使う場合:
```bash
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
DOCKER_BUILDKIT=1 docker build \
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
-t your-bentopdf .
```
ソースからビルドする場合:
```bash
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
```
### オプション2:独自のバックエンドプロキシ
独自のプロキシエンドポイントを作ることもできます。要件は次のとおりです。
1. `url` クエリパラメータ付きのGETリクエストを受け付ける
2. サーバーからそのURLを取得する(サーバー側ではCORSの制限はありません)
3. 次のヘッダーを付けてレスポンスを返す:
- `Access-Control-Allow-Origin: https://your-domain.com`
- `Access-Control-Allow-Methods: GET, OPTIONS`
- `X-Content-Type-Options: nosniff`
Express.jsでの実装例:
```javascript
app.get('/api/cert-proxy', async (req, res) => {
const targetUrl = req.query.url;
// Validate it's a certificate URL
if (!isValidCertUrl(targetUrl)) {
return res.status(400).json({ error: 'Invalid URL' });
}
try {
const response = await fetch(targetUrl);
const data = await response.arrayBuffer();
res.set('Access-Control-Allow-Origin', 'https://your-domain.com');
res.set('Content-Type', 'application/octet-stream');
res.set('X-Content-Type-Options', 'nosniff');
res.send(Buffer.from(data));
} catch (error) {
res.status(500).json({ error: 'Proxy error' });
}
});
```
## セキュリティ上の考慮事項
同梱のCloudflare Workerには、いくつかのセキュリティ対策があります。
| 機能 | 説明 |
| ---------------------------- | ------------------------------------------------------------------------------------------------- |
| **オリジン検証** | `ALLOWED_ORIGINS` に記載されたドメインからのリクエストのみ許可 |
| **URL制限** | 証明書のURL(`.crt`、`.cer`、`.pem`、`/certs/`、`/ocsp`、`/crl`)のみ許可 |
| **プライベートIPのブロック** | IPv4/IPv6のプライベート範囲、リンクローカル、ループバック、10進数IP、クラウドメタデータをブロック |
| **Content-Typeの安全性** | 安全な証明書のMIMEタイプのみ返し、上流のcontent-typeインジェクションをブロック |
| **ファイルサイズ制限** | レスポンスを10MBの上限でストリーミングし、超過するとダウンロード途中で中断 |
| **レート制限** | IPまたはIPv6 /64ごとに1分あたり60リクエスト(KVが必要、KVエラー時はフェイルオープン) |
| **HMAC署名** | 任意のクライアント側署名(気軽な悪用の抑止) |
### DNSリバインディング(Cloudflare以外でのセルフホスト)
このWorkerは、プライベートまたは内部のIPに解決されるURLをすべて拒否します。証明書チェーンのURLは、その証明書を発行したCA(FNMT、企業のCAなど)を指すため、ホストの固定許可リストは使えず、宛先を事前に把握できません。そのため、リバインディングの小さな隙が残ります。自分のドメインのDNSを運用する人が、安全性チェックには公開IPを、その直後の実際のリクエストにはプライベートIPを返す可能性があります。
Cloudflare上では、WorkersがプライベートIPや `169.254.169.254` のメタデータエンドポイントに到達できないため、悪用はできません。リバインドしても何にも届きません。ただし、Workerに侵入経路を与えないでください。Tunnel、Service、プライベートネットワークのバインディングはアタッチしないでください(必要なのは `RATE_LIMIT_KV` だけです)。
VPC内でCloudflare以外(workerdやNodeなど)で実行する場合、ランタイムは内部IPに到達*できてしまいます*。前段にエグレスファイアウォールを置き、プライベート範囲(`10.0.0.0/8`、`172.16.0.0/12`、`192.168.0.0/16`、`127.0.0.0/8`、`169.254.0.0/16`、およびそのIPv6相当)をブロックしてください。Cloudflareを離れると、アプリケーションコードではこれを強制できません。
## プロキシを無効にする
CORSプロキシを使いたくない場合は、環境変数を空文字列に設定します。
```
VITE_CORS_PROXY_URL=
```
**注**:プロキシがないと、外部からのチェーン取得が必要な証明書(FNMTや一部の企業CAなど)での署名は、「Failed to fetch」エラーで失敗します。
## トラブルシューティング
### 「Signing error: TypeError: Failed to fetch」
通常は、次のいずれかを意味します。
1. **CORSプロキシが未設定**:`VITE_CORS_PROXY_URL` を設定して再ビルドしてください
2. **混合コンテンツがブロックされている**:サイトはHTTPSですが、証明書の発行者URLはHTTPです。CORSプロキシで解決できます。
3. **CORSプロキシがオリジンを拒否している**:`cors-proxy-worker.js` の `ALLOWED_ORIGINS` 配列にドメインが含まれているか確認してください
### プロキシからの「403 Forbidden」
ドメインが `ALLOWED_ORIGINS` リストに含まれていません。`cors-proxy-worker.js` を編集します。
```js
const ALLOWED_ORIGINS = ['https://your-domain.com'];
```
その後、再デプロイします:`npx wrangler deploy`
### プロキシのテスト
```bash
curl -H "Origin: https://your-domain.com" \
"https://your-proxy.workers.dev?url=http://www.cert.fnmt.es/certs/ACUSU.crt"
```
### プロキシなしで動作する証明書
一部の証明書はP12/PFXファイルに完全なチェーンを含んでおり、外部からの取得を必要としません。
- 自己署名証明書
- 中間証明書をバンドルしている一部の商用CA
- 完全なチェーンを手動で組み立てた証明書
+622
View File
@@ -0,0 +1,622 @@
# Docker / Podmanでデプロイする
本番環境でBentoPDFをセルフホストする最も簡単な方法です。
> [!IMPORTANT]
> **Officeファイル変換に必要なヘッダー**
>
> LibreOfficeベースのツール(Word、Excel、PowerPointの変換)が `SharedArrayBuffer` を使うには、次のHTTPヘッダーが必要です。
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> ページはセキュアコンテキストから配信される必要もあります。ローカルでのテストでは `http://localhost` が使えますが、`http://192.168.x.x` などのLAN IPは通常これに該当しないため、ヘッダーが設定されていてもプレーンなHTTPではOffice変換が失敗します。
>
> 公式のコンテナイメージにはこれらのヘッダーが含まれています。リバースプロキシ(Traefik、Caddyなど)を使う場合は、これらのヘッダーが保持されるか追加されるようにし、ループバック以外からのアクセスにはHTTPSを使ってください。
> [!TIP]
> **Podmanユーザー:** すべての `docker` コマンドは、`docker` を `podman` に、`docker-compose` を `podman-compose` に置き換えればPodmanでも動作します。
## クイックスタート
> [!TIP]
> BentoPDFには2つのビルドがあります。
>
> - **セルフホスト版ビルド**:`ghcr.io/alam00000/bentopdf-simple:latest`。マーケティング用の要素(ヒーロー、FAQ、お客様の声、フッター)**なし**で、すべてのPDFツールを備えています。社内・チーム・組織向けのデプロイに使います。機能を減らした「ライト」版では**ありません**。
> - **商用版ビルド**:`ghcr.io/alam00000/bentopdf:latest`。マーケティングサイト全体を含み、bentopdf.com自体や、公開向けにデプロイする商用ライセンス保持者が使います。
>
> 迷ったらセルフホスト版ビルドを取得してください。
```bash
# Docker
docker run -d \
--name bentopdf \
-p 3000:8080 \
--restart unless-stopped \
ghcr.io/alam00000/bentopdf-simple:latest
# Podman
podman run -d \
--name bentopdf \
-p 3000:8080 \
ghcr.io/alam00000/bentopdf-simple:latest
```
## Docker Compose / Podman Compose
`docker-compose.yml` を作成します。
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
container_name: bentopdf
ports:
- '3000:8080'
restart: unless-stopped
healthcheck:
test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8080']
interval: 30s
timeout: 10s
retries: 3
```
実行します。
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## 独自イメージをビルドする
```dockerfile
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginxinc/nginx-unprivileged:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
```
ビルドして実行します。
```bash
docker build -t bentopdf:custom .
docker run -d -p 3000:8080 bentopdf:custom
```
## 環境変数
| 変数 | 説明 | デフォルト |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `SIMPLE_MODE` | セルフホスト版をビルドします(bentopdf.com のマーケティング要素であるヒーロー、FAQ、お客様の声、フッターを非表示にします)。すべてのPDFツールは残ります。 | `false` |
| `BASE_URL` | サブディレクトリへのデプロイ | `/` |
| `VITE_WASM_PYMUPDF_URL` | PyMuPDF WASMモジュールのURL | `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/` |
| `VITE_WASM_GS_URL` | Ghostscript WASMモジュールのURL | `https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/` |
| `VITE_WASM_CPDF_URL` | CoherentPDF WASMモジュールのURL | `https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/` |
| `VITE_TESSERACT_WORKER_URL` | OCRワーカースクリプトのURL | _(空。Tesseract.jsのデフォルトCDNを使用)_ |
| `VITE_TESSERACT_CORE_URL` | OCRコアランタイムのディレクトリ | _(空。Tesseract.jsのデフォルトCDNを使用)_ |
| `VITE_TESSERACT_LANG_URL` | OCR traineddataのディレクトリ | _(空。Tesseract.jsのデフォルトCDNを使用)_ |
| `VITE_TESSERACT_AVAILABLE_LANGUAGES` | UIに表示するOCR言語(カンマ区切り) | _(空。全カタログを表示)_ |
| `VITE_OCR_FONT_BASE_URL` | OCRテキストレイヤー用フォントのディレクトリ | _(空。リモートのNotoフォントURLを使用)_ |
| `VITE_DEFAULT_LANGUAGE` | デフォルトのUI言語 | `en` |
| `VITE_BRAND_NAME` | カスタムブランド名 | `BentoPDF` |
| `VITE_BRAND_LOGO` | `public/` からの相対ロゴパス | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | カスタムのフッター/著作権テキスト | `© 2026 BentoPDF. All rights reserved.` |
| `DISABLE_TOOLS` | 非表示にするツールID(カンマ区切り) | _(空。すべてのツールが有効)_ |
| `DISABLE_GITHUB_STARS` | ページ読み込み時のGitHubスター数の取得をスキップし、CSPから `api.github.com` を除外します。Simple Modeのビルドでは常にスキップされます。 | `false` |
| `SITE_URL` | canonical、hreflang、サイトマップのリンクに書き込まれる公開URL。検索エンジンにデプロイ先を独立したサイトとして扱わせたい場合を除き、デフォルトのままにしてください。 | `https://www.bentopdf.com` |
| `ALLOW_INDEXING` | カスタムの `SITE_URL` を使う場合、検索結果でbentopdf.comと競合しないよう、すべてのページが `noindex` になります。検索エンジンにインデックスさせるには `true` に設定します。 | `false` |
WASMモジュールのURLには、CDNのデフォルトがあらかじめ設定されているため、高度な機能はすべてそのまま動作します。エアギャップ環境やセルフホスト環境では、これらを上書きしてください。
### 検索エンジン
デフォルトでは、すべてのページが元のページとしてbentopdf.comを検索エンジンに示すため、デプロイ先が重複ページとして表示されることはありません。プライベートな環境や社内のインスタンスでは、何も変更する必要はありません。
- **インスタンスを検索結果にまったく表示させない:** コンテナを `-e ROBOTS_NOINDEX=true` で実行します。再起動だけで済み、再ビルドは不要です。
- **インスタンスを検索結果で独立したサイトにする:** `--build-arg SITE_URL=https://your-domain.example --build-arg ALLOW_INDEXING=true` を付けてビルドします。`ALLOW_INDEXING=true` がないと、カスタムの `SITE_URL` ではすべてのページが `noindex` になります。
### Content-Security-Policy
nginxイメージには、強制モードの `Content-Security-Policy` ヘッダーが含まれています。CSPの `script-src`、`connect-src`、`font-src` ディレクティブは、上記の `VITE_*` URL変数から**ビルド時に生成**されます。`--build-arg` で渡したホストは自動的にポリシーに追加されます。
そのため:
- ビルド時に `VITE_CORS_PROXY_URL` や `VITE_WASM_*_URL` を上書きすると、CSPがそれらのオリジンを自動的に許可します。追加の設定は不要です。
- `cdn.jsdelivr.net` 上のURLでは、`script-src` はCDN全体ではなく、バージョンまで含む正確なパッケージフォルダ(例:`https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`)だけを許可します。jsDelivrは公開されているあらゆるnpmパッケージやGitHubリポジトリを配信するため、ホスト全体を許可すると、そのどれもがインスタンス上で実行できてしまうからです。それ以外のホスト上のURLは、オリジン単位で許可されます。
- アプリ内の詳細設定ページで、_実行時_ にカスタムのWASM URLを設定した場合、それらのオリジンはCSPに含まれないため、ブラウザはそれらへのフェッチをブロックします。ビルド時に設定したバージョンのみが許可されるため、`cdn.jsdelivr.net` 上の同じパッケージの別バージョンにも当てはまります。実行時の設定は試験用です。恒久的なカスタムURLには、対応する `VITE_*` のビルド引数を設定してください。
- 3つの `VITE_WASM_*_URL` をすべて上書きしたエアギャップ環境のデプロイでは、それらの `cdn.jsdelivr.net` パッケージフォルダもCSPから削除されます(各デフォルトは追加ではなく置換されます)。`cdn.jsdelivr.net` 上のTesseract OCRのデフォルトは、3つの `VITE_TESSERACT_*` も設定するまでポリシーに残ります。同様に、`VITE_CORS_PROXY_URL` を設定すると、公開の `bentopdf-cors-proxy.bentopdf.workers.dev` というデフォルトが置き換えられます。
CSPの `script-src` に `'unsafe-eval'` が含まれているのは、LibreOffice WASMランタイム(Word/Excel/PowerPoint変換ツールで使用)とwasm-vips画像ライブラリ(PDFからTIFFで使用)が、`new Function()` で内部のディスパッチコードをコンパイルするためです。これを削除するとそれらのツールが動作しなくなります。`SIMPLE_MODE`(LibreOfficeなし)でビルドし、PDFからTIFFが不要な場合は、生成された `security-headers.conf` を手動で編集して `'unsafe-eval'` を外すことで、より厳格なポリシーにできます。
OCRでは、`VITE_TESSERACT_*` 変数を空のままにするとデフォルトのオンラインアセットを使い、セルフホスト/オフラインのOCRでは3つすべてを一緒に設定します。ワーカー、コアランタイム、traineddataディレクトリは対応している必要があるため、一部だけの上書きは拒否されます。完全オフラインで検索可能なPDFを出力するには、`VITE_OCR_FONT_BASE_URL` も設定して、OCRテキストレイヤー用フォントを公開のNotoフォントURLではなく社内サーバーから読み込むようにします。
`VITE_DEFAULT_LANGUAGE` は、初めて訪れたユーザーのUI言語を設定します。サポートされる値:`en`、`ar`、`be`、`fr`、`de`、`es`、`zh`、`zh-TW`、`vi`、`tr`、`id`、`it`、`pt`、`nl`、`da`。ユーザーは引き続き言語を切り替えられ、これは既定値を変えるだけです。
例:
```bash
# Build with French as the default language
docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf .
docker run -d -p 3000:8080 bentopdf
```
### カスタムブランディング
デフォルトのBentoPDFのロゴ、名前、フッターテキストを独自のものに置き換えます。ロゴファイルを `public/` フォルダに置く(または既存の画像を使う)、ビルド時にブランディング用の変数を渡します。
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
-t acmepdf .
```
ブランディングはフルモードとSimple Modeの両方で動作し、他のすべてのビルド時オプションと組み合わせられます。
### 特定のツールを無効にする
コンプライアンスやセキュリティ要件に応じて、UIからツールを非表示にします。無効にしたツールは、ホームページ、検索結果、キーボードショートカット、ワークフロービルダーから削除されます。URLに直接アクセスすると「ツールは利用できません」ページが表示されます。
ツールIDは、ページURLから `.html` を除いたものです。たとえばツールが `bentopdf.com/edit-pdf.html` にある場合、IDは `edit-pdf` です。
#### ツールIDの調べ方
最も簡単な方法:BentoPDFで任意のツールを開き、URLを見ます。パスの最後の部分(`.html` を除く)がツールIDです。
<details>
<summary>ツールIDの完全な一覧</summary>
**編集と注釈:** `edit-pdf`, `bookmark`, `table-of-contents`, `page-numbers`, `add-page-labels`, `bates-numbering`, `add-watermark`, `header-footer`, `invert-colors`, `scanner-effect`, `adjust-colors`, `background-color`, `text-color`, `sign-pdf`, `add-stamps`, `remove-annotations`, `crop-pdf`, `form-filler`, `form-creator`, `remove-blank-pages`
**PDFに変換:** `image-to-pdf`, `jpg-to-pdf`, `png-to-pdf`, `webp-to-pdf`, `svg-to-pdf`, `bmp-to-pdf`, `heic-to-pdf`, `tiff-to-pdf`, `txt-to-pdf`, `markdown-to-pdf`, `json-to-pdf`, `csv-to-pdf`, `rtf-to-pdf`, `odt-to-pdf`, `word-to-pdf`, `excel-to-pdf`, `powerpoint-to-pdf`, `xps-to-pdf`, `mobi-to-pdf`, `epub-to-pdf`, `fb2-to-pdf`, `cbz-to-pdf`, `wpd-to-pdf`, `wps-to-pdf`, `xml-to-pdf`, `pages-to-pdf`, `odg-to-pdf`, `ods-to-pdf`, `odp-to-pdf`, `pub-to-pdf`, `vsd-to-pdf`, `psd-to-pdf`, `email-to-pdf`
**PDFから変換:** `pdf-to-jpg`, `pdf-to-png`, `pdf-to-webp`, `pdf-to-bmp`, `pdf-to-tiff`, `pdf-to-cbz`, `pdf-to-svg`, `pdf-to-csv`, `pdf-to-excel`, `pdf-to-greyscale`, `pdf-to-json`, `pdf-to-docx`, `extract-images`, `pdf-to-markdown`, `prepare-pdf-for-ai`, `pdf-to-text`
**整理と管理:** `ocr-pdf`, `merge-pdf`, `alternate-merge`, `organize-pdf`, `add-attachments`, `extract-attachments`, `edit-attachments`, `pdf-multi-tool`, `pdf-layers`, `extract-tables`, `split-pdf`, `divide-pages`, `extract-pages`, `delete-pages`, `add-blank-page`, `reverse-pages`, `rotate-pdf`, `rotate-custom`, `n-up-pdf`, `pdf-booklet`, `combine-single-page`, `view-metadata`, `edit-metadata`, `pdf-to-zip`, `compare-pdfs`, `posterize-pdf`, `page-dimensions`
**最適化と修復:** `compress-pdf`, `pdf-to-pdfa`, `fix-page-size`, `linearize-pdf`, `remove-restrictions`, `repair-pdf`, `rasterize-pdf`, `deskew-pdf`, `font-to-outline`
**セキュリティ:** `encrypt-pdf`, `sanitize-pdf`, `decrypt-pdf`, `flatten-pdf`, `remove-metadata`, `change-permissions`, `digital-sign-pdf`, `validate-signature-pdf`, `timestamp-pdf`
</details>
#### オプション1:ビルド時(Dockerのビルド引数)
```bash
docker build \
--build-arg DISABLE_TOOLS="edit-pdf,sign-pdf,encrypt-pdf" \
-t bentopdf .
```
これにより、無効化リストがJavaScriptバンドルに組み込まれます。変更するには再ビルドが必要です。
#### オプション2:実行時(config.json)
`config.json` ファイルを配信ディレクトリにマウントします。再ビルドは不要です。
```json
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
```bash
docker run -d \
-p 3000:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
ghcr.io/alam00000/bentopdf-simple:latest
```
Docker Composeを使う場合:
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
ports:
- '3000:8080'
volumes:
- ./config.json:/usr/share/nginx/html/config.json:ro
```
両方の方法を組み合わせることもでき、その場合はリストが統合されます。どちらかに含まれるツールは無効になります。
#### エディタ機能を無効にする
エディタツール全体を無効にせずに、PDFエディタ内の特定の機能(墨消し、注釈、フォームなど)だけを無効にすることもできます。`config.json` に `editorDisabledCategories` を追加します。
```json
{
"editorDisabledCategories": ["redaction", "annotation-stamp"]
}
```
<details>
<summary>エディタカテゴリの完全な一覧</summary>
**ズーム:** `zoom`, `zoom-in`, `zoom-out`, `zoom-fit-page`, `zoom-fit-width`, `zoom-marquee`, `zoom-level`
**注釈:** `annotation`, `annotation-markup`, `annotation-highlight`, `annotation-underline`, `annotation-strikeout`, `annotation-squiggly`, `annotation-ink`, `annotation-text`, `annotation-stamp`
**図形:** `annotation-shape`, `annotation-rectangle`, `annotation-circle`, `annotation-line`, `annotation-arrow`, `annotation-polygon`, `annotation-polyline`
**フォーム:** `form`, `form-textfield`, `form-checkbox`, `form-radio`, `form-select`, `form-listbox`, `form-fill-mode`
**墨消し:** `redaction`, `redaction-area`, `redaction-text`, `redaction-apply`, `redaction-clear`
**ドキュメント:** `document`, `document-open`, `document-close`, `document-print`, `document-capture`, `document-export`, `document-fullscreen`, `document-protect`
**ページ:** `page`, `spread`, `rotate`, `scroll`, `navigation`
**パネル:** `panel`, `panel-sidebar`, `panel-search`, `panel-comment`
**ツール:** `tools`, `pan`, `pointer`, `capture`
**選択:** `selection`, `selection-copy`
**履歴:** `history`, `history-undo`, `history-redo`
</details>
カテゴリは階層構造になっており、親(例:`annotation`)を無効にすると、その子もすべて無効になります。
### GitHubスターカウンターを無効にする
ナビゲーションバーには、ページ読み込み時に `api.github.com` から取得するGitHubスター数のライブ表示があります。これはPDF機能とは無関係な、唯一の外部リクエストです。ビルド時に無効にすると、フェッチのコードがバンドルから取り除かれ、Content-Security-Policyから `api.github.com` も除外されます。
```bash
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
```
Simple Modeのビルド(`bentopdf-simple`)では常にスターカウンターをスキップします。このフラグが必要なのは、商用版ビルドまたはカスタムのフルビルドの場合だけです。
### カスタムWASM URL(エアギャップ/セルフホスト)
> [!IMPORTANT]
> WASMのURLは**ビルド時**にJavaScriptへ組み込まれます。WASMファイルは実行時に**ユーザーのブラウザ**がダウンロードするもので、Dockerはビルド中にダウンロードしません。エアギャップのネットワークでは、ブラウザから到達できる社内サーバーでWASMファイルをホストする必要があります。
**エアギャップ環境の完全なワークフロー:**
```bash
# 1. On a machine WITH internet — download WASM packages
bash scripts/prepare-airgap.sh --list-ocr-languages
bash scripts/prepare-airgap.sh --search-ocr-language german
# 2. Download WASM/OCR packages
npm pack @bentopdf/pymupdf-wasm@0.11.14
npm pack @bentopdf/gs-wasm
npm pack coherentpdf
npm pack tesseract.js@7.0.0
npm pack tesseract.js-core@7.0.0
mkdir -p tesseract-langdata
curl -fsSL https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz -o tesseract-langdata/eng.traineddata.gz
mkdir -p ocr-fonts
curl -fsSL https://raw.githack.com/googlefonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf -o ocr-fonts/NotoSans-Regular.ttf
# 3. Build the image with your internal server URLs
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
--build-arg VITE_TESSERACT_AVAILABLE_LANGUAGES=eng,deu \
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
-t bentopdf .
# 4. Export the image
docker save bentopdf -o bentopdf.tar
# 5. Transfer bentopdf.tar + the .tgz packages + tesseract-langdata/ + ocr-fonts/ into the air-gapped network
# 6. Inside the air-gapped network — load and run
docker load -i bentopdf.tar
# Extract WASM packages to your internal web server
mkdir -p /var/www/wasm/pymupdf /var/www/wasm/gs /var/www/wasm/cpdf /var/www/wasm/ocr/core /var/www/wasm/ocr/lang-data /var/www/wasm/ocr/fonts
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C /var/www/wasm/pymupdf --strip-components=1
tar xzf bentopdf-gs-wasm-*.tgz -C /var/www/wasm/gs --strip-components=1
tar xzf coherentpdf-*.tgz -C /var/www/wasm/cpdf --strip-components=1
TEMP_TESS=$(mktemp -d)
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
cp "$TEMP_TESS/package/dist/worker.min.js" /var/www/wasm/ocr/worker.min.js
rm -rf "$TEMP_TESS"
tar xzf tesseract.js-core-7.0.0.tgz -C /var/www/wasm/ocr/core --strip-components=1
cp ./tesseract-langdata/*.traineddata.gz /var/www/wasm/ocr/lang-data/
cp ./ocr-fonts/* /var/www/wasm/ocr/fonts/
# Run BentoPDF
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
```
`bash scripts/prepare-airgap.sh --list-ocr-languages` で表示されるコード、または `bash scripts/prepare-airgap.sh --search-ocr-language <term>` で名前から検索したコードを、`--ocr-languages` に使います。OCR言語のサブセットを制限してビルドする場合は、同じコードを `VITE_TESSERACT_AVAILABLE_LANGUAGES` に渡し、アプリにバンドルされた言語だけが表示されるようにします。完全オフラインのOCR出力には、同梱の `ocr-fonts/` ディレクトリもホストし、`VITE_OCR_FONT_BASE_URL` をそこに向けてください。
変数を空文字列に設定すると、そのモジュールが無効になります(ユーザーは詳細設定から手動で設定する必要があります)。
## カスタムポート
デフォルトでは、BentoPDFはコンテナ内のポート `8080` で待ち受けます。変更するには、実行時に `PORT` 環境変数を設定します。
```bash
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
```
| 変数 | 説明 | デフォルト |
| ------ | --------------------------------- | ---------- |
| `PORT` | コンテナ内のNginxの待ち受けポート | `8080` |
これは標準のDockerfileとnonrootのDockerfileの両方で動作します。
## カスタムユーザーID(PUID/PGID)
特定の非rootユーザーでの実行が必要な環境(NASデバイス、セキュリティコンテキストを使うKubernetes、組織のポリシーなど)向けに、BentoPDFはLSIO形式のPUID/PGIDに対応した別のDockerfileを提供しています。
### ビルドと実行
```bash
# Build the non-root image
docker build -f Dockerfile.nonroot -t bentopdf-nonroot .
# Run with custom UID/GID
docker run -d \
--name bentopdf \
-p 3000:8080 \
-e PUID=1000 \
-e PGID=1000 \
--restart unless-stopped \
bentopdf-nonroot
```
### 環境変数
| 変数 | 説明 | デフォルト |
| -------------- | ------------------------ | ---------- |
| `PUID` | 実行するユーザーID | `1000` |
| `PGID` | 実行するグループID | `1000` |
| `PORT` | Nginxの待ち受けポート | `8080` |
| `DISABLE_IPV6` | IPv6リスナーを無効にする | `false` |
### Docker Compose
```yaml
services:
bentopdf:
build:
context: .
dockerfile: Dockerfile.nonroot
container_name: bentopdf
ports:
- '3000:8080'
environment:
- PUID=1000
- PGID=1000
restart: unless-stopped
```
### 仕組み
コンテナはrootで起動し、指定されたPUID/PGIDでユーザーを作成して、書き込み可能なすべてのディレクトリの所有権を調整し、`su-exec` で権限を落とします。nginxプロセスは、指定したユーザーとして完全に実行されます。
> [!NOTE]
> 標準の `Dockerfile` は `nginx-unprivileged`(UID 101)を使い、ほとんどのデプロイで推奨されます。`Dockerfile.nonroot` は、特定のUID/GIDが必要な場合にのみ使ってください。
> [!WARNING]
> PUID/PGIDは `0`(root)にできません。エントリポイントが入力を検証し、無効な値の場合はエラーで終了します。
## Traefik(リバースプロキシ)との併用
```yaml
services:
traefik:
image: traefik:v2.10
command:
- '--providers.docker=true'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--certificatesresolvers.letsencrypt.acme.email=you@example.com'
- '--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json'
- '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web'
ports:
- '80:80'
- '443:443'
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.bentopdf.rule=Host(`pdf.example.com`)'
- 'traefik.http.routers.bentopdf.entrypoints=websecure'
- 'traefik.http.routers.bentopdf.tls.certresolver=letsencrypt'
- 'traefik.http.services.bentopdf.loadbalancer.server.port=8080'
# Required headers for SharedArrayBuffer (LibreOffice WASM)
- 'traefik.http.routers.bentopdf.middlewares=bentopdf-headers'
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Opener-Policy=same-origin'
- 'traefik.http.middlewares.bentopdf-headers.headers.customresponseheaders.Cross-Origin-Embedder-Policy=require-corp'
restart: unless-stopped
```
## Caddy(リバースプロキシ)との併用
```yaml
services:
caddy:
image: caddy:2
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
restart: unless-stopped
volumes:
caddy_data:
```
Caddyfile:
```
pdf.example.com {
reverse_proxy bentopdf:8080
header Cross-Origin-Opener-Policy "same-origin"
header Cross-Origin-Embedder-Policy "require-corp"
}
```
## リソース制限
```yaml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
```
## Podman Quadlet(Systemd連携)
[Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html)を使うと、Podmanコンテナをsystemdサービスとして実行できます。Linuxシステムでの本番デプロイに最適です。
### 基本的なQuadletのセットアップ
コンテナユニットファイルを `~/.config/containers/systemd/bentopdf.container`(ユーザー)または `/etc/containers/systemd/bentopdf.container`(システム)に作成します。
```ini
[Unit]
Description=BentoPDF - Privacy-first PDF toolkit
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### 有効化と起動
```bash
# Reload systemd to detect new unit
systemctl --user daemon-reload
# Start the service
systemctl --user start bentopdf
# Enable on boot
systemctl --user enable bentopdf
# Check status
systemctl --user status bentopdf
# View logs
journalctl --user -u bentopdf -f
```
> [!TIP]
> システム全体にデプロイする場合は、`--user` フラグなしで `systemctl` を使い、ファイルを `/etc/containers/systemd/` に置いてください。
### ヘルスチェック付きのQuadlet
```ini
[Unit]
Description=BentoPDF with health monitoring
After=network-online.target
Wants=network-online.target
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
HealthCmd=wget --spider -q http://localhost:8080 || exit 1
HealthInterval=30s
HealthTimeout=10s
HealthRetries=3
[Service]
Restart=always
TimeoutStartSec=300
[Install]
WantedBy=default.target
```
### Quadletによる自動更新
Podmanは、新しいイメージが利用可能になると、コンテナを自動的に更新できます。
```bash
# Enable auto-update timer
systemctl --user enable --now podman-auto-update.timer
# Check for updates manually
podman auto-update
# Dry run (check without updating)
podman auto-update --dry-run
```
### Quadletのネットワーク設定
カスタムのネットワーク設定には、ネットワークファイル `bentopdf.network` を作成します。
```ini
[Network]
Subnet=10.89.0.0/24
Gateway=10.89.0.1
```
続いて、コンテナファイルでそれを参照します。
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
Network=bentopdf.network
```
## 更新
```bash
# Pull latest image
docker compose pull
# Recreate container
docker compose up -d
```
+266
View File
@@ -0,0 +1,266 @@
# Hostingerにデプロイする
[Hostinger](https://hostinger.com)は人気のある共有ホスティングプロバイダーです。このガイドでは、Hostingerの共有ホスティングにBentoPDFをデプロイする方法を説明します。
## 前提条件
- ファイルマネージャーにアクセスできるHostingerのホスティングプラン
- ビルド用にローカルにインストールされたNode.js
## ステップ1:プロジェクトをビルドする
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
ビルドされたファイルは `dist` フォルダに出力されます。
## ステップ2:Hostingerにアップロードする
### ルートドメインへのデプロイ
1. Hostingerのアカウントにログインします
2. **ファイルマネージャー** → **public_html** に移動します
3. 既存のファイルをすべて**削除**します(必要ならバックアップしてください)
4. ローカルの `dist` フォルダの中身をすべて `public_html` に**アップロード**します
### サブディレクトリへのデプロイ
サブディレクトリ(例:`yourdomain.com/pdf-tools/`)にデプロイする場合:
1. 正しいベースURLを指定してビルドします。
```bash
BASE_URL=/pdf-tools/ npm run build
```
2. Hostingerにフォルダを作成します。
- **ファイルマネージャー** → **public_html** に移動します
- 新しいフォルダ `pdf-tools` を作成します
3. `dist` の中身をすべて `public_html/pdf-tools/` にアップロードします
## ステップ3:.htaccessファイルを作成する
デプロイ先フォルダのルート(`public_html` または `public_html/pdf-tools/`)に `.htaccess` ファイルを作成します。
::: warning 重要
サブディレクトリにデプロイする場合は、`RewriteBase /` を `RewriteBase /pdf-tools/`(またはフォルダ名)に変更してください。
:::
```apache
RewriteEngine On
RewriteBase /
# ============================================
# 1. SECURITY HEADERS (CRITICAL FOR WASM)
# ============================================
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# REQUIRED for soffice.js (SharedArrayBuffer)
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
</IfModule>
# ============================================
# 2. BROWSER CACHING
# ============================================
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
ExpiresByType image/x-icon "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/ttf "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/wasm "access plus 1 year"
ExpiresByType application/gzip "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
# ============================================
# 3. COMPRESSION (STANDARD)
# ============================================
# Prevent server from double-compressing files
SetEnvIfNoCase Request_URI "\.gz$" no-gzip
SetEnvIfNoCase Request_URI "\.br$" no-gzip
SetEnvIfNoCase Request_URI "\.wasm$" no-gzip
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE font/woff
AddOutputFilterByType DEFLATE font/ttf
</IfModule>
# ============================================
# 4. MIME TYPES & SPECIAL FILE HANDLING
# ============================================
AddType application/javascript .js .mjs
AddType application/wasm .wasm
AddType font/woff2 .woff2
AddType font/woff .woff
AddType image/webp .webp
# Handle soffice.wasm.gz correctly
<FilesMatch "soffice\.wasm\.gz$">
ForceType application/wasm
Header set Content-Encoding "gzip"
Header set Cross-Origin-Resource-Policy "cross-origin"
Header append Vary Accept-Encoding
</FilesMatch>
# Handle data.gz
<FilesMatch "soffice\.data\.gz$">
ForceType application/octet-stream
Header set Content-Encoding "gzip"
Header append Vary Accept-Encoding
</FilesMatch>
# ============================================
# 5. REDIRECTS & ROUTING
# ============================================
# Canonical WWW (update domain as needed)
RewriteCond %{HTTP_HOST} ^bentopdf\.com [NC]
RewriteRule ^(.*)$ https://www.bentopdf.com/$1 [L,R=301]
# Force HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Remove trailing slash (except for language root directories)
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/$
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]
# Existing files/dirs - serve directly
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# ============================================
# 5.1. LANGUAGE ROUTES
# ============================================
# Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt
# English has no prefix - served from root
# English prefix redirects to root (for SEO consistency)
RewriteRule ^en/?$ / [R=301,L]
RewriteRule ^en/(.+)$ /$1 [R=301,L]
# Language prefix root (e.g., /de/ -> /de/index.html)
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L]
# Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html)
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# ============================================
# 5.5. DOCS ROUTING (VitePress)
# ============================================
RewriteCond %{REQUEST_URI} ^/docs
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html [L]
# ============================================
# 6. ADD .HTML EXTENSION IF FILE EXISTS (ROOT LEVEL ONLY)
# ============================================
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
# ============================================
# 7. ERROR PAGES
# ============================================
ErrorDocument 404 /404.html
```
## サブディレクトリ用の.htaccessの例
`yourdomain.com/pdf-tools/` の場合は、次の行を更新します。
```apache
RewriteBase /pdf-tools/
# ... (same content as above, but update paths) ...
# Language prefix root
RewriteCond %{DOCUMENT_ROOT}/pdf-tools/$1/index.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf-tools/$1/index.html [L]
# Language prefix with path
RewriteCond %{DOCUMENT_ROOT}/pdf-tools/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf-tools/$1/$2.html [L]
# Root level .html extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
ErrorDocument 404 /pdf-tools/404.html
```
## トラブルシューティング
### WASMファイルが読み込まれない
`.htaccess` でMIMEタイプが正しく設定されていることを確認してください。
```apache
AddType application/wasm .wasm
```
### LibreOfficeツールが動作しない
SharedArrayBufferにはセキュリティヘッダーが不可欠です。
```apache
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Cross-Origin-Embedder-Policy "require-corp"
```
ヘッダーが適用されない場合は、Hostingerのサポートに連絡して `mod_headers` を有効にしてもらってください。
### ページを更新すると404エラーになる
`.html` 拡張子のルールと言語ルートが正しく設定されていることを確認してください。BentoPDFはSPAのルーティングではなく、静的なHTMLファイルを使います。
```apache
# Language routes serve actual files from language directories
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
# Root level pages
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^/]+)$ $1.html [L]
```
### ファイルアップロードの上限
Hostingerにはファイルサイズの上限がある場合があります。一括アップロードに失敗する場合は、WASMファイルを別途アップロードしてください。
+538
View File
@@ -0,0 +1,538 @@
# セルフホスティングガイド
BentoPDF は、自分のインフラでセルフホストできます。このガイドでは、さまざまなデプロイ方法を紹介します。
## Docker / Podman でクイックスタート
BentoPDF をセルフホストする最も手早い方法です。
> [!TIP]
> BentoPDF には 2 種類のビルドがあります。
>
> - **セルフホスト版ビルド**: `ghcr.io/alam00000/bentopdf-simple:latest`。マーケティング用の要素(ヒーロー、FAQ、お客様の声、フッター)**なし**で、すべての PDF ツールを利用できます。社内、チーム、組織向けのデプロイにはこちらを使ってください。機能を削減した「ライト」版では**ありません**。
> - **商用版ビルド**: `ghcr.io/alam00000/bentopdf:latest`。マーケティングサイト全体を含み、bentopdf.com 自体や、一般公開向けのデプロイを行う商用ライセンス保有者が使用します。
>
> 迷った場合は、セルフホスト版ビルドを取得してください。
> [!IMPORTANT]
> Office ファイルの変換には `SharedArrayBuffer` が必要です。そのため、アプリはクロスオリジン分離され、かつセキュアコンテキストから配信される必要があります。公式イメージは必要な COOP/COEP ヘッダーをすでに送信していますが、`http://192.168.x.x` のようなプレーン HTTP のローカルネットワークオリジンでは、ブラウザーが `SharedArrayBuffer` を無効にします。
>
> `http://localhost` は、同じデバイス上でのテストにのみ使用してください。ユーザーが LAN の IP やホスト名で BentoPDF にアクセスする場合は、HTTPS で終端してください。
```bash
# Docker
docker run -d -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Podman
podman run -d -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
```
または、Docker Compose / Podman Compose を使います。
```yaml
# docker-compose.yml
services:
bentopdf:
image: ghcr.io/alam00000/bentopdf-simple:latest
ports:
- '3000:8080'
restart: unless-stopped
```
```bash
# Docker Compose
docker compose up -d
# Podman Compose
podman-compose up -d
```
## Podman Quadlet(Linux Systemd)
BentoPDF を systemd サービスとして実行します。`~/.config/containers/systemd/bentopdf.container` を作成します。
```ini
[Container]
Image=ghcr.io/alam00000/bentopdf-simple:latest
ContainerName=bentopdf
PublishPort=3000:8080
AutoUpdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now bentopdf
```
Quadlet の詳しいドキュメントは、[Docker デプロイガイド](/ja/self-hosting/docker)を参照してください。
## ソースからビルドする
```bash
# Clone and build
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
# The built files are in the `dist` folder
```
## 設定オプション
### セルフホスト版ビルド(シンプルモード)
セルフホスト版ビルド(`bentopdf-simple` イメージ。シンプルモードとも呼ばれます)は、商用版ビルドと**機能的に同一**です。すべての PDF ツールが含まれ、同じように動作します。bentopdf.com 自体や、商用の一般公開向けデプロイでのみ意味を持つマーケティング用の要素を非表示にしているだけです。**機能を削減した「ライト」版ではありません。**
**セルフホスト版ビルドで非表示になるもの**(見た目のみで、PDF 機能は削除されません):
- ナビゲーションバー、ヒーローセクション、機能セクション、FAQ、お客様の声、フッター
- ページタイトルが「PDFツール」に更新されます
**セルフホスト版ビルドで維持されるもの**(実際に PDF の処理を行うすべて):
- すべての PDF ツール(結合、分割、編集、署名、OCR、Office 変換など)
- カスタムブランディングのサポート、すべてのビルド時および実行時の設定
商用版ビルド(`ghcr.io/alam00000/bentopdf:latest`)は bentopdf.com 自体を動かしているもので、一般公開向けのデプロイを行う商用ライセンス保有者が使用します。社内ツールでは意味をなさない、ヒーロー、FAQ、お客様の声、フッターが追加されています。
チーム、組織、または社内ツールとして BentoPDF をセルフホストする場合は、セルフホスト版ビルドを取得してください。
```bash
# Use the pre-built image (recommended)
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
# Or build it yourself
SIMPLE_MODE=true npm run build
```
詳細は [SIMPLE_MODE.md](https://github.com/alam00000/bentopdf/blob/main/SIMPLE_MODE.md) を参照してください。
### 商用版ビルド(独自ブランドでの一般公開向けデプロイ)
商用版ビルド(`bentopdf` イメージ。`-simple` サフィックスなし)は、bentopdf.com 自体を動かしているものです。すべての PDF ツールに加えて、マーケティングサイト全体(ヒーロー、機能、FAQ、お客様の声、フッター)が含まれます。**BentoPDF を独自ブランドの一般公開向け PDF サービスとしてデプロイ**したい場合に、このビルドを使用します。たとえば次のようなケースです。
- 自分のドメインで、エンドユーザー向けのホスト型 SaaS として BentoPDF を運用する
- ツールだけでなく、ランディングページの体験(マーケティングセクション + ツール)が欲しい
- 商用ライセンス保有者として、商用製品に BentoPDF を組み込む
**そのまま実行する**(BentoPDF のブランディングのまま。ビルドのプレビューに便利です):
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
**独自のブランドでビルドする**(典型的な商用ワークフロー):
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. All rights reserved." \
-t acmepdf .
docker run -p 3000:8080 acmepdf
```
その他のビルド時オプション(`BASE_URL`、`VITE_DEFAULT_LANGUAGE`、`DISABLE_TOOLS`、WASM URL の上書きなど)も、セルフホスト版ビルドと同じように使えます。
::: warning ライセンス
商用版ビルドの実行は、BentoPDF の 2 つのライセンスオプションのどちらでも許可されています。
- **AGPL-3.0**(無料): ブランディングの変更や周辺のビジネスロジックを含め、デプロイのソースコード全体を AGPL の下で公開する場合に許可されます。
- **商用ライセンス**($79 の買い切り): ブランディングのフォークやビジネスコードをオープンソース化しない、クローズドソース/プロプライエタリなデプロイに必要です。
詳しい比較は[ライセンスのページ](https://www.bentopdf.com/licensing)を参照してください。AGPL ライセンスの WASM モジュール(PyMuPDF、Ghostscript、CoherentPDF)は実行時に CDN から読み込まれるため、イメージには含まれず、ライセンス上の立場も変わりません。
:::
### ベース URL
サブディレクトリにデプロイします。
```bash
BASE_URL=/pdf-tools/ npm run build
```
### カスタムブランディング
デフォルトの BentoPDF のロゴ、名前、フッターテキストを、ビルド時に独自のものへ置き換えます。
| 変数 | 説明 | デフォルト |
| ------------------ | ---------------------------------------- | --------------------------------------- |
| `VITE_BRAND_NAME` | ヘッダーとフッターに表示されるブランド名 | `BentoPDF` |
| `VITE_BRAND_LOGO` | `public/` からの相対ロゴパス | `images/favicon-no-bg.svg` |
| `VITE_FOOTER_TEXT` | カスタムのフッター/著作権テキスト | `© 2026 BentoPDF. All rights reserved.` |
```bash
# Place your logo in public/, then build
VITE_BRAND_NAME="AcmePDF" \
VITE_BRAND_LOGO="images/acme-logo.svg" \
VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
npm run build
```
または Docker を使います。
```bash
docker build \
--build-arg VITE_BRAND_NAME="AcmePDF" \
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
-t acmepdf .
```
ブランディングはフルモードとシンプルモードの両方で機能し、他のすべてのビルド時オプション(`BASE_URL`、`SIMPLE_MODE`、`VITE_DEFAULT_LANGUAGE`)と組み合わせられます。
### 特定のツールを無効にする
コンプライアンスやセキュリティのために、個別のツールを非表示にします。無効にしたツールは、ホームページ、検索、ショートカット、ワークフロービルダー、直接の URL アクセスから削除されます。
ツール ID は、`.html` を除いたページの URL です。任意のツールを開いて URL を確認してください(例: `edit-pdf`、`sign-pdf`、`encrypt-pdf`)。
**ビルド時**(バンドルに組み込まれます):
```bash
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
```
**実行時**(再ビルド不要。`config.json` をマウントします):
```json
{
"disabledTools": ["edit-pdf", "sign-pdf"]
}
```
```bash
docker run -d -p 3000:8080 \
-v ./config.json:/usr/share/nginx/html/config.json:ro \
ghcr.io/alam00000/bentopdf-simple:latest
```
両方の方法を組み合わせることもできます。リストはマージされます。
PDF エディタ内の特定の機能(例: 墨消し)だけを、ツール全体を無効にせずに無効にすることもできます。`config.json` に `editorDisabledCategories` を追加します。カテゴリーの一覧は [Docker ガイド](/ja/self-hosting/docker#エディタ機能を無効にする)を参照してください。
## デプロイガイド
プラットフォームを選んでください。
- [Vercel](/ja/self-hosting/vercel)
- [Netlify](/ja/self-hosting/netlify)
- [Cloudflare Pages](/ja/self-hosting/cloudflare)
- [AWS S3 + CloudFront](/ja/self-hosting/aws)
- [Hostinger](/ja/self-hosting/hostinger)
- [Nginx](/ja/self-hosting/nginx)
- [Apache](/ja/self-hosting/apache)
- [Docker](/ja/self-hosting/docker)
- [Kubernetes](/ja/self-hosting/kubernetes)
- [CORS Proxy](/ja/self-hosting/cors-proxy) - デジタル署名に必要です
## よくある問題
### Word / ODT / Excel / PowerPoint から PDF への変換がハングする(SharedArrayBuffer が利用できない)
**症状**: LibreOffice ベースの文書変換(Word、ODT、Excel、PowerPoint から PDF)が約 55% で止まる、または開始できません。ブラウザーのコンソールに次のように表示されることがあります。
> `ReferenceError: SharedArrayBuffer is not defined`
…または、`window.crossOriginIsolated` が `false` を返す、あるいは WASM のコンパイルで `expected magic word 00 61 73 6d, found 1f 8b 08 08` と報告されます。
**原因**: LibreOffice WASM には `SharedArrayBuffer` が必要で、ブラウザーはページが**クロスオリジン分離**され、かつ**セキュアコンテキスト**から配信されている場合にのみ、これを有効にします。つまり、次の 2 つの条件を満たす必要があります。
1. すべてのレスポンスに、次の両方のヘッダーが含まれていること。
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
2. ページが `https://...` または `http://localhost` から配信されていること。LAN IP(例: `http://192.168.x.x`)上のプレーン HTTP はセキュアとは見なされず、ブラウザーはそこで `SharedArrayBuffer` を無効にします。
`00 61 73 6d / 1f 8b 08 08` の不一致は別のサブ症状です。事前圧縮された `.wasm.gz` / `.data.gz` ファイルに `Content-Encoding: gzip` レスポンスヘッダーがないため、ブラウザーは展開された WASM ではなく、生の gzip バイト列を受け取っています。
**対処法**: 正確な設定は、各プラットフォームのデプロイガイドを参照してください。
- [Nginx →](/ja/self-hosting/nginx#word-odt-excel-から-pdf-への変換が動作しない)
- [Apache →](/ja/self-hosting/apache#word-odt-excelからpdfへの変換が動作しない)
- [AWS S3 + CloudFront →](/ja/self-hosting/aws#ステップ3b-レスポンスヘッダーポリシー-libreoffice-wasmに必須)
- [Cloudflare Pages →](/ja/self-hosting/cloudflare#設定ファイル)(`_headers` ファイル)
- [Netlify →](/ja/self-hosting/netlify#word-odt-excel-から-pdf-への変換が-55-で止まる)(`netlify.toml`)
- [Vercel →](/ja/self-hosting/vercel#word-odt-excel-から-pdf-への変換が動作しない)(`vercel.json`)
- [Hostinger →](/ja/self-hosting/hostinger#libreofficeツールが動作しない)(`.htaccess`)
- [Kubernetes →](/ja/self-hosting/kubernetes#sharedarraybuffer-ヘッダーが引き続き機能することを確認する-ingress-gateway)
- **Docker**: 同梱の nginx 設定によって自動的に処理されるため、操作は不要です。
**確認**: 任意の BentoPDF ページで DevTools のコンソールを開き、次を実行します。
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
ページが HTTPS または `http://localhost` で、かつすべてのレスポンスに COEP/COOP ヘッダーが含まれていれば、両方のチェックに合格します。`http://192.168.x.x` などのループバック以外の HTTP オリジンを使っている場合は、HTTPS で終端してください。ヘッダーだけで解決する方法はありません。
### `.mjs` ファイルが `application/octet-stream` として配信される
**症状**: PDFへ署名 / Form Filler など一部のツールで、ビューアが空白になる、または読み込みに失敗します。ブラウザーのコンソールに次のように表示されます。
> `Failed to load module script: The server responded with a non-JavaScript MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec.`
**原因**: Web サーバーまたはリバースプロキシに、`.mjs` ファイル(内蔵の PDF ビューアは、この拡張子で ES モジュールを同梱しています)の MIME タイプのマッピングがありません。多くの標準的なサーバー設定では、認識できない拡張子はデフォルトで `application/octet-stream` になり、ブラウザーはそれを ES モジュールとして実行することを拒否します。
**対処法**: 正確なスニペットは、各プラットフォームのデプロイガイドを参照してください。
- [Nginx →](/ja/self-hosting/nginx#pdfへ署名-または-form-filler-で空白のビューアが表示される-mjs-の-mime-エラー)
- [Apache →](/ja/self-hosting/apache#pdfへ署名またはフォーム入力で空白のビューアが表示される-mjs-のmimeエラー)
- [AWS S3 + CloudFront →](/ja/self-hosting/aws#ステップ2-ビルドしてアップロードする)(`aws s3 cp ... --include "*.mjs"` を参照)
- [Kubernetes →](/ja/self-hosting/kubernetes#mjs-の-mime-タイプエラー-pdfへ署名-form-filler-の-iframe-が空白になる)
- **Docker、Vercel、Netlify、Cloudflare Pages、Hostinger**: 自動的に処理されるため、操作は不要です。
**確認**: DevTools → Network タブを開き、失敗している `.mjs` リクエストを見つけて、`Content-Type` レスポンスヘッダーを確認します。`application/javascript` になっているはずです。それでも `application/octet-stream` の場合は、外側のリバースプロキシや CDN がタイプを再判定している可能性があります。配信経路の各層を確認してください。
## WASM の設定(AGPL コンポーネント)
BentoPDF は、AGPL ライセンスの処理ライブラリをソースコードに**同梱していません**が、すべての機能がそのまま動作するよう、CDN の URL を**あらかじめ設定**しています。手動での設定は不要です。
::: tip デフォルトでゼロコンフィグ
v2.0.0 以降、WASM モジュールは環境変数によって jsDelivr CDN から読み込まれるようあらかじめ設定されています。すべての高度な機能は、ユーザーが何も設定しなくてもすぐに使えます。
:::
| コンポーネント | ライセンス | 機能 |
| --------------- | ---------- | ---------------------------------------------- |
| **PyMuPDF** | AGPL-3.0 | EPUB/MOBI/FB2/XPS の変換、画像の抽出、表の抽出 |
| **Ghostscript** | AGPL-3.0 | PDF/A 変換、圧縮、傾き補正、ラスタライズ |
| **CoherentPDF** | AGPL-3.0 | 目次、添付ファイル、しおり付きの PDF 結合 |
### デフォルトの環境変数
これらは `.env.production` に設定され、ビルドに組み込まれます。
```bash
VITE_WASM_PYMUPDF_URL=https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/
VITE_WASM_GS_URL=https://cdn.jsdelivr.net/npm/@bentopdf/gs-wasm@0.1.1/assets/
VITE_WASM_CPDF_URL=https://cdn.jsdelivr.net/npm/coherentpdf@2.5.5/dist/
VITE_TESSERACT_WORKER_URL=
VITE_TESSERACT_CORE_URL=
VITE_TESSERACT_LANG_URL=
VITE_TESSERACT_AVAILABLE_LANGUAGES=
VITE_OCR_FONT_BASE_URL=
```
### WASM URL を上書きする
カスタムデプロイでは、ビルド時にデフォルトを上書きできます。
```bash
# Via Docker build args
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://your-server.com/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://your-server.com/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://your-server.com/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://your-server.com/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://your-server.com/ocr/lang-data \
--build-arg VITE_TESSERACT_AVAILABLE_LANGUAGES=eng,deu \
--build-arg VITE_OCR_FONT_BASE_URL=https://your-server.com/ocr/fonts \
-t bentopdf .
# Or via .env.production before building from source
VITE_WASM_PYMUPDF_URL=https://your-server.com/pymupdf/ npm run build
```
モジュールを完全に無効にするには(詳細設定からユーザーが手動で設定する必要があります)、その変数を空文字列に設定します。
OCR の場合は、すべての `VITE_TESSERACT_*` 変数を空のままにしてデフォルトのオンラインアセットを使うか、セルフホスト/オフライン OCR 用に worker/core/lang の URL をまとめて設定します。特定の OCR 言語のみをバンドルする場合は、`VITE_TESSERACT_AVAILABLE_LANGUAGES` にも同じカンマ区切りのコードを設定してください。これにより、UI にはインストール済みの言語のみが表示され、サポートされていない選択は説明付きのエラーで失敗します。完全オフラインで検索可能な PDF を出力するには、バンドルされた OCR フォントを配信する社内ディレクトリを `VITE_OCR_FONT_BASE_URL` にも設定してください。
ユーザーは、UI の **詳細設定** からいつでもこれらのデフォルトを上書きできます。ブラウザーに保存されたユーザーの上書き設定は、環境のデフォルトより優先されます。
### エアギャップ / オフラインデプロイ
インターネットにアクセスできないネットワーク(政府、医療、金融など)向けです。WASM の URL は**ビルド時に** JavaScript へ組み込まれ、実際の WASM ファイルは実行時に**ユーザーのブラウザー**がダウンロードします。そのため、インターネットに接続できるマシンですべてを準備してから、隔離されたネットワークへ転送する必要があります。
#### 自動化スクリプト(推奨)
付属の `prepare-airgap.sh` スクリプトが、WASM パッケージのダウンロード、Docker イメージのビルド、セットアップスクリプト付きの自己完結型バンドルの作成まで、プロセス全体を自動化します。
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
# Show supported OCR language codes (for --ocr-languages)
bash scripts/prepare-airgap.sh --list-ocr-languages
# Search OCR language codes by name or abbreviation
bash scripts/prepare-airgap.sh --search-ocr-language german
# Interactive mode — prompts for all options
bash scripts/prepare-airgap.sh
# Or fully automated
bash scripts/prepare-airgap.sh --wasm-base-url https://internal.example.com/wasm
```
これにより、次のバンドルディレクトリが作成されます。
```
bentopdf-airgap-bundle/
bentopdf.tar # Docker image
*.tgz # WASM packages (PyMuPDF, Ghostscript, CoherentPDF, Tesseract)
tesseract-langdata/ # OCR traineddata files
ocr-fonts/ # OCR text-layer font files
setup.sh # Setup script for the air-gapped side
README.md # Instructions
```
USB、社内のアーティファクトリポジトリ、または承認された方法で、バンドルをエアギャップネットワークに転送します。その後、付属のセットアップスクリプトを実行します。
```bash
cd bentopdf-airgap-bundle
bash setup.sh
```
セットアップスクリプトは、Docker イメージの読み込み、WASM ファイルの展開を行い、必要に応じてコンテナーを起動します。
**スクリプトのオプション:**
| フラグ | 説明 | デフォルト |
| ------------------------------ | ------------------------------------------------------------------- | ---------------------------------------------- |
| `--wasm-base-url <url>` | WASM を社内でホストする場所 | _(必須。指定がない場合は入力を求められます)_ |
| `--image-name <name>` | Docker イメージのタグ | `bentopdf` |
| `--output-dir <path>` | 出力するバンドルのディレクトリ | `./bentopdf-airgap-bundle` |
| `--simple-mode` | シンプルモードを有効にする | オフ |
| `--base-url <path>` | サブディレクトリのベース URL(例: `/pdf/`) | `/` |
| `--language <code>` | デフォルトの UI 言語(例: `fr`、`de`) | _(なし)_ |
| `--brand-name <name>` | カスタムのブランド名 | _(なし)_ |
| `--brand-logo <path>` | `public/` からの相対ロゴパス | _(なし)_ |
| `--footer-text <text>` | カスタムのフッターテキスト | _(なし)_ |
| `--ocr-languages <list>` | バンドルする OCR 言語(カンマ区切り) | `eng` |
| `--list-ocr-languages` | サポートされている OCR コードと名前を表示して終了する | オフ |
| `--search-ocr-language <term>` | 名前または略称で OCR コードを検索する | オフ |
| `--dockerfile <path>` | 使用する Dockerfile | `Dockerfile` |
| `--skip-docker` | Docker のビルドとエクスポートをスキップする | オフ |
| `--skip-wasm` | WASM のダウンロードをスキップする(既存の `.tgz` ファイルを再利用) | オフ |
対話式のプロンプトでは `list` と入力すると、サポートされているすべての Tesseract コードの一覧を表示でき、`search <term>` で一致するコードを検索できます(例: `search german`、`search chi`)。
::: warning 同一オリジンの要件
WASM ファイルは、BentoPDF アプリと**同じオリジン**から配信する必要があります。Web ワーカーは `importScripts()` を使用しますが、これはスクリプトをクロスオリジンで読み込めません。たとえば、BentoPDF が `https://internal.example.com` で動作している場合、WASM のベース URL も `https://internal.example.com/wasm` にする必要があります。
:::
#### 手動の手順
<details>
<summary>スクリプトを使わずに手動で行う場合</summary>
**ステップ 1: WASM と OCR のパッケージをダウンロードする**(インターネットに接続できるマシンで)
```bash
npm pack @bentopdf/pymupdf-wasm@0.11.14
npm pack @bentopdf/gs-wasm
npm pack coherentpdf
npm pack tesseract.js@7.0.0
npm pack tesseract.js-core@7.0.0
mkdir -p tesseract-langdata
curl -fsSL https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz -o tesseract-langdata/eng.traineddata.gz
mkdir -p ocr-fonts
curl -fsSL https://raw.githack.com/googlefonts/noto-fonts/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf -o ocr-fonts/NotoSans-Regular.ttf
```
**ステップ 2: 社内 URL を指定して Docker イメージをビルドする**
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
docker build \
--build-arg VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/ \
--build-arg VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/ \
--build-arg VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/ \
--build-arg VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js \
--build-arg VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core \
--build-arg VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data \
--build-arg VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts \
-t bentopdf .
```
**ステップ 3: Docker イメージをエクスポートする**
```bash
docker save bentopdf -o bentopdf.tar
```
**ステップ 4: エアギャップネットワークへ転送する**
USB、社内のアーティファクトリポジトリ、または承認された転送方法でコピーします。
- `bentopdf.tar`: Docker イメージ
- ステップ 1 の 5 つの `.tgz` WASM/OCR パッケージ
- ステップ 1 の `tesseract-langdata/` ディレクトリ
- ステップ 1 の `ocr-fonts/` ディレクトリ
**ステップ 5: エアギャップネットワーク内でセットアップする**
```bash
# Load the Docker image
docker load -i bentopdf.tar
# Extract WASM packages
mkdir -p ./wasm/pymupdf ./wasm/gs ./wasm/cpdf ./wasm/ocr/core ./wasm/ocr/lang-data ./wasm/ocr/fonts
tar xzf bentopdf-pymupdf-wasm-0.11.14.tgz -C ./wasm/pymupdf --strip-components=1
tar xzf bentopdf-gs-wasm-*.tgz -C ./wasm/gs --strip-components=1
tar xzf coherentpdf-*.tgz -C ./wasm/cpdf --strip-components=1
TEMP_TESS=$(mktemp -d)
tar xzf tesseract.js-7.0.0.tgz -C "$TEMP_TESS"
cp "$TEMP_TESS/package/dist/worker.min.js" ./wasm/ocr/worker.min.js
rm -rf "$TEMP_TESS"
tar xzf tesseract.js-core-7.0.0.tgz -C ./wasm/ocr/core --strip-components=1
cp ./tesseract-langdata/*.traineddata.gz ./wasm/ocr/lang-data/
cp ./ocr-fonts/* ./wasm/ocr/fonts/
# Run BentoPDF
docker run -d -p 3000:8080 --restart unless-stopped bentopdf
```
ステップ 2 で設定した URL でファイルにアクセスできることを確認してください。`.../ocr/worker.min.js`、`.../ocr/core`、`.../ocr/lang-data`、`.../ocr/fonts` も含まれます。
</details>
::: info Docker ではなくソースからビルドする場合
`npm run build` を実行する前に、`.env.production` に変数を設定します。
```bash
VITE_WASM_PYMUPDF_URL=https://internal-server.example.com/wasm/pymupdf/
VITE_WASM_GS_URL=https://internal-server.example.com/wasm/gs/
VITE_WASM_CPDF_URL=https://internal-server.example.com/wasm/cpdf/
VITE_TESSERACT_WORKER_URL=https://internal-server.example.com/wasm/ocr/worker.min.js
VITE_TESSERACT_CORE_URL=https://internal-server.example.com/wasm/ocr/core
VITE_TESSERACT_LANG_URL=https://internal-server.example.com/wasm/ocr/lang-data
VITE_OCR_FONT_BASE_URL=https://internal-server.example.com/wasm/ocr/fonts
```
:::
### 独自の WASM プロキシをホストする
AGPL の WASM ファイルを適切な CORS ヘッダー付きで配信する必要がある場合は、シンプルなプロキシをデプロイできます。実装例は [Cloudflare WASM Proxy ガイド](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md)を参照してください。
::: tip なぜ分離しているのか
この分離により、次のことが保証されます。
- 商用ユーザーにとって明確な法令遵守
- BentoPDF のコアがデュアルライセンス(AGPL-3.0 / 商用)のまま維持されること
- WASM ファイルはソースにバンドルされるのではなく、実行時に読み込まれること
:::
## システム要件
| 要件 | 最小 |
| ---------- | -------------------------------------- |
| ストレージ | 約 100 MB(AGPL モジュールを除くコア) |
| RAM | 512 MB |
| CPU | 最近のプロセッサー |
::: tip
BentoPDF は静的サイトです。データベースやバックエンドサーバーは不要です!
:::
+211
View File
@@ -0,0 +1,211 @@
# Kubernetes でデプロイする
静的サイトに Kubernetes は大げさかもしれませんが、すでに Helm + GitOps で標準化している場合には最適な選択肢になり得ます。
> [!IMPORTANT]
> **Office ファイル変換に必要なヘッダー**
>
> LibreOffice ベースのツール(Word、Excel、PowerPoint の変換)には、`SharedArrayBuffer` を使うために次の HTTP ヘッダーが必要です。
>
> - `Cross-Origin-Opener-Policy: same-origin`
> - `Cross-Origin-Embedder-Policy: require-corp`
>
> 公式の BentoPDF nginx イメージにはこれらのヘッダーが含まれています。Kubernetes では **Ingress/Gateway コントローラーもリバースプロキシ**であるため、これらのヘッダーが保持されるようにするか、エッジで追加してください。
## 前提条件
- Kubernetes クラスター
- Helm v3
- **ポート 8080** で配信する BentoPDF の nginx イメージ(例: `ghcr.io/alam00000/bentopdf:<tag>`)
## Helm でデプロイする
### このリポジトリからインストールする(ローカルチャート)
```bash
kubectl create namespace bentopdf
helm upgrade --install bentopdf /path/to/bentopdf/chart \
--namespace bentopdf \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
### GHCR からインストールする(OCI チャート)
チャートが OCI アーティファクトとして GHCR に公開されている場合:
```bash
export GHCR_USERNAME="<github-org-or-user>"
helm upgrade --install bentopdf oci://ghcr.io/$GHCR_USERNAME/charts/bentopdf \
--namespace bentopdf \
--create-namespace \
--version 0.1.0 \
--set image.repository=ghcr.io/alam00000/bentopdf \
--set image.tag=latest
```
## 公開する
### ポートフォワード(簡易テスト)
```bash
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
```
### Ingress(任意)
Ingress を有効にします(nginx-ingress の例)。
```yaml
ingress:
enabled: true
className: nginx
hosts:
- host: pdf.example.com
paths:
- path: /
pathType: Prefix
```
### Gateway API(任意)
このチャートは、Gateway API の `Gateway` と `HTTPRoute` に対応しています。
例(Cloudflare Gateway API operator):
```yaml
gateway:
enabled: true
name: bento-tunnel
namespace: bentopdf
gatewayClassName: cloudflare
httpRoute:
enabled: true
parentRefs:
- name: bento-tunnel
namespace: bentopdf
sectionName: http
hostnames:
- pdfs.example.com
```
## SharedArrayBuffer ヘッダーが引き続き機能することを確認する(Ingress/Gateway)
### 本来の動作
BentoPDF の nginx 設定が、必要なレスポンスヘッダーを設定します。ほとんどの Ingress/Gateway コントローラーは、**アップストリームのレスポンスヘッダーをそのまま通過させます**。
### 動作しなくなる原因
- レスポンスヘッダーを**上書き**または**削除**するコントローラー/エッジのポリシー
- 異なる COOP/COEP の値を設定する「セキュリティヘッダー」ミドルウェア
### 確認方法
公開エンドポイントに対して次を実行します。
```bash
curl -I https://pdf.example.com/ | egrep -i 'cross-origin-opener-policy|cross-origin-embedder-policy'
```
次のように表示されるはずです。
- `Cross-Origin-Opener-Policy: same-origin`
- `Cross-Origin-Embedder-Policy: require-corp`
### Ingress コントローラーがヘッダーを保持しない場合
エッジ(コントローラー固有の方法)でヘッダーを追加します。**nginx-ingress** の例:
```yaml
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
```
## `.mjs` の MIME タイプエラー(PDFへ署名 / Form Filler の iframe が空白になる)
ブラウザーのコンソールに、`.mjs` リクエストに対して `Failed to load module script: ... non-JavaScript MIME type "application/octet-stream"` と表示される場合、BentoPDF の nginx の前段にある Ingress/Gateway コントローラーが、アップストリームの `Content-Type` ヘッダーを `application/octet-stream` に置き換えています。
BentoPDF イメージの nginx は、すでに `.mjs` を `application/javascript` として配信しています。修正方法は、コントローラーによる削除や置き換えを止めることです。nginx-ingress の場合:
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
location ~* \.mjs$ {
types {} default_type application/javascript;
}
```
または、Gateway API の `ResponseHeaderModifier` フィルターを使って、`.mjs` パスに `Content-Type: application/javascript` を設定します。DevTools → Network タブで確認してください。失敗していた `.mjs` リクエストに `content-type: application/javascript` と表示されるはずです。
### Gateway API を使っていて、ヘッダーを強制的に追加したい場合
Gateway API は `ResponseHeaderModifier` フィルターに対応しています。`httpRoute.rules[*].filters` に追加できます。
```yaml
httpRoute:
enabled: true
hostnames: [pdf.example.com]
parentRefs:
- name: bento-tunnel
namespace: misc
sectionName: http
rules:
- matches:
- path: { type: PathPrefix, value: / }
filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
set:
- name: Cross-Origin-Opener-Policy
value: same-origin
- name: Cross-Origin-Embedder-Policy
value: require-corp
```
対応するフィルターは Gateway コントローラーによって異なります。フィルターが無視される場合は、代わりにエッジ/コントローラー層でヘッダーを追加してください。
## 特定のツールを無効にする
ConfigMap を使うと、イメージを再ビルドせずに、実行時にツールを無効にできます。
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bentopdf-config
namespace: bentopdf
data:
config.json: |
{
"disabledTools": ["edit-pdf", "sign-pdf", "encrypt-pdf"]
}
```
配信されるディレクトリにマウントします。
```yaml
spec:
containers:
- name: bentopdf
volumeMounts:
- name: config
mountPath: /usr/share/nginx/html/config.json
subPath: config.json
readOnly: true
volumes:
- name: config
configMap:
name: bentopdf-config
```
ツール ID は、`.html` を除いたページの URL です。任意のツールを開いて URL を確認してください(例: `edit-pdf`、`merge-pdf`、`compress-pdf`)。無効にしたツールは、ホームページ、検索、ショートカット、ワークフロービルダー、直接の URL アクセスから非表示になります。すべてのオプションについては、[Docker ガイド](/ja/self-hosting/docker#特定のツールを無効にする)を参照してください。
+139
View File
@@ -0,0 +1,139 @@
# Netlify にデプロイする
[Netlify](https://netlify.com) は、手厚い無料枠を備えた優れた静的サイトホスティングを提供しています。
## ワンクリックデプロイ
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
## 手動デプロイ
### ステップ 1: リポジトリを接続する
1. [Netlify](https://app.netlify.com) にログインします
2. 「Add new site」→「Import an existing project」をクリックします
3. GitHub アカウントを接続します
4. BentoPDF のフォークを選択します
### ステップ 2: ビルド設定を行う
| 設定 | 値 |
| ----------------- | --------------- |
| Build command | `npm run build` |
| Publish directory | `dist` |
| Node version | 18+ |
### ステップ 3: デプロイする
「Deploy site」をクリックして、ビルドが完了するのを待ちます。
## 設定ファイル
プロジェクトのルートに `netlify.toml` を作成します。
```toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "18"
# Required security headers for SharedArrayBuffer (used by LibreOffice WASM)
[[headers]]
for = "/*"
[headers.values]
Cross-Origin-Embedder-Policy = "require-corp"
Cross-Origin-Opener-Policy = "same-origin"
Cross-Origin-Resource-Policy = "cross-origin"
# Pre-compressed LibreOffice WASM binary - must be served with Content-Encoding
[[headers]]
for = "/libreoffice-wasm/soffice.wasm.gz"
[headers.values]
Content-Type = "application/wasm"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
# Pre-compressed LibreOffice WASM data - must be served with Content-Encoding
[[headers]]
for = "/libreoffice-wasm/soffice.data.gz"
[headers.values]
Content-Type = "application/octet-stream"
Content-Encoding = "gzip"
Cache-Control = "public, max-age=31536000, immutable"
# Cache other WASM files
[[headers]]
for = "*.wasm"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
Content-Type = "application/wasm"
# SPA routing
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
```
::: warning 重要
Word/ODT/Excel/PowerPoint から PDF への変換には、`Cross-Origin-Embedder-Policy` と `Cross-Origin-Opener-Policy` ヘッダーが必要です。これらがないと `SharedArrayBuffer` が使えず、LibreOffice WASM エンジンの初期化に失敗します。
`.wasm.gz` と `.data.gz` ファイルの `Content-Encoding: gzip` ヘッダーは、ブラウザーにそれらを自動的に展開させるためのものです。これがないと、ブラウザーは生の gzip バイト列を受け取り、WASM のコンパイルに失敗します。
:::
## 環境変数
Site settings → Environment variables で設定します。
| 変数 | 説明 |
| ----------------------- | -------------------------------------------------------- |
| `SIMPLE_MODE` | 最小構成のビルドにするには `true` を設定します |
| `VITE_BRAND_NAME` | カスタムブランド名("BentoPDF" を置き換えます) |
| `VITE_BRAND_LOGO` | `public/` からの相対ロゴパス(例: `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | カスタムのフッター/著作権テキスト |
| `VITE_DEFAULT_LANGUAGE` | デフォルトの UI 言語(例: `fr`、`de`、`es`) |
## カスタムドメイン
1. Site settings → Domain management に移動します
2. 「Add custom domain」をクリックします
3. 表示される DNS の設定手順に従います
## ラージメディア
大きな WASM ファイルには、[Netlify Large Media](https://docs.netlify.com/large-media/overview/) の有効化を検討してください。
```bash
netlify lm:setup
git lfs track "*.wasm"
```
## トラブルシューティング
### Word/ODT/Excel から PDF への変換が 55% で止まる
文書の変換が 55% で止まる場合は、DevTools のコンソールを開いて次を確認します。
```js
console.log(window.crossOriginIsolated); // should be true
console.log(typeof SharedArrayBuffer); // should be "function"
```
`crossOriginIsolated` が `false` の場合、`netlify.toml` の COEP/COOP ヘッダーが適用されていません。ファイルがプロジェクトのルートにあることを確認して、再デプロイしてください。
コンソールに `expected magic word 00 61 73 6d, found 1f 8b 08 08` と表示される場合は、`.wasm.gz` ファイルに `Content-Encoding: gzip` ヘッダーがありません。`soffice.wasm.gz` と `soffice.data.gz` 用の `[[headers]]` ブロックが `netlify.toml` に含まれていることを確認してください。
### ビルドが失敗する
Node のバージョンの互換性を確認します。
```toml
[build.environment]
NODE_VERSION = "20"
```
### 初回の読み込みが遅い
Site settings → Build & deploy → Asset optimization で、アセットの最適化を有効にします。
+232
View File
@@ -0,0 +1,232 @@
# Nginx でデプロイする
Nginx を使って、BentoPDF を自分のサーバーでホストします。
## 前提条件
- Ubuntu/Debian サーバー
- Nginx がインストール済みであること
- SSL 証明書(推奨: Let's Encrypt)
## ステップ 1: プロジェクトをビルドする
```bash
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf
npm install
npm run build
```
ブランディングをカスタマイズするには、ビルド前に環境変数を設定します。
```bash
VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build
```
## ステップ 2: ファイルをコピーする
```bash
sudo mkdir -p /var/www/bentopdf
sudo cp -r dist/* /var/www/bentopdf/
sudo chown -R www-data:www-data /var/www/bentopdf
```
## ステップ 3: Nginx の設定
`/etc/nginx/sites-available/bentopdf` を作成します。
```nginx
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
root /var/www/bentopdf;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/wasm;
gzip_min_length 1000;
# WASM MIME type
types {
application/wasm wasm;
}
# Required headers for SharedArrayBuffer (LibreOffice WASM)
# These must be on every response - especially HTML pages
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "cross-origin" always;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Pre-compressed LibreOffice WASM binary
location ~* /libreoffice-wasm/soffice\.wasm\.gz$ {
gzip off;
types {} default_type application/wasm;
add_header Content-Encoding gzip;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# Pre-compressed LibreOffice WASM data
location ~* /libreoffice-wasm/soffice\.data\.gz$ {
gzip off;
types {} default_type application/octet-stream;
add_header Content-Encoding gzip;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|wasm)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
# SPA routing - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
}
}
```
## ステップ 4: サイトを有効にする
```bash
sudo ln -s /etc/nginx/sites-available/bentopdf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```
## ステップ 5: Let's Encrypt による SSL
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## サブディレクトリへのデプロイ
`/pdf/` でホストするには、次のようにします。
```nginx
location /pdf/ {
alias /var/www/bentopdf/;
try_files $uri $uri/ /pdf/index.html;
}
```
ビルドは次のコマンドで行います。
```bash
BASE_URL=/pdf/ npm run build
```
## パフォーマンスチューニング
`nginx.conf` に追加します。
```nginx
http {
# Enable sendfile
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Increase buffer sizes
client_max_body_size 100M;
# Worker connections
worker_connections 2048;
}
```
## トラブルシューティング
### WASM が読み込まれない
MIME タイプが設定されていることを確認します。
```nginx
types {
application/wasm wasm;
}
```
### PDFへ署名 または Form Filler で空白のビューアが表示される(`.mjs` の MIME エラー)
ブラウザーのコンソールに次のように表示される場合:
```
Failed to load module script: The server responded with a non-JavaScript
MIME type of "application/octet-stream". Strict MIME type checking is
enforced for module scripts per HTML spec.
```
…かつ失敗しているリクエストが `.mjs` で終わっている場合、お使いの nginx に `.mjs → application/javascript` のマッピングがありません。内蔵の PDF ビューア(PDFへ署名、Form Filler、その他いくつかのツールで使用)は `.mjs` 拡張子の ES モジュールファイルを同梱していますが、nginx の標準の `mime.types` にはデフォルトで含まれていません。
`/etc/nginx/mime.types` にある既存の `application/javascript` の行に `mjs` を追加して修正します。
```nginx
application/javascript js mjs;
```
> `application/javascript` の行を*もう 1 行*追加しないでください。nginx は `mime.types` を 1 行ずつ解析し、拡張子との対応付けは 1 つの型につき 1 つしか有効にならないため、バージョンによっては 2 行目が黙って無視されます。既存の行を編集して、`js` の隣に `mjs` を追加してください。
または、server ブロック内でローカルに上書きします(グローバルなファイルの編集は不要です)。
```nginx
types {
application/javascript js mjs;
application/wasm wasm;
}
```
変更後に nginx をリロードします。
```bash
sudo nginx -t && sudo systemctl reload nginx
```
確認するには、ブラウザーの DevTools → Network タブで失敗している `.mjs` リクエストを見つけ、`Content-Type` レスポンスヘッダーを確認します。`application/octet-stream` ではなく `application/javascript` になっているはずです。
### Word/ODT/Excel から PDF への変換が動作しない
LibreOffice WASM には `SharedArrayBuffer` が必要で、そのためには `Cross-Origin-Embedder-Policy` と `Cross-Origin-Opener-Policy` ヘッダーが必要です。セキュアコンテキストも必要なため、`http://localhost` はローカルでのテストには使えますが、`http://192.168.x.x` などの LAN IP では通常 HTTPS が必要です。なお、`location` ブロック内の nginx の `add_header` ディレクティブは、server レベルの `add_header` ディレクティブを**上書き**します。マージはされません。独自の `add_header` を持つすべての `location` ブロックに、COEP/COOP ヘッダーを含める必要があります。
次のコマンドで確認します。
```bash
curl -I https://your-domain.com/ | grep -i cross-origin
```
nginx の前段にリバースプロキシがある場合は、それらのヘッダーが保持されていることを確認してください。
### 502 Bad Gateway
Nginx のエラーログを確認します。
```bash
sudo tail -f /var/log/nginx/error.log
```
+122
View File
@@ -0,0 +1,122 @@
# Vercel にデプロイする
[Vercel](https://vercel.com) は、静的サイトを最も簡単にデプロイできるサービスです。
## ワンクリックデプロイ
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
## 手動デプロイ
### ステップ 1: リポジトリをフォークする
[bentopdf/bentopdf](https://github.com/alam00000/bentopdf) を自分の GitHub アカウントにフォークします。
### ステップ 2: Vercel にインポートする
1. [vercel.com/new](https://vercel.com/new) にアクセスします
2. フォークしたリポジトリを選択します
3. プロジェクトを設定します。
| 設定 | 値 |
| ---------------- | --------------- |
| Framework Preset | Vite |
| Build Command | `npm run build` |
| Output Directory | `dist` |
| Install Command | `npm install` |
### ステップ 3: 環境変数(任意)
必要に応じて、次の変数を追加します。
| 変数 | 説明 |
| ----------------------- | -------------------------------------------------------- |
| `SIMPLE_MODE` | 最小限の UI にするには `true` を設定します |
| `VITE_BRAND_NAME` | カスタムブランド名("BentoPDF" を置き換えます) |
| `VITE_BRAND_LOGO` | `public/` からの相対ロゴパス(例: `images/my-logo.svg`) |
| `VITE_FOOTER_TEXT` | カスタムのフッター/著作権テキスト |
| `VITE_DEFAULT_LANGUAGE` | デフォルトの UI 言語(例: `fr`、`de`、`es`) |
### ステップ 4: デプロイする
「Deploy」をクリックして、ビルドが完了するのを待ちます。
## カスタムドメイン
1. プロジェクトの設定を開きます
2. 「Domains」に移動します
3. カスタムドメインを追加します
4. 指示に従って DNS を設定します
## 制限事項
::: warning 大きなファイル
Vercel のサーバーレス関数には 50MB の制限があります。BentoPDF は静的サイトなので影響はないはずですが、WASM モジュールは大きく(合計で約 100MB)、`/public` フォルダーから配信されるようにしてください。
:::
## トラブルシューティング
### ビルドのタイムアウト
ビルドがタイムアウトする場合は、次を試してください。
```json
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist"
}
```
### 更新すると 404 になる
SPA のルーティング用に `vercel.json` を追加します。
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
```
### Word/ODT/Excel から PDF への変換が動作しない
LibreOffice WASM による変換には `SharedArrayBuffer` が必要で、すべてのページで次のレスポンスヘッダーが必要です。
- `Cross-Origin-Embedder-Policy: require-corp`
- `Cross-Origin-Opener-Policy: same-origin`
事前圧縮された `.wasm.gz` と `.data.gz` ファイルにも、ブラウザーが展開できるよう `Content-Encoding: gzip` が必要です。
これらを `vercel.json` に追加します。
```json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" },
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Resource-Policy", "value": "cross-origin" }
]
},
{
"source": "/libreoffice-wasm/soffice.wasm.gz",
"headers": [
{ "key": "Content-Type", "value": "application/wasm" },
{ "key": "Content-Encoding", "value": "gzip" }
]
},
{
"source": "/libreoffice-wasm/soffice.data.gz",
"headers": [
{ "key": "Content-Type", "value": "application/octet-stream" },
{ "key": "Content-Encoding", "value": "gzip" }
]
}
]
}
```
確認するには、DevTools のコンソールを開いて `console.log(window.crossOriginIsolated)` を実行します。`true` が返れば正常です。