BREAKING CHANGE: Dockerfile 結構重新設計 ## 重構內容 ### Multi-Stage Build 結構 - Stage 1 [base]: Bun runtime 基礎 - Stage 2 [install]: Node 依賴安裝 - Stage 3 [prerelease]: 應用程式建構 - Stage 4 [system-tools]: APT 系統工具(拆分為 14 個獨立 RUN) - Stage 5 [fonts]: 字型安裝 - Stage 6 [python-tools]: Python CLI 工具(拆分為 9 個獨立 RUN) - Stage 7 [models]: 模型下載 - Stage 8 [release]: 最終 Image ### 新增 Scripts - install-system-tools.sh: 系統工具安裝 - install-fonts.sh: 字型安裝 - install-python-tools.sh: Python 工具安裝 - download-models.sh: 模型下載 - verify-installation.sh: 完整安裝驗證 ### 刪除舊檔案 - Dockerfile.backup - Dockerfile.offline - Dockerfile.old ### 路徑更新 - MinerU 模型: /opt/convertx/models/mineru/PDF-Extract-Kit-1.0 - BabelDOC 快取: /root/.cache/babeldoc ### Multi-Arch 支援 - linux/amd64: 功能完整 - linux/arm64: 安全降級(MinerU/resvg 跳過) Closes #docker-refactor
628 lines
24 KiB
Docker
628 lines
24 KiB
Docker
# ==============================================================================
|
||
# ConvertX-CN 官方 Docker Image
|
||
# 版本:v0.1.16 - 完整重構版
|
||
# ==============================================================================
|
||
#
|
||
# 📦 Image 說明:
|
||
# - 這是 ConvertX-CN 官方 Docker Hub Image 的生產 Dockerfile
|
||
# - ⚠️ 所有模型、字型、tokenizer 已在 build 階段完整預下載
|
||
# - ⚠️ Runtime 完全離線運行(僅翻譯服務允許連網)
|
||
#
|
||
# 🔒 Offline-first 設計原則:
|
||
# 1. Runtime(docker run 後):
|
||
# ❌ 禁止任何模型、字型、tokenizer、metadata 下載
|
||
# ❌ MinerU / BabelDOC / PDFMathTranslate 不得嘗試連網
|
||
# ✅ 只有翻譯服務(Google / DeepL / Azure / OpenAI)允許連網
|
||
# 2. Build time(docker build 時):
|
||
# ✅ 允許連網下載所有資源
|
||
# ✅ 所有「可能會在 runtime 下載的東西」必須提前固定存放
|
||
#
|
||
# 🤖 預下載模型清單:
|
||
# - PDFMathTranslate: DocLayout-YOLO ONNX(佈局分析)
|
||
# - BabelDOC: DocLayout-YOLO + 字型資源 + tiktoken
|
||
# - MinerU: PDF-Extract-Kit-1.0(Pipeline 模型)
|
||
#
|
||
# 🏗️ Multi-Stage Build 結構:
|
||
# Stage 1 [base] : Bun runtime 基礎
|
||
# Stage 2 [install] : Node 依賴安裝
|
||
# Stage 3 [prerelease] : 應用程式建構
|
||
# Stage 4 [system-tools] : APT 系統工具
|
||
# Stage 5 [fonts] : 字型安裝
|
||
# Stage 6 [python-tools] : Python CLI 工具
|
||
# Stage 7 [models] : 模型下載
|
||
# Stage 8 [release] : 最終 Image
|
||
#
|
||
# 🌍 Multi-Arch 支援:
|
||
# - linux/amd64: 功能完整
|
||
# - linux/arm64: 安全降級(不支援的工具會跳過)
|
||
#
|
||
# 📊 Image 大小:約 10-14 GB(含完整模型)
|
||
#
|
||
# ==============================================================================
|
||
|
||
# ==============================================================================
|
||
# Stage 1: Base - Bun Runtime
|
||
# ==============================================================================
|
||
FROM debian:bookworm-slim AS base
|
||
LABEL org.opencontainers.image.source="https://github.com/pi-docket/ConvertX-CN"
|
||
LABEL org.opencontainers.image.description="ConvertX-CN - 完全離線化檔案轉換服務"
|
||
LABEL org.opencontainers.image.version="v0.1.16"
|
||
WORKDIR /app
|
||
|
||
# 設定非互動模式
|
||
ENV DEBIAN_FRONTEND=noninteractive
|
||
|
||
# 配置 APT 重試機制
|
||
RUN set -ex && \
|
||
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries && \
|
||
echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
|
||
echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
|
||
echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
|
||
echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
|
||
|
||
# 安裝基礎工具
|
||
RUN set -ex && \
|
||
apt-get update && \
|
||
apt-get install -y --no-install-recommends \
|
||
curl \
|
||
unzip \
|
||
ca-certificates && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 安裝 Bun(根據架構選擇版本)
|
||
ARG BUN_VERSION=1.3.6
|
||
RUN set -ex && \
|
||
ARCH=$(uname -m) && \
|
||
if [ "$ARCH" = "aarch64" ]; then \
|
||
BUN_ASSET="bun-linux-aarch64.zip"; \
|
||
else \
|
||
BUN_ASSET="bun-linux-x64-baseline.zip"; \
|
||
fi && \
|
||
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
|
||
-o /tmp/bun.zip \
|
||
"https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${BUN_ASSET}" && \
|
||
unzip -j /tmp/bun.zip -d /usr/local/bin && \
|
||
rm /tmp/bun.zip && \
|
||
chmod +x /usr/local/bin/bun
|
||
|
||
# ==============================================================================
|
||
# Stage 2: Install - Node Dependencies
|
||
# ==============================================================================
|
||
FROM base AS install
|
||
|
||
# 開發依賴
|
||
RUN mkdir -p /temp/dev
|
||
COPY package.json bun.lock /temp/dev/
|
||
RUN cd /temp/dev && bun install --frozen-lockfile
|
||
|
||
# 生產依賴
|
||
RUN mkdir -p /temp/prod
|
||
COPY package.json bun.lock /temp/prod/
|
||
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||
|
||
# ==============================================================================
|
||
# Stage 3: Prerelease - Build App
|
||
# ==============================================================================
|
||
FROM base AS prerelease
|
||
WORKDIR /app
|
||
COPY --from=install /temp/dev/node_modules node_modules
|
||
COPY . .
|
||
RUN bun run build
|
||
|
||
# ==============================================================================
|
||
# Stage 4: System Tools(拆分為多個 RUN 以提升可調試性和 cache 效率)
|
||
# ==============================================================================
|
||
FROM base AS system-tools
|
||
|
||
# 4.1 配置 APT
|
||
RUN set -ex && \
|
||
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries && \
|
||
echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
|
||
echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
|
||
echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/80-retries && \
|
||
echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
|
||
|
||
# 4.2 基礎系統工具
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
locales ca-certificates curl wget unzip openssl git xz-utils && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.3 核心轉換工具
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
assimp-utils dcraw dvisvgm ghostscript graphicsmagick \
|
||
mupdf-tools poppler-utils potrace qpdf && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.4 dasel(JSON/YAML/TOML 轉換)
|
||
RUN set -ex && \
|
||
ARCH=$(uname -m) && \
|
||
if [ "$ARCH" = "aarch64" ]; then DASEL_ARCH="linux_arm64"; \
|
||
else DASEL_ARCH="linux_amd64"; fi && \
|
||
curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \
|
||
"https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" \
|
||
-o /usr/local/bin/dasel && \
|
||
chmod +x /usr/local/bin/dasel
|
||
|
||
# 4.5 resvg(僅 AMD64)
|
||
RUN set -ex && \
|
||
ARCH=$(uname -m) && \
|
||
if [ "$ARCH" = "aarch64" ]; then \
|
||
echo "⚠️ resvg 無 ARM64 版本,跳過"; \
|
||
else \
|
||
curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \
|
||
"https://github.com/linebender/resvg/releases/download/v0.44.0/resvg-linux-x86_64.tar.gz" \
|
||
-o /tmp/resvg.tar.gz && \
|
||
tar -xzf /tmp/resvg.tar.gz -C /tmp/ && \
|
||
mv /tmp/resvg /usr/local/bin/resvg && \
|
||
chmod +x /usr/local/bin/resvg && \
|
||
rm -rf /tmp/resvg.tar.gz; \
|
||
fi
|
||
|
||
# 4.6 deark(編譯安裝)
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends build-essential && \
|
||
cd /tmp && git clone --depth 1 https://github.com/jsummers/deark.git && \
|
||
cd deark && make -j$(nproc) && \
|
||
cp deark /usr/local/bin/deark && chmod +x /usr/local/bin/deark && \
|
||
cd / && rm -rf /tmp/deark && \
|
||
apt-get remove -y build-essential && apt-get autoremove -y && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.7 vtracer
|
||
RUN set -ex && \
|
||
ARCH=$(uname -m) && \
|
||
if [ "$ARCH" = "aarch64" ]; then \
|
||
VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \
|
||
else \
|
||
VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \
|
||
fi && \
|
||
curl -L --retry 3 --retry-delay 5 --retry-all-errors \
|
||
-o /tmp/vtracer.tar.gz \
|
||
"https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \
|
||
tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \
|
||
mv /tmp/vtracer /usr/local/bin/vtracer && \
|
||
chmod +x /usr/local/bin/vtracer && \
|
||
rm -rf /tmp/vtracer.tar.gz
|
||
|
||
# 4.8 FFmpeg
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
ffmpeg libavcodec-extra libva2 && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.9 圖像處理工具(ImageMagick, Inkscape, vips 等)
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
imagemagick inkscape libheif-examples libjxl-tools \
|
||
libvips-tools xauth xvfb && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.10 文件處理工具(Calibre, Pandoc)
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
calibre libemail-outlook-message-perl pandoc && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.11 LibreOffice
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends libreoffice && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.12 TexLive 基礎
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
texlive-base texlive-latex-base texlive-latex-recommended \
|
||
texlive-fonts-recommended texlive-xetex latexmk lmodern && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.13 TexLive 語言包
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
texlive-lang-cjk texlive-lang-german texlive-lang-french \
|
||
texlive-lang-arabic texlive-lang-other && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 4.14 Tesseract OCR
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-tra \
|
||
tesseract-ocr-chi-sim tesseract-ocr-jpn tesseract-ocr-kor \
|
||
tesseract-ocr-deu tesseract-ocr-fra ocrmypdf && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# ==============================================================================
|
||
# Stage 5: Fonts(拆分安裝)
|
||
# ==============================================================================
|
||
FROM system-tools AS fonts
|
||
|
||
# 5.1 系統字型
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-core \
|
||
fonts-noto-color-emoji fonts-liberation fonts-dejavu-core \
|
||
fonts-dejavu-extra fonts-freefont-ttf fonts-droid-fallback && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 5.2 複製自訂字型
|
||
RUN mkdir -p /usr/share/fonts/truetype/custom
|
||
COPY fonts/ /usr/share/fonts/truetype/custom/
|
||
|
||
# 5.3 設定 BabelDOC 字型目錄
|
||
RUN mkdir -p /root/.cache/babeldoc/fonts && \
|
||
for font in GoNotoKurrent-Regular.ttf SourceHanSerifCN-Regular.ttf \
|
||
SourceHanSerifTW-Regular.ttf SourceHanSerifJP-Regular.ttf \
|
||
SourceHanSerifKR-Regular.ttf BiauKai.ttf; do \
|
||
[ -f "/usr/share/fonts/truetype/custom/${font}" ] && \
|
||
cp "/usr/share/fonts/truetype/custom/${font}" /root/.cache/babeldoc/fonts/ || true; \
|
||
done
|
||
|
||
# 5.4 更新字型快取
|
||
RUN fc-cache -fv
|
||
|
||
# ==============================================================================
|
||
# Stage 6: Python Tools(拆分安裝)
|
||
# ==============================================================================
|
||
FROM fonts AS python-tools
|
||
|
||
# 6.1 Python 基礎環境
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
python3 python3-pip python3-venv python3-numpy \
|
||
python3-tinycss2 python3-opencv python3-img2pdf && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 6.2 uv 套件管理器
|
||
RUN pip3 install --no-cache-dir --break-system-packages uv
|
||
|
||
# 6.3 huggingface_hub
|
||
RUN uv pip install --system --break-system-packages --no-cache huggingface_hub
|
||
|
||
# 6.4 endesive(PDF 簽章)
|
||
RUN apt-get update --fix-missing && \
|
||
apt-get install -y --no-install-recommends \
|
||
build-essential swig libpcsclite-dev python3-dev && \
|
||
uv pip install --system --break-system-packages --no-cache endesive && \
|
||
apt-get remove -y build-essential swig python3-dev && \
|
||
apt-get autoremove -y && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 6.5 markitdown
|
||
RUN uv pip install --system --break-system-packages --no-cache "markitdown[all]"
|
||
|
||
# 6.6 pdf2zh(PDFMathTranslate)
|
||
RUN uv pip install --system --break-system-packages --no-cache pdf2zh
|
||
|
||
# 6.7 babeldoc
|
||
RUN uv pip install --system --break-system-packages --no-cache babeldoc || \
|
||
echo "⚠️ babeldoc 安裝可能有警告"
|
||
|
||
# 6.8 MinerU(僅 AMD64)
|
||
RUN set -ex && \
|
||
ARCH=$(uname -m) && \
|
||
if [ "$ARCH" = "aarch64" ]; then \
|
||
echo "⚠️ ARM64:MinerU 不支援,跳過安裝"; \
|
||
else \
|
||
uv pip install --system --break-system-packages --no-cache -U "mineru[all]"; \
|
||
fi
|
||
|
||
# 6.9 tiktoken
|
||
RUN uv pip install --system --break-system-packages --no-cache tiktoken
|
||
|
||
# 設定 PATH
|
||
ENV PATH="/root/.local/bin:/usr/local/bin:${PATH}"
|
||
|
||
# ==============================================================================
|
||
# Stage 7: Models Download(拆分下載)
|
||
# ==============================================================================
|
||
FROM python-tools AS models
|
||
|
||
# 設定模型目錄環境變數
|
||
ENV MINERU_MODELS_DIR="/opt/convertx/models/mineru"
|
||
ENV BABELDOC_CACHE_DIR="/root/.cache/babeldoc"
|
||
|
||
# 7.1 創建目錄結構
|
||
RUN mkdir -p /opt/convertx/models/mineru && \
|
||
mkdir -p /root/.cache/babeldoc/models && \
|
||
mkdir -p /root/.cache/babeldoc/fonts && \
|
||
mkdir -p /root/.cache/babeldoc/cmap && \
|
||
mkdir -p /root/.cache/babeldoc/tiktoken
|
||
|
||
# 7.2 複製預下載的 ONNX 模型
|
||
COPY models/ /root/.cache/babeldoc/models/
|
||
|
||
# 7.3 下載 MinerU Pipeline 模型(僅 AMD64)
|
||
RUN set -ex && \
|
||
ARCH=$(uname -m) && \
|
||
if [ "$ARCH" = "aarch64" ]; then \
|
||
echo "⚠️ ARM64:跳過 MinerU 模型下載"; \
|
||
else \
|
||
python3 -c " \
|
||
from huggingface_hub import snapshot_download; \
|
||
import os; \
|
||
models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru'); \
|
||
print(f'下載 PDF-Extract-Kit-1.0 到 {models_dir}...'); \
|
||
snapshot_download( \
|
||
repo_id='opendatalab/PDF-Extract-Kit-1.0', \
|
||
local_dir=f'{models_dir}/PDF-Extract-Kit-1.0', \
|
||
local_dir_use_symlinks=False, \
|
||
resume_download=True \
|
||
); \
|
||
print('✅ PDF-Extract-Kit-1.0 下載完成'); \
|
||
"; \
|
||
fi
|
||
|
||
# 7.4 產生 MinerU 配置檔
|
||
RUN set -ex && \
|
||
ARCH=$(uname -m) && \
|
||
mkdir -p /opt/convertx && \
|
||
if [ "$ARCH" = "aarch64" ]; then \
|
||
echo '{"models-dir":{"pipeline":"","vlm":""},"model-source":"local","note":"ARM64 - MinerU not supported"}' > /opt/convertx/mineru.json && \
|
||
cp /opt/convertx/mineru.json /root/mineru.json && \
|
||
echo "⚠️ ARM64:產生空的 mineru.json"; \
|
||
else \
|
||
python3 -c " \
|
||
import json; \
|
||
import os; \
|
||
mineru_models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru'); \
|
||
config = { \
|
||
'models-dir': { \
|
||
'pipeline': f'{mineru_models_dir}/PDF-Extract-Kit-1.0', \
|
||
'vlm': '' \
|
||
}, \
|
||
'model-source': 'local', \
|
||
'latex-delimiter-config': { \
|
||
'display': {'left': '@@', 'right': '@@'}, \
|
||
'inline': {'left': '@', 'right': '@'} \
|
||
} \
|
||
}; \
|
||
os.makedirs('/opt/convertx', exist_ok=True); \
|
||
with open('/opt/convertx/mineru.json', 'w') as f: \
|
||
json.dump(config, f, indent=2); \
|
||
with open('/root/mineru.json', 'w') as f: \
|
||
json.dump(config, f, indent=2); \
|
||
print('✅ mineru.json 已產生'); \
|
||
"; \
|
||
fi
|
||
|
||
# 7.5 BabelDOC warmup
|
||
RUN set -ex && \
|
||
export BABELDOC_CACHE_PATH="/root/.cache/babeldoc" && \
|
||
if command -v babeldoc >/dev/null 2>&1; then \
|
||
babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告"; \
|
||
else \
|
||
echo "⚠️ babeldoc 不可用,跳過 warmup"; \
|
||
fi
|
||
|
||
# 7.6 下載 tiktoken 編碼
|
||
RUN python3 -c " \
|
||
try: \
|
||
import tiktoken; \
|
||
for enc_name in ['cl100k_base', 'p50k_base', 'r50k_base']: \
|
||
try: \
|
||
enc = tiktoken.get_encoding(enc_name); \
|
||
print(f'✅ tiktoken {enc_name} 已下載'); \
|
||
except Exception as e: \
|
||
print(f'⚠️ tiktoken {enc_name} 下載失敗: {e}'); \
|
||
except ImportError: \
|
||
print('⚠️ tiktoken 未安裝,跳過'); \
|
||
"
|
||
|
||
# 7.7 清理下載快取
|
||
RUN rm -rf /tmp/hf_download_cache /root/.cache/huggingface \
|
||
/root/.cache/pip /root/.cache/uv && \
|
||
find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||
|
||
# ==============================================================================
|
||
# Stage 8: Final Release Image
|
||
# ==============================================================================
|
||
FROM python-tools AS release
|
||
WORKDIR /app
|
||
|
||
# 8.1 從 models stage 複製模型和配置
|
||
COPY --from=models /opt/convertx /opt/convertx
|
||
COPY --from=models /root/.cache/babeldoc /root/.cache/babeldoc
|
||
COPY --from=models /root/mineru.json /root/mineru.json
|
||
|
||
# 8.2 複製應用程式
|
||
COPY --from=install /temp/prod/node_modules node_modules
|
||
COPY --from=prerelease /app/public/ /app/public/
|
||
COPY --from=prerelease /app/dist /app/dist
|
||
|
||
# 8.3 確保字型目錄完整(fonts stage 已安裝,這裡確保 COPY 覆蓋)
|
||
RUN mkdir -p /usr/share/fonts/truetype/custom
|
||
COPY fonts/ /usr/share/fonts/truetype/custom/
|
||
COPY models/ /root/.cache/babeldoc/models/
|
||
|
||
# 8.4 更新字型快取
|
||
RUN fc-cache -fv
|
||
|
||
# ==============================================================================
|
||
# PDF 簽章憑證
|
||
# ==============================================================================
|
||
RUN mkdir -p /app/certs && \
|
||
openssl req -x509 -newkey rsa:2048 \
|
||
-keyout /tmp/key.pem -out /tmp/cert.pem \
|
||
-days 3650 -nodes \
|
||
-subj "/CN=PDF Packager Default/O=ConvertX-CN/C=TW" && \
|
||
openssl pkcs12 -export \
|
||
-inkey /tmp/key.pem -in /tmp/cert.pem \
|
||
-out /app/certs/default.p12 \
|
||
-passout pass: && \
|
||
rm -f /tmp/key.pem /tmp/cert.pem && \
|
||
chmod 644 /app/certs/default.p12
|
||
|
||
# ==============================================================================
|
||
# Locale 設定
|
||
# ==============================================================================
|
||
RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||
sed -i 's/# zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/' /etc/locale.gen && \
|
||
sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen && \
|
||
sed -i 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen && \
|
||
sed -i 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen && \
|
||
sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen && \
|
||
sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \
|
||
locale-gen
|
||
|
||
# ==============================================================================
|
||
# 最終清理
|
||
# ==============================================================================
|
||
RUN rm -rf /usr/share/doc/texlive* && \
|
||
rm -rf /usr/share/texlive/texmf-dist/doc && \
|
||
rm -rf /usr/share/doc/* && \
|
||
rm -rf /usr/share/man/* && \
|
||
rm -rf /usr/share/info/* && \
|
||
rm -rf /tmp/* && \
|
||
rm -rf /var/tmp/*
|
||
|
||
# 複製驗證腳本
|
||
COPY scripts/verify-models.sh /app/scripts/verify-models.sh
|
||
COPY scripts/verify-installation.sh /app/scripts/verify-installation.sh
|
||
RUN chmod +x /app/scripts/*.sh
|
||
|
||
# 創建資料目錄
|
||
RUN mkdir -p data
|
||
|
||
# ==============================================================================
|
||
# 🔒 Runtime 離線驗證
|
||
# ==============================================================================
|
||
RUN echo "======================================" && \
|
||
echo "🔒 Runtime 離線驗證" && \
|
||
echo "======================================" && \
|
||
ARCH=$(uname -m) && \
|
||
VALIDATION_PASSED=true && \
|
||
\
|
||
# 驗證核心工具
|
||
echo "🔍 驗證核心工具..." && \
|
||
for cmd in ffmpeg convert gm vips inkscape pandoc soffice; do \
|
||
if command -v ${cmd} >/dev/null 2>&1; then \
|
||
echo " ✅ ${cmd}: $(which ${cmd})"; \
|
||
else \
|
||
echo " ❌ ${cmd}: 未找到" && VALIDATION_PASSED=false; \
|
||
fi; \
|
||
done && \
|
||
\
|
||
# 驗證 MinerU(僅 AMD64)
|
||
echo "🔍 驗證 MinerU..." && \
|
||
if [ "$ARCH" != "aarch64" ]; then \
|
||
if command -v mineru >/dev/null 2>&1; then \
|
||
echo " ✅ mineru: $(which mineru)"; \
|
||
else \
|
||
echo " ❌ mineru 不可執行" && VALIDATION_PASSED=false; \
|
||
fi && \
|
||
if [ -d "/opt/convertx/models/mineru/PDF-Extract-Kit-1.0" ]; then \
|
||
echo " ✅ MinerU 模型目錄存在"; \
|
||
else \
|
||
echo " ❌ MinerU 模型目錄不存在" && VALIDATION_PASSED=false; \
|
||
fi && \
|
||
if [ -f "/root/mineru.json" ]; then \
|
||
echo " ✅ mineru.json 存在"; \
|
||
else \
|
||
echo " ❌ mineru.json 不存在" && VALIDATION_PASSED=false; \
|
||
fi; \
|
||
else \
|
||
echo " ⚠️ ARM64:跳過 MinerU 驗證"; \
|
||
fi && \
|
||
\
|
||
# 驗證 BabelDOC
|
||
echo "🔍 驗證 BabelDOC..." && \
|
||
if command -v babeldoc >/dev/null 2>&1; then \
|
||
echo " ✅ babeldoc: $(which babeldoc)"; \
|
||
else \
|
||
echo " ⚠️ babeldoc 不可用"; \
|
||
fi && \
|
||
\
|
||
# 驗證 pdf2zh
|
||
echo "🔍 驗證 pdf2zh..." && \
|
||
if command -v pdf2zh >/dev/null 2>&1; then \
|
||
echo " ✅ pdf2zh: $(which pdf2zh)"; \
|
||
else \
|
||
echo " ⚠️ pdf2zh 不可用"; \
|
||
fi && \
|
||
\
|
||
# 驗證 ImageMagick
|
||
echo "🔍 驗證 ImageMagick..." && \
|
||
if command -v convert >/dev/null 2>&1; then \
|
||
echo " ✅ ImageMagick: $(convert --version | head -1)"; \
|
||
else \
|
||
echo " ❌ ImageMagick 未安裝" && VALIDATION_PASSED=false; \
|
||
fi && \
|
||
\
|
||
# 驗證 ONNX 模型
|
||
echo "🔍 驗證 ONNX 模型..." && \
|
||
if [ -f "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then \
|
||
echo " ✅ DocLayout-YOLO ONNX 存在"; \
|
||
else \
|
||
echo " ⚠️ DocLayout-YOLO ONNX 不存在"; \
|
||
fi && \
|
||
\
|
||
# 驗證字型
|
||
echo "🔍 驗證字型..." && \
|
||
FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l || echo "0") && \
|
||
echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \
|
||
\
|
||
echo "======================================" && \
|
||
if [ "$VALIDATION_PASSED" = "true" ]; then \
|
||
echo "✅ 離線驗證通過!"; \
|
||
else \
|
||
echo "❌ 離線驗證失敗!" && exit 1; \
|
||
fi && \
|
||
echo "======================================"
|
||
|
||
# ==============================================================================
|
||
# 🔐 Runtime 環境變數(強制離線模式)
|
||
# ==============================================================================
|
||
|
||
# 1️⃣ 系統 Locale
|
||
ENV LANG=zh_TW.UTF-8
|
||
ENV LC_ALL=zh_TW.UTF-8
|
||
|
||
# 2️⃣ Headless 環境
|
||
ENV QT_QPA_PLATFORM="offscreen"
|
||
ENV DISPLAY=":99"
|
||
ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox"
|
||
ENV CALIBRE_USE_SYSTEM_THEME="0"
|
||
|
||
# 3️⃣ 翻譯服務設定(這是唯一允許連網的服務)
|
||
ENV PDFMATHTRANSLATE_SERVICE="google"
|
||
ENV BABELDOC_SERVICE="google"
|
||
|
||
# 4️⃣ 🔒 強制離線模式(禁止模型/資源下載)
|
||
# HuggingFace 完全離線
|
||
ENV HF_HOME="/nonexistent"
|
||
ENV HF_HUB_OFFLINE="1"
|
||
ENV TRANSFORMERS_OFFLINE="1"
|
||
ENV HF_DATASETS_OFFLINE="1"
|
||
ENV TRANSFORMERS_CACHE="/nonexistent"
|
||
|
||
# MinerU 強制本地模型
|
||
ENV MINERU_MODEL_SOURCE="local"
|
||
ENV MINERU_CONFIG="/root/mineru.json"
|
||
ENV MINERU_MODELS_DIR="/opt/convertx/models/mineru"
|
||
|
||
# BabelDOC 離線模式
|
||
ENV BABELDOC_OFFLINE="1"
|
||
ENV BABELDOC_CACHE_PATH="/root/.cache/babeldoc"
|
||
|
||
# 禁止 pip 安裝
|
||
ENV PIP_NO_INDEX="1"
|
||
ENV PIP_NO_CACHE_DIR="1"
|
||
|
||
# 5️⃣ PDF 簽章設定
|
||
ENV PDF_SIGN_P12_PATH="/app/certs/default.p12"
|
||
ENV PDF_SIGN_P12_PASSWORD=""
|
||
ENV PDF_SIGN_REASON="ConvertX-CN PDF Packager"
|
||
ENV PDF_SIGN_LOCATION="Taiwan"
|
||
ENV PDF_SIGN_CONTACT="convertx-cn@localhost"
|
||
|
||
# 6️⃣ 應用程式設定
|
||
ENV PANDOC_PDF_ENGINE=pdflatex
|
||
ENV NODE_ENV=production
|
||
|
||
# ==============================================================================
|
||
# 暴露端口 & 啟動
|
||
# ==============================================================================
|
||
EXPOSE 3000/tcp
|
||
|
||
ENTRYPOINT [ "bun", "run", "dist/src/index.js" ]
|