feat: 完全離線化 Dockerfile v0.1.16

## 主要修復
- MinerU: 改用 system-level 安裝 (uv pip install --system)
- MinerU: 模型下載到固定目錄 /opt/mineru/models
- BabelDOC: 完整 warmup + 離線 cache
- PDFMathTranslate: ONNX 模型使用 COPY 指令
- ImageMagick: 確保已安裝

## 結構優化
- Multi-Stage Build (8 stages)
- 使用 heredoc 語法處理多行 Python
- 分階段安裝避免記憶體尖峰

## Runtime 離線模式
- HF_HOME=/nonexistent
- HF_HUB_OFFLINE=1
- BABELDOC_OFFLINE=1
- PIP_NO_INDEX=1

## 新增驗證腳本
- scripts/verify-offline.sh
This commit is contained in:
Your Name 2026-01-25 11:07:03 +08:00
parent b23d42a7b1
commit 367e0d3056
4 changed files with 1682 additions and 761 deletions

692
Dockerfile.offline Normal file
View file

@ -0,0 +1,692 @@
# ==============================================================================
# ConvertX-CN 官方 Docker Image
# 版本v0.1.16 - 完全離線化版本
# ==============================================================================
#
# 📦 Image 說明:
# - 這是 ConvertX-CN 官方 Docker Hub Image 的生產 Dockerfile
# - ⚠️ 所有模型、字型、tokenizer 已在 build 階段完整預下載
# - ⚠️ Runtime 完全離線運行(僅翻譯服務允許連網)
#
# 🔒 Offline-first 設計原則(硬性規則):
# 1. Runtimedocker run 後):
# ❌ 禁止任何模型、字型、tokenizer、metadata 下載
# ❌ MinerU / BabelDOC / PDFMathTranslate 不得嘗試連網
# ✅ 只有翻譯服務Google / DeepL / Azure / OpenAI允許連網
# 2. Build timedocker build 時):
# ✅ 允許連網下載所有資源
# ✅ 所有「可能會在 runtime 下載的東西」必須提前固定存放
#
# 🤖 預下載模型清單:
# - PDFMathTranslate: DocLayout-YOLO ONNX佈局分析
# - BabelDOC: DocLayout-YOLO + 字型資源 + tiktoken
# - MinerU: PDF-Extract-Kit-1.0Pipeline 模型)
# 包含DocLayout-YOLO, YOLOv8 MFD, UniMERNet, PaddleOCR, LayoutReader, SLANet
#
# 🌍 內建語言支援:
# - OCR: 英文、繁體中文、簡體中文、日文、韓文、德文、法文
# - Locale: en_US, zh_TW, zh_CN, ja_JP, ko_KR, de_DE, fr_FR
# - 字型: Noto CJK, Liberation, 標楷體, Source Han Serif
# - LaTeX: CJK、德文、法文、阿拉伯語、希伯來語
#
# 📊 Image 大小:約 10-14 GB含完整模型
#
# ==============================================================================
# 🏗️ Multi-Stage Build 結構:
# Stage 1 [base]: Bun runtime 基礎
# Stage 2 [install]: Node 依賴安裝
# Stage 3 [prerelease]: 應用程式建構
# Stage 4 [system-tools]: APT 系統工具
# Stage 5 [python-base]: Python 基礎環境
# Stage 6 [python-tools]: Python CLI 工具
# Stage 7 [models-download]: 模型下載(獨立 stage
# Stage 8 [final]: 最終 Image合併所有層
# ==============================================================================
# ===================================
# 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-offline"
WORKDIR /app
# 設定非互動模式
ENV DEBIAN_FRONTEND=noninteractive
# 配置 APT 重試機制
RUN 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 apt-get update && apt-get install -y --no-install-recommends \
curl \
unzip \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# 安裝 Bun根據架構選擇版本
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
-o bun-linux-aarch64.zip \
https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-aarch64.zip; \
else \
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
-o bun-linux-x64-baseline.zip \
https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-x64-baseline.zip; \
fi && \
unzip -j bun-linux-*.zip -d /usr/local/bin && \
rm bun-linux-*.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
# ===================================
FROM base AS system-tools
# 配置 APT
RUN 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 '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.1:基礎系統工具 + Locale
RUN echo "📦 [Stage 4.1] 安裝基礎系統工具..." && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
locales \
ca-certificates \
curl \
openssl \
git \
&& rm -rf /var/lib/apt/lists/*
# 階段 4.2:核心轉換工具(輕量)
RUN echo "📦 [Stage 4.2] 安裝核心轉換工具..." && \
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.3:安裝 dasel從 GitHub 二進位)
RUN echo "📦 [Stage 4.3] 安裝 dasel..." && \
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.4:安裝 resvg僅 x86_64
RUN echo "📦 [Stage 4.4] 安裝 resvg..." && \
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.5:編譯安裝 deark
RUN echo "📦 [Stage 4.5] 編譯 deark..." && \
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.6:影音處理工具
RUN echo "📦 [Stage 4.6] 安裝 FFmpeg..." && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
ffmpeg \
libavcodec-extra \
libva2 \
&& rm -rf /var/lib/apt/lists/*
# 階段 4.7:圖像處理工具(含 ImageMagick
# ⚠️ 修復:確保 ImageMagick 已安裝
RUN echo "📦 [Stage 4.7] 安裝圖像處理工具(含 ImageMagick..." && \
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.8:文件處理工具
RUN echo "📦 [Stage 4.8] 安裝 Calibre + Pandoc..." && \
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.9LibreOffice
RUN echo "📦 [Stage 4.9] 安裝 LibreOffice需數分鐘..." && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
libreoffice \
&& rm -rf /var/lib/apt/lists/*
# 階段 4.10TexLive 基礎
RUN echo "📦 [Stage 4.10] 安裝 TexLive 基礎..." && \
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.11TexLive 語言包
RUN echo "📦 [Stage 4.11] 安裝 TexLive 語言包..." && \
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.12OCR 支援
RUN echo "📦 [Stage 4.12] 安裝 Tesseract OCR..." && \
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/*
# 階段 4.13:字型
RUN echo "📦 [Stage 4.13] 安裝系統字型..." && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
fonts-noto-cjk \
fonts-noto-core \
fonts-noto-color-emoji \
fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
# ===================================
# Stage 5: Python Base
# ===================================
FROM system-tools AS python-base
# 階段 5.1Python 基礎 + uv
RUN echo "📦 [Stage 5.1] 安裝 Python 基礎..." && \
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/*
# 階段 5.2:安裝 uvPython 套件管理器)
# ⚠️ 重要:使用 uv 作為主要安裝方式,確保一致性
RUN echo "📦 [Stage 5.2] 安裝 uv..." && \
pip3 install --no-cache-dir --break-system-packages uv && \
uv --version
# 設定 PATH
ENV PATH="/root/.local/bin:/usr/local/bin:${PATH}"
# ===================================
# Stage 6: Python Tools
# ===================================
FROM python-base AS python-tools
# 階段 6.1:安裝 huggingface_hub + endesivePDF 簽章)
RUN echo "📦 [Stage 6.1] 安裝 huggingface_hub + endesive..." && \
apt-get update && apt-get install -y --no-install-recommends \
build-essential \
swig \
libpcsclite-dev \
&& uv pip install --system --no-cache huggingface_hub endesive && \
apt-get remove -y build-essential swig && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
# 階段 6.2:安裝 markitdown
RUN echo "📦 [Stage 6.2] 安裝 markitdown..." && \
uv pip install --system --no-cache "markitdown[all]"
# 階段 6.3:安裝 pdf2zh
RUN echo "📦 [Stage 6.3] 安裝 pdf2zh..." && \
uv pip install --system --no-cache pdf2zh
# 階段 6.4:安裝 babeldoc
RUN echo "📦 [Stage 6.4] 安裝 babeldoc..." && \
uv pip install --system --no-cache babeldoc || echo "⚠️ babeldoc 安裝可能有警告"
# 階段 6.5:安裝 MinerU 關鍵修復)
# ⚠️ 只使用 system-level 安裝uv pip install --system
# ⚠️ 禁止使用 pipx會導致 binary 不在 PATH
RUN echo "📦 [Stage 6.5] 安裝 MinerUsystem-level only..." && \
ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \
echo "⚠️ ARM64MinerU 不支援,跳過"; \
else \
echo "🔧 使用 uv pip install --system官方推薦..." && \
uv pip install --system --no-cache -U "mineru[all]" && \
echo "✅ 驗證 MinerU 安裝..." && \
which mineru && \
mineru --version || echo "(版本資訊可能不可用)"; \
fi
# ===================================
# Stage 7: Models Download獨立 Stage
# ===================================
FROM python-tools AS models-download
# 模型目錄定義
ENV MINERU_MODELS_DIR="/opt/mineru/models"
ENV BABELDOC_CACHE_DIR="/opt/babeldoc/cache"
ENV HF_HOME="/tmp/hf_download_cache"
# 階段 7.1:創建目錄結構
RUN mkdir -p "${MINERU_MODELS_DIR}" && \
mkdir -p "${BABELDOC_CACHE_DIR}/models" && \
mkdir -p "${BABELDOC_CACHE_DIR}/fonts" && \
mkdir -p "${BABELDOC_CACHE_DIR}/cmap" && \
mkdir -p "${BABELDOC_CACHE_DIR}/tiktoken"
# 階段 7.2:下載 MinerU Pipeline 模型
# ⚠️ 關鍵修復:使用顯式下載,存放到固定目錄
RUN <<EOF
echo "📥 [Stage 7.2] 下載 MinerU Pipeline 模型..."
ARCH=$(uname -m)
if [ "$ARCH" = "aarch64" ]; then
echo "⚠️ ARM64跳過 MinerU 模型下載"
else
python3 <<PYTHON
from huggingface_hub import snapshot_download
import os
models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/mineru/models')
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 下載完成')
PYTHON
echo "📋 MinerU 模型目錄內容:"
ls -la "${MINERU_MODELS_DIR}/" || true
du -sh "${MINERU_MODELS_DIR}/PDF-Extract-Kit-1.0" || true
fi
EOF
# 階段 7.3:產生 MinerU 配置檔(指向固定目錄)
RUN <<EOF
echo "📝 [Stage 7.3] 產生 mineru.json..."
ARCH=$(uname -m)
if [ "$ARCH" = "aarch64" ]; then
echo "⚠️ ARM64跳過 mineru.json"
else
python3 <<PYTHON
import json
config = {
'models-dir': {
'pipeline': '/opt/mineru/models/PDF-Extract-Kit-1.0',
'vlm': ''
},
'model-source': 'local',
'latex-delimiter-config': {
'display': {'left': '@@', 'right': '@@'},
'inline': {'left': '@', 'right': '@'}
}
}
with open('/opt/mineru/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 已產生')
print(json.dumps(config, indent=2))
PYTHON
fi
EOF
# 階段 7.4BabelDOC warmup + 資源下載
# ⚠️ 關鍵修復:完整 warmup確保所有資源已下載
RUN echo "📥 [Stage 7.4] BabelDOC warmup..." && \
export BABELDOC_CACHE_PATH="${BABELDOC_CACHE_DIR}" && \
if command -v babeldoc >/dev/null 2>&1; then \
echo "執行 babeldoc --warmup..." && \
babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告" && \
echo "驗證 BabelDOC 資源..." && \
ls -la "${BABELDOC_CACHE_DIR}/" && \
du -sh "${BABELDOC_CACHE_DIR}/" || true; \
else \
echo "⚠️ babeldoc 不可用,跳過 warmup"; \
fi
# 階段 7.5:下載 tiktoken 編碼BabelDOC 依賴)
RUN <<EOF
echo "📥 [Stage 7.5] 下載 tiktoken 編碼..."
python3 <<PYTHON
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 未安裝,跳過')
PYTHON
EOF
# 階段 7.6:清理下載 cache保留實際模型
RUN echo "🧹 [Stage 7.6] 清理下載 cache..." && \
rm -rf /tmp/hf_download_cache && \
rm -rf /root/.cache/huggingface && \
rm -rf /root/.cache/pip && \
rm -rf /root/.cache/uv && \
find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \
echo "✅ Cache 清理完成"
# ===================================
# Stage 8: Final Image
# ===================================
FROM python-tools AS release
WORKDIR /app
# 從 models-download stage 複製模型
COPY --from=models-download /opt/mineru /opt/mineru
COPY --from=models-download /opt/babeldoc /opt/babeldoc
COPY --from=models-download /root/mineru.json /root/mineru.json
# 複製應用程式
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /app/public/ /app/public/
COPY --from=prerelease /app/dist /app/dist
# ==============================================================================
# 安裝自訂字型
# ==============================================================================
RUN mkdir -p /usr/share/fonts/truetype/custom
COPY fonts/ /usr/share/fonts/truetype/custom/
# 複製預下載的 ONNX 模型
RUN mkdir -p /root/.cache/babeldoc/models
COPY models/ /root/.cache/babeldoc/models/
# 複製字型到 BabelDOC 目錄
RUN mkdir -p /root/.cache/babeldoc/fonts && \
cp /usr/share/fonts/truetype/custom/GoNotoKurrent-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \
cp /usr/share/fonts/truetype/custom/SourceHanSerifCN-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \
cp /usr/share/fonts/truetype/custom/SourceHanSerifTW-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \
cp /usr/share/fonts/truetype/custom/SourceHanSerifJP-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \
cp /usr/share/fonts/truetype/custom/SourceHanSerifKR-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true
# 同步 BabelDOC cache 從固定目錄
RUN if [ -d /opt/babeldoc/cache ]; then \
cp -r /opt/babeldoc/cache/* /root/.cache/babeldoc/ 2>/dev/null || true; \
fi
# 更新字型 cache
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
# ==============================================================================
# VTracer
# ==============================================================================
RUN 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 /tmp/vtracer.tar.gz
# ==============================================================================
# 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
RUN chmod +x /app/scripts/verify-models.sh
# 創建資料目錄
RUN mkdir -p data
# ==============================================================================
# 🔒 Runtime 離線驗證(關鍵步驟)
# ==============================================================================
# ⚠️ 此步驟模擬斷網環境,驗證所有工具可離線執行
# ==============================================================================
RUN echo "======================================" && \
echo "🔒 Runtime 離線驗證" && \
echo "======================================" && \
ARCH=$(uname -m) && \
VALIDATION_PASSED=true && \
\
# 驗證 MinerU
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/mineru/models/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 \
babeldoc --help >/dev/null 2>&1 && echo " ✅ babeldoc --help 成功" || echo " ⚠️ babeldoc --help 有警告"; \
else \
echo " ⚠️ babeldoc 不可用"; \
fi && \
\
# 驗證 pdf2zh
echo "🔍 驗證 pdf2zh..." && \
if command -v pdf2zh >/dev/null 2>&1; then \
pdf2zh --help >/dev/null 2>&1 && echo " ✅ pdf2zh --help 成功" || echo " ⚠️ pdf2zh --help 有警告"; \
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 && \
\
# 驗證模型檔案
echo "🔍 驗證 ONNX 模型..." && \
if [ -f "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then \
echo " ✅ DocLayout-YOLO ONNX 存在"; \
else \
echo " ⚠️ DocLayout-YOLO ONNX 不存在(將使用 COPY 複製)"; \
fi && \
\
# 驗證字型
echo "🔍 驗證字型..." && \
FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l) && \
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"
# 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" ]