feat: 新增完整的 Dockerfile,優化安裝流程與依賴管理

This commit is contained in:
Your Name 2026-01-25 11:07:13 +08:00
parent 367e0d3056
commit b0d6aea234

806
Dockerfile.backup Normal file
View file

@ -0,0 +1,806 @@
# ==============================================================================
# ConvertX-CN 官方 Docker Image
# 版本v0.1.16
# ==============================================================================
#
# 📦 Image 說明:
# - 這是 ConvertX-CN 官方 Docker Hub Image 的生產 Dockerfile
# - 已內建完整功能,無需額外擴充
# - ⚠️ 所有模型已在 build 階段預下載runtime 不依賴網路
#
# 🌍 內建語言支援:
# - OCR: 英文、繁體中文、簡體中文、日文、韓文、德文、法文
# - Locale: en_US, zh_TW, zh_CN, ja_JP, ko_KR, de_DE, fr_FR
# - 字型: Noto CJK, Liberation, 標楷體
# - LaTeX: CJK、德文、法文、阿拉伯語、希伯來語
#
# 🤖 預下載模型清單:
# - PDFMathTranslate: DocLayout-YOLO ONNX佈局分析
# - BabelDOC: DocLayout-YOLO + 字型資源(顯式下載,無 warmup
# - MinerU: PDF-Extract-Kit-1.0Pipeline 模型)
# 包含DocLayout-YOLO, YOLOv8 MFD, UniMERNet, PaddleOCR, LayoutReader, SLANet
#
# 📊 Image 大小:約 8-12 GB含模型
#
# ⚠️ Base Image使用 debian:bookworm穩定版
# - 確保 Multi-Arch (amd64/arm64) 構建穩定性
# - 避免 trixie (testing) 套件同步不穩定問題
#
# 🔒 Offline-first 設計原則:
# - 所有下載行為僅發生在 Docker build 階段
# - Runtime 完全離線運行,不依賴任何網路請求
# - 禁止任何 CLI warmup / 隱性下載行為
# - 所有 cache 在同一 RUN 內清除,避免 layer diff 膨脹
#
# ==============================================================================
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 - 精簡版檔案轉換服務"
WORKDIR /app
# 設定非互動模式(避免 debconf 等待輸入導致 build 卡住)
ENV DEBIAN_FRONTEND=noninteractive
# 配置 APT 重試機制(解決 Multi-Arch Build 時的網路不穩定問題)
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
# install bun
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
unzip \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# if architecture is arm64, use the arm64 version of 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
RUN unzip -j bun-linux-*.zip -d /usr/local/bin && \
rm bun-linux-*.zip && \
chmod +x /usr/local/bin/bun
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production
FROM base AS prerelease
WORKDIR /app
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
# ENV NODE_ENV=production
RUN bun run build
# copy production dependencies and source code into final image
FROM base AS release
# ==============================================================================
# 依賴安裝(分段安裝,優化 Multi-Arch Build 穩定性)
# ==============================================================================
#
# ✅ 核心轉換工具:完整保留
# ✅ TexLive完整 CJK + 德法 + 阿拉伯/希伯來語
# ✅ OCR7 種主要語言
# ✅ 字型Noto CJK + Liberation + 標楷體
# ✅ OpenCV電腦視覺轉換支援
# ✅ 額外影片編解碼器
# ✅ PDFMathTranslatePDF 翻譯引擎
#
# 📝 分段安裝說明:
# - 將套件拆分為多個 RUN 層,避免 QEMU 模擬時記憶體不足
# - 每段安裝後清理 apt cache減少中間層大小
# - 最終 squash 時會合併為單一層
#
# ==============================================================================
# 配置 APT 重試機制(解決 Multi-Arch Build 時的網路不穩定問題)
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
# 階段 1基礎系統工具
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 1/11安裝基礎系統工具" && \
echo "========================================" && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
locales \
ca-certificates \
curl \
&& rm -rf /var/lib/apt/lists/* && \
echo "✅ 階段 1/11 完成:基礎系統工具已安裝"
# 階段 2核心轉換工具小型
# 注意dasel 和 resvg 在 bookworm 中不存在,後續用二進位檔案安裝
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 2/11安裝核心轉換工具" && \
echo "========================================" && \
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/* && \
echo "✅ 階段 2/11 完成:核心轉換工具已安裝"
# 階段 2.1:安裝 dasel從 GitHub 下載二進位檔案)
RUN echo "" && \
echo " 🔧 階段 2.1:安裝 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 && \
echo " ✅ dasel 安裝完成"
# 階段 2.2:安裝 resvg從 GitHub 下載二進位檔案)
# 注意resvg 官方只提供 x86_64 版本ARM64 需從源碼編譯或跳過
RUN echo "" && \
echo " 🔧 階段 2.2:安裝 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 && \
echo " ✅ resvg 安裝完成"; \
fi
# 階段 2.3:安裝 deark從源碼編譯
# deark 是一個用於解碼和轉換各種二進位格式的工具
# @see https://github.com/jsummers/deark
RUN echo "" && \
echo " 🔧 階段 2.3:安裝 deark..." && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
build-essential \
git \
&& 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 git && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* && \
echo " ✅ deark 安裝完成"
# 階段 3影音處理工具
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 3/11安裝影音處理工具" && \
echo "========================================" && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
ffmpeg \
libavcodec-extra \
libva2 \
&& rm -rf /var/lib/apt/lists/* && \
echo "✅ 階段 3/11 完成影音處理工具已安裝ffmpeg"
# 階段 4圖像處理工具
# 注意bookworm 使用 imagemagick版本 6trixie 才有 imagemagick-7
# 注意Inkscape 需要 xvfb 在無 DISPLAY 環境下執行某些操作(如 PNG 轉 SVG
# 注意xvfb-run 需要 xauth 才能正常運作
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 4/11安裝圖像處理工具" && \
echo "========================================" && \
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/* && \
echo "✅ 階段 4/11 完成圖像處理工具已安裝ImageMagick, Inkscape, VIPS"
# 階段 5文件處理工具
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 5/11安裝文件處理工具" && \
echo "========================================" && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
calibre \
libemail-outlook-message-perl \
pandoc \
&& rm -rf /var/lib/apt/lists/* && \
echo "✅ 階段 5/11 完成文件處理工具已安裝Calibre, Pandoc"
# 階段 6LibreOffice最大的套件單獨安裝
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 6/11安裝 LibreOffice較大需要數分鐘" && \
echo "========================================" && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
libreoffice \
&& rm -rf /var/lib/apt/lists/* && \
echo "✅ 階段 6/11 完成LibreOffice 已安裝"
# 階段 7TexLive 基礎
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 7/11安裝 TexLive 基礎(較大,需要數分鐘)" && \
echo "========================================" && \
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/* && \
echo "✅ 階段 7/11 完成TexLive 基礎已安裝"
# 階段 8TexLive 語言包
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 8/11安裝 TexLive 語言包CJK + 歐語)" && \
echo "========================================" && \
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/* && \
echo "✅ 階段 8/11 完成TexLive 語言包已安裝"
# 階段 9OCR 支援
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 9/11安裝 OCR 支援Tesseract + ocrmypdf" && \
echo "========================================" && \
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/* && \
echo "✅ 階段 9/11 完成OCR 支援已安裝7 種語言)"
# 階段 10字型
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 10/11安裝字型Noto CJK + Liberation" && \
echo "========================================" && \
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/* && \
echo "✅ 階段 10/11 完成:字型已安裝"
# 階段 11Python 依賴
RUN echo "" && \
echo "========================================" && \
echo "📦 階段 11/11安裝 Python 依賴" && \
echo "========================================" && \
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
python3 \
python3-pip \
python3-numpy \
python3-tinycss2 \
python3-opencv \
python3-img2pdf \
pipx \
&& rm -rf /var/lib/apt/lists/* && \
echo "✅ 階段 11/11 完成Python 依賴已安裝" && \
echo "" && \
echo "========================================" && \
echo "✅ 所有 APT 套件安裝完成!" && \
echo "========================================"
# ==============================================================================
# 🔐 階段 11.1PDF Packager 預設簽章憑證(開箱即用)
# ==============================================================================
#
# 產生自簽憑證供 PDF Packager 簽章功能使用
# 此憑證僅供測試/展示用途,正式環境請替換為自己的憑證
# 📚 詳細說明請參考 docs/功能說明/PDF-Packager.md
#
# ==============================================================================
RUN echo "" && \
echo "========================================" && \
echo "🔐 階段 11.1:產生 PDF Packager 預設簽章憑證" && \
echo "========================================" && \
mkdir -p /app/certs && \
# 產生自簽憑證(有效期 10 年)
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" && \
# 匯出為 PKCS12 格式(空密碼)
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 && \
echo "✅ 預設簽章憑證已產生: /app/certs/default.p12"
# ==============================================================================
# 🔥 階段 12-UNIFIEDPython 工具安裝 + 模型下載(單一 RUN 原則)
# ==============================================================================
#
# ⚠️ 關鍵設計原則:
# 1. 所有 pipx install 和模型下載必須在同一個 RUN 中完成
# 2. 所有 cache 在同一個 RUN 結尾清除
# 3. 禁止任何 CLI warmup / 隱性下載行為
# 4. 僅使用顯式 HuggingFace snapshot_download 下載模型
#
# ⬇️ 此 RUN 包含所有 Docker build 階段下載:
# - Python 工具markitdown, pdf2zh, babeldoc, mineru
# - 模型DocLayout-YOLO ONNX, MinerU Pipeline 模型
# - 字型GoNotoKurrent, Source Han Serif
# - Runtime 不會再下載任何資源
#
# ==============================================================================
ENV PATH="/root/.local/bin:${PATH}"
ENV PIPX_HOME="/root/.local/pipx"
ENV PIPX_BIN_DIR="/root/.local/bin"
# 禁止 pip 隱性下載(強制離線模式在安裝完成後啟用)
ENV PIP_NO_CACHE_DIR=1
# HuggingFace 環境變數(安裝時允許下載,安裝完成後設為離線)
ENV HF_HOME="/root/.cache/huggingface"
# ==============================================================================
# PDF Packager 簽章預設配置(開箱即用)
# ==============================================================================
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"
# ==============================================================================
# 🔥 Cache Busting強制重新執行模型下載層
# ==============================================================================
# 當 CACHE_BUST 改變時,後續所有層都會重新執行
# 這確保模型下載不會被損壞的 cache 跳過
# ==============================================================================
ARG CACHE_BUST=5
# ==============================================================================
# 階段 12A安裝 huggingface_hub + endesive
# ==============================================================================
# ⚠️ endesive 依賴 pykcs11需要 C++ 編譯器和 SWIG
RUN echo "Cache bust: ${CACHE_BUST}" && \
set -eu && \
echo "===========================================================" && \
echo "📦 [1/8] 安裝 huggingface_hub + endesivePDF 簽章)..." && \
echo "===========================================================" && \
# 安裝 pykcs11 編譯依賴
apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
swig \
libpcsclite-dev && \
pip3 install --no-cache-dir --break-system-packages huggingface_hub endesive && \
# 清理編譯依賴(保留 libpcsclite-dev runtime 需要)
apt-get remove -y build-essential swig && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* && \
echo "✅ huggingface_hub + endesive 安裝完成"
# ==============================================================================
# 階段 12B安裝 markitdown
# ==============================================================================
RUN set -eu && \
echo "===========================================================" && \
echo "📦 [2/8] 安裝 markitdown[all]..." && \
echo "===========================================================" && \
pipx install "markitdown[all]" && \
echo "✅ markitdown 安裝完成"
# ==============================================================================
# 階段 12C安裝 pdf2zh
# ==============================================================================
RUN set -eu && \
echo "===========================================================" && \
echo "📦 [3/8] 安裝 pdf2zh..." && \
echo "===========================================================" && \
pipx install "pdf2zh" && \
echo "✅ pdf2zh 安裝完成"
# ==============================================================================
# 階段 12D安裝 babeldoc
# ==============================================================================
RUN set -eu && \
echo "===========================================================" && \
echo "📦 [4/8] 安裝 babeldoc..." && \
echo "===========================================================" && \
(pipx install "babeldoc" && echo "✅ babeldoc 安裝完成") || \
echo "⚠️ babeldoc 安裝失敗,跳過..."
# ==============================================================================
# 階段 12E安裝 MinerU 必要套件,單獨 RUN
# ==============================================================================
# ⚠️ 只使用 system-level 安裝pip3 install --break-system-packages
# 移除 pipx 方式,因為 pipx 安裝到 ~/.local/bin 不在 PATH 中
# 📝 官方推薦pip install uv && uv pip install -U "mineru[all]"
# ==============================================================================
RUN set -eu && \
echo "===========================================================" && \
echo "📦 [5/8] 安裝 MinerU必要套件" && \
echo "===========================================================" && \
ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \
echo "⚠️ ARM64 架構MinerU 不支援,跳過安裝"; \
else \
echo "🔧 安裝 MinerUamd64..." && \
echo "" && \
# 先安裝 uv官方推薦的套件管理器
echo "📝 步驟 1: 安裝 uvPython 套件管理器)..." && \
pip3 install --no-cache-dir --break-system-packages uv && \
echo "✅ uv 安裝完成,版本:$(uv --version 2>/dev/null || echo '未知')" && \
echo "" && \
# 方法 1: uv pip install官方推薦
echo "[嘗試 1/3] uv pip install mineru[all]...(官方推薦)" && \
if uv pip install --system -U "mineru[all]"; then \
echo "✅ 方法 1uv pip成功"; \
else \
echo "⚠️ 方法 1 失敗,等待 15 秒後重試..." && \
sleep 15 && \
# 方法 2: uv pip install 重試
echo "[嘗試 2/3] uv pip install mineru[all](重試)..." && \
if uv pip install --system -U "mineru[all]"; then \
echo "✅ 方法 2uv pip 重試)成功"; \
else \
echo "⚠️ 方法 2 失敗,嘗試 pip3 直接安裝..." && \
# 方法 3: pip3 install最後備用
echo "[嘗試 3/3] pip3 install mineru[all]..." && \
pip3 install --no-cache-dir --break-system-packages "mineru[all]" && \
echo "✅ 方法 3pip3成功"; \
fi; \
fi && \
echo "" && \
# 驗證安裝結果
echo "📋 驗證 MinerU 安裝..." && \
echo "PATH: $PATH" && \
echo "檢查 /usr/local/bin/mineru..." && \
ls -la /usr/local/bin/mineru* 2>/dev/null || echo "(無 mineru 檔案)" && \
if command -v mineru >/dev/null 2>&1; then \
echo "✅ MinerU 安裝成功" && \
echo "路徑:$(command -v mineru)" && \
mineru --version 2>/dev/null || echo "(版本資訊不可用)"; \
else \
echo "❌ MinerU 安裝失敗" && \
echo "診斷資訊:" && \
echo " Python: $(python3 --version)" && \
echo " pip: $(pip3 --version)" && \
echo " which python3: $(which python3)" && \
python3 -c "import magic_pdf; print('magic_pdf 模組可用')" 2>/dev/null || echo " magic_pdf 模組不可用" && \
exit 1; \
fi; \
fi && \
echo "✅ MinerU 安裝步驟完成"
# ==============================================================================
# 階段 12FBabelDOC warmup
# ==============================================================================
RUN set -eu && \
echo "===========================================================" && \
echo "📥 [6/8] 下載 BabelDOC 完整資源(使用 --warmup..." && \
echo "===========================================================" && \
mkdir -p /root/.cache/babeldoc/fonts && \
mkdir -p /root/.cache/babeldoc/cmap && \
mkdir -p /root/.cache/babeldoc/tiktoken && \
if command -v babeldoc >/dev/null 2>&1; then \
echo "使用 babeldoc --warmup 下載資源..." && \
(babeldoc --warmup 2>&1 || echo "⚠️ babeldoc --warmup 執行完成(可能有警告)") && \
echo "✅ BabelDOC warmup 完成"; \
else \
echo "⚠️ babeldoc 不可用,跳過 warmup"; \
fi && \
echo "驗證 BabelDOC 資源..." && \
ls -lh /root/.cache/babeldoc/models/ 2>/dev/null || echo "(models 目錄)" && \
ls -lh /root/.cache/babeldoc/fonts/ 2>/dev/null || echo "(fonts 目錄)" && \
du -sh /root/.cache/babeldoc/ 2>/dev/null || true && \
echo "✅ BabelDOC 資源準備完成"
# ==============================================================================
# 階段 12GMinerU 模型下載
# ==============================================================================
# ⚠️ 關鍵:模型必須移動到固定目錄,不能依賴 HF cache
# 否則後續清理 HF cache 會導致 runtime 炸掉
RUN set -eu && \
echo "===========================================================" && \
echo "📥 [7/8] 下載 MinerU Pipeline 模型..." && \
echo "===========================================================" && \
ARCH=$(uname -m) && \
MINERU_MODELS_DIR="/opt/mineru/models" && \
if [ "$ARCH" = "aarch64" ]; then \
echo "⚠️ ARM64 架構MinerU 不支援,跳過模型下載"; \
else \
mkdir -p "$MINERU_MODELS_DIR" && \
echo "目標目錄:$MINERU_MODELS_DIR" && \
# 嘗試使用 mineru-models-download CLI
if command -v mineru-models-download >/dev/null 2>&1; then \
echo "使用 mineru-models-download CLI..." && \
mineru-models-download -s huggingface -m pipeline && \
# mineru-models-download 會建立 /root/mineru.json 並下載到 HF cache
# 需要讀取 mineru.json 找到模型位置並移動
if [ -f /root/mineru.json ]; then \
DOWNLOADED_DIR=$(python3 -c "import json; f=open('/root/mineru.json'); d=json.load(f); print(d.get('models-dir',{}).get('pipeline',''))" 2>/dev/null || echo "") && \
if [ -n "$DOWNLOADED_DIR" ] && [ -d "$DOWNLOADED_DIR" ]; then \
echo "移動模型從 $DOWNLOADED_DIR 到 $MINERU_MODELS_DIR/PDF-Extract-Kit-1.0" && \
mv "$DOWNLOADED_DIR" "$MINERU_MODELS_DIR/PDF-Extract-Kit-1.0"; \
fi; \
fi; \
else \
echo "mineru-models-download 不可用,使用顯式 HuggingFace 下載..." && \
python3 -c "from huggingface_hub import snapshot_download; snapshot_download(repo_id='opendatalab/PDF-Extract-Kit-1.0', local_dir='$MINERU_MODELS_DIR/PDF-Extract-Kit-1.0', local_dir_use_symlinks=False); print('PDF-Extract-Kit-1.0 downloaded')"; \
fi && \
# 產生/更新 mineru.json 指向固定目錄
python3 -c "import json; config={'models-dir':{'pipeline':'$MINERU_MODELS_DIR/PDF-Extract-Kit-1.0','vlm':''},'model-source':'local','latex-delimiter-config':{'display':{'left':'@@','right':'@@'},'inline':{'left':'@','right':'@'}}}; f=open('/root/mineru.json','w'); json.dump(config,f,indent=2); f.close(); print('mineru.json updated')" && \
echo "驗證模型目錄:" && \
ls -la "$MINERU_MODELS_DIR/" && \
du -sh "$MINERU_MODELS_DIR/PDF-Extract-Kit-1.0" 2>/dev/null || echo "(無法計算大小)"; \
fi && \
echo "✅ MinerU 模型下載步驟完成"
# ==============================================================================
# 階段 12HCache 清理 + 最終驗證
# ==============================================================================
RUN set -eu && \
echo "===========================================================" && \
echo "🧹 [8/8] 清理 Cache + 最終驗證" && \
echo "===========================================================" && \
\
# Cache 清理
rm -rf /root/.cache/huggingface && \
rm -rf /root/.cache/pip && \
rm -rf /root/.cache/uv && \
rm -rf /root/.local/pipx/.cache && \
rm -rf /tmp/* && \
rm -rf /var/tmp/* && \
find /root/.local -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \
find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \
echo "✅ Cache 清理完成" && \
echo "" && \
\
# 模型驗證
echo "===========================================================" && \
echo "🔒 嚴格模型驗證(確保開箱即用)" && \
echo "===========================================================" && \
VALIDATION_FAILED=0 && \
ARCH=$(uname -m) && \
\
echo "🔍 驗證 BabelDOC ONNX 模型..." && \
echo " ⏭️ 跳過驗證(模型將透過 COPY models/ 指令複製)" && \
\
echo "🔍 驗證 PDFMathTranslate 字型..." && \
echo " ⏭️ 跳過驗證(字型將透過 COPY fonts/ 指令複製)" && \
\
echo "🔍 驗證 MinerU 安裝..." && \
MINERU_MODELS_DIR="/opt/mineru/models" && \
if [ "$ARCH" = "aarch64" ]; then \
echo " ⚠️ ARM64 架構:跳過 MinerU 驗證"; \
elif command -v mineru >/dev/null 2>&1; then \
echo " ✅ MinerU 已安裝: $(command -v mineru)" && \
if [ -d "$MINERU_MODELS_DIR/PDF-Extract-Kit-1.0" ]; then \
MINERU_SIZE=$(du -sb "$MINERU_MODELS_DIR/PDF-Extract-Kit-1.0" 2>/dev/null | cut -f1 || echo "0") && \
echo " ✅ MinerU 模型存在於固定目錄 ($((MINERU_SIZE/1024/1024)) MB)"; \
else \
echo " ⚠️ MinerU 模型目錄不存在(模型將在首次使用時下載)"; \
fi; \
else \
echo " ❌ MinerU 未安裝amd64 必須安裝)" && \
VALIDATION_FAILED=1; \
fi && \
\
echo "" && \
if [ "$VALIDATION_FAILED" -eq 1 ]; then \
echo "❌ 模型驗證失敗Image 不應發布。" && \
exit 1; \
else \
echo "✅ 所有必要模型驗證通過!"; \
fi && \
echo "" && \
echo "===========================================================" && \
echo "✅ 階段 12 完成:所有 Python 工具 + 模型已安裝" && \
echo "==========================================================="
# ==============================================================================
# 最終清理(模型下載完成後)
# ==============================================================================
# ⚠️ 此清理步驟獨立於模型下載 RUN僅清理文件檔案
# 模型相關 cache 已在上一個 RUN 中清除
# ==============================================================================
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/*
# ==============================================================================
# 設定 locale支援中文 PDF 避免亂碼)
# ==============================================================================
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
# ==============================================================================
# 安裝自訂字型(標楷體 + PDFMathTranslate 多語言字型)
# ==============================================================================
# ✅ fonts/ 目錄包含:
# - BiauKai.ttf標楷體
# - GoNotoKurrent-Regular.ttf通用 Noto 字型)
# - SourceHanSerifCN-Regular.ttf簡體中文
# - SourceHanSerifTW-Regular.ttf繁體中文
# - SourceHanSerifJP-Regular.ttf日文
# - SourceHanSerifKR-Regular.ttf韓文
# ==============================================================================
RUN mkdir -p /usr/share/fonts/truetype/custom
COPY fonts/ /usr/share/fonts/truetype/custom/
# ==============================================================================
# 複製預下載的 ONNX 模型DocLayout-YOLO
# ==============================================================================
# ✅ models/ 目錄包含:
# - doclayout_yolo_docstructbench_imgsz1024.onnx (72MB)
# ==============================================================================
RUN mkdir -p /root/.cache/babeldoc/models
COPY models/ /root/.cache/babeldoc/models/
# 複製字型到 BabelDOC 目錄(供 PDFMathTranslate/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 && \
echo "✅ 字型已複製到 BabelDOC 目錄" && \
ls -lh /root/.cache/babeldoc/fonts/ && \
echo "✅ ONNX 模型已複製到 BabelDOC 目錄" && \
ls -lh /root/.cache/babeldoc/models/
RUN fc-cache -fv
# ==============================================================================
# Install VTracer binary向量追蹤工具
# ==============================================================================
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
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /app/public/ /app/public/
COPY --from=prerelease /app/dist /app/dist
# 複製模型驗證腳本
COPY scripts/verify-models.sh /app/scripts/verify-models.sh
RUN chmod +x /app/scripts/verify-models.sh
RUN mkdir data
EXPOSE 3000/tcp
# ==============================================================================
# 🔧 環境變數總覽
# ==============================================================================
#
# 📂 分類說明:
# 1. 系統路徑與 Locale
# 2. Headless 環境GUI 工具支援)
# 3. 翻譯服務設定
# 4. Runtime 離線模式
# 5. 應用程式設定
#
# ==============================================================================
# ------------------------------------------------------------------------------
# 1⃣ 系統 Locale支援中文 PDF 避免亂碼)
# ------------------------------------------------------------------------------
ENV LANG=zh_TW.UTF-8
ENV LC_ALL=zh_TW.UTF-8
# ------------------------------------------------------------------------------
# 2⃣ Headless 環境設定
# ------------------------------------------------------------------------------
# ⚠️ 解決 GTK/Qt 在無 DISPLAY 環境的問題
# 某些 GUI 工具Inkscape、Calibre、LibreOffice需要這些設定
# ------------------------------------------------------------------------------
# Qt 離屏模式
ENV QT_QPA_PLATFORM="offscreen"
# 虛擬 display配合 xvfb-run 使用)
ENV DISPLAY=":99"
# Calibre/Qt WebEngine 需要(禁用 Chromium sandboxDocker 環境無法使用)
ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox"
ENV CALIBRE_USE_SYSTEM_THEME="0"
# ------------------------------------------------------------------------------
# 3⃣ 翻譯服務設定
# ------------------------------------------------------------------------------
# PDFMathTranslate / BabelDOC 預設翻譯服務
# 支援google, bing, deepl, ollama
# ⚠️ google/bing/deepl 需要網路連接
# ollama 可完全離線(需設定 OLLAMA_HOST
# ------------------------------------------------------------------------------
ENV PDFMATHTRANSLATE_SERVICE="google"
ENV BABELDOC_SERVICE="google"
# Ollama 設定(若使用本地 LLM
# ENV OLLAMA_HOST="http://localhost:11434"
# ------------------------------------------------------------------------------
# 4⃣ Runtime 離線模式(禁止模型下載)
# ------------------------------------------------------------------------------
# ⚠️ 這些設定禁止 runtime 下載「模型」,但不影響翻譯 API 調用
# ------------------------------------------------------------------------------
# HuggingFace 離線模式
ENV HF_HUB_OFFLINE="1"
ENV TRANSFORMERS_OFFLINE="1"
ENV HF_DATASETS_OFFLINE="1"
# BabelDOC 模型離線模式
ENV BABELDOC_OFFLINE="1"
ENV BABELDOC_CACHE_PATH="/root/.cache/babeldoc"
# MinerU 強制使用本地模型
ENV MINERU_MODEL_SOURCE="local"
# 禁止 pip 安裝新套件
ENV PIP_NO_INDEX="1"
# ------------------------------------------------------------------------------
# 5⃣ 應用程式設定
# ------------------------------------------------------------------------------
# Pandoc PDF 引擎(使用 pdflatex 以獲得最佳相容性)
ENV PANDOC_PDF_ENGINE=pdflatex
# Node 環境
ENV NODE_ENV=production
ENTRYPOINT [ "bun", "run", "dist/src/index.js" ]