name: Release
on:
release:
types: [published, prereleased]
workflow_dispatch:
inputs:
update_homebrew:
description: 'Update Homebrew formula after build'
required: false
default: 'false'
type: choice
options: ['true','false']
release_tag:
description: 'Release tag to upload artifacts to (e.g. v1.2.3)'
required: false
permissions:
contents: write
jobs:
build:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
environment: packaging
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
artifact_name: bssh
asset_name: bssh-linux-x86_64
archive_ext: ".tar.gz"
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
artifact_name: bssh
asset_name: bssh-linux-x86_64-musl
archive_ext: ".tar.gz"
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04-arm
artifact_name: bssh
asset_name: bssh-linux-aarch64
archive_ext: ".tar.gz"
- target: aarch64-unknown-linux-musl
os: ubuntu-24.04-arm
artifact_name: bssh
asset_name: bssh-linux-aarch64-musl
archive_ext: ".tar.gz"
- target: aarch64-apple-darwin
os: macos-14
artifact_name: bssh
asset_name: bssh-macos-aarch64
archive_ext: ".zip"
env:
BIN_NAME: bssh
BUNDLE_ID: ${{ vars.BUNDLE_ID }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
${{ runner.os }}-cargo-${{ matrix.target }}-
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl tools (Linux musl only)
if: contains(matrix.target, 'musl')
run: |
sudo apt update
sudo apt install -y musl-tools
- name: Build release binaries
run: cargo build --release --target ${{ matrix.target }} --locked --bin bssh --bin bssh-server --bin bssh-keygen
- name: Import Distribution certificate
if: runner.os == 'macOS'
uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.DEV_ID_CERT_P12 }}
p12-password: ${{ secrets.DEV_ID_CERT_PASSWORD }}
- name: Code sign macOS binaries
if: runner.os == 'macOS'
run: |
BIN_DIR=target/${{ matrix.target }}/release
for bin in bssh bssh-server bssh-keygen; do
codesign --force --timestamp --options runtime \
--sign "Distribution" "$BIN_DIR/$bin"
done
- name: Package Linux binaries (tar.gz)
if: runner.os == 'Linux'
run: |
BIN_DIR=target/${{ matrix.target }}/release
ASSET_BASE="${{ matrix.asset_name }}"
SERVER_ASSET_BASE="${ASSET_BASE/bssh/bssh-server}"
KEYGEN_ASSET_BASE="${ASSET_BASE/bssh/bssh-keygen}"
# Package bssh
mkdir -p package-bssh
cp "$BIN_DIR/bssh" package-bssh/
cp docs/man/bssh.1 package-bssh/
tar -C package-bssh -czf "${ASSET_BASE}.tar.gz" .
# Package bssh-server
mkdir -p package-bssh-server
cp "$BIN_DIR/bssh-server" package-bssh-server/
cp docs/man/bssh-server.8 package-bssh-server/
tar -C package-bssh-server -czf "${SERVER_ASSET_BASE}.tar.gz" .
# Package bssh-keygen
mkdir -p package-bssh-keygen
cp "$BIN_DIR/bssh-keygen" package-bssh-keygen/
cp docs/man/bssh-keygen.1 package-bssh-keygen/
tar -C package-bssh-keygen -czf "${KEYGEN_ASSET_BASE}.tar.gz" .
- name: Package macOS binaries (zip)
if: runner.os == 'macOS'
run: |
BIN_DIR="target/${{ matrix.target }}/release"
ASSET_BASE="${{ matrix.asset_name }}"
SERVER_ASSET_BASE="${ASSET_BASE/bssh/bssh-server}"
KEYGEN_ASSET_BASE="${ASSET_BASE/bssh/bssh-keygen}"
# Package bssh
mkdir -p package-bssh
cp "$BIN_DIR/bssh" package-bssh/
cp docs/man/bssh.1 package-bssh/
ditto -c -k --sequesterRsrc package-bssh "${ASSET_BASE}.zip"
# Package bssh-server
mkdir -p package-bssh-server
cp "$BIN_DIR/bssh-server" package-bssh-server/
cp docs/man/bssh-server.8 package-bssh-server/
ditto -c -k --sequesterRsrc package-bssh-server "${SERVER_ASSET_BASE}.zip"
# Package bssh-keygen
mkdir -p package-bssh-keygen
cp "$BIN_DIR/bssh-keygen" package-bssh-keygen/
cp docs/man/bssh-keygen.1 package-bssh-keygen/
ditto -c -k --sequesterRsrc package-bssh-keygen "${KEYGEN_ASSET_BASE}.zip"
- name: Generate checksums
run: |
ASSET_BASE="${{ matrix.asset_name }}"
SERVER_ASSET_BASE="${ASSET_BASE/bssh/bssh-server}"
KEYGEN_ASSET_BASE="${ASSET_BASE/bssh/bssh-keygen}"
EXT="${{ matrix.archive_ext }}"
for file in "${ASSET_BASE}${EXT}" "${SERVER_ASSET_BASE}${EXT}" "${KEYGEN_ASSET_BASE}${EXT}"; do
if [[ "$RUNNER_OS" == "Linux" ]]; then
sha256sum "$file" > "$file.sha256"
else
shasum -a 256 "$file" > "$file.sha256"
fi
done
- name: Upload release artifacts
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
run: |
ASSET_BASE="${{ matrix.asset_name }}"
SERVER_ASSET_BASE="${ASSET_BASE/bssh/bssh-server}"
KEYGEN_ASSET_BASE="${ASSET_BASE/bssh/bssh-keygen}"
EXT="${{ matrix.archive_ext }}"
TAG="${{ github.event.release.tag_name || github.event.inputs.release_tag }}"
gh release upload "$TAG" \
"${ASSET_BASE}${EXT}" \
"${ASSET_BASE}${EXT}.sha256" \
"${SERVER_ASSET_BASE}${EXT}" \
"${SERVER_ASSET_BASE}${EXT}.sha256" \
"${KEYGEN_ASSET_BASE}${EXT}" \
"${KEYGEN_ASSET_BASE}${EXT}.sha256" \
--clobber
env:
GH_TOKEN: ${{ github.token }}
publish-release:
name: Publish pre-release as official
needs: [build]
if: github.event_name == 'release' && github.event.release.prerelease
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Convert pre-release to official release
run: |
TAG="${{ github.event.release.tag_name }}"
gh release edit "$TAG" --prerelease=false
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
notify-teams:
name: Notify Teams on release
needs: [build]
if: github.event_name == 'release'
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Build Adaptive Card payload
env:
TAG: ${{ github.event.release.tag_name }}
NAME: ${{ github.event.release.name }}
URL: ${{ github.event.release.html_url }}
BODY: ${{ github.event.release.body }}
REPO: ${{ github.repository }}
run: |
TRIMMED=$(printf '%s' "$BODY" | head -c 2000)
jq -n \
--arg tag "$TAG" --arg name "$NAME" \
--arg url "$URL" --arg body "$TRIMMED" --arg repo "$REPO" '
{
type: "message",
attachments: [{
contentType: "application/vnd.microsoft.card.adaptive",
content: {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
type: "AdaptiveCard",
version: "1.5",
body: [
{ type: "TextBlock", size: "Large", weight: "Bolder",
text: ("🚀 " + $repo + " " + $tag + " released") },
{ type: "TextBlock", text: $name, wrap: true, isSubtle: true },
{ type: "TextBlock", text: $body, wrap: true }
],
actions: [
{ type: "Action.OpenUrl", title: "View release", url: $url }
]
}
}]
}' > card.json
- name: POST to Teams workflow
if: env.WEBHOOK_URL != ''
env:
WEBHOOK_URL: ${{ secrets.TEAMS_RELEASE_NOTIFICATION_WORKFLOW_URL }}
run: |
curl -sSf -X POST \
-H "Content-Type: application/json" \
--data-binary @card.json \
"$WEBHOOK_URL"