diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index 7298903..0c3d26b 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -60,13 +60,66 @@ on: type: boolean default: false -# 新 push 立即取消舊 job,只部署最新版本 +# Gitea 1.25 不可靠執行 workflow concurrency;root selector 會在 deploy +# 前以 main tip 做 coalescing。不要中途取消 rsync / migration。 concurrency: group: cd-deploy-${{ gitea.ref }} - cancel-in-progress: true + cancel-in-progress: false jobs: + select-latest-carrier: + runs-on: ewoooc-host + timeout-minutes: 3 + outputs: + selected: ${{ steps.carrier.outputs.selected }} + source_sha: ${{ steps.carrier.outputs.source_sha }} + latest_sha: ${{ steps.carrier.outputs.latest_sha }} + steps: + - name: Select latest main carrier + id: carrier + env: + GITEA_TOKEN: ${{ gitea.token }} + GITEA_SERVER_URL: ${{ gitea.server_url }} + GITEA_REPOSITORY: ${{ gitea.repository }} + GITEA_SHA: ${{ gitea.sha }} + run: | + set -euo pipefail + : "${GITEA_TOKEN:?missing Gitea token}" + : "${GITEA_SERVER_URL:?missing Gitea server URL}" + : "${GITEA_REPOSITORY:?missing Gitea repository}" + [[ "${GITEA_SHA}" =~ ^[0-9a-f]{40}$ ]] || { + echo "BLOCKER ewoooc_cd_source_sha_invalid" + exit 1 + } + sleep "${EWOOOC_CD_CARRIER_DEBOUNCE_SECONDS:-15}" + source_url="${GITEA_SERVER_URL%/}/${GITEA_REPOSITORY}.git" + auth_header="Basic $(printf 'x-access-token:%s' "${GITEA_TOKEN}" | base64 | tr -d '\n')" + latest_sha="$( + GIT_CONFIG_COUNT=1 \ + GIT_CONFIG_KEY_0=http.extraHeader \ + GIT_CONFIG_VALUE_0="Authorization: ${auth_header}" \ + git \ + ls-remote --heads "${source_url}" refs/heads/main | awk 'NR == 1 {print $1}' + )" + unset auth_header GITEA_TOKEN + [[ "${latest_sha}" =~ ^[0-9a-f]{40}$ ]] || { + echo "BLOCKER ewoooc_cd_main_tip_unresolved" + exit 1 + } + selected=false + status=superseded_by_latest_main_carrier + if [ "${GITEA_SHA}" = "${latest_sha}" ]; then + selected=true + status=selected_latest_main_carrier + fi + echo "selected=${selected}" >> "${GITHUB_OUTPUT}" + echo "source_sha=${GITEA_SHA}" >> "${GITHUB_OUTPUT}" + echo "latest_sha=${latest_sha}" >> "${GITHUB_OUTPUT}" + echo "EWOOOC_CD_CARRIER status=${status} source=${GITEA_SHA} latest=${latest_sha}" + deploy: + needs: [select-latest-carrier] + if: ${{ needs.select-latest-carrier.outputs.selected == 'true' }} timeout-minutes: 20 runs-on: ewoooc-host @@ -186,7 +239,16 @@ jobs: # rsync --inplace 避免 app.py/config.py 單檔 bind mount 因 inode replacement 卡住舊版本 - name: 安裝 rsync / ssh run: | - apt-get update -qq && apt-get install -y -qq rsync openssh-client + missing=() + command -v rsync >/dev/null 2>&1 || missing+=(rsync) + command -v ssh >/dev/null 2>&1 || missing+=(openssh-client) + if [ "${#missing[@]}" -eq 0 ]; then + echo "ewoooc_deploy_tools_ready=1 install_performed=0" + else + apt-get update -qq + apt-get install -y -qq "${missing[@]}" + echo "ewoooc_deploy_tools_ready=1 install_performed=1" + fi - name: AI 觀測台 Pre-deploy 靜態 QA if: steps.observability_qa.outputs.needed == 'true'