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