Make it easy to install mutiple server instances
[cascardo/ipsilon.git] / ipsilon / install / ipsilon-server-install
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2014  Simo Sorce <simo@redhat.com>
4 #
5 # see file 'COPYING' for use and warranty information
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 from ipsilon.login.common import LoginMgrsInstall
21 from ipsilon.providers.common import ProvidersInstall
22 from ipsilon.util.data import Store
23 from ipsilon.tools import files
24 import argparse
25 import cherrypy
26 import logging
27 import os
28 import pwd
29 import shutil
30 import socket
31 import sys
32 import time
33
34
35 TEMPLATES = '/usr/share/ipsilon/templates/install'
36 CONFDIR = '/etc/ipsilon'
37 DATADIR = '/var/lib/ipsilon'
38 HTTPDCONFD = '/etc/httpd/conf.d'
39
40
41 class ConfigurationError(Exception):
42
43     def __init__(self, message):
44         super(ConfigurationError, self).__init__(message)
45         self.message = message
46
47     def __str__(self):
48         return repr(self.message)
49
50
51 #Silence cherrypy logging to screen
52 cherrypy.log.screen = False
53
54 # Regular logging
55 LOGFILE = '/var/log/ipsilon-install.log'
56 logger = logging.getLogger()
57
58
59 def openlogs():
60     global logger  # pylint: disable=W0603
61     if os.path.isfile(LOGFILE):
62         try:
63             created = '%s' % time.ctime(os.path.getctime(LOGFILE))
64             shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created))
65         except IOError:
66             pass
67     logger = logging.getLogger()
68     try:
69         lh = logging.FileHandler(LOGFILE)
70     except IOError, e:
71         print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e))
72         lh = logging.StreamHandler(sys.stderr)
73     formatter = logging.Formatter('[%(asctime)s] %(message)s')
74     lh.setFormatter(formatter)
75     logger.addHandler(lh)
76
77
78 def install(plugins, args):
79     logger.info('Installation initiated')
80     now = time.strftime("%Y%m%d%H%M%S", time.gmtime())
81     instance_conf = os.path.join(CONFDIR, args['instance'])
82
83     logger.info('Installing default config files')
84     ipsilon_conf = os.path.join(instance_conf, 'ipsilon.conf')
85     idp_conf = os.path.join(instance_conf, 'idp.conf')
86     args['httpd_conf'] = os.path.join(HTTPDCONFD,
87                                       'ipsilon-%s.conf' % args['instance'])
88     args['data_dir'] = os.path.join(DATADIR, args['instance'])
89     if os.path.exists(ipsilon_conf):
90         shutil.move(ipsilon_conf, '%s.bakcup.%s' % (ipsilon_conf, now))
91     if os.path.exists(idp_conf):
92         shutil.move(idp_conf, '%s.backup.%s' % (idp_conf, now))
93     if not os.path.exists(instance_conf):
94         os.makedirs(instance_conf, 0700)
95     confopts = {'instance': args['instance'], 'datadir': args['data_dir']}
96     files.write_from_template(ipsilon_conf,
97                               os.path.join(TEMPLATES, 'ipsilon.conf'),
98                               confopts)
99     files.write_from_template(idp_conf,
100                               os.path.join(TEMPLATES, 'idp.conf'),
101                               confopts)
102     if not os.path.exists(args['httpd_conf']):
103         os.symlink(idp_conf, args['httpd_conf'])
104     os.makedirs(os.path.join(args['data_dir'], 'sessions'), 0700)
105     data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
106     if not os.path.exists(data_conf):
107         os.symlink(ipsilon_conf, data_conf)
108     # Load the cherrypy config from the newly installed file so
109     # that db paths and all is properly set before configuring
110     # components
111     cherrypy.config.update(ipsilon_conf)
112
113     # Move pre-existing admin db away
114     admin_db = cherrypy.config['admin.config.db']
115     if os.path.exists(admin_db):
116         shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
117
118     # Rebuild user db
119     users_db = cherrypy.config['user.prefs.db']
120     if os.path.exists(users_db):
121         shutil.move(users_db, '%s.backup.%s' % (users_db, now))
122     db = Store()
123     db.save_user_preferences(args['admin_user'], {'is_admin': 1})
124
125     logger.info('Configuring login managers')
126     for plugin_name in args['lm_order']:
127         plugin = plugins['Login Managers'][plugin_name]
128         plugin.configure(args)
129
130     logger.info('Configuring Authentication Providers')
131     for plugin_name in plugins['Auth Providers']:
132         plugin = plugins['Auth Providers'][plugin_name]
133         plugin.configure(args)
134
135     # Fixup permissions so only the ipsilon user can read these files
136     files.fix_user_dirs(instance_conf, opts['system_user'], mode=0500)
137     files.fix_user_dirs(args['data_dir'], opts['system_user'])
138
139 def uninstall(plugins, args):
140     logger.info('Uninstallation initiated')
141     raise Exception('Not Implemented')
142
143
144 def find_plugins():
145     plugins = {
146         'Login Managers': LoginMgrsInstall().plugins,
147         'Auth Providers': ProvidersInstall().plugins
148     }
149     return plugins
150
151
152 def parse_args(plugins):
153     parser = argparse.ArgumentParser(description='Ipsilon Install Options')
154     parser.add_argument('--version',
155                         action='version', version='%(prog)s 0.1')
156     parser.add_argument('-o', '--login-managers-order', dest='lm_order',
157                         help='Comma separated list of login managers')
158     parser.add_argument('--hostname',
159                         help="Machine's fully qualified host name")
160     parser.add_argument('--instance', default='idp',
161                         help="IdP instance name, each is a separate idp")
162     parser.add_argument('--system-user', default='ipsilon',
163                         help="User account used to run the server")
164     parser.add_argument('--admin-user', default='admin',
165                         help="User account that is assigned admin privileges")
166     parser.add_argument('--ipa', choices=['yes', 'no'], default='yes',
167                         help='Detect and use an IPA server for authentication')
168     parser.add_argument('--uninstall', action='store_true',
169                         help="Uninstall the server and all data")
170
171     lms = []
172
173     for plugin_group in plugins:
174         group = parser.add_argument_group(plugin_group)
175         for plugin_name in plugins[plugin_group]:
176             plugin = plugins[plugin_group][plugin_name]
177             if plugin.ptype == 'login':
178                 lms.append(plugin.name)
179             plugin.install_args(group)
180
181     args = vars(parser.parse_args())
182
183     if not args['hostname']:
184         args['hostname'] = socket.getfqdn()
185
186     if len(args['hostname'].split('.')) < 2:
187         raise ConfigurationError('Hostname: %s is not a FQDN')
188
189     try:
190         pwd.getpwnam(args['system_user'])
191     except KeyError:
192         raise ConfigurationError('User: %s not found on the system')
193
194     if args['lm_order'] is None:
195         args['lm_order'] = []
196         for name in lms:
197             if args[name] == 'yes':
198                 args['lm_order'].append(name)
199     else:
200         args['lm_order'] = args['lm_order'].split(',')
201
202     if len(args['lm_order']) == 0:
203         #force the basic pam provider if nothing else is selected
204         if 'pam' not in args:
205             parser.print_help()
206             sys.exit(-1)
207         args['lm_order'] = ['pam']
208         args['pam'] = 'yes'
209
210     #FIXME: check instance is only alphanums
211
212     return args
213
214 if __name__ == '__main__':
215     opts = []
216     out = 0
217     openlogs()
218     try:
219         fplugins = find_plugins()
220         opts = parse_args(fplugins)
221
222         logger.setLevel(logging.DEBUG)
223
224         logger.info('Intallation arguments:')
225         for k in sorted(opts.iterkeys()):
226             logger.info('%s: %s', k, opts[k])
227
228         if 'uninstall' in opts and opts['uninstall'] is True:
229             uninstall(fplugins, opts)
230
231         install(fplugins, opts)
232     except Exception, e:  # pylint: disable=broad-except
233         logger.exception(e)
234         if 'uninstall' in opts and opts['uninstall'] is True:
235             print 'Uninstallation aborted.'
236         else:
237             print 'Installation aborted.'
238         print 'See log file %s for details' % LOGFILE
239         out = 1
240     finally:
241         if out == 0:
242             if 'uninstall' in opts and opts['uninstall'] is True:
243                 print 'Uninstallation complete.'
244             else:
245                 print 'Installation complete.'
246     sys.exit(out)