feat: 完全離線化 Dockerfile v0.1.16
## 主要修復 - MinerU: 改用 system-level 安裝 (uv pip install --system) - MinerU: 模型下載到固定目錄 /opt/mineru/models - BabelDOC: 完整 warmup + 離線 cache - PDFMathTranslate: ONNX 模型使用 COPY 指令 - ImageMagick: 確保已安裝 ## 結構優化 - Multi-Stage Build (8 stages) - 使用 heredoc 語法處理多行 Python - 分階段安裝避免記憶體尖峰 ## Runtime 離線模式 - HF_HOME=/nonexistent - HF_HUB_OFFLINE=1 - BABELDOC_OFFLINE=1 - PIP_NO_INDEX=1 ## 新增驗證腳本 - scripts/verify-offline.sh
This commit is contained in:
parent
b23d42a7b1
commit
367e0d3056
4 changed files with 1682 additions and 761 deletions
692
Dockerfile.offline
Normal file
692
Dockerfile.offline
Normal file
|
|
@ -0,0 +1,692 @@
|
|||
# ==============================================================================
|
||||
# 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 簽章)
|
||||
RUN echo "📦 [Stage 6.1] 安裝 huggingface_hub + endesive..." && \
|
||||
apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
swig \
|
||||
libpcsclite-dev \
|
||||
&& uv pip install --system --no-cache huggingface_hub endesive && \
|
||||
apt-get remove -y build-essential swig && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 階段 6.2:安裝 markitdown
|
||||
RUN echo "📦 [Stage 6.2] 安裝 markitdown..." && \
|
||||
uv pip install --system --no-cache "markitdown[all]"
|
||||
|
||||
# 階段 6.3:安裝 pdf2zh
|
||||
RUN echo "📦 [Stage 6.3] 安裝 pdf2zh..." && \
|
||||
uv pip install --system --no-cache pdf2zh
|
||||
|
||||
# 階段 6.4:安裝 babeldoc
|
||||
RUN echo "📦 [Stage 6.4] 安裝 babeldoc..." && \
|
||||
uv pip install --system --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 --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 <<EOF
|
||||
echo "📥 [Stage 7.2] 下載 MinerU Pipeline 模型..."
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "aarch64" ]; then
|
||||
echo "⚠️ ARM64:跳過 MinerU 模型下載"
|
||||
else
|
||||
python3 <<PYTHON
|
||||
from huggingface_hub import snapshot_download
|
||||
import os
|
||||
|
||||
models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/mineru/models')
|
||||
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 下載完成')
|
||||
PYTHON
|
||||
echo "📋 MinerU 模型目錄內容:"
|
||||
ls -la "${MINERU_MODELS_DIR}/" || true
|
||||
du -sh "${MINERU_MODELS_DIR}/PDF-Extract-Kit-1.0" || true
|
||||
fi
|
||||
EOF
|
||||
|
||||
# 階段 7.3:產生 MinerU 配置檔(指向固定目錄)
|
||||
RUN <<EOF
|
||||
echo "📝 [Stage 7.3] 產生 mineru.json..."
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "aarch64" ]; then
|
||||
echo "⚠️ ARM64:跳過 mineru.json"
|
||||
else
|
||||
python3 <<PYTHON
|
||||
import json
|
||||
|
||||
config = {
|
||||
'models-dir': {
|
||||
'pipeline': '/opt/mineru/models/PDF-Extract-Kit-1.0',
|
||||
'vlm': ''
|
||||
},
|
||||
'model-source': 'local',
|
||||
'latex-delimiter-config': {
|
||||
'display': {'left': '@@', 'right': '@@'},
|
||||
'inline': {'left': '@', 'right': '@'}
|
||||
}
|
||||
}
|
||||
|
||||
with open('/opt/mineru/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 已產生')
|
||||
print(json.dumps(config, indent=2))
|
||||
PYTHON
|
||||
fi
|
||||
EOF
|
||||
|
||||
# 階段 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 <<EOF
|
||||
echo "📥 [Stage 7.5] 下載 tiktoken 編碼..."
|
||||
python3 <<PYTHON
|
||||
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 未安裝,跳過')
|
||||
PYTHON
|
||||
EOF
|
||||
|
||||
# 階段 7.6:清理下載 cache(保留實際模型)
|
||||
RUN echo "🧹 [Stage 7.6] 清理下載 cache..." && \
|
||||
rm -rf /tmp/hf_download_cache && \
|
||||
rm -rf /root/.cache/huggingface && \
|
||||
rm -rf /root/.cache/pip && \
|
||||
rm -rf /root/.cache/uv && \
|
||||
find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/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" ]
|
||||
Loading…
Add table
Add a link
Reference in a new issue