diff --git a/Dockerfile.backup b/Dockerfile.backup new file mode 100644 index 0000000..4c9d909 --- /dev/null +++ b/Dockerfile.backup @@ -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.0(Pipeline 模型) +# 包含: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 + 德法 + 阿拉伯/希伯來語 +# ✅ OCR:7 種主要語言 +# ✅ 字型:Noto CJK + Liberation + 標楷體 +# ✅ OpenCV:電腦視覺轉換支援 +# ✅ 額外影片編解碼器 +# ✅ PDFMathTranslate:PDF 翻譯引擎 +# +# 📝 分段安裝說明: +# - 將套件拆分為多個 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(版本 6),trixie 才有 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)" + +# 階段 6:LibreOffice(最大的套件,單獨安裝) +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 已安裝" + +# 階段 7:TexLive 基礎 +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 基礎已安裝" + +# 階段 8:TexLive 語言包 +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 語言包已安裝" + +# 階段 9:OCR 支援 +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 完成:字型已安裝" + +# 階段 11:Python 依賴 +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.1:PDF 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-UNIFIED:Python 工具安裝 + 模型下載(單一 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 + endesive(PDF 簽章)..." && \ + 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 "🔧 安裝 MinerU(amd64)..." && \ + echo "" && \ + # 先安裝 uv(官方推薦的套件管理器) + echo "📝 步驟 1: 安裝 uv(Python 套件管理器)..." && \ + 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 "✅ 方法 1(uv 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 "✅ 方法 2(uv 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 "✅ 方法 3(pip3)成功"; \ + 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 安裝步驟完成" + +# ============================================================================== +# 階段 12F:BabelDOC 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 資源準備完成" + +# ============================================================================== +# 階段 12G:MinerU 模型下載 +# ============================================================================== +# ⚠️ 關鍵:模型必須移動到固定目錄,不能依賴 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 模型下載步驟完成" + +# ============================================================================== +# 階段 12H:Cache 清理 + 最終驗證 +# ============================================================================== +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 sandbox,Docker 環境無法使用) +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" ]