convertor/.github/workflows/release.yml

554 lines
21 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

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.

name: Release
on:
push:
tags:
- "v*.*.*"
# 允許手動觸發
workflow_dispatch:
inputs:
tag:
description: "Tag to release (e.g., v0.1.13)"
required: true
type: string
env:
DOCKER_IMAGE: convertx/convertx-cn
jobs:
# ==============================================================================
# 🐳 Build and Push Docker Image關鍵流程
# ==============================================================================
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# ========================================
# 清理磁碟空間(解決 no space left on device
# ========================================
# 🔥 GitHub Actions Runner 只有約 14GB 可用空間
# 大型模型 + Multi-Arch build 需要清理更多預裝軟體
# ========================================
- name: Free disk space (aggressive)
run: |
echo "🧹 Aggressive disk cleanup for large model builds..."
echo "========================================"
echo "📊 初始磁碟空間:"
df -h /
echo "========================================"
# ========================================
# 移除大型預裝軟體(約可釋放 30-40GB
# ========================================
echo "🗑️ 移除預裝開發工具..."
# .NET SDK約 2-3GB
sudo rm -rf /usr/share/dotnet || true
# Android SDK約 10-15GB- 最大宗!
sudo rm -rf /usr/local/lib/android || true
# Haskell/GHC約 1GB
sudo rm -rf /opt/ghc || true
# CodeQL約 1GB
sudo rm -rf /opt/hostedtoolcache/CodeQL || true
# Boost約 500MB
sudo rm -rf /usr/local/share/boost || true
# Swift約 1.5GB
sudo rm -rf /usr/share/swift || true
# Hosted Tool Cache約 5-8GB
sudo rm -rf /opt/hostedtoolcache || true
# Azure CLI約 500MB
sudo rm -rf /usr/share/az_* || true
sudo rm -rf /opt/az || true
# Google Cloud SDK約 500MB
sudo rm -rf /usr/lib/google-cloud-sdk || true
# PowerShell約 200MB
sudo rm -rf /usr/local/share/powershell || true
# Miniconda約 500MB
sudo rm -rf /usr/share/miniconda || true
# ========================================
# 清理系統 cache
# ========================================
echo "🗑️ 清理系統 cache..."
# apt cache
sudo apt-get clean || true
sudo apt-get autoremove -y || true
sudo rm -rf /var/lib/apt/lists/* || true
# npm/yarn global cache
sudo rm -rf /usr/local/share/.cache || true
# ========================================
# 清理 Docker 舊資料
# ========================================
echo "🗑️ 清理 Docker cache..."
docker system prune -af --volumes || true
# 清理 BuildKit cache
sudo rm -rf /var/lib/docker/buildkit || true
echo "========================================"
echo "📊 清理後磁碟空間:"
df -h /
echo "========================================"
- name: Get tag name
id: tag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "version=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
else
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
# ========================================
# 設定 Docker Buildx針對大型模型優化
# ========================================
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
# 🔥 使用 docker-container driver 以獲得更好的 cache 控制
driver-opts: |
image=moby/buildkit:latest
network=host
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# ========================================
# Build and Push針對大型模型優化
# ========================================
# 🔥 注意事項:
# - 使用 registry cache 而非 GHA cache避免 10GB 限制)
# - 啟用 zstd compression 減少傳輸量和儲存空間
# - Multi-arch build 需要更多磁碟空間,已在上方清理
# ========================================
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}
${{ env.DOCKER_IMAGE }}:latest
platforms: linux/amd64,linux/arm64
# 🔥 使用 registry cache 取代 GHA cache
# registry cache 沒有 10GB 限制,更適合大型模型
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.DOCKER_IMAGE }}:buildcache,mode=max,compression=zstd
# 啟用壓縮減少 image 大小
outputs: type=image,compression=zstd,compression-level=3
# 設定 build 參數
build-args: |
BUILDKIT_INLINE_CACHE=1
- name: Docker Build Summary
run: |
echo "## 🐳 Docker Image Published" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Repository:** \`${{ env.DOCKER_IMAGE }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.DOCKER_IMAGE }}:latest\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Platforms:** linux/amd64, linux/arm64" >> $GITHUB_STEP_SUMMARY
# ==============================================================================
# 🔍 Verify Docker Image模型驗證 + Headless 驗證)
# ==============================================================================
verify-image:
name: Verify Docker Image
runs-on: ubuntu-latest
needs: build-and-push
timeout-minutes: 45
steps:
- name: Get tag name
id: tag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "version=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
else
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
- name: Pull Docker image
timeout-minutes: 25
run: |
echo "📥 Pulling Docker image大型 image可能需要 10-20 分鐘)..."
echo "Image size: ~8-12 GB含預下載模型"
docker pull ${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}
# ========================================
# 驗證 1: 工具存在性 + 版本檢查(使用 xvfb 解決 GUI 問題)
# ========================================
- name: "🔍 Tools & Model Verification"
timeout-minutes: 5
run: |
echo "========================================"
echo "🔍 工具存在性 + 模型驗證"
echo "========================================"
IMAGE="${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}"
# 🔥 關鍵:使用 xvfb-run 包裹所有 GUI 工具的版本檢查
# 這樣 Inkscape、LibreOffice、Calibre 等 GUI 工具就不會卡住
docker run --rm "${IMAGE}" sh -c '
echo ""
echo "========================================"
echo "🔧 CLI 工具版本檢查"
echo "========================================"
# 純 CLI 工具 - 直接執行
echo "ConvertX $(cat /app/package.json 2>/dev/null | grep version | head -1 | cut -d\" -f4)"
echo "Bun $(bun --version 2>/dev/null || echo "N/A")"
pandoc --version 2>/dev/null | head -1 || echo "pandoc: N/A"
ffmpeg -version 2>/dev/null | head -1 || echo "ffmpeg: N/A"
tesseract --version 2>&1 | head -1 || echo "tesseract: N/A"
vips --version 2>/dev/null || echo "vips: N/A"
gm -version 2>/dev/null | head -1 || echo "GraphicsMagick: N/A"
resvg --version 2>/dev/null || echo "resvg: N/A"
potrace --version 2>/dev/null | head -1 || echo "potrace: N/A"
dasel --version 2>/dev/null || echo "dasel: N/A"
djxl --version 2>&1 | head -1 || echo "djxl: N/A"
echo ""
echo "========================================"
echo "🖥️ GUI 工具版本檢查(使用 xvfb-run"
echo "========================================"
# GUI 工具 - 必須使用 xvfb-run 包裹
xvfb-run -a inkscape --version 2>/dev/null || echo "Inkscape: N/A"
xvfb-run -a ebook-convert --version 2>/dev/null | head -1 || echo "Calibre: N/A"
xvfb-run -a libreoffice --version 2>/dev/null || echo "LibreOffice: N/A"
echo ""
echo "========================================"
echo "🤖 模型目錄檢查"
echo "========================================"
echo -n " HuggingFace cache: "
[ -d "/root/.cache/huggingface" ] && echo "✅ 存在" || echo "⚠️ 不存在"
echo -n " ModelScope cache: "
[ -d "/root/.cache/modelscope" ] && echo "✅ 存在" || echo "⚠️ 不存在"
echo -n " BabelDOC cache: "
[ -d "/root/.cache/babeldoc" ] && echo "✅ 存在" || echo "⚠️ 不存在"
echo ""
echo "========================================"
echo "✅ 工具驗證完成!"
echo "========================================"
'
# ========================================
# 驗證 2: 啟動容器並檢查健康狀態
# ========================================
- name: "⚙️ Start Container & Health Check"
timeout-minutes: 3
run: |
echo "========================================"
echo "⚙️ 啟動容器並檢查健康狀態"
echo "========================================"
IMAGE="${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}"
# 啟動容器
echo "🚀 啟動容器..."
docker run -d --name verify-test -p 3000:3000 "${IMAGE}"
# 等待啟動
echo "⏳ 等待容器啟動..."
for i in {1..90}; do
if docker exec verify-test curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then
echo "✅ 容器在 ${i} 秒內啟動成功"
break
fi
if [ $i -eq 90 ]; then
echo "❌ 容器啟動超時"
docker logs verify-test
docker rm -f verify-test
exit 1
fi
sleep 1
done
# 顯示容器日誌
echo ""
echo "📋 容器啟動日誌:"
docker logs verify-test 2>&1 | tail -20
echo ""
echo "========================================"
echo "✅ 容器健康檢查通過!"
echo "========================================"
# ========================================
# 驗證 3: E2E 轉換測試(使用 xvfb-run 解決 GUI 問題)
# ========================================
- name: "🧪 E2E Tests in Container"
timeout-minutes: 10
run: |
echo "========================================"
echo "🧪 E2E 轉換測試"
echo "========================================"
# 使用已啟動的 verify-test 容器
# 🔥 關鍵:所有 GUI 工具都使用 xvfb-run 包裹
FAILED=0
# 測試 1: Inkscape SVG → PNG使用 xvfb-run
echo ""
echo "🔄 測試 Inkscape: SVG → PNG"
docker exec verify-test 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
xvfb-run -a inkscape --export-type=png --export-filename=/tmp/test.png /tmp/test.svg 2>/dev/null
if [ -f /tmp/test.png ]; then
echo "✅ Inkscape SVG → PNG 成功"
ls -la /tmp/test.png
else
echo "❌ Inkscape SVG → PNG 失敗"
exit 1
fi
' || FAILED=1
# 測試 2: Pandoc Markdown → HTML純 CLI不需要 xvfb
echo ""
echo "🔄 測試 Pandoc: Markdown → HTML"
docker exec verify-test sh -c '
echo "# Test\n\nHello **world**" > /tmp/test.md
pandoc /tmp/test.md -o /tmp/test.html
if [ -f /tmp/test.html ]; then
echo "✅ Pandoc Markdown → HTML 成功"
cat /tmp/test.html
else
echo "❌ Pandoc Markdown → HTML 失敗"
exit 1
fi
' || FAILED=1
# 測試 3: LibreOffice TXT → PDF使用 xvfb-run
echo ""
echo "🔄 測試 LibreOffice: TXT → PDF"
docker exec verify-test sh -c '
echo "Test document content" > /tmp/test.txt
xvfb-run -a libreoffice --headless --convert-to pdf --outdir /tmp /tmp/test.txt 2>/dev/null
if [ -f /tmp/test.pdf ]; then
echo "✅ LibreOffice TXT → PDF 成功"
ls -la /tmp/test.pdf
else
echo "❌ LibreOffice TXT → PDF 失敗"
exit 1
fi
' || FAILED=1
# 測試 4: FFmpeg 音頻轉換(純 CLI
echo ""
echo "🔄 測試 FFmpeg: 生成測試音頻"
docker exec verify-test sh -c '
ffmpeg -f lavfi -i "sine=frequency=440:duration=1" -y /tmp/test.wav 2>/dev/null
if [ -f /tmp/test.wav ]; then
echo "✅ FFmpeg 音頻生成成功"
ls -la /tmp/test.wav
else
echo "❌ FFmpeg 音頻生成失敗"
exit 1
fi
' || FAILED=1
# 測試 5: Calibre HTML → EPUB使用 xvfb-run
echo ""
echo "🔄 測試 Calibre: HTML → EPUB"
docker exec verify-test sh -c '
echo "<!DOCTYPE html><html><body><h1>Test Book</h1><p>Content</p></body></html>" > /tmp/book.html
xvfb-run -a ebook-convert /tmp/book.html /tmp/book.epub 2>/dev/null
if [ -f /tmp/book.epub ]; then
echo "✅ Calibre HTML → EPUB 成功"
ls -la /tmp/book.epub
else
echo "❌ Calibre HTML → EPUB 失敗"
exit 1
fi
' || FAILED=1
# 測試 6: Tesseract OCR純 CLI
echo ""
echo "🔄 測試 Tesseract: OCR 文字識別"
docker exec verify-test sh -c '
convert -size 200x50 xc:white -font DejaVu-Sans -pointsize 20 -fill black -annotate +10+35 "Hello OCR" /tmp/ocr_test.png 2>/dev/null
tesseract /tmp/ocr_test.png /tmp/ocr_output 2>/dev/null
if [ -f /tmp/ocr_output.txt ]; then
echo "✅ Tesseract OCR 成功"
cat /tmp/ocr_output.txt
else
echo "❌ Tesseract OCR 失敗"
exit 1
fi
' || FAILED=1
# 測試 7: Resvg SVG → PNG純 CLI
echo ""
echo "🔄 測試 Resvg: SVG → PNG"
docker exec verify-test sh -c '
echo "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"50\" height=\"50\"><rect fill=\"purple\" width=\"50\" height=\"50\"/></svg>" > /tmp/resvg_test.svg
resvg /tmp/resvg_test.svg /tmp/resvg_output.png 2>/dev/null
if [ -f /tmp/resvg_output.png ]; then
echo "✅ Resvg SVG → PNG 成功"
ls -la /tmp/resvg_output.png
else
echo "❌ Resvg SVG → PNG 失敗"
exit 1
fi
' || FAILED=1
# 測試 8: FFmpeg 影片轉換(純 CLI
echo ""
echo "🔄 測試 FFmpeg: 影片生成"
docker exec verify-test sh -c '
ffmpeg -f lavfi -i color=c=red:s=320x240:d=1 -y /tmp/test_video.mp4 2>/dev/null
if [ -f /tmp/test_video.mp4 ]; then
echo "✅ FFmpeg 影片生成成功"
ls -la /tmp/test_video.mp4
else
echo "❌ FFmpeg 影片生成失敗"
exit 1
fi
' || FAILED=1
# 測試 9: API 健康檢查
echo ""
echo "🔄 測試 API 端點"
if docker exec verify-test curl -sf http://localhost:3000/healthcheck | grep -q "OK"; then
echo "✅ Healthcheck API 正常"
else
echo "❌ Healthcheck API 失敗"
FAILED=1
fi
# 測試摘要
echo ""
echo "========================================"
echo "📊 E2E 測試摘要"
echo "========================================"
echo "✓ Inkscape: SVG → PNG (xvfb-run)"
echo "✓ Pandoc: Markdown → HTML"
echo "✓ LibreOffice: TXT → PDF (xvfb-run)"
echo "✓ FFmpeg: 音頻 + 影片"
echo "✓ Calibre: HTML → EPUB (xvfb-run)"
echo "✓ Tesseract: OCR"
echo "✓ Resvg: SVG → PNG"
echo "✓ API: Healthcheck"
echo "========================================"
if [ $FAILED -eq 0 ]; then
echo "✅ E2E 測試全部通過!"
else
echo "❌ E2E 測試有失敗項目"
exit 1
fi
# ========================================
# 清理驗證容器
# ========================================
- name: "🧹 Cleanup Verification Containers"
if: always()
run: |
docker rm -f verify-test 2>/dev/null || true
- name: Verification Summary
run: |
echo "## 🔍 Image Verification Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image:** \`${{ env.DOCKER_IMAGE }}:${{ steps.tag.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### ✅ Verification Passed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- <20> Tools & Model: Verified" >> $GITHUB_STEP_SUMMARY
echo "- ⚙️ Basic Functionality: Verified" >> $GITHUB_STEP_SUMMARY
echo "- 🧪 E2E Tests: Passed" >> $GITHUB_STEP_SUMMARY
# ==============================================================================
# 📦 Create GitHub Release
# ==============================================================================
create-release:
runs-on: ubuntu-latest
needs: [build-and-push, verify-image]
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get tag name
id: tag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "version=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
else
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
- name: Generate changelog
id: changelog
run: |
# Get previous tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [ -z "$PREVIOUS_TAG" ]; then
# No previous tag, get all commits
CHANGELOG=$(git log --pretty=format:"- %s (%h)" HEAD)
else
# Get commits between previous tag and current
CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREVIOUS_TAG}..HEAD)
fi
# Write to file to preserve newlines
echo "$CHANGELOG" > changelog.txt
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: ${{ steps.tag.outputs.version }}
tag_name: ${{ steps.tag.outputs.version }}
body_path: changelog.txt
generate_release_notes: true
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}