mirror of
https://github.com/domcyrus/rustnet.git
synced 2026-02-22 14:51:01 -06:00
fix: release workflow (#17)
* Remove musl targets to simplify Linux builds and fix cross-compilation issues * Enable eBPF by default on Linux via linux-default feature for better packet capture * Add macOS code signing and notarization support with graceful fallback for unsigned builds * Fix Windows MSI packaging with improved WiX configuration and Npcap library linking * Auto-extract changelog content from CHANGELOG.md into GitHub release notes * Fix ARM cross-compilation (aarch64, armv7) with proper library paths and eBPF support * Add comprehensive installation documentation for DMG, MSI, DEB, and RPM packages * Allow re-running releases with --clobber flag for artifact uploads
This commit is contained in:
234
.github/workflows/release.yml
vendored
234
.github/workflows/release.yml
vendored
@@ -1,5 +1,24 @@
|
||||
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:
|
||||
@@ -18,11 +37,8 @@ jobs:
|
||||
matrix:
|
||||
build:
|
||||
- linux-aarch64-gnu
|
||||
- linux-aarch64-musl
|
||||
- linux-armv7-gnueabihf
|
||||
- linux-armv7-musleabihf
|
||||
- linux-x64-gnu
|
||||
- linux-x64-musl
|
||||
- macos-aarch64
|
||||
- macos-x64
|
||||
- windows-x64-msvc
|
||||
@@ -33,19 +49,11 @@ jobs:
|
||||
- build: linux-aarch64-gnu
|
||||
target: aarch64-unknown-linux-gnu
|
||||
cargo: cross
|
||||
- build: linux-aarch64-musl
|
||||
target: aarch64-unknown-linux-musl
|
||||
cargo: cross
|
||||
- build: linux-armv7-gnueabihf
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
cargo: cross
|
||||
- build: linux-armv7-musleabihf
|
||||
target: armv7-unknown-linux-musleabihf
|
||||
cargo: cross
|
||||
- build: linux-x64-gnu
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- build: linux-x64-musl
|
||||
target: x86_64-unknown-linux-musl
|
||||
- build: macos-aarch64
|
||||
os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
@@ -67,21 +75,20 @@ jobs:
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libpcap-dev libelf-dev clang llvm
|
||||
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'
|
||||
uses: taiki-e/cache-cargo-install-action@v2
|
||||
with:
|
||||
tool: cross
|
||||
git: https://github.com/cross-rs/cross.git
|
||||
rev: 085092c
|
||||
tool: cross@0.2.5
|
||||
|
||||
- name: Build release binary
|
||||
shell: bash
|
||||
@@ -90,7 +97,12 @@ jobs:
|
||||
run: |
|
||||
mkdir -p "$RUSTNET_ASSET_DIR"
|
||||
# build.rs will handle Npcap SDK download on Windows
|
||||
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }}
|
||||
# Use linux-default feature for all Linux targets to enable eBPF
|
||||
if [[ "${{ matrix.target }}" == *"linux"* ]]; then
|
||||
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }} --features "linux-default"
|
||||
else
|
||||
${{ matrix.cargo }} build --verbose --release --target ${{ matrix.target }}
|
||||
fi
|
||||
|
||||
- name: Create release archive
|
||||
shell: bash
|
||||
@@ -103,9 +115,10 @@ jobs:
|
||||
# Copy binary
|
||||
cp "target/${{ matrix.target }}/release/$RUSTNET_BIN" "$staging/"
|
||||
|
||||
# Copy assets if they exist
|
||||
if [ -d "$RUSTNET_ASSET_DIR" ]; then
|
||||
cp -r "$RUSTNET_ASSET_DIR" "$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
|
||||
@@ -141,19 +154,59 @@ jobs:
|
||||
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
|
||||
gh release create ${{ github.ref_name }} \
|
||||
--title "Release ${{ github.ref_name }}" \
|
||||
--draft \
|
||||
--generate-notes
|
||||
# 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"
|
||||
gh release upload ${{ github.ref_name }} "$artifact" --clobber
|
||||
done
|
||||
|
||||
package-installers:
|
||||
@@ -172,12 +225,13 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libpcap-dev libelf-dev clang llvm
|
||||
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: |
|
||||
@@ -193,11 +247,17 @@ jobs:
|
||||
armhf) target="armv7-unknown-linux-gnueabihf" ;;
|
||||
esac
|
||||
|
||||
# Extract binary from artifact
|
||||
# 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"
|
||||
@@ -211,11 +271,17 @@ jobs:
|
||||
for arch in x86_64 aarch64; do
|
||||
target="$arch-unknown-linux-gnu"
|
||||
|
||||
# Extract binary from artifact
|
||||
# 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
|
||||
|
||||
@@ -233,7 +299,7 @@ jobs:
|
||||
run: |
|
||||
# Upload all installer packages
|
||||
for installer in installers/*; do
|
||||
gh release upload ${{ github.ref_name }} "$installer"
|
||||
gh release upload ${{ github.ref_name }} "$installer" --clobber
|
||||
done
|
||||
|
||||
package-macos:
|
||||
@@ -254,6 +320,7 @@ jobs:
|
||||
- name: Install packaging tools
|
||||
run: |
|
||||
cargo install toml-cli
|
||||
cargo install apple-codesign
|
||||
brew install create-dmg
|
||||
|
||||
- name: Download build artifact
|
||||
@@ -272,14 +339,66 @@ jobs:
|
||||
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}
|
||||
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}
|
||||
|
||||
# Create DMG
|
||||
- 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" \
|
||||
@@ -296,7 +415,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload ${{ github.ref_name }} "Rustnet_macOS_${{ matrix.arch }}.dmg"
|
||||
gh release upload ${{ github.ref_name }} "Rustnet_macOS_${{ matrix.arch }}.dmg" --clobber
|
||||
|
||||
package-windows:
|
||||
name: package-windows
|
||||
@@ -322,12 +441,12 @@ jobs:
|
||||
-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 subsequent steps
|
||||
# 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 "Verifying WiX installation..."
|
||||
Get-ChildItem "$env:TEMP\wix" | Select-Object -First 5
|
||||
Write-Host "WiX tools installed:"
|
||||
Get-ChildItem "$env:TEMP\wix\*.exe" | Select-Object Name
|
||||
Write-Host "::endgroup::"
|
||||
|
||||
- name: Install Rust and tools
|
||||
@@ -339,8 +458,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cargo install cargo-wix
|
||||
echo "cargo-wix installed successfully"
|
||||
cargo wix --version || echo "Warning: cargo-wix version check failed"
|
||||
cargo wix --version
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -352,40 +470,50 @@ jobs:
|
||||
shell: powershell
|
||||
run: |
|
||||
# Extract binary and assets
|
||||
Expand-Archive -LiteralPath "artifacts\rustnet-${{ github.ref_name }}-${{ matrix.target }}.zip" -DestinationPath .
|
||||
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 assets if they exist
|
||||
if (Test-Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets") {
|
||||
Copy-Item -Path "rustnet-${{ github.ref_name }}-${{ matrix.target }}\assets" -Destination "." -Recurse -Force
|
||||
Write-Host "Assets copied successfully"
|
||||
# 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 assets directory found in artifact"
|
||||
# Create empty assets directory to prevent cargo-wix from failing
|
||||
New-Item -ItemType Directory -Path "assets" -Force
|
||||
Write-Host "Warning: No services file found"
|
||||
}
|
||||
|
||||
# Verify directory structure
|
||||
Write-Host "Directory structure:"
|
||||
Get-ChildItem -Path "target\${{ matrix.target }}\release" -Recurse | Select-Object FullName
|
||||
# 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
|
||||
$msiFile = Get-ChildItem -Path "target\wix" -Filter "*.msi" | Select-Object -First 1
|
||||
if ($msiFile) {
|
||||
$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 "MSI file not found in target\wix directory"
|
||||
Get-ChildItem -Path "target" -Recurse | Select-Object FullName
|
||||
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
|
||||
}
|
||||
|
||||
@@ -393,4 +521,4 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload ${{ github.ref_name }} "Rustnet_Windows_${{ matrix.arch }}.msi"
|
||||
gh release upload ${{ github.ref_name }} "Rustnet_Windows_${{ matrix.arch }}.msi" --clobber
|
||||
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -1721,7 +1721,6 @@ dependencies = [
|
||||
"http_req",
|
||||
"libbpf-cargo",
|
||||
"libbpf-rs",
|
||||
"libbpf-sys",
|
||||
"libc",
|
||||
"log",
|
||||
"num_cpus",
|
||||
@@ -1732,6 +1731,7 @@ dependencies = [
|
||||
"ring",
|
||||
"simple-logging",
|
||||
"simplelog",
|
||||
"vmlinux",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -2198,6 +2198,11 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "vmlinux"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/libbpf/vmlinux.h.git?rev=83a228cf37fc65f2d14e4896a04922b5ee531a94#83a228cf37fc65f2d14e4896a04922b5ee531a94"
|
||||
|
||||
[[package]]
|
||||
name = "vsprintf"
|
||||
version = "2.0.0"
|
||||
|
||||
19
Cargo.toml
19
Cargo.toml
@@ -44,7 +44,6 @@ aes = "0.8"
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
procfs = "0.16"
|
||||
libbpf-rs = { version = "0.25", optional = true }
|
||||
libbpf-sys = { version = "1.4", optional = true }
|
||||
bytes = { version = "1.5", optional = true }
|
||||
libc = { version = "0.2", optional = true }
|
||||
|
||||
@@ -60,10 +59,20 @@ zip = "2.1"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.build-dependencies]
|
||||
libbpf-cargo = "0.25"
|
||||
vmlinux = { version = "0.0", git = "https://github.com/libbpf/vmlinux.h.git", rev = "83a228cf37fc65f2d14e4896a04922b5ee531a94" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
ebpf = ["libbpf-rs", "libbpf-sys", "bytes", "libc"]
|
||||
linux-default = ["ebpf"]
|
||||
ebpf = ["libbpf-rs", "bytes", "libc"]
|
||||
|
||||
# Minimal cross configuration to override dependency conflicts
|
||||
[workspace.metadata.cross.build.env]
|
||||
passthrough = [
|
||||
"CARGO_INCREMENTAL",
|
||||
"CARGO_NET_RETRY",
|
||||
"CARGO_NET_TIMEOUT",
|
||||
]
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer = "domcyrus <domcyrus@example.com>"
|
||||
@@ -124,3 +133,9 @@ libpcap = "*"
|
||||
libelf = "*"
|
||||
clang = "*"
|
||||
llvm = "*"
|
||||
|
||||
[package.metadata.wix]
|
||||
upgrade-guid = "455c823b-9665-43e0-baa4-bd0fcb762463"
|
||||
path-guid = "d3a2452e-f04f-4d4f-becf-c3580f49f8fc"
|
||||
license = false
|
||||
eula = false
|
||||
|
||||
16
Cross.toml
Normal file
16
Cross.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:edge"
|
||||
pre-build = [
|
||||
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||
"apt-get update -y",
|
||||
"apt-get install -y libelf-dev:arm64 zlib1g-dev:arm64 libpcap-dev:arm64 gcc-aarch64-linux-gnu protobuf-compiler libseccomp-dev:arm64 libbpf-dev clang llvm"
|
||||
]
|
||||
|
||||
[target.armv7-unknown-linux-gnueabihf]
|
||||
image = "ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:edge"
|
||||
pre-build = [
|
||||
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||
"apt-get update -y",
|
||||
"apt-get install -y libelf-dev:armhf zlib1g-dev:armhf libpcap-dev:armhf gcc-arm-linux-gnueabihf protobuf-compiler libseccomp-dev:armhf libbpf-dev clang llvm"
|
||||
]
|
||||
|
||||
100
README.md
100
README.md
@@ -63,6 +63,106 @@ When built with the `ebpf` feature on Linux, RustNet uses kernel eBPF programs f
|
||||
|
||||
## Installation
|
||||
|
||||
### Installing from Release Packages
|
||||
|
||||
Pre-built packages are available for each release on the [GitHub Releases](https://github.com/domcyrus/rustnet/releases) page.
|
||||
|
||||
#### macOS DMG Installation
|
||||
|
||||
> **💡 Prefer Homebrew?** If you have Homebrew installed, using `brew install` is easier and avoids Gatekeeper bypass steps. See [Homebrew Installation](#option-3-homebrew-installation) for instructions.
|
||||
|
||||
1. **Download** the appropriate DMG for your architecture:
|
||||
- `Rustnet_macOS_AppleSilicon.dmg` for Apple Silicon Macs (M1/M2/M3)
|
||||
- `Rustnet_macOS_Intel.dmg` for Intel-based Macs
|
||||
|
||||
2. **Open the DMG** and drag Rustnet.app to your Applications folder
|
||||
|
||||
3. **Bypass Gatekeeper** (for unsigned builds):
|
||||
- When you first try to open Rustnet, macOS will block it because the app is not signed
|
||||
- Go to **System Settings → Privacy & Security**
|
||||
- Scroll down to find the message about Rustnet being blocked
|
||||
- Click **"Open Anyway"** to allow the application to run
|
||||
- You may need to confirm this choice when launching the app again
|
||||
|
||||
4. **Run Rustnet**:
|
||||
- Double-click Rustnet.app to launch it in a Terminal window with sudo
|
||||
- Or run from command line: `sudo /Applications/Rustnet.app/Contents/MacOS/rustnet`
|
||||
|
||||
5. **Optional: Create a symlink for shell access**:
|
||||
```bash
|
||||
# Create a symlink so you can run 'rustnet' from anywhere
|
||||
sudo ln -s /Applications/Rustnet.app/Contents/MacOS/rustnet /usr/local/bin/rustnet
|
||||
|
||||
# Now you can run from any terminal:
|
||||
sudo rustnet
|
||||
```
|
||||
|
||||
6. **Optional: Setup BPF permissions** (to avoid needing sudo):
|
||||
- Install Wireshark's BPF permission helper: `brew install --cask wireshark-chmodbpf`
|
||||
- Log out and back in for group changes to take effect
|
||||
- See the [Permissions](#permissions) section for detailed setup instructions
|
||||
|
||||
#### Windows MSI Installation
|
||||
|
||||
1. **Install Npcap Runtime** (required for packet capture):
|
||||
- Download from https://npcap.com/dist/
|
||||
- Run the installer and select **"WinPcap API compatible mode"**
|
||||
|
||||
2. **Download and install** the appropriate MSI package:
|
||||
- `Rustnet_Windows_64-bit.msi` for 64-bit Windows
|
||||
- `Rustnet_Windows_32-bit.msi` for 32-bit Windows
|
||||
|
||||
3. **Run the installer** and follow the installation wizard
|
||||
|
||||
4. **Run Rustnet**:
|
||||
- Open Command Prompt or PowerShell
|
||||
- Run: `rustnet.exe`
|
||||
- Note: Depending on your Npcap installation settings, you may or may not need Administrator privileges
|
||||
|
||||
#### Linux Package Installation
|
||||
|
||||
**Debian/Ubuntu (.deb packages):**
|
||||
|
||||
```bash
|
||||
# Download the appropriate package for your architecture:
|
||||
# - Rustnet_LinuxDEB_amd64.deb (x86_64)
|
||||
# - Rustnet_LinuxDEB_arm64.deb (ARM64)
|
||||
# - Rustnet_LinuxDEB_armhf.deb (ARMv7)
|
||||
|
||||
# Install the package
|
||||
sudo dpkg -i Rustnet_LinuxDEB_amd64.deb
|
||||
|
||||
# Install dependencies if needed
|
||||
sudo apt-get install -f
|
||||
|
||||
# Run with sudo
|
||||
sudo rustnet
|
||||
|
||||
# Optional: Grant capabilities to run without sudo
|
||||
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/rustnet
|
||||
rustnet
|
||||
```
|
||||
|
||||
**RedHat/Fedora/CentOS (.rpm packages):**
|
||||
|
||||
```bash
|
||||
# Download the appropriate package for your architecture:
|
||||
# - Rustnet_LinuxRPM_x86_64.rpm
|
||||
# - Rustnet_LinuxRPM_aarch64.rpm
|
||||
|
||||
# Install the package
|
||||
sudo rpm -i Rustnet_LinuxRPM_x86_64.rpm
|
||||
# Or with dnf/yum:
|
||||
sudo dnf install Rustnet_LinuxRPM_x86_64.rpm
|
||||
|
||||
# Run with sudo
|
||||
sudo rustnet
|
||||
|
||||
# Optional: Grant capabilities to run without sudo
|
||||
sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/rustnet
|
||||
rustnet
|
||||
```
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust 2024 edition or later (install from [rustup.rs](https://rustup.rs/))
|
||||
|
||||
117
build.rs
117
build.rs
@@ -5,7 +5,10 @@ fn main() -> Result<()> {
|
||||
// Generate shell completions and manpage
|
||||
generate_assets()?;
|
||||
|
||||
// Only compile eBPF programs on Linux when the feature is enabled
|
||||
// Add library search paths for cross-compilation
|
||||
setup_cross_compilation_libs();
|
||||
|
||||
// Compile eBPF programs on Linux when the feature is enabled
|
||||
if cfg!(target_os = "linux") && env::var("CARGO_FEATURE_EBPF").is_ok() {
|
||||
compile_ebpf_programs();
|
||||
}
|
||||
@@ -21,6 +24,26 @@ fn main() -> Result<()> {
|
||||
|
||||
include!("src/cli.rs");
|
||||
|
||||
fn setup_cross_compilation_libs() {
|
||||
let target = env::var("TARGET").unwrap_or_default();
|
||||
|
||||
match target.as_str() {
|
||||
"aarch64-unknown-linux-gnu" => {
|
||||
println!("cargo:rustc-link-search=native=/usr/lib/aarch64-linux-gnu");
|
||||
println!("cargo:rustc-link-lib=elf");
|
||||
println!("cargo:rustc-link-lib=z");
|
||||
}
|
||||
"armv7-unknown-linux-gnueabihf" => {
|
||||
println!("cargo:rustc-link-search=native=/usr/lib/arm-linux-gnueabihf");
|
||||
println!("cargo:rustc-link-lib=elf");
|
||||
println!("cargo:rustc-link-lib=z");
|
||||
}
|
||||
_ => {
|
||||
// For other targets, including native builds, let pkg-config handle it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_assets() -> Result<()> {
|
||||
use clap::ValueEnum;
|
||||
use clap_complete::Shell;
|
||||
@@ -86,25 +109,34 @@ fn download_windows_npcap_sdk() -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
// extract DLL
|
||||
let lib_path = if cfg!(target_arch = "aarch64") {
|
||||
"Lib/ARM64/Packet.lib"
|
||||
} else if cfg!(target_arch = "x86_64") {
|
||||
"Lib/x64/Packet.lib"
|
||||
} else if cfg!(target_arch = "x86") {
|
||||
"Lib/Packet.lib"
|
||||
// extract libraries based on target architecture
|
||||
let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
|
||||
let (packet_lib_path, wpcap_lib_path) = if target.contains("aarch64") {
|
||||
("Lib/ARM64/Packet.lib", "Lib/ARM64/wpcap.lib")
|
||||
} else if target.contains("x86_64") {
|
||||
("Lib/x64/Packet.lib", "Lib/x64/wpcap.lib")
|
||||
} else if target.contains("i686") || target.contains("i586") {
|
||||
("Lib/Packet.lib", "Lib/wpcap.lib")
|
||||
} else {
|
||||
panic!("Unsupported target!")
|
||||
panic!("Unsupported target: {}", target)
|
||||
};
|
||||
let mut archive = zip::ZipArchive::new(io::Cursor::new(npcap_zip))?;
|
||||
let mut npcap_lib = archive.by_name(lib_path)?;
|
||||
|
||||
// write DLL
|
||||
let mut archive = zip::ZipArchive::new(io::Cursor::new(npcap_zip))?;
|
||||
|
||||
// Extract Packet.lib
|
||||
let mut packet_lib = archive.by_name(packet_lib_path)?;
|
||||
let lib_dir = PathBuf::from(env::var("OUT_DIR")?).join("npcap_sdk");
|
||||
let lib_path = lib_dir.join("Packet.lib");
|
||||
fs::create_dir_all(&lib_dir)?;
|
||||
let mut lib_file = fs::File::create(lib_path)?;
|
||||
io::copy(&mut npcap_lib, &mut lib_file)?;
|
||||
let packet_lib_dest = lib_dir.join("Packet.lib");
|
||||
let mut packet_file = fs::File::create(packet_lib_dest)?;
|
||||
io::copy(&mut packet_lib, &mut packet_file)?;
|
||||
drop(packet_lib);
|
||||
|
||||
// Extract wpcap.lib
|
||||
let mut wpcap_lib = archive.by_name(wpcap_lib_path)?;
|
||||
let wpcap_lib_dest = lib_dir.join("wpcap.lib");
|
||||
let mut wpcap_file = fs::File::create(wpcap_lib_dest)?;
|
||||
io::copy(&mut wpcap_lib, &mut wpcap_file)?;
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
@@ -119,6 +151,7 @@ fn download_windows_npcap_sdk() -> Result<()> {
|
||||
#[cfg(all(target_os = "linux", feature = "ebpf"))]
|
||||
fn compile_ebpf_programs() {
|
||||
use libbpf_cargo::SkeletonBuilder;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut out = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
@@ -128,46 +161,27 @@ fn compile_ebpf_programs() {
|
||||
|
||||
println!("cargo:warning=Building eBPF program using libbpf-cargo");
|
||||
|
||||
match SkeletonBuilder::new()
|
||||
// Get target architecture for cross-compilation
|
||||
let arch = env::var("CARGO_CFG_TARGET_ARCH")
|
||||
.expect("CARGO_CFG_TARGET_ARCH must be set in build script");
|
||||
|
||||
// Map Rust arch names to eBPF target arch defines
|
||||
let target_arch_define = match arch.as_str() {
|
||||
"x86_64" => "-D__TARGET_ARCH_x86",
|
||||
"aarch64" => "-D__TARGET_ARCH_arm64",
|
||||
"arm" => "-D__TARGET_ARCH_arm",
|
||||
_ => "-D__TARGET_ARCH_x86", // fallback
|
||||
};
|
||||
|
||||
SkeletonBuilder::new()
|
||||
.source(src)
|
||||
.clang_args([
|
||||
"-I/usr/include",
|
||||
"-I/usr/include/linux",
|
||||
"-I/usr/include/x86_64-linux-gnu",
|
||||
"-D__TARGET_ARCH_x86",
|
||||
OsStr::new("-I"),
|
||||
vmlinux::include_path_root().join(&arch).as_os_str(),
|
||||
OsStr::new(target_arch_define),
|
||||
])
|
||||
.build_and_generate(&out)
|
||||
{
|
||||
Ok(_) => {
|
||||
println!("cargo:warning=eBPF skeleton generated successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("cargo:warning=Failed to build eBPF program: {}", e);
|
||||
|
||||
// Create a placeholder skeleton file that compiles but returns None
|
||||
let placeholder_skeleton = r#"
|
||||
// Placeholder skeleton for failed compilation
|
||||
#[allow(dead_code)]
|
||||
pub mod socket_tracker {
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct SocketTrackerSkel;
|
||||
|
||||
impl SocketTrackerSkel {
|
||||
pub fn open() -> Result<Self> {
|
||||
Err(anyhow::anyhow!("eBPF compilation failed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
std::fs::write(&out, placeholder_skeleton).unwrap_or_else(|e| {
|
||||
println!(
|
||||
"cargo:warning=Failed to create placeholder skeleton file: {}",
|
||||
e
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
println!("cargo:rerun-if-changed={}", src);
|
||||
}
|
||||
@@ -176,3 +190,4 @@ pub mod socket_tracker {
|
||||
fn compile_ebpf_programs() {
|
||||
// No-op when not on Linux or eBPF feature is not enabled
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
# Wrapper script for Rustnet macOS app
|
||||
# This script launches the Rustnet binary in a terminal
|
||||
# Note: Rustnet requires sudo for packet capture. Run: sudo rustnet
|
||||
|
||||
# Get the directory where the app bundle is located
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
@@ -11,7 +12,7 @@ if [[ "$TERM_PROGRAM" == "Apple_Terminal" ]] || [[ "$TERM_PROGRAM" == "iTerm.app
|
||||
# Already in terminal, just run the binary
|
||||
exec "$DIR/rustnet" "$@"
|
||||
else
|
||||
# Not in terminal, open Terminal and run the command
|
||||
# This will open a new Terminal window with rustnet running
|
||||
osascript -e "tell application \"Terminal\" to do script \"cd '$DIR' && ./rustnet; exit\""
|
||||
# Not in terminal, open Terminal and prompt for sudo
|
||||
# This will open a new Terminal window with rustnet running with sudo
|
||||
osascript -e "tell application \"Terminal\" to do script \"cd '$DIR' && sudo ./rustnet; exit\""
|
||||
fi
|
||||
@@ -1,9 +1,19 @@
|
||||
<?xml version='1.0' encoding='windows-1252'?>
|
||||
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
|
||||
|
||||
<!-- Define platform-specific variables based on target architecture -->
|
||||
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
|
||||
<?define Win64 = "yes" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else ?>
|
||||
<?define Win64 = "no" ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?endif ?>
|
||||
|
||||
<Product
|
||||
Id='*'
|
||||
Name='Rustnet'
|
||||
UpgradeCode='12345678-1234-1234-1234-123456789012'
|
||||
UpgradeCode='455c823b-9665-43e0-baa4-bd0fcb762463'
|
||||
Manufacturer='domcyrus'
|
||||
Language='1033'
|
||||
Codepage='1252'
|
||||
@@ -13,37 +23,52 @@
|
||||
Keywords='Installer'
|
||||
Description='Rustnet Network Monitor Installer'
|
||||
Manufacturer='domcyrus'
|
||||
InstallerVersion='100'
|
||||
InstallerVersion='500'
|
||||
Languages='1033'
|
||||
Compressed='yes'
|
||||
SummaryCodepage='1252'
|
||||
Platform='$(var.Platform)'
|
||||
/>
|
||||
|
||||
<Media Id='1' Cabinet='Sample.cab' EmbedCab='yes' DiskPrompt="CD-ROM #1" />
|
||||
<MajorUpgrade
|
||||
DowngradeErrorMessage="A newer version of [ProductName] is already installed."
|
||||
Schedule="afterInstallInitialize"
|
||||
AllowSameVersionUpgrades="yes" />
|
||||
|
||||
<Media Id='1' Cabinet='rustnet.cab' EmbedCab='yes' DiskPrompt="CD-ROM #1" />
|
||||
<Property Id='DiskPrompt' Value="Rustnet Installation [1]" />
|
||||
|
||||
<Directory Id='TARGETDIR' Name='SourceDir'>
|
||||
<Directory Id='ProgramFilesFolder'>
|
||||
<Directory Id='$(var.PlatformProgramFilesFolder)'>
|
||||
<Directory Id='APPLICATIONROOTDIRECTORY' Name='Rustnet'>
|
||||
<Component Id='MainExecutable' Guid='*'>
|
||||
<Component Id='MainExecutable' Guid='*' Win64='$(var.Win64)'>
|
||||
<File Id='RustnetExe'
|
||||
Name='rustnet.exe'
|
||||
DiskId='1'
|
||||
Source='$(var.CargoTargetBinDir)\rustnet.exe'
|
||||
KeyPath='yes'/>
|
||||
<Environment Id='PATH' Name='PATH' Value='[APPLICATIONROOTDIRECTORY]'
|
||||
Permanent='no' Part='last' Action='set' System='yes' />
|
||||
</Component>
|
||||
<Component Id='AppIcon' Guid='*'>
|
||||
<Component Id='AppIcon' Guid='*' Win64='$(var.Win64)'>
|
||||
<File Id='RustnetIco'
|
||||
Name='rustnet.ico'
|
||||
DiskId='1'
|
||||
Source='resources\packaging\windows\graphics\rustnet.ico'/>
|
||||
</Component>
|
||||
<Component Id='AssetsDir' Guid='*' Win64='$(var.Win64)'>
|
||||
<File Id='ServicesFile'
|
||||
Name='services'
|
||||
DiskId='1'
|
||||
Source='assets\services'
|
||||
KeyPath='yes'/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="Rustnet">
|
||||
<Component Id="ApplicationShortcut" Guid="*">
|
||||
<Component Id="ApplicationShortcut" Guid="*" Win64="$(var.Win64)">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="Rustnet"
|
||||
Description="Cross-platform network monitoring tool"
|
||||
@@ -51,7 +76,7 @@
|
||||
WorkingDirectory="APPLICATIONROOTDIRECTORY"
|
||||
Icon="rustnet.ico"/>
|
||||
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\Microsoft\Rustnet" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
|
||||
<RegistryValue Root="HKCU" Key="Software\domcyrus\Rustnet" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
@@ -61,9 +86,13 @@
|
||||
|
||||
<Feature
|
||||
Id='Complete'
|
||||
Level='1'>
|
||||
Title='Rustnet'
|
||||
Description='Complete installation of Rustnet Network Monitor'
|
||||
Level='1'
|
||||
ConfigurableDirectory='APPLICATIONROOTDIRECTORY'>
|
||||
<ComponentRef Id='MainExecutable' />
|
||||
<ComponentRef Id='AppIcon' />
|
||||
<ComponentRef Id='AssetsDir' />
|
||||
<ComponentRef Id='ApplicationShortcut' />
|
||||
</Feature>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Socket tracker eBPF program
|
||||
// CO-RE (Compile Once - Run Everywhere) version using BTF
|
||||
|
||||
#include "vmlinux_min.h"
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
Reference in New Issue
Block a user