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