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