# Release pipeline (ADR-0011 §3): pushing a `vX.Y.Z[-(alpha|beta|rc).N]` tag on # `main` builds every package and publishes them, with SHA256SUMS, in a single # GitHub Release. A tag with a suffix becomes a pre-release (the internal # channel). A manual run builds the same packages as workflow artifacts and # publishes nothing, to try the pipeline without spending a version. # # The version comes only from the tag (or the manual input) and is passed to # Gradle as -PtitanVersion, which validates it and derives every per-format # version from it (root build.gradle.kts). A release version makes the level-3 # agent mandatory, so a missing Go toolchain fails the build. # # The agent job builds every titan-agent binary once. They are published with # the Release, where the app downloads the targets it does not bundle # (ADR-0010), and every other job takes these same files through # -PtitanAgentPrebuilt, so the digests pinned in each app match the assets. # # The publish job also writes titan-update.json, the list of packages with # their SHA-256, signed with TITAN_UPDATE_SIGNING_KEY: the apps update # themselves only through it (ADR-0025). # # Once the Release exists, the aur and winget jobs hand the new version to # those package managers. # # Canary (ADR-0027): every push to main that touches more than docs and the # product page in site/ (or a # manual run with channel=canary on main) builds the canary app, a separate # app installed next to the stable one, and publishes it in the rolling # `canary` pre-release, replaced as a whole each time. Its version is the patch # after the last stable tag plus `-canary.`; Gradle gets the # channel through ORG_GRADLE_PROJECT_titanChannel. Only the APK, the MSI and # the tar.gz are built, and nothing goes to AUR or winget. # # Third-party actions are pinned by commit SHA and the Komac binary by its # SHA-256. Secrets are only read by the Android, publish, AUR and winget jobs, and this # workflow never runs for pull requests. name: Release on: push: tags: ['v*'] branches: [main] # Path filters only apply to the branch: a tag always builds. paths-ignore: - 'docs/**' - '**.md' - 'site/**' - '.github/workflows/pages.yml' workflow_dispatch: inputs: channel: description: 'stable: build the version below and publish nothing. canary: build and publish the canary (main only).' type: choice options: [stable, canary] default: stable version: description: 'Stable version to build: X.Y.Z or X.Y.Z-(alpha|beta|rc).N (ignored for canary)' required: true default: '0.0.1-alpha.1' permissions: contents: read concurrency: group: release-${{ github.ref }} # A newer canary supersedes the one being built; a release never stops. cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} env: JAVA_VERSION: '21' # The desktop packages bundle this JDK through jpackage. It is the Microsoft # Build of OpenJDK, the first 21 build with the October 2026 CA blocklist, # downloaded and checked here because setup-java's Microsoft manifest does # not list it yet. DESKTOP_JDK_VERSION: '21.0.13' DESKTOP_JDK_URL: https://aka.ms/download-jdk/microsoft-jdk-21.0.13.0.2 DESKTOP_JDK_LINUX_SHA256: 5bf17d27de3fe9d102981236cdea68482e3b5ef68a44ad21c6439b45d7d34da2 DESKTOP_JDK_WINDOWS_SHA256: c5706a9cf482ef1a0622a86eea5b13d557b07f04554572af061e2d02b58c0f99 # The agent's go.mod only states the minimum language version; the shipped # binaries are built with a current, supported toolchain. GO_VERSION: '1.27.x' GRADLE_OPTS: -Dorg.gradle.daemon=false # SHA-256 of the release signing certificate (public: it is inside every APK). # The Android job refuses to publish an APK signed with any other key, since # Obtainium and IzzyOnDroid users could not update over it. ANDROID_CERT_SHA256: 3929d8997ac2b23f61517a0561bbb10fe3d9e4a0a6547ee28e703c87c3ecdeab jobs: version: name: Resolve version runs-on: ubuntu-latest timeout-minutes: 5 outputs: version: ${{ steps.resolve.outputs.version }} prerelease: ${{ steps.resolve.outputs.prerelease }} channel: ${{ steps.resolve.outputs.channel }} app: ${{ steps.resolve.outputs.app }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - id: resolve env: RAW: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }} CANARY: ${{ github.ref_type == 'branch' && (github.event_name == 'push' || inputs.channel == 'canary') }} run: | if [[ "$CANARY" == true ]]; then if [[ "$GITHUB_REF" != refs/heads/main ]]; then echo "::error::The canary is only built from main" exit 1 fi # The patch after the last stable tag: canary.N of what comes next. last=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1) [[ -n "$last" ]] || { echo "::error::No stable tag to base the canary on"; exit 1; } IFS=. read -r major minor patch <<<"${last#v}" version="$major.$minor.$((patch + 1))-canary.$GITHUB_RUN_NUMBER" { echo "version=$version" echo "prerelease=true" echo "channel=canary" echo "app=titan-ssh-canary" } >> "$GITHUB_OUTPUT" echo "Canary $version" exit 0 fi version="${RAW#v}" # Gradle performs the authoritative check; this only fails fast. if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc)\.[0-9]+)?$ ]]; then echo "::error::Invalid version '$RAW': expected X.Y.Z or X.Y.Z-(alpha|beta|rc).N" exit 1 fi if [[ "$GITHUB_EVENT_NAME" == push ]] && ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then echo "::error::Tag $GITHUB_REF_NAME does not point to a commit on main" exit 1 fi prerelease=false [[ "$version" == *-* ]] && prerelease=true { echo "version=$version" echo "prerelease=$prerelease" echo "channel=stable" echo "app=titan-ssh" } >> "$GITHUB_OUTPUT" echo "Version $version (pre-release: $prerelease)" agent: name: Agent binaries needs: version runs-on: ubuntu-latest timeout-minutes: 20 env: VERSION: ${{ needs.version.outputs.version }} ORG_GRADLE_PROJECT_titanChannel: ${{ needs.version.outputs.channel }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: ${{ env.GO_VERSION }} cache-dependency-path: agent/go.sum - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build run: ./gradlew :shared:agentReleaseAssets "-PtitanVersion=$VERSION" --console=plain - name: List working-directory: shared/build/agentBinaries/release run: ls -l && sha256sum -- * - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: agent path: shared/build/agentBinaries/release/ if-no-files-found: error test: name: Tests needs: [version, agent] runs-on: ubuntu-22.04 timeout-minutes: 30 env: VERSION: ${{ needs.version.outputs.version }} CHANNEL: ${{ needs.version.outputs.channel }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: ${{ env.GO_VERSION }} cache-dependency-path: agent/go.sum - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent path: ${{ runner.temp }}/agent - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Agent tests working-directory: agent # The canary agent is built with -tags canary (ADR-0027): vet that too. run: | go test -race -count=1 ./... go vet -tags canary ./... # The tests expect the stable app's names, so a canary run tests the # same code as a development build (it builds the agent itself). - name: Desktop tests run: | if [[ "$CHANNEL" == canary ]]; then ./gradlew :shared:desktopTest --console=plain else ./gradlew :shared:desktopTest "-PtitanVersion=$VERSION" "-PtitanAgentPrebuilt=$RUNNER_TEMP/agent" --console=plain fi android: name: Android APK and AAB needs: [version, agent] runs-on: ubuntu-latest timeout-minutes: 45 env: VERSION: ${{ needs.version.outputs.version }} APP: ${{ needs.version.outputs.app }} ORG_GRADLE_PROJECT_titanChannel: ${{ needs.version.outputs.channel }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: temurin java-version: ${{ env.JAVA_VERSION }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent path: ${{ runner.temp }}/agent - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Restore the signing keystore env: KEYSTORE_BASE64: ${{ secrets.TITAN_KEYSTORE_BASE64 }} run: | if [[ -z "$KEYSTORE_BASE64" ]]; then echo "::error::Secret TITAN_KEYSTORE_BASE64 is not set; an unsigned APK cannot be released" exit 1 fi echo "$KEYSTORE_BASE64" | base64 --decode > "$RUNNER_TEMP/release.jks" - name: Build env: TITAN_KEYSTORE_FILE: ${{ runner.temp }}/release.jks TITAN_KEY_ALIAS: ${{ secrets.TITAN_KEY_ALIAS }} TITAN_KEYSTORE_PASSWORD: ${{ secrets.TITAN_KEYSTORE_PASSWORD }} # PKCS12: the key shares the store password. TITAN_KEY_PASSWORD: ${{ secrets.TITAN_KEYSTORE_PASSWORD }} run: | # The canary is never on Google Play: no AAB. tasks=(:androidApp:assembleRelease) [[ "$ORG_GRADLE_PROJECT_titanChannel" == canary ]] || tasks+=(:androidApp:bundleRelease) ./gradlew "${tasks[@]}" "-PtitanVersion=$VERSION" "-PtitanAgentPrebuilt=$RUNNER_TEMP/agent" --console=plain - name: Remove the keystore if: always() run: rm -f "$RUNNER_TEMP/release.jks" - name: Check the signature and collect run: | apk=androidApp/build/outputs/apk/release/androidApp-release.apk aab=androidApp/build/outputs/bundle/release/androidApp-release.aab # Without a signing config AGP writes androidApp-release-unsigned.apk. test -f "$apk" || { echo "::error::No signed APK was produced"; exit 1; } apksigner="$(find "$ANDROID_HOME/build-tools" -maxdepth 2 -name apksigner | sort -V | tail -n 1)" "$apksigner" verify --print-certs "$apk" | tee "$RUNNER_TEMP/certs.txt" grep -qi "certificate SHA-256 digest: $ANDROID_CERT_SHA256" "$RUNNER_TEMP/certs.txt" \ || { echo "::error::The APK is not signed with the release key"; exit 1; } # Stable, unambiguous names: Obtainium picks the only .apk asset. mkdir dist cp "$apk" "dist/$APP-$VERSION.apk" if [[ -f "$aab" ]]; then cp "$aab" "dist/$APP-$VERSION.aab"; fi - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: android path: dist/ if-no-files-found: error linux: name: Linux deb, rpm and tar.gz needs: [version, agent] # jpackage takes the .deb dependencies from the build machine: one built on # 24.04 needs libpng16-16t64, missing from 22.04 and Debian 12. The .rpm # only ever declares xdg-utils (jpackage 21 looks up the library packages, # even inside Fedora, but does not write them), so it is built here too. runs-on: ubuntu-22.04 timeout-minutes: 30 env: VERSION: ${{ needs.version.outputs.version }} ORG_GRADLE_PROJECT_titanChannel: ${{ needs.version.outputs.channel }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Download the desktop JDK run: | curl -fsSL --retry 3 -o "$RUNNER_TEMP/jdk.tar.gz" "$DESKTOP_JDK_URL-linux-x64.tar.gz" cd "$RUNNER_TEMP" && echo "$DESKTOP_JDK_LINUX_SHA256 jdk.tar.gz" | sha256sum -c - - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: jdkfile jdk-file: ${{ runner.temp }}/jdk.tar.gz java-version: ${{ env.DESKTOP_JDK_VERSION }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent path: ${{ runner.temp }}/agent - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 # jpackage only declares the libraries it finds installed, so they are # installed explicitly instead of relying on the runner image. - name: Install packaging tools and the app's libraries run: > sudo apt-get update && sudo apt-get install -y --no-install-recommends fakeroot rpm libx11-6 libxext6 libxi6 libxrender1 libxtst6 libasound2 libfreetype6 libfontconfig1 - name: Build run: | # The canary updates itself from the tar.gz only. tasks=(:desktopApp:packageTarGz) [[ "$ORG_GRADLE_PROJECT_titanChannel" == canary ]] || tasks+=(:desktopApp:packageDeb :desktopApp:packageRpm) ./gradlew "${tasks[@]}" "-PtitanVersion=$VERSION" "-PtitanAgentPrebuilt=$RUNNER_TEMP/agent" --console=plain - name: Collect run: | mkdir dist shopt -s nullglob cp desktopApp/build/compose/binaries/main/deb/*.deb desktopApp/build/compose/binaries/main/rpm/*.rpm dist/ 2>/dev/null || true cp desktopApp/build/compose/binaries/main/tar.gz/*.tar.gz dist/ # GitHub turns `~` into `.` in asset names, which would break # SHA256SUMS. The version inside the packages keeps its `~`. # (A stable version has no `~`: nullglob skips the loop.) for f in dist/*~*; do mv "$f" "${f//\~/-}"; done ls -l dist if [[ "$ORG_GRADLE_PROJECT_titanChannel" != canary ]]; then dpkg-deb --field dist/*.deb Depends rpm -qpR dist/*.rpm fi - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: linux path: dist/ if-no-files-found: error windows: name: Windows MSI needs: [version, agent] runs-on: windows-latest timeout-minutes: 30 env: VERSION: ${{ needs.version.outputs.version }} APP: ${{ needs.version.outputs.app }} ORG_GRADLE_PROJECT_titanChannel: ${{ needs.version.outputs.channel }} defaults: run: shell: bash steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Download the desktop JDK run: | curl -fsSL --retry 3 -o "$RUNNER_TEMP/jdk.zip" "$DESKTOP_JDK_URL-windows-x64.zip" cd "$RUNNER_TEMP" && echo "$DESKTOP_JDK_WINDOWS_SHA256 jdk.zip" | sha256sum -c - - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: distribution: jdkfile jdk-file: ${{ runner.temp }}/jdk.zip java-version: ${{ env.DESKTOP_JDK_VERSION }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent path: ${{ runner.temp }}/agent - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 - name: Build run: ./gradlew :desktopApp:packageMsi "-PtitanVersion=$VERSION" "-PtitanAgentPrebuilt=$RUNNER_TEMP/agent" --console=plain - name: Collect run: | # jpackage names the MSI after its internal X.Y.(Z*100+S) version; # the published name carries the real one. mkdir dist cp desktopApp/build/compose/binaries/main/msi/*.msi "dist/$APP-$VERSION-windows-x64.msi" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: windows path: dist/ if-no-files-found: error publish: name: Publish the GitHub Release if: github.event_name == 'push' && github.ref_type == 'tag' needs: [version, agent, test, android, linux, windows] runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write env: VERSION: ${{ needs.version.outputs.version }} PRERELEASE: ${{ needs.version.outputs.prerelease }} GH_TOKEN: ${{ github.token }} steps: # Only the manifest script and the public keys the apps trust. - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: | tools/release shared/update-keys.txt sparse-checkout-cone-mode: false - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: dist merge-multiple: true # ADR-0025: the apps install a package only through this signed manifest. - name: Update manifest env: TITAN_UPDATE_SIGNING_KEY: ${{ secrets.TITAN_UPDATE_SIGNING_KEY }} run: bash tools/release/update-manifest.sh dist "$VERSION" shared/update-keys.txt stable - name: Checksums working-directory: dist run: | sha256sum -- * > SHA256SUMS cat SHA256SUMS - name: Create the release run: | flags=() [[ "$PRERELEASE" == true ]] && flags+=(--prerelease) gh release create "$GITHUB_REF_NAME" dist/* \ --repo "$GITHUB_REPOSITORY" \ --verify-tag \ --title "titan-ssh $VERSION" \ --generate-notes \ "${flags[@]}" # ADR-0027: the rolling canary pre-release. It is deleted with its tag and # created again on this commit, so it only ever holds the newest build; the # canary app reads it through releases/tags/canary. Never "latest", and the # stable app ignores pre-releases. canary: name: Publish the canary if: needs.version.outputs.channel == 'canary' needs: [version, agent, test, android, linux, windows] runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write env: VERSION: ${{ needs.version.outputs.version }} GH_TOKEN: ${{ github.token }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: | tools/release shared/update-keys.txt sparse-checkout-cone-mode: false - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: dist merge-multiple: true - name: Update manifest env: TITAN_UPDATE_SIGNING_KEY: ${{ secrets.TITAN_UPDATE_SIGNING_KEY }} run: bash tools/release/update-manifest.sh dist "$VERSION" shared/update-keys.txt canary - name: Checksums working-directory: dist run: | sha256sum -- * > SHA256SUMS cat SHA256SUMS - name: Replace the canary release run: | gh release delete canary --repo "$GITHUB_REPOSITORY" --cleanup-tag --yes 2>/dev/null || true gh release create canary dist/* \ --repo "$GITHUB_REPOSITORY" \ --target "$GITHUB_SHA" \ --title "titan-ssh canary $VERSION" \ --notes "Build canary de main ($GITHUB_SHA) para probar antes de publicar. No es una versión: se sustituye con cada build. Se instala junto a titan-ssh como una app aparte (ADR-0027)." \ --prerelease \ --latest=false # ADR-0011 §4: package managers fed from the published Release. Stable # versions go to titan-ssh-bin / DanielPerezMartinez.TitanSSH and # pre-releases to titan-ssh-beta-bin / DanielPerezMartinez.TitanSSH.Beta, so # stable users never get a beta. Each channel's first version is submitted by # hand (see the task notes); from then on these jobs keep them up to date. # Each job only runs once its repository variable (AUR_ENABLED, # WINGET_ENABLED) is 'true', i.e. after that first version exists: before, # it would fail on every release. aur: name: Publish to AUR if: github.event_name == 'push' && vars.AUR_ENABLED == 'true' needs: [version, publish] runs-on: ubuntu-latest # makepkg only exists on Arch. container: archlinux:base-devel timeout-minutes: 15 env: VERSION: ${{ needs.version.outputs.version }} steps: - name: Install git and ssh run: pacman -Syu --noconfirm --needed git openssh - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: linux path: dist - name: Build the package run: | sha256="$(sha256sum "dist/titan-ssh-$VERSION-linux-x64.tar.gz" | cut -d' ' -f1)" desktopApp/packaging/aur/render.sh "$VERSION" "$sha256" /tmp/aur # makepkg refuses to run as root. useradd -m builder chown -R builder /tmp/aur # Downloads the tar.gz from the Release and checks it against the # SHA-256 of the one built here; -d skips installing the runtime # dependencies, which packaging does not need. runuser -u builder -- bash -c 'cd /tmp/aur && makepkg -fd --noconfirm && makepkg --printsrcinfo > .SRCINFO' - name: Push to AUR env: AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} run: | if [[ -z "$AUR_SSH_PRIVATE_KEY" ]]; then echo "::error::Secret AUR_SSH_PRIVATE_KEY is not set" exit 1 fi install -d -m 700 ~/.ssh printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur chmod 600 ~/.ssh/aur cp desktopApp/packaging/aur/known_hosts ~/.ssh/known_hosts export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/aur -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes" pkgname="$(sed -n 's/^pkgname=//p' /tmp/aur/PKGBUILD)" # Cloning a package that does not exist yet gives an empty repository, # and the first push creates it. git clone "ssh://aur@aur.archlinux.org/$pkgname.git" /tmp/repo cp /tmp/aur/PKGBUILD /tmp/aur/.SRCINFO /tmp/repo/ cd /tmp/repo git config user.name 'Daniel Pérez Martínez' git config user.email 'danielperezmartinez@users.noreply.github.com' git add PKGBUILD .SRCINFO if git diff --cached --quiet; then echo "$pkgname is already at $VERSION" exit 0 fi git commit -m "Update to $VERSION" git push origin HEAD:master - name: Remove the key if: always() run: rm -f ~/.ssh/aur winget: name: Submit to winget if: github.event_name == 'push' && vars.WINGET_ENABLED == 'true' needs: [version, publish] runs-on: ubuntu-latest timeout-minutes: 15 env: VERSION: ${{ needs.version.outputs.version }} PRERELEASE: ${{ needs.version.outputs.prerelease }} # Komac opens the pull request on microsoft/winget-pkgs from the token # owner's fork, and reads the MSI to fill ProductCode, UpgradeCode and the # DisplayVersion (the MSI's X.Y.(Z*100+S) version). Pinned by hash. KOMAC_VERSION: '2.16.0' KOMAC_SHA256: 7d2707fa6210f2789a3702de49fbd150b736dbf426ee0b9bc8e098736f9fd82d steps: - name: Install Komac run: | archive="$RUNNER_TEMP/komac.tar.gz" curl -fsSL -o "$archive" \ "https://github.com/russellbanks/Komac/releases/download/v$KOMAC_VERSION/komac-$KOMAC_VERSION-x86_64-unknown-linux-gnu.tar.gz" echo "$KOMAC_SHA256 $archive" | sha256sum -c - tar -xzf "$archive" -C "$RUNNER_TEMP" komac - name: Open the pull request env: GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }} run: | if [[ -z "$GITHUB_TOKEN" ]]; then echo "::error::Secret WINGET_TOKEN is not set" exit 1 fi id=DanielPerezMartinez.TitanSSH [[ "$PRERELEASE" == true ]] && id+=.Beta base="https://github.com/$GITHUB_REPOSITORY/releases" "$RUNNER_TEMP/komac" update "$id" \ --version "$VERSION" \ --urls "$base/download/$GITHUB_REF_NAME/titan-ssh-$VERSION-windows-x64.msi" \ --release-notes-url "$base/tag/$GITHUB_REF_NAME" \ --submit