diff --git a/.gitea/workflows/pixel10.yml b/.gitea/workflows/pixel10.yml new file mode 100644 index 00000000..fab5cf84 --- /dev/null +++ b/.gitea/workflows/pixel10.yml @@ -0,0 +1,99 @@ +name: Pixel 10 APK + +on: + workflow_dispatch: + push: + branches: + - pixel10 + +jobs: + build: + runs-on: ubuntu-latest + env: + KEY_JKS: ${{ secrets.PIXEL10_KEY_JKS }} + KEY_PATH: ${{ github.workspace }}/apps/weblibre/pixel10-release.jks + KEY_PASSWORD: ${{ secrets.PIXEL10_KEY_PASSWORD }} + KEY_ALIAS: ${{ secrets.PIXEL10_KEY_ALIAS }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - uses: actions/setup-go@v5 + with: + go-version: 1.25.x + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: 3.44.5 + cache: true + + - name: Install Android NDK + shell: bash + run: | + set -euo pipefail + ndk_version="$(sed -n 's/^weblibre\.ndkVersion[[:space:]]*=[[:space:]]*//p' apps/weblibre/android/gradle.properties)" + test -n "$ndk_version" + yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$ndk_version" || test "${PIPESTATUS[1]}" -eq 0 + { + echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$ndk_version" + echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/$ndk_version" + echo "NDK_HOME=$ANDROID_HOME/ndk/$ndk_version" + } >> "$GITHUB_ENV" + + - name: Install workspace dependencies + run: | + dart pub global activate melos 7.8.1 + melos bootstrap + + - name: Generate bundled assets + run: | + melos run update-assets --no-select + melos run build-components --no-select + + - name: Checkout pinned native sources + shell: bash + run: | + set -euo pipefail + source native/go_mobile_runtime/pins.env + git clone https://github.com/SagerNet/sing-box.git "$RUNNER_TEMP/sing-box" + git -C "$RUNNER_TEMP/sing-box" checkout "$SING_BOX_TAG" + test "$(git -C "$RUNNER_TEMP/sing-box" rev-parse HEAD)" = "$SING_BOX_COMMIT" + git clone https://github.com/tladesignz/IPtProxy.git "$RUNNER_TEMP/IPtProxy" + git -C "$RUNNER_TEMP/IPtProxy" checkout "$IPTPROXY_TAG" + test "$(git -C "$RUNNER_TEMP/IPtProxy" rev-parse HEAD)" = "$IPTPROXY_COMMIT" + git -C "$RUNNER_TEMP/IPtProxy" submodule update --init dnstt + + - name: Build native runtime + env: + SING_BOX_SOURCE: ${{ runner.temp }}/sing-box + IPTPROXY_SOURCE: ${{ runner.temp }}/IPtProxy + run: melos run build-go-runtime --no-select + + - name: Create release key + shell: bash + run: | + set -euo pipefail + test -n "$KEY_JKS" + printf '%s' "$KEY_JKS" | base64 -d > "$KEY_PATH" + chmod 600 "$KEY_PATH" + + - name: Build and verify Pixel 10 APK + run: melos run build-browser-pixel10 --no-select + + - uses: actions/upload-artifact@v4 + with: + name: mrbweblibre-pixel10 + path: apps/weblibre/build/app/outputs/flutter-apk/app-pixel10-release.apk + + - name: Remove release key + if: always() + run: rm -f "$KEY_PATH" diff --git a/PIXEL_10.md b/PIXEL_10.md new file mode 100644 index 00000000..266460c7 --- /dev/null +++ b/PIXEL_10.md @@ -0,0 +1,57 @@ +# MrbWebLibre for Google Pixel 10 + +This fork keeps WebLibre's upstream behavior and adds a focused build profile +for the Google Pixel 10 family. + +## What is optimized + +- **ARM64 only:** Pixel 10 uses a 64-bit Tensor G5 platform. The `pixel10` + flavor excludes unused 32-bit native libraries, reducing the APK size and + eliminating accidental 32-bit packaging. +- **Impeller enabled:** the Flutter renderer remains enabled for smooth GPU + rendering on the 60–120 Hz Pixel display. +- **High refresh rate by default:** WebLibre's existing `RefreshRateMode.high` + default is retained. It can still be changed under General settings when + battery life matters more than maximum smoothness. +- **16 KB page-size gate:** every native ARM64 library in the final APK is + checked for 16 KB-compatible ELF LOAD-segment alignment. This catches an + incompatible Gecko, Flutter, Tor, or proxy binary before the APK is shipped. +- **Separate app identity:** `cc.mrblake.mrbweblibre` installs alongside the + upstream `eu.weblibre.gecko` app and cannot overwrite an upstream-signed APK. + +The fork deliberately does not spoof the device model or change Gecko web +preferences only for Pixels. Those changes would increase fingerprinting and +make upstream security updates harder to merge. + +## Build + +Use the Flutter and Android versions pinned by the upstream CI workflow, build +the generated assets and native runtime, then run: + +```bash +melos run build-browser-pixel10 --no-select +``` + +The command produces: + +```text +apps/weblibre/build/app/outputs/flutter-apk/app-pixel10-release.apk +``` + +Release builds require the same signing environment variables as upstream: +`KEY_PATH`, `KEY_ALIAS`, and `KEY_PASSWORD`. + +## Verify an existing APK + +```bash +scripts/verify-pixel10-apk.sh path/to/app-pixel10-release.apk +``` + +The verifier requires `unzip` and GNU `readelf` (usually supplied by the +`binutils` package). + +## Keeping the fork current + +The upstream remote should point at `https://github.com/FaFre/WebLibre.git`. +Rebase the `pixel10` branch on upstream `main`, run the full test suite, build +the APK, and do a real-device smoke test before publishing an update. diff --git a/README.md b/README.md index e406f822..4eef48c4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,12 @@ # WebLibre +> [!NOTE] +> This repository contains **MrbWebLibre**, a Google Pixel 10-focused fork of +> WebLibre. See [the Pixel 10 build profile](PIXEL_10.md) for the ARM64-only +> flavor, 16 KB native-library verification, build instructions, and the +> differences from upstream. +

A privacy-focused Android browser with powerful browsing separation, local-first tools, and deep customization.

diff --git a/apps/weblibre/android/app/build.gradle b/apps/weblibre/android/app/build.gradle index 7678e5e4..ece76b2e 100644 --- a/apps/weblibre/android/app/build.gradle +++ b/apps/weblibre/android/app/build.gradle @@ -73,6 +73,19 @@ android { versionNameSuffix "-alpha" manifestPlaceholders = [appName: "WebLibre Alpha (Legacy)", enableImpeller: "false"] } + // Pixel 10 devices are 64-bit only and ship a 120 Hz display. Keep a + // separate, side-by-side installable identity for the optimized fork, + // retain Impeller, and prevent transitive dependencies from packaging + // unused 32-bit native libraries. + pixel10 { + dimension "track" + applicationId "cc.mrblake.mrbweblibre" + versionNameSuffix "-pixel10" + manifestPlaceholders = [appName: "MrbWebLibre", enableImpeller: "true"] + ndk { + abiFilters "arm64-v8a" + } + } } sourceSets { diff --git a/pubspec.yaml b/pubspec.yaml index 4320f906..128f9763 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -106,3 +106,10 @@ melos: set -e cd apps/weblibre flutter build apk --release --flavor alphaLegacy --target-platform android-arm,android-arm64 --split-per-abi --no-tree-shake-icons + build-browser-pixel10: + description: Build and verify the ARM64-only MrbWebLibre release for Google Pixel 10 + run: | + set -e + cd apps/weblibre + flutter build apk --release --flavor pixel10 --target-platform android-arm64 --no-tree-shake-icons + ../../scripts/verify-pixel10-apk.sh build/app/outputs/flutter-apk/app-pixel10-release.apk diff --git a/scripts/verify-pixel10-apk.sh b/scripts/verify-pixel10-apk.sh new file mode 100755 index 00000000..aaeeba76 --- /dev/null +++ b/scripts/verify-pixel10-apk.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +set -euo pipefail + +readonly REQUIRED_PAGE_SIZE=$((16 * 1024)) + +usage() { + echo "Usage: $0 path/to/app-pixel10-release.apk" >&2 +} + +if [[ $# -ne 1 ]]; then + usage + exit 2 +fi + +apk="$1" +if [[ ! -f "$apk" ]]; then + echo "APK not found: $apk" >&2 + exit 1 +fi + +for command_name in unzip readelf; do + if ! command -v "$command_name" >/dev/null 2>&1; then + echo "Required command not found: $command_name" >&2 + exit 1 + fi +done + +native_entries="$(unzip -Z1 "$apk" | sed -n '/^lib\/.*\.so$/p')" +if [[ -z "$native_entries" ]]; then + echo "No native libraries found in $apk" >&2 + exit 1 +fi + +unexpected_abis=() +while IFS= read -r entry; do + if [[ "$entry" != lib/arm64-v8a/* ]]; then + unexpected_abis+=("$entry") + fi +done <<< "$native_entries" + +if [[ ${#unexpected_abis[@]} -gt 0 ]]; then + echo "Pixel 10 APK contains non-ARM64 native libraries:" >&2 + printf ' %s\n' "${unexpected_abis[@]}" >&2 + exit 1 +fi + +temporary_root="${TMPDIR:-/tmp}" +if [[ ! -d "$temporary_root" || ! -w "$temporary_root" ]]; then + temporary_root="$PWD" +fi +temporary_directory="$(mktemp -d "$temporary_root/mrbweblibre-verify.XXXXXX")" +trap 'rm -rf -- "$temporary_directory"' EXIT +unzip -qq "$apk" 'lib/arm64-v8a/*.so' -d "$temporary_directory" + +failures=0 +checked=0 +native_library_list="$temporary_directory/native-libraries.list" +find "$temporary_directory/lib/arm64-v8a" -type f -name '*.so' -print0 > "$native_library_list" +while IFS= read -r -d '' library; do + checked=$((checked + 1)) + machine="$(readelf -hW "$library" | sed -n 's/^[[:space:]]*Machine:[[:space:]]*//p')" + if [[ "$machine" != "AArch64" ]]; then + echo "Architecture failure: ${library#"$temporary_directory"/} is $machine, expected AArch64" >&2 + failures=$((failures + 1)) + continue + fi + + load_alignments="$(readelf -lW "$library" | awk '$1 == "LOAD" { print $NF }')" + + if [[ -z "$load_alignments" ]]; then + echo "No ELF LOAD segments found: ${library#"$temporary_directory"/}" >&2 + failures=$((failures + 1)) + continue + fi + + while IFS= read -r alignment; do + if (( alignment < REQUIRED_PAGE_SIZE )); then + echo "16 KB alignment failure: ${library#"$temporary_directory"/} has LOAD alignment $alignment" >&2 + failures=$((failures + 1)) + break + fi + done <<< "$load_alignments" +done < "$native_library_list" + +if (( failures > 0 )); then + echo "Pixel 10 compatibility verification failed ($failures issue(s))." >&2 + exit 1 +fi + +echo "Pixel 10 APK verified: ARM64-only; $checked native libraries use 16 KB-compatible ELF alignment."