返回 CodeWhale
release-republish.yml
根目录 / .github / workflows / release-republish.yml
1 name: Release Republish Channels
2
3 # Recovery for derived distribution channels — the container image and the
4 # Homebrew tap — when a released tag has them missing or stale.
5 #
6 # This is a separate workflow, dispatched from the DEFAULT BRANCH, on purpose.
7 # `workflow_dispatch` reads its input schema from the workflow file at the ref
8 # being dispatched, and `release.yml` requires being dispatched from the tag
9 # itself. Any recovery option added to `release.yml` is therefore unusable on
10 # tags that predate it — which is every tag that could ever need recovery.
11 # Taking the tag as an input sidesteps that entirely.
12 #
13 # It never writes GitHub Release bytes. `release.yml` owns those, and they stay
14 # immutable; this only (re)publishes channels derived from an existing release.
15
16 on:
17 workflow_dispatch:
18 inputs:
19 version:
20 description: 'Released version to republish, without v (e.g. 0.9.1). The tag must already exist and have a published GitHub Release.'
21 required: true
22 type: string
23 channels:
24 description: 'Which derived channels to republish.'
25 required: true
26 default: 'docker+homebrew'
27 type: choice
28 options:
29 - docker+homebrew
30 - docker
31 - homebrew
32
33 concurrency:
34 group: release-republish-${{ inputs.version }}
35 cancel-in-progress: false
36
37 permissions:
38 contents: read
39
40 jobs:
41 resolve:
42 runs-on: ubuntu-latest
43 outputs:
44 tag: ${{ steps.release.outputs.tag }}
45 sha: ${{ steps.release.outputs.sha }}
46 version: ${{ steps.release.outputs.version }}
47 steps:
48 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
49 with:
50 fetch-depth: 0
51 - name: Resolve released tag
52 id: release
53 shell: bash
54 env:
55 INPUT_VERSION: ${{ inputs.version }}
56 run: |
57 set -euo pipefail
58
59 if ! [[ "${INPUT_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
60 echo "::error::Version '${INPUT_VERSION}' must use X.Y.Z." >&2
61 exit 1
62 fi
63 tag="v${INPUT_VERSION}"
64
65 if ! git rev-parse --verify "refs/tags/${tag}^{commit}" >/dev/null 2>&1; then
66 echo "::error::Tag ${tag} does not exist." >&2
67 exit 1
68 fi
69 sha="$(git rev-parse "refs/tags/${tag}^{commit}")"
70
71 {
72 echo "tag=${tag}"
73 echo "sha=${sha}"
74 echo "version=${INPUT_VERSION}"
75 } >> "${GITHUB_OUTPUT}"
76 echo "Resolved ${tag} -> ${sha}"
77 - name: Require an existing published release
78 # The inverse of release.yml's immutability guard. That one refuses to
79 # run when assets exist; this one refuses to run when they do not,
80 # because there is nothing to derive a channel from.
81 env:
82 GH_TOKEN: ${{ github.token }}
83 TAG: ${{ steps.release.outputs.tag }}
84 run: |
85 set -euo pipefail
86 count="$(gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" --json assets --jq '.assets | length')"
87 if [[ "${count}" -eq 0 ]]; then
88 echo "::error::${TAG} has no published assets. Run release.yml, not this recovery workflow." >&2
89 exit 1
90 fi
91 echo "${TAG} has ${count} published assets."
92 - name: Verify the remote tag still points at this commit
93 env:
94 EXPECTED_SHA: ${{ steps.release.outputs.sha }}
95 TAG: ${{ steps.release.outputs.tag }}
96 run: |
97 ./scripts/release/verify-remote-tag.sh \
98 "https://github.com/${GITHUB_REPOSITORY}.git" \
99 "${TAG}" \
100 "${EXPECTED_SHA}"
101
102 docker:
103 needs: resolve
104 if: ${{ contains(inputs.channels, 'docker') }}
105 runs-on: ubuntu-latest
106 permissions:
107 contents: read
108 packages: write
109 steps:
110 - name: Checkout release source
111 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
112 with:
113 ref: ${{ needs.resolve.outputs.sha }}
114 path: source
115 - name: Checkout release infrastructure
116 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
117 with:
118 ref: ${{ needs.resolve.outputs.sha }}
119 path: infra
120 - name: Set up QEMU
121 uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
122 - name: Set up Docker Buildx
123 uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
124 - name: Log in to GitHub Container Registry
125 uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4
126 with:
127 registry: ghcr.io
128 username: ${{ github.repository_owner }}
129 password: ${{ secrets.GITHUB_TOKEN }}
130 - name: Normalize image name
131 id: image
132 shell: bash
133 run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
134 - name: Revalidate release tag before container publish
135 env:
136 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
137 TAG: ${{ needs.resolve.outputs.tag }}
138 run: |
139 ./infra/scripts/release/verify-remote-tag.sh \
140 "https://github.com/${GITHUB_REPOSITORY}.git" \
141 "${TAG}" \
142 "${EXPECTED_SHA}"
143 - name: Build and push
144 uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
145 env:
146 DOCKER_BUILD_RECORD_UPLOAD: false
147 DOCKER_BUILD_SUMMARY: false
148 with:
149 context: source
150 file: infra/Dockerfile
151 platforms: linux/amd64,linux/arm64
152 push: true
153 build-args: |
154 DEEPSEEK_BUILD_SHA=${{ needs.resolve.outputs.sha }}
155 tags: |
156 ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
157 ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.version }}
158 ${{ steps.image.outputs.name }}:latest
159 cache-from: type=gha
160 cache-to: type=gha,mode=max
161 - name: Smoke published container entrypoints
162 shell: bash
163 env:
164 IMAGE: ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
165 run: |
166 set -euo pipefail
167 docker pull "${IMAGE}"
168 docker run --rm --entrypoint codewhale "${IMAGE}" --version
169 docker run --rm --entrypoint codew "${IMAGE}" --version
170 docker run --rm --entrypoint codewhale-tui "${IMAGE}" --version
171
172 homebrew:
173 needs: resolve
174 if: ${{ contains(inputs.channels, 'homebrew') }}
175 runs-on: ubuntu-latest
176 permissions:
177 contents: read
178 steps:
179 - name: Check Homebrew tap token
180 id: homebrew-token
181 env:
182 TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
183 run: |
184 if [[ -z "${TOKEN:-}" ]]; then
185 echo "::error::No Homebrew tap token configured; cannot republish the tap." >&2
186 exit 1
187 fi
188 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
189 with:
190 ref: ${{ needs.resolve.outputs.sha }}
191 - name: Download checksum manifest
192 env:
193 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
194 run: |
195 gh release download "${{ needs.resolve.outputs.tag }}" \
196 --repo "${{ github.repository }}" \
197 --pattern 'codewhale-artifacts-sha256.txt' \
198 --dir /tmp
199 - name: Revalidate release tag before Homebrew tap write
200 env:
201 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
202 TAG: ${{ needs.resolve.outputs.tag }}
203 run: |
204 ./scripts/release/verify-remote-tag.sh \
205 "https://github.com/${GITHUB_REPOSITORY}.git" \
206 "${TAG}" \
207 "${EXPECTED_SHA}"
208 - name: Update Homebrew tap
209 env:
210 TAG: ${{ needs.resolve.outputs.tag }}
211 MANIFEST: /tmp/codewhale-artifacts-sha256.txt
212 TAP_REPO: Hmbown/homebrew-deepseek-tui
213 TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
214 run: bash .github/scripts/update-homebrew-tap.sh
215
215 lines YAML