Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 01:00 UTC
| 1 | name: Release |
| 2 | |
| 3 | on: |
| 4 | push: |
| 5 | tags: |
| 6 | - 'v*.*.*' |
| 7 | |
| 8 | env: |
| 9 | # https://github.com/actions/setup-go/issues/491 |
| 10 | GOTOOLCHAIN: local |
| 11 | |
| 12 | permissions: |
| 13 | contents: read |
| 14 | |
| 15 | jobs: |
| 16 | verify-tag: |
| 17 | name: Verify Tag Signature and Approvals |
| 18 | runs-on: ubuntu-latest |
| 19 | permissions: |
| 20 | contents: write |
| 21 | pull-requests: write |
| 22 | issues: write |
| 23 | |
| 24 | outputs: |
| 25 | verification_passed: ${{ steps.verify.outputs.passed }} |
| 26 | tag_version: ${{ steps.info.outputs.version }} |
| 27 | proposal_issue_number: ${{ steps.find_proposal.outputs.result && fromJson(steps.find_proposal.outputs.result).number || '' }} |
| 28 | |
| 29 | # Values from the event, step outputs and API results are only ever |
| 30 | # passed through env:, never interpolated as expressions into run: or |
| 31 | # script: bodies. |
| 32 | steps: |
| 33 | # Validate the tag name before anything else uses it. Git allows |
| 34 | # characters like $, ( and ; in tag names that match the v*.*.* trigger, |
| 35 | # and the tag name ends up in shell commands (including GoReleaser hooks). |
| 36 | - name: Validate tag name |
| 37 | run: | |
| 38 | if [[ ! "$GITHUB_REF" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then |
| 39 | echo "❌ Refusing to release $GITHUB_REF: not a vMAJOR.MINOR.PATCH[-PRERELEASE] tag" |
| 40 | exit 1 |
| 41 | fi |
| 42 | |
| 43 | - name: Checkout code |
| 44 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| 45 | with: |
| 46 | fetch-depth: 0 |
| 47 | # Force fetch upstream tags -- because 65 minutes |
| 48 | # tl;dr: actions/checkout@v3 runs this line: |
| 49 | # git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/ |
| 50 | # which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran: |
| 51 | # git fetch --prune --unshallow |
| 52 | # which doesn't overwrite that tag because that would be destructive. |
| 53 | # Credit to @francislavoie for the investigation. |
| 54 | # https://github.com/actions/checkout/issues/290#issuecomment-680260080 |
| 55 | - name: Force fetch upstream tags |
| 56 | run: git fetch --tags --force |
| 57 | |
| 58 | - name: Get tag info |
| 59 | id: info |
| 60 | run: | |
| 61 | echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" |
| 62 | echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" |
| 63 | |
| 64 | # https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027 |
| 65 | - name: Print Go version and environment |
| 66 | id: vars |
| 67 | run: | |
| 68 | printf "Using go at: $(which go)\n" |
| 69 | printf "Go version: $(go version)\n" |
| 70 | printf "\n\nGo environment:\n\n" |
| 71 | go env |
| 72 | printf "\n\nSystem environment:\n\n" |
| 73 | env |
| 74 | echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT |
| 75 | echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT |
| 76 | |
| 77 | # Add "pip install" CLI tools to PATH |
| 78 | echo ~/.local/bin >> $GITHUB_PATH |
| 79 | |
| 80 | # Parse semver |
| 81 | TAG=${GITHUB_REF/refs\/tags\//} |
| 82 | SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)' |
| 83 | TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"` |
| 84 | TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"` |
| 85 | TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"` |
| 86 | TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"` |
| 87 | echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT |
| 88 | echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT |
| 89 | echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT |
| 90 | echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT |
| 91 | |
| 92 | - name: Validate commits and tag signatures |
| 93 | id: verify |
| 94 | env: |
| 95 | signing_keys: ${{ secrets.SIGNING_KEYS }} |
| 96 | TAG: ${{ steps.vars.outputs.version_tag }} |
| 97 | run: | |
| 98 | # Read the string into an array, splitting by IFS |
| 99 | IFS=";" read -ra keys_collection <<< "$signing_keys" |
| 100 | |
| 101 | # ref: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#example-usage-of-the-runner-context |
| 102 | touch "$RUNNER_TEMP/allowed_signers" |
| 103 | |
| 104 | # Iterate and print the split elements |
| 105 | for item in "${keys_collection[@]}"; do |
| 106 | |
| 107 | # trim leading whitespaces |
| 108 | item="${item##*( )}" |
| 109 | |
| 110 | # trim trailing whitespaces |
| 111 | item="${item%%*( )}" |
| 112 | |
| 113 | IFS=" " read -ra key_components <<< "$item" |
| 114 | # git wants it in format: email address, type, public key |
| 115 | # ssh has it in format: type, public key, email address |
| 116 | echo "${key_components[2]} namespaces=\"git\" ${key_components[0]} ${key_components[1]}" >> "$RUNNER_TEMP/allowed_signers" |
| 117 | done |
| 118 | |
| 119 | git config set --global gpg.ssh.allowedSignersFile "$RUNNER_TEMP/allowed_signers" |
| 120 | |
| 121 | echo "Verifying the tag: $TAG" |
| 122 | |
| 123 | # Verify the tag is signed |
| 124 | if ! git verify-tag -v "$TAG" 2>&1; then |
| 125 | echo "❌ Tag verification failed!" |
| 126 | echo "passed=false" >> $GITHUB_OUTPUT |
| 127 | git push --delete origin "$TAG" |
| 128 | exit 1 |
| 129 | fi |
| 130 | # Run it again to capture the output |
| 131 | git verify-tag -v "$TAG" 2>&1 | tee /tmp/verify-output.txt; |
| 132 | |
| 133 | # SSH verification output typically includes the key fingerprint |
| 134 | # Use GNU grep with Perl regex for cleaner extraction (Linux environment) |
| 135 | KEY_SHA256=$(grep -oP "SHA256:[\"']?\K[A-Za-z0-9+/=]+(?=[\"']?)" /tmp/verify-output.txt | head -1 || echo "") |
| 136 | |
| 137 | if [ -z "$KEY_SHA256" ]; then |
| 138 | # Try alternative pattern with "key" prefix |
| 139 | KEY_SHA256=$(grep -oP "key SHA256:[\"']?\K[A-Za-z0-9+/=]+(?=[\"']?)" /tmp/verify-output.txt | head -1 || echo "") |
| 140 | fi |
| 141 | |
| 142 | if [ -z "$KEY_SHA256" ]; then |
| 143 | # Fallback: extract any base64-like string (40+ chars) |
| 144 | KEY_SHA256=$(grep -oP '[A-Za-z0-9+/]{40,}=?' /tmp/verify-output.txt | head -1 || echo "") |
| 145 | fi |
| 146 | |
| 147 | if [ -z "$KEY_SHA256" ]; then |
| 148 | echo "Somehow could not extract SSH key fingerprint from git verify-tag output" |
| 149 | echo "Cancelling flow and deleting tag" |
| 150 | echo "passed=false" >> $GITHUB_OUTPUT |
| 151 | git push --delete origin "$TAG" |
| 152 | exit 1 |
| 153 | fi |
| 154 | |
| 155 | echo "✅ Tag verification succeeded!" |
| 156 | echo "passed=true" >> $GITHUB_OUTPUT |
| 157 | echo "key_id=$KEY_SHA256" >> $GITHUB_OUTPUT |
| 158 | |
| 159 | # Fails closed: there must be exactly one open proposal for this exact |
| 160 | # version, with enough maintainer approvals of its current head commit. |
| 161 | # The tag is left in place on failure, so the job can be re-run once |
| 162 | # the proposal is approved. |
| 163 | - name: Find related release proposal |
| 164 | id: find_proposal |
| 165 | uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| 166 | env: |
| 167 | TAG: ${{ steps.vars.outputs.version_tag }} |
| 168 | MAINTAINER_LOGINS: ${{ secrets.MAINTAINER_LOGINS }} |
| 169 | with: |
| 170 | script: | |
| 171 | const { checkProposal, REQUIRED_APPROVALS } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/release-proposal.js`); |
| 172 | |
| 173 | const { pr, targetCommit, approvers, approved } = await checkProposal({ |
| 174 | github, context, version: process.env.TAG, maintainerLogins: process.env.MAINTAINER_LOGINS, |
| 175 | }); |
| 176 | console.log(`Found proposal PR #${pr.number}, for commit ${targetCommit}`); |
| 177 | console.log(`Maintainer approvals of ${pr.head.sha}: ${approvers.join(', ') || 'none'}`); |
| 178 | |
| 179 | if (!approved) { |
| 180 | core.setFailed(`Proposal #${pr.number} has ${approvers.length} maintainer approvals of its current head commit; ${REQUIRED_APPROVALS} are required`); |
| 181 | return; |
| 182 | } |
| 183 | |
| 184 | return { |
| 185 | number: pr.number, |
| 186 | approvals: approvers.length, |
| 187 | approvers: approvers.map(login => `@${login}`).join(', '), |
| 188 | proposedCommit: targetCommit |
| 189 | }; |
| 190 | result-encoding: json |
| 191 | |
| 192 | - name: Verify proposal commit |
| 193 | env: |
| 194 | PROPOSED_COMMIT: ${{ fromJson(steps.find_proposal.outputs.result).proposedCommit }} |
| 195 | CURRENT_COMMIT: ${{ steps.info.outputs.sha }} |
| 196 | TAG: ${{ steps.vars.outputs.version_tag }} |
| 197 | run: | |
| 198 | echo "Proposed commit: $PROPOSED_COMMIT" |
| 199 | echo "Current commit: $CURRENT_COMMIT" |
| 200 | |
| 201 | # Both are full SHAs, so compare them exactly |
| 202 | if [[ ! "$PROPOSED_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then |
| 203 | echo "❌ Proposal has no valid target commit: $PROPOSED_COMMIT" |
| 204 | exit 1 |
| 205 | fi |
| 206 | if [ "$PROPOSED_COMMIT" != "$CURRENT_COMMIT" ]; then |
| 207 | echo "❌ Commit mismatch!" |
| 208 | echo "The tag points to commit $CURRENT_COMMIT but the proposal was for $PROPOSED_COMMIT" |
| 209 | echo "This indicates an error in tag creation." |
| 210 | # Delete the tag remotely |
| 211 | git push --delete origin "$TAG" |
| 212 | echo "Tag $TAG has been deleted" |
| 213 | exit 1 |
| 214 | fi |
| 215 | |
| 216 | echo "✅ Commit hash matches proposal" |
| 217 | |
| 218 | - name: Update release proposal PR |
| 219 | uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| 220 | env: |
| 221 | RESULT: ${{ steps.find_proposal.outputs.result }} |
| 222 | TAG: ${{ steps.info.outputs.version }} |
| 223 | KEY_ID: ${{ steps.verify.outputs.key_id }} |
| 224 | SHA: ${{ steps.info.outputs.sha }} |
| 225 | with: |
| 226 | script: | |
| 227 | const result = JSON.parse(process.env.RESULT); |
| 228 | |
| 229 | // Add in-progress label |
| 230 | await github.rest.issues.addLabels({ |
| 231 | owner: context.repo.owner, |
| 232 | repo: context.repo.repo, |
| 233 | issue_number: result.number, |
| 234 | labels: ['release-in-progress'] |
| 235 | }); |
| 236 | |
| 237 | // Remove approved label if present |
| 238 | try { |
| 239 | await github.rest.issues.removeLabel({ |
| 240 | owner: context.repo.owner, |
| 241 | repo: context.repo.repo, |
| 242 | issue_number: result.number, |
| 243 | name: 'approved' |
| 244 | }); |
| 245 | } catch (e) { |
| 246 | console.log('Approved label not found:', e.message); |
| 247 | } |
| 248 | |
| 249 | const commentBody = [ |
| 250 | '## 🚀 Release Workflow Started', |
| 251 | '', |
| 252 | `- **Tag:** ${process.env.TAG}`, |
| 253 | `- **Signed by key:** ${process.env.KEY_ID}`, |
| 254 | `- **Commit:** ${process.env.SHA}`, |
| 255 | `- **Approved by:** ${result.approvers}`, |
| 256 | '', |
| 257 | 'Release workflow is now running. This PR will be updated when the release is published.' |
| 258 | ].join('\n'); |
| 259 | |
| 260 | await github.rest.issues.createComment({ |
| 261 | owner: context.repo.owner, |
| 262 | repo: context.repo.repo, |
| 263 | issue_number: result.number, |
| 264 | body: commentBody |
| 265 | }); |
| 266 | |
| 267 | - name: Summary |
| 268 | env: |
| 269 | PROPOSED_COMMIT: ${{ fromJson(steps.find_proposal.outputs.result).proposedCommit }} |
| 270 | APPROVERS: ${{ fromJson(steps.find_proposal.outputs.result).approvers }} |
| 271 | TAG: ${{ steps.info.outputs.version }} |
| 272 | SHA: ${{ steps.info.outputs.sha }} |
| 273 | KEY_ID: ${{ steps.verify.outputs.key_id }} |
| 274 | run: | |
| 275 | { |
| 276 | echo "## Tag Verification Summary 🔐" |
| 277 | echo "" |
| 278 | echo "- **Tag:** $TAG" |
| 279 | echo "- **Commit:** $SHA" |
| 280 | echo "- **Proposed Commit:** $PROPOSED_COMMIT" |
| 281 | echo "- **Signature:** ✅ Verified" |
| 282 | echo "- **Signed by:** $KEY_ID" |
| 283 | echo "- **Approvals:** ✅ Sufficient" |
| 284 | echo "- **Approved by:** $APPROVERS" |
| 285 | echo "" |
| 286 | echo "Proceeding with release build..." |
| 287 | } >> "$GITHUB_STEP_SUMMARY" |
| 288 | |
| 289 | release: |
| 290 | name: Release |
| 291 | needs: verify-tag |
| 292 | if: ${{ needs.verify-tag.outputs.verification_passed == 'true' }} |
| 293 | strategy: |
| 294 | matrix: |
| 295 | os: |
| 296 | - ubuntu-latest |
| 297 | go: |
| 298 | - '1.27' |
| 299 | |
| 300 | include: |
| 301 | # Set the minimum Go patch version for the given Go minor |
| 302 | # Usable via ${{ matrix.GO_SEMVER }} |
| 303 | - go: '1.27' |
| 304 | GO_SEMVER: '~1.27.0' |
| 305 | |
| 306 | runs-on: ${{ matrix.os }} |
| 307 | # https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233 |
| 308 | # https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings |
| 309 | permissions: |
| 310 | id-token: write |
| 311 | # https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents |
| 312 | # "Releases" is part of `contents`, so it needs the `write` |
| 313 | contents: write |
| 314 | issues: write |
| 315 | pull-requests: write |
| 316 | |
| 317 | steps: |
| 318 | # Same check as in verify-tag: the tag name ends up in GoReleaser's |
| 319 | # shell hooks via $TAG. |
| 320 | - name: Validate tag name |
| 321 | run: | |
| 322 | if [[ ! "$GITHUB_REF" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then |
| 323 | echo "❌ Refusing to release $GITHUB_REF: not a vMAJOR.MINOR.PATCH[-PRERELEASE] tag" |
| 324 | exit 1 |
| 325 | fi |
| 326 | |
| 327 | - name: Harden the runner (Audit all outbound calls) |
| 328 | uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 |
| 329 | with: |
| 330 | egress-policy: audit |
| 331 | |
| 332 | - name: Checkout code |
| 333 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| 334 | with: |
| 335 | fetch-depth: 0 |
| 336 | |
| 337 | - name: Install Go |
| 338 | uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 |
| 339 | with: |
| 340 | go-version: ${{ matrix.GO_SEMVER }} |
| 341 | check-latest: true |
| 342 | |
| 343 | # Force fetch upstream tags -- because 65 minutes |
| 344 | # tl;dr: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v4.2.2 runs this line: |
| 345 | # git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/ |
| 346 | # which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran: |
| 347 | # git fetch --prune --unshallow |
| 348 | # which doesn't overwrite that tag because that would be destructive. |
| 349 | # Credit to @francislavoie for the investigation. |
| 350 | # https://github.com/actions/checkout/issues/290#issuecomment-680260080 |
| 351 | - name: Force fetch upstream tags |
| 352 | run: git fetch --tags --force |
| 353 | |
| 354 | # https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027 |
| 355 | - name: Print Go version and environment |
| 356 | id: vars |
| 357 | run: | |
| 358 | printf "Using go at: $(which go)\n" |
| 359 | printf "Go version: $(go version)\n" |
| 360 | printf "\n\nGo environment:\n\n" |
| 361 | go env |
| 362 | printf "\n\nSystem environment:\n\n" |
| 363 | env |
| 364 | echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT |
| 365 | echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT |
| 366 | |
| 367 | # Add "pip install" CLI tools to PATH |
| 368 | echo ~/.local/bin >> $GITHUB_PATH |
| 369 | |
| 370 | # Parse semver |
| 371 | TAG=${GITHUB_REF/refs\/tags\//} |
| 372 | SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)' |
| 373 | TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"` |
| 374 | TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"` |
| 375 | TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"` |
| 376 | TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"` |
| 377 | echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT |
| 378 | echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT |
| 379 | echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT |
| 380 | echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT |
| 381 | |
| 382 | # Cloudsmith CLI tooling for pushing releases |
| 383 | # See https://help.cloudsmith.io/docs/cli |
| 384 | - name: Install Cloudsmith CLI |
| 385 | run: pip install --upgrade cloudsmith-cli |
| 386 | |
| 387 | - name: Install Cosign |
| 388 | uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 |
| 389 | - name: Cosign version |
| 390 | run: cosign version |
| 391 | - name: Install Syft |
| 392 | uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # main |
| 393 | - name: Syft version |
| 394 | run: syft version |
| 395 | - name: Install xcaddy |
| 396 | run: | |
| 397 | go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest |
| 398 | xcaddy version |
| 399 | # Generate the draft's notes in the same format as GitHub's "Generate |
| 400 | # release notes" button, compared against the previous *published* |
| 401 | # release by version (not the previous tag, which may be an abandoned |
| 402 | # release attempt), and without PRs that already shipped in it as |
| 403 | # backports. GoReleaser's own changelog can do neither. |
| 404 | - name: Generate release notes |
| 405 | env: |
| 406 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 407 | TAG: ${{ steps.vars.outputs.version_tag }} |
| 408 | NOTES_FILE: ${{ runner.temp }}/release-notes.md |
| 409 | run: | |
| 410 | PREVIOUS_RELEASE=$(.github/scripts/previous-release.sh "$TAG") |
| 411 | echo "Previous published release: ${PREVIOUS_RELEASE:-none}" |
| 412 | .github/scripts/release-notes.sh "$TAG" "$TAG" "$PREVIOUS_RELEASE" > "$NOTES_FILE" |
| 413 | cat "$NOTES_FILE" |
| 414 | |
| 415 | # GoReleaser will take care of publishing those artifacts into the release |
| 416 | - name: Run GoReleaser |
| 417 | uses: goreleaser/goreleaser-action@a4f02ab5355a589de60d917a659ce617ed19b3de # v7.3.0 |
| 418 | with: |
| 419 | version: latest |
| 420 | args: release --clean --timeout 60m --release-notes ${{ runner.temp }}/release-notes.md |
| 421 | env: |
| 422 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 423 | TAG: ${{ steps.vars.outputs.version_tag }} |
| 424 | |
| 425 | # Only publish on non-special tags (e.g. non-beta) |
| 426 | # We will continue to push to Gemfury for the foreseeable future, although |
| 427 | # Cloudsmith is probably better, to not break things for existing users of Gemfury. |
| 428 | # See https://gemfury.com/caddy/deb:caddy |
| 429 | - name: Publish .deb to Gemfury |
| 430 | if: ${{ steps.vars.outputs.tag_special == '' }} |
| 431 | env: |
| 432 | GEMFURY_PUSH_TOKEN: ${{ secrets.GEMFURY_PUSH_TOKEN }} |
| 433 | run: | |
| 434 | for filename in dist/*.deb; do |
| 435 | # armv6 and armv7 are both "armhf" so we can skip the duplicate |
| 436 | if [[ "$filename" == *"armv6"* ]]; then |
| 437 | echo "Skipping $filename" |
| 438 | continue |
| 439 | fi |
| 440 | |
| 441 | curl -F package=@"$filename" https://${GEMFURY_PUSH_TOKEN}:@push.fury.io/caddy/ |
| 442 | done |
| 443 | |
| 444 | # Publish only special tags (unstable/beta/rc) to the "testing" repo |
| 445 | # See https://cloudsmith.io/~caddy/repos/testing/ |
| 446 | - name: Publish .deb to Cloudsmith (special tags) |
| 447 | if: ${{ steps.vars.outputs.tag_special != '' }} |
| 448 | env: |
| 449 | CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} |
| 450 | run: | |
| 451 | for filename in dist/*.deb; do |
| 452 | # armv6 and armv7 are both "armhf" so we can skip the duplicate |
| 453 | if [[ "$filename" == *"armv6"* ]]; then |
| 454 | echo "Skipping $filename" |
| 455 | continue |
| 456 | fi |
| 457 | |
| 458 | echo "Pushing $filename to 'testing'" |
| 459 | cloudsmith push deb caddy/testing/any-distro/any-version $filename |
| 460 | done |
| 461 | |
| 462 | # Publish stable tags to Cloudsmith to both repos, "stable" and "testing" |
| 463 | # See https://cloudsmith.io/~caddy/repos/stable/ |
| 464 | - name: Publish .deb to Cloudsmith (stable tags) |
| 465 | if: ${{ steps.vars.outputs.tag_special == '' }} |
| 466 | env: |
| 467 | CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} |
| 468 | run: | |
| 469 | for filename in dist/*.deb; do |
| 470 | # armv6 and armv7 are both "armhf" so we can skip the duplicate |
| 471 | if [[ "$filename" == *"armv6"* ]]; then |
| 472 | echo "Skipping $filename" |
| 473 | continue |
| 474 | fi |
| 475 | |
| 476 | echo "Pushing $filename to 'stable'" |
| 477 | cloudsmith push deb caddy/stable/any-distro/any-version $filename |
| 478 | |
| 479 | echo "Pushing $filename to 'testing'" |
| 480 | cloudsmith push deb caddy/testing/any-distro/any-version $filename |
| 481 | done |
| 482 | |
| 483 | - name: Update release proposal PR |
| 484 | if: needs.verify-tag.outputs.proposal_issue_number != '' |
| 485 | uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 |
| 486 | env: |
| 487 | PROPOSAL_NUMBER: ${{ needs.verify-tag.outputs.proposal_issue_number }} |
| 488 | with: |
| 489 | script: | |
| 490 | const prNumber = parseInt(process.env.PROPOSAL_NUMBER, 10); |
| 491 | |
| 492 | if (prNumber) { |
| 493 | // Get PR details to find the branch |
| 494 | const pr = await github.rest.pulls.get({ |
| 495 | owner: context.repo.owner, |
| 496 | repo: context.repo.repo, |
| 497 | pull_number: prNumber |
| 498 | }); |
| 499 | |
| 500 | const branchName = pr.data.head.ref; |
| 501 | |
| 502 | // Remove in-progress label |
| 503 | try { |
| 504 | await github.rest.issues.removeLabel({ |
| 505 | owner: context.repo.owner, |
| 506 | repo: context.repo.repo, |
| 507 | issue_number: prNumber, |
| 508 | name: 'release-in-progress' |
| 509 | }); |
| 510 | } catch (e) { |
| 511 | console.log('Label not found:', e.message); |
| 512 | } |
| 513 | |
| 514 | // Add released label |
| 515 | await github.rest.issues.addLabels({ |
| 516 | owner: context.repo.owner, |
| 517 | repo: context.repo.repo, |
| 518 | issue_number: prNumber, |
| 519 | labels: ['released'] |
| 520 | }); |
| 521 | |
| 522 | // Add final comment |
| 523 | await github.rest.issues.createComment({ |
| 524 | owner: context.repo.owner, |
| 525 | repo: context.repo.repo, |
| 526 | issue_number: prNumber, |
| 527 | body: '## ✅ Release Published\n\nThe release has been successfully published and is now available.' |
| 528 | }); |
| 529 | |
| 530 | // Close the PR if it's still open |
| 531 | if (pr.data.state === 'open') { |
| 532 | await github.rest.pulls.update({ |
| 533 | owner: context.repo.owner, |
| 534 | repo: context.repo.repo, |
| 535 | pull_number: prNumber, |
| 536 | state: 'closed' |
| 537 | }); |
| 538 | console.log(`Closed PR #${prNumber}`); |
| 539 | } |
| 540 | |
| 541 | // Delete the branch |
| 542 | try { |
| 543 | await github.rest.git.deleteRef({ |
| 544 | owner: context.repo.owner, |
| 545 | repo: context.repo.repo, |
| 546 | ref: `heads/${branchName}` |
| 547 | }); |
| 548 | console.log(`Deleted branch: ${branchName}`); |
| 549 | } catch (e) { |
| 550 | console.log(`Could not delete branch ${branchName}: ${e.message}`); |
| 551 | } |
| 552 | } |