mirror of
https://github.com/caddyserver/caddy.git
synced 2025-12-08 06:09:53 +00:00
* ci: implement new release flow Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * remove redundant validation Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * extract key sha Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * pin github-scripts Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * switch to PR-based flow Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * don't use top-level permissions Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * restricted global perms + specific local perms Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * make PR draft Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> --------- Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
565 lines
23 KiB
YAML
565 lines
23 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*.*.*'
|
|
|
|
env:
|
|
# https://github.com/actions/setup-go/issues/491
|
|
GOTOOLCHAIN: local
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
verify-tag:
|
|
name: Verify Tag Signature and Approvals
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
issues: write
|
|
|
|
outputs:
|
|
verification_passed: ${{ steps.verify.outputs.passed }}
|
|
tag_version: ${{ steps.info.outputs.version }}
|
|
proposal_issue_number: ${{ steps.find_proposal.outputs.result && fromJson(steps.find_proposal.outputs.result).number || '' }}
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
with:
|
|
fetch-depth: 0
|
|
# Force fetch upstream tags -- because 65 minutes
|
|
# tl;dr: actions/checkout@v3 runs this line:
|
|
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
|
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
|
# git fetch --prune --unshallow
|
|
# which doesn't overwrite that tag because that would be destructive.
|
|
# Credit to @francislavoie for the investigation.
|
|
# https://github.com/actions/checkout/issues/290#issuecomment-680260080
|
|
- name: Force fetch upstream tags
|
|
run: git fetch --tags --force
|
|
|
|
- name: Get tag info
|
|
id: info
|
|
run: |
|
|
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
|
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
|
|
|
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
|
|
- name: Print Go version and environment
|
|
id: vars
|
|
run: |
|
|
printf "Using go at: $(which go)\n"
|
|
printf "Go version: $(go version)\n"
|
|
printf "\n\nGo environment:\n\n"
|
|
go env
|
|
printf "\n\nSystem environment:\n\n"
|
|
env
|
|
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
|
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
|
|
# Add "pip install" CLI tools to PATH
|
|
echo ~/.local/bin >> $GITHUB_PATH
|
|
|
|
# Parse semver
|
|
TAG=${GITHUB_REF/refs\/tags\//}
|
|
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
|
|
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
|
|
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
|
|
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
|
|
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
|
|
echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT
|
|
echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT
|
|
echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT
|
|
echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Validate commits and tag signatures
|
|
id: verify
|
|
env:
|
|
signing_keys: ${{ secrets.SIGNING_KEYS }}
|
|
run: |
|
|
# Read the string into an array, splitting by IFS
|
|
IFS=";" read -ra keys_collection <<< "$signing_keys"
|
|
|
|
# ref: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#example-usage-of-the-runner-context
|
|
touch "${{ runner.temp }}/allowed_signers"
|
|
|
|
# Iterate and print the split elements
|
|
for item in "${keys_collection[@]}"; do
|
|
|
|
# trim leading whitespaces
|
|
item="${item##*( )}"
|
|
|
|
# trim trailing whitespaces
|
|
item="${item%%*( )}"
|
|
|
|
IFS=" " read -ra key_components <<< "$item"
|
|
# git wants it in format: email address, type, public key
|
|
# ssh has it in format: type, public key, email address
|
|
echo "${key_components[2]} namespaces=\"git\" ${key_components[0]} ${key_components[1]}" >> "${{ runner.temp }}/allowed_signers"
|
|
done
|
|
|
|
git config set --global gpg.ssh.allowedSignersFile "${{ runner.temp }}/allowed_signers"
|
|
|
|
echo "Verifying the tag: ${{ steps.vars.outputs.version_tag }}"
|
|
|
|
# Verify the tag is signed
|
|
if ! git verify-tag -v "${{ steps.vars.outputs.version_tag }}" 2>&1; then
|
|
echo "❌ Tag verification failed!"
|
|
echo "passed=false" >> $GITHUB_OUTPUT
|
|
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
|
|
exit 1
|
|
fi
|
|
# Run it again to capture the output
|
|
git verify-tag -v "${{ steps.vars.outputs.version_tag }}" 2>&1 | tee /tmp/verify-output.txt;
|
|
|
|
# SSH verification output typically includes the key fingerprint
|
|
# Use GNU grep with Perl regex for cleaner extraction (Linux environment)
|
|
KEY_SHA256=$(grep -oP "SHA256:[\"']?\K[A-Za-z0-9+/=]+(?=[\"']?)" /tmp/verify-output.txt | head -1 || echo "")
|
|
|
|
if [ -z "$KEY_SHA256" ]; then
|
|
# Try alternative pattern with "key" prefix
|
|
KEY_SHA256=$(grep -oP "key SHA256:[\"']?\K[A-Za-z0-9+/=]+(?=[\"']?)" /tmp/verify-output.txt | head -1 || echo "")
|
|
fi
|
|
|
|
if [ -z "$KEY_SHA256" ]; then
|
|
# Fallback: extract any base64-like string (40+ chars)
|
|
KEY_SHA256=$(grep -oP '[A-Za-z0-9+/]{40,}=?' /tmp/verify-output.txt | head -1 || echo "")
|
|
fi
|
|
|
|
if [ -z "$KEY_SHA256" ]; then
|
|
echo "Somehow could not extract SSH key fingerprint from git verify-tag output"
|
|
echo "Cancelling flow and deleting tag"
|
|
echo "passed=false" >> $GITHUB_OUTPUT
|
|
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✅ Tag verification succeeded!"
|
|
echo "passed=true" >> $GITHUB_OUTPUT
|
|
echo "key_id=$KEY_SHA256" >> $GITHUB_OUTPUT
|
|
|
|
- name: Find related release proposal
|
|
id: find_proposal
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
script: |
|
|
const version = '${{ steps.vars.outputs.version_tag }}';
|
|
|
|
// Search for PRs with release-proposal label that match this version
|
|
const prs = await github.rest.pulls.list({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
state: 'open', // Changed to 'all' to find both open and closed PRs
|
|
sort: 'updated',
|
|
direction: 'desc'
|
|
});
|
|
|
|
// Find the most recent PR for this version
|
|
const proposal = prs.data.find(pr =>
|
|
pr.title.includes(version) &&
|
|
pr.labels.some(label => label.name === 'release-proposal')
|
|
);
|
|
|
|
if (!proposal) {
|
|
console.log(`⚠️ No release proposal PR found for ${version}`);
|
|
console.log('This might be a hotfix or emergency release');
|
|
return { number: null, approved: true, approvals: 0, proposedCommit: null };
|
|
}
|
|
|
|
console.log(`Found proposal PR #${proposal.number} for version ${version}`);
|
|
|
|
// Extract commit hash from PR body
|
|
const commitMatch = proposal.body.match(/\*\*Target Commit:\*\*\s*`([a-f0-9]+)`/);
|
|
const proposedCommit = commitMatch ? commitMatch[1] : null;
|
|
|
|
if (proposedCommit) {
|
|
console.log(`Proposal was for commit: ${proposedCommit}`);
|
|
} else {
|
|
console.log('⚠️ No target commit hash found in PR body');
|
|
}
|
|
|
|
// Get PR reviews to extract approvers
|
|
let approvers = 'Validated by automation';
|
|
let approvalCount = 2; // Minimum required
|
|
|
|
try {
|
|
const reviews = await github.rest.pulls.listReviews({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: proposal.number
|
|
});
|
|
|
|
// Get latest review per user and filter for approvals
|
|
const latestReviewsByUser = {};
|
|
reviews.data.forEach(review => {
|
|
const username = review.user.login;
|
|
if (!latestReviewsByUser[username] || new Date(review.submitted_at) > new Date(latestReviewsByUser[username].submitted_at)) {
|
|
latestReviewsByUser[username] = review;
|
|
}
|
|
});
|
|
|
|
const approvalReviews = Object.values(latestReviewsByUser).filter(review =>
|
|
review.state === 'APPROVED'
|
|
);
|
|
|
|
if (approvalReviews.length > 0) {
|
|
approvers = approvalReviews.map(r => '@' + r.user.login).join(', ');
|
|
approvalCount = approvalReviews.length;
|
|
console.log(`Found ${approvalCount} approvals from: ${approvers}`);
|
|
}
|
|
} catch (error) {
|
|
console.log(`Could not fetch reviews: ${error.message}`);
|
|
}
|
|
|
|
return {
|
|
number: proposal.number,
|
|
approved: true,
|
|
approvals: approvalCount,
|
|
approvers: approvers,
|
|
proposedCommit: proposedCommit
|
|
};
|
|
result-encoding: json
|
|
|
|
- name: Verify proposal commit
|
|
run: |
|
|
APPROVALS='${{ steps.find_proposal.outputs.result }}'
|
|
|
|
# Parse JSON
|
|
PROPOSED_COMMIT=$(echo "$APPROVALS" | jq -r '.proposedCommit')
|
|
CURRENT_COMMIT="${{ steps.info.outputs.sha }}"
|
|
|
|
echo "Proposed commit: $PROPOSED_COMMIT"
|
|
echo "Current commit: $CURRENT_COMMIT"
|
|
|
|
# Check if commits match (if proposal had a target commit)
|
|
if [ "$PROPOSED_COMMIT" != "null" ] && [ -n "$PROPOSED_COMMIT" ]; then
|
|
# Normalize both commits to full SHA for comparison
|
|
PROPOSED_FULL=$(git rev-parse "$PROPOSED_COMMIT" 2>/dev/null || echo "")
|
|
CURRENT_FULL=$(git rev-parse "$CURRENT_COMMIT" 2>/dev/null || echo "")
|
|
|
|
if [ -z "$PROPOSED_FULL" ]; then
|
|
echo "⚠️ Could not resolve proposed commit: $PROPOSED_COMMIT"
|
|
elif [ "$PROPOSED_FULL" != "$CURRENT_FULL" ]; then
|
|
echo "❌ Commit mismatch!"
|
|
echo "The tag points to commit $CURRENT_FULL but the proposal was for $PROPOSED_FULL"
|
|
echo "This indicates an error in tag creation."
|
|
# Delete the tag remotely
|
|
git push --delete origin "${{ steps.vars.outputs.version_tag }}"
|
|
echo "Tag ${{steps.vars.outputs.version_tag}} has been deleted"
|
|
exit 1
|
|
else
|
|
echo "✅ Commit hash matches proposal"
|
|
fi
|
|
else
|
|
echo "⚠️ No target commit found in proposal (might be legacy release)"
|
|
fi
|
|
|
|
echo "✅ Tag verification completed"
|
|
|
|
- name: Update release proposal PR
|
|
if: fromJson(steps.find_proposal.outputs.result).number != null
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
script: |
|
|
const result = ${{ steps.find_proposal.outputs.result }};
|
|
|
|
if (result.number) {
|
|
// Add in-progress label
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: result.number,
|
|
labels: ['release-in-progress']
|
|
});
|
|
|
|
// Remove approved label if present
|
|
try {
|
|
await github.rest.issues.removeLabel({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: result.number,
|
|
name: 'approved'
|
|
});
|
|
} catch (e) {
|
|
console.log('Approved label not found:', e.message);
|
|
}
|
|
|
|
const commentBody = [
|
|
'## 🚀 Release Workflow Started',
|
|
'',
|
|
'- **Tag:** ${{ steps.info.outputs.version }}',
|
|
'- **Signed by key:** ${{ steps.verify.outputs.key_id }}',
|
|
'- **Commit:** ${{ steps.info.outputs.sha }}',
|
|
'- **Approved by:** ' + result.approvers,
|
|
'',
|
|
'Release workflow is now running. This PR will be updated when the release is published.'
|
|
].join('\n');
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: result.number,
|
|
body: commentBody
|
|
});
|
|
}
|
|
|
|
- name: Summary
|
|
run: |
|
|
APPROVALS='${{ steps.find_proposal.outputs.result }}'
|
|
PROPOSED_COMMIT=$(echo "$APPROVALS" | jq -r '.proposedCommit // "N/A"')
|
|
APPROVERS=$(echo "$APPROVALS" | jq -r '.approvers // "N/A"')
|
|
|
|
echo "## Tag Verification Summary 🔐" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Tag:** ${{ steps.info.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Commit:** ${{ steps.info.outputs.sha }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Proposed Commit:** $PROPOSED_COMMIT" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Signature:** ✅ Verified" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Signed by:** ${{ steps.verify.outputs.key_id }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Approvals:** ✅ Sufficient" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Approved by:** $APPROVERS" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "Proceeding with release build..." >> $GITHUB_STEP_SUMMARY
|
|
|
|
release:
|
|
name: Release
|
|
needs: verify-tag
|
|
if: ${{ needs.verify-tag.outputs.verification_passed == 'true' }}
|
|
strategy:
|
|
matrix:
|
|
os:
|
|
- ubuntu-latest
|
|
go:
|
|
- '1.25'
|
|
|
|
include:
|
|
# Set the minimum Go patch version for the given Go minor
|
|
# Usable via ${{ matrix.GO_SEMVER }}
|
|
- go: '1.25'
|
|
GO_SEMVER: '~1.25.0'
|
|
|
|
runs-on: ${{ matrix.os }}
|
|
# https://github.com/sigstore/cosign/issues/1258#issuecomment-1002251233
|
|
# https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings
|
|
permissions:
|
|
id-token: write
|
|
# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps#permission-on-contents
|
|
# "Releases" is part of `contents`, so it needs the `write`
|
|
contents: write
|
|
issues: write
|
|
pull-requests: write
|
|
|
|
steps:
|
|
- name: Harden the runner (Audit all outbound calls)
|
|
uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout code
|
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install Go
|
|
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
|
with:
|
|
go-version: ${{ matrix.GO_SEMVER }}
|
|
check-latest: true
|
|
|
|
# Force fetch upstream tags -- because 65 minutes
|
|
# tl;dr: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4.2.2 runs this line:
|
|
# git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +ebc278ec98bb24f2852b61fde2a9bf2e3d83818b:refs/tags/
|
|
# which makes its own local lightweight tag, losing all the annotations in the process. Our earlier script ran:
|
|
# git fetch --prune --unshallow
|
|
# which doesn't overwrite that tag because that would be destructive.
|
|
# Credit to @francislavoie for the investigation.
|
|
# https://github.com/actions/checkout/issues/290#issuecomment-680260080
|
|
- name: Force fetch upstream tags
|
|
run: git fetch --tags --force
|
|
|
|
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
|
|
- name: Print Go version and environment
|
|
id: vars
|
|
run: |
|
|
printf "Using go at: $(which go)\n"
|
|
printf "Go version: $(go version)\n"
|
|
printf "\n\nGo environment:\n\n"
|
|
go env
|
|
printf "\n\nSystem environment:\n\n"
|
|
env
|
|
echo "version_tag=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
|
|
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
|
|
# Add "pip install" CLI tools to PATH
|
|
echo ~/.local/bin >> $GITHUB_PATH
|
|
|
|
# Parse semver
|
|
TAG=${GITHUB_REF/refs\/tags\//}
|
|
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
|
|
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
|
|
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
|
|
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
|
|
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
|
|
echo "tag_major=${TAG_MAJOR}" >> $GITHUB_OUTPUT
|
|
echo "tag_minor=${TAG_MINOR}" >> $GITHUB_OUTPUT
|
|
echo "tag_patch=${TAG_PATCH}" >> $GITHUB_OUTPUT
|
|
echo "tag_special=${TAG_SPECIAL}" >> $GITHUB_OUTPUT
|
|
|
|
# Cloudsmith CLI tooling for pushing releases
|
|
# See https://help.cloudsmith.io/docs/cli
|
|
- name: Install Cloudsmith CLI
|
|
run: pip install --upgrade cloudsmith-cli
|
|
|
|
- name: Install Cosign
|
|
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # main
|
|
- name: Cosign version
|
|
run: cosign version
|
|
- name: Install Syft
|
|
uses: anchore/sbom-action/download-syft@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # main
|
|
- name: Syft version
|
|
run: syft version
|
|
- name: Install xcaddy
|
|
run: |
|
|
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
|
|
xcaddy version
|
|
# GoReleaser will take care of publishing those artifacts into the release
|
|
- name: Run GoReleaser
|
|
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
|
|
with:
|
|
version: latest
|
|
args: release --clean --timeout 60m
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ steps.vars.outputs.version_tag }}
|
|
COSIGN_EXPERIMENTAL: 1
|
|
|
|
# Only publish on non-special tags (e.g. non-beta)
|
|
# We will continue to push to Gemfury for the foreseeable future, although
|
|
# Cloudsmith is probably better, to not break things for existing users of Gemfury.
|
|
# See https://gemfury.com/caddy/deb:caddy
|
|
- name: Publish .deb to Gemfury
|
|
if: ${{ steps.vars.outputs.tag_special == '' }}
|
|
env:
|
|
GEMFURY_PUSH_TOKEN: ${{ secrets.GEMFURY_PUSH_TOKEN }}
|
|
run: |
|
|
for filename in dist/*.deb; do
|
|
# armv6 and armv7 are both "armhf" so we can skip the duplicate
|
|
if [[ "$filename" == *"armv6"* ]]; then
|
|
echo "Skipping $filename"
|
|
continue
|
|
fi
|
|
|
|
curl -F package=@"$filename" https://${GEMFURY_PUSH_TOKEN}:@push.fury.io/caddy/
|
|
done
|
|
|
|
# Publish only special tags (unstable/beta/rc) to the "testing" repo
|
|
# See https://cloudsmith.io/~caddy/repos/testing/
|
|
- name: Publish .deb to Cloudsmith (special tags)
|
|
if: ${{ steps.vars.outputs.tag_special != '' }}
|
|
env:
|
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
|
run: |
|
|
for filename in dist/*.deb; do
|
|
# armv6 and armv7 are both "armhf" so we can skip the duplicate
|
|
if [[ "$filename" == *"armv6"* ]]; then
|
|
echo "Skipping $filename"
|
|
continue
|
|
fi
|
|
|
|
echo "Pushing $filename to 'testing'"
|
|
cloudsmith push deb caddy/testing/any-distro/any-version $filename
|
|
done
|
|
|
|
# Publish stable tags to Cloudsmith to both repos, "stable" and "testing"
|
|
# See https://cloudsmith.io/~caddy/repos/stable/
|
|
- name: Publish .deb to Cloudsmith (stable tags)
|
|
if: ${{ steps.vars.outputs.tag_special == '' }}
|
|
env:
|
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
|
run: |
|
|
for filename in dist/*.deb; do
|
|
# armv6 and armv7 are both "armhf" so we can skip the duplicate
|
|
if [[ "$filename" == *"armv6"* ]]; then
|
|
echo "Skipping $filename"
|
|
continue
|
|
fi
|
|
|
|
echo "Pushing $filename to 'stable'"
|
|
cloudsmith push deb caddy/stable/any-distro/any-version $filename
|
|
|
|
echo "Pushing $filename to 'testing'"
|
|
cloudsmith push deb caddy/testing/any-distro/any-version $filename
|
|
done
|
|
|
|
- name: Update release proposal PR
|
|
if: needs.verify-tag.outputs.proposal_issue_number != ''
|
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
with:
|
|
script: |
|
|
const prNumber = parseInt('${{ needs.verify-tag.outputs.proposal_issue_number }}');
|
|
|
|
if (prNumber) {
|
|
// Get PR details to find the branch
|
|
const pr = await github.rest.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber
|
|
});
|
|
|
|
const branchName = pr.data.head.ref;
|
|
|
|
// Remove in-progress label
|
|
try {
|
|
await github.rest.issues.removeLabel({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
name: 'release-in-progress'
|
|
});
|
|
} catch (e) {
|
|
console.log('Label not found:', e.message);
|
|
}
|
|
|
|
// Add released label
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
labels: ['released']
|
|
});
|
|
|
|
// Add final comment
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: prNumber,
|
|
body: '## ✅ Release Published\n\nThe release has been successfully published and is now available.'
|
|
});
|
|
|
|
// Close the PR if it's still open
|
|
if (pr.data.state === 'open') {
|
|
await github.rest.pulls.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
state: 'closed'
|
|
});
|
|
console.log(`Closed PR #${prNumber}`);
|
|
}
|
|
|
|
// Delete the branch
|
|
try {
|
|
await github.rest.git.deleteRef({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
ref: `heads/${branchName}`
|
|
});
|
|
console.log(`Deleted branch: ${branchName}`);
|
|
} catch (e) {
|
|
console.log(`Could not delete branch ${branchName}: ${e.message}`);
|
|
}
|
|
}
|