Add server install option to turn on debugging
[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.helpers.common import EnvHelpersInstall
23 from ipsilon.util.data import Store
24 from ipsilon.tools import files
25 import ConfigParser
26 import argparse
27 import cherrypy
28 import logging
29 import os
30 import pwd
31 import shutil
32 import socket
33 import subprocess
34 import sys
35 import time
36
37
38 TEMPLATES = '/usr/share/ipsilon/templates/install'
39 CONFDIR = '/etc/ipsilon'
40 DATADIR = '/var/lib/ipsilon'
41 HTTPDCONFD = '/etc/httpd/conf.d'
42 BINDIR = '/usr/sbin'
43 STATICDIR = '/usr/share/ipsilon'
44 WSGI_SOCKET_PREFIX = None
45
46
47 class ConfigurationError(Exception):
48
49     def __init__(self, message):
50         super(ConfigurationError, self).__init__(message)
51         self.message = message
52
53     def __str__(self):
54         return repr(self.message)
55
56
57 #Silence cherrypy logging to screen
58 cherrypy.log.screen = False
59
60 # Regular logging
61 LOGFILE = '/var/log/ipsilon-install.log'
62 logger = logging.getLogger()
63
64
65 def openlogs():
66     global logger  # pylint: disable=W0603
67     if os.path.isfile(LOGFILE):
68         try:
69             created = '%s' % time.ctime(os.path.getctime(LOGFILE))
70             shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created))
71         except IOError:
72             pass
73     logger = logging.getLogger()
74     try:
75         lh = logging.FileHandler(LOGFILE)
76     except IOError, e:
77         print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e))
78         lh = logging.StreamHandler(sys.stderr)
79     formatter = logging.Formatter('[%(asctime)s] %(message)s')
80     lh.setFormatter(formatter)
81     logger.addHandler(lh)
82
83
84 def install(plugins, args):
85     logger.info('Installation initiated')
86     now = time.strftime("%Y%m%d%H%M%S", time.gmtime())
87     instance_conf = os.path.join(CONFDIR, args['instance'])
88
89     logger.info('Installing default config files')
90     ipsilon_conf = os.path.join(instance_conf, 'ipsilon.conf')
91     idp_conf = os.path.join(instance_conf, 'idp.conf')
92     args['httpd_conf'] = os.path.join(HTTPDCONFD,
93                                       'ipsilon-%s.conf' % args['instance'])
94     args['data_dir'] = os.path.join(DATADIR, args['instance'])
95     if os.path.exists(ipsilon_conf):
96         shutil.move(ipsilon_conf, '%s.bakcup.%s' % (ipsilon_conf, now))
97     if os.path.exists(idp_conf):
98         shutil.move(idp_conf, '%s.backup.%s' % (idp_conf, now))
99     if not os.path.exists(instance_conf):
100         os.makedirs(instance_conf, 0700)
101     confopts = {'instance': args['instance'],
102                 'datadir': args['data_dir'],
103                 'sysuser': args['system_user'],
104                 'ipsilondir': BINDIR,
105                 'staticdir': STATICDIR,
106                 'debugging': "True" if args['server_debugging'] else "False"}
107     if WSGI_SOCKET_PREFIX:
108         confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX
109     files.write_from_template(ipsilon_conf,
110                               os.path.join(TEMPLATES, 'ipsilon.conf'),
111                               confopts)
112     files.write_from_template(idp_conf,
113                               os.path.join(TEMPLATES, 'idp.conf'),
114                               confopts)
115     if not os.path.exists(args['httpd_conf']):
116         os.symlink(idp_conf, args['httpd_conf'])
117     sessdir = os.path.join(args['data_dir'], 'sessions')
118     if not os.path.exists(sessdir):
119         os.makedirs(sessdir, 0700)
120     data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
121     if not os.path.exists(data_conf):
122         os.symlink(ipsilon_conf, data_conf)
123     # Load the cherrypy config from the newly installed file so
124     # that db paths and all is properly set before configuring
125     # components
126     cherrypy.config.update(ipsilon_conf)
127
128     # Move pre-existing admin db away
129     admin_db = cherrypy.config['admin.config.db']
130     if os.path.exists(admin_db):
131         shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
132
133     # Rebuild user db
134     users_db = cherrypy.config['user.prefs.db']
135     if os.path.exists(users_db):
136         shutil.move(users_db, '%s.backup.%s' % (users_db, now))
137     db = Store()
138     db.save_user_preferences(args['admin_user'], {'is_admin': 1})
139
140     logger.info('Configuring environment helpers')
141     for plugin_name in plugins['Environment Helpers']:
142         plugin = plugins['Environment Helpers'][plugin_name]
143         plugin.configure_server(args)
144
145     logger.info('Configuring login managers')
146     for plugin_name in args['lm_order']:
147         plugin = plugins['Login Managers'][plugin_name]
148         plugin.configure(args)
149
150     logger.info('Configuring Authentication Providers')
151     for plugin_name in plugins['Auth Providers']:
152         plugin = plugins['Auth Providers'][plugin_name]
153         plugin.configure(args)
154
155     # Fixup permissions so only the ipsilon user can read these files
156     files.fix_user_dirs(instance_conf, opts['system_user'])
157     files.fix_user_dirs(args['data_dir'], opts['system_user'])
158     try:
159         subprocess.call(['/usr/sbin/restorecon', '-R', args['data_dir']])
160     except Exception:  # pylint: disable=broad-except
161         pass
162
163 def uninstall(plugins, args):
164     logger.info('Uninstallation initiated')
165     raise Exception('Not Implemented')
166
167
168 def find_plugins():
169     plugins = {
170         'Environment Helpers': EnvHelpersInstall().plugins,
171         'Login Managers': LoginMgrsInstall().plugins,
172         'Auth Providers': ProvidersInstall().plugins
173     }
174     return plugins
175
176
177 def parse_config_profile(args):
178     config = ConfigParser.ConfigParser()
179     files = config.read(args['config_profile'])
180     if len(files) == 0:
181         raise ConfigurationError('Config Profile file %s not found!' %
182                                  args['config_profile'])
183
184     if 'globals' in config.sections():
185         G = config.options('globals')
186         for g in G:
187             val = config.get('globals', g)
188             if g in globals():
189                 globals()[g] = val
190             else:
191                 for k in globals().keys():
192                     if k.lower() == g.lower():
193                         globals()[k] = val
194                         break
195
196     if 'arguments' in config.sections():
197         A = config.options('arguments')
198         for a in A:
199             args[a] = config.get('arguments', a)
200
201     return args
202
203
204 def parse_args(plugins):
205     parser = argparse.ArgumentParser(description='Ipsilon Install Options')
206     parser.add_argument('--version',
207                         action='version', version='%(prog)s 0.1')
208     parser.add_argument('-o', '--login-managers-order', dest='lm_order',
209                         help='Comma separated list of login managers')
210     parser.add_argument('--hostname',
211                         help="Machine's fully qualified host name")
212     parser.add_argument('--instance', default='idp',
213                         help="IdP instance name, each is a separate idp")
214     parser.add_argument('--system-user', default='ipsilon',
215                         help="User account used to run the server")
216     parser.add_argument('--admin-user', default='admin',
217                         help="User account that is assigned admin privileges")
218     parser.add_argument('--config-profile', default=None,
219                         help="File containing install options")
220     parser.add_argument('--server-debugging', action='store_true',
221                         help="Uninstall the server and all data")
222     parser.add_argument('--uninstall', action='store_true',
223                         help="Uninstall the server and all data")
224
225     lms = []
226
227     for plugin_group in plugins:
228         group = parser.add_argument_group(plugin_group)
229         for plugin_name in plugins[plugin_group]:
230             plugin = plugins[plugin_group][plugin_name]
231             if plugin.ptype == 'login':
232                 lms.append(plugin.name)
233             plugin.install_args(group)
234
235     args = vars(parser.parse_args())
236
237     if args['config_profile']:
238         args = parse_config_profile(args)
239
240     if not args['hostname']:
241         args['hostname'] = socket.getfqdn()
242
243     if len(args['hostname'].split('.')) < 2:
244         raise ConfigurationError('Hostname: %s is not a FQDN')
245
246     try:
247         pwd.getpwnam(args['system_user'])
248     except KeyError:
249         raise ConfigurationError('User: %s not found on the system')
250
251     if args['lm_order'] is None:
252         args['lm_order'] = []
253         for name in lms:
254             if args[name] == 'yes':
255                 args['lm_order'].append(name)
256     else:
257         args['lm_order'] = args['lm_order'].split(',')
258
259     if len(args['lm_order']) == 0:
260         #force the basic pam provider if nothing else is selected
261         if 'pam' not in args:
262             parser.print_help()
263             sys.exit(-1)
264         args['lm_order'] = ['pam']
265         args['pam'] = 'yes'
266
267     #FIXME: check instance is only alphanums
268
269     return args
270
271 if __name__ == '__main__':
272     opts = []
273     out = 0
274     openlogs()
275     try:
276         fplugins = find_plugins()
277         opts = parse_args(fplugins)
278
279         logger.setLevel(logging.DEBUG)
280
281         logger.info('Intallation arguments:')
282         for k in sorted(opts.iterkeys()):
283             logger.info('%s: %s', k, opts[k])
284
285         if 'uninstall' in opts and opts['uninstall'] is True:
286             uninstall(fplugins, opts)
287
288         install(fplugins, opts)
289     except Exception, e:  # pylint: disable=broad-except
290         logger.exception(e)
291         if 'uninstall' in opts and opts['uninstall'] is True:
292             print 'Uninstallation aborted.'
293         else:
294             print 'Installation aborted.'
295         print 'See log file %s for details' % LOGFILE
296         out = 1
297     finally:
298         if out == 0:
299             if 'uninstall' in opts and opts['uninstall'] is True:
300                 print 'Uninstallation complete.'
301             else:
302                 print 'Installation complete.'
303                 print 'Please restart HTTPD to enable the IdP instance.'
304     sys.exit(out)