Files
Termix/.github/workflows/electron.yml
2025-10-29 19:20:38 -05:00

1166 lines
44 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }}