Files
imager/.github/workflows/build.yml
SuperKali 40ac3227d8 ci: preserve release notes during artifact uploads
Add omitBodyDuringUpdate and omitNameDuringUpdate to all upload jobs
to prevent overwriting auto-generated release notes with PR references.

- Remove generateReleaseNotes: false from upload jobs (not needed)
- Add omitBodyDuringUpdate: true to preserve release notes
- Add omitNameDuringUpdate: true to preserve release names
- Finalize job keeps omitBodyDuringUpdate but omits omitNameDuringUpdate
  to ensure release title is set correctly when publishing

This ensures 'What's Changed' section with PR references remains
visible when releases are created from tags.
2025-12-25 11:10:43 +01:00

746 lines
23 KiB
YAML

name: Build and Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
build_linux_x64:
description: 'Build Linux x64'
type: boolean
default: true
build_linux_arm64:
description: 'Build Linux ARM64'
type: boolean
default: true
build_macos:
description: 'Build macOS (x64 + ARM64)'
type: boolean
default: true
build_windows_x64:
description: 'Build Windows x64'
type: boolean
default: true
build_windows_arm64:
description: 'Build Windows ARM64'
type: boolean
default: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
NODE_VERSION: '20'
TAURI_CLI_VERSION: '^2'
# Tauri updater signing key (set in GitHub Secrets)
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
jobs:
create-release:
name: Create draft release (early)
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
release_tag: ${{ steps.relmeta.outputs.tag }}
release_name: ${{ steps.relmeta.outputs.name }}
steps:
- name: Compute release tag/name (start at v1.0.0)
id: relmeta
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
TAG="${GITHUB_REF_NAME}"
NAME="${GITHUB_REF_NAME}"
else
# Get latest release tag (if any)
LAST_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || true)
if [[ -z "$LAST_TAG" ]]; then
TAG="v1.0.0"
else
# Expect vMAJOR.MINOR.PATCH
IFS='.' read -r MAJOR MINOR PATCH <<<"${LAST_TAG#v}"
PATCH=$((PATCH + 1))
TAG="v${MAJOR}.${MINOR}.${PATCH}"
fi
NAME="$TAG"
fi
echo "tag=$TAG" >>"$GITHUB_OUTPUT"
echo "name=$NAME" >>"$GITHUB_OUTPUT"
- name: Create/Update GitHub Release (draft ${{ steps.relmeta.outputs.tag }})
uses: ncipollo/release-action@v1
with:
tag: ${{ steps.relmeta.outputs.tag }}
name: "Armbian Imager ${{ needs.create-release.outputs.release_tag }}"
commit: ${{ github.sha }}
draft: true
prerelease: false
generateReleaseNotes: true
allowUpdates: true
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
replacesArtifacts: false
build-linux-x64:
name: build-linux (x86_64-unknown-linux-gnu)
needs: [create-release]
if: ${{ github.event_name == 'push' || inputs.build_linux_x64 }}
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Set version from release tag
shell: bash
run: |
set -euo pipefail
TAG='${{ needs.create-release.outputs.release_tag }}'
VERSION="${TAG#v}"
echo "Setting version to $VERSION"
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
package.json
sed -i \
-e "s/^version = \".*\"/version = \"$VERSION\"/" \
src-tauri/Cargo.toml
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
src-tauri/tauri.conf.json
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf \
libssl-dev \
libgtk-3-dev \
squashfs-tools \
xdg-utils
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Cache cargo bin (tauri-cli)
uses: actions/cache@v4
with:
path: ~/.cargo/bin
key: cargo-bin-${{ runner.os }}-${{ runner.arch }}-stable-${{ hashFiles('**/Cargo.lock') }}
- name: Install npm dependencies
run: npm ci
- name: Build frontend
run: npm run build
- name: Install Tauri CLI (if missing)
shell: bash
run: |
if ! command -v cargo-tauri >/dev/null 2>&1; then
cargo install tauri-cli --version "${TAURI_CLI_VERSION}" --locked
fi
- name: Build Tauri app
run: cargo tauri build --bundles deb,appimage
- name: Upload artifacts to GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.create-release.outputs.release_tag }}
name: "Armbian Imager ${{ needs.create-release.outputs.release_tag }}"
draft: true
prerelease: false
allowUpdates: true
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
replacesArtifacts: false
artifacts: |
src-tauri/target/release/bundle/deb/*.deb
src-tauri/target/release/bundle/appimage/*.AppImage
src-tauri/target/release/bundle/appimage/*.AppImage.sig
build-linux-arm64:
name: build-linux (aarch64-unknown-linux-gnu)
needs: [create-release]
if: ${{ github.event_name == 'push' || inputs.build_linux_arm64 }}
runs-on: ubuntu-24.04-arm
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Set version from release tag
shell: bash
run: |
set -euo pipefail
TAG='${{ needs.create-release.outputs.release_tag }}'
VERSION="${TAG#v}"
echo "Setting version to $VERSION"
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
package.json
sed -i \
-e "s/^version = \".*\"/version = \"$VERSION\"/" \
src-tauri/Cargo.toml
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
src-tauri/tauri.conf.json
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf \
libssl-dev \
libgtk-3-dev \
squashfs-tools \
xdg-utils
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Cache cargo bin (tauri-cli)
uses: actions/cache@v4
with:
path: ~/.cargo/bin
key: cargo-bin-${{ runner.os }}-${{ runner.arch }}-stable-${{ hashFiles('**/Cargo.lock') }}
- name: Install npm dependencies
run: npm ci
- name: Build frontend
run: npm run build
- name: Install Tauri CLI (if missing)
shell: bash
run: |
if ! command -v cargo-tauri >/dev/null 2>&1; then
cargo install tauri-cli --version "${TAURI_CLI_VERSION}" --locked
fi
- name: Build Tauri app
run: cargo tauri build --bundles deb,appimage
- name: Upload artifacts to GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.create-release.outputs.release_tag }}
name: "Armbian Imager ${{ needs.create-release.outputs.release_tag }}"
draft: true
prerelease: false
allowUpdates: true
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
replacesArtifacts: false
artifacts: |
src-tauri/target/release/bundle/deb/*.deb
src-tauri/target/release/bundle/appimage/*.AppImage
src-tauri/target/release/bundle/appimage/*.AppImage.sig
build-macos:
name: build-macos (${{ matrix.target }})
needs: [create-release]
if: ${{ github.event_name == 'push' || inputs.build_macos }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-apple-darwin
arch: x64
os: macos-15
- target: aarch64-apple-darwin
arch: arm64
os: macos-latest
runs-on: ${{ matrix.os }}
permissions:
contents: write
env:
# Ad-hoc signing: allows app to run after "xattr -cr" on macOS
APPLE_SIGNING_IDENTITY: "-"
steps:
- uses: actions/checkout@v4
- name: Set version from release tag
shell: bash
run: |
set -euo pipefail
TAG='${{ needs.create-release.outputs.release_tag }}'
VERSION="${TAG#v}"
echo "Setting version to $VERSION"
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
package.json
sed -i \
-e "s/^version = \".*\"/version = \"$VERSION\"/" \
src-tauri/Cargo.toml
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
src-tauri/tauri.conf.json
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Setup Rust (add target)
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
# ensure cache separation per target
key: macos-${{ matrix.target }}
- name: Cache cargo bin (tauri-cli)
uses: actions/cache@v4
with:
path: ~/.cargo/bin
key: cargo-bin-${{ runner.os }}-${{ runner.arch }}-stable-${{ hashFiles('**/Cargo.lock') }}
- name: Install npm dependencies
run: npm ci
- name: Build frontend
run: npm run build
- name: Install Tauri CLI (if missing)
shell: bash
run: |
if ! command -v cargo-tauri >/dev/null 2>&1; then
cargo install tauri-cli --version "${TAURI_CLI_VERSION}" --locked
fi
- name: Build Tauri app (per-target output dir)
shell: bash
run: |
cargo tauri build --target "${{ matrix.target }}" --bundles dmg,app
- name: Zip .app bundle(s) and rename updater artifacts
shell: bash
env:
CARGO_TARGET_DIR: src-tauri/target/${{ matrix.target }}
run: |
set -euo pipefail
shopt -s nullglob
for app in "$CARGO_TARGET_DIR/release/bundle/macos/"*.app; do
base="$(basename "$app" .app)"
ditto -c -k --sequesterRsrc --keepParent "$app" "$CARGO_TARGET_DIR/release/bundle/macos/${base}-${{ matrix.arch }}.app.zip"
done
# Rename updater tar.gz to include arch
for tarball in "$CARGO_TARGET_DIR/release/bundle/macos/"*.tar.gz; do
base="$(basename "$tarball" .tar.gz)"
mv "$tarball" "$CARGO_TARGET_DIR/release/bundle/macos/${base}-${{ matrix.arch }}.tar.gz"
done
for sig in "$CARGO_TARGET_DIR/release/bundle/macos/"*.tar.gz.sig; do
base="$(basename "$sig" .tar.gz.sig)"
mv "$sig" "$CARGO_TARGET_DIR/release/bundle/macos/${base}-${{ matrix.arch }}.tar.gz.sig"
done
- name: Upload macOS artifacts to GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.create-release.outputs.release_tag }}
name: "Armbian Imager ${{ needs.create-release.outputs.release_tag }}"
draft: true
prerelease: false
allowUpdates: true
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
replacesArtifacts: false
artifacts: |
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*-${{ matrix.arch }}.app.zip
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*-${{ matrix.arch }}.tar.gz
src-tauri/target/${{ matrix.target }}/release/bundle/macos/*-${{ matrix.arch }}.tar.gz.sig
src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
build-windows-x64:
name: build-windows (x86_64-pc-windows-msvc)
needs: [create-release]
if: ${{ github.event_name == 'push' || inputs.build_windows_x64 }}
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Set version from release tag
shell: bash
run: |
set -euo pipefail
TAG='${{ needs.create-release.outputs.release_tag }}'
VERSION="${TAG#v}"
echo "Setting version to $VERSION"
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
package.json
sed -i \
-e "s/^version = \".*\"/version = \"$VERSION\"/" \
src-tauri/Cargo.toml
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
src-tauri/tauri.conf.json
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Cache cargo bin (tauri-cli)
uses: actions/cache@v4
with:
path: |
~/.cargo/bin
~\.cargo\bin
key: cargo-bin-${{ runner.os }}-${{ runner.arch }}-stable-${{ hashFiles('**/Cargo.lock') }}
- name: Install npm dependencies
run: npm ci
- name: Build frontend
run: npm run build
- name: Install Tauri CLI (if missing)
shell: bash
run: |
if ! command -v cargo-tauri >/dev/null 2>&1; then
cargo install tauri-cli --version "${TAURI_CLI_VERSION}" --locked
fi
- name: Build Tauri app
run: cargo tauri build --target x86_64-pc-windows-msvc --bundles msi,nsis
- name: Upload artifacts to GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.create-release.outputs.release_tag }}
name: "Armbian Imager ${{ needs.create-release.outputs.release_tag }}"
draft: true
prerelease: false
allowUpdates: true
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
replacesArtifacts: false
artifacts: |
src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi
src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe.sig
build-windows-arm64:
name: build-windows (aarch64-pc-windows-msvc)
needs: [create-release]
if: ${{ github.event_name == 'push' || inputs.build_windows_arm64 }}
runs-on: windows-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Set version from release tag
shell: bash
run: |
set -euo pipefail
TAG='${{ needs.create-release.outputs.release_tag }}'
VERSION="${TAG#v}"
echo "Setting version to $VERSION"
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
package.json
sed -i \
-e "s/^version = \".*\"/version = \"$VERSION\"/" \
src-tauri/Cargo.toml
sed -i \
-e "s/\"version\": \"[0-9.]*\"/\"version\": \"$VERSION\"/" \
src-tauri/tauri.conf.json
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-pc-windows-msvc
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
- name: Cache cargo bin (tauri-cli)
uses: actions/cache@v4
with:
path: |
~/.cargo/bin
~\.cargo\bin
key: cargo-bin-${{ runner.os }}-${{ runner.arch }}-stable-${{ hashFiles('**/Cargo.lock') }}
- name: Install npm dependencies
run: npm ci
- name: Build frontend
run: npm run build
- name: Install Tauri CLI (if missing)
shell: bash
run: |
if ! command -v cargo-tauri >/dev/null 2>&1; then
cargo install tauri-cli --version "${TAURI_CLI_VERSION}" --locked
fi
- name: Build Tauri app
run: cargo tauri build --target aarch64-pc-windows-msvc --bundles msi,nsis
- name: Upload artifacts to GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.create-release.outputs.release_tag }}
name: "Armbian Imager ${{ needs.create-release.outputs.release_tag }}"
draft: true
prerelease: false
allowUpdates: true
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
replacesArtifacts: false
artifacts: |
src-tauri/target/aarch64-pc-windows-msvc/release/bundle/msi/*.msi
src-tauri/target/aarch64-pc-windows-msvc/release/bundle/nsis/*.exe
src-tauri/target/aarch64-pc-windows-msvc/release/bundle/nsis/*.exe.sig
generate-update-manifest:
name: Generate latest.json for updater
needs:
- create-release
- build-linux-x64
- build-linux-arm64
- build-macos
- build-windows-x64
- build-windows-arm64
if: |
always() &&
(startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') &&
!contains(needs.*.result, 'failure') &&
!contains(needs.*.result, 'cancelled')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download release assets and generate latest.json
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.create-release.outputs.release_tag }}
run: |
set -euo pipefail
VERSION="${TAG#v}"
REPO="${{ github.repository }}"
BASE_URL="https://github.com/${REPO}/releases/download/${TAG}"
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Function to get signature content from release
get_sig() {
local asset_name="$1"
gh release download "$TAG" --pattern "${asset_name}.sig" --output - 2>/dev/null || echo ""
}
# Wait for assets to be available
sleep 10
# Get list of assets
ASSETS=$(gh release view "$TAG" --json assets --jq '.assets[].name')
# Initialize platforms object
declare -A PLATFORMS
# macOS x64
MAC_X64_SIG=$(get_sig "Armbian.Imager.app-x64.tar.gz" || echo "")
if [[ -n "$MAC_X64_SIG" ]]; then
PLATFORMS["darwin-x86_64"]="{\"signature\": \"${MAC_X64_SIG}\", \"url\": \"${BASE_URL}/Armbian.Imager.app-x64.tar.gz\"}"
fi
# macOS ARM64
MAC_ARM_SIG=$(get_sig "Armbian.Imager.app-arm64.tar.gz" || echo "")
if [[ -n "$MAC_ARM_SIG" ]]; then
PLATFORMS["darwin-aarch64"]="{\"signature\": \"${MAC_ARM_SIG}\", \"url\": \"${BASE_URL}/Armbian.Imager.app-arm64.tar.gz\"}"
fi
# Linux x64
LINUX_X64_SIG=$(get_sig "Armbian.Imager_${VERSION}_amd64.AppImage" || echo "")
if [[ -n "$LINUX_X64_SIG" ]]; then
PLATFORMS["linux-x86_64"]="{\"signature\": \"${LINUX_X64_SIG}\", \"url\": \"${BASE_URL}/Armbian.Imager_${VERSION}_amd64.AppImage\"}"
fi
# Linux ARM64
LINUX_ARM_SIG=$(get_sig "Armbian.Imager_${VERSION}_aarch64.AppImage" || echo "")
if [[ -n "$LINUX_ARM_SIG" ]]; then
PLATFORMS["linux-aarch64"]="{\"signature\": \"${LINUX_ARM_SIG}\", \"url\": \"${BASE_URL}/Armbian.Imager_${VERSION}_aarch64.AppImage\"}"
fi
# Windows x64
WIN_X64_SIG=$(get_sig "Armbian.Imager_${VERSION}_x64-setup.exe" || echo "")
if [[ -n "$WIN_X64_SIG" ]]; then
PLATFORMS["windows-x86_64"]="{\"signature\": \"${WIN_X64_SIG}\", \"url\": \"${BASE_URL}/Armbian.Imager_${VERSION}_x64-setup.exe\"}"
fi
# Windows ARM64
WIN_ARM_SIG=$(get_sig "Armbian.Imager_${VERSION}_arm64-setup.exe" || echo "")
if [[ -n "$WIN_ARM_SIG" ]]; then
PLATFORMS["windows-aarch64"]="{\"signature\": \"${WIN_ARM_SIG}\", \"url\": \"${BASE_URL}/Armbian.Imager_${VERSION}_arm64-setup.exe\"}"
fi
# Build platforms JSON
PLATFORMS_JSON="{"
first=true
for key in "${!PLATFORMS[@]}"; do
if [[ "$first" == "true" ]]; then
first=false
else
PLATFORMS_JSON+=","
fi
PLATFORMS_JSON+="\"${key}\": ${PLATFORMS[$key]}"
done
PLATFORMS_JSON+="}"
# Generate latest.json
cat > latest.json <<EOF
{
"version": "${VERSION}",
"pub_date": "${PUB_DATE}",
"platforms": ${PLATFORMS_JSON}
}
EOF
# Pretty print for debugging
cat latest.json | jq .
- name: Upload latest.json to release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.create-release.outputs.release_tag }}
allowUpdates: true
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
artifacts: latest.json
finalize-release:
name: Publish release (draft -> false) + cleanup
needs:
- create-release
- build-linux-x64
- build-linux-arm64
- build-macos
- build-windows-x64
- build-windows-arm64
- generate-update-manifest
if: |
always() &&
(startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') &&
!contains(needs.*.result, 'failure') &&
!contains(needs.*.result, 'cancelled')
runs-on: ubuntu-latest
permissions:
contents: write
actions: write
steps:
- name: Publish GitHub Release (set draft=false)
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.create-release.outputs.release_tag }}
name: "Armbian Imager ${{ needs.create-release.outputs.release_tag }}"
allowUpdates: true
omitBodyDuringUpdate: true
draft: false
prerelease: false
- name: Delete old workflow runs
uses: Mattraks/delete-workflow-runs@v2
with:
token: ${{ github.token }}
repository: ${{ github.repository }}
retain_days: 7
keep_minimum_runs: 5