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