Metadata-Version: 2.4
Name: morphe-builder
Version: 1.0.2
Summary: Build verified Morphe-patched Android APKs and modules
Author: Mikel Villota
License-Expression: MIT
Project-URL: Documentation, https://villoh.github.io/morphe-builder/
Project-URL: Issues, https://github.com/Villoh/morphe-builder/issues
Project-URL: Repository, https://github.com/Villoh/morphe-builder
Keywords: android,apk,morphe,patcher,revanced
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: axml==0.0.2
Requires-Dist: goopdl==1.2.0
Provides-Extra: tui
Requires-Dist: textual==6.6.0; extra == "tui"
Requires-Dist: tomlkit==0.13.2; extra == "tui"
Dynamic: license-file

<!-- markdownlint-disable MD013 -->

<div align="center">

<img src="https://raw.githubusercontent.com/Villoh/morphe-builder/main/assets/logo.png" alt="Morphe Builder logo" width="220">

# Morphe Builder

**Verified Morphe-patched Android builds from stock Google Play splits, configured in one TOML file.**

[Documentation](https://villoh.github.io/morphe-builder/) | [GitHub Template guide](docs/template-usage.md) | [Releases](https://github.com/Villoh/morphe-builder/releases)

[![Daily verified update](https://img.shields.io/github/actions/workflow/status/Villoh/morphe-builder/daily-verified-build.yml?branch=main&style=for-the-badge&label=Daily%20update&labelColor=000000&logo=githubactions&logoColor=white)](https://github.com/Villoh/morphe-builder/actions/workflows/daily-verified-build.yml)
[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-FFFFFF?style=for-the-badge&labelColor=000000&logo=python&logoColor=white)](https://www.python.org/)
[![Java](https://img.shields.io/badge/java-21%2B-FFFFFF?style=for-the-badge&labelColor=000000&logo=openjdk&logoColor=white)](https://openjdk.org/)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json&style=for-the-badge&label=managed%20with&labelColor=000000&color=261230)](https://docs.astral.sh/uv/)

</div>

---

Morphe Builder downloads stock APK splits from Google Play, verifies their integrity and signing certificate, merges them, patches them with the [Morphe](https://github.com/MorpheApp/morphe-desktop) CLI, signs the result, and packages non-root APKs plus deterministic Magisk/KernelSU modules.

Every app, architecture, patch source, and tool version is declared in a single TOML file, so a fork can build its own release matrix in CI without touching Python code. Nothing untrusted is published: downloads stay in a temporary directory until manifest coverage, hashes, package identity, version, ABI, and signer all pass, and each published set carries deterministic provenance.

Google Play delivery uses pinned [`goopdl`](https://github.com/Villoh/goopdl) releases from PyPI, with verified `direct`, `archive`, APKMirror, and Uptodown fallbacks.

## Requirements

| Requirement | Version | Notes |
| --- | --- | --- |
| [`Python`](https://www.python.org/) | 3.11–3.13 | 3.11 minimum for standard-library `tomllib` |
| [`Java`](https://openjdk.org/) | 21+ | Required by Morphe CLI, APKEditor, and uber-apk-signer |
| [`uv`](https://docs.astral.sh/uv/) | latest | Only for development installs from source |

Windows and Linux are supported. See [`docs/security.md`](docs/security.md) for platform limits.

## Features

- **Verified downloads**: Google manifest coverage, safe paths, sizes, SHA-256 hashes, package identity, version, split requirements, ABI, and signing certificate are all checked before publication.
- **Signer pinning**: YouTube and YouTube Music require known Google signers; any app can pin its own `expected-signer` SHA-256.
- **Provider fallback**: fixed order `google-play → direct → archive → apkmirror → uptodown`; integrity failures stay terminal instead of falling through to a later provider.
- **Config-driven matrix**: apps, architectures, densities, patch sets, and module output live in TOML; no hardcoded app registry.
- **Per-app toolchains**: each app can override Morphe CLI version, patch source, and patch version, resolved and cached per `owner/repo@tag`.
- **Pinned, hashed tooling**: Morphe CLI, patches, APKEditor, and uber-apk-signer are resolved to exact tags with recorded asset hashes in `toolchain-provenance.json`.
- **Non-root APKs and modules**: deterministic Magisk/KernelSU modules that install matching stock, survive reboots, and receive updates from the Magisk app.
- **Reproducible provenance**: `provenance.json`, `build-summary.json`, `release-manifest.json`, and `CHANGELOG.md` record every source, version, and hash.
- **Atomic publication**: existing output is never overwritten; a failed job removes its temporary set and publishes nothing.
- **Verified CI releases**: daily update checks resolve upstream versions, skip unchanged builds, verify every artifact, and attach GitHub artifact attestations.
- **Secret-safe by design**: no committed credentials, no cached tokens or APK payloads, redacted error output.

## Build without installing

Use this repository as GitHub Template or fork, edit [`morphe.toml`](morphe.toml), and run **Manual verified build** from Actions. GitHub runner installs toolchain, builds verified artifacts, and uploads them without requiring Python, Java, `uv`, `pipx`, or Morphe Builder on your computer.

Start here: [`docs/template-usage.md`](docs/template-usage.md).

## Installation

Local installation is optional. Morphe Builder depends on reviewed, exactly pinned [`goopdl`](https://pypi.org/project/goopdl/) releases from PyPI.

Global tool install:

```bash
uv tool install morphe-builder
```

Into an existing environment:

```bash
python -m pip install morphe-builder
```

Development install from source:

```bash
git clone https://github.com/Villoh/morphe-builder
cd morphe-builder
uv sync
uv run morphe-builder --help
```

Optional local terminal UI:

```bash
uv sync --extra tui
uv run morphe-builder
```

With explicit options, use the compatible `tui` alias:

```bash
uv run morphe-builder tui --config morphe.toml --keystore /path/outside/repository/builder.p12
```

Textual remains optional: base installs keep the CLI available and print an install hint when the TUI is requested. The TUI has selectable full-width tables, a clickable/collapsible icon sidebar, right-click application actions, structured add/edit/remove forms for `morphe.toml`, and an auto-refreshing patch selector backed by each app's configured `patches-source`. Configurable patches expose a typed options modal. Saving writes exact `exclusive-patches` and scalar `patch-options`. Configuration writes use optional `tomlkit` round-trip editing, validate through `load_config`, and atomically replace the file so comments and formatting survive. The TUI also confirms and runs the existing build flow, shows redacted progress/results, and cleans disposable cache work. It never displays or stores credentials, provides web mode, embeds runtime PNG rendering, or hard-cancels work. Header and splash use dependency-free monochrome text art generated from `assets/logo.png`. Theme and keybinding preferences remain local-only, outside `morphe.toml` and build provenance. Normal CLI automation needs no TUI dependencies. See the [Terminal UI guide](docs/tui.md).

## TUI preview

<p align="center">
  <img src="https://raw.githubusercontent.com/Villoh/morphe-builder/main/assets/tui-preview.gif" alt="Morphe Builder TUI preview" width="900">
</p>

See the [Terminal UI guide](docs/tui.md) for installation, navigation, patch selection, build monitoring, and cache cleanup.

## Quick start

```bash
morphe-builder auth --arch arm64
morphe-builder tools
MORPHE_KEYSTORE_PASSWORD=secret morphe-builder build morphe.toml \
  --keystore /path/outside/repository/builder.p12 \
  --output build
```

`auth` stores Google Play credentials outside the repository, `tools` fetches and hashes the pinned Java toolchain, and `build` produces the full configured matrix from [`morphe.toml`](morphe.toml).

### Google Play access

Reliable direct downloads use two environment variables together:

| Variable | Value |
| --- | --- |
| `GOOPDL_ACCOUNT_EMAIL` | Dedicated Google account email. |
| `GOOPDL_AAS_TOKEN` | Persistent AAS token starting with `aas_et/`. |

A partial configuration fails closed. Without either value, `goopdl` uses the anonymous dispenser and the builder continues through configured verified fallbacks when Play is unavailable.

Generate a token through an isolated browser with `morphe-builder aastoken --browser`. No-computer option: [`goopdl-auth`](https://github.com/Villoh/goopdl-auth) helps create credentials without running Morphe Builder, `goopdl`, or Termux locally. Full local and CI setup: [`docs/authentication.md`](docs/authentication.md). Treat an AAS token as a password: never place it in TOML, argv, committed files, caches, artifacts, or logs.

### Other environment variables

| Variable | Used by | Purpose |
| --- | --- | --- |
| `MORPHE_KEYSTORE_PASSWORD` | `build` | Signing keystore password. Required; never accepted in argv. |
| `GOOPDL_BROWSER` | `aastoken --browser` | Browser executable when automatic detection fails. |
| `MORPHE_DISPENSER_URL` | `auth`, `download` | Custom anonymous dispenser, used only when no Google account is configured. |
| `MORPHE_MODULE_VERSION_CODE` | `build` | Monotonic module `versionCode`. CI passes the workflow run number. |
| `GITHUB_TOKEN` | `tools`, `build`, `list-patches` | Authenticates GitHub API calls. Never sent to asset downloads. |
| `HTTP_PROXY`, `HTTPS_PROXY` | `build` | Proxy for provider downloads. |
| `XDG_CACHE_HOME`, `LOCALAPPDATA` | all cached commands | Override the default cache root. |

## Configuration

Everything a fork needs to change lives in [`morphe.toml`](morphe.toml). No Python code has to be touched to build a different set of apps.

The file has one `[toolchain]` table with the default Morphe CLI and patch sources, then one ordered `[[apps]]` entry per app:

```toml
[toolchain]
morphe-source = "MorpheApp/morphe-desktop"
morphe-version = "latest"
patches-source = "MorpheApp/morphe-patches"
patches-version = "latest"
# Both default to false. Enable per app when needed instead.
include-universal-patches = false
include-experimental-versions = false

[[apps]]
package = "com.google.android.youtube"
name = "YouTube"
slug = "youtube"
patched-package = "app.morphe.android.youtube"
version = "auto"
build-mode = "apk"
arch = "arm64-v8a"
density = "xxhdpi"
include-patches = ["GmsCore support", "SponsorBlock"]
exclude-patches = []

[apps.patch-options."SponsorBlock"]
toast-on-connection-error = true

[apps.google-play]
profile = "D2"
country = "US"

[apps.fallbacks]
apkmirror = ["https://www.apkmirror.com/apk/google-inc/youtube/"]
```

Only `package` and `name` are required. Everything else has a documented default:

| Setting | Required | Default when omitted |
| --- | :---: | --- |
| `package` | yes | Validated Android package name. |
| `name` | yes | Display name used in release notes and modules. |
| `slug` | no | `package` with dots replaced by hyphens. Controls output, cache, and module names; must be unique. |
| `patched-package` | no | No package change is expected after patching. Set it when patches rename the package. |
| `version` | no | `auto`: highest version supported by every selected patch. Also accepts `latest` or an exact version name. |
| `version-code` | no | Resolved from the version name. Requires an explicit `version`; `auto` and `latest` are rejected with it. |
| `build-mode` | no | `apk`: non-root APK only. Use `module` or `both` for Magisk/KernelSU modules. |
| `arch` | no | `arm64-v8a`. Also `arm-v7a`, `all`, or `both` (two independent jobs). |
| `density` | no | The profile's only available density split is selected automatically. |
| `include-patches` | no | Empty: the patch bundle's default selection is used. |
| `exclude-patches` | no | Empty: nothing is removed from the selection. |
| `exclusive-patches` | no | Unset. When set, only these patches apply; cannot be combined with include/exclude. |
| `patch-options` | no | Patch defaults. Scalar values only, never shell-interpolated. Every option must belong to a selected patch. |
| `expected-signer` | no | Unpinned: a valid provider-served signer is accepted and logged as `unpinned signer accepted`. YouTube and YouTube Music always enforce built-in Google signer pins. |
| `include-universal-patches` | no | Inherits `[toolchain]` (`false`). Include Universal patches, such as `Change package name`, for this app. |
| `include-experimental-versions` | no | Inherits `[toolchain]` (`false`). Include experimental versions when resolving this app. |
| `expect-corrupted-version-code` | no | `false`. Explicit workaround for upstream patched-manifest bug: expect output `versionCode` `2147483647`, not stock code. |
| `google-play.profile` | no | `goopdl` picks a profile. Leave unset with `arch = "both"`, since one profile cannot cover both ABIs. |
| `google-play.country` | no | `goopdl` account region. |
| `google-play.proxy` | no | Direct connection. |
| `google-play.dispenser` | no | `MORPHE_DISPENSER_URL`, or the built-in public dispenser. |
| `fallbacks.*` | no | Empty: Google Play is the only source, so a Play failure fails the job. |
| `patches-source`, `patches-version`, `cli-source`, `morphe-version` | no | The `[toolchain]` values. |
| `patches-sha256` | no | Optional explicit SHA-256 pin for the patch asset; requires `patches-version` and still verifies downloaded bytes. |

`include-universal-patches` and `include-experimental-versions` may instead be set under `[toolchain]` to establish defaults for every app. Both default to `false`; app-level values override them. Universal patches do not constrain compatible-version selection. A selection containing only Universal patches fails because it cannot establish a source version.

`expect-corrupted-version-code` is intentionally narrow. Current `MorpheApp/morphe-patches` changes patched `versionCode` to `2147483647` while recompiling affected manifests. Enable it only for an affected app; provenance retains the real downloaded version code. It applies to both non-root APKs and root module payloads. Root modules always disable `Change package name` because they must retain stock package identity.

The `[toolchain]` table itself is optional; omitting it uses `MorpheApp/morphe-desktop` and `MorpheApp/morphe-patches` at `latest`.

Parsing is strict: unknown keys, duplicate packages, duplicate slugs, unsupported values, and conflicting patch selections are all rejected before any download starts. Run `morphe-builder list-patches morphe.toml` to see which version each app will resolve to before committing a change.

For CI-built releases, declare at least one `fallbacks` entry per app. Anonymous Google Play access is unreliable from hosted runners, and a verified APKMirror or Uptodown source keeps the build working without credentials.

Full field reference, version-resolution order, and fallback URL rules: [`docs/configuration.md`](docs/configuration.md).

## Usage

| Command | Purpose |
| --- | --- |
| `auth` | Authenticate against Google Play and cache the session outside the repository. |
| `download` | Download and verify one package's splits into an output directory. |
| `tools` | Resolve, download, and hash the pinned Java toolchain. |
| `list-patches` | Show patch-compatible versions for the configured apps. |
| `build` | Run the full configured matrix: download, merge, patch, sign, verify, package. |
| `verify` | Re-verify an existing split set against a package, architecture, and version. |
| `clean` | Remove the disposable work cache only; trusted sets and tools are preserved. |
| `tui` | Open optional local Textual dashboard for inspected, confirmed builds and cache cleanup. |

```bash
morphe-builder download com.example.app --arch arm64 --output downloads
morphe-builder list-patches morphe.toml
morphe-builder verify trusted/splits com.google.android.youtube \
  --arch arm64 --version-name 21.04.223
```

All commands accept `--json` (progress on stderr, machine-readable result on stdout). `--arch both` runs independent ARM64 and ARMv7 jobs. Keystore passwords come from `MORPHE_KEYSTORE_PASSWORD`, never argv. Exit codes are stable per failure category.

Full reference, exit-code table, and cache semantics: [`docs/cli.md`](docs/cli.md).

## Documentation

| Topic | Document |
| --- | --- |
| Installation and first build | [`docs/getting-started.md`](docs/getting-started.md) |
| GitHub Template/fork without local installation | [`docs/template-usage.md`](docs/template-usage.md) |
| System architecture and trust boundaries | [`docs/architecture.md`](docs/architecture.md) |
| Google Play authentication | [`docs/authentication.md`](docs/authentication.md) |
| CLI reference and exit codes | [`docs/cli.md`](docs/cli.md) |
| TOML configuration and version resolution | [`docs/configuration.md`](docs/configuration.md) |
| Toolchain cache, pins, and checksums | [`docs/toolchain.md`](docs/toolchain.md) |
| Google Play delivery and integrity checks | [`docs/google-play-integrity.md`](docs/google-play-integrity.md) |
| Provider fallback order | [`docs/providers.md`](docs/providers.md) |
| Split selection and merge policy | [`docs/merge.md`](docs/merge.md) |
| Signed APK build flow | [`docs/build.md`](docs/build.md) |
| Magisk/KernelSU module format | [`docs/modules.md`](docs/modules.md) |
| Threat model and platform limits | [`docs/security.md`](docs/security.md) |
| CI, release verification, and publication | [`docs/releases.md`](docs/releases.md) |
| Third-party licenses | [`docs/THIRD_PARTY.md`](docs/THIRD_PARTY.md) |
| Troubleshooting | [`docs/troubleshooting.md`](docs/troubleshooting.md) |
| Contributor guide | [`docs/contributing.md`](docs/contributing.md) |
| Repository rules for contributors and agents | [`docs/rules/README.md`](docs/rules/README.md) |
| Active roadmap | [`docs/roadmap/ROADMAP.md`](docs/roadmap/ROADMAP.md) |
| Completed milestones and evidence | [`docs/roadmap/COMPLETED.md`](docs/roadmap/COMPLETED.md) |

## License

Morphe Builder is released under the [MIT License](LICENSE). Downloaded Morphe tooling remains separately licensed under GPL-3.0 and is not bundled in the Python distribution. See [`docs/THIRD_PARTY.md`](docs/THIRD_PARTY.md).

## Credits

- [`MorpheApp/morphe-desktop`](https://github.com/MorpheApp/morphe-desktop) and [`MorpheApp/morphe-patches`](https://github.com/MorpheApp/morphe-patches): the patching CLI and patch bundles this builder orchestrates. Morphe does the patching; this project only prepares verified stock input and packages the output.
- [`j-hc/revanced-magisk-module`](https://github.com/j-hc/revanced-magisk-module): prior art for the Magisk/KernelSU module approach and CI build matrix. Reviewed as design context under GPL-3.0; no code is copied. See [`docs/modules.md`](docs/modules.md).
- [`REAndroid/APKEditor`](https://github.com/REAndroid/APKEditor) and [`patrickfav/uber-apk-signer`](https://github.com/patrickfav/uber-apk-signer): split merge and APK signing tools.
- [ReVanced](https://revanced.app/): the patch ecosystem Morphe builds on; ReVanced-compatible patch bundles can be selected per app via `patches-source`.
