docs: add Dockerfile refactoring documentation v0.1.17

This commit is contained in:
Your Name 2026-01-25 13:17:43 +08:00
parent 8867eef33e
commit e862cb6667
2 changed files with 485 additions and 302 deletions

View file

@ -54,36 +54,36 @@ ENV DEBIAN_FRONTEND=noninteractive
# 配置 APT 重試機制 # 配置 APT 重試機制
RUN set -ex && \ RUN set -ex && \
echo 'Acquire::Retries "5";' > /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::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \ echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \ echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
# 安裝基礎工具 # 安裝基礎工具
RUN set -ex && \ RUN set -ex && \
apt-get update && \ apt-get update && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
curl \ curl \
unzip \ unzip \
ca-certificates && \ ca-certificates && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 安裝 Bun根據架構選擇版本 # 安裝 Bun根據架構選擇版本
ARG BUN_VERSION=1.3.6 ARG BUN_VERSION=1.3.6
RUN set -ex && \ RUN set -ex && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
BUN_ASSET="bun-linux-aarch64.zip"; \ BUN_ASSET="bun-linux-aarch64.zip"; \
else \ else \
BUN_ASSET="bun-linux-x64-baseline.zip"; \ BUN_ASSET="bun-linux-x64-baseline.zip"; \
fi && \ fi && \
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \ curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
-o /tmp/bun.zip \ -o /tmp/bun.zip \
"https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${BUN_ASSET}" && \ "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${BUN_ASSET}" && \
unzip -j /tmp/bun.zip -d /usr/local/bin && \ unzip -j /tmp/bun.zip -d /usr/local/bin && \
rm /tmp/bun.zip && \ rm /tmp/bun.zip && \
chmod +x /usr/local/bin/bun chmod +x /usr/local/bin/bun
# ============================================================================== # ==============================================================================
# Stage 2: Install - Node Dependencies # Stage 2: Install - Node Dependencies
@ -116,121 +116,121 @@ FROM base AS system-tools
# 4.1 配置 APT # 4.1 配置 APT
RUN set -ex && \ RUN set -ex && \
echo 'Acquire::Retries "5";' > /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::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \ echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/80-retries && \ echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/80-retries && \
echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
# 4.2 基礎系統工具 # 4.2 基礎系統工具
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
locales ca-certificates curl wget unzip openssl git xz-utils && \ locales ca-certificates curl wget unzip openssl git xz-utils && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.3 核心轉換工具 # 4.3 核心轉換工具
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
assimp-utils dcraw dvisvgm ghostscript graphicsmagick \ assimp-utils dcraw dvisvgm ghostscript graphicsmagick \
mupdf-tools poppler-utils potrace qpdf && \ mupdf-tools poppler-utils potrace qpdf && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.4 daselJSON/YAML/TOML 轉換) # 4.4 daselJSON/YAML/TOML 轉換)
RUN set -ex && \ RUN set -ex && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then DASEL_ARCH="linux_arm64"; \ if [ "$ARCH" = "aarch64" ]; then DASEL_ARCH="linux_arm64"; \
else DASEL_ARCH="linux_amd64"; fi && \ else DASEL_ARCH="linux_amd64"; fi && \
curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \ curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \
"https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" \ "https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" \
-o /usr/local/bin/dasel && \ -o /usr/local/bin/dasel && \
chmod +x /usr/local/bin/dasel chmod +x /usr/local/bin/dasel
# 4.5 resvg僅 AMD64 # 4.5 resvg僅 AMD64
RUN set -ex && \ RUN set -ex && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
echo "⚠️ resvg 無 ARM64 版本,跳過"; \ echo "⚠️ resvg 無 ARM64 版本,跳過"; \
else \ else \
curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \ 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" \ "https://github.com/linebender/resvg/releases/download/v0.44.0/resvg-linux-x86_64.tar.gz" \
-o /tmp/resvg.tar.gz && \ -o /tmp/resvg.tar.gz && \
tar -xzf /tmp/resvg.tar.gz -C /tmp/ && \ tar -xzf /tmp/resvg.tar.gz -C /tmp/ && \
mv /tmp/resvg /usr/local/bin/resvg && \ mv /tmp/resvg /usr/local/bin/resvg && \
chmod +x /usr/local/bin/resvg && \ chmod +x /usr/local/bin/resvg && \
rm -rf /tmp/resvg.tar.gz; \ rm -rf /tmp/resvg.tar.gz; \
fi fi
# 4.6 deark編譯安裝 # 4.6 deark編譯安裝
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends build-essential && \ apt-get install -y --no-install-recommends build-essential && \
cd /tmp && git clone --depth 1 https://github.com/jsummers/deark.git && \ cd /tmp && git clone --depth 1 https://github.com/jsummers/deark.git && \
cd deark && make -j$(nproc) && \ cd deark && make -j$(nproc) && \
cp deark /usr/local/bin/deark && chmod +x /usr/local/bin/deark && \ cp deark /usr/local/bin/deark && chmod +x /usr/local/bin/deark && \
cd / && rm -rf /tmp/deark && \ cd / && rm -rf /tmp/deark && \
apt-get remove -y build-essential && apt-get autoremove -y && \ apt-get remove -y build-essential && apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.7 vtracer # 4.7 vtracer
RUN set -ex && \ RUN set -ex && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \ VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \
else \ else \
VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \ VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \
fi && \ fi && \
curl -L --retry 3 --retry-delay 5 --retry-all-errors \ curl -L --retry 3 --retry-delay 5 --retry-all-errors \
-o /tmp/vtracer.tar.gz \ -o /tmp/vtracer.tar.gz \
"https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \ "https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \
tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \ tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \
mv /tmp/vtracer /usr/local/bin/vtracer && \ mv /tmp/vtracer /usr/local/bin/vtracer && \
chmod +x /usr/local/bin/vtracer && \ chmod +x /usr/local/bin/vtracer && \
rm -rf /tmp/vtracer.tar.gz rm -rf /tmp/vtracer.tar.gz
# 4.8 FFmpeg # 4.8 FFmpeg
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
ffmpeg libavcodec-extra libva2 && \ ffmpeg libavcodec-extra libva2 && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.9 圖像處理工具ImageMagick, Inkscape, vips 等) # 4.9 圖像處理工具ImageMagick, Inkscape, vips 等)
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
imagemagick inkscape libheif-examples libjxl-tools \ imagemagick inkscape libheif-examples libjxl-tools \
libvips-tools xauth xvfb && \ libvips-tools xauth xvfb && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.10 文件處理工具Calibre, Pandoc # 4.10 文件處理工具Calibre, Pandoc
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
calibre libemail-outlook-message-perl pandoc && \ calibre libemail-outlook-message-perl pandoc && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.11 LibreOffice # 4.11 LibreOffice
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends libreoffice && \ apt-get install -y --no-install-recommends libreoffice && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.12 TexLive 基礎 # 4.12 TexLive 基礎
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
texlive-base texlive-latex-base texlive-latex-recommended \ texlive-base texlive-latex-base texlive-latex-recommended \
texlive-fonts-recommended texlive-xetex latexmk lmodern && \ texlive-fonts-recommended texlive-xetex latexmk lmodern && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.13 TexLive 語言包 # 4.13 TexLive 語言包
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
texlive-lang-cjk texlive-lang-german texlive-lang-french \ texlive-lang-cjk texlive-lang-german texlive-lang-french \
texlive-lang-arabic texlive-lang-other && \ texlive-lang-arabic texlive-lang-other && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 4.14 Tesseract OCR # 4.14 Tesseract OCR
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-tra \ tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-tra \
tesseract-ocr-chi-sim tesseract-ocr-jpn tesseract-ocr-kor \ tesseract-ocr-chi-sim tesseract-ocr-jpn tesseract-ocr-kor \
tesseract-ocr-deu tesseract-ocr-fra ocrmypdf && \ tesseract-ocr-deu tesseract-ocr-fra ocrmypdf && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# ============================================================================== # ==============================================================================
# Stage 5: Fonts拆分安裝 # Stage 5: Fonts拆分安裝
@ -239,11 +239,11 @@ FROM system-tools AS fonts
# 5.1 系統字型 # 5.1 系統字型
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-core \ fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-core \
fonts-noto-color-emoji fonts-liberation fonts-dejavu-core \ fonts-noto-color-emoji fonts-liberation fonts-dejavu-core \
fonts-dejavu-extra fonts-freefont-ttf fonts-droid-fallback && \ fonts-dejavu-extra fonts-freefont-ttf fonts-droid-fallback && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 5.2 複製自訂字型 # 5.2 複製自訂字型
RUN mkdir -p /usr/share/fonts/truetype/custom RUN mkdir -p /usr/share/fonts/truetype/custom
@ -251,12 +251,12 @@ COPY fonts/ /usr/share/fonts/truetype/custom/
# 5.3 設定 BabelDOC 字型目錄 # 5.3 設定 BabelDOC 字型目錄
RUN mkdir -p /root/.cache/babeldoc/fonts && \ RUN mkdir -p /root/.cache/babeldoc/fonts && \
for font in GoNotoKurrent-Regular.ttf SourceHanSerifCN-Regular.ttf \ for font in GoNotoKurrent-Regular.ttf SourceHanSerifCN-Regular.ttf \
SourceHanSerifTW-Regular.ttf SourceHanSerifJP-Regular.ttf \ SourceHanSerifTW-Regular.ttf SourceHanSerifJP-Regular.ttf \
SourceHanSerifKR-Regular.ttf BiauKai.ttf; do \ SourceHanSerifKR-Regular.ttf BiauKai.ttf; do \
[ -f "/usr/share/fonts/truetype/custom/${font}" ] && \ [ -f "/usr/share/fonts/truetype/custom/${font}" ] && \
cp "/usr/share/fonts/truetype/custom/${font}" /root/.cache/babeldoc/fonts/ || true; \ cp "/usr/share/fonts/truetype/custom/${font}" /root/.cache/babeldoc/fonts/ || true; \
done done
# 5.4 更新字型快取 # 5.4 更新字型快取
RUN fc-cache -fv RUN fc-cache -fv
@ -268,10 +268,10 @@ FROM fonts AS python-tools
# 6.1 Python 基礎環境 # 6.1 Python 基礎環境
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv python3-numpy \ python3 python3-pip python3-venv python3-numpy \
python3-tinycss2 python3-opencv python3-img2pdf && \ python3-tinycss2 python3-opencv python3-img2pdf && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 6.2 uv 套件管理器 # 6.2 uv 套件管理器
RUN pip3 install --no-cache-dir --break-system-packages 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 endesivePDF 簽章) # 6.4 endesivePDF 簽章)
RUN apt-get update --fix-missing && \ RUN apt-get update --fix-missing && \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
build-essential swig libpcsclite-dev python3-dev && \ build-essential swig libpcsclite-dev python3-dev && \
uv pip install --system --break-system-packages --no-cache endesive && \ uv pip install --system --break-system-packages --no-cache endesive && \
apt-get remove -y build-essential swig python3-dev && \ apt-get remove -y build-essential swig python3-dev && \
apt-get autoremove -y && \ apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# 6.5 markitdown # 6.5 markitdown
RUN uv pip install --system --break-system-packages --no-cache "markitdown[all]" 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 # 6.7 babeldoc
RUN uv pip install --system --break-system-packages --no-cache babeldoc || \ RUN uv pip install --system --break-system-packages --no-cache babeldoc || \
echo "⚠️ babeldoc 安裝可能有警告" echo "⚠️ babeldoc 安裝可能有警告"
# 6.8 MinerU僅 AMD64 # 6.8 MinerU僅 AMD64
RUN set -ex && \ RUN set -ex && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
echo "⚠️ ARM64MinerU 不支援,跳過安裝"; \ echo "⚠️ ARM64MinerU 不支援,跳過安裝"; \
else \ else \
uv pip install --system --break-system-packages --no-cache -U "mineru[all]"; \ uv pip install --system --break-system-packages --no-cache -U "mineru[all]"; \
fi fi
# 6.9 tiktoken # 6.9 tiktoken
RUN uv pip install --system --break-system-packages --no-cache 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 創建目錄結構 # 7.1 創建目錄結構
RUN mkdir -p /opt/convertx/models/mineru && \ RUN mkdir -p /opt/convertx/models/mineru && \
mkdir -p /root/.cache/babeldoc/models && \ mkdir -p /root/.cache/babeldoc/models && \
mkdir -p /root/.cache/babeldoc/fonts && \ mkdir -p /root/.cache/babeldoc/fonts && \
mkdir -p /root/.cache/babeldoc/cmap && \ mkdir -p /root/.cache/babeldoc/cmap && \
mkdir -p /root/.cache/babeldoc/tiktoken mkdir -p /root/.cache/babeldoc/tiktoken
# 7.2 複製預下載的 ONNX 模型 # 7.2 複製預下載的 ONNX 模型
COPY models/ /root/.cache/babeldoc/models/ COPY models/ /root/.cache/babeldoc/models/
# 7.3 下載 MinerU Pipeline 模型(僅 AMD64 # 7.3 下載 MinerU Pipeline 模型(僅 AMD64
RUN set -ex && \ RUN set -ex && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
echo "⚠️ ARM64跳過 MinerU 模型下載"; \ echo "⚠️ ARM64跳過 MinerU 模型下載"; \
else \ else \
python3 -c " \ python3 -c " \
from huggingface_hub import snapshot_download; \ from huggingface_hub import snapshot_download; \
import os; \ import os; \
models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru'); \ models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru'); \
print(f'下載 PDF-Extract-Kit-1.0 到 {models_dir}...'); \ print(f'下載 PDF-Extract-Kit-1.0 到 {models_dir}...'); \
snapshot_download( \ snapshot_download( \
repo_id='opendatalab/PDF-Extract-Kit-1.0', \ repo_id='opendatalab/PDF-Extract-Kit-1.0', \
local_dir=f'{models_dir}/PDF-Extract-Kit-1.0', \ local_dir=f'{models_dir}/PDF-Extract-Kit-1.0', \
local_dir_use_symlinks=False, \ local_dir_use_symlinks=False, \
resume_download=True \ resume_download=True \
); \ ); \
print('✅ PDF-Extract-Kit-1.0 下載完成'); \ print('✅ PDF-Extract-Kit-1.0 下載完成'); \
"; \ "; \
fi fi
# 7.4 產生 MinerU 配置檔 # 7.4 產生 MinerU 配置檔
RUN set -ex && \ RUN set -ex && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
mkdir -p /opt/convertx && \ mkdir -p /opt/convertx && \
if [ "$ARCH" = "aarch64" ]; then \ if [ "$ARCH" = "aarch64" ]; then \
echo '{"models-dir":{"pipeline":"","vlm":""},"model-source":"local","note":"ARM64 - MinerU not supported"}' > /opt/convertx/mineru.json && \ 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 && \ cp /opt/convertx/mineru.json /root/mineru.json && \
echo "⚠️ ARM64產生空的 mineru.json"; \ echo "⚠️ ARM64產生空的 mineru.json"; \
else \ else \
python3 -c " \ python3 -c " \
import json; \ import json; \
import os; \ import os; \
mineru_models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru'); \ mineru_models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru'); \
config = { \ config = { \
'models-dir': { \ 'models-dir': { \
'pipeline': f'{mineru_models_dir}/PDF-Extract-Kit-1.0', \ 'pipeline': f'{mineru_models_dir}/PDF-Extract-Kit-1.0', \
'vlm': '' \ 'vlm': '' \
}, \ }, \
'model-source': 'local', \ 'model-source': 'local', \
'latex-delimiter-config': { \ 'latex-delimiter-config': { \
'display': {'left': '@@', 'right': '@@'}, \ 'display': {'left': '@@', 'right': '@@'}, \
'inline': {'left': '@', 'right': '@'} \ 'inline': {'left': '@', 'right': '@'} \
} \ } \
}; \ }; \
os.makedirs('/opt/convertx', exist_ok=True); \ os.makedirs('/opt/convertx', exist_ok=True); \
with open('/opt/convertx/mineru.json', 'w') as f: \ with open('/opt/convertx/mineru.json', 'w') as f: \
json.dump(config, f, indent=2); \ json.dump(config, f, indent=2); \
with open('/root/mineru.json', 'w') as f: \ with open('/root/mineru.json', 'w') as f: \
json.dump(config, f, indent=2); \ json.dump(config, f, indent=2); \
print('✅ mineru.json 已產生'); \ print('✅ mineru.json 已產生'); \
"; \ "; \
fi fi
# 7.5 BabelDOC warmup # 7.5 BabelDOC warmup
RUN set -ex && \ RUN set -ex && \
export BABELDOC_CACHE_PATH="/root/.cache/babeldoc" && \ export BABELDOC_CACHE_PATH="/root/.cache/babeldoc" && \
if command -v babeldoc >/dev/null 2>&1; then \ if command -v babeldoc >/dev/null 2>&1; then \
babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告"; \ babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告"; \
else \ else \
echo "⚠️ babeldoc 不可用,跳過 warmup"; \ echo "⚠️ babeldoc 不可用,跳過 warmup"; \
fi fi
# 7.6 下載 tiktoken 編碼 # 7.6 下載 tiktoken 編碼
RUN python3 -c " \ RUN python3 -c " \
try: \ try: \
import tiktoken; \ import tiktoken; \
for enc_name in ['cl100k_base', 'p50k_base', 'r50k_base']: \ for enc_name in ['cl100k_base', 'p50k_base', 'r50k_base']: \
try: \ try: \
enc = tiktoken.get_encoding(enc_name); \ enc = tiktoken.get_encoding(enc_name); \
print(f'✅ tiktoken {enc_name} 已下載'); \ print(f'✅ tiktoken {enc_name} 已下載'); \
except Exception as e: \ except Exception as e: \
print(f'⚠️ tiktoken {enc_name} 下載失敗: {e}'); \ print(f'⚠️ tiktoken {enc_name} 下載失敗: {e}'); \
except ImportError: \ except ImportError: \
print('⚠️ tiktoken 未安裝,跳過'); \ print('⚠️ tiktoken 未安裝,跳過'); \
" "
# 7.7 清理下載快取 # 7.7 清理下載快取
RUN rm -rf /tmp/hf_download_cache /root/.cache/huggingface \ RUN rm -rf /tmp/hf_download_cache /root/.cache/huggingface \
/root/.cache/pip /root/.cache/uv && \ /root/.cache/pip /root/.cache/uv && \
find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
# ============================================================================== # ==============================================================================
# Stage 8: Final Release Image # Stage 8: Final Release Image
@ -442,39 +442,39 @@ RUN fc-cache -fv
# PDF 簽章憑證 # PDF 簽章憑證
# ============================================================================== # ==============================================================================
RUN mkdir -p /app/certs && \ RUN mkdir -p /app/certs && \
openssl req -x509 -newkey rsa:2048 \ openssl req -x509 -newkey rsa:2048 \
-keyout /tmp/key.pem -out /tmp/cert.pem \ -keyout /tmp/key.pem -out /tmp/cert.pem \
-days 3650 -nodes \ -days 3650 -nodes \
-subj "/CN=PDF Packager Default/O=ConvertX-CN/C=TW" && \ -subj "/CN=PDF Packager Default/O=ConvertX-CN/C=TW" && \
openssl pkcs12 -export \ openssl pkcs12 -export \
-inkey /tmp/key.pem -in /tmp/cert.pem \ -inkey /tmp/key.pem -in /tmp/cert.pem \
-out /app/certs/default.p12 \ -out /app/certs/default.p12 \
-passout pass: && \ -passout pass: && \
rm -f /tmp/key.pem /tmp/cert.pem && \ rm -f /tmp/key.pem /tmp/cert.pem && \
chmod 644 /app/certs/default.p12 chmod 644 /app/certs/default.p12
# ============================================================================== # ==============================================================================
# Locale 設定 # Locale 設定
# ============================================================================== # ==============================================================================
RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
sed -i 's/# zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/' /etc/locale.gen && \ sed -i 's/# zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/' /etc/locale.gen && \
sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen && \ sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen && \
sed -i 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen && \ sed -i 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen && \
sed -i 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen && \ sed -i 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen && \
sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen && \ sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen && \
sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \ sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \
locale-gen locale-gen
# ============================================================================== # ==============================================================================
# 最終清理 # 最終清理
# ============================================================================== # ==============================================================================
RUN rm -rf /usr/share/doc/texlive* && \ RUN rm -rf /usr/share/doc/texlive* && \
rm -rf /usr/share/texlive/texmf-dist/doc && \ rm -rf /usr/share/texlive/texmf-dist/doc && \
rm -rf /usr/share/doc/* && \ rm -rf /usr/share/doc/* && \
rm -rf /usr/share/man/* && \ rm -rf /usr/share/man/* && \
rm -rf /usr/share/info/* && \ rm -rf /usr/share/info/* && \
rm -rf /tmp/* && \ rm -rf /tmp/* && \
rm -rf /var/tmp/* rm -rf /var/tmp/*
# 複製驗證腳本 # 複製驗證腳本
COPY scripts/verify-models.sh /app/scripts/verify-models.sh COPY scripts/verify-models.sh /app/scripts/verify-models.sh
@ -488,87 +488,87 @@ RUN mkdir -p data
# 🔒 Runtime 離線驗證 # 🔒 Runtime 離線驗證
# ============================================================================== # ==============================================================================
RUN echo "======================================" && \ RUN echo "======================================" && \
echo "🔒 Runtime 離線驗證" && \ echo "🔒 Runtime 離線驗證" && \
echo "======================================" && \ echo "======================================" && \
ARCH=$(uname -m) && \ ARCH=$(uname -m) && \
VALIDATION_PASSED=true && \ VALIDATION_PASSED=true && \
\ \
# 驗證核心工具 # 驗證核心工具
echo "🔍 驗證核心工具..." && \ echo "🔍 驗證核心工具..." && \
for cmd in ffmpeg convert gm vips inkscape pandoc soffice; do \ for cmd in ffmpeg convert gm vips inkscape pandoc soffice; do \
if command -v ${cmd} >/dev/null 2>&1; then \ if command -v ${cmd} >/dev/null 2>&1; then \
echo " ✅ ${cmd}: $(which ${cmd})"; \ echo " ✅ ${cmd}: $(which ${cmd})"; \
else \ else \
echo " ❌ ${cmd}: 未找到" && VALIDATION_PASSED=false; \ echo " ❌ ${cmd}: 未找到" && VALIDATION_PASSED=false; \
fi; \ fi; \
done && \ done && \
\ \
# 驗證 MinerU僅 AMD64 # 驗證 MinerU僅 AMD64
echo "🔍 驗證 MinerU..." && \ echo "🔍 驗證 MinerU..." && \
if [ "$ARCH" != "aarch64" ]; then \ if [ "$ARCH" != "aarch64" ]; then \
if command -v mineru >/dev/null 2>&1; then \ if command -v mineru >/dev/null 2>&1; then \
echo " ✅ mineru: $(which mineru)"; \ echo " ✅ mineru: $(which mineru)"; \
else \ else \
echo " ❌ mineru 不可執行" && VALIDATION_PASSED=false; \ echo " ❌ mineru 不可執行" && VALIDATION_PASSED=false; \
fi && \ fi && \
if [ -d "/opt/convertx/models/mineru/PDF-Extract-Kit-1.0" ]; then \ if [ -d "/opt/convertx/models/mineru/PDF-Extract-Kit-1.0" ]; then \
echo " ✅ MinerU 模型目錄存在"; \ echo " ✅ MinerU 模型目錄存在"; \
else \ else \
echo " ❌ MinerU 模型目錄不存在" && VALIDATION_PASSED=false; \ echo " ❌ MinerU 模型目錄不存在" && VALIDATION_PASSED=false; \
fi && \ fi && \
if [ -f "/root/mineru.json" ]; then \ if [ -f "/root/mineru.json" ]; then \
echo " ✅ mineru.json 存在"; \ echo " ✅ mineru.json 存在"; \
else \ else \
echo " ❌ mineru.json 不存在" && VALIDATION_PASSED=false; \ echo " ❌ mineru.json 不存在" && VALIDATION_PASSED=false; \
fi; \ fi; \
else \ else \
echo " ⚠️ ARM64跳過 MinerU 驗證"; \ echo " ⚠️ ARM64跳過 MinerU 驗證"; \
fi && \ fi && \
\ \
# 驗證 BabelDOC # 驗證 BabelDOC
echo "🔍 驗證 BabelDOC..." && \ echo "🔍 驗證 BabelDOC..." && \
if command -v babeldoc >/dev/null 2>&1; then \ if command -v babeldoc >/dev/null 2>&1; then \
echo " ✅ babeldoc: $(which babeldoc)"; \ echo " ✅ babeldoc: $(which babeldoc)"; \
else \ else \
echo " ⚠️ babeldoc 不可用"; \ echo " ⚠️ babeldoc 不可用"; \
fi && \ fi && \
\ \
# 驗證 pdf2zh # 驗證 pdf2zh
echo "🔍 驗證 pdf2zh..." && \ echo "🔍 驗證 pdf2zh..." && \
if command -v pdf2zh >/dev/null 2>&1; then \ if command -v pdf2zh >/dev/null 2>&1; then \
echo " ✅ pdf2zh: $(which pdf2zh)"; \ echo " ✅ pdf2zh: $(which pdf2zh)"; \
else \ else \
echo " ⚠️ pdf2zh 不可用"; \ echo " ⚠️ pdf2zh 不可用"; \
fi && \ fi && \
\ \
# 驗證 ImageMagick # 驗證 ImageMagick
echo "🔍 驗證 ImageMagick..." && \ echo "🔍 驗證 ImageMagick..." && \
if command -v convert >/dev/null 2>&1; then \ if command -v convert >/dev/null 2>&1; then \
echo " ✅ ImageMagick: $(convert --version | head -1)"; \ echo " ✅ ImageMagick: $(convert --version | head -1)"; \
else \ else \
echo " ❌ ImageMagick 未安裝" && VALIDATION_PASSED=false; \ echo " ❌ ImageMagick 未安裝" && VALIDATION_PASSED=false; \
fi && \ fi && \
\ \
# 驗證 ONNX 模型 # 驗證 ONNX 模型
echo "🔍 驗證 ONNX 模型..." && \ echo "🔍 驗證 ONNX 模型..." && \
if [ -f "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then \ if [ -f "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then \
echo " ✅ DocLayout-YOLO ONNX 存在"; \ echo " ✅ DocLayout-YOLO ONNX 存在"; \
else \ else \
echo " ⚠️ DocLayout-YOLO ONNX 不存在"; \ echo " ⚠️ DocLayout-YOLO ONNX 不存在"; \
fi && \ fi && \
\ \
# 驗證字型 # 驗證字型
echo "🔍 驗證字型..." && \ echo "🔍 驗證字型..." && \
FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l || echo "0") && \ FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l || echo "0") && \
echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \ echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \
\ \
echo "======================================" && \ echo "======================================" && \
if [ "$VALIDATION_PASSED" = "true" ]; then \ if [ "$VALIDATION_PASSED" = "true" ]; then \
echo "✅ 離線驗證通過!"; \ echo "✅ 離線驗證通過!"; \
else \ else \
echo "❌ 離線驗證失敗!" && exit 1; \ echo "❌ 離線驗證失敗!" && exit 1; \
fi && \ fi && \
echo "======================================" echo "======================================"
# ============================================================================== # ==============================================================================
# 🔐 Runtime 環境變數(強制離線模式) # 🔐 Runtime 環境變數(強制離線模式)

View file

@ -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-tools14 個獨立 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: fonts4 個獨立 RUN
| RUN | 內容 |
|-----|------|
| 5.1 | 系統字型Noto CJK, Liberation 等) |
| 5.2 | 複製自訂字型 |
| 5.3 | 設定 BabelDOC 字型目錄 |
| 5.4 | 更新字型快取 |
### Stage 6: python-tools9 個獨立 RUN
| RUN | 內容 |
|-----|------|
| 6.1 | Python 基礎環境 |
| 6.2 | uv 套件管理器 |
| 6.3 | huggingface_hub |
| 6.4 | endesivePDF 簽章) |
| 6.5 | markitdown |
| 6.6 | pdf2zhPDFMathTranslate |
| 6.7 | babeldoc |
| 6.8 | MinerU僅 AMD64 |
| 6.9 | tiktoken |
### Stage 7: models7 個獨立 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 | `⚠️ ARM64MinerU 不支援,跳過安裝` |
| 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 <container> /app/scripts/verify-models.sh
docker exec <container> /app/scripts/verify-installation.sh
```