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