diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index b50a8905..80ab5da6 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,1165 +1,94 @@ -name: Build and Push Electron App +name: Build and Push Docker Image on: workflow_dispatch: inputs: - build_type: - description: "Platform to build for" + version: + description: "Version to build (e.g., 1.8.0)" required: true - default: "all" - type: choice - options: - - all - - windows - - linux - - macos - artifact_destination: - description: "What to do with the built app" + production: + description: "Is this a production build?" required: true - default: "file" - type: choice - options: - - none - - file - - release - - submit + default: false + type: boolean 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 == '' - + build: + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v5 with: fetch-depth: 1 - - name: Setup Node.js - uses: actions/setup-node@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 with: - node-version: "20" - cache: "npm" + platforms: linux/amd64,linux/arm64,linux/arm/v7 - - name: Install dependencies + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Determine tags + id: tags 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++ - } - } + VERSION=${{ github.event.inputs.version }} + PROD=${{ github.event.inputs.production }} - - 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" + TAGS=() + ALL_TAGS=() - - 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 + if [ "$PROD" = "true" ]; then + # Production build → push release + latest to both GHCR and Docker Hub + TAGS+=("release-$VERSION" "latest") + for tag in "${TAGS[@]}"; do + ALL_TAGS+=("ghcr.io/lukegus/termix:$tag") + ALL_TAGS+=("docker.io/bugattiguy527/termix:$tag") + done else - echo "has_certs=false" >> $GITHUB_OUTPUT - echo "⚠️ Code signing certificates not configured. MAS build will be unsigned." + # Dev build → push only dev-x.x.x to GHCR + TAGS+=("dev-$VERSION") + for tag in "${TAGS[@]}"; do + ALL_TAGS+=("ghcr.io/lukegus/termix:$tag") + done 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 "ALL_TAGS=${ALL_TAGS[*]}" >> $GITHUB_ENV + echo "All tags to build:" + printf '%s\n' "${ALL_TAGS[@]}" - # 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 + - name: Login to GHCR + uses: docker/login-action@v3 with: - name: termix_macos_mas - path: release/*_*_*_mas.pkg - retention-days: 30 - if-no-files-found: warn + registry: ghcr.io + username: lukegus + password: ${{ secrets.GHCR_TOKEN }} - - name: Upload macOS Universal DMG - uses: actions/upload-artifact@v4 - if: hashFiles('release/*_universal_*_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none' + - name: Login to Docker Hub (prod only) + if: ${{ github.event.inputs.production == 'true' }} + uses: docker/login-action@v3 with: - name: termix_macos_universal_dmg - path: release/*_universal_*_dmg.dmg - retention-days: 30 + username: bugattiguy527 + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Upload macOS x64 DMG - uses: actions/upload-artifact@v4 - if: hashFiles('release/*_x64_*_dmg.dmg') != '' && github.event.inputs.artifact_destination != 'none' + - name: Build and push multi-arch image + uses: docker/build-push-action@v5 with: - name: termix_macos_x64_dmg - path: release/*_x64_*_dmg.dmg - retention-days: 30 + context: . + file: ./docker/Dockerfile + push: true + platforms: linux/amd64,linux/arm64,linux/arm/v7 + tags: ${{ env.ALL_TAGS }} + build-args: | + BUILDKIT_INLINE_CACHE=1 + BUILDKIT_CONTEXT_KEEP_GIT_DIR=1 + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + outputs: type=registry,compression=zstd,compression-level=19 - - 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 + - name: Cleanup Docker 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 }} + docker image prune -af + docker system prune -af --volumes