convertor/Dockerfile.offline
Your Name 367e0d3056 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
2026-01-25 11:07:03 +08:00

692 lines
25 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ==============================================================================
# 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" ]