Files
rustnet/.github/workflows/release.yml
dependabot[bot] 1a47b1a8d8 chore(deps): bump vmactions/freebsd-vm in the actions group (#82)
Bumps the actions group with 1 update: [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm).


Updates `vmactions/freebsd-vm` from 1.2.7 to 1.2.8
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](b9c3f24600...0cd283ca69)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-version: 1.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-04 21:00:57 +01:00

568 lines
21 KiB
YAML

name: Release
# This workflow builds and packages Rustnet for multiple platforms.
#
# For macOS code signing and notarization, configure these GitHub repository secrets:
# - APPLE_DEVELOPER_CERTIFICATE_P12: Base64-encoded .p12 certificate file
# (Export from Keychain: Developer ID Application certificate → Export as .p12 → base64 encode)
# - APPLE_DEVELOPER_CERTIFICATE_PASSWORD: Password for the .p12 file
# - APPLE_ID_ISSUER_ID: App Store Connect API issuer ID (from App Store Connect → Users and Access → Keys)
# - APPLE_ID_KEY_ID: App Store Connect API key ID
# - APPLE_ID_KEY: App Store Connect API key content (.p8 file contents)
#
# Without these secrets, the DMG will be unsigned and users must use "Open Anyway" in
# System Settings → Privacy & Security to bypass Gatekeeper protection.
#
# To obtain Apple Developer credentials:
# 1. Join the Apple Developer Program ($99/year): https://developer.apple.com/programs/
# 2. Create a Developer ID Application certificate in your Apple Developer account
# 3. Create an App Store Connect API key with "Developer" role
# 4. Export certificate as .p12, encode with: base64 -i certificate.p12 | pbcopy
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
permissions:
contents: write
env:
RUST_BACKTRACE: 1
RUSTNET_ASSET_DIR: assets
jobs:
build-release:
name: build-release
runs-on: ${{ matrix.os }}
strategy:
matrix:
build:
- linux-aarch64-gnu
- linux-armv7-gnueabihf
- linux-x64-gnu
- macos-aarch64
- macos-x64
- windows-x64-msvc
- windows-x86-msvc
include:
- os: ubuntu-22.04 # default - pinned for GLIBC 2.35 compatibility
- cargo: cargo # default; overwrite with `cross` if necessary
- build: linux-aarch64-gnu
target: aarch64-unknown-linux-gnu
cargo: cross
- build: linux-armv7-gnueabihf
target: armv7-unknown-linux-gnueabihf
cargo: cross
- build: linux-x64-gnu
target: x86_64-unknown-linux-gnu
- build: macos-aarch64
os: macos-14
target: aarch64-apple-darwin
- build: macos-x64
os: macos-14
target: x86_64-apple-darwin
- build: windows-x64-msvc
os: windows-latest
target: x86_64-pc-windows-msvc
- build: windows-x86-msvc
os: windows-latest
target: i686-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install Linux dependencies
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update -y
sudo apt-get install -y libpcap-dev libelf-dev zlib1g-dev clang llvm pkg-config
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: ${{ matrix.target }}
components: rustfmt
- name: Install cross
if: matrix.cargo == 'cross'
run: cargo install cross@0.2.5
- name: Build release binary
shell: bash
env:
RUSTFLAGS: "-C strip=symbols"
run: |
mkdir -p "$RUSTNET_ASSET_DIR"
# build.rs will handle Npcap SDK download on Windows
# eBPF is enabled by default on Linux, but must be disabled for other platforms
if [[ "${{ matrix.target }}" == *"linux"* ]]; then
# Linux builds: use default features (includes eBPF)
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }}
else
# Non-Linux builds: disable default features (no eBPF)
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }} --no-default-features
fi
- name: Create release archive
shell: bash
env:
RUSTNET_BIN: ${{ contains(matrix.os, 'windows') && 'rustnet.exe' || 'rustnet' }}
run: |
staging="rustnet-${{ github.ref_name }}-${{ matrix.target }}"
mkdir -p "$staging"
# Copy binary
cp "target/${{ matrix.target }}/release/$RUSTNET_BIN" "$staging/"
# Copy services file if it exists
if [ -f "$RUSTNET_ASSET_DIR/services" ]; then
mkdir -p "$staging/assets"
cp "$RUSTNET_ASSET_DIR/services" "$staging/assets/"
fi
# Copy documentation
cp README.md "$staging/"
cp LICENSE "$staging/" 2>/dev/null || true
# Create archive
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
7z a "$staging.zip" "$staging"
echo "ASSET=$staging.zip" >> $GITHUB_ENV
else
tar czf "$staging.tar.gz" "$staging"
echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV
fi
- name: Upload build artifacts
uses: actions/upload-artifact@v5
with:
name: build-${{ matrix.target }}
path: ${{ env.ASSET }}
if-no-files-found: error
build-freebsd:
name: build-freebsd
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Build on FreeBSD
uses: vmactions/freebsd-vm@0cd283ca698d48b59cda444a9948f48a30709627 # v1.2.8
with:
usesh: true
prepare: |
pkg install -y curl libpcap rust
run: |
# Build release binary (without eBPF - not available on FreeBSD)
cargo build --verbose --release --no-default-features
# Create release archive
mkdir -p "rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd"
cp target/release/rustnet "rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd/"
# Copy assets if available
if [ -d "assets" ]; then
mkdir -p "rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd/assets"
cp -r assets/* "rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd/assets/" || true
fi
# Copy documentation
cp README.md "rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd/" || true
cp LICENSE "rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd/" || true
# Create tarball
tar czf "rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd.tar.gz" "rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd"
- name: Upload FreeBSD artifact
uses: actions/upload-artifact@v5
with:
name: build-x86_64-unknown-freebsd
path: rustnet-${{ github.ref_name }}-x86_64-unknown-freebsd.tar.gz
if-no-files-found: error
create-release:
name: create-release
runs-on: ubuntu-latest
needs: [build-release, build-freebsd]
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Download all artifacts
uses: actions/download-artifact@v6
with:
path: artifacts
- name: Extract changelog for release
run: |
# Extract the version-specific section from CHANGELOG.md
VERSION="${{ github.ref_name }}"
# Remove 'v' prefix if present for matching changelog headers
VERSION_NUMBER="${VERSION#v}"
# Extract content between version headers
awk -v version="$VERSION_NUMBER" '
/^## \[/ {
if (found) exit
if ($0 ~ "\\[" version "\\]") {
found=1
next
}
}
found { print }
' CHANGELOG.md > release_notes.md
# Check if we extracted any content
if [ ! -s release_notes.md ]; then
echo "⚠️ No changelog entry found for $VERSION, will use auto-generated notes"
echo "USE_GENERATED_NOTES=true" >> $GITHUB_ENV
else
echo "✅ Extracted changelog content for $VERSION"
cat release_notes.md
fi
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Create the release if it doesn't exist
if ! gh release view ${{ github.ref_name }} 2>/dev/null; then
if [ "$USE_GENERATED_NOTES" = "true" ]; then
# Fallback to auto-generated notes if changelog extraction failed
gh release create ${{ github.ref_name }} \
--title "Release ${{ github.ref_name }}" \
--draft \
--generate-notes
else
# Use extracted changelog content
gh release create ${{ github.ref_name }} \
--title "Release ${{ github.ref_name }}" \
--draft \
--notes-file release_notes.md
fi
fi
# Upload all build artifacts
for artifact in artifacts/build-*/rustnet-*; do
gh release upload ${{ github.ref_name }} "$artifact" --clobber
done
package-installers:
name: package-installers
runs-on: ubuntu-22.04
needs: create-release
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Download build artifacts
uses: actions/download-artifact@v6
with:
path: artifacts
- name: Install dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y libpcap-dev libelf-dev zlib1g-dev clang llvm pkg-config
- name: Install Rust and tools
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,armv7-unknown-linux-gnueabihf
components: rustfmt
- name: Install packaging tools
run: |
cargo install cargo-deb cargo-generate-rpm
- name: Package Debian packages
run: |
mkdir -p installers
for arch in amd64 arm64 armhf; do
case $arch in
amd64) target="x86_64-unknown-linux-gnu" ;;
arm64) target="aarch64-unknown-linux-gnu" ;;
armhf) target="armv7-unknown-linux-gnueabihf" ;;
esac
# Extract binary and assets from artifact
tar -xzf "artifacts/build-$target/rustnet-${{ github.ref_name }}-$target.tar.gz"
mkdir -p "target/$target/release"
cp "rustnet-${{ github.ref_name }}-$target/rustnet" "target/$target/release/"
# Ensure services file exists for packaging
mkdir -p assets
if [ -f "rustnet-${{ github.ref_name }}-$target/assets/services" ]; then
cp "rustnet-${{ github.ref_name }}-$target/assets/services" assets/
fi
# Create deb package
cargo deb --no-build --no-strip --target $target
mv "target/$target/debian"/*.deb "installers/Rustnet_LinuxDEB_$arch.deb"
# Clean up for next iteration
rm -rf "rustnet-${{ github.ref_name }}-$target" "target/$target"
done
- name: Package RPM packages
run: |
for arch in x86_64 aarch64; do
target="$arch-unknown-linux-gnu"
# Extract binary and assets from artifact
tar -xzf "artifacts/build-$target/rustnet-${{ github.ref_name }}-$target.tar.gz"
mkdir -p "target/$target/release"
cp "rustnet-${{ github.ref_name }}-$target/rustnet" "target/$target/release/"
# Ensure services file exists for packaging
mkdir -p assets
if [ -f "rustnet-${{ github.ref_name }}-$target/assets/services" ]; then
cp "rustnet-${{ github.ref_name }}-$target/assets/services" assets/
fi
# Fix library linking if needed
patchelf --replace-needed libpcap.so.0.8 libpcap.so.1 "target/$target/release/rustnet" 2>/dev/null || true
# Create rpm package
cargo generate-rpm --target $target
mv "target/$target/generate-rpm"/*.rpm "installers/Rustnet_LinuxRPM_$arch.rpm"
# Clean up for next iteration
rm -rf "rustnet-${{ github.ref_name }}-$target" "target/$target"
done
- name: Upload installer packages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Upload all installer packages
for installer in installers/*; do
gh release upload ${{ github.ref_name }} "$installer" --clobber
done
package-macos:
name: package-macos
runs-on: macos-latest
needs: create-release
strategy:
matrix:
include:
- arch: Intel
target: x86_64-apple-darwin
- arch: AppleSilicon
target: aarch64-apple-darwin
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install packaging tools
run: |
cargo install toml-cli
cargo install apple-codesign
brew install create-dmg
- name: Download build artifact
uses: actions/download-artifact@v6
with:
name: build-${{ matrix.target }}
path: artifacts
- name: Package for macOS
run: |
# Extract binary
tar -xzf "artifacts/rustnet-${{ github.ref_name }}-${{ matrix.target }}.tar.gz"
# Get version and update plist
VERSION=$(toml get Cargo.toml package.version --raw)
sed -i'.bak' -e "s/0\.0\.0/${VERSION}/g" -e "s/fffffff/${GITHUB_SHA:0:7}/g" resources/packaging/macos/Info.plist
# Create app bundle
mkdir -p "Rustnet.app/Contents/"{MacOS,Resources/assets}
cp resources/packaging/macos/Info.plist "Rustnet.app/Contents/"
cp resources/packaging/macos/graphics/rustnet.icns "Rustnet.app/Contents/Resources/"
cp "rustnet-${{ github.ref_name }}-${{ matrix.target }}/rustnet" "Rustnet.app/Contents/MacOS/"
cp resources/packaging/macos/wrapper.sh "Rustnet.app/Contents/MacOS/"
cp "rustnet-${{ github.ref_name }}-${{ matrix.target }}/assets/services" "Rustnet.app/Contents/Resources/assets/"
chmod +x "Rustnet.app/Contents/MacOS/"{rustnet,wrapper.sh}
- name: Code sign and notarize app bundle
env:
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
APPLE_API_ISSUER: ${{ secrets.APPLE_ID_ISSUER_ID }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_ID_KEY_ID }}
APPLE_API_KEY: ${{ secrets.APPLE_ID_KEY }}
run: |
# Skip signing if secrets are not configured
if [ -z "$APPLE_CERTIFICATE_P12" ]; then
echo "⚠️ Code signing secrets not configured. DMG will be unsigned."
echo "⚠️ Users will need to use 'Open Anyway' in System Settings > Privacy & Security"
echo "SKIP_SIGNING=true" >> $GITHUB_ENV
exit 0
fi
# Decode certificate and save to file
echo "$APPLE_CERTIFICATE_P12" | base64 --decode > certificate.p12
# Create temporary JSON for API credentials
cat > api-key.json <<EOF
{
"issuer": "$APPLE_API_ISSUER",
"key_id": "$APPLE_API_KEY_ID",
"private_key": "$APPLE_API_KEY"
}
EOF
# Sign the app bundle with hardened runtime
rcodesign sign \
--p12-file certificate.p12 \
--p12-password "$APPLE_CERTIFICATE_PASSWORD" \
--code-signature-flags runtime \
Rustnet.app
# Create zip for notarization (required format)
ditto -c -k --keepParent Rustnet.app Rustnet.zip
# Submit for notarization and wait for result
rcodesign notary-submit \
--api-key-path api-key.json \
--wait \
Rustnet.zip
# Staple the notarization ticket to the app
rcodesign staple Rustnet.app
# Cleanup sensitive files
rm -f certificate.p12 api-key.json Rustnet.zip
- name: Create DMG
run: |
create-dmg \
--volname "Rustnet Installer" \
--background "resources/packaging/macos/graphics/dmg_bg.png" \
--window-pos 200 120 \
--window-size 900 450 \
--icon-size 100 \
--app-drop-link 620 240 \
--icon "Rustnet.app" 300 240 \
--hide-extension "Rustnet.app" \
"Rustnet_macOS_${{ matrix.arch }}.dmg" \
"Rustnet.app"
- name: Upload macOS package
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${{ github.ref_name }} "Rustnet_macOS_${{ matrix.arch }}.dmg" --clobber
package-windows:
name: package-windows
runs-on: windows-latest
needs: create-release
strategy:
matrix:
include:
- arch: 32-bit
target: i686-pc-windows-msvc
- arch: 64-bit
target: x86_64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install dependencies
shell: powershell
run: |
Write-Host "::group::WiX Toolset"
Invoke-WebRequest `
-Uri "https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip" `
-OutFile "$env:TEMP\wix-binaries.zip" -Verbose
Expand-Archive -LiteralPath "$env:TEMP\wix-binaries.zip" -DestinationPath "$env:TEMP\wix" -Verbose
# Add WiX to PATH for all subsequent steps
"$env:TEMP\wix" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
# Verify WiX installation
Write-Host "WiX tools installed:"
Get-ChildItem "$env:TEMP\wix\*.exe" | Select-Object Name
Write-Host "::endgroup::"
- name: Install Rust and tools
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install packaging tools
shell: bash
run: |
cargo install cargo-wix
cargo wix --version
- name: Download build artifact
uses: actions/download-artifact@v6
with:
name: build-${{ matrix.target }}
path: artifacts
- name: Package for Windows
shell: powershell
run: |
# Extract binary and assets
Write-Host "Extracting artifact..."
Expand-Archive -LiteralPath "artifacts\rustnet-${{ github.ref_name }}-${{ matrix.target }}.zip" -DestinationPath . -Force
# Create target directory structure
New-Item -ItemType Directory -Path "target\${{ matrix.target }}\release" -Force
# Copy binary
Copy-Item -Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\rustnet.exe" -Destination "target\${{ matrix.target }}\release\" -Force
Write-Host "Binary copied to target directory"
# Copy services file if it exists
New-Item -ItemType Directory -Path "assets" -Force
if (Test-Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets\services") {
Copy-Item -Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets\services" -Destination "assets\" -Force
Write-Host "Services file copied successfully"
} else {
Write-Host "Warning: No services file found"
}
# Setup WiX configuration (cargo-wix expects main.wxs in wix/ directory)
Write-Host "Setting up WiX configuration..."
New-Item -ItemType Directory -Path "wix" -Force
Copy-Item -Path "resources\packaging\windows\wix\main.wxs" -Destination "wix\main.wxs" -Force
Write-Host "WiX configuration copied"
# Verify candle.exe and light.exe are in PATH
Write-Host "Checking WiX tools..."
Get-Command candle.exe -ErrorAction SilentlyContinue | Select-Object Source
Get-Command light.exe -ErrorAction SilentlyContinue | Select-Object Source
# Create MSI package
Write-Host "Creating MSI package..."
cargo wix --no-build --nocapture --target ${{ matrix.target }}
# Find and rename the MSI file
$msiFiles = Get-ChildItem -Path "target\wix" -Filter "*.msi" -ErrorAction SilentlyContinue
if ($msiFiles) {
$msiFile = $msiFiles | Select-Object -First 1
Move-Item -Path $msiFile.FullName -Destination "Rustnet_Windows_${{ matrix.arch }}.msi" -Force
Write-Host "MSI package created: Rustnet_Windows_${{ matrix.arch }}.msi"
} else {
Write-Error "No MSI file found in target\wix\"
Write-Host "Contents of target directory:"
Get-ChildItem -Path "target" -Recurse -ErrorAction SilentlyContinue | Select-Object FullName
exit 1
}
- name: Upload Windows package
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${{ github.ref_name }} "Rustnet_Windows_${{ matrix.arch }}.msi" --clobber