chiark / gitweb /
Merge branch 'replace_optparse_with_argparse' into 'master'
[fdroidserver.git] / fdroidserver / init.py
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3 #
4 # update.py - part of the FDroid server tools
5 # Copyright (C) 2010-2013, Ciaran Gultnieks, ciaran@ciarang.com
6 # Copyright (C) 2013-2014 Daniel Martí <mvdan@mvdan.cc>
7 # Copyright (C) 2013 Hans-Christoph Steiner <hans@eds.org>
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU Affero General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU Affero General Public License for more details.
18 #
19 # You should have received a copy of the GNU Affero General Public License
20 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
22 import glob
23 import os
24 import re
25 import shutil
26 import socket
27 import sys
28 from argparse import ArgumentParser
29 import logging
30
31 import common
32
33 config = {}
34 options = None
35
36
37 def disable_in_config(key, value):
38     '''write a key/value to the local config.py, then comment it out'''
39     with open('config.py', 'r') as f:
40         data = f.read()
41     pattern = '\n[\s#]*' + key + '\s*=\s*"[^"]*"'
42     repl = '\n#' + key + ' = "' + value + '"'
43     data = re.sub(pattern, repl, data)
44     with open('config.py', 'w') as f:
45         f.writelines(data)
46
47
48 def main():
49
50     global options, config
51
52     # Parse command line...
53     parser = ArgumentParser()
54     parser.add_argument("-v", "--verbose", action="store_true", default=False,
55                         help="Spew out even more information than normal")
56     parser.add_argument("-q", "--quiet", action="store_true", default=False,
57                         help="Restrict output to warnings and errors")
58     parser.add_argument("-d", "--distinguished-name", default=None,
59                         help="X.509 'Distiguished Name' used when generating keys")
60     parser.add_argument("--keystore", default=None,
61                         help="Path to the keystore for the repo signing key")
62     parser.add_argument("--repo-keyalias", default=None,
63                         help="Alias of the repo signing key in the keystore")
64     parser.add_argument("--android-home", default=None,
65                         help="Path to the Android SDK (sometimes set in ANDROID_HOME)")
66     parser.add_argument("--no-prompt", action="store_true", default=False,
67                         help="Do not prompt for Android SDK path, just fail")
68     options = parser.parse_args()
69
70     # find root install prefix
71     tmp = os.path.dirname(sys.argv[0])
72     if os.path.basename(tmp) == 'bin':
73         prefix = None
74         egg_link = os.path.join(tmp, '..', 'local/lib/python2.7/site-packages/fdroidserver.egg-link')
75         if os.path.exists(egg_link):
76             # installed from local git repo
77             examplesdir = os.path.join(open(egg_link).readline().rstrip(), 'examples')
78         else:
79             prefix = os.path.dirname(os.path.dirname(__file__))  # use .egg layout
80             if not prefix.endswith('.egg'):  # use UNIX layout
81                 prefix = os.path.dirname(tmp)
82             examplesdir = prefix + '/share/doc/fdroidserver/examples'
83     else:
84         # we're running straight out of the git repo
85         prefix = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
86         examplesdir = prefix + '/examples'
87
88     aapt = None
89     fdroiddir = os.getcwd()
90     test_config = dict()
91     common.fill_config_defaults(test_config)
92
93     # track down where the Android SDK is, the default is to use the path set
94     # in ANDROID_HOME if that exists, otherwise None
95     if options.android_home is not None:
96         test_config['sdk_path'] = options.android_home
97     elif not common.test_sdk_exists(test_config):
98         if os.path.isfile('/usr/bin/aapt'):
99             # remove sdk_path and build_tools, they are not required
100             test_config.pop('sdk_path', None)
101             test_config.pop('build_tools', None)
102             # make sure at least aapt is found, since this can't do anything without it
103             test_config['aapt'] = common.find_sdk_tools_cmd('aapt')
104         else:
105             # if neither --android-home nor the default sdk_path exist, prompt the user
106             default_sdk_path = '/opt/android-sdk'
107             while not options.no_prompt:
108                 try:
109                     s = raw_input('Enter the path to the Android SDK ('
110                                   + default_sdk_path + ') here:\n> ')
111                 except KeyboardInterrupt:
112                     print('')
113                     sys.exit(1)
114                 if re.match('^\s*$', s) is not None:
115                     test_config['sdk_path'] = default_sdk_path
116                 else:
117                     test_config['sdk_path'] = s
118                 if common.test_sdk_exists(test_config):
119                     break
120     if not common.test_sdk_exists(test_config):
121         sys.exit(3)
122
123     if not os.path.exists('config.py'):
124         # 'metadata' and 'tmp' are created in fdroid
125         if not os.path.exists('repo'):
126             os.mkdir('repo')
127         shutil.copy(os.path.join(examplesdir, 'fdroid-icon.png'), fdroiddir)
128         shutil.copyfile(os.path.join(examplesdir, 'config.py'), 'config.py')
129         os.chmod('config.py', 0o0600)
130         # If android_home is None, test_config['sdk_path'] will be used and
131         # "$ANDROID_HOME" may be used if the env var is set up correctly.
132         # If android_home is not None, the path given from the command line
133         # will be directly written in the config.
134         if 'sdk_path' in test_config:
135             common.write_to_config(test_config, 'sdk_path', options.android_home)
136     else:
137         logging.warn('Looks like this is already an F-Droid repo, cowardly refusing to overwrite it...')
138         logging.info('Try running `fdroid init` in an empty directory.')
139         sys.exit()
140
141     if 'aapt' not in test_config or not os.path.isfile(test_config['aapt']):
142         # try to find a working aapt, in all the recent possible paths
143         build_tools = os.path.join(test_config['sdk_path'], 'build-tools')
144         aaptdirs = []
145         aaptdirs.append(os.path.join(build_tools, test_config['build_tools']))
146         aaptdirs.append(build_tools)
147         for f in os.listdir(build_tools):
148             if os.path.isdir(os.path.join(build_tools, f)):
149                 aaptdirs.append(os.path.join(build_tools, f))
150         for d in sorted(aaptdirs, reverse=True):
151             if os.path.isfile(os.path.join(d, 'aapt')):
152                 aapt = os.path.join(d, 'aapt')
153                 break
154         if os.path.isfile(aapt):
155             dirname = os.path.basename(os.path.dirname(aapt))
156             if dirname == 'build-tools':
157                 # this is the old layout, before versioned build-tools
158                 test_config['build_tools'] = ''
159             else:
160                 test_config['build_tools'] = dirname
161             common.write_to_config(test_config, 'build_tools')
162         common.ensure_build_tools_exists(test_config)
163
164     # now that we have a local config.py, read configuration...
165     config = common.read_config(options)
166
167     # the NDK is optional and there may be multiple versions of it, so it's
168     # left for the user to configure
169
170     # find or generate the keystore for the repo signing key. First try the
171     # path written in the default config.py.  Then check if the user has
172     # specified a path from the command line, which will trump all others.
173     # Otherwise, create ~/.local/share/fdroidserver and stick it in there.  If
174     # keystore is set to NONE, that means that Java will look for keys in a
175     # Hardware Security Module aka Smartcard.
176     keystore = config['keystore']
177     if options.keystore:
178         keystore = os.path.abspath(options.keystore)
179         if options.keystore == 'NONE':
180             keystore = options.keystore
181         else:
182             keystore = os.path.abspath(options.keystore)
183             if not os.path.exists(keystore):
184                 logging.info('"' + keystore
185                              + '" does not exist, creating a new keystore there.')
186     common.write_to_config(test_config, 'keystore', keystore)
187     repo_keyalias = None
188     if options.repo_keyalias:
189         repo_keyalias = options.repo_keyalias
190         common.write_to_config(test_config, 'repo_keyalias', repo_keyalias)
191     if options.distinguished_name:
192         keydname = options.distinguished_name
193         common.write_to_config(test_config, 'keydname', keydname)
194     if keystore == 'NONE':  # we're using a smartcard
195         common.write_to_config(test_config, 'repo_keyalias', '1')  # seems to be the default
196         disable_in_config('keypass', 'never used with smartcard')
197         common.write_to_config(test_config, 'smartcardoptions',
198                                ('-storetype PKCS11 -providerName SunPKCS11-OpenSC '
199                                 + '-providerClass sun.security.pkcs11.SunPKCS11 '
200                                 + '-providerArg opensc-fdroid.cfg'))
201         # find opensc-pkcs11.so
202         if not os.path.exists('opensc-fdroid.cfg'):
203             if os.path.exists('/usr/lib/opensc-pkcs11.so'):
204                 opensc_so = '/usr/lib/opensc-pkcs11.so'
205             elif os.path.exists('/usr/lib64/opensc-pkcs11.so'):
206                 opensc_so = '/usr/lib64/opensc-pkcs11.so'
207             else:
208                 files = glob.glob('/usr/lib/' + os.uname()[4] + '-*-gnu/opensc-pkcs11.so')
209                 if len(files) > 0:
210                     opensc_so = files[0]
211                 else:
212                     opensc_so = '/usr/lib/opensc-pkcs11.so'
213                     logging.warn('No OpenSC PKCS#11 module found, ' +
214                                  'install OpenSC then edit "opensc-fdroid.cfg"!')
215             with open(os.path.join(examplesdir, 'opensc-fdroid.cfg'), 'r') as f:
216                 opensc_fdroid = f.read()
217             opensc_fdroid = re.sub('^library.*', 'library = ' + opensc_so, opensc_fdroid,
218                                    flags=re.MULTILINE)
219             with open('opensc-fdroid.cfg', 'w') as f:
220                 f.write(opensc_fdroid)
221     elif not os.path.exists(keystore):
222         password = common.genpassword()
223         c = dict(test_config)
224         c['keystorepass'] = password
225         c['keypass'] = password
226         c['repo_keyalias'] = socket.getfqdn()
227         c['keydname'] = 'CN=' + c['repo_keyalias'] + ', OU=F-Droid'
228         common.write_to_config(test_config, 'keystorepass', password)
229         common.write_to_config(test_config, 'keypass', password)
230         common.write_to_config(test_config, 'repo_keyalias', c['repo_keyalias'])
231         common.write_to_config(test_config, 'keydname', c['keydname'])
232         common.genkeystore(c)
233
234     logging.info('Built repo based in "' + fdroiddir + '"')
235     logging.info('with this config:')
236     logging.info('  Android SDK:\t\t\t' + config['sdk_path'])
237     if aapt:
238         logging.info('  Android SDK Build Tools:\t' + os.path.dirname(aapt))
239     logging.info('  Android NDK r10e (optional):\t$ANDROID_NDK')
240     logging.info('  Keystore for signing key:\t' + keystore)
241     if repo_keyalias is not None:
242         logging.info('  Alias for key in store:\t' + repo_keyalias)
243     logging.info('\nTo complete the setup, add your APKs to "' +
244                  os.path.join(fdroiddir, 'repo') + '"' + '''
245 then run "fdroid update -c; fdroid update".  You might also want to edit
246 "config.py" to set the URL, repo name, and more.  You should also set up
247 a signing key (a temporary one might have been automatically generated).
248
249 For more info: https://f-droid.org/manual/fdroid.html#Simple-Binary-Repository
250 and https://f-droid.org/manual/fdroid.html#Signing
251 ''')