diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index cc32f3d1..34f78ea8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,7 +17,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - name: Checkout repository uses: actions/checkout@v5 diff --git a/.github/workflows/electron.yml b/.github/workflows/electron.yml index bd1a5b4c..7c087e03 100644 --- a/.github/workflows/electron.yml +++ b/.github/workflows/electron.yml @@ -27,7 +27,7 @@ on: 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 == '' + if: (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == '') && github.event.inputs.artifact_destination != 'submit' permissions: contents: write @@ -72,10 +72,6 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npm run build && npx electron-builder --win --x64 --ia32 - - name: List release files - run: | - dir release - - name: Upload Windows x64 NSIS Installer uses: actions/upload-artifact@v4 if: hashFiles('release/termix_windows_x64_nsis.exe') != '' && github.event.inputs.artifact_destination != 'none' @@ -136,7 +132,7 @@ jobs: 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 == '' + if: (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == '') && github.event.inputs.artifact_destination != 'submit' permissions: contents: write @@ -199,17 +195,6 @@ jobs: cd .. - - name: List release files - run: | - ls -la release/ - - - name: Debug electron-builder output - if: always() - run: | - if [ -f "release/builder-debug.yml" ]; then - cat release/builder-debug.yml - fi - - name: Upload Linux x64 AppImage uses: actions/upload-artifact@v4 if: hashFiles('release/termix_linux_x64_appimage.AppImage') != '' && github.event.inputs.artifact_destination != 'none' @@ -282,9 +267,96 @@ jobs: path: release/termix_linux_armv7l_portable.tar.gz retention-days: 30 + - name: Install Flatpak builder and dependencies + run: | + sudo apt-get update + sudo apt-get install -y flatpak flatpak-builder imagemagick + + - name: Add Flathub repository + run: | + sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + + - name: Install Flatpak runtime and SDK + run: | + sudo flatpak install -y flathub org.freedesktop.Platform//24.08 + sudo flatpak install -y flathub org.freedesktop.Sdk//24.08 + sudo flatpak install -y flathub org.electronjs.Electron2.BaseApp//24.08 + + - name: Get version for Flatpak + id: flatpak-version + run: | + VERSION=$(node -p "require('./package.json').version") + RELEASE_DATE=$(date +%Y-%m-%d) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT + + - name: Prepare Flatpak files + run: | + VERSION="${{ steps.flatpak-version.outputs.version }}" + RELEASE_DATE="${{ steps.flatpak-version.outputs.release_date }}" + + CHECKSUM_X64=$(sha256sum "release/termix_linux_x64_appimage.AppImage" | awk '{print $1}') + CHECKSUM_ARM64=$(sha256sum "release/termix_linux_arm64_appimage.AppImage" | awk '{print $1}') + + mkdir -p flatpak-build + cp flatpak/com.karmaa.termix.yml flatpak-build/ + cp flatpak/com.karmaa.termix.desktop flatpak-build/ + cp flatpak/com.karmaa.termix.metainfo.xml flatpak-build/ + cp public/icon.svg flatpak-build/com.karmaa.termix.svg + convert public/icon.png -resize 256x256 flatpak-build/icon-256.png + convert public/icon.png -resize 128x128 flatpak-build/icon-128.png + + cd flatpak-build + sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_x64_appimage.AppImage|file://$(realpath ../release/termix_linux_x64_appimage.AppImage)|g" com.karmaa.termix.yml + sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_arm64_appimage.AppImage|file://$(realpath ../release/termix_linux_arm64_appimage.AppImage)|g" com.karmaa.termix.yml + sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" com.karmaa.termix.yml + sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" com.karmaa.termix.yml + sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" com.karmaa.termix.metainfo.xml + sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" com.karmaa.termix.metainfo.xml + + - name: Build Flatpak bundle + run: | + cd flatpak-build + flatpak-builder --repo=repo --force-clean --disable-rofiles-fuse build-dir com.karmaa.termix.yml + + # Determine the architecture + ARCH=$(uname -m) + if [ "$ARCH" = "x86_64" ]; then + FLATPAK_ARCH="x86_64" + elif [ "$ARCH" = "aarch64" ]; then + FLATPAK_ARCH="aarch64" + else + FLATPAK_ARCH="$ARCH" + fi + + # Build bundle for the current architecture + flatpak build-bundle repo ../release/termix_linux_flatpak.flatpak com.karmaa.termix --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo + + - name: Create flatpakref file + run: | + VERSION="${{ steps.flatpak-version.outputs.version }}" + cp flatpak/com.karmaa.termix.flatpakref release/ + sed -i "s|VERSION_PLACEHOLDER|release-${VERSION}-tag|g" release/com.karmaa.termix.flatpakref + + - name: Upload Flatpak bundle + uses: actions/upload-artifact@v4 + if: hashFiles('release/termix_linux_flatpak.flatpak') != '' && github.event.inputs.artifact_destination != 'none' + with: + name: termix_linux_flatpak + path: release/termix_linux_flatpak.flatpak + retention-days: 30 + + - name: Upload Flatpakref + uses: actions/upload-artifact@v4 + if: hashFiles('release/com.karmaa.termix.flatpakref') != '' && github.event.inputs.artifact_destination != 'none' + with: + name: termix_linux_flatpakref + path: release/com.karmaa.termix.flatpakref + retention-days: 30 + build-macos: runs-on: macos-latest - if: github.event.inputs.build_type == 'macos' || github.event.inputs.build_type == 'all' + if: (github.event.inputs.build_type == 'macos' || github.event.inputs.build_type == 'all') && github.event.inputs.artifact_destination != 'submit' needs: [] permissions: contents: write @@ -425,11 +497,6 @@ jobs: export GH_TOKEN="${{ secrets.GITHUB_TOKEN }}" npx electron-builder --mac dmg --universal --x64 --arm64 --publish never - - name: List release directory - if: steps.check_certs.outputs.has_certs == 'true' - run: | - ls -R release/ || echo "Release directory not found" - - name: Upload macOS MAS PKG if: steps.check_certs.outputs.has_certs == 'true' && hashFiles('release/termix_macos_universal_mas.pkg') != '' && (github.event.inputs.artifact_destination == 'file' || github.event.inputs.artifact_destination == 'release' || github.event.inputs.artifact_destination == 'submit') uses: actions/upload-artifact@v4 @@ -463,42 +530,51 @@ jobs: path: release/termix_macos_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 + - name: Get version for Homebrew + id: homebrew-version run: | - if [ -n "${{ secrets.APPLE_KEY_ID }}" ] && [ -n "${{ secrets.APPLE_ISSUER_ID }}" ] && [ -n "${{ secrets.APPLE_KEY_CONTENT }}" ]; then - echo "has_credentials=true" >> $GITHUB_OUTPUT - fi + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT - - 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 + - name: Generate Homebrew Cask + if: hashFiles('release/termix_macos_universal_dmg.dmg') != '' && (github.event.inputs.artifact_destination == 'file' || github.event.inputs.artifact_destination == 'release') + run: | + VERSION="${{ steps.homebrew-version.outputs.version }}" + DMG_PATH="release/termix_macos_universal_dmg.dmg" + + CHECKSUM=$(shasum -a 256 "$DMG_PATH" | awk '{print $1}') + + mkdir -p homebrew-generated + cp Casks/termix.rb homebrew-generated/termix.rb + + sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-generated/termix.rb + sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-generated/termix.rb + sed -i '' "s|version \".*\"|version \"$VERSION\"|g" homebrew-generated/termix.rb + sed -i '' "s|sha256 \".*\"|sha256 \"$CHECKSUM\"|g" homebrew-generated/termix.rb + sed -i '' "s|release-[0-9.]*-tag|release-$VERSION-tag|g" homebrew-generated/termix.rb + + - name: Upload Homebrew Cask as artifact + uses: actions/upload-artifact@v4 + if: hashFiles('homebrew-generated/termix.rb') != '' && github.event.inputs.artifact_destination == 'file' with: - ruby-version: "3.2" - bundler-cache: false + name: termix_macos_homebrew_cask + path: homebrew-generated/termix.rb + retention-days: 30 - - name: Install Fastlane - if: steps.check_asc_creds.outputs.has_credentials == 'true' && github.event.inputs.artifact_destination == 'submit' + - name: Upload Homebrew Cask to release + if: hashFiles('homebrew-generated/termix.rb') != '' && github.event.inputs.artifact_destination == 'release' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gem install fastlane -N + VERSION="${{ steps.homebrew-version.outputs.version }}" + RELEASE_TAG="release-$VERSION-tag" - - 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 + gh release list --repo ${{ github.repository }} --limit 100 | grep -q "$RELEASE_TAG" || { + echo "Release $RELEASE_TAG not found" exit 1 - fi + } - mkdir -p ~/private_keys - echo "${{ secrets.APPLE_KEY_CONTENT }}" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_KEY_ID }}.p8 - - xcrun altool --upload-app -f "$PKG_FILE" \ - --type macos \ - --apiKey "${{ secrets.APPLE_KEY_ID }}" \ - --apiIssuer "${{ secrets.APPLE_ISSUER_ID }}" - continue-on-error: true + gh release upload "$RELEASE_TAG" homebrew-generated/termix.rb --repo ${{ github.repository }} --clobber - name: Clean up keychains if: always() @@ -508,8 +584,7 @@ jobs: submit-to-chocolatey: runs-on: windows-latest - if: github.event.inputs.artifact_destination == 'submit' - needs: [build-windows] + if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == '') permissions: contents: read @@ -525,20 +600,25 @@ jobs: $VERSION = (Get-Content package.json | ConvertFrom-Json).version echo "version=$VERSION" >> $env:GITHUB_OUTPUT - - name: Download Windows x64 MSI artifact - uses: actions/download-artifact@v4 - with: - name: termix_windows_x64_msi - path: artifact - - - name: Get MSI file info + - name: Download and prepare MSI info from public release 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 + $MSI_NAME = "termix_windows_x64_msi.msi" + $DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$($VERSION)-tag/$($MSI_NAME)" + Write-Host "Downloading from $DOWNLOAD_URL" + New-Item -ItemType Directory -Force -Path "release_asset" + $DOWNLOAD_PATH = "release_asset\$MSI_NAME" + + try { + Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $DOWNLOAD_PATH -UseBasicParsing + } catch { + Write-Error "Failed to download MSI from $DOWNLOAD_URL. Please ensure the release and asset exist." + exit 1 + } + + $CHECKSUM = (Get-FileHash -Path $DOWNLOAD_PATH -Algorithm SHA256).Hash echo "msi_name=$MSI_NAME" >> $env:GITHUB_OUTPUT echo "checksum=$CHECKSUM" >> $env:GITHUB_OUTPUT @@ -609,8 +689,8 @@ jobs: submit-to-flatpak: runs-on: ubuntu-latest - if: github.event.inputs.artifact_destination == 'submit' - needs: [build-linux] + if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == '') + needs: [] permissions: contents: read @@ -628,30 +708,27 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT - - 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 + - name: Download and prepare AppImage info from public release id: appimage-info run: | VERSION="${{ steps.package-version.outputs.version }}" + mkdir -p release_assets - APPIMAGE_X64_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}') + APPIMAGE_X64_NAME="termix_linux_x64_appimage.AppImage" + URL_X64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_X64_NAME" + PATH_X64="release_assets/$APPIMAGE_X64_NAME" + echo "Downloading x64 AppImage from $URL_X64" + curl -L -o "$PATH_X64" "$URL_X64" + chmod +x "$PATH_X64" + CHECKSUM_X64=$(sha256sum "$PATH_X64" | awk '{print $1}') - APPIMAGE_ARM64_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}') + APPIMAGE_ARM64_NAME="termix_linux_arm64_appimage.AppImage" + URL_ARM64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_ARM64_NAME" + PATH_ARM64="release_assets/$APPIMAGE_ARM64_NAME" + echo "Downloading arm64 AppImage from $URL_ARM64" + curl -L -o "$PATH_ARM64" "$URL_ARM64" + chmod +x "$PATH_ARM64" + CHECKSUM_ARM64=$(sha256sum "$PATH_ARM64" | awk '{print $1}') echo "appimage_x64_name=$APPIMAGE_X64_NAME" >> $GITHUB_OUTPUT echo "checksum_x64=$CHECKSUM_X64" >> $GITHUB_OUTPUT @@ -690,10 +767,6 @@ jobs: sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" flatpak-submission/com.karmaa.termix.metainfo.xml sed -i "s/DATE_PLACEHOLDER/$RELEASE_DATE/g" flatpak-submission/com.karmaa.termix.metainfo.xml - - name: List submission files - run: | - ls -la flatpak-submission/ - - name: Upload Flatpak submission as artifact uses: actions/upload-artifact@v4 with: @@ -703,8 +776,8 @@ jobs: submit-to-homebrew: runs-on: macos-latest - if: github.event.inputs.artifact_destination == 'submit' - needs: [build-macos] + if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'macos') + needs: [] permissions: contents: read @@ -720,19 +793,28 @@ jobs: VERSION=$(node -p "require('./package.json').version") echo "version=$VERSION" >> $GITHUB_OUTPUT - - name: Download macOS Universal DMG artifact - uses: actions/download-artifact@v4 - with: - name: termix_macos_universal_dmg - path: artifact - - - name: Get DMG file info + - name: Download and prepare DMG info from public release 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}') + DMG_NAME="termix_macos_universal_dmg.dmg" + URL="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$DMG_NAME" + + mkdir -p release_asset + DOWNLOAD_PATH="release_asset/$DMG_NAME" + echo "Downloading DMG from $URL" + + if command -v curl &> /dev/null; then + curl -L -o "$DOWNLOAD_PATH" "$URL" + elif command -v wget &> /dev/null; then + wget -O "$DOWNLOAD_PATH" "$URL" + else + echo "Neither curl nor wget is available, installing curl" + brew install curl + curl -L -o "$DOWNLOAD_PATH" "$URL" + fi + + CHECKSUM=$(shasum -a 256 "$DOWNLOAD_PATH" | awk '{print $1}') echo "dmg_name=$DMG_NAME" >> $GITHUB_OUTPUT echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT @@ -745,23 +827,15 @@ jobs: mkdir -p homebrew-submission/Casks/t - cp homebrew/termix.rb homebrew-submission/Casks/t/termix.rb + cp Casks/termix.rb homebrew-submission/Casks/t/termix.rb sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-submission/Casks/t/termix.rb sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-submission/Casks/t/termix.rb - name: Verify Cask syntax run: | - if ! command -v brew &> /dev/null; then - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - fi - ruby -c homebrew-submission/Casks/t/termix.rb - - name: List submission files - run: | - find homebrew-submission -type f - - name: Upload Homebrew submission as artifact uses: actions/upload-artifact@v4 with: @@ -789,10 +863,6 @@ jobs: env: GH_TOKEN: ${{ github.token }} - - name: Display artifact structure - run: | - ls -R artifacts/ - - name: Upload artifacts to latest release run: | cd artifacts @@ -808,3 +878,130 @@ jobs: done env: GH_TOKEN: ${{ github.token }} + + submit-to-testflight: + runs-on: macos-latest + if: github.event.inputs.artifact_destination == 'submit' && (github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'macos') + needs: [] + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 1 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: | + for i in 1 2 3; + do + if npm ci; then + break + else + if [ $i -eq 3 ]; then + exit 1 + fi + sleep 10 + fi + done + npm install --force @rollup/rollup-darwin-arm64 + npm install dmg-license + + - name: Check for Code Signing Certificates + id: check_certs + run: | + if [ -n "${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.MAC_P12_PASSWORD }}" ]; then + echo "has_certs=true" >> $GITHUB_OUTPUT + fi + + - name: Import Code Signing Certificates + if: steps.check_certs.outputs.has_certs == 'true' + env: + MAC_BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }} + MAC_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.MAC_INSTALLER_CERTIFICATE_BASE64 }} + MAC_P12_PASSWORD: ${{ secrets.MAC_P12_PASSWORD }} + MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }} + run: | + APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12 + INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH + + if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then + echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH + fi + + security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + + if [ -f "$INSTALLER_CERT_PATH" ]; then + security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + fi + + security list-keychain -d user -s $KEYCHAIN_PATH + + security find-identity -v -p codesigning $KEYCHAIN_PATH + + - name: Build macOS App Store Package + if: steps.check_certs.outputs.has_certs == 'true' + env: + ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + CURRENT_VERSION=$(node -p "require('./package.json').version") + BUILD_VERSION="${{ github.run_number }}" + + npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION" + + - name: Check for App Store Connect API credentials + id: check_asc_creds + run: | + if [ -n "${{ secrets.APPLE_KEY_ID }}" ] && [ -n "${{ secrets.APPLE_ISSUER_ID }}" ] && [ -n "${{ secrets.APPLE_KEY_CONTENT }}" ]; then + echo "has_credentials=true" >> $GITHUB_OUTPUT + fi + + - name: Setup Ruby for Fastlane + if: steps.check_asc_creds.outputs.has_credentials == 'true' + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.2" + bundler-cache: false + + - name: Install Fastlane + if: steps.check_asc_creds.outputs.has_credentials == 'true' + run: | + gem install fastlane -N + + - name: Deploy to App Store Connect (TestFlight) + if: steps.check_asc_creds.outputs.has_credentials == 'true' + run: | + PKG_FILE=$(find release -name "termix_macos_universal_mas.pkg" -type f | head -n 1) + if [ -z "$PKG_FILE" ]; then + echo "PKG file not found, exiting." + exit 1 + fi + + mkdir -p ~/private_keys + echo "${{ secrets.APPLE_KEY_CONTENT }}" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_KEY_ID }}.p8 + + xcrun altool --upload-app -f "$PKG_FILE" \ + --type macos \ + --apiKey "${{ secrets.APPLE_KEY_ID }}" \ + --apiIssuer "${{ secrets.APPLE_ISSUER_ID }}" + continue-on-error: true + + - name: Clean up keychains + if: always() + run: | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml new file mode 100644 index 00000000..7c9db568 --- /dev/null +++ b/.github/workflows/translate.yml @@ -0,0 +1,437 @@ +name: Auto Translate + +on: + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + translate-zh: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t zh --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-zh + path: src/locales/zh.json + continue-on-error: true + + translate-ru: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t ru --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-ru + path: src/locales/ru.json + continue-on-error: true + + translate-pt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t pt --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-pt + path: src/locales/pt.json + continue-on-error: true + + translate-fr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t fr --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-fr + path: src/locales/fr.json + continue-on-error: true + + translate-es: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t es --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-es + path: src/locales/es.json + continue-on-error: true + + translate-de: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t de --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-de + path: src/locales/de.json + continue-on-error: true + + translate-hi: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t hi --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-hi + path: src/locales/hi.json + continue-on-error: true + + translate-bn: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t bn --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-bn + path: src/locales/bn.json + continue-on-error: true + + translate-ja: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t ja --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-ja + path: src/locales/ja.json + continue-on-error: true + + translate-vi: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t vi --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-vi + path: src/locales/vi.json + continue-on-error: true + + translate-tr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t tr --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-tr + path: src/locales/tr.json + continue-on-error: true + + translate-ko: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t ko --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-ko + path: src/locales/ko.json + continue-on-error: true + + translate-it: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t it --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-it + path: src/locales/it.json + continue-on-error: true + + translate-he: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t he --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-he + path: src/locales/he.json + continue-on-error: true + + translate-ar: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t ar --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-ar + path: src/locales/ar.json + continue-on-error: true + + translate-pl: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t pl --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-pl + path: src/locales/pl.json + continue-on-error: true + + translate-nl: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t nl --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-nl + path: src/locales/nl.json + continue-on-error: true + + translate-sv: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t sv --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-sv + path: src/locales/sv.json + continue-on-error: true + + translate-id: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t id --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-id + path: src/locales/id.json + continue-on-error: true + + translate-th: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t th --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-th + path: src/locales/th.json + continue-on-error: true + + translate-uk: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t uk --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-uk + path: src/locales/uk.json + continue-on-error: true + + translate-cs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t cs --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-cs + path: src/locales/cs.json + continue-on-error: true + + translate-ro: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t ro --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-ro + path: src/locales/ro.json + continue-on-error: true + + translate-el: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t el --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-el + path: src/locales/el.json + continue-on-error: true + + translate-nb: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - run: npx i18n-auto-translation -k ${{ secrets.GOOGLE_TRANSLATE_API_KEY }} -d "src/locales" -f en -t nb --maxLinesPerRequest 1 + - uses: actions/upload-artifact@v4 + with: + name: translations-nb + path: src/locales/nb.json + continue-on-error: true + + create-pr: + needs: + [ + translate-zh, + translate-ru, + translate-pt, + translate-fr, + translate-es, + translate-de, + translate-hi, + translate-bn, + translate-ja, + translate-vi, + translate-tr, + translate-ko, + translate-it, + translate-he, + translate-ar, + translate-pl, + translate-nl, + translate-sv, + translate-id, + translate-th, + translate-uk, + translate-cs, + translate-ro, + translate-el, + translate-nb, + ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GHCR_TOKEN }} + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: translations-temp + + - name: Move translations to src/locales + run: | + cp translations-temp/translations-zh/zh.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-ru/ru.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-pt/pt.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-fr/fr.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-es/es.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-de/de.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-hi/hi.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-bn/bn.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-ja/ja.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-vi/vi.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-tr/tr.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-ko/ko.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-it/it.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-he/he.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-ar/ar.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-pl/pl.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-nl/nl.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-sv/sv.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-id/id.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-th/th.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-uk/uk.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-cs/cs.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-ro/ro.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-el/el.json src/locales/ 2>/dev/null || true + cp translations-temp/translations-nb/nb.json src/locales/ 2>/dev/null || true + rm -rf translations-temp + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GHCR_TOKEN }} + commit-message: "chore: auto-translate to multiple languages" + branch: translations-update + delete-branch: true + title: "chore: Update translations for all languages" diff --git a/.gitignore b/.gitignore index af4f217b..c3d02880 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ dist-ssr /.mcp.json /nul /.vscode/ +/CLAUDE.md diff --git a/Casks/termix.rb b/Casks/termix.rb index 6f6d0d86..ccbeab85 100644 --- a/Casks/termix.rb +++ b/Casks/termix.rb @@ -1,6 +1,6 @@ cask "termix" do - version "1.9.0" - sha256 "8fedd242b3cae1ebfd0c391a36f1c246a26ecac258b02478ee8dea2f33cd6d96" + version "1.10.0" + sha256 "327c5026006c949f992447835aa6754113f731065b410bedbfa5da5af7cb2386" url "https://github.com/Termix-SSH/Termix/releases/download/release-#{version}-tag/termix_macos_universal_dmg.dmg" name "Termix" diff --git a/README-CN.md b/README-CN.md index a882666c..593c6a23 100644 --- a/README-CN.md +++ b/README-CN.md @@ -51,20 +51,22 @@ Termix 是一个开源、永久免费、自托管的一体化服务器管理平 - **SSH 终端访问** - 功能齐全的终端,具有分屏支持(最多 4 个面板)和类似浏览器的选项卡系统。包括对自定义终端的支持,包括常见终端主题、字体和其他组件 - **SSH 隧道管理** - 创建和管理 SSH 隧道,具有自动重新连接和健康监控功能 - **远程文件管理器** - 直接在远程服务器上管理文件,支持查看和编辑代码、图像、音频和视频。无缝上传、下载、重命名、删除和移动文件 +- **Docker 管理** - 启动、停止、暂停、删除容器。查看容器统计信息。使用 docker exec 终端控制容器。它不是用来替代 Portainer 或 Dockge,而是用于简单管理你的容器而不是创建它们。 - **SSH 主机管理器** - 保存、组织和管理您的 SSH 连接,支持标签和文件夹,并轻松保存可重用的登录信息,同时能够自动部署 SSH 密钥 - **服务器统计** - 在任何 SSH 服务器上查看 CPU、内存和磁盘使用情况以及网络、正常运行时间和系统信息 - **仪表板** - 在仪表板上一目了然地查看服务器信息 +- **RBAC** - 创建角色并在用户/角色之间共享主机 - **用户认证** - 安全的用户管理,具有管理员控制以及 OIDC 和 2FA (TOTP) 支持。查看所有平台上的活动用户会话并撤销权限。将您的 OIDC/本地帐户链接在一起。 - **数据库加密** - 后端存储为加密的 SQLite 数据库文件。查看[文档](https://docs.termix.site/security)了解更多信息。 - **数据导出/导入** - 导出和导入 SSH 主机、凭据和文件管理器数据 - **自动 SSL 设置** - 内置 SSL 证书生成和管理,支持 HTTPS 重定向 -- **现代用户界面** - 使用 React、Tailwind CSS 和 Shadcn 构建的简洁的桌面/移动设备友好界面 -- **语言** - 内置支持英语、中文、德语和葡萄牙语 +- **现代用户界面** - 使用 React、Tailwind CSS 和 Shadcn 构建的简洁的桌面/移动设备友好界面。可选择基于深色或浅色模式的用户界面。 +- **语言** - 内置支持约 30 种语言(通过 Google 翻译批量翻译,结果可能有所不同) - **平台支持** - 可作为 Web 应用程序、桌面应用程序(Windows、Linux 和 macOS)以及适用于 iOS 和 Android 的专用移动/平板电脑应用程序。 - **SSH 工具** - 创建可重用的命令片段,单击即可执行。在多个打开的终端上同时运行一个命令。 - **命令历史** - 自动完成并查看以前运行的 SSH 命令 - **命令面板** - 双击左 Shift 键可快速使用键盘访问 SSH 连接 -- **SSH 功能丰富** - 支持跳板机、warpgate、基于 TOTP 的连接等。 +- **SSH 功能丰富** - 支持跳板机、warpgate、基于 TOTP 的连接、SOCKS5、密码自动填充等。 # 计划功能 @@ -140,6 +142,12 @@ volumes:
+
+
+
+
@@ -147,7 +155,7 @@ volumes: 你的浏览器不支持 video 标签。
-视频和图像可能已过时。 +某些视频和图像可能已过时或可能无法完美展示功能。 # 许可证 diff --git a/README.md b/README.md index 0128358a..b13da02b 100644 --- a/README.md +++ b/README.md @@ -16,17 +16,6 @@ Achieved on September 1st, 2025 -#### Top Technologies - -[](#) -[](#) -[](#) -[](#) -[](#) -[](#) -[](#) -[](#) -