refactor(docker): 完整重構 Dockerfile v0.1.16
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
This commit is contained in:
parent
e43649bc96
commit
55b0f519ab
10 changed files with 1797 additions and 2008 deletions
694
Dockerfile
694
Dockerfile
|
|
@ -1,6 +1,6 @@
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# ConvertX-CN 官方 Docker Image
|
# ConvertX-CN 官方 Docker Image
|
||||||
# 版本:v0.1.16 - 完全離線化版本
|
# 版本:v0.1.16 - 完整重構版
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
#
|
#
|
||||||
# 📦 Image 說明:
|
# 📦 Image 說明:
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
# - ⚠️ 所有模型、字型、tokenizer 已在 build 階段完整預下載
|
# - ⚠️ 所有模型、字型、tokenizer 已在 build 階段完整預下載
|
||||||
# - ⚠️ Runtime 完全離線運行(僅翻譯服務允許連網)
|
# - ⚠️ Runtime 完全離線運行(僅翻譯服務允許連網)
|
||||||
#
|
#
|
||||||
# 🔒 Offline-first 設計原則(硬性規則):
|
# 🔒 Offline-first 設計原則:
|
||||||
# 1. Runtime(docker run 後):
|
# 1. Runtime(docker run 後):
|
||||||
# ❌ 禁止任何模型、字型、tokenizer、metadata 下載
|
# ❌ 禁止任何模型、字型、tokenizer、metadata 下載
|
||||||
# ❌ MinerU / BabelDOC / PDFMathTranslate 不得嘗試連網
|
# ❌ MinerU / BabelDOC / PDFMathTranslate 不得嘗試連網
|
||||||
|
|
@ -21,72 +21,73 @@
|
||||||
# - PDFMathTranslate: DocLayout-YOLO ONNX(佈局分析)
|
# - PDFMathTranslate: DocLayout-YOLO ONNX(佈局分析)
|
||||||
# - BabelDOC: DocLayout-YOLO + 字型資源 + tiktoken
|
# - BabelDOC: DocLayout-YOLO + 字型資源 + tiktoken
|
||||||
# - MinerU: PDF-Extract-Kit-1.0(Pipeline 模型)
|
# - 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 結構:
|
# 🏗️ Multi-Stage Build 結構:
|
||||||
# Stage 1 [base] : Bun runtime 基礎
|
# Stage 1 [base] : Bun runtime 基礎
|
||||||
# Stage 2 [install] : Node 依賴安裝
|
# Stage 2 [install] : Node 依賴安裝
|
||||||
# Stage 3 [prerelease] : 應用程式建構
|
# Stage 3 [prerelease] : 應用程式建構
|
||||||
# Stage 4 [system-tools] : APT 系統工具
|
# Stage 4 [system-tools] : APT 系統工具
|
||||||
# Stage 5 [python-base]: Python 基礎環境
|
# Stage 5 [fonts] : 字型安裝
|
||||||
# Stage 6 [python-tools] : Python CLI 工具
|
# Stage 6 [python-tools] : Python CLI 工具
|
||||||
# Stage 7 [models-download]: 模型下載(獨立 stage)
|
# Stage 7 [models] : 模型下載
|
||||||
# Stage 8 [final]: 最終 Image(合併所有層)
|
# Stage 8 [release] : 最終 Image
|
||||||
|
#
|
||||||
|
# 🌍 Multi-Arch 支援:
|
||||||
|
# - linux/amd64: 功能完整
|
||||||
|
# - linux/arm64: 安全降級(不支援的工具會跳過)
|
||||||
|
#
|
||||||
|
# 📊 Image 大小:約 10-14 GB(含完整模型)
|
||||||
|
#
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
# Stage 1: Base - Bun Runtime
|
# Stage 1: Base - Bun Runtime
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
FROM debian:bookworm-slim AS base
|
FROM debian:bookworm-slim AS base
|
||||||
LABEL org.opencontainers.image.source="https://github.com/pi-docket/ConvertX-CN"
|
LABEL org.opencontainers.image.source="https://github.com/pi-docket/ConvertX-CN"
|
||||||
LABEL org.opencontainers.image.description="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
|
WORKDIR /app
|
||||||
|
|
||||||
# 設定非互動模式
|
# 設定非互動模式
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
# 配置 APT 重試機制
|
# 配置 APT 重試機制
|
||||||
RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \
|
RUN set -ex && \
|
||||||
&& echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries && \
|
||||||
&& echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
|
||||||
&& echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
|
||||||
&& echo 'DPkg::Lock::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 \
|
RUN set -ex && \
|
||||||
|
apt-get update && \
|
||||||
|
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(根據架構選擇版本)
|
||||||
RUN ARCH=$(uname -m) && \
|
ARG BUN_VERSION=1.3.6
|
||||||
|
RUN set -ex && \
|
||||||
|
ARCH=$(uname -m) && \
|
||||||
if [ "$ARCH" = "aarch64" ]; then \
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
|
BUN_ASSET="bun-linux-aarch64.zip"; \
|
||||||
-o bun-linux-aarch64.zip \
|
|
||||||
https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-aarch64.zip; \
|
|
||||||
else \
|
else \
|
||||||
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
|
BUN_ASSET="bun-linux-x64-baseline.zip"; \
|
||||||
-o bun-linux-x64-baseline.zip \
|
|
||||||
https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-x64-baseline.zip; \
|
|
||||||
fi && \
|
fi && \
|
||||||
unzip -j bun-linux-*.zip -d /usr/local/bin && \
|
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
|
||||||
rm bun-linux-*.zip && \
|
-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
|
chmod +x /usr/local/bin/bun
|
||||||
|
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
# Stage 2: Install - Node Dependencies
|
# Stage 2: Install - Node Dependencies
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
FROM base AS install
|
FROM base AS install
|
||||||
|
|
||||||
# 開發依賴
|
# 開發依賴
|
||||||
|
|
@ -99,54 +100,43 @@ RUN mkdir -p /temp/prod
|
||||||
COPY package.json bun.lock /temp/prod/
|
COPY package.json bun.lock /temp/prod/
|
||||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||||
|
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
# Stage 3: Prerelease - Build App
|
# Stage 3: Prerelease - Build App
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
FROM base AS prerelease
|
FROM base AS prerelease
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=install /temp/dev/node_modules node_modules
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
# Stage 4: System Tools
|
# Stage 4: System Tools(拆分為多個 RUN 以提升可調試性和 cache 效率)
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
FROM base AS system-tools
|
FROM base AS system-tools
|
||||||
|
|
||||||
# 配置 APT
|
# 4.1 配置 APT
|
||||||
RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \
|
RUN set -ex && \
|
||||||
&& echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries && \
|
||||||
&& echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \
|
||||||
&& echo 'Acquire::ftp::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.1:基礎系統工具 + Locale
|
# 4.2 基礎系統工具
|
||||||
RUN echo "📦 [Stage 4.1] 安裝基礎系統工具..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
locales \
|
locales ca-certificates curl wget unzip openssl git xz-utils && \
|
||||||
ca-certificates \
|
rm -rf /var/lib/apt/lists/*
|
||||||
curl \
|
|
||||||
openssl \
|
|
||||||
git \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 階段 4.2:核心轉換工具(輕量)
|
# 4.3 核心轉換工具
|
||||||
RUN echo "📦 [Stage 4.2] 安裝核心轉換工具..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
assimp-utils \
|
assimp-utils dcraw dvisvgm ghostscript graphicsmagick \
|
||||||
dcraw \
|
mupdf-tools poppler-utils potrace qpdf && \
|
||||||
dvisvgm \
|
rm -rf /var/lib/apt/lists/*
|
||||||
ghostscript \
|
|
||||||
graphicsmagick \
|
|
||||||
mupdf-tools \
|
|
||||||
poppler-utils \
|
|
||||||
potrace \
|
|
||||||
qpdf \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 階段 4.3:安裝 dasel(從 GitHub 二進位)
|
# 4.4 dasel(JSON/YAML/TOML 轉換)
|
||||||
RUN echo "📦 [Stage 4.3] 安裝 dasel..." && \
|
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 && \
|
||||||
|
|
@ -155,8 +145,8 @@ RUN echo "📦 [Stage 4.3] 安裝 dasel..." && \
|
||||||
-o /usr/local/bin/dasel && \
|
-o /usr/local/bin/dasel && \
|
||||||
chmod +x /usr/local/bin/dasel
|
chmod +x /usr/local/bin/dasel
|
||||||
|
|
||||||
# 階段 4.4:安裝 resvg(僅 x86_64)
|
# 4.5 resvg(僅 AMD64)
|
||||||
RUN echo "📦 [Stage 4.4] 安裝 resvg..." && \
|
RUN set -ex && \
|
||||||
ARCH=$(uname -m) && \
|
ARCH=$(uname -m) && \
|
||||||
if [ "$ARCH" = "aarch64" ]; then \
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
echo "⚠️ resvg 無 ARM64 版本,跳過"; \
|
echo "⚠️ resvg 無 ARM64 版本,跳過"; \
|
||||||
|
|
@ -170,331 +160,282 @@ RUN echo "📦 [Stage 4.4] 安裝 resvg..." && \
|
||||||
rm -rf /tmp/resvg.tar.gz; \
|
rm -rf /tmp/resvg.tar.gz; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 階段 4.5:編譯安裝 deark
|
# 4.6 deark(編譯安裝)
|
||||||
RUN echo "📦 [Stage 4.5] 編譯 deark..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends build-essential && \
|
||||||
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 && \
|
cp deark /usr/local/bin/deark && chmod +x /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 remove -y build-essential && apt-get autoremove -y && \
|
||||||
apt-get autoremove -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 階段 4.6:影音處理工具
|
# 4.7 vtracer
|
||||||
RUN echo "📦 [Stage 4.6] 安裝 FFmpeg..." && \
|
RUN set -ex && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
ARCH=$(uname -m) && \
|
||||||
ffmpeg \
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
libavcodec-extra \
|
VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \
|
||||||
libva2 \
|
else \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
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)
|
# 4.8 FFmpeg
|
||||||
# ⚠️ 修復:確保 ImageMagick 已安裝
|
RUN apt-get update --fix-missing && \
|
||||||
RUN echo "📦 [Stage 4.7] 安裝圖像處理工具(含 ImageMagick)..." && \
|
apt-get install -y --no-install-recommends \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
ffmpeg libavcodec-extra libva2 && \
|
||||||
imagemagick \
|
rm -rf /var/lib/apt/lists/*
|
||||||
inkscape \
|
|
||||||
libheif-examples \
|
|
||||||
libjxl-tools \
|
|
||||||
libvips-tools \
|
|
||||||
xauth \
|
|
||||||
xvfb \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 階段 4.8:文件處理工具
|
# 4.9 圖像處理工具(ImageMagick, Inkscape, vips 等)
|
||||||
RUN echo "📦 [Stage 4.8] 安裝 Calibre + Pandoc..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
calibre \
|
imagemagick inkscape libheif-examples libjxl-tools \
|
||||||
libemail-outlook-message-perl \
|
libvips-tools xauth xvfb && \
|
||||||
pandoc \
|
rm -rf /var/lib/apt/lists/*
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 階段 4.9:LibreOffice
|
# 4.10 文件處理工具(Calibre, Pandoc)
|
||||||
RUN echo "📦 [Stage 4.9] 安裝 LibreOffice(需數分鐘)..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
libreoffice \
|
calibre libemail-outlook-message-perl pandoc && \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 階段 4.10:TexLive 基礎
|
# 4.11 LibreOffice
|
||||||
RUN echo "📦 [Stage 4.10] 安裝 TexLive 基礎..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends libreoffice && \
|
||||||
texlive-base \
|
rm -rf /var/lib/apt/lists/*
|
||||||
texlive-latex-base \
|
|
||||||
texlive-latex-recommended \
|
|
||||||
texlive-fonts-recommended \
|
|
||||||
texlive-xetex \
|
|
||||||
latexmk \
|
|
||||||
lmodern \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 階段 4.11:TexLive 語言包
|
# 4.12 TexLive 基礎
|
||||||
RUN echo "📦 [Stage 4.11] 安裝 TexLive 語言包..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
texlive-lang-cjk \
|
texlive-base texlive-latex-base texlive-latex-recommended \
|
||||||
texlive-lang-german \
|
texlive-fonts-recommended texlive-xetex latexmk lmodern && \
|
||||||
texlive-lang-french \
|
rm -rf /var/lib/apt/lists/*
|
||||||
texlive-lang-arabic \
|
|
||||||
texlive-lang-other \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 階段 4.12:OCR 支援
|
# 4.13 TexLive 語言包
|
||||||
RUN echo "📦 [Stage 4.12] 安裝 Tesseract OCR..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
tesseract-ocr \
|
texlive-lang-cjk texlive-lang-german texlive-lang-french \
|
||||||
tesseract-ocr-eng \
|
texlive-lang-arabic texlive-lang-other && \
|
||||||
tesseract-ocr-chi-tra \
|
rm -rf /var/lib/apt/lists/*
|
||||||
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:字型
|
# 4.14 Tesseract OCR
|
||||||
RUN echo "📦 [Stage 4.13] 安裝系統字型..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
fonts-noto-cjk \
|
tesseract-ocr tesseract-ocr-eng tesseract-ocr-chi-tra \
|
||||||
fonts-noto-core \
|
tesseract-ocr-chi-sim tesseract-ocr-jpn tesseract-ocr-kor \
|
||||||
fonts-noto-color-emoji \
|
tesseract-ocr-deu tesseract-ocr-fra ocrmypdf && \
|
||||||
fonts-liberation \
|
rm -rf /var/lib/apt/lists/*
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
# Stage 5: Python Base
|
# Stage 5: Fonts(拆分安裝)
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
FROM system-tools AS python-base
|
FROM system-tools AS fonts
|
||||||
|
|
||||||
# 階段 5.1:Python 基礎 + uv
|
# 5.1 系統字型
|
||||||
RUN echo "📦 [Stage 5.1] 安裝 Python 基礎..." && \
|
RUN apt-get update --fix-missing && \
|
||||||
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
python3 \
|
fonts-noto-cjk fonts-noto-cjk-extra fonts-noto-core \
|
||||||
python3-pip \
|
fonts-noto-color-emoji fonts-liberation fonts-dejavu-core \
|
||||||
python3-venv \
|
fonts-dejavu-extra fonts-freefont-ttf fonts-droid-fallback && \
|
||||||
python3-numpy \
|
rm -rf /var/lib/apt/lists/*
|
||||||
python3-tinycss2 \
|
|
||||||
python3-opencv \
|
|
||||||
python3-img2pdf \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 階段 5.2:安裝 uv(Python 套件管理器)
|
# 5.2 複製自訂字型
|
||||||
# ⚠️ 重要:使用 uv 作為主要安裝方式,確保一致性
|
RUN mkdir -p /usr/share/fonts/truetype/custom
|
||||||
RUN echo "📦 [Stage 5.2] 安裝 uv..." && \
|
COPY fonts/ /usr/share/fonts/truetype/custom/
|
||||||
pip3 install --no-cache-dir --break-system-packages uv && \
|
|
||||||
uv --version
|
|
||||||
|
|
||||||
# 設定 PATH
|
# 5.3 設定 BabelDOC 字型目錄
|
||||||
ENV PATH="/root/.local/bin:/usr/local/bin:${PATH}"
|
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
|
||||||
|
|
||||||
# ===================================
|
# 5.4 更新字型快取
|
||||||
# Stage 6: Python Tools
|
RUN fc-cache -fv
|
||||||
# ===================================
|
|
||||||
FROM python-base AS python-tools
|
|
||||||
|
|
||||||
# 階段 6.1:安裝 huggingface_hub + endesive(PDF 簽章)
|
# ==============================================================================
|
||||||
# ⚠️ pykcs11 需要 python3-dev 提供 Python.h
|
# Stage 6: Python Tools(拆分安裝)
|
||||||
RUN echo "📦 [Stage 6.1] 安裝 huggingface_hub + endesive..." && \
|
# ==============================================================================
|
||||||
apt-get update && apt-get install -y --no-install-recommends \
|
FROM fonts AS python-tools
|
||||||
build-essential \
|
|
||||||
swig \
|
# 6.1 Python 基礎環境
|
||||||
libpcsclite-dev \
|
RUN apt-get update --fix-missing && \
|
||||||
python3-dev \
|
apt-get install -y --no-install-recommends \
|
||||||
&& uv pip install --system --break-system-packages --no-cache huggingface_hub endesive && \
|
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 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.2:安裝 markitdown
|
# 6.5 markitdown
|
||||||
RUN echo "📦 [Stage 6.2] 安裝 markitdown..." && \
|
RUN uv pip install --system --break-system-packages --no-cache "markitdown[all]"
|
||||||
uv pip install --system --break-system-packages --no-cache "markitdown[all]"
|
|
||||||
|
|
||||||
# 階段 6.3:安裝 pdf2zh
|
# 6.6 pdf2zh(PDFMathTranslate)
|
||||||
RUN echo "📦 [Stage 6.3] 安裝 pdf2zh..." && \
|
RUN uv pip install --system --break-system-packages --no-cache pdf2zh
|
||||||
uv pip install --system --break-system-packages --no-cache pdf2zh
|
|
||||||
|
|
||||||
# 階段 6.4:安裝 babeldoc
|
# 6.7 babeldoc
|
||||||
RUN echo "📦 [Stage 6.4] 安裝 babeldoc..." && \
|
RUN uv pip install --system --break-system-packages --no-cache babeldoc || \
|
||||||
uv pip install --system --break-system-packages --no-cache babeldoc || echo "⚠️ babeldoc 安裝可能有警告"
|
echo "⚠️ babeldoc 安裝可能有警告"
|
||||||
|
|
||||||
# 階段 6.5:安裝 MinerU(⚠️ 關鍵修復)
|
# 6.8 MinerU(僅 AMD64)
|
||||||
# ⚠️ 只使用 system-level 安裝(uv pip install --system)
|
RUN set -ex && \
|
||||||
# ⚠️ 禁止使用 pipx(會導致 binary 不在 PATH)
|
|
||||||
RUN echo "📦 [Stage 6.5] 安裝 MinerU(system-level only)..." && \
|
|
||||||
ARCH=$(uname -m) && \
|
ARCH=$(uname -m) && \
|
||||||
if [ "$ARCH" = "aarch64" ]; then \
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
echo "⚠️ ARM64:MinerU 不支援,跳過"; \
|
echo "⚠️ ARM64:MinerU 不支援,跳過安裝"; \
|
||||||
else \
|
else \
|
||||||
echo "🔧 使用 uv pip install --system(官方推薦)..." && \
|
uv pip install --system --break-system-packages --no-cache -U "mineru[all]"; \
|
||||||
uv pip install --system --break-system-packages --no-cache -U "mineru[all]" && \
|
|
||||||
echo "✅ 驗證 MinerU 安裝..." && \
|
|
||||||
which mineru && \
|
|
||||||
mineru --version || echo "(版本資訊可能不可用)"; \
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ===================================
|
# 6.9 tiktoken
|
||||||
# Stage 7: Models Download(獨立 Stage)
|
RUN uv pip install --system --break-system-packages --no-cache tiktoken
|
||||||
# ===================================
|
|
||||||
FROM python-tools AS models-download
|
|
||||||
|
|
||||||
# 模型目錄定義
|
# 設定 PATH
|
||||||
ENV MINERU_MODELS_DIR="/opt/mineru/models"
|
ENV PATH="/root/.local/bin:/usr/local/bin:${PATH}"
|
||||||
ENV BABELDOC_CACHE_DIR="/opt/babeldoc/cache"
|
|
||||||
ENV HF_HOME="/tmp/hf_download_cache"
|
|
||||||
|
|
||||||
# 階段 7.1:創建目錄結構
|
# ==============================================================================
|
||||||
RUN mkdir -p "${MINERU_MODELS_DIR}" && \
|
# Stage 7: Models Download(拆分下載)
|
||||||
mkdir -p "${BABELDOC_CACHE_DIR}/models" && \
|
# ==============================================================================
|
||||||
mkdir -p "${BABELDOC_CACHE_DIR}/fonts" && \
|
FROM python-tools AS models
|
||||||
mkdir -p "${BABELDOC_CACHE_DIR}/cmap" && \
|
|
||||||
mkdir -p "${BABELDOC_CACHE_DIR}/tiktoken"
|
|
||||||
|
|
||||||
# 階段 7.2:下載 MinerU Pipeline 模型
|
# 設定模型目錄環境變數
|
||||||
# ⚠️ 關鍵修復:使用顯式下載,存放到固定目錄
|
ENV MINERU_MODELS_DIR="/opt/convertx/models/mineru"
|
||||||
RUN <<EOF
|
ENV BABELDOC_CACHE_DIR="/root/.cache/babeldoc"
|
||||||
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')
|
# 7.1 創建目錄結構
|
||||||
print(f'下載 PDF-Extract-Kit-1.0 到 {models_dir}...')
|
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
|
||||||
|
|
||||||
snapshot_download(
|
# 7.2 複製預下載的 ONNX 模型
|
||||||
repo_id='opendatalab/PDF-Extract-Kit-1.0',
|
COPY models/ /root/.cache/babeldoc/models/
|
||||||
local_dir=f'{models_dir}/PDF-Extract-Kit-1.0',
|
|
||||||
local_dir_use_symlinks=False,
|
# 7.3 下載 MinerU Pipeline 模型(僅 AMD64)
|
||||||
resume_download=True
|
RUN set -ex && \
|
||||||
)
|
ARCH=$(uname -m) && \
|
||||||
print('✅ PDF-Extract-Kit-1.0 下載完成')
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
PYTHON
|
echo "⚠️ ARM64:跳過 MinerU 模型下載"; \
|
||||||
echo "📋 MinerU 模型目錄內容:"
|
else \
|
||||||
ls -la "${MINERU_MODELS_DIR}/" || true
|
python3 -c " \
|
||||||
du -sh "${MINERU_MODELS_DIR}/PDF-Extract-Kit-1.0" || true
|
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
|
fi
|
||||||
EOF
|
|
||||||
|
|
||||||
# 階段 7.3:產生 MinerU 配置檔(指向固定目錄)
|
# 7.4 產生 MinerU 配置檔
|
||||||
RUN <<EOF
|
RUN set -ex && \
|
||||||
echo "📝 [Stage 7.3] 產生 mineru.json..."
|
ARCH=$(uname -m) && \
|
||||||
ARCH=$(uname -m)
|
mkdir -p /opt/convertx && \
|
||||||
if [ "$ARCH" = "aarch64" ]; then
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
echo "⚠️ ARM64:跳過 mineru.json"
|
echo '{"models-dir":{"pipeline":"","vlm":""},"model-source":"local","note":"ARM64 - MinerU not supported"}' > /opt/convertx/mineru.json && \
|
||||||
else
|
cp /opt/convertx/mineru.json /root/mineru.json && \
|
||||||
python3 <<PYTHON
|
echo "⚠️ ARM64:產生空的 mineru.json"; \
|
||||||
import json
|
else \
|
||||||
|
python3 -c " \
|
||||||
config = {
|
import json; \
|
||||||
'models-dir': {
|
import os; \
|
||||||
'pipeline': '/opt/mineru/models/PDF-Extract-Kit-1.0',
|
mineru_models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/convertx/models/mineru'); \
|
||||||
'vlm': ''
|
config = { \
|
||||||
},
|
'models-dir': { \
|
||||||
'model-source': 'local',
|
'pipeline': f'{mineru_models_dir}/PDF-Extract-Kit-1.0', \
|
||||||
'latex-delimiter-config': {
|
'vlm': '' \
|
||||||
'display': {'left': '@@', 'right': '@@'},
|
}, \
|
||||||
'inline': {'left': '@', 'right': '@'}
|
'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)
|
}; \
|
||||||
|
os.makedirs('/opt/convertx', exist_ok=True); \
|
||||||
with open('/root/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: \
|
||||||
print('✅ mineru.json 已產生')
|
json.dump(config, f, indent=2); \
|
||||||
print(json.dumps(config, indent=2))
|
print('✅ mineru.json 已產生'); \
|
||||||
PYTHON
|
"; \
|
||||||
fi
|
fi
|
||||||
EOF
|
|
||||||
|
|
||||||
# 階段 7.4:BabelDOC warmup + 資源下載
|
# 7.5 BabelDOC warmup
|
||||||
# ⚠️ 關鍵修復:完整 warmup,確保所有資源已下載
|
RUN set -ex && \
|
||||||
RUN echo "📥 [Stage 7.4] BabelDOC warmup..." && \
|
export BABELDOC_CACHE_PATH="/root/.cache/babeldoc" && \
|
||||||
export BABELDOC_CACHE_PATH="${BABELDOC_CACHE_DIR}" && \
|
|
||||||
if command -v babeldoc >/dev/null 2>&1; then \
|
if command -v babeldoc >/dev/null 2>&1; then \
|
||||||
echo "執行 babeldoc --warmup..." && \
|
babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告"; \
|
||||||
babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告" && \
|
|
||||||
echo "驗證 BabelDOC 資源..." && \
|
|
||||||
ls -la "${BABELDOC_CACHE_DIR}/" && \
|
|
||||||
du -sh "${BABELDOC_CACHE_DIR}/" || true; \
|
|
||||||
else \
|
else \
|
||||||
echo "⚠️ babeldoc 不可用,跳過 warmup"; \
|
echo "⚠️ babeldoc 不可用,跳過 warmup"; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 階段 7.5:下載 tiktoken 編碼(BabelDOC 依賴)
|
# 7.6 下載 tiktoken 編碼
|
||||||
RUN <<EOF
|
RUN python3 -c " \
|
||||||
echo "📥 [Stage 7.5] 下載 tiktoken 編碼..."
|
try: \
|
||||||
python3 <<PYTHON
|
import tiktoken; \
|
||||||
try:
|
for enc_name in ['cl100k_base', 'p50k_base', 'r50k_base']: \
|
||||||
import tiktoken
|
try: \
|
||||||
for enc_name in ['cl100k_base', 'p50k_base', 'r50k_base']:
|
enc = tiktoken.get_encoding(enc_name); \
|
||||||
try:
|
print(f'✅ tiktoken {enc_name} 已下載'); \
|
||||||
enc = tiktoken.get_encoding(enc_name)
|
except Exception as e: \
|
||||||
print(f'✅ tiktoken {enc_name} 已下載')
|
print(f'⚠️ tiktoken {enc_name} 下載失敗: {e}'); \
|
||||||
except Exception as e:
|
except ImportError: \
|
||||||
print(f'⚠️ tiktoken {enc_name} 下載失敗: {e}')
|
print('⚠️ tiktoken 未安裝,跳過'); \
|
||||||
except ImportError:
|
"
|
||||||
print('⚠️ tiktoken 未安裝,跳過')
|
|
||||||
PYTHON
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 階段 7.6:清理下載 cache(保留實際模型)
|
# 7.7 清理下載快取
|
||||||
RUN echo "🧹 [Stage 7.6] 清理下載 cache..." && \
|
RUN rm -rf /tmp/hf_download_cache /root/.cache/huggingface \
|
||||||
rm -rf /tmp/hf_download_cache && \
|
/root/.cache/pip /root/.cache/uv && \
|
||||||
rm -rf /root/.cache/huggingface && \
|
find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||||||
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
|
# Stage 8: Final Release Image
|
||||||
# ===================================
|
# ==============================================================================
|
||||||
FROM python-tools AS release
|
FROM python-tools AS release
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 從 models-download stage 複製模型
|
# 8.1 從 models stage 複製模型和配置
|
||||||
COPY --from=models-download /opt/mineru /opt/mineru
|
COPY --from=models /opt/convertx /opt/convertx
|
||||||
COPY --from=models-download /opt/babeldoc /opt/babeldoc
|
COPY --from=models /root/.cache/babeldoc /root/.cache/babeldoc
|
||||||
COPY --from=models-download /root/mineru.json /root/mineru.json
|
COPY --from=models /root/mineru.json /root/mineru.json
|
||||||
|
|
||||||
# 複製應用程式
|
# 8.2 複製應用程式
|
||||||
COPY --from=install /temp/prod/node_modules node_modules
|
COPY --from=install /temp/prod/node_modules node_modules
|
||||||
COPY --from=prerelease /app/public/ /app/public/
|
COPY --from=prerelease /app/public/ /app/public/
|
||||||
COPY --from=prerelease /app/dist /app/dist
|
COPY --from=prerelease /app/dist /app/dist
|
||||||
|
|
||||||
# ==============================================================================
|
# 8.3 確保字型目錄完整(fonts stage 已安裝,這裡確保 COPY 覆蓋)
|
||||||
# 安裝自訂字型
|
|
||||||
# ==============================================================================
|
|
||||||
RUN mkdir -p /usr/share/fonts/truetype/custom
|
RUN mkdir -p /usr/share/fonts/truetype/custom
|
||||||
COPY fonts/ /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/
|
COPY models/ /root/.cache/babeldoc/models/
|
||||||
|
|
||||||
# 複製字型到 BabelDOC 目錄
|
# 8.4 更新字型快取
|
||||||
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
|
RUN fc-cache -fv
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
@ -512,23 +453,6 @@ RUN mkdir -p /app/certs && \
|
||||||
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
|
||||||
|
|
||||||
# ==============================================================================
|
|
||||||
# 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 設定
|
# Locale 設定
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
@ -544,25 +468,24 @@ RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/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
|
||||||
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
|
RUN mkdir -p data
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 🔒 Runtime 離線驗證(關鍵步驟)
|
# 🔒 Runtime 離線驗證
|
||||||
# ==============================================================================
|
|
||||||
# ⚠️ 此步驟模擬斷網環境,驗證所有工具可離線執行
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
RUN echo "======================================" && \
|
RUN echo "======================================" && \
|
||||||
echo "🔒 Runtime 離線驗證" && \
|
echo "🔒 Runtime 離線驗證" && \
|
||||||
|
|
@ -570,15 +493,25 @@ RUN echo "======================================" && \
|
||||||
ARCH=$(uname -m) && \
|
ARCH=$(uname -m) && \
|
||||||
VALIDATION_PASSED=true && \
|
VALIDATION_PASSED=true && \
|
||||||
\
|
\
|
||||||
# 驗證 MinerU
|
# 驗證核心工具
|
||||||
|
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..." && \
|
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/mineru/models/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; \
|
||||||
|
|
@ -595,7 +528,7 @@ RUN echo "======================================" && \
|
||||||
# 驗證 BabelDOC
|
# 驗證 BabelDOC
|
||||||
echo "🔍 驗證 BabelDOC..." && \
|
echo "🔍 驗證 BabelDOC..." && \
|
||||||
if command -v babeldoc >/dev/null 2>&1; then \
|
if command -v babeldoc >/dev/null 2>&1; then \
|
||||||
babeldoc --help >/dev/null 2>&1 && echo " ✅ babeldoc --help 成功" || echo " ⚠️ babeldoc --help 有警告"; \
|
echo " ✅ babeldoc: $(which babeldoc)"; \
|
||||||
else \
|
else \
|
||||||
echo " ⚠️ babeldoc 不可用"; \
|
echo " ⚠️ babeldoc 不可用"; \
|
||||||
fi && \
|
fi && \
|
||||||
|
|
@ -603,7 +536,7 @@ RUN echo "======================================" && \
|
||||||
# 驗證 pdf2zh
|
# 驗證 pdf2zh
|
||||||
echo "🔍 驗證 pdf2zh..." && \
|
echo "🔍 驗證 pdf2zh..." && \
|
||||||
if command -v pdf2zh >/dev/null 2>&1; then \
|
if command -v pdf2zh >/dev/null 2>&1; then \
|
||||||
pdf2zh --help >/dev/null 2>&1 && echo " ✅ pdf2zh --help 成功" || echo " ⚠️ pdf2zh --help 有警告"; \
|
echo " ✅ pdf2zh: $(which pdf2zh)"; \
|
||||||
else \
|
else \
|
||||||
echo " ⚠️ pdf2zh 不可用"; \
|
echo " ⚠️ pdf2zh 不可用"; \
|
||||||
fi && \
|
fi && \
|
||||||
|
|
@ -611,22 +544,22 @@ RUN echo "======================================" && \
|
||||||
# 驗證 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 模型
|
||||||
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 不存在(將使用 COPY 複製)"; \
|
echo " ⚠️ DocLayout-YOLO ONNX 不存在"; \
|
||||||
fi && \
|
fi && \
|
||||||
\
|
\
|
||||||
# 驗證字型
|
# 驗證字型
|
||||||
echo "🔍 驗證字型..." && \
|
echo "🔍 驗證字型..." && \
|
||||||
FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l) && \
|
FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l || echo "0") && \
|
||||||
echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \
|
echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \
|
||||||
\
|
\
|
||||||
echo "======================================" && \
|
echo "======================================" && \
|
||||||
|
|
@ -666,6 +599,7 @@ ENV TRANSFORMERS_CACHE="/nonexistent"
|
||||||
# MinerU 強制本地模型
|
# MinerU 強制本地模型
|
||||||
ENV MINERU_MODEL_SOURCE="local"
|
ENV MINERU_MODEL_SOURCE="local"
|
||||||
ENV MINERU_CONFIG="/root/mineru.json"
|
ENV MINERU_CONFIG="/root/mineru.json"
|
||||||
|
ENV MINERU_MODELS_DIR="/opt/convertx/models/mineru"
|
||||||
|
|
||||||
# BabelDOC 離線模式
|
# BabelDOC 離線模式
|
||||||
ENV BABELDOC_OFFLINE="1"
|
ENV BABELDOC_OFFLINE="1"
|
||||||
|
|
|
||||||
|
|
@ -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" ]
|
|
||||||
|
|
@ -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 <<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" ]
|
|
||||||
306
scripts/download-models.sh
Normal file
306
scripts/download-models.sh
Normal file
|
|
@ -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 "$@"
|
||||||
174
scripts/install-fonts.sh
Normal file
174
scripts/install-fonts.sh
Normal file
|
|
@ -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 "$@"
|
||||||
219
scripts/install-python-tools.sh
Normal file
219
scripts/install-python-tools.sh
Normal file
|
|
@ -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 "$@"
|
||||||
305
scripts/install-system-tools.sh
Normal file
305
scripts/install-system-tools.sh
Normal file
|
|
@ -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 <<EOF
|
||||||
|
Acquire::Retries "5";
|
||||||
|
Acquire::http::Timeout "120";
|
||||||
|
Acquire::https::Timeout "120";
|
||||||
|
Acquire::ftp::Timeout "120";
|
||||||
|
APT::Get::Assume-Yes "true";
|
||||||
|
DPkg::Lock::Timeout "120";
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 基礎系統工具
|
||||||
|
# ==============================================================================
|
||||||
|
install_base_tools() {
|
||||||
|
log_info "安裝基礎系統工具..."
|
||||||
|
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/*
|
||||||
|
log_success "基礎系統工具安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 核心轉換工具(輕量)
|
||||||
|
# ==============================================================================
|
||||||
|
install_core_converters() {
|
||||||
|
log_info "安裝核心轉換工具..."
|
||||||
|
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/*
|
||||||
|
log_success "核心轉換工具安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# dasel(JSON/YAML/TOML 轉換)
|
||||||
|
# ==============================================================================
|
||||||
|
install_dasel() {
|
||||||
|
log_info "安裝 dasel..."
|
||||||
|
local DASEL_VERSION="2.8.1"
|
||||||
|
local DASEL_ARCH
|
||||||
|
|
||||||
|
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/v${DASEL_VERSION}/dasel_${DASEL_ARCH}" \
|
||||||
|
-o /usr/local/bin/dasel
|
||||||
|
chmod +x /usr/local/bin/dasel
|
||||||
|
log_success "dasel 安裝完成: $(dasel --version)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# resvg(SVG 渲染器)
|
||||||
|
# ==============================================================================
|
||||||
|
install_resvg() {
|
||||||
|
log_info "安裝 resvg..."
|
||||||
|
local RESVG_VERSION="0.44.0"
|
||||||
|
|
||||||
|
if [ "${ARCH}" = "aarch64" ]; then
|
||||||
|
log_warn "resvg 無 ARM64 預編譯版本,跳過安裝"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \
|
||||||
|
"https://github.com/linebender/resvg/releases/download/v${RESVG_VERSION}/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
|
||||||
|
log_success "resvg 安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# deark(檔案格式解碼器)
|
||||||
|
# ==============================================================================
|
||||||
|
install_deark() {
|
||||||
|
log_info "編譯安裝 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/*
|
||||||
|
log_success "deark 安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# vtracer(向量圖追蹤)
|
||||||
|
# ==============================================================================
|
||||||
|
install_vtracer() {
|
||||||
|
log_info "安裝 vtracer..."
|
||||||
|
local VTRACER_VERSION="0.6.4"
|
||||||
|
local VTRACER_ASSET
|
||||||
|
|
||||||
|
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/${VTRACER_VERSION}/${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
|
||||||
|
log_success "vtracer 安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# FFmpeg(影音處理)
|
||||||
|
# ==============================================================================
|
||||||
|
install_ffmpeg() {
|
||||||
|
log_info "安裝 FFmpeg..."
|
||||||
|
apt-get update --fix-missing
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg \
|
||||||
|
libavcodec-extra \
|
||||||
|
libva2
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
log_success "FFmpeg 安裝完成: $(ffmpeg -version | head -1)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 圖像處理工具(ImageMagick, Inkscape, vips 等)
|
||||||
|
# ==============================================================================
|
||||||
|
install_image_tools() {
|
||||||
|
log_info "安裝圖像處理工具..."
|
||||||
|
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/*
|
||||||
|
log_success "圖像處理工具安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 文件處理工具(Calibre, Pandoc)
|
||||||
|
# ==============================================================================
|
||||||
|
install_document_tools() {
|
||||||
|
log_info "安裝 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/*
|
||||||
|
log_success "Calibre + Pandoc 安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# LibreOffice
|
||||||
|
# ==============================================================================
|
||||||
|
install_libreoffice() {
|
||||||
|
log_info "安裝 LibreOffice(需數分鐘)..."
|
||||||
|
apt-get update --fix-missing
|
||||||
|
apt-get install -y --no-install-recommends libreoffice
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
log_success "LibreOffice 安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# TexLive(LaTeX)
|
||||||
|
# ==============================================================================
|
||||||
|
install_texlive() {
|
||||||
|
log_info "安裝 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/*
|
||||||
|
|
||||||
|
log_info "安裝 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/*
|
||||||
|
log_success "TexLive 安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Tesseract OCR
|
||||||
|
# ==============================================================================
|
||||||
|
install_tesseract() {
|
||||||
|
log_info "安裝 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/*
|
||||||
|
log_success "Tesseract OCR 安裝完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 主程式
|
||||||
|
# ==============================================================================
|
||||||
|
main() {
|
||||||
|
log_info "=========================================="
|
||||||
|
log_info "開始安裝系統工具 (${ARCH})"
|
||||||
|
log_info "=========================================="
|
||||||
|
|
||||||
|
configure_apt
|
||||||
|
install_base_tools
|
||||||
|
install_core_converters
|
||||||
|
install_dasel
|
||||||
|
install_resvg
|
||||||
|
install_deark
|
||||||
|
install_vtracer
|
||||||
|
install_ffmpeg
|
||||||
|
install_image_tools
|
||||||
|
install_document_tools
|
||||||
|
install_libreoffice
|
||||||
|
install_texlive
|
||||||
|
install_tesseract
|
||||||
|
|
||||||
|
log_success "=========================================="
|
||||||
|
log_success "所有系統工具安裝完成!"
|
||||||
|
log_success "=========================================="
|
||||||
|
}
|
||||||
|
|
||||||
|
# 執行
|
||||||
|
main "$@"
|
||||||
341
scripts/verify-installation.sh
Normal file
341
scripts/verify-installation.sh
Normal file
|
|
@ -0,0 +1,341 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==============================================================================
|
||||||
|
# ConvertX-CN 安裝驗證腳本
|
||||||
|
# ==============================================================================
|
||||||
|
# 用途:在 Docker build 最終階段驗證所有工具和模型
|
||||||
|
# 執行環境:Docker build stage (final) 或 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)
|
||||||
|
|
||||||
|
# 計數器
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
WARN=0
|
||||||
|
SKIP=0
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 檢查工具函數
|
||||||
|
# ==============================================================================
|
||||||
|
check_command() {
|
||||||
|
local cmd="$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 command -v "${cmd}" >/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 "$@"
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# ConvertX-CN 模型驗證腳本 v2.0
|
# ConvertX-CN 模型驗證腳本 v3.0
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
#
|
#
|
||||||
# 用途:驗證 Docker Image 中的預下載模型是否完整
|
# 用途:驗證 Docker Image 中的預下載模型是否完整
|
||||||
# 執行:docker exec <container> /app/scripts/verify-models.sh
|
# 執行:docker exec <container> /app/scripts/verify-models.sh
|
||||||
#
|
#
|
||||||
|
# 版本:v3.0 - 適配重構後的目錄結構
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo "🔍 ConvertX-CN 模型驗證 v2.0"
|
echo "🔍 ConvertX-CN 模型驗證 v3.0"
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|
@ -21,12 +22,18 @@ FAIL=0
|
||||||
WARN=0
|
WARN=0
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
|
# 新的模型目錄結構
|
||||||
|
MODELS_BASE_DIR="/opt/convertx/models"
|
||||||
|
MINERU_MODELS_DIR="${MODELS_BASE_DIR}/mineru"
|
||||||
|
BABELDOC_CACHE_DIR="/root/.cache/babeldoc"
|
||||||
|
|
||||||
# 檢查函數
|
# 檢查函數
|
||||||
check_file() {
|
check_file() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local name="$2"
|
local name="$2"
|
||||||
if [ -f "$path" ]; then
|
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)"
|
echo "✅ $name: $path ($size)"
|
||||||
((PASS++))
|
((PASS++))
|
||||||
else
|
else
|
||||||
|
|
@ -40,7 +47,8 @@ check_dir() {
|
||||||
local name="$2"
|
local name="$2"
|
||||||
local required="${3:-false}"
|
local required="${3:-false}"
|
||||||
if [ -d "$path" ]; then
|
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)"
|
echo "✅ $name: $path ($size)"
|
||||||
((PASS++))
|
((PASS++))
|
||||||
else
|
else
|
||||||
|
|
@ -59,7 +67,7 @@ check_dir() {
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "📦 PDFMathTranslate / BabelDOC 模型"
|
echo "📦 PDFMathTranslate / BabelDOC 模型"
|
||||||
echo "----------------------------------------"
|
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 ""
|
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/SourceHanSerifTW-Regular.ttf" "思源宋體(繁體)"
|
||||||
check_file "/usr/share/fonts/truetype/custom/SourceHanSerifJP-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/SourceHanSerifKR-Regular.ttf" "思源宋體(韓文)"
|
||||||
|
check_file "/usr/share/fonts/truetype/custom/BiauKai.ttf" "標楷體"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 3. MinerU 模型(新路徑:/opt/mineru)
|
# 3. MinerU 模型(新路徑:/opt/convertx/models/mineru)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "📦 MinerU 模型"
|
echo "📦 MinerU 模型"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
|
|
@ -83,8 +92,9 @@ if [ "$ARCH" = "aarch64" ]; then
|
||||||
echo "⚠️ ARM64 架構:MinerU 不支援,跳過驗證"
|
echo "⚠️ ARM64 架構:MinerU 不支援,跳過驗證"
|
||||||
((WARN++))
|
((WARN++))
|
||||||
else
|
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 "/root/mineru.json" "MinerU 配置檔"
|
||||||
|
check_file "/opt/convertx/mineru.json" "MinerU 配置檔(備份)"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|
@ -93,9 +103,9 @@ echo ""
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "📦 BabelDOC 快取"
|
echo "📦 BabelDOC 快取"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
check_dir "/root/.cache/babeldoc" "BabelDOC 快取"
|
check_dir "${BABELDOC_CACHE_DIR}" "BabelDOC 快取"
|
||||||
check_dir "/root/.cache/babeldoc/models" "BabelDOC Models"
|
check_dir "${BABELDOC_CACHE_DIR}/models" "BabelDOC Models"
|
||||||
check_dir "/root/.cache/babeldoc/fonts" "BabelDOC Fonts"
|
check_dir "${BABELDOC_CACHE_DIR}/fonts" "BabelDOC Fonts"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ else
|
||||||
check_command "mineru" "MinerU CLI"
|
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"
|
check_dir "$MINERU_MODELS" "MinerU Pipeline 模型" "true"
|
||||||
|
|
||||||
# 檢查配置檔
|
# 檢查配置檔
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue