diff --git a/.github/workflows/release-desktop-rpm.yml b/.github/workflows/release-desktop-rpm.yml new file mode 100644 index 00000000..8befc722 --- /dev/null +++ b/.github/workflows/release-desktop-rpm.yml @@ -0,0 +1,296 @@ +name: Build Desktop RPM + +on: + push: + tags: ['v*.*.*'] + workflow_dispatch: + inputs: + draft: + description: 'Create as draft release' + required: false + default: true + type: boolean + +permissions: + contents: write + +concurrency: + group: release-desktop-rpm-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - platform: ubuntu-22.04 + target_triple: x86_64-unknown-linux-gnu + builder_args: --linux rpm --x64 + label: Linux-x64 + arch: x64 + - platform: ubuntu-22.04-arm + target_triple: aarch64-unknown-linux-gnu + builder_args: --linux rpm --arm64 + label: Linux-ARM64 + arch: arm64 + + runs-on: ${{ matrix.platform }} + name: Build RPM (${{ matrix.label }}) + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Read version + id: version + shell: bash + run: | + VERSION=$(node -p "require('./desktop/package.json').version") + echo "value=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Validate tag matches version + if: github.event_name == 'push' + shell: bash + run: | + EXPECTED_TAG="v${{ steps.version.outputs.value }}" + if [ "${GITHUB_REF_NAME}" != "$EXPECTED_TAG" ]; then + echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${EXPECTED_TAG}" + exit 1 + fi + + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential curl wget file libfuse2 rpm + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install root dependencies + run: bun install + + - name: Install desktop dependencies + working-directory: desktop + run: bun install + + - name: Install adapter dependencies + working-directory: adapters + run: bun install + + - name: Build sidecars + working-directory: desktop + env: + BUN_INSTALL_CACHE_DIR: ${{ runner.temp }}/bun-install-cache + SIDECAR_TARGET_TRIPLE: ${{ matrix.target_triple }} + run: bun run build:sidecars + + - name: Build renderer and Electron bundles + working-directory: desktop + run: | + bun run build + bun run build:electron + + - name: Build Electron release artifacts (RPM) + working-directory: desktop + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CSC_IDENTITY_AUTO_DISCOVERY: 'false' + run: node ./node_modules/electron-builder/out/cli/cli.js ${{ matrix.builder_args }} --publish never + + - name: Validate matrix release asset set + shell: bash + working-directory: desktop/build-artifacts/electron + env: + APP_VERSION: ${{ steps.version.outputs.value }} + run: | + case "${{ matrix.label }}" in + Linux-x64) + expected=( + "Claude-Code-Haha-${APP_VERSION}-linux-x86_64.rpm" + "latest-linux.yml" + ) + ;; + Linux-ARM64) + expected=( + "Claude-Code-Haha-${APP_VERSION}-linux-aarch64.rpm" + "latest-linux-arm64.yml" + ) + ;; + *) + echo "::error::No expected asset list for matrix label ${{ matrix.label }}" + exit 1 + ;; + esac + + missing=() + for file in "${expected[@]}"; do + [ -f "$file" ] || missing+=("$file") + done + if [ "${#missing[@]}" -gt 0 ]; then + printf '::error::Missing release assets for %s: %s\n' "${{ matrix.label }}" "${missing[*]}" + exit 1 + fi + + - name: Namespace update metadata assets + shell: bash + working-directory: desktop/build-artifacts/electron + run: | + shopt -s nullglob + for file in latest*.yml; do + mv "$file" "${file%.yml}-${{ matrix.label }}.yml" + done + + - name: Upload update metadata for merge + uses: actions/upload-artifact@v4 + with: + name: desktop-update-metadata-rpm-${{ matrix.label }} + path: desktop/build-artifacts/electron/latest*.yml + if-no-files-found: ignore + + - name: Upload release artifacts for final publish + uses: actions/upload-artifact@v4 + with: + name: desktop-release-artifacts-rpm-${{ matrix.label }} + path: | + desktop/build-artifacts/electron/*.rpm + desktop/build-artifacts/electron/*.yml + if-no-files-found: error + + publish-release: + needs: build + runs-on: ubuntu-latest + if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.draft == false) + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Read version + id: version + shell: bash + run: | + VERSION=$(node -p "require('./desktop/package.json').version") + echo "value=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Validate tag matches version + shell: bash + run: | + EXPECTED_TAG="v${{ steps.version.outputs.value }}" + if [ "${GITHUB_REF_NAME}" != "$EXPECTED_TAG" ]; then + echo "::error::Tag ${GITHUB_REF_NAME} does not match app version ${EXPECTED_TAG}" + exit 1 + fi + + - name: Load release notes + id: release_notes + shell: bash + run: | + NOTES_FILE="release-notes/v${{ steps.version.outputs.value }}.md" + if [ ! -f "$NOTES_FILE" ]; then + echo "::error::Missing release notes file: $NOTES_FILE" + exit 1 + fi + { + echo 'body<<__RELEASE_NOTES__' + cat "$NOTES_FILE" + echo + echo '__RELEASE_NOTES__' + } >> "$GITHUB_OUTPUT" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install root dependencies + run: bun install + + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + pattern: desktop-release-artifacts-rpm-* + path: artifacts/release-assets + merge-multiple: true + + - name: Validate complete release asset set + shell: bash + env: + APP_VERSION: ${{ steps.version.outputs.value }} + run: | + expected=( + "Claude-Code-Haha-${APP_VERSION}-linux-x86_64.rpm" + "Claude-Code-Haha-${APP_VERSION}-linux-aarch64.rpm" + ) + + missing=() + for file in "${expected[@]}"; do + [ -f "artifacts/release-assets/$file" ] || missing+=("$file") + done + if [ "${#missing[@]}" -gt 0 ]; then + printf '::error::Missing complete release assets: %s\n' "${missing[*]}" + exit 1 + fi + + - name: Download update metadata artifacts + uses: actions/download-artifact@v4 + with: + pattern: desktop-update-metadata-rpm-* + path: artifacts/update-metadata + merge-multiple: true + + - name: Merge standard update metadata + run: bun run scripts/release-update-metadata.ts --metadata-dir artifacts/update-metadata --out-dir artifacts/update-metadata-standard + + - name: Validate standard update metadata set + shell: bash + run: | + expected=( + "latest-linux.yml" + "latest-linux-arm64.yml" + ) + missing=() + for file in "${expected[@]}"; do + [ -f "artifacts/update-metadata-standard/$file" ] || missing+=("$file") + done + if [ "${#missing[@]}" -gt 0 ]; then + printf '::error::Missing standard update metadata: %s\n' "${missing[*]}" + exit 1 + fi + + - name: Publish RPM release assets + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.value }} + name: Claude Code Haha v${{ steps.version.outputs.value }} (RPM) + body: | + ## RPM Packages for Linux + + This release includes RPM packages for Linux systems. + + ### Installation + + **Fedora/RHEL/CentOS (x86_64):** + ```bash + sudo dnf install ./Claude-Code-Haha-${{ steps.version.outputs.value }}-linux-x86_64.rpm + ``` + + **For ARM64 systems (aarch64):** + ```bash + sudo dnf install ./Claude-Code-Haha-${{ steps.version.outputs.value }}-linux-aarch64.rpm + ``` + + ${{ steps.release_notes.outputs.body }} + draft: false + prerelease: false + fail_on_unmatched_files: true + files: | + artifacts/release-assets/**/*.rpm + artifacts/update-metadata-standard/*.yml