chiark / gitweb /
f8f71bfba75447aa5494a3f6728cd11c02234033
[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     common.setup_global_opts(parser)
55     parser.add_argument("-d", "--distinguished-name", default=None,
56                         help="X.509 'Distiguished Name' used when generating keys")
57     parser.add_argument("--keystore", default=None,
58                         help="Path to the keystore for the repo signing key")
59     parser.add_argument("--repo-keyalias", default=None,
60                         help="Alias of the repo signing key in the keystore")
61     parser.add_argument("--android-home", default=None,
62                         help="Path to the Android SDK (sometimes set in ANDROID_HOME)")
63     parser.add_argument("--no-prompt", action="store_true", default=False,
64                         help="Do not prompt for Android SDK path, just fail")
65     options = parser.parse_args()
66
67     # find root install prefix
68     tmp = os.path.dirname(sys.argv[0])
69     examplesdir = None
70     if os.path.basename(tmp) == 'bin':
71         egg_link = os.path.join(tmp, '..', 'local/lib/python2.7/site-packages/fdroidserver.egg-link')
72         if os.path.exists(egg_link):
73             # installed from local git repo
74             examplesdir = os.path.join(open(egg_link).readline().rstrip(), 'examples')
75         else:
76             # try .egg layout
77             examplesdir = os.path.dirname(os.path.dirname(__file__)) + '/share/doc/fdroidserver/examples'
78             if not os.path.exists(examplesdir):  # use UNIX layout
79                 examplesdir = os.path.dirname(tmp) + '/share/doc/fdroidserver/examples'
80     else:
81         # we're running straight out of the git repo
82         prefix = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
83         examplesdir = prefix + '/examples'
84
85     aapt = None
86     fdroiddir = os.getcwd()
87     test_config = dict()
88     common.fill_config_defaults(test_config)
89
90     # track down where the Android SDK is, the default is to use the path set
91     # in ANDROID_HOME if that exists, otherwise None
92     if options.android_home is not None:
93         test_config['sdk_path'] = options.android_home
94     elif not common.test_sdk_exists(test_config):
95         if os.path.isfile('/usr/bin/aapt'):
96             # remove sdk_path and build_tools, they are not required
97             test_config.pop('sdk_path', None)
98             test_config.pop('build_tools', None)
99             # make sure at least aapt is found, since this can't do anything without it
100             test_config['aapt'] = common.find_sdk_tools_cmd('aapt')
101         else:
102             # if neither --android-home nor the default sdk_path exist, prompt the user
103             default_sdk_path = '/opt/android-sdk'
104             while not options.no_prompt:
105                 try:
106                     s = raw_input('Enter the path to the Android SDK ('
107                                   + default_sdk_path + ') here:\n> ')
108                 except KeyboardInterrupt:
109                     print('')
110                     sys.exit(1)
111                 if re.match('^\s*$', s) is not None:
112                     test_config['sdk_path'] = default_sdk_path
113                 else:
114                     test_config['sdk_path'] = s
115                 if common.test_sdk_exists(test_config):
116                     break
117     if not common.test_sdk_exists(test_config):
118         sys.exit(3)
119
120     if not os.path.exists('config.py'):
121         # 'metadata' and 'tmp' are created in fdroid
122         if not os.path.exists('repo'):
123             os.mkdir('repo')
124         shutil.copy(os.path.join(examplesdir, 'fdroid-icon.png'), fdroiddir)
125         shutil.copyfile(os.path.join(examplesdir, 'config.py'), 'config.py')
126         os.chmod('config.py', 0o0600)
127         # If android_home is None, test_config['sdk_path'] will be used and
128         # "$ANDROID_HOME" may be used if the env var is set up correctly.
129         # If android_home is not None, the path given from the command line
130         # will be directly written in the config.
131         if 'sdk_path' in test_config:
132             common.write_to_config(test_config, 'sdk_path', options.android_home)
133     else:
134         logging.warn('Looks like this is already an F-Droid repo, cowardly refusing to overwrite it...')
135         logging.info('Try running `fdroid init` in an empty directory.')
136         sys.exit()
137
138     if 'aapt' not in test_config or not os.path.isfile(test_config['aapt']):
139         # try to find a working aapt, in all the recent possible paths
140         build_tools = os.path.join(test_config['sdk_path'], 'build-tools')
141         aaptdirs = []
142         aaptdirs.append(os.path.join(build_tools, test_config['build_tools']))
143         aaptdirs.append(build_tools)
144         for f in os.listdir(build_tools):
145             if os.path.isdir(os.path.join(build_tools, f)):
146                 aaptdirs.append(os.path.join(build_tools, f))
147         for d in sorted(aaptdirs, reverse=True):
148             if os.path.isfile(os.path.join(d, 'aapt')):
149                 aapt = os.path.join(d, 'aapt')
150                 break
151         if os.path.isfile(aapt):
152             dirname = os.path.basename(os.path.dirname(aapt))
153             if dirname == 'build-tools':
154                 # this is the old layout, before versioned build-tools
155                 test_config['build_tools'] = ''
156             else:
157                 test_config['build_tools'] = dirname
158             common.write_to_config(test_config, 'build_tools')
159         common.ensure_build_tools_exists(test_config)
160
161     # now that we have a local config.py, read configuration...
162     config = common.read_config(options)
163
164     # the NDK is optional and there may be multiple versions of it, so it's
165     # left for the user to configure
166
167     # find or generate the keystore for the repo signing key. First try the
168     # path written in the default config.py.  Then check if the user has
169     # specified a path from the command line, which will trump all others.
170     # Otherwise, create ~/.local/share/fdroidserver and stick it in there.  If
171     # keystore is set to NONE, that means that Java will look for keys in a
172     # Hardware Security Module aka Smartcard.
173     keystore = config['keystore']
174     if options.keystore:
175         keystore = os.path.abspath(options.keystore)
176         if options.keystore == 'NONE':
177             keystore = options.keystore
178         else:
179             keystore = os.path.abspath(options.keystore)
180             if not os.path.exists(keystore):
181                 logging.info('"' + keystore
182                              + '" does not exist, creating a new keystore there.')
183     common.write_to_config(test_config, 'keystore', keystore)
184     repo_keyalias = None
185     if options.repo_keyalias:
186         repo_keyalias = options.repo_keyalias
187         common.write_to_config(test_config, 'repo_keyalias', repo_keyalias)
188     if options.distinguished_name:
189         keydname = options.distinguished_name
190         common.write_to_config(test_config, 'keydname', keydname)
191     if keystore == 'NONE':  # we're using a smartcard
192         common.write_to_config(test_config, 'repo_keyalias', '1')  # seems to be the default
193         disable_in_config('keypass', 'never used with smartcard')
194         common.write_to_config(test_config, 'smartcardoptions',
195                                ('-storetype PKCS11 -providerName SunPKCS11-OpenSC '
196                                 + '-providerClass sun.security.pkcs11.SunPKCS11 '
197                                 + '-providerArg opensc-fdroid.cfg'))
198         # find opensc-pkcs11.so
199         if not os.path.exists('opensc-fdroid.cfg'):
200             if os.path.exists('/usr/lib/opensc-pkcs11.so'):
201                 opensc_so = '/usr/lib/opensc-pkcs11.so'
202             elif os.path.exists('/usr/lib64/opensc-pkcs11.so'):
203                 opensc_so = '/usr/lib64/opensc-pkcs11.so'
204             else:
205                 files = glob.glob('/usr/lib/' + os.uname()[4] + '-*-gnu/opensc-pkcs11.so')
206                 if len(files) > 0:
207                     opensc_so = files[0]
208                 else:
209                     opensc_so = '/usr/lib/opensc-pkcs11.so'
210                     logging.warn('No OpenSC PKCS#11 module found, ' +
211                                  'install OpenSC then edit "opensc-fdroid.cfg"!')
212             with open(os.path.join(examplesdir, 'opensc-fdroid.cfg'), 'r') as f:
213                 opensc_fdroid = f.read()
214             opensc_fdroid = re.sub('^library.*', 'library = ' + opensc_so, opensc_fdroid,
215                                    flags=re.MULTILINE)
216             with open('opensc-fdroid.cfg', 'w') as f:
217                 f.write(opensc_fdroid)
218     elif not os.path.exists(keystore):
219         password = common.genpassword()
220         c = dict(test_config)
221         c['keystorepass'] = password
222         c['keypass'] = password
223         c['repo_keyalias'] = socket.getfqdn()
224         c['keydname'] = 'CN=' + c['repo_keyalias'] + ', OU=F-Droid'
225         common.write_to_config(test_config, 'keystorepass', password)
226         common.write_to_config(test_config, 'keypass', password)
227         common.write_to_config(test_config, 'repo_keyalias', c['repo_keyalias'])
228         common.write_to_config(test_config, 'keydname', c['keydname'])
229         common.genkeystore(c)
230
231     logging.info('Built repo based in "' + fdroiddir + '"')
232     logging.info('with this config:')
233     logging.info('  Android SDK:\t\t\t' + config['sdk_path'])
234     if aapt:
235         logging.info('  Android SDK Build Tools:\t' + os.path.dirname(aapt))
236     logging.info('  Android NDK r10e (optional):\t$ANDROID_NDK')
237     logging.info('  Keystore for signing key:\t' + keystore)
238     if repo_keyalias is not None:
239         logging.info('  Alias for key in store:\t' + repo_keyalias)
240     logging.info('\nTo complete the setup, add your APKs to "' +
241                  os.path.join(fdroiddir, 'repo') + '"' + '''
242 then run "fdroid update -c; fdroid update".  You might also want to edit
243 "config.py" to set the URL, repo name, and more.  You should also set up
244 a signing key (a temporary one might have been automatically generated).
245
246 For more info: https://f-droid.org/manual/fdroid.html#Simple-Binary-Repository
247 and https://f-droid.org/manual/fdroid.html#Signing
248 ''')