Skip to content

Package Installers (jpackage) #98

Package Installers (jpackage)

Package Installers (jpackage) #98

name: Package Installers (jpackage)
permissions:
contents: write
actions: read
on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g., 1.2.3)"
required: true
type: string
workflow_call:
inputs:
version:
description: "Release version (e.g., 1.2.3)"
required: true
type: string
workflow_run:
workflows: [ "Create Release" ]
types:
- completed
jobs:
build-package:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' || !github.event.workflow_run.conclusion }}
name: Build installers on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
env:
VERSION: ${{ inputs.version || github.event.inputs.version }}
strategy:
fail-fast: false
matrix:
include:
# Linux builds - split by installer type for parallelism
- os: ubuntu-latest
arch: native
installer_type: deb
- os: ubuntu-latest
arch: native
installer_type: rpm
# Windows build
- os: windows-latest
arch: native
installer_type: msi
# macOS build - Apple Silicon (also runs on Intel via Rosetta 2)
- os: macos-latest
arch: arm64
installer_type: pkg
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Verify runner architecture (macOS only)
if: matrix.arch != 'native'
run: |
EXPECTED="${{ matrix.arch }}"
ACTUAL="$(uname -m)"
echo "Expected architecture: $EXPECTED"
echo "Actual architecture: $ACTUAL"
if [ "$EXPECTED" = "arm64" ] && [ "$ACTUAL" != "arm64" ]; then
echo "ERROR: Expected arm64 but runner is $ACTUAL"
exit 1
fi
if [ "$EXPECTED" = "x86_64" ] && [ "$ACTUAL" != "x86_64" ]; then
echo "ERROR: Expected x86_64 but runner is $ACTUAL"
exit 1
fi
echo "✓ Architecture verification passed"
shell: bash
- name: Set up Temurin JDK 21 (default/native)
if: matrix.arch == 'native'
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: 'gradle'
- name: Set up Temurin JDK 21 (Intel x86_64)
if: matrix.arch == 'x86_64'
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: 'gradle'
architecture: 'x64'
- name: Set up Temurin JDK 21 (Apple Silicon arm64)
if: matrix.arch == 'arm64'
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: 'gradle'
architecture: 'arm64'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v5
- name: Ensure WiX 3.x available (Windows)
if: matrix.os == 'windows-latest'
shell: powershell
run: |
$wix = Get-Command candle.exe -ErrorAction SilentlyContinue
if ($wix) {
Write-Host "WiX already available: $($wix.Path)"
candle.exe -? | Select-Object -First 1
exit 0
} else {
choco install wixtoolset -y --no-progress
}
- name: Install Linux packaging prerequisites
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y rpm fakeroot libfuse2
- name: Verify Java and jpackage
run: |
java -version
jpackage --version
- name: Build and create installers for this OS (quiet)
run: |
set -e
# Ensure log directory exists
mkdir -p build
# Determine which jpackage task to run based on installer type
case "${{ matrix.installer_type }}" in
deb)
JPACKAGE_TASK="jpackageLinuxDeb"
;;
rpm)
JPACKAGE_TASK="jpackageLinuxRpm"
;;
msi)
JPACKAGE_TASK="jpackageWin"
;;
pkg)
JPACKAGE_TASK="jpackageMacPkg"
;;
*)
echo "Unknown installer type: ${{ matrix.installer_type }}"
exit 1
;;
esac
echo "Running Gradle task: gui:$JPACKAGE_TASK"
# Run Gradle quietly and capture all output to a log. On failure, print only the tail.
# Use --parallel to enable parallel task execution within Gradle
./gradlew --no-daemon --parallel --stacktrace --quiet --console=plain -Dorg.gradle.warning.mode=none \
gui:$JPACKAGE_TASK \
> build/ci-gradle.log 2>&1 \
|| (echo "Gradle build failed — showing last 400 lines of log:" && tail -n 400 build/ci-gradle.log && exit 1)
echo "Gradle build succeeded. (Logs suppressed; see build/ci-gradle.log in the artifact if needed.)"
# Copy installers to build/dist for artifact upload
mkdir -p build/dist
find gui/build/jpackage -type f \( -name "*.deb" -o -name "*.rpm" -o -name "*.msi" -o -name "*.pkg" \) -exec cp {} build/dist/ \;
shell: bash
- name: Re-run macOS with deep jpackage diagnostics (only on macOS)
if: startsWith(matrix.os, 'macos') && failure()
run: |
echo "Re-running macOS packaging with --debug to surface jpackage stderr"
./gradlew --no-daemon --debug -PciVerbose=true gui:jpackageMacPkg
shell: bash
- name: Rename macOS .pkg files to match release version
if: startsWith(matrix.os, 'macos')
env:
MACOS_ARCH: ${{ matrix.arch }}
run: |
set -e
echo "=== Renaming macOS .pkg files ==="
# Use the version from workflow input or gradle.properties
VERSION="${{ inputs.version || github.event.inputs.version }}"
if [ -z "$VERSION" ]; then
echo "No version from workflow input, reading from gradle.properties"
VERSION=$(grep '^version=' gradle.properties | cut -d'=' -f2 | tr -d '\r')
fi
if [ -z "$VERSION" ]; then
echo "ERROR: Could not determine version for renaming!"
exit 1
fi
echo "Target version: $VERSION"
echo ""
# Check if build/dist exists
if [ ! -d "build/dist" ]; then
echo "ERROR: build/dist directory does not exist!"
echo "Listing contents of build:"
ls -la build/ || echo "build/ does not exist"
exit 1
fi
echo "=== Contents of build/dist before renaming: ==="
find build/dist -type f -name '*.pkg' -exec ls -lh {} \; || echo "No .pkg files found"
echo ""
# Find all .pkg files and rename them (using array to avoid subshell issues)
FOUND_FILES=0
RENAMED_FILES=0
while IFS= read -r -d '' PKG_ORIG; do
FOUND_FILES=$((FOUND_FILES + 1))
BASENAME=$(basename "$PKG_ORIG")
echo "Found .pkg file: $PKG_ORIG"
# Check if filename contains version pattern like 1.34.3
if [[ "$BASENAME" =~ -1\.[0-9]+\.[0-9]+\.pkg$ ]]; then
# Replace 1.x.y with the target version
PKG_NEW=$(echo "$PKG_ORIG" | sed "s/-1\.\([0-9]\+\)\.\([0-9]\+\)\.pkg$/-${VERSION}.pkg/")
echo " Pattern matched! Renaming to: $PKG_NEW"
if mv "$PKG_ORIG" "$PKG_NEW"; then
echo " ✓ Successfully renamed"
RENAMED_FILES=$((RENAMED_FILES + 1))
else
echo " ✗ Failed to rename!"
exit 1
fi
# Note: We no longer append arch suffix since we only build one macOS PKG (ARM, works on Intel via Rosetta 2)
else
echo " Skipping (no version 1.x.y pattern found in: $BASENAME)"
fi
echo ""
done < <(find build/dist -type f -name '*.pkg' -print0)
echo "=== Summary ==="
echo "Files found: $FOUND_FILES"
echo "Files renamed: $RENAMED_FILES"
echo ""
if [ $FOUND_FILES -eq 0 ]; then
echo "WARNING: No .pkg files found in build/dist!"
fi
echo "=== Final .pkg files in build/dist: ==="
find build/dist -type f -name '*.pkg' -exec ls -lh {} \; || echo "No .pkg files found"
shell: bash
- name: Import GPG key (Linux/macOS)
if: matrix.os != 'windows-latest'
run: echo "$GPG_PRIVATE_KEY" | gpg --batch --import
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
- name: Import GPG key (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
Set-Content -Path gpg-key.asc -Value $env:GPG_PRIVATE_KEY -NoNewline
gpg --batch --import gpg-key.asc
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
- name: Generate SHA256 checksums (Linux/macOS)
if: matrix.os != 'windows-latest'
run: |
cd build/dist
find . -type f -exec sha256sum {} + > SHA256SUMS
- name: Generate SHA256 checksums (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
Set-Location build/dist
Get-ChildItem -Recurse -File | ForEach-Object {
$hash = Get-FileHash $_.FullName -Algorithm SHA256
"$($hash.Hash) $($_.FullName.Substring((Get-Location).Path.Length + 1))" | Out-File -Encoding ascii -Append SHA256SUMS
}
- name: Sign SHA256SUMS (Linux/macOS)
if: matrix.os != 'windows-latest'
run: |
cd build/dist
gpg --batch --yes --pinentry-mode loopback \
--passphrase "$GPG_PASSPHRASE" \
--armor --detach-sign SHA256SUMS
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Sign SHA256SUMS (Windows)
if: matrix.os == 'windows-latest'
shell: pwsh
run: |
Set-Location build/dist
gpg --batch --yes --pinentry-mode loopback --passphrase "$env:GPG_PASSPHRASE" --armor --detach-sign SHA256SUMS
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: Upload installers
if: always()
uses: actions/upload-artifact@v4
with:
name: installers-${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.installer_type }}
path: |
build/dist/**/*
build/ci-gradle.log
build/dist/SHA256SUMS
build/dist/SHA256SUMS.asc
if-no-files-found: warn
upload-to-release:
name: Upload installers to GitHub Release
needs: build-package
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' || github.event_name == 'push' || github.event_name == 'workflow_run'
steps:
- name: Download all installer artifacts
uses: actions/download-artifact@v4
with:
path: all-installers
- name: Download existing release assets (JARs, sample bots, etc.)
run: |
set -euo pipefail
# Determine version (handle both workflow_dispatch and workflow_call)
VERSION="${{ inputs.version || github.event.inputs.version }}"
if [ -z "$VERSION" ]; then
echo "No version provided, detecting from latest release..."
VERSION=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases/latest" \
| jq -r '.tag_name' | sed 's/^v//')
fi
if [ -z "$VERSION" ] || [ "$VERSION" == "null" ]; then
echo "ERROR: Could not determine version"
exit 1
fi
TAG="v${VERSION}"
echo "Downloading assets for release: $TAG"
# Get release info (including draft releases)
releases=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases")
release_info=$(echo "$releases" | jq -r ".[] | select(.tag_name == \"$TAG\")")
if [ -z "$release_info" ]; then
echo "WARNING: Release $TAG not found or has no assets yet. Skipping asset download."
exit 0
fi
# Show ALL assets in the release for debugging
echo "=== ALL Assets in Release $TAG ==="
echo "$release_info" | jq -r '.assets[] | "\(.name) (\(.size) bytes)"'
echo ""
# Create directory for release assets
mkdir -p all-installers/release-assets
# Extract asset information for debugging
echo "=== Release Assets Found ==="
echo "$release_info" | jq -r '.assets[] | select(.name | test("\\.(jar|zip)$")) | "\(.name) (\(.size) bytes, ID: \(.id))"'
echo "=== Starting Downloads ==="
# Download all non-installer assets (JARs, sample bots)
# Use the asset API URL with Accept header for draft releases
while IFS='|' read -r asset_id asset_name asset_size; do
if [ -n "$asset_id" ]; then
echo "Downloading: $asset_name ($asset_size bytes, ID: $asset_id)"
# Use GitHub API to download asset with proper authentication
# This works for both draft and published releases
if ! curl -fSL \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/octet-stream" \
-o "all-installers/release-assets/$asset_name" \
"https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset_id" 2>&1; then
echo "ERROR: Failed to download $asset_name"
exit 1
fi
# Show what we got
downloaded_size=$(stat -c%s "all-installers/release-assets/$asset_name" 2>/dev/null || stat -f%z "all-installers/release-assets/$asset_name" 2>/dev/null || echo "unknown")
echo " Downloaded: $downloaded_size bytes"
# Verify size matches
if [ "$downloaded_size" != "$asset_size" ] && [ "$downloaded_size" != "unknown" ]; then
echo " WARNING: Downloaded size ($downloaded_size) doesn't match expected size ($asset_size)"
fi
fi
done < <(echo "$release_info" | jq -r '.assets[] | select(.name | test("\\.(jar|zip)$")) | "\(.id)|\(.name)|\(.size)"')
# Verify downloaded files are not HTML error pages
echo ""
echo "=== Verifying Downloaded Files ==="
file_count=0
for file in all-installers/release-assets/*; do
if [ -f "$file" ]; then
file_count=$((file_count + 1))
filename=$(basename "$file")
# Check file type
filetype=$(file -b "$file")
# Check if file is HTML (error page) instead of expected binary
if echo "$filetype" | grep -qi "HTML"; then
echo "❌ ERROR: $filename is HTML (likely an error page)"
echo "File type: $filetype"
echo "Content preview:"
head -n 20 "$file"
exit 1
fi
# Show size and checksum
size=$(ls -lh "$file" | awk '{print $5}')
checksum=$(sha256sum "$file" | awk '{print $1}')
echo "✓ $filename"
echo " Size: $size"
echo " Type: $filetype"
echo " SHA256: $checksum"
fi
done
if [ $file_count -eq 0 ]; then
echo "WARNING: No files were downloaded!"
else
echo ""
echo "Successfully verified $file_count file(s)"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: List downloaded files (debug)
run: |
find all-installers
- name: Ensure jq is available (required for URL encoding)
run: |
if ! command -v jq >/dev/null 2>&1; then
sudo apt-get update && sudo apt-get install -y jq
else
echo "jq already installed"
fi
- name: Deduplicate SHA256SUMS files and remove build logs
run: |
# Keep only the first found SHA256SUMS and SHA256SUMS.asc
find all-installers -name 'SHA256SUMS' | head -n 1 | xargs -I{} cp {} all-installers/SHA256SUMS
find all-installers -name 'SHA256SUMS.asc' | head -n 1 | xargs -I{} cp {} all-installers/SHA256SUMS.asc
# Remove all other SHA256SUMS and SHA256SUMS.asc
find all-installers -name 'SHA256SUMS' ! -path 'all-installers/SHA256SUMS' -delete
find all-installers -name 'SHA256SUMS.asc' ! -path 'all-installers/SHA256SUMS.asc' -delete
# Remove ci-gradle.log files (debug logs not needed in release)
find all-installers -name 'ci-gradle.log' -delete
- name: Ensure release is published with correct tag
run: |
set -euo pipefail
# Determine version
VERSION="${{ inputs.version || github.event.inputs.version }}"
if [ -z "$VERSION" ]; then
echo "No version provided via inputs. Attempting to detect from latest release..."
VERSION=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases/latest" \
| jq -r '.tag_name' | sed 's/^v//')
fi
if [ -z "$VERSION" ] || [ "$VERSION" == "null" ]; then
echo "ERROR: Could not determine release version"
exit 1
fi
TAG="v${VERSION}"
REPO="${{ github.repository }}"
API_URL="https://api.github.com/repos/$REPO/releases"
echo "Ensuring release $TAG exists and is published..."
# Find release by tag
release_info=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" "$API_URL" | jq -c ".[] | select(.tag_name == \"$TAG\")" | head -n 1)
release_id=$(echo "$release_info" | jq -r '.id // empty')
is_draft=$(echo "$release_info" | jq -r '.draft // empty')
if [ -z "$release_id" ]; then
echo "Release with tag $TAG not found. Creating published release..."
create_response=$(curl -sS -X POST "$API_URL" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\": \"$TAG\", \"name\": \"Release $VERSION\", \"draft\": false, \"prerelease\": false}")
echo "Created release: $(echo "$create_response" | jq -r '.html_url')"
elif [ "$is_draft" = "true" ]; then
echo "Release $TAG is a draft (ID: $release_id). Publishing it..."
curl -sS -X PATCH "$API_URL/$release_id" \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"draft\": false}"
echo "Release $TAG is now published."
else
echo "Release $TAG is already published (ID: $release_id)."
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
- name: Upload installers to GitHub Release
run: |
set -euo pipefail
# Determine version from inputs or detect from latest release (handle both workflow_dispatch and workflow_call)
VERSION="${{ inputs.version || github.event.inputs.version }}"
if [ -z "$VERSION" ]; then
echo "No version provided via inputs. Attempting to detect from latest release..."
VERSION=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases/latest" \
| jq -r '.tag_name' | sed 's/^v//')
fi
if [ -z "$VERSION" ] || [ "$VERSION" == "null" ]; then
echo "ERROR: Could not determine release version"
exit 1
fi
TAG="v${VERSION}"
echo "Working with release version: $VERSION (tag: $TAG)"
# Import GPG key so we can sign a combined SHA256SUMS for ALL artifacts
echo "$GPG_PRIVATE_KEY" > /tmp/gpg-key.asc
gpg --batch --import /tmp/gpg-key.asc
# Create combined SHA256SUMS from all downloaded artifacts (exclude existing SHA files)
cd all-installers
# Generate checksums with only filenames (no paths)
# Note: For .pkg files, we fix the version in the filename (1.x.y -> actual VERSION)
> SHA256SUMS # Clear the file
find . -type f ! -name 'SHA256SUMS*' ! -name 'ci-gradle.log' -print0 | sort -z | while IFS= read -r -d '' file; do
# Calculate checksum and extract just the filename (no path)
filename=$(basename "$file")
checksum=$(sha256sum "$file" | awk '{print $1}')
# Fix .pkg filename in checksum (macOS jpackage uses 1.x.y due to Apple requirements,
# but we want the filename to show the actual version (e.g., 0.x.y)
if [[ "$filename" =~ -1\.[0-9]+\.[0-9]+\.pkg$ ]]; then
fixed_filename=$(echo "$filename" | sed "s/-1\.\([0-9]\+\)\.\([0-9]\+\)\.pkg$/-${VERSION}.pkg/")
echo " Fixing .pkg checksum filename: $filename -> $fixed_filename"
filename="$fixed_filename"
fi
# Detect architecture from the original file basename and append arch to .pkg checksum filename
orig_base=$(basename "$file")
arch=""
if [[ "$orig_base" =~ (x86_64|x86|intel) ]]; then
arch="x86_64"
elif [[ "$orig_base" =~ (arm64|aarch64|arm) ]]; then
arch="arm64"
fi
if [ -n "$arch" ]; then
if [[ "$filename" =~ \.pkg$ ]] && [[ "$filename" != *".$arch.pkg" ]]; then
filename=$(echo "$filename" | sed -E "s/\.pkg$/.$arch.pkg/")
echo " Appending arch to checksum filename: $filename"
fi
fi
printf "%s %s\n" "$checksum" "$filename" >> SHA256SUMS
done
# Sign the combined SHA256SUMS
gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" --armor --detach-sign SHA256SUMS
# Return to the original directory
cd ..
# Get upload URL for the release
# Note: We query all releases because draft releases may not be fetchable by tag
API_URL="https://api.github.com/repos/${{ github.repository }}/releases"
echo "Fetching releases from: $API_URL"
echo "Looking for release with tag: $TAG"
releases_response=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" "$API_URL")
# Find the release matching our tag (including draft releases) - get only the first match
release_info=$(echo "$releases_response" | jq -c ".[] | select(.tag_name == \"$TAG\")" | head -n 1)
if [ -z "$release_info" ]; then
echo "ERROR: Could not find release with tag '$TAG'"
echo "Available releases:"
echo "$releases_response" | jq -r '.[] | "\(.tag_name) - \(.name) (draft: \(.draft))"' | head -10
exit 1
fi
upload_url=$(echo "$release_info" | jq -r '.upload_url' | sed -e 's/{.*//')
release_id=$(echo "$release_info" | jq -r '.id')
is_draft=$(echo "$release_info" | jq -r '.draft')
if [ -z "$upload_url" ] || [ "$upload_url" == "null" ]; then
echo "ERROR: Could not get upload URL for release"
echo "Release info:"
echo "$release_info" | jq .
exit 1
fi
echo "Found release: ID=$release_id, tag=$TAG, draft=$is_draft"
echo "Upload URL: $upload_url"
# Helper to URI-encode strings (jq required)
urlencode() { jq -nr --arg v "$1" '$v|@uri'; }
# Function to delete ALL installer assets (cleanup stale assets from previous runs)
cleanup_installer_assets() {
echo "=== Cleaning up existing installer assets ==="
local assets=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases/$release_id/assets")
# Find all assets matching installer patterns (.deb, .rpm, .msi, .pkg)
echo "$assets" | jq -r '.[] | select(.name | test("\\.(deb|rpm|msi|pkg)$")) | "\(.id)|\(.name)"' | while IFS='|' read -r asset_id asset_name; do
if [ -n "$asset_id" ] && [ "$asset_id" != "null" ]; then
echo "Deleting stale installer asset: $asset_name (ID: $asset_id)"
curl -sS -X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset_id"
fi
done
echo "=== Cleanup complete ==="
}
# Function to delete an existing asset if it exists
delete_existing_asset() {
local asset_name="$1"
# Get list of assets for this release
local assets=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases/$release_id/assets")
# Find asset with matching name
local asset_id=$(echo "$assets" | jq -r ".[] | select(.name == \"$asset_name\") | .id")
if [ -n "$asset_id" ] && [ "$asset_id" != "null" ]; then
echo "Deleting existing asset: $asset_name (ID: $asset_id)"
curl -sS -X DELETE \
-H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset_id"
echo
fi
}
# Upload function: uploads a file and sets a user-friendly asset name and label
upload_asset() {
local file_path="$1"
local desired_name="$2"
local label="$3"
# Delete existing asset if it exists (prevents 422 errors on re-runs)
delete_existing_asset "$desired_name"
# determine MIME type
mime=$(file --brief --mime-type "$file_path" || echo application/octet-stream)
enc_label=$(urlencode "$label")
# Upload via GitHub uploads API
echo "Uploading $file_path as '$desired_name' (label: $label)"
curl --fail -sS -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: $mime" \
--data-binary @"$file_path" \
"$upload_url?name=$(urlencode "$desired_name")&label=${enc_label}"
echo
}
# Clean up all old installer assets before uploading new ones
cleanup_installer_assets
# Map files to human-friendly labels and corrected asset names
while IFS= read -r -d '' f; do
rel=${f#./}
base=$(basename "$rel")
# Skip SHA256SUMS files - they will be uploaded explicitly at the end
if [[ "$base" == "SHA256SUMS" || "$base" == "SHA256SUMS.asc" ]]; then
continue
fi
# Default: upload with same name and a generic label
asset_name="$base"
label="${base}"
# Fix common naming issues and set friendly labels
case "$base" in
robocode-tank-royale-gui-*.rpm)
# Convert: name-version-release.arch.rpm -> name-version.arch.rpm (strip release)
# Example: robocode-tank-royale-gui-0.34.2-1.x86_64.rpm -> robocode-tank-royale-gui-0.34.2.x86_64.rpm
# Use perl with lookahead to only replace the '-<digits>.' that precedes the arch and .rpm
asset_name=$(echo "$base" | perl -pe 's/-\d+\.(?=[^.]+\.rpm$)/./')
# We only build one RPM (native on ubuntu-latest = x86_64)
label="GUI for Linux (rpm)"
;;
robocode-tank-royale-gui_*.deb | robocode-tank-royale-gui-*.deb)
# We only build one DEB (native on ubuntu-latest = amd64)
label="GUI for Linux (deb)"
;;
robocode-tank-royale-gui-*.msi)
label="GUI for Windows (msi)"
;;
robocode-tank-royale-gui-*.pkg)
# Fix macOS .pkg version: jpackage uses 1.x.y due to Apple requirements,
# but we want the filename to show the actual version (e.g., 0.x.y)
if [[ "$base" =~ -1\.[0-9]+\.[0-9]+\.pkg$ ]]; then
asset_name=$(echo "$base" | sed "s/-1\.\([0-9]\+\)\.\([0-9]\+\)\.pkg$/-${VERSION}.pkg/")
echo " Fixing macOS .pkg version: $base -> $asset_name"
fi
# Label for macOS (ARM build works on both Apple Silicon and Intel via Rosetta 2)
label="GUI for macOS (pkg) - Apple Silicon - arm64"
;;
robocode-tankroyale-gui-*.jar)
label="GUI (jar)"
;;
robocode-tankroyale-server-*.jar)
label="Server (jar)"
;;
*)
# Detect sample-bots archives by name inspection
lower=$(echo "$base" | tr '[:upper:]' '[:lower:]')
if [[ "$lower" == *sample* && ( "$lower" == *csharp* || "$lower" == *cs* ) ]]; then
label="Sample bots for C# (zip)"
elif [[ "$lower" == *sample* && "$lower" == *java* ]]; then
label="Sample bots for Java (zip)"
elif [[ "$lower" == *sample* && ( "$lower" == *python* || "$lower" == *py* ) ]]; then
label="Sample bots for Python (zip)"
fi
;;
esac
upload_asset "$f" "$asset_name" "$label"
done < <(find all-installers -type f -print0)
# Finally upload the combined checksums and signature
upload_asset "all-installers/SHA256SUMS" "SHA256SUMS" "SHA256 checksums"
upload_asset "all-installers/SHA256SUMS.asc" "SHA256SUMS.asc" "SHA256 checksums (GPG signature)"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
shell: bash