chiark / gitweb /
fix bug listing new key created in init
[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 hashlib
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 from common import FDroidPopen, BuildException
33
34 config = {}
35 options = None
36
37 def write_to_config(key, value):
38     '''write a key/value to the local config.py'''
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 genpassword():
49     '''generate a random password for when generating keys'''
50     h = hashlib.sha256()
51     h.update(os.urandom(16)) # salt
52     h.update(bytes(socket.getfqdn()))
53     return h.digest().encode('base64').strip()
54
55
56 def genkey(keystore, repo_keyalias, password, keydname):
57     '''generate a new keystore with a new key in it for signing repos'''
58     logging.info('Generating a new key in "' + keystore + '"...')
59     common.write_password_file("keystorepass", password)
60     common.write_password_file("keypass", password)
61     p = FDroidPopen(['keytool', '-genkey',
62                 '-keystore', keystore, '-alias', repo_keyalias,
63                 '-keyalg', 'RSA', '-keysize', '4096',
64                 '-sigalg', 'SHA256withRSA',
65                 '-validity', '10000',
66                 '-storepass:file', config['keystorepassfile'],
67                 '-keypass:file', config['keypassfile'],
68                 '-dname', keydname])
69     # TODO keypass should be sent via stdin
70     if p.returncode != 0:
71         raise BuildException("Failed to generate key", p.stdout)
72     # now show the lovely key that was just generated
73     p = FDroidPopen(['keytool', '-list', '-v',
74                      '-keystore', keystore, '-alias', repo_keyalias,
75                      '-storepass:file', config['keystorepassfile']])
76     logging.info(p.stdout.strip() + '\n\n')
77
78
79 def main():
80
81     global options, config
82
83     # Parse command line...
84     parser = OptionParser()
85     parser.add_option("-v", "--verbose", action="store_true", default=False,
86                       help="Spew out even more information than normal")
87     parser.add_option("-q", "--quiet", action="store_true", default=False,
88                       help="Restrict output to warnings and errors")
89     parser.add_option("-d", "--distinguished-name", default=None,
90                       help="X.509 'Distiguished Name' used when generating keys")
91     parser.add_option("--keystore", default=None,
92                       help="Path to the keystore for the repo signing key")
93     parser.add_option("--repo-keyalias", default=None,
94                       help="Alias of the repo signing key in the keystore")
95     (options, args) = parser.parse_args()
96
97     # find root install prefix
98     tmp = os.path.dirname(sys.argv[0])
99     if os.path.basename(tmp) == 'bin':
100         prefix = os.path.dirname(tmp)
101         examplesdir = prefix + '/share/doc/fdroidserver/examples'
102     else:
103         # we're running straight out of the git repo
104         prefix = tmp
105         examplesdir = prefix + '/examples'
106
107     fdroiddir = os.getcwd()
108
109     if not os.path.exists('config.py') and not os.path.exists('repo'):
110         # 'metadata' and 'tmp' are created in fdroid
111         os.mkdir('repo')
112         shutil.copy(os.path.join(examplesdir, 'fdroid-icon.png'), fdroiddir)
113         shutil.copyfile(os.path.join(examplesdir, 'config.py'), 'config.py')
114         os.chmod('config.py', 0o0600)
115     else:
116         logging.warn('Looks like this is already an F-Droid repo, cowardly refusing to overwrite it...')
117         logging.info('Try running `fdroid init` in an empty directory.')
118         sys.exit()
119
120     # now that we have a local config.py, read configuration...
121     config = common.read_config(options)
122
123     # track down where the Android SDK is
124     if os.path.isdir(config['sdk_path']):
125         logging.info('Using "' + config['sdk_path'] + '" for the Android SDK')
126         sdk_path = config['sdk_path']
127     elif 'ANDROID_HOME' in os.environ.keys():
128         sdk_path = os.environ['ANDROID_HOME']
129     else:
130         default_sdk_path = '/opt/android-sdk'
131         while True:
132             s = raw_input('Enter the path to the Android SDK (' + default_sdk_path + '): ')
133             if re.match('^\s*$', s) != None:
134                 sdk_path = default_sdk_path
135             else:
136                 sdk_path = s
137             if os.path.isdir(os.path.join(sdk_path, 'build-tools')):
138                 break
139             else:
140                 logging.info('"' + s + '" does not contain the Android SDK! Try again...')
141     if os.path.isdir(sdk_path):
142         write_to_config('sdk_path', sdk_path)
143
144     # try to find a working aapt, in all the recent possible paths
145     build_tools = os.path.join(sdk_path, 'build-tools')
146     aaptdirs = []
147     aaptdirs.append(os.path.join(build_tools, config['build_tools']))
148     aaptdirs.append(build_tools)
149     for f in sorted(os.listdir(build_tools), reverse=True):
150         if os.path.isdir(os.path.join(build_tools, f)):
151             aaptdirs.append(os.path.join(build_tools, f))
152     for d in aaptdirs:
153         if os.path.isfile(os.path.join(d, 'aapt')):
154             aapt = os.path.join(d, 'aapt')
155             break
156     if os.path.isfile(aapt):
157         dirname = os.path.basename(os.path.dirname(aapt))
158         if dirname == 'build-tools':
159             # this is the old layout, before versioned build-tools
160             write_to_config('build_tools', '')
161         else:
162             write_to_config('build_tools', dirname)
163
164     # track down where the Android NDK is
165     ndk_path = '/opt/android-ndk'
166     if os.path.isdir(config['ndk_path']):
167         ndk_path = config['ndk_path']
168     elif 'ANDROID_NDK' in os.environ.keys():
169         logging.info('using ANDROID_NDK')
170         ndk_path = os.environ['ANDROID_NDK']
171     if os.path.isdir(ndk_path):
172         write_to_config('ndk_path', ndk_path)
173     # the NDK is optional so we don't prompt the user for it if its not found
174
175     # find or generate the keystore for the repo signing key. First try the
176     # path written in the default config.py.  Then check if the user has
177     # specified a path from the command line, which will trump all others.
178     # Otherwise, create ~/.local/share/fdroidserver and stick it in there.  If
179     # keystore is set to NONE, that means that Java will look for keys in a
180     # Hardware Security Module aka Smartcard.
181     keystore = config['keystore']
182     if options.keystore:
183         keystore = os.path.abspath(options.keystore)
184         if options.keystore == 'NONE':
185             keystore = options.keystore
186         else:
187             keystore = os.path.abspath(options.keystore)
188             if not os.path.exists(keystore):
189                 logging.info('"' + keystore
190                              + '" does not exist, creating a new keystore there.')
191     write_to_config('keystore', keystore)
192     if options.repo_keyalias:
193         repo_keyalias = options.repo_keyalias
194         write_to_config('repo_keyalias', repo_keyalias)
195     if options.distinguished_name:
196         keydname = options.distinguished_name
197         write_to_config('keydname', keydname)
198     if not os.path.isfile(keystore):
199         # no existing or specified keystore, generate the whole thing
200         keystoredir = os.path.dirname(keystore)
201         if not os.path.exists(keystoredir):
202             os.makedirs(keystoredir, mode=0o700)
203         password = genpassword()
204         write_to_config('keystorepass', password)
205         write_to_config('keypass', password)
206         if not options.repo_keyalias:
207             repo_keyalias = socket.getfqdn()
208             write_to_config('repo_keyalias', repo_keyalias)
209         if not options.distinguished_name:
210             keydname = 'CN=' + repo_keyalias + ', OU=F-Droid'
211             write_to_config('keydname', keydname)
212         genkey(keystore, repo_keyalias, password, keydname)
213
214     logging.info('Built repo based in "' + fdroiddir + '"')
215     logging.info('with this config:')
216     logging.info('  Android SDK:\t\t\t' + sdk_path)
217     logging.info('  Android SDK Build Tools:\t' + os.path.dirname(aapt))
218     logging.info('  Android NDK (optional):\t' + ndk_path)
219     logging.info('  Keystore for signing key:\t' + keystore)
220     logging.info('\nTo complete the setup, add your APKs to "' +
221           os.path.join(fdroiddir, 'repo') + '"' +
222 '''
223 then run "fdroid update -c; fdroid update".  You might also want to edit
224 "config.py" to set the URL, repo name, and more.  You should also set up
225 a signing key.
226
227 For more info: https://f-droid.org/manual/fdroid.html#Simple-Binary-Repository
228 and https://f-droid.org/manual/fdroid.html#Signing
229 ''')