返回 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 timeout-minutes: 10
43 runs-on: ubuntu-latest
44 outputs:
45 tag: ${{ steps.release.outputs.tag }}
46 sha: ${{ steps.release.outputs.sha }}
47 version: ${{ steps.release.outputs.version }}
48 steps:
49 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
50 with:
51 fetch-depth: 0
52 - name: Resolve released tag
53 id: release
54 shell: bash
55 env:
56 INPUT_VERSION: ${{ inputs.version }}
57 run: |
58 set -euo pipefail
59
60 if ! [[ "${INPUT_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
61 echo "::error::Version '${INPUT_VERSION}' must use X.Y.Z." >&2
62 exit 1
63 fi
64 tag="v${INPUT_VERSION}"
65
66 if ! git rev-parse --verify "refs/tags/${tag}^{commit}" >/dev/null 2>&1; then
67 echo "::error::Tag ${tag} does not exist." >&2
68 exit 1
69 fi
70 sha="$(git rev-parse "refs/tags/${tag}^{commit}")"
71
72 {
73 echo "tag=${tag}"
74 echo "sha=${sha}"
75 echo "version=${INPUT_VERSION}"
76 } >> "${GITHUB_OUTPUT}"
77 echo "Resolved ${tag} -> ${sha}"
78 - name: Require an existing published release
79 # The inverse of release.yml's immutability guard. That one refuses to
80 # run when assets exist; this one refuses to run when they do not,
81 # because there is nothing to derive a channel from.
82 env:
83 GH_TOKEN: ${{ github.token }}
84 TAG: ${{ steps.release.outputs.tag }}
85 run: |
86 set -euo pipefail
87 count="$(gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" --json assets --jq '.assets | length')"
88 if [[ "${count}" -eq 0 ]]; then
89 echo "::error::${TAG} has no published assets. Run release.yml, not this recovery workflow." >&2
90 exit 1
91 fi
92 echo "${TAG} has ${count} published assets."
93 - name: Verify the remote tag still points at this commit
94 env:
95 EXPECTED_SHA: ${{ steps.release.outputs.sha }}
96 TAG: ${{ steps.release.outputs.tag }}
97 run: |
98 ./scripts/release/verify-remote-tag.sh \
99 "https://github.com/${GITHUB_REPOSITORY}.git" \
100 "${TAG}" \
101 "${EXPECTED_SHA}"
102
103 docker:
104 timeout-minutes: 30
105 needs: resolve
106 if: ${{ contains(inputs.channels, 'docker') }}
107 runs-on: ubuntu-latest
108 permissions:
109 contents: read
110 packages: write
111 steps:
112 - name: Checkout release source
113 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
114 with:
115 ref: ${{ needs.resolve.outputs.sha }}
116 path: source
117 - name: Checkout release infrastructure
118 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
119 with:
120 ref: ${{ needs.resolve.outputs.sha }}
121 path: infra
122 - name: Set up QEMU
123 uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4
124 - name: Set up Docker Buildx
125 uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
126 - name: Log in to GitHub Container Registry
127 uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
128 with:
129 registry: ghcr.io
130 username: ${{ github.repository_owner }}
131 password: ${{ secrets.GITHUB_TOKEN }}
132 - name: Normalize image name
133 id: image
134 shell: bash
135 run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
136 - name: Revalidate release tag before container publish
137 env:
138 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
139 TAG: ${{ needs.resolve.outputs.tag }}
140 run: |
141 ./infra/scripts/release/verify-remote-tag.sh \
142 "https://github.com/${GITHUB_REPOSITORY}.git" \
143 "${TAG}" \
144 "${EXPECTED_SHA}"
145 - name: Build and push
146 uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
147 env:
148 DOCKER_BUILD_RECORD_UPLOAD: false
149 DOCKER_BUILD_SUMMARY: false
150 with:
151 context: source
152 file: infra/Dockerfile
153 platforms: linux/amd64,linux/arm64
154 push: true
155 build-args: |
156 CODEWHALE_BUILD_SHA=${{ needs.resolve.outputs.sha }}
157 tags: |
158 ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
159 ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.version }}
160 ${{ steps.image.outputs.name }}:latest
161 cache-from: type=gha
162 cache-to: type=gha,mode=max
163 - name: Smoke published container entrypoints
164 shell: bash
165 env:
166 IMAGE: ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
167 run: |
168 set -euo pipefail
169 docker pull "${IMAGE}"
170 docker run --rm --entrypoint codewhale "${IMAGE}" --version
171 docker run --rm --entrypoint codew "${IMAGE}" --version
172
173 homebrew:
174 timeout-minutes: 20
175 needs: resolve
176 if: ${{ contains(inputs.channels, 'homebrew') }}
177 runs-on: ubuntu-latest
178 permissions:
179 contents: read
180 steps:
181 - name: Check Homebrew tap token
182 id: homebrew-token
183 env:
184 TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
185 run: |
186 if [[ -z "${TOKEN:-}" ]]; then
187 echo "::error::No Homebrew tap token configured; cannot republish the tap." >&2
188 exit 1
189 fi
190 # Recovery logic must come from the current protected default branch.
191 # The released bytes remain pinned by the tag and checksum manifest;
192 # checking out the old tag here would also restore the bug being repaired.
193 - name: Checkout release infrastructure
194 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
195 with:
196 ref: ${{ github.event.repository.default_branch }}
197 - name: Download checksum manifest
198 env:
199 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
200 run: |
201 gh release download "${{ needs.resolve.outputs.tag }}" \
202 --repo "${{ github.repository }}" \
203 --pattern 'codewhale-artifacts-sha256.txt' \
204 --dir /tmp
205 - name: Revalidate release tag before Homebrew tap write
206 env:
207 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
208 TAG: ${{ needs.resolve.outputs.tag }}
209 run: |
210 ./scripts/release/verify-remote-tag.sh \
211 "https://github.com/${GITHUB_REPOSITORY}.git" \
212 "${TAG}" \
213 "${EXPECTED_SHA}"
214 - name: Update Homebrew tap
215 env:
216 TAG: ${{ needs.resolve.outputs.tag }}
217 MANIFEST: /tmp/codewhale-artifacts-sha256.txt
218 TAP_REPO: Hmbown/homebrew-deepseek-tui
219 TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
220 run: bash .github/scripts/update-homebrew-tap.sh
221
221 lines YAML