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