Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7138ed3
Replace SITE_DEPLOY_PAT with gh-cli-site-deployer App
williammartin May 22, 2026
da101bf
Merge remote-tracking branch 'origin/wm-site-deploy-app' into niik/de…
niik Jun 16, 2026
7d92f7d
Initial conversion to reusable workflow
niik Jun 16, 2026
8f2ecd2
Update deployment.yml
niik Jun 16, 2026
9522f6e
Mint a token from GitHub app
niik Jun 17, 2026
9ac613a
Use client-id input for create-github-app-token
niik Jun 17, 2026
f8dec08
Rename DEPLOY_APP_ID secret to DEPLOY_APP_CLIENT_ID
niik Jun 17, 2026
4f43068
Update deployment.yml
niik Jun 17, 2026
f69cf0f
Add 20-minute timeouts to deploy jobs
niik Jun 30, 2026
83f9562
Skip Windows code signing outside production deploys
niik Jun 30, 2026
e64de0c
Add dry_run flag to gate release publishing
niik Jul 1, 2026
0e3afb4
Fix macOS signing/notarization keychain and gating
niik Jul 1, 2026
57e8cce
Update deployment.yml
niik Jul 1, 2026
9e37641
Use workflow_dispatch for deployment flow
niik Jul 1, 2026
d6be637
Update deployment.yml
niik Jul 1, 2026
3b24ab7
Update deployment.yml
niik Jul 1, 2026
637455c
Let's try a different approach
niik Jul 2, 2026
86ded77
Add diagnostic step to list macOS signing identities
niik Jul 2, 2026
0d8d855
Try this then
niik Jul 2, 2026
52669eb
Don't see how this could have worked in the past
niik Jul 2, 2026
ef90664
Don't need this any more
niik Jul 2, 2026
eeb9a3c
Update release deep-dive doc for macOS signing and dry_run changes
niik Jul 2, 2026
070ed9b
Forward dry_run to deployment workflow from script/release
niik Jul 2, 2026
7441b07
Remove ref input from deployment workflow
niik Jul 2, 2026
7a6ed73
Pass macOS signing secrets via env instead of inline interpolation
niik Jul 2, 2026
b63b811
Gate macOS signing on DO_SIGN_ARTIFACTS
niik Jul 2, 2026
64840c7
Mark dry runs in the workflow run name
niik Jul 2, 2026
a79c060
Revert to missing var
niik Jul 2, 2026
b5b459a
Merge branch 'trunk' into niik/deploy
niik Jul 2, 2026
ed04ff2
Gate publishing the site on the production environment
niik Jul 3, 2026
1b7da92
Fix pkg installer var in deep-dive code snippet
niik Jul 3, 2026
ee7fd9d
Correct pkg signing NOTE in deep-dive
niik Jul 3, 2026
7822e91
Quote $KEYCHAIN in notarytool submit invocation
niik Jul 3, 2026
c62577b
Merge branch 'trunk' into niik/deploy
niik Jul 3, 2026
9cde87c
Apply suggestions from code review
niik Jul 3, 2026
57a479f
Update deep-dive doc for final release workflow state
niik Jul 3, 2026
c14cbaa
Merge pull request #13780 from cli/niik/deploy
tidy-dev Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 77 additions & 30 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Deployment
run-name: ${{ inputs.tag_name }} / ${{ inputs.environment }}
run-name: ${{ inputs.tag_name }} / ${{ inputs.environment }}${{ inputs.dry_run == true && ' (dry run)' || '' }}

concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
Expand All @@ -16,17 +16,24 @@ on:
tag_name:
required: true
type: string
description: "The tag name for the release (e.g. v2.100.0)."
environment:
default: production
type: environment
description: "The deployment environment."
platforms:
default: "linux,macos,windows"
type: string
description: "Comma-separated list of platforms to build."
release:
description: "Whether to create a GitHub Release"
description: "Whether to run the final release job. the dry_run flag still blocks final submissions."
type: boolean
default: true

dry_run:
description: "Perform a dry run without publishing artifacts or creating a release"
type: boolean
default: true

jobs:
validate-tag-name:
runs-on: ubuntu-latest
Expand All @@ -44,6 +51,7 @@ jobs:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
if: contains(inputs.platforms, 'linux')
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand Down Expand Up @@ -87,31 +95,55 @@ jobs:
runs-on: macos-latest
environment: ${{ inputs.environment }}
if: contains(inputs.platforms, 'macos')
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: 'go.mod'
- name: Configure macOS signing

- name: Install code signing certificate
if: inputs.environment == 'production'
shell: bash
env:
APPLE_DEVELOPER_ID: ${{ vars.APPLE_DEVELOPER_ID }}
APPLE_APPLICATION_CERT: ${{ secrets.APPLE_APPLICATION_CERT }}
APPLE_APPLICATION_CERT_PASSWORD: ${{ secrets.APPLE_APPLICATION_CERT_PASSWORD }}
DEVELOPER_ID_CERT: ${{ secrets.GATEWATCHER_DEVELOPER_ID_CERT }}
DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.GATEWATCHER_DEVELOPER_ID_PASSWORD }}
run: |
keychain="$RUNNER_TEMP/buildagent.keychain"
keychain_password="password1"
# create a keychain for the certificate
PW=pwd.${{ github.run_number }}
security create-keychain -p $PW "$RUNNER_TEMP/build.keychain"
security set-keychain-settings -lut 21600 "$RUNNER_TEMP/build.keychain"
security default-keychain -s "$RUNNER_TEMP/build.keychain"
security unlock-keychain -p $PW "$RUNNER_TEMP/build.keychain"

security create-keychain -p "$keychain_password" "$keychain"
security default-keychain -s "$keychain"
security unlock-keychain -p "$keychain_password" "$keychain"

base64 -D <<<"$APPLE_APPLICATION_CERT" > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$keychain" -P "$APPLE_APPLICATION_CERT_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S "apple-tool:,apple:,codesign:" -s -k "$keychain_password" "$keychain"
# import the certificate
base64 -d <<< "$DEVELOPER_ID_CERT" > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$RUNNER_TEMP/build.keychain" -P "$DEVELOPER_ID_CERT_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $PW "$RUNNER_TEMP/build.keychain"
rm "$RUNNER_TEMP/cert.p12"

- name: Add App Store Connect API key to keychain
if: inputs.environment == 'production'
uses: nodeselector/setup-apple-codesign@ab275d0f6fb63ef9e20b12b42ea0d567f935723c
id: setup-apple-codesign
with:
asset-type: "app-store-connect-api-key"
app-store-connect-api-key-key-id: ${{ secrets.GATEWATCHER_APP_STORE_CONNECT_API_KEY_ID }}
app-store-connect-api-key-issuer-id: ${{ secrets.GATEWATCHER_APP_STORE_CONNECT_API_ISSUER_ID }}
app-store-connect-api-key-base64-private-key: ${{ secrets.GATEWATCHER_APP_STORE_CONNECT_API_BASE64_PRIVATE_KEY }}

- name: Configure notarization credentials
if: inputs.environment == 'production'
shell: bash
run: |
xcrun notarytool store-credentials "notarytool-password" \
--key "${{ steps.setup-apple-codesign.outputs.app-store-connect-api-key-key-path }}" \
--key-id "${{ steps.setup-apple-codesign.outputs.app-store-connect-api-key-key-id }}" \
--issuer "${{ steps.setup-apple-codesign.outputs.app-store-connect-api-key-issuer-id }}" \
--keychain "$RUNNER_TEMP/build.keychain"

- name: Install GoReleaser
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
Expand All @@ -128,14 +160,16 @@ jobs:
- name: Build release binaries
env:
TAG_NAME: ${{ inputs.tag_name }}
APPLE_DEVELOPER_ID: ${{ vars.APPLE_DEVELOPER_ID }}
KEYCHAIN: ${{ runner.temp }}/build.keychain
DEVELOPER_ID_CERT_IDENTIFIER: ${{ vars.MAC_APP_SIGNING_IDENTITY }}
DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }}
run: script/release --local "$TAG_NAME" --platform macos
- name: Notarize macOS archives
if: inputs.environment == 'production'
env:
APPLE_ID: ${{ vars.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_DEVELOPER_ID: ${{ vars.APPLE_DEVELOPER_ID }}
DEVELOPER_ID_CERT_IDENTIFIER: ${{ vars.MAC_APP_SIGNING_IDENTITY }}
KEYCHAIN: ${{ runner.temp }}/build.keychain
DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }} # Technically redundant given the step guard above, but kept for consistency with the build step.
run: |
shopt -s failglob
script/sign dist/gh_*_macOS_*.zip
Expand Down Expand Up @@ -167,6 +201,7 @@ jobs:
runs-on: windows-2022
environment: ${{ inputs.environment }}
if: contains(inputs.platforms, 'windows')
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand Down Expand Up @@ -225,6 +260,7 @@ jobs:
DLIB_PATH: ${{ runner.temp }}\acs\bin\x64\Azure.CodeSigning.Dlib.dll
METADATA_PATH: ${{ runner.temp }}\acs\metadata.json
TAG_NAME: ${{ inputs.tag_name }}
DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }}
run: script/release --local "$TAG_NAME" --platform windows
- name: Set up MSBuild
id: setupmsbuild
Expand Down Expand Up @@ -265,6 +301,7 @@ jobs:
AZURE_TENANT_ID: ${{ secrets.SPN_GITHUB_CLI_SIGNING_TENANT_ID }}
DLIB_PATH: ${{ runner.temp }}\acs\bin\x64\Azure.CodeSigning.Dlib.dll
METADATA_PATH: ${{ runner.temp }}\acs\metadata.json
DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }} # Technically this could just be true since we don't run this step if the environment is not production, but we keep it the same as the build step for consistency.
run: |
Get-ChildItem -Path .\dist -Filter *.msi | ForEach-Object {
.\script\sign.ps1 $_.FullName
Expand All @@ -288,14 +325,25 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Merge built artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- name: Generate site deploy token
id: site-deploy-token
if: inputs.environment == 'production'
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.SITE_DEPLOY_APP_CLIENT_ID }}
private-key: ${{ secrets.SITE_DEPLOY_APP_PRIVATE_KEY }}
owner: github
repositories: cli.github.com
- name: Checkout documentation site
if: ${{ inputs.environment == 'production' }}
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: github/cli.github.com
path: site
fetch-depth: 0
token: ${{ secrets.SITE_DEPLOY_PAT }}
token: ${{ steps.site-deploy-token.outputs.token}}
- name: Update site man pages
if: ${{ inputs.environment == 'production' }}
env:
GIT_COMMITTER_NAME: cli automation
GIT_AUTHOR_NAME: cli automation
Expand Down Expand Up @@ -339,32 +387,30 @@ jobs:
cp script/rpmmacros ~/.rpmmacros
rpmsign --addsign dist/*.rpm
- name: Attest release artifacts
if: inputs.environment == 'production'
if: inputs.environment == 'production' && !inputs.dry_run
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-path: "dist/gh_*"
create-storage-record: false # (default: true)
- name: Run createrepo
env:
GPG_SIGN: ${{ inputs.environment == 'production' }}
if: ${{ inputs.environment == 'production' }}
run: |
mkdir -p site/packages/rpm
cp dist/*.rpm site/packages/rpm/
./script/createrepo.sh
cp -r dist/repodata site/packages/rpm/
pushd site/packages/rpm
[ "$GPG_SIGN" = "false" ] || gpg --yes --detach-sign --armor repodata/repomd.xml
gpg --yes --detach-sign --armor repodata/repomd.xml
popd
- name: Run reprepro
if: ${{ inputs.environment == 'production' }}
env:
GPG_SIGN: ${{ inputs.environment == 'production' }}
# We are no longer adding to the distribution list.
# All apt distributions should use "stable" according to our install documentation.
# In the future we will remove legacy distributions listed here.
RELEASES: "cosmic eoan disco groovy focal stable oldstable testing sid unstable buster bullseye stretch jessie bionic trusty precise xenial hirsute impish kali-rolling"
run: |
mkdir -p upload
[ "$GPG_SIGN" = "true" ] || sed -i.bak '/^SignWith:/d' script/distributions
for release in $RELEASES; do
for file in dist/*.deb; do
reprepro --confdir="+b/script" includedeb "$release" "$file"
Expand All @@ -376,7 +422,7 @@ jobs:
- name: Create the release
env:
# In non-production environments, the assets will not have been signed
DO_PUBLISH: ${{ inputs.environment == 'production' }}
DO_PUBLISH: ${{ inputs.environment == 'production' && !inputs.dry_run }}
TAG_NAME: ${{ inputs.tag_name }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
Expand All @@ -398,8 +444,9 @@ jobs:
[ "$DO_PUBLISH" = "false" ] || guard=""
script/label-assets dist/gh_* | xargs $guard gh release create "${release_args[@]}" --
- name: Publish site
if: ${{ inputs.environment == 'production' }}
env:
DO_PUBLISH: ${{ inputs.environment == 'production' && !contains(inputs.tag_name, '-') }}
DO_PUBLISH: ${{ inputs.environment == 'production' && !contains(inputs.tag_name, '-') && !inputs.dry_run }}
TAG_NAME: ${{ inputs.tag_name }}
GIT_COMMITTER_NAME: cli automation
GIT_AUTHOR_NAME: cli automation
Expand All @@ -414,4 +461,4 @@ jobs:
else
git log --oneline @{upstream}..
git diff --name-status @{upstream}..
fi
fi
Loading
Loading