Skip to content

fix: silent install via root now triggers Magisk prompt#673

Merged
rainxchzed merged 2 commits into
mainfrom
fix/651-root-installer-magisk
May 27, 2026
Merged

fix: silent install via root now triggers Magisk prompt#673
rainxchzed merged 2 commits into
mainfrom
fix/651-root-installer-magisk

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 26, 2026

Closes #651.

The previous root installer spawned su via Runtime.exec directly against a hardcoded list of su binary paths. On modern Magisk (≥27) + Android 14/15 this never triggers the superuser daemon prompt — the binary is hidden from non-granted callers, so the probe immediately returns "no su" and the Root tile shows "No root" with no permission dialog.

Rewrote RootServiceManager on top of libsu 6.0.0 (the canonical lib from the Magisk author, used by LSPosed/Shizuku/etc.). It handles binary discovery, the daemon socket, MagiskHide quirks, and works out of the box with KernelSU + APatch.

Behaviour changes:

  • Status: Shell.isAppGrantedRoot() (null → NOT_AVAILABLE, false → PERMISSION_NEEDED, true → READY).
  • Prompt: Shell.getShell() forces main shell creation, which surfaces the Magisk/KernelSU/APatch grant dialog properly.
  • installPackage: stages APK to /data/local/tmp (avoids SELinux path-access issues), pm install -r -i <installer> <tmp>, cleans up.
  • uninstallPackage: pm uninstall <pkg> via libsu shell.
  • Public API + RootStatus enum unchanged; all callsites (SilentInstallerDispatcher, AndroidInstallerStatusProvider, AutoUpdateWorker, Tweaks UI) untouched.

Added JitPack repo scoped to com.github.topjohnwu only (no wide-net resolution).

Tested by compile-verifying :composeApp:assembleDebug. I do not have a rooted device to manually verify; flagging for community confirmation.

Summary by CodeRabbit

  • Bug Fixes

    • On Android 14+, silent installation via root now correctly triggers Magisk/KernelSU permission prompts instead of reporting "No root".
  • Improvements

    • Root-enabled install/uninstall flows made more reliable and consistent, with better root-state handling and clearer success/failure reporting.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0c05b119-a105-4ed5-b48b-e2405235710f

📥 Commits

Reviewing files that changed from the base of the PR and between fa65e78 and a26254a.

📒 Files selected for processing (1)
  • core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt

Walkthrough

This PR integrates libsu (via JitPack and the version catalog), configures a default libsu Shell, replaces su-binary probing with Shell.isAppGrantedRoot() for root status, and rewrites package install/uninstall to use Shell.cmd(...).exec() with staged APK handling and "Success" output checks.

Changes

libsu integration and root service refactoring

Layer / File(s) Summary
Dependency configuration for libsu
gradle/libs.versions.toml, settings.gradle.kts, core/data/build.gradle.kts
Added libsu = "6.0.0" to the version catalog, defined libsu-core library, added JitPack maven("https://jitpack.io") (including com.github.topjohnwu groups), and declared implementation(libs.libsu.core) in androidMain.
RootServiceManager refactoring with Shell API
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt
Imports updated to use com.topjohnwu.superuser.Shell; configureDefaultShell() with a configured guard added and invoked from initialize(), refreshStatus(), and requestPermission(); computeStatus() now uses Shell.isAppGrantedRoot(); installPackage()/uninstallPackage() reimplemented to stage APKs, run pm install/pm uninstall via Shell.cmd(...).exec(), detect success by stdout containing "Success", and cleanup staged files, removing prior Runtime.exec and su-discovery helpers.
Changelog entry
core/presentation/src/commonMain/composeResources/files/whatsnew/19.json
Adds a FIXED bullet to v1.9.0 noting that on Android 14+ silent root installs now correctly trigger Magisk/KernelSU permission prompts and rewrite the root path via libsu.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • OpenHub-Store/GitHub-Store#610: Also modifies RootServiceManager's root detection/execution flow; #610 replaced stat-based su probing with exec-based probes, while this PR switches to libsu's Shell API.

Poem

🐰
I hopped into code with nimble feet,
Replaced old su hunts with libsu neat,
Magisk pops now show their face,
Silent installs run in place,
A tiny hop for package peace.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly matches the main fix described in the changeset—migrating to libsu to properly trigger Magisk prompts for silent root installation.
Linked Issues check ✅ Passed All core requirements from issue #651 are met: libsu integration enables proper root detection via Shell.isAppGrantedRoot(), Shell.getShell() forces permission dialogs, and installPackage/uninstallPackage execute via libsu shell.
Out of Scope Changes check ✅ Passed All changes directly support the libsu migration objective: build configuration updates, RootServiceManager refactoring for libsu compatibility, 'What's New' documentation of the fix, and JitPack repository addition are all in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/651-root-installer-magisk

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 26, 2026

Greptile Summary

Rewrites RootServiceManager from a hand-rolled Runtime.exec + hardcoded su path prober to libsu 6.0.0, fixing the Magisk ≥27 / Android 14+ regression where the hidden su binary was never found and no permission dialog was raised.

  • Root status & prompt: replaces the su-path probe loop with Shell.isAppGrantedRoot() (null/false/true → NOT_AVAILABLE/PERMISSION_NEEDED/READY) and uses Shell.getShell() to surface the Magisk/KernelSU/APatch dialog.
  • installPackage: APK is staged to /data/local/tmp via root cp, installed with pm install -r -i <installer>, and cleaned up in a try/finally; path arguments are single-quote–escaped via shellQuote.
  • Build config: JitPack repository added with includeGroupAndSubgroups("com.github.topjohnwu") scope guard to prevent wide dependency resolution.

Confidence Score: 5/5

Safe to merge pending community confirmation on a rooted device; no logic defects in the changed code paths

The rewrite correctly delegates shell lifecycle to libsu, the double-checked locking pattern is sound, shell arguments are properly quoted, and the try/finally cleanup is correctly scoped

RootServiceManager.kt — the only substantive change; all other files are build configuration

Important Files Changed

Filename Overview
core/data/src/androidMain/kotlin/zed/rainxch/core/data/services/root/RootServiceManager.kt Full rewrite of root shell management using libsu; correct double-checked locking, proper shell quoting, and try/finally cleanup — one minor concern with fire-and-forget cleanup and a note on the 10s timeout being below libsu's 20s default
core/data/build.gradle.kts Adds libs.libsu.core dependency to androidMain; minimal and correct change
gradle/libs.versions.toml Adds libsu 6.0.0 version pin and libsu-core module alias; correctly placed alongside other third-party root libs
settings.gradle.kts JitPack repo added with includeGroupAndSubgroups scope guard — correctly prevents wide-net resolution
core/presentation/src/commonMain/composeResources/files/whatsnew/19.json What's New entry added with correct trailing comma on preceding element; JSON is valid

Sequence Diagram

sequenceDiagram
    participant App
    participant RootServiceManager
    participant libsu as libsu (Shell)
    participant Magisk as Magisk / KernelSU daemon

    App->>RootServiceManager: initialize()
    RootServiceManager->>libsu: "Shell.setDefaultBuilder(timeout=10s)"
    RootServiceManager->>libsu: Shell.isAppGrantedRoot()
    libsu-->>RootServiceManager: null / false / true
    RootServiceManager-->>App: "status = NOT_AVAILABLE / PERMISSION_NEEDED / READY"

    App->>RootServiceManager: requestPermission()
    RootServiceManager->>libsu: Shell.getShell() [blocks on IO thread]
    libsu->>Magisk: su socket handshake
    Magisk-->>App: Show grant dialog (UI thread)
    App-->>Magisk: User taps Allow
    Magisk-->>libsu: Root shell granted
    libsu-->>RootServiceManager: "Shell (status=ROOT_SHELL)"
    RootServiceManager->>libsu: Shell.isAppGrantedRoot()
    libsu-->>RootServiceManager: true
    RootServiceManager-->>App: "status = READY"

    App->>RootServiceManager: installPackage(apkFile, installer)
    RootServiceManager->>libsu: "Shell.cmd(cp src tmp && chmod 644 tmp).exec()"
    libsu-->>RootServiceManager: copyRes
    RootServiceManager->>libsu: Shell.cmd(pm install -i pkg -r tmp).exec()
    libsu-->>RootServiceManager: result (stdout / exit code)
    RootServiceManager->>libsu: Shell.cmd(rm -f tmp).submit()
    RootServiceManager-->>App: STATUS_SUCCESS / STATUS_FAILURE
Loading

Fix All in Claude Code

Reviews (2): Last reviewed commit: "fix(root): wrap install in outer try/fin..." | Re-trigger Greptile

@rainxchzed rainxchzed merged commit 5ef10ce into main May 27, 2026
1 check passed
@rainxchzed rainxchzed deleted the fix/651-root-installer-magisk branch May 27, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Root installation option says "No root" and never triggers Magisk permission prompt

1 participant