ModOS patches supported Android ROM OTA images with a selected set of modules while preserving AVB/OTA signing and update metadata. The maintained ROM profiles currently cover GrapheneOS and LineageOS. The project relies on upstream components from chenxiaolong and other projects, but keeps fork-specific release, trust, and compatibility policy in this repository.
ModOS is maintained as a soft fork of pixincreate/PixeneOS: upstream structure and fixes are preferred unless a fork-specific requirement needs a small, isolated extension. Fork-specific behavior should live behind typed configuration, ROM profiles, pinned helper contracts, or thin workflow triggers rather than copied build pipelines.
The Actions inventory is intentionally small: the upstream-style CI, release, multi-device release, and Renovate workflows, plus the reusable shared ROM build and the thin LineageOS release trigger. CI enforces this inventory so temporary acceptance or one-shot workflows cannot be committed accidentally.
- BCR
- Custota
- MSD
- OEMUnlockOnBoot
- AlterInstaller
- Optional Magisk using the repository selected by the build
- Optional local boot animation
- Optional locked F-Droid Privileged Extension integration (default-off)
Note
- ModOS is not affiliated with GrapheneOS, LineageOS, or the upstream projects it integrates.
- Linux is the supported host platform for the complete patching workflow.
Host prerequisites include Git, Python 3, curl, jq, unzip, xxd, e2fsprogs, and pkg-config. A Linux host is recommended; WSL or a Linux VM can also be used.
Executable tools are not expected to be preinstalled in PATH. PixeneOS authenticates and installs the exact AFSR, AVBRoot, and Custota-tool archives declared in locks/executable-tools-v1.json, including archive/member hashes, modes, and the reviewed upstream signer binding. See Executable tool trust.
The maintained 0cwa/my-avbroot-setup helper is pinned to an exact Git revision and checked by the compatibility manifest in tools/compat/avbroot_setup_compat.json. Its Python dependencies are installed from its pyproject.toml with uv.
Module versions are selected in src/declarations.sh; disabled modules are neither acquired nor passed to the patch helper.
This repository acts as a server.
- release.yml resolves the ROM version and module-selection fingerprint, then verifies whether the exact device/flavor/selection asset triplet already exists.
- The reusable build workflow authenticates locked executable tools, verifies the pinned helper contract, downloads the selected modules, and patches/signs the OTA.
- Published builds upload the OTA, its Custota signature, and selection metadata to the repository release.
- After the exact assets are verified, the
gh-pagesOTA metadata is updated. Older assets for the same selection fingerprint are pruned without deleting other valid variants.
Reading the AVBRoot docs is essential before proceeding with PixeneOS.
- Ensure the device has an unpatched version of the selected supported ROM (GrapheneOS or LineageOS) installed. The version must match the one from PixeneOS. It is important to make sure that the version installed matches the version on PixeneOS
- Start with a version before the latest to ensure OTA functionality.
Important
Factory image and OTA image are different. AVBRoot is meant to deal with OTA images. So does PixeneOS.
Important
In case you run into an issue that throws Device is corrupt. It can't be trusted soon after first install, try sideloading the OTA once before proceeding with flashing the custom AVB public key. This suggestion is based on the experience of users who faced this issue. See #89.
Also, check the FAQ section for more information on this issue.
Caution
If flashing fails, do not switch the slot.
It is easier to use the web installer to flash GrapheneOS. However, it is recommended to use the manual method since it makes it possible to install an older version of GrapheneOS unlike the web installer which always installs the latest version.
- Use the web installer to install GrapheneOS
- Once installed, do not re-lock the bootloader by clicking
Lock bootloaderunder theLocking the bootloadersection - Proceed to the patching section
-
Ensure Fastboot version is
34or newer.35or above is recommended as older versions are known to have bugs that prevent commands likefastboot flashallfrom running.fastboot --version
-
Reboot into
fastbootmode and unlock the bootloader if not already. This will trigger a data wipe. Ensure data is backed up.fastboot flashing unlock
-
When setting PixeneOS up for the first time, the device must already be running the correct OS. Flash the original unpatched OTA or factory image if needed.
bsdtar xvf DEVICE_NAME-factory-VERSION.zip # tar on Windows and macOS ./flash-all.sh # or .bat on Windows
-
Proceed to the patching section
-
Download the OTA from the current repository's releases. Ensure the version matches the installed version.
Extract the partition images from the patched OTA that are different from the original.
avbroot ota extract \ --input /path/to/ota.zip.patched \ --directory extracted \ --fastbootTo extract and flash all OS partitions, pass
--all. -
Set the
ANDROID_PRODUCT_OUTenvironment variable to the directory containing the extracted files.For
sh/bash/zsh(Linux, macOS, WSL):export ANDROID_PRODUCT_OUT=extractedFor PowerShell (Windows):
$env:ANDROID_PRODUCT_OUT = "extracted"
For cmd (Windows):
set ANDROID_PRODUCT_OUT=extracted
-
Flash the partition images.
fastboot flashall --skip-reboot
Note: This only flashes the OS partitions. The bootloader and modem/radio partitions are left untouched due to fastboot limitations. If they are not already up to date or if unsure, after fastboot completes, follow the steps in the updates section to sideload the patched OTA once. Sideloading OTAs always ensures that all partitions are up to date.
Alternatively, for Pixel devices, running
flash-base.shfrom the factory image will also update the bootloader and modem. -
Set up the custom AVB public key in the bootloader after rebooting from fastbootd to bootloader.
fastboot reboot-bootloader fastboot erase avb_custom_key fastboot flash avb_custom_key /path/to/avb_pkmd.bin
-
Sideload the OTA (This helps avoid or reduce the possibility of running into the
Device is corrupt. It can't be trustederror).- Run
fastboot reboot recoveryto get into recovery mode - An Android icon lying down with the text
No commandshould be visible on the screen - Hold the power button and press the volume up button a single time to get into the recovery UI
- Using the volume buttons, navigate to
Apply update from ADBand select it with the power button - As the recovery prompt says, use
adb sideload /path/to/ota.zip.patchedto sideload the patched OTA - After the sideload completes, select 'Reboot to bootloader'
- Run
-
Reboot into fastboot and lock the bootloader. This will trigger a data wipe.
fastboot flashing lock
Confirm by pressing volume down and then power. Then reboot.
Caution
Do not uncheck OEM unlocking!
- For future updates, see the updates section.
Root changes the device security model and can introduce compatibility breakage across ROM updates. Use it only when you understand the trade-offs for your device and selected ROM.
ModOS defaults to the official topjohnwu/Magisk repository. The source remains configurable through MAGISK[REPOSITORY] in env.toml, but alternate Magisk forks are opt-in rather than part of the GrapheneOS build contract. If you use Zygisk Next for GrapheneOS compatibility, install and manage it separately from the OTA build. Magisk/Zygisk behavior can change across releases, so rooted builds should be revalidated after ROM, Magisk, or Zygisk changes.
ModOS currently pins official Magisk v30.7 as its avbroot compatibility baseline. This mirrors the current rooted-GrapheneOS reference pairing with avbroot 3.34.1. avbroot 3.34.0 and newer explicitly recognize Magisk 31000, but parser/patch-format support is not the same as target-device runtime validation, so ModOS does not automatically advance to the highest Magisk tag. Override MAGISK_VERSION only for an intentional compatibility test.
With avbroot, Magisk is not updated using the app's normal Direct install path. Magisk version changes must be made by repatching/re-signing the OTA and installing that OTA. The manager's Additional setup / Environment fix is a separate runtime-environment step and does not replace OTA repatching.
A Magisk-patched OTA is only the boot-image half of a working Magisk installation. On a clean install, after a data wipe, or whenever Magisk reports that additional setup is required, install/open the matching Magisk manager APK, accept Additional setup / Environment fix, and allow the device to reboot. That step provisions the runtime binaries (including su) under /data/adb/magisk; those files are device state and cannot be embedded in or verified from an OTA image.
After that reboot, verify runtime root on the actual device:
adb shell su -c idA working setup should report uid=0(root). ModOS CI verifies that the Magisk OTA has a distinct Magisk-patched boot target with the configured preinit device; it deliberately does not call that static check proof of runtime root.
For one build flavor, the existing boolean ROOT remains supported (false = rootless, true = Magisk). ROOT_MODE is an optional string override with rootless, magisk, or both. both prepares the OTA and shared modules once, then emits the normal rootless and Magisk variants from the same prepared image set. Each output keeps its own module-selection fingerprint, Custota signature, update metadata, and /rootless/ or /magisk/ publication pointer.
KernelSU is not integrated by this repository. Adding another root implementation would require an explicit compatibility and signature-verification design rather than treating it as interchangeable with Magisk.
Note
For Magisk preinit, see Magisk preinit
Magisk versions 25211 and newer require a writable partition for storing custom SELinux rules that need to be accessed during early boot stages. This can only be determined on a real device, so avbroot requires the partition to be explicitly specified via --magisk-preinit-device <name>. To find the partition name:
-
Extract the boot image from the original/unpatched OTA:
avbroot ota extract \ --input /path/to/ota.zip \ --directory . \ --boot-only -
Patch the boot image via the Magisk app on the target device.
The Magisk app will print out a line like:
Pre-init storage partition device ID: <name>
Alternatively, run:
avbroot boot magisk-info \ --image magisk_patched-*.imgThe partition name will be shown as
PREINITDEVICE=<name>.Now that the partition name is known, it can be passed to avbroot when patching via
--magisk-preinit-device <name>. The partition name should be saved somewhere for future reference since it's unlikely to change across Magisk updates.If the device is unbootable, patch and flash the OTA once using
--ignore-magisk-warnings, then repatch and reflash the OTA with--magisk-preinit-device <name>.
Updates can be done by patching (or re-patching) the OTA using adb sideload:
- Reboot to recovery mode. If stuck at
No command, press Volume up while holding Power button. - Sideload the patched OTA with
adb sideloadby using volume buttons to toggle toApply update from ADBwhich can be confirmed by pressing the power button
PixeneOS leverages Custota:
- Disable the system updater app.
- Open Custota and set the OTA server URL to the repository's GitHub Pages publication URL, using the form
https://<owner>.github.io/<repository>/<rootless/magisk>.
For more info, refer to the current repository's server branch.
PixeneOS can be run locally on Linux.
-
Clone or fork the repository.
-
Review the checked-in
env.tomlexample and set the device, ROM family/update channel, root settings, and module toggles you need. Configuration is typed and validated bysrc/config_schema.sh. -
Run the patch pipeline:
. src/main.sh
Local runs generate the patched OTA but do not publish release assets or update gh-pages. Configuration precedence is: declaration defaults, then env.toml, then explicit caller/workflow inputs. Invalid or unknown TOML keys fail closed.
To use a local Android boot animation, place the ZIP at exactly
custom/boot-animation/bootanimation.zip. Builds remain unchanged by default;
enable the feature explicitly with ADDITIONALS_BOOT_ANIMATION=true, or add
'ADDITIONALS[BOOT_ANIMATION]' = true to env.toml. The archive is validated
before patching, and its exact SHA-256 is included in the module-selection
fingerprint. The payload is not read or required while the option is disabled.
By default, generated Custota metadata points patched OTA downloads at GitHub Releases for the current repository. These environment variables can override that behavior:
PIXENEOS_RELEASE_OWNER: GitHub release asset owner. Defaults to the owner fromGITHUB_REPOSITORY, then0cwa.PIXENEOS_RELEASE_REPOSITORY: GitHub release asset repository. Defaults to the repository fromGITHUB_REPOSITORY, thenPixeneOS.PIXENEOS_RELEASE_BASE_URL: Full release asset URL prefix, excluding the OTA filename. When set, generated metadata appends the patched OTA filename to this prefix instead of using the default GitHub Releases URL. If you use alternate hosting, publish both the patched OTA and its.csigsignature at the same prefix.PIXENEOS_AVBROOT_SETUP_SOURCE: Custom clone URL for themy-avbroot-setuphelper repository. Defaults tohttps://github.com/0cwa/my-avbroot-setup.
To make the patched OTA available to the device, it needs to be hosted on the server. PixeneOS uses GitHub for pushing updates, handled by release.yml.
To set up automated release, add the following variables in GitHub secrets:
EMAIL: Email address associated with the GitHub account.- Base64 encoded keys:
AVB_KEYCERT_OTAOTA_KEY
- Passphrases used to generate the keys:
PASSPHRASE_AVBPASSPHRASE_OTA
Scheduled runs normally skip an exact selection that is already published. Set FORCE_UPDATE = true under [build] in env.toml to rebuild the current ROM version; manual runs can use release-type: force-publish. Superseded assets are cleaned only when their selection metadata matches the same device, ROM family, and module-selection fingerprint.
Run multi-release.yml manually to build multiple GrapheneOS devices through the same release preflight. Enter a comma-separated list such as bramble, shiba; rooted builds use device:MAGISK_PREINIT, for example bramble:sda10, shiba:sda10. When the workflow input is empty it reads DEVICES from env.toml.
- To remove root, set the repository's GitHub Pages publication URL ending in
/rootless/in Custota. - To add root, set the repository's GitHub Pages publication URL ending in
/magisk/in Custota.
-
To see the list of available commands:
. src/util_functions.sh && help
helpcommand will display the help message. -
To see the list of supported tools:
. src/util_functions.sh && supported_tools
supported_toolscommand will display the list of tools that are supported. -
To generate AVB keys:
. src/util_functions.sh && generate_keys
This command will generate the AVB/OTA signing files under the local
.keys/directory (avb.key,ota.key,ota.crt, andavb_pkmd.bin).
Warning
Treat .keys/ and the generated signing files as private local material. The repository ignores .keys/ and common key filenames; execute src/setup_hooks.sh to install the pre-commit hook. The hook preserves the .keys/ guard and runs the PixeneOS secrets scanner. Install gitleaks as well for full standard scanner coverage matching CI.
-
To create and make the release:
. src/util_functions.sh && create_and_make_release
-
To call individual functions/commands:
. src/<file>.sh && <function_name>
To revert to stock GrapheneOS, LineageOS, or firmware:
-
Reboot into fastboot mode and unlock the bootloader. This will trigger a data wipe. Ensure data is backed up.
-
Erase the custom AVB public key:
fastboot erase avb_custom_key
-
Flash the stock firmware.
To know more about the projects used in this repository, refer to the following links:
- AFSR
- AlterInstaller
- AVBRoot
- BCR
- Custota
- GrapheneOS
- Magisk (or the repository configured in
env.toml) - MSD
- OEMUnlockOnBoot
- Rooted Graphene
Check the FAQs to learn about common issues faced by users and their solutions.
Per ADR-0003, new project-authored PixeneOS code is licensed under AGPL-3.0-or-later. Project-authored source files marked with AGPL SPDX identifiers follow that notice; see the root LICENSE file for the license text.
Third-party-derived code, dependencies, modules, tools, release artifacts, and downloaded components retain their upstream licenses and copyright notices. This repository previously used MIT-oriented root license wording with Copyright (c) 2024 Pa1NarK; that historical notice is preserved in LICENSE for provenance rather than silently erased.
- GrapheneOS -- for the OS
- Chenxiaolong -- for additional features and tools
- Rooted-Graphene -- for motivation and inspiration
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.