5 コミット 59482a58db ... 768052147b

作者 SHA1 メッセージ 日付
  James McConnell 768052147b fix: define METHOD vars in metadata step (unbound variable) (#294) 6 日 前
  James McConnell f11bcf947f fix: properly guard NoMount steps when disabled (#293) 6 日 前
  Trinadh Thatakula 220ae1c91c docs: add guide for building a specific kernel from a fork (#290) 6 日 前
  James McConnell 6bf605bf36 chore: bump PIN_NOMOUNT to dev tip 09f3c41f (#292) 6 日 前
  James McConnell f14f17bcc0 fix: tolerate missing 0x132eb5f1 in 6.6.46-2024-09 abi .stg (#291) 6 日 前

+ 66 - 2
.github/actions/apply-device-patches/action.yml

@@ -94,8 +94,72 @@ runs:
 
         (
           cd "$STAGE_DIR"
-          patch -p1 --dry-run < "$PATCH"
-          patch -p1 --no-backup-if-mismatch < "$PATCH"
+          if patch -p1 --dry-run < "$PATCH" 2>/dev/null; then
+            patch -p1 --no-backup-if-mismatch < "$PATCH"
+          else
+            echo "Rigid .stg patch failed (upstream drift), falling back to idempotent insert"
+            python3 - android/abi_gki_aarch64.stg <<'PYEOF'
+        import sys
+        stg_path = sys.argv[1]
+        with open(stg_path) as f:
+            src = f.read()
+        def ensure_before(anchor, block, label):
+            global src
+            if label in src:
+                print(f"already present: {label}")
+                return
+            if anchor in src:
+                src = src.replace(anchor, block + anchor, 1)
+                print(f"inserted {label} at anchor")
+            else:
+                # STG text format is order-insensitive: append as last resort
+                if not src.endswith('\n'):
+                    src += '\n'
+                src += block
+                print(f"inserted {label} at EOF (anchor missing)")
+        func1 = ('function {\n  id: 0x1e5195df\n  return_type_id: 0x48b5725f\n'
+                 '  parameter_id: 0x3d551c03\n  parameter_id: 0x6720d32f\n}\n')
+        func2 = ('function {\n  id: 0xc18e39fb\n  return_type_id: 0x4585663f\n'
+                 '  parameter_id: 0x3d551c03\n}\n')
+        elf1 = ('elf_symbol {\n  id: 0xb0801f6e\n  name: "kdp_set_cred_non_rcu"\n  is_defined: true\n'
+                '  symbol_type: FUNCTION\n  crc: 0x738bae5e\n  type_id: 0x1e5195df\n'
+                '  full_name: "kdp_set_cred_non_rcu"\n}\n')
+        elf2 = ('elf_symbol {\n  id: 0x3037c5bc\n  name: "kdp_usecount_dec_and_test"\n  is_defined: true\n'
+                '  symbol_type: FUNCTION\n  crc: 0xda582aa5\n  type_id: 0xc18e39fb\n'
+                '  full_name: "kdp_usecount_dec_and_test"\n}\n')
+        elf3 = ('elf_symbol {\n  id: 0x8334a496\n  name: "kdp_usecount_inc"\n  is_defined: true\n'
+                '  symbol_type: FUNCTION\n  crc: 0xfb342499\n  type_id: 0x1fcd1693\n'
+                '  full_name: "kdp_usecount_inc"\n}\n')
+        ensure_before('function {\n  id: 0x1e571002', func1, 'id: 0x1e5195df')
+        ensure_before('function {\n  id: 0xc18f1240', func2, 'id: 0xc18e39fb')
+        elf_anchor = 'elf_symbol {\n  id: 0x493ce9fc\n  name: "loops_per_jiffy"'
+        for block, label in [(elf1, '"kdp_set_cred_non_rcu"'), (elf2, '"kdp_usecount_dec_and_test"'), (elf3, '"kdp_usecount_inc"')]:
+            ensure_before(elf_anchor, block, label)
+        # interface symbol_ids must land inside the interface block
+        iface_ids = ['0xb0801f6e', '0x3037c5bc', '0x8334a496']
+        missing = [i for i in iface_ids if f'symbol_id: {i}' not in src]
+        if missing:
+            lines = [f'  symbol_id: {i}\n' for i in missing]
+            # 0xc750a072 exists in all snapshots (incl. 2024-09 where
+            # 0x132eb5f1 is missing upstream); insert right after it.
+            anchor = '  symbol_id: 0xc750a072\n'
+            if anchor in src:
+                src = src.replace(anchor, anchor + ''.join(lines), 1)
+                print(f"inserted interface ids after 0xc750a072: {missing}")
+            else:
+                idx = src.find('interface {')
+                assert idx != -1, 'interface block not found in .stg'
+                end = src.find('\n}\n', idx)
+                assert end != -1, 'interface block close not found'
+                src = src[:end] + ''.join(lines).rstrip('\n') + src[end:]
+                print(f"inserted interface ids before interface close: {missing}")
+        else:
+            print('interface ids already present')
+        with open(stg_path, 'w') as f:
+            f.write(src)
+        print('fallback .stg insert done')
+        PYEOF
+          fi
           printf '%s\n' \
             'kdp_set_cred_non_rcu' \
             'kdp_usecount_dec_and_test' \

+ 77 - 25
.github/workflows/build.yml

@@ -326,7 +326,7 @@ jobs:
         kernel_version: ${{ inputs.kernel_version }}
 
     - name: Integrate NoMount
-      if: inputs.nomount_commit != '' || contains(inputs.feature_set, 'NoMount') || inputs.feature_set == 'All'
+      if: contains(inputs.feature_set, 'NoMount') || inputs.feature_set == 'All'
       uses: ./.github/actions/nomount
       with:
         commit: ${{ inputs.nomount_commit }}
@@ -529,6 +529,13 @@ jobs:
         fi
         SUMMARY_FILE="/tmp/build-summary-${SUMMARY_BASE}.md"
         echo "SUMMARY_BASE=${SUMMARY_BASE}" >> "$GITHUB_ENV"
+
+        # NoMount is optional (nomount_commit input is always populated, so
+        # feature_set is the only reliable signal for whether it was built).
+        NOMOUNT_ENABLED="false"
+        if [ "${{ contains(inputs.feature_set, 'NoMount') || inputs.feature_set == 'All' }}" = "true" ]; then
+          NOMOUNT_ENABLED="true"
+        fi
         
         # Determine build status
         if [ "${{ job.status }}" = "success" ]; then
@@ -552,7 +559,11 @@ jobs:
         ROOT_MANAGER="${ROOT_MANAGER:-KernelSU-Next Manager}"
         ROOT_VERSION="${ROOT_VERSION:-${KSU_TAG}}"
         ROOT_SHA="${ROOT_COMMIT:-${KSU_COMMIT:-N/A}}"
-        NOMOUNT_SHA="${NOMOUNT_COMMIT:-${{ inputs.nomount_commit }}}"
+        if [ "$NOMOUNT_ENABLED" = "true" ]; then
+          NOMOUNT_SHA="${NOMOUNT_COMMIT:-${{ inputs.nomount_commit }}}"
+        else
+          NOMOUNT_SHA="N/A"
+        fi
         SUSFS_SHA="${SUSFS_COMMIT:-${{ inputs.susfs_commit }}}"
         
         # Get SUSFS version from susfs.h if available
@@ -611,13 +622,29 @@ jobs:
         compression-level: 9
 
     - name: Write Build-verified only artifact metadata
-      if: success() && inputs.root_flavor != '' && inputs.nomount_commit != ''
+      if: success() && inputs.root_flavor != ''
       shell: bash
       env:
         GH_TOKEN: ${{ github.token }}
       run: |
         set -euo pipefail
 
+        # NoMount is optional: only resolve its artifact when requested.
+        # (nomount_commit input is always populated, so feature_set is the
+        # only reliable signal for whether NoMount was integrated.)
+        NOMOUNT_ENABLED="false"
+        if [ "${{ contains(inputs.feature_set, 'NoMount') || inputs.feature_set == 'All' }}" = "true" ]; then
+          NOMOUNT_ENABLED="true"
+        fi
+
+        if [ "$NOMOUNT_ENABLED" = "true" ]; then
+          METHOD="GKI source build with SHA-pinned root implementation, SUSFS, and NoMount"
+          NOMOUNT_MODULE_COMMIT="${{ inputs.nomount_commit }}"
+        else
+          METHOD="GKI source build with SHA-pinned root implementation and SUSFS"
+          NOMOUNT_MODULE_COMMIT="N/A"
+        fi
+
         metadata_file="/tmp/${SUMMARY_BASE}-metadata.json"
         summary_file="/tmp/build-summary-${SUMMARY_BASE}.md"
         artifacts_api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100"
@@ -661,12 +688,17 @@ jobs:
         read -r kernel_artifact_url kernel_artifact_sha256 < <(
           resolve_artifact "${{ steps.extract.outputs.file_name }}-AnyKernel3"
         )
-        read -r nomount_module_url nomount_module_sha256 < <(
-          resolve_artifact "NoMount-Metamodule"
-        )
+        if [ "$NOMOUNT_ENABLED" = "true" ]; then
+          read -r nomount_module_url nomount_module_sha256 < <(
+            resolve_artifact "NoMount-Metamodule"
+          )
+        else
+          nomount_module_url="N/A"
+          nomount_module_sha256="N/A"
+        fi
 
         jq -n \
-          --arg method "GKI source build with SHA-pinned root implementation, SUSFS, and NoMount" \
+          --arg method "$METHOD" \
           --arg root_implementation "${ROOT_IMPLEMENTATION:-KernelSU-Next (legacy path)}" \
           --arg root_manager "${ROOT_MANAGER:-KernelSU-Next Manager}" \
           --arg root_version "${ROOT_VERSION:-${KSU_GIT_TAG:-N/A}}" \
@@ -679,7 +711,7 @@ jobs:
           --arg provenance_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
           --arg artifact_url "$kernel_artifact_url" \
           --arg sha256 "$kernel_artifact_sha256" \
-          --arg nomount_module_commit "${{ inputs.nomount_commit }}" \
+          --arg nomount_module_commit "$NOMOUNT_MODULE_COMMIT" \
           --arg nomount_module_url "$nomount_module_url" \
           --arg nomount_module_sha256 "$nomount_module_sha256" \
           --arg status "Build-verified only" \
@@ -717,23 +749,43 @@ jobs:
             status: $status
           }' > "$metadata_file"
 
-        jq -e '
-          .status == "Build-verified only" and
-          (.root.manager | length > 0) and
-          (.root.version | length > 0) and
-          (.susfs_revision | test("^[0-9a-f]{40}$")) and
-          (.nomount_commit | test("^[0-9a-f]{40}$")) and
-          (.kernel_source_commit | test("^[0-9a-f]{40}$")) and
-          (.artifact_url | startswith("https://")) and
-          (.sha256 | test("^sha256:[0-9a-f]{64}$")) and
-          (.nomount_metamodule.commit == .nomount_commit) and
-          (.nomount_metamodule.artifact_url | startswith("https://")) and
-          (.nomount_metamodule.sha256 | test("^sha256:[0-9a-f]{64}$")) and
-          .catalog.availability == "eligible-with-provenance-and-checksums" and
-          .catalog.device_compatibility == "not-validated" and
-          .catalog.flashability == "not-guaranteed" and
-          .catalog.boot == "not-guaranteed"
-        ' "$metadata_file" > /dev/null
+        if [ "$NOMOUNT_ENABLED" = "true" ]; then
+          jq -e '
+            .status == "Build-verified only" and
+            (.root.manager | length > 0) and
+            (.root.version | length > 0) and
+            (.susfs_revision | test("^[0-9a-f]{40}$")) and
+            (.nomount_commit | test("^[0-9a-f]{40}$")) and
+            (.kernel_source_commit | test("^[0-9a-f]{40}$")) and
+            (.artifact_url | startswith("https://")) and
+            (.sha256 | test("^sha256:[0-9a-f]{64}$")) and
+            (.nomount_metamodule.commit == .nomount_commit) and
+            (.nomount_metamodule.artifact_url | startswith("https://")) and
+            (.nomount_metamodule.sha256 | test("^sha256:[0-9a-f]{64}$")) and
+            .catalog.availability == "eligible-with-provenance-and-checksums" and
+            .catalog.device_compatibility == "not-validated" and
+            .catalog.flashability == "not-guaranteed" and
+            .catalog.boot == "not-guaranteed"
+          ' "$metadata_file" > /dev/null
+        else
+          jq -e '
+            .status == "Build-verified only" and
+            (.root.manager | length > 0) and
+            (.root.version | length > 0) and
+            (.susfs_revision | test("^[0-9a-f]{40}$")) and
+            (.nomount_commit == "N/A") and
+            (.kernel_source_commit | test("^[0-9a-f]{40}$")) and
+            (.artifact_url | startswith("https://")) and
+            (.sha256 | test("^sha256:[0-9a-f]{64}$")) and
+            (.nomount_metamodule.commit == "N/A") and
+            (.nomount_metamodule.artifact_url == "N/A") and
+            (.nomount_metamodule.sha256 == "N/A") and
+            .catalog.availability == "eligible-with-provenance-and-checksums" and
+            .catalog.device_compatibility == "not-validated" and
+            .catalog.flashability == "not-guaranteed" and
+            .catalog.boot == "not-guaranteed"
+          ' "$metadata_file" > /dev/null
+        fi
 
         # Merge the verified metadata into the combined summary markdown so both
         # live in a single artifact: human-readable table + machine JSON block.

+ 1 - 1
.github/workflows/main.yml

@@ -176,7 +176,7 @@ jobs:
           }
 
           # Audited pins (from root-variants.yml). Used only when commit_mode=verified.
-          PIN_NOMOUNT="7eb06026fe788ceea0f3c4de4a0b42ffa0a4cc99"
+          PIN_NOMOUNT="09f3c41f068977d96ff8e552327428a307f8d286"
           PIN_KERNELSU="3c1240625655978f319a98398031100b80e9da7c"
           PIN_RESUKISU="3c1882886dbbb54f4aae7ddf205f8ccde32c2a34"
           declare -A PIN_SUSFS=(

+ 6 - 0
README.md

@@ -35,6 +35,12 @@ Generic kernels built on [Google's GKI sources](https://android.googlesource.com
 
 ---
 
+## Build Your Own Kernel
+
+Fork the repository and follow **[Build Your Own Kernel](docs/build-from-fork.md)** to select one kernel family, patch level, root implementation, and feature set in GitHub Actions.
+
+---
+
 ## Installation
 
 See **[Installation Guide](https://github.com/WildKernels/GKI_KernelSU_SUSFS/wiki/Installation)**.

+ 269 - 0
docs/build-from-fork.md

@@ -0,0 +1,269 @@
+# Build a Kernel in Your Fork
+
+This guide shows how to fork the repository, build one specific GKI kernel with GitHub Actions, and download the result. You do not need a local Linux build environment for the normal workflow.
+
+> [!CAUTION]
+> A successful build does not guarantee that a kernel will boot on a particular device. Confirm the device's GKI/KMI family, keep the stock `boot.img`, and have a tested recovery method before flashing. See the [Installation Guide](installation.md) before using an artifact.
+
+## 1. Fork the Repository
+
+1. Open [WildKernels/GKI_KernelSU_SUSFS](https://github.com/WildKernels/GKI_KernelSU_SUSFS).
+2. Select **Fork**, choose your account, and create the fork.
+3. Open the **Actions** tab in your fork.
+4. If GitHub says workflows are disabled, select **I understand my workflows, go ahead and enable them**.
+
+Normal kernel builds use the repository's built-in `GITHUB_TOKEN`. You do not need to create a personal access token or repository secret.
+
+If a build reports that the token cannot create releases or update Actions data, open **Settings → Actions → General → Workflow permissions** in your fork and allow **Read and write permissions**. Organization policy can prevent a repository from granting those permissions.
+
+> [!NOTE]
+> Forking this repository copies the build orchestration. Kernel sources, root implementations, SUSFS, patches, AnyKernel3, managers, and other components are still fetched from their configured upstream repositories.
+
+## 2. Identify the Correct Kernel Family
+
+Choose the kernel from the device's stock kernel/KMI branch—not only from the Android userspace version shown in Settings. For example, a phone running Android 15 may still use an `android14-6.1` kernel branch.
+
+You can start by checking the running kernel:
+
+```bash
+adb shell uname -r
+```
+
+If the output does not identify the Android common-kernel generation, check the device's stock firmware information or OEM kernel sources before building.
+
+The workflow currently exposes these families:
+
+| Workflow selection | Android common-kernel generation | Linux series |
+|---|---:|---:|
+| `5.10.x-android12` | Android 12 | 5.10 |
+| `5.10.x-android13` | Android 13 | 5.10 |
+| `5.15.x-android13` | Android 13 | 5.15 |
+| `5.15.x-android14` | Android 14 | 5.15 |
+| `6.1.x-android14` | Android 14 | 6.1 |
+| `6.6.x-android15` | Android 15 | 6.6 |
+| `6.12.x-android16` | Android 16 | 6.12 |
+
+The **OS patch level** field accepts one of the following:
+
+- A patch date present in the selected family's config, such as `2025-01`.
+- A numeric Linux sublevel present in that config, such as `118`. A sublevel can occur under more than one patch date, in which case every matching row is built.
+- `lts`, which builds the current tip of that family's configured LTS branch.
+- `All`, which builds every configured row for the selected family.
+
+The matrix files under [`.github/config/`](../.github/config/) are the source of truth for available dates and sublevels. To guarantee one matrix row, select a unique patch date or `lts`; if you use a numeric sublevel, check the config first to confirm it occurs only once.
+
+> [!WARNING]
+> Do not leave **Kernel Version**, **OS patch level**, and **Root Flavor** set to `All` for a first test. Those defaults fan out across every exposed family, every matching matrix row, and all three root implementations, potentially creating hundreds of kernel jobs.
+
+## 3. Run One Build in the GitHub UI
+
+1. Open **Actions** in your fork.
+2. Select **Build Kernels**.
+3. Select **Run workflow**.
+4. Choose the branch containing your desired changes, normally `main`.
+5. Set a specific **Kernel Version**, **OS patch level**, and **Root Flavor**.
+6. Select **Run workflow**.
+
+Recommended settings for a first build:
+
+| Input | Recommended value | Reason |
+|---|---|---|
+| Release Type | `Action` | Runs the build without creating a numbered `rN` release. It still replaces the fork's `nightly` prerelease. |
+| Use cache | `false` for the first build | Avoids creating cache releases while testing the fork. Enable it later to speed up repeat builds. |
+| Kernel Version | One exact family | Prevents an all-family fan-out. |
+| OS patch level | One unique date or `lts` | Selects one matrix row. A numeric sublevel can match multiple dates. |
+| Kernel Branding | Your short brand name | Changes the kernel's local version string. |
+| Commit mode | `verified` | Uses the project's verified component pins where pins are supported. |
+| Root Flavor | One implementation | Produces one kernel instead of KernelSU-Next, KernelSU, and ReSukiSU builds. |
+| Feature toggles | Keep the defaults initially | Establishes a known baseline before customizing features. |
+| Test release notes | `false` | `true` skips the kernel builds and only previews release notes. |
+
+There is no rootless build option. Choose one of `KernelSU-Next`, `KernelSU`, or `ReSukiSU`. Install the matching manager after flashing; see [Post-install Setup](post-install.md).
+
+### Example: Current Android 15 / Linux 6.6 LTS
+
+Use:
+
+| Input | Value |
+|---|---|
+| Kernel Version | `6.6.x-android15` |
+| OS patch level | `lts` |
+| Commit mode | `verified` |
+| Root Flavor | `KernelSU` |
+
+The workflow syncs `common-android15-6.6-lts` and reads the actual numeric `SUBLEVEL` from the synced kernel Makefile. Because that branch moves, a later LTS build can produce a newer sublevel.
+
+### Example: Android 14 / Linux 6.1.118
+
+Use:
+
+| Input | Value |
+|---|---|
+| Kernel Version | `6.1.x-android14` |
+| OS patch level | `118` or `2025-01` |
+| Commit mode | `verified` |
+| Root Flavor | `KernelSU` |
+
+Both selectors resolve to the configured `2025-01` row. The expected artifact prefix is:
+
+```text
+6.1.118-android14-2025-01-KernelSU
+```
+
+## 4. Run the Same Build with GitHub CLI
+
+Install and authenticate [GitHub CLI](https://cli.github.com/), then replace `YOUR_USERNAME` with the owner of the fork.
+
+Android 15 / Linux 6.6 LTS:
+
+```bash
+gh workflow run main.yml \
+  -R YOUR_USERNAME/GKI_KernelSU_SUSFS \
+  --ref main \
+  -f release_type=Action \
+  -f kernel_build_version=6.6.x-android15 \
+  -f os_patch_level=lts \
+  -f brand_name=MyKernel \
+  -f commit_mode=verified \
+  -f root_flavor=KernelSU \
+  -f use_cache=false
+```
+
+Android 14 / Linux 6.1.118:
+
+```bash
+gh workflow run main.yml \
+  -R YOUR_USERNAME/GKI_KernelSU_SUSFS \
+  --ref main \
+  -f release_type=Action \
+  -f kernel_build_version=6.1.x-android14 \
+  -f os_patch_level=118 \
+  -f brand_name=MyKernel \
+  -f commit_mode=verified \
+  -f root_flavor=KernelSU \
+  -f use_cache=false
+```
+
+Find and watch the run:
+
+```bash
+gh run list \
+  -R YOUR_USERNAME/GKI_KernelSU_SUSFS \
+  --workflow main.yml \
+  --limit 10
+
+gh run watch \
+  -R YOUR_USERNAME/GKI_KernelSU_SUSFS \
+  RUN_ID \
+  --exit-status
+```
+
+## 5. Understand Source Modes and Side Effects
+
+### Commit mode
+
+- `verified` is the recommended normal mode. It uses verified commits for components that the workflow pins.
+- `latest` resolves supported components from their current branch tips when the run starts.
+- `update` builds latest component tips and can edit, commit, and push verified pins back to the selected branch. Use it only when deliberately maintaining those pins. In the current workflow, the promotion job is skipped when **Kernel Version** selects only one family; it is intended for the all-family maintenance path, not the single-kernel path in this guide.
+
+Even `verified` is not a complete lockfile: the Android kernel branch, KernelSU-Next, some patch/helper repositories, managers, and other components can still be fetched from moving upstream tips. Keep the workflow run URL and the matching `BuildInfo` artifact for provenance.
+
+### Release type
+
+- `Action` builds Actions artifacts and replaces the fork's `nightly` prerelease/tag with a link to that run. Use this mode for the single-family builds in this guide.
+- `Pre-Release` creates the next numbered `rN` prerelease and uploads release assets when the workflow runs the all-family path.
+- `Release` creates the next numbered `rN` stable release and uploads release assets when the workflow runs the all-family path.
+
+> [!IMPORTANT]
+> In the current workflow, selecting one exact kernel family causes the numbered release job to be skipped because the other family jobs are skipped. Do not select `Pre-Release` or `Release` for a single-family run; use `Action`. Running all families solely to create a numbered release is expensive and is not recommended for an initial fork build.
+
+### Build cache
+
+This project stores compiler caches in specially named GitHub Releases rather than using `actions/cache`. A missing cache on the first run is normal. With **Use cache** enabled, the workflow can create or update cache tags and releases in your fork. The separate **Clear Cache** workflow permanently deletes those cache releases after its confirmation input is supplied.
+
+## 6. Download the Result
+
+From the web UI:
+
+1. Open the completed workflow run.
+2. Scroll to **Artifacts**.
+3. Download the artifact ending in `-AnyKernel3` for the selected root flavor.
+4. Download the matching `-BuildInfo` artifact and keep it with the kernel.
+5. Download the manager APK for the same root flavor and any required module artifacts.
+
+Common artifacts include:
+
+- `*-AnyKernel3` — kernel package contents.
+- `*-BuildInfo` — source and artifact provenance, including checksums.
+- Manager APK artifacts — install the manager matching the selected root flavor.
+- `NoMount-Metamodule` — optional mount metamodule when applicable.
+
+To download with GitHub CLI:
+
+```bash
+gh run download \
+  -R YOUR_USERNAME/GKI_KernelSU_SUSFS \
+  RUN_ID \
+  -p '*-AnyKernel3' \
+  -p '*-BuildInfo' \
+  -p '*.apk' \
+  -p 'NoMount-Metamodule' \
+  -D ./artifacts
+```
+
+`gh run download` extracts each artifact into a directory. Before flashing, create the AnyKernel3 ZIP with `anykernel.sh` and the other package files at the archive root—not inside an extra parent directory.
+
+Follow the [Installation Guide](installation.md), then complete the [Post-install Setup](post-install.md). Kernel Flasher requires existing root; for an unrooted first installation, see the [manual `magiskboot` method](magiskboot.md).
+
+## 7. Customize Safely
+
+For input-only changes such as branding, root flavor, or most feature toggles, you do not need to edit the repository. Select the values when dispatching the workflow.
+
+> [!WARNING]
+> Keep **SUSFS** and **NoMount** enabled for now. Disabling SUSFS leaves its commit unavailable to the required metadata validation. Disabling NoMount still passes its resolved commit into the kernel build but skips the corresponding metamodule artifact. Either choice causes a later metadata step to fail, so these toggles do not currently produce a supported SUSFS-free or NoMount-free build.
+
+For source or workflow changes, keep your fork's `main` branch synchronized and work on a separate branch:
+
+```bash
+git clone https://github.com/YOUR_USERNAME/GKI_KernelSU_SUSFS.git
+cd GKI_KernelSU_SUSFS
+git remote add upstream https://github.com/WildKernels/GKI_KernelSU_SUSFS.git
+git switch -c my-kernel
+# Make and commit your changes, then:
+git push -u origin my-kernel
+```
+
+Run the workflow with `my-kernel` selected in the web UI or change `--ref main` to `--ref my-kernel` in the CLI examples.
+
+To update an unchanged fork `main` later:
+
+```bash
+git fetch upstream
+git switch main
+git merge --ff-only upstream/main
+git push origin main
+```
+
+If your fork's `main` contains custom commits, `--ff-only` will stop instead of rewriting them. Merge or rebase those changes deliberately; do not force-push without understanding what will be replaced.
+
+## Troubleshooting
+
+### The Run workflow button is missing
+
+Enable workflows from the fork's **Actions** tab and confirm `main.yml` exists on the fork's default branch. You also need write access to the fork.
+
+### No target matches the patch level
+
+Open the selected family's JSON file under [`.github/config/`](../.github/config/) and use an exact `date` or `sublevel` value. Not every family provides every sublevel, and `lts` only works where an LTS row is configured.
+
+### The workflow creates more jobs than expected
+
+Cancel the run and check all three selectors. Use one **Kernel Version**, one **OS patch level**, and one **Root Flavor** rather than `All`.
+
+### Release or cache steps fail with a permission error
+
+Check **Settings → Actions → General → Workflow permissions** and the organization policy applied to the fork. Normal builds do not require a custom token.
+
+### The kernel builds but does not boot
+
+Do not retry by changing random feature switches. Restore the stock boot image, verify the exact kernel/KMI family, and collect the information requested by the project's issue templates. Generic GKI compatibility is broad, not universal.