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 == '' 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: | # Retry npm ci up to 3 times on failure $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 } Write-Host "npm ci attempt $attempt failed, retrying in 10 seconds..." 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 echo "Building version: $VERSION" - name: Build Windows (All Architectures) run: npm run build && npx electron-builder --win --x64 --ia32 - name: List release files run: | echo "Contents of release directory:" dir release - name: Upload Windows x64 NSIS Installer uses: actions/upload-artifact@v4 if: hashFiles('release/*_x64_*_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_windows_x64_nsis path: release/*_x64_*_nsis.exe retention-days: 30 - name: Upload Windows ia32 NSIS Installer uses: actions/upload-artifact@v4 if: hashFiles('release/*_ia32_*_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_windows_ia32_nsis path: release/*_ia32_*_nsis.exe retention-days: 30 - name: Upload Windows x64 MSI Installer uses: actions/upload-artifact@v4 if: hashFiles('release/*_x64_*_msi.msi') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_windows_x64_msi path: release/*_x64_*_msi.msi retention-days: 30 - name: Upload Windows ia32 MSI Installer uses: actions/upload-artifact@v4 if: hashFiles('release/*_ia32_*_msi.msi') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_windows_ia32_msi path: release/*_ia32_*_msi.msi retention-days: 30 - name: Create Windows x64 Portable zip if: hashFiles('release/win-unpacked/*') != '' run: | $VERSION = "${{ steps.package-version.outputs.version }}" Compress-Archive -Path "release\win-unpacked\*" -DestinationPath "termix_windows_x64_${VERSION}_portable.zip" - name: Create Windows ia32 Portable zip if: hashFiles('release/win-ia32-unpacked/*') != '' run: | $VERSION = "${{ steps.package-version.outputs.version }}" Compress-Archive -Path "release\win-ia32-unpacked\*" -DestinationPath "termix_windows_ia32_${VERSION}_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 == '' 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: | rm -f package-lock.json # Retry npm install up to 3 times on failure for i in 1 2 3; do if npm install; then break else if [ $i -eq 3 ]; then echo "npm install failed after 3 attempts" exit 1 fi echo "npm install attempt $i failed, retrying in 10 seconds..." 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 (All Architectures) run: npm run build && npx electron-builder --linux --x64 --arm64 --armv7l - name: Rename tar.gz files to match convention run: | VERSION=$(node -p "require('./package.json').version") cd release # Rename x64 tar.gz if it exists if [ -f "termix-${VERSION}-x64.tar.gz" ]; then mv "termix-${VERSION}-x64.tar.gz" "termix_linux_x64_${VERSION}_portable.tar.gz" echo "Renamed x64 tar.gz" fi # Rename arm64 tar.gz if it exists if [ -f "termix-${VERSION}-arm64.tar.gz" ]; then mv "termix-${VERSION}-arm64.tar.gz" "termix_linux_arm64_${VERSION}_portable.tar.gz" echo "Renamed arm64 tar.gz" fi # Rename armv7l tar.gz if it exists if [ -f "termix-${VERSION}-armv7l.tar.gz" ]; then mv "termix-${VERSION}-armv7l.tar.gz" "termix_linux_armv7l_${VERSION}_portable.tar.gz" echo "Renamed armv7l tar.gz" fi cd .. - name: List release files run: | echo "Contents of release directory:" ls -la release/ - name: Upload Linux x64 AppImage uses: actions/upload-artifact@v4 if: hashFiles('release/*_x64_*_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_linux_x64_appimage path: release/*_x64_*_appimage.AppImage retention-days: 30 - name: Upload Linux arm64 AppImage uses: actions/upload-artifact@v4 if: hashFiles('release/*_arm64_*_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_linux_arm64_appimage path: release/*_arm64_*_appimage.AppImage retention-days: 30 - name: Upload Linux x64 DEB uses: actions/upload-artifact@v4 if: hashFiles('release/*_x64_*_deb.deb') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_linux_x64_deb path: release/*_x64_*_deb.deb retention-days: 30 - name: Upload Linux arm64 DEB uses: actions/upload-artifact@v4 if: hashFiles('release/*_arm64_*_deb.deb') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_linux_arm64_deb path: release/*_arm64_*_deb.deb retention-days: 30 - name: Upload Linux armv7l DEB uses: actions/upload-artifact@v4 if: hashFiles('release/*_armv7l_*_deb.deb') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_linux_armv7l_deb path: release/*_armv7l_*_deb.deb retention-days: 30 - name: Upload Linux x64 tar.gz uses: actions/upload-artifact@v4 if: hashFiles('release/*_x64_*_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_linux_x64_portable path: release/*_x64_*_portable.tar.gz retention-days: 30 - name: Upload Linux arm64 tar.gz uses: actions/upload-artifact@v4 if: hashFiles('release/*_arm64_*_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_linux_arm64_portable path: release/*_arm64_*_portable.tar.gz retention-days: 30 - name: Upload Linux armv7l tar.gz uses: actions/upload-artifact@v4 if: hashFiles('release/*_armv7l_*_portable.tar.gz') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_linux_armv7l_portable path: release/*_armv7l_*_portable.tar.gz retention-days: 30 build-macos: runs-on: macos-latest if: github.event.inputs.build_type == 'macos' || github.event.inputs.build_type == 'all' needs: [] 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: | # Retry npm ci up to 3 times on failure for i in 1 2 3; do if npm ci; then break else if [ $i -eq 3 ]; then echo "npm ci failed after 3 attempts" exit 1 fi echo "npm ci attempt $i failed, retrying in 10 seconds..." 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 else echo "has_certs=false" >> $GITHUB_OUTPUT echo "⚠️ Code signing certificates not configured. MAS build will be unsigned." 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 # Decode certificates echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then echo "Decoding installer certificate..." echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH else echo "⚠️ MAC_INSTALLER_CERTIFICATE_BASE64 is empty" fi # Create and configure keychain 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 # Import application certificate echo "Importing application certificate..." security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH # Import installer certificate if it exists if [ -f "$INSTALLER_CERT_PATH" ]; then echo "Importing installer certificate..." security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH else echo "⚠️ Installer certificate file not found, skipping import" fi security list-keychain -d user -s $KEYCHAIN_PATH echo "Imported certificates:" 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 run: | # Get current version for display CURRENT_VERSION=$(node -p "require('./package.json').version") BUILD_VERSION="${{ github.run_number }}" echo "✅ Package version: $CURRENT_VERSION (unchanged)" echo "✅ Build number for Apple: $BUILD_VERSION" # Build MAS with custom buildVersion 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 echo "Cleaned up MAS keychain" - 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 echo "✅ Developer ID certificates configured for DMG signing" else echo "has_dev_id_certs=false" >> $GITHUB_OUTPUT echo "⚠️ Developer ID certificates not configured. DMG will be unsigned." echo "Add DEVELOPER_ID_CERTIFICATE_BASE64 and DEVELOPER_ID_P12_PASSWORD secrets to enable DMG signing." 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 # Decode Developer ID certificate echo -n "$DEVELOPER_ID_CERTIFICATE_BASE64" | base64 --decode -o $DEV_CERT_PATH if [ -n "$DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64" ]; then echo "Decoding Developer ID installer certificate..." echo -n "$DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $DEV_INSTALLER_CERT_PATH else echo "⚠️ DEVELOPER_ID_INSTALLER_CERTIFICATE_BASE64 is empty (optional)" fi # Create and configure keychain 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 # Import Developer ID Application certificate echo "Importing Developer ID Application certificate..." security import $DEV_CERT_PATH -P "$DEVELOPER_ID_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH # Import Developer ID Installer certificate if it exists if [ -f "$DEV_INSTALLER_CERT_PATH" ]; then echo "Importing Developer ID Installer certificate..." 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 echo "Imported Developer ID certificates:" 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: | # Build DMG without running npm run build again (already built above or skip if no certs) if [ "${{ steps.check_certs.outputs.has_certs }}" == "true" ]; then # Frontend already built, just package DMG npx electron-builder --mac dmg --universal --x64 --arm64 else # No certs, need to build frontend first npm run build && npx electron-builder --mac dmg --universal --x64 --arm64 fi - name: List release directory if: steps.check_certs.outputs.has_certs == 'true' run: | echo "Contents of release directory:" ls -R release/ || echo "Release directory not found" - name: Upload macOS MAS PKG if: steps.check_certs.outputs.has_certs == 'true' && hashFiles('release/*_*_*_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_mas path: release/*_*_*_mas.pkg retention-days: 30 if-no-files-found: warn - name: Upload macOS Universal DMG uses: actions/upload-artifact@v4 if: hashFiles('release/*_universal_*_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_macos_universal_dmg path: release/*_universal_*_dmg.dmg retention-days: 30 - name: Upload macOS x64 DMG uses: actions/upload-artifact@v4 if: hashFiles('release/*_x64_*_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_macos_x64_dmg path: release/*_x64_*_dmg.dmg retention-days: 30 - name: Upload macOS arm64 DMG uses: actions/upload-artifact@v4 if: hashFiles('release/*_arm64_*_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none' with: name: termix_macos_arm64_dmg path: release/*_arm64_*_dmg.dmg retention-days: 30 - name: Check for App Store Connect API credentials if: steps.check_certs.outputs.has_certs == 'true' 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 if [ "${{ github.event.inputs.artifact_destination }}" == "submit" ]; then echo "✅ App Store Connect API credentials found. Will deploy to TestFlight." else echo "ℹ️ App Store Connect API credentials found, but store submission is disabled." fi else echo "has_credentials=false" >> $GITHUB_OUTPUT echo "⚠️ App Store Connect API credentials not configured. Skipping deployment." echo "Add APPLE_KEY_ID, APPLE_ISSUER_ID, and APPLE_KEY_CONTENT secrets to enable automatic deployment." fi - name: Setup Ruby for Fastlane if: steps.check_asc_creds.outputs.has_credentials == 'true' && github.event.inputs.artifact_destination == 'submit' 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' && github.event.inputs.artifact_destination == 'submit' run: | gem install fastlane -N fastlane --version - name: Deploy to App Store Connect (TestFlight) if: steps.check_asc_creds.outputs.has_credentials == 'true' && github.event.inputs.artifact_destination == 'submit' run: | PKG_FILE=$(find release -name "*.pkg" -type f | head -n 1) if [ -z "$PKG_FILE" ]; then echo "Error: No .pkg file found in release directory" exit 1 fi echo "Found package: $PKG_FILE" # Create API key file mkdir -p ~/private_keys echo "${{ secrets.APPLE_KEY_CONTENT }}" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_KEY_ID }}.p8 # Upload to App Store Connect using xcrun altool xcrun altool --upload-app -f "$PKG_FILE" \ --type macos \ --apiKey "${{ secrets.APPLE_KEY_ID }}" \ --apiIssuer "${{ secrets.APPLE_ISSUER_ID }}" echo "✅ Upload complete! Build will appear in App Store Connect after processing (10-30 minutes)" continue-on-error: true - 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' needs: [build-windows] 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 echo "Building Chocolatey package for version: $VERSION" - name: Download Windows x64 MSI artifact uses: actions/download-artifact@v4 with: name: termix_windows_x64_msi path: artifact - name: Get MSI file info id: msi-info run: | $VERSION = "${{ steps.package-version.outputs.version }}" $MSI_FILE = Get-ChildItem -Path artifact -Filter "*.msi" | Select-Object -First 1 $MSI_NAME = $MSI_FILE.Name $CHECKSUM = (Get-FileHash -Path $MSI_FILE.FullName -Algorithm SHA256).Hash echo "msi_name=$MSI_NAME" >> $env:GITHUB_OUTPUT echo "checksum=$CHECKSUM" >> $env:GITHUB_OUTPUT echo "MSI File: $MSI_NAME" echo "SHA256: $CHECKSUM" - 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 }}" # Construct the download URL with the actual release tag format $DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$MSI_NAME" # Copy chocolatey files to build directory New-Item -ItemType Directory -Force -Path "choco-build" Copy-Item -Path "chocolatey\*" -Destination "choco-build" -Recurse -Force # Update chocolateyinstall.ps1 with actual values $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)) # Update nuspec with version (preserve UTF-8 encoding without BOM) $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)) echo "Chocolatey package prepared for version $VERSION" echo "Download URL: $DOWNLOAD_URL" # Verify the nuspec is valid echo "" echo "Verifying nuspec content:" Get-Content "choco-build\termix-ssh.nuspec" -Head 10 echo "" - 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 echo "Packing Chocolatey package..." choco pack termix-ssh.nuspec if ($LASTEXITCODE -ne 0) { echo "❌ Failed to pack Chocolatey package" exit 1 } echo "" echo "✅ Package created successfully" echo "Package contents:" Get-ChildItem *.nupkg | ForEach-Object { echo $_.Name } - name: Check for Chocolatey API Key id: check_choco_key run: | if ("${{ secrets.CHOCOLATEY_API_KEY }}" -ne "") { echo "has_key=true" >> $env:GITHUB_OUTPUT echo "✅ Chocolatey API key found. Will push to Chocolatey." } else { echo "has_key=false" >> $env:GITHUB_OUTPUT echo "⚠️ Chocolatey API key not configured. Package will be created but not pushed." echo "Add CHOCOLATEY_API_KEY secret to enable automatic submission." } - 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) { echo "" echo "✅ Package pushed to Chocolatey successfully!" echo "View at: https://community.chocolatey.org/packages/termix-ssh/$VERSION" } else { throw "Chocolatey push failed with exit code $LASTEXITCODE" } } catch { echo "" echo "❌ Failed to push to Chocolatey" echo "" echo "Common reasons:" echo "1. Package ID 'termix-ssh' is already owned by another user" echo "2. You need to register/claim the package ID first" echo "3. API key doesn't have push permissions" echo "" echo "Solutions:" echo "1. Check if package exists: https://community.chocolatey.org/packages/termix-ssh" echo "2. If it exists and is yours, contact Chocolatey support to claim it" echo "3. Register a new package ID at: https://community.chocolatey.org/" echo "" echo "The package artifact has been saved for manual submission." echo "" exit 1 } - 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: [build-linux] 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 echo "Building Flatpak submission for version: $VERSION" - name: Download Linux x64 AppImage artifact uses: actions/download-artifact@v4 with: name: termix_linux_x64_appimage path: artifact-x64 - name: Download Linux arm64 AppImage artifact uses: actions/download-artifact@v4 with: name: termix_linux_arm64_appimage path: artifact-arm64 - name: Get AppImage file info id: appimage-info run: | VERSION="${{ steps.package-version.outputs.version }}" # x64 AppImage APPIMAGE_X64_FILE=$(find artifact-x64 -name "*.AppImage" -type f | head -n 1) APPIMAGE_X64_NAME=$(basename "$APPIMAGE_X64_FILE") CHECKSUM_X64=$(sha256sum "$APPIMAGE_X64_FILE" | awk '{print $1}') # arm64 AppImage APPIMAGE_ARM64_FILE=$(find artifact-arm64 -name "*.AppImage" -type f | head -n 1) APPIMAGE_ARM64_NAME=$(basename "$APPIMAGE_ARM64_FILE") CHECKSUM_ARM64=$(sha256sum "$APPIMAGE_ARM64_FILE" | 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 echo "x64 AppImage: $APPIMAGE_X64_NAME" echo "x64 SHA256: $CHECKSUM_X64" echo "arm64 AppImage: $APPIMAGE_ARM64_NAME" echo "arm64 SHA256: $CHECKSUM_ARM64" - 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 }}" # Create submission directory mkdir -p flatpak-submission # Copy Flatpak files to submission directory 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/ # Copy and prepare icons 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 # Update manifest with version and checksums 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 # Update metainfo with version and date 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 echo "✅ Flatpak submission files prepared for version $VERSION" echo "x64 Download URL: https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_X64_NAME" echo "arm64 Download URL: https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_ARM64_NAME" - name: Create submission instructions run: | cat > flatpak-submission/SUBMISSION_INSTRUCTIONS.md << 'EOF' # Flathub Submission Instructions for Termix ## Automatic Submission (Recommended) All files needed for Flathub submission are in this artifact. Follow these steps: 1. **Fork the Flathub repository**: - Go to https://github.com/flathub/flathub - Click "Fork" button 2. **Clone your fork**: ```bash git clone https://github.com/YOUR-USERNAME/flathub.git cd flathub git checkout -b com.karmaa.termix ``` 3. **Copy all files from this artifact** to the root of your flathub fork 4. **Commit and push**: ```bash git add . git commit -m "Add Termix ${{ steps.package-version.outputs.version }}" git push origin com.karmaa.termix ``` 5. **Create Pull Request**: - Go to https://github.com/YOUR-USERNAME/flathub - Click "Compare & pull request" - Submit PR to flathub/flathub ## Files in this submission: - `com.karmaa.termix.yml` - Flatpak manifest - `com.karmaa.termix.desktop` - Desktop entry - `com.karmaa.termix.metainfo.xml` - AppStream metadata - `flathub.json` - Flathub configuration - `com.karmaa.termix.svg` - SVG icon - `icon-256.png` - 256x256 icon - `icon-128.png` - 128x128 icon ## Version Information: - Version: ${{ steps.package-version.outputs.version }} - Release Date: ${{ steps.package-version.outputs.release_date }} - x64 AppImage SHA256: ${{ steps.appimage-info.outputs.checksum_x64 }} - arm64 AppImage SHA256: ${{ steps.appimage-info.outputs.checksum_arm64 }} ## After Submission: 1. Flathub maintainers will review your submission (usually 1-5 days) 2. They may request changes - be responsive to feedback 3. Once approved, Termix will be available via: `flatpak install flathub com.karmaa.termix` ## Resources: - [Flathub Submission Guidelines](https://docs.flathub.org/docs/for-app-authors/submission) - [Flatpak Documentation](https://docs.flatpak.org/) EOF echo "✅ Created submission instructions" - name: List submission files run: | echo "Flatpak submission files:" ls -la flatpak-submission/ - name: Upload Flatpak submission as artifact uses: actions/upload-artifact@v4 with: name: flatpak-submission path: flatpak-submission/* retention-days: 30 - name: Display next steps run: | echo "" echo "🎉 Flatpak submission files ready!" echo "" echo "📦 Download the 'flatpak-submission' artifact and follow SUBMISSION_INSTRUCTIONS.md" echo "" echo "Quick summary:" echo "1. Fork https://github.com/flathub/flathub" echo "2. Copy artifact files to your fork" echo "3. Create PR to flathub/flathub" echo "" submit-to-homebrew: runs-on: macos-latest if: github.event.inputs.artifact_destination == 'submit' needs: [build-macos] 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 echo "Building Homebrew Cask for version: $VERSION" - name: Download macOS Universal DMG artifact uses: actions/download-artifact@v4 with: name: termix_macos_universal_dmg path: artifact - name: Get DMG file info id: dmg-info run: | VERSION="${{ steps.package-version.outputs.version }}" DMG_FILE=$(find artifact -name "*.dmg" -type f | head -n 1) DMG_NAME=$(basename "$DMG_FILE") CHECKSUM=$(shasum -a 256 "$DMG_FILE" | awk '{print $1}') echo "dmg_name=$DMG_NAME" >> $GITHUB_OUTPUT echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT echo "DMG File: $DMG_NAME" echo "SHA256: $CHECKSUM" - 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 }}" # Create submission directory mkdir -p homebrew-submission/Casks/t # Copy Homebrew cask file cp homebrew/termix.rb homebrew-submission/Casks/t/termix.rb cp homebrew/README.md homebrew-submission/ # Update cask with version and checksum 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 echo "✅ Homebrew Cask prepared for version $VERSION" echo "Download URL: https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$DMG_NAME" - name: Verify Cask syntax run: | # Install Homebrew if not present (should be on macos-latest) if ! command -v brew &> /dev/null; then echo "Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" fi # Basic syntax check ruby -c homebrew-submission/Casks/t/termix.rb echo "✅ Cask syntax is valid" - name: Create submission instructions run: | cat > homebrew-submission/SUBMISSION_INSTRUCTIONS.md << 'EOF' # Homebrew Cask Submission Instructions for Termix ## Option 1: Submit to Official Homebrew Cask (Recommended) ### Prerequisites - macOS with Homebrew installed - GitHub account ### Steps 1. **Fork the Homebrew Cask repository**: - Go to https://github.com/Homebrew/homebrew-cask - Click "Fork" button 2. **Clone your fork**: ```bash git clone https://github.com/YOUR-USERNAME/homebrew-cask.git cd homebrew-cask git checkout -b termix ``` 3. **Copy the cask file**: - Copy `Casks/t/termix.rb` from this artifact to your fork at `Casks/t/termix.rb` - Note: Casks are organized by first letter in subdirectories 4. **Test the cask locally**: ```bash brew install --cask ./Casks/t/termix.rb brew uninstall --cask termix ``` 5. **Run audit checks**: ```bash brew audit --cask --online ./Casks/t/termix.rb brew style ./Casks/t/termix.rb ``` 6. **Commit and push**: ```bash git add Casks/t/termix.rb git commit -m "Add Termix ${{ steps.package-version.outputs.version }}" git push origin termix ``` 7. **Create Pull Request**: - Go to https://github.com/YOUR-USERNAME/homebrew-cask - Click "Compare & pull request" - Fill in the PR template - Submit to Homebrew/homebrew-cask ### PR Requirements Your PR should include: - Clear commit message: "Add Termix X.Y.Z" or "Update Termix to X.Y.Z" - All audit checks passing - Working download URL - Valid SHA256 checksum ## Option 2: Create Your Own Tap (Alternative) If you want more control and faster updates: 1. **Create a tap repository**: - Create repo: `Termix-SSH/homebrew-termix` - Add `Casks/termix.rb` to the repo 2. **Users install with**: ```bash brew tap termix-ssh/termix brew install --cask termix ``` ### Advantages of Custom Tap - No approval process - Instant updates - Full control - Can include beta versions ### Disadvantages - Less discoverable - Users must add tap first - You maintain it yourself ## Files in this submission: - `Casks/t/termix.rb` - Homebrew Cask formula - `README.md` - Detailed documentation - `SUBMISSION_INSTRUCTIONS.md` - This file ## Version Information: - Version: ${{ steps.package-version.outputs.version }} - DMG SHA256: ${{ steps.dmg-info.outputs.checksum }} - DMG URL: https://github.com/Termix-SSH/Termix/releases/download/release-${{ steps.package-version.outputs.version }}-tag/${{ steps.dmg-info.outputs.dmg_name }} ## After Submission: ### Official Homebrew Cask: 1. Maintainers will review (usually 24-48 hours) 2. May request changes or fixes 3. Once merged, users can install with: `brew install --cask termix` 4. Homebrew bot will auto-update for future releases ### Custom Tap: 1. Push to your tap repository 2. Immediately available to users 3. Update the cask file for each new release ## Resources: - [Homebrew Cask Documentation](https://docs.brew.sh/Cask-Cookbook) - [Acceptable Casks](https://docs.brew.sh/Acceptable-Casks) - [How to Open a PR](https://docs.brew.sh/How-To-Open-a-Homebrew-Pull-Request) EOF echo "✅ Created submission instructions" - name: List submission files run: | echo "Homebrew submission files:" find homebrew-submission -type f - name: Upload Homebrew submission as artifact uses: actions/upload-artifact@v4 with: name: homebrew-submission path: homebrew-submission/* retention-days: 30 - name: Display next steps run: | echo "" echo "🍺 Homebrew Cask ready!" echo "" echo "📦 Download the 'homebrew-submission' artifact and follow SUBMISSION_INSTRUCTIONS.md" echo "" echo "Quick summary:" echo "Option 1 (Recommended): Fork https://github.com/Homebrew/homebrew-cask and submit PR" echo "Option 2 (Alternative): Create your own tap at Termix-SSH/homebrew-termix" echo "" 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 id: get_release run: | echo "Fetching latest release from ${{ github.repository }}..." LATEST_RELEASE=$(gh release list --repo ${{ github.repository }} --limit 1 --json tagName,name,isLatest -q '.[0]') if [ -z "$LATEST_RELEASE" ]; then echo "ERROR: No releases found in ${{ github.repository }}" exit 1 fi RELEASE_TAG=$(echo "$LATEST_RELEASE" | jq -r '.tagName') RELEASE_NAME=$(echo "$LATEST_RELEASE" | jq -r '.name') echo "tag=$RELEASE_TAG" >> $GITHUB_OUTPUT echo "name=$RELEASE_NAME" >> $GITHUB_OUTPUT echo "Latest release: $RELEASE_NAME ($RELEASE_TAG)" env: GH_TOKEN: ${{ github.token }} - name: Display artifact structure run: | echo "Artifact structure:" ls -R artifacts/ - name: Upload artifacts to latest release run: | RELEASE_TAG="${{ steps.get_release.outputs.tag }}" echo "Uploading artifacts to release: $RELEASE_TAG" echo "" cd artifacts for dir in */; do echo "Processing directory: $dir" cd "$dir" for file in *; do if [ -f "$file" ]; then echo "Uploading: $file" gh release upload "$RELEASE_TAG" "$file" --repo ${{ github.repository }} --clobber echo "✓ $file uploaded successfully" fi done cd .. done echo "" echo "All artifacts uploaded to: https://github.com/${{ github.repository }}/releases/tag/$RELEASE_TAG" env: GH_TOKEN: ${{ github.token }}