From dfd68244dd2bd9ec67e17dbc93d6f159876c15c9 Mon Sep 17 00:00:00 2001
From: oxmc <67136658+oxmc@users.noreply.github.com>
Date: Tue, 21 Jul 2026 17:45:09 -0700
Subject: [PATCH] Add Aurora Store, fix microG signature spoofing, overhaul
updater
- AuroraStore: bundle the privileged preload variant with privapp +
default-permissions; pull from AuroraOSS GitLab in vendorsetup.sh
- microG sig-spoofing: keep FAKE_PACKAGE_SIGNATURE signature|privileged
(system-level, non-requestable); drop the dead dangerous-model
default-permissions grant; make GsfProxy privileged
- updater: rewrite for correct sources (GitHub microG / GitLab Aurora /
F-Droid), fix apps/ module paths, handle .microg_release and Aurora's
.version_tag, add requirements.txt (replaces Pipfile)
- README: rewrite for the trimmed PawletOS fork
---
README.md | 105 ++++++------
apps/AuroraStore/.version_tag | 1 +
apps/AuroraStore/Android.bp | 34 ++++
.../default-permissions-com.aurora.store.xml | 22 +++
.../privapp-permissions-com.aurora.store.xml | 20 +++
apps/FDroid/additional_repos.json | 14 ++
...efault-permissions-com.android.vending.xml | 4 -
apps/GMS/GmsCore/microg.xml | 2 +-
apps/GMS/GsfProxy/Android.bp | 1 +
gms.mk | 3 +-
updater/Pipfile | 12 --
updater/Pipfile.lock | 107 ------------
updater/__init__.py | 0
updater/certificates.py | 11 --
updater/git.py | 17 --
updater/main.py | 157 ++++++++++++++----
updater/requirements.txt | 3 +
updater/sources.py | 38 -----
updater/utils/__init__.py | 0
updater/utils/certificates.py | 15 ++
updater/utils/git.py | 28 ++++
updater/utils/sources.py | 94 +++++++++++
vendorsetup.sh | 54 ++++--
23 files changed, 449 insertions(+), 293 deletions(-)
create mode 100644 apps/AuroraStore/.version_tag
create mode 100644 apps/AuroraStore/Android.bp
create mode 100644 apps/AuroraStore/default-permissions-com.aurora.store.xml
create mode 100644 apps/AuroraStore/privapp-permissions-com.aurora.store.xml
delete mode 100644 updater/Pipfile
delete mode 100644 updater/Pipfile.lock
create mode 100644 updater/__init__.py
delete mode 100644 updater/certificates.py
delete mode 100644 updater/git.py
create mode 100644 updater/requirements.txt
delete mode 100644 updater/sources.py
create mode 100644 updater/utils/__init__.py
create mode 100644 updater/utils/certificates.py
create mode 100644 updater/utils/git.py
create mode 100644 updater/utils/sources.py
diff --git a/README.md b/README.md
index 6c8a036..f0d67cd 100644
--- a/README.md
+++ b/README.md
@@ -1,59 +1,52 @@
-# microG Mobile Services
+# vendor/partner_gms — PawletOS
+
+Bundles a small set of prebuilt FOSS APKs (microG + app stores) into the
+PawletOS build. Forked from
+[lineageos4microg/android_vendor_partner_gms](https://github.com/lineageos4microg/android_vendor_partner_gms)
+and trimmed down for PawletOS — this repo now just documents and installs the
+apps below; it is no longer kept in sync with upstream.
+
+All APKs are official, unmodified prebuilts signed by their original developers
+(`presigned: true`), so signature spoofing works and the stores can self-update.
+
+## Included apps
+
+| App | Package | Purpose |
+|-----|---------|---------|
+| GmsCore | `com.google.android.gms` | microG — FOSS reimplementation of Google Play Services |
+| FakeStore | `com.android.vending` | Empty stub that mocks the Play Store's presence for apps that require it |
+| GsfProxy | `com.google.android.gsf` | GmsCore proxy for legacy GCM compatibility |
+| F-Droid | `org.fdroid.fdroid` | FOSS app catalogue |
+| F-Droid Privileged Extension | `org.fdroid.fdroid.privileged` | Silent install/removal for F-Droid |
+| Aurora Store | `com.aurora.store` | Play Store client (preload variant) |
+
+`apps/FDroid/additional_repos.xml` pre-seeds the microG F-Droid repo.
+
+## How it's wired
+
+- **`vendorsetup.sh`** downloads each APK (from GitHub / F-Droid / GitLab) into
+ its `apps/**` folder at sync time. Versions are pinned by the `.version_code`
+ file next to each app (and `.microg_release` for the microG bundle).
+- **`gms.mk`** adds the modules to `PRODUCT_PACKAGES`.
+- Each app's **`Android.bp`** imports the APK and, where needed, installs its
+ `privapp-permissions-*.xml` (privileged allowlist) and
+ `default-permissions-*.xml` (runtime pre-grants).
+
+## Signature spoofing
+
+Restricted / system-level only. The `FAKE_PACKAGE_SIGNATURE` permission is
+declared `signature|privileged` by the framework patch in
+`patches/frameworks/base/`, so **no third-party app can request it**. It is
+granted only via privapp-permissions to the privileged microG apps that need it
+(`com.google.android.gms`, `com.android.vending`).
+
+## Maintainer notes
+
+When bumping to a new microG release, update the pinned versions:
-***Note to maintainers:***
-When updating to a new vesion of microG the following files must be updated
- `.microg_release`
-- `FakeStore/.version_code`
-- `GmsCore/.version_code`
-)
+- `apps/GMS/GmsCore/.version_code`
+- `apps/FakeStore/.version_code`
-
-This is a collection of FOSS APKs, coupled with the respective Makefiles for an
-easy integration in the Android build system.
-
-To include them in your build, add a repo manifest file to include this repository as `vendor/partner_gms` and set
-`WITH_GMS` to `true` when building.
-
-Example manifest:
-
-```xml
-
-
-
-
-```
-
-Note 1. You do not need to set `CUSTOM_PACKAGES` for the packages to be included when building with [our Docker engine](https://github.com/lineageos4microg/docker-lineage-cicd). LineageOS did not make those changes in the 17.1 branch, so to make a 17.1 build, you will need to apply the changes to `AndroidManifest.xml` and `strings.xml` from [the Android Q LOS 17.1 branch-specific patch](https://github.com/lineageos4microg/docker-lineage-cicd/blob/35b7190bda4f2988845f09e028d266a23fd8b559/src/signature_spoofing_patches/android_frameworks_base-Q.patch). You do not need to apply the other changes in that patch. (See [this issue, and all it's comments](https://github.com/lineageos4microg/android_vendor_partner_gms/issues/42#issuecomment-2674105896) for more information about this)
-
-Note 2. LineageOS now support ***restricted*** signature spoofing, in 18.1 and later branches, so it is no longer neccessary to patch their sources, unless you want ***unrestricted*** signature spoofing
-~~For the microG packages to work, signature spoofing must be enabled, by patching the LineageOS sources before building.~~
-- ~~If you are building with [our Docker engine](https://github.com/lineageos4microg/docker-lineage-cicd), this is done automatically according to the values of the `SIGNATURE_SPOOFING` variable - see [here](https://github.com/lineageos4microg/docker-lineage-cicd#signature-spoofing). By default, this variable is set to `restricted`, allowing only system apps (those built-in to the ROM) to 'spoof' the signature of another app.~~
-- ~~If you are building manually, you can download and include the patches from [here](https://github.com/lineageos4microg/docker-lineage-cicd/tree/master/src/signature_spoofing_patches). If you follow the [microG patching instructions](https://github.com/microg/GmsCore/wiki/Signature-Spoofing), you ROM will allow _**any**_ app, not only system apps, to spoof app signatures. To apply only 'restricted' signature spoofing, you should do something similar to what the Docker engine `build.sh` does in [this code](https://github.com/lineageos4microg/docker-lineage-cicd/blob/c77eabe036a1620499a8c087b732e039e0734656/src/build.sh#L229C1-L231C1).~~
-
-Note 3. If you encounter problems related to APK / app signing when using these components you may need to add the following line in the Android.mk for the component in question:
-```
-LOCAL_REPLACE_PREBUILT_APK_INSTALLED := $(LOCAL_PATH)/$(LOCAL_MODULE).apk
-```
-Such problems can occur when
-
- the app / APK is resigned with your keys; (this should not happen if the line LOCAL_CERTIFICATE := PRESIGNED is included in the app makefile)
- app / APK signatures are 'stripped` during the during the deodexing phase of the build. For some apps the deodexed app ends up unsigned, and so will not run.
-
-The symptoms of the problem as some apps from this repo (e.g. FakeStore and GmsCore) missing completely from your launcher and acting like they're not installed.
-
-(Some background to this issue can be found [here](https://github.com/lineageos4microg/android_vendor_partner_gms/issues/30) and [here](https://gitlab.com/iode/os/public/lineage/vendor_extra/-/issues/4))
-
----------------
-
-The included APKs are:
- * FDroid packages (binaries sourced from [here](https://f-droid.org/packages/org.fdroid.fdroid/) and [here](https://f-droid.org/packages/org.fdroid.fdroid.privileged/))
- * FDroid: a catalogue of FOSS (Free and Open Source Software) applications for the Android platform
- * FDroid Privileged Extension: a FDroid extension to ease the installation/removal of apps
- * additional_repos.xml: a simple package to include the [microG F-Droid repository](https://microg.org/fdroid/repo/) in the ROM (requires FDroid >= 1.5)
- * microG packages (binaries sourced from [here](https://github.com/microg/GmsCore/releases))
- * GmsCore: the main component of microG, a FOSS reimplementation of the Google Play Services (requires FakeStore - and GsfProxy and for older versions - for full functionality)
- * FakeStore: an empty package that mocks the existence of the Google Play Store
- * GsfProxy: From [here](https://github.com/microg/GsfProxy/releases)a GmsCore proxy for legacy GCM compatibility
-
-These are official unmodified prebuilt binaries, signed by the
-corresponding developers.
+Other apps pin their own `.version_code` (`apps/GMS/GsfProxy`,
+`apps/FDroid`, `apps/FDroid/FDroidPrivilegedExtension`, `apps/AuroraStore`).
diff --git a/apps/AuroraStore/.version_tag b/apps/AuroraStore/.version_tag
new file mode 100644
index 0000000..f99c658
--- /dev/null
+++ b/apps/AuroraStore/.version_tag
@@ -0,0 +1 @@
+4.8.3
diff --git a/apps/AuroraStore/Android.bp b/apps/AuroraStore/Android.bp
new file mode 100644
index 0000000..54b2193
--- /dev/null
+++ b/apps/AuroraStore/Android.bp
@@ -0,0 +1,34 @@
+prebuilt_etc {
+ name: "privapp-permissions-com.aurora.store.xml",
+ src: "privapp-permissions-com.aurora.store.xml",
+ sub_dir: "permissions",
+ product_specific: true,
+}
+
+prebuilt_etc {
+ name: "default-permissions-com.aurora.store.xml",
+ src: "default-permissions-com.aurora.store.xml",
+ sub_dir: "default-permissions",
+ product_specific: true,
+}
+
+android_app_import {
+ name: "AuroraStore",
+ apk: "AuroraStore.apk",
+ presigned: true,
+ preprocessed: true,
+ skip_preprocessed_apk_checks: true,
+ privileged: true,
+ dex_preopt: {
+ enabled: false,
+ },
+ product_specific: true,
+ optional_uses_libs: [
+ "androidx.window.extensions",
+ "androidx.window.sidecar",
+ ],
+ required: [
+ "privapp-permissions-com.aurora.store.xml",
+ "default-permissions-com.aurora.store.xml",
+ ],
+}
diff --git a/apps/AuroraStore/default-permissions-com.aurora.store.xml b/apps/AuroraStore/default-permissions-com.aurora.store.xml
new file mode 100644
index 0000000..a34f7e2
--- /dev/null
+++ b/apps/AuroraStore/default-permissions-com.aurora.store.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/AuroraStore/privapp-permissions-com.aurora.store.xml b/apps/AuroraStore/privapp-permissions-com.aurora.store.xml
new file mode 100644
index 0000000..f53e08b
--- /dev/null
+++ b/apps/AuroraStore/privapp-permissions-com.aurora.store.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/apps/FDroid/additional_repos.json b/apps/FDroid/additional_repos.json
index f5aa7dc..7d88e99 100644
--- a/apps/FDroid/additional_repos.json
+++ b/apps/FDroid/additional_repos.json
@@ -1,4 +1,18 @@
[
+ {
+ "name": "F-Droid Official repo",
+ "address": "https://f-droid.org/repo",
+ "description": "This is the official F-Droid repository. It contains free and open source applications for Android.",
+ "certificate": "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB",
+ "enabled": true
+ },
+ {
+ "name": "IzzyOnDroid F-Droid repo",
+ "address": "https://apt.izzysoft.de/fdroid/repo",
+ "description": "This is an F-Droid style repository for Android apps, provided by IzzyOnDroid. Applications in this repository are official binaries built by the original application developers, taken from their resp. repositories (mostly Github).",
+ "certificate": "3BF0D6ABFEAE2F401707B6D966BE743BF0EEE49C2561B9BA39073711F628937A",
+ "enabled": true
+ },
{
"name": "microG F-Droid repo",
"address": "https://microg.org/fdroid/repo",
diff --git a/apps/FakeStore/default-permissions-com.android.vending.xml b/apps/FakeStore/default-permissions-com.android.vending.xml
index 2e31d04..94b93c0 100644
--- a/apps/FakeStore/default-permissions-com.android.vending.xml
+++ b/apps/FakeStore/default-permissions-com.android.vending.xml
@@ -1,15 +1,11 @@
-
-
-
-
diff --git a/apps/GMS/GmsCore/microg.xml b/apps/GMS/GmsCore/microg.xml
index 06d25a4..701c08a 100644
--- a/apps/GMS/GmsCore/microg.xml
+++ b/apps/GMS/GmsCore/microg.xml
@@ -21,7 +21,7 @@
-
+
diff --git a/apps/GMS/GsfProxy/Android.bp b/apps/GMS/GsfProxy/Android.bp
index 55eb35a..62dbc8f 100644
--- a/apps/GMS/GsfProxy/Android.bp
+++ b/apps/GMS/GsfProxy/Android.bp
@@ -3,6 +3,7 @@ android_app_import {
apk: "GsfProxy.apk",
presigned: true,
preprocessed: true,
+ privileged: true,
dex_preopt: {
enabled: false,
},
diff --git a/gms.mk b/gms.mk
index 97d1991..3fbc994 100644
--- a/gms.mk
+++ b/gms.mk
@@ -3,4 +3,5 @@ PRODUCT_PACKAGES += \
GsfProxy \
FakeStore \
FDroid \
- FDroidPrivilegedExtension
+ FDroidPrivilegedExtension \
+ AuroraStore
diff --git a/updater/Pipfile b/updater/Pipfile
deleted file mode 100644
index 90dc377..0000000
--- a/updater/Pipfile
+++ /dev/null
@@ -1,12 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
-
-[packages]
-requests-cache = "==0.9.1"
-
-[dev-packages]
-
-[requires]
-python_version = "3.9"
diff --git a/updater/Pipfile.lock b/updater/Pipfile.lock
deleted file mode 100644
index 8791c11..0000000
--- a/updater/Pipfile.lock
+++ /dev/null
@@ -1,107 +0,0 @@
-{
- "_meta": {
- "hash": {
- "sha256": "43267acd3e0a2938456d3924e25339a1da500cb295a1afee2e8a41843ba8af63"
- },
- "pipfile-spec": 6,
- "requires": {
- "python_version": "3.9"
- },
- "sources": [
- {
- "name": "pypi",
- "url": "https://pypi.org/simple",
- "verify_ssl": true
- }
- ]
- },
- "default": {
- "appdirs": {
- "hashes": [
- "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
- "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
- ],
- "version": "==1.4.4"
- },
- "attrs": {
- "hashes": [
- "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4",
- "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==21.4.0"
- },
- "cattrs": {
- "hashes": [
- "sha256:211800f725cdecedcbcf4c753bbd22d248312b37d130f06045434acb7d9b34e1",
- "sha256:35dd9063244263e63bd0bd24ea61e3015b00272cead084b2c40d788b0f857c46"
- ],
- "markers": "python_version >= '3.7' and python_version < '4.0'",
- "version": "==1.10.0"
- },
- "certifi": {
- "hashes": [
- "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
- "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
- ],
- "version": "==2021.10.8"
- },
- "charset-normalizer": {
- "hashes": [
- "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45",
- "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"
- ],
- "markers": "python_version >= '3'",
- "version": "==2.0.11"
- },
- "idna": {
- "hashes": [
- "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
- "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
- ],
- "markers": "python_version >= '3'",
- "version": "==3.3"
- },
- "requests": {
- "hashes": [
- "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
- "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==2.27.1"
- },
- "requests-cache": {
- "hashes": [
- "sha256:3e3384c48dca231ee4c49e7ba53162bc0d99e16721baf085bfba1552d065d151",
- "sha256:7737f83f0f48481a904bb9a9402233db5090931e46f9644c502646a573848d35"
- ],
- "index": "pypi",
- "version": "==0.9.1"
- },
- "six": {
- "hashes": [
- "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
- "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
- "version": "==1.16.0"
- },
- "url-normalize": {
- "hashes": [
- "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2",
- "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.4.3"
- },
- "urllib3": {
- "hashes": [
- "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed",
- "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4.0'",
- "version": "==1.26.8"
- }
- },
- "develop": {}
-}
diff --git a/updater/__init__.py b/updater/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/updater/certificates.py b/updater/certificates.py
deleted file mode 100644
index 547e4e7..0000000
--- a/updater/certificates.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import subprocess
-
-
-def get_apk_certificate(file: str):
- output = subprocess.check_output(['keytool', '-printcert', '-rfc', '-jarfile', file], text=True)
- lines = output.split("\n")
- return '\n'.join(lines[
- lines.index('-----BEGIN CERTIFICATE-----'):
- (lines.index('-----END CERTIFICATE-----')+1)
- ])
-
diff --git a/updater/git.py b/updater/git.py
deleted file mode 100644
index 1c74b32..0000000
--- a/updater/git.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import subprocess
-
-
-user_name = 'Updater Robot'
-user_email = 'robot@nowhere.invalid'
-
-
-def add_commit_push(directory: str, message: str):
- diff = subprocess.run(['git', 'diff', '--cached', '--exit-code'], capture_output=True, text=True)
- if diff.returncode != 0:
- status = subprocess.run(['git', 'status'], capture_output=True, text=True)
- raise Exception('Unknown staged changes found: {}'.format(status.stdout))
-
- subprocess.run(['git', 'add', '--all', directory], check=True)
- subprocess.run(['git', '-c', 'user.name={}'.format(user_name), '-c', 'user.email={}'.format(user_email),
- 'commit', '--message', message])
- subprocess.run(['git', 'push'])
diff --git a/updater/main.py b/updater/main.py
index 996fd93..8015647 100644
--- a/updater/main.py
+++ b/updater/main.py
@@ -1,49 +1,138 @@
+import os
+import re
+import tempfile
import urllib.request
from os import path
-import certificates
-import git
-from sources import ApkRelease, fdroid_recommended_release
+from .utils import certificates, sources
+
+# Repo root (vendor/partner_gms), i.e. the parent of this updater/ folder.
+BASE = path.abspath(path.join(path.dirname(__file__), ".."))
+
+FDROID_MAIN = "https://f-droid.org/repo"
-def update_if_needed(module: str, release: ApkRelease):
- module_dir = path.abspath(path.join(path.dirname(__file__), '..', module))
- with open(path.join(module_dir, '.version_code'), 'r+') as version_code_file:
- version_code = int(version_code_file.read())
- if version_code < release.version_code:
- print('updating {} to {}'.format(module, release.version_name))
- apk_filename = path.join(module_dir, '{}.apk'.format(module))
+def _read(p):
+ with open(p, "r") as f:
+ return f.read().strip()
- old_sig = certificates.get_apk_certificate(apk_filename)
- print('downloading {} ...'.format(release.download_url))
- urllib.request.urlretrieve(release.download_url, apk_filename)
+def _write(p, value):
+ with open(p, "w") as f:
+ f.write(str(value) + "\n")
- new_sig = certificates.get_apk_certificate(apk_filename)
- if old_sig != new_sig:
- raise Exception('Signature mismatch for {} old sig: {} new sig: {}'.format(module, old_sig, new_sig))
- version_code_file.seek(0)
- version_code_file.write(str(release.version_code))
- version_code_file.truncate()
- version_code_file.close()
+def _verify_same_signer(module, download_url):
+ """Best-effort: if a local APK is present, refuse to bump the pin when the
+ new release is signed by a different certificate (which would break the
+ presigned / signature-spoofing setup). APKs are gitignored, so on a fresh
+ tree there is nothing to compare against and we skip with a warning."""
+ apk_name = path.basename(module) + ".apk"
+ local_apk = path.join(BASE, module, apk_name)
+ if not path.exists(local_apk):
+ print(" ! no local {} to compare against; skipping signer check".format(apk_name))
+ return
- print('Need to update {} to {}'.format(module, release.version_name))
+ old_sig = certificates.get_apk_certificate(local_apk)
+ fd, tmp = tempfile.mkstemp(suffix=".apk")
+ os.close(fd)
+ try:
+ urllib.request.urlretrieve(download_url, tmp)
+ new_sig = certificates.get_apk_certificate(tmp)
+ if old_sig != new_sig:
+ raise Exception(
+ "signer changed for {} — refusing to pin new version".format(module)
+ )
+ finally:
+ try:
+ os.remove(tmp)
+ except OSError:
+ pass
+
+
+def apply(module, pin_file, release):
+ """Update a single pin file. `module` is repo-relative, e.g. apps/GMS/GmsCore."""
+ p = path.join(BASE, module, pin_file)
+ current = _read(p)
+
+ if release.numeric:
+ if int(current) == int(release.pin):
+ print("{}: up to date ({})".format(module, current))
return
+ if int(current) > int(release.pin):
+ print(
+ "{}: local {} ahead of upstream {} — leaving".format(
+ module, current, release.pin
+ )
+ )
+ return
+ elif current == release.pin:
+ print("{}: up to date ({})".format(module, current))
+ return
- print('commit and push...')
- git.add_commit_push(module_dir, 'Update {} to {}'.format(module, release.version_name))
+ print("{}: {} -> {} ({})".format(module, current, release.pin, release.display))
+ _verify_same_signer(module, release.download_url)
+ _write(p, release.pin)
- elif version_code > release.version_code:
- print('{} ahead of suggested version ({} > {})'.format(module, version_code, release.version_code))
- elif version_code == release.version_code:
- print('{} up to date.'.format(module))
-fdroid_main_repo = 'https://www.f-droid.org/repo'
-fdroid_microg_repo = 'https://microg.org/fdroid/repo'
+def update_microg():
+ """GmsCore + FakeStore ship in one microG GmsCore GitHub release. Pin each
+ APK's versionCode and the shared release tag that vendorsetup.sh needs to
+ build the download URLs (.microg_release)."""
+ rel = sources.github_latest("microg", "GMSCore")
+ tag = rel["tag_name"]
-update_if_needed('FakeStore', fdroid_recommended_release(fdroid_microg_repo, 'com.android.vending'))
-update_if_needed('FDroid', fdroid_recommended_release(fdroid_main_repo, 'org.fdroid.fdroid'))
-update_if_needed('FDroidPrivilegedExtension', fdroid_recommended_release(fdroid_main_repo, 'org.fdroid.fdroid.privileged'))
-update_if_needed('GmsCore', fdroid_recommended_release(fdroid_microg_repo, 'com.google.android.gms'))
-#update_if_needed('GsfProxy', fdroid_recommended_release(fdroid_microg_repo, 'com.google.android.gsf'))
+ gms_name, gms_url = sources.github_asset(rel, r"^com\.google\.android\.gms-\d+\.apk$")
+ fs_name, fs_url = sources.github_asset(rel, r"^com\.android\.vending-\d+\.apk$")
+ gms_vc = re.search(r"-(\d+)\.apk$", gms_name).group(1)
+ fs_vc = re.search(r"-(\d+)\.apk$", fs_name).group(1)
+
+ apply(
+ "apps/GMS/GmsCore", ".version_code",
+ sources.Release(gms_vc, gms_url, numeric=True, display=tag),
+ )
+ apply(
+ "apps/FakeStore", ".version_code",
+ sources.Release(fs_vc, fs_url, numeric=True, display=tag),
+ )
+
+ mr = path.join(BASE, ".microg_release")
+ if _read(mr) != tag:
+ print(".microg_release: {} -> {}".format(_read(mr), tag))
+ _write(mr, tag)
+ else:
+ print(".microg_release: up to date ({})".format(tag))
+
+
+def update_fdroid():
+ apply(
+ "apps/FDroid", ".version_code",
+ sources.fdroid_recommended(FDROID_MAIN, "org.fdroid.fdroid"),
+ )
+ apply(
+ "apps/FDroid/FDroidPrivilegedExtension", ".version_code",
+ sources.fdroid_recommended(FDROID_MAIN, "org.fdroid.fdroid.privileged"),
+ )
+
+
+def update_aurora():
+ """Aurora Store OEM 'preload' variant from the AuroraOSS GitLab (tag-based)."""
+ tag, url = sources.gitlab_latest_upload(
+ "6922885", r"/uploads/[a-f0-9]+/AuroraStore-preload-[^)\"]+\.apk"
+ )
+ apply(
+ "apps/AuroraStore", ".version_tag",
+ sources.Release(tag, url, numeric=False, display=tag),
+ )
+
+
+def main():
+ update_microg()
+ update_fdroid()
+ update_aurora()
+ # GsfProxy is no longer updated upstream — its .version_code (tag) is pinned
+ # manually and pulled by vendorsetup.sh from GitHub.
+
+
+if __name__ == "__main__":
+ main()
diff --git a/updater/requirements.txt b/updater/requirements.txt
new file mode 100644
index 0000000..256e467
--- /dev/null
+++ b/updater/requirements.txt
@@ -0,0 +1,3 @@
+# Updater dependencies. Install with: pip install -r requirements.txt
+# requests-cache brings in requests as a transitive dependency.
+requests-cache==0.9.1
diff --git a/updater/sources.py b/updater/sources.py
deleted file mode 100644
index 51c0bbf..0000000
--- a/updater/sources.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from xml.dom import minidom, pulldom
-
-import requests_cache
-
-requests_session = requests_cache.CachedSession('updater', backend='memory')
-
-
-class ApkRelease:
- version_name: str
- version_code: int
- download_url: str
-
- def __init__(self, version_name: str, version_code: int, download_url: str):
- self.version_name = version_name
- self.version_code = version_code
- self.download_url = download_url
-
-
-def _child_el_content(el: minidom.Element, tag_name: str):
- return el.getElementsByTagName(tag_name).item(0).firstChild.data
-
-
-def fdroid_recommended_release(repo: str, application_id: str):
- with requests_session.get('{}/index.xml'.format(repo)) as r:
- doc = pulldom.parseString(r.text)
- for event, node in doc:
- if event == pulldom.START_ELEMENT and node.tagName == 'application':
- if node.getAttribute('id') == application_id:
- doc.expandNode(node)
- marketvercode = _child_el_content(node, 'marketvercode')
- for p in node.getElementsByTagName('package'):
- if _child_el_content(p, 'versioncode') == marketvercode:
- return ApkRelease(
- _child_el_content(p, 'version'),
- int(marketvercode),
- '{}/{}'.format(repo, _child_el_content(p, 'apkname'))
- )
- raise Exception('Did not find {} in repo {}'.format(application_id, repo))
diff --git a/updater/utils/__init__.py b/updater/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/updater/utils/certificates.py b/updater/utils/certificates.py
new file mode 100644
index 0000000..86ad66f
--- /dev/null
+++ b/updater/utils/certificates.py
@@ -0,0 +1,15 @@
+import subprocess
+
+
+def get_apk_certificate(file: str):
+ output = subprocess.check_output(
+ ["keytool", "-printcert", "-rfc", "-jarfile", file], text=True
+ )
+ lines = output.split("\n")
+ return "\n".join(
+ lines[
+ lines.index("-----BEGIN CERTIFICATE-----") : (
+ lines.index("-----END CERTIFICATE-----") + 1
+ )
+ ]
+ )
diff --git a/updater/utils/git.py b/updater/utils/git.py
new file mode 100644
index 0000000..974e63b
--- /dev/null
+++ b/updater/utils/git.py
@@ -0,0 +1,28 @@
+import subprocess
+
+user_name = "Updater Robot"
+user_email = "robot@nowhere.invalid"
+
+
+def add_commit_push(directory: str, message: str):
+ diff = subprocess.run(
+ ["git", "diff", "--cached", "--exit-code"], capture_output=True, text=True
+ )
+ if diff.returncode != 0:
+ status = subprocess.run(["git", "status"], capture_output=True, text=True)
+ raise Exception("Unknown staged changes found: {}".format(status.stdout))
+
+ subprocess.run(["git", "add", "--all", directory], check=True)
+ subprocess.run(
+ [
+ "git",
+ "-c",
+ "user.name={}".format(user_name),
+ "-c",
+ "user.email={}".format(user_email),
+ "commit",
+ "--message",
+ message,
+ ]
+ )
+ subprocess.run(["git", "push"])
diff --git a/updater/utils/sources.py b/updater/utils/sources.py
new file mode 100644
index 0000000..d57dd52
--- /dev/null
+++ b/updater/utils/sources.py
@@ -0,0 +1,94 @@
+import re
+from xml.dom import pulldom
+
+import requests_cache
+
+# Cached so repeated calls in one run (e.g. GmsCore + FakeStore share the same
+# GitHub release) don't re-hit the network.
+session = requests_cache.CachedSession("updater", backend="memory")
+
+
+class Release:
+ """A resolved upstream release.
+
+ pin: the string written to the module's pin file and used to decide
+ whether an update is needed. For F-Droid apps this is the
+ integer versionCode; for GitHub/GitLab apps it is the tag.
+ numeric: True when `pin` is an integer versionCode (ordered
+ numerically); False for tag-based releases (compared for
+ equality only).
+ download_url: direct link to the APK, used for the signer check.
+ display: human-readable version for logging.
+ """
+
+ def __init__(self, pin, download_url, numeric, display=None):
+ self.pin = str(pin)
+ self.download_url = download_url
+ self.numeric = numeric
+ self.display = display or str(pin)
+
+
+def _text(el, tag_name):
+ return el.getElementsByTagName(tag_name).item(0).firstChild.data
+
+
+def fdroid_recommended(repo, application_id):
+ """Latest recommended (marketvercode) build from an F-Droid index.xml repo."""
+ with session.get("{}/index.xml".format(repo)) as r:
+ r.raise_for_status()
+ doc = pulldom.parseString(r.text)
+ for event, node in doc:
+ if (
+ event == pulldom.START_ELEMENT
+ and node.tagName == "application"
+ and node.getAttribute("id") == application_id
+ ):
+ doc.expandNode(node)
+ market = _text(node, "marketvercode")
+ for p in node.getElementsByTagName("package"):
+ if _text(p, "versioncode") == market:
+ return Release(
+ pin=int(market),
+ download_url="{}/{}".format(repo, _text(p, "apkname")),
+ numeric=True,
+ display=_text(p, "version"),
+ )
+ raise Exception("did not find {} in {}".format(application_id, repo))
+
+
+def github_latest(owner, repo):
+ """Raw 'latest release' JSON from GitHub (carries tag_name + assets)."""
+ url = "https://api.github.com/repos/{}/{}/releases/latest".format(owner, repo)
+ with session.get(url) as r:
+ r.raise_for_status()
+ return r.json()
+
+
+def github_asset(release_json, name_regex):
+ """(name, download_url) of the first asset whose name matches name_regex."""
+ rx = re.compile(name_regex)
+ for a in release_json.get("assets", []):
+ if rx.search(a["name"]):
+ return a["name"], a["browser_download_url"]
+ raise Exception("no GitHub asset matching /{}/".format(name_regex))
+
+
+def gitlab_latest_upload(project_id, upload_regex):
+ """Newest GitLab release carrying a description upload matching upload_regex.
+
+ Returns (tag, download_url). Mirrors vendorsetup.sh: the APKs are markdown
+ upload attachments in the release description, downloadable anonymously only
+ via the project-id upload path (/-/project//uploads//) —
+ namespace paths 403/404. Releases come back newest-first.
+ """
+ api = "https://gitlab.com/api/v4/projects/{}/releases".format(project_id)
+ with session.get(api) as r:
+ r.raise_for_status()
+ releases = r.json()
+ rx = re.compile(upload_regex)
+ for rel in releases:
+ m = rx.search(rel.get("description", ""))
+ if m:
+ url = "https://gitlab.com/-/project/{}{}".format(project_id, m.group(0))
+ return rel["tag_name"], url
+ raise Exception("no GitLab release upload matching /{}/".format(upload_regex))
diff --git a/vendorsetup.sh b/vendorsetup.sh
index bf2d28f..1bed695 100755
--- a/vendorsetup.sh
+++ b/vendorsetup.sh
@@ -11,12 +11,17 @@ download_apk() {
local destination_apk=$2
if [ -f "$destination_apk" ]; then
- echo "$destination_apk exists: not downloading"
- ## To Do
- # Deal with the situation where we have an OLDER version hanging around
- # may have to be handled in the Docker image
+ echo "$destination_apk exists: checking for updates"
+ local source_size destination_size
+ source_size=$(curl -sI "$source_apk" | grep -i Content-Length | awk '{print $2}' | tr -d '\r')
+ destination_size=$(stat -c %s "$destination_apk")
+ if [ "$source_size" != "$destination_size" ]; then
+ echo "updating $destination_apk"
+ curl -L --output "$destination_apk" "$source_apk"
+ else
+ echo "$destination_apk is up to date: skipping download"
+ fi
else
- # echo "downloading $source_apk to $destination_apk"
curl -L --output "$destination_apk" "$source_apk"
fi
}
@@ -25,7 +30,7 @@ get-fdroid-components() {
local fdroid_repo="https://f-droid.org/repo/"
local versioncode id apk_to_download
- # F-Droid client app
+ # F-Droid client app (nested under apps/FDroid/)
versioncode=$(cat "apps/FDroid/.version_code")
id="org.fdroid.fdroid"
apk_to_download="$fdroid_repo"/"$id"_"$versioncode".apk
@@ -49,23 +54,48 @@ get-microg-components() {
apk_to_download="$microg_repo_base"/GMSCore/releases/download/"$microg_release"/"$id"-"$versioncode".apk
download_apk "$apk_to_download" "apps/GMS/GmsCore/GmsCore.apk"
- # FakeStore
+ # GsfProxy (nested under apps/GMS/)
+ versioncode=$(cat "apps/GMS/GsfProxy/.version_code")
+ apk_to_download="$microg_repo_base"/android_packages_apps_GsfProxy/releases/download/"$versioncode"/GsfProxy.apk
+ download_apk "$apk_to_download" "apps/GMS/GsfProxy/GsfProxy.apk"
+
+ # FakeStore (nested under apps/FakeStore/)
versioncode=$(cat "apps/FakeStore/.version_code")
id="com.android.vending"
apk_to_download="$microg_repo_base"/GMSCore/releases/download/"$microg_release"/"$id"-"$versioncode".apk
download_apk "$apk_to_download" "apps/FakeStore/FakeStore.apk"
+}
- # GsfProxy (nested under apps/GMS/)
- # file: https://github.com/microg/android_packages_apps_GsfProxy/releases/download/v0.1.0/GsfProxy.apk
- versioncode=$(cat "apps/GMS/GsfProxy/.version_code")
- apk_to_download="$microg_repo_base"/android_packages_apps_GsfProxy/releases/download/"$versioncode"/GsfProxy.apk
- download_apk "$apk_to_download" "apps/GMS/GsfProxy/GsfProxy.apk"
+get-aurora-components() {
+ local project_id="6922885" # gitlab.com/AuroraOSS/AuroraStore
+ local api="https://gitlab.com/api/v4/projects/$project_id/releases"
+ local tag apk_path apk_url
+
+ # Aurora Store OEM "preload" variant from the official AuroraOSS GitLab.
+ # .version_tag holds the release TAG (e.g. 4.8.3). The release APKs are
+ # markdown upload attachments in the release description; the only public
+ # anonymous download is the project-id upload path
+ # (/-/project//uploads//) — namespace paths 403/404. So pull
+ # the hash from the release description and build that URL. Saved as
+ # AuroraStore.apk.
+ tag=$(cat "apps/AuroraStore/.version_tag")
+
+ apk_path=$(curl -sL "$api/$tag" \
+ | grep -oE '/uploads/[a-f0-9]+/AuroraStore-preload-[^)"]+\.apk' \
+ | head -n1)
+ if [ -z "$apk_path" ]; then
+ echo "ERROR: could not resolve Aurora Store preload upload for tag $tag" >&2
+ return 1
+ fi
+ apk_url="https://gitlab.com/-/project/$project_id$apk_path"
+ download_apk "$apk_url" "apps/AuroraStore/AuroraStore.apk"
}
# This script is called from the root directory, so we need to cd
cd vendor/partner_gms
get-fdroid-components
get-microg-components
+get-aurora-components
# and back to the root directory
cd ../..