From e862cb6667d4b596a0c0ba287db36e4b0d3c854d Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 25 Jan 2026 13:17:43 +0800 Subject: [PATCH] docs: add Dockerfile refactoring documentation v0.1.17 --- Dockerfile | 604 +++++++++++++-------------- docs/開發指南/Dockerfile-重構說明.md | 183 ++++++++ 2 files changed, 485 insertions(+), 302 deletions(-) create mode 100644 docs/開發指南/Dockerfile-重構說明.md diff --git a/Dockerfile b/Dockerfile index 6eea403..40d165b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,36 +54,36 @@ ENV DEBIAN_FRONTEND=noninteractive # 配置 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 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \ - echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries + 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 && \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - curl \ - unzip \ - ca-certificates && \ - rm -rf /var/lib/apt/lists/* + apt-get update && \ + apt-get install -y --no-install-recommends \ + curl \ + unzip \ + ca-certificates && \ + rm -rf /var/lib/apt/lists/* # 安裝 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 + 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 @@ -116,121 +116,121 @@ FROM base AS system-tools # 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 + 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.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/* + apt-get install -y --no-install-recommends \ + locales ca-certificates curl wget unzip openssl git xz-utils && \ + 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/* + 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.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 + 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.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 + 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.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/* + 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.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 + 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.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/* + apt-get install -y --no-install-recommends \ + ffmpeg libavcodec-extra libva2 && \ + 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/* + apt-get install -y --no-install-recommends \ + imagemagick inkscape libheif-examples libjxl-tools \ + libvips-tools xauth xvfb && \ + 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/* + apt-get install -y --no-install-recommends \ + calibre libemail-outlook-message-perl pandoc && \ + 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/* + apt-get install -y --no-install-recommends libreoffice && \ + 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/* + 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.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/* + 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.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/* + 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: Fonts(拆分安裝) @@ -239,11 +239,11 @@ FROM system-tools AS fonts # 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/* + 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 複製自訂字型 RUN mkdir -p /usr/share/fonts/truetype/custom @@ -251,12 +251,12 @@ COPY fonts/ /usr/share/fonts/truetype/custom/ # 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 + 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 # 5.4 更新字型快取 RUN fc-cache -fv @@ -268,10 +268,10 @@ 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/* + 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 @@ -281,12 +281,12 @@ 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/* + 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.5 markitdown RUN uv pip install --system --break-system-packages --no-cache "markitdown[all]" @@ -296,16 +296,16 @@ RUN uv pip install --system --break-system-packages --no-cache pdf2zh # 6.7 babeldoc RUN uv pip install --system --break-system-packages --no-cache babeldoc || \ - echo "⚠️ babeldoc 安裝可能有警告" + echo "⚠️ babeldoc 安裝可能有警告" # 6.8 MinerU(僅 AMD64) RUN set -ex && \ - ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - echo "⚠️ ARM64:MinerU 不支援,跳過安裝"; \ - else \ - uv pip install --system --break-system-packages --no-cache -U "mineru[all]"; \ - fi + ARCH=$(uname -m) && \ + if [ "$ARCH" = "aarch64" ]; then \ + echo "⚠️ ARM64:MinerU 不支援,跳過安裝"; \ + else \ + uv pip install --system --break-system-packages --no-cache -U "mineru[all]"; \ + fi # 6.9 tiktoken RUN uv pip install --system --break-system-packages --no-cache tiktoken @@ -324,95 +324,95 @@ ENV BABELDOC_CACHE_DIR="/root/.cache/babeldoc" # 7.1 創建目錄結構 RUN mkdir -p /opt/convertx/models/mineru && \ - mkdir -p /root/.cache/babeldoc/models && \ - mkdir -p /root/.cache/babeldoc/fonts && \ - mkdir -p /root/.cache/babeldoc/cmap && \ - mkdir -p /root/.cache/babeldoc/tiktoken + mkdir -p /root/.cache/babeldoc/models && \ + mkdir -p /root/.cache/babeldoc/fonts && \ + mkdir -p /root/.cache/babeldoc/cmap && \ + mkdir -p /root/.cache/babeldoc/tiktoken # 7.2 複製預下載的 ONNX 模型 COPY models/ /root/.cache/babeldoc/models/ # 7.3 下載 MinerU Pipeline 模型(僅 AMD64) RUN set -ex && \ - ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - echo "⚠️ ARM64:跳過 MinerU 模型下載"; \ - else \ - python3 -c " \ -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}...'); \ -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 下載完成'); \ -"; \ - fi + ARCH=$(uname -m) && \ + if [ "$ARCH" = "aarch64" ]; then \ + echo "⚠️ ARM64:跳過 MinerU 模型下載"; \ + else \ + python3 -c " \ + 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}...'); \ + 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 下載完成'); \ + "; \ + fi # 7.4 產生 MinerU 配置檔 RUN set -ex && \ - ARCH=$(uname -m) && \ - mkdir -p /opt/convertx && \ - if [ "$ARCH" = "aarch64" ]; then \ - echo '{"models-dir":{"pipeline":"","vlm":""},"model-source":"local","note":"ARM64 - MinerU not supported"}' > /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 + ARCH=$(uname -m) && \ + mkdir -p /opt/convertx && \ + if [ "$ARCH" = "aarch64" ]; then \ + echo '{"models-dir":{"pipeline":"","vlm":""},"model-source":"local","note":"ARM64 - MinerU not supported"}' > /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 # 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 + 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 # 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 未安裝,跳過'); \ -" + 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 未安裝,跳過'); \ + " # 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 + /root/.cache/pip /root/.cache/uv && \ + find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true # ============================================================================== # Stage 8: Final Release Image @@ -442,39 +442,39 @@ 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 + 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/* + 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 @@ -488,87 +488,87 @@ RUN mkdir -p data # 🔒 Runtime 離線驗證 # ============================================================================== RUN 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 "======================================" + 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 環境變數(強制離線模式) diff --git a/docs/開發指南/Dockerfile-重構說明.md b/docs/開發指南/Dockerfile-重構說明.md new file mode 100644 index 0000000..547c4d0 --- /dev/null +++ b/docs/開發指南/Dockerfile-重構說明.md @@ -0,0 +1,183 @@ +# ConvertX-CN Dockerfile 重構說明 + +## 版本:v0.1.17 + +此次重構完全重新設計了 Docker 建構流程,以達成以下目標: + +1. **完全離線運行**:所有模型、字型在 build 階段下載完成 +2. **Multi-Arch 支援**:同時支援 linux/amd64 和 linux/arm64 +3. **可維護性**:每個安裝步驟拆分為獨立 RUN +4. **可追蹤性**:每個 stage 職責明確 + +--- + +## 🏗️ Multi-Stage Build 結構 + +| Stage | 名稱 | RUN 數量 | 說明 | +|-------|------|----------|------| +| 1 | `base` | 3 | Bun runtime 基礎環境 | +| 2 | `install` | 4 | Node.js 依賴安裝 | +| 3 | `prerelease` | 1 | 應用程式建構 | +| 4 | `system-tools` | 14 | APT 系統工具 | +| 5 | `fonts` | 4 | 系統字型 + 自訂字型 | +| 6 | `python-tools` | 9 | Python CLI 工具 | +| 7 | `models` | 7 | AI 模型下載 | +| 8 | `release` | 多個 | 最終 Image | + +--- + +## 📦 各 Stage 安裝內容 + +### Stage 4: system-tools(14 個獨立 RUN) + +| RUN | 內容 | +|-----|------| +| 4.1 | APT 配置 | +| 4.2 | 基礎系統工具(curl, git, etc.) | +| 4.3 | 核心轉換工具(ghostscript, graphicsmagick, etc.) | +| 4.4 | dasel | +| 4.5 | resvg(僅 AMD64) | +| 4.6 | deark(編譯安裝) | +| 4.7 | vtracer | +| 4.8 | FFmpeg | +| 4.9 | 圖像處理工具(ImageMagick, Inkscape, vips) | +| 4.10 | 文件處理工具(Calibre, Pandoc) | +| 4.11 | LibreOffice | +| 4.12 | TexLive 基礎 | +| 4.13 | TexLive 語言包 | +| 4.14 | Tesseract OCR | + +### Stage 5: fonts(4 個獨立 RUN) + +| RUN | 內容 | +|-----|------| +| 5.1 | 系統字型(Noto CJK, Liberation 等) | +| 5.2 | 複製自訂字型 | +| 5.3 | 設定 BabelDOC 字型目錄 | +| 5.4 | 更新字型快取 | + +### Stage 6: python-tools(9 個獨立 RUN) + +| RUN | 內容 | +|-----|------| +| 6.1 | Python 基礎環境 | +| 6.2 | uv 套件管理器 | +| 6.3 | huggingface_hub | +| 6.4 | endesive(PDF 簽章) | +| 6.5 | markitdown | +| 6.6 | pdf2zh(PDFMathTranslate) | +| 6.7 | babeldoc | +| 6.8 | MinerU(僅 AMD64) | +| 6.9 | tiktoken | + +### Stage 7: models(7 個獨立 RUN) + +| RUN | 內容 | +|-----|------| +| 7.1 | 創建目錄結構 | +| 7.2 | 複製預下載的 ONNX 模型 | +| 7.3 | 下載 MinerU Pipeline 模型(僅 AMD64) | +| 7.4 | 產生 MinerU 配置檔 | +| 7.5 | BabelDOC warmup | +| 7.6 | 下載 tiktoken 編碼 | +| 7.7 | 清理下載快取 | + +--- + +## 📁 模型目錄結構 + +``` +/opt/convertx/ +├── models/ +│ └── mineru/ +│ └── PDF-Extract-Kit-1.0/ +└── mineru.json + +/root/.cache/babeldoc/ +├── models/ +│ └── doclayout_yolo_docstructbench_imgsz1024.onnx +├── fonts/ +├── cmap/ +└── tiktoken/ +``` + +--- + +## 🌍 Multi-Arch 處理 + +### AMD64(完整功能) + +所有工具和模型都必須正確安裝,任何失敗都會導致 build 失敗。 + +### ARM64(安全降級) + +以下功能會被跳過,但 build 不會失敗: + +| 工具/模型 | 原因 | Log 訊息 | +|-----------|------|----------| +| resvg | 無 ARM64 預編譯版本 | `⚠️ resvg 無 ARM64 版本,跳過` | +| MinerU | 依賴僅支援 x86_64 | `⚠️ ARM64:MinerU 不支援,跳過安裝` | +| MinerU 模型 | 跟隨 MinerU | `⚠️ ARM64:跳過 MinerU 模型下載` | + +--- + +## 🔒 離線模式環境變數 + +```bash +# HuggingFace 完全離線 +HF_HOME="/nonexistent" +HF_HUB_OFFLINE="1" +TRANSFORMERS_OFFLINE="1" +HF_DATASETS_OFFLINE="1" + +# MinerU 強制本地模型 +MINERU_MODEL_SOURCE="local" +MINERU_CONFIG="/root/mineru.json" +MINERU_MODELS_DIR="/opt/convertx/models/mineru" + +# BabelDOC 離線模式 +BABELDOC_OFFLINE="1" +BABELDOC_CACHE_PATH="/root/.cache/babeldoc" + +# 禁止 pip 安裝 +PIP_NO_INDEX="1" +``` + +--- + +## 🔧 Build 指令 + +### 單架構 build(本機測試) +```bash +docker build -t convertx-cn:latest . +``` + +### Multi-arch build(推送到 Docker Hub) +```bash +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --tag convertx/convertx-cn:latest \ + --push . +``` + +--- + +## 📊 預估 Image 大小 + +| 架構 | 大小 | +|------|------| +| AMD64 | 10-14 GB | +| ARM64 | 6-8 GB(無 MinerU 模型)| + +--- + +## ✅ 驗證方式 + +### Build 時驗證 +Dockerfile 最後階段會自動執行驗證,失敗會中止 build。 + +### Runtime 驗證 +```bash +docker exec /app/scripts/verify-models.sh +docker exec /app/scripts/verify-installation.sh +```