morphe-builder 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. morphe_builder-0.1.0/LICENSE +21 -0
  2. morphe_builder-0.1.0/PKG-INFO +298 -0
  3. morphe_builder-0.1.0/README.md +272 -0
  4. morphe_builder-0.1.0/pyproject.toml +50 -0
  5. morphe_builder-0.1.0/setup.cfg +4 -0
  6. morphe_builder-0.1.0/src/morphe_builder/__init__.py +839 -0
  7. morphe_builder-0.1.0/src/morphe_builder/apkmirror.py +261 -0
  8. morphe_builder-0.1.0/src/morphe_builder/build.py +468 -0
  9. morphe_builder-0.1.0/src/morphe_builder/compatibility.py +450 -0
  10. morphe_builder-0.1.0/src/morphe_builder/config.py +363 -0
  11. morphe_builder-0.1.0/src/morphe_builder/merge.py +401 -0
  12. morphe_builder-0.1.0/src/morphe_builder/module.py +554 -0
  13. morphe_builder-0.1.0/src/morphe_builder/orchestrator.py +644 -0
  14. morphe_builder-0.1.0/src/morphe_builder/providers.py +371 -0
  15. morphe_builder-0.1.0/src/morphe_builder/release.py +409 -0
  16. morphe_builder-0.1.0/src/morphe_builder/toolchain.py +562 -0
  17. morphe_builder-0.1.0/src/morphe_builder/uptodown.py +283 -0
  18. morphe_builder-0.1.0/src/morphe_builder.egg-info/PKG-INFO +298 -0
  19. morphe_builder-0.1.0/src/morphe_builder.egg-info/SOURCES.txt +34 -0
  20. morphe_builder-0.1.0/src/morphe_builder.egg-info/dependency_links.txt +1 -0
  21. morphe_builder-0.1.0/src/morphe_builder.egg-info/entry_points.txt +2 -0
  22. morphe_builder-0.1.0/src/morphe_builder.egg-info/requires.txt +2 -0
  23. morphe_builder-0.1.0/src/morphe_builder.egg-info/top_level.txt +1 -0
  24. morphe_builder-0.1.0/tests/test_apkmirror.py +183 -0
  25. morphe_builder-0.1.0/tests/test_build.py +474 -0
  26. morphe_builder-0.1.0/tests/test_configuration_and_compatibility.py +391 -0
  27. morphe_builder-0.1.0/tests/test_merge.py +323 -0
  28. morphe_builder-0.1.0/tests/test_module.py +273 -0
  29. morphe_builder-0.1.0/tests/test_morphe_builder.py +674 -0
  30. morphe_builder-0.1.0/tests/test_orchestrator.py +349 -0
  31. morphe_builder-0.1.0/tests/test_providers.py +218 -0
  32. morphe_builder-0.1.0/tests/test_release.py +183 -0
  33. morphe_builder-0.1.0/tests/test_security.py +152 -0
  34. morphe_builder-0.1.0/tests/test_toolchain.py +217 -0
  35. morphe_builder-0.1.0/tests/test_uptodown.py +173 -0
  36. morphe_builder-0.1.0/tests/test_workflows.py +150 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mikel Villota
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,298 @@
1
+ Metadata-Version: 2.4
2
+ Name: morphe-builder
3
+ Version: 0.1.0
4
+ Summary: Build verified Morphe-patched Android APKs and modules
5
+ Author: Mikel Villota
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/Villoh/morphe-builder#readme
8
+ Project-URL: Issues, https://github.com/Villoh/morphe-builder/issues
9
+ Project-URL: Repository, https://github.com/Villoh/morphe-builder
10
+ Keywords: android,apk,morphe,patcher,revanced
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Requires-Python: <3.14,>=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: apkutils2==1.0.0
24
+ Requires-Dist: gplaydl==0.0.0
25
+ Dynamic: license-file
26
+
27
+ <!-- markdownlint-disable MD013 -->
28
+
29
+ <div align="center">
30
+
31
+ # Morphe Builder
32
+
33
+ **Verified Morphe-patched Android builds from stock Google Play splits, configured in one TOML file.**
34
+
35
+ [![Daily verified update](https://img.shields.io/github/actions/workflow/status/Villoh/morphe-builder/daily-update.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-update.yml)
36
+ [![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/)
37
+ [![Java](https://img.shields.io/badge/java-21%2B-FFFFFF?style=for-the-badge&labelColor=000000&logo=openjdk&logoColor=white)](https://openjdk.org/)
38
+ [![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/)
39
+
40
+ </div>
41
+
42
+ ---
43
+
44
+ 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.
45
+
46
+ 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.
47
+
48
+ Google Play delivery uses [`gplaydl`](https://github.com/Villoh/gplaydl) (branch `integration`), with verified `direct`, `archive`, APKMirror, and Uptodown fallbacks.
49
+
50
+ ## Requirements
51
+
52
+ | Requirement | Version | Notes |
53
+ | --- | --- | --- |
54
+ | [`Python`](https://www.python.org/) | 3.11–3.13 | 3.11 minimum for standard-library `tomllib` |
55
+ | [`Java`](https://openjdk.org/) | 21+ | Required by Morphe CLI, APKEditor, and uber-apk-signer |
56
+ | [`uv`](https://docs.astral.sh/uv/) | latest | Only for development installs from source |
57
+
58
+ Windows and Linux are supported. See [`docs/security.md`](docs/security.md) for platform limits.
59
+
60
+ ## Features
61
+
62
+ - **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.
63
+ - **Signer pinning**: YouTube and YouTube Music require known Google signers; any app can pin its own `expected-signer` SHA-256.
64
+ - **Provider fallback**: fixed order `google-play → direct → archive → apkmirror → uptodown`; integrity failures stay terminal instead of falling through to a later provider.
65
+ - **Config-driven matrix**: apps, architectures, densities, patch sets, and module output live in TOML; no hardcoded app registry.
66
+ - **Per-app toolchains**: each app can override Morphe CLI version, patch source, and patch version, resolved and cached per `owner/repo@tag`.
67
+ - **Pinned, hashed tooling**: Morphe CLI, patches, APKEditor, and uber-apk-signer are resolved to exact tags with recorded asset hashes in `toolchain-provenance.json`.
68
+ - **Non-root APKs and modules**: deterministic Magisk/KernelSU modules that install matching stock, survive reboots, and receive updates from the Magisk app.
69
+ - **Reproducible provenance**: `provenance.json`, `build-summary.json`, `release-manifest.json`, and `CHANGELOG.md` record every source, version, and hash.
70
+ - **Atomic publication**: existing output is never overwritten; a failed job removes its temporary set and publishes nothing.
71
+ - **Verified CI releases**: daily update checks resolve upstream versions, skip unchanged builds, verify every artifact, and attach GitHub artifact attestations.
72
+ - **Secret-safe by design**: no committed credentials, no cached tokens or APK payloads, redacted error output.
73
+
74
+ ## Installation
75
+
76
+ Morphe Builder pins a reviewed `gplaydl` fork commit. PyPI cannot encode VCS dependency sources, and the unrelated upstream `gplaydl` package is incompatible, so install both packages in one resolver transaction.
77
+
78
+ Global tool install:
79
+
80
+ ```bash
81
+ uv tool install \
82
+ --with "gplaydl @ git+https://github.com/Villoh/gplaydl.git@de7dfba240b09bc986523c1637ac672b0c295980" \
83
+ morphe-builder
84
+ ```
85
+
86
+ Into an existing environment:
87
+
88
+ ```bash
89
+ python -m pip install \
90
+ morphe-builder \
91
+ "gplaydl @ git+https://github.com/Villoh/gplaydl.git@de7dfba240b09bc986523c1637ac672b0c295980"
92
+ ```
93
+
94
+ Do not install `morphe-builder` without the pinned override: its `gplaydl==0.0.0` package requirement deliberately fails closed instead of selecting the incompatible PyPI distribution.
95
+
96
+ Development install from source:
97
+
98
+ ```bash
99
+ git clone https://github.com/Villoh/morphe-builder
100
+ cd morphe-builder
101
+ uv sync
102
+ uv run morphe-builder --help
103
+ ```
104
+
105
+ ## Quick start
106
+
107
+ ```bash
108
+ morphe-builder auth --arch arm64
109
+ morphe-builder tools
110
+ MORPHE_KEYSTORE_PASSWORD=secret morphe-builder build morphe.toml \
111
+ --keystore /path/outside/repository/builder.p12 \
112
+ --output build
113
+ ```
114
+
115
+ `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).
116
+
117
+ ### Google Play access
118
+
119
+ Set these two together to download directly from Google's servers through the [`gplaydl`](https://github.com/Villoh/gplaydl) fork. This is the recommended setup: it is the only reliable path from CI runners, and it avoids the shared anonymous dispenser entirely.
120
+
121
+ | Variable | Value |
122
+ | --- | --- |
123
+ | `GPLAYDL_ACCOUNT_EMAIL` | Google account email used for Play authentication. |
124
+ | `GPLAYDL_AAS_TOKEN` | Persistent AAS token for that account. Must start with `aas_et/`. |
125
+
126
+ A value starting with `g.a` is a temporary bearer token, not an AAS token, and is rejected. Both variables are required together; a partial configuration fails instead of silently falling back. Treat the token as a password: use a dedicated account, keep it only in environment variables or GitHub Environment secrets, and never place it in TOML, argv, or committed files. Changing the account password invalidates it.
127
+
128
+ <details>
129
+ <summary><b>How to generate an AAS token with <code>gplaydl</code></b></summary>
130
+
131
+ `gplaydl aastoken` prints a Google account AAS token. Credentials and tokens are used in memory only; no `credentials.txt` or `token.txt` file is created.
132
+
133
+ **OAuth mode (recommended)**
134
+
135
+ ```bash
136
+ gplaydl aastoken --oauth
137
+ ```
138
+
139
+ Get the required one-time `oauth_token`:
140
+
141
+ 1. Open Google [EmbeddedSetup](https://accounts.google.com/EmbeddedSetup).
142
+ 2. Sign in and select **I agree**.
143
+ 3. The page may stay frozen or loading forever. This is expected; the cookie is normally already available.
144
+ 4. Open browser developer tools (<kbd>F12</kbd>).
145
+ 5. In Chrome or Edge, open **Application › Cookies › `https://accounts.google.com`**.
146
+ 6. Copy the value of the `oauth_token` cookie. It starts with `oauth2_4/`.
147
+ 7. Run `gplaydl aastoken --oauth`, enter the same email, then paste the token. Both inputs are handled interactively and the token is hidden.
148
+
149
+ The `oauth_token` is short-lived and single-use. The resulting AAS token is printed to the console.
150
+
151
+ **Password or app-password mode**
152
+
153
+ ```bash
154
+ # Interactive: asks for email and hides password input
155
+ gplaydl aastoken
156
+
157
+ # Arguments: convenient, but password remains in shell history
158
+ gplaydl aastoken user@example.com password
159
+ ```
160
+
161
+ Google commonly rejects both account passwords and 16-character app passwords with `BadAuthentication`. Keep 2-Step Verification enabled and use OAuth mode instead.
162
+
163
+ </details>
164
+
165
+ Without these variables, `gplaydl` falls back to the public anonymous dispenser, which is frequently blocked from datacenter IP ranges. In that case the builder continues through the verified APKMirror and Uptodown fallbacks declared in your configuration.
166
+
167
+ ### Other environment variables
168
+
169
+ | Variable | Used by | Purpose |
170
+ | --- | --- | --- |
171
+ | `MORPHE_KEYSTORE_PASSWORD` | `build` | Signing keystore password. Required; never accepted in argv. |
172
+ | `MORPHE_DISPENSER_URL` | `auth`, `download` | Custom anonymous dispenser, used only when no Google account is configured. |
173
+ | `MORPHE_MODULE_VERSION_CODE` | `build` | Monotonic module `versionCode`. CI passes the workflow run number. |
174
+ | `GITHUB_TOKEN` | `tools`, `build`, `list-patches` | Authenticates GitHub API calls. Never sent to asset downloads. |
175
+ | `HTTP_PROXY`, `HTTPS_PROXY` | `build` | Proxy for provider downloads. |
176
+ | `XDG_CACHE_HOME`, `LOCALAPPDATA` | all cached commands | Override the default cache root. |
177
+
178
+ ## Configuration
179
+
180
+ 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.
181
+
182
+ The file has one `[toolchain]` table with the default Morphe CLI and patch sources, then one ordered `[[apps]]` entry per app:
183
+
184
+ ```toml
185
+ [toolchain]
186
+ morphe-source = "MorpheApp/morphe-desktop"
187
+ morphe-version = "latest"
188
+ patches-source = "MorpheApp/morphe-patches"
189
+ patches-version = "latest"
190
+
191
+ [[apps]]
192
+ package = "com.google.android.youtube"
193
+ name = "YouTube"
194
+ slug = "youtube"
195
+ patched-package = "app.morphe.android.youtube"
196
+ version = "auto"
197
+ build-mode = "apk"
198
+ arch = "arm64-v8a"
199
+ density = "xxhdpi"
200
+ include-patches = ["GmsCore support", "SponsorBlock"]
201
+ exclude-patches = []
202
+
203
+ [apps.patch-options."SponsorBlock"]
204
+ toast-on-connection-error = true
205
+
206
+ [apps.google-play]
207
+ profile = "D2"
208
+ country = "US"
209
+
210
+ [apps.fallbacks]
211
+ apkmirror = ["https://www.apkmirror.com/apk/google-inc/youtube/"]
212
+ ```
213
+
214
+ Only `package` and `name` are required. Everything else has a documented default:
215
+
216
+ | Setting | Required | Default when omitted |
217
+ | --- | :---: | --- |
218
+ | `package` | yes | Validated Android package name. |
219
+ | `name` | yes | Display name used in release notes and modules. |
220
+ | `slug` | no | `package` with dots replaced by hyphens. Controls output, cache, and module names; must be unique. |
221
+ | `patched-package` | no | No package change is expected after patching. Set it when patches rename the package. |
222
+ | `version` | no | `auto`: highest version supported by every selected patch. Also accepts `latest` or an exact version name. |
223
+ | `version-code` | no | Resolved from the version name. Requires an explicit `version`; `auto` and `latest` are rejected with it. |
224
+ | `build-mode` | no | `apk`: non-root APK only. Use `module` or `both` for Magisk/KernelSU modules. |
225
+ | `arch` | no | `arm64-v8a`. Also `arm-v7a`, `all`, or `both` (two independent jobs). |
226
+ | `density` | no | The profile's only available density split is selected automatically. |
227
+ | `include-patches` | no | Empty: the patch bundle's default selection is used. |
228
+ | `exclude-patches` | no | Empty: nothing is removed from the selection. |
229
+ | `exclusive-patches` | no | Unset. When set, only these patches apply; cannot be combined with include/exclude. |
230
+ | `patch-options` | no | Patch defaults. Scalar values only, never shell-interpolated. Every option must belong to a selected patch. |
231
+ | `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. |
232
+ | `google-play.profile` | no | `gplaydl` picks a profile. Leave unset with `arch = "both"`, since one profile cannot cover both ABIs. |
233
+ | `google-play.country` | no | `gplaydl` account region. |
234
+ | `google-play.proxy` | no | Direct connection. |
235
+ | `google-play.dispenser` | no | `MORPHE_DISPENSER_URL`, or the built-in public dispenser. |
236
+ | `fallbacks.*` | no | Empty: Google Play is the only source, so a Play failure fails the job. |
237
+ | `patches-source`, `patches-version`, `cli-source`, `morphe-version` | no | The `[toolchain]` values. |
238
+
239
+ The `[toolchain]` table itself is optional; omitting it uses `MorpheApp/morphe-desktop` and `MorpheApp/morphe-patches` at `latest`.
240
+
241
+ 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.
242
+
243
+ 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.
244
+
245
+ Full field reference, version-resolution order, and fallback URL rules: [`docs/configuration.md`](docs/configuration.md).
246
+
247
+ ## Usage
248
+
249
+ | Command | Purpose |
250
+ | --- | --- |
251
+ | `auth` | Authenticate against Google Play and cache the session outside the repository. |
252
+ | `download` | Download and verify one package's splits into an output directory. |
253
+ | `tools` | Resolve, download, and hash the pinned Java toolchain. |
254
+ | `list-patches` | Show patch-compatible versions for the configured apps. |
255
+ | `build` | Run the full configured matrix: download, merge, patch, sign, verify, package. |
256
+ | `verify` | Re-verify an existing split set against a package, architecture, and version. |
257
+ | `clean` | Remove the disposable work cache only; trusted sets and tools are preserved. |
258
+
259
+ ```bash
260
+ morphe-builder download com.example.app --arch arm64 --output downloads
261
+ morphe-builder list-patches morphe.toml
262
+ morphe-builder verify trusted/splits com.google.android.youtube \
263
+ --arch arm64 --version-name 21.04.223
264
+ ```
265
+
266
+ 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.
267
+
268
+ Full reference, exit-code table, and cache semantics: [`docs/cli.md`](docs/cli.md).
269
+
270
+ ## Documentation
271
+
272
+ | Topic | Document |
273
+ | --- | --- |
274
+ | CLI reference and exit codes | [`docs/cli.md`](docs/cli.md) |
275
+ | TOML configuration and version resolution | [`docs/configuration.md`](docs/configuration.md) |
276
+ | Toolchain cache, pins, and checksums | [`docs/toolchain.md`](docs/toolchain.md) |
277
+ | Google Play delivery and integrity checks | [`docs/google-play-integrity.md`](docs/google-play-integrity.md) |
278
+ | Provider fallback order | [`docs/providers.md`](docs/providers.md) |
279
+ | Split selection and merge policy | [`docs/merge.md`](docs/merge.md) |
280
+ | Signed APK build flow | [`docs/build.md`](docs/build.md) |
281
+ | Magisk/KernelSU module format | [`docs/modules.md`](docs/modules.md) |
282
+ | Threat model and platform limits | [`docs/security.md`](docs/security.md) |
283
+ | CI, release verification, and publication | [`docs/releases.md`](docs/releases.md) |
284
+ | Third-party licenses | [`docs/THIRD_PARTY.md`](docs/THIRD_PARTY.md) |
285
+ | Repository rules for contributors and agents | [`docs/rules/README.md`](docs/rules/README.md) |
286
+ | Active roadmap | [`docs/roadmap/ROADMAP.md`](docs/roadmap/ROADMAP.md) |
287
+ | Completed milestones and evidence | [`docs/roadmap/COMPLETED.md`](docs/roadmap/COMPLETED.md) |
288
+
289
+ ## License
290
+
291
+ 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).
292
+
293
+ ## Credits
294
+
295
+ - [`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.
296
+ - [`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).
297
+ - [`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.
298
+ - [ReVanced](https://revanced.app/): the patch ecosystem Morphe builds on; ReVanced-compatible patch bundles can be selected per app via `patches-source`.
@@ -0,0 +1,272 @@
1
+ <!-- markdownlint-disable MD013 -->
2
+
3
+ <div align="center">
4
+
5
+ # Morphe Builder
6
+
7
+ **Verified Morphe-patched Android builds from stock Google Play splits, configured in one TOML file.**
8
+
9
+ [![Daily verified update](https://img.shields.io/github/actions/workflow/status/Villoh/morphe-builder/daily-update.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-update.yml)
10
+ [![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/)
11
+ [![Java](https://img.shields.io/badge/java-21%2B-FFFFFF?style=for-the-badge&labelColor=000000&logo=openjdk&logoColor=white)](https://openjdk.org/)
12
+ [![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/)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ 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.
19
+
20
+ 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.
21
+
22
+ Google Play delivery uses [`gplaydl`](https://github.com/Villoh/gplaydl) (branch `integration`), with verified `direct`, `archive`, APKMirror, and Uptodown fallbacks.
23
+
24
+ ## Requirements
25
+
26
+ | Requirement | Version | Notes |
27
+ | --- | --- | --- |
28
+ | [`Python`](https://www.python.org/) | 3.11–3.13 | 3.11 minimum for standard-library `tomllib` |
29
+ | [`Java`](https://openjdk.org/) | 21+ | Required by Morphe CLI, APKEditor, and uber-apk-signer |
30
+ | [`uv`](https://docs.astral.sh/uv/) | latest | Only for development installs from source |
31
+
32
+ Windows and Linux are supported. See [`docs/security.md`](docs/security.md) for platform limits.
33
+
34
+ ## Features
35
+
36
+ - **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.
37
+ - **Signer pinning**: YouTube and YouTube Music require known Google signers; any app can pin its own `expected-signer` SHA-256.
38
+ - **Provider fallback**: fixed order `google-play → direct → archive → apkmirror → uptodown`; integrity failures stay terminal instead of falling through to a later provider.
39
+ - **Config-driven matrix**: apps, architectures, densities, patch sets, and module output live in TOML; no hardcoded app registry.
40
+ - **Per-app toolchains**: each app can override Morphe CLI version, patch source, and patch version, resolved and cached per `owner/repo@tag`.
41
+ - **Pinned, hashed tooling**: Morphe CLI, patches, APKEditor, and uber-apk-signer are resolved to exact tags with recorded asset hashes in `toolchain-provenance.json`.
42
+ - **Non-root APKs and modules**: deterministic Magisk/KernelSU modules that install matching stock, survive reboots, and receive updates from the Magisk app.
43
+ - **Reproducible provenance**: `provenance.json`, `build-summary.json`, `release-manifest.json`, and `CHANGELOG.md` record every source, version, and hash.
44
+ - **Atomic publication**: existing output is never overwritten; a failed job removes its temporary set and publishes nothing.
45
+ - **Verified CI releases**: daily update checks resolve upstream versions, skip unchanged builds, verify every artifact, and attach GitHub artifact attestations.
46
+ - **Secret-safe by design**: no committed credentials, no cached tokens or APK payloads, redacted error output.
47
+
48
+ ## Installation
49
+
50
+ Morphe Builder pins a reviewed `gplaydl` fork commit. PyPI cannot encode VCS dependency sources, and the unrelated upstream `gplaydl` package is incompatible, so install both packages in one resolver transaction.
51
+
52
+ Global tool install:
53
+
54
+ ```bash
55
+ uv tool install \
56
+ --with "gplaydl @ git+https://github.com/Villoh/gplaydl.git@de7dfba240b09bc986523c1637ac672b0c295980" \
57
+ morphe-builder
58
+ ```
59
+
60
+ Into an existing environment:
61
+
62
+ ```bash
63
+ python -m pip install \
64
+ morphe-builder \
65
+ "gplaydl @ git+https://github.com/Villoh/gplaydl.git@de7dfba240b09bc986523c1637ac672b0c295980"
66
+ ```
67
+
68
+ Do not install `morphe-builder` without the pinned override: its `gplaydl==0.0.0` package requirement deliberately fails closed instead of selecting the incompatible PyPI distribution.
69
+
70
+ Development install from source:
71
+
72
+ ```bash
73
+ git clone https://github.com/Villoh/morphe-builder
74
+ cd morphe-builder
75
+ uv sync
76
+ uv run morphe-builder --help
77
+ ```
78
+
79
+ ## Quick start
80
+
81
+ ```bash
82
+ morphe-builder auth --arch arm64
83
+ morphe-builder tools
84
+ MORPHE_KEYSTORE_PASSWORD=secret morphe-builder build morphe.toml \
85
+ --keystore /path/outside/repository/builder.p12 \
86
+ --output build
87
+ ```
88
+
89
+ `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).
90
+
91
+ ### Google Play access
92
+
93
+ Set these two together to download directly from Google's servers through the [`gplaydl`](https://github.com/Villoh/gplaydl) fork. This is the recommended setup: it is the only reliable path from CI runners, and it avoids the shared anonymous dispenser entirely.
94
+
95
+ | Variable | Value |
96
+ | --- | --- |
97
+ | `GPLAYDL_ACCOUNT_EMAIL` | Google account email used for Play authentication. |
98
+ | `GPLAYDL_AAS_TOKEN` | Persistent AAS token for that account. Must start with `aas_et/`. |
99
+
100
+ A value starting with `g.a` is a temporary bearer token, not an AAS token, and is rejected. Both variables are required together; a partial configuration fails instead of silently falling back. Treat the token as a password: use a dedicated account, keep it only in environment variables or GitHub Environment secrets, and never place it in TOML, argv, or committed files. Changing the account password invalidates it.
101
+
102
+ <details>
103
+ <summary><b>How to generate an AAS token with <code>gplaydl</code></b></summary>
104
+
105
+ `gplaydl aastoken` prints a Google account AAS token. Credentials and tokens are used in memory only; no `credentials.txt` or `token.txt` file is created.
106
+
107
+ **OAuth mode (recommended)**
108
+
109
+ ```bash
110
+ gplaydl aastoken --oauth
111
+ ```
112
+
113
+ Get the required one-time `oauth_token`:
114
+
115
+ 1. Open Google [EmbeddedSetup](https://accounts.google.com/EmbeddedSetup).
116
+ 2. Sign in and select **I agree**.
117
+ 3. The page may stay frozen or loading forever. This is expected; the cookie is normally already available.
118
+ 4. Open browser developer tools (<kbd>F12</kbd>).
119
+ 5. In Chrome or Edge, open **Application › Cookies › `https://accounts.google.com`**.
120
+ 6. Copy the value of the `oauth_token` cookie. It starts with `oauth2_4/`.
121
+ 7. Run `gplaydl aastoken --oauth`, enter the same email, then paste the token. Both inputs are handled interactively and the token is hidden.
122
+
123
+ The `oauth_token` is short-lived and single-use. The resulting AAS token is printed to the console.
124
+
125
+ **Password or app-password mode**
126
+
127
+ ```bash
128
+ # Interactive: asks for email and hides password input
129
+ gplaydl aastoken
130
+
131
+ # Arguments: convenient, but password remains in shell history
132
+ gplaydl aastoken user@example.com password
133
+ ```
134
+
135
+ Google commonly rejects both account passwords and 16-character app passwords with `BadAuthentication`. Keep 2-Step Verification enabled and use OAuth mode instead.
136
+
137
+ </details>
138
+
139
+ Without these variables, `gplaydl` falls back to the public anonymous dispenser, which is frequently blocked from datacenter IP ranges. In that case the builder continues through the verified APKMirror and Uptodown fallbacks declared in your configuration.
140
+
141
+ ### Other environment variables
142
+
143
+ | Variable | Used by | Purpose |
144
+ | --- | --- | --- |
145
+ | `MORPHE_KEYSTORE_PASSWORD` | `build` | Signing keystore password. Required; never accepted in argv. |
146
+ | `MORPHE_DISPENSER_URL` | `auth`, `download` | Custom anonymous dispenser, used only when no Google account is configured. |
147
+ | `MORPHE_MODULE_VERSION_CODE` | `build` | Monotonic module `versionCode`. CI passes the workflow run number. |
148
+ | `GITHUB_TOKEN` | `tools`, `build`, `list-patches` | Authenticates GitHub API calls. Never sent to asset downloads. |
149
+ | `HTTP_PROXY`, `HTTPS_PROXY` | `build` | Proxy for provider downloads. |
150
+ | `XDG_CACHE_HOME`, `LOCALAPPDATA` | all cached commands | Override the default cache root. |
151
+
152
+ ## Configuration
153
+
154
+ 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.
155
+
156
+ The file has one `[toolchain]` table with the default Morphe CLI and patch sources, then one ordered `[[apps]]` entry per app:
157
+
158
+ ```toml
159
+ [toolchain]
160
+ morphe-source = "MorpheApp/morphe-desktop"
161
+ morphe-version = "latest"
162
+ patches-source = "MorpheApp/morphe-patches"
163
+ patches-version = "latest"
164
+
165
+ [[apps]]
166
+ package = "com.google.android.youtube"
167
+ name = "YouTube"
168
+ slug = "youtube"
169
+ patched-package = "app.morphe.android.youtube"
170
+ version = "auto"
171
+ build-mode = "apk"
172
+ arch = "arm64-v8a"
173
+ density = "xxhdpi"
174
+ include-patches = ["GmsCore support", "SponsorBlock"]
175
+ exclude-patches = []
176
+
177
+ [apps.patch-options."SponsorBlock"]
178
+ toast-on-connection-error = true
179
+
180
+ [apps.google-play]
181
+ profile = "D2"
182
+ country = "US"
183
+
184
+ [apps.fallbacks]
185
+ apkmirror = ["https://www.apkmirror.com/apk/google-inc/youtube/"]
186
+ ```
187
+
188
+ Only `package` and `name` are required. Everything else has a documented default:
189
+
190
+ | Setting | Required | Default when omitted |
191
+ | --- | :---: | --- |
192
+ | `package` | yes | Validated Android package name. |
193
+ | `name` | yes | Display name used in release notes and modules. |
194
+ | `slug` | no | `package` with dots replaced by hyphens. Controls output, cache, and module names; must be unique. |
195
+ | `patched-package` | no | No package change is expected after patching. Set it when patches rename the package. |
196
+ | `version` | no | `auto`: highest version supported by every selected patch. Also accepts `latest` or an exact version name. |
197
+ | `version-code` | no | Resolved from the version name. Requires an explicit `version`; `auto` and `latest` are rejected with it. |
198
+ | `build-mode` | no | `apk`: non-root APK only. Use `module` or `both` for Magisk/KernelSU modules. |
199
+ | `arch` | no | `arm64-v8a`. Also `arm-v7a`, `all`, or `both` (two independent jobs). |
200
+ | `density` | no | The profile's only available density split is selected automatically. |
201
+ | `include-patches` | no | Empty: the patch bundle's default selection is used. |
202
+ | `exclude-patches` | no | Empty: nothing is removed from the selection. |
203
+ | `exclusive-patches` | no | Unset. When set, only these patches apply; cannot be combined with include/exclude. |
204
+ | `patch-options` | no | Patch defaults. Scalar values only, never shell-interpolated. Every option must belong to a selected patch. |
205
+ | `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. |
206
+ | `google-play.profile` | no | `gplaydl` picks a profile. Leave unset with `arch = "both"`, since one profile cannot cover both ABIs. |
207
+ | `google-play.country` | no | `gplaydl` account region. |
208
+ | `google-play.proxy` | no | Direct connection. |
209
+ | `google-play.dispenser` | no | `MORPHE_DISPENSER_URL`, or the built-in public dispenser. |
210
+ | `fallbacks.*` | no | Empty: Google Play is the only source, so a Play failure fails the job. |
211
+ | `patches-source`, `patches-version`, `cli-source`, `morphe-version` | no | The `[toolchain]` values. |
212
+
213
+ The `[toolchain]` table itself is optional; omitting it uses `MorpheApp/morphe-desktop` and `MorpheApp/morphe-patches` at `latest`.
214
+
215
+ 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.
216
+
217
+ 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.
218
+
219
+ Full field reference, version-resolution order, and fallback URL rules: [`docs/configuration.md`](docs/configuration.md).
220
+
221
+ ## Usage
222
+
223
+ | Command | Purpose |
224
+ | --- | --- |
225
+ | `auth` | Authenticate against Google Play and cache the session outside the repository. |
226
+ | `download` | Download and verify one package's splits into an output directory. |
227
+ | `tools` | Resolve, download, and hash the pinned Java toolchain. |
228
+ | `list-patches` | Show patch-compatible versions for the configured apps. |
229
+ | `build` | Run the full configured matrix: download, merge, patch, sign, verify, package. |
230
+ | `verify` | Re-verify an existing split set against a package, architecture, and version. |
231
+ | `clean` | Remove the disposable work cache only; trusted sets and tools are preserved. |
232
+
233
+ ```bash
234
+ morphe-builder download com.example.app --arch arm64 --output downloads
235
+ morphe-builder list-patches morphe.toml
236
+ morphe-builder verify trusted/splits com.google.android.youtube \
237
+ --arch arm64 --version-name 21.04.223
238
+ ```
239
+
240
+ 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.
241
+
242
+ Full reference, exit-code table, and cache semantics: [`docs/cli.md`](docs/cli.md).
243
+
244
+ ## Documentation
245
+
246
+ | Topic | Document |
247
+ | --- | --- |
248
+ | CLI reference and exit codes | [`docs/cli.md`](docs/cli.md) |
249
+ | TOML configuration and version resolution | [`docs/configuration.md`](docs/configuration.md) |
250
+ | Toolchain cache, pins, and checksums | [`docs/toolchain.md`](docs/toolchain.md) |
251
+ | Google Play delivery and integrity checks | [`docs/google-play-integrity.md`](docs/google-play-integrity.md) |
252
+ | Provider fallback order | [`docs/providers.md`](docs/providers.md) |
253
+ | Split selection and merge policy | [`docs/merge.md`](docs/merge.md) |
254
+ | Signed APK build flow | [`docs/build.md`](docs/build.md) |
255
+ | Magisk/KernelSU module format | [`docs/modules.md`](docs/modules.md) |
256
+ | Threat model and platform limits | [`docs/security.md`](docs/security.md) |
257
+ | CI, release verification, and publication | [`docs/releases.md`](docs/releases.md) |
258
+ | Third-party licenses | [`docs/THIRD_PARTY.md`](docs/THIRD_PARTY.md) |
259
+ | Repository rules for contributors and agents | [`docs/rules/README.md`](docs/rules/README.md) |
260
+ | Active roadmap | [`docs/roadmap/ROADMAP.md`](docs/roadmap/ROADMAP.md) |
261
+ | Completed milestones and evidence | [`docs/roadmap/COMPLETED.md`](docs/roadmap/COMPLETED.md) |
262
+
263
+ ## License
264
+
265
+ 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).
266
+
267
+ ## Credits
268
+
269
+ - [`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.
270
+ - [`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).
271
+ - [`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.
272
+ - [ReVanced](https://revanced.app/): the patch ecosystem Morphe builds on; ReVanced-compatible patch bundles can be selected per app via `patches-source`.
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "morphe-builder"
7
+ version = "0.1.0"
8
+ description = "Build verified Morphe-patched Android APKs and modules"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Mikel Villota" }]
13
+ requires-python = ">=3.11,<3.14"
14
+ keywords = ["android", "apk", "morphe", "patcher", "revanced"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Operating System :: Microsoft :: Windows",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Software Development :: Build Tools",
25
+ ]
26
+ dependencies = [
27
+ "apkutils2==1.0.0",
28
+ # PyPI cannot encode reviewed VCS sources. Version 0.0.0 prevents pip from
29
+ # silently substituting the incompatible upstream gplaydl distribution.
30
+ "gplaydl==0.0.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Documentation = "https://github.com/Villoh/morphe-builder#readme"
35
+ Issues = "https://github.com/Villoh/morphe-builder/issues"
36
+ Repository = "https://github.com/Villoh/morphe-builder"
37
+
38
+ [project.scripts]
39
+ morphe-builder = "morphe_builder:main"
40
+
41
+ [tool.uv]
42
+ # Quarantine new releases before dependency resolution admits them.
43
+ exclude-newer = "3 days"
44
+
45
+ [tool.setuptools]
46
+ package-dir = { "" = "src" }
47
+ packages = ["morphe_builder"]
48
+
49
+ [tool.uv.sources]
50
+ gplaydl = { git = "https://github.com/Villoh/gplaydl.git", rev = "de7dfba240b09bc986523c1637ac672b0c295980" }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+