chiark / gitweb /
nightly: support arbitrary keystore files for setup
[fdroidserver.git] / fdroidserver / nightly.py
index 3834f79e9752ebf9194b87d68b9ee2644f5948b2..1e150c804b41d19c21c36cdd0f6681027ea3f61f 100644 (file)
@@ -28,6 +28,7 @@ import shutil
 import subprocess
 import sys
 import tempfile
+import yaml
 from urllib.parse import urlparse
 from argparse import ArgumentParser
 
@@ -46,13 +47,15 @@ DISTINGUISHED_NAME = 'CN=Android Debug,O=Android,C=US'
 NIGHTLY = '-nightly'
 
 
-def _ssh_key_from_debug_keystore():
+def _ssh_key_from_debug_keystore(keystore=KEYSTORE_FILE):
     tmp_dir = tempfile.mkdtemp(prefix='.')
     privkey = os.path.join(tmp_dir, '.privkey')
     key_pem = os.path.join(tmp_dir, '.key.pem')
     p12 = os.path.join(tmp_dir, '.keystore.p12')
-    subprocess.check_call([common.config['keytool'], '-importkeystore',
-                           '-srckeystore', KEYSTORE_FILE, '-srcalias', KEY_ALIAS,
+    _config = dict()
+    common.fill_config_defaults(_config)
+    subprocess.check_call([_config['keytool'], '-importkeystore',
+                           '-srckeystore', keystore, '-srcalias', KEY_ALIAS,
                            '-srcstorepass', PASSWORD, '-srckeypass', PASSWORD,
                            '-destkeystore', p12, '-destalias', KEY_ALIAS,
                            '-deststorepass', PASSWORD, '-destkeypass', PASSWORD,
@@ -67,8 +70,9 @@ def _ssh_key_from_debug_keystore():
 
     rsakey = paramiko.RSAKey.from_private_key_file(privkey)
     fingerprint = base64.b64encode(hashlib.sha256(rsakey.asbytes()).digest()).decode('ascii').rstrip('=')
-    ssh_private_key_file = os.path.join(tmp_dir, 'debug_keystore_' + fingerprint + '_id_rsa')
-    os.rename(privkey, ssh_private_key_file)
+    ssh_private_key_file = os.path.join(tmp_dir, 'debug_keystore_'
+                                        + fingerprint.replace('/', '_') + '_id_rsa')
+    shutil.move(privkey, ssh_private_key_file)
 
     pub = rsakey.get_name() + ' ' + rsakey.get_base64() + ' ' + ssh_private_key_file
     with open(ssh_private_key_file + '.pub', 'w') as fp:
@@ -83,6 +87,8 @@ def main():
 
     parser = ArgumentParser(usage="%(prog)s")
     common.setup_global_opts(parser)
+    parser.add_argument("--keystore", default=KEYSTORE_FILE,
+                        help=_("Specify which debug keystore file to use."))
     parser.add_argument("--show-secret-var", action="store_true", default=False,
                         help=_("Print the secret variable to the terminal for easy copy/paste"))
     parser.add_argument("--file", default='app/build/outputs/apk/*.apk',
@@ -91,7 +97,6 @@ def main():
                         help=_("Don't use rsync checksums"))
     # TODO add --with-btlog
     options = parser.parse_args()
-    common.read_config(None)
 
     # force a tighter umask since this writes private key material
     umask = os.umask(0o077)
@@ -155,6 +160,7 @@ def main():
         repo_url = repo_base + '/repo'
         git_mirror_path = os.path.join(repo_basedir, 'git-mirror')
         git_mirror_repodir = os.path.join(git_mirror_path, 'fdroid', 'repo')
+        git_mirror_metadatadir = os.path.join(git_mirror_path, 'fdroid', 'metadata')
         if not os.path.isdir(git_mirror_repodir):
             logging.debug(_('cloning {url}').format(url=clone_url))
             try:
@@ -186,20 +192,22 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
         mirror_git_repo.git.add(all=True)
         mirror_git_repo.index.commit("update README")
 
-        icon_path = os.path.join(repo_basedir, 'icon.png')
+        icon_path = os.path.join(git_mirror_path, 'icon.png')
         try:
             import qrcode
-            img = qrcode.make('Some data here')
-            with open(icon_path, 'wb') as fp:
-                fp.write(img)
+            qrcode.make(repo_url).save(icon_path)
         except Exception:
             exampleicon = os.path.join(common.get_examples_dir(), 'fdroid-icon.png')
             shutil.copy(exampleicon, icon_path)
         mirror_git_repo.git.add(all=True)
         mirror_git_repo.index.commit("update repo/website icon")
+        shutil.copy(icon_path, repo_basedir)
 
         os.chdir(repo_basedir)
-        common.local_rsync(options, git_mirror_repodir + '/', 'repo/')
+        if os.path.isdir(git_mirror_repodir):
+            common.local_rsync(options, git_mirror_repodir + '/', 'repo/')
+        if os.path.isdir(git_mirror_metadatadir):
+            common.local_rsync(options, git_mirror_metadatadir + '/', 'metadata/')
 
         ssh_private_key_file = _ssh_key_from_debug_keystore()
         # this is needed for GitPython to find the SSH key
@@ -230,6 +238,8 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
         with open('config.py', 'w') as fp:
             fp.write(config)
         os.chmod('config.py', 0o600)
+        config = common.read_config(options)
+        common.assert_config_keystore(config)
 
         for root, dirs, files in os.walk(cibase):
             for d in ('fdroid', '.git', '.gradle'):
@@ -238,12 +248,14 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
             for f in files:
                 if f.endswith('-debug.apk'):
                     apkfilename = os.path.join(root, f)
-                    logging.debug(_('copying {apkfilename} into {path}')
-                                  .format(apkfilename=apkfilename, path=repodir))
+                    logging.debug(_('Striping mystery signature from {apkfilename}')
+                                  .format(apkfilename=apkfilename))
                     destapk = os.path.join(repodir, os.path.basename(f))
-                    shutil.copyfile(apkfilename, destapk)
-                    shutil.copystat(apkfilename, destapk)
-                    os.chmod(destapk, 0o644)
+                    os.chmod(apkfilename, 0o644)
+                    logging.debug(_('Resigning {apkfilename} with provided debug.keystore')
+                                  .format(apkfilename=os.path.basename(apkfilename)))
+                    common.apk_strip_signatures(apkfilename, strip_manifest=True)
+                    common.sign_apk(apkfilename, destapk, KEY_ALIAS)
 
         if options.verbose:
             logging.debug(_('attempting bare ssh connection to test deploy key:'))
@@ -254,7 +266,23 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
             except subprocess.CalledProcessError:
                 pass
 
-        subprocess.check_call(['fdroid', 'update', '--rename-apks', '--verbose'], cwd=repo_basedir)
+        app_url = clone_url[:-len(NIGHTLY)]
+        template = dict()
+        template['AuthorName'] = clone_url.split('/')[4]
+        template['AuthorWebSite'] = '/'.join(clone_url.split('/')[:4])
+        template['Categories'] = ['nightly']
+        template['SourceCode'] = app_url
+        template['IssueTracker'] = app_url + '/issues'
+        template['Summary'] = 'Nightly build of ' + urlparse(app_url).path[1:]
+        template['Description'] = template['Summary']
+        with open('template.yml', 'w') as fp:
+            yaml.dump(template, fp)
+
+        subprocess.check_call(['fdroid', 'update', '--rename-apks', '--create-metadata', '--verbose'],
+                              cwd=repo_basedir)
+        common.local_rsync(options, repo_basedir + '/metadata/', git_mirror_metadatadir + '/')
+        mirror_git_repo.git.add(all=True)
+        mirror_git_repo.index.commit("update app metadata")
         try:
             subprocess.check_call(['fdroid', 'server', 'update', '--verbose'], cwd=repo_basedir)
         except subprocess.CalledProcessError:
@@ -265,20 +293,30 @@ Last updated: {date}'''.format(repo_git_base=repo_git_base,
             shutil.rmtree(os.path.dirname(ssh_private_key_file))
 
     else:
+        if not os.path.isfile(options.keystore):
+            androiddir = os.path.dirname(options.keystore)
+            if not os.path.exists(androiddir):
+                os.mkdir(androiddir)
+                logging.info(_('created {path}').format(path=androiddir))
+            logging.error(_('{path} does not exist!  Create it by running:').format(path=options.keystore)
+                          + '\n    keytool -genkey -v -keystore ' + options.keystore + ' -storepass android \\'
+                          + '\n     -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 \\'
+                          + '\n     -dname "CN=Android Debug,O=Android,C=US"')
+            sys.exit(1)
         ssh_dir = os.path.join(os.getenv('HOME'), '.ssh')
         os.makedirs(os.path.dirname(ssh_dir), exist_ok=True)
-        privkey = _ssh_key_from_debug_keystore()
+        privkey = _ssh_key_from_debug_keystore(options.keystore)
         ssh_private_key_file = os.path.join(ssh_dir, os.path.basename(privkey))
-        os.rename(privkey, ssh_private_key_file)
-        os.rename(privkey + '.pub', ssh_private_key_file + '.pub')
+        shutil.move(privkey, ssh_private_key_file)
+        shutil.move(privkey + '.pub', ssh_private_key_file + '.pub')
         if shutil.rmtree.avoids_symlink_attacks:
             shutil.rmtree(os.path.dirname(privkey))
 
         if options.show_secret_var:
-            with open(KEYSTORE_FILE, 'rb') as fp:
+            with open(options.keystore, 'rb') as fp:
                 debug_keystore = base64.standard_b64encode(fp.read()).decode('ascii')
             print(_('\n{path} encoded for the DEBUG_KEYSTORE secret variable:')
-                  .format(path=KEYSTORE_FILE))
+                  .format(path=options.keystore))
             print(debug_keystore)
 
     os.umask(umask)