From 55b0f519ab89c462e02ee7551d1999ddcaa4c9b7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 25 Jan 2026 13:15:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor(docker):=20=E5=AE=8C=E6=95=B4=E9=87=8D?= =?UTF-8?q?=E6=A7=8B=20Dockerfile=20v0.1.16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: Dockerfile 結構重新設計 ## 重構內容 ### Multi-Stage Build 結構 - Stage 1 [base]: Bun runtime 基礎 - Stage 2 [install]: Node 依賴安裝 - Stage 3 [prerelease]: 應用程式建構 - Stage 4 [system-tools]: APT 系統工具(拆分為 14 個獨立 RUN) - Stage 5 [fonts]: 字型安裝 - Stage 6 [python-tools]: Python CLI 工具(拆分為 9 個獨立 RUN) - Stage 7 [models]: 模型下載 - Stage 8 [release]: 最終 Image ### 新增 Scripts - install-system-tools.sh: 系統工具安裝 - install-fonts.sh: 字型安裝 - install-python-tools.sh: Python 工具安裝 - download-models.sh: 模型下載 - verify-installation.sh: 完整安裝驗證 ### 刪除舊檔案 - Dockerfile.backup - Dockerfile.offline - Dockerfile.old ### 路徑更新 - MinerU 模型: /opt/convertx/models/mineru/PDF-Extract-Kit-1.0 - BabelDOC 快取: /root/.cache/babeldoc ### Multi-Arch 支援 - linux/amd64: 功能完整 - linux/arm64: 安全降級(MinerU/resvg 跳過) Closes #docker-refactor --- Dockerfile | 924 +++++++++++++++----------------- Dockerfile.backup | 806 ---------------------------- Dockerfile.offline | 694 ------------------------ scripts/download-models.sh | 306 +++++++++++ scripts/install-fonts.sh | 174 ++++++ scripts/install-python-tools.sh | 219 ++++++++ scripts/install-system-tools.sh | 305 +++++++++++ scripts/verify-installation.sh | 341 ++++++++++++ scripts/verify-models.sh | 34 +- scripts/verify-offline.sh | 2 +- 10 files changed, 1797 insertions(+), 2008 deletions(-) delete mode 100644 Dockerfile.backup delete mode 100644 Dockerfile.offline create mode 100644 scripts/download-models.sh create mode 100644 scripts/install-fonts.sh create mode 100644 scripts/install-python-tools.sh create mode 100644 scripts/install-system-tools.sh create mode 100644 scripts/verify-installation.sh diff --git a/Dockerfile b/Dockerfile index 244cab1..fa0fa4e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # ============================================================================== # ConvertX-CN 官方 Docker Image -# 版本:v0.1.16 - 完全離線化版本 +# 版本:v0.1.16 - 完整重構版 # ============================================================================== # # 📦 Image 說明: @@ -8,7 +8,7 @@ # - ⚠️ 所有模型、字型、tokenizer 已在 build 階段完整預下載 # - ⚠️ Runtime 完全離線運行(僅翻譯服務允許連網) # -# 🔒 Offline-first 設計原則(硬性規則): +# 🔒 Offline-first 設計原則: # 1. Runtime(docker run 後): # ❌ 禁止任何模型、字型、tokenizer、metadata 下載 # ❌ MinerU / BabelDOC / PDFMathTranslate 不得嘗試連網 @@ -21,72 +21,73 @@ # - PDFMathTranslate: DocLayout-YOLO ONNX(佈局分析) # - BabelDOC: DocLayout-YOLO + 字型資源 + tiktoken # - MinerU: PDF-Extract-Kit-1.0(Pipeline 模型) -# 包含:DocLayout-YOLO, YOLOv8 MFD, UniMERNet, PaddleOCR, LayoutReader, SLANet # -# 🌍 內建語言支援: -# - OCR: 英文、繁體中文、簡體中文、日文、韓文、德文、法文 -# - Locale: en_US, zh_TW, zh_CN, ja_JP, ko_KR, de_DE, fr_FR -# - 字型: Noto CJK, Liberation, 標楷體, Source Han Serif -# - LaTeX: CJK、德文、法文、阿拉伯語、希伯來語 +# 🏗️ Multi-Stage Build 結構: +# Stage 1 [base] : Bun runtime 基礎 +# Stage 2 [install] : Node 依賴安裝 +# Stage 3 [prerelease] : 應用程式建構 +# Stage 4 [system-tools] : APT 系統工具 +# Stage 5 [fonts] : 字型安裝 +# Stage 6 [python-tools] : Python CLI 工具 +# Stage 7 [models] : 模型下載 +# Stage 8 [release] : 最終 Image +# +# 🌍 Multi-Arch 支援: +# - linux/amd64: 功能完整 +# - linux/arm64: 安全降級(不支援的工具會跳過) # # 📊 Image 大小:約 10-14 GB(含完整模型) # # ============================================================================== -# 🏗️ Multi-Stage Build 結構: -# Stage 1 [base]: Bun runtime 基礎 -# Stage 2 [install]: Node 依賴安裝 -# Stage 3 [prerelease]: 應用程式建構 -# Stage 4 [system-tools]: APT 系統工具 -# Stage 5 [python-base]: Python 基礎環境 -# Stage 6 [python-tools]: Python CLI 工具 -# Stage 7 [models-download]: 模型下載(獨立 stage) -# Stage 8 [final]: 最終 Image(合併所有層) -# ============================================================================== -# =================================== +# ============================================================================== # Stage 1: Base - Bun Runtime -# =================================== +# ============================================================================== FROM debian:bookworm-slim AS base LABEL org.opencontainers.image.source="https://github.com/pi-docket/ConvertX-CN" LABEL org.opencontainers.image.description="ConvertX-CN - 完全離線化檔案轉換服務" -LABEL org.opencontainers.image.version="v0.1.16-offline" +LABEL org.opencontainers.image.version="v0.1.16" WORKDIR /app # 設定非互動模式 ENV DEBIAN_FRONTEND=noninteractive # 配置 APT 重試機制 -RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries +RUN set -ex && \ + echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries && \ + echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \ + echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \ + echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \ + echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries # 安裝基礎工具 -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - unzip \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* +RUN set -ex && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + curl \ + unzip \ + ca-certificates && \ + rm -rf /var/lib/apt/lists/* # 安裝 Bun(根據架構選擇版本) -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \ - -o bun-linux-aarch64.zip \ - https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-aarch64.zip; \ - else \ - curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \ - -o bun-linux-x64-baseline.zip \ - https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-x64-baseline.zip; \ - fi && \ - unzip -j bun-linux-*.zip -d /usr/local/bin && \ - rm bun-linux-*.zip && \ - chmod +x /usr/local/bin/bun +ARG BUN_VERSION=1.3.6 +RUN set -ex && \ + ARCH=$(uname -m) && \ + if [ "$ARCH" = "aarch64" ]; then \ + BUN_ASSET="bun-linux-aarch64.zip"; \ + else \ + BUN_ASSET="bun-linux-x64-baseline.zip"; \ + fi && \ + curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \ + -o /tmp/bun.zip \ + "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${BUN_ASSET}" && \ + unzip -j /tmp/bun.zip -d /usr/local/bin && \ + rm /tmp/bun.zip && \ + chmod +x /usr/local/bin/bun -# =================================== +# ============================================================================== # Stage 2: Install - Node Dependencies -# =================================== +# ============================================================================== FROM base AS install # 開發依賴 @@ -99,543 +100,475 @@ RUN mkdir -p /temp/prod COPY package.json bun.lock /temp/prod/ RUN cd /temp/prod && bun install --frozen-lockfile --production -# =================================== +# ============================================================================== # Stage 3: Prerelease - Build App -# =================================== +# ============================================================================== FROM base AS prerelease WORKDIR /app COPY --from=install /temp/dev/node_modules node_modules COPY . . RUN bun run build -# =================================== -# Stage 4: System Tools -# =================================== +# ============================================================================== +# Stage 4: System Tools(拆分為多個 RUN 以提升可調試性和 cache 效率) +# ============================================================================== FROM base AS system-tools -# 配置 APT -RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries +# 4.1 配置 APT +RUN set -ex && \ + 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 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/80-retries && \ + echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries -# 階段 4.1:基礎系統工具 + Locale -RUN echo "📦 [Stage 4.1] 安裝基礎系統工具..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - locales \ - ca-certificates \ - curl \ - openssl \ - git \ - && rm -rf /var/lib/apt/lists/* +# 4.2 基礎系統工具 +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + locales ca-certificates curl wget unzip openssl git xz-utils && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.2:核心轉換工具(輕量) -RUN echo "📦 [Stage 4.2] 安裝核心轉換工具..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - assimp-utils \ - dcraw \ - dvisvgm \ - ghostscript \ - graphicsmagick \ - mupdf-tools \ - poppler-utils \ - potrace \ - qpdf \ - && rm -rf /var/lib/apt/lists/* +# 4.3 核心轉換工具 +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + assimp-utils dcraw dvisvgm ghostscript graphicsmagick \ + mupdf-tools poppler-utils potrace qpdf && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.3:安裝 dasel(從 GitHub 二進位) -RUN echo "📦 [Stage 4.3] 安裝 dasel..." && \ - ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then DASEL_ARCH="linux_arm64"; \ - else DASEL_ARCH="linux_amd64"; fi && \ - curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \ - "https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" \ - -o /usr/local/bin/dasel && \ - chmod +x /usr/local/bin/dasel +# 4.4 dasel(JSON/YAML/TOML 轉換) +RUN set -ex && \ + ARCH=$(uname -m) && \ + if [ "$ARCH" = "aarch64" ]; then DASEL_ARCH="linux_arm64"; \ + else DASEL_ARCH="linux_amd64"; fi && \ + curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \ + "https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" \ + -o /usr/local/bin/dasel && \ + chmod +x /usr/local/bin/dasel -# 階段 4.4:安裝 resvg(僅 x86_64) -RUN echo "📦 [Stage 4.4] 安裝 resvg..." && \ - ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - echo "⚠️ resvg 無 ARM64 版本,跳過"; \ - else \ - curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \ - "https://github.com/linebender/resvg/releases/download/v0.44.0/resvg-linux-x86_64.tar.gz" \ - -o /tmp/resvg.tar.gz && \ - tar -xzf /tmp/resvg.tar.gz -C /tmp/ && \ - mv /tmp/resvg /usr/local/bin/resvg && \ - chmod +x /usr/local/bin/resvg && \ - rm -rf /tmp/resvg.tar.gz; \ - fi +# 4.5 resvg(僅 AMD64) +RUN set -ex && \ + ARCH=$(uname -m) && \ + if [ "$ARCH" = "aarch64" ]; then \ + echo "⚠️ resvg 無 ARM64 版本,跳過"; \ + else \ + curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \ + "https://github.com/linebender/resvg/releases/download/v0.44.0/resvg-linux-x86_64.tar.gz" \ + -o /tmp/resvg.tar.gz && \ + tar -xzf /tmp/resvg.tar.gz -C /tmp/ && \ + mv /tmp/resvg /usr/local/bin/resvg && \ + chmod +x /usr/local/bin/resvg && \ + rm -rf /tmp/resvg.tar.gz; \ + fi -# 階段 4.5:編譯安裝 deark -RUN echo "📦 [Stage 4.5] 編譯 deark..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - build-essential \ - && cd /tmp && \ - git clone --depth 1 https://github.com/jsummers/deark.git && \ - cd deark && make -j$(nproc) && \ - cp deark /usr/local/bin/deark && \ - chmod +x /usr/local/bin/deark && \ - cd / && rm -rf /tmp/deark && \ - apt-get remove -y build-essential && \ - apt-get autoremove -y && \ - rm -rf /var/lib/apt/lists/* +# 4.6 deark(編譯安裝) +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends build-essential && \ + cd /tmp && git clone --depth 1 https://github.com/jsummers/deark.git && \ + cd deark && make -j$(nproc) && \ + cp deark /usr/local/bin/deark && chmod +x /usr/local/bin/deark && \ + cd / && rm -rf /tmp/deark && \ + apt-get remove -y build-essential && apt-get autoremove -y && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.6:影音處理工具 -RUN echo "📦 [Stage 4.6] 安裝 FFmpeg..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - ffmpeg \ - libavcodec-extra \ - libva2 \ - && rm -rf /var/lib/apt/lists/* +# 4.7 vtracer +RUN set -ex && \ + 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 -rf /tmp/vtracer.tar.gz -# 階段 4.7:圖像處理工具(含 ImageMagick) -# ⚠️ 修復:確保 ImageMagick 已安裝 -RUN echo "📦 [Stage 4.7] 安裝圖像處理工具(含 ImageMagick)..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - imagemagick \ - inkscape \ - libheif-examples \ - libjxl-tools \ - libvips-tools \ - xauth \ - xvfb \ - && rm -rf /var/lib/apt/lists/* +# 4.8 FFmpeg +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + ffmpeg libavcodec-extra libva2 && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.8:文件處理工具 -RUN echo "📦 [Stage 4.8] 安裝 Calibre + Pandoc..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - calibre \ - libemail-outlook-message-perl \ - pandoc \ - && rm -rf /var/lib/apt/lists/* +# 4.9 圖像處理工具(ImageMagick, Inkscape, vips 等) +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + imagemagick inkscape libheif-examples libjxl-tools \ + libvips-tools xauth xvfb && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.9:LibreOffice -RUN echo "📦 [Stage 4.9] 安裝 LibreOffice(需數分鐘)..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - libreoffice \ - && rm -rf /var/lib/apt/lists/* +# 4.10 文件處理工具(Calibre, Pandoc) +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + calibre libemail-outlook-message-perl pandoc && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.10:TexLive 基礎 -RUN echo "📦 [Stage 4.10] 安裝 TexLive 基礎..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - texlive-base \ - texlive-latex-base \ - texlive-latex-recommended \ - texlive-fonts-recommended \ - texlive-xetex \ - latexmk \ - lmodern \ - && rm -rf /var/lib/apt/lists/* +# 4.11 LibreOffice +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends libreoffice && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.11:TexLive 語言包 -RUN echo "📦 [Stage 4.11] 安裝 TexLive 語言包..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - texlive-lang-cjk \ - texlive-lang-german \ - texlive-lang-french \ - texlive-lang-arabic \ - texlive-lang-other \ - && rm -rf /var/lib/apt/lists/* +# 4.12 TexLive 基礎 +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + texlive-base texlive-latex-base texlive-latex-recommended \ + texlive-fonts-recommended texlive-xetex latexmk lmodern && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.12:OCR 支援 -RUN echo "📦 [Stage 4.12] 安裝 Tesseract OCR..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - tesseract-ocr \ - tesseract-ocr-eng \ - tesseract-ocr-chi-tra \ - tesseract-ocr-chi-sim \ - tesseract-ocr-jpn \ - tesseract-ocr-kor \ - tesseract-ocr-deu \ - tesseract-ocr-fra \ - ocrmypdf \ - && rm -rf /var/lib/apt/lists/* +# 4.13 TexLive 語言包 +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + texlive-lang-cjk texlive-lang-german texlive-lang-french \ + texlive-lang-arabic texlive-lang-other && \ + rm -rf /var/lib/apt/lists/* -# 階段 4.13:字型 -RUN echo "📦 [Stage 4.13] 安裝系統字型..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - fonts-noto-cjk \ - fonts-noto-core \ - fonts-noto-color-emoji \ - fonts-liberation \ - && rm -rf /var/lib/apt/lists/* +# 4.14 Tesseract OCR +RUN 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/* -# =================================== -# Stage 5: Python Base -# =================================== -FROM system-tools AS python-base +# ============================================================================== +# Stage 5: Fonts(拆分安裝) +# ============================================================================== +FROM system-tools AS fonts -# 階段 5.1:Python 基礎 + uv -RUN echo "📦 [Stage 5.1] 安裝 Python 基礎..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - python3 \ - python3-pip \ - python3-venv \ - python3-numpy \ - python3-tinycss2 \ - python3-opencv \ - python3-img2pdf \ - && rm -rf /var/lib/apt/lists/* +# 5.1 系統字型 +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-core \ + fonts-noto-color-emoji fonts-liberation fonts-dejavu-core \ + fonts-dejavu-extra fonts-freefont-ttf fonts-droid-fallback && \ + rm -rf /var/lib/apt/lists/* -# 階段 5.2:安裝 uv(Python 套件管理器) -# ⚠️ 重要:使用 uv 作為主要安裝方式,確保一致性 -RUN echo "📦 [Stage 5.2] 安裝 uv..." && \ - pip3 install --no-cache-dir --break-system-packages uv && \ - uv --version +# 5.2 複製自訂字型 +RUN mkdir -p /usr/share/fonts/truetype/custom +COPY fonts/ /usr/share/fonts/truetype/custom/ -# 設定 PATH -ENV PATH="/root/.local/bin:/usr/local/bin:${PATH}" +# 5.3 設定 BabelDOC 字型目錄 +RUN mkdir -p /root/.cache/babeldoc/fonts && \ + for font in GoNotoKurrent-Regular.ttf SourceHanSerifCN-Regular.ttf \ + SourceHanSerifTW-Regular.ttf SourceHanSerifJP-Regular.ttf \ + SourceHanSerifKR-Regular.ttf BiauKai.ttf; do \ + [ -f "/usr/share/fonts/truetype/custom/${font}" ] && \ + cp "/usr/share/fonts/truetype/custom/${font}" /root/.cache/babeldoc/fonts/ || true; \ + done -# =================================== -# Stage 6: Python Tools -# =================================== -FROM python-base AS python-tools +# 5.4 更新字型快取 +RUN fc-cache -fv -# 階段 6.1:安裝 huggingface_hub + endesive(PDF 簽章) -# ⚠️ pykcs11 需要 python3-dev 提供 Python.h -RUN echo "📦 [Stage 6.1] 安裝 huggingface_hub + endesive..." && \ - apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - swig \ - libpcsclite-dev \ - python3-dev \ - && uv pip install --system --break-system-packages --no-cache huggingface_hub endesive && \ +# ============================================================================== +# Stage 6: Python Tools(拆分安裝) +# ============================================================================== +FROM fonts AS python-tools + +# 6.1 Python 基礎環境 +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + python3 python3-pip python3-venv python3-numpy \ + python3-tinycss2 python3-opencv python3-img2pdf && \ + rm -rf /var/lib/apt/lists/* + +# 6.2 uv 套件管理器 +RUN pip3 install --no-cache-dir --break-system-packages uv + +# 6.3 huggingface_hub +RUN uv pip install --system --break-system-packages --no-cache huggingface_hub + +# 6.4 endesive(PDF 簽章) +RUN apt-get update --fix-missing && \ + apt-get install -y --no-install-recommends \ + build-essential swig libpcsclite-dev python3-dev && \ + uv pip install --system --break-system-packages --no-cache endesive && \ apt-get remove -y build-essential swig python3-dev && \ apt-get autoremove -y && \ rm -rf /var/lib/apt/lists/* -# 階段 6.2:安裝 markitdown -RUN echo "📦 [Stage 6.2] 安裝 markitdown..." && \ - uv pip install --system --break-system-packages --no-cache "markitdown[all]" +# 6.5 markitdown +RUN uv pip install --system --break-system-packages --no-cache "markitdown[all]" -# 階段 6.3:安裝 pdf2zh -RUN echo "📦 [Stage 6.3] 安裝 pdf2zh..." && \ - uv pip install --system --break-system-packages --no-cache pdf2zh +# 6.6 pdf2zh(PDFMathTranslate) +RUN uv pip install --system --break-system-packages --no-cache pdf2zh -# 階段 6.4:安裝 babeldoc -RUN echo "📦 [Stage 6.4] 安裝 babeldoc..." && \ - uv pip install --system --break-system-packages --no-cache babeldoc || echo "⚠️ babeldoc 安裝可能有警告" +# 6.7 babeldoc +RUN uv pip install --system --break-system-packages --no-cache babeldoc || \ + echo "⚠️ babeldoc 安裝可能有警告" -# 階段 6.5:安裝 MinerU(⚠️ 關鍵修復) -# ⚠️ 只使用 system-level 安裝(uv pip install --system) -# ⚠️ 禁止使用 pipx(會導致 binary 不在 PATH) -RUN echo "📦 [Stage 6.5] 安裝 MinerU(system-level only)..." && \ +# 6.8 MinerU(僅 AMD64) +RUN set -ex && \ ARCH=$(uname -m) && \ if [ "$ARCH" = "aarch64" ]; then \ - echo "⚠️ ARM64:MinerU 不支援,跳過"; \ + echo "⚠️ ARM64:MinerU 不支援,跳過安裝"; \ else \ - echo "🔧 使用 uv pip install --system(官方推薦)..." && \ - uv pip install --system --break-system-packages --no-cache -U "mineru[all]" && \ - echo "✅ 驗證 MinerU 安裝..." && \ - which mineru && \ - mineru --version || echo "(版本資訊可能不可用)"; \ + uv pip install --system --break-system-packages --no-cache -U "mineru[all]"; \ fi -# =================================== -# Stage 7: Models Download(獨立 Stage) -# =================================== -FROM python-tools AS models-download +# 6.9 tiktoken +RUN uv pip install --system --break-system-packages --no-cache tiktoken -# 模型目錄定義 -ENV MINERU_MODELS_DIR="/opt/mineru/models" -ENV BABELDOC_CACHE_DIR="/opt/babeldoc/cache" -ENV HF_HOME="/tmp/hf_download_cache" +# 設定 PATH +ENV PATH="/root/.local/bin:/usr/local/bin:${PATH}" -# 階段 7.1:創建目錄結構 -RUN mkdir -p "${MINERU_MODELS_DIR}" && \ - mkdir -p "${BABELDOC_CACHE_DIR}/models" && \ - mkdir -p "${BABELDOC_CACHE_DIR}/fonts" && \ - mkdir -p "${BABELDOC_CACHE_DIR}/cmap" && \ - mkdir -p "${BABELDOC_CACHE_DIR}/tiktoken" +# ============================================================================== +# Stage 7: Models Download(拆分下載) +# ============================================================================== +FROM python-tools AS models -# 階段 7.2:下載 MinerU Pipeline 模型 -# ⚠️ 關鍵修復:使用顯式下載,存放到固定目錄 -RUN < /opt/convertx/mineru.json && \ + cp /opt/convertx/mineru.json /root/mineru.json && \ + echo "⚠️ ARM64:產生空的 mineru.json"; \ + else \ + python3 -c " \ +import json; \ +import os; \ +mineru_models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru'); \ +config = { \ + 'models-dir': { \ + 'pipeline': f'{mineru_models_dir}/PDF-Extract-Kit-1.0', \ + 'vlm': '' \ + }, \ + 'model-source': 'local', \ + 'latex-delimiter-config': { \ + 'display': {'left': '@@', 'right': '@@'}, \ + 'inline': {'left': '@', 'right': '@'} \ + } \ +}; \ +os.makedirs('/opt/convertx', exist_ok=True); \ +with open('/opt/convertx/mineru.json', 'w') as f: \ + json.dump(config, f, indent=2); \ +with open('/root/mineru.json', 'w') as f: \ + json.dump(config, f, indent=2); \ +print('✅ mineru.json 已產生'); \ +"; \ + fi -with open('/opt/mineru/mineru.json', 'w') as f: - json.dump(config, f, indent=2) +# 7.5 BabelDOC warmup +RUN set -ex && \ + export BABELDOC_CACHE_PATH="/root/.cache/babeldoc" && \ + if command -v babeldoc >/dev/null 2>&1; then \ + babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告"; \ + else \ + echo "⚠️ babeldoc 不可用,跳過 warmup"; \ + fi -with open('/root/mineru.json', 'w') as f: - json.dump(config, f, indent=2) +# 7.6 下載 tiktoken 編碼 +RUN python3 -c " \ +try: \ + import tiktoken; \ + for enc_name in ['cl100k_base', 'p50k_base', 'r50k_base']: \ + try: \ + enc = tiktoken.get_encoding(enc_name); \ + print(f'✅ tiktoken {enc_name} 已下載'); \ + except Exception as e: \ + print(f'⚠️ tiktoken {enc_name} 下載失敗: {e}'); \ +except ImportError: \ + print('⚠️ tiktoken 未安裝,跳過'); \ +" -print('✅ mineru.json 已產生') -print(json.dumps(config, indent=2)) -PYTHON -fi -EOF +# 7.7 清理下載快取 +RUN rm -rf /tmp/hf_download_cache /root/.cache/huggingface \ + /root/.cache/pip /root/.cache/uv && \ + find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true -# 階段 7.4:BabelDOC warmup + 資源下載 -# ⚠️ 關鍵修復:完整 warmup,確保所有資源已下載 -RUN echo "📥 [Stage 7.4] BabelDOC warmup..." && \ - export BABELDOC_CACHE_PATH="${BABELDOC_CACHE_DIR}" && \ - if command -v babeldoc >/dev/null 2>&1; then \ - echo "執行 babeldoc --warmup..." && \ - babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告" && \ - echo "驗證 BabelDOC 資源..." && \ - ls -la "${BABELDOC_CACHE_DIR}/" && \ - du -sh "${BABELDOC_CACHE_DIR}/" || true; \ - else \ - echo "⚠️ babeldoc 不可用,跳過 warmup"; \ - fi - -# 階段 7.5:下載 tiktoken 編碼(BabelDOC 依賴) -RUN </dev/null || true && \ - echo "✅ Cache 清理完成" - -# =================================== -# Stage 8: Final Image -# =================================== +# ============================================================================== +# Stage 8: Final Release Image +# ============================================================================== FROM python-tools AS release WORKDIR /app -# 從 models-download stage 複製模型 -COPY --from=models-download /opt/mineru /opt/mineru -COPY --from=models-download /opt/babeldoc /opt/babeldoc -COPY --from=models-download /root/mineru.json /root/mineru.json +# 8.1 從 models stage 複製模型和配置 +COPY --from=models /opt/convertx /opt/convertx +COPY --from=models /root/.cache/babeldoc /root/.cache/babeldoc +COPY --from=models /root/mineru.json /root/mineru.json -# 複製應用程式 +# 8.2 複製應用程式 COPY --from=install /temp/prod/node_modules node_modules COPY --from=prerelease /app/public/ /app/public/ COPY --from=prerelease /app/dist /app/dist -# ============================================================================== -# 安裝自訂字型 -# ============================================================================== +# 8.3 確保字型目錄完整(fonts stage 已安裝,這裡確保 COPY 覆蓋) RUN mkdir -p /usr/share/fonts/truetype/custom COPY fonts/ /usr/share/fonts/truetype/custom/ - -# 複製預下載的 ONNX 模型 -RUN mkdir -p /root/.cache/babeldoc/models COPY models/ /root/.cache/babeldoc/models/ -# 複製字型到 BabelDOC 目錄 -RUN mkdir -p /root/.cache/babeldoc/fonts && \ - cp /usr/share/fonts/truetype/custom/GoNotoKurrent-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \ - cp /usr/share/fonts/truetype/custom/SourceHanSerifCN-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \ - cp /usr/share/fonts/truetype/custom/SourceHanSerifTW-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \ - cp /usr/share/fonts/truetype/custom/SourceHanSerifJP-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \ - cp /usr/share/fonts/truetype/custom/SourceHanSerifKR-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true - -# 同步 BabelDOC cache 從固定目錄 -RUN if [ -d /opt/babeldoc/cache ]; then \ - cp -r /opt/babeldoc/cache/* /root/.cache/babeldoc/ 2>/dev/null || true; \ - fi - -# 更新字型 cache +# 8.4 更新字型快取 RUN fc-cache -fv # ============================================================================== # PDF 簽章憑證 # ============================================================================== RUN mkdir -p /app/certs && \ - openssl req -x509 -newkey rsa:2048 \ - -keyout /tmp/key.pem -out /tmp/cert.pem \ - -days 3650 -nodes \ - -subj "/CN=PDF Packager Default/O=ConvertX-CN/C=TW" && \ - openssl pkcs12 -export \ - -inkey /tmp/key.pem -in /tmp/cert.pem \ - -out /app/certs/default.p12 \ - -passout pass: && \ - rm -f /tmp/key.pem /tmp/cert.pem && \ - chmod 644 /app/certs/default.p12 - -# ============================================================================== -# VTracer -# ============================================================================== -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \ - else \ - VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \ - fi && \ - curl -L --retry 3 --retry-delay 5 --retry-all-errors \ - -o /tmp/vtracer.tar.gz \ - "https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \ - tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \ - mv /tmp/vtracer /usr/local/bin/vtracer && \ - chmod +x /usr/local/bin/vtracer && \ - rm /tmp/vtracer.tar.gz + openssl req -x509 -newkey rsa:2048 \ + -keyout /tmp/key.pem -out /tmp/cert.pem \ + -days 3650 -nodes \ + -subj "/CN=PDF Packager Default/O=ConvertX-CN/C=TW" && \ + openssl pkcs12 -export \ + -inkey /tmp/key.pem -in /tmp/cert.pem \ + -out /app/certs/default.p12 \ + -passout pass: && \ + rm -f /tmp/key.pem /tmp/cert.pem && \ + chmod 644 /app/certs/default.p12 # ============================================================================== # Locale 設定 # ============================================================================== RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \ - locale-gen + sed -i 's/# zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/' /etc/locale.gen && \ + sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen && \ + sed -i 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen && \ + sed -i 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen && \ + sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen && \ + sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \ + locale-gen # ============================================================================== # 最終清理 # ============================================================================== -RUN rm -rf /usr/share/doc/texlive* \ - && rm -rf /usr/share/texlive/texmf-dist/doc \ - && rm -rf /usr/share/doc/* \ - && rm -rf /usr/share/man/* \ - && rm -rf /usr/share/info/* \ - && rm -rf /tmp/* \ - && rm -rf /var/tmp/* +RUN rm -rf /usr/share/doc/texlive* && \ + rm -rf /usr/share/texlive/texmf-dist/doc && \ + rm -rf /usr/share/doc/* && \ + rm -rf /usr/share/man/* && \ + rm -rf /usr/share/info/* && \ + rm -rf /tmp/* && \ + rm -rf /var/tmp/* # 複製驗證腳本 COPY scripts/verify-models.sh /app/scripts/verify-models.sh -RUN chmod +x /app/scripts/verify-models.sh +COPY scripts/verify-installation.sh /app/scripts/verify-installation.sh +RUN chmod +x /app/scripts/*.sh # 創建資料目錄 RUN mkdir -p data # ============================================================================== -# 🔒 Runtime 離線驗證(關鍵步驟) -# ============================================================================== -# ⚠️ 此步驟模擬斷網環境,驗證所有工具可離線執行 +# 🔒 Runtime 離線驗證 # ============================================================================== RUN echo "======================================" && \ - echo "🔒 Runtime 離線驗證" && \ - echo "======================================" && \ - ARCH=$(uname -m) && \ - VALIDATION_PASSED=true && \ - \ - # 驗證 MinerU - echo "🔍 驗證 MinerU..." && \ - if [ "$ARCH" != "aarch64" ]; then \ - if command -v mineru >/dev/null 2>&1; then \ - echo " ✅ mineru 可執行: $(which mineru)"; \ - else \ - echo " ❌ mineru 不可執行" && VALIDATION_PASSED=false; \ - fi && \ - if [ -d "/opt/mineru/models/PDF-Extract-Kit-1.0" ]; then \ - echo " ✅ MinerU 模型目錄存在"; \ - else \ - echo " ❌ MinerU 模型目錄不存在" && VALIDATION_PASSED=false; \ - fi && \ - if [ -f "/root/mineru.json" ]; then \ - echo " ✅ mineru.json 存在"; \ - else \ - echo " ❌ mineru.json 不存在" && VALIDATION_PASSED=false; \ - fi; \ - else \ - echo " ⚠️ ARM64:跳過 MinerU 驗證"; \ - fi && \ - \ - # 驗證 BabelDOC - echo "🔍 驗證 BabelDOC..." && \ - if command -v babeldoc >/dev/null 2>&1; then \ - babeldoc --help >/dev/null 2>&1 && echo " ✅ babeldoc --help 成功" || echo " ⚠️ babeldoc --help 有警告"; \ - else \ - echo " ⚠️ babeldoc 不可用"; \ - fi && \ - \ - # 驗證 pdf2zh - echo "🔍 驗證 pdf2zh..." && \ - if command -v pdf2zh >/dev/null 2>&1; then \ - pdf2zh --help >/dev/null 2>&1 && echo " ✅ pdf2zh --help 成功" || echo " ⚠️ pdf2zh --help 有警告"; \ - else \ - echo " ⚠️ pdf2zh 不可用"; \ - fi && \ - \ - # 驗證 ImageMagick - echo "🔍 驗證 ImageMagick..." && \ - if command -v convert >/dev/null 2>&1; then \ - echo " ✅ ImageMagick 已安裝: $(convert --version | head -1)"; \ - else \ - echo " ❌ ImageMagick 未安裝" && VALIDATION_PASSED=false; \ - fi && \ - \ - # 驗證模型檔案 - echo "🔍 驗證 ONNX 模型..." && \ - if [ -f "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then \ - echo " ✅ DocLayout-YOLO ONNX 存在"; \ - else \ - echo " ⚠️ DocLayout-YOLO ONNX 不存在(將使用 COPY 複製)"; \ - fi && \ - \ - # 驗證字型 - echo "🔍 驗證字型..." && \ - FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l) && \ - echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \ - \ - echo "======================================" && \ - if [ "$VALIDATION_PASSED" = "true" ]; then \ - echo "✅ 離線驗證通過!"; \ - else \ - echo "❌ 離線驗證失敗!" && exit 1; \ - fi && \ - echo "======================================" + echo "🔒 Runtime 離線驗證" && \ + echo "======================================" && \ + ARCH=$(uname -m) && \ + VALIDATION_PASSED=true && \ + \ + # 驗證核心工具 + echo "🔍 驗證核心工具..." && \ + for cmd in ffmpeg convert gm vips inkscape pandoc soffice; do \ + if command -v ${cmd} >/dev/null 2>&1; then \ + echo " ✅ ${cmd}: $(which ${cmd})"; \ + else \ + echo " ❌ ${cmd}: 未找到" && VALIDATION_PASSED=false; \ + fi; \ + done && \ + \ + # 驗證 MinerU(僅 AMD64) + echo "🔍 驗證 MinerU..." && \ + if [ "$ARCH" != "aarch64" ]; then \ + if command -v mineru >/dev/null 2>&1; then \ + echo " ✅ mineru: $(which mineru)"; \ + else \ + echo " ❌ mineru 不可執行" && VALIDATION_PASSED=false; \ + fi && \ + if [ -d "/opt/convertx/models/mineru/PDF-Extract-Kit-1.0" ]; then \ + echo " ✅ MinerU 模型目錄存在"; \ + else \ + echo " ❌ MinerU 模型目錄不存在" && VALIDATION_PASSED=false; \ + fi && \ + if [ -f "/root/mineru.json" ]; then \ + echo " ✅ mineru.json 存在"; \ + else \ + echo " ❌ mineru.json 不存在" && VALIDATION_PASSED=false; \ + fi; \ + else \ + echo " ⚠️ ARM64:跳過 MinerU 驗證"; \ + fi && \ + \ + # 驗證 BabelDOC + echo "🔍 驗證 BabelDOC..." && \ + if command -v babeldoc >/dev/null 2>&1; then \ + echo " ✅ babeldoc: $(which babeldoc)"; \ + else \ + echo " ⚠️ babeldoc 不可用"; \ + fi && \ + \ + # 驗證 pdf2zh + echo "🔍 驗證 pdf2zh..." && \ + if command -v pdf2zh >/dev/null 2>&1; then \ + echo " ✅ pdf2zh: $(which pdf2zh)"; \ + else \ + echo " ⚠️ pdf2zh 不可用"; \ + fi && \ + \ + # 驗證 ImageMagick + echo "🔍 驗證 ImageMagick..." && \ + if command -v convert >/dev/null 2>&1; then \ + echo " ✅ ImageMagick: $(convert --version | head -1)"; \ + else \ + echo " ❌ ImageMagick 未安裝" && VALIDATION_PASSED=false; \ + fi && \ + \ + # 驗證 ONNX 模型 + echo "🔍 驗證 ONNX 模型..." && \ + if [ -f "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then \ + echo " ✅ DocLayout-YOLO ONNX 存在"; \ + else \ + echo " ⚠️ DocLayout-YOLO ONNX 不存在"; \ + fi && \ + \ + # 驗證字型 + echo "🔍 驗證字型..." && \ + FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l || echo "0") && \ + echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \ + \ + echo "======================================" && \ + if [ "$VALIDATION_PASSED" = "true" ]; then \ + echo "✅ 離線驗證通過!"; \ + else \ + echo "❌ 離線驗證失敗!" && exit 1; \ + fi && \ + echo "======================================" # ============================================================================== # 🔐 Runtime 環境變數(強制離線模式) @@ -666,6 +599,7 @@ ENV TRANSFORMERS_CACHE="/nonexistent" # MinerU 強制本地模型 ENV MINERU_MODEL_SOURCE="local" ENV MINERU_CONFIG="/root/mineru.json" +ENV MINERU_MODELS_DIR="/opt/convertx/models/mineru" # BabelDOC 離線模式 ENV BABELDOC_OFFLINE="1" diff --git a/Dockerfile.backup b/Dockerfile.backup deleted file mode 100644 index 4c9d909..0000000 --- a/Dockerfile.backup +++ /dev/null @@ -1,806 +0,0 @@ -# ============================================================================== -# 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" ] diff --git a/Dockerfile.offline b/Dockerfile.offline deleted file mode 100644 index 5100f79..0000000 --- a/Dockerfile.offline +++ /dev/null @@ -1,694 +0,0 @@ -# ============================================================================== -# ConvertX-CN 官方 Docker Image -# 版本:v0.1.16 - 完全離線化版本 -# ============================================================================== -# -# 📦 Image 說明: -# - 這是 ConvertX-CN 官方 Docker Hub Image 的生產 Dockerfile -# - ⚠️ 所有模型、字型、tokenizer 已在 build 階段完整預下載 -# - ⚠️ Runtime 完全離線運行(僅翻譯服務允許連網) -# -# 🔒 Offline-first 設計原則(硬性規則): -# 1. Runtime(docker run 後): -# ❌ 禁止任何模型、字型、tokenizer、metadata 下載 -# ❌ MinerU / BabelDOC / PDFMathTranslate 不得嘗試連網 -# ✅ 只有翻譯服務(Google / DeepL / Azure / OpenAI)允許連網 -# 2. Build time(docker build 時): -# ✅ 允許連網下載所有資源 -# ✅ 所有「可能會在 runtime 下載的東西」必須提前固定存放 -# -# 🤖 預下載模型清單: -# - PDFMathTranslate: DocLayout-YOLO ONNX(佈局分析) -# - BabelDOC: DocLayout-YOLO + 字型資源 + tiktoken -# - MinerU: PDF-Extract-Kit-1.0(Pipeline 模型) -# 包含:DocLayout-YOLO, YOLOv8 MFD, UniMERNet, PaddleOCR, LayoutReader, SLANet -# -# 🌍 內建語言支援: -# - OCR: 英文、繁體中文、簡體中文、日文、韓文、德文、法文 -# - Locale: en_US, zh_TW, zh_CN, ja_JP, ko_KR, de_DE, fr_FR -# - 字型: Noto CJK, Liberation, 標楷體, Source Han Serif -# - LaTeX: CJK、德文、法文、阿拉伯語、希伯來語 -# -# 📊 Image 大小:約 10-14 GB(含完整模型) -# -# ============================================================================== -# 🏗️ Multi-Stage Build 結構: -# Stage 1 [base]: Bun runtime 基礎 -# Stage 2 [install]: Node 依賴安裝 -# Stage 3 [prerelease]: 應用程式建構 -# Stage 4 [system-tools]: APT 系統工具 -# Stage 5 [python-base]: Python 基礎環境 -# Stage 6 [python-tools]: Python CLI 工具 -# Stage 7 [models-download]: 模型下載(獨立 stage) -# Stage 8 [final]: 最終 Image(合併所有層) -# ============================================================================== - -# =================================== -# Stage 1: Base - Bun Runtime -# =================================== -FROM debian:bookworm-slim AS base -LABEL org.opencontainers.image.source="https://github.com/pi-docket/ConvertX-CN" -LABEL org.opencontainers.image.description="ConvertX-CN - 完全離線化檔案轉換服務" -LABEL org.opencontainers.image.version="v0.1.16-offline" -WORKDIR /app - -# 設定非互動模式 -ENV DEBIAN_FRONTEND=noninteractive - -# 配置 APT 重試機制 -RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries - -# 安裝基礎工具 -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - unzip \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -# 安裝 Bun(根據架構選擇版本) -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \ - -o bun-linux-aarch64.zip \ - https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-aarch64.zip; \ - else \ - curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \ - -o bun-linux-x64-baseline.zip \ - https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-x64-baseline.zip; \ - fi && \ - unzip -j bun-linux-*.zip -d /usr/local/bin && \ - rm bun-linux-*.zip && \ - chmod +x /usr/local/bin/bun - -# =================================== -# Stage 2: Install - Node Dependencies -# =================================== -FROM base AS install - -# 開發依賴 -RUN mkdir -p /temp/dev -COPY package.json bun.lock /temp/dev/ -RUN cd /temp/dev && bun install --frozen-lockfile - -# 生產依賴 -RUN mkdir -p /temp/prod -COPY package.json bun.lock /temp/prod/ -RUN cd /temp/prod && bun install --frozen-lockfile --production - -# =================================== -# Stage 3: Prerelease - Build App -# =================================== -FROM base AS prerelease -WORKDIR /app -COPY --from=install /temp/dev/node_modules node_modules -COPY . . -RUN bun run build - -# =================================== -# Stage 4: System Tools -# =================================== -FROM base AS system-tools - -# 配置 APT -RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/80-retries \ - && echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries - -# 階段 4.1:基礎系統工具 + Locale -RUN echo "📦 [Stage 4.1] 安裝基礎系統工具..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - locales \ - ca-certificates \ - curl \ - openssl \ - git \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.2:核心轉換工具(輕量) -RUN echo "📦 [Stage 4.2] 安裝核心轉換工具..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - assimp-utils \ - dcraw \ - dvisvgm \ - ghostscript \ - graphicsmagick \ - mupdf-tools \ - poppler-utils \ - potrace \ - qpdf \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.3:安裝 dasel(從 GitHub 二進位) -RUN echo "📦 [Stage 4.3] 安裝 dasel..." && \ - ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then DASEL_ARCH="linux_arm64"; \ - else DASEL_ARCH="linux_amd64"; fi && \ - curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \ - "https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" \ - -o /usr/local/bin/dasel && \ - chmod +x /usr/local/bin/dasel - -# 階段 4.4:安裝 resvg(僅 x86_64) -RUN echo "📦 [Stage 4.4] 安裝 resvg..." && \ - ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - echo "⚠️ resvg 無 ARM64 版本,跳過"; \ - else \ - curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \ - "https://github.com/linebender/resvg/releases/download/v0.44.0/resvg-linux-x86_64.tar.gz" \ - -o /tmp/resvg.tar.gz && \ - tar -xzf /tmp/resvg.tar.gz -C /tmp/ && \ - mv /tmp/resvg /usr/local/bin/resvg && \ - chmod +x /usr/local/bin/resvg && \ - rm -rf /tmp/resvg.tar.gz; \ - fi - -# 階段 4.5:編譯安裝 deark -RUN echo "📦 [Stage 4.5] 編譯 deark..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - build-essential \ - && cd /tmp && \ - git clone --depth 1 https://github.com/jsummers/deark.git && \ - cd deark && make -j$(nproc) && \ - cp deark /usr/local/bin/deark && \ - chmod +x /usr/local/bin/deark && \ - cd / && rm -rf /tmp/deark && \ - apt-get remove -y build-essential && \ - apt-get autoremove -y && \ - rm -rf /var/lib/apt/lists/* - -# 階段 4.6:影音處理工具 -RUN echo "📦 [Stage 4.6] 安裝 FFmpeg..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - ffmpeg \ - libavcodec-extra \ - libva2 \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.7:圖像處理工具(含 ImageMagick) -# ⚠️ 修復:確保 ImageMagick 已安裝 -RUN echo "📦 [Stage 4.7] 安裝圖像處理工具(含 ImageMagick)..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - imagemagick \ - inkscape \ - libheif-examples \ - libjxl-tools \ - libvips-tools \ - xauth \ - xvfb \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.8:文件處理工具 -RUN echo "📦 [Stage 4.8] 安裝 Calibre + Pandoc..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - calibre \ - libemail-outlook-message-perl \ - pandoc \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.9:LibreOffice -RUN echo "📦 [Stage 4.9] 安裝 LibreOffice(需數分鐘)..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - libreoffice \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.10:TexLive 基礎 -RUN echo "📦 [Stage 4.10] 安裝 TexLive 基礎..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - texlive-base \ - texlive-latex-base \ - texlive-latex-recommended \ - texlive-fonts-recommended \ - texlive-xetex \ - latexmk \ - lmodern \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.11:TexLive 語言包 -RUN echo "📦 [Stage 4.11] 安裝 TexLive 語言包..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - texlive-lang-cjk \ - texlive-lang-german \ - texlive-lang-french \ - texlive-lang-arabic \ - texlive-lang-other \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.12:OCR 支援 -RUN echo "📦 [Stage 4.12] 安裝 Tesseract OCR..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - tesseract-ocr \ - tesseract-ocr-eng \ - tesseract-ocr-chi-tra \ - tesseract-ocr-chi-sim \ - tesseract-ocr-jpn \ - tesseract-ocr-kor \ - tesseract-ocr-deu \ - tesseract-ocr-fra \ - ocrmypdf \ - && rm -rf /var/lib/apt/lists/* - -# 階段 4.13:字型 -RUN echo "📦 [Stage 4.13] 安裝系統字型..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - fonts-noto-cjk \ - fonts-noto-core \ - fonts-noto-color-emoji \ - fonts-liberation \ - && rm -rf /var/lib/apt/lists/* - -# =================================== -# Stage 5: Python Base -# =================================== -FROM system-tools AS python-base - -# 階段 5.1:Python 基礎 + uv -RUN echo "📦 [Stage 5.1] 安裝 Python 基礎..." && \ - apt-get update --fix-missing && apt-get install -y --no-install-recommends \ - python3 \ - python3-pip \ - python3-venv \ - python3-numpy \ - python3-tinycss2 \ - python3-opencv \ - python3-img2pdf \ - && rm -rf /var/lib/apt/lists/* - -# 階段 5.2:安裝 uv(Python 套件管理器) -# ⚠️ 重要:使用 uv 作為主要安裝方式,確保一致性 -RUN echo "📦 [Stage 5.2] 安裝 uv..." && \ - pip3 install --no-cache-dir --break-system-packages uv && \ - uv --version - -# 設定 PATH -ENV PATH="/root/.local/bin:/usr/local/bin:${PATH}" - -# =================================== -# Stage 6: Python Tools -# =================================== -FROM python-base AS python-tools - -# 階段 6.1:安裝 huggingface_hub + endesive(PDF 簽章) -# ⚠️ pykcs11 需要 python3-dev 提供 Python.h -RUN echo "📦 [Stage 6.1] 安裝 huggingface_hub + endesive..." && \ - apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - swig \ - libpcsclite-dev \ - python3-dev \ - && uv pip install --system --break-system-packages --no-cache huggingface_hub endesive && \ - apt-get remove -y build-essential swig python3-dev && \ - apt-get autoremove -y && \ - rm -rf /var/lib/apt/lists/* - -# 階段 6.2:安裝 markitdown -RUN echo "📦 [Stage 6.2] 安裝 markitdown..." && \ - uv pip install --system --break-system-packages --no-cache "markitdown[all]" - -# 階段 6.3:安裝 pdf2zh -RUN echo "📦 [Stage 6.3] 安裝 pdf2zh..." && \ - uv pip install --system --break-system-packages --no-cache pdf2zh - -# 階段 6.4:安裝 babeldoc -RUN echo "📦 [Stage 6.4] 安裝 babeldoc..." && \ - uv pip install --system --break-system-packages --no-cache babeldoc || echo "⚠️ babeldoc 安裝可能有警告" - -# 階段 6.5:安裝 MinerU(⚠️ 關鍵修復) -# ⚠️ 只使用 system-level 安裝(uv pip install --system) -# ⚠️ 禁止使用 pipx(會導致 binary 不在 PATH) -RUN echo "📦 [Stage 6.5] 安裝 MinerU(system-level only)..." && \ - ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - echo "⚠️ ARM64:MinerU 不支援,跳過"; \ - else \ - echo "🔧 使用 uv pip install --system(官方推薦)..." && \ - uv pip install --system --break-system-packages --no-cache -U "mineru[all]" && \ - echo "✅ 驗證 MinerU 安裝..." && \ - which mineru && \ - mineru --version || echo "(版本資訊可能不可用)"; \ - fi - -# =================================== -# Stage 7: Models Download(獨立 Stage) -# =================================== -FROM python-tools AS models-download - -# 模型目錄定義 -ENV MINERU_MODELS_DIR="/opt/mineru/models" -ENV BABELDOC_CACHE_DIR="/opt/babeldoc/cache" -ENV HF_HOME="/tmp/hf_download_cache" - -# 階段 7.1:創建目錄結構 -RUN mkdir -p "${MINERU_MODELS_DIR}" && \ - mkdir -p "${BABELDOC_CACHE_DIR}/models" && \ - mkdir -p "${BABELDOC_CACHE_DIR}/fonts" && \ - mkdir -p "${BABELDOC_CACHE_DIR}/cmap" && \ - mkdir -p "${BABELDOC_CACHE_DIR}/tiktoken" - -# 階段 7.2:下載 MinerU Pipeline 模型 -# ⚠️ 關鍵修復:使用顯式下載,存放到固定目錄 -RUN </dev/null 2>&1; then \ - echo "執行 babeldoc --warmup..." && \ - babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告" && \ - echo "驗證 BabelDOC 資源..." && \ - ls -la "${BABELDOC_CACHE_DIR}/" && \ - du -sh "${BABELDOC_CACHE_DIR}/" || true; \ - else \ - echo "⚠️ babeldoc 不可用,跳過 warmup"; \ - fi - -# 階段 7.5:下載 tiktoken 編碼(BabelDOC 依賴) -RUN </dev/null || true && \ - echo "✅ Cache 清理完成" - -# =================================== -# Stage 8: Final Image -# =================================== -FROM python-tools AS release -WORKDIR /app - -# 從 models-download stage 複製模型 -COPY --from=models-download /opt/mineru /opt/mineru -COPY --from=models-download /opt/babeldoc /opt/babeldoc -COPY --from=models-download /root/mineru.json /root/mineru.json - -# 複製應用程式 -COPY --from=install /temp/prod/node_modules node_modules -COPY --from=prerelease /app/public/ /app/public/ -COPY --from=prerelease /app/dist /app/dist - -# ============================================================================== -# 安裝自訂字型 -# ============================================================================== -RUN mkdir -p /usr/share/fonts/truetype/custom -COPY fonts/ /usr/share/fonts/truetype/custom/ - -# 複製預下載的 ONNX 模型 -RUN mkdir -p /root/.cache/babeldoc/models -COPY models/ /root/.cache/babeldoc/models/ - -# 複製字型到 BabelDOC 目錄 -RUN mkdir -p /root/.cache/babeldoc/fonts && \ - cp /usr/share/fonts/truetype/custom/GoNotoKurrent-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \ - cp /usr/share/fonts/truetype/custom/SourceHanSerifCN-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \ - cp /usr/share/fonts/truetype/custom/SourceHanSerifTW-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \ - cp /usr/share/fonts/truetype/custom/SourceHanSerifJP-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \ - cp /usr/share/fonts/truetype/custom/SourceHanSerifKR-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true - -# 同步 BabelDOC cache 從固定目錄 -RUN if [ -d /opt/babeldoc/cache ]; then \ - cp -r /opt/babeldoc/cache/* /root/.cache/babeldoc/ 2>/dev/null || true; \ - fi - -# 更新字型 cache -RUN fc-cache -fv - -# ============================================================================== -# PDF 簽章憑證 -# ============================================================================== -RUN mkdir -p /app/certs && \ - openssl req -x509 -newkey rsa:2048 \ - -keyout /tmp/key.pem -out /tmp/cert.pem \ - -days 3650 -nodes \ - -subj "/CN=PDF Packager Default/O=ConvertX-CN/C=TW" && \ - openssl pkcs12 -export \ - -inkey /tmp/key.pem -in /tmp/cert.pem \ - -out /app/certs/default.p12 \ - -passout pass: && \ - rm -f /tmp/key.pem /tmp/cert.pem && \ - chmod 644 /app/certs/default.p12 - -# ============================================================================== -# VTracer -# ============================================================================== -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \ - else \ - VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \ - fi && \ - curl -L --retry 3 --retry-delay 5 --retry-all-errors \ - -o /tmp/vtracer.tar.gz \ - "https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \ - tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \ - mv /tmp/vtracer /usr/local/bin/vtracer && \ - chmod +x /usr/local/bin/vtracer && \ - rm /tmp/vtracer.tar.gz - -# ============================================================================== -# Locale 設定 -# ============================================================================== -RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen && \ - sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \ - locale-gen - -# ============================================================================== -# 最終清理 -# ============================================================================== -RUN rm -rf /usr/share/doc/texlive* \ - && rm -rf /usr/share/texlive/texmf-dist/doc \ - && rm -rf /usr/share/doc/* \ - && rm -rf /usr/share/man/* \ - && rm -rf /usr/share/info/* \ - && rm -rf /tmp/* \ - && rm -rf /var/tmp/* - -# 複製驗證腳本 -COPY scripts/verify-models.sh /app/scripts/verify-models.sh -RUN chmod +x /app/scripts/verify-models.sh - -# 創建資料目錄 -RUN mkdir -p data - -# ============================================================================== -# 🔒 Runtime 離線驗證(關鍵步驟) -# ============================================================================== -# ⚠️ 此步驟模擬斷網環境,驗證所有工具可離線執行 -# ============================================================================== -RUN echo "======================================" && \ - echo "🔒 Runtime 離線驗證" && \ - echo "======================================" && \ - ARCH=$(uname -m) && \ - VALIDATION_PASSED=true && \ - \ - # 驗證 MinerU - echo "🔍 驗證 MinerU..." && \ - if [ "$ARCH" != "aarch64" ]; then \ - if command -v mineru >/dev/null 2>&1; then \ - echo " ✅ mineru 可執行: $(which mineru)"; \ - else \ - echo " ❌ mineru 不可執行" && VALIDATION_PASSED=false; \ - fi && \ - if [ -d "/opt/mineru/models/PDF-Extract-Kit-1.0" ]; then \ - echo " ✅ MinerU 模型目錄存在"; \ - else \ - echo " ❌ MinerU 模型目錄不存在" && VALIDATION_PASSED=false; \ - fi && \ - if [ -f "/root/mineru.json" ]; then \ - echo " ✅ mineru.json 存在"; \ - else \ - echo " ❌ mineru.json 不存在" && VALIDATION_PASSED=false; \ - fi; \ - else \ - echo " ⚠️ ARM64:跳過 MinerU 驗證"; \ - fi && \ - \ - # 驗證 BabelDOC - echo "🔍 驗證 BabelDOC..." && \ - if command -v babeldoc >/dev/null 2>&1; then \ - babeldoc --help >/dev/null 2>&1 && echo " ✅ babeldoc --help 成功" || echo " ⚠️ babeldoc --help 有警告"; \ - else \ - echo " ⚠️ babeldoc 不可用"; \ - fi && \ - \ - # 驗證 pdf2zh - echo "🔍 驗證 pdf2zh..." && \ - if command -v pdf2zh >/dev/null 2>&1; then \ - pdf2zh --help >/dev/null 2>&1 && echo " ✅ pdf2zh --help 成功" || echo " ⚠️ pdf2zh --help 有警告"; \ - else \ - echo " ⚠️ pdf2zh 不可用"; \ - fi && \ - \ - # 驗證 ImageMagick - echo "🔍 驗證 ImageMagick..." && \ - if command -v convert >/dev/null 2>&1; then \ - echo " ✅ ImageMagick 已安裝: $(convert --version | head -1)"; \ - else \ - echo " ❌ ImageMagick 未安裝" && VALIDATION_PASSED=false; \ - fi && \ - \ - # 驗證模型檔案 - echo "🔍 驗證 ONNX 模型..." && \ - if [ -f "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then \ - echo " ✅ DocLayout-YOLO ONNX 存在"; \ - else \ - echo " ⚠️ DocLayout-YOLO ONNX 不存在(將使用 COPY 複製)"; \ - fi && \ - \ - # 驗證字型 - echo "🔍 驗證字型..." && \ - FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l) && \ - echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \ - \ - echo "======================================" && \ - if [ "$VALIDATION_PASSED" = "true" ]; then \ - echo "✅ 離線驗證通過!"; \ - else \ - echo "❌ 離線驗證失敗!" && exit 1; \ - fi && \ - echo "======================================" - -# ============================================================================== -# 🔐 Runtime 環境變數(強制離線模式) -# ============================================================================== - -# 1️⃣ 系統 Locale -ENV LANG=zh_TW.UTF-8 -ENV LC_ALL=zh_TW.UTF-8 - -# 2️⃣ Headless 環境 -ENV QT_QPA_PLATFORM="offscreen" -ENV DISPLAY=":99" -ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox" -ENV CALIBRE_USE_SYSTEM_THEME="0" - -# 3️⃣ 翻譯服務設定(這是唯一允許連網的服務) -ENV PDFMATHTRANSLATE_SERVICE="google" -ENV BABELDOC_SERVICE="google" - -# 4️⃣ 🔒 強制離線模式(禁止模型/資源下載) -# HuggingFace 完全離線 -ENV HF_HOME="/nonexistent" -ENV HF_HUB_OFFLINE="1" -ENV TRANSFORMERS_OFFLINE="1" -ENV HF_DATASETS_OFFLINE="1" -ENV TRANSFORMERS_CACHE="/nonexistent" - -# MinerU 強制本地模型 -ENV MINERU_MODEL_SOURCE="local" -ENV MINERU_CONFIG="/root/mineru.json" - -# BabelDOC 離線模式 -ENV BABELDOC_OFFLINE="1" -ENV BABELDOC_CACHE_PATH="/root/.cache/babeldoc" - -# 禁止 pip 安裝 -ENV PIP_NO_INDEX="1" -ENV PIP_NO_CACHE_DIR="1" - -# 5️⃣ PDF 簽章設定 -ENV PDF_SIGN_P12_PATH="/app/certs/default.p12" -ENV PDF_SIGN_P12_PASSWORD="" -ENV PDF_SIGN_REASON="ConvertX-CN PDF Packager" -ENV PDF_SIGN_LOCATION="Taiwan" -ENV PDF_SIGN_CONTACT="convertx-cn@localhost" - -# 6️⃣ 應用程式設定 -ENV PANDOC_PDF_ENGINE=pdflatex -ENV NODE_ENV=production - -# ============================================================================== -# 暴露端口 & 啟動 -# ============================================================================== -EXPOSE 3000/tcp - -ENTRYPOINT [ "bun", "run", "dist/src/index.js" ] diff --git a/scripts/download-models.sh b/scripts/download-models.sh new file mode 100644 index 0000000..f7708ac --- /dev/null +++ b/scripts/download-models.sh @@ -0,0 +1,306 @@ +#!/usr/bin/env bash +# ============================================================================== +# ConvertX-CN 模型下載腳本 +# ============================================================================== +# 用途:下載所有 AI 模型(MinerU, BabelDOC, PDFMathTranslate) +# 執行環境:Docker build stage (models-download) +# +# ⚠️ 重要:此腳本在 build time 執行,下載所有模型到固定目錄 +# Runtime 完全離線,不得有任何下載行為 +# ============================================================================== + +set -euo pipefail + +# 顏色輸出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}📥 [INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}✅ [SUCCESS]${NC} $1"; } +log_warn() { echo -e "${YELLOW}⚠️ [WARN]${NC} $1"; } +log_error() { echo -e "${RED}❌ [ERROR]${NC} $1"; } + +# 架構檢測 +ARCH=$(uname -m) +log_info "檢測架構: ${ARCH}" + +# ============================================================================== +# 目錄定義 +# ============================================================================== +MODELS_BASE_DIR="/opt/convertx/models" +MINERU_MODELS_DIR="${MODELS_BASE_DIR}/mineru" +BABELDOC_CACHE_DIR="/root/.cache/babeldoc" +BABELDOC_MODELS_DIR="${BABELDOC_CACHE_DIR}/models" +BABELDOC_FONTS_DIR="${BABELDOC_CACHE_DIR}/fonts" +BABELDOC_CMAP_DIR="${BABELDOC_CACHE_DIR}/cmap" +BABELDOC_TIKTOKEN_DIR="${BABELDOC_CACHE_DIR}/tiktoken" + +# ============================================================================== +# 創建目錄結構 +# ============================================================================== +create_directories() { + log_info "創建模型目錄結構..." + + mkdir -p "${MINERU_MODELS_DIR}" + mkdir -p "${BABELDOC_MODELS_DIR}" + mkdir -p "${BABELDOC_FONTS_DIR}" + mkdir -p "${BABELDOC_CMAP_DIR}" + mkdir -p "${BABELDOC_TIKTOKEN_DIR}" + + log_success "目錄結構創建完成" +} + +# ============================================================================== +# 下載 MinerU Pipeline 模型 +# ============================================================================== +download_mineru_models() { + log_info "下載 MinerU Pipeline 模型..." + + if [ "${ARCH}" = "aarch64" ]; then + log_warn "ARM64:跳過 MinerU 模型下載(不支援)" + return 0 + fi + + python3 << 'PYTHON' +from huggingface_hub import snapshot_download +import os + +models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru') +print(f'下載 PDF-Extract-Kit-1.0 到 {models_dir}...') + +try: + snapshot_download( + repo_id='opendatalab/PDF-Extract-Kit-1.0', + local_dir=f'{models_dir}/PDF-Extract-Kit-1.0', + local_dir_use_symlinks=False, + resume_download=True + ) + print('✅ PDF-Extract-Kit-1.0 下載完成') +except Exception as e: + print(f'❌ 下載失敗: {e}') + raise +PYTHON + + # 顯示模型大小 + log_info "MinerU 模型目錄內容:" + du -sh "${MINERU_MODELS_DIR}/PDF-Extract-Kit-1.0" 2>/dev/null || true + ls -la "${MINERU_MODELS_DIR}/" 2>/dev/null || true + + log_success "MinerU 模型下載完成" +} + +# ============================================================================== +# 產生 MinerU 配置檔 +# ============================================================================== +generate_mineru_config() { + log_info "產生 MinerU 配置檔..." + + if [ "${ARCH}" = "aarch64" ]; then + log_warn "ARM64:跳過 MinerU 配置" + return 0 + fi + + python3 << 'PYTHON' +import json +import os + +mineru_models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru') + +config = { + 'models-dir': { + 'pipeline': f'{mineru_models_dir}/PDF-Extract-Kit-1.0', + 'vlm': '' + }, + 'model-source': 'local', + 'latex-delimiter-config': { + 'display': {'left': '@@', 'right': '@@'}, + 'inline': {'left': '@', 'right': '@'} + } +} + +# 寫入多個位置 +config_paths = [ + '/opt/convertx/mineru.json', + '/root/mineru.json' +] + +for path in config_paths: + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w') as f: + json.dump(config, f, indent=2) + print(f'✅ 已產生: {path}') + +print(json.dumps(config, indent=2)) +PYTHON + + log_success "MinerU 配置檔產生完成" +} + +# ============================================================================== +# BabelDOC warmup(下載所有資源) +# ============================================================================== +babeldoc_warmup() { + log_info "執行 BabelDOC warmup..." + + export BABELDOC_CACHE_PATH="${BABELDOC_CACHE_DIR}" + + if command -v babeldoc >/dev/null 2>&1; then + babeldoc --warmup 2>&1 || { + log_warn "BabelDOC warmup 可能有警告,但繼續" + } + + log_info "BabelDOC 快取內容:" + ls -la "${BABELDOC_CACHE_DIR}/" 2>/dev/null || true + du -sh "${BABELDOC_CACHE_DIR}/" 2>/dev/null || true + else + log_warn "babeldoc 不可用,跳過 warmup" + fi + + log_success "BabelDOC warmup 完成" +} + +# ============================================================================== +# 下載 tiktoken 編碼 +# ============================================================================== +download_tiktoken() { + log_info "下載 tiktoken 編碼..." + + python3 << 'PYTHON' +try: + import tiktoken + + encodings = ['cl100k_base', 'p50k_base', 'r50k_base'] + + for enc_name in encodings: + try: + enc = tiktoken.get_encoding(enc_name) + print(f'✅ tiktoken {enc_name} 已下載') + except Exception as e: + print(f'⚠️ tiktoken {enc_name} 下載失敗: {e}') + +except ImportError: + print('⚠️ tiktoken 未安裝,跳過') +PYTHON + + log_success "tiktoken 編碼下載完成" +} + +# ============================================================================== +# 複製預下載的 ONNX 模型(從 COPY 複製) +# ============================================================================== +setup_onnx_models() { + log_info "設定 ONNX 模型..." + + # 如果有預下載的模型,複製到正確位置 + local ONNX_MODEL="doclayout_yolo_docstructbench_imgsz1024.onnx" + local SRC_PATHS=( + "/app/models/${ONNX_MODEL}" + "/tmp/models/${ONNX_MODEL}" + ) + + for src in "${SRC_PATHS[@]}"; do + if [ -f "${src}" ]; then + cp "${src}" "${BABELDOC_MODELS_DIR}/" + log_success "已複製 ONNX 模型: ${src} -> ${BABELDOC_MODELS_DIR}/" + return 0 + fi + done + + log_warn "未找到預下載的 ONNX 模型,將依賴 babeldoc --warmup" +} + +# ============================================================================== +# 驗證下載結果 +# ============================================================================== +verify_downloads() { + log_info "驗證模型下載..." + + echo "" + echo "模型目錄統計:" + echo "==============================================" + + # MinerU + if [ "${ARCH}" != "aarch64" ]; then + if [ -d "${MINERU_MODELS_DIR}/PDF-Extract-Kit-1.0" ]; then + local size + size=$(du -sh "${MINERU_MODELS_DIR}/PDF-Extract-Kit-1.0" 2>/dev/null | awk '{print $1}') + echo " ✅ MinerU Pipeline: ${size}" + else + echo " ❌ MinerU Pipeline: 不存在" + fi + + if [ -f "/root/mineru.json" ]; then + echo " ✅ MinerU 配置檔: 已產生" + else + echo " ❌ MinerU 配置檔: 不存在" + fi + else + echo " ⚠️ MinerU: ARM64 跳過" + fi + + # BabelDOC + if [ -d "${BABELDOC_CACHE_DIR}" ]; then + local size + size=$(du -sh "${BABELDOC_CACHE_DIR}" 2>/dev/null | awk '{print $1}') + echo " ✅ BabelDOC 快取: ${size}" + else + echo " ⚠️ BabelDOC 快取: 不存在" + fi + + # ONNX 模型 + if [ -f "${BABELDOC_MODELS_DIR}/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then + local size + size=$(ls -lh "${BABELDOC_MODELS_DIR}/doclayout_yolo_docstructbench_imgsz1024.onnx" | awk '{print $5}') + echo " ✅ DocLayout-YOLO ONNX: ${size}" + else + echo " ⚠️ DocLayout-YOLO ONNX: 不存在" + fi + + echo "==============================================" + log_success "驗證完成" +} + +# ============================================================================== +# 清理下載快取 +# ============================================================================== +cleanup_cache() { + log_info "清理下載快取..." + + rm -rf /tmp/hf_download_cache 2>/dev/null || true + rm -rf /root/.cache/huggingface 2>/dev/null || true + rm -rf /root/.cache/pip 2>/dev/null || true + rm -rf /root/.cache/uv 2>/dev/null || true + + # 清理 Python cache + find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + + log_success "快取清理完成" +} + +# ============================================================================== +# 主程式 +# ============================================================================== +main() { + log_info "==========================================" + log_info "開始下載模型 (${ARCH})" + log_info "==========================================" + + create_directories + download_mineru_models + generate_mineru_config + setup_onnx_models + babeldoc_warmup + download_tiktoken + verify_downloads + cleanup_cache + + log_success "==========================================" + log_success "所有模型下載完成!" + log_success "==========================================" +} + +# 執行 +main "$@" diff --git a/scripts/install-fonts.sh b/scripts/install-fonts.sh new file mode 100644 index 0000000..bb47491 --- /dev/null +++ b/scripts/install-fonts.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +# ============================================================================== +# ConvertX-CN 字型安裝腳本 +# ============================================================================== +# 用途:安裝所有系統字型和自訂字型 +# 執行環境:Docker build stage (fonts) +# ============================================================================== + +set -euo pipefail + +# 顏色輸出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}🔤 [INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}✅ [SUCCESS]${NC} $1"; } +log_warn() { echo -e "${YELLOW}⚠️ [WARN]${NC} $1"; } +log_error() { echo -e "${RED}❌ [ERROR]${NC} $1"; } + +# 目錄定義 +CUSTOM_FONTS_DIR="/usr/share/fonts/truetype/custom" +BABELDOC_FONTS_DIR="/root/.cache/babeldoc/fonts" + +# ============================================================================== +# 系統字型(APT) +# ============================================================================== +install_system_fonts() { + log_info "安裝系統字型..." + apt-get update --fix-missing + apt-get install -y --no-install-recommends \ + fonts-noto-cjk \ + fonts-noto-cjk-extra \ + fonts-noto-core \ + fonts-noto-color-emoji \ + fonts-liberation \ + fonts-dejavu-core \ + fonts-dejavu-extra \ + fonts-freefont-ttf \ + fonts-droid-fallback + rm -rf /var/lib/apt/lists/* + log_success "系統字型安裝完成" +} + +# ============================================================================== +# 複製自訂字型 +# ============================================================================== +install_custom_fonts() { + log_info "安裝自訂字型..." + + # 創建目錄 + mkdir -p "${CUSTOM_FONTS_DIR}" + mkdir -p "${BABELDOC_FONTS_DIR}" + + # 檢查並複製字型 + local FONTS_SRC="${1:-/app/fonts}" + + if [ -d "${FONTS_SRC}" ]; then + log_info "複製字型從 ${FONTS_SRC}..." + + # 複製所有 TTF 字型 + find "${FONTS_SRC}" -type f \( -name "*.ttf" -o -name "*.TTF" -o -name "*.otf" -o -name "*.OTF" \) \ + -exec cp -v {} "${CUSTOM_FONTS_DIR}/" \; 2>/dev/null || true + + # 設定權限 + chmod 644 "${CUSTOM_FONTS_DIR}"/* 2>/dev/null || true + + # 統計 + local COUNT + COUNT=$(find "${CUSTOM_FONTS_DIR}" -type f | wc -l) + log_success "已複製 ${COUNT} 個自訂字型" + else + log_warn "字型來源目錄 ${FONTS_SRC} 不存在" + fi +} + +# ============================================================================== +# 同步字型到 BabelDOC 目錄 +# ============================================================================== +sync_babeldoc_fonts() { + log_info "同步字型到 BabelDOC 目錄..." + + mkdir -p "${BABELDOC_FONTS_DIR}" + + # 複製關鍵字型到 BabelDOC + local fonts=( + "GoNotoKurrent-Regular.ttf" + "SourceHanSerifCN-Regular.ttf" + "SourceHanSerifTW-Regular.ttf" + "SourceHanSerifJP-Regular.ttf" + "SourceHanSerifKR-Regular.ttf" + "BiauKai.ttf" + ) + + for font in "${fonts[@]}"; do + if [ -f "${CUSTOM_FONTS_DIR}/${font}" ]; then + cp "${CUSTOM_FONTS_DIR}/${font}" "${BABELDOC_FONTS_DIR}/" + log_info " 已同步: ${font}" + fi + done + + log_success "BabelDOC 字型同步完成" +} + +# ============================================================================== +# 更新字型快取 +# ============================================================================== +update_font_cache() { + log_info "更新字型快取..." + fc-cache -fv + log_success "字型快取更新完成" +} + +# ============================================================================== +# 驗證字型安裝 +# ============================================================================== +verify_fonts() { + log_info "驗證字型安裝..." + + echo "系統字型目錄統計:" + echo " /usr/share/fonts: $(find /usr/share/fonts -type f \( -name '*.ttf' -o -name '*.otf' \) | wc -l) 個字型" + echo " ${CUSTOM_FONTS_DIR}: $(find "${CUSTOM_FONTS_DIR}" -type f 2>/dev/null | wc -l) 個字型" + echo " ${BABELDOC_FONTS_DIR}: $(find "${BABELDOC_FONTS_DIR}" -type f 2>/dev/null | wc -l) 個字型" + + # 列出自訂字型 + if [ -d "${CUSTOM_FONTS_DIR}" ]; then + echo "" + echo "自訂字型清單:" + ls -la "${CUSTOM_FONTS_DIR}/" 2>/dev/null || echo " (無)" + fi + + # 驗證關鍵字型 + echo "" + echo "關鍵字型驗證:" + local required_fonts=( + "Noto Sans CJK" + "Source Han Serif" + "Liberation" + ) + + for font in "${required_fonts[@]}"; do + if fc-list | grep -qi "${font}"; then + echo " ✅ ${font}: 已安裝" + else + echo " ⚠️ ${font}: 未找到" + fi + done + + log_success "字型驗證完成" +} + +# ============================================================================== +# 主程式 +# ============================================================================== +main() { + log_info "==========================================" + log_info "開始安裝字型" + log_info "==========================================" + + install_system_fonts + install_custom_fonts "${1:-/app/fonts}" + sync_babeldoc_fonts + update_font_cache + verify_fonts + + log_success "==========================================" + log_success "所有字型安裝完成!" + log_success "==========================================" +} + +# 執行(可接收字型來源目錄參數) +main "$@" diff --git a/scripts/install-python-tools.sh b/scripts/install-python-tools.sh new file mode 100644 index 0000000..d7f4fb2 --- /dev/null +++ b/scripts/install-python-tools.sh @@ -0,0 +1,219 @@ +#!/usr/bin/env bash +# ============================================================================== +# ConvertX-CN Python 工具安裝腳本 +# ============================================================================== +# 用途:安裝所有 Python CLI 工具 +# 執行環境:Docker build stage (python-tools) +# ============================================================================== + +set -euo pipefail + +# 顏色輸出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}🐍 [INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}✅ [SUCCESS]${NC} $1"; } +log_warn() { echo -e "${YELLOW}⚠️ [WARN]${NC} $1"; } +log_error() { echo -e "${RED}❌ [ERROR]${NC} $1"; } + +# 架構檢測 +ARCH=$(uname -m) +log_info "檢測架構: ${ARCH}" + +# ============================================================================== +# Python 基礎環境 +# ============================================================================== +install_python_base() { + log_info "安裝 Python 基礎環境..." + apt-get update --fix-missing + apt-get install -y --no-install-recommends \ + python3 \ + python3-pip \ + python3-venv \ + python3-numpy \ + python3-tinycss2 \ + python3-opencv \ + python3-img2pdf + rm -rf /var/lib/apt/lists/* + log_success "Python 基礎環境安裝完成: $(python3 --version)" +} + +# ============================================================================== +# uv 套件管理器 +# ============================================================================== +install_uv() { + log_info "安裝 uv 套件管理器..." + pip3 install --no-cache-dir --break-system-packages uv + log_success "uv 安裝完成: $(uv --version)" +} + +# ============================================================================== +# huggingface_hub(模型下載) +# ============================================================================== +install_huggingface_hub() { + log_info "安裝 huggingface_hub..." + uv pip install --system --break-system-packages --no-cache huggingface_hub + log_success "huggingface_hub 安裝完成" +} + +# ============================================================================== +# endesive(PDF 簽章) +# ============================================================================== +install_endesive() { + log_info "安裝 endesive(PDF 簽章)..." + + # endesive 需要編譯依賴 + apt-get update --fix-missing + apt-get install -y --no-install-recommends \ + build-essential \ + swig \ + libpcsclite-dev \ + python3-dev + + uv pip install --system --break-system-packages --no-cache endesive + + # 清理編譯依賴 + apt-get remove -y build-essential swig python3-dev + apt-get autoremove -y + rm -rf /var/lib/apt/lists/* + + log_success "endesive 安裝完成" +} + +# ============================================================================== +# markitdown +# ============================================================================== +install_markitdown() { + log_info "安裝 markitdown..." + uv pip install --system --break-system-packages --no-cache "markitdown[all]" + log_success "markitdown 安裝完成" +} + +# ============================================================================== +# pdf2zh(PDFMathTranslate) +# ============================================================================== +install_pdf2zh() { + log_info "安裝 pdf2zh..." + uv pip install --system --break-system-packages --no-cache pdf2zh + + if command -v pdf2zh >/dev/null 2>&1; then + log_success "pdf2zh 安裝完成" + else + log_warn "pdf2zh 可能安裝到非標準路徑" + fi +} + +# ============================================================================== +# babeldoc +# ============================================================================== +install_babeldoc() { + log_info "安裝 babeldoc..." + uv pip install --system --break-system-packages --no-cache babeldoc || { + log_warn "babeldoc 安裝可能有警告,但繼續" + } + + if command -v babeldoc >/dev/null 2>&1; then + log_success "babeldoc 安裝完成" + else + log_warn "babeldoc 可能安裝到非標準路徑" + fi +} + +# ============================================================================== +# MinerU +# ============================================================================== +install_mineru() { + log_info "安裝 MinerU..." + + if [ "${ARCH}" = "aarch64" ]; then + log_warn "ARM64:MinerU 不完全支援,跳過安裝" + return 0 + fi + + # 使用 system-level 安裝 + uv pip install --system --break-system-packages --no-cache -U "mineru[all]" + + # 驗證安裝 + if command -v mineru >/dev/null 2>&1; then + log_success "MinerU 安裝完成: $(which mineru)" + mineru --version 2>/dev/null || log_info "(版本資訊可能不可用)" + else + log_warn "MinerU 可能安裝到非標準路徑" + fi +} + +# ============================================================================== +# tiktoken(BabelDOC 依賴) +# ============================================================================== +install_tiktoken() { + log_info "安裝 tiktoken..." + uv pip install --system --break-system-packages --no-cache tiktoken + log_success "tiktoken 安裝完成" +} + +# ============================================================================== +# 驗證安裝 +# ============================================================================== +verify_installations() { + log_info "驗證 Python 工具安裝..." + + local tools=( + "python3:Python" + "pip3:pip" + "uv:uv" + "markitdown:markitdown" + "pdf2zh:pdf2zh" + "babeldoc:babeldoc" + ) + + # MinerU 僅在 AMD64 + if [ "${ARCH}" != "aarch64" ]; then + tools+=("mineru:MinerU") + fi + + echo "" + echo "工具驗證結果:" + for tool_pair in "${tools[@]}"; do + local cmd="${tool_pair%%:*}" + local name="${tool_pair#*:}" + + if command -v "${cmd}" >/dev/null 2>&1; then + echo " ✅ ${name}: $(which "${cmd}")" + else + echo " ⚠️ ${name}: 未在 PATH 中找到" + fi + done + + log_success "驗證完成" +} + +# ============================================================================== +# 主程式 +# ============================================================================== +main() { + log_info "==========================================" + log_info "開始安裝 Python 工具 (${ARCH})" + log_info "==========================================" + + install_python_base + install_uv + install_huggingface_hub + install_endesive + install_markitdown + install_pdf2zh + install_babeldoc + install_mineru + install_tiktoken + verify_installations + + log_success "==========================================" + log_success "所有 Python 工具安裝完成!" + log_success "==========================================" +} + +# 執行 +main "$@" diff --git a/scripts/install-system-tools.sh b/scripts/install-system-tools.sh new file mode 100644 index 0000000..ba2c4a3 --- /dev/null +++ b/scripts/install-system-tools.sh @@ -0,0 +1,305 @@ +#!/usr/bin/env bash +# ============================================================================== +# ConvertX-CN 系統工具安裝腳本 +# ============================================================================== +# 用途:安裝所有系統級轉換工具和依賴 +# 執行環境:Docker build stage (system-tools) +# ============================================================================== + +set -euo pipefail + +# 顏色輸出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log_info() { echo -e "${BLUE}📦 [INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}✅ [SUCCESS]${NC} $1"; } +log_warn() { echo -e "${YELLOW}⚠️ [WARN]${NC} $1"; } +log_error() { echo -e "${RED}❌ [ERROR]${NC} $1"; } + +# 架構檢測 +ARCH=$(uname -m) +log_info "檢測架構: ${ARCH}" + +# ============================================================================== +# APT 配置 +# ============================================================================== +configure_apt() { + log_info "配置 APT 重試機制..." + cat > /etc/apt/apt.conf.d/80-retries </dev/null 2>&1; then + local path + path=$(which "${cmd}") + echo " ✅ ${name}: ${path}" + ((PASS++)) + else + if [ "${required}" = "true" ]; then + echo " ❌ ${name}: 未找到(必要)" + ((FAIL++)) + else + echo " ⚠️ ${name}: 未找到(可選)" + ((WARN++)) + fi + fi +} + +check_file() { + local path="$1" + local name="$2" + local required="${3:-true}" + local arch_only="${4:-all}" + + # 架構檢查 + if [ "${arch_only}" != "all" ] && [ "${ARCH}" != "${arch_only}" ]; then + echo " ⏭️ ${name}: 跳過(僅 ${arch_only})" + ((SKIP++)) + return 0 + fi + + if [ -f "${path}" ]; then + local size + size=$(ls -lh "${path}" 2>/dev/null | awk '{print $5}') + echo " ✅ ${name}: ${size}" + ((PASS++)) + else + if [ "${required}" = "true" ]; then + echo " ❌ ${name}: 不存在" + ((FAIL++)) + else + echo " ⚠️ ${name}: 不存在(可選)" + ((WARN++)) + fi + fi +} + +check_dir() { + local path="$1" + local name="$2" + local required="${3:-true}" + local arch_only="${4:-all}" + + # 架構檢查 + if [ "${arch_only}" != "all" ] && [ "${ARCH}" != "${arch_only}" ]; then + echo " ⏭️ ${name}: 跳過(僅 ${arch_only})" + ((SKIP++)) + return 0 + fi + + if [ -d "${path}" ]; then + local size + size=$(du -sh "${path}" 2>/dev/null | awk '{print $1}') + echo " ✅ ${name}: ${size}" + ((PASS++)) + else + if [ "${required}" = "true" ]; then + echo " ❌ ${name}: 不存在" + ((FAIL++)) + else + echo " ⚠️ ${name}: 不存在(可選)" + ((WARN++)) + fi + fi +} + +# ============================================================================== +# 1. 核心系統工具 +# ============================================================================== +verify_system_tools() { + echo "" + echo "📦 核心系統工具" + echo "==============================================" + + check_command "ffmpeg" "FFmpeg" + check_command "convert" "ImageMagick" + check_command "gm" "GraphicsMagick" + check_command "vips" "libvips" + check_command "inkscape" "Inkscape" + check_command "resvg" "resvg" "false" "x86_64" + check_command "dvisvgm" "dvisvgm" + check_command "xelatex" "XeLaTeX" + check_command "soffice" "LibreOffice" + check_command "pandoc" "Pandoc" + check_command "ebook-convert" "Calibre" + check_command "assimp" "Assimp" + check_command "dasel" "dasel" + check_command "msgconvert" "msgconvert" + check_command "deark" "deark" + check_command "vtracer" "vtracer" + check_command "heif-convert" "libheif" + check_command "djxl" "libjxl" + check_command "ocrmypdf" "OCRmyPDF" + check_command "tesseract" "Tesseract" + check_command "potrace" "Potrace" + check_command "qpdf" "qpdf" + check_command "gs" "Ghostscript" +} + +# ============================================================================== +# 2. Python 工具 +# ============================================================================== +verify_python_tools() { + echo "" + echo "🐍 Python 工具" + echo "==============================================" + + check_command "python3" "Python" + check_command "pip3" "pip" + check_command "uv" "uv" + check_command "markitdown" "markitdown" + check_command "pdf2zh" "PDFMathTranslate" + check_command "babeldoc" "BabelDOC" + check_command "mineru" "MinerU" "true" "x86_64" +} + +# ============================================================================== +# 3. 模型檔案 +# ============================================================================== +verify_models() { + echo "" + echo "🤖 AI 模型" + echo "==============================================" + + # MinerU 模型 + check_dir "/opt/convertx/models/mineru/PDF-Extract-Kit-1.0" "MinerU Pipeline" "true" "x86_64" + check_file "/root/mineru.json" "MinerU 配置檔" "true" "x86_64" + + # BabelDOC 模型 + check_file "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" "DocLayout-YOLO ONNX" "false" + check_dir "/root/.cache/babeldoc" "BabelDOC 快取" "false" +} + +# ============================================================================== +# 4. 字型 +# ============================================================================== +verify_fonts() { + echo "" + echo "🔤 字型" + echo "==============================================" + + check_file "/usr/share/fonts/truetype/custom/GoNotoKurrent-Regular.ttf" "Noto 通用字型" + check_file "/usr/share/fonts/truetype/custom/SourceHanSerifCN-Regular.ttf" "思源宋體(簡體)" + check_file "/usr/share/fonts/truetype/custom/SourceHanSerifTW-Regular.ttf" "思源宋體(繁體)" + check_file "/usr/share/fonts/truetype/custom/SourceHanSerifJP-Regular.ttf" "思源宋體(日文)" + check_file "/usr/share/fonts/truetype/custom/SourceHanSerifKR-Regular.ttf" "思源宋體(韓文)" + check_file "/usr/share/fonts/truetype/custom/BiauKai.ttf" "標楷體" "false" + + # 系統字型 + echo "" + echo " 系統字型統計:" + local noto_count + noto_count=$(fc-list | grep -ci "noto" || echo "0") + echo " - Noto 字型: ${noto_count} 個" + + local total_count + total_count=$(fc-list | wc -l) + echo " - 總字型數: ${total_count} 個" +} + +# ============================================================================== +# 5. OCR 語言 +# ============================================================================== +verify_ocr_languages() { + echo "" + echo "📝 OCR 語言" + echo "==============================================" + + local languages=("eng" "chi_tra" "chi_sim" "jpn" "kor" "deu" "fra") + + for lang in "${languages[@]}"; do + if tesseract --list-langs 2>&1 | grep -q "^${lang}$"; then + echo " ✅ ${lang}: 已安裝" + ((PASS++)) + else + echo " ❌ ${lang}: 未安裝" + ((FAIL++)) + fi + done +} + +# ============================================================================== +# 6. Locale 設定 +# ============================================================================== +verify_locales() { + echo "" + echo "🌍 Locale 設定" + echo "==============================================" + + local locales=("en_US.UTF-8" "zh_TW.UTF-8" "zh_CN.UTF-8" "ja_JP.UTF-8" "ko_KR.UTF-8") + + for locale in "${locales[@]}"; do + if locale -a 2>/dev/null | grep -q "${locale}"; then + echo " ✅ ${locale}: 已啟用" + ((PASS++)) + else + echo " ⚠️ ${locale}: 未啟用" + ((WARN++)) + fi + done +} + +# ============================================================================== +# 7. PDF 簽章 +# ============================================================================== +verify_pdf_signing() { + echo "" + echo "🔐 PDF 簽章" + echo "==============================================" + + check_file "/app/certs/default.p12" "預設憑證" "false" +} + +# ============================================================================== +# 8. 離線模式驗證 +# ============================================================================== +verify_offline_mode() { + echo "" + echo "🔒 離線模式環境變數" + echo "==============================================" + + local vars=( + "HF_HUB_OFFLINE" + "TRANSFORMERS_OFFLINE" + "MINERU_MODEL_SOURCE" + "BABELDOC_OFFLINE" + ) + + for var in "${vars[@]}"; do + local value="${!var:-}" + if [ -n "${value}" ]; then + echo " ✅ ${var}=${value}" + ((PASS++)) + else + echo " ⚠️ ${var}: 未設定" + ((WARN++)) + fi + done +} + +# ============================================================================== +# 總結 +# ============================================================================== +print_summary() { + echo "" + echo "==============================================" + echo "📊 驗證總結" + echo "==============================================" + echo " ✅ 通過: ${PASS}" + echo " ❌ 失敗: ${FAIL}" + echo " ⚠️ 警告: ${WARN}" + echo " ⏭️ 跳過: ${SKIP}" + echo "==============================================" + + if [ "${FAIL}" -gt 0 ]; then + log_error "驗證失敗!有 ${FAIL} 個必要項目未通過" + return 1 + else + log_success "驗證通過!" + return 0 + fi +} + +# ============================================================================== +# 主程式 +# ============================================================================== +main() { + log_info "==========================================" + log_info "ConvertX-CN 安裝驗證" + log_info "架構: ${ARCH}" + log_info "==========================================" + + verify_system_tools + verify_python_tools + verify_models + verify_fonts + verify_ocr_languages + verify_locales + verify_pdf_signing + verify_offline_mode + + print_summary +} + +# 執行 +main "$@" diff --git a/scripts/verify-models.sh b/scripts/verify-models.sh index cee7f32..1f403c2 100644 --- a/scripts/verify-models.sh +++ b/scripts/verify-models.sh @@ -1,17 +1,18 @@ -#!/bin/bash +#!/usr/bin/env bash # ============================================================================== -# ConvertX-CN 模型驗證腳本 v2.0 +# ConvertX-CN 模型驗證腳本 v3.0 # ============================================================================== # # 用途:驗證 Docker Image 中的預下載模型是否完整 # 執行:docker exec /app/scripts/verify-models.sh # +# 版本:v3.0 - 適配重構後的目錄結構 # ============================================================================== -set -e +set -euo pipefail echo "========================================" -echo "🔍 ConvertX-CN 模型驗證 v2.0" +echo "🔍 ConvertX-CN 模型驗證 v3.0" echo "========================================" echo "" @@ -21,12 +22,18 @@ FAIL=0 WARN=0 ARCH=$(uname -m) +# 新的模型目錄結構 +MODELS_BASE_DIR="/opt/convertx/models" +MINERU_MODELS_DIR="${MODELS_BASE_DIR}/mineru" +BABELDOC_CACHE_DIR="/root/.cache/babeldoc" + # 檢查函數 check_file() { local path="$1" local name="$2" if [ -f "$path" ]; then - local size=$(ls -lh "$path" | awk '{print $5}') + local size + size=$(ls -lh "$path" | awk '{print $5}') echo "✅ $name: $path ($size)" ((PASS++)) else @@ -40,7 +47,8 @@ check_dir() { local name="$2" local required="${3:-false}" if [ -d "$path" ]; then - local size=$(du -sh "$path" 2>/dev/null | awk '{print $1}') + local size + size=$(du -sh "$path" 2>/dev/null | awk '{print $1}') echo "✅ $name: $path ($size)" ((PASS++)) else @@ -59,7 +67,7 @@ check_dir() { # ============================================================================== echo "📦 PDFMathTranslate / BabelDOC 模型" echo "----------------------------------------" -check_file "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" "DocLayout-YOLO ONNX" +check_file "${BABELDOC_CACHE_DIR}/models/doclayout_yolo_docstructbench_imgsz1024.onnx" "DocLayout-YOLO ONNX" echo "" # ============================================================================== @@ -72,10 +80,11 @@ check_file "/usr/share/fonts/truetype/custom/SourceHanSerifCN-Regular.ttf" "思 check_file "/usr/share/fonts/truetype/custom/SourceHanSerifTW-Regular.ttf" "思源宋體(繁體)" check_file "/usr/share/fonts/truetype/custom/SourceHanSerifJP-Regular.ttf" "思源宋體(日文)" check_file "/usr/share/fonts/truetype/custom/SourceHanSerifKR-Regular.ttf" "思源宋體(韓文)" +check_file "/usr/share/fonts/truetype/custom/BiauKai.ttf" "標楷體" echo "" # ============================================================================== -# 3. MinerU 模型(新路徑:/opt/mineru) +# 3. MinerU 模型(新路徑:/opt/convertx/models/mineru) # ============================================================================== echo "📦 MinerU 模型" echo "----------------------------------------" @@ -83,8 +92,9 @@ if [ "$ARCH" = "aarch64" ]; then echo "⚠️ ARM64 架構:MinerU 不支援,跳過驗證" ((WARN++)) else - check_dir "/opt/mineru/models/PDF-Extract-Kit-1.0" "PDF-Extract-Kit-1.0 Pipeline" "true" + check_dir "${MINERU_MODELS_DIR}/PDF-Extract-Kit-1.0" "PDF-Extract-Kit-1.0 Pipeline" "true" check_file "/root/mineru.json" "MinerU 配置檔" + check_file "/opt/convertx/mineru.json" "MinerU 配置檔(備份)" fi echo "" @@ -93,9 +103,9 @@ echo "" # ============================================================================== echo "📦 BabelDOC 快取" echo "----------------------------------------" -check_dir "/root/.cache/babeldoc" "BabelDOC 快取" -check_dir "/root/.cache/babeldoc/models" "BabelDOC Models" -check_dir "/root/.cache/babeldoc/fonts" "BabelDOC Fonts" +check_dir "${BABELDOC_CACHE_DIR}" "BabelDOC 快取" +check_dir "${BABELDOC_CACHE_DIR}/models" "BabelDOC Models" +check_dir "${BABELDOC_CACHE_DIR}/fonts" "BabelDOC Fonts" echo "" # ============================================================================== diff --git a/scripts/verify-offline.sh b/scripts/verify-offline.sh index 56db57b..9bca718 100644 --- a/scripts/verify-offline.sh +++ b/scripts/verify-offline.sh @@ -130,7 +130,7 @@ else check_command "mineru" "MinerU CLI" # 檢查模型目錄 - MINERU_MODELS="/opt/mineru/models/PDF-Extract-Kit-1.0" + MINERU_MODELS="/opt/convertx/models/mineru/PDF-Extract-Kit-1.0" check_dir "$MINERU_MODELS" "MinerU Pipeline 模型" "true" # 檢查配置檔