diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5b8a98f..e0fa69d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -119,15 +119,15 @@ jobs: BUILD_TIME_INPUT: ${{ inputs.BUILD_TIME }} LTO_INPUT: ${{ inputs.LTO }} BBG_INPUT: ${{ inputs.BBG }} + NETWORKING_INPUT: ${{ inputs.NETWORKING }} run: | set -euo pipefail - if [[ "$ROOT_MANAGER_INPUT" == "KernelSU Next + SUSFS" || "$ROOT_MANAGER_INPUT" == "KSUN + SUSFS & BakaSU + SUSFS" ]]; then - # The device-tested candidate (Issue #19): fullLTO, BBG on, a sublevel whose trees are locked. - [[ "$KERNEL_VERSION_INPUT" == "6.1" && "${LTO_INPUT:-}" == fullLTO* && "${BBG_INPUT:-true}" == "true" ]] \ - || { echo "::error::KSUN+SUSFS needs kernel 6.1, a fullLTO package and BBG enabled" >&2; exit 1; } - python3 scripts/ksun_susfs_sublevel.py resolve "${KERNEL_SUBLEVEL_INPUT:-lts}" \ - || { echo "::error::KSUN+SUSFS is not verified for sublevel ${KERNEL_SUBLEVEL_INPUT}" >&2; exit 1; } - fi + case "$LTO_INPUT" in + "noneLTO + NoMount + Clean LTO"|"thinLTO + NoMount + Clean LTO"|"fullLTO + NoMount + Clean LTO") ;; + *) echo "::error::Invalid LTO package" >&2; exit 1 ;; + esac + case "$BBG_INPUT" in true|false) ;; *) echo "::error::BBG must be true or false" >&2; exit 1 ;; esac + case "$NETWORKING_INPUT" in true|false) ;; *) echo "::error::Networking must be true or false" >&2; exit 1 ;; esac # Cogan builds kernel 6.1 only (owner decision 2026-10-16); the form offers nothing else. [[ "$KERNEL_VERSION_INPUT" == "6.1" ]] || { echo "::error::Cogan builds kernel 6.1 only" >&2; exit 1; } # "lts" (any case), "Auto" or empty mean the latest pinned sublevel. @@ -160,6 +160,9 @@ jobs: KERNEL_VERSION_INPUT: ${{ inputs.KERNEL_VERSION }} KERNEL_SUBLEVEL_INPUT: ${{ inputs.KERNEL_SUBLEVEL }} BUILD_TIME_INPUT: ${{ inputs.BUILD_TIME }} + LTO_INPUT: ${{ inputs.LTO }} + BBG_INPUT: ${{ inputs.BBG }} + NETWORKING_INPUT: ${{ inputs.NETWORKING }} run: | { echo "## Cogan Kernel — Validation Summary" @@ -169,13 +172,12 @@ jobs: echo "- Root manager: ${ROOT_MANAGER_INPUT}" echo "- Kernel: ${KERNEL_VERSION_INPUT} / sublevel ${KERNEL_SUBLEVEL_INPUT}" echo "- Build time: ${BUILD_TIME_INPUT}" + echo "- LTO: ${LTO_INPUT}; BBG: ${BBG_INPUT}; Networking: ${NETWORKING_INPUT}" echo "- Status validasi: ${VALIDATION_OUTCOME}" if [[ "${VALIDATION_OUTCOME}" != "success" ]]; then echo "" echo "**Persiapan gagal; kompilasi tidak dijalankan.**" - if [[ "${ROOT_MANAGER_INPUT}" == "KernelSU Next + SUSFS" || "${ROOT_MANAGER_INPUT}" == "KSUN + SUSFS & BakaSU + SUSFS" ]]; then - echo "KSUN + SUSFS butuh paket fullLTO, BBG aktif dan sublevel yang sudah diverifikasi: lts atau 167–177." - elif [[ "${ROOT_MANAGER_INPUT}" == *SUSFS* || "${ROOT_MANAGER_INPUT}" == "All variants (advanced)" ]]; then + if [[ "${ROOT_MANAGER_INPUT}" == *SUSFS* || "${ROOT_MANAGER_INPUT}" == "All variants (advanced)" ]]; then echo "Varian SUSFS butuh sublevel lts atau 167 ke atas. Periksa pesan error pertama pada langkah **Validate sublevel, build time and optional features**." else echo "Periksa pesan error pertama pada langkah **Validate sublevel, build time and optional features**." @@ -192,13 +194,12 @@ jobs: echo "- Root manager: ${ROOT_MANAGER_INPUT}" echo "- Kernel: ${KERNEL_VERSION_INPUT} / sublevel ${KERNEL_SUBLEVEL_INPUT}" echo "- Build time: ${BUILD_TIME_INPUT}" + echo "- LTO: ${LTO_INPUT}; BBG: ${BBG_INPUT}; Networking: ${NETWORKING_INPUT}" echo "- Validation: ${VALIDATION_OUTCOME}" if [[ "${VALIDATION_OUTCOME}" != "success" ]]; then echo "" echo "**Preparation failed; no kernel compilation ran.**" - if [[ "${ROOT_MANAGER_INPUT}" == "KernelSU Next + SUSFS" || "${ROOT_MANAGER_INPUT}" == "KSUN + SUSFS & BakaSU + SUSFS" ]]; then - echo "KSUN + SUSFS needs a fullLTO package, BBG enabled and a verified sublevel: lts or 167–177." - elif [[ "${ROOT_MANAGER_INPUT}" == *SUSFS* || "${ROOT_MANAGER_INPUT}" == "All variants (advanced)" ]]; then + if [[ "${ROOT_MANAGER_INPUT}" == *SUSFS* || "${ROOT_MANAGER_INPUT}" == "All variants (advanced)" ]]; then echo "SUSFS variants need sublevel lts or 167 and newer. Inspect the first error in **Validate sublevel, build time and optional features**." else echo "Inspect the first error in **Validate sublevel, build time and optional features**." diff --git a/.github/workflows/ksun-susfs-apply-check.yml b/.github/workflows/ksun-susfs-apply-check.yml index 2ad4c6f..5f835f4 100644 --- a/.github/workflows/ksun-susfs-apply-check.yml +++ b/.github/workflows/ksun-susfs-apply-check.yml @@ -1,6 +1,19 @@ name: KSUN SUSFS Patch Apply Check on: workflow_dispatch: + inputs: + KERNEL_SUBLEVEL: + description: "Kernel Sublevel (lts atau angka; SUSFS 6.1.167 ke atas)" + type: string + default: "lts" + BBG: + description: "BBG" + type: boolean + default: true + NETWORKING: + description: "Networking" + type: boolean + default: true permissions: contents: read @@ -10,41 +23,39 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 25 steps: - - name: Checkout project dev + - name: Checkout project uses: actions/checkout@v4 with: - ref: dev persist-credentials: false - - name: Fetch pinned SUSFS source archive - shell: bash + - name: Resolve current kernel pin + id: source + env: + SUBLEVEL_INPUT: ${{ inputs.KERNEL_SUBLEVEL }} run: | - set -Eeuo pipefail - mkdir -p "$RUNNER_TEMP/susfs" - curl --fail --location --retry 2 --connect-timeout 20 \ - 'https://gitlab.com/pershoot/susfs4ksu/-/archive/a9bb4dd3ed248ada636b480bfdff70af704f1463/susfs4ksu-a9bb4dd3ed248ada636b480bfdff70af704f1463.tar.gz' \ - -o "$RUNNER_TEMP/susfs.tar.gz" - tar -xzf "$RUNNER_TEMP/susfs.tar.gz" -C "$RUNNER_TEMP/susfs" --strip-components=1 + set -euo pipefail + sublevel="$(python3 scripts/ksun_susfs_sublevel.py resolve "$SUBLEVEL_INPUT")" + commit="$(python3 scripts/ksun_susfs_sublevel.py commit "$sublevel")" + echo "sublevel=$sublevel" >> "$GITHUB_OUTPUT" + echo "commit=$commit" >> "$GITHUB_OUTPUT" - - name: Prepare locked SUSFS patch - shell: bash + - name: Fetch selected kernel and check production patch series + env: + KERNEL_COMMIT: ${{ steps.source.outputs.commit }} + SUBLEVEL: ${{ steps.source.outputs.sublevel }} + BBG: ${{ inputs.BBG }} + NETWORKING: ${{ inputs.NETWORKING }} run: | - set -Eeuo pipefail - python3 scripts/prepare_susfs_kernel_patch.py \ - "$RUNNER_TEMP/susfs/kernel_patches/50_add_susfs_in_gki-android14-6.1.patch" \ - "$RUNNER_TEMP/prepared-susfs.patch" - - - name: Fetch locked kernel and check patch - shell: bash - run: | - set -Eeuo pipefail - git clone --filter=blob:none --no-checkout \ - https://github.com/ahmed-alnassif/GKI-Duchamp-6.1.git \ - "$RUNNER_TEMP/kernel" - cd "$RUNNER_TEMP/kernel" - git fetch --filter=blob:none origin a7ba124af5a535078d3f83b3e145f9cc8ec651b8 - git checkout --detach a7ba124af5a535078d3f83b3e145f9cc8ec651b8 - test "$(git rev-parse HEAD)" = a7ba124af5a535078d3f83b3e145f9cc8ec651b8 - test -z "$(git status --porcelain)" - git apply --check --verbose "$RUNNER_TEMP/prepared-susfs.patch" - echo 'PASS: git apply --check' + set -euo pipefail + mkdir -p "$RUNNER_TEMP/ksun-susfs/kernel" + kernel="$RUNNER_TEMP/ksun-susfs/kernel" + git -C "$kernel" init -q + git -C "$kernel" fetch -q --depth=1 https://github.com/ahmed-alnassif/GKI-Duchamp-6.1.git "$KERNEL_COMMIT" + git -C "$kernel" checkout -q --detach FETCH_HEAD + python3 scripts/stage_ksun_susfs_candidate.py "$kernel" "$RUNNER_TEMP/ksun-susfs/ksun" "$SUBLEVEL" + { + echo '## KSUN + SUSFS — Patch Apply Check' + echo "Kernel: 6.1.$SUBLEVEL ($KERNEL_COMMIT); BBG=$BBG; Networking=$NETWORKING" + echo 'Patch berhasil diterapkan pada source terpilih. Kompilasi dan uji perangkat belum dijalankan.' + echo 'Patches applied to the selected source. Compilation and device testing have not run.' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 7e97831..bd8ca04 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -49,6 +49,7 @@ jobs: python3 -m unittest discover -s tests -p 'test_ksun_susfs_preflight.py' python3 -m unittest discover -s tests -p 'test_ksun_susfs_replay.py' python3 -m unittest discover -s tests -p 'test_ksun_susfs_sublevel.py' + python3 -m unittest discover -s tests -p 'test_ksun_susfs_options.py' python3 -m unittest discover -s tests -p 'test_ksun_susfs_resolved_config.py' python3 -m unittest discover -s tests -p 'test_cogan_manual_update.py' python3 -m unittest discover -s tests -p 'test_apk_identity.py' diff --git a/README.en.md b/README.en.md index ce57ca4..5fe5a9d 100644 --- a/README.en.md +++ b/README.en.md @@ -54,7 +54,7 @@ A generic kernel built from GKI 6.1 sources ([GKI-Duchamp-6.1](https://github.co | KernelSU | `KernelSU` | KernelSU | ❌ | | | KernelSU + SUSFS | `KernelSU+SuSFS` | KernelSU | ✅ | | | KernelSU Next | `KernelSU-Next` | KernelSU Next | ❌ | | -| KernelSU Next + SUSFS | `KernelSU-Next+SuSFS` | KernelSU Next (pershoot fork) | ✅ | Kernel **6.1.167–6.1.177** (`lts` = 177; every sublevel has locked trees), Full LTO, BBG enabled. BBRv3 and extra networking are **always on** (the Networking box is ignored) | +| KernelSU Next + SUSFS | `KernelSU-Next+SuSFS` | KernelSU Next (pershoot fork) | ✅ | Kernel **6.1.167 and newer** from Manual Update pins (`lts` = current pin). None/Thin/Full LTO, BBG and Networking follow the form; no historical trial evidence or recorded trees required | | BakaSU + SUSFS | `BakaSU+SuSFS` | BakaSU | ✅ | **Suspended upstream** at pin `2489b38582a0`; waiting for a SUSFS compatibility update | | KSUN + SUSFS & BakaSU + SUSFS | `KernelSU-Next+SuSFS` and `BakaSU+SuSFS` | Both, separate ZIPs | ✅ | The two variants above in one run (KSUN + SUSFS rules apply) | | Vanilla | `Vanilla` | ❌ | ❌ | No root | @@ -166,7 +166,7 @@ Open **Actions → Cogan Kernel Builder → Run workflow** in your repository. | Build Revision | Optional, `r` + digits (e.g. `r9`). Used in the kernel version (`6.1.177-Cogan-r9-…`), the ZIP name, the flasher banner and the release name; when empty, the run number is used. Rebuilding on the same day? Bump the revision (`r10`) so release names do not clash | | Kernel Sublevel | `lts` = latest, or just the number (e.g. `177`, not `6.1.177`); 6.1.160 and newer, SUSFS variants 6.1.167 and newer | | Build Time | `N` = real build time, or a fixed time such as `Thu Jan 1 00:00:00 UTC 2026` | -| Networking | BBRv3 and extra networking (enabled by default; always on for KSUN + SUSFS) | +| Networking | BBRv3 and extra networking (enabled by default; can be disabled for every variant) | | BBG | Baseband Guard (enabled by default) | **Build output:** a green run lists numbered artifacts you can download directly (no extraction needed): @@ -178,7 +178,7 @@ Open **Actions → Cogan Kernel Builder → Run workflow** in your repository. | `3-MANAGER-SPOOFED__.apk` | The same manager with a random package name, from the same commit; BakaSU CI builds are copied to `cogan-mirror` because Actions artifacts expire | Install **one**: 2 or 3 | | `4-NOMOUNT-MODULE_.zip` | Official NoMount module, the same version as the NoMount in the kernel | Install in the manager → Modules | -`build-…` and `build-env-…` artifacts are internal; ignore them. The run summary shows the same table with SHA256 values, plus a **Kernel features** line (e.g. `SUSFS, BBRv3 (default), BBG, NoMount, Full LTO`) read from the built config. The same line shows in Kernel Flasher while flashing. **Cogan Manual Update** pins all these versions together. When upstream has something newer (a GKID 6.1 kernel sublevel, SUSFS, a root manager or NoMount), the run summary shows a **🔔 Reminder**: run Cogan Manual Update (`update` → `kernel-6.1`, `susfs`, `root-managers` or `nomount`), then rebuild. The SUSFS reminder only appears on SUSFS variants. The KSUN + SUSFS variant gets no kernel or SUSFS reminder: its SUSFS is the frozen v2 source, and a new kernel sublevel can only be used after its trees are recorded with `scripts/ksun_susfs_sublevel.py record`. +`build-…` and `build-env-…` artifacts are internal; ignore them. The run summary shows the same table with SHA256 values, plus a **Kernel features** line (e.g. `SUSFS, BBRv3 (default), BBG, NoMount, Full LTO`) read from the built config. The same line shows in Kernel Flasher while flashing. **Cogan Manual Update** pins all these versions together. When upstream has something newer (a GKID 6.1 kernel sublevel, SUSFS, a root manager or NoMount), the run summary shows a **🔔 Reminder**: run Cogan Manual Update (`update` → `kernel-6.1`, `susfs`, `root-managers` or `nomount`), then rebuild. The SUSFS reminder only appears on SUSFS variants. KSUN + SUSFS follows the same current kernel pin and gets kernel reminders. The pershoot fork's bundled SUSFS is updated separately from generic SUSFS. All LTO/BBG/Networking combinations can run without historical trial evidence; patch checksums, source compatibility and the resolved build configuration are checked on each build. Old test records remain an archive, not a feature unlock requirement. **Updating components:** open **Actions → Cogan Manual Update**. - `check` only reports versions. diff --git a/README.md b/README.md index a3772e7..2af0567 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Kernel generik yang dibangun dari sumber GKI 6.1 ([GKI-Duchamp-6.1](https://gith | KernelSU | `KernelSU` | KernelSU | ❌ | | | KernelSU + SUSFS | `KernelSU+SuSFS` | KernelSU | ✅ | | | KernelSU Next | `KernelSU-Next` | KernelSU Next | ❌ | | -| KernelSU Next + SUSFS | `KernelSU-Next+SuSFS` | KernelSU Next (fork pershoot) | ✅ | Kernel **6.1.167–6.1.177** (`lts` = 177; tiap sublevel punya tree terkunci), Full LTO, BBG aktif. BBRv3 dan paket jaringan tambahan **selalu aktif** (centang Networking diabaikan) | +| KernelSU Next + SUSFS | `KernelSU-Next+SuSFS` | KernelSU Next (fork pershoot) | ✅ | Kernel **6.1.167 ke atas** dari pin Manual Update (`lts` = pin terbaru). None/Thin/Full LTO, BBG dan Networking mengikuti form; tidak perlu data percobaan atau pencatatan tree lama | | BakaSU + SUSFS | `BakaSU+SuSFS` | BakaSU | ✅ | **Ditahan upstream** pada pin `2489b38582a0`; menunggu pembaruan kompatibilitas SUSFS | | KSUN + SUSFS & BakaSU + SUSFS | `KernelSU-Next+SuSFS` dan `BakaSU+SuSFS` | Keduanya, ZIP terpisah | ✅ | Dua varian di atas dalam satu run (aturan KSUN + SUSFS berlaku) | | Vanilla | `Vanilla` | ❌ | ❌ | Tanpa root | @@ -166,7 +166,7 @@ Buka **Actions → Cogan Kernel Builder → Run workflow** di repositori Anda. | Build Revision | Opsional, format `r` + angka (mis. `r9`). Dipakai di versi kernel (`6.1.177-Cogan-r9-…`), nama ZIP, banner flasher dan nama rilis; tanpa isian dipakai nomor run. Build ulang di hari yang sama? Naikkan revisinya (`r10`) agar nama rilis tidak bentrok | | Kernel Sublevel | `lts` = terbaru, atau angka saja (mis. `177`, bukan `6.1.177`); tersedia 6.1.160 ke atas, varian SUSFS 6.1.167 ke atas | | Build Time | `N` = waktu build asli, atau waktu tetap seperti `Thu Jan 1 00:00:00 UTC 2026` | -| Networking | BBRv3 dan paket jaringan tambahan (default aktif; KSUN + SUSFS selalu aktif) | +| Networking | BBRv3 dan paket jaringan tambahan (default aktif; bisa dimatikan pada semua varian) | | BBG | Baseband Guard (default aktif) | **Hasil build:** saat run hijau, artifact bernomor bisa langsung diunduh (tidak perlu diekstrak): @@ -178,7 +178,7 @@ Buka **Actions → Cogan Kernel Builder → Run workflow** di repositori Anda. | `3-MANAGER-SPOOFED__.apk` | Manager yang sama dengan nama paket acak, dari commit yang sama; build CI BakaSU disalin ke `cogan-mirror` karena artifact Actions kedaluwarsa | Install **salah satu**: 2 atau 3 | | `4-NOMOUNT-MODULE_.zip` | Modul NoMount resmi, versi sama dengan NoMount di kernel | Pasang di manager → Modul | -Artifact `build-…` dan `build-env-…` adalah file internal; abaikan saja. Ringkasan run berisi tabel yang sama beserta SHA256, dan baris **Fitur kernel** (mis. `SUSFS, BBRv3 (default), BBG, NoMount, Full LTO`) yang dibaca dari config hasil build. Baris yang sama muncul di Kernel Flasher saat flash. Semua versi ini dikunci bersama oleh **Cogan Manual Update**. Kalau upstream punya versi yang lebih baru (sublevel kernel GKID 6.1, SUSFS, root manager atau NoMount), ringkasan run menampilkan **🔔 Pengingat**: jalankan Cogan Manual Update (`update` → `kernel-6.1`, `susfs`, `root-managers` atau `nomount`), lalu build ulang. Pengingat SUSFS hanya muncul di varian SUSFS. Varian KSUN + SUSFS tidak mendapat pengingat kernel dan SUSFS: SUSFS-nya sumber v2 yang dibekukan, dan sublevel kernel baru baru bisa dipakai setelah tree-nya dicatat dengan `scripts/ksun_susfs_sublevel.py record`. +Artifact `build-…` dan `build-env-…` adalah file internal; abaikan saja. Ringkasan run berisi tabel yang sama beserta SHA256, dan baris **Fitur kernel** (mis. `SUSFS, BBRv3 (default), BBG, NoMount, Full LTO`) yang dibaca dari config hasil build. Baris yang sama muncul di Kernel Flasher saat flash. Semua versi ini dikunci bersama oleh **Cogan Manual Update**. Kalau upstream punya versi yang lebih baru (sublevel kernel GKID 6.1, SUSFS, root manager atau NoMount), ringkasan run menampilkan **🔔 Pengingat**: jalankan Cogan Manual Update (`update` → `kernel-6.1`, `susfs`, `root-managers` atau `nomount`), lalu build ulang. Pengingat SUSFS hanya muncul di varian SUSFS. KSUN + SUSFS mengikuti pin kernel terbaru yang sama dan mendapat pengingat kernel. SUSFS bawaan fork pershoot diperbarui terpisah dari SUSFS generik. Semua kombinasi LTO/BBG/Networking bisa dijalankan tanpa bukti percobaan lama; checksum patch, kompatibilitas source dan konfigurasi hasil build tetap diperiksa pada setiap build. Catatan uji lama disimpan sebagai arsip, bukan syarat membuka fitur. **Memperbarui komponen:** buka **Actions → Cogan Manual Update**. - `check` hanya menampilkan laporan versi. diff --git a/build.sh b/build.sh index 0ec406a..866d0e0 100755 --- a/build.sh +++ b/build.sh @@ -1,17 +1,23 @@ #!/usr/bin/env bash build_dir="."; [[ "${BASH_SOURCE[0]}" == */* ]] && build_dir="${BASH_SOURCE[0]%/*}" -# KSUN + SUSFS builds only the validated, device-tested isolated candidate (Issue #19), -# on a kernel sublevel whose patched trees are locked (configs/ksun-susfs-6.1-features.lock.json). -# Reject other combinations before downloads, installers, or host changes. +# Workflow choices are independent of historical device/build evidence. +# Source pins, strict patch application and the resolved .config are checked per build. +LTO="${LTO:-fullLTO}" +case "$LTO" in + noneLTO|thinLTO|fullLTO) ;; + *) echo "Invalid LTO: use noneLTO, thinLTO or fullLTO" >&2; exit 1 ;; +esac +for feature in BBG NETWORKING; do + value="${!feature:-true}" + case "$value" in true|false) ;; *) echo "$feature must be true or false" >&2; exit 1 ;; esac +done KSUN_SUSFS_CANDIDATE=false if [[ "${KSU:-}" == "KSUN" && "${KSU_SUSFS:-false}" == "true" ]]; then - if [[ "${KERNEL_VERSION:-6.1}" != "6.1" || "${KSU_COMPAT:-false}" == "true" || "${LTO:-fullLTO}" != "fullLTO" \ - || "${BBG:-true}" != "true" ]]; then - echo "KSUN+SUSFS supports only kernel 6.1, fullLTO, BBG enabled and no Compat variant" >&2 + if [[ "${KERNEL_VERSION:-6.1}" != "6.1" || "${KSU_COMPAT:-false}" == "true" ]]; then + echo "KSUN+SUSFS supports kernel 6.1 without the legacy Compat variant" >&2 exit 1 fi - # lts = the newest sublevel verified for KSUN+SUSFS; a number must be a verified one. KSUN_SUSFS_SUBLEVEL="$(python3 "$build_dir/scripts/ksun_susfs_sublevel.py" resolve "${KERNEL_SUBLEVEL:-lts}")" || exit 1 echo "KSUN+SUSFS kernel sublevel: 6.1.${KSUN_SUSFS_SUBLEVEL} (form value: ${KERNEL_SUBLEVEL:-lts})" KERNEL_SUBLEVEL="$KSUN_SUSFS_SUBLEVEL" @@ -108,18 +114,6 @@ elif [ "${KERNEL_SUBLEVEL:-Auto}" != "Auto" ]; then echo "Specific sublevels require kernel 6.1" >&2; exit 1 fi -# KSUN+SUSFS builds the kernel commit its patched trees were recorded on, not the GKID -# pin: GKID may move within a sublevel after KSUN+SUSFS was verified there (Manual -# Update run 18: 6.1.177 a7ba124a -> 8c24bbff). The other variants follow the new pin; -# KSUN+SUSFS keeps the recorded commit until scripts/ksun_susfs_sublevel.py record runs. -if [ "$KSUN_SUSFS_CANDIDATE" = "true" ]; then - KSUN_SUSFS_KERNEL_COMMIT="$(python3 "$WORKDIR/scripts/ksun_susfs_sublevel.py" commit "$KSUN_SUSFS_SUBLEVEL")" || exit 1 - if [ "$KSUN_SUSFS_KERNEL_COMMIT" != "$KERNEL_BRANCH" ]; then - echo "KSUN+SUSFS: 6.1.$KSUN_SUSFS_SUBLEVEL stays at recorded commit $KSUN_SUSFS_KERNEL_COMMIT (GKID pin $KERNEL_BRANCH is not recorded for KSUN+SUSFS yet)" - fi - KERNEL_BRANCH="$KSUN_SUSFS_KERNEL_COMMIT" -fi - ANDROID_RELEASE="$(android_release_for_version "$KERNEL_VERSION")" echo "KERNEL_VERSION=$KERNEL_VERSION" >> $GITHUB_ENV echo "ANDROID_RELEASE=$ANDROID_RELEASE" >> $GITHUB_ENV @@ -164,7 +158,7 @@ if kernel_version_eq "$KERNEL_VERSION" "6.1"; then fi if [ "$KSUN_SUSFS_CANDIDATE" = "true" ]; then - log "Staging the validated KSUN + SUSFS candidate (replay + frozen core + features)" + log "Staging KSUN + built-in SUSFS on the selected current kernel pin" python3 "$WORKDIR/scripts/stage_ksun_susfs_candidate.py" "$KSRC" "$WORKDIR/ksun" "$KSUN_SUSFS_SUBLEVEL" || exit 1 fi @@ -265,7 +259,7 @@ if ! kernel_version_eq "$KERNEL_VERSION" "6.1"; then elif ! grep -qE "disagrees about version of symbol.*but ignore" kernel/module/version.c; then # Only the GKID-6.1 tip carries the GKID patch stack; older sublevels are plain # android14-6.1-lts. Give them the same stack (scripts/gkid_parity.py explains why); - # KSUN+SUSFS already got it, strictly and tree-checked, while staging. + # KSUN+SUSFS already got it through strict patch application while staging. log "Sublevel without the GKID patch stack: applying Cogan's GKID-equivalent patches" apply_kernel_patches || warning "Some GKID-equivalent performance patches did not apply" cleanup_abi_gki_protected_exports @@ -278,7 +272,7 @@ fi if [ "$KSUN_SUSFS_CANDIDATE" = "true" ]; then - log "KSUN+SUSFS: BBRv3 comes from candidate feature patches 004 (+ bbr3 name, 005); networking extras from the locked fragment" + log "KSUN+SUSFS: Networking=${NETWORKING:-true}; optional BBRv3 patches handled during source staging" elif [ "${NETWORKING:-true}" = "true" ]; then log "Applying BBRv3 patch (Networking enabled)" # A failure stops the build: the fuzz fallback cannot dry-run this multi-commit series, @@ -292,7 +286,7 @@ fi if [ "$KSUN_SUSFS_CANDIDATE" = "true" ]; then - log "KSUN+SUSFS: BBG comes from candidate feature patches 001 and 003" + log "KSUN+SUSFS: BBG=${BBG:-true}; optional BBG patches handled during source staging" elif [ "${BBG:-true}" = "true" ]; then log "BBG included (Baseband Guard, pinned)" # setup.sh cannot clone a commit directly, so clone at the pin and let it reuse the checkout. @@ -460,7 +454,7 @@ AK3_ZIP_NAME=${AK3_ZIP_NAME//VARIANT/$VARIANT} log "Applying configs..." if [ "$KSUN_SUSFS_CANDIDATE" = "true" ]; then - log "KSUN+SUSFS: config = gki_defconfig + locked candidate fragment (see Generating config)" + log "KSUN+SUSFS: config = gki_defconfig + built-in root/SUSFS + requested LTO/BBG/Networking" else source "$WORKDIR/configs/gki_defconfig.sh" fi @@ -524,20 +518,18 @@ KMI_CHECK="$WORKDIR/py/kmi-check-6.x.py" echo "::group::[*] Generating config" make ${MAKE_ARGS[@]} "$KERNEL_DEFCONFIG" if [ "$KSUN_SUSFS_CANDIDATE" = "true" ]; then - # Same method as the validated record: merge only the locked fragment, then olddefconfig. - scripts/kconfig/merge_config.sh -m -O "$OUTDIR" "$OUTDIR/.config" "$WORKDIR/configs/ksun-susfs-6.1-features.config" || exit 1 + python3 "$WORKDIR/scripts/ksun_susfs_config.py" "$OUTDIR/.config" "$OUTDIR/cogan-requested.config" \ + --lto "${LTO:-fullLTO}" --bbg "${BBG:-true}" --networking "${NETWORKING:-true}" || exit 1 + scripts/kconfig/merge_config.sh -m -O "$OUTDIR" "$OUTDIR/.config" "$OUTDIR/cogan-requested.config" || exit 1 make ${MAKE_ARGS[@]} olddefconfig || exit 1 - python3 "$WORKDIR/scripts/check_ksun_susfs_resolved_config.py" "$OUTDIR/.config" || exit 1 + python3 "$WORKDIR/scripts/check_ksun_susfs_resolved_config.py" "$OUTDIR/.config" \ + --fragment "$OUTDIR/cogan-requested.config" || exit 1 fi echo "::endgroup::" # Report the resolved kernel configuration, not merely the requested fragments. # Diagnostics only: avoid failing established builds on kernel-version differences. -if [ "$KSUN_SUSFS_CANDIDATE" = "true" ]; then - log "Resolved Networking config (KSUN+SUSFS: always on from the locked fragment; Networking option ignored)" -else - log "Resolved Networking config (NETWORKING=${NETWORKING:-true})" -fi +log "Resolved Networking config (NETWORKING=${NETWORKING:-true})" for symbol in CONFIG_TCP_CONG_BBR CONFIG_DEFAULT_BBR CONFIG_NET_SCH_FQ CONFIG_NET_SCH_FQ_CODEL CONFIG_IP_SET CONFIG_NETFILTER_XT_SET; do if grep -q "^${symbol}=y$" "$OUTDIR/.config"; then log " ${symbol}=y" diff --git a/configs/gki_defconfig.sh b/configs/gki_defconfig.sh index 0719eb3..c23a946 100644 --- a/configs/gki_defconfig.sh +++ b/configs/gki_defconfig.sh @@ -18,6 +18,11 @@ fi echo "🧩 Adding Built-in Features" apply_config "$WORKDIR/configs/features.config" "$DEFCONFIG" +case "${BBG:-true}" in + true) echo 'CONFIG_BBG=y' >> "$DEFCONFIG" ;; + false) echo 'CONFIG_BBG=n' >> "$DEFCONFIG" ;; + *) echo "ERROR: BBG must be true or false" >&2; exit 1 ;; +esac if [ "$KSU_SUSFS" = "true" ]; then echo "🔧 Mode: SuSFS Hook Enabled" @@ -54,18 +59,26 @@ case "$LTO" in cat >> "$DEFCONFIG" <> "$DEFCONFIG" <> "$DEFCONFIG" <&2; exit 1 ;; esac if [ "$No_DS" = "true" ]; then diff --git a/configs/ksun-susfs-6.1.config b/configs/ksun-susfs-6.1.config new file mode 100644 index 0000000..e506465 --- /dev/null +++ b/configs/ksun-susfs-6.1.config @@ -0,0 +1,27 @@ +# Built-in KSUN + kernel-patched SUSFS and the fixed NoMount bundle. +# LTO, BBG, CONFIG_LSM and optional networking are generated from this run's inputs. +CONFIG_ARM64=y +CONFIG_KPROBES=y +CONFIG_THREAD_INFO_IN_TASK=y +CONFIG_KSU=y +CONFIG_KSU_SUSFS=y +CONFIG_KSU_SUSFS_SUS_PATH=y +CONFIG_KSU_SUSFS_SUS_MOUNT=y +CONFIG_KSU_SUSFS_SUS_KSTAT=y +CONFIG_KSU_SUSFS_SPOOF_UNAME=y +CONFIG_KSU_SUSFS_ENABLE_LOG=y +CONFIG_KSU_SUSFS_HIDE_KSU_SUSFS_SYMBOLS=y +CONFIG_KSU_SUSFS_SPOOF_CMDLINE_OR_BOOTCONFIG=y +CONFIG_KSU_SUSFS_OPEN_REDIRECT=y +CONFIG_KSU_SUSFS_SUS_MAP=y +# Samsung-only initialization aborts on generic GKI kernels; keep these disabled. +# CONFIG_KSU_SAMSUNG_KDP is not set +# CONFIG_KSU_SAMSUNG_RKP is not set +# CONFIG_KSU_SAMSUNG_DEFEX is not set +# CONFIG_BBG_BLOCK_BOOT is not set +# CONFIG_BBG_BLOCK_RECOVERY is not set +CONFIG_NOMOUNT=y +CONFIG_KEYS=y +CONFIG_CFI_CLANG=y +CONFIG_SECURITY_SELINUX=y +# CONFIG_NTSYNC is not set diff --git a/docs/WORK-AGREEMENT.md b/docs/WORK-AGREEMENT.md index 39b5f61..e51c7ff 100644 --- a/docs/WORK-AGREEMENT.md +++ b/docs/WORK-AGREEMENT.md @@ -38,6 +38,8 @@ Dokumen ini adalah sumber utama aturan kerja dan urutan prioritas. Owner cukup m - Perkembangan Cogan tidak ditahan, dibatasi atau diubah demi perangkat: repo perangkat mengunci commit mesinnya sendiri, jadi perubahan Cogan tidak pernah memecahkan build perangkat. - Bila pekerjaan untuk perangkat ternyata butuh mengubah Cogan, berhenti dulu dan tanya owner. +19. **Pilihan build bebas dari basis percobaan lama** (permintaan owner 2026-10-18, run `39075972745`). LTO None/Thin/Full, BBG dan Networking mengikuti form, termasuk KSUN+SUSFS. Tidak perlu hasil run, uji HP, tabel tree atau pencatatan sublevel terdahulu untuk membuka pilihan. Semua varian mengikuti pin kernel Manual Update saat ini; kompatibilitas patch dan hasil Kconfig diperiksa pada source build tersebut. Ketentuan percobaan lama yang mengunci kombinasi ini (termasuk W60) digantikan aturan ini. Checksum, source pin, kompatibilitas nyata, status gagal build dan penghentian SUSFS dari upstream tetap diperiksa. + ## B. Temuan audit (2026-10-15) | # | Temuan | Dampak | @@ -59,6 +61,7 @@ Dokumen ini adalah sumber utama aturan kerja dan urutan prioritas. Owner cukup m | ID | Prio | Pekerjaan | Status | |----|------|-----------|--------| +| W63 | P1 | Hapus pengunci workflow/build berbasis percobaan lama (owner 2026-10-18). | **Selesai (kode + uji lokal)** — FullLTO/BBG wajib, Networking paksa aktif dan override pin/tree percobaan dihapus dari jalur aktif. Form dan build menerima None/Thin/Full LTO serta BBG/Networking sesuai pilihan; config hasil build wajib cocok. Build dan Apply Check mengikuti pin Manual Update saat ini. Source asli `8c24bbff` lolos staging keempat kombinasi BBG/Networking; 12 kombinasi konfigurasi diuji offline. 59 tes KSUN/SUSFS, 44 Manual Update, 21 unduhan, 20 catatan rilis dan root matrix lulus. Kompilasi/flash tetap terpisah; BakaSU masih mengikuti guard upstream | | W1 | P0 | Port fix SUSFS #25 + #26 dari `master` ke `dev` (fail-fast patch & cek `.config` yang benar-benar menghentikan build). Uji lokal sekali. | **Selesai** 2026-10-15 — hunk identik dengan `7e13e87`/`eab1188`; `validate_shell.py` OK, 60 unit test OK | | W2 | P0 | Tetapkan **satu branch utama**. | **Diputuskan** 2026-10-15 — `dev` branch utama (sudah default di GitHub); `master` dibekukan, tidak diubah. Semua PR ke `dev` | | W3 | P1 | Integrasi SUSFS 6.1: `git apply --check` patch kandidat BBG spinlock pada source asli, terapkan sekali, review jalur locking (lanjutan handoff `docs/validation/ksun-susfs-6.1-work-handoff.md`). | **Selesai** 2026-10-15 — check PASS, diterapkan sekali, locking aman. Integrasi ke replay = keputusan owner | @@ -310,6 +313,12 @@ Dokumen ini adalah sumber utama aturan kerja dan urutan prioritas. Owner cukup m - **Berikutnya:** PR summary ke `dev`, merge setelah Validate hijau. Teks berlaku pada run baru; run #21 yang sudah selesai tidak diubah atau dijalankan ulang. - **Menunggu upstream:** kompatibilitas BakaSU/SUSFS tetap HOLD. Tidak ada kompilasi kernel, rilis atau flash dari sesi ini. +- **2026-10-18 (W63, hapus pengunci percobaan workflow)** + - **Selesai:** run `39075972745` menolak None LTO sebelum kompilasi akibat syarat FullLTO/BBG dari uji lama. Pengunci itu, Networking yang diabaikan, tabel tree/sublevel historis dan override pin kernel lama dihapus dari jalur build aktif. Semua pilihan yang ditawarkan form mengikuti input. None LTO juga sekarang mematikan Thin/Full LTO secara eksplisit pada varian lain. Apply Check memakai source pin dan seri patch produksi saat ini; Manual Update/ringkasan tidak lagi mengarahkan ke rekaman tree lama. + - **Validasi lokal:** patch asli pada kernel pin `8c24bbff2982175a7ca0e588e982ec56783fc8e7` lolos untuk empat kombinasi BBG/Networking tanpa mengubah patch; NoMount + SUSFS tetap bawaan kernel. 12 kombinasi config/audit serta staging sintetis pada sublevel baru yang tidak tercatat diuji offline. 59 tes KSUN/SUSFS, 44 Manual Update, 21 unduhan, 20 catatan rilis, root matrix dan validator sintaks lulus. Tes opsi diulang sekali setelah tambahan override BBG varian lain. Tidak mengklaim kompilasi Image atau uji perangkat untuk kombinasi baru. + - **Berikutnya:** satu PR perubahan umum ke `dev`, merge setelah Validate hijau. Owner kemudian dapat menjalankan build dengan pilihan LTO/BBG/Networking yang diinginkan; tidak perlu run percobaan untuk membuka opsi. + - **Status teknis:** pin/checksum, penerapan patch ketat, audit config aktual, kegagalan compile dan aturan rilis tetap berlaku. Guard upstream BakaSU bukan pembatas percobaan lokal; penangguhan SUSFS masih dilaporkan sebagaimana sumber upstream. + ## F. Perintah hapus branch lama (W7, dijalankan owner) ```sh diff --git a/docs/validation/ksun-susfs-6.1-work-handoff.md b/docs/validation/ksun-susfs-6.1-work-handoff.md index 524e742..e777f34 100644 --- a/docs/validation/ksun-susfs-6.1-work-handoff.md +++ b/docs/validation/ksun-susfs-6.1-work-handoff.md @@ -1,5 +1,7 @@ # KSUN + SUSFS 6.1 — Work handoff checkpoint (2026-10-15) +> **Catatan historis; aturan build diperbarui 2026-10-18 (W63/A19).** Pembatas kombinasi dari percobaan ini tidak berlaku untuk workflow aktif. LTO None/Thin/Full, BBG dan Networking mengikuti form; kernel mengikuti pin Manual Update saat ini. Build memeriksa patch dan konfigurasi aktual tanpa mensyaratkan tree, objek atau run lama. Lihat [kesepakatan kerja](../WORK-AGREEMENT.md). Catatan dan hash di bawah hanya merekam hasil uji saat itu. + **Status: OWNER STOPPED at 2026-10-15 14:41 WIB. V2 CORE + BBG/NOMOUNT SOURCE STAGED. NOT READY FOR COMPILATION, RELEASE OR FLASH.** Resume with **Lanjutkan**. Repository `cogan17/Cogan`, branch `dev`. Built-in **KernelSU Next + kernel-patched SUSFS** only. Production KSUN and both diff --git a/docs/validation/ksun-susfs-resume-gates.md b/docs/validation/ksun-susfs-resume-gates.md index aa69c72..b5df10f 100644 --- a/docs/validation/ksun-susfs-resume-gates.md +++ b/docs/validation/ksun-susfs-resume-gates.md @@ -1,5 +1,7 @@ # KSUN + SUSFS 6.1 — Resume gate (manual, fail closed) +> **Arsip percobaan, bukan prasyarat workflow aktif (2026-10-18, W63/A19).** Build dan Apply Check memakai pin kernel saat ini dan memeriksa patch pada source terpilih. Pilihan LTO/BBG/Networking tidak dikunci oleh hasil uji atau hash historis di bawah. Ketentuan aktif ada di [kesepakatan kerja](../WORK-AGREEMENT.md). + Scope: **KSUN + SUSFS only**. This document does not authorize a production workflow, release, flash, or removal of the existing KSUN+SUSFS guard. **V2 continuation (2026-10-15):** the new, explicitly selected source-only candidate has passed API review and isolated core-source integration. Use `ksun-susfs-6.1-work-handoff.md`, `ksun-susfs-6.1-api-review.md` and `ksun-susfs-6.1-v2-integration.json` for that route. The historical recovery gates below remain evidence for the older candidate; do not apply the historical preflight/config/object hashes to v2. Its compilation gates are still pending. diff --git a/scripts/check_ksun_susfs_resolved_config.py b/scripts/check_ksun_susfs_resolved_config.py index 92f85c0..75b7c38 100644 --- a/scripts/check_ksun_susfs_resolved_config.py +++ b/scripts/check_ksun_susfs_resolved_config.py @@ -5,20 +5,18 @@ """ import argparse import hashlib -import json import re from pathlib import Path import sys ROOT = Path(__file__).resolve().parents[1] -LOCK = ROOT / "configs/ksun-susfs-6.1-features.lock.json" # Kconfig symbol names may contain lowercase letters (e.g. CONFIG_FONT_8x16). SYMBOL = re.compile(r"CONFIG_[A-Za-z0-9_]+") -def audit(config_bytes, lock): +def parse_config(config_bytes): values = {} for raw in config_bytes.decode("utf-8").splitlines(): if raw.startswith("CONFIG_") and "=" in raw: @@ -37,6 +35,11 @@ def audit(config_bytes, lock): values[key] = "n" elif raw.startswith(("CONFIG_", "# CONFIG_")): raise ValueError("Malformed config entry: " + raw[:120]) + return values + + +def audit(config_bytes, lock): + values = parse_config(config_bytes) errors = [] spec = lock["configuration"] for key, expected in spec["required"].items(): @@ -68,8 +71,10 @@ def audit(config_bytes, lock): if not (lsm_raw.startswith('"') and lsm_raw.endswith('"')): errors.append("CONFIG_LSM: expected quoted Kconfig string") lsm = lsm_raw[1:-1].split(",") if lsm_raw.startswith('"') and lsm_raw.endswith('"') else [] - if "baseband_guard" not in lsm: + if values.get("CONFIG_BBG", "n") == "y" and "baseband_guard" not in lsm: errors.append("CONFIG_LSM: baseband_guard token absent") + if values.get("CONFIG_BBG", "n") != "y" and "baseband_guard" in lsm: + errors.append("CONFIG_LSM: baseband_guard enabled while BBG is disabled") if values.get("CONFIG_KSU_SUSFS", "n") == "y" and values.get("CONFIG_KSU", "n") != "y": errors.append("SUSFS enabled without built-in KSU") return errors @@ -78,6 +83,8 @@ def audit(config_bytes, lock): def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("config", type=Path, help="Existing final O=/.config after olddefconfig") + parser.add_argument("--fragment", type=Path, required=True, + help="Requested fragment generated for this build, independent of old evidence") args = parser.parse_args() try: if args.config.is_symlink(): @@ -86,23 +93,21 @@ def main(): if not config.is_file(): raise ValueError("Expected regular final config, not symlink") raw = config.read_bytes() - lock = json.loads(LOCK.read_text(encoding="utf-8")) - if lock["configuration"]["target"] != "gki_defconfig": - raise ValueError("Unexpected locked config target") - for guard in ("production_enabled", "run_full_build", "release_allowed", - "flash_allowed", "lkm_allowed"): - if lock["guards"].get(guard) is not False: - raise ValueError("Production guard must remain false: " + guard) - errors = audit(raw, lock) + if args.fragment.is_symlink(): + raise ValueError("Symlink fragment is not accepted") + requested = parse_config(args.fragment.read_bytes()) + if not requested: + raise ValueError("Requested fragment is empty") + errors = audit(raw, {"configuration": {"required": requested, "forbidden": []}}) print("resolved_config_sha256=" + hashlib.sha256(raw).hexdigest()) if errors: for error in errors: print("BLOCKED: " + error, file=sys.stderr) return 1 - print("PASS: locked fragment flags, required/forbidden options, and complete CONFIG_LSM") + print("PASS: resolved config matches this build's requested flags and complete CONFIG_LSM") print("NOTE: this does not prove compiler, link, runtime or production readiness") return 0 - except (OSError, ValueError, KeyError, UnicodeError, json.JSONDecodeError) as exc: + except (OSError, ValueError, KeyError, UnicodeError) as exc: print("BLOCKED: " + str(exc), file=sys.stderr) return 1 diff --git a/scripts/cogan_manual_update.py b/scripts/cogan_manual_update.py index 7bb85ff..142aa91 100644 --- a/scripts/cogan_manual_update.py +++ b/scripts/cogan_manual_update.py @@ -37,7 +37,6 @@ ROOT = Path(__file__).resolve().parents[1] LOCK = ROOT / "configs/upstream.lock" -KSUN_SUSFS_LOCK = ROOT / "configs/ksun-susfs-6.1-features.lock.json" SHA = re.compile(r"[0-9a-f]{40}") SHA256 = re.compile(r"[0-9a-f]{64}") STABLE_TAG = re.compile(r"v(\d+)\.(\d+)\.(\d+)") @@ -568,15 +567,6 @@ def select(components): return wanted -def ksun_susfs_verified(): - """Kernel commit per sublevel that the frozen KSUN + SUSFS candidate was verified on.""" - try: - sublevels = json.loads(KSUN_SUSFS_LOCK.read_text())["sublevels"] - return {int(sublevel): entry["kernel_commit"] for sublevel, entry in sublevels.items()} - except (OSError, ValueError, KeyError, TypeError): - return {} - - def current_sublevel(lock): """Sublevel whose lock entry equals the Auto pin (KERNEL_COMMIT_6_1).""" matches = [int(SUBLEVEL_KEY.fullmatch(key).group(1)) for key, value in lock.items() @@ -586,7 +576,7 @@ def current_sublevel(lock): def plan(lock, groups, lister=ls_remote, sublevel_of=kernel_sublevel, sha256_of=archive_sha256, assets_of=release_assets, asset_sha_of=asset_sha256, ci_of=ci_artifact_apk, - verified_of=ksun_susfs_verified, bakasu_of=bakasu_snapshot, + bakasu_of=bakasu_snapshot, bakasu_check=check_bakasu_susfs): """Compare pins with upstream. Returns (rows, updates) without side effects.""" rows, updates = [], {} @@ -603,12 +593,6 @@ def plan(lock, groups, lister=ls_remote, sublevel_of=kernel_sublevel, sha256_of= new_sublevel = sublevel_of(commit) except (OSError, ValueError) as exc: error = "Sublevel unreadable (" + type(exc).__name__ + ")" - verified = verified_of().get(new_sublevel) - if verified and verified != commit: - # GKID moved within a sublevel KSUN + SUSFS was verified (and device-tested) on: - # the other variants follow the new commit, KSUN + SUSFS keeps its own until - # it is verified again (scripts/ksun_susfs_sublevel.py record). - notes.append(f"KSUN + SUSFS tetap di 6.1.{new_sublevel} {verified[:12]} sampai diverifikasi ulang") if companions and tag and not error and group in groups: cache = {} diff --git a/scripts/ksun_susfs_config.py b/scripts/ksun_susfs_config.py new file mode 100644 index 0000000..f1b4722 --- /dev/null +++ b/scripts/ksun_susfs_config.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Generate the KSUN/SUSFS fragment from the current build's selected features.""" +import argparse +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +CORE = ROOT / "configs/ksun-susfs-6.1.config" +CUSTOM = ROOT / "configs/custom.config" +LTO_SYMBOLS = {"noneLTO": "CONFIG_LTO_NONE", "thinLTO": "CONFIG_LTO_CLANG_THIN", + "fullLTO": "CONFIG_LTO_CLANG_FULL"} + + +def fragment(base, lto, bbg, networking): + if lto not in LTO_SYMBOLS or bbg not in ("true", "false") or networking not in ("true", "false"): + raise ValueError("Invalid LTO, BBG or Networking input") + matches = re.findall(r'^CONFIG_LSM="([A-Za-z0-9_,]+)"$', base, re.MULTILINE) + if len(matches) != 1: + raise ValueError("Base .config must contain one quoted CONFIG_LSM list") + lsm = [name for name in matches[0].split(",") if name != "baseband_guard"] + if "selinux" not in lsm: + raise ValueError("Base CONFIG_LSM is missing selinux") + if bbg == "true": + lsm.append("baseband_guard") + lines = [CORE.read_text().rstrip(), f"CONFIG_BBG={'y' if bbg == 'true' else 'n'}", + 'CONFIG_LSM="' + ",".join(lsm) + '"'] + lines += [f"{symbol}={'y' if mode == lto else 'n'}" for mode, symbol in LTO_SYMBOLS.items()] + if networking == "true": + marker = "# Networking extras\n" + custom = CUSTOM.read_text() + if custom.count(marker) != 1: + raise ValueError("Networking extras section is missing or ambiguous") + lines.append(custom.split(marker, 1)[1].rstrip()) + return "\n".join(lines) + "\n" + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("base", type=Path, help="This build's gki_defconfig output") + parser.add_argument("output", type=Path) + parser.add_argument("--lto", choices=LTO_SYMBOLS, required=True) + parser.add_argument("--bbg", choices=("true", "false"), required=True) + parser.add_argument("--networking", choices=("true", "false"), required=True) + args = parser.parse_args(argv) + try: + text = fragment(args.base.read_text(), args.lto, args.bbg, args.networking) + args.output.write_text(text) + print(f"Requested config: LTO={args.lto}, BBG={args.bbg}, Networking={args.networking}") + return 0 + except (OSError, ValueError) as exc: + print("BLOCKED: " + str(exc), file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/ksun_susfs_replay.py b/scripts/ksun_susfs_replay.py index 803073c..930a7a8 100644 --- a/scripts/ksun_susfs_replay.py +++ b/scripts/ksun_susfs_replay.py @@ -30,7 +30,9 @@ def main(): help="Apply verified patches; default only verifies inputs") parser.add_argument("--kernel-commit", help="Expected HEAD instead of the manifest's kernel_commit " - "(another locked sublevel; the patches stay the same)") + "(the selected source pin; the patches stay the same)") + parser.add_argument("--exclude-patch", action="append", default=[], + help="Omit an optional patch by its exact manifest path; all checksums are still verified") args = parser.parse_args() try: tree = args.kernel.resolve(strict=True) @@ -65,6 +67,11 @@ def main(): if hashlib.sha256(raw).hexdigest() != expected: raise ValueError("patch checksum mismatch: " + str(path)) resolved.append((path, raw)) + unknown = set(args.exclude_patch) - {entry["path"] for entry in patches} + if unknown: + raise ValueError("unknown excluded patch: " + ", ".join(sorted(unknown))) + resolved = [item for entry, item in zip(patches, resolved) + if entry["path"] not in args.exclude_patch] print(f"Verified {len(resolved)} patch checksums and clean kernel checkout.") if not args.apply: print("VERIFY ONLY: no patch applied. Use --apply for strict sequential replay.") diff --git a/scripts/ksun_susfs_sublevel.py b/scripts/ksun_susfs_sublevel.py index f322983..934197b 100644 --- a/scripts/ksun_susfs_sublevel.py +++ b/scripts/ksun_susfs_sublevel.py @@ -1,30 +1,12 @@ #!/usr/bin/env python3 -"""Kernel sublevels of the KSUN + SUSFS build (offline, fail closed). - -Usage: - ksun_susfs_sublevel.py resolve SUBLEVEL - Print the sublevel KSUN + SUSFS builds for the form value SUBLEVEL: - lts/auto/empty = the newest verified one, a number = that one if verified. - ksun_susfs_sublevel.py commit SUBLEVEL - Print the kernel commit KSUN + SUSFS was recorded on for SUBLEVEL (same - values as resolve). The build fetches this commit, not the GKID pin of - configs/upstream.lock: GKID may move within a sublevel after KSUN + SUSFS - was verified there (Manual Update run 18: 6.1.177 a7ba124a -> 8c24bbff), - and KSUN + SUSFS keeps the recorded commit until it is recorded again. - ksun_susfs_sublevel.py record KERNEL_REPO COMMIT - Compute the locked identities of one GKID 6.1 commit. - -record: KERNEL_REPO is any Git repository that already holds COMMIT (for example a -`git fetch --depth=1` of the pinned sublevel); its work tree, index and refs are -not touched. The same checksum-verified series that stage_ksun_susfs_candidate.py -replays (core 001-003, frozen snapshot, features 001-005, then the GKID-equivalent -stack of gkid_parity.py when the commit is plain android14-6.1-lts) is applied -strictly, without fuzz, to a disposable index. On success it prints the JSON entry to add -under "sublevels" in configs/ksun-susfs-6.1-features.lock.json; the build then -accepts that sublevel for KSUN + SUSFS and checks the same trees again. - -A sublevel whose context differs fails here and stays unavailable: the patches -are never adapted automatically. +"""Resolve KSUN + SUSFS sublevels from the current Manual Update pins. + + resolve SUBLEVEL lts/auto/empty follows KERNEL_COMMIT_6_1; a number uses its pin. + commit SUBLEVEL Print the same kernel commit used by the other variants. + record REPO COMMIT Optional historical replay report; never needed to unlock builds. + +Source availability and the real SUSFS minimum (6.1.167) are checked here. +Patch applicability is checked against the selected source during staging. """ import hashlib import json @@ -40,7 +22,9 @@ ROOT = Path(__file__).resolve().parents[1] BASE = ROOT / "kernel-patches/susfs/6.1-v2" -FEATURE_LOCK = ROOT / "configs/ksun-susfs-6.1-features.lock.json" +FEATURE_LOCK = ROOT / "configs/ksun-susfs-6.1-features.lock.json" # historical record command only +UPSTREAM_LOCK = ROOT / "configs/upstream.lock" +MIN_SUBLEVEL = 167 MANIFESTS = (BASE / "replay/kernel-replay.json", BASE / "features/kernel-replay.json") SHA = re.compile(r"[0-9a-f]{40}") @@ -105,30 +89,52 @@ def apply(patches): "gkid_stack": stack, "final_kernel_tree": final} -def resolve(value, lock): - verified = sorted(lock["sublevels"], key=int) +def read_pins(path=None): + pins = {} + for line in (path or UPSTREAM_LOCK).read_text(encoding="utf-8").splitlines(): + if line.startswith("KERNEL_COMMIT_6_1") and "=" in line: + key, value = line.split("=", 1) + if re.fullmatch(r"KERNEL_COMMIT_6_1(?:_[0-9]+)?", key): + if not SHA.fullmatch(value): + raise ValueError(f"Invalid kernel pin: {key}") + pins[key] = value + return pins + + +def resolve(value, pins): + available = sorted(int(key.removeprefix("KERNEL_COMMIT_6_1_")) for key in pins + if re.fullmatch(r"KERNEL_COMMIT_6_1_[0-9]+", key)) wanted = value.strip().lower() if wanted in ("", "lts", "auto"): - return verified[-1] - if wanted in verified: - return wanted - raise ValueError(f"KSUN+SUSFS is not verified for sublevel {value!r}; " - f"use lts or one of: {' '.join(verified)}") + matches = [n for n in available if pins[f"KERNEL_COMMIT_6_1_{n}"] == pins.get("KERNEL_COMMIT_6_1")] + if not matches: + raise ValueError("Current kernel pin has no sublevel entry; run Cogan Manual Update") + wanted = str(max(matches)) + if not re.fullmatch(r"[1-9][0-9]*", wanted) or int(wanted) not in available: + raise ValueError(f"Kernel sublevel {value!r} is not available; use lts or one of: " + + " ".join(str(n) for n in available if n >= MIN_SUBLEVEL)) + if int(wanted) < MIN_SUBLEVEL: + raise ValueError(f"SUSFS variants need kernel 6.1.{MIN_SUBLEVEL} or newer (requested 6.1.{wanted})") + return wanted + + +def commit(value, pins): + result = pins[f"KERNEL_COMMIT_6_1_{resolve(value, pins)}"] + if not SHA.fullmatch(result): + raise ValueError("Kernel pin must be a full SHA") + return result def main(argv): try: - lock = json.loads(FEATURE_LOCK.read_text(encoding="utf-8")) if len(argv) == 3 and argv[1] == "resolve": - print(resolve(argv[2], lock)) + print(resolve(argv[2], read_pins())) return 0 if len(argv) == 3 and argv[1] == "commit": - commit = lock["sublevels"][resolve(argv[2], lock)]["kernel_commit"] - if not SHA.fullmatch(commit): - raise ValueError(f"locked kernel_commit is not a full SHA: {commit!r}") - print(commit) + print(commit(argv[2], read_pins())) return 0 if len(argv) == 4 and argv[1] == "record": + lock = json.loads(FEATURE_LOCK.read_text(encoding="utf-8")) sublevel, entry = identities(Path(argv[2]), argv[3], lock) print(json.dumps({sublevel: entry}, indent=2)) return 0 diff --git a/scripts/prepare_downloads.py b/scripts/prepare_downloads.py index ae185e2..f191a0a 100644 --- a/scripts/prepare_downloads.py +++ b/scripts/prepare_downloads.py @@ -14,8 +14,8 @@ release than the pin. Vanilla builds get only the kernel. A moved tag or a wrong checksum fails (after the kernel is prepared, so it can still be downloaded). The reminders also cover the GKID 6.1 kernel (a new sublevel or commit) and, for -SUSFS variants, SUSFS 6.1 (with its SUSFS_VERSION); the frozen KSUN+SUSFS kernel -gets none for those. +SUSFS variants, SUSFS 6.1 (with its SUSFS_VERSION). KSUN+SUSFS follows the same +kernel pin; its bundled SUSFS source is updated separately from generic SUSFS. BakaSU may use one official post-rename CI snapshot when the latest release is still ReSukiSU. Its ref equals the source commit, both APKs are mirrored, and @@ -192,12 +192,9 @@ def susfs_reminder(lock, lister, text_fetch): def source_reminders(lock, susfs, candidate, lister, text_fetch): - """Kernel and SUSFS reminders; KSUN+SUSFS gets none: its SUSFS is the frozen v2 source and - a new kernel sublevel needs its patched trees recorded in the feature lock first.""" - if candidate: - return [] + """Every variant follows the kernel pin; the KSUN fork bundles a separate SUSFS source.""" notes = [kernel_reminder(lock, lister, text_fetch)] - if susfs == "true": + if susfs == "true" and not candidate: notes.append(susfs_reminder(lock, lister, text_fetch)) return [note for note in notes if note] diff --git a/scripts/release_notes.py b/scripts/release_notes.py index 56a4e7d..eb66204 100644 --- a/scripts/release_notes.py +++ b/scripts/release_notes.py @@ -102,8 +102,7 @@ def variant_section(env, lock, features_lock, repo): lines.append(f"- Fitur / features: {recorded}") kernel = env.get("KERNEL_SOURCE_BRANCH", "") source = env.get("KERNEL_SOURCE_REPO", "") - lines.append(f"- Kernel: {source + ' ' if source else ''}`{short(kernel) or '?'}`" - + (" (kandidat beku / frozen candidate)" if candidate else "")) + lines.append(f"- Kernel: {source + ' ' if source else ''}`{short(kernel) or '?'}`") if ksu in pd.MANAGERS and env.get("ROOT_MANAGER_REPO"): label, pin_key = pd.MANAGERS[ksu] ref, commit = env.get("ROOT_MANAGER_REF", ""), env.get("ROOT_MANAGER_SHA", "") diff --git a/scripts/stage_ksun_susfs_candidate.py b/scripts/stage_ksun_susfs_candidate.py index 52e5f4c..7968234 100644 --- a/scripts/stage_ksun_susfs_candidate.py +++ b/scripts/stage_ksun_susfs_candidate.py @@ -1,47 +1,50 @@ #!/usr/bin/env python3 -"""Stage the validated KSUN + SUSFS 6.1 candidate for a build (fail closed). - -Reproduces exactly what was verified offline and built locally: - kernel @ locked sublevel commit -> core replay (driver glue, SUSFS, companions) - KSUN fork @ manifest commit -> offline version metadata - frozen core snapshots (deterministic commit-tree, metadata from the lock) - feature replay (BBG, NoMount, BBG allow-cache fix, BBRv3 + bbr3 name) -> expected index tree - GKID-equivalent stack (gkid_parity.py) on plain android14-6.1-lts sublevels -> final tree - -Usage: python3 scripts/stage_ksun_susfs_candidate.py KERNEL_DIR KSUN_DIR [SUBLEVEL] -SUBLEVEL is one of "sublevels" in the feature lock (default: 177, the -device-tested kernel of the replay manifest); every tree of that sublevel is -checked. KERNEL_DIR must be a clean checkout of that sublevel's commit; KSUN_DIR -must not exist and must be the sibling that drivers/kernelsu points to. +"""Stage built-in KSUN + SUSFS on the current selected Manual Update kernel pin. + +Usage: stage_ksun_susfs_candidate.py KERNEL_DIR KSUN_DIR [SUBLEVEL] + +No historical tree/config/build evidence is needed. Each selected patch is +checksum-verified and preflighted on the actual source before it is applied. +BBG and NETWORKING follow the build environment (true/false, default true). +NoMount stays bundled. Source pins and the SUSFS-compatible KSUN fork are retained. """ import json import os +import re import subprocess import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import gkid_parity # noqa: E402 +import ksun_susfs_sublevel as sublevels # noqa: E402 ROOT = Path(__file__).resolve().parents[1] BASE = ROOT / "kernel-patches/susfs/6.1-v2" -FEATURE_LOCK = ROOT / "configs/ksun-susfs-6.1-features.lock.json" SOURCE_LOCK = ROOT / "configs/ksun-susfs-6.1-v2.lock.json" REPLAY = ROOT / "scripts/ksun_susfs_replay.py" -def git(repo, *args, env=None): +def git(repo, *args, env=None, data=None): result = subprocess.run(["git", "-C", str(repo), *args], capture_output=True, - text=True, env=env, timeout=600) + input=data, text=True, env=env, timeout=600) if result.returncode: raise RuntimeError(f"git {' '.join(args)} failed: {result.stderr.strip()[:500]}") return result.stdout.strip() -def replay(tree, manifest, head=None): - command = [sys.executable, str(REPLAY), str(tree), str(manifest), "--apply"] - if head: - command += ["--kernel-commit", head] +def enabled(name): + value = os.environ.get(name, "true") + if value not in ("true", "false"): + raise ValueError(f"{name} must be true or false") + return value == "true" + + +def replay(tree, manifest, head, excluded=()): + command = [sys.executable, str(REPLAY), str(tree), str(manifest), "--apply", + "--kernel-commit", head] + for name in excluded: + command += ["--exclude-patch", name] result = subprocess.run(command, capture_output=True, text=True, timeout=600) print(result.stdout, end="") if result.returncode: @@ -54,22 +57,22 @@ def expect(label, actual, expected): print(f"[+] {label}: {actual}") -def freeze(repo, core_tree, parent, snapshot, meta): - """Stage everything, then commit-tree with the recorded fixed identity.""" +def snapshot(repo): + """Make a clean replay boundary from this build's source, without old tree IDs.""" git(repo, "add", "-A") - expect(f"{repo.name} core tree", git(repo, "write-tree"), core_tree) - env = {**os.environ, - "GIT_AUTHOR_NAME": meta["author_name"], "GIT_AUTHOR_EMAIL": meta["author_email"], - "GIT_AUTHOR_DATE": meta["author_date"], "GIT_COMMITTER_NAME": meta["committer_name"], - "GIT_COMMITTER_EMAIL": meta["committer_email"], "GIT_COMMITTER_DATE": meta["committer_date"]} - result = subprocess.run(["git", "-C", str(repo), "commit-tree", core_tree, "-p", parent], - input=meta["message"], capture_output=True, text=True, env=env, timeout=60) - if result.returncode: - raise RuntimeError("commit-tree failed: " + result.stderr.strip()) - expect(f"{repo.name} frozen snapshot", result.stdout.strip(), snapshot) - git(repo, "reset", "-q", "--soft", snapshot) + tree, parent = git(repo, "write-tree"), git(repo, "rev-parse", "HEAD") + date = git(repo, "show", "-s", "--format=%cI", "HEAD") + env = {**os.environ, "GIT_AUTHOR_NAME": "Cogan source staging", + "GIT_AUTHOR_EMAIL": "source-staging@example.invalid", "GIT_AUTHOR_DATE": date, + "GIT_COMMITTER_NAME": "Cogan source staging", + "GIT_COMMITTER_EMAIL": "source-staging@example.invalid", "GIT_COMMITTER_DATE": date} + commit = git(repo, "commit-tree", tree, "-p", parent, env=env, + data="Cogan KSUN/SUSFS source stage\n") + git(repo, "reset", "-q", "--soft", commit) if git(repo, "status", "--porcelain", "--untracked-files=all"): - raise RuntimeError(f"{repo.name} not clean after freezing") + raise RuntimeError(f"{repo.name} not clean after source snapshot") + print(f"[+] {repo.name} core tree: {tree}") + return commit def main(argv): @@ -78,49 +81,50 @@ def main(argv): return 2 kernel, ksun = Path(argv[1]).resolve(), Path(argv[2]).absolute() try: - lock = json.loads(FEATURE_LOCK.read_text()) - sources = json.loads(SOURCE_LOCK.read_text()) - ksun_manifest = json.loads((BASE / "replay/ksun-replay.json").read_text()) - meta = lock["frozen_core"] - sublevel = argv[3] if len(argv) == 4 else "177" - if sublevel not in lock["sublevels"]: - raise RuntimeError(f"KSUN+SUSFS is not verified for 6.1.{sublevel}; " - f"available: {', '.join(sorted(lock['sublevels'], key=int))}") - target = lock["sublevels"][sublevel] - print(f"[+] KSUN+SUSFS sublevel 6.1.{sublevel}") - - expect("kernel checkout", git(kernel, "rev-parse", "HEAD"), target["kernel_commit"]) - if ksun.exists(): + bbg, networking = enabled("BBG"), enabled("NETWORKING") + pins = sublevels.read_pins() + sublevel = sublevels.resolve(argv[3] if len(argv) == 4 else "lts", pins) + kernel_commit = sublevels.commit(sublevel, pins) + expect("kernel checkout", git(kernel, "rev-parse", "HEAD"), kernel_commit) + makefile = git(kernel, "show", "HEAD:Makefile") + fields = dict(re.findall(r"^(VERSION|PATCHLEVEL|SUBLEVEL) = (\d+)$", makefile, re.MULTILINE)) + if fields != {"VERSION": "6", "PATCHLEVEL": "1", "SUBLEVEL": sublevel}: + raise ValueError("Kernel Makefile does not match the selected 6.1 sublevel") + if git(kernel, "status", "--porcelain", "--untracked-files=all"): + raise ValueError("kernel checkout is dirty; refusing staging") + if ksun.exists() or ksun.is_symlink(): raise RuntimeError(f"{ksun} already exists") if ksun.parent != kernel.parent: raise RuntimeError("KSUN must be a sibling of the kernel checkout (drivers/kernelsu -> ../../ksun/kernel)") + sources = json.loads(SOURCE_LOCK.read_text()) + ksun_pin = sources["sources"]["ksun"] + manifest = json.loads((BASE / "replay/ksun-replay.json").read_text()) + expect("KSUN manifest pin", manifest["kernel_commit"], ksun_pin["commit"]) ksun.mkdir() git(ksun, "init", "-q") - git(ksun, "fetch", "-q", "--depth=1", sources["sources"]["ksun"]["repository"], - ksun_manifest["kernel_commit"]) + git(ksun, "fetch", "-q", "--depth=1", ksun_pin["repository"], ksun_pin["commit"]) git(ksun, "checkout", "-q", "--detach", "FETCH_HEAD") - expect("KSUN checkout", git(ksun, "rev-parse", "HEAD"), ksun_manifest["kernel_commit"]) - - replay(kernel, BASE / "replay/kernel-replay.json", target["kernel_commit"]) - replay(ksun, BASE / "replay/ksun-replay.json") - freeze(kernel, target["core_kernel_tree"], target["kernel_commit"], target["kernel_snapshot"], meta) - freeze(ksun, lock["core_ksun_tree"], ksun_manifest["kernel_commit"], meta["ksun_snapshot"], meta) - - replay(kernel, BASE / "features/kernel-replay.json", target["kernel_snapshot"]) - git(kernel, "add", "-A") - expect("feature tree", git(kernel, "write-tree"), target["integrated_kernel_tree"]) - - stack = gkid_parity.has_stack((kernel / gkid_parity.VERSION_C).read_text(errors="replace")) - if stack != target["gkid_stack"]: - raise RuntimeError(f"GKID patch stack present={stack}, lock says {target['gkid_stack']}") - if not stack: + expect("KSUN checkout", git(ksun, "rev-parse", "HEAD"), ksun_pin["commit"]) + + replay(kernel, BASE / "replay/kernel-replay.json", kernel_commit) + replay(ksun, BASE / "replay/ksun-replay.json", ksun_pin["commit"]) + head = snapshot(kernel) + snapshot(ksun) + excluded = [] + if not bbg: + excluded += ["001-bbg.patch", "003-bbg-allow-cache-spinlock.patch"] + if not networking: + excluded += ["004-bbrv3.patch", "005-bbr3-alias.patch"] + replay(kernel, BASE / "features/kernel-replay.json", head, excluded) + + if not gkid_parity.has_stack((kernel / gkid_parity.VERSION_C).read_text(errors="replace")): removed = gkid_parity.apply(kernel) - print(f"[+] GKID-equivalent stack applied ({len(gkid_parity.patches())} patches, " - f"removed {', '.join(removed)})") - git(kernel, "add", "-A") - expect("final tree", git(kernel, "write-tree"), target["final_kernel_tree"]) - print("CANDIDATE STAGED: KSUN + SUSFS 6.1 with BBG (001, 003), NoMount (002) and BBRv3 (004, bbr3 name 005)") + print(f"[+] GKID-equivalent stack applied; removed {', '.join(removed)}") + git(kernel, "add", "-A") + print(f"[+] final source tree: {git(kernel, 'write-tree')}") + print(f"SOURCE STAGED: KSUN + built-in SUSFS 6.1.{sublevel}; NoMount; " + f"BBG={str(bbg).lower()}; Networking={str(networking).lower()}") return 0 except (OSError, KeyError, ValueError, RuntimeError, subprocess.SubprocessError) as exc: print("BLOCKED: " + str(exc)[:2000], file=sys.stderr) diff --git a/tests/test_cogan_manual_update.py b/tests/test_cogan_manual_update.py index a040bab..8dfb4e6 100644 --- a/tests/test_cogan_manual_update.py +++ b/tests/test_cogan_manual_update.py @@ -567,12 +567,8 @@ def test_new_commit_on_same_sublevel_replaces_that_entry(self): lister = fake_lister({"KERNEL_COMMIT_6_1": NEW}) rows, updates = plan(self.lock, {"kernel-6.1"}, lister, sublevel_of=lambda commit: 177) self.assertEqual(updates, {"KERNEL_COMMIT_6_1": NEW, "KERNEL_COMMIT_6_1_177": NEW}) - # KSUN + SUSFS was verified on another 6.1.177 commit: the report says it stays there. row = next(row for row in rows if row[0] == "Kernel GKID 6.1") - self.assertIn("KSUN + SUSFS tetap di 6.1.177", row[-1]) - _, updates = plan(self.lock, {"kernel-6.1"}, lister, sublevel_of=lambda commit: 177, - verified_of=lambda: {177: NEW}) - self.assertEqual(updates, {"KERNEL_COMMIT_6_1": NEW, "KERNEL_COMMIT_6_1_177": NEW}) + self.assertNotIn("sampai diverifikasi ulang", row[-1]) def test_unreadable_sublevel_blocks_the_kernel_update(self): def unreadable(commit): @@ -690,7 +686,9 @@ def test_sublevel_is_free_text_checked_against_the_lock(self): def run(version, value): env = {**os.environ, "ROOT_MANAGER_INPUT": "KernelSU", "KERNEL_VERSION_INPUT": version, - "KERNEL_SUBLEVEL_INPUT": value, "BUILD_TIME_INPUT": "N"} + "KERNEL_SUBLEVEL_INPUT": value, "BUILD_TIME_INPUT": "N", + "LTO_INPUT": "noneLTO + NoMount + Clean LTO", "BBG_INPUT": "false", + "NETWORKING_INPUT": "false"} return subprocess.run(["bash", "-c", preflight], cwd=ROOT, env=env, capture_output=True, text=True) for value in ("lts", "LTS", "Auto", "", "160", "177"): diff --git a/tests/test_ksun_susfs_options.py b/tests/test_ksun_susfs_options.py new file mode 100644 index 0000000..5a0e616 --- /dev/null +++ b/tests/test_ksun_susfs_options.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Current feature selections must reach the source tree and resolved config.""" +import contextlib +import hashlib +import io +import itertools +import json +import os +import subprocess +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +from scripts import check_ksun_susfs_resolved_config as audit +from scripts import ksun_susfs_config as config +from scripts import stage_ksun_susfs_candidate as stage + +ROOT = Path(__file__).resolve().parents[1] +BASE_CONFIG = 'CONFIG_LSM="landlock,lockdown,yama,integrity,selinux,bpf"\n' + + +class ConfigOptionsTests(unittest.TestCase): + def test_build_fragment_covers_all_feature_combinations(self): + for lto, bbg, networking in itertools.product(config.LTO_SYMBOLS, ("false", "true"), ("false", "true")): + with self.subTest(lto=lto, bbg=bbg, networking=networking): + fragment = config.fragment(BASE_CONFIG, lto, bbg, networking).encode() + values = audit.parse_config(fragment) + self.assertEqual(values["CONFIG_KSU_SUSFS"], "y") + self.assertEqual(values["CONFIG_NOMOUNT"], "y") + self.assertEqual(values["CONFIG_KSU_SAMSUNG_KDP"], "n") + self.assertEqual(values["CONFIG_BBG"], "y" if bbg == "true" else "n") + self.assertEqual("baseband_guard" in values["CONFIG_LSM"], bbg == "true") + self.assertIn("selinux", values["CONFIG_LSM"]) + self.assertEqual(values.get("CONFIG_DEFAULT_BBR"), "y" if networking == "true" else None) + expected = config.LTO_SYMBOLS[lto] + self.assertEqual([key for key in config.LTO_SYMBOLS.values() if values[key] == "y"], [expected]) + spec = {"configuration": {"required": values, "forbidden": []}} + self.assertEqual(audit.audit(fragment, spec), []) + wrong = fragment.replace(f"{expected}=y".encode(), f"{expected}=n".encode()) + self.assertTrue(any(expected in error for error in audit.audit(wrong, spec))) + wrong = fragment.replace(b"CONFIG_NOMOUNT=y", b"CONFIG_NOMOUNT=n") + self.assertTrue(any("CONFIG_NOMOUNT" in error for error in audit.audit(wrong, spec))) + + def test_standard_variants_explicitly_set_every_lto_choice(self): + for mode, expected in config.LTO_SYMBOLS.items(): + with self.subTest(mode=mode), tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + dest = root / "arch/arm64/configs/gki_defconfig" + dest.parent.mkdir(parents=True) + dest.write_text("") + env = {**os.environ, "WORKDIR": str(ROOT), "KSU": "KSUN", "KSU_SUSFS": "false", + "KSU_COMPAT": "false", "C_LTO": "true", "LTO": mode, "NETWORKING": "false", "BBG": "false", + "DROIDSPACES": "false", "NH": "false", "No_DS": "true"} + result = subprocess.run(["bash", str(ROOT / "configs/gki_defconfig.sh")], cwd=root, + env=env, capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stderr) + text = dest.read_text() + self.assertIn(f"{expected}=y\n", text) + self.assertGreater(text.rfind("CONFIG_BBG=n"), text.rfind("CONFIG_BBG=y")) + for key in config.LTO_SYMBOLS.values(): + if key != expected: + self.assertIn(f"{key}=n\n", text) + + def test_invalid_requests_or_missing_selinux_are_rejected(self): + for args in (("bad", "true", "true"), ("noneLTO", "bad", "true"), ("noneLTO", "true", "bad")): + with self.assertRaises(ValueError): + config.fragment(BASE_CONFIG, *args) + with self.assertRaisesRegex(ValueError, "selinux"): + config.fragment('CONFIG_LSM="landlock"\n', "noneLTO", "false", "false") + + +class StageOptionsTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.kernel, self.ksun = self.root / "kernel", self.root / "ksun" + self.upstream = self.root / "ksun-upstream" + self.kernel.mkdir() + (self.kernel / "kernel/module").mkdir(parents=True) + (self.kernel / "kernel/module/version.c").write_text('pr_warn("disagrees about version of symbol, but ignore...");\n') + (self.kernel / "Makefile").write_text("VERSION = 6\nPATCHLEVEL = 1\nSUBLEVEL = 178\n") + self.upstream.mkdir() + (self.upstream / "version.txt").write_text("pinned\n") + self.kernel_pin, self.ksun_pin = self.init(self.kernel), self.init(self.upstream) + self.base = self.root / "patches" + (self.base / "replay").mkdir(parents=True) + (self.base / "features").mkdir() + self.manifest(self.base / "replay/kernel-replay.json", "0" * 40, + [("core.patch", "susfs.txt", None, "built-in\n")]) + self.manifest(self.base / "replay/ksun-replay.json", self.ksun_pin, + [("version.patch", "version.txt", "pinned\n", "offline\n")]) + self.manifest(self.base / "features/kernel-replay.json", "0" * 40, [ + ("001-bbg.patch", "bbg.txt", None, "bbg\n"), + ("002-nomount.patch", "nomount.txt", None, "nomount\n"), + ("003-bbg-allow-cache-spinlock.patch", "bbg.txt", "bbg\n", "bbg locking fix\n"), + ("004-bbrv3.patch", "bbr.txt", None, "bbrv3\n"), + ("005-bbr3-alias.patch", "bbr.txt", "bbrv3\n", "bbrv3 bbr3\n")]) + source_lock = self.root / "source.json" + source_lock.write_text(json.dumps({"sources": {"ksun": { + "repository": str(self.upstream), "commit": self.ksun_pin}}})) + pins = self.root / "upstream.lock" + pins.write_text(f"KERNEL_COMMIT_6_1={self.kernel_pin}\nKERNEL_COMMIT_6_1_178={self.kernel_pin}\n") + for target, name, value in ((stage, "BASE", self.base), (stage, "SOURCE_LOCK", source_lock), + (stage.sublevels, "UPSTREAM_LOCK", pins)): + patcher = mock.patch.object(target, name, value) + patcher.start() + self.addCleanup(patcher.stop) + + def init(self, repo): + stage.git(repo, "init", "-q") + stage.git(repo, "add", "-A") + stage.git(repo, "-c", "user.name=T", "-c", "user.email=t@example.invalid", "commit", "-qm", "base") + return stage.git(repo, "rev-parse", "HEAD") + + def manifest(self, path, pin, entries): + patches = [] + for name, target, before, after in entries: + header = f"diff --git a/{target} b/{target}\n" + if before is None: + raw = header + f"new file mode 100644\n--- /dev/null\n+++ b/{target}\n@@ -0,0 +1 @@\n+{after}" + else: + raw = header + f"--- a/{target}\n+++ b/{target}\n@@ -1 +1 @@\n-{before}+{after}" + (path.parent / name).write_text(raw) + patches.append({"path": name, "sha256": hashlib.sha256(raw.encode()).hexdigest()}) + path.write_text(json.dumps({"kernel_commit": pin, "patches": patches})) + + def test_stage_follows_each_checkbox_without_recorded_sublevel_or_tree_ids(self): + for bbg, networking in itertools.product(("false", "true"), repeat=2): + with self.subTest(bbg=bbg, networking=networking): + stage.git(self.kernel, "reset", "--hard", self.kernel_pin) + if self.ksun.exists(): + import shutil + shutil.rmtree(self.ksun) + with mock.patch.dict(os.environ, BBG=bbg, NETWORKING=networking), contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(stage.main(["stage", str(self.kernel), str(self.ksun), "lts"]), 0) + self.assertEqual((self.kernel / "susfs.txt").read_text(), "built-in\n") + self.assertEqual((self.kernel / "nomount.txt").read_text(), "nomount\n") + self.assertEqual((self.kernel / "bbg.txt").exists(), bbg == "true") + self.assertEqual((self.kernel / "bbr.txt").exists(), networking == "true") + if bbg == "true": + self.assertEqual((self.kernel / "bbg.txt").read_text(), "bbg locking fix\n") + if networking == "true": + self.assertEqual((self.kernel / "bbr.txt").read_text(), "bbrv3 bbr3\n") + self.assertEqual((self.ksun / "version.txt").read_text(), "offline\n") + + def test_wrong_source_pin_still_fails_before_fetch(self): + (self.kernel / "extra").write_text("new\n") + stage.git(self.kernel, "add", "extra") + stage.git(self.kernel, "-c", "user.name=T", "-c", "user.email=t@example.invalid", "commit", "-qm", "moved") + with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): + self.assertEqual(stage.main(["stage", str(self.kernel), str(self.ksun), "lts"]), 1) + self.assertFalse(self.ksun.exists()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_ksun_susfs_replay.py b/tests/test_ksun_susfs_replay.py index cd47b90..3d6ee6d 100644 --- a/tests/test_ksun_susfs_replay.py +++ b/tests/test_ksun_susfs_replay.py @@ -62,6 +62,21 @@ def test_checksum_mismatch_blocks(self): self.assertNotEqual(result.returncode, 0) self.assertEqual((self.kernel / "sample.txt").read_text(), "before\n") + def test_optional_patch_can_be_omitted(self): + result = self.run_replay("--apply", "--exclude-patch", self.patch.name) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual((self.kernel / "sample.txt").read_text(), "before\n") + + def test_excluded_patch_still_requires_checksum_integrity(self): + self.write_manifest("0" * 64) + result = self.run_replay("--apply", "--exclude-patch", self.patch.name) + self.assertNotEqual(result.returncode, 0) + self.assertEqual((self.kernel / "sample.txt").read_text(), "before\n") + + def test_unknown_excluded_patch_is_rejected(self): + result = self.run_replay("--apply", "--exclude-patch", "typo.patch") + self.assertNotEqual(result.returncode, 0) + def test_dirty_checkout_blocks(self): (self.kernel / "sample.txt").write_text("dirty\n") result = self.run_replay("--apply") diff --git a/tests/test_ksun_susfs_resolved_config.py b/tests/test_ksun_susfs_resolved_config.py index 0291db8..22e9ab2 100644 --- a/tests/test_ksun_susfs_resolved_config.py +++ b/tests/test_ksun_susfs_resolved_config.py @@ -152,26 +152,16 @@ def test_repository_lock_and_fragment_integrity(self): self.assertIn("CONFIG_KSU_SUSFS_SUS_PATH=y", fragment.decode()) self.assertIn("# CONFIG_NTSYNC is not set", fragment.decode()) - def test_cli_rejects_enabled_production_guard(self): - import json + def test_cli_checks_this_runs_fragment_without_historical_guards(self): import tempfile from unittest.mock import patch with tempfile.TemporaryDirectory() as tmp: - config_path = Path(tmp) / ".config" + config_path, fragment_path = Path(tmp) / ".config", Path(tmp) / "requested.config" config_path.write_text(GOOD, encoding="utf-8") - lock_path = Path(tmp) / "lock.json" - lock_path.write_text(json.dumps({ - "configuration": {"target": "gki_defconfig", **LOCK["configuration"]}, - "guards": { - "production_enabled": True, - "run_full_build": False, - "release_allowed": False, - "flash_allowed": False, - "lkm_allowed": False, - }, - }), encoding="utf-8") - with patch.object(gate, "LOCK", lock_path), patch.object( - gate.sys, "argv", ["gate", str(config_path)]): + fragment_path.write_text(GOOD, encoding="utf-8") + with patch.object(gate.sys, "argv", ["gate", str(config_path), "--fragment", str(fragment_path)]): + self.assertEqual(0, gate.main()) + config_path.write_text(GOOD.replace("CONFIG_NOMOUNT=y", "CONFIG_NOMOUNT=n")) self.assertEqual(1, gate.main()) def test_bbg_recovery_partition_protection_forbidden(self): diff --git a/tests/test_ksun_susfs_sublevel.py b/tests/test_ksun_susfs_sublevel.py index 0b28857..07b7b96 100644 --- a/tests/test_ksun_susfs_sublevel.py +++ b/tests/test_ksun_susfs_sublevel.py @@ -33,14 +33,7 @@ def test_device_tested_177_keeps_its_exact_identities(self): def test_every_sublevel_uses_the_pinned_kernel_commit(self): self.assertEqual(sorted(LOCK["sublevels"], key=int), [str(n) for n in range(167, 178)]) for sublevel, entry in LOCK["sublevels"].items(): - # GKID may move within its newest sublevel after KSUN + SUSFS was verified there - # (Manual Update run 18: 6.1.177 a7ba124a -> 8c24bbff). The other variants follow - # the new commit; KSUN + SUSFS keeps the device-tested one until it is recorded again. - if entry["gkid_stack"] and entry["kernel_commit"] != UPSTREAM[f"KERNEL_COMMIT_6_1_{sublevel}"]: - manifest = json.loads((ROOT / "kernel-patches/susfs/6.1-v2/replay/kernel-replay.json").read_text()) - self.assertEqual(entry["kernel_commit"], manifest["kernel_commit"], sublevel) - else: - self.assertEqual(entry["kernel_commit"], UPSTREAM[f"KERNEL_COMMIT_6_1_{sublevel}"], sublevel) + # Historical identities remain an archive and do not constrain current pins. self.assertEqual(set(entry), {"kernel_commit", "core_kernel_tree", "kernel_snapshot", "integrated_kernel_tree", "gkid_stack", "final_kernel_tree"}) for key, value in entry.items(): @@ -53,15 +46,25 @@ def test_every_sublevel_uses_the_pinned_kernel_commit(self): trees = [entry["integrated_kernel_tree"] for entry in LOCK["sublevels"].values()] self.assertEqual(len(set(trees)), len(trees)) - def test_resolve_maps_lts_to_the_newest_verified_sublevel(self): - self.assertEqual(ks.resolve("lts", LOCK), "177") + def test_resolve_uses_current_pins_without_a_historical_table(self): for value in ("LTS", "Auto", "", " lts "): - self.assertEqual(ks.resolve(value, LOCK), "177", value) + self.assertEqual(ks.resolve(value, UPSTREAM), "177", value) for value in ("167", "170", "177"): - self.assertEqual(ks.resolve(value, LOCK), value) - for value in ("166", "160", "178", "6.1.177", "0177", "abc"): - with self.assertRaisesRegex(ValueError, "not verified"): - ks.resolve(value, LOCK) + self.assertEqual(ks.resolve(value, UPSTREAM), value) + for value in ("178", "6.1.177", "0177", "abc"): + with self.assertRaisesRegex(ValueError, "not available"): + ks.resolve(value, UPSTREAM) + for value in ("160", "166"): + with self.assertRaisesRegex(ValueError, "need kernel 6.1.167"): + ks.resolve(value, UPSTREAM) + # Manual Update can introduce a new sublevel or move the pin within one. + newer = {**UPSTREAM, "KERNEL_COMMIT_6_1": "a" * 40, "KERNEL_COMMIT_6_1_178": "a" * 40} + self.assertEqual(ks.resolve("lts", newer), "178") + self.assertEqual(ks.commit("lts", newer), "a" * 40) + moved = {**UPSTREAM, "KERNEL_COMMIT_6_1": "b" * 40, "KERNEL_COMMIT_6_1_177": "b" * 40} + self.assertEqual(ks.commit("177", moved), "b" * 40) + with mock.patch.object(ks, "FEATURE_LOCK", Path("/nonexistent-historical-lock")): + self.assertEqual(ks.main(["sublevel", "resolve", "lts"]), 0) def test_cli(self): def run(*args): @@ -69,27 +72,20 @@ def run(*args): self.assertEqual(run("resolve", "lts").stdout.strip(), "177") rejected = run("resolve", "165") self.assertEqual(rejected.returncode, 1) - self.assertIn("use lts or one of: 167", rejected.stderr) + self.assertIn("SUSFS variants need kernel 6.1.167", rejected.stderr) self.assertEqual(run().returncode, 2) self.assertEqual(run("record", "only-one-argument").returncode, 2) - # commit prints the recorded kernel commit, never the (possibly newer) GKID pin. + # commit follows the current Manual Update pin, including new source commits. for value, sublevel in (("lts", "177"), ("170", "170"), ("167", "167")): self.assertEqual(run("commit", value).stdout.strip(), - LOCK["sublevels"][sublevel]["kernel_commit"], value) + UPSTREAM[f"KERNEL_COMMIT_6_1_{sublevel}"], value) self.assertEqual(run("commit", "165").returncode, 1) self.assertEqual(run("commit").returncode, 2) - def test_build_fetches_the_recorded_kernel_commit_for_ksun_susfs(self): - # Build #21 (run 39044879371): KSUN + SUSFS fetched the GKID pin 8c24bbff of - # 6.1.177 and staging stopped at "kernel checkout: got 8c24bbff..., expected a7ba124a...". + def test_build_follows_manual_update_without_a_historical_commit_override(self): build = (ROOT / "build.sh").read_text() - select = build.index('scripts/ksun_susfs_sublevel.py" commit "$KSUN_SUSFS_SUBLEVEL"') - override = build.index('KERNEL_BRANCH="$KSUN_SUSFS_KERNEL_COMMIT"') - env = build.index('echo "KERNEL_SOURCE_BRANCH=$KERNEL_BRANCH"') - fetch = build.index('fetch -q --depth=1 origin "$KERNEL_BRANCH"') - self.assertLess(select, override) - self.assertLess(override, env) - self.assertLess(env, fetch) + self.assertNotIn('KERNEL_BRANCH="$KSUN_SUSFS_KERNEL_COMMIT"', build) + self.assertIn('KERNEL_BRANCH="$SUBLEVEL_PIN"', build) def test_record_refuses_a_kernel_that_is_not_6_1(self): with tempfile.TemporaryDirectory() as directory: @@ -112,7 +108,8 @@ def test_build_gives_plain_lts_sublevels_the_gkid_stack(self): build) self.assertIn("GKID parity incomplete", build) stage = (ROOT / "scripts/stage_ksun_susfs_candidate.py").read_text() - self.assertIn('expect("final tree", git(kernel, "write-tree"), target["final_kernel_tree"])', stage) + self.assertIn('gkid_parity.apply(kernel)', stage) + self.assertNotIn('target["final_kernel_tree"]', stage) def test_build_rejects_sources_that_are_not_android_common_kernels(self): build = (ROOT / "build.sh").read_text() diff --git a/tests/test_prepare_downloads.py b/tests/test_prepare_downloads.py index f84f76a..8cc5f79 100644 --- a/tests/test_prepare_downloads.py +++ b/tests/test_prepare_downloads.py @@ -257,13 +257,13 @@ def test_new_susfs_commit_with_the_same_version_names_the_commit(self): self.assertIn(f"SUSFS 6.1 punya commit baru / has a new commit `{NEW[:12]}` " f"(terpasang / pinned: `{pinned[:12]}`, v2.3.0)", summary) - def test_frozen_test_kernel_gets_no_kernel_or_susfs_reminder(self): + def test_ksun_susfs_gets_current_kernel_but_not_generic_susfs_reminder(self): lock = pd.mu.read_lock(REAL_LOCK) _, _, _, summary, _ = self.run_main( "KSUN", susfs="true", variant="KernelSU-Next+SuSFS", tags={"maxsteeel/nomount": lock["NOMOUNT_COMMIT"]}, heads={KERNEL_URL: NEW, SUSFS_URL: NEW}, texts={NEW: makefile(180)}) - self.assertNotIn("Kernel GKID", summary) + self.assertIn("Kernel GKID 6.1.180", summary) self.assertNotIn("SUSFS 6.1 punya", summary) def test_unreachable_upstream_never_fails_the_build(self): diff --git a/tests/test_release_notes.py b/tests/test_release_notes.py index faa4761..fb87ca2 100644 --- a/tests/test_release_notes.py +++ b/tests/test_release_notes.py @@ -64,7 +64,7 @@ def test_candidate_uses_its_own_bbg_nomount_and_susfs(self): text = section(CANDIDATE) bbg, nomount = rn.feature_commit(FEATURES_LOCK, "bbg"), rn.feature_commit(FEATURES_LOCK, "nomount") self.assertTrue(bbg and nomount) - self.assertIn("(kandidat beku / frozen candidate)", text) + self.assertNotIn("(kandidat beku / frozen candidate)", text) self.assertIn("Root: pershoot/KernelSU-Next candidate-v2 (`bbbbbbbb`)", text) self.assertIn("fork pershoot menerima manager KSUN resmi", text) self.assertIn("SUSFS: `99999999`", text) diff --git a/tests/test_root_matrix.py b/tests/test_root_matrix.py index 85cd5c8..e78bf6c 100644 --- a/tests/test_root_matrix.py +++ b/tests/test_root_matrix.py @@ -51,12 +51,11 @@ def main(): both = entries(matrix(manager="KSUN + SUSFS & BakaSU + SUSFS", BUILD_VANILLA=True)) assert [(e["name"], e["KSU"], e["KSU_SUSFS"]) for e in both] == [ ("6.1-KSUN+SUSFS", "KSUN", "true"), ("6.1-BSU+SUSFS", "BSU", "true")] - # KSUN+SUSFS builds only the validated, device-tested isolated candidate (Issue #19) - # on a sublevel with locked trees; anything else is rejected before tools run. + # KSUN+SUSFS checks the selected current pins and actual patches, independent of trial evidence. build_script = (ROOT / "build.sh").read_text() assert 'elif [ "$KSU" = "KSUN" ]; then' in build_script assert 'if susfs_included; then' in build_script - assert 'KSUN+SUSFS supports only kernel 6.1, fullLTO' in build_script + assert 'KSUN+SUSFS supports kernel 6.1 without the legacy Compat variant' in build_script assert 'stage_ksun_susfs_candidate.py" "$KSRC" "$WORKDIR/ksun" "$KSUN_SUSFS_SUBLEVEL"' in build_script assert '+Test' not in build_script # no longer labelled as a test build # One build tag everywhere (revision, else CI run) and cogan17@GKID. @@ -78,29 +77,38 @@ def main(): step["run"] for step in workflow["jobs"]["prepare"]["steps"] if step["name"] == "Validate sublevel, build time and optional features" ) - def early(**variables): + def early(prefix_only=False, **variables): # A rejected choice must exit before any tool, download or workspace mutation: # only python3 (the read-only sublevel check) is on PATH. with tempfile.TemporaryDirectory() as directory, tempfile.TemporaryDirectory() as tools: os.symlink(sys.executable, Path(tools) / "python3") env = os.environ.copy() env.update(PATH=tools, **variables) + command = (["/bin/bash", "-c", build_script.split('WORKDIR="$(pwd)"', 1)[0]] + if prefix_only else ["/bin/bash", str(ROOT / "build.sh")]) result = subprocess.run( - ["/bin/bash", str(ROOT / "build.sh")], cwd=directory, + command, cwd=ROOT if prefix_only else directory, env=env, text=True, capture_output=True, ) assert not list(Path(directory).iterdir()), "Rejected build changed the workspace" return result - for bad, message in (({"KERNEL_VERSION": "6.6"}, "KSUN+SUSFS supports only"), - ({"LTO": "thinLTO"}, "KSUN+SUSFS supports only"), - ({"BBG": "false"}, "KSUN+SUSFS supports only"), - ({"KSU_COMPAT": "true"}, "KSUN+SUSFS supports only"), - ({"KERNEL_SUBLEVEL": "166"}, "KSUN+SUSFS is not verified"), - ({"KERNEL_SUBLEVEL": "6.1.177"}, "KSUN+SUSFS is not verified")): + for bad, message in (({"KERNEL_VERSION": "6.6"}, "KSUN+SUSFS supports kernel 6.1"), + ({"LTO": "invalid"}, "Invalid LTO"), + ({"BBG": "invalid"}, "BBG must be true or false"), + ({"NETWORKING": "invalid"}, "NETWORKING must be true or false"), + ({"KSU_COMPAT": "true"}, "legacy Compat variant"), + ({"KERNEL_SUBLEVEL": "166"}, "SUSFS variants need kernel 6.1.167"), + ({"KERNEL_SUBLEVEL": "6.1.177"}, "not available")): rejected = early(KSU="KSUN", KSU_SUSFS="true", **bad) assert rejected.returncode == 1, (bad, rejected.stderr) assert message in rejected.stderr, rejected.stderr assert 'command not found' not in rejected.stderr, rejected.stderr + for lto in ("noneLTO", "thinLTO", "fullLTO"): + for bbg in ("false", "true"): + for networking in ("false", "true"): + result = early(prefix_only=True, KSU="KSUN", KSU_SUSFS="true", LTO=lto, + BBG=bbg, NETWORKING=networking) + assert result.returncode == 0, (lto, bbg, networking, result.stderr) # Every SUSFS variant needs 6.1.167+ (the SUSFS kernel patch needs its fs/namespace.c context). for ksu in ("BSU", "KSU"): rejected = early(KSU=ksu, KSU_SUSFS="true", KERNEL_SUBLEVEL="166") @@ -119,18 +127,26 @@ def early(**variables): def prepare(**inputs): env = {**os.environ, "ROOT_MANAGER_INPUT": "KernelSU Next + SUSFS", "KERNEL_VERSION_INPUT": "6.1", "KERNEL_SUBLEVEL_INPUT": "lts", "BUILD_TIME_INPUT": "N", - "LTO_INPUT": "fullLTO + NoMount + Clean LTO", "BBG_INPUT": "true", **inputs} + "LTO_INPUT": "fullLTO + NoMount + Clean LTO", "BBG_INPUT": "true", + "NETWORKING_INPUT": "true", **inputs} return subprocess.run(["/bin/bash", "-c", preflight], cwd=ROOT, env=env, text=True, capture_output=True) assert prepare().returncode == 0, prepare().stderr for sublevel in ("177", "176", "168", "167"): assert prepare(KERNEL_SUBLEVEL_INPUT=sublevel).returncode == 0, sublevel - for bad in ({"KERNEL_VERSION_INPUT": "6.6"}, {"LTO_INPUT": "thinLTO + NoMount + Clean LTO"}, - {"BBG_INPUT": "false"}, {"KERNEL_SUBLEVEL_INPUT": "166"}, + for manager in ("KernelSU Next + SUSFS", "KSUN + SUSFS & BakaSU + SUSFS", "All variants (advanced)"): + for lto in ("noneLTO", "thinLTO", "fullLTO"): + for bbg in ("false", "true"): + for networking in ("false", "true"): + result = prepare(ROOT_MANAGER_INPUT=manager, LTO_INPUT=f"{lto} + NoMount + Clean LTO", + BBG_INPUT=bbg, NETWORKING_INPUT=networking) + assert result.returncode == 0, (manager, lto, bbg, networking, result.stderr) + for bad in ({"KERNEL_VERSION_INPUT": "6.6"}, {"LTO_INPUT": "invalid"}, + {"BBG_INPUT": "invalid"}, {"NETWORKING_INPUT": "invalid"}, + {"KERNEL_SUBLEVEL_INPUT": "166"}, {"ROOT_MANAGER_INPUT": "KSUN + SUSFS & BakaSU + SUSFS", "KERNEL_SUBLEVEL_INPUT": "160"}): rejected = prepare(**bad) assert rejected.returncode == 1, (bad, rejected.stderr) - assert 'KSUN+SUSFS needs' in rejected.stderr or 'KSUN+SUSFS is not verified' in rejected.stderr, rejected.stderr # SUSFS variants: 6.1.167 and newer; variants without SUSFS keep 160+. for manager in ("BakaSU + SUSFS", "KernelSU + SUSFS", "All variants (advanced)"): rejected = prepare(ROOT_MANAGER_INPUT=manager, KERNEL_SUBLEVEL_INPUT="166")