|
| 1 | +#!/bin/bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +# ------------------------------------------------------------------- |
| 5 | +# Maven Central release script with curl-based upload fallback. |
| 6 | +# |
| 7 | +# JReleaser's Feign HTTP client has a hard read-timeout (~6 min) that |
| 8 | +# cannot be reliably overridden via Gradle DSL for large bundles. |
| 9 | +# This script first attempts jreleaserDeploy; if that fails, it falls |
| 10 | +# back to uploading the bundle via curl with a generous timeout, |
| 11 | +# then polls the Central Portal for deployment status. |
| 12 | +# ------------------------------------------------------------------- |
| 13 | + |
| 14 | +UPLOAD_TIMEOUT=${1:-900} # curl upload timeout in seconds (default 15 min) |
| 15 | +MAX_STATUS_CHECKS=${2:-100} # max status poll attempts |
| 16 | +STATUS_CHECK_INTERVAL=${3:-20} # seconds between status polls |
| 17 | + |
| 18 | +CENTRAL_API="https://central.sonatype.com/api/v1/publisher" |
| 19 | + |
| 20 | +# Credentials from environment (set by GitHub Actions) |
| 21 | +USERNAME="${JRELEASER_MAVENCENTRAL_USERNAME:?JRELEASER_MAVENCENTRAL_USERNAME not set}" |
| 22 | +PASSWORD="${JRELEASER_MAVENCENTRAL_PASSWORD:?JRELEASER_MAVENCENTRAL_PASSWORD not set}" |
| 23 | +AUTH_TOKEN=$(echo -n "${USERNAME}:${PASSWORD}" | base64) |
| 24 | + |
| 25 | +# --- Step 1: Try jreleaserDeploy (signs artifacts and creates bundle) --- |
| 26 | +echo "==> Attempting jreleaserDeploy (signs + bundles + uploads)..." |
| 27 | +if ./gradlew jreleaserDeploy --no-daemon --stacktrace; then |
| 28 | + echo "==> jreleaserDeploy succeeded" |
| 29 | + exit 0 |
| 30 | +fi |
| 31 | + |
| 32 | +echo "==> jreleaserDeploy failed (likely upload timeout). Falling back to curl upload." |
| 33 | + |
| 34 | +# --- Step 2: Locate the bundle created by JReleaser --- |
| 35 | +BUNDLE_DIR="build/jreleaser/deploy/mavenCentral/sonatype" |
| 36 | +BUNDLE=$(find "$BUNDLE_DIR" -name "*.zip" -type f 2>/dev/null | head -1) |
| 37 | + |
| 38 | +if [ -z "$BUNDLE" ]; then |
| 39 | + echo "ERROR: No bundle zip found in $BUNDLE_DIR" |
| 40 | + echo "Contents of build/jreleaser/deploy/:" |
| 41 | + find build/jreleaser/deploy/ -type f 2>/dev/null || true |
| 42 | + exit 1 |
| 43 | +fi |
| 44 | + |
| 45 | +echo "==> Found bundle: $BUNDLE" |
| 46 | + |
| 47 | +# --- Step 3: Upload bundle via curl with retries --- |
| 48 | +upload_bundle() { |
| 49 | + local attempt=$1 |
| 50 | + echo "==> Upload attempt $attempt — timeout ${UPLOAD_TIMEOUT}s" |
| 51 | + |
| 52 | + HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" \ |
| 53 | + --max-time "$UPLOAD_TIMEOUT" \ |
| 54 | + --connect-timeout 60 \ |
| 55 | + -X POST "$CENTRAL_API/upload" \ |
| 56 | + -H "Authorization: Bearer $AUTH_TOKEN" \ |
| 57 | + -F "bundle=@$BUNDLE;type=application/octet-stream" \ |
| 58 | + -F "publishingType=AUTOMATIC" \ |
| 59 | + 2>&1) || true |
| 60 | + |
| 61 | + HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -1) |
| 62 | + RESPONSE_BODY=$(echo "$HTTP_RESPONSE" | sed '$d') |
| 63 | + |
| 64 | + echo "==> Upload response: HTTP $HTTP_CODE" |
| 65 | + |
| 66 | + if [ "$HTTP_CODE" = "201" ]; then |
| 67 | + DEPLOYMENT_ID="$RESPONSE_BODY" |
| 68 | + echo "==> Upload successful. Deployment ID: $DEPLOYMENT_ID" |
| 69 | + return 0 |
| 70 | + else |
| 71 | + echo "==> Upload failed: $RESPONSE_BODY" |
| 72 | + return 1 |
| 73 | + fi |
| 74 | +} |
| 75 | + |
| 76 | +DEPLOYMENT_ID="" |
| 77 | +for attempt in 1 2 3 4 5; do |
| 78 | + if upload_bundle "$attempt"; then |
| 79 | + break |
| 80 | + fi |
| 81 | + if [ "$attempt" -eq 5 ]; then |
| 82 | + echo "ERROR: All upload attempts failed" |
| 83 | + exit 1 |
| 84 | + fi |
| 85 | + echo "==> Retrying upload in 30 seconds..." |
| 86 | + sleep 30 |
| 87 | +done |
| 88 | + |
| 89 | +# --- Step 4: Poll for deployment status --- |
| 90 | +if [ -z "$DEPLOYMENT_ID" ]; then |
| 91 | + echo "ERROR: No deployment ID obtained" |
| 92 | + exit 1 |
| 93 | +fi |
| 94 | + |
| 95 | +echo "==> Polling deployment status (max $MAX_STATUS_CHECKS checks, every ${STATUS_CHECK_INTERVAL}s)..." |
| 96 | + |
| 97 | +for (( i=1; i<=MAX_STATUS_CHECKS; i++ )); do |
| 98 | + sleep "$STATUS_CHECK_INTERVAL" |
| 99 | + |
| 100 | + STATUS_RESPONSE=$(curl -s \ |
| 101 | + --max-time 60 \ |
| 102 | + -X POST "$CENTRAL_API/status?id=$DEPLOYMENT_ID" \ |
| 103 | + -H "Authorization: Bearer $AUTH_TOKEN" \ |
| 104 | + -H "Accept: application/json" \ |
| 105 | + 2>&1) || true |
| 106 | + |
| 107 | + DEPLOYMENT_STATE=$(echo "$STATUS_RESPONSE" | grep -o '"deploymentState":"[^"]*"' | head -1 | cut -d'"' -f4) |
| 108 | + |
| 109 | + echo " [$i/$MAX_STATUS_CHECKS] Deployment state: ${DEPLOYMENT_STATE:-UNKNOWN}" |
| 110 | + |
| 111 | + case "$DEPLOYMENT_STATE" in |
| 112 | + PUBLISHED) |
| 113 | + echo "==> Deployment published successfully!" |
| 114 | + exit 0 |
| 115 | + ;; |
| 116 | + FAILED) |
| 117 | + echo "ERROR: Deployment failed on Maven Central" |
| 118 | + echo "Response: $STATUS_RESPONSE" |
| 119 | + exit 1 |
| 120 | + ;; |
| 121 | + VALIDATED|PUBLISHING) |
| 122 | + echo " Deployment is progressing..." |
| 123 | + ;; |
| 124 | + PENDING|VALIDATING) |
| 125 | + echo " Still processing..." |
| 126 | + ;; |
| 127 | + *) |
| 128 | + echo " Unexpected state, continuing to poll..." |
| 129 | + ;; |
| 130 | + esac |
| 131 | +done |
| 132 | + |
| 133 | +echo "ERROR: Timed out waiting for deployment status after $MAX_STATUS_CHECKS checks" |
| 134 | +exit 1 |
0 commit comments