bssh 2.4.3

Parallel SSH command execution tool for cluster management
Documentation
name: Update Homebrew Formula

on:
  workflow_dispatch:
    inputs:
      release_tag:
        description: 'Release tag (e.g. v0.6.3)'
        required: false

  workflow_call:
    inputs:
      release_tag:
        description: 'Release tag passed from caller workflow'
        required: false
        type: string

permissions:
  contents: write

jobs:
  update-homebrew:
    name: Update Homebrew Formula
    runs-on: macos-latest
    environment: packaging

    # No job-level `if` here, on purpose. This job used to carry
    #
    #     if: github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call'
    #
    # which is unsatisfiable on the `uses:` path. Inside a called workflow
    # `github.event_name` reports the *caller's* originating event, which is
    # `release` during a release run, and never `workflow_call`. So the job was
    # skipped in 0s on every release (v2.4.1 job 91700817094, v2.4.2 job
    # 94746343757) and the tap had to be bumped afterwards by hand.
    #
    # The `on:` block above already declares the only two ways this workflow can
    # start, so an event gate here can only exclude legitimate runs. Whether the
    # formula should be updated at all is the caller's decision; see the
    # `update-homebrew` job in release.yml.

    steps:
      - name: Checkout this repository
        uses: actions/checkout@v6

      - name: Install gnu-sed
        run: brew install gnu-sed

      - name: Determine version tag
        id: get_version
        run: |
          # Check workflow_dispatch input first, then workflow_call input
          INPUT_TAG="${{ github.event.inputs.release_tag || inputs.release_tag }}"
          if [ -n "$INPUT_TAG" ]; then
            echo "VERSION=$INPUT_TAG" >> $GITHUB_ENV
          else
            # Get the latest official (non-pre-release) release
            TAG=$(gh release view --json tagName --jq .tagName)
            echo "VERSION=$TAG" >> $GITHUB_ENV
          fi
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Clone Homebrew tap repository
        run: |
          git clone https://x-access-token:${{ secrets.HOMEBREW_TAP_TOKEN }}@github.com/lablup/homebrew-tap.git
          cd homebrew-tap
          git config user.name "GitHub Action"
          git config user.email "actions@github.com"

      - name: Download release artifacts and calculate SHA256
        run: |
          cd homebrew-tap
          set -euo pipefail

          RAW_VERSION="${{ env.VERSION }}"
          VERSION="${RAW_VERSION#v}"  # Remove leading 'v'
          echo "VERSION_NO_V=$VERSION" >> $GITHUB_ENV  # Export version without 'v' to GitHub env

          BASE="https://github.com/lablup/bssh/releases/download/v${VERSION}"
          mkdir -p tmp

          # `-f` so a missing or renamed asset fails here rather than leaving a
          # zero-byte file whose checksum gets committed to the tap, and an
          # archive integrity check so a truncated download cannot turn into a
          # valid-looking sha256 either.
          fetch() {  # name url dest
            local name="$1" url="$2" dest="$3"
            curl -fLs --retry 3 "$url" -o "$dest"
            if [ ! -s "$dest" ]; then
              echo "::error::Empty download: ${url}"
              exit 1
            fi
            case "$dest" in
              *.zip)    unzip -qqt "$dest" > /dev/null ;;
              *.tar.gz) tar -tzf "$dest" > /dev/null ;;
            esac
            {
              echo "${name}_url=${url}"
              echo "${name}_sha=$(shasum -a 256 "$dest" | awk '{print $1}')"
            } >> "$GITHUB_ENV"
          }

          fetch mac              "${BASE}/bssh-macos-aarch64.zip"            tmp/mac.zip
          fetch linux_arm        "${BASE}/bssh-linux-aarch64.tar.gz"         tmp/linux-arm.tar.gz
          fetch linux_x86        "${BASE}/bssh-linux-x86_64.tar.gz"          tmp/linux-x86.tar.gz

          fetch server_mac       "${BASE}/bssh-server-macos-aarch64.zip"     tmp/server-mac.zip
          fetch server_linux_arm "${BASE}/bssh-server-linux-aarch64.tar.gz"  tmp/server-linux-arm.tar.gz
          fetch server_linux_x86 "${BASE}/bssh-server-linux-x86_64.tar.gz"   tmp/server-linux-x86.tar.gz

          fetch keygen_mac       "${BASE}/bssh-keygen-macos-aarch64.zip"     tmp/keygen-mac.zip
          fetch keygen_linux_arm "${BASE}/bssh-keygen-linux-aarch64.tar.gz"  tmp/keygen-linux-arm.tar.gz
          fetch keygen_linux_x86 "${BASE}/bssh-keygen-linux-x86_64.tar.gz"   tmp/keygen-linux-x86.tar.gz

      - name: Update formulae
        run: |
          cd homebrew-tap
          set -euo pipefail

          VERSION="${{ env.VERSION_NO_V }}"  # Use version without 'v'

          # Stanzas are located by name, never by line offset. This step used to
          # rewrite each checksum with
          #
          #     gsed -i "/ARTIFACT\"/!b;n;c\      sha256 \"...\""
          #
          # which matched the url line, advanced one line, and replaced whatever
          # it landed on. That holds only while sha256 sits directly beneath url
          # inside an on_macos / on_linux block. lablup/mlxcel ran the same
          # construct against a formula whose url had moved to the top level
          # with a `version` stanza in between: it overwrote `version` with the
          # new checksum and left the previous release's sha256 below, so the
          # formula had no version, two sha256 lines, and failed `brew install`
          # on checksum mismatch. It sat broken in the tap for three days
          # (lablup/homebrew-tap 9969ec4). Nothing here depends on layout now,
          # and the guards abort instead of editing the wrong line.

          set_version() {  # file version
            local file="$1" version="$2" n
            n=$(grep -c "^[[:space:]]*version " "$file" || true)
            if [ "$n" -ne 1 ]; then
              echo "::error::${file}: expected exactly one version stanza, found ${n}."
              exit 1
            fi
            gsed -i "s|^\([[:space:]]*\)version .*|\1version \"${version}\"|" "$file"
          }

          # Rewrites the url stanza whose value contains ARTIFACT, then the
          # first sha256 stanza below it, preserving each line's indentation.
          set_artifact() {  # file artifact url sha
            local file="$1" artifact="$2" url="$3" sha="$4" n
            n=$(awk -v a="$artifact" '$0 ~ /^[[:space:]]*url / && index($0, a) { c++ } END { print c+0 }' "$file")
            if [ "$n" -ne 1 ]; then
              echo "::error::${file}: expected exactly one url stanza for ${artifact}, found ${n}."
              exit 1
            fi
            awk -v a="$artifact" -v u="$url" -v s="$sha" '
              !seen && $0 ~ /^[[:space:]]*url / && index($0, a) {
                match($0, /^[[:space:]]*/)
                printf "%surl \"%s\"\n", substr($0, 1, RLENGTH), u
                seen = 1
                next
              }
              seen && !hit && $0 ~ /^[[:space:]]*sha256 / {
                match($0, /^[[:space:]]*/)
                printf "%ssha256 \"%s\"\n", substr($0, 1, RLENGTH), s
                hit = 1
                next
              }
              { print }
              END { exit(hit ? 0 : 1) }
            ' "$file" > "${file}.new" || {
              echo "::error::${file}: no sha256 stanza follows the url for ${artifact}."
              rm -f "${file}.new"
              exit 1
            }
            mv "${file}.new" "$file"
          }

          set_version  Formula/bssh.rb "$VERSION"
          set_artifact Formula/bssh.rb bssh-macos-aarch64.zip    "$mac_url"       "$mac_sha"
          set_artifact Formula/bssh.rb bssh-linux-aarch64.tar.gz "$linux_arm_url" "$linux_arm_sha"
          set_artifact Formula/bssh.rb bssh-linux-x86_64.tar.gz  "$linux_x86_url" "$linux_x86_sha"

          if [ -f Formula/bssh-server.rb ]; then
            set_version  Formula/bssh-server.rb "$VERSION"
            set_artifact Formula/bssh-server.rb bssh-server-macos-aarch64.zip    "$server_mac_url"       "$server_mac_sha"
            set_artifact Formula/bssh-server.rb bssh-server-linux-aarch64.tar.gz "$server_linux_arm_url" "$server_linux_arm_sha"
            set_artifact Formula/bssh-server.rb bssh-server-linux-x86_64.tar.gz  "$server_linux_x86_url" "$server_linux_x86_sha"
          else
            echo "Warning: Formula/bssh-server.rb not found, skipping server formula update"
          fi

          if [ -f Formula/bssh-keygen.rb ]; then
            set_version  Formula/bssh-keygen.rb "$VERSION"
            set_artifact Formula/bssh-keygen.rb bssh-keygen-macos-aarch64.zip    "$keygen_mac_url"       "$keygen_mac_sha"
            set_artifact Formula/bssh-keygen.rb bssh-keygen-linux-aarch64.tar.gz "$keygen_linux_arm_url" "$keygen_linux_arm_sha"
            set_artifact Formula/bssh-keygen.rb bssh-keygen-linux-x86_64.tar.gz  "$keygen_linux_x86_url" "$keygen_linux_x86_sha"
          else
            echo "Warning: Formula/bssh-keygen.rb not found, skipping keygen formula update"
          fi

      - name: Validate updated formulae
        run: |
          cd homebrew-tap
          set -euo pipefail

          VERSION="${{ env.VERSION_NO_V }}"

          # `brew style` is the check the tap itself is expected to pass, and it
          # only applies formula cops to paths under Formula/. `ruby -c` is not
          # sufficient on its own: the mlxcel corruption described above was
          # valid Ruby and would have passed a syntax check.
          check() {  # file expected_sha...
            local file="$1"; shift
            local n sha

            echo "=== ${file} ==="
            cat "$file"

            ruby -c "$file"
            brew style "$file"

            if ! grep -q "^[[:space:]]*version \"${VERSION}\"$" "$file"; then
              echo "::error::${file}: version stanza is not ${VERSION}."
              exit 1
            fi

            # A substitution that silently no-ops would leave the previous
            # release's checksum behind, so require the exact expected set.
            n=$(grep -c "^[[:space:]]*sha256 " "$file" || true)
            if [ "$n" -ne "$#" ]; then
              echo "::error::${file}: expected $# sha256 stanzas, found ${n}."
              exit 1
            fi
            for sha in "$@"; do
              if ! grep -q "^[[:space:]]*sha256 \"${sha}\"$" "$file"; then
                echo "::error::${file}: sha256 ${sha} was not written."
                exit 1
              fi
            done
          }

          check Formula/bssh.rb "$mac_sha" "$linux_arm_sha" "$linux_x86_sha"

          if [ -f Formula/bssh-server.rb ]; then
            check Formula/bssh-server.rb "$server_mac_sha" "$server_linux_arm_sha" "$server_linux_x86_sha"
          fi

          if [ -f Formula/bssh-keygen.rb ]; then
            check Formula/bssh-keygen.rb "$keygen_mac_sha" "$keygen_linux_arm_sha" "$keygen_linux_x86_sha"
          fi

      - name: Commit and push changes to tap
        run: |
          cd homebrew-tap
          set -euo pipefail

          for f in Formula/bssh.rb Formula/bssh-server.rb Formula/bssh-keygen.rb; do
            if [ -f "$f" ]; then
              git add "$f"
            fi
          done

          # Re-running the workflow for a version already in the tap is not an
          # error; `git commit` would fail on an empty index otherwise.
          if git diff --cached --quiet; then
            echo "Formulae already up to date for v${{ env.VERSION_NO_V }}, nothing to push."
            exit 0
          fi

          git commit -m "bump: bssh, bssh-server, and bssh-keygen to v${{ env.VERSION_NO_V }}"
          git push origin main