1
0
mirror of https://gitlab.com/fdroid/fdroidserver.git synced 2024-11-14 02:50:12 +01:00

install: function to fetch, verify and install the F-Droid.apk

This commit is contained in:
Hans-Christoph Steiner 2024-02-27 20:02:51 +01:00
parent 681d705da0
commit c7bc8d0fea
2 changed files with 107 additions and 9 deletions

View File

@ -20,6 +20,7 @@
import sys
import os
import glob
import locale
import logging
from argparse import ArgumentParser
@ -83,6 +84,60 @@ def download_fdroid_apk():
return net.download_using_mirrors([mirror])
def install_fdroid_apk(privacy_mode=False):
"""Download and install F-Droid.apk using all tricks we can muster.
By default, this first tries to fetch the official install APK
which is offered when someone clicks the "download" button on
https://f-droid.org/. Then it will try all the mirrors and
methods until it gets something successful, or runs out of
options.
There is privacy_mode which tries to download from mirrors first,
so that this downloads from a mirror that has many different kinds
of files available, thereby breaking the clear link to F-Droid.
Returns
-------
None for success or the error message.
"""
if locale.getlocale()[0].split('_')[-1] in ('CN', 'HK', 'IR', 'TM'):
logging.warning(_('Privacy mode was enabled based on your locale.'))
privacy_mode = True
if privacy_mode or not (config and config.get('jarsigner')):
download_methods = [download_fdroid_apk]
else:
download_methods = [download_apk, download_fdroid_apk]
for method in download_methods:
try:
f = method()
break
except Exception as e:
logging.info(e)
else:
return _('F-Droid.apk could not be downloaded from any known source!')
if config and config['apksigner']:
# TODO this should always verify, but that requires APK sig verification in Python #94
logging.info(_('Verifying package {path} with apksigner.').format(path=f))
common.verify_apk_signature(f)
fingerprint = common.apk_signer_fingerprint(f)
if fingerprint.upper() != common.FDROIDORG_FINGERPRINT:
return _('{path} has the wrong fingerprint ({fingerprint})!').format(
path=f, fingerprint=fingerprint
)
if config and config.get('adb'):
if devices():
install_apks_to_devices([f])
os.remove(f)
else:
os.remove(f)
return _('No devices found for `adb install`! Please plug one in.')
def devices():
"""Get the list of device serials for use with adb commands."""
p = common.SdkToolsPopen(['adb', "devices"])
@ -162,17 +217,16 @@ def main():
common.set_console_logging(options.verbose)
if not options.appid and not options.all:
parser.error(
_("option %s: If you really want to install all the signed apps, use --all")
% "all"
)
# TODO implement me, including a -y/--yes flag
print('TODO prompt the user if they want to download and install F-Droid.apk')
config = common.read_config()
output_dir = 'repo'
if not os.path.isdir(output_dir):
logging.info(_("No signed output directory - nothing to do"))
sys.exit(0)
if (options.appid or options.all) and not os.path.isdir(output_dir):
logging.error(_("No signed output directory - nothing to do"))
# TODO prompt user if they want to download from f-droid.org
sys.exit(1)
if options.appid:
vercodes = common.read_pkg_args(options.appid, True)
@ -196,13 +250,16 @@ def main():
raise FDroidException(_("No signed APK available for %s") % appid)
install_apks_to_devices(apks.values())
else:
elif options.all:
apks = {
common.publishednameinfo(apkfile)[0]: apkfile
for apkfile in sorted(glob.glob(os.path.join(output_dir, '*.apk')))
}
install_apks_to_devices(apks.values())
else:
sys.exit(install_fdroid_apk())
logging.info('\n' + _('Finished'))

View File

@ -171,13 +171,54 @@ class InstallTest(unittest.TestCase):
common.fill_config_defaults(common.config)
self.assertEqual([], fdroidserver.install.devices())
@staticmethod
def _download_raise(privacy_mode):
raise Exception('fake failed download')
@patch('fdroidserver.install.download_apk')
@patch('fdroidserver.install.download_fdroid_apk')
def test_install_fdroid_apk_privacy_mode_true(
self, download_fdroid_apk, download_apk
):
download_apk.side_effect = self._download_raise
download_fdroid_apk.side_effect = self._download_raise
fdroidserver.common.config = {'jarsigner': 'fakepath'}
install.install_fdroid_apk(privacy_mode=True)
download_apk.assert_not_called()
download_fdroid_apk.assert_called_once()
@patch('fdroidserver.install.download_apk')
@patch('fdroidserver.install.download_fdroid_apk')
def test_install_fdroid_apk_privacy_mode_false(
self, download_fdroid_apk, download_apk
):
download_apk.side_effect = self._download_raise
download_fdroid_apk.side_effect = self._download_raise
fdroidserver.common.config = {'jarsigner': 'fakepath'}
install.install_fdroid_apk(privacy_mode=False)
download_apk.assert_not_called()
download_fdroid_apk.assert_called_once()
@patch('fdroidserver.install.download_apk')
@patch('fdroidserver.install.download_fdroid_apk')
@patch('locale.getlocale', lambda: ('zh_CN', 'UTF-8'))
def test_install_fdroid_apk_privacy_mode_locale_auto(
self, download_fdroid_apk, download_apk
):
download_apk.side_effect = self._download_raise
download_fdroid_apk.side_effect = self._download_raise
fdroidserver.common.config = {'jarsigner': 'fakepath'}
install.install_fdroid_apk(privacy_mode=None)
download_apk.assert_not_called()
download_fdroid_apk.assert_called_once()
@patch('fdroidserver.net.download_using_mirrors', lambda m: 'testvalue')
def test_download_fdroid_apk_smokecheck(self):
self.assertEqual('testvalue', install.download_fdroid_apk())
@unittest.skipUnless(os.getenv('test_download_fdroid_apk'), 'requires net access')
def test_download_fdroid_apk(self):
f = fdroidserver.install.download_fdroid_apk()
f = install.download_fdroid_apk()
self.assertTrue(Path(f).exists())