fix: 調整 Dockerfile 中 APT 重試機制與安裝步驟,簡化命令格式

This commit is contained in:
Your Name 2026-01-22 22:18:41 +08:00
parent a9867ee97c
commit 566ee48bfe

View file

@ -42,29 +42,29 @@ WORKDIR /app
# 配置 APT 重試機制(解決 Multi-Arch Build 時的網路不穩定問題) # 配置 APT 重試機制(解決 Multi-Arch Build 時的網路不穩定問題)
RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \ 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::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
&& echo 'Acquire::https::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 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
&& echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
# install bun # install bun
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
curl \ curl \
unzip \ unzip \
ca-certificates \ ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# if architecture is arm64, use the arm64 version of bun # if architecture is arm64, use the arm64 version of bun
RUN ARCH=$(uname -m) && \ RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
curl -fsSL -o bun-linux-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-aarch64.zip; \ curl -fsSL -o bun-linux-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-aarch64.zip; \
else \ else \
curl -fsSL -o bun-linux-x64-baseline.zip https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-x64-baseline.zip; \ curl -fsSL -o bun-linux-x64-baseline.zip https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-x64-baseline.zip; \
fi fi
RUN unzip -j bun-linux-*.zip -d /usr/local/bin && \ RUN unzip -j bun-linux-*.zip -d /usr/local/bin && \
rm bun-linux-*.zip && \ rm bun-linux-*.zip && \
chmod +x /usr/local/bin/bun chmod +x /usr/local/bin/bun
# install dependencies into temp directory # install dependencies into temp directory
# this will cache them and speed up future builds # this will cache them and speed up future builds
@ -110,135 +110,135 @@ FROM base AS release
# 配置 APT 重試機制(解決 Multi-Arch Build 時的網路不穩定問題) # 配置 APT 重試機制(解決 Multi-Arch Build 時的網路不穩定問題)
RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \ 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::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
&& echo 'Acquire::https::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 '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 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/80-retries \
&& echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
# 階段 1基礎系統工具 # 階段 1基礎系統工具
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
locales \ locales \
ca-certificates \ ca-certificates \
curl \ curl \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 2核心轉換工具小型 # 階段 2核心轉換工具小型
# 注意dasel 和 resvg 在 bookworm 中不存在,後續用二進位檔案安裝 # 注意dasel 和 resvg 在 bookworm 中不存在,後續用二進位檔案安裝
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
assimp-utils \ assimp-utils \
dcraw \ dcraw \
dvisvgm \ dvisvgm \
ghostscript \ ghostscript \
graphicsmagick \ graphicsmagick \
mupdf-tools \ mupdf-tools \
poppler-utils \ poppler-utils \
potrace \ potrace \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 2.1:安裝 dasel從 GitHub 下載二進位檔案) # 階段 2.1:安裝 dasel從 GitHub 下載二進位檔案)
# ⬇️ Docker build 階段下載runtime 不會再下載 # ⬇️ Docker build 階段下載runtime 不會再下載
RUN ARCH=$(uname -m) && \ RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
DASEL_ARCH="linux_arm64"; \ DASEL_ARCH="linux_arm64"; \
else \ else \
DASEL_ARCH="linux_amd64"; \ DASEL_ARCH="linux_amd64"; \
fi && \ fi && \
curl -sSLf "https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" -o /usr/local/bin/dasel && \ curl -sSLf "https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" -o /usr/local/bin/dasel && \
chmod +x /usr/local/bin/dasel chmod +x /usr/local/bin/dasel
# 階段 2.2:安裝 resvg從 GitHub 下載二進位檔案) # 階段 2.2:安裝 resvg從 GitHub 下載二進位檔案)
# 注意resvg 官方只提供 x86_64 版本ARM64 需從源碼編譯或跳過 # 注意resvg 官方只提供 x86_64 版本ARM64 需從源碼編譯或跳過
# ⬇️ Docker build 階段下載runtime 不會再下載 # ⬇️ Docker build 階段下載runtime 不會再下載
RUN ARCH=$(uname -m) && \ RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
echo "⚠️ resvg 沒有 ARM64 預編譯版本,跳過安裝(可改用 ImageMagick 或 Inkscape 替代)"; \ echo "⚠️ resvg 沒有 ARM64 預編譯版本,跳過安裝(可改用 ImageMagick 或 Inkscape 替代)"; \
else \ else \
curl -sSLf "https://github.com/linebender/resvg/releases/download/v0.44.0/resvg-linux-x86_64.tar.gz" -o /tmp/resvg.tar.gz && \ curl -sSLf "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/ && \ tar -xzf /tmp/resvg.tar.gz -C /tmp/ && \
mv /tmp/resvg /usr/local/bin/resvg && \ mv /tmp/resvg /usr/local/bin/resvg && \
chmod +x /usr/local/bin/resvg && \ chmod +x /usr/local/bin/resvg && \
rm -rf /tmp/resvg.tar.gz; \ rm -rf /tmp/resvg.tar.gz; \
fi fi
# 階段 3影音處理工具 # 階段 3影音處理工具
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
ffmpeg \ ffmpeg \
libavcodec-extra \ libavcodec-extra \
libva2 \ libva2 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 4圖像處理工具 # 階段 4圖像處理工具
# 注意bookworm 使用 imagemagick版本 6trixie 才有 imagemagick-7 # 注意bookworm 使用 imagemagick版本 6trixie 才有 imagemagick-7
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
imagemagick \ imagemagick \
inkscape \ inkscape \
libheif-examples \ libheif-examples \
libjxl-tools \ libjxl-tools \
libvips-tools \ libvips-tools \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 5文件處理工具 # 階段 5文件處理工具
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
calibre \ calibre \
libemail-outlook-message-perl \ libemail-outlook-message-perl \
pandoc \ pandoc \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 6LibreOffice最大的套件單獨安裝 # 階段 6LibreOffice最大的套件單獨安裝
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
libreoffice \ libreoffice \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 7TexLive 基礎 # 階段 7TexLive 基礎
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
texlive-base \ texlive-base \
texlive-latex-base \ texlive-latex-base \
texlive-latex-recommended \ texlive-latex-recommended \
texlive-fonts-recommended \ texlive-fonts-recommended \
texlive-xetex \ texlive-xetex \
latexmk \ latexmk \
lmodern \ lmodern \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 8TexLive 語言包 # 階段 8TexLive 語言包
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
texlive-lang-cjk \ texlive-lang-cjk \
texlive-lang-german \ texlive-lang-german \
texlive-lang-french \ texlive-lang-french \
texlive-lang-arabic \ texlive-lang-arabic \
texlive-lang-other \ texlive-lang-other \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 9OCR 支援 # 階段 9OCR 支援
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
tesseract-ocr \ tesseract-ocr \
tesseract-ocr-eng \ tesseract-ocr-eng \
tesseract-ocr-chi-tra \ tesseract-ocr-chi-tra \
tesseract-ocr-chi-sim \ tesseract-ocr-chi-sim \
tesseract-ocr-jpn \ tesseract-ocr-jpn \
tesseract-ocr-kor \ tesseract-ocr-kor \
tesseract-ocr-deu \ tesseract-ocr-deu \
tesseract-ocr-fra \ tesseract-ocr-fra \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 10字型 # 階段 10字型
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
fonts-noto-cjk \ fonts-noto-cjk \
fonts-noto-core \ fonts-noto-core \
fonts-noto-color-emoji \ fonts-noto-color-emoji \
fonts-liberation \ fonts-liberation \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 階段 11Python 依賴 # 階段 11Python 依賴
RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
python3 \ python3 \
python3-pip \ python3-pip \
python3-numpy \ python3-numpy \
python3-tinycss2 \ python3-tinycss2 \
python3-opencv \ python3-opencv \
pipx \ pipx \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# ============================================================================== # ==============================================================================
# 🔧 Python 工具安裝策略 # 🔧 Python 工具安裝策略
@ -256,32 +256,32 @@ RUN apt-get update --fix-missing && apt-get install -y --no-install-recommends \
# ⬇️ 此步驟只安裝 huggingface_hub 用於後續模型下載 # ⬇️ 此步驟只安裝 huggingface_hub 用於後續模型下載
# runtime 不會再使用此套件下載任何資源 # runtime 不會再使用此套件下載任何資源
RUN pip3 install --no-cache-dir --break-system-packages huggingface_hub \ RUN pip3 install --no-cache-dir --break-system-packages huggingface_hub \
&& rm -rf /root/.cache/pip /tmp/* && rm -rf /root/.cache/pip /tmp/*
# 階段 12-A安裝 markitdown文件轉換工具 # 階段 12-A安裝 markitdown文件轉換工具
# 注意markitdown[all] 可能依賴 transformers但不會在 import 時下載模型 # 注意markitdown[all] 可能依賴 transformers但不會在 import 時下載模型
# ⬇️ 此步驟為 Docker build 階段安裝runtime 不會再下載 # ⬇️ 此步驟為 Docker build 階段安裝runtime 不會再下載
RUN pipx install "markitdown[all]" \ RUN pipx install "markitdown[all]" \
&& rm -rf /root/.cache/pip /root/.local/pipx/.cache /tmp/* && rm -rf /root/.cache/pip /root/.local/pipx/.cache /tmp/*
# 階段 12-B安裝 pdf2zhPDFMathTranslate 引擎) # 階段 12-B安裝 pdf2zhPDFMathTranslate 引擎)
# 注意pdf2zh 的模型將在後續階段顯式下載 # 注意pdf2zh 的模型將在後續階段顯式下載
# ⬇️ 此步驟為 Docker build 階段安裝runtime 不會再下載 # ⬇️ 此步驟為 Docker build 階段安裝runtime 不會再下載
RUN pipx install "pdf2zh" \ RUN pipx install "pdf2zh" \
&& rm -rf /root/.cache/huggingface /root/.cache/pip /root/.local/pipx/.cache /tmp/* && rm -rf /root/.cache/huggingface /root/.cache/pip /root/.local/pipx/.cache /tmp/*
# 階段 12-C安裝 babeldocBabelDOC 引擎) # 階段 12-C安裝 babeldocBabelDOC 引擎)
# ⚠️ 注意:不使用 --warmup資源將在後續階段顯式下載 # ⚠️ 注意:不使用 --warmup資源將在後續階段顯式下載
# ⬇️ 此步驟為 Docker build 階段安裝runtime 不會再下載 # ⬇️ 此步驟為 Docker build 階段安裝runtime 不會再下載
RUN (pipx install "babeldoc" || echo "⚠️ babeldoc 安裝失敗,跳過...") \ RUN (pipx install "babeldoc" || echo "⚠️ babeldoc 安裝失敗,跳過...") \
&& rm -rf /root/.cache/huggingface /root/.cache/pip /root/.local/pipx/.cache /tmp/* && rm -rf /root/.cache/huggingface /root/.cache/pip /root/.local/pipx/.cache /tmp/*
# 階段 12-D安裝 mineruMinerU PDF 解析引擎) # 階段 12-D安裝 mineruMinerU PDF 解析引擎)
# ⚠️ 注意mineru[all] 依賴大量 HuggingFace 套件,但安裝時不應下載模型 # ⚠️ 注意mineru[all] 依賴大量 HuggingFace 套件,但安裝時不應下載模型
# 模型下載將在後續階段顯式執行 # 模型下載將在後續階段顯式執行
# ⬇️ 此步驟為 Docker build 階段安裝runtime 不會再下載 # ⬇️ 此步驟為 Docker build 階段安裝runtime 不會再下載
RUN (pipx install "mineru[all]" || echo "⚠️ mineru 安裝失敗(可能是 arm64 相容性問題),跳過...") \ RUN (pipx install "mineru[all]" || echo "⚠️ mineru 安裝失敗(可能是 arm64 相容性問題),跳過...") \
&& rm -rf /root/.cache/huggingface /root/.cache/pip /root/.cache/torch /root/.local/pipx/.cache /tmp/* && rm -rf /root/.cache/huggingface /root/.cache/pip /root/.cache/torch /root/.local/pipx/.cache /tmp/*
# Add pipx bin directory to PATH必須在模型下載前設定 # Add pipx bin directory to PATH必須在模型下載前設定
ENV PATH="/root/.local/bin:${PATH}" ENV PATH="/root/.local/bin:${PATH}"
@ -347,215 +347,215 @@ ENV PATH="/root/.local/bin:${PATH}"
# runtime 不會再下載任何資源 # runtime 不會再下載任何資源
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
RUN set -eux && \ RUN set -eux && \
echo "===========================================================" && \ echo "===========================================================" && \
echo "🚀 開始統一模型下載(單一 RUN 優化 BuildKit layer" && \ echo "🚀 開始統一模型下載(單一 RUN 優化 BuildKit layer" && \
echo "===========================================================" && \ echo "===========================================================" && \
\ \
# ======================================== # ========================================
# [1/5] PDFMathTranslate DocLayout-YOLO ONNX 模型 # [1/5] PDFMathTranslate DocLayout-YOLO ONNX 模型
# ======================================== # ========================================
# ⬇️ Docker build 階段下載 DocLayout-YOLO ONNX 模型 # ⬇️ Docker build 階段下載 DocLayout-YOLO ONNX 模型
# 這是 pdf2zh 用於 PDF 版面分析的核心模型 # 這是 pdf2zh 用於 PDF 版面分析的核心模型
# runtime 不會再下載此資源 # runtime 不會再下載此資源
echo "" && \ echo "" && \
echo "📥 [1/5] 下載 DocLayout-YOLO ONNX 模型..." && \ echo "📥 [1/5] 下載 DocLayout-YOLO ONNX 模型..." && \
mkdir -p /models/pdfmathtranslate && \ mkdir -p /models/pdfmathtranslate && \
python3 -c " \ python3 -c " \
import os; \ import os; \
os.environ['HF_HUB_DISABLE_PROGRESS_BARS'] = '0'; \ os.environ['HF_HUB_DISABLE_PROGRESS_BARS'] = '0'; \
from huggingface_hub import snapshot_download; \ from huggingface_hub import snapshot_download; \
snapshot_download( \ snapshot_download( \
repo_id='wybxc/DocLayout-YOLO-DocStructBench-onnx', \ repo_id='wybxc/DocLayout-YOLO-DocStructBench-onnx', \
local_dir='/models/pdfmathtranslate', \ local_dir='/models/pdfmathtranslate', \
allow_patterns=['*.onnx'], \ allow_patterns=['*.onnx'], \
local_dir_use_symlinks=False \ local_dir_use_symlinks=False \
)" && \ )" && \
echo "✅ DocLayout-YOLO ONNX 下載完成" && \ echo "✅ DocLayout-YOLO ONNX 下載完成" && \
ls -lh /models/pdfmathtranslate/*.onnx 2>/dev/null || ls -lh /models/pdfmathtranslate/ && \ ls -lh /models/pdfmathtranslate/*.onnx 2>/dev/null || ls -lh /models/pdfmathtranslate/ && \
\ \
# 🔥 立即清理 HuggingFace cache關鍵避免 blob 重複進入 layer # 🔥 立即清理 HuggingFace cache關鍵避免 blob 重複進入 layer
rm -rf /root/.cache/huggingface && \ rm -rf /root/.cache/huggingface && \
\ \
# ======================================== # ========================================
# [2/5] BabelDOC 資源(顯式下載,禁止 warmup # [2/5] BabelDOC 資源(顯式下載,禁止 warmup
# ======================================== # ========================================
# ⚠️ 重要:不使用 babeldoc --warmup因為 # ⚠️ 重要:不使用 babeldoc --warmup因為
# 1. --warmup 內部使用 async HTTP在 QEMU 下不穩定 # 1. --warmup 內部使用 async HTTP在 QEMU 下不穩定
# 2. --warmup 可能失敗但不報錯 # 2. --warmup 可能失敗但不報錯
# 3. 我們需要完全可控的下載流程 # 3. 我們需要完全可控的下載流程
# #
# BabelDOC 翻譯服務使用外部 APIGoogle/DeepL/OpenAI # BabelDOC 翻譯服務使用外部 APIGoogle/DeepL/OpenAI
# 本地只需要字型和基礎資源,這些已透過 apt 安裝 # 本地只需要字型和基礎資源,這些已透過 apt 安裝
# ⬇️ Docker build 階段準備 BabelDOC 快取目錄 # ⬇️ Docker build 階段準備 BabelDOC 快取目錄
# runtime 使用 API 翻譯,不需要本地模型 # runtime 使用 API 翻譯,不需要本地模型
echo "" && \ echo "" && \
echo "📥 [2/5] 準備 BabelDOC 資源..." && \ echo "📥 [2/5] 準備 BabelDOC 資源..." && \
mkdir -p /root/.cache/babeldoc && \ mkdir -p /root/.cache/babeldoc && \
if command -v babeldoc >/dev/null 2>&1; then \ if command -v babeldoc >/dev/null 2>&1; then \
echo "✅ BabelDOC 已安裝,翻譯將使用外部 APIGoogle/DeepL/OpenAI"; \ echo "✅ BabelDOC 已安裝,翻譯將使用外部 APIGoogle/DeepL/OpenAI"; \
echo " 本地不需要下載翻譯模型"; \ echo " 本地不需要下載翻譯模型"; \
else \ else \
echo "⚠️ BabelDOC 未安裝,跳過"; \ echo "⚠️ BabelDOC 未安裝,跳過"; \
fi && \ fi && \
echo "✅ BabelDOC 步驟完成" && \ echo "✅ BabelDOC 步驟完成" && \
\ \
# ======================================== # ========================================
# [3/5] PDFMathTranslate 多語言字型 # [3/5] PDFMathTranslate 多語言字型
# ======================================== # ========================================
# ⬇️ Docker build 階段下載 PDFMathTranslate 所需字型 # ⬇️ Docker build 階段下載 PDFMathTranslate 所需字型
# 這些字型用於 PDF 翻譯時保持正確的文字渲染 # 這些字型用於 PDF 翻譯時保持正確的文字渲染
# runtime 不會再下載此資源 # runtime 不會再下載此資源
echo "" && \ echo "" && \
echo "📥 [3/5] 下載 PDFMathTranslate 多語言字型..." && \ echo "📥 [3/5] 下載 PDFMathTranslate 多語言字型..." && \
mkdir -p /app && \ mkdir -p /app && \
curl -fSL --retry 3 --retry-delay 5 -o /app/GoNotoKurrent-Regular.ttf \ curl -fSL --retry 3 --retry-delay 5 -o /app/GoNotoKurrent-Regular.ttf \
"https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf" && \ "https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf" && \
curl -fSL --retry 3 --retry-delay 5 -o /app/SourceHanSerifCN-Regular.ttf \ curl -fSL --retry 3 --retry-delay 5 -o /app/SourceHanSerifCN-Regular.ttf \
"https://github.com/timelic/source-han-serif/releases/download/main/SourceHanSerifCN-Regular.ttf" && \ "https://github.com/timelic/source-han-serif/releases/download/main/SourceHanSerifCN-Regular.ttf" && \
curl -fSL --retry 3 --retry-delay 5 -o /app/SourceHanSerifTW-Regular.ttf \ curl -fSL --retry 3 --retry-delay 5 -o /app/SourceHanSerifTW-Regular.ttf \
"https://github.com/timelic/source-han-serif/releases/download/main/SourceHanSerifTW-Regular.ttf" && \ "https://github.com/timelic/source-han-serif/releases/download/main/SourceHanSerifTW-Regular.ttf" && \
curl -fSL --retry 3 --retry-delay 5 -o /app/SourceHanSerifJP-Regular.ttf \ curl -fSL --retry 3 --retry-delay 5 -o /app/SourceHanSerifJP-Regular.ttf \
"https://github.com/timelic/source-han-serif/releases/download/main/SourceHanSerifJP-Regular.ttf" && \ "https://github.com/timelic/source-han-serif/releases/download/main/SourceHanSerifJP-Regular.ttf" && \
curl -fSL --retry 3 --retry-delay 5 -o /app/SourceHanSerifKR-Regular.ttf \ curl -fSL --retry 3 --retry-delay 5 -o /app/SourceHanSerifKR-Regular.ttf \
"https://github.com/timelic/source-han-serif/releases/download/main/SourceHanSerifKR-Regular.ttf" && \ "https://github.com/timelic/source-han-serif/releases/download/main/SourceHanSerifKR-Regular.ttf" && \
echo "✅ 字型下載完成" && \ echo "✅ 字型下載完成" && \
\ \
# ======================================== # ========================================
# [4/5] MinerU Pipeline 模型(顯式下載) # [4/5] MinerU Pipeline 模型(顯式下載)
# ======================================== # ========================================
# ⬇️ Docker build 階段下載 MinerU Pipeline 模型 # ⬇️ Docker build 階段下載 MinerU Pipeline 模型
# 包含DocLayout-YOLO, YOLOv8 MFD, UniMERNet, PaddleOCR, LayoutReader, SLANet # 包含DocLayout-YOLO, YOLOv8 MFD, UniMERNet, PaddleOCR, LayoutReader, SLANet
# runtime 不會再下載任何資源 # runtime 不會再下載任何資源
echo "" && \ echo "" && \
echo "📥 [4/5] 下載 MinerU Pipeline 模型..." && \ echo "📥 [4/5] 下載 MinerU Pipeline 模型..." && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
MINERU_MODELS_DIR="/models/mineru" && \ MINERU_MODELS_DIR="/models/mineru" && \
mkdir -p "$MINERU_MODELS_DIR" && \ mkdir -p "$MINERU_MODELS_DIR" && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
echo "⚠️ ARM64 架構MinerU 可能不完全支援,嘗試下載模型..."; \ echo "⚠️ ARM64 架構MinerU 可能不完全支援,嘗試下載模型..."; \
fi && \ fi && \
if command -v mineru >/dev/null 2>&1; then \ if command -v mineru >/dev/null 2>&1; then \
echo "使用顯式 Python 腳本下載 MinerU 模型..."; \ echo "使用顯式 Python 腳本下載 MinerU 模型..."; \
python3 -c " \ python3 -c " \
import os, json; \ import os, json; \
os.environ['HF_HUB_DISABLE_PROGRESS_BARS'] = '0'; \ os.environ['HF_HUB_DISABLE_PROGRESS_BARS'] = '0'; \
from huggingface_hub import snapshot_download; \ from huggingface_hub import snapshot_download; \
# PDF-Extract-Kit-1.0 Pipeline 模型 # PDF-Extract-Kit-1.0 Pipeline 模型
models_dir = '/models/mineru'; \ models_dir = '/models/mineru'; \
print('下載 PDF-Extract-Kit-1.0 模型...'); \ print('下載 PDF-Extract-Kit-1.0 模型...'); \
try: \ try: \
snapshot_download( \ snapshot_download( \
repo_id='opendatalab/PDF-Extract-Kit-1.0', \ repo_id='opendatalab/PDF-Extract-Kit-1.0', \
local_dir=os.path.join(models_dir, 'PDF-Extract-Kit-1.0'), \ local_dir=os.path.join(models_dir, 'PDF-Extract-Kit-1.0'), \
local_dir_use_symlinks=False \ local_dir_use_symlinks=False \
); \ ); \
print('✅ PDF-Extract-Kit-1.0 下載完成'); \ print('✅ PDF-Extract-Kit-1.0 下載完成'); \
except Exception as e: \ except Exception as e: \
print(f'⚠️ PDF-Extract-Kit-1.0 下載失敗: {e}'); \ print(f'⚠️ PDF-Extract-Kit-1.0 下載失敗: {e}'); \
# 建立 mineru.json 設定檔 # 建立 mineru.json 設定檔
config = { \ config = { \
'models-dir': { \ 'models-dir': { \
'pipeline': os.path.join(models_dir, 'PDF-Extract-Kit-1.0'), \ 'pipeline': os.path.join(models_dir, 'PDF-Extract-Kit-1.0'), \
'vlm': '' \ 'vlm': '' \
}, \ }, \
'model-source': 'local', \ 'model-source': 'local', \
'latex-delimiter-config': { \ 'latex-delimiter-config': { \
'display': {'left': '\$\$', 'right': '\$\$'}, \ 'display': {'left': '\$\$', 'right': '\$\$'}, \
'inline': {'left': '\$', 'right': '\$'} \ 'inline': {'left': '\$', 'right': '\$'} \
} \ } \
}; \ }; \
with open('/root/mineru.json', 'w') as f: \ with open('/root/mineru.json', 'w') as f: \
json.dump(config, f, indent=2); \ json.dump(config, f, indent=2); \
print('✅ mineru.json 已建立'); \ print('✅ mineru.json 已建立'); \
" || echo "⚠️ MinerU 模型下載失敗,將在 runtime 時嘗試下載"; \ " || echo "⚠️ MinerU 模型下載失敗,將在 runtime 時嘗試下載"; \
else \ else \
echo "⚠️ mineru 命令不可用,跳過模型下載"; \ echo "⚠️ mineru 命令不可用,跳過模型下載"; \
echo '{"models-dir":{"pipeline":"","vlm":""},"model-source":"huggingface","latex-delimiter-config":{"display":{"left":"$$","right":"$$"},"inline":{"left":"$","right":"$"}}}' > /root/mineru.json; \ echo '{"models-dir":{"pipeline":"","vlm":""},"model-source":"huggingface","latex-delimiter-config":{"display":{"left":"$$","right":"$$"},"inline":{"left":"$","right":"$"}}}' > /root/mineru.json; \
fi && \ fi && \
echo "✅ MinerU 模型下載步驟完成" && \ echo "✅ MinerU 模型下載步驟完成" && \
\ \
# 🔥 再次清理 HuggingFace cacheMinerU 也會產生) # 🔥 再次清理 HuggingFace cacheMinerU 也會產生)
rm -rf /root/.cache/huggingface && \ rm -rf /root/.cache/huggingface && \
\ \
# ======================================== # ========================================
# [5/5] 最終驗證 # [5/5] 最終驗證
# ======================================== # ========================================
echo "" && \ echo "" && \
echo "📥 [5/5] 驗證模型完整性..." && \ echo "📥 [5/5] 驗證模型完整性..." && \
echo "" && \ echo "" && \
\ \
# ======================================== # ========================================
# 🔥 最終 Cache 清理(關鍵!避免 overlayfs diff 爆炸) # 🔥 最終 Cache 清理(關鍵!避免 overlayfs diff 爆炸)
# ======================================== # ========================================
echo "===========================================================" && \ echo "===========================================================" && \
echo "🧹 清理所有下載快取(降低 layer diff 大小)" && \ echo "🧹 清理所有下載快取(降低 layer diff 大小)" && \
echo "===========================================================" && \ echo "===========================================================" && \
# HuggingFace Hub cache最大宗包含所有 blob # HuggingFace Hub cache最大宗包含所有 blob
rm -rf /root/.cache/huggingface && \ rm -rf /root/.cache/huggingface && \
# pip / Python build cache # pip / Python build cache
rm -rf /root/.cache/pip && \ rm -rf /root/.cache/pip && \
rm -rf /root/.cache/uv && \ rm -rf /root/.cache/uv && \
# pipx cache # pipx cache
rm -rf /root/.local/pipx/.cache && \ rm -rf /root/.local/pipx/.cache && \
# torch cache可能由 MinerU 產生) # torch cache可能由 MinerU 產生)
rm -rf /root/.cache/torch && \ rm -rf /root/.cache/torch && \
# 通用 cache 目錄 # 通用 cache 目錄
rm -rf /tmp/* && \ rm -rf /tmp/* && \
rm -rf /var/tmp/* && \ rm -rf /var/tmp/* && \
# Python bytecode cache可選節省少量空間 # Python bytecode cache可選節省少量空間
find /root/.local -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \ 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 && \ find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \
\ \
echo "" && \ echo "" && \
echo "===========================================================" && \ echo "===========================================================" && \
echo "📋 模型檔案驗證" && \ echo "📋 模型檔案驗證" && \
echo "===========================================================" && \ echo "===========================================================" && \
echo "" && \ echo "" && \
echo "🔹 PDFMathTranslate 模型:" && \ echo "🔹 PDFMathTranslate 模型:" && \
ONNX_COUNT=$(find /models/pdfmathtranslate -name "*.onnx" 2>/dev/null | wc -l) && \ ONNX_COUNT=$(find /models/pdfmathtranslate -name "*.onnx" 2>/dev/null | wc -l) && \
if [ "$ONNX_COUNT" -gt 0 ]; then \ if [ "$ONNX_COUNT" -gt 0 ]; then \
echo " ✅ 找到 $ONNX_COUNT 個 ONNX 模型:"; \ echo " ✅ 找到 $ONNX_COUNT 個 ONNX 模型:"; \
ls -lh /models/pdfmathtranslate/*.onnx 2>/dev/null || find /models/pdfmathtranslate -name "*.onnx" -exec ls -lh {} \;; \ ls -lh /models/pdfmathtranslate/*.onnx 2>/dev/null || find /models/pdfmathtranslate -name "*.onnx" -exec ls -lh {} \;; \
else \ else \
echo " ❌ /models/pdfmathtranslate 中沒有 ONNX 模型"; \ echo " ❌ /models/pdfmathtranslate 中沒有 ONNX 模型"; \
fi && \ fi && \
echo "" && \ echo "" && \
echo "🔹 PDFMathTranslate 字型:" && \ echo "🔹 PDFMathTranslate 字型:" && \
ls -lh /app/*.ttf 2>/dev/null || echo " ⚠️ 無字型檔案" && \ ls -lh /app/*.ttf 2>/dev/null || echo " ⚠️ 無字型檔案" && \
echo "" && \ echo "" && \
echo "🔹 BabelDOC 狀態:" && \ echo "🔹 BabelDOC 狀態:" && \
if command -v babeldoc >/dev/null 2>&1; then \ if command -v babeldoc >/dev/null 2>&1; then \
echo " ✅ BabelDOC 已安裝(使用外部 API 翻譯)"; \ echo " ✅ BabelDOC 已安裝(使用外部 API 翻譯)"; \
else \ else \
echo " ⚠️ BabelDOC 未安裝"; \ echo " ⚠️ BabelDOC 未安裝"; \
fi && \ fi && \
echo "" && \ echo "" && \
echo "🔹 MinerU 模型目錄:" && \ echo "🔹 MinerU 模型目錄:" && \
if [ -d "/models/mineru/PDF-Extract-Kit-1.0" ]; then \ if [ -d "/models/mineru/PDF-Extract-Kit-1.0" ]; then \
echo " ✅ MinerU Pipeline 模型已下載"; \ echo " ✅ MinerU Pipeline 模型已下載"; \
du -sh /models/mineru/PDF-Extract-Kit-1.0 2>/dev/null || true; \ du -sh /models/mineru/PDF-Extract-Kit-1.0 2>/dev/null || true; \
else \ else \
echo " ⚠️ MinerU 模型目錄不存在(可能需要 runtime 下載)"; \ echo " ⚠️ MinerU 模型目錄不存在(可能需要 runtime 下載)"; \
fi && \ fi && \
echo "" && \ echo "" && \
echo "🔹 mineru.json 設定:" && \ echo "🔹 mineru.json 設定:" && \
if [ -f /root/mineru.json ]; then \ if [ -f /root/mineru.json ]; then \
cat /root/mineru.json; \ cat /root/mineru.json; \
else \ else \
echo " ⚠️ mineru.json 不存在"; \ echo " ⚠️ mineru.json 不存在"; \
fi && \ fi && \
echo "" && \ echo "" && \
echo "🔹 確認 HuggingFace cache 已清除:" && \ echo "🔹 確認 HuggingFace cache 已清除:" && \
if [ -d "/root/.cache/huggingface" ]; then \ if [ -d "/root/.cache/huggingface" ]; then \
echo " ❌ 警告HuggingFace cache 仍存在!"; \ echo " ❌ 警告HuggingFace cache 仍存在!"; \
du -sh /root/.cache/huggingface 2>/dev/null || true; \ du -sh /root/.cache/huggingface 2>/dev/null || true; \
else \ else \
echo " ✅ HuggingFace cache 已清除"; \ echo " ✅ HuggingFace cache 已清除"; \
fi && \ fi && \
echo "" && \ echo "" && \
echo "===========================================================" && \ echo "===========================================================" && \
echo "✅ 模型下載完成,所有快取已清理" && \ echo "✅ 模型下載完成,所有快取已清理" && \
echo "===========================================================" echo "==========================================================="
# ============================================================================== # ==============================================================================
# 環境變數設定 # 環境變數設定
@ -582,22 +582,22 @@ ENV TRANSFORMERS_OFFLINE="1"
# 最終清理(模型下載完成後) # 最終清理(模型下載完成後)
# ============================================================================== # ==============================================================================
RUN rm -rf /usr/share/doc/texlive* \ RUN rm -rf /usr/share/doc/texlive* \
&& rm -rf /usr/share/texlive/texmf-dist/doc \ && rm -rf /usr/share/texlive/texmf-dist/doc \
&& rm -rf /usr/share/doc/* \ && rm -rf /usr/share/doc/* \
&& rm -rf /usr/share/man/* \ && rm -rf /usr/share/man/* \
&& rm -rf /usr/share/info/* && rm -rf /usr/share/info/*
# ============================================================================== # ==============================================================================
# 設定 locale支援中文 PDF 避免亂碼) # 設定 locale支援中文 PDF 避免亂碼)
# ============================================================================== # ==============================================================================
RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ 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_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/# 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/# 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/# 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/# 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 && \ sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \
locale-gen locale-gen
# 預設使用 zh_TW.UTF-8 確保中文 PDF 正確顯示 # 預設使用 zh_TW.UTF-8 確保中文 PDF 正確顯示
ENV LANG=zh_TW.UTF-8 ENV LANG=zh_TW.UTF-8
@ -615,16 +615,16 @@ RUN fc-cache -fv
# ⬇️ Docker build 階段下載runtime 不會再下載 # ⬇️ Docker build 階段下載runtime 不會再下載
# ============================================================================== # ==============================================================================
RUN ARCH=$(uname -m) && \ RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \ VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \
else \ else \
VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \ VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \
fi && \ fi && \
curl -L --retry 3 --retry-delay 5 -o /tmp/vtracer.tar.gz "https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \ curl -L --retry 3 --retry-delay 5 -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/ && \ tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \
mv /tmp/vtracer /usr/local/bin/vtracer && \ mv /tmp/vtracer /usr/local/bin/vtracer && \
chmod +x /usr/local/bin/vtracer && \ chmod +x /usr/local/bin/vtracer && \
rm /tmp/vtracer.tar.gz rm /tmp/vtracer.tar.gz
COPY --from=install /temp/prod/node_modules node_modules COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /app/public/ /app/public/ COPY --from=prerelease /app/public/ /app/public/