{"id":26711228,"url":"https://github.com/PixelUpdater/PixelUpdater","last_synced_at":"2025-03-27T10:01:38.257Z","repository":{"id":197468825,"uuid":"698210383","full_name":"PixelUpdater/PixelUpdater","owner":"PixelUpdater","description":"Android A/B OTA updater app for rooted Pixels","archived":false,"fork":true,"pushed_at":"2025-03-17T14:30:12.000Z","size":1858,"stargazers_count":102,"open_issues_count":8,"forks_count":3,"subscribers_count":4,"default_branch":"development","last_synced_at":"2025-03-17T15:45:38.726Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"chenxiaolong/Custota","license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PixelUpdater.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-09-29T12:07:00.000Z","updated_at":"2025-03-13T21:26:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"50a7f42f-271b-404b-981e-6a4741bab36e","html_url":"https://github.com/PixelUpdater/PixelUpdater","commit_stats":null,"previous_names":["pixelupdater/pixelupdater"],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PixelUpdater%2FPixelUpdater","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PixelUpdater%2FPixelUpdater/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PixelUpdater%2FPixelUpdater/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PixelUpdater%2FPixelUpdater/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PixelUpdater","download_url":"https://codeload.github.com/PixelUpdater/PixelUpdater/tar.gz/refs/heads/development","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245823298,"owners_count":20678172,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-27T10:01:20.684Z","updated_at":"2025-03-27T10:01:38.199Z","avatar_url":"https://github.com/PixelUpdater.png","language":"Kotlin","funding_links":[],"categories":["📱 OS-Specific Customization"],"sub_categories":["Themes and Visual Mods"],"readme":"# Pixel Updater\n\n\u003cimg src=\"app/images/icon.svg\" alt=\"app icon\" width=\"72\" /\u003e\n\n![latest release badge](https://img.shields.io/github/v/release/PixelUpdater/PixelUpdater?sort=semver)\n![license badge](https://img.shields.io/github/license/PixelUpdater/PixelUpdater)\n\nPixel Updater is an app for installing Android A/B OTA updates from Google's OTA server.\n\nPixel Updater is installed via a Magisk module so that it can run as a system app.\n\n\u003cimg src=\"app/images/light.png\" alt=\"light mode screenshot\" width=\"200\" /\u003e \u003cimg src=\"app/images/dark.png\" alt=\"dark mode screenshot\" width=\"200\" /\u003e\n\nPixel Updater was forked from [Custota](https://github.com/chenxiaolong/Custota) by [Andrew Gunnerson](https://github.com/chenxiaolong)\n\n## Features\n\n* Supports Android 13 and newer.\n* Supports pausing, resuming, and cancelling updates.\n* Supports skipping optional post-install scripts to speed up updates.\n* Never communicates with any server besides Google's OTA server.\n* Patches with Magisk after an OTA by default\n* Syncs the verity and verification flags after an OTA by default.\n\n## Limitations\n\n* The device must be rooted with Magisk. KernelSU/APatch is not supported.\n* The device must be a Google Pixel and support A/B updates.\n* Incremental updates are not supported.\n* Pre-downloading an update to install later is not supported.\n  * Pixel Updater runs `update_engine` in streaming mode, which downloads and installs OTAs at the same time.\n* The stock OS' Settings app on Pixel devices always launches the builtin OTA updater.\n  * These shortcuts in the Settings app are loaded from GmsCore (part of Google Play Services) via a mechanism called \"settings slices\" and cannot be overridden. Apps that launch the OTA updater via the standard `android.settings.SYSTEM_UPDATE_SETTINGS` intent will show a prompt to pick between Pixel Updater or the builtin OTA updater.\n\n## Usage\n\n1. Download the latest version from the [releases page](https://github.com/PixelUpdater/PixelUpdater/releases). To verify the digital signature, see the [verifying digital signatures](#verifying-digital-signatures) section.\n\n2. Install the Pixel Updater module in Magisk.\n\n3. Reboot and open Pixel Updater.\n\n4. That's it!\n\nPixel Updater will optionally automatically check for updates periodically.\n\nTo reduce battery usage, the scheduling of the update checks is controlled by Android. They run at most once every 6 hours and will not run at all if the `Require unmetered network` or `Require sufficient battery level` conditions aren't met.\n\n## Permissions\n\n* `ACCESS_CACHE_FILESYSTEM` (**automatically granted by system app permissions**)\n  * Needed to store temporary OTA files.\n* `ACCESS_NETWORK_STATE` (**automatically granted at install time**)\n  * Needed on Android 14+ for unmetered network background run condition.\n* `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_SPECIAL_USE` (**automatically granted at install time**)\n  * Needed to run the OTA update service in the background.\n* `INTERNET` (**automatically granted at install time**)\n  * Needed to communicate with the OTA server. Pixel Updater **does not and will never** communicate with any server outside of Google's OTA server. There are no ads, analytics, or any sort of tracking.\n* `MANAGE_CARRIER_OEM_UNLOCK_STATE`, `MANAGE_USER_OEM_UNLOCK_STATE`, `READ_OEM_UNLOCK_STATE` (**automatically granted by system app permissions**)\n  * Needed to show the bootloader unlock status.\n* `POST_NOTIFICATIONS` (**must be granted by the user**)\n  * Android requires a notification to be shown in order for the updater service to reliably run in the background.\n* `REBOOT` (**automatically granted by system app permissions**)\n  * Needed to reboot the device when the user explicitly presses the reboot button in Pixel Updater's notification after an update is installed.\n* `RECEIVE_BOOT_COMPLETED` (**automatically granted at install time**)\n  * Needed to schedule periodic update checks.\n* `WAKE_LOCK` (**automatically granted at install time**)\n  * Needed to keep the CPU awake while an update is being installed.\n\n## Advanced features\n\n### Debug mode\n\nPixel Updater has hidden debug options that can be enabled or disabled by long pressing the version number.\n\n### Logs\n\nTo access the Pixel Updater's logs, enable debug mode and press `Open log directory` to open the log directory in the system file manager (DocumentsUI). Or alternatively, browse to `/sdcard/Android/data/com.github.pixelupdater.pixelupdater/files` manually.\n\n* `check.log`, `install.log`, and `revert.log`: Logs for the last check/install/revert operation.\n* `crash.log`: Logs for the last crash.\n* `magisk.log`: Logs for the Magisk patch operation.\n* `/data/local/tmp/pixelupdater_selinux.log`: Logs for the SELinux changes made during boot.\n  * This log cannot be saved to the normal log directory because it is written prior to the user unlocking the device for the first time after booting.\n\nWhen reporting bugs, please include the log files as it is extremely helpful for identifying what might be going wrong. The logs should contain no sensitive information.\n\n(To monitor `update_engine`'s own logs, run `adb logcat '*:S' update_engine`.)\n\n### Reinstallation\n\nFor testing, Pixel Updater can allow the current OS version (i.e. matching build fingerprint) to be reinstalled. To do so, enable debug mode and then enable the `Allow reinstall` toggle.\n\n### Reverting an update\n\nNormally, an update can be cancelled by pressing the `Cancel` button in the notification while it is being downloaded or installed. However, if the opportunity to do so was missed and the update has already been installed, the update can be cancelled by enabling debug mode and pressing the `Revert completed update` option. This stops the bootloader slot from being switched on reboot. Note that an update can only be reverted if the device hasn't been rebooted yet. Also note that revert does not undo the the changes made to the inactive slot; it simply prevents the device from switching slot on the next reboot.\n\n## How it works\n\nThe A/B update process in Android is handled by a builtin component called `update_engine`. The engine is used both for sideloading OTA updates when booted into recovery mode and for regular OTA updates while booted into Android. It is responsible for checking the signature of `payload.bin` inside the OTA zip against `/system/etc/security/otacerts.zip`, verifying existing partition checksums (for incremental updates), and then installing the payload. It also handles the download process when used in the streaming mode, which is always requested by Pixel Updater. Pixel Updater itself is responsible for the other parts, such as checking for updates, verifying compatibility, and preventing downgrades (which could cause the device to be unbootable due to Android Verified Boot's rollback index mechanism).\n\nIn order for Pixel Updater to talk to `update_engine` or even discover that the component exists, the SELinux policy must be modified to allow this access. The module ships with a [script](./app/module/post-fs-data.sh) that these modifications (non-persistently) on boot.\n\nThere are two parts to the SELinux changes:\n\n1. There's a [`pixelupdater_selinux` native executable](./app/src/main/cpp/pixelupdater_selinux) that performs all of the policy modifications. It takes the `untrusted_app` domain and makes a copy of it as `pixelupdater_app`. Then, it adds the relevant rules to allow only `pixelupdater_app` to access `update_engine`. The domain is copied from `untrusted_app` instead of the normal `priv_app` domain that is assigned to system apps because Pixel Updater does not require any of the additional privileges that would have been granted by `priv_app`.\n\n2. An `seapp_contexts` rule is added to `/system/etc/selinux/plat_seapp_contexts`, which actually sets up the association between Pixel Updater (app package ID: `com.github.pixelupdater.pixelupdater`) and the new SELinux domain (`pixelupdater_app`).\n\nThese changes help limit Pixel Updater's privileges to exactly what is needed and avoids potentially increasing the attack surface via other apps.\n\n### Update process\n\nWhen Pixel Updater checks for updates, it will:\n\n1. Download the `metadata.pb` entry and validate the fields to determine if the OTA is suitable for installation on the device.\n2. Show the update prompt if the OTA fingerprint does not match the running system and the security patch level is newer than the running system.\n\nWhen the user chooses to install an update, Pixel Updater will repeat the first step above and then:\n\n1. Download the `payload_metadata.bin` entry (`payload.bin`'s header), write it to `/data/ota_package/payload_metadata.bin`, and ask `update_engine` to verify that the listed partition operations are suitable to be performed on the current device.\n2. Download `care_map.pb` and write it to `/data/ota_package/care_map.pb`. This is unused for devices with virtual A/B partitioning (ie. snapuserd).\n3. Download `payload_properties.txt`, parse the payload digests, and ask `update_engine` to download and install `payload.bin`.\n\n## Verifying digital signatures\n\nBoth the zip file and the APK contained within are digitally signed.\n\n### Verifying zip file signature\n\nFirst, save the public key to a file listing the keys to be trusted.\n\n```bash\necho pixelupdater ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMhOIXQSss8MibtGMGOyvJHeB3e7osngI5nsLLU3H55P \u003e pixelupdater_trusted_keys\n```\n\nThen, verify the signature of the zip file using the list of trusted keys.\n\n```bash\nssh-keygen -Y verify -f pixelupdater_trusted_keys -I pixelupdater -n file -s PixelUpdater-\u003cversion\u003e-release.zip.sig \u003c PixelUpdater-\u003cversion\u003e-release.zip\n```\n\nIf the file is successfully verified, the output will be:\n\n```text\nGood \"file\" signature for pixelupdater with ED25519 key SHA256:1cqrYVWgvt/jDU+RvaCJHpdET6yZHEInmLPUy2PDK5A\n```\n\n### Verifying apk signature\n\nFirst, extract the apk from the zip and then run:\n\n```text\napksigner verify --print-certs system/priv-app/com.github.pixelupdater.pixelupdater/app-release.apk\n```\n\nThen, check that the SHA-256 digest of the APK signing certificate is:\n\n```text\n766538c98337dae57f6c1e15f2c5ca89c62b25c4ba49b06a244c96f6e0d5e957\n```\n\n## Building from source\n\n### Building app and module\n\nPixel Updater can be built like most other Android apps using Android Studio or the gradle command line.\n\nTo build the APK:\n\n```bash\n./gradlew assembleDebug\n```\n\nTo build the Magisk module zip (which automatically runs the `assembleDebug` task if needed):\n\n```bash\n./gradlew zipDebug\n```\n\nThe output file is written to `app/build/distributions/debug/`. The APK will be signed with the default autogenerated debug key.\n\nTo create a release build with a specific signing key, set up the following environment variables:\n\n```bash\nexport RELEASE_KEYSTORE=/path/to/keystore.jks\nexport RELEASE_KEY_ALIAS=alias_name\n\nread -r -s RELEASE_KEYSTORE_PASSPHRASE\nread -r -s RELEASE_KEY_PASSPHRASE\nexport RELEASE_KEYSTORE_PASSPHRASE\nexport RELEASE_KEY_PASSPHRASE\n```\n\nand then build the release zip:\n\n```bash\n./gradlew zipRelease\n```\n\n## Contributing\n\nBug fix and translation pull requests are welcome and much appreciated!\n\nIf you are interested in implementing a new feature and would like to see it included in Pixel Updater, please open an issue to discuss it first. We intend for Pixel Updater to be as simple and low-maintenance as possible, so we are not too inclined to add new features, but we could be convinced otherwise.\n\n## License\n\nPixel Updater is licensed under GPLv3. Please see [`LICENSE`](./LICENSE) for the full license text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FPixelUpdater%2FPixelUpdater","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FPixelUpdater%2FPixelUpdater","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FPixelUpdater%2FPixelUpdater/lists"}