[FIX] Generation script
[cds-indico.git] / indico / MaKaC / consoleScripts / installBase.py
blob9e4c7d21b484b142f2f3e17ed989f4086ae60ce3
1 # -*- coding: utf-8 -*-
2 ##
3 ##
4 ## This file is part of CDS Indico.
5 ## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
6 ##
7 ## CDS Indico is free software; you can redistribute it and/or
8 ## modify it under the terms of the GNU General Public License as
9 ## published by the Free Software Foundation; either version 2 of the
10 ## License, or (at your option) any later version.
12 ## CDS Indico is distributed in the hope that it will be useful, but
13 ## WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ## General Public License for more details.
17 ## You should have received a copy of the GNU General Public License
18 ## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
19 ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21 """
22 This file contains functions used by both 'python setup.py install' and after-easy_install
23 based installations.
24 """
26 import commands
27 import os
28 import re
29 import shutil
30 import sys
31 import pkg_resources
32 import copy
35 if sys.platform == 'linux2':
36 import pwd
37 import grp
39 import MaKaC
41 globals()['INDICO_INSTALL'] = False
43 LOCALDATABASEDIR = '/opt/indico/db'
45 # Egg directory + etc/indico.conf
46 PWD_INDICO_CONF = os.path.abspath(os.path.join(
47 os.path.split(os.path.dirname(MaKaC.__file__))[0],'etc', 'indico.conf'
50 def setIndicoInstallMode(newmode):
51 """
52 Sets indico install mode.
53 This function and getIndicoInstallMode are used by some __init__.py inside
54 MaKaC to load or not some modules. At installation time those modules are not
55 available. That's why we need to skip them. They are imported there only as
56 shortcuts.
57 """
58 global INDICO_INSTALL
59 INDICO_INSTALL = newmode
62 def getIndicoInstallMode():
63 global INDICO_INSTALL
64 return INDICO_INSTALL
67 def createDirs(directories):
68 '''Creates directories that are not automatically created by setup.install or easy_install'''
69 for d in ['log', 'tmp', 'cache', 'archive']:
70 if not os.path.exists(directories[d]):
71 os.makedirs(directories[d])
73 def upgrade_indico_conf(existing_conf, new_conf, mixinValues={}):
74 """
75 Copies new_conf values to existing_conf file preserving existing_conf's values
77 If mixinValues is given its items will be preserved above existing_conf's values and new_conf's values
78 """
80 # We retrieve values from newest indico.conf
81 execfile(new_conf)
82 new_values = locals().copy()
83 # We retrieve values from existing indico.conf
84 execfile(existing_conf)
85 existing_values = locals().copy()
87 new_values.update(existing_values)
88 new_values.update(mixinValues)
91 # We have to preserve options not currently present in the bundled indico.conf because they
92 # may belong to a plugin. This functionality can lead to forget adding new options to
93 # Configuration.py so be careful my friend.
95 # We remove vars defined here that aren't options
96 for k in ('new_values', 'new_conf', 'existing_conf', 'mixinValues'):
97 new_values.pop(k)
99 result_values = new_values
101 # We update a current copy of indico.conf with the new values
102 new_contents = open(new_conf).read()
104 for k in new_values:
105 if new_values[k].__class__ == str:
106 regexp = re.compile('^(%s[ ]*=[ ]*[\'"]{1})([^\'"]*)([\'"]{1})' % k, re.MULTILINE)
107 if regexp.search(new_contents):
108 new_contents = re.sub(regexp, "\\g<1>%s\\3" % result_values[k], new_contents)
109 else:
110 new_contents = "%s\n%s = '%s'" % (new_contents, k, result_values[k])
111 elif new_values[k].__class__ == int:
112 regexp = re.compile('^(%s[ ]*=[ ]*)([0-9]+)' % k, re.MULTILINE)
113 if regexp.search(new_contents):
114 new_contents = re.sub(regexp, "\\g<1>%s" % result_values[k], new_contents)
115 else:
116 new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
118 elif new_values[k].__class__ == bool:
119 regexp = re.compile('^(%s[ ]*=[ ]*)(True|False)' % k, re.MULTILINE)
120 if regexp.search(new_contents):
121 new_contents = re.sub(regexp, "\\g<1>%s" % result_values[k], new_contents)
122 else:
123 new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
125 elif new_values[k].__class__ == tuple:
126 regexp = re.compile('^(%s[ ]*=[ ]*)[\(]{1}([^\)]+)[\)]{1}' % k, re.MULTILINE)
127 if regexp.search(new_contents):
128 new_contents = re.sub(regexp, "\\g<1>%s" % str(result_values[k]), new_contents)
129 else:
130 new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
132 elif new_values[k].__class__ == dict:
133 regexp = re.compile('^(%s[ ]*=[ ]*)[\{](.+)[\}$]' % k, re.MULTILINE)
134 if regexp.search(new_contents):
135 new_contents = re.sub(regexp, "\\g<1>%s" % str(result_values[k]), new_contents)
136 else:
137 new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
139 elif new_values[k].__class__ == list:
140 regexp = re.compile('^(%s[ ]*=[ ]*)[\[]{1}([^\]]+)[\]]{1}' % k, re.MULTILINE)
141 if regexp.search(new_contents):
142 new_contents = re.sub(regexp, "\\g<1>%s" % str(result_values[k]), new_contents)
143 else:
144 new_contents = "%s\n%s = %s" % (new_contents, k, str(result_values[k]))
145 else:
146 raise Exception('Invalid config value "%s = %s"' % (k, new_values[k]))
148 # We write unknown options to the end of the file, they may not be just outdated options but plugins'
150 open(existing_conf, 'w').write(new_contents)
153 def modifyOnDiskIndicoConfOption(indico_conf, optionName, optionValue):
154 upgrade_indico_conf(indico_conf, indico_conf, {optionName: optionValue})
157 def updateIndicoConfPathInsideMaKaCConfig(indico_conf_path, makacconfigpy_path):
158 '''Modifies the location of indico.conf referenced inside makacconfigpy_path to
159 point to indico_conf_path'''
160 fdata = open(makacconfigpy_path).read()
161 fdata = re.sub('indico_conf[ ]*=[ ]*[\'"]{1}([^\'"]*)[\'"]{1}', "indico_conf = \"%s\"" % indico_conf_path, fdata)
162 open(makacconfigpy_path, 'w').write(fdata)
164 def _updateMaKaCEggCache(file, path):
165 fdata = open(file).read()
166 fdata = re.sub('\/opt\/indico\/tmp', path, fdata)
167 open(file, 'w').write(fdata)
170 def compileAllLanguages(cmd):
171 '''Generates .mo files from .po files'''
173 try:
174 pkg_resources.require('babel')
175 except pkg_resources.DistributionNotFound:
176 print """
177 Babel not found! Babel is needed for internationalization if you're building Indico from source. Please install it and re-run this program.
178 i.e. try 'easy_install babel'"""
179 sys.exit(-1)
181 # call commands directly
182 cc = cmd.distribution.get_command_obj('compile_catalog')
183 cc.run()
184 gjs = cmd.distribution.get_command_obj('compile_catalog_js')
185 gjs.run()
188 def copyTreeSilently(source, target):
189 '''Copies source tree to target tree overwriting existing files'''
190 source = os.path.normpath(source)
191 target = os.path.normpath(target)
192 for root, dirs, files in os.walk(source, topdown=False):
193 for name in files:
194 fullpath = os.path.normpath(os.path.join(root, name))
195 dstfile = os.path.normpath(fullpath.replace(source, target))
196 targetdir = os.path.dirname(dstfile)
197 if not os.path.exists(targetdir):
198 os.makedirs(targetdir)
199 try:
200 shutil.copy(fullpath, dstfile)
201 except Exception, e:
202 print e
205 def jsCompress():
206 '''Packs and minifies javascript files'''
208 try:
209 pkg_resources.require('JSTools')
210 except pkg_resources.DistributionNotFound:
211 print """
212 JSTools not found! JSTools is needed for JavaScript compression, if you're building Indico from source. Please install it.
213 i.e. try 'easy_install jstools'"""
214 sys.exit(-1)
216 jsbuildPath = 'jsbuild'
217 os.chdir('./etc/js')
218 os.system('%s -o ../../indico/htdocs/js/jquery/pack jquery.cfg' % jsbuildPath)
219 os.system('%s -o ../../indico/htdocs/js/indico/pack indico.cfg' % jsbuildPath)
220 os.system('%s -o ../../indico/htdocs/js/presentation/pack presentation.cfg' % jsbuildPath )
221 os.system('%s -o ../../indico/MaKaC/plugins/InstantMessaging/htdocs/js instantmessaging.cfg ' % jsbuildPath )
222 os.system('%s -o ../../indico/ext/livesync/htdocs/js livesync.cfg' % jsbuildPath )
223 os.chdir('../..')
226 def _checkModPythonIsInstalled():
227 pass
230 def _guessApacheUidGid():
232 finalUser, finalGroup = None, None
234 for username in ['apache', 'www-data', 'www']:
235 try:
236 finalUser = pwd.getpwnam(username)
237 break
238 except KeyError:
239 pass
241 for groupname in ['apache', 'www-data', 'www']:
242 try:
243 finalGroup = grp.getgrnam(groupname)
244 break
245 except KeyError:
246 pass
248 return finalUser, finalGroup
250 def _findApacheUserGroup(uid, gid):
251 # if we are on linux we need to give proper permissions to the results directory
252 if sys.platform == "linux2":
253 prompt = True
255 # Check to see if uid/gid were provided through commandline
256 if uid and gid:
257 try:
258 accessuser = pwd.getpwuid(int(uid)).pw_name
259 accessgroup = grp.getgrgid(int(gid)).gr_name
260 prompt = False
261 except KeyError:
262 print 'uid/gid pair (%s/%s) provided through command line is false' % (uid, gid)
263 else:
264 print "uid/gid not provided. Trying to guess them... ",
265 uid, gid = _guessApacheUidGid()
266 if uid and gid:
267 print "found %s(%s) %s(%s)" % (uid.pw_name, uid.pw_uid,
268 gid.gr_name, gid.gr_gid)
269 accessuser = uid.pw_name
270 accessgroup = gid.gr_name
271 prompt = False
273 if prompt == True:
274 valid_credentials = False
275 while not valid_credentials:
276 accessuser = raw_input("\nPlease enter the user that will be running the Apache server: ")
277 accessgroup = raw_input("\nPlease enter the group that will be running the Apache server: ")
278 try:
279 pwd.getpwnam(accessuser)
280 grp.getgrnam(accessgroup)
281 valid_credentials = True
282 except KeyError:
283 print "\nERROR: Invalid user/group pair (%s/%s)" % (accessuser, accessgroup)
285 return accessuser, accessgroup
286 else: #for windows
287 return "apache", "apache"
290 def _checkDirPermissions(directories, dbInstalledBySetupPy=False, accessuser=None, accessgroup=None):
291 '''Makes sure that directories which need write access from Apache have
292 the correct permissions
294 - dbInstalledBySetupPy if True, means that the dbdir has been created by the setup
295 process and needs to be checked.
297 - uid and gid: if they are valid user_ids and group_ids they will be used to chown
298 the directories instead of the indico.conf ones.
301 print "\nWe need to 'sudo' in order to set the permissions of some directories..."
303 dirs2check = list(directories[x] for x in ['htdocs', 'log', 'tmp', 'cache', 'archive'] if directories.has_key(x))
304 if dbInstalledBySetupPy:
305 dirs2check.append(dbInstalledBySetupPy)
307 for dir in dirs2check:
308 print commands.getoutput("if test $(which sudo); then CMD=\"sudo\"; fi; $CMD chown -R %s:%s %s" % (accessuser, accessgroup, dir))
311 def _existingConfiguredEgg():
312 '''Returns true if an existing EGG has been detected.'''
314 # remove '.' and './indico'
315 path = copy.copy(sys.path)
316 path = path[2:]
318 env = pkg_resources.Environment(search_path=path)
319 env.scan(search_path=path)
321 # search for all indico dists
322 indico_dists = env['indico']
324 for dist in indico_dists:
325 eggPath = dist.location
327 print "* EGG found at %s..." % eggPath,
328 fdata = open(os.path.join(eggPath,'MaKaC','common','MaKaCConfig.py'), 'r').read()
330 m = re.search('^\s*indico_conf\s*=\s*[\'"]{1}([^\'"]*)[\'"]{1}', fdata, re.MULTILINE)
331 if m and m.group(1) != '':
332 print '%s' % m.group(1)
333 return m.group(1)
334 else:
335 print 'unconfigured'
336 return None
338 def _extractDirsFromConf(conf):
339 execfile(conf)
340 values = locals().copy()
342 return {'bin': values['BinDir'],
343 'doc': values['DocumentationDir'],
344 'etc': values['ConfigurationDir'],
345 'htdocs': values['HtdocsDir'],
346 'tmp': values['UploadedFilesTempDir'],
347 'log': values['LogDir'],
348 'cache': values['XMLCacheDir'],
349 'archive': values['ArchiveDir'],
350 'db': LOCALDATABASEDIR}
352 def _replacePrefixInConf(filePath, prefix):
353 fdata = open(filePath).read()
354 fdata = re.sub('\/opt\/indico', prefix, fdata)
355 open(filePath, 'w').write(fdata)
357 def _updateDbConfigFiles(dbDir, logDir, cfgDir, tmpDir, uid):
358 filePath = os.path.join(cfgDir, 'zodb.conf')
359 fdata = open(filePath).read()
360 fdata = re.sub('\/opt\/indico\/db', dbDir, fdata)
361 fdata = re.sub('\/opt\/indico\/log', logDir, fdata)
362 open(filePath, 'w').write(fdata)
364 filePath = os.path.join(cfgDir, 'zdctl.conf')
365 fdata = open(filePath).read()
366 fdata = re.sub('\/opt\/indico\/db', dbDir, fdata)
367 fdata = re.sub('\/opt\/indico\/etc', cfgDir, fdata)
368 fdata = re.sub('\/opt\/indico\/tmp', tmpDir, fdata)
369 fdata = re.sub('(\s+user\s+)apache', '\g<1>%s' % uid, fdata)
370 open(filePath, 'w').write(fdata)
373 def indico_pre_install(defaultPrefix, force_upgrade=False, existingConfig=None):
375 defaultPrefix is the default prefix dir where Indico will be installed
378 upgrade = False
380 # Configuration specified in the command-line
381 if existingConfig:
382 existing = existingConfig
383 # upgrade is mandatory
384 upgrade = True
385 else:
386 # Config not specified
387 # automatically find an EGG in the site-packages path
388 existing = _existingConfiguredEgg()
390 # if an EGG was found but upgrade is not forced
391 if existing and not force_upgrade:
393 # Ask the user
394 opt = None
396 while opt not in ('', 'e', 'E', 'u'):
397 opt = raw_input('''
398 An existing Indico configuration has been detected at:
402 At this point you can:
404 [u]pgrade the existing installation
406 [E]xit this installation process
408 What do you want to do [u/E]? ''' % existing)
409 if opt in ('', 'e', 'E'):
410 print "\nExiting installation..\n"
411 sys.exit()
412 elif opt == 'u':
413 upgrade = True
414 else:
415 print "\nInvalid answer. Exiting installation..\n"
416 sys.exit()
417 # if and EGG was found and upgrade is forced
418 elif existing and force_upgrade:
419 upgrade = True
421 if upgrade:
422 print 'Upgrading the existing Indico installation..'
423 return _extractDirsFromConf(existing)
424 else:
425 # then, in case no previous installation exists
426 return fresh_install(defaultPrefix)
429 def fresh_install(defaultPrefix):
431 # start from scratch
432 print "No previous installation of Indico was found."
433 print "Please specify a directory prefix:"
435 # ask for a directory prefix
436 prefixDir = raw_input('[%s]: ' % defaultPrefix).strip()
438 if prefixDir == '':
439 prefixDir = defaultPrefix
441 configDir = os.path.join(prefixDir, 'etc')
443 # create the directory that will contain the configuration files
444 if not os.path.exists(configDir):
445 os.makedirs(configDir)
447 indicoconfpath = os.path.join(configDir, 'indico.conf')
449 opt = raw_input('''
450 You now need to configure Indico, by editing indico.conf or letting us do it for you.
451 At this point you can:
453 [c]opy the default values in etc/indico.conf.sample to a new etc/indico.conf
454 and continue the installation
456 [A]bort the installation in order to inspect etc/indico.conf.sample
457 and / or to make your own etc/indico.conf
459 What do you want to do [c/a]? ''')
460 if opt in ('c', 'C'):
461 shutil.copy(PWD_INDICO_CONF + '.sample', indicoconfpath)
462 _replacePrefixInConf(indicoconfpath, prefixDir)
463 elif opt in ('', 'a', 'A'):
464 print "\nExiting installation..\n"
465 sys.exit()
466 else:
467 print "\nInvalid answer. Exiting installation..\n"
468 sys.exit()
470 activemakacconfig = os.path.join(os.path.dirname(os.path.abspath(MaKaC.__file__)), 'common', 'MaKaCConfig.py')
471 updateIndicoConfPathInsideMaKaCConfig(indicoconfpath, activemakacconfig)
473 return dict((dirName, os.path.join(prefixDir, dirName))
474 for dirName in ['bin','doc','etc','htdocs','tmp','log','cache','db','archive'])
477 def indico_post_install(targetDirs, sourceDirs, makacconfig_base_dir, package_dir, force_no_db = False, uid=None, gid=None, dbDir=LOCALDATABASEDIR):
478 from MaKaC.common.Configuration import Config
480 if 'db' in targetDirs:
481 # we don't want that the db directory be created
482 dbDir = targetDirs['db']
483 del targetDirs['db']
485 print "Creating directories for resources... ",
486 # Create the directories where the resources will be installed
487 createDirs(targetDirs)
488 print "done!"
490 # target configuration file (may exist or not)
491 newConf = os.path.join(targetDirs['etc'],'indico.conf')
492 # source configuration file (package)
493 sourceConf = os.path.join(sourceDirs['etc'], 'indico.conf.sample')
495 # if there is a source config
496 if os.path.exists(sourceConf):
497 if not os.path.exists(newConf):
498 # just copy if there is no config yet
499 shutil.copy(sourceConf, newConf)
500 else:
501 print "Upgrading indico.conf...",
502 # upgrade the existing one
503 upgrade_indico_conf(newConf, sourceConf)
504 print "done!"
506 # change MaKaCConfig.py to include the config
507 updateIndicoConfPathInsideMaKaCConfig(newConf,
508 os.path.join(makacconfig_base_dir, 'MaKaCConfig.py'))
510 # copy the db config files
511 for f in [xx for xx in ('%s/zdctl.conf' % targetDirs['etc'],
512 '%s/zodb.conf' % targetDirs['etc'],
513 '%s/logging.conf' %targetDirs['etc']) if not os.path.exists(xx)]:
514 shutil.copy('%s.sample' % f, f)
516 # Shall we create a DB?
517 dbInstalledBySetupPy = False
518 dbpath = None
520 if force_no_db:
521 print 'Skipping database detection'
522 else:
523 if os.path.exists(dbDir):
524 dbpath = dbDir
525 print 'Successfully found a database directory at %s' % dbDir
526 else:
527 opt = None
528 while opt not in ('Y', 'y', 'n', ''):
529 opt = raw_input('''\nWe cannot find a configured database at %s.
531 Do you want to create a new database now [Y/n]? ''' % dbDir)
532 if opt in ('Y', 'y', ''):
533 dbInstalledBySetupPy = True
534 dbpath_ok = False
535 while not dbpath_ok:
536 dbpath = raw_input('''\nWhere do you want to install the database [%s]? ''' % dbDir)
537 if dbpath.strip() == '':
538 dbpath = dbDir
540 try:
541 os.makedirs(dbpath)
542 dbpath_ok = True
543 except Exception, e:
544 print e
545 print 'Unable to create database at %s, please make sure that you have permissions to create that directory' % dbpath
547 elif opt == 'n':
548 pass
550 #we delete an existing vars.js.tpl.tmp
551 tmp_dir = targetDirs['tmp']
553 varsJsTplTmpPath = os.path.join(tmp_dir, 'vars.js.tpl.tmp')
554 if os.path.exists(varsJsTplTmpPath):
555 print 'Old vars.js.tpl.tmp found at: %s. Removing' % varsJsTplTmpPath
556 os.remove(varsJsTplTmpPath)
558 if dbInstalledBySetupPy:
559 dbParam = dbpath
560 else:
561 dbParam = None
563 # find the apache user/group
564 user, group = _findApacheUserGroup(uid, gid)
566 # change indico.conf
567 modifyOnDiskIndicoConfOption('%s/indico.conf' % targetDirs['etc'], 'ApacheUser', user)
568 modifyOnDiskIndicoConfOption('%s/indico.conf' % targetDirs['etc'], 'ApacheGroup', group)
570 # set the directory for the egg cache
571 _updateMaKaCEggCache(os.path.join(package_dir, 'MaKaC', '__init__.py'), targetDirs['tmp'])
573 if not force_no_db and dbpath:
574 # change the db config files (paths + apache user/group)
575 _updateDbConfigFiles(dbpath, targetDirs['log'], targetDirs['etc'], targetDirs['tmp'], user)
577 # check permissions
578 _checkDirPermissions(targetDirs, dbInstalledBySetupPy=dbParam, accessuser=user, accessgroup=group)
579 # check that mod_python is installed
580 _checkModPythonIsInstalled()
582 print """
584 Congratulations!
585 Indico has been installed correctly.
587 indico.conf: %s/indico.conf
589 BinDir: %s
590 DocumentationDir: %s
591 ConfigurationDir: %s
592 HtdocsDir: %s
594 For information on how to configure Apache HTTPD, take a look at:
596 http://indico-software.org/wiki/Admin/Installation#a3.ConfiguringApache
599 Please do not forget to start the 'taskDaemon' in order to use alarms, creation
600 of off-line websites, reminders, etc. You can find it in './bin/taskDaemon.py'
603 """ % (targetDirs['etc'], targetDirs['bin'], targetDirs['doc'], targetDirs['etc'], targetDirs['htdocs'], _databaseText(targetDirs['etc']))
605 def _databaseText(cfgPrefix):
606 return """If you are running ZODB on this host:
607 - Review %s/zodb.conf and %s/zdctl.conf to make sure everything is ok.
608 - To start the database run: zdaemon -C %s/zdctl.conf start
609 """ % (cfgPrefix, cfgPrefix, cfgPrefix)