Skip to content

Release to Maven Central #2

Release to Maven Central

Release to Maven Central #2

Workflow file for this run

name: Release to Maven Central
on:
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., 0.1.0)'
required: true
permissions: read-all
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4.7.1
with:
distribution: 'temurin'
java-version: '25'
cache: 'maven'
server-id: central
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE
- name: Configure git identity and non-interactive GPG for signed commit/tag
run: |
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
# Use the GPG key imported by setup-java (MAVEN_GPG_PRIVATE_KEY) for
# both commit and tag signing — same trust path as the artifact.
KEYID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}')
if [ -z "$KEYID" ]; then
echo "no GPG secret key in agent — release-java.yml needs MAVEN_GPG_PRIVATE_KEY" >&2
exit 1
fi
git config user.signingkey "$KEYID"
git config gpg.format openpgp
git config commit.gpgsign true
git config tag.gpgsign true
# Reviewer finding cf64b44d (RAN-47, R5-1):
# `git commit -S` / `git tag -s` invoke gpg interactively by default
# and fail in non-interactive Actions shells when the imported key
# has a passphrase. setup-java only wires the passphrase for Maven
# signing (via MAVEN_GPG_PASSPHRASE in settings.xml); git itself
# has no equivalent autoconfig. Configure the gpg-agent for loopback
# pinentry, point gpg.program at a thin wrapper that injects
# --batch / --pinentry-mode loopback / --passphrase from
# MAVEN_GPG_PASSPHRASE, so signing succeeds non-interactively.
mkdir -p "$HOME/.gnupg"
chmod 700 "$HOME/.gnupg"
printf '%s\n' 'use-agent' 'pinentry-mode loopback' > "$HOME/.gnupg/gpg.conf"
printf '%s\n' 'allow-loopback-pinentry' > "$HOME/.gnupg/gpg-agent.conf"
gpgconf --kill gpg-agent || true
# Wrapper script: git invokes this with the same flags as gpg.
# We exec into gpg with --batch + loopback + the passphrase from
# the env (MAVEN_GPG_PASSPHRASE is set on each step that signs).
cat > "$HOME/.gnupg/gpg-loopback.sh" <<'WRAPPER'
#!/usr/bin/env bash
# Non-interactive gpg wrapper for `git commit -S` / `git tag -s`.
# MAVEN_GPG_PASSPHRASE is set on the workflow step that signs.
exec gpg --batch --yes --pinentry-mode loopback \
--passphrase "${MAVEN_GPG_PASSPHRASE:-}" "$@"
WRAPPER
chmod +x "$HOME/.gnupg/gpg-loopback.sh"
git config gpg.program "$HOME/.gnupg/gpg-loopback.sh"
- name: Set release version and create signed release commit
env:
RELEASE_VERSION: ${{ inputs.version }}
# Picked up by the gpg-loopback wrapper script for `git commit -S`.
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
# Commit the version bump on a detached HEAD off the workflow's
# checkout. The commit is reachable only via the tag created below —
# no push to `main`, so this works under branch protection.
# The commit captures the exact source tree that the deploy step
# will build from, fixing the prior "tag diverges from released
# artifact" gap (Reviewer finding 47b718b9).
run: |
mvn -B -ntp versions:set -DnewVersion="$RELEASE_VERSION" -DgenerateBackupPoms=false
git add pom.xml
git commit -S -m "chore(release): ${RELEASE_VERSION}"
- name: Deploy to Maven Central
env:
MAVEN_USERNAME: ${{ secrets.OSS_NEXUS_USER }}
MAVEN_PASSWORD: ${{ secrets.OSS_NEXUS_PASS }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: mvn -B -ntp -P release clean deploy
- name: Create signed annotated tag and push
env:
RELEASE_VERSION: ${{ inputs.version }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
# Annotated + GPG-signed tag pointing at the release commit (the
# current HEAD after the commit step above). Push only the tag —
# the release commit lives only as a tag-reachable object so we
# never need to update `main`, and branch protection stays clean.
run: |
git tag -s "v${RELEASE_VERSION}" -m "codeiq ${RELEASE_VERSION}"
git push origin "refs/tags/v${RELEASE_VERSION}"
- uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
tag_name: v${{ inputs.version }}
generate_release_notes: true
files: |
target/code-iq-*-cli.jar