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' 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' 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: ubuntu-latest if: github.event.inputs.artifact_destination == 'submit' 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' 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 PATH="release_asset/$DMG_NAME" echo "Downloading DMG from $URL" curl -L -o "$PATH" "$URL" CHECKSUM=$(shasum -a 256 "$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' 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 artifact-mas -name "*.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