# ============================================================ # Docker Build & Push(Remote Host via Tailscale SSH) # ============================================================ # 說明:使用 Tailscale SSH 連線到私有主機執行 Docker build # 重要:此 workflow 完全不使用傳統 SSH key,僅使用 Tailscale SSH # ============================================================ name: Docker Build & Push (Remote Host via Tailscale) on: # 手動觸發,支援輸入 image tag workflow_dispatch: inputs: image_tag: description: "Docker Image Tag(例如:0.1.10.test)" required: true default: "0.1.10.test" type: string # ============================================================ # 環境變數設定 # ============================================================ env: # 遠端主機工作目錄 REMOTE_WORKDIR: /home/bioailab/miniconda3/lid/app/docker_build # 專案名稱 PROJECT_NAME: ConvertX-CN # Docker Hub 映像庫 DOCKER_IMAGE_REPO: convertx/convertx-cn jobs: build-and-push: name: 🚀 遠端 Docker Build & Push runs-on: ubuntu-latest steps: # ======================================== # 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 key,runner 結束後自動離開 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 連線(非互動式) # 說明:使用 tailscale ssh 測試連線,確保不需要人工登入 # 重要:需要在 Tailscale ACL 中設定 SSH policy # ======================================== - name: 🔐 測試 Tailscale SSH 連線(非互動式) run: | echo "========================================" echo "🔐 測試 Tailscale SSH 連線(非互動式)" echo "========================================" echo "使用者: ${{ secrets.SSH_USER }}" echo "主機: ${{ secrets.SSH_HOST }}" echo "" # 設定超時時間,避免卡住 echo "⏳ 嘗試 SSH 連線(超時 30 秒)..." # tailscale ssh 不支援 -o 選項,直接使用 # 使用 timeout 命令確保不會卡住 RESULT=$(timeout 30 tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \ "echo '✅ Tailscale SSH 連線成功!' && echo '主機名稱:' && hostname && echo '當前使用者:' && whoami" 2>&1) || true echo "${RESULT}" # 檢查是否成功 if echo "${RESULT}" | grep -q "Tailscale SSH 連線成功"; then echo "" echo "✅ SSH 非互動式測試成功!" elif echo "${RESULT}" | grep -qi "authenticate\|login.tailscale.com\|additional check"; then echo "" echo "========================================" echo "❌ 錯誤:Tailscale SSH 需要設定 ACL Policy!" echo "========================================" echo "" echo "🔧 這不是 Auth Key 的問題,而是 Tailscale SSH ACL 設定問題" echo "" echo "📋 請在 Tailscale Admin Console 中設定 SSH Policy:" echo "" echo " 1. 前往 https://login.tailscale.com/admin/acls" echo "" echo " 2. 在 ACL 中加入以下 SSH policy(放在 acls 之後):" echo "" echo ' "ssh": [' echo ' {' echo ' "action": "accept",' echo ' "src": ["autogroup:member"],' echo ' "dst": ["tag:ci-target"],' echo ' "users": ["autogroup:nonroot"]' echo ' }' echo ' ]' echo "" echo " 3. 在遠端主機上執行:" echo " sudo tailscale up --ssh --advertise-tags=tag:ci-target" echo "" echo " 4. 確保 Auth Key 有 tag:ci-target 的權限" echo "" echo "========================================" echo "🔄 或者:使用傳統 SSH over Tailscale 網路(備選方案)" echo "========================================" exit 1 else echo "" echo "⚠️ 連線失敗,錯誤訊息:" echo "${RESULT}" exit 1 fi 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 "📋 測試項目 2:Git 版本" echo "----------------------------------------" git --version echo "✅ Git 可用" echo "" echo "📋 測試項目 3:Docker 版本" echo "----------------------------------------" docker version --format '{{.Server.Version}}' || docker --version echo "✅ Docker 可用" echo "" echo "📋 測試項目 4:Docker 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 Buildx(Multi-Arch 必要) # 說明:建立支援多架構的 buildx builder # ======================================== - name: 🔧 設定 Docker Buildx run: | echo "========================================" echo "🔧 設定 Docker Buildx(Multi-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 & Push # 說明:使用 buildx 同時建構 amd64 和 arm64 並推送 # ======================================== - name: 🔨 Multi-Arch Docker Build & Push run: | echo "========================================" echo "🔨 開始 Multi-Arch Docker Build & Push" echo "========================================" echo "映像名稱: ${{ env.DOCKER_IMAGE_REPO }}:${{ github.event.inputs.image_tag }}" 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 }}:${{ github.event.inputs.image_tag }}" BUILDER_NAME="multiarch-builder-${{ github.run_id }}" echo "📂 進入專案目錄..." cd "${WORKDIR}/${PROJECT}" pwd echo "" echo "🔨 執行 Multi-Arch Docker buildx..." echo "映像標籤: ${IMAGE}" echo "目標平台: linux/amd64, linux/arm64" echo "" # 確保使用正確的 builder docker buildx use "${BUILDER_NAME}" # Multi-arch build + push docker buildx build \ --platform linux/amd64,linux/arm64 \ -t "${IMAGE}" \ --push \ --progress=plain \ . echo "" echo "✅ Multi-Arch Docker build & push 完成!" echo "" echo "📦 映像已推送至 Docker Hub:" echo " ${IMAGE}" echo "" echo "🏗️ 支援的架構:" echo " - linux/amd64" echo " - linux/arm64" REMOTE_EOF echo "========================================" # ======================================== # Step 11: 驗證 Docker Hub 映像 # 說明:確認映像已成功推送並檢查架構 # ======================================== - name: ✅ 驗證 Docker Hub 映像 run: | echo "========================================" echo "✅ 驗證 Docker Hub 映像" echo "========================================" echo "映像: ${{ env.DOCKER_IMAGE_REPO }}:${{ github.event.inputs.image_tag }}" echo "" tailscale ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'REMOTE_EOF' set -e IMAGE="${{ env.DOCKER_IMAGE_REPO }}:${{ github.event.inputs.image_tag }}" echo "📋 檢查映像 manifest(顯示支援的架構)..." echo "" docker buildx imagetools inspect "${IMAGE}" echo "" echo "✅ 映像驗證完成" REMOTE_EOF echo "========================================" # ======================================== # Step 12: 清理本次 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 13: 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 14: 顯示完成摘要 # 說明:顯示本次 build 的摘要資訊 # ======================================== - name: 📋 Build 完成摘要 run: | echo "========================================" echo "✅ Multi-Arch Docker Build & Push 完成!" echo "========================================" echo "" echo "📦 映像資訊:" echo " - 映像名稱: ${{ env.DOCKER_IMAGE_REPO }}:${{ github.event.inputs.image_tag }}" 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 "" echo "📝 使用方式:" echo " docker pull ${{ env.DOCKER_IMAGE_REPO }}:${{ github.event.inputs.image_tag }}" echo "" echo "========================================" echo "🎉 所有步驟已成功完成!" echo "========================================"