feat: 完全離線化 Dockerfile v0.1.16
## 主要修復 - MinerU: 改用 system-level 安裝 (uv pip install --system) - MinerU: 模型下載到固定目錄 /opt/mineru/models - BabelDOC: 完整 warmup + 離線 cache - PDFMathTranslate: ONNX 模型使用 COPY 指令 - ImageMagick: 確保已安裝 ## 結構優化 - Multi-Stage Build (8 stages) - 使用 heredoc 語法處理多行 Python - 分階段安裝避免記憶體尖峰 ## Runtime 離線模式 - HF_HOME=/nonexistent - HF_HUB_OFFLINE=1 - BABELDOC_OFFLINE=1 - PIP_NO_INDEX=1 ## 新增驗證腳本 - scripts/verify-offline.sh
This commit is contained in:
parent
b23d42a7b1
commit
367e0d3056
4 changed files with 1682 additions and 761 deletions
1058
Dockerfile
1058
Dockerfile
File diff suppressed because it is too large
Load diff
692
Dockerfile.offline
Normal file
692
Dockerfile.offline
Normal file
|
|
@ -0,0 +1,692 @@
|
||||||
|
# ==============================================================================
|
||||||
|
# ConvertX-CN 官方 Docker Image
|
||||||
|
# 版本:v0.1.16 - 完全離線化版本
|
||||||
|
# ==============================================================================
|
||||||
|
#
|
||||||
|
# 📦 Image 說明:
|
||||||
|
# - 這是 ConvertX-CN 官方 Docker Hub Image 的生產 Dockerfile
|
||||||
|
# - ⚠️ 所有模型、字型、tokenizer 已在 build 階段完整預下載
|
||||||
|
# - ⚠️ Runtime 完全離線運行(僅翻譯服務允許連網)
|
||||||
|
#
|
||||||
|
# 🔒 Offline-first 設計原則(硬性規則):
|
||||||
|
# 1. Runtime(docker run 後):
|
||||||
|
# ❌ 禁止任何模型、字型、tokenizer、metadata 下載
|
||||||
|
# ❌ MinerU / BabelDOC / PDFMathTranslate 不得嘗試連網
|
||||||
|
# ✅ 只有翻譯服務(Google / DeepL / Azure / OpenAI)允許連網
|
||||||
|
# 2. Build time(docker build 時):
|
||||||
|
# ✅ 允許連網下載所有資源
|
||||||
|
# ✅ 所有「可能會在 runtime 下載的東西」必須提前固定存放
|
||||||
|
#
|
||||||
|
# 🤖 預下載模型清單:
|
||||||
|
# - PDFMathTranslate: DocLayout-YOLO ONNX(佈局分析)
|
||||||
|
# - BabelDOC: DocLayout-YOLO + 字型資源 + tiktoken
|
||||||
|
# - MinerU: PDF-Extract-Kit-1.0(Pipeline 模型)
|
||||||
|
# 包含:DocLayout-YOLO, YOLOv8 MFD, UniMERNet, PaddleOCR, LayoutReader, SLANet
|
||||||
|
#
|
||||||
|
# 🌍 內建語言支援:
|
||||||
|
# - OCR: 英文、繁體中文、簡體中文、日文、韓文、德文、法文
|
||||||
|
# - Locale: en_US, zh_TW, zh_CN, ja_JP, ko_KR, de_DE, fr_FR
|
||||||
|
# - 字型: Noto CJK, Liberation, 標楷體, Source Han Serif
|
||||||
|
# - LaTeX: CJK、德文、法文、阿拉伯語、希伯來語
|
||||||
|
#
|
||||||
|
# 📊 Image 大小:約 10-14 GB(含完整模型)
|
||||||
|
#
|
||||||
|
# ==============================================================================
|
||||||
|
# 🏗️ Multi-Stage Build 結構:
|
||||||
|
# Stage 1 [base]: Bun runtime 基礎
|
||||||
|
# Stage 2 [install]: Node 依賴安裝
|
||||||
|
# Stage 3 [prerelease]: 應用程式建構
|
||||||
|
# Stage 4 [system-tools]: APT 系統工具
|
||||||
|
# Stage 5 [python-base]: Python 基礎環境
|
||||||
|
# Stage 6 [python-tools]: Python CLI 工具
|
||||||
|
# Stage 7 [models-download]: 模型下載(獨立 stage)
|
||||||
|
# Stage 8 [final]: 最終 Image(合併所有層)
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 1: Base - Bun Runtime
|
||||||
|
# ===================================
|
||||||
|
FROM debian:bookworm-slim AS base
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/pi-docket/ConvertX-CN"
|
||||||
|
LABEL org.opencontainers.image.description="ConvertX-CN - 完全離線化檔案轉換服務"
|
||||||
|
LABEL org.opencontainers.image.version="v0.1.16-offline"
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 設定非互動模式
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# 配置 APT 重試機制
|
||||||
|
RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
|
||||||
|
|
||||||
|
# 安裝基礎工具
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
unzip \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 安裝 Bun(根據架構選擇版本)
|
||||||
|
RUN ARCH=$(uname -m) && \
|
||||||
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
|
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
|
||||||
|
-o bun-linux-aarch64.zip \
|
||||||
|
https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-aarch64.zip; \
|
||||||
|
else \
|
||||||
|
curl -fsSL --retry 3 --retry-delay 5 --retry-all-errors \
|
||||||
|
-o bun-linux-x64-baseline.zip \
|
||||||
|
https://github.com/oven-sh/bun/releases/download/bun-v1.3.6/bun-linux-x64-baseline.zip; \
|
||||||
|
fi && \
|
||||||
|
unzip -j bun-linux-*.zip -d /usr/local/bin && \
|
||||||
|
rm bun-linux-*.zip && \
|
||||||
|
chmod +x /usr/local/bin/bun
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 2: Install - Node Dependencies
|
||||||
|
# ===================================
|
||||||
|
FROM base AS install
|
||||||
|
|
||||||
|
# 開發依賴
|
||||||
|
RUN mkdir -p /temp/dev
|
||||||
|
COPY package.json bun.lock /temp/dev/
|
||||||
|
RUN cd /temp/dev && bun install --frozen-lockfile
|
||||||
|
|
||||||
|
# 生產依賴
|
||||||
|
RUN mkdir -p /temp/prod
|
||||||
|
COPY package.json bun.lock /temp/prod/
|
||||||
|
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 3: Prerelease - Build App
|
||||||
|
# ===================================
|
||||||
|
FROM base AS prerelease
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
|
COPY . .
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 4: System Tools
|
||||||
|
# ===================================
|
||||||
|
FROM base AS system-tools
|
||||||
|
|
||||||
|
# 配置 APT
|
||||||
|
RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'Acquire::https::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'APT::Get::Assume-Yes "true";' >> /etc/apt/apt.conf.d/80-retries \
|
||||||
|
&& echo 'DPkg::Lock::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
|
||||||
|
|
||||||
|
# 階段 4.1:基礎系統工具 + Locale
|
||||||
|
RUN echo "📦 [Stage 4.1] 安裝基礎系統工具..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
locales \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
openssl \
|
||||||
|
git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.2:核心轉換工具(輕量)
|
||||||
|
RUN echo "📦 [Stage 4.2] 安裝核心轉換工具..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
assimp-utils \
|
||||||
|
dcraw \
|
||||||
|
dvisvgm \
|
||||||
|
ghostscript \
|
||||||
|
graphicsmagick \
|
||||||
|
mupdf-tools \
|
||||||
|
poppler-utils \
|
||||||
|
potrace \
|
||||||
|
qpdf \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.3:安裝 dasel(從 GitHub 二進位)
|
||||||
|
RUN echo "📦 [Stage 4.3] 安裝 dasel..." && \
|
||||||
|
ARCH=$(uname -m) && \
|
||||||
|
if [ "$ARCH" = "aarch64" ]; then DASEL_ARCH="linux_arm64"; \
|
||||||
|
else DASEL_ARCH="linux_amd64"; fi && \
|
||||||
|
curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \
|
||||||
|
"https://github.com/TomWright/dasel/releases/download/v2.8.1/dasel_${DASEL_ARCH}" \
|
||||||
|
-o /usr/local/bin/dasel && \
|
||||||
|
chmod +x /usr/local/bin/dasel
|
||||||
|
|
||||||
|
# 階段 4.4:安裝 resvg(僅 x86_64)
|
||||||
|
RUN echo "📦 [Stage 4.4] 安裝 resvg..." && \
|
||||||
|
ARCH=$(uname -m) && \
|
||||||
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
|
echo "⚠️ resvg 無 ARM64 版本,跳過"; \
|
||||||
|
else \
|
||||||
|
curl -sSLf --retry 3 --retry-delay 5 --retry-all-errors \
|
||||||
|
"https://github.com/linebender/resvg/releases/download/v0.44.0/resvg-linux-x86_64.tar.gz" \
|
||||||
|
-o /tmp/resvg.tar.gz && \
|
||||||
|
tar -xzf /tmp/resvg.tar.gz -C /tmp/ && \
|
||||||
|
mv /tmp/resvg /usr/local/bin/resvg && \
|
||||||
|
chmod +x /usr/local/bin/resvg && \
|
||||||
|
rm -rf /tmp/resvg.tar.gz; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 階段 4.5:編譯安裝 deark
|
||||||
|
RUN echo "📦 [Stage 4.5] 編譯 deark..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
&& cd /tmp && \
|
||||||
|
git clone --depth 1 https://github.com/jsummers/deark.git && \
|
||||||
|
cd deark && make -j$(nproc) && \
|
||||||
|
cp deark /usr/local/bin/deark && \
|
||||||
|
chmod +x /usr/local/bin/deark && \
|
||||||
|
cd / && rm -rf /tmp/deark && \
|
||||||
|
apt-get remove -y build-essential && \
|
||||||
|
apt-get autoremove -y && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.6:影音處理工具
|
||||||
|
RUN echo "📦 [Stage 4.6] 安裝 FFmpeg..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
ffmpeg \
|
||||||
|
libavcodec-extra \
|
||||||
|
libva2 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.7:圖像處理工具(含 ImageMagick)
|
||||||
|
# ⚠️ 修復:確保 ImageMagick 已安裝
|
||||||
|
RUN echo "📦 [Stage 4.7] 安裝圖像處理工具(含 ImageMagick)..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
imagemagick \
|
||||||
|
inkscape \
|
||||||
|
libheif-examples \
|
||||||
|
libjxl-tools \
|
||||||
|
libvips-tools \
|
||||||
|
xauth \
|
||||||
|
xvfb \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.8:文件處理工具
|
||||||
|
RUN echo "📦 [Stage 4.8] 安裝 Calibre + Pandoc..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
calibre \
|
||||||
|
libemail-outlook-message-perl \
|
||||||
|
pandoc \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.9:LibreOffice
|
||||||
|
RUN echo "📦 [Stage 4.9] 安裝 LibreOffice(需數分鐘)..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
libreoffice \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.10:TexLive 基礎
|
||||||
|
RUN echo "📦 [Stage 4.10] 安裝 TexLive 基礎..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
texlive-base \
|
||||||
|
texlive-latex-base \
|
||||||
|
texlive-latex-recommended \
|
||||||
|
texlive-fonts-recommended \
|
||||||
|
texlive-xetex \
|
||||||
|
latexmk \
|
||||||
|
lmodern \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.11:TexLive 語言包
|
||||||
|
RUN echo "📦 [Stage 4.11] 安裝 TexLive 語言包..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
texlive-lang-cjk \
|
||||||
|
texlive-lang-german \
|
||||||
|
texlive-lang-french \
|
||||||
|
texlive-lang-arabic \
|
||||||
|
texlive-lang-other \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.12:OCR 支援
|
||||||
|
RUN echo "📦 [Stage 4.12] 安裝 Tesseract OCR..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
tesseract-ocr \
|
||||||
|
tesseract-ocr-eng \
|
||||||
|
tesseract-ocr-chi-tra \
|
||||||
|
tesseract-ocr-chi-sim \
|
||||||
|
tesseract-ocr-jpn \
|
||||||
|
tesseract-ocr-kor \
|
||||||
|
tesseract-ocr-deu \
|
||||||
|
tesseract-ocr-fra \
|
||||||
|
ocrmypdf \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 4.13:字型
|
||||||
|
RUN echo "📦 [Stage 4.13] 安裝系統字型..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
fonts-noto-cjk \
|
||||||
|
fonts-noto-core \
|
||||||
|
fonts-noto-color-emoji \
|
||||||
|
fonts-liberation \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 5: Python Base
|
||||||
|
# ===================================
|
||||||
|
FROM system-tools AS python-base
|
||||||
|
|
||||||
|
# 階段 5.1:Python 基礎 + uv
|
||||||
|
RUN echo "📦 [Stage 5.1] 安裝 Python 基礎..." && \
|
||||||
|
apt-get update --fix-missing && apt-get install -y --no-install-recommends \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-venv \
|
||||||
|
python3-numpy \
|
||||||
|
python3-tinycss2 \
|
||||||
|
python3-opencv \
|
||||||
|
python3-img2pdf \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 5.2:安裝 uv(Python 套件管理器)
|
||||||
|
# ⚠️ 重要:使用 uv 作為主要安裝方式,確保一致性
|
||||||
|
RUN echo "📦 [Stage 5.2] 安裝 uv..." && \
|
||||||
|
pip3 install --no-cache-dir --break-system-packages uv && \
|
||||||
|
uv --version
|
||||||
|
|
||||||
|
# 設定 PATH
|
||||||
|
ENV PATH="/root/.local/bin:/usr/local/bin:${PATH}"
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 6: Python Tools
|
||||||
|
# ===================================
|
||||||
|
FROM python-base AS python-tools
|
||||||
|
|
||||||
|
# 階段 6.1:安裝 huggingface_hub + endesive(PDF 簽章)
|
||||||
|
RUN echo "📦 [Stage 6.1] 安裝 huggingface_hub + endesive..." && \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
swig \
|
||||||
|
libpcsclite-dev \
|
||||||
|
&& uv pip install --system --no-cache huggingface_hub endesive && \
|
||||||
|
apt-get remove -y build-essential swig && \
|
||||||
|
apt-get autoremove -y && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 階段 6.2:安裝 markitdown
|
||||||
|
RUN echo "📦 [Stage 6.2] 安裝 markitdown..." && \
|
||||||
|
uv pip install --system --no-cache "markitdown[all]"
|
||||||
|
|
||||||
|
# 階段 6.3:安裝 pdf2zh
|
||||||
|
RUN echo "📦 [Stage 6.3] 安裝 pdf2zh..." && \
|
||||||
|
uv pip install --system --no-cache pdf2zh
|
||||||
|
|
||||||
|
# 階段 6.4:安裝 babeldoc
|
||||||
|
RUN echo "📦 [Stage 6.4] 安裝 babeldoc..." && \
|
||||||
|
uv pip install --system --no-cache babeldoc || echo "⚠️ babeldoc 安裝可能有警告"
|
||||||
|
|
||||||
|
# 階段 6.5:安裝 MinerU(⚠️ 關鍵修復)
|
||||||
|
# ⚠️ 只使用 system-level 安裝(uv pip install --system)
|
||||||
|
# ⚠️ 禁止使用 pipx(會導致 binary 不在 PATH)
|
||||||
|
RUN echo "📦 [Stage 6.5] 安裝 MinerU(system-level only)..." && \
|
||||||
|
ARCH=$(uname -m) && \
|
||||||
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
|
echo "⚠️ ARM64:MinerU 不支援,跳過"; \
|
||||||
|
else \
|
||||||
|
echo "🔧 使用 uv pip install --system(官方推薦)..." && \
|
||||||
|
uv pip install --system --no-cache -U "mineru[all]" && \
|
||||||
|
echo "✅ 驗證 MinerU 安裝..." && \
|
||||||
|
which mineru && \
|
||||||
|
mineru --version || echo "(版本資訊可能不可用)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 7: Models Download(獨立 Stage)
|
||||||
|
# ===================================
|
||||||
|
FROM python-tools AS models-download
|
||||||
|
|
||||||
|
# 模型目錄定義
|
||||||
|
ENV MINERU_MODELS_DIR="/opt/mineru/models"
|
||||||
|
ENV BABELDOC_CACHE_DIR="/opt/babeldoc/cache"
|
||||||
|
ENV HF_HOME="/tmp/hf_download_cache"
|
||||||
|
|
||||||
|
# 階段 7.1:創建目錄結構
|
||||||
|
RUN mkdir -p "${MINERU_MODELS_DIR}" && \
|
||||||
|
mkdir -p "${BABELDOC_CACHE_DIR}/models" && \
|
||||||
|
mkdir -p "${BABELDOC_CACHE_DIR}/fonts" && \
|
||||||
|
mkdir -p "${BABELDOC_CACHE_DIR}/cmap" && \
|
||||||
|
mkdir -p "${BABELDOC_CACHE_DIR}/tiktoken"
|
||||||
|
|
||||||
|
# 階段 7.2:下載 MinerU Pipeline 模型
|
||||||
|
# ⚠️ 關鍵修復:使用顯式下載,存放到固定目錄
|
||||||
|
RUN <<EOF
|
||||||
|
echo "📥 [Stage 7.2] 下載 MinerU Pipeline 模型..."
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
if [ "$ARCH" = "aarch64" ]; then
|
||||||
|
echo "⚠️ ARM64:跳過 MinerU 模型下載"
|
||||||
|
else
|
||||||
|
python3 <<PYTHON
|
||||||
|
from huggingface_hub import snapshot_download
|
||||||
|
import os
|
||||||
|
|
||||||
|
models_dir = os.environ.get('MINERU_MODELS_DIR', '/opt/mineru/models')
|
||||||
|
print(f'下載 PDF-Extract-Kit-1.0 到 {models_dir}...')
|
||||||
|
|
||||||
|
snapshot_download(
|
||||||
|
repo_id='opendatalab/PDF-Extract-Kit-1.0',
|
||||||
|
local_dir=f'{models_dir}/PDF-Extract-Kit-1.0',
|
||||||
|
local_dir_use_symlinks=False,
|
||||||
|
resume_download=True
|
||||||
|
)
|
||||||
|
print('✅ PDF-Extract-Kit-1.0 下載完成')
|
||||||
|
PYTHON
|
||||||
|
echo "📋 MinerU 模型目錄內容:"
|
||||||
|
ls -la "${MINERU_MODELS_DIR}/" || true
|
||||||
|
du -sh "${MINERU_MODELS_DIR}/PDF-Extract-Kit-1.0" || true
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 階段 7.3:產生 MinerU 配置檔(指向固定目錄)
|
||||||
|
RUN <<EOF
|
||||||
|
echo "📝 [Stage 7.3] 產生 mineru.json..."
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
if [ "$ARCH" = "aarch64" ]; then
|
||||||
|
echo "⚠️ ARM64:跳過 mineru.json"
|
||||||
|
else
|
||||||
|
python3 <<PYTHON
|
||||||
|
import json
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'models-dir': {
|
||||||
|
'pipeline': '/opt/mineru/models/PDF-Extract-Kit-1.0',
|
||||||
|
'vlm': ''
|
||||||
|
},
|
||||||
|
'model-source': 'local',
|
||||||
|
'latex-delimiter-config': {
|
||||||
|
'display': {'left': '@@', 'right': '@@'},
|
||||||
|
'inline': {'left': '@', 'right': '@'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with open('/opt/mineru/mineru.json', 'w') as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
with open('/root/mineru.json', 'w') as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
|
||||||
|
print('✅ mineru.json 已產生')
|
||||||
|
print(json.dumps(config, indent=2))
|
||||||
|
PYTHON
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 階段 7.4:BabelDOC warmup + 資源下載
|
||||||
|
# ⚠️ 關鍵修復:完整 warmup,確保所有資源已下載
|
||||||
|
RUN echo "📥 [Stage 7.4] BabelDOC warmup..." && \
|
||||||
|
export BABELDOC_CACHE_PATH="${BABELDOC_CACHE_DIR}" && \
|
||||||
|
if command -v babeldoc >/dev/null 2>&1; then \
|
||||||
|
echo "執行 babeldoc --warmup..." && \
|
||||||
|
babeldoc --warmup 2>&1 || echo "⚠️ warmup 可能有警告" && \
|
||||||
|
echo "驗證 BabelDOC 資源..." && \
|
||||||
|
ls -la "${BABELDOC_CACHE_DIR}/" && \
|
||||||
|
du -sh "${BABELDOC_CACHE_DIR}/" || true; \
|
||||||
|
else \
|
||||||
|
echo "⚠️ babeldoc 不可用,跳過 warmup"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 階段 7.5:下載 tiktoken 編碼(BabelDOC 依賴)
|
||||||
|
RUN <<EOF
|
||||||
|
echo "📥 [Stage 7.5] 下載 tiktoken 編碼..."
|
||||||
|
python3 <<PYTHON
|
||||||
|
try:
|
||||||
|
import tiktoken
|
||||||
|
for enc_name in ['cl100k_base', 'p50k_base', 'r50k_base']:
|
||||||
|
try:
|
||||||
|
enc = tiktoken.get_encoding(enc_name)
|
||||||
|
print(f'✅ tiktoken {enc_name} 已下載')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'⚠️ tiktoken {enc_name} 下載失敗: {e}')
|
||||||
|
except ImportError:
|
||||||
|
print('⚠️ tiktoken 未安裝,跳過')
|
||||||
|
PYTHON
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 階段 7.6:清理下載 cache(保留實際模型)
|
||||||
|
RUN echo "🧹 [Stage 7.6] 清理下載 cache..." && \
|
||||||
|
rm -rf /tmp/hf_download_cache && \
|
||||||
|
rm -rf /root/.cache/huggingface && \
|
||||||
|
rm -rf /root/.cache/pip && \
|
||||||
|
rm -rf /root/.cache/uv && \
|
||||||
|
find /usr -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \
|
||||||
|
echo "✅ Cache 清理完成"
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Stage 8: Final Image
|
||||||
|
# ===================================
|
||||||
|
FROM python-tools AS release
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 從 models-download stage 複製模型
|
||||||
|
COPY --from=models-download /opt/mineru /opt/mineru
|
||||||
|
COPY --from=models-download /opt/babeldoc /opt/babeldoc
|
||||||
|
COPY --from=models-download /root/mineru.json /root/mineru.json
|
||||||
|
|
||||||
|
# 複製應用程式
|
||||||
|
COPY --from=install /temp/prod/node_modules node_modules
|
||||||
|
COPY --from=prerelease /app/public/ /app/public/
|
||||||
|
COPY --from=prerelease /app/dist /app/dist
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 安裝自訂字型
|
||||||
|
# ==============================================================================
|
||||||
|
RUN mkdir -p /usr/share/fonts/truetype/custom
|
||||||
|
COPY fonts/ /usr/share/fonts/truetype/custom/
|
||||||
|
|
||||||
|
# 複製預下載的 ONNX 模型
|
||||||
|
RUN mkdir -p /root/.cache/babeldoc/models
|
||||||
|
COPY models/ /root/.cache/babeldoc/models/
|
||||||
|
|
||||||
|
# 複製字型到 BabelDOC 目錄
|
||||||
|
RUN mkdir -p /root/.cache/babeldoc/fonts && \
|
||||||
|
cp /usr/share/fonts/truetype/custom/GoNotoKurrent-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \
|
||||||
|
cp /usr/share/fonts/truetype/custom/SourceHanSerifCN-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \
|
||||||
|
cp /usr/share/fonts/truetype/custom/SourceHanSerifTW-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \
|
||||||
|
cp /usr/share/fonts/truetype/custom/SourceHanSerifJP-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true && \
|
||||||
|
cp /usr/share/fonts/truetype/custom/SourceHanSerifKR-Regular.ttf /root/.cache/babeldoc/fonts/ 2>/dev/null || true
|
||||||
|
|
||||||
|
# 同步 BabelDOC cache 從固定目錄
|
||||||
|
RUN if [ -d /opt/babeldoc/cache ]; then \
|
||||||
|
cp -r /opt/babeldoc/cache/* /root/.cache/babeldoc/ 2>/dev/null || true; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 更新字型 cache
|
||||||
|
RUN fc-cache -fv
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# PDF 簽章憑證
|
||||||
|
# ==============================================================================
|
||||||
|
RUN mkdir -p /app/certs && \
|
||||||
|
openssl req -x509 -newkey rsa:2048 \
|
||||||
|
-keyout /tmp/key.pem -out /tmp/cert.pem \
|
||||||
|
-days 3650 -nodes \
|
||||||
|
-subj "/CN=PDF Packager Default/O=ConvertX-CN/C=TW" && \
|
||||||
|
openssl pkcs12 -export \
|
||||||
|
-inkey /tmp/key.pem -in /tmp/cert.pem \
|
||||||
|
-out /app/certs/default.p12 \
|
||||||
|
-passout pass: && \
|
||||||
|
rm -f /tmp/key.pem /tmp/cert.pem && \
|
||||||
|
chmod 644 /app/certs/default.p12
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# VTracer
|
||||||
|
# ==============================================================================
|
||||||
|
RUN ARCH=$(uname -m) && \
|
||||||
|
if [ "$ARCH" = "aarch64" ]; then \
|
||||||
|
VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \
|
||||||
|
else \
|
||||||
|
VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \
|
||||||
|
fi && \
|
||||||
|
curl -L --retry 3 --retry-delay 5 --retry-all-errors \
|
||||||
|
-o /tmp/vtracer.tar.gz \
|
||||||
|
"https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \
|
||||||
|
tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \
|
||||||
|
mv /tmp/vtracer /usr/local/bin/vtracer && \
|
||||||
|
chmod +x /usr/local/bin/vtracer && \
|
||||||
|
rm /tmp/vtracer.tar.gz
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# Locale 設定
|
||||||
|
# ==============================================================================
|
||||||
|
RUN sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
|
sed -i 's/# zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
|
sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
|
sed -i 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
|
sed -i 's/# ko_KR.UTF-8 UTF-8/ko_KR.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
|
sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
|
sed -i 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \
|
||||||
|
locale-gen
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 最終清理
|
||||||
|
# ==============================================================================
|
||||||
|
RUN rm -rf /usr/share/doc/texlive* \
|
||||||
|
&& rm -rf /usr/share/texlive/texmf-dist/doc \
|
||||||
|
&& rm -rf /usr/share/doc/* \
|
||||||
|
&& rm -rf /usr/share/man/* \
|
||||||
|
&& rm -rf /usr/share/info/* \
|
||||||
|
&& rm -rf /tmp/* \
|
||||||
|
&& rm -rf /var/tmp/*
|
||||||
|
|
||||||
|
# 複製驗證腳本
|
||||||
|
COPY scripts/verify-models.sh /app/scripts/verify-models.sh
|
||||||
|
RUN chmod +x /app/scripts/verify-models.sh
|
||||||
|
|
||||||
|
# 創建資料目錄
|
||||||
|
RUN mkdir -p data
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 🔒 Runtime 離線驗證(關鍵步驟)
|
||||||
|
# ==============================================================================
|
||||||
|
# ⚠️ 此步驟模擬斷網環境,驗證所有工具可離線執行
|
||||||
|
# ==============================================================================
|
||||||
|
RUN echo "======================================" && \
|
||||||
|
echo "🔒 Runtime 離線驗證" && \
|
||||||
|
echo "======================================" && \
|
||||||
|
ARCH=$(uname -m) && \
|
||||||
|
VALIDATION_PASSED=true && \
|
||||||
|
\
|
||||||
|
# 驗證 MinerU
|
||||||
|
echo "🔍 驗證 MinerU..." && \
|
||||||
|
if [ "$ARCH" != "aarch64" ]; then \
|
||||||
|
if command -v mineru >/dev/null 2>&1; then \
|
||||||
|
echo " ✅ mineru 可執行: $(which mineru)"; \
|
||||||
|
else \
|
||||||
|
echo " ❌ mineru 不可執行" && VALIDATION_PASSED=false; \
|
||||||
|
fi && \
|
||||||
|
if [ -d "/opt/mineru/models/PDF-Extract-Kit-1.0" ]; then \
|
||||||
|
echo " ✅ MinerU 模型目錄存在"; \
|
||||||
|
else \
|
||||||
|
echo " ❌ MinerU 模型目錄不存在" && VALIDATION_PASSED=false; \
|
||||||
|
fi && \
|
||||||
|
if [ -f "/root/mineru.json" ]; then \
|
||||||
|
echo " ✅ mineru.json 存在"; \
|
||||||
|
else \
|
||||||
|
echo " ❌ mineru.json 不存在" && VALIDATION_PASSED=false; \
|
||||||
|
fi; \
|
||||||
|
else \
|
||||||
|
echo " ⚠️ ARM64:跳過 MinerU 驗證"; \
|
||||||
|
fi && \
|
||||||
|
\
|
||||||
|
# 驗證 BabelDOC
|
||||||
|
echo "🔍 驗證 BabelDOC..." && \
|
||||||
|
if command -v babeldoc >/dev/null 2>&1; then \
|
||||||
|
babeldoc --help >/dev/null 2>&1 && echo " ✅ babeldoc --help 成功" || echo " ⚠️ babeldoc --help 有警告"; \
|
||||||
|
else \
|
||||||
|
echo " ⚠️ babeldoc 不可用"; \
|
||||||
|
fi && \
|
||||||
|
\
|
||||||
|
# 驗證 pdf2zh
|
||||||
|
echo "🔍 驗證 pdf2zh..." && \
|
||||||
|
if command -v pdf2zh >/dev/null 2>&1; then \
|
||||||
|
pdf2zh --help >/dev/null 2>&1 && echo " ✅ pdf2zh --help 成功" || echo " ⚠️ pdf2zh --help 有警告"; \
|
||||||
|
else \
|
||||||
|
echo " ⚠️ pdf2zh 不可用"; \
|
||||||
|
fi && \
|
||||||
|
\
|
||||||
|
# 驗證 ImageMagick
|
||||||
|
echo "🔍 驗證 ImageMagick..." && \
|
||||||
|
if command -v convert >/dev/null 2>&1; then \
|
||||||
|
echo " ✅ ImageMagick 已安裝: $(convert --version | head -1)"; \
|
||||||
|
else \
|
||||||
|
echo " ❌ ImageMagick 未安裝" && VALIDATION_PASSED=false; \
|
||||||
|
fi && \
|
||||||
|
\
|
||||||
|
# 驗證模型檔案
|
||||||
|
echo "🔍 驗證 ONNX 模型..." && \
|
||||||
|
if [ -f "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" ]; then \
|
||||||
|
echo " ✅ DocLayout-YOLO ONNX 存在"; \
|
||||||
|
else \
|
||||||
|
echo " ⚠️ DocLayout-YOLO ONNX 不存在(將使用 COPY 複製)"; \
|
||||||
|
fi && \
|
||||||
|
\
|
||||||
|
# 驗證字型
|
||||||
|
echo "🔍 驗證字型..." && \
|
||||||
|
FONTS_COUNT=$(ls /usr/share/fonts/truetype/custom/*.ttf 2>/dev/null | wc -l) && \
|
||||||
|
echo " ✅ 自訂字型數量: ${FONTS_COUNT}" && \
|
||||||
|
\
|
||||||
|
echo "======================================" && \
|
||||||
|
if [ "$VALIDATION_PASSED" = "true" ]; then \
|
||||||
|
echo "✅ 離線驗證通過!"; \
|
||||||
|
else \
|
||||||
|
echo "❌ 離線驗證失敗!" && exit 1; \
|
||||||
|
fi && \
|
||||||
|
echo "======================================"
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 🔐 Runtime 環境變數(強制離線模式)
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# 1️⃣ 系統 Locale
|
||||||
|
ENV LANG=zh_TW.UTF-8
|
||||||
|
ENV LC_ALL=zh_TW.UTF-8
|
||||||
|
|
||||||
|
# 2️⃣ Headless 環境
|
||||||
|
ENV QT_QPA_PLATFORM="offscreen"
|
||||||
|
ENV DISPLAY=":99"
|
||||||
|
ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox"
|
||||||
|
ENV CALIBRE_USE_SYSTEM_THEME="0"
|
||||||
|
|
||||||
|
# 3️⃣ 翻譯服務設定(這是唯一允許連網的服務)
|
||||||
|
ENV PDFMATHTRANSLATE_SERVICE="google"
|
||||||
|
ENV BABELDOC_SERVICE="google"
|
||||||
|
|
||||||
|
# 4️⃣ 🔒 強制離線模式(禁止模型/資源下載)
|
||||||
|
# HuggingFace 完全離線
|
||||||
|
ENV HF_HOME="/nonexistent"
|
||||||
|
ENV HF_HUB_OFFLINE="1"
|
||||||
|
ENV TRANSFORMERS_OFFLINE="1"
|
||||||
|
ENV HF_DATASETS_OFFLINE="1"
|
||||||
|
ENV TRANSFORMERS_CACHE="/nonexistent"
|
||||||
|
|
||||||
|
# MinerU 強制本地模型
|
||||||
|
ENV MINERU_MODEL_SOURCE="local"
|
||||||
|
ENV MINERU_CONFIG="/root/mineru.json"
|
||||||
|
|
||||||
|
# BabelDOC 離線模式
|
||||||
|
ENV BABELDOC_OFFLINE="1"
|
||||||
|
ENV BABELDOC_CACHE_PATH="/root/.cache/babeldoc"
|
||||||
|
|
||||||
|
# 禁止 pip 安裝
|
||||||
|
ENV PIP_NO_INDEX="1"
|
||||||
|
ENV PIP_NO_CACHE_DIR="1"
|
||||||
|
|
||||||
|
# 5️⃣ PDF 簽章設定
|
||||||
|
ENV PDF_SIGN_P12_PATH="/app/certs/default.p12"
|
||||||
|
ENV PDF_SIGN_P12_PASSWORD=""
|
||||||
|
ENV PDF_SIGN_REASON="ConvertX-CN PDF Packager"
|
||||||
|
ENV PDF_SIGN_LOCATION="Taiwan"
|
||||||
|
ENV PDF_SIGN_CONTACT="convertx-cn@localhost"
|
||||||
|
|
||||||
|
# 6️⃣ 應用程式設定
|
||||||
|
ENV PANDOC_PDF_ENGINE=pdflatex
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 暴露端口 & 啟動
|
||||||
|
# ==============================================================================
|
||||||
|
EXPOSE 3000/tcp
|
||||||
|
|
||||||
|
ENTRYPOINT [ "bun", "run", "dist/src/index.js" ]
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# ConvertX-CN 模型驗證腳本
|
# ConvertX-CN 模型驗證腳本 v2.0
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
#
|
#
|
||||||
# 用途:驗證 Docker Image 中的預下載模型是否完整
|
# 用途:驗證 Docker Image 中的預下載模型是否完整
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo "🔍 ConvertX-CN 模型驗證"
|
echo "🔍 ConvertX-CN 模型驗證 v2.0"
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|
@ -19,6 +19,7 @@ echo ""
|
||||||
PASS=0
|
PASS=0
|
||||||
FAIL=0
|
FAIL=0
|
||||||
WARN=0
|
WARN=0
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
# 檢查函數
|
# 檢查函數
|
||||||
check_file() {
|
check_file() {
|
||||||
|
|
@ -37,22 +38,28 @@ check_file() {
|
||||||
check_dir() {
|
check_dir() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local name="$2"
|
local name="$2"
|
||||||
|
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=$(du -sh "$path" 2>/dev/null | awk '{print $1}')
|
||||||
echo "✅ $name: $path ($size)"
|
echo "✅ $name: $path ($size)"
|
||||||
((PASS++))
|
((PASS++))
|
||||||
else
|
else
|
||||||
echo "⚠️ $name: $path 不存在"
|
if [ "$required" = "true" ]; then
|
||||||
|
echo "❌ $name: $path 不存在(必要)"
|
||||||
|
((FAIL++))
|
||||||
|
else
|
||||||
|
echo "⚠️ $name: $path 不存在(可選)"
|
||||||
((WARN++))
|
((WARN++))
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 1. PDFMathTranslate 模型
|
# 1. PDFMathTranslate / BabelDOC ONNX 模型
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "📦 PDFMathTranslate 模型"
|
echo "📦 PDFMathTranslate / BabelDOC 模型"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
check_file "/models/pdfmathtranslate/model.onnx" "DocLayout-YOLO ONNX"
|
check_file "/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx" "DocLayout-YOLO ONNX"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
@ -60,20 +67,25 @@ echo ""
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "🔤 PDFMathTranslate 字型"
|
echo "🔤 PDFMathTranslate 字型"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
check_file "/app/GoNotoKurrent-Regular.ttf" "Noto 通用字型"
|
check_file "/usr/share/fonts/truetype/custom/GoNotoKurrent-Regular.ttf" "Noto 通用字型"
|
||||||
check_file "/app/SourceHanSerifCN-Regular.ttf" "思源宋體(簡體)"
|
check_file "/usr/share/fonts/truetype/custom/SourceHanSerifCN-Regular.ttf" "思源宋體(簡體)"
|
||||||
check_file "/app/SourceHanSerifTW-Regular.ttf" "思源宋體(繁體)"
|
check_file "/usr/share/fonts/truetype/custom/SourceHanSerifTW-Regular.ttf" "思源宋體(繁體)"
|
||||||
check_file "/app/SourceHanSerifJP-Regular.ttf" "思源宋體(日文)"
|
check_file "/usr/share/fonts/truetype/custom/SourceHanSerifJP-Regular.ttf" "思源宋體(日文)"
|
||||||
check_file "/app/SourceHanSerifKR-Regular.ttf" "思源宋體(韓文)"
|
check_file "/usr/share/fonts/truetype/custom/SourceHanSerifKR-Regular.ttf" "思源宋體(韓文)"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 3. MinerU 模型
|
# 3. MinerU 模型(新路徑:/opt/mineru)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "📦 MinerU 模型"
|
echo "📦 MinerU 模型"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
check_dir "/root/.cache/huggingface/hub/PDF-Extract-Kit-1.0" "PDF-Extract-Kit-1.0 Pipeline"
|
if [ "$ARCH" = "aarch64" ]; then
|
||||||
check_dir "/root/.cache/huggingface/hub/MinerU-VLM" "MinerU VLM(可選)"
|
echo "⚠️ ARM64 架構:MinerU 不支援,跳過驗證"
|
||||||
|
((WARN++))
|
||||||
|
else
|
||||||
|
check_dir "/opt/mineru/models/PDF-Extract-Kit-1.0" "PDF-Extract-Kit-1.0 Pipeline" "true"
|
||||||
|
check_file "/root/mineru.json" "MinerU 配置檔"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
@ -81,25 +93,22 @@ echo ""
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "📦 BabelDOC 快取"
|
echo "📦 BabelDOC 快取"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
if [ -d "/root/.cache/babeldoc" ]; then
|
|
||||||
check_dir "/root/.cache/babeldoc" "BabelDOC 快取"
|
check_dir "/root/.cache/babeldoc" "BabelDOC 快取"
|
||||||
elif [ -d "/root/.local/share/babeldoc" ]; then
|
check_dir "/root/.cache/babeldoc/models" "BabelDOC Models"
|
||||||
check_dir "/root/.local/share/babeldoc" "BabelDOC 資料"
|
check_dir "/root/.cache/babeldoc/fonts" "BabelDOC Fonts"
|
||||||
else
|
|
||||||
echo "⚠️ BabelDOC 快取目錄未找到(可能使用其他路徑)"
|
|
||||||
((WARN++))
|
|
||||||
fi
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 5. 環境變數檢查
|
# 5. 環境變數檢查(離線模式)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "🔧 環境變數"
|
echo "🔧 環境變數(離線模式)"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
echo "PDFMATHTRANSLATE_MODELS_PATH: ${PDFMATHTRANSLATE_MODELS_PATH:-未設定}"
|
|
||||||
echo "NOTO_FONT_PATH: ${NOTO_FONT_PATH:-未設定}"
|
|
||||||
echo "MINERU_MODEL_SOURCE: ${MINERU_MODEL_SOURCE:-未設定}"
|
|
||||||
echo "HF_HUB_OFFLINE: ${HF_HUB_OFFLINE:-未設定}"
|
echo "HF_HUB_OFFLINE: ${HF_HUB_OFFLINE:-未設定}"
|
||||||
|
echo "TRANSFORMERS_OFFLINE: ${TRANSFORMERS_OFFLINE:-未設定}"
|
||||||
|
echo "HF_HOME: ${HF_HOME:-未設定}"
|
||||||
|
echo "MINERU_MODEL_SOURCE: ${MINERU_MODEL_SOURCE:-未設定}"
|
||||||
|
echo "BABELDOC_OFFLINE: ${BABELDOC_OFFLINE:-未設定}"
|
||||||
|
echo "PIP_NO_INDEX: ${PIP_NO_INDEX:-未設定}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
@ -107,14 +116,38 @@ echo ""
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
echo "📝 MinerU 設定檔"
|
echo "📝 MinerU 設定檔"
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
if [ -f "/root/mineru.json" ]; then
|
if [ "$ARCH" = "aarch64" ]; then
|
||||||
|
echo "⚠️ ARM64 架構:跳過 MinerU 配置驗證"
|
||||||
|
((WARN++))
|
||||||
|
elif [ -f "/root/mineru.json" ]; then
|
||||||
echo "✅ /root/mineru.json 存在"
|
echo "✅ /root/mineru.json 存在"
|
||||||
cat /root/mineru.json
|
cat /root/mineru.json
|
||||||
((PASS++))
|
((PASS++))
|
||||||
else
|
else
|
||||||
echo "⚠️ /root/mineru.json 不存在"
|
echo "❌ /root/mineru.json 不存在"
|
||||||
|
((FAIL++))
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# 7. 工具可執行性驗證
|
||||||
|
# ==============================================================================
|
||||||
|
echo "🔧 工具可執行性"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
for cmd in mineru babeldoc pdf2zh markitdown convert ffmpeg pandoc; do
|
||||||
|
if command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
echo "✅ $cmd: $(which $cmd)"
|
||||||
|
((PASS++))
|
||||||
|
else
|
||||||
|
if [ "$cmd" = "mineru" ] && [ "$ARCH" = "aarch64" ]; then
|
||||||
|
echo "⚠️ $cmd: ARM64 不支援"
|
||||||
|
((WARN++))
|
||||||
|
else
|
||||||
|
echo "⚠️ $cmd: 未找到"
|
||||||
((WARN++))
|
((WARN++))
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
@ -134,7 +167,7 @@ if [ $FAIL -gt 0 ]; then
|
||||||
exit 1
|
exit 1
|
||||||
elif [ $WARN -gt 0 ]; then
|
elif [ $WARN -gt 0 ]; then
|
||||||
echo "⚠️ 驗證完成,但有警告。"
|
echo "⚠️ 驗證完成,但有警告。"
|
||||||
echo " 部分可選模型可能需要 runtime 下載。"
|
echo " 部分可選功能可能需要確認。"
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
echo "✅ 所有模型驗證通過!"
|
echo "✅ 所有模型驗證通過!"
|
||||||
|
|
|
||||||
310
scripts/verify-offline.sh
Normal file
310
scripts/verify-offline.sh
Normal file
|
|
@ -0,0 +1,310 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# ==============================================================================
|
||||||
|
# ConvertX-CN 離線驗證腳本 v2.0
|
||||||
|
# ==============================================================================
|
||||||
|
#
|
||||||
|
# 用途:驗證 Docker Image 在完全離線環境下可正常運作
|
||||||
|
# 執行方式:
|
||||||
|
# 1. 容器內執行:/app/scripts/verify-offline.sh
|
||||||
|
# 2. 網路隔離測試:docker run --network=none <image> /app/scripts/verify-offline.sh
|
||||||
|
#
|
||||||
|
# 驗證項目:
|
||||||
|
# 1. MinerU:可執行 + 模型存在 + 配置正確
|
||||||
|
# 2. BabelDOC:可執行 + cache 存在
|
||||||
|
# 3. PDFMathTranslate (pdf2zh):可執行 + ONNX 模型存在
|
||||||
|
# 4. ImageMagick:可執行
|
||||||
|
# 5. 字型:自訂字型已安裝
|
||||||
|
# 6. 環境變數:離線模式已正確設定
|
||||||
|
#
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ ConvertX-CN 離線驗證腳本 v2.0 ║"
|
||||||
|
echo "║ Runtime Zero-Download Verification ║"
|
||||||
|
echo "╚══════════════════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 計數器
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
WARN=0
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
|
||||||
|
# 顏色定義
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 檢查函數
|
||||||
|
pass() {
|
||||||
|
echo -e "${GREEN}✅ $1${NC}"
|
||||||
|
((PASS++))
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo -e "${RED}❌ $1${NC}"
|
||||||
|
((FAIL++))
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||||
|
((WARN++))
|
||||||
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
echo -e " ℹ️ $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_command() {
|
||||||
|
local cmd="$1"
|
||||||
|
local name="$2"
|
||||||
|
if command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
pass "$name 可執行: $(which $cmd)"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
fail "$name 未安裝或不在 PATH"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_file() {
|
||||||
|
local path="$1"
|
||||||
|
local name="$2"
|
||||||
|
if [ -f "$path" ]; then
|
||||||
|
local size=$(ls -lh "$path" 2>/dev/null | awk '{print $5}')
|
||||||
|
pass "$name: $path ($size)"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
fail "$name: $path 不存在"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_dir() {
|
||||||
|
local path="$1"
|
||||||
|
local name="$2"
|
||||||
|
local required="${3:-false}"
|
||||||
|
if [ -d "$path" ]; then
|
||||||
|
local size=$(du -sh "$path" 2>/dev/null | awk '{print $1}')
|
||||||
|
pass "$name: $path ($size)"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
if [ "$required" = "true" ]; then
|
||||||
|
fail "$name: $path 不存在(必要)"
|
||||||
|
else
|
||||||
|
warn "$name: $path 不存在(可選)"
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_env() {
|
||||||
|
local var="$1"
|
||||||
|
local expected="$2"
|
||||||
|
local actual="${!var}"
|
||||||
|
if [ -n "$actual" ]; then
|
||||||
|
if [ -n "$expected" ] && [ "$actual" != "$expected" ]; then
|
||||||
|
warn "$var = $actual(預期: $expected)"
|
||||||
|
else
|
||||||
|
pass "$var = $actual"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "$var 未設定"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📦 1. MinerU 驗證"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
if [ "$ARCH" = "aarch64" ]; then
|
||||||
|
warn "ARM64 架構:MinerU 不支援,跳過驗證"
|
||||||
|
else
|
||||||
|
# 檢查 mineru 命令
|
||||||
|
check_command "mineru" "MinerU CLI"
|
||||||
|
|
||||||
|
# 檢查模型目錄
|
||||||
|
MINERU_MODELS="/opt/mineru/models/PDF-Extract-Kit-1.0"
|
||||||
|
check_dir "$MINERU_MODELS" "MinerU Pipeline 模型" "true"
|
||||||
|
|
||||||
|
# 檢查配置檔
|
||||||
|
if check_file "/root/mineru.json" "MinerU 配置檔"; then
|
||||||
|
info "配置內容:"
|
||||||
|
cat /root/mineru.json 2>/dev/null | head -10
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 驗證配置指向正確目錄
|
||||||
|
if [ -f "/root/mineru.json" ]; then
|
||||||
|
CONFIGURED_PATH=$(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 [ "$CONFIGURED_PATH" = "$MINERU_MODELS" ]; then
|
||||||
|
pass "mineru.json 指向正確目錄"
|
||||||
|
else
|
||||||
|
fail "mineru.json 指向錯誤目錄: $CONFIGURED_PATH"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📦 2. BabelDOC 驗證"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
check_command "babeldoc" "BabelDOC CLI"
|
||||||
|
|
||||||
|
# 檢查 cache 目錄
|
||||||
|
check_dir "/root/.cache/babeldoc" "BabelDOC Cache"
|
||||||
|
check_dir "/root/.cache/babeldoc/models" "BabelDOC Models"
|
||||||
|
check_dir "/root/.cache/babeldoc/fonts" "BabelDOC Fonts"
|
||||||
|
|
||||||
|
# 檢查 ONNX 模型
|
||||||
|
BABELDOC_ONNX="/root/.cache/babeldoc/models/doclayout_yolo_docstructbench_imgsz1024.onnx"
|
||||||
|
check_file "$BABELDOC_ONNX" "DocLayout-YOLO ONNX"
|
||||||
|
|
||||||
|
# 測試 babeldoc --help(不應觸發下載)
|
||||||
|
if command -v babeldoc >/dev/null 2>&1; then
|
||||||
|
if timeout 10 babeldoc --help >/dev/null 2>&1; then
|
||||||
|
pass "babeldoc --help 可離線執行"
|
||||||
|
else
|
||||||
|
warn "babeldoc --help 執行異常"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📦 3. PDFMathTranslate (pdf2zh) 驗證"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
check_command "pdf2zh" "pdf2zh CLI"
|
||||||
|
|
||||||
|
# 測試 pdf2zh --help
|
||||||
|
if command -v pdf2zh >/dev/null 2>&1; then
|
||||||
|
if timeout 10 pdf2zh --help >/dev/null 2>&1; then
|
||||||
|
pass "pdf2zh --help 可離線執行"
|
||||||
|
else
|
||||||
|
warn "pdf2zh --help 執行異常"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "📦 4. ImageMagick 驗證"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
if check_command "convert" "ImageMagick (convert)"; then
|
||||||
|
VERSION=$(convert --version 2>/dev/null | head -1)
|
||||||
|
info "版本: $VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_command "identify" "ImageMagick (identify)"
|
||||||
|
check_command "mogrify" "ImageMagick (mogrify)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🔤 5. 字型驗證"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
CUSTOM_FONTS_DIR="/usr/share/fonts/truetype/custom"
|
||||||
|
if check_dir "$CUSTOM_FONTS_DIR" "自訂字型目錄"; then
|
||||||
|
FONT_COUNT=$(ls -1 "$CUSTOM_FONTS_DIR"/*.ttf 2>/dev/null | wc -l)
|
||||||
|
info "字型檔案數量: $FONT_COUNT"
|
||||||
|
ls -1 "$CUSTOM_FONTS_DIR"/*.ttf 2>/dev/null | while read f; do
|
||||||
|
info " - $(basename $f)"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 驗證必要字型
|
||||||
|
check_file "$CUSTOM_FONTS_DIR/GoNotoKurrent-Regular.ttf" "Noto 通用字型"
|
||||||
|
check_file "$CUSTOM_FONTS_DIR/SourceHanSerifCN-Regular.ttf" "思源宋體(簡體)"
|
||||||
|
check_file "$CUSTOM_FONTS_DIR/SourceHanSerifTW-Regular.ttf" "思源宋體(繁體)"
|
||||||
|
check_file "$CUSTOM_FONTS_DIR/SourceHanSerifJP-Regular.ttf" "思源宋體(日文)"
|
||||||
|
check_file "$CUSTOM_FONTS_DIR/SourceHanSerifKR-Regular.ttf" "思源宋體(韓文)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🔧 6. 離線環境變數驗證"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
echo "📋 HuggingFace 離線設定:"
|
||||||
|
check_env "HF_HUB_OFFLINE" "1"
|
||||||
|
check_env "TRANSFORMERS_OFFLINE" "1"
|
||||||
|
check_env "HF_DATASETS_OFFLINE" "1"
|
||||||
|
check_env "HF_HOME" "/nonexistent"
|
||||||
|
check_env "TRANSFORMERS_CACHE" "/nonexistent"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 MinerU 設定:"
|
||||||
|
check_env "MINERU_MODEL_SOURCE" "local"
|
||||||
|
check_env "MINERU_CONFIG"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 BabelDOC 設定:"
|
||||||
|
check_env "BABELDOC_OFFLINE" "1"
|
||||||
|
check_env "BABELDOC_CACHE_PATH"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📋 pip 設定:"
|
||||||
|
check_env "PIP_NO_INDEX" "1"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🔧 7. 其他核心工具驗證"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
check_command "ffmpeg" "FFmpeg"
|
||||||
|
check_command "pandoc" "Pandoc"
|
||||||
|
check_command "libreoffice" "LibreOffice"
|
||||||
|
check_command "tesseract" "Tesseract OCR"
|
||||||
|
check_command "gs" "Ghostscript"
|
||||||
|
check_command "inkscape" "Inkscape"
|
||||||
|
check_command "markitdown" "MarkItDown"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
echo ""
|
||||||
|
echo "╔══════════════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ 驗證結果總結 ║"
|
||||||
|
echo "╚══════════════════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
echo " ✅ 通過: $PASS"
|
||||||
|
echo " ⚠️ 警告: $WARN"
|
||||||
|
echo " ❌ 失敗: $FAIL"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $FAIL -gt 0 ]; then
|
||||||
|
echo -e "${RED}╔══════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${RED}║ ❌ 離線驗證失敗! ║${NC}"
|
||||||
|
echo -e "${RED}║ 部分必要組件缺失,Image 不應發布到 Production。 ║${NC}"
|
||||||
|
echo -e "${RED}╚══════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
exit 1
|
||||||
|
elif [ $WARN -gt 0 ]; then
|
||||||
|
echo -e "${YELLOW}╔══════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${YELLOW}║ ⚠️ 離線驗證通過,但有警告 ║${NC}"
|
||||||
|
echo -e "${YELLOW}║ 部分可選功能可能受限,請確認是否符合需求。 ║${NC}"
|
||||||
|
echo -e "${YELLOW}╚══════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}╔══════════════════════════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${GREEN}║ ✅ 離線驗證完全通過! ║${NC}"
|
||||||
|
echo -e "${GREEN}║ 系統可在 docker run --network=none 環境正常運作。 ║${NC}"
|
||||||
|
echo -e "${GREEN}║ (翻譯服務除外,需網路連接) ║${NC}"
|
||||||
|
echo -e "${GREEN}╚══════════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
Loading…
Add table
Add a link
Reference in a new issue