Skip to content

Release 0.2.6: make Android FFI verification NDK-tool aware #42

Release 0.2.6: make Android FFI verification NDK-tool aware

Release 0.2.6: make Android FFI verification NDK-tool aware #42

Workflow file for this run

name: Release pb-mapper UI
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
workflow_dispatch:
env:
UI_VERSION: ${{ github.ref_name }}
permissions:
contents: write
jobs:
get-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Generate UI release notes from changelog
shell: bash
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME#v}"
awk -v ver="${VERSION}" '
$0 ~ "^## \\[" ver "\\]" {capture=1}
capture {
if ($0 ~ "^## \\[" && $0 !~ "^## \\[" ver "\\]") exit
print
}
' CHANGELOG.md > changelog_section.md
{
echo "# pb-mapper UI Release ${GITHUB_REF_NAME}"
echo
echo "Cross-platform graphical interface for pb-mapper network tunneling tool."
echo
echo "## Downloads"
echo "- **Windows**: Download the .exe installer or .zip portable version"
echo "- **Linux**: AppImage, DEB, or other Linux packages"
echo "- **macOS**: DMG installer"
echo "- **Android**: APK files"
echo "- **iOS**: Unsigned IPA (for development)"
echo
echo "## Latest Changelog"
echo
if [ -s changelog_section.md ]; then
cat changelog_section.md
else
echo "_No changelog section found for version ${VERSION} in CHANGELOG.md._"
fi
} > ui_release_notes.md
- uses: actions/upload-artifact@v4
with:
name: ui-release-notes
path: ui_release_notes.md
build-windows:
runs-on: windows-latest
needs: [get-release]
steps:
- uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: ui-release-notes
- name: Install make
run: choco install make
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.35.1
channel: "stable"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.88.0
- name: Build latest Windows FFI
run: |
make build-pb-mapper-ffi-windows
- name: Build Windows app with latest FFI
run: |
make release-ui-windows-no-ffi
- name: Verify Windows bundle uses latest FFI
run: |
$src = "ui/native/windows/x64/pb_mapper_ffi.dll"
$dst = "ui/build/windows/x64/runner/Release/pb_mapper_ffi.dll"
if (!(Test-Path $src)) { throw "FFI source dll not found: $src" }
if (!(Test-Path $dst)) { throw "FFI dll not found in built UI bundle: $dst" }
$srcHash = (Get-FileHash -Algorithm SHA256 $src).Hash
$dstHash = (Get-FileHash -Algorithm SHA256 $dst).Hash
if ($srcHash -ne $dstHash) {
throw "FFI hash mismatch. source=$srcHash bundle=$dstHash"
}
- name: Upload Windows artifacts
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}-ui
target_commitish: ${{ github.sha }}
name: "pb-mapper UI ${{ github.ref_name }}"
draft: ${{ github.event_name == 'workflow_dispatch' }}
body_path: ui_release_notes.md
files: ui/dist/*/*
build-linux:
runs-on: ubuntu-22.04
needs: [get-release]
steps:
- uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: ui-release-notes
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: 3.35.1
channel: "stable"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.88.0
- name: Install dependencies
run: |
sudo apt-get update -y
sudo apt-get install -y locate ninja-build libgtk-3-dev libayatana-appindicator3-dev libnotify-dev \
binutils coreutils desktop-file-utils fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-setuptools squashfs-tools strace util-linux zsync
- name: Build latest Linux FFI
run: |
make build-pb-mapper-ffi-linux
- name: Build Linux app with latest FFI
run: |
make build-ui-linux-release-no-ffi
- name: Verify Linux bundle uses latest FFI
run: |
SRC_FFI="ui/native/linux/x64/libpb_mapper_ffi.so"
DST_FFI="ui/build/linux/x64/release/bundle/lib/libpb_mapper_ffi.so"
test -f "${SRC_FFI}"
test -f "${DST_FFI}"
SRC_HASH="$(sha256sum "${SRC_FFI}" | awk '{print $1}')"
DST_HASH="$(sha256sum "${DST_FFI}" | awk '{print $1}')"
if [ "${SRC_HASH}" != "${DST_HASH}" ]; then
echo "FFI hash mismatch: src=${SRC_HASH} dst=${DST_HASH}"
exit 1
fi
- name: Package Linux artifacts
run: |
cd ui
# Create directory structure for packaging
mkdir -p dist/${UI_VERSION}
# Create ZIP package
cd build/linux/x64/release/bundle
zip -r ../../../../../dist/${UI_VERSION}/pb-mapper-linux.zip .
# Create AppImage (basic)
cd ../../../../../
mkdir -p AppDir/usr/bin
mkdir -p AppDir/usr/share/applications
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps
# Copy binary and dependencies
cp -r build/linux/x64/release/bundle/* AppDir/usr/bin/
# Copy icon
cp assets/app_icon.png AppDir/usr/share/icons/hicolor/256x256/apps/pb-mapper.png
cp AppDir/usr/share/icons/hicolor/256x256/apps/pb-mapper.png AppDir/pb-mapper.png
# Create desktop file
cat > AppDir/usr/share/applications/pb-mapper.desktop << EOF
[Desktop Entry]
Name=pb-mapper
Comment=Network port mapping and tunneling tool
Exec=ui
Icon=pb-mapper
Terminal=false
Type=Application
Categories=Network;
EOF
# Copy desktop file to root
cp AppDir/usr/share/applications/pb-mapper.desktop AppDir/
# Create AppRun
cat > AppDir/AppRun << 'EOF'
#!/bin/bash
HERE="$(dirname "$(readlink -f "${0}")")"
export LD_LIBRARY_PATH="${HERE}/usr/bin/lib:${LD_LIBRARY_PATH}"
exec "${HERE}/usr/bin/ui" "$@"
EOF
chmod +x AppDir/AppRun
# Build AppImage
wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage AppDir dist/${UI_VERSION}/pb-mapper-linux.AppImage
- name: Upload Linux artifacts
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}-ui
target_commitish: ${{ github.sha }}
draft: ${{ github.event_name == 'workflow_dispatch' }}
body_path: ui_release_notes.md
files: ui/dist/${{ env.UI_VERSION }}/*
build-android:
runs-on: ubuntu-latest
needs: [get-release]
steps:
- uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: ui-release-notes
- uses: actions/setup-java@v3
with:
distribution: "liberica"
java-version: "17"
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.35.1
channel: "stable"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.88.0
- name: Set up Android NDK
uses: nttld/setup-ndk@v1
with:
ndk-version: r26d
link-to-sdk: true
- name: Build latest Android FFI
run: |
make build-pb-mapper-ffi-android
- name: Build Android APKs with latest FFI
run: |
make build-ui-android-release-no-ffi
- name: Verify Android APKs use latest FFI
run: |
set -euo pipefail
tmp_dir="$(mktemp -d)"
trap 'rm -rf "${tmp_dir}"' EXIT
ndk_bin=""
for ndk_var in ANDROID_NDK_HOME ANDROID_NDK_ROOT ANDROID_NDK_LATEST_HOME ANDROID_NDK; do
ndk_home="${!ndk_var:-}"
if [ -n "${ndk_home}" ] && [ -d "${ndk_home}/toolchains/llvm/prebuilt/linux-x86_64/bin" ]; then
ndk_bin="${ndk_home}/toolchains/llvm/prebuilt/linux-x86_64/bin"
break
fi
done
readelf_bin=""
strip_bin=""
nm_bin=""
if [ -n "${ndk_bin}" ]; then
[ -x "${ndk_bin}/llvm-readelf" ] && readelf_bin="${ndk_bin}/llvm-readelf"
[ -x "${ndk_bin}/llvm-strip" ] && strip_bin="${ndk_bin}/llvm-strip"
[ -x "${ndk_bin}/llvm-nm" ] && nm_bin="${ndk_bin}/llvm-nm"
fi
if [ -z "${readelf_bin}" ] && command -v readelf >/dev/null 2>&1; then
readelf_bin="$(command -v readelf)"
fi
if [ -z "${strip_bin}" ] && command -v llvm-strip >/dev/null 2>&1; then
strip_bin="$(command -v llvm-strip)"
fi
if [ -z "${strip_bin}" ] && command -v objcopy >/dev/null 2>&1; then
strip_bin="$(command -v objcopy)"
fi
if [ -z "${nm_bin}" ] && command -v nm >/dev/null 2>&1; then
nm_bin="$(command -v nm)"
fi
echo "Android ELF tools: readelf=${readelf_bin:-none} strip=${strip_bin:-none} nm=${nm_bin:-none}"
declare -A APK_BY_ABI=(
["arm64-v8a"]="app-arm64-v8a-release.apk"
["armeabi-v7a"]="app-armeabi-v7a-release.apk"
["x86_64"]="app-x86_64-release.apk"
)
for abi in "${!APK_BY_ABI[@]}"; do
src="ui/native/android/${abi}/libpb_mapper_ffi.so"
apk="ui/build/app/outputs/flutter-apk/${APK_BY_ABI[$abi]}"
extracted="${tmp_dir}/${abi}.apk.so"
test -f "${src}"
test -f "${apk}"
unzip -p "${apk}" "lib/${abi}/libpb_mapper_ffi.so" > "${extracted}"
test -s "${extracted}"
src_hash="$(sha256sum "${src}" | awk '{print $1}')"
apk_hash="$(sha256sum "${extracted}" | awk '{print $1}')"
if [ "${src_hash}" = "${apk_hash}" ]; then
continue
fi
src_build_id=""
apk_build_id=""
if [ -n "${readelf_bin}" ]; then
src_build_id="$("${readelf_bin}" -n "${src}" 2>/dev/null | awk '/Build ID:/ {print $3; exit}' || true)"
apk_build_id="$("${readelf_bin}" -n "${extracted}" 2>/dev/null | awk '/Build ID:/ {print $3; exit}' || true)"
fi
if [ -n "${src_build_id}" ] && [ "${src_build_id}" = "${apk_build_id}" ]; then
echo "FFI raw hash mismatch for ${abi}, but ELF Build ID matches (${src_build_id}); accepting packaged artifact."
continue
fi
src_norm="${tmp_dir}/${abi}.src.norm.so"
apk_norm="${tmp_dir}/${abi}.apk.norm.so"
cp "${src}" "${src_norm}"
cp "${extracted}" "${apk_norm}"
if [ -n "${strip_bin}" ]; then
"${strip_bin}" --strip-debug "${src_norm}" >/dev/null 2>&1 || true
"${strip_bin}" --strip-debug "${apk_norm}" >/dev/null 2>&1 || true
fi
src_norm_hash="$(sha256sum "${src_norm}" | awk '{print $1}')"
apk_norm_hash="$(sha256sum "${apk_norm}" | awk '{print $1}')"
if [ "${src_norm_hash}" = "${apk_norm_hash}" ]; then
echo "FFI raw hash mismatch for ${abi}, but debug-stripped hashes match; accepting packaged artifact."
continue
fi
src_symbols=""
apk_symbols=""
src_sym_hash=""
apk_sym_hash=""
if [ -n "${nm_bin}" ]; then
src_symbols="$("${nm_bin}" -D --defined-only "${src}" 2>/dev/null | awk '{print $3}' | sort -u || true)"
apk_symbols="$("${nm_bin}" -D --defined-only "${extracted}" 2>/dev/null | awk '{print $3}' | sort -u || true)"
fi
if [ -n "${src_symbols}" ] && [ -n "${apk_symbols}" ]; then
src_sym_hash="$(printf "%s\n" "${src_symbols}" | sha256sum | awk '{print $1}')"
apk_sym_hash="$(printf "%s\n" "${apk_symbols}" | sha256sum | awk '{print $1}')"
fi
if [ -n "${src_sym_hash}" ] && [ "${src_sym_hash}" = "${apk_sym_hash}" ]; then
echo "FFI raw hash mismatch for ${abi}, but exported symbol hashes match; accepting packaged artifact."
continue
fi
echo "FFI mismatch for ${abi}: src_hash=${src_hash} apk_hash=${apk_hash} src_build_id=${src_build_id:-none} apk_build_id=${apk_build_id:-none} src_norm_hash=${src_norm_hash} apk_norm_hash=${apk_norm_hash} src_sym_hash=${src_sym_hash:-none} apk_sym_hash=${apk_sym_hash:-none}"
exit 1
done
- name: Rename Android APKs
run: |
cd ui/build/app/outputs/flutter-apk
mv app-arm64-v8a-release.apk pb-mapper-arm64-v8a.apk
mv app-armeabi-v7a-release.apk pb-mapper-armeabi-v7a.apk
mv app-x86_64-release.apk pb-mapper-x86_64.apk
- name: Upload Android artifacts
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}-ui
target_commitish: ${{ github.sha }}
draft: ${{ github.event_name == 'workflow_dispatch' }}
body_path: ui_release_notes.md
files: |
ui/build/app/outputs/flutter-apk/pb-mapper-arm64-v8a.apk
ui/build/app/outputs/flutter-apk/pb-mapper-armeabi-v7a.apk
ui/build/app/outputs/flutter-apk/pb-mapper-x86_64.apk
build-macos:
runs-on: macos-latest
needs: [get-release]
steps:
- uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: ui-release-notes
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.35.1
channel: "stable"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.88.0
- name: Install appdmg
run: |
npm install -g appdmg
- name: Build latest macOS FFI
run: |
make build-pb-mapper-ffi-macos
- name: Build macOS app with latest FFI
run: |
make build-ui-macos-release-no-ffi
- name: Verify macOS app bundle uses latest FFI
run: |
SRC_FFI="ui/native/macos/libpb_mapper_ffi.dylib"
DST_FFI="$(find ui/build/macos/Build/Products/Release -type f -name libpb_mapper_ffi.dylib | head -n1)"
test -f "${SRC_FFI}"
test -n "${DST_FFI}"
test -f "${DST_FFI}"
SRC_HASH="$(shasum -a 256 "${SRC_FFI}" | awk '{print $1}')"
DST_HASH="$(shasum -a 256 "${DST_FFI}" | awk '{print $1}')"
if [ "${SRC_HASH}" != "${DST_HASH}" ]; then
echo "FFI hash mismatch: src=${SRC_HASH} dst=${DST_HASH}"
exit 1
fi
- name: Package macOS artifacts
run: |
cd ui
PROJECT_DIR=$(pwd)
cd build/macos/Build/Products/Release
cat>appdmg.json<<EOF
{
"title": "pb-mapper",
"icon": "pb-mapper.app/Contents/Resources/AppIcon.icns",
"contents": [
{ "x": 448, "y": 344, "type": "link", "path": "/Applications" },
{ "x": 192, "y": 344, "type": "file", "path": "pb-mapper.app" }
]
}
EOF
mkdir dist
echo "DMG_ASSET=ui/build/macos/Build/Products/Release/dist/pb-mapper-macos.dmg" >> $GITHUB_ENV
appdmg appdmg.json dist/pb-mapper-macos.dmg
- name: Upload macOS artifacts
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}-ui
target_commitish: ${{ github.sha }}
draft: ${{ github.event_name == 'workflow_dispatch' }}
body_path: ui_release_notes.md
files: |
${{env.DMG_ASSET}}
build-ios:
runs-on: macos-latest
needs: [get-release]
steps:
- uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: ui-release-notes
- uses: subosito/flutter-action@v2
with:
flutter-version: 3.35.1
channel: "stable"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.88.0
- name: Build latest iOS FFI
run: |
make build-pb-mapper-ffi-ios
- name: Build iOS app with latest FFI (no-codesign)
run: |
make build-ui-ios-release-no-ffi
cd ui
cd build/ios/iphoneos
mkdir Payload
mv Runner.app Payload/
zip -r pb-mapper-ios-unsigned.ipa Payload/
- name: Upload iOS artifacts
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}-ui
target_commitish: ${{ github.sha }}
draft: ${{ github.event_name == 'workflow_dispatch' }}
body_path: ui_release_notes.md
files: |
ui/build/ios/iphoneos/pb-mapper-ios-unsigned.ipa