convertor/.github/workflows/docker-build-remote.yml
Your Name 1b8cae30a0 feat: 新增 Lite 版 v0.1.15-lite
- Dockerfile.lite: 輕量版,僅保留英/簡/繁語言
- 移除 Inkscape/VIPS/ImageMagick 以減小體積 (< 1.5 GB)
- docker-build-lite.yml: 自動觸發 Lite 版發布
- docker-build-remote.yml: 改進交互邏輯,支援 standard/full/lite 版本
- release.yml: 修正只觸發一般版發布
2026-01-24 15:47:04 +08:00

1111 lines
44 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# Docker Build & PushRemote Host via Tailscale SSH
# ============================================================
# 說明:使用 Tailscale SSH 連線到私有主機執行 Docker build
# 重要:此 workflow 完全不使用傳統 SSH key僅使用 Tailscale SSH
# ============================================================
name: Docker Build & Push (Remote Host via Tailscale)
on:
# 手動觸發,支援輸入 image tag 與 build target
workflow_dispatch:
inputs:
image_tag:
description: "📦 版本號例如0.1.15、0.1.15-lite、0.1.15.test"
required: true
default: ""
type: string
build_target:
description: "🎯 Build 目標版本auto = 自動從版本號偵測)"
required: true
default: "auto"
type: choice
options:
- auto
- standard
- full
- lite
# ============================================================
# 環境變數設定
# ============================================================
env:
# 遠端主機工作目錄
REMOTE_WORKDIR: /home/bioailab/miniconda3/lid/app/docker_build
# 專案名稱
PROJECT_NAME: ConvertX-CN
# Docker Hub 映像庫
DOCKER_IMAGE_REPO: convertx/convertx-cn
# ============================================================
# 權限設定GITHUB_TOKEN
# ============================================================
permissions:
contents: write # 必要:創建 Release、上傳 assets、創建/推送 tag
actions: read # 可選:讀取 workflow 資訊(用於 generate release notes
jobs:
build-and-push:
name: 🚀 遠端 Docker Build & Push
runs-on: ubuntu-latest
steps:
# ========================================
# Step 0: 解析版本快捷選項
# 說明:根據 version_preset 自動設定 IMAGE_TAG 和 BUILD_TARGET
# ========================================
- name: 🔧 解析版本設定
id: parse_version
run: |
echo "========================================"
echo "🔧 解析版本設定"
echo "========================================"
INPUT_TAG="${{ github.event.inputs.image_tag }}"
BUILD_TARGET_INPUT="${{ github.event.inputs.build_target }}"
echo "輸入參數:"
echo " image_tag: ${INPUT_TAG}"
echo " build_target: ${BUILD_TARGET_INPUT}"
echo ""
# 驗證版本號不為空
if [ -z "${INPUT_TAG}" ]; then
echo "❌ 錯誤:必須填寫版本號"
exit 1
fi
# ========================================
# 判斷 build targetauto 時根據 tag 自動判斷)
# ========================================
if [ "${BUILD_TARGET_INPUT}" = "auto" ]; then
if [[ "${INPUT_TAG}" == *"-lite"* ]]; then
FINAL_BUILD_TARGET="lite"
echo "🔍 自動偵測:版本號含 '-lite',選擇 Lite 版"
elif [[ "${INPUT_TAG}" == *"-full"* ]]; then
FINAL_BUILD_TARGET="full"
echo "🔍 自動偵測:版本號含 '-full',選擇 Full 版"
else
FINAL_BUILD_TARGET="standard"
echo "🔍 自動偵測:標準版本號,選擇一般版"
fi
else
FINAL_BUILD_TARGET="${BUILD_TARGET_INPUT}"
fi
# ========================================
# 根據 build target 決定最終 tag
# ========================================
# 移除可能的後綴以獲取基礎版本號
BASE_TAG="${INPUT_TAG%-lite}"
BASE_TAG="${BASE_TAG%-full}"
case "${FINAL_BUILD_TARGET}" in
"lite")
FINAL_TAG="${BASE_TAG}-lite"
DOCKERFILE="Dockerfile.lite"
LATEST_TAG="latest-lite"
EDITION="Lite 版(輕量版)"
;;
"full")
FINAL_TAG="${BASE_TAG}-full"
DOCKERFILE="Dockerfile.full"
LATEST_TAG="latest-full"
EDITION="Full 版(擴充版)"
;;
*)
FINAL_TAG="${BASE_TAG}"
DOCKERFILE="Dockerfile"
LATEST_TAG="latest"
EDITION="一般版(標準版)"
;;
esac
# ========================================
# 判斷是否為正式版本(決定是否更新 latest
# 正式版本格式:純數字+點號(如 0.1.15、0.1.15.1
# 測試版本格式:含有 test/dev/alpha/beta 或其他非數字字元
# ========================================
if [[ "${BASE_TAG}" =~ ^[0-9]+(\.[0-9]+)+$ ]]; then
IS_OFFICIAL="true"
echo "✅ 正式版本:將同時更新 ${LATEST_TAG}"
else
IS_OFFICIAL="false"
echo "⚠️ 測試版本:不更新 ${LATEST_TAG}"
fi
echo ""
echo "========================================"
echo "📋 最終設定:"
echo "========================================"
echo " 版本號: ${FINAL_TAG}"
echo " Build 目標: ${FINAL_BUILD_TARGET}"
echo " Dockerfile: ${DOCKERFILE}"
echo " 版本類型: ${EDITION}"
echo " Latest Tag: ${LATEST_TAG}"
echo " 正式版本: ${IS_OFFICIAL}"
echo "========================================"
# 輸出到 GITHUB_OUTPUT
echo "image_tag=${FINAL_TAG}" >> $GITHUB_OUTPUT
echo "base_tag=${BASE_TAG}" >> $GITHUB_OUTPUT
echo "build_target=${FINAL_BUILD_TARGET}" >> $GITHUB_OUTPUT
echo "dockerfile=${DOCKERFILE}" >> $GITHUB_OUTPUT
echo "latest_tag=${LATEST_TAG}" >> $GITHUB_OUTPUT
echo "edition=${EDITION}" >> $GITHUB_OUTPUT
echo "is_official=${IS_OFFICIAL}" >> $GITHUB_OUTPUT
# ========================================
# Step 1: Checkout repository
# 說明:檢出程式碼(雖然 build 在遠端,但需要 workflow 完整性)
# ========================================
- name: 📥 Checkout repository
uses: actions/checkout@v4
# ========================================
# Step 2: 啟動 Tailscale非互動模式
# 說明:使用 Tailscale Auth Key 加入 tailnet啟用 SSH
# 重要:必須使用 --ssh 參數才能使用 tailscale ssh
# ========================================
- name: 🔗 啟動 Tailscale 連線(非互動模式)
uses: tailscale/github-action@v2
with:
authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
args: "--ssh --accept-dns=false --accept-routes=false"
# 使用 ephemeral keyrunner 結束後自動離開 tailnet
# --ssh: 啟用 Tailscale SSH 功能
# --accept-dns=false: 不使用 Tailscale DNS
# --accept-routes=false: 不接受其他節點的路由
# ========================================
# Step 3: 驗證 Tailscale 連線
# 說明:確認 Tailscale 已成功連線並顯示狀態
# ========================================
- name: ✅ 驗證 Tailscale 連線狀態
run: |
echo "========================================"
echo "🔍 檢查 Tailscale 連線狀態"
echo "========================================"
# 顯示 Tailscale 狀態
echo ""
echo "📊 Tailscale 狀態:"
tailscale status
# 顯示本機 Tailscale IP
echo ""
echo "🌐 本機 Tailscale IP"
TAILSCALE_IP=$(tailscale ip -4)
echo "${TAILSCALE_IP}"
# 確認 SSH 功能已啟用
echo ""
echo "🔐 檢查 SSH 功能狀態:"
if tailscale status --json | grep -q '"SSH":true'; then
echo "✅ Tailscale SSH 功能已啟用"
else
echo "⚠️ 正在檢查 SSH 狀態..."
tailscale status --json | head -50
fi
# 等待 Tailscale 完全連線
echo ""
echo "⏳ 等待 Tailscale 連線穩定..."
sleep 5
# 測試與目標主機的連通性
echo ""
echo "📡 測試與遠端主機的連通性..."
echo "目標主機: ${{ secrets.SSH_HOST }}"
if tailscale ping --c 3 "${{ secrets.SSH_HOST }}"; then
echo ""
echo "✅ Tailscale 連線測試成功!"
else
echo ""
echo "⚠️ Tailscale ping 失敗,但 SSH 可能仍可運作,繼續執行..."
fi
echo "========================================"
# ========================================
# Step 4: Tailscale SSH 連線(手動網頁驗證)
# 說明:首次連線需要點擊驗證 URL 完成授權
# ========================================
- name: 🔐 測試 Tailscale SSH 連線
run: |
echo "========================================"
echo "🔐 測試 Tailscale SSH 連線"
echo "========================================"
echo "使用者: ${{ secrets.SSH_USER }}"
echo "主機: ${{ secrets.SSH_HOST }}"
echo ""
echo "⚠️ 如果出現驗證 URL請在瀏覽器中開啟並完成驗證"
echo "⏳ 驗證完成後,連線會自動繼續..."
echo ""
echo "========================================"
# 執行 SSH 連線,輸出會包含驗證連結
# 使用 timeout 避免無限等待10 分鐘)
timeout 600 tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
"echo '✅ Tailscale SSH 連線成功!' && echo '主機名稱:' && hostname && echo '當前使用者:' && whoami" \
|| { echo ""; echo "❌ SSH 連線失敗或超時,請確認已完成網頁驗證"; exit 1; }
echo ""
echo "========================================"
# ========================================
# Step 5: Smoke Test環境驗證
# 說明:快速驗證遠端環境是否正常
# ========================================
- name: 🧪 Smoke Test環境驗證
run: |
echo "========================================"
echo "🧪 Smoke Test - 驗證遠端環境"
echo "========================================"
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'REMOTE_EOF'
set -e
echo "📋 測試項目 1進入目標目錄"
echo "----------------------------------------"
WORKDIR="/home/bioailab/miniconda3/lid/app/docker_build"
mkdir -p "${WORKDIR}"
cd "${WORKDIR}"
echo "✅ 目前目錄: $(pwd)"
echo ""
echo "📋 測試項目 2Git 版本"
echo "----------------------------------------"
git --version
echo "✅ Git 可用"
echo ""
echo "📋 測試項目 3Docker 版本"
echo "----------------------------------------"
docker version --format '{{.Server.Version}}' || docker --version
echo "✅ Docker 可用"
echo ""
echo "📋 測試項目 4Docker Buildx 版本"
echo "----------------------------------------"
docker buildx version
echo "✅ Docker Buildx 可用"
echo ""
echo "========================================"
echo "✅ 所有 Smoke Test 通過!"
echo "========================================"
REMOTE_EOF
echo "========================================"
# ========================================
# Step 6: 準備遠端工作目錄
# 說明:確保遠端工作目錄存在
# ========================================
- name: 📁 準備遠端工作目錄
run: |
echo "========================================"
echo "📁 準備遠端工作目錄"
echo "========================================"
echo "工作目錄: ${{ env.REMOTE_WORKDIR }}"
echo ""
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
"mkdir -p ${{ env.REMOTE_WORKDIR }} && echo '✅ 工作目錄已就緒'"
echo "========================================"
# ========================================
# Step 7: Clone 或更新專案程式碼
# 說明:在遠端主機上 clone 或 pull 最新程式碼
# ========================================
- name: 📥 Clone 或更新專案程式碼
run: |
echo "========================================"
echo "📥 Clone 或更新專案程式碼"
echo "========================================"
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'REMOTE_EOF'
set -e
WORKDIR="${{ env.REMOTE_WORKDIR }}"
PROJECT="${{ env.PROJECT_NAME }}"
REPO_URL="https://github.com/pi-docket/ConvertX-CN.git"
echo "📂 進入工作目錄: ${WORKDIR}"
cd "${WORKDIR}"
if [ -d "${PROJECT}" ]; then
echo "📦 專案已存在,執行 git pull..."
cd "${PROJECT}"
git fetch origin
git reset --hard origin/main
git pull origin main
echo "✅ 程式碼更新完成"
else
echo "📦 專案不存在,執行 git clone..."
git clone "${REPO_URL}" "${PROJECT}"
cd "${PROJECT}"
echo "✅ 專案 clone 完成"
fi
echo ""
echo "📋 目前 commit"
git log -1 --oneline
echo ""
echo "✅ 程式碼準備完成"
REMOTE_EOF
echo "========================================"
# ========================================
# Step 8: Docker Login
# 說明:在遠端主機登入 Docker Hub
# ========================================
- name: 🔐 Docker Hub 登入
run: |
echo "========================================"
echo "🔐 Docker Hub 登入"
echo "========================================"
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
"echo '${{ secrets.DOCKERHUB_TOKEN }}' | docker login -u '${{ secrets.DOCKERHUB_USERNAME }}' --password-stdin && echo '✅ Docker Hub 登入成功'"
echo "========================================"
# ========================================
# Step 9: 設定 Docker BuildxMulti-Arch 必要)
# 說明:建立支援多架構的 buildx builder
# ========================================
- name: 🔧 設定 Docker Buildx
run: |
echo "========================================"
echo "🔧 設定 Docker BuildxMulti-Arch 支援)"
echo "========================================"
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'REMOTE_EOF'
set -e
BUILDER_NAME="multiarch-builder-${{ github.run_id }}"
echo "📋 檢查現有 buildx builders..."
docker buildx ls
echo ""
echo "🔧 建立新的 multi-arch builder: ${BUILDER_NAME}"
docker buildx create --name "${BUILDER_NAME}" --driver docker-container --use --bootstrap
echo ""
echo "✅ Buildx builder 已就緒"
echo ""
echo "📋 目前 builder 狀態:"
docker buildx inspect --bootstrap
echo ""
echo "🏗️ 支援的平台:"
docker buildx inspect | grep -i platforms || true
REMOTE_EOF
echo "========================================"
# ========================================
# Step 10: Multi-Arch Docker Build
# 說明:使用 buildx 同時建構 amd64 和 arm64
# 支援 full/lite 版本選擇
# ========================================
- name: 🔨 Multi-Arch Docker Build
run: |
echo "========================================"
echo "🔨 開始 Multi-Arch Docker Build"
echo "========================================"
# 使用 parse_version 步驟的輸出
FINAL_TAG="${{ steps.parse_version.outputs.image_tag }}"
DOCKERFILE="${{ steps.parse_version.outputs.dockerfile }}"
LATEST_TAG="${{ steps.parse_version.outputs.latest_tag }}"
IS_OFFICIAL="${{ steps.parse_version.outputs.is_official }}"
EDITION="${{ steps.parse_version.outputs.edition }}"
echo "🔹 Build 目標: ${EDITION}"
echo "🔹 使用 Dockerfile: ${DOCKERFILE}"
echo "🔹 版本: v${FINAL_TAG}"
echo "🔹 映像名稱: ${{ env.DOCKER_IMAGE_REPO }}:${FINAL_TAG}"
if [ "${IS_OFFICIAL}" = "true" ]; then
echo "✅ 正式版本,將同時標記 ${LATEST_TAG}"
ADD_LATEST="true"
else
echo "⚠️ 測試版本,不標記 ${LATEST_TAG}"
ADD_LATEST="false"
fi
echo "目標架構: linux/amd64, linux/arm64"
echo ""
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << REMOTE_EOF
set -e
WORKDIR="${{ env.REMOTE_WORKDIR }}"
PROJECT="${{ env.PROJECT_NAME }}"
IMAGE="${{ env.DOCKER_IMAGE_REPO }}:${FINAL_TAG}"
IMAGE_LATEST="${{ env.DOCKER_IMAGE_REPO }}:${LATEST_TAG}"
BUILDER_NAME="multiarch-builder-${{ github.run_id }}"
ADD_LATEST="${ADD_LATEST}"
DOCKERFILE="${DOCKERFILE}"
echo "📂 進入專案目錄..."
cd "\${WORKDIR}/\${PROJECT}"
pwd
echo ""
echo "🔨 執行 Multi-Arch Docker buildx..."
echo "映像標籤: \${IMAGE}"
echo "Dockerfile: \${DOCKERFILE}"
echo "目標平台: linux/amd64, linux/arm64"
echo ""
# 確保使用正確的 builder
docker buildx use "\${BUILDER_NAME}"
# 根據版本類型決定是否加入 latest tag
if [ "\${ADD_LATEST}" = "true" ]; then
echo "📌 同時標記 \${IMAGE_LATEST}"
BUILD_TAGS="-t \${IMAGE} -t \${IMAGE_LATEST}"
else
echo "📌 僅標記版本號(測試版本)"
BUILD_TAGS="-t \${IMAGE}"
fi
# Multi-arch build只建構不推送
if ! docker buildx build \\
--platform linux/amd64,linux/arm64 \\
-f \${DOCKERFILE} \\
\${BUILD_TAGS} \\
--progress=plain \\
. 2>&1; then
echo ""
echo "========================================"
echo "❌ Docker Build 失敗!"
echo "========================================"
echo ""
echo "🔧 常見問題與解決方案:"
echo ""
echo "1⃣ apt-get 安裝失敗arm64 QEMU 問題)"
echo " → 建議:拆分 apt-get install 為多段 RUN"
echo " → 或:對 arm64 跳過不穩定套件"
echo ""
echo "2⃣ 網路問題"
echo " → 建議:加入 apt-get update --fix-missing"
echo ""
echo "3⃣ 記憶體不足"
echo " → 建議:增加 Docker 記憶體限制"
echo ""
exit 1
fi
echo ""
echo "✅ Multi-Arch Docker Build 完成!"
echo ""
echo "🏗️ 已建構的架構:"
echo " - linux/amd64"
echo " - linux/arm64"
REMOTE_EOF
echo "========================================"
# ========================================
# Step 11: Docker Push 到 Docker Hub
# 說明:推送建構好的映像到 Docker Hub
# 支援 standard/full/lite 版本選擇
# ========================================
- name: 📤 Docker Push 到 Docker Hub
run: |
echo "========================================"
echo "📤 推送映像到 Docker Hub"
echo "========================================"
# 使用 parse_version 步驟的輸出
FINAL_TAG="${{ steps.parse_version.outputs.image_tag }}"
DOCKERFILE="${{ steps.parse_version.outputs.dockerfile }}"
LATEST_TAG="${{ steps.parse_version.outputs.latest_tag }}"
IS_OFFICIAL="${{ steps.parse_version.outputs.is_official }}"
EDITION="${{ steps.parse_version.outputs.edition }}"
echo "🔹 Push 目標: ${EDITION}"
echo "🔹 版本: v${FINAL_TAG}"
echo "🔹 映像: ${{ env.DOCKER_IMAGE_REPO }}:${FINAL_TAG}"
if [ "${IS_OFFICIAL}" = "true" ]; then
echo "✅ 正式版本,將同時推送 ${LATEST_TAG}"
ADD_LATEST="true"
else
echo "⚠️ 測試版本,不推送 ${LATEST_TAG}"
ADD_LATEST="false"
fi
echo ""
# 檢查 Docker Hub 登錄狀態,未登錄則重新登錄
echo "🔐 檢查 Docker Hub 登錄狀態..."
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << EOF
if docker info 2>/dev/null | grep -q "Username:"; then
echo "✅ Docker Hub 已登錄"
else
echo "⚠️ Docker Hub 未登錄,正在重新登錄..."
echo '${{ secrets.DOCKERHUB_TOKEN }}' | docker login -u '${{ secrets.DOCKERHUB_USERNAME }}' --password-stdin
echo "✅ Docker Hub 重新登錄成功"
fi
EOF
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << REMOTE_EOF
set -e
WORKDIR="${{ env.REMOTE_WORKDIR }}"
PROJECT="${{ env.PROJECT_NAME }}"
IMAGE="${{ env.DOCKER_IMAGE_REPO }}:${FINAL_TAG}"
IMAGE_LATEST="${{ env.DOCKER_IMAGE_REPO }}:${LATEST_TAG}"
BUILDER_NAME="multiarch-builder-${{ github.run_id }}"
ADD_LATEST="${ADD_LATEST}"
DOCKERFILE="${DOCKERFILE}"
echo "📂 進入專案目錄..."
cd "\${WORKDIR}/\${PROJECT}"
echo ""
echo "📤 執行 Docker Push..."
echo "映像標籤: \${IMAGE}"
echo "Dockerfile: \${DOCKERFILE}"
# 確保使用正確的 builder
docker buildx use "\${BUILDER_NAME}"
# 根據版本類型決定是否加入 latest tag
if [ "\${ADD_LATEST}" = "true" ]; then
echo "同時推送: \${IMAGE_LATEST}"
BUILD_TAGS="-t \${IMAGE} -t \${IMAGE_LATEST}"
else
echo "(測試版本,不推送 ${LATEST_TAG}"
BUILD_TAGS="-t \${IMAGE}"
fi
echo ""
# 使用 buildx build --push 來推送 multi-arch 映像
if ! docker buildx build \
--platform linux/amd64,linux/arm64 \
-f \${DOCKERFILE} \
\${BUILD_TAGS} \
--push \
--progress=plain \
. 2>&1; then
echo ""
echo "========================================"
echo "❌ Docker Push 失敗!"
echo "========================================"
echo ""
echo "🔧 可能的原因:"
echo " 1. Docker Hub 登入已過期"
echo " 2. 網路連線問題"
echo " 3. Docker Hub 配額限制"
echo ""
exit 1
fi
echo ""
echo "✅ Docker Push 完成!"
echo ""
echo "📦 映像已推送至 Docker Hub"
echo " \${IMAGE}"
if [ "\${ADD_LATEST}" = "true" ]; then
echo " \${IMAGE_LATEST}"
fi
echo ""
echo "🏗️ 支援的架構:"
echo " - linux/amd64"
echo " - linux/arm64"
REMOTE_EOF
echo "========================================"
# ========================================
# Step 13: 驗證 Docker Hub 映像
# 說明:確認映像已成功推送並檢查架構
# 支援 standard/full/lite 版本
# ========================================
- name: ✅ 驗證 Docker Hub 映像
run: |
echo "========================================"
echo "✅ 驗證 Docker Hub 映像"
echo "========================================"
FINAL_TAG="${{ steps.parse_version.outputs.image_tag }}"
EDITION="${{ steps.parse_version.outputs.edition }}"
echo "🔹 版本類型: ${EDITION}"
echo "🔹 版本: v${FINAL_TAG}"
echo "🔹 映像: ${{ env.DOCKER_IMAGE_REPO }}:${FINAL_TAG}"
echo ""
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << REMOTE_EOF
set -e
IMAGE="${{ env.DOCKER_IMAGE_REPO }}:${FINAL_TAG}"
echo "📋 檢查映像 manifest顯示支援的架構..."
echo ""
docker buildx imagetools inspect "\${IMAGE}"
echo ""
echo "✅ 映像驗證完成"
REMOTE_EOF
echo "========================================"
# ========================================
# Step 14: 清理本次 Build 資源
# 說明:只清理本次 build 產生的 builder 和 cache
# ========================================
- name: 🧹 清理本次 Build 資源
if: always()
run: |
echo "========================================"
echo "🧹 清理本次 Build 資源"
echo "========================================"
echo "Builder 名稱: multiarch-builder-${{ github.run_id }}"
echo ""
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'REMOTE_EOF'
set -e
BUILDER_NAME="multiarch-builder-${{ github.run_id }}"
echo "🗑️ 移除本次使用的 buildx builder..."
if docker buildx rm "${BUILDER_NAME}" 2>/dev/null; then
echo "✅ Builder ${BUILDER_NAME} 已移除"
else
echo "⚠️ Builder 可能已被移除或不存在"
fi
echo ""
echo "🗑️ 清理本次 build 產生的 cache..."
# 只清理 buildx 的 dangling cache不影響其他 image
docker buildx prune -f --filter "until=1h" 2>/dev/null || true
echo "✅ Build cache 已清理"
echo ""
echo "📋 目前剩餘的 builders"
docker buildx ls
echo ""
echo "📦 目前剩餘的 convertx 相關映像:"
docker images | grep convertx || echo "(無本地 convertx 相關映像,因使用 --push 直接推送)"
REMOTE_EOF
echo ""
echo "✅ 清理完成(僅清理本次 build 的資源)"
echo "========================================"
# ========================================
# Step 15: Docker Logout
# 說明:登出 Docker Hub
# ========================================
- name: 🔒 Docker Hub 登出
if: always()
run: |
echo "========================================"
echo "🔒 Docker Hub 登出"
echo "========================================"
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
"docker logout && echo '✅ 已登出 Docker Hub'" || true
echo "========================================"
# ========================================
# Step 16: 顯示完成摘要
# 說明:顯示本次 build 的摘要資訊
# 支援 standard/full/lite 版本
# ========================================
- name: 📋 Build 完成摘要
run: |
echo "========================================"
echo "✅ Multi-Arch Docker Build & Push 完成!"
echo "========================================"
echo ""
# 使用 parse_version 步驟的輸出
FINAL_TAG="${{ steps.parse_version.outputs.image_tag }}"
BASE_TAG="${{ steps.parse_version.outputs.base_tag }}"
DOCKERFILE="${{ steps.parse_version.outputs.dockerfile }}"
LATEST_TAG="${{ steps.parse_version.outputs.latest_tag }}"
IS_OFFICIAL="${{ steps.parse_version.outputs.is_official }}"
EDITION="${{ steps.parse_version.outputs.edition }}"
echo "🏷️ 版本: v${FINAL_TAG}"
echo "📦 版本類型: ${EDITION}"
echo ""
echo "📦 映像資訊:"
echo " - 版本標籤: ${{ env.DOCKER_IMAGE_REPO }}:${FINAL_TAG}"
# 判斷是否為正式版本
if [ "${IS_OFFICIAL}" = "true" ]; then
echo " - Latest 標籤: ${{ env.DOCKER_IMAGE_REPO }}:${LATEST_TAG} ✅"
else
echo " - Latest 標籤: (測試版本,未更新)"
fi
echo " - Docker Hub: https://hub.docker.com/r/${{ env.DOCKER_IMAGE_REPO }}/tags"
echo ""
echo "🏗️ 支援的架構:"
echo " - linux/amd64"
echo " - linux/arm64"
echo ""
echo "🔧 Build 設定:"
echo " - 遠端主機: ${{ secrets.SSH_HOST }}"
echo " - 工作目錄: ${{ env.REMOTE_WORKDIR }}"
echo " - 專案名稱: ${{ env.PROJECT_NAME }}"
echo " - Dockerfile: ${DOCKERFILE}"
echo ""
echo "📝 使用方式:"
echo " docker pull ${{ env.DOCKER_IMAGE_REPO }}:${FINAL_TAG}"
if [ "${IS_OFFICIAL}" = "true" ]; then
echo " docker pull ${{ env.DOCKER_IMAGE_REPO }}:${LATEST_TAG}"
fi
echo ""
echo "========================================"
echo "🎉 所有步驟已成功完成!"
echo "========================================"
# ========================================
# Step 17: 生成 Changelog
# 說明:從 git log 生成變更記錄(與 release.yml 邏輯一致)
# ========================================
- name: 📝 Generate Changelog
if: ${{ !contains(steps.parse_version.outputs.image_tag, 'test') && !contains(steps.parse_version.outputs.image_tag, 'dev') && !contains(steps.parse_version.outputs.image_tag, 'alpha') && !contains(steps.parse_version.outputs.image_tag, 'beta') }}
id: changelog
run: |
echo "========================================"
echo "📝 生成 Changelog"
echo "========================================"
# 取得完整 git history
git fetch --unshallow 2>/dev/null || git fetch --all
# 取得前一個 tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then
echo "📋 無前一個 tag取得所有 commits..."
CHANGELOG=$(git log --pretty=format:"- %s (%h)" HEAD)
else
echo "📋 取得 ${PREVIOUS_TAG} 到 HEAD 的 commits..."
CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREVIOUS_TAG}..HEAD)
fi
# 建立 changelog 檔案(與 release.yml 風格一致)
echo "$CHANGELOG" > changelog.txt
# 加入 Full Changelog 連結
echo "" >> changelog.txt
echo "Full Changelog: https://github.com/${{ github.repository }}/compare/${PREVIOUS_TAG}...v${{ github.event.inputs.image_tag }}" >> changelog.txt
echo ""
echo "✅ Changelog 已生成:"
cat changelog.txt
echo "========================================"
# ========================================
# Step 18: 創建 GitHub Release
# 說明:為正式版本自動創建 GitHub Release
# 注意:不使用 generate_release_notes改用 body_path 提供完整 changelog
# 注意:如果 Release 已存在會失敗,使用 continue-on-error 跳過
# ========================================
- name: 📦 Create GitHub Release
if: ${{ !contains(steps.parse_version.outputs.image_tag, 'test') && !contains(steps.parse_version.outputs.image_tag, 'dev') && !contains(steps.parse_version.outputs.image_tag, 'alpha') && !contains(steps.parse_version.outputs.image_tag, 'beta') }}
continue-on-error: true
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.parse_version.outputs.image_tag }}
name: v${{ steps.parse_version.outputs.image_tag }}
body_path: changelog.txt
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ========================================
# Step 19: 更新遠端 ConvertX-CN 服務
# 說明:拉取最新映像並重新啟動服務(所有步驟完成後執行)
# ========================================
- name: 🔄 更新遠端 ConvertX-CN 服務
run: |
echo "========================================"
echo "🔄 更新遠端 ConvertX-CN 服務"
echo "========================================"
echo "版本: v${{ steps.parse_version.outputs.image_tag }}"
echo ""
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << REMOTE_EOF
set -e
IMAGE_TAG="v${{ steps.parse_version.outputs.image_tag }}"
# 1. 進入專案資料夾
cd /home/bioailab/miniconda3/lid/app/convertx-cn
# 2. 停止服務
echo "📋 停止服務..."
docker compose down
# 3. 拉取最新映像檔
echo ""
echo "📋 拉取最新映像檔(\${IMAGE_TAG}..."
docker compose pull
# 4. 重新啟動
echo ""
echo "📋 重新啟動服務..."
docker compose up -d
# 5. 顯示服務狀態與 log
echo ""
echo "📋 服務狀態:"
docker compose ps
echo ""
echo "📋 服務 log最近 100 行):"
docker logs convertx-cn --tail=100
echo ""
echo "✅ ConvertX-CN 服務已更新至 \${IMAGE_TAG}"
REMOTE_EOF
echo "========================================"
# ========================================
# Step 20: Docker 容器 E2E 測試
# 說明:在已更新的遠端服務上執行完整 E2E 測試
# ========================================
- name: 🧪 Docker 容器 E2E 測試
run: |
echo "========================================"
echo "🧪 Docker 容器 E2E 測試(已部署服務)"
echo "========================================"
echo "版本: v${{ github.event.inputs.image_tag }}"
echo ""
tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'REMOTE_EOF'
set -e
CONTAINER_NAME="convertx-cn"
echo "⏳ 等待服務就緒..."
for i in {1..60}; do
if docker exec "${CONTAINER_NAME}" curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then
echo "✅ 服務在 ${i} 秒內就緒"
break
fi
if [ $i -eq 60 ]; then
echo "❌ 服務啟動超時"
docker logs "${CONTAINER_NAME}" --tail=50
exit 1
fi
sleep 1
done
echo ""
echo "========================================"
echo "📋 執行 E2E 轉換測試..."
echo "========================================"
FAILED=0
PASSED=0
# 測試 1: Inkscape SVG → PNG
docker exec "${CONTAINER_NAME}" sh -c '
echo "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" height=\"100\"><circle cx=\"50\" cy=\"50\" r=\"40\" fill=\"red\"/></svg>" > /tmp/test.svg
inkscape --export-type=png --export-filename=/tmp/test.png /tmp/test.svg 2>/dev/null
[ -f /tmp/test.png ] && echo "✅ Inkscape SVG → PNG" || exit 1
' && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1))
# 測試 2: Inkscape SVG → PDF
docker exec "${CONTAINER_NAME}" sh -c '
inkscape --export-type=pdf --export-filename=/tmp/test.pdf /tmp/test.svg 2>/dev/null
[ -f /tmp/test.pdf ] && echo "✅ Inkscape SVG → PDF" || exit 1
' && PASSED=$((PASSED + 1)) || echo "⚠️ Inkscape SVG → PDF 跳過"
# 測試 3: Pandoc Markdown → HTML
docker exec "${CONTAINER_NAME}" sh -c '
echo "# Test\n\nHello **world**" > /tmp/test.md
pandoc /tmp/test.md -o /tmp/test.html
[ -f /tmp/test.html ] && echo "✅ Pandoc Markdown → HTML" || exit 1
' && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1))
# 測試 4: Pandoc Markdown → DOCX
docker exec "${CONTAINER_NAME}" sh -c '
pandoc /tmp/test.md -o /tmp/test_pandoc.docx
[ -f /tmp/test_pandoc.docx ] && echo "✅ Pandoc Markdown → DOCX" || exit 1
' && PASSED=$((PASSED + 1)) || echo "⚠️ Pandoc DOCX 跳過"
# 測試 5: Pandoc Markdown → LaTeX
docker exec "${CONTAINER_NAME}" sh -c '
pandoc /tmp/test.md -o /tmp/test.tex
[ -f /tmp/test.tex ] && echo "✅ Pandoc Markdown → LaTeX" || exit 1
' && PASSED=$((PASSED + 1)) || echo "⚠️ Pandoc LaTeX 跳過"
# 測試 6: LibreOffice TXT → PDF
docker exec "${CONTAINER_NAME}" sh -c '
echo "Test document content" > /tmp/test.txt
libreoffice --headless --convert-to pdf --outdir /tmp /tmp/test.txt 2>/dev/null
[ -f /tmp/test.pdf ] && echo "✅ LibreOffice TXT → PDF" || exit 1
' && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1))
# 測試 7: LibreOffice TXT → ODT → DOCX
docker exec "${CONTAINER_NAME}" sh -c '
libreoffice --headless --convert-to odt --outdir /tmp /tmp/test.txt 2>/dev/null
if [ -f /tmp/test.odt ]; then
libreoffice --headless --convert-to docx --outdir /tmp /tmp/test.odt 2>/dev/null
[ -f /tmp/test.docx ] && echo "✅ LibreOffice ODT → DOCX" || exit 1
fi
' && PASSED=$((PASSED + 1)) || echo "⚠️ LibreOffice ODT 跳過"
# 測試 8: FFmpeg 音頻生成
docker exec "${CONTAINER_NAME}" sh -c '
ffmpeg -f lavfi -i "sine=frequency=440:duration=1" -y /tmp/test.wav 2>/dev/null
[ -f /tmp/test.wav ] && echo "✅ FFmpeg 音頻生成" || exit 1
' && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1))
# 測試 9: FFmpeg WAV → MP3
docker exec "${CONTAINER_NAME}" sh -c '
ffmpeg -i /tmp/test.wav -y /tmp/test.mp3 2>/dev/null
[ -f /tmp/test.mp3 ] && echo "✅ FFmpeg WAV → MP3" || exit 1
' && PASSED=$((PASSED + 1)) || echo "⚠️ FFmpeg MP3 跳過"
# 測試 10: FFmpeg 影片生成
docker exec "${CONTAINER_NAME}" sh -c '
ffmpeg -f lavfi -i color=c=red:s=320x240:d=1 -y /tmp/test_video.mp4 2>/dev/null
[ -f /tmp/test_video.mp4 ] && echo "✅ FFmpeg 影片生成" || exit 1
' && PASSED=$((PASSED + 1)) || echo "⚠️ FFmpeg 影片 跳過"
# 測試 11: FFmpeg MP4 → GIF
docker exec "${CONTAINER_NAME}" sh -c '
if [ -f /tmp/test_video.mp4 ]; then
ffmpeg -i /tmp/test_video.mp4 -vf "fps=10,scale=160:-1" -y /tmp/test_video.gif 2>/dev/null
[ -f /tmp/test_video.gif ] && echo "✅ FFmpeg MP4 → GIF" || exit 1
fi
' && PASSED=$((PASSED + 1)) || echo "⚠️ FFmpeg GIF 跳過"
# 測試 12: ImageMagick 圖片生成
docker exec "${CONTAINER_NAME}" sh -c '
convert -size 100x100 xc:blue /tmp/blue.png
[ -f /tmp/blue.png ] && echo "✅ ImageMagick 圖片生成" || exit 1
' && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1))
# 測試 13: ImageMagick PNG → JPEG
docker exec "${CONTAINER_NAME}" sh -c '
convert /tmp/blue.png /tmp/blue.jpg
[ -f /tmp/blue.jpg ] && echo "✅ ImageMagick PNG → JPEG" || exit 1
' && PASSED=$((PASSED + 1)) || echo "⚠️ ImageMagick JPEG 跳過"
# 測試 14: ImageMagick PNG → WEBP
docker exec "${CONTAINER_NAME}" sh -c '
convert /tmp/blue.png /tmp/blue.webp
[ -f /tmp/blue.webp ] && echo "✅ ImageMagick PNG → WEBP" || exit 1
' && PASSED=$((PASSED + 1)) || echo "⚠️ ImageMagick WEBP 跳過"
# 測試 15: Calibre HTML → EPUB
docker exec "${CONTAINER_NAME}" sh -c '
if command -v ebook-convert &> /dev/null; then
echo "<!DOCTYPE html><html><body><h1>Test</h1></body></html>" > /tmp/book.html
ebook-convert /tmp/book.html /tmp/book.epub 2>/dev/null
[ -f /tmp/book.epub ] && echo "✅ Calibre HTML → EPUB" || exit 1
else
exit 1
fi
' && PASSED=$((PASSED + 1)) || echo "⚠️ Calibre 跳過"
# 測試 16: Potrace PBM → SVG
docker exec "${CONTAINER_NAME}" sh -c '
if command -v potrace &> /dev/null; then
echo "P1 4 4 0 1 1 0 1 0 0 1 1 0 0 1 0 1 1 0" > /tmp/test.pbm
potrace -s -o /tmp/potrace.svg /tmp/test.pbm
[ -f /tmp/potrace.svg ] && echo "✅ Potrace PBM → SVG" || exit 1
else
exit 1
fi
' && PASSED=$((PASSED + 1)) || echo "⚠️ Potrace 跳過"
# 測試 17: Tesseract OCR
docker exec "${CONTAINER_NAME}" sh -c '
if command -v tesseract &> /dev/null; then
convert -size 200x50 xc:white -font DejaVu-Sans -pointsize 20 -fill black -annotate +10+35 "Test" /tmp/ocr.png 2>/dev/null
tesseract /tmp/ocr.png /tmp/ocr 2>/dev/null
[ -f /tmp/ocr.txt ] && echo "✅ Tesseract OCR" || exit 1
else
exit 1
fi
' && PASSED=$((PASSED + 1)) || echo "⚠️ Tesseract 跳過"
# 測試 18: GraphicsMagick
docker exec "${CONTAINER_NAME}" sh -c '
if command -v gm &> /dev/null; then
gm convert -size 100x100 xc:green /tmp/gm.png
gm convert /tmp/gm.png /tmp/gm.gif
[ -f /tmp/gm.gif ] && echo "✅ GraphicsMagick PNG → GIF" || exit 1
else
exit 1
fi
' && PASSED=$((PASSED + 1)) || echo "⚠️ GraphicsMagick 跳過"
# 測試 19: Resvg
docker exec "${CONTAINER_NAME}" sh -c '
if command -v resvg &> /dev/null; then
resvg /tmp/test.svg /tmp/resvg.png
[ -f /tmp/resvg.png ] && echo "✅ Resvg SVG → PNG" || exit 1
else
exit 1
fi
' && PASSED=$((PASSED + 1)) || echo "⚠️ Resvg 跳過"
# 測試 20: VIPS
docker exec "${CONTAINER_NAME}" sh -c '
if command -v vips &> /dev/null; then
vips copy /tmp/blue.png /tmp/vips.webp
[ -f /tmp/vips.webp ] && echo "✅ VIPS PNG → WEBP" || exit 1
else
exit 1
fi
' && PASSED=$((PASSED + 1)) || echo "⚠️ VIPS 跳過"
# 測試 21: API Healthcheck
if docker exec "${CONTAINER_NAME}" curl -sf http://localhost:3000/healthcheck | grep -q "OK"; then
echo "✅ API Healthcheck"
PASSED=$((PASSED + 1))
else
echo "❌ API Healthcheck 失敗"
FAILED=$((FAILED + 1))
fi
# 結果摘要
echo ""
echo "========================================"
echo "📊 E2E 測試結果"
echo "========================================"
echo "✅ 通過: ${PASSED} 項"
echo "⚠️ 跳過/失敗: 部分可選測試"
echo ""
if [ $FAILED -gt 3 ]; then
echo "❌ 關鍵測試失敗過多!"
exit 1
else
echo "✅ E2E 測試通過!"
fi
echo "========================================"
REMOTE_EOF
echo "========================================"