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