From 5c5f65f3939a353caac1b0aa55748c6d4c5eb409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sat, 25 Apr 2026 22:54:20 +0800 Subject: [PATCH 1/4] fix: strip android native build ids for fdroid --- .github/workflows/release.yml | 2 - scripts/release/verify-fdroid-native-libs.sh | 193 ++++++++++++++++++- 2 files changed, 184 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 98e8f6692..1c21bfe12 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,8 +36,6 @@ jobs: curl -u ${{ secrets.BASIC_AUTH }} -o android/app/app.key ${{ secrets.URL_PREFIX }}app.key curl -u ${{ secrets.BASIC_AUTH }} -o android/key.properties ${{ secrets.URL_PREFIX }}key.properties - name: Build - env: - LDFLAGS: -Wl,--build-id=none run: dart run fl_build -p android - name: Verify F-Droid native libraries run: scripts/release/verify-fdroid-native-libs.sh diff --git a/scripts/release/verify-fdroid-native-libs.sh b/scripts/release/verify-fdroid-native-libs.sh index b9084d23f..8e6fe1cec 100755 --- a/scripts/release/verify-fdroid-native-libs.sh +++ b/scripts/release/verify-fdroid-native-libs.sh @@ -5,6 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" APK_DIR="${APK_DIR:-$REPO_ROOT/build/app/outputs/flutter-apk}" APP_NAME="${APP_NAME:-ServerBox}" +KEY_PROPERTIES="${KEY_PROPERTIES:-$REPO_ROOT/android/key.properties}" require_cmd() { local name="$1" @@ -14,16 +15,149 @@ require_cmd() { fi } +android_sdk_root() { + local candidates=( + "${ANDROID_HOME:-}" + "${ANDROID_SDK_ROOT:-}" + "$HOME/Library/Android/sdk" + "$HOME/Android/Sdk" + "/opt/android-sdk" + "/usr/local/lib/android/sdk" + ) + + for candidate in "${candidates[@]}"; do + if [[ -n "$candidate" && -d "$candidate" ]]; then + printf '%s\n' "$candidate" + return + fi + done +} + +find_latest_android_tool() { + local name="$1" + local sdk_root + sdk_root="$(android_sdk_root)" + if [[ -z "$sdk_root" || ! -d "$sdk_root/build-tools" ]]; then + return 1 + fi + + find "$sdk_root/build-tools" -type f -name "$name" | sort | tail -n 1 +} + +find_llvm_objcopy() { + local sdk_root + local found='' + sdk_root="$(android_sdk_root)" + if [[ -n "$sdk_root" && -d "$sdk_root/ndk" ]]; then + found="$(find "$sdk_root/ndk" -type f -path '*/toolchains/llvm/prebuilt/*/bin/llvm-objcopy' | sort | tail -n 1)" + fi + if [[ -n "$found" ]]; then + printf '%s\n' "$found" + return + fi + command -v llvm-objcopy || command -v objcopy +} + +find_readelf() { + local sdk_root + local found='' + sdk_root="$(android_sdk_root)" + if [[ -n "$sdk_root" && -d "$sdk_root/ndk" ]]; then + found="$(find "$sdk_root/ndk" -type f -path '*/toolchains/llvm/prebuilt/*/bin/llvm-readelf' | sort | tail -n 1)" + fi + if [[ -n "$found" ]]; then + printf '%s\n' "$found" + return + fi + command -v readelf || command -v llvm-readelf +} + +property_value() { + local name="$1" + grep -E "^[[:space:]]*$name[[:space:]]*=" "$KEY_PROPERTIES" | + tail -n 1 | + sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]]*$//' || true +} + +has_build_id() { + local so_file="$1" + "$READELF" -n "$so_file" 2>/dev/null | grep -q 'Build ID' +} + require_cmd find -require_cmd readelf +require_cmd grep +require_cmd sed +require_cmd sort require_cmd unzip +require_cmd zip + +OBJCOPY="${OBJCOPY:-}" +READELF="${READELF:-}" +APKSIGNER="${APKSIGNER:-}" +ZIPALIGN="${ZIPALIGN:-}" + +if [[ -z "$OBJCOPY" ]]; then + OBJCOPY="$(find_llvm_objcopy || true)" +fi +if [[ -z "$READELF" ]]; then + READELF="$(find_readelf || true)" +fi +if [[ -z "$APKSIGNER" ]]; then + APKSIGNER="$(find_latest_android_tool apksigner || true)" +fi +if [[ -z "$ZIPALIGN" ]]; then + ZIPALIGN="$(find_latest_android_tool zipalign || true)" +fi + +if [[ -z "$OBJCOPY" || ! -x "$OBJCOPY" ]]; then + echo 'llvm-objcopy or objcopy is required to remove ELF build-id notes' >&2 + exit 1 +fi + +if [[ -z "$READELF" || ! -x "$READELF" ]]; then + echo 'readelf or llvm-readelf is required to inspect ELF build-id notes' >&2 + exit 1 +fi + +if [[ -z "$APKSIGNER" || ! -x "$APKSIGNER" ]]; then + echo 'Android build-tools apksigner is required to re-sign patched APKs' >&2 + exit 1 +fi + +if [[ -z "$ZIPALIGN" || ! -x "$ZIPALIGN" ]]; then + echo 'Android build-tools zipalign is required before re-signing patched APKs' >&2 + exit 1 +fi + +if [[ ! -f "$KEY_PROPERTIES" ]]; then + echo "key.properties not found: $KEY_PROPERTIES" >&2 + exit 1 +fi + +store_file="$(property_value storeFile)" +store_password="$(property_value storePassword)" +key_alias="$(property_value keyAlias)" +key_password="$(property_value keyPassword)" + +if [[ -z "$store_file" || -z "$store_password" || -z "$key_alias" || -z "$key_password" ]]; then + echo "storeFile, storePassword, keyAlias, and keyPassword are required in $KEY_PROPERTIES" >&2 + exit 1 +fi + +if [[ "$store_file" != /* ]]; then + store_file="$REPO_ROOT/android/app/$store_file" +fi + +if [[ ! -f "$store_file" ]]; then + echo "signing store file not found: $store_file" >&2 + exit 1 +fi shopt -s nullglob tmp_dir="$(mktemp -d)" trap 'rm -rf "$tmp_dir"' EXIT -failures=() apks=() patterns=( "${APP_NAME}_*_arm64.apk" @@ -52,18 +186,59 @@ for apk in "${apks[@]}"; do extract_dir="$tmp_dir/$apk_name" mkdir -p "$extract_dir" unzip -qq "$apk" "lib/*/*.so" -d "$extract_dir" + patched_files=() while IFS= read -r so_file; do - if readelf -n "$so_file" | grep -q 'Build ID'; then - failures+=("$apk_name:${so_file#$extract_dir/}") + if has_build_id "$so_file"; then + rel_path="${so_file#$extract_dir/}" + tmp_so="$so_file.tmp" + "$OBJCOPY" --remove-section=.note.gnu.build-id "$so_file" "$tmp_so" + mv "$tmp_so" "$so_file" + if has_build_id "$so_file"; then + echo "failed to remove ELF build-id note: $apk_name:$rel_path" >&2 + exit 1 + fi + patched_files+=("$rel_path") fi done < <(find "$extract_dir" -type f -name '*.so' | sort) + + if [[ ${#patched_files[@]} -eq 0 ]]; then + continue + fi + + echo "Removing ELF build-id notes from $apk_name" + ( + cd "$extract_dir" + zip -q -0 "$apk" "${patched_files[@]}" + ) + + aligned_apk="$tmp_dir/$apk_name.aligned.apk" + "$ZIPALIGN" -f -p 4 "$apk" "$aligned_apk" + mv "$aligned_apk" "$apk" + + "$APKSIGNER" sign \ + --ks "$store_file" \ + --ks-key-alias "$key_alias" \ + --ks-pass "pass:$store_password" \ + --key-pass "pass:$key_password" \ + "$apk" done -if [[ ${#failures[@]} -ne 0 ]]; then - echo 'native libraries still contain ELF build-id notes:' >&2 - printf ' %s\n' "${failures[@]}" >&2 - exit 1 -fi +for apk in "${apks[@]}"; do + apk_name="$(basename "$apk")" + verify_dir="$tmp_dir/verify-$apk_name" + mkdir -p "$verify_dir" + unzip -qq "$apk" "lib/*/*.so" -d "$verify_dir" + + while IFS= read -r so_file; do + if has_build_id "$so_file"; then + rel_path="${so_file#$verify_dir/}" + echo "native library still contains ELF build-id note: $apk_name:$rel_path" >&2 + exit 1 + fi + done < <(find "$verify_dir" -type f -name '*.so' | sort) + + "$APKSIGNER" verify "$apk" +done echo 'F-Droid native library verification passed.' From 0eb8abd10e61d0e65840f5e4eeadfeb17e06c55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sat, 25 Apr 2026 23:19:40 +0800 Subject: [PATCH 2/4] fix --- scripts/release/verify-fdroid-native-libs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/release/verify-fdroid-native-libs.sh b/scripts/release/verify-fdroid-native-libs.sh index 8e6fe1cec..225fb46fe 100755 --- a/scripts/release/verify-fdroid-native-libs.sh +++ b/scripts/release/verify-fdroid-native-libs.sh @@ -190,7 +190,7 @@ for apk in "${apks[@]}"; do while IFS= read -r so_file; do if has_build_id "$so_file"; then - rel_path="${so_file#$extract_dir/}" + rel_path="${so_file#"$extract_dir"/}" tmp_so="$so_file.tmp" "$OBJCOPY" --remove-section=.note.gnu.build-id "$so_file" "$tmp_so" mv "$tmp_so" "$so_file" @@ -232,7 +232,7 @@ for apk in "${apks[@]}"; do while IFS= read -r so_file; do if has_build_id "$so_file"; then - rel_path="${so_file#$verify_dir/}" + rel_path="${so_file#"$verify_dir"/}" echo "native library still contains ELF build-id note: $apk_name:$rel_path" >&2 exit 1 fi From b9154d7ee7c75ff722392675edd89c43516b8bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sat, 25 Apr 2026 23:38:23 +0800 Subject: [PATCH 3/4] fix --- scripts/release/verify-fdroid-native-libs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/verify-fdroid-native-libs.sh b/scripts/release/verify-fdroid-native-libs.sh index 225fb46fe..d32e44fc5 100755 --- a/scripts/release/verify-fdroid-native-libs.sh +++ b/scripts/release/verify-fdroid-native-libs.sh @@ -74,7 +74,7 @@ find_readelf() { property_value() { local name="$1" - grep -E "^[[:space:]]*$name[[:space:]]*=" "$KEY_PROPERTIES" | + grep -E "^[[:space:]]*${name}[[:space:]]*=" "$KEY_PROPERTIES" | tail -n 1 | sed -E 's/^[^=]*=[[:space:]]*//; s/[[:space:]]*$//' || true } From 19a9d8acd76b391654c2885c53e99e507e2c579a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sun, 26 Apr 2026 00:03:05 +0800 Subject: [PATCH 4/4] fix --- scripts/release/verify-fdroid-native-libs.sh | 133 ++++++++++++++++--- 1 file changed, 112 insertions(+), 21 deletions(-) diff --git a/scripts/release/verify-fdroid-native-libs.sh b/scripts/release/verify-fdroid-native-libs.sh index d32e44fc5..45fe5664c 100755 --- a/scripts/release/verify-fdroid-native-libs.sh +++ b/scripts/release/verify-fdroid-native-libs.sh @@ -15,7 +15,7 @@ require_cmd() { fi } -android_sdk_root() { +android_sdk_roots() { local candidates=( "${ANDROID_HOME:-}" "${ANDROID_SDK_ROOT:-}" @@ -24,51 +24,142 @@ android_sdk_root() { "/opt/android-sdk" "/usr/local/lib/android/sdk" ) + local roots='' + local candidate for candidate in "${candidates[@]}"; do - if [[ -n "$candidate" && -d "$candidate" ]]; then - printf '%s\n' "$candidate" - return + if [[ -z "$candidate" || ! -d "$candidate" ]]; then + continue fi + + if printf '%s' "$roots" | grep -Fxq "$candidate"; then + continue + fi + roots+="$candidate"$'\n' done + + printf '%s' "$roots" +} + +android_tool_version() { + local path="$1" + local version='0' + case "$path" in + */build-tools/*) + version="${path#*/build-tools/}" + version="${version%%/*}" + ;; + */ndk/*) + version="${path#*/ndk/}" + version="${version%%/*}" + ;; + esac + + printf '%s\n' "$version" +} + +version_sort_key() { + local version="$1" + local part + local padded + local key='' + + IFS='.' read -ra parts <<< "$version" + for part in "${parts[@]}"; do + if [[ "$part" =~ ^[0-9]+$ ]]; then + printf -v padded '%010d' "$part" + else + padded="$part" + fi + key+="$padded." + done + + printf '%s\n' "$key" +} + +select_latest_android_path() { + local path + local version + local key + + while IFS= read -r path; do + if [[ -z "$path" ]]; then + continue + fi + version="$(android_tool_version "$path")" + key="$(version_sort_key "$version")" + printf '%s\t%s\n' "$key" "$path" + done | + sort | + tail -n 1 | + while IFS=$'\t' read -r _ path; do + printf '%s\n' "$path" + done } find_latest_android_tool() { local name="$1" local sdk_root - sdk_root="$(android_sdk_root)" - if [[ -z "$sdk_root" || ! -d "$sdk_root/build-tools" ]]; then + local matches=() + local path + + while IFS= read -r sdk_root; do + if [[ -z "$sdk_root" || ! -d "$sdk_root/build-tools" ]]; then + continue + fi + while IFS= read -r path; do + matches+=("$path") + done < <(find "$sdk_root/build-tools" -type f -name "$name") + done < <(android_sdk_roots) + + if [[ ${#matches[@]} -eq 0 ]]; then return 1 fi - find "$sdk_root/build-tools" -type f -name "$name" | sort | tail -n 1 + printf '%s\n' "${matches[@]}" | select_latest_android_path } find_llvm_objcopy() { local sdk_root - local found='' - sdk_root="$(android_sdk_root)" - if [[ -n "$sdk_root" && -d "$sdk_root/ndk" ]]; then - found="$(find "$sdk_root/ndk" -type f -path '*/toolchains/llvm/prebuilt/*/bin/llvm-objcopy' | sort | tail -n 1)" - fi - if [[ -n "$found" ]]; then - printf '%s\n' "$found" + local matches=() + local path + + while IFS= read -r sdk_root; do + if [[ -z "$sdk_root" || ! -d "$sdk_root/ndk" ]]; then + continue + fi + while IFS= read -r path; do + matches+=("$path") + done < <(find "$sdk_root/ndk" -type f -path '*/toolchains/llvm/prebuilt/*/bin/llvm-objcopy') + done < <(android_sdk_roots) + + if [[ ${#matches[@]} -ne 0 ]]; then + printf '%s\n' "${matches[@]}" | select_latest_android_path return fi + command -v llvm-objcopy || command -v objcopy } find_readelf() { local sdk_root - local found='' - sdk_root="$(android_sdk_root)" - if [[ -n "$sdk_root" && -d "$sdk_root/ndk" ]]; then - found="$(find "$sdk_root/ndk" -type f -path '*/toolchains/llvm/prebuilt/*/bin/llvm-readelf' | sort | tail -n 1)" - fi - if [[ -n "$found" ]]; then - printf '%s\n' "$found" + local matches=() + local path + + while IFS= read -r sdk_root; do + if [[ -z "$sdk_root" || ! -d "$sdk_root/ndk" ]]; then + continue + fi + while IFS= read -r path; do + matches+=("$path") + done < <(find "$sdk_root/ndk" -type f -path '*/toolchains/llvm/prebuilt/*/bin/llvm-readelf') + done < <(android_sdk_roots) + + if [[ ${#matches[@]} -ne 0 ]]; then + printf '%s\n' "${matches[@]}" | select_latest_android_path return fi + command -v readelf || command -v llvm-readelf }