Build and Push Electron App #85
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Push Electron App | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| build_type: | |
| description: "Platform to build for" | |
| required: true | |
| default: "all" | |
| type: choice | |
| options: | |
| - all | |
| - windows | |
| - linux | |
| - macos | |
| artifact_destination: | |
| description: "What to do with the built app" | |
| required: true | |
| default: "file" | |
| type: choice | |
| options: | |
| - none | |
| - file | |
| - release | |
| - submit | |
| jobs: | |
| build-windows: | |
| runs-on: windows-latest | |
| if: (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == '') && github.event.inputs.artifact_destination != 'submit' | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: | | |
| $maxAttempts = 3 | |
| $attempt = 1 | |
| while ($attempt -le $maxAttempts) { | |
| try { | |
| npm ci | |
| break | |
| } catch { | |
| if ($attempt -eq $maxAttempts) { | |
| Write-Error "npm ci failed after $maxAttempts attempts" | |
| exit 1 | |
| } | |
| Start-Sleep -Seconds 10 | |
| $attempt++ | |
| } | |
| } | |
| - name: Get version | |
| id: package-version | |
| run: | | |
| $VERSION = (Get-Content package.json | ConvertFrom-Json).version | |
| echo "version=$VERSION" >> $env:GITHUB_OUTPUT | |
| - name: Build Windows (All Architectures) | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: npm run build && npx electron-builder --win --x64 --ia32 | |
| - name: Upload Windows x64 NSIS Installer | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_windows_x64_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_windows_x64_nsis | |
| path: release/termix_windows_x64_nsis.exe | |
| retention-days: 30 | |
| - name: Upload Windows ia32 NSIS Installer | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_windows_ia32_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_windows_ia32_nsis | |
| path: release/termix_windows_ia32_nsis.exe | |
| retention-days: 30 | |
| - name: Upload Windows x64 MSI Installer | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_windows_x64_msi.msi') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_windows_x64_msi | |
| path: release/termix_windows_x64_msi.msi | |
| retention-days: 30 | |
| - name: Upload Windows ia32 MSI Installer | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_windows_ia32_msi.msi') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_windows_ia32_msi | |
| path: release/termix_windows_ia32_msi.msi | |
| retention-days: 30 | |
| - name: Create Windows x64 Portable zip | |
| if: hashFiles('release/win-unpacked/*') != '' | |
| run: | | |
| Compress-Archive -Path "release\win-unpacked\*" -DestinationPath "termix_windows_x64_portable.zip" | |
| - name: Create Windows ia32 Portable zip | |
| if: hashFiles('release/win-ia32-unpacked/*') != '' | |
| run: | | |
| Compress-Archive -Path "release\win-ia32-unpacked\*" -DestinationPath "termix_windows_ia32_portable.zip" | |
| - name: Upload Windows x64 Portable | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('termix_windows_x64_portable.zip') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_windows_x64_portable | |
| path: termix_windows_x64_portable.zip | |
| retention-days: 30 | |
| - name: Upload Windows ia32 Portable | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('termix_windows_ia32_portable.zip') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_windows_ia32_portable | |
| path: termix_windows_ia32_portable.zip | |
| retention-days: 30 | |
| build-linux: | |
| runs-on: blacksmith-4vcpu-ubuntu-2404 | |
| if: (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == '') && github.event.inputs.artifact_destination != 'submit' | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| - name: Install system dependencies for AppImage | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libfuse2 | |
| - name: Install dependencies | |
| run: | | |
| for i in 1 2 3; | |
| do | |
| if npm ci; then | |
| break | |
| else | |
| if [ $i -eq 3 ]; then | |
| exit 1 | |
| fi | |
| sleep 10 | |
| fi | |
| done | |
| npm install --force @rollup/rollup-linux-x64-gnu | |
| npm install --force @rollup/rollup-linux-arm64-gnu | |
| npm install --force @rollup/rollup-linux-arm-gnueabihf | |
| - name: Build Linux x64 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DEBUG: electron-builder | |
| run: npm run build && npx electron-builder --linux --x64 | |
| - name: Build Linux arm64 and armv7l | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: npx electron-builder --linux --arm64 --armv7l | |
| - name: Rename Linux artifacts for consistency | |
| run: | | |
| cd release | |
| if [ -f "termix_linux_amd64_deb.deb" ]; then | |
| mv "termix_linux_amd64_deb.deb" "termix_linux_x64_deb.deb" | |
| fi | |
| if [ -f "termix_linux_x86_64_appimage.AppImage" ]; then | |
| mv "termix_linux_x86_64_appimage.AppImage" "termix_linux_x64_appimage.AppImage" | |
| fi | |
| cd .. | |
| - name: Upload Linux x64 AppImage | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_x64_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_x64_appimage | |
| path: release/termix_linux_x64_appimage.AppImage | |
| retention-days: 30 | |
| - name: Upload Linux arm64 AppImage | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_arm64_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_arm64_appimage | |
| path: release/termix_linux_arm64_appimage.AppImage | |
| retention-days: 30 | |
| - name: Upload Linux armv7l AppImage | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_armv7l_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_armv7l_appimage | |
| path: release/termix_linux_armv7l_appimage.AppImage | |
| retention-days: 30 | |
| - name: Upload Linux x64 DEB | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_x64_deb.deb') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_x64_deb | |
| path: release/termix_linux_x64_deb.deb | |
| retention-days: 30 | |
| - name: Upload Linux arm64 DEB | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_arm64_deb.deb') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_arm64_deb | |
| path: release/termix_linux_arm64_deb.deb | |
| retention-days: 30 | |
| - name: Upload Linux armv7l DEB | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_armv7l_deb.deb') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_armv7l_deb | |
| path: release/termix_linux_armv7l_deb.deb | |
| retention-days: 30 | |
| - name: Upload Linux x64 tar.gz | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_x64_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_x64_portable | |
| path: release/termix_linux_x64_portable.tar.gz | |
| retention-days: 30 | |
| - name: Upload Linux arm64 tar.gz | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_arm64_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_arm64_portable | |
| path: release/termix_linux_arm64_portable.tar.gz | |
| retention-days: 30 | |
| - name: Upload Linux armv7l tar.gz | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_armv7l_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_armv7l_portable | |
| path: release/termix_linux_armv7l_portable.tar.gz | |
| retention-days: 30 | |
| - name: Install Flatpak builder and dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y flatpak flatpak-builder imagemagick | |
| - name: Add Flathub repository | |
| run: | | |
| sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo | |
| - name: Install Flatpak runtime and SDK | |
| run: | | |
| sudo flatpak install -y flathub org.freedesktop.Platform//24.08 | |
| sudo flatpak install -y flathub org.freedesktop.Sdk//24.08 | |
| sudo flatpak install -y flathub org.electronjs.Electron2.BaseApp//24.08 | |
| - name: Get version for Flatpak | |
| id: flatpak-version | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| RELEASE_DATE=$(date +%Y-%m-%d) | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT | |
| - name: Prepare Flatpak files | |
| run: | | |
| VERSION="${{ steps.flatpak-version.outputs.version }}" | |
| RELEASE_DATE="${{ steps.flatpak-version.outputs.release_date }}" | |
| CHECKSUM_X64=$(sha256sum "release/termix_linux_x64_appimage.AppImage" | awk '{print $1}') | |
| CHECKSUM_ARM64=$(sha256sum "release/termix_linux_arm64_appimage.AppImage" | awk '{print $1}') | |
| mkdir -p flatpak-build | |
| cp flatpak/com.karmaa.termix.yml flatpak-build/ | |
| cp flatpak/com.karmaa.termix.desktop flatpak-build/ | |
| cp flatpak/com.karmaa.termix.metainfo.xml flatpak-build/ | |
| cp public/icon.svg flatpak-build/com.karmaa.termix.svg | |
| convert public/icon.png -resize 256x256 flatpak-build/icon-256.png | |
| convert public/icon.png -resize 128x128 flatpak-build/icon-128.png | |
| cd flatpak-build | |
| sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_x64_appimage.AppImage|file://$(realpath ../release/termix_linux_x64_appimage.AppImage)|g" com.karmaa.termix.yml | |
| sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_arm64_appimage.AppImage|file://$(realpath ../release/termix_linux_arm64_appimage.AppImage)|g" com.karmaa.termix.yml | |
| sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" com.karmaa.termix.yml | |
| sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" com.karmaa.termix.yml | |
| sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" com.karmaa.termix.metainfo.xml | |
| sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" com.karmaa.termix.metainfo.xml | |
| - name: Build Flatpak bundle | |
| run: | | |
| cd flatpak-build | |
| flatpak-builder --repo=repo --force-clean --disable-rofiles-fuse build-dir com.karmaa.termix.yml | |
| # Determine the architecture | |
| ARCH=$(uname -m) | |
| if [ "$ARCH" = "x86_64" ]; then | |
| FLATPAK_ARCH="x86_64" | |
| elif [ "$ARCH" = "aarch64" ]; then | |
| FLATPAK_ARCH="aarch64" | |
| else | |
| FLATPAK_ARCH="$ARCH" | |
| fi | |
| # Build bundle for the current architecture | |
| flatpak build-bundle repo ../release/termix_linux_flatpak.flatpak com.karmaa.termix --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo | |
| - name: Create flatpakref file | |
| run: | | |
| VERSION="${{ steps.flatpak-version.outputs.version }}" | |
| cp flatpak/com.karmaa.termix.flatpakref release/ | |
| sed -i "s|VERSION_PLACEHOLDER|release-${VERSION}-tag|g" release/com.karmaa.termix.flatpakref | |
| - name: Upload Flatpak bundle | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_linux_flatpak.flatpak') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_flatpak | |
| path: release/termix_linux_flatpak.flatpak | |
| retention-days: 30 | |
| - name: Upload Flatpakref | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/com.karmaa.termix.flatpakref') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_linux_flatpakref | |
| path: release/com.karmaa.termix.flatpakref | |
| retention-days: 30 | |
| build-macos: | |
| runs-on: macos-latest | |
| if: (github.event.inputs.build_type == 'macos' || github.event.inputs.build_type == 'all') && github.event.inputs.artifact_destination != 'submit' | |
| needs: [] | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: | | |
| for i in 1 2 3; | |
| do | |
| if npm ci; then | |
| break | |
| else | |
| if [ $i -eq 3 ]; then | |
| exit 1 | |
| fi | |
| sleep 10 | |
| fi | |
| done | |
| npm install --force @rollup/rollup-darwin-arm64 | |
| npm install dmg-license | |
| - name: Check for Code Signing Certificates | |
| id: check_certs | |
| run: | | |
| if [ -n "${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.MAC_P12_PASSWORD }}" ]; then | |
| echo "has_certs=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Import Code Signing Certificates | |
| if: steps.check_certs.outputs.has_certs == 'true' | |
| env: | |
| MAC_BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }} | |
| MAC_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.MAC_INSTALLER_CERTIFICATE_BASE64 }} | |
| MAC_P12_PASSWORD: ${{ secrets.MAC_P12_PASSWORD }} | |
| MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }} | |
| run: | | |
| APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12 | |
| INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH | |
| if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then | |
| echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH | |
| fi | |
| security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| if [ -f "$INSTALLER_CERT_PATH" ]; then | |
| security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| fi | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| security find-identity -v -p codesigning $KEYCHAIN_PATH | |
| - name: Build macOS App Store Package | |
| if: steps.check_certs.outputs.has_certs == 'true' | |
| env: | |
| ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| CURRENT_VERSION=$(node -p "require('./package.json').version") | |
| BUILD_VERSION="${{ github.run_number }}" | |
| npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION" | |
| - name: Clean up MAS keychain before DMG build | |
| if: steps.check_certs.outputs.has_certs == 'true' | |
| run: | | |
| security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true | |
| - name: Check for Developer ID Certificates | |
| id: check_dev_id_certs | |
| run: | | |
| if [ -n "${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.DEVELOPER_ID_P12_PASSWORD }}" ]; then | |
| echo "has_dev_id_certs=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Import Developer ID Certificates | |
| if: steps.check_dev_id_certs.outputs.has_dev_id_certs == 'true' | |
| env: | |
| DEVELOPER_ID_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_CERTIFICATE_BASE64 }} | |
| DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64 }} | |
| DEVELOPER_ID_P12_PASSWORD: ${{ secrets.DEVELOPER_ID_P12_PASSWORD }} | |
| MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }} | |
| run: | | |
| DEV_CERT_PATH=$RUNNER_TEMP/dev_certificate.p12 | |
| DEV_INSTALLER_CERT_PATH=$RUNNER_TEMP/dev_installer_certificate.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/dev-signing.keychain-db | |
| echo -n "$DEVELOPER_ID_CERTIFICATE_BASE64" | base64 --decode -o $DEV_CERT_PATH | |
| if [ -n "$DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64" ]; then | |
| echo -n "$DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $DEV_INSTALLER_CERT_PATH | |
| fi | |
| security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $DEV_CERT_PATH -P "$DEVELOPER_ID_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| if [ -f "$DEV_INSTALLER_CERT_PATH" ]; then | |
| security import $DEV_INSTALLER_CERT_PATH -P "$DEVELOPER_ID_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| fi | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| security find-identity -v -p codesigning $KEYCHAIN_PATH | |
| - name: Build macOS DMG | |
| env: | |
| ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| if [ "${{ steps.check_certs.outputs.has_certs }}" != "true" ]; then | |
| npm run build | |
| fi | |
| export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}" | |
| npx electron-builder --mac dmg --universal --x64 --arm64 --publish never | |
| - name: Upload macOS MAS PKG | |
| if: steps.check_certs.outputs.has_certs == 'true' && hashFiles('release/termix_macos_universal_mas.pkg') != '' && (github.event.inputs.artifact_destination == 'file' || github.event.inputs.artifact_destination == 'release' || github.event.inputs.artifact_destination == 'submit') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: termix_macos_universal_mas | |
| path: release/termix_macos_universal_mas.pkg | |
| retention-days: 30 | |
| if-no-files-found: warn | |
| - name: Upload macOS Universal DMG | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_macos_universal_dmg | |
| path: release/termix_macos_universal_dmg.dmg | |
| retention-days: 30 | |
| - name: Upload macOS x64 DMG | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_macos_x64_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_macos_x64_dmg | |
| path: release/termix_macos_x64_dmg.dmg | |
| retention-days: 30 | |
| - name: Upload macOS arm64 DMG | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('release/termix_macos_arm64_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none' | |
| with: | |
| name: termix_macos_arm64_dmg | |
| path: release/termix_macos_arm64_dmg.dmg | |
| retention-days: 30 | |
| - name: Get version for Homebrew | |
| id: homebrew-version | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| - name: Generate Homebrew Cask | |
| if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && (github.event.inputs.artifact_destination == 'file' || github.event.inputs.artifact_destination == 'release') | |
| run: | | |
| VERSION="${{ steps.homebrew-version.outputs.version }}" | |
| DMG_PATH="release/termix_macos_universal_dmg.dmg" | |
| CHECKSUM=$(shasum -a 256 "$DMG_PATH" | awk '{print $1}') | |
| mkdir -p homebrew-generated | |
| cp Casks/termix.rb homebrew-generated/termix.rb | |
| sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-generated/termix.rb | |
| sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-generated/termix.rb | |
| sed -i '' "s|version \".*\"|version \"$VERSION\"|g" homebrew-generated/termix.rb | |
| sed -i '' "s|sha256 \".*\"|sha256 \"$CHECKSUM\"|g" homebrew-generated/termix.rb | |
| sed -i '' "s|release-[0-9.]*-tag|release-$VERSION-tag|g" homebrew-generated/termix.rb | |
| - name: Upload Homebrew Cask as artifact | |
| uses: actions/upload-artifact@v4 | |
| if: hashFiles('homebrew-generated/termix.rb') != '' && github.event.inputs.artifact_destination == 'file' | |
| with: | |
| name: termix_macos_homebrew_cask | |
| path: homebrew-generated/termix.rb | |
| retention-days: 30 | |
| - name: Upload Homebrew Cask to release | |
| if: hashFiles('homebrew-generated/termix.rb') != '' && github.event.inputs.artifact_destination == 'release' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ steps.homebrew-version.outputs.version }}" | |
| RELEASE_TAG="release-$VERSION-tag" | |
| gh release list --repo ${{ github.repository }} --limit 100 | grep -q "$RELEASE_TAG" || { | |
| echo "Release $RELEASE_TAG not found" | |
| exit 1 | |
| } | |
| gh release upload "$RELEASE_TAG" homebrew-generated/termix.rb --repo ${{ github.repository }} --clobber | |
| - name: Clean up keychains | |
| if: always() | |
| run: | | |
| security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true | |
| security delete-keychain $RUNNER_TEMP/dev-signing.keychain-db || true | |
| submit-to-chocolatey: | |
| runs-on: windows-latest | |
| if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == '') | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Get version from package.json | |
| id: package-version | |
| run: | | |
| $VERSION = (Get-Content package.json | ConvertFrom-Json).version | |
| echo "version=$VERSION" >> $env:GITHUB_OUTPUT | |
| - name: Download and prepare MSI info from public release | |
| id: msi-info | |
| run: | | |
| $VERSION = "${{ steps.package-version.outputs.version }}" | |
| $MSI_NAME = "termix_windows_x64_msi.msi" | |
| $DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$($VERSION)-tag/$($MSI_NAME)" | |
| Write-Host "Downloading from $DOWNLOAD_URL" | |
| New-Item -ItemType Directory -Force -Path "release_asset" | |
| $DOWNLOAD_PATH = "release_asset\$MSI_NAME" | |
| try { | |
| Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $DOWNLOAD_PATH -UseBasicParsing | |
| } catch { | |
| Write-Error "Failed to download MSI from $DOWNLOAD_URL. Please ensure the release and asset exist." | |
| exit 1 | |
| } | |
| $CHECKSUM = (Get-FileHash -Path $DOWNLOAD_PATH -Algorithm SHA256).Hash | |
| echo "msi_name=$MSI_NAME" >> $env:GITHUB_OUTPUT | |
| echo "checksum=$CHECKSUM" >> $env:GITHUB_OUTPUT | |
| - name: Prepare Chocolatey package | |
| run: | | |
| $VERSION = "${{ steps.package-version.outputs.version }}" | |
| $CHECKSUM = "${{ steps.msi-info.outputs.checksum }}" | |
| $MSI_NAME = "${{ steps.msi-info.outputs.msi_name }}" | |
| $DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$MSI_NAME" | |
| New-Item -ItemType Directory -Force -Path "choco-build" | |
| Copy-Item -Path "chocolatey\*" -Destination "choco-build" -Recurse -Force | |
| $installScript = Get-Content "choco-build\tools\chocolateyinstall.ps1" -Raw -Encoding UTF8 | |
| $installScript = $installScript -replace 'DOWNLOAD_URL_PLACEHOLDER', $DOWNLOAD_URL | |
| $installScript = $installScript -replace 'CHECKSUM_PLACEHOLDER', $CHECKSUM | |
| [System.IO.File]::WriteAllText("$PWD\choco-build\tools\chocolateyinstall.ps1", $installScript, [System.Text.UTF8Encoding]::new($false)) | |
| $nuspec = Get-Content "choco-build\termix-ssh.nuspec" -Raw -Encoding UTF8 | |
| $nuspec = $nuspec -replace 'VERSION_PLACEHOLDER', $VERSION | |
| [System.IO.File]::WriteAllText("$PWD\choco-build\termix-ssh.nuspec", $nuspec, [System.Text.UTF8Encoding]::new($false)) | |
| - name: Install Chocolatey | |
| run: | | |
| Set-ExecutionPolicy Bypass -Scope Process -Force | |
| [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 | |
| iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) | |
| - name: Pack Chocolatey package | |
| run: | | |
| cd choco-build | |
| choco pack termix-ssh.nuspec | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Chocolatey push failed with exit code $LASTEXITCODE" | |
| } | |
| - name: Check for Chocolatey API Key | |
| id: check_choco_key | |
| run: | | |
| if ("${{ secrets.CHOCOLATEY_API_KEY }}" -ne "") { | |
| echo "has_key=true" >> $env:GITHUB_OUTPUT | |
| } | |
| - name: Push to Chocolatey | |
| if: steps.check_choco_key.outputs.has_key == 'true' | |
| run: | | |
| $VERSION = "${{ steps.package-version.outputs.version }}" | |
| cd choco-build | |
| choco apikey --key "${{ secrets.CHOCOLATEY_API_KEY }}" --source https://push.chocolatey.org/ | |
| try { | |
| choco push "termix-ssh.$VERSION.nupkg" --source https://push.chocolatey.org/ | |
| if ($LASTEXITCODE -eq 0) { | |
| } else { | |
| throw "Chocolatey push failed with exit code $LASTEXITCODE" | |
| } | |
| } catch { | |
| } | |
| - name: Upload Chocolatey package as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: chocolatey-package | |
| path: choco-build/*.nupkg | |
| retention-days: 30 | |
| submit-to-flatpak: | |
| runs-on: blacksmith-4vcpu-ubuntu-2404 | |
| if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == '') | |
| needs: [] | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Get version from package.json | |
| id: package-version | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| RELEASE_DATE=$(date +%Y-%m-%d) | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT | |
| - name: Download and prepare AppImage info from public release | |
| id: appimage-info | |
| run: | | |
| VERSION="${{ steps.package-version.outputs.version }}" | |
| mkdir -p release_assets | |
| APPIMAGE_X64_NAME="termix_linux_x64_appimage.AppImage" | |
| URL_X64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_X64_NAME" | |
| PATH_X64="release_assets/$APPIMAGE_X64_NAME" | |
| echo "Downloading x64 AppImage from $URL_X64" | |
| curl -L -o "$PATH_X64" "$URL_X64" | |
| chmod +x "$PATH_X64" | |
| CHECKSUM_X64=$(sha256sum "$PATH_X64" | awk '{print $1}') | |
| APPIMAGE_ARM64_NAME="termix_linux_arm64_appimage.AppImage" | |
| URL_ARM64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_ARM64_NAME" | |
| PATH_ARM64="release_assets/$APPIMAGE_ARM64_NAME" | |
| echo "Downloading arm64 AppImage from $URL_ARM64" | |
| curl -L -o "$PATH_ARM64" "$URL_ARM64" | |
| chmod +x "$PATH_ARM64" | |
| CHECKSUM_ARM64=$(sha256sum "$PATH_ARM64" | awk '{print $1}') | |
| echo "appimage_x64_name=$APPIMAGE_X64_NAME" >> $GITHUB_OUTPUT | |
| echo "checksum_x64=$CHECKSUM_X64" >> $GITHUB_OUTPUT | |
| echo "appimage_arm64_name=$APPIMAGE_ARM64_NAME" >> $GITHUB_OUTPUT | |
| echo "checksum_arm64=$CHECKSUM_ARM64" >> $GITHUB_OUTPUT | |
| - name: Install ImageMagick for icon generation | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y imagemagick | |
| - name: Prepare Flatpak submission files | |
| run: | | |
| VERSION="${{ steps.package-version.outputs.version }}" | |
| CHECKSUM_X64="${{ steps.appimage-info.outputs.checksum_x64 }}" | |
| CHECKSUM_ARM64="${{ steps.appimage-info.outputs.checksum_arm64 }}" | |
| RELEASE_DATE="${{ steps.package-version.outputs.release_date }}" | |
| APPIMAGE_X64_NAME="${{ steps.appimage-info.outputs.appimage_x64_name }}" | |
| APPIMAGE_ARM64_NAME="${{ steps.appimage-info.outputs.appimage_arm64_name }}" | |
| mkdir -p flatpak-submission | |
| cp flatpak/com.karmaa.termix.yml flatpak-submission/ | |
| cp flatpak/com.karmaa.termix.desktop flatpak-submission/ | |
| cp flatpak/com.karmaa.termix.metainfo.xml flatpak-submission/ | |
| cp flatpak/flathub.json flatpak-submission/ | |
| cp public/icon.svg flatpak-submission/com.karmaa.termix.svg | |
| convert public/icon.png -resize 256x256 flatpak-submission/icon-256.png | |
| convert public/icon.png -resize 128x128 flatpak-submission/icon-128.png | |
| sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" flatpak-submission/com.karmaa.termix.yml | |
| sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" flatpak-submission/com.karmaa.termix.yml | |
| sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" flatpak-submission/com.karmaa.termix.yml | |
| sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" flatpak-submission/com.karmaa.termix.metainfo.xml | |
| sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" flatpak-submission/com.karmaa.termix.metainfo.xml | |
| - name: Upload Flatpak submission as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: flatpak-submission | |
| path: flatpak-submission/* | |
| retention-days: 30 | |
| submit-to-homebrew: | |
| runs-on: macos-latest | |
| if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'macos') | |
| needs: [] | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Get version from package.json | |
| id: package-version | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| - name: Download and prepare DMG info from public release | |
| id: dmg-info | |
| run: | | |
| VERSION="${{ steps.package-version.outputs.version }}" | |
| DMG_NAME="termix_macos_universal_dmg.dmg" | |
| URL="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$DMG_NAME" | |
| mkdir -p release_asset | |
| DOWNLOAD_PATH="release_asset/$DMG_NAME" | |
| echo "Downloading DMG from $URL" | |
| if command -v curl &> /dev/null; then | |
| curl -L -o "$DOWNLOAD_PATH" "$URL" | |
| elif command -v wget &> /dev/null; then | |
| wget -O "$DOWNLOAD_PATH" "$URL" | |
| else | |
| echo "Neither curl nor wget is available, installing curl" | |
| brew install curl | |
| curl -L -o "$DOWNLOAD_PATH" "$URL" | |
| fi | |
| CHECKSUM=$(shasum -a 256 "$DOWNLOAD_PATH" | awk '{print $1}') | |
| echo "dmg_name=$DMG_NAME" >> $GITHUB_OUTPUT | |
| echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT | |
| - name: Prepare Homebrew submission files | |
| run: | | |
| VERSION="${{ steps.package-version.outputs.version }}" | |
| CHECKSUM="${{ steps.dmg-info.outputs.checksum }}" | |
| DMG_NAME="${{ steps.dmg-info.outputs.dmg_name }}" | |
| mkdir -p homebrew-submission/Casks/t | |
| cp Casks/termix.rb homebrew-submission/Casks/t/termix.rb | |
| sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-submission/Casks/t/termix.rb | |
| sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-submission/Casks/t/termix.rb | |
| - name: Verify Cask syntax | |
| run: | | |
| ruby -c homebrew-submission/Casks/t/termix.rb | |
| - name: Upload Homebrew submission as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: homebrew-submission | |
| path: homebrew-submission/* | |
| retention-days: 30 | |
| upload-to-release: | |
| runs-on: blacksmith-4vcpu-ubuntu-2404 | |
| if: github.event.inputs.artifact_destination == 'release' | |
| needs: [build-windows, build-linux, build-macos] | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Get latest release tag | |
| id: get_release | |
| run: | | |
| echo "RELEASE_TAG=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName -q '.[0].tagName')" >> $GITHUB_ENV | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Upload artifacts to latest release | |
| run: | | |
| cd artifacts | |
| for dir in */; do | |
| cd "$dir" | |
| for file in *; | |
| do | |
| if [ -f "$file" ]; then | |
| gh release upload "$RELEASE_TAG" "$file" --repo ${{ github.repository }} --clobber | |
| fi | |
| done | |
| cd .. | |
| done | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| submit-to-testflight: | |
| runs-on: macos-latest | |
| if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'macos') | |
| needs: [] | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 1 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| - name: Install dependencies | |
| run: | | |
| for i in 1 2 3; | |
| do | |
| if npm ci; then | |
| break | |
| else | |
| if [ $i -eq 3 ]; then | |
| exit 1 | |
| fi | |
| sleep 10 | |
| fi | |
| done | |
| npm install --force @rollup/rollup-darwin-arm64 | |
| npm install dmg-license | |
| - name: Check for Code Signing Certificates | |
| id: check_certs | |
| run: | | |
| if [ -n "${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.MAC_P12_PASSWORD }}" ]; then | |
| echo "has_certs=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Import Code Signing Certificates | |
| if: steps.check_certs.outputs.has_certs == 'true' | |
| env: | |
| MAC_BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }} | |
| MAC_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.MAC_INSTALLER_CERTIFICATE_BASE64 }} | |
| MAC_P12_PASSWORD: ${{ secrets.MAC_P12_PASSWORD }} | |
| MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }} | |
| run: | | |
| APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12 | |
| INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH | |
| if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then | |
| echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH | |
| fi | |
| security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| if [ -f "$INSTALLER_CERT_PATH" ]; then | |
| security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| fi | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| security find-identity -v -p codesigning $KEYCHAIN_PATH | |
| - name: Build macOS App Store Package | |
| if: steps.check_certs.outputs.has_certs == 'true' | |
| env: | |
| ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| CURRENT_VERSION=$(node -p "require('./package.json').version") | |
| BUILD_VERSION="${{ github.run_number }}" | |
| npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION" | |
| - name: Check for App Store Connect API credentials | |
| id: check_asc_creds | |
| run: | | |
| if [ -n "${{ secrets.APPLE_KEY_ID }}" ] && [ -n "${{ secrets.APPLE_ISSUER_ID }}" ] && [ -n "${{ secrets.APPLE_KEY_CONTENT }}" ]; then | |
| echo "has_credentials=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Setup Ruby for Fastlane | |
| if: steps.check_asc_creds.outputs.has_credentials == 'true' | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: "3.2" | |
| bundler-cache: false | |
| - name: Install Fastlane | |
| if: steps.check_asc_creds.outputs.has_credentials == 'true' | |
| run: | | |
| gem install fastlane -N | |
| - name: Deploy to App Store Connect (TestFlight) | |
| if: steps.check_asc_creds.outputs.has_credentials == 'true' | |
| run: | | |
| PKG_FILE=$(find release -name "termix_macos_universal_mas.pkg" -type f | head -n 1) | |
| if [ -z "$PKG_FILE" ]; then | |
| echo "PKG file not found, exiting." | |
| exit 1 | |
| fi | |
| mkdir -p ~/private_keys | |
| echo "${{ secrets.APPLE_KEY_CONTENT }}" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_KEY_ID }}.p8 | |
| xcrun altool --upload-app -f "$PKG_FILE" \ | |
| --type macos \ | |
| --apiKey "${{ secrets.APPLE_KEY_ID }}" \ | |
| --apiIssuer "${{ secrets.APPLE_ISSUER_ID }}" | |
| continue-on-error: true | |
| - name: Clean up keychains | |
| if: always() | |
| run: | | |
| security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true |