mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-13 17:54:19 +01:00
feat: translate all pages, UI messages and docs and fix SEO redirects and indexing
This commit is contained in:
1 parent
5e019de5ae
commit
4edb1718cc
3344 files changed
+415643
-4759
No files matched your search
@@ -0,0 +1,275 @@
|
||||
# 使用 Apache 部署
|
||||
|
||||
使用 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/javascript`,而不是 `application/octet-stream`。
|
||||
|
||||
### Rewrite 不生效
|
||||
|
||||
请检查 mod_rewrite 是否已启用:
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
```
|
||||
|
||||
### Word/ODT/Excel 转 PDF 无法使用
|
||||
|
||||
LibreOffice WASM 需要 `SharedArrayBuffer`,这需要以下响应头:
|
||||
|
||||
```apache
|
||||
Header always set Cross-Origin-Embedder-Policy "require-corp"
|
||||
Header always set Cross-Origin-Opener-Policy "same-origin"
|
||||
```
|
||||
|
||||
它还需要安全上下文。`http://localhost` 可用于本地测试,但 `http://192.168.x.x` 等局域网 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
|
||||
```
|
||||
@@ -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 → Create distribution
|
||||
2. Origin domain:选择您的 S3 存储桶
|
||||
3. 启用 "Origin Access Control"
|
||||
4. Default root object:`index.html`
|
||||
5. 创建分配
|
||||
|
||||
## 第 3b 步:响应头策略(LibreOffice WASM 必需)
|
||||
|
||||
基于 LibreOffice 的转换(Word、Excel、PowerPoint 转 PDF)需要 `SharedArrayBuffer`,而这需要特定的响应头。请创建一个 CloudFront 响应头策略(Response Headers Policy):
|
||||
|
||||
1. 前往 CloudFront → Policies → Response headers
|
||||
2. 创建包含以下响应头的自定义策略:
|
||||
|
||||
| 响应头 | 值 |
|
||||
| ------------------------------ | -------------- |
|
||||
| `Cross-Origin-Embedder-Policy` | `require-corp` |
|
||||
| `Cross-Origin-Opener-Policy` | `same-origin` |
|
||||
| `Cross-Origin-Resource-Policy` | `cross-origin` |
|
||||
|
||||
3. 将该策略附加到分配的默认缓存行为
|
||||
|
||||
或者通过 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 步:自定义错误页面
|
||||
|
||||
将 404 配置为返回 `index.html`,以支持 SPA 路由:
|
||||
|
||||
1. CloudFront → Error pages
|
||||
2. 创建自定义错误响应:
|
||||
- HTTP error code:404
|
||||
- Response page path:`/index.html`
|
||||
- HTTP response code:200
|
||||
|
||||
## 成本估算
|
||||
|
||||
| 资源 | 预估成本 |
|
||||
| ----------------------- | ----------- |
|
||||
| S3 存储(约 500MB) | 约 $0.01/月 |
|
||||
| CloudFront(1TB 流量) | 约 $85/月 |
|
||||
| CloudFront(10GB 流量) | 约 $0.85/月 |
|
||||
|
||||
::: tip
|
||||
对于不常访问的文件,可使用 S3 Intelligent Tiering 来优化成本。
|
||||
:::
|
||||
|
||||
## 使用 Terraform 自动化
|
||||
|
||||
```hcl
|
||||
# main.tf
|
||||
resource "aws_s3_bucket" "bentopdf" {
|
||||
bucket = "your-bentopdf-bucket"
|
||||
}
|
||||
|
||||
resource "aws_cloudfront_distribution" "bentopdf" {
|
||||
origin {
|
||||
domain_name = aws_s3_bucket.bentopdf.bucket_regional_domain_name
|
||||
origin_id = "S3Origin"
|
||||
}
|
||||
|
||||
enabled = true
|
||||
default_root_object = "index.html"
|
||||
|
||||
default_cache_behavior {
|
||||
allowed_methods = ["GET", "HEAD"]
|
||||
cached_methods = ["GET", "HEAD"]
|
||||
target_origin_id = "S3Origin"
|
||||
|
||||
viewer_protocol_policy = "redirect-to-https"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,154 @@
|
||||
# 部署到 Cloudflare Pages
|
||||
|
||||
[Cloudflare Pages](https://pages.cloudflare.com) 提供快速的全球静态站点托管,带宽不限量。
|
||||
|
||||
## 快速部署
|
||||
|
||||
1. 前往 [Cloudflare Pages](https://dash.cloudflare.com/?to=/:account/pages)
|
||||
2. 点击 "Create a project"
|
||||
3. 连接您的 GitHub 仓库
|
||||
|
||||
## 构建配置
|
||||
|
||||
| 设置 | 值 |
|
||||
| ---------------------- | --------------- |
|
||||
| Framework preset | None |
|
||||
| Build command | `npm run build` |
|
||||
| Build output directory | `dist` |
|
||||
| Root directory | `/` |
|
||||
|
||||
## 环境变量
|
||||
|
||||
在 Settings → Environment variables 中添加以下变量:
|
||||
|
||||
| 变量 | 值 |
|
||||
| ----------------------- | ------------------------------------- |
|
||||
| `NODE_VERSION` | `18` |
|
||||
| `SIMPLE_MODE` | `false`(可选) |
|
||||
| `VITE_BRAND_NAME` | 自定义品牌名称(可选) |
|
||||
| `VITE_BRAND_LOGO` | 相对于 `public/` 的 Logo 路径(可选) |
|
||||
| `VITE_FOOTER_TEXT` | 自定义页脚/版权文本(可选) |
|
||||
| `VITE_DEFAULT_LANGUAGE` | 默认界面语言,例如 `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 引擎将无法初始化。
|
||||
:::
|
||||
|
||||
创建 `_redirects` 以支持 SPA 路由:
|
||||
|
||||
```
|
||||
/* /index.html 200
|
||||
```
|
||||
|
||||
## 自定义域名
|
||||
|
||||
1. 前往您的 Pages 项目
|
||||
2. 点击 "Custom domains"
|
||||
3. 添加您的域名
|
||||
4. 如果域名托管在 Cloudflare,Cloudflare 会自动配置 DNS
|
||||
|
||||
## 优势
|
||||
|
||||
- **免费且带宽不限量**
|
||||
- **全球 CDN**,拥有 300+ 个边缘节点
|
||||
- **自动 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 |
|
||||
| **文件大小限制** | 每个请求最大 10MB |
|
||||
| **速率限制** | 每个 IP 每分钟 60 次请求(需要 KV) |
|
||||
| **私有 IP 拦截** | 拦截 localhost 和内部 IP |
|
||||
|
||||
### 启用速率限制
|
||||
|
||||
```bash
|
||||
# Create KV namespace
|
||||
npx wrangler kv namespace create "RATE_LIMIT_KV"
|
||||
|
||||
# Add to wrangler.toml with returned ID:
|
||||
# [[kv_namespaces]]
|
||||
# binding = "RATE_LIMIT_KV"
|
||||
# id = "YOUR_ID"
|
||||
|
||||
npx wrangler deploy
|
||||
```
|
||||
|
||||
### 使用代理 URL 构建
|
||||
|
||||
```bash
|
||||
VITE_CORS_PROXY_URL=https://your-worker.workers.dev npm run build
|
||||
```
|
||||
|
||||
或者使用 Docker:
|
||||
|
||||
```bash
|
||||
export VITE_CORS_PROXY_URL="https://your-worker.workers.dev"
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
--secret id=VITE_CORS_PROXY_URL,env=VITE_CORS_PROXY_URL \
|
||||
-t your-bentopdf .
|
||||
```
|
||||
|
||||
> **注意:** HMAC 签名的设置请参阅 [README](https://github.com/alam00000/bentopdf#digital-signature-cors-proxy-required)。
|
||||
@@ -0,0 +1,180 @@
|
||||
# 用于获取证书的 CORS 代理
|
||||
|
||||
数字签名工具使用 CORS 代理从外部证书颁发机构 (CA) 获取颁发者证书。这是必要的,因为许多 CA 服务器的响应中不包含 CORS 响应头,导致浏览器无法直接获取。
|
||||
|
||||
此外,许多 CA 服务器通过纯 HTTP 提供证书。当您的 BentoPDF 实例通过 HTTPS 托管时,浏览器会阻止这些 HTTP 请求(混合内容策略)。CORS 代理通过 HTTPS 端点并附带正确的响应头转发请求,从而同时解决这两个问题。
|
||||
|
||||
## 工作原理
|
||||
|
||||
使用证书为 PDF 签名时:
|
||||
|
||||
1. `zgapdfsigner` 库会尝试构建完整的证书链
|
||||
2. 它从您证书的 AIA (Authority Information Access) 扩展中嵌入的 URL 获取颁发者证书
|
||||
3. 这些请求会经由 CORS 代理转发,代理会添加必要的 `Access-Control-Allow-Origin` 响应头
|
||||
4. 代理将证书数据返回给浏览器
|
||||
|
||||
## 自托管 CORS 代理
|
||||
|
||||
如果您自托管 BentoPDF,则需要部署自己的 CORS 代理,数字签名才能处理需要获取证书链的证书。
|
||||
|
||||
### 方案 1:Cloudflare Workers(推荐)
|
||||
|
||||
1. **安装 Wrangler CLI**:
|
||||
|
||||
```bash
|
||||
npm install -g wrangler
|
||||
```
|
||||
|
||||
2. **登录 Cloudflare**:
|
||||
|
||||
```bash
|
||||
wrangler login
|
||||
```
|
||||
|
||||
3. **克隆 BentoPDF 并更新允许的来源**:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf/cloudflare
|
||||
```
|
||||
|
||||
打开 `cors-proxy-worker.js`,将 `ALLOWED_ORIGINS` 数组改为您的域名:
|
||||
|
||||
```js
|
||||
const ALLOWED_ORIGINS = [
|
||||
'https://your-domain.com',
|
||||
'https://www.your-domain.com',
|
||||
];
|
||||
```
|
||||
|
||||
::: warning 重要
|
||||
如果不做此修改,代理会以 **403 Forbidden** 错误拒绝来自您站点的所有请求。默认设置只允许来自 `bentopdf.com` 的请求。
|
||||
:::
|
||||
|
||||
4. **部署代理**:
|
||||
|
||||
```bash
|
||||
wrangler deploy
|
||||
```
|
||||
|
||||
记下您的 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 私有地址段、链路本地、环回、十进制 IP 以及云元数据地址 |
|
||||
| **Content-Type 安全** | 仅返回安全的证书 MIME 类型,阻止上游 content-type 注入 |
|
||||
| **文件大小限制** | 以流式方式返回响应,限制为 10MB,超出时在下载中途中止 |
|
||||
| **速率限制** | 每个 IP 或 IPv6 /64 每分钟 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
|
||||
- 您手动组装了完整证书链的证书
|
||||
@@ -0,0 +1,622 @@
|
||||
# 使用 Docker / Podman 部署
|
||||
|
||||
在生产环境中自托管 BentoPDF 的最简单方式。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Office 文件转换所需的响应头**
|
||||
>
|
||||
> 基于 LibreOffice 的工具(Word、Excel、PowerPoint 转换)需要以下 HTTP 响应头来支持 `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> 页面还必须通过安全上下文提供。`http://localhost` 可用于本地测试,但 `http://192.168.x.x` 等局域网 IP 通常不符合要求,因此即使响应头存在,通过纯 HTTP 进行 Office 转换也会失败。
|
||||
>
|
||||
> 官方容器镜像已包含这些响应头。如果使用反向代理(Traefik、Caddy 等),请确保保留或添加这些响应头,并对非环回访问使用 HTTPS。
|
||||
|
||||
> [!TIP]
|
||||
> **Podman 用户:** 将 `docker` 替换为 `podman`、将 `docker-compose` 替换为 `podman-compose`,所有 `docker` 命令都同样适用于 Podman。
|
||||
|
||||
## 快速开始
|
||||
|
||||
> [!TIP]
|
||||
> BentoPDF 提供两种构建版本:
|
||||
>
|
||||
> - **自托管版本**:`ghcr.io/alam00000/bentopdf-simple:latest`。包含所有 PDF 工具,但**不含**营销页面元素(没有首页横幅、FAQ、用户评价和页脚)。适用于内部/团队/组织部署。它**不是**功能精简的 "lite" 版本。
|
||||
> - **商业版本**:`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 worker 脚本 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` | 在界面中提供的 OCR 语言,以逗号分隔 | _(空;显示完整列表)_ |
|
||||
| `VITE_OCR_FONT_BASE_URL` | OCR 文本层字体目录 | _(空;使用远程 Noto 字体 URL)_ |
|
||||
| `VITE_DEFAULT_LANGUAGE` | 默认界面语言 | `en` |
|
||||
| `VITE_BRAND_NAME` | 自定义品牌名称 | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | 相对于 `public/` 的 Logo 路径 | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | 自定义页脚/版权文本 | `© 2026 BentoPDF. All rights reserved.` |
|
||||
| `DISABLE_TOOLS` | 要隐藏的工具 ID,以逗号分隔 | _(空;启用所有工具)_ |
|
||||
| `DISABLE_GITHUB_STARS` | 页面加载时跳过获取 GitHub star 数量,并从 CSP 中移除 `api.github.com`。Simple Mode 构建始终会跳过。 | `false` |
|
||||
| `SITE_URL` | 写入 canonical、hreflang 和站点地图链接的公开 URL。除非您希望搜索引擎将您的部署视为独立站点,否则请保持默认值。 | `https://www.bentopdf.com` |
|
||||
| `ALLOW_INDEXING` | 使用自定义 `SITE_URL` 时,每个页面都会被标记为 `noindex`,以免您的副本在搜索结果中与 bentopdf.com 竞争。设为 `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` 只允许精确到版本的包文件夹,例如 `https://cdn.jsdelivr.net/npm/@bentopdf/pymupdf-wasm@0.11.16/`,而不是整个 CDN。jsDelivr 提供任何公开的 npm 包或 GitHub 仓库,允许整个主机会让其中任何内容都能在您的实例上运行。其他主机上的 URL 则按来源允许。
|
||||
- 如果您在*运行时*通过应用内的高级设置页面配置自定义 WASM URL,这些来源**不在** CSP 中,浏览器会阻止对它们的请求。对于 `cdn.jsdelivr.net` 上同一个包的不同版本也是如此,因为只允许构建时设置的版本。运行时配置仅用于试验;要永久使用自定义 URL,请设置对应的 `VITE_*` 构建参数。
|
||||
- 覆盖全部三个 `VITE_WASM_*_URL` 值的离线隔离部署,也会从 CSP 中移除这些 `cdn.jsdelivr.net` 包文件夹(每个默认值都会被替换,而不是追加)。`cdn.jsdelivr.net` 上的 Tesseract OCR 默认值会保留在策略中,直到您同时设置三个 `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。部分覆盖会被拒绝,因为 worker、核心运行时和 traineddata 目录必须相互匹配。要完全离线输出可搜索 PDF,还需设置 `VITE_OCR_FONT_BASE_URL`,让 OCR 文本层字体从您的内部服务器加载,而不是公共的 Noto 字体 URL。
|
||||
|
||||
`VITE_DEFAULT_LANGUAGE` 设置首次访问者的界面语言。支持的值:`en`、`ar`、`be`、`fr`、`de`、`es`、`zh`、`zh-TW`、`vi`、`tr`、`id`、`it`、`pt`、`nl`、`da`。用户仍可切换语言,这只会更改默认值。
|
||||
|
||||
示例:
|
||||
|
||||
```bash
|
||||
# Build with French as the default language
|
||||
docker build --build-arg VITE_DEFAULT_LANGUAGE=fr -t bentopdf .
|
||||
docker run -d -p 3000:8080 bentopdf
|
||||
```
|
||||
|
||||
### 自定义品牌
|
||||
|
||||
将默认的 BentoPDF 标志、名称和页脚文本替换为您自己的。将您的 Logo 文件放入 `public/` 文件夹(或使用现有图片),然后在构建时传入品牌变量:
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg VITE_BRAND_NAME="AcmePDF" \
|
||||
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
|
||||
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
|
||||
-t acmepdf .
|
||||
```
|
||||
|
||||
品牌定制在完整模式和 Simple Mode 下都可使用,并且可以与所有其他构建时选项组合。
|
||||
|
||||
### 禁用特定工具
|
||||
|
||||
出于合规或安全要求,可以在界面中隐藏工具。被禁用的工具会从主页、搜索结果、键盘快捷键和工作流构建器中移除。直接通过 URL 访问会显示"工具不可用"页面。
|
||||
|
||||
工具 ID 是去掉 `.html` 的页面 URL。例如,如果工具位于 `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 Star 计数器
|
||||
|
||||
导航栏会显示实时的 GitHub star 数量,页面加载时从 `api.github.com` 获取。这是唯一一个不用于 PDF 功能的外部请求。可以在构建时禁用它,从包中移除获取代码,并从 Content-Security-Policy 中移除 `api.github.com`:
|
||||
|
||||
```bash
|
||||
docker build --build-arg DISABLE_GITHUB_STARS=true -t bentopdf .
|
||||
```
|
||||
|
||||
Simple Mode 构建(`bentopdf-simple`)始终会跳过 star 计数器,此参数只在商业版本或自定义的完整构建中需要。
|
||||
|
||||
### 自定义 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
|
||||
```
|
||||
|
||||
对于 `--ocr-languages`,请使用 `bash scripts/prepare-airgap.sh --list-ocr-languages` 打印出的代码,或使用 `bash scripts/prepare-airgap.sh --search-ocr-language <term>` 按名称搜索。当您使用受限的 OCR 子集构建时,请将相同的代码传给 `VITE_TESSERACT_AVAILABLE_LANGUAGES`,这样应用只会显示已打包的语言。要获得完整的离线 OCR 输出,还需托管随附的 `ocr-fonts/` 目录,并将 `VITE_OCR_FONT_BASE_URL` 指向它。
|
||||
|
||||
将变量设为空字符串即可禁用对应模块(用户必须通过高级设置手动配置)。
|
||||
|
||||
## 自定义端口
|
||||
|
||||
默认情况下,BentoPDF 在容器内监听 `8080` 端口。要更改此设置,请在运行时设置 `PORT` 环境变量:
|
||||
|
||||
```bash
|
||||
docker run -p 3000:9090 -e PORT=9090 ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
| 变量 | 说明 | 默认值 |
|
||||
| ------ | --------------------- | ------ |
|
||||
| `PORT` | 容器内 Nginx 监听端口 | `8080` |
|
||||
|
||||
标准 Dockerfile 和 nonroot Dockerfile 都支持此设置。
|
||||
|
||||
## 自定义用户 ID(PUID/PGID)
|
||||
|
||||
对于需要以特定非 root 用户运行的环境(NAS 设备、带安全上下文的 Kubernetes、组织策略),BentoPDF 提供了一个单独的 Dockerfile,支持 LSIO 风格的 PUID/PGID。
|
||||
|
||||
### 构建并运行
|
||||
|
||||
```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),推荐用于大多数部署。仅在需要特定 UID/GID 时才使用 `Dockerfile.nonroot`。
|
||||
|
||||
> [!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
|
||||
```
|
||||
@@ -0,0 +1,266 @@
|
||||
# 部署到 Hostinger
|
||||
|
||||
[Hostinger](https://hostinger.com) 是一家流行的共享主机提供商。本指南介绍如何将 BentoPDF 部署到 Hostinger 的共享主机。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 带有文件管理器访问权限的 Hostinger 主机套餐
|
||||
- 本地已安装用于构建的 Node.js
|
||||
|
||||
## 第 1 步:构建项目
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
构建产物位于 `dist` 文件夹中。
|
||||
|
||||
## 第 2 步:上传到 Hostinger
|
||||
|
||||
### 根域名部署
|
||||
|
||||
1. 登录您的 Hostinger 账户
|
||||
2. 前往 **File Manager** → **public_html**
|
||||
3. **删除**所有现有文件(如有需要请先备份)
|
||||
4. 将本地 `dist` 文件夹的全部内容**上传**到 `public_html`
|
||||
|
||||
### 子目录部署
|
||||
|
||||
如果部署到子目录(例如 `yourdomain.com/pdf-tools/`):
|
||||
|
||||
1. 使用正确的基础 URL 构建:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
2. 在 Hostinger 中创建文件夹:
|
||||
- 前往 **File Manager** → **public_html**
|
||||
- 新建文件夹:`pdf-tools`
|
||||
3. 将 `dist` 的全部内容上传到 `public_html/pdf-tools/`
|
||||
|
||||
## 第 3 步:创建 .htaccess 文件
|
||||
|
||||
在部署文件夹的根目录(`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 使用静态 HTML 文件,而不是 SPA 路由:
|
||||
|
||||
```apache
|
||||
# Language routes serve actual files from language directories
|
||||
RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f
|
||||
RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L]
|
||||
|
||||
# Root level pages
|
||||
RewriteCond %{REQUEST_FILENAME}.html -f
|
||||
RewriteRule ^([^/]+)$ $1.html [L]
|
||||
```
|
||||
|
||||
### 文件上传限制
|
||||
|
||||
Hostinger 可能对文件大小有限制。如果批量上传失败,请单独上传 WASM 文件。
|
||||
@@ -0,0 +1,538 @@
|
||||
# 自托管指南
|
||||
|
||||
BentoPDF 可以部署在您自己的基础设施上。本指南介绍多种部署方式。
|
||||
|
||||
## 使用 Docker / Podman 快速开始
|
||||
|
||||
自托管 BentoPDF 最快的方式:
|
||||
|
||||
> [!TIP]
|
||||
> BentoPDF 提供两种构建版本:
|
||||
>
|
||||
> - **自托管版本**:`ghcr.io/alam00000/bentopdf-simple:latest`。包含所有 PDF 工具,但**不含**营销页面元素(没有首页横幅、FAQ、用户评价和页脚)。适用于内部、团队或组织部署。它**不是**功能精简的“轻量”版本。
|
||||
> - **商业版本**:`ghcr.io/alam00000/bentopdf:latest`。完整的营销网站,bentopdf.com 本身以及运行面向公众部署的商业许可持有者使用该版本。
|
||||
>
|
||||
> 如果拿不准,请拉取自托管版本。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Office 文件转换需要 `SharedArrayBuffer`,这意味着应用必须同时满足跨源隔离,并通过安全上下文提供服务。官方镜像已经发送所需的 COOP/COEP 标头,但浏览器仍会在 `http://192.168.x.x` 这类普通 HTTP 局域网来源上禁用 `SharedArrayBuffer`。
|
||||
>
|
||||
> `http://localhost` 仅用于同一设备上的测试。如果用户通过局域网 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 部署指南](/zh/self-hosting/docker)。
|
||||
|
||||
## 从源码构建
|
||||
|
||||
```bash
|
||||
# Clone and build
|
||||
git clone https://github.com/alam00000/bentopdf.git
|
||||
cd bentopdf
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# The built files are in the `dist` folder
|
||||
```
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 自托管版本(Simple Mode)
|
||||
|
||||
自托管版本(`bentopdf-simple` 镜像,也称为 Simple Mode)与商业版本**功能完全相同**,所有 PDF 工具都在,行为也一致。它只是隐藏了仅适用于 bentopdf.com 本身或商业面向公众部署的营销页面元素。**它不是功能精简的“轻量”版本。**
|
||||
|
||||
**自托管版本隐藏的内容**(仅外观层面,不会移除任何 PDF 功能):
|
||||
|
||||
- 导航栏、首页横幅、功能介绍区、FAQ、用户评价、页脚
|
||||
- 页面标题更新为“PDF 工具”
|
||||
|
||||
**自托管版本保留的内容**(所有真正处理 PDF 的部分):
|
||||
|
||||
- 所有 PDF 工具(合并、拆分、编辑、签名、OCR、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 品牌,适合预览该版本):
|
||||
|
||||
```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 的两种许可方式都允许运行商业版本:
|
||||
|
||||
- **AGPL-3.0**(免费):如果您的部署以 AGPL 公开完整源代码(包括任何品牌修改和周边业务逻辑),则允许使用。
|
||||
- **商业许可**($79 终身):闭源或专有部署需要此许可,即您不公开品牌分支或业务代码的情况。
|
||||
|
||||
完整对比请参阅[许可页面](https://www.bentopdf.com/licensing)。采用 AGPL 许可的 WASM 模块(PyMuPDF、Ghostscript、CoherentPDF)在运行时从 CDN 加载,因此不会进入您的镜像,也不会改变您的许可状况。
|
||||
:::
|
||||
|
||||
### Base URL
|
||||
|
||||
部署到子目录:
|
||||
|
||||
```bash
|
||||
BASE_URL=/pdf-tools/ npm run build
|
||||
```
|
||||
|
||||
### 自定义品牌
|
||||
|
||||
在构建时将默认的 BentoPDF 标志、名称和页脚文字替换为您自己的:
|
||||
|
||||
| 变量 | 说明 | 默认值 |
|
||||
| ------------------ | --------------------------- | --------------------------------------- |
|
||||
| `VITE_BRAND_NAME` | 显示在页眉和页脚的品牌名称 | `BentoPDF` |
|
||||
| `VITE_BRAND_LOGO` | 相对于 `public/` 的标志路径 | `images/favicon-no-bg.svg` |
|
||||
| `VITE_FOOTER_TEXT` | 自定义页脚/版权文字 | `© 2026 BentoPDF. All rights reserved.` |
|
||||
|
||||
```bash
|
||||
# Place your logo in public/, then build
|
||||
VITE_BRAND_NAME="AcmePDF" \
|
||||
VITE_BRAND_LOGO="images/acme-logo.svg" \
|
||||
VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
|
||||
npm run build
|
||||
```
|
||||
|
||||
或通过 Docker:
|
||||
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg VITE_BRAND_NAME="AcmePDF" \
|
||||
--build-arg VITE_BRAND_LOGO="images/acme-logo.svg" \
|
||||
--build-arg VITE_FOOTER_TEXT="© 2026 Acme Corp. Internal use only." \
|
||||
-t acmepdf .
|
||||
```
|
||||
|
||||
品牌定制在完整模式和 Simple Mode 下均可使用,并可与所有其他构建时选项(`BASE_URL`、`SIMPLE_MODE`、`VITE_DEFAULT_LANGUAGE`)组合使用。
|
||||
|
||||
### 禁用特定工具
|
||||
|
||||
出于合规或安全考虑,可以隐藏个别工具。被禁用的工具会从首页、搜索、快捷键、工作流构建器以及直接 URL 访问中移除。
|
||||
|
||||
工具 ID 即页面 URL 去掉 `.html` 的部分,打开任意工具并查看 URL 即可(例如 `edit-pdf`、`sign-pdf`、`encrypt-pdf`)。
|
||||
|
||||
**构建时**(写入打包产物):
|
||||
|
||||
```bash
|
||||
DISABLE_TOOLS="edit-pdf,sign-pdf" npm run build
|
||||
```
|
||||
|
||||
**运行时**(无需重新构建,挂载 `config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"disabledTools": ["edit-pdf", "sign-pdf"]
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
docker run -d -p 3000:8080 \
|
||||
-v ./config.json:/usr/share/nginx/html/config.json:ro \
|
||||
ghcr.io/alam00000/bentopdf-simple:latest
|
||||
```
|
||||
|
||||
两种方法可以同时使用,列表会合并。
|
||||
|
||||
您还可以在 `config.json` 中添加 `editorDisabledCategories`,在不禁用整个工具的情况下禁用 PDF 编辑器内的特定功能(例如涂黑)。完整的类别列表请参阅 [Docker 指南](/zh/self-hosting/docker#禁用编辑器功能)。
|
||||
|
||||
## 部署指南
|
||||
|
||||
选择您的平台:
|
||||
|
||||
- [Vercel](/zh/self-hosting/vercel)
|
||||
- [Netlify](/zh/self-hosting/netlify)
|
||||
- [Cloudflare Pages](/zh/self-hosting/cloudflare)
|
||||
- [AWS S3 + CloudFront](/zh/self-hosting/aws)
|
||||
- [Hostinger](/zh/self-hosting/hostinger)
|
||||
- [Nginx](/zh/self-hosting/nginx)
|
||||
- [Apache](/zh/self-hosting/apache)
|
||||
- [Docker](/zh/self-hosting/docker)
|
||||
- [Kubernetes](/zh/self-hosting/kubernetes)
|
||||
- [CORS 代理](/zh/self-hosting/cors-proxy):数字签名所必需
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Word / ODT / Excel / PowerPoint 转 PDF 卡住(SharedArrayBuffer 不可用)
|
||||
|
||||
**现象**:基于 LibreOffice 的文档转换(Word、ODT、Excel、PowerPoint 转 PDF)在约 55% 处卡住或无法启动。浏览器控制台可能显示:
|
||||
|
||||
> `ReferenceError: SharedArrayBuffer is not defined`
|
||||
|
||||
……或者 `window.crossOriginIsolated` 返回 `false`,或者 WASM 编译报告 `expected magic word 00 61 73 6d, found 1f 8b 08 08`。
|
||||
|
||||
**原因**:LibreOffice WASM 需要 `SharedArrayBuffer`,而浏览器只有在页面**跨源隔离**且通过**安全上下文**提供时才会启用它。这意味着必须同时满足两点:
|
||||
|
||||
1. 每个响应都包含以下两个标头:
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
2. 页面通过 `https://...` 或 `http://localhost` 提供。局域网 IP 上的普通 HTTP(例如 `http://192.168.x.x`)不属于安全上下文,浏览器会在那里禁用 `SharedArrayBuffer`。
|
||||
|
||||
`00 61 73 6d / 1f 8b 08 08` 不匹配是另一个子症状:预压缩的 `.wasm.gz` / `.data.gz` 文件缺少 `Content-Encoding: gzip` 响应标头,因此浏览器收到的是未解压的原始 gzip 字节,而不是 WASM。
|
||||
|
||||
**解决方法**:请参阅对应平台的部署指南以获取确切配置:
|
||||
|
||||
- [Nginx →](/zh/self-hosting/nginx#word-odt-excel-转-pdf-无法使用)
|
||||
- [Apache →](/zh/self-hosting/apache#word-odt-excel-转-pdf-无法使用)
|
||||
- [AWS S3 + CloudFront →](/zh/self-hosting/aws#第-3b-步-响应头策略-libreoffice-wasm-必需)
|
||||
- [Cloudflare Pages →](/zh/self-hosting/cloudflare#配置文件)(`_headers` 文件)
|
||||
- [Netlify →](/zh/self-hosting/netlify#word-odt-excel-转-pdf-卡在-55)(`netlify.toml`)
|
||||
- [Vercel →](/zh/self-hosting/vercel#word-odt-excel-转-pdf-无法使用)(`vercel.json`)
|
||||
- [Hostinger →](/zh/self-hosting/hostinger#libreoffice-工具无法使用)(`.htaccess`)
|
||||
- [Kubernetes →](/zh/self-hosting/kubernetes#确保-sharedarraybuffer-标头仍然有效-ingress-gateway)
|
||||
- **Docker**:由内置的 nginx 配置自动处理,无需任何操作。
|
||||
|
||||
**验证**:在任意 BentoPDF 页面打开开发者工具控制台并运行:
|
||||
|
||||
```js
|
||||
console.log(window.crossOriginIsolated); // should be true
|
||||
console.log(typeof SharedArrayBuffer); // should be "function"
|
||||
```
|
||||
|
||||
如果页面使用 HTTPS 或 `http://localhost`,并且每个响应都带有 COEP/COOP 标头,两项检查都会通过。如果您使用的是 `http://192.168.x.x` 或其他非回环 HTTP 来源,请使用 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` 文件的 MIME 类型映射(内置的 PDF 查看器使用该扩展名的 ES 模块)。许多默认服务器配置会将无法识别的扩展名归为 `application/octet-stream`,浏览器拒绝将其作为 ES 模块执行。
|
||||
|
||||
**解决方法**:请参阅对应平台的部署指南以获取确切的配置片段:
|
||||
|
||||
- [Nginx →](/zh/self-hosting/nginx#签署-pdf-或-form-filler-显示空白查看器-mjs-mime-错误)
|
||||
- [Apache →](/zh/self-hosting/apache#签名-pdf-或表单填写器显示空白查看器-mjs-mime-错误)
|
||||
- [AWS S3 + CloudFront →](/zh/self-hosting/aws#第-2-步-构建并上传)(参见 `aws s3 cp ... --include "*.mjs"`)
|
||||
- [Kubernetes →](/zh/self-hosting/kubernetes#mjs-mime-类型错误-签署-pdf-form-filler-iframe-空白)
|
||||
- **Docker、Vercel、Netlify、Cloudflare Pages、Hostinger**:自动处理,无需任何操作。
|
||||
|
||||
**验证**:打开开发者工具的 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_*` 变量留空并使用默认的在线资源,要么同时设置 worker、core 和 lang 的 URL,以实现自托管或离线 OCR。如果您只捆绑了特定的 OCR 语言,还请将 `VITE_TESSERACT_AVAILABLE_LANGUAGES` 设为相同的逗号分隔代码,这样界面只会提供已安装的语言,选择不支持的语言时会给出明确的错误信息。若要完全离线生成可搜索的 PDF,还需将 `VITE_OCR_FONT_BASE_URL` 设为提供内置 OCR 字体的内部目录。
|
||||
|
||||
用户也可以随时通过界面中的**高级设置**覆盖这些默认值,浏览器中保存的用户覆盖设置优先于环境默认值。
|
||||
|
||||
### 离线隔离(Air-Gapped)部署
|
||||
|
||||
适用于没有互联网访问的网络(政府、医疗、金融等)。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` | 启用 Simple Mode | 关闭 |
|
||||
| `--base-url <path>` | 子目录基础 URL(例如 `/pdf/`) | `/` |
|
||||
| `--language <code>` | 默认界面语言(例如 `fr`、`de`) | _(无)_ |
|
||||
| `--brand-name <name>` | 自定义品牌名称 | _(无)_ |
|
||||
| `--brand-logo <path>` | 相对于 `public/` 的标志路径 | _(无)_ |
|
||||
| `--footer-text <text>` | 自定义页脚文字 | _(无)_ |
|
||||
| `--ocr-languages <list>` | 要捆绑的 OCR 语言,以逗号分隔 | `eng` |
|
||||
| `--list-ocr-languages` | 打印支持的 OCR 代码和名称后退出 | 关闭 |
|
||||
| `--search-ocr-language <term>` | 按名称或缩写搜索 OCR 代码 | 关闭 |
|
||||
| `--dockerfile <path>` | 要使用的 Dockerfile | `Dockerfile` |
|
||||
| `--skip-docker` | 跳过 Docker 构建和导出 | 关闭 |
|
||||
| `--skip-wasm` | 跳过 WASM 下载(复用现有 `.tgz` 文件) | 关闭 |
|
||||
|
||||
交互式提示也接受 `list` 以打印完整的 Tesseract 支持代码列表,以及 `search <term>` 以查找匹配项,例如 `search german` 或 `search chi`。
|
||||
|
||||
::: warning 同源要求
|
||||
WASM 文件必须与 BentoPDF 应用从**同一来源**提供。Web Worker 使用 `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 中的五个 `.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 代理
|
||||
|
||||
如果您需要通过正确的 CORS 标头提供 AGPL WASM 文件,可以部署一个简单的代理。示例实现请参阅 [Cloudflare WASM 代理指南](https://github.com/alam00000/bentopdf/blob/main/cloudflare/WASM-PROXY.md)。
|
||||
|
||||
::: tip 为什么要分离?
|
||||
这种分离可以确保:
|
||||
|
||||
- 为商业用户提供清晰的法律合规性
|
||||
- BentoPDF 核心保持其双重许可(AGPL-3.0 / 商业)
|
||||
- WASM 文件在运行时加载,而不是捆绑在源代码中
|
||||
:::
|
||||
|
||||
## 系统要求
|
||||
|
||||
| 要求 | 最低配置 |
|
||||
| ---- | --------------------------------- |
|
||||
| 存储 | 约 100 MB(不含 AGPL 模块的核心) |
|
||||
| 内存 | 512 MB |
|
||||
| CPU | 任意现代处理器 |
|
||||
|
||||
::: tip
|
||||
BentoPDF 是静态站点,不需要数据库或后端服务器!
|
||||
:::
|
||||
@@ -0,0 +1,211 @@
|
||||
# 使用 Kubernetes 部署
|
||||
|
||||
对于静态站点来说,Kubernetes 可能有些大材小用,但如果您已经统一采用 Helm + GitOps,它会非常合适。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Office 文件转换所需的标头**
|
||||
>
|
||||
> 基于 LibreOffice 的工具(Word、Excel、PowerPoint 转换)需要以下 HTTP 标头来支持 `SharedArrayBuffer`:
|
||||
>
|
||||
> - `Cross-Origin-Opener-Policy: same-origin`
|
||||
> - `Cross-Origin-Embedder-Policy: require-corp`
|
||||
>
|
||||
> 官方 BentoPDF nginx 镜像已包含这些标头。在 Kubernetes 中,**Ingress/Gateway 控制器同样是反向代理**,因此请确保这些标头被保留(或在边缘层添加)。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- Kubernetes 集群
|
||||
- Helm v3
|
||||
- 一个 BentoPDF nginx 镜像(例如 `ghcr.io/alam00000/bentopdf:<tag>`),监听 **8080 端口**
|
||||
|
||||
## 使用 Helm 部署
|
||||
|
||||
### 从本仓库安装(本地 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
|
||||
```
|
||||
|
||||
### 从 GHCR 安装(OCI chart)
|
||||
|
||||
如果 chart 已作为 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
|
||||
```
|
||||
|
||||
## 对外暴露
|
||||
|
||||
### Port-forward(快速测试)
|
||||
|
||||
```bash
|
||||
kubectl -n bentopdf port-forward deploy/bentopdf 8080:8080
|
||||
```
|
||||
|
||||
### Ingress(可选)
|
||||
|
||||
启用 Ingress(以 nginx-ingress 为例):
|
||||
|
||||
```yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
hosts:
|
||||
- host: pdf.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
```
|
||||
|
||||
### Gateway API(可选)
|
||||
|
||||
此 chart 支持 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`。使用开发者工具的 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 即页面 URL 去掉 `.html` 的部分,打开任意工具并查看 URL 即可(例如 `edit-pdf`、`merge-pdf`、`compress-pdf`)。被禁用的工具会从首页、搜索、快捷键、工作流构建器以及直接 URL 访问中隐藏。完整选项请参阅 [Docker 指南](/zh/self-hosting/docker#禁用特定工具)。
|
||||
@@ -0,0 +1,139 @@
|
||||
# 部署到 Netlify
|
||||
|
||||
[Netlify](https://netlify.com) 提供出色的静态站点托管,并有慷慨的免费额度。
|
||||
|
||||
## 一键部署
|
||||
|
||||
[](https://app.netlify.com/start/deploy?repository=https://github.com/alam00000/bentopdf)
|
||||
|
||||
## 手动部署
|
||||
|
||||
### 步骤 1:连接仓库
|
||||
|
||||
1. 登录 [Netlify](https://app.netlify.com)
|
||||
2. 点击 "Add new site" → "Import an existing project"
|
||||
3. 连接您的 GitHub 账户
|
||||
4. 选择您的 BentoPDF 分支
|
||||
|
||||
### 步骤 2:配置构建设置
|
||||
|
||||
| 设置 | 值 |
|
||||
| --------- | --------------- |
|
||||
| 构建命令 | `npm run build` |
|
||||
| 发布目录 | `dist` |
|
||||
| Node 版本 | 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` | 默认界面语言(例如 `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%,请打开开发者工具控制台并检查:
|
||||
|
||||
```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` 标头。请确保 `netlify.toml` 中包含 `soffice.wasm.gz` 和 `soffice.data.gz` 的 `[[headers]]` 块。
|
||||
|
||||
### 构建失败
|
||||
|
||||
检查 Node 版本兼容性:
|
||||
|
||||
```toml
|
||||
[build.environment]
|
||||
NODE_VERSION = "20"
|
||||
```
|
||||
|
||||
### 首次加载缓慢
|
||||
|
||||
在 Site settings → Build & deploy → Asset optimization 中启用资源优化。
|
||||
@@ -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` 中,将 `mjs` 加到现有的 `application/javascript` 行:
|
||||
|
||||
```nginx
|
||||
application/javascript js mjs;
|
||||
```
|
||||
|
||||
> 请勿再追加*第二行* `application/javascript`。nginx 逐行解析 `mime.types`,每种类型只有一个扩展名映射生效,因此在某些版本中第二行会被静默忽略。请编辑现有的行,把 `mjs` 与 `js` 放在一起。
|
||||
|
||||
或者在您的 server 块内局部覆盖(无需修改全局文件):
|
||||
|
||||
```nginx
|
||||
types {
|
||||
application/javascript js mjs;
|
||||
application/wasm wasm;
|
||||
}
|
||||
```
|
||||
|
||||
修改后重新加载 nginx:
|
||||
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
确认方法:在浏览器开发者工具的 Network 标签中,找到失败的 `.mjs` 请求并检查 `Content-Type` 响应标头。它现在应为 `application/javascript`,而不是 `application/octet-stream`。
|
||||
|
||||
### Word/ODT/Excel 转 PDF 无法使用
|
||||
|
||||
LibreOffice WASM 需要 `SharedArrayBuffer`,而这需要 `Cross-Origin-Embedder-Policy` 和 `Cross-Origin-Opener-Policy` 标头。它还需要安全上下文,因此 `http://localhost` 可用于本地测试,但 `http://192.168.x.x` 或其他局域网 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
|
||||
```
|
||||
@@ -0,0 +1,122 @@
|
||||
# 部署到 Vercel
|
||||
|
||||
[Vercel](https://vercel.com) 为静态站点提供最简单的部署体验。
|
||||
|
||||
## 一键部署
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/alam00000/bentopdf)
|
||||
|
||||
## 手动部署
|
||||
|
||||
### 步骤 1:Fork 仓库
|
||||
|
||||
将 [bentopdf/bentopdf](https://github.com/alam00000/bentopdf) Fork 到您的 GitHub 账户。
|
||||
|
||||
### 步骤 2:导入到 Vercel
|
||||
|
||||
1. 前往 [vercel.com/new](https://vercel.com/new)
|
||||
2. 选择您 Fork 的仓库
|
||||
3. 配置项目:
|
||||
|
||||
| 设置 | 值 |
|
||||
| ---------------- | --------------- |
|
||||
| Framework Preset | Vite |
|
||||
| Build Command | `npm run build` |
|
||||
| Output Directory | `dist` |
|
||||
| Install Command | `npm install` |
|
||||
|
||||
### 步骤 3:环境变量(可选)
|
||||
|
||||
如有需要,添加以下变量:
|
||||
|
||||
| 变量 | 说明 |
|
||||
| ----------------------- | -------------------------------------------------------- |
|
||||
| `SIMPLE_MODE` | 设为 `true` 以使用精简界面 |
|
||||
| `VITE_BRAND_NAME` | 自定义品牌名称(替换 "BentoPDF") |
|
||||
| `VITE_BRAND_LOGO` | 相对于 `public/` 的标志路径(例如 `images/my-logo.svg`) |
|
||||
| `VITE_FOOTER_TEXT` | 自定义页脚/版权文字 |
|
||||
| `VITE_DEFAULT_LANGUAGE` | 默认界面语言(例如 `fr`、`de`、`es`) |
|
||||
|
||||
### 步骤 4:部署
|
||||
|
||||
点击 "Deploy" 并等待构建完成。
|
||||
|
||||
## 自定义域名
|
||||
|
||||
1. 前往项目设置
|
||||
2. 进入 "Domains"
|
||||
3. 添加您的自定义域名
|
||||
4. 按说明配置 DNS
|
||||
|
||||
## 限制
|
||||
|
||||
::: warning 大文件
|
||||
Vercel 的 serverless 函数有 50MB 的限制。由于 BentoPDF 是静态站点,这通常不会影响您,但 WASM 模块很大(总计约 100MB)。请确保它们从 `/public` 文件夹提供。
|
||||
:::
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 构建超时
|
||||
|
||||
如果构建超时,请尝试:
|
||||
|
||||
```json
|
||||
// vercel.json
|
||||
{
|
||||
"buildCommand": "npm run build",
|
||||
"outputDirectory": "dist"
|
||||
}
|
||||
```
|
||||
|
||||
### 刷新后出现 404
|
||||
|
||||
添加 `vercel.json` 以支持 SPA 路由:
|
||||
|
||||
```json
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
|
||||
}
|
||||
```
|
||||
|
||||
### Word/ODT/Excel 转 PDF 无法使用
|
||||
|
||||
LibreOffice WASM 转换需要 `SharedArrayBuffer`,这要求所有页面都带有以下响应标头:
|
||||
|
||||
- `Cross-Origin-Embedder-Policy: require-corp`
|
||||
- `Cross-Origin-Opener-Policy: same-origin`
|
||||
|
||||
预压缩的 `.wasm.gz` 和 `.data.gz` 文件还需要 `Content-Encoding: gzip`,浏览器才会对其解压。
|
||||
|
||||
将以下内容添加到您的 `vercel.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"headers": [
|
||||
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" },
|
||||
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
|
||||
{ "key": "Cross-Origin-Resource-Policy", "value": "cross-origin" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/libreoffice-wasm/soffice.wasm.gz",
|
||||
"headers": [
|
||||
{ "key": "Content-Type", "value": "application/wasm" },
|
||||
{ "key": "Content-Encoding", "value": "gzip" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/libreoffice-wasm/soffice.data.gz",
|
||||
"headers": [
|
||||
{ "key": "Content-Type", "value": "application/octet-stream" },
|
||||
{ "key": "Content-Encoding", "value": "gzip" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
要验证,请打开开发者工具控制台并运行 `console.log(window.crossOriginIsolated)`,它应返回 `true`。
|
||||
Reference in new issue
Block a user