# ============================================================================== # Upstream Sync Build + Smoke Test # 功能: # 1. dev 分支有 push 時自動觸發 # 2. 建構 Docker image(僅 amd64 用於測試) # 3. 執行 smoke test(10 分鐘內完成) # 4. 測試通過後推送 multi-arch image # ============================================================================== name: Upstream Sync Build on: push: branches: - dev env: DOCKER_IMAGE: convertx/convertx-cn TEST_IMAGE: convertx-cn-test # Smoke test 超時時間(秒) SMOKE_TEST_TIMEOUT: 600 concurrency: group: upstream-sync-${{ github.ref }} cancel-in-progress: true jobs: # ========================================================================== # Job 1: 快速建構測試用 image(僅 amd64) # ========================================================================== build-test-image: name: Build Test Image runs-on: ubuntu-latest outputs: image_tag: ${{ steps.date.outputs.date }} steps: - name: Checkout repository uses: actions/checkout@v4 # ======================================== # 清理磁碟空間(解決 no space left on device) # ======================================== - name: Free disk space run: | echo "🧹 Cleaning up disk space..." echo "Before cleanup:" df -h # 移除不需要的預裝軟體 sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/local/lib/android sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/share/swift # 清理 apt cache sudo apt-get clean sudo apt-get autoremove -y # 清理 Docker 舊資料 docker system prune -af --volumes || true echo "" echo "After cleanup:" df -h - name: Get current date id: date run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build test image (amd64 only) uses: docker/build-push-action@v6 with: context: . push: false load: true tags: ${{ env.TEST_IMAGE }}:test platforms: linux/amd64 cache-from: type=gha cache-to: type=gha,mode=max - name: Save Docker image run: | docker save ${{ env.TEST_IMAGE }}:test -o /tmp/test-image.tar - name: Upload test image uses: actions/upload-artifact@v4 with: name: test-image path: /tmp/test-image.tar retention-days: 1 # ========================================================================== # Job 2: Smoke Test(必須在 10 分鐘內完成) # ========================================================================== smoke-test: name: Smoke Test runs-on: ubuntu-latest needs: build-test-image timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Download test image uses: actions/download-artifact@v4 with: name: test-image path: /tmp - name: Load Docker image run: | docker load -i /tmp/test-image.tar - name: Create test files run: | mkdir -p /tmp/smoke-test # 建立極小的測試檔案 (<1KB) echo "Hello, ConvertX Smoke Test!" > /tmp/smoke-test/test.txt echo "這是中文測試檔案" >> /tmp/smoke-test/test.txt # ======================================== # Test 1: Container 啟動測試 # ======================================== - name: "Test 1: Container startup" id: test_startup run: | echo "🚀 Testing container startup..." # 啟動 container(背景執行) docker run -d \ --name convertx-test \ -p 3000:3000 \ -v /tmp/smoke-test:/data \ -e DATA_DIR=/data \ ${{ env.TEST_IMAGE }}:test # 等待啟動(最多 60 秒) echo "⏳ Waiting for container to start..." for i in {1..60}; do if docker exec convertx-test curl -sf http://localhost:3000/healthcheck > /dev/null 2>&1; then echo "✅ Container started successfully in ${i}s" echo "startup_time=${i}" >> $GITHUB_OUTPUT exit 0 fi sleep 1 done echo "❌ Container failed to start within 60s" docker logs convertx-test exit 1 # ======================================== # Test 2: 關鍵工具存在性檢查 # ======================================== - name: "Test 2: Tool availability check" id: test_tools run: | echo "🔧 Checking critical tools..." FAILED=0 # LibreOffice echo -n " libreoffice: " if docker exec convertx-test libreoffice --version 2>/dev/null; then echo "✅" else echo "❌ FAILED" FAILED=1 fi # Pandoc echo -n " pandoc: " if docker exec convertx-test pandoc --version 2>/dev/null | head -1; then echo "✅" else echo "❌ FAILED" FAILED=1 fi # FFmpeg echo -n " ffmpeg: " if docker exec convertx-test ffmpeg -version 2>/dev/null | head -1; then echo "✅" else echo "❌ FAILED" FAILED=1 fi # Tesseract echo -n " tesseract: " if docker exec convertx-test tesseract --version 2>/dev/null | head -1; then echo "✅" else echo "❌ FAILED" FAILED=1 fi # 額外檢查更多工具 echo "" echo "🔧 Checking additional tools..." # ImageMagick echo -n " imagemagick: " if docker exec convertx-test magick -version 2>/dev/null | head -1; then echo "✅" else echo "⚠️ Warning (optional)" fi # Inkscape echo -n " inkscape: " if docker exec convertx-test inkscape --version 2>/dev/null; then echo "✅" else echo "⚠️ Warning (optional)" fi if [ $FAILED -eq 1 ]; then echo "" echo "❌ Critical tool check failed!" exit 1 fi echo "" echo "✅ All critical tools available" # ======================================== # Test 3: Headless 環境驗證 # ======================================== - name: "Test 3: Headless environment verification" run: | echo "🖥️ Verifying headless environment..." # 檢查 DISPLAY 環境變數 echo "" echo "📋 檢查 DISPLAY 環境變數..." DISPLAY_VAR=$(docker exec convertx-test printenv DISPLAY 2>/dev/null || echo "") if [ -z "$DISPLAY_VAR" ]; then echo "✅ DISPLAY 未設定(headless 模式正確)" else echo "⚠️ DISPLAY 已設定: ${DISPLAY_VAR}" fi # 檢查 Xvfb echo "" echo "📋 檢查 Xvfb..." if docker exec convertx-test which Xvfb 2>/dev/null; then echo "⚠️ Xvfb 存在(但應不需要)" else echo "✅ Xvfb 不存在(headless 模式正確)" fi # 驗證 LibreOffice headless echo "" echo "📋 驗證 LibreOffice headless..." if docker exec convertx-test libreoffice --headless --version 2>/dev/null | head -1; then echo "✅ LibreOffice headless 模式可用" else echo "❌ LibreOffice headless 模式失敗" exit 1 fi echo "" echo "✅ Headless environment verification passed" # ======================================== # Test 4: 模型下載驗證 # ======================================== - name: "Test 4: Model download verification" run: | echo "🤖 Verifying model downloads..." # 檢查 HuggingFace cache echo "" echo "📋 檢查 HuggingFace cache..." HF_CACHE=$(docker exec convertx-test ls -la /root/.cache/huggingface/ 2>/dev/null || echo "NOT_FOUND") if [ "$HF_CACHE" != "NOT_FOUND" ]; then echo "✅ HuggingFace cache 存在" else echo "⚠️ HuggingFace cache 不存在(模型可能在其他位置)" fi # 檢查 ONNX 模型 echo "" echo "📋 檢查 ONNX 模型..." ONNX_COUNT=$(docker exec convertx-test find /root -name "*.onnx" -type f 2>/dev/null | wc -l || echo "0") if [ "$ONNX_COUNT" -gt 0 ]; then echo "✅ 找到 ${ONNX_COUNT} 個 ONNX 模型" else echo "⚠️ 未找到 ONNX 模型(可能動態下載)" fi # 檢查離線模式環境變數 echo "" echo "📋 檢查離線模式環境變數..." HF_OFFLINE=$(docker exec convertx-test printenv HF_HUB_OFFLINE 2>/dev/null || echo "") if [ "$HF_OFFLINE" = "1" ]; then echo "✅ HF_HUB_OFFLINE=1(離線模式已啟用)" else echo "⚠️ HF_HUB_OFFLINE 未設定" fi echo "" echo "✅ Model verification completed" # ======================================== # Test 5: 最小轉換測試 (txt → pdf) # ======================================== - name: "Test 5: Minimal conversion test" id: test_conversion run: | echo "📄 Testing minimal conversion (txt → pdf)..." # 使用 API 進行轉換測試 # 先上傳檔案 echo " Uploading test file..." UPLOAD_RESPONSE=$(curl -sf -X POST \ -F "file=@/tmp/smoke-test/test.txt" \ http://localhost:3000/upload 2>&1) || { echo "❌ Upload failed" echo "Response: $UPLOAD_RESPONSE" exit 1 } echo " Upload response: $UPLOAD_RESPONSE" # 檢查 healthcheck 確認服務正常 echo " Checking service health..." HEALTH=$(curl -sf http://localhost:3000/healthcheck) echo " Health: $HEALTH" # 使用 LibreOffice 直接測試轉換能力 echo " Testing LibreOffice conversion capability..." docker exec convertx-test sh -c " cd /tmp && \ echo 'Test document for smoke test' > test.txt && \ libreoffice --headless --convert-to pdf test.txt && \ ls -la test.pdf " && echo "✅ Conversion test passed" || { echo "❌ Conversion test failed" exit 1 } # ======================================== # Test 6: API 端點檢查 # ======================================== - name: "Test 6: API endpoints check" run: | echo "🌐 Checking API endpoints..." # Healthcheck echo -n " /healthcheck: " if curl -sf http://localhost:3000/healthcheck > /dev/null; then echo "✅" else echo "❌" exit 1 fi # Root page echo -n " / (root): " if curl -sf http://localhost:3000/ > /dev/null; then echo "✅" else echo "❌" exit 1 fi # Converters list echo -n " /converters: " if curl -sf http://localhost:3000/converters > /dev/null; then echo "✅" else echo "⚠️ (may require auth)" fi echo "" echo "✅ API endpoints check passed" - name: Cleanup test container if: always() run: | docker stop convertx-test || true docker rm convertx-test || true - name: Smoke test summary if: always() run: | echo "## 🧪 Smoke Test Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| 測試項目 | 狀態 |" >> $GITHUB_STEP_SUMMARY echo "|----------|------|" >> $GITHUB_STEP_SUMMARY echo "| Container 啟動 | ✅ 通過 (${startup_time:-N/A}s) |" >> $GITHUB_STEP_SUMMARY echo "| 工具檢查 (libreoffice, pandoc, ffmpeg, tesseract) | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY echo "| Headless 環境驗證 | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY echo "| 模型下載驗證 | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY echo "| 最小轉換測試 | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY echo "| API 端點檢查 | ✅ 通過 |" >> $GITHUB_STEP_SUMMARY # ========================================================================== # Job 3: 測試通過後,建構並推送 multi-arch image # ========================================================================== build-and-push: name: Build & Push Multi-arch Image runs-on: ubuntu-latest needs: [build-test-image, smoke-test] steps: - name: Checkout repository uses: actions/checkout@v4 # ======================================== # 清理磁碟空間(multi-arch build 需要更多空間) # ======================================== - name: Free disk space run: | echo "🧹 Cleaning up disk space for multi-arch build..." echo "Before cleanup:" df -h # 移除不需要的預裝軟體 sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/local/lib/android sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/share/swift sudo rm -rf /opt/hostedtoolcache # 清理 apt cache sudo apt-get clean sudo apt-get autoremove -y # 清理 Docker 舊資料 docker system prune -af --volumes || true echo "" echo "After cleanup:" df -h - name: Get current date id: date run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image uses: docker/build-push-action@v6 with: context: . push: true tags: ${{ env.DOCKER_IMAGE }}:upstream-${{ steps.date.outputs.date }} platforms: linux/amd64,linux/arm64 cache-from: type=gha cache-to: type=gha,mode=max - name: Summary run: | echo "## 🐳 Docker Image Published" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Image:** \`${{ env.DOCKER_IMAGE }}:upstream-${{ steps.date.outputs.date }}\`" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### ✅ Smoke Test 已通過" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| 測試項目 | 狀態 |" >> $GITHUB_STEP_SUMMARY echo "|----------|------|" >> $GITHUB_STEP_SUMMARY echo "| Container 啟動 | ✅ |" >> $GITHUB_STEP_SUMMARY echo "| 工具存在性檢查 | ✅ |" >> $GITHUB_STEP_SUMMARY echo "| Headless 環境驗證 | ✅ |" >> $GITHUB_STEP_SUMMARY echo "| 模型下載驗證 | ✅ |" >> $GITHUB_STEP_SUMMARY echo "| 最小轉換測試 | ✅ |" >> $GITHUB_STEP_SUMMARY echo "| API 端點檢查 | ✅ |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "---" >> $GITHUB_STEP_SUMMARY echo "⚠️ 這是 upstream 同步版本。請使用 version tags 取得穩定版本。" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "❌ **不會更新 \`latest\` tag**" >> $GITHUB_STEP_SUMMARY