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 <