293 lines
11 KiB
YAML
293 lines
11 KiB
YAML
name: OneClient Release Build
|
|
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
CARGO_PACKAGER_SIGN_PRIVATE_KEY: ${{ secrets.CARGO_PACKAGER_SIGN_PRIVATE_KEY }}
|
|
CARGO_PACKAGER_SIGN_PRIVATE_KEY_PASSWORD: ${{ secrets.CARGO_PACKAGER_SIGN_PRIVATE_KEY_PASSWORD }}
|
|
|
|
jobs:
|
|
prepare:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
outputs:
|
|
version: ${{ steps.version.outputs.version }}
|
|
tag: ${{ steps.version.outputs.tag }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Resolve version from Cargo.toml
|
|
id: version
|
|
shell: bash
|
|
run: |
|
|
VERSION=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/')
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "tag=oneclient-$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Create/ensure draft release
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ steps.version.outputs.tag }}
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
if ! gh release view "$TAG" >/dev/null 2>&1; then
|
|
gh release create "$TAG" \
|
|
--draft \
|
|
--title "OneClient $VERSION" \
|
|
--notes "Automated release for OneClient $VERSION."
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
package:
|
|
needs: prepare
|
|
permissions:
|
|
contents: write
|
|
# Required so SignPath can download the uploaded build artifact.
|
|
actions: read
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- platform: macos-latest
|
|
target: aarch64-apple-darwin
|
|
os_key: darwin
|
|
arch: aarch64
|
|
formats: app,dmg
|
|
- platform: macos-latest
|
|
target: x86_64-apple-darwin
|
|
os_key: darwin
|
|
arch: x86_64
|
|
formats: app,dmg
|
|
- platform: ubuntu-22.04
|
|
target: ''
|
|
os_key: linux
|
|
arch: x86_64
|
|
formats: deb,appimage
|
|
- platform: windows-latest
|
|
target: ''
|
|
os_key: windows
|
|
arch: x86_64
|
|
formats: nsis
|
|
|
|
runs-on: ${{ matrix.platform }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Setup Rust
|
|
uses: ./.github/actions/setup-rust
|
|
with:
|
|
target: ${{ matrix.target }}
|
|
save-cache: true
|
|
restore-cache: true
|
|
|
|
- name: (Linux) Install system dependencies
|
|
if: matrix.platform == 'ubuntu-22.04'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
build-essential cmake pkg-config clang python3 \
|
|
libfontconfig-dev libfreetype-dev \
|
|
libgl1-mesa-dev libx11-dev libxcursor-dev libxi-dev libxrandr-dev \
|
|
libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libdbus-1-dev \
|
|
libfuse2
|
|
|
|
- name: Install cargo-packager
|
|
shell: bash
|
|
run: cargo install cargo-packager --version 0.11.8 --locked
|
|
|
|
- name: Import Apple Developer certificate
|
|
if: startsWith(matrix.platform, 'macos')
|
|
env:
|
|
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
|
run: |
|
|
echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12
|
|
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
security default-keychain -s build.keychain
|
|
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
|
security set-keychain-settings -t 3600 -u build.keychain
|
|
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
|
CERT_ID=$(security find-identity -v -p codesigning build.keychain | awk -F'"' '/Developer ID Application/ {print $2; exit}')
|
|
if [ -z "$CERT_ID" ]; then
|
|
echo "::error::No Developer ID Application certificate found in keychain."
|
|
exit 1
|
|
fi
|
|
echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> "$GITHUB_ENV"
|
|
|
|
- name: Build release binary
|
|
shell: bash
|
|
run: |
|
|
if [ -n "${{ matrix.target }}" ]; then
|
|
cargo build --release --bin oneclient_app --target ${{ matrix.target }}
|
|
else
|
|
cargo build --release --bin oneclient_app
|
|
fi
|
|
|
|
- name: Package
|
|
shell: bash
|
|
env:
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
run: |
|
|
ARGS="--release --formats ${{ matrix.formats }}"
|
|
if [ -n "${{ matrix.target }}" ]; then
|
|
ARGS="$ARGS --target ${{ matrix.target }}"
|
|
fi
|
|
cargo packager $ARGS --verbose
|
|
|
|
- name: Resolve packager output dir
|
|
id: outdir
|
|
shell: bash
|
|
run: |
|
|
if [ -n "${{ matrix.target }}" ]; then
|
|
echo "dir=target/${{ matrix.target }}/release" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "dir=target/release" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: (Windows) Locate installer
|
|
if: matrix.platform == 'windows-latest'
|
|
id: win_installer
|
|
shell: bash
|
|
run: echo "path=$(ls ${{ steps.outdir.outputs.dir }}/*-setup.exe | head -n1)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: (Windows) Upload unsigned installer artifact
|
|
if: matrix.platform == 'windows-latest'
|
|
id: win_upload_unsigned
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: oneclient-windows-installer-unsigned
|
|
path: ${{ steps.win_installer.outputs.path }}
|
|
|
|
- name: (Windows) Submit SignPath signing request
|
|
if: matrix.platform == 'windows-latest'
|
|
uses: signpath/github-action-submit-signing-request@v1
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
|
project-slug: onelauncher
|
|
signing-policy-slug: test-signing
|
|
artifact-configuration-slug: onelauncher-installer
|
|
github-artifact-id: ${{ steps.win_upload_unsigned.outputs.artifact-id }}
|
|
wait-for-completion: true
|
|
output-artifact-directory: signed
|
|
|
|
- name: (Windows) Replace installer with signed one + recompute updater sig
|
|
if: matrix.platform == 'windows-latest'
|
|
shell: bash
|
|
run: |
|
|
signed="$(ls signed/*-setup.exe | head -n1)"
|
|
# Overwrite the unsigned installer in the packager out dir with the signed exe.
|
|
cp "$signed" "${{ steps.win_installer.outputs.path }}"
|
|
# Recompute the updater signature over the SIGNED exe.
|
|
cargo packager signer sign \
|
|
--private-key "$CARGO_PACKAGER_SIGN_PRIVATE_KEY" \
|
|
--password "$CARGO_PACKAGER_SIGN_PRIVATE_KEY_PASSWORD" \
|
|
"${{ steps.win_installer.outputs.path }}"
|
|
|
|
- name: Upload installers and build manifest fragment
|
|
shell: bash
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ needs.prepare.outputs.tag }}
|
|
OUT_DIR: ${{ steps.outdir.outputs.dir }}
|
|
OS_KEY: ${{ matrix.os_key }}
|
|
ARCH: ${{ matrix.arch }}
|
|
VERSION: ${{ needs.prepare.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
|
|
# Identify the single updater artifact for this platform (the file with a
|
|
# sibling .sig): -setup.exe (nsis), *.app.tar.gz (app), *.AppImage.tar.gz (appimage).
|
|
updater_file=""; fmt=""; ext=""
|
|
case "$OS_KEY" in
|
|
windows) updater_file="$(ls "$OUT_DIR"/*-setup.exe | head -n1)"; fmt="nsis"; ext="exe" ;;
|
|
darwin) updater_file="$(ls "$OUT_DIR"/*.app.tar.gz | head -n1)"; fmt="app"; ext="app.tar.gz" ;;
|
|
linux) updater_file="$(ls "$OUT_DIR"/*.AppImage.tar.gz 2>/dev/null | head -n1)"; fmt="appimage"; ext="AppImage.tar.gz"
|
|
if [ -z "$updater_file" ]; then updater_file="$(ls "$OUT_DIR"/*.AppImage | head -n1)"; ext="AppImage"; fi ;;
|
|
esac
|
|
|
|
# The updater bundle isn't always arch-tagged by cargo-packager, so the two
|
|
# macOS legs would collide on the release. Rename the updater artifact + its .sig
|
|
# to an arch-unique name before upload, and point the manifest at that name.
|
|
unique="OneClient_${VERSION}_${OS_KEY}_${ARCH}.${ext}"
|
|
cp "$updater_file" "$OUT_DIR/$unique"
|
|
cp "$updater_file.sig" "$OUT_DIR/$unique.sig"
|
|
|
|
# Upload human-facing installers (dmg/deb/AppImage/signed exe) + the renamed
|
|
# updater bundle and its .sig. Skip the un-renamed updater bundle to avoid clashes.
|
|
for f in "$OUT_DIR"/*.dmg "$OUT_DIR"/*.deb "$OUT_DIR"/*.AppImage "$OUT_DIR"/*-setup.exe "$OUT_DIR/$unique" "$OUT_DIR/$unique.sig"; do
|
|
[ -e "$f" ] || continue
|
|
gh release upload "$TAG" "$f" --clobber
|
|
done
|
|
|
|
base="$unique"
|
|
mkdir -p fragments
|
|
SIG="$(cat "$updater_file.sig")" \
|
|
URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/${base}" \
|
|
FMT="$fmt" \
|
|
KEY="${OS_KEY}-${ARCH}" \
|
|
python3 - <<'PY'
|
|
import json, os
|
|
frag = {
|
|
os.environ["KEY"]: {
|
|
"signature": os.environ["SIG"],
|
|
"url": os.environ["URL"],
|
|
"format": os.environ["FMT"],
|
|
}
|
|
}
|
|
with open(f"fragments/{os.environ['KEY']}.json", "w") as f:
|
|
json.dump(frag, f)
|
|
PY
|
|
|
|
- name: Upload manifest fragment
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: manifest-${{ matrix.os_key }}-${{ matrix.arch }}
|
|
path: fragments/*.json
|
|
|
|
manifest:
|
|
needs: [prepare, package]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Download all manifest fragments
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
pattern: manifest-*
|
|
path: fragments
|
|
merge-multiple: true
|
|
|
|
- name: Assemble latest.json
|
|
env:
|
|
VERSION: ${{ needs.prepare.outputs.version }}
|
|
run: |
|
|
python3 - <<'PY'
|
|
import json, glob, os, datetime
|
|
platforms = {}
|
|
for path in glob.glob("fragments/*.json"):
|
|
with open(path) as f:
|
|
platforms.update(json.load(f))
|
|
manifest = {
|
|
"version": os.environ["VERSION"],
|
|
"pub_date": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"platforms": platforms,
|
|
}
|
|
with open("latest.json", "w") as f:
|
|
json.dump(manifest, f, indent=2)
|
|
print(json.dumps(manifest, indent=2))
|
|
PY
|
|
|
|
- name: Upload latest.json to release
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ needs.prepare.outputs.tag }}
|
|
run: gh release upload "$TAG" latest.json --clobber
|