bssh 2.0.1

Parallel SSH command execution tool for cluster management
Documentation
# .github/workflows/debian_build.yml
name: Build Debian Packages

# ═══════════════════════════════════════════════════════════════
# Workflow Triggers
# ═══════════════════════════════════════════════════════════════
# This workflow runs when:
# 1. The release workflow completes successfully
# 2. Manually triggered via workflow_dispatch
# ───────────────────────────────────────────────────────────────
on:
  workflow_run:
    workflows: ["Release"]
    types: [completed]
  
  workflow_dispatch:
    inputs:
      release_tag:
        description: 'Release tag to build packages for (e.g. v0.5.0). Empty = latest release'
        required: false

# Required permissions for release uploads
permissions:
  contents: write

jobs:
  # ═══════════════════════════════════════════════════════════════
  # Job: Build Debian Packages
  # ═══════════════════════════════════════════════════════════════
  # Creates .deb packages using pre-built binaries from releases
  # ───────────────────────────────────────────────────────────────
  build-deb:
    name: Build ${{ matrix.package }} (${{ matrix.distro }}, ${{ matrix.arch }})
    runs-on: ${{ matrix.os }}

    # Only run if the release workflow succeeded or manual trigger
    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}

    strategy:
      fail-fast: false
      matrix:
        package: [bssh, bssh-server, bssh-keygen]
        distro: [jammy, noble, resolute]
        arch: [amd64, arm64]
        include:
          # OS runner mappings
          - arch: amd64
            distro: jammy
            os: ubuntu-22.04
            version: "22.04"
          - arch: arm64
            distro: jammy
            os: ubuntu-22.04-arm
            version: "22.04"
          - arch: amd64
            distro: noble
            os: ubuntu-24.04
            version: "24.04"
          - arch: arm64
            distro: noble
            os: ubuntu-24.04-arm
            version: "24.04"
          - arch: amd64
            distro: resolute
            os: ubuntu-24.04
            version: "26.04"
          - arch: arm64
            distro: resolute
            os: ubuntu-24.04-arm
            version: "26.04"
          # Binary asset mappings
          - arch: amd64
            package: bssh
            binary_asset: bssh-linux-x86_64.tar.gz
          - arch: arm64
            package: bssh
            binary_asset: bssh-linux-aarch64.tar.gz
          - arch: amd64
            package: bssh-server
            binary_asset: bssh-server-linux-x86_64.tar.gz
          - arch: arm64
            package: bssh-server
            binary_asset: bssh-server-linux-aarch64.tar.gz
          - arch: amd64
            package: bssh-keygen
            binary_asset: bssh-keygen-linux-x86_64.tar.gz
          - arch: arm64
            package: bssh-keygen
            binary_asset: bssh-keygen-linux-aarch64.tar.gz
    
    steps:
    # ───────────────────────────────────────────────────────────────
    # Step 1: Checkout source code
    # ───────────────────────────────────────────────────────────────
    - name: Checkout code
      uses: actions/checkout@v4
      with:
        fetch-depth: 0  # Full history for changelog generation

    # ───────────────────────────────────────────────────────────────
    # Step 2: Determine release tag
    # ───────────────────────────────────────────────────────────────
    - name: Get release tag
      id: get_tag
      run: |
        if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.release_tag }}" ]; then
          echo "tag=${{ github.event.inputs.release_tag }}" >> "$GITHUB_OUTPUT"
        else
          # Get the latest release tag
          TAG=$(gh release list --limit 1 --json tagName -q '.[0].tagName')
          echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
        fi
      env:
        GH_TOKEN: ${{ github.token }}

    # ───────────────────────────────────────────────────────────────
    # Step 3: Update changelog
    # ───────────────────────────────────────────────────────────────
    - name: Update debian/changelog from release
      run: |
        bash ./debian/update-changelog.sh -d ${{ matrix.distro }} ${{ steps.get_tag.outputs.tag }}
      env:
        GH_TOKEN: ${{ github.token }}
        
    # ───────────────────────────────────────────────────────────────
    # Step 4: Download pre-built binary from release
    # ───────────────────────────────────────────────────────────────
    - name: Download release binary
      run: |
        gh release download "${{ steps.get_tag.outputs.tag }}" \
          --pattern "${{ matrix.binary_asset }}" \
          --output "${{ matrix.binary_asset }}"

        # Extract the binary
        tar -xzf "${{ matrix.binary_asset }}"

        # Verify the binary exists
        if [ ! -f "${{ matrix.package }}" ]; then
          echo "Error: ${{ matrix.package }} binary not found after extraction"
          exit 1
        fi

        # Make it executable
        chmod +x "${{ matrix.package }}"
      env:
        GH_TOKEN: ${{ github.token }}
    
    # ───────────────────────────────────────────────────────────────
    # Step 5: Setup build environment
    # ───────────────────────────────────────────────────────────────
    - name: Install build dependencies
      run: |
        sudo apt update
        sudo apt install -y \
          devscripts \
          debhelper \
          dh-make \
          fakeroot \
          lintian \
          dpkg-dev \
          build-essential
    
    # ───────────────────────────────────────────────────────────────
    # Step 6: Prepare for building
    # ───────────────────────────────────────────────────────────────
    - name: Prepare package version
      run: |
        # Get version from release tag (remove 'v' prefix)
        VERSION="${{ steps.get_tag.outputs.tag }}"
        VERSION="${VERSION#v}"
        echo "PACKAGE_VERSION=${VERSION}" >> "$GITHUB_ENV"
        
        # Verify changelog was updated correctly
        head -n 1 debian/changelog | grep -q "${VERSION}-1~${{ matrix.distro }}1" || {
          echo "Error: Changelog version mismatch. Expected ${VERSION}-1~${{ matrix.distro }}1"
          echo "Found: $(head -n 1 debian/changelog)"
          exit 1
        }
    
    # ───────────────────────────────────────────────────────────────
    # Step 7: Setup for binary package build
    # ───────────────────────────────────────────────────────────────
    - name: Setup for binary build
      run: |
        PKG="${{ matrix.package }}"

        # Use package-specific control file
        if [ -f "debian/control.${PKG}.binary" ]; then
          cp "debian/control.${PKG}.binary" debian/control
        elif [ -f debian/control.binary ] && [ "$PKG" = "bssh" ]; then
          cp debian/control.binary debian/control
        fi

        # Use package-specific rules file
        if [ -f "debian/rules.${PKG}.binary" ]; then
          cp "debian/rules.${PKG}.binary" debian/rules
          chmod +x debian/rules
        elif [ -f debian/rules.binary ] && [ "$PKG" = "bssh" ]; then
          cp debian/rules.binary debian/rules
          chmod +x debian/rules
        fi

        # Set target architecture in control file
        sed -i '/^Architecture:/c\Architecture: '${{ matrix.arch }} debian/control
    
    # ───────────────────────────────────────────────────────────────
    # Step 8: Build binary package
    # ───────────────────────────────────────────────────────────────
    - name: Build binary package
      run: |
        # Build binary package using the pre-built binary
        dpkg-buildpackage -b -uc -us

    # ───────────────────────────────────────────────────────────────
    # Step 9: Run lintian checks
    # ───────────────────────────────────────────────────────────────
    - name: Check package with lintian
      run: |
        lintian --info --display-level ">=warning" ../*.deb || true

    # ───────────────────────────────────────────────────────────────
    # Step 10: Prepare artifacts
    # ───────────────────────────────────────────────────────────────
    - name: Prepare artifacts
      run: |
        set -e
        PKG="${{ matrix.package }}"
        VER="${{ env.PACKAGE_VERSION }}"
        DIST="${{ matrix.distro }}"
        SERIES="${{ matrix.version }}"
        ARCH="${{ matrix.arch }}"

        mkdir -p artifacts

        for f in ../*_${ARCH}.deb; do
          mv "$f" "artifacts/${PKG}_${VER}_ubuntu${SERIES}.${DIST}_${ARCH}.deb"
        done

        for f in ../*_${ARCH}.buildinfo; do
          mv "$f" "artifacts/${PKG}_${VER}_ubuntu${SERIES}.${DIST}_${ARCH}.buildinfo"
        done

    # ───────────────────────────────────────────────────────────────
    # Step 11: Upload build artifacts
    # ───────────────────────────────────────────────────────────────
    - name: Upload build artifacts
      uses: actions/upload-artifact@v4
      with:
        name: ${{ matrix.package }}_${{ env.PACKAGE_VERSION }}_ubuntu${{ matrix.version }}.${{ matrix.distro }}_${{ matrix.arch }}
        path: artifacts/
        retention-days: 7

    # ───────────────────────────────────────────────────────────────
    # Step 12: Delete existing release artifacts (if any)
    # ───────────────────────────────────────────────────────────────
    - name: Delete existing release artifacts
      if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
      run: |
        # Get list of assets to delete
        TAG="${{ steps.get_tag.outputs.tag }}"
        PKG="${{ matrix.package }}"
        DIST="${{ matrix.distro }}"
        ARCH="${{ matrix.arch }}"

        # Delete existing .deb files for this specific package/distribution/architecture
        gh release view "$TAG" --json assets -q '.assets[].name' | while read -r asset; do
          if [[ "$asset" == "${PKG}_"*"ubuntu"*"$DIST"*"$ARCH"*".deb" ]]; then
            echo "Deleting existing asset: $asset"
            gh release delete-asset "$TAG" "$asset" -y || true
          fi
        done
      env:
        GH_TOKEN: ${{ github.token }}
    
    # ───────────────────────────────────────────────────────────────
    # Step 13: Upload new release artifacts
    # ───────────────────────────────────────────────────────────────
    - name: Upload release artifact
      if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
      uses: softprops/action-gh-release@v2
      with:
        tag_name: ${{ steps.get_tag.outputs.tag }}
        files: artifacts/*.deb
        # Don't fail if some files don't match the pattern
        fail_on_unmatched_files: false
        # Don't update the "latest" release pointer
        make_latest: false