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
This commit is contained in:
oxmc
2026-07-21 17:45:09 -07:00
parent 795dafc62e
commit dfd68244dd
23 changed files with 449 additions and 293 deletions
+49 -56
View File
@@ -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
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<project path="vendor/partner_gms" name="lineageos4microg/android_vendor_partner_gms" remote="github" revision="master" />
</manifest>
```
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`).
+1
View File
@@ -0,0 +1 @@
4.8.3
+34
View File
@@ -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",
],
}
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 oxmc / PawletOS
SPDX-License-Identifier: Apache-2.0
Default (pre-)grants for the bundled Aurora Store so its runtime permissions
don't prompt the user on first run. This only covers runtime/dangerous
permissions; appop permissions (REQUEST_INSTALL_PACKAGES, MANAGE_EXTERNAL_STORAGE)
are not granted here — MANAGE_EXTERNAL_STORAGE comes from its "preinstalled"
protection flag, and privileged/signature grants live in
privapp-permissions-com.aurora.store.xml. fixed="false" lets the user still
revoke them.
-->
<exceptions>
<exception package="com.aurora.store">
<!-- Notifications: downloads, available updates, errors -->
<permission name="android.permission.POST_NOTIFICATIONS" fixed="false" />
<!-- Legacy storage for OBB expansion files (API <= 32) -->
<permission name="android.permission.READ_EXTERNAL_STORAGE" fixed="false" />
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" fixed="false" />
</exception>
</exceptions>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 oxmc / PawletOS
SPDX-License-Identifier: Apache-2.0
Privileged-permission allowlist for the bundled Aurora Store (preload
variant). Only signature|privileged permissions belong here; Aurora's other
permissions (INTERNET, FOREGROUND_SERVICE*, QUERY_ALL_PACKAGES,
REQUEST_INSTALL_PACKAGES/REQUEST_DELETE_PACKAGES, UPDATE_PACKAGES_WITHOUT_USER_ACTION,
MANAGE_EXTERNAL_STORAGE, POST_NOTIFICATIONS, ...) are normal/runtime/appop and
are granted without an allowlist. Without this entry, an enforcing build kills
system_server at boot because Aurora is installed as a privileged app.
-->
<permissions>
<privapp-permissions package="com.aurora.store">
<!-- Claim update ownership so Aurora's later updates to apps it
installed apply silently (signature|privileged, Android 14+). -->
<permission name="android.permission.ENFORCE_UPDATE_OWNERSHIP" />
</privapp-permissions>
</permissions>
+14
View File
@@ -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",
@@ -1,15 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<exceptions>
<exception package="com.android.vending">
<!-- for permissive signature spoofing, where the permission is "dangerous" -->
<permission name="android.permission.FAKE_PACKAGE_SIGNATURE" fixed="false"/>
<!-- Account -->
<permission name="android.permission.GET_ACCOUNTS" fixed="false" />
<!-- Location -->
<permission name="android.permission.ACCESS_COARSE_LOCATION" fixed="false" />
<!-- Notifications -->
<permission name="android.permission.POST_NOTIFICATIONS" fixed="false" />
</exception>
</exceptions>
+1 -1
View File
@@ -21,7 +21,7 @@
<boolean name="nominatim_enabled" value="true" />
<boolean name="vending_licensing" value="true" />
<boolean name="vending_licensing_purchase_free_apps" value="true" />
<boolean name="vending_billing" value="true" />
<boolean name="vending_billing" value="false" />
<boolean name="vending_asset_delivery" value="true" />
<boolean name="vending_device_sync" value="true" />
</map>
+1
View File
@@ -3,6 +3,7 @@ android_app_import {
apk: "GsfProxy.apk",
presigned: true,
preprocessed: true,
privileged: true,
dex_preopt: {
enabled: false,
},
+2 -1
View File
@@ -3,4 +3,5 @@ PRODUCT_PACKAGES += \
GsfProxy \
FakeStore \
FDroid \
FDroidPrivilegedExtension
FDroidPrivilegedExtension \
AuroraStore
-12
View File
@@ -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"
-107
View File
@@ -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": {}
}
View File
-11
View File
@@ -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)
])
-17
View File
@@ -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'])
+123 -34
View File
@@ -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()
+3
View File
@@ -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
-38
View File
@@ -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))
View File
+15
View File
@@ -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
)
]
)
+28
View File
@@ -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"])
+94
View File
@@ -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/<id>/uploads/<hash>/<file>) —
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))
+42 -12
View File
@@ -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/<id>/uploads/<hash>/<file>) — 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 ../..