b5a6371877a8f620513fc9c65197de19584db83e
[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     sessdir = os.path.join(args['data_dir'], 'sessions')
105     if not os.path.exists(sessdir):
106         os.makedirs(sessdir, 0700)
107     data_conf = os.path.join(args['data_dir'], 'ipsilon.conf')
108     if not os.path.exists(data_conf):
109         os.symlink(ipsilon_conf, data_conf)
110     # Load the cherrypy config from the newly installed file so
111     # that db paths and all is properly set before configuring
112     # components
113     cherrypy.config.update(ipsilon_conf)
114
115     # Move pre-existing admin db away
116     admin_db = cherrypy.config['admin.config.db']
117     if os.path.exists(admin_db):
118         shutil.move(admin_db, '%s.backup.%s' % (admin_db, now))
119
120     # Rebuild user db
121     users_db = cherrypy.config['user.prefs.db']
122     if os.path.exists(users_db):
123         shutil.move(users_db, '%s.backup.%s' % (users_db, now))
124     db = Store()
125     db.save_user_preferences(args['admin_user'], {'is_admin': 1})
126
127     logger.info('Configuring login managers')
128     for plugin_name in args['lm_order']:
129         plugin = plugins['Login Managers'][plugin_name]
130         plugin.configure(args)
131
132     logger.info('Configuring Authentication Providers')
133     for plugin_name in plugins['Auth Providers']:
134         plugin = plugins['Auth Providers'][plugin_name]
135         plugin.configure(args)
136
137     # Fixup permissions so only the ipsilon user can read these files
138     files.fix_user_dirs(instance_conf, opts['system_user'], mode=0500)
139     files.fix_user_dirs(args['data_dir'], opts['system_user'])
140
141 def uninstall(plugins, args):
142     logger.info('Uninstallation initiated')
143     raise Exception('Not Implemented')
144
145
146 def find_plugins():
147     plugins = {
148         'Login Managers': LoginMgrsInstall().plugins,
149         'Auth Providers': ProvidersInstall().plugins
150     }
151     return plugins
152
153
154 def parse_args(plugins):
155     parser = argparse.ArgumentParser(description='Ipsilon Install Options')
156     parser.add_argument('--version',
157                         action='version', version='%(prog)s 0.1')
158     parser.add_argument('-o', '--login-managers-order', dest='lm_order',
159                         help='Comma separated list of login managers')
160     parser.add_argument('--hostname',
161                         help="Machine's fully qualified host name")
162     parser.add_argument('--instance', default='idp',
163                         help="IdP instance name, each is a separate idp")
164     parser.add_argument('--system-user', default='ipsilon',
165                         help="User account used to run the server")
166     parser.add_argument('--admin-user', default='admin',
167                         help="User account that is assigned admin privileges")
168     parser.add_argument('--ipa', choices=['yes', 'no'], default='yes',
169                         help='Detect and use an IPA server for authentication')
170     parser.add_argument('--uninstall', action='store_true',
171                         help="Uninstall the server and all data")
172
173     lms = []
174
175     for plugin_group in plugins:
176         group = parser.add_argument_group(plugin_group)
177         for plugin_name in plugins[plugin_group]:
178             plugin = plugins[plugin_group][plugin_name]
179             if plugin.ptype == 'login':
180                 lms.append(plugin.name)
181             plugin.install_args(group)
182
183     args = vars(parser.parse_args())
184
185     if not args['hostname']:
186         args['hostname'] = socket.getfqdn()
187
188     if len(args['hostname'].split('.')) < 2:
189         raise ConfigurationError('Hostname: %s is not a FQDN')
190
191     try:
192         pwd.getpwnam(args['system_user'])
193     except KeyError:
194         raise ConfigurationError('User: %s not found on the system')
195
196     if args['lm_order'] is None:
197         args['lm_order'] = []
198         for name in lms:
199             if args[name] == 'yes':
200                 args['lm_order'].append(name)
201     else:
202         args['lm_order'] = args['lm_order'].split(',')
203
204     if len(args['lm_order']) == 0:
205         #force the basic pam provider if nothing else is selected
206         if 'pam' not in args:
207             parser.print_help()
208             sys.exit(-1)
209         args['lm_order'] = ['pam']
210         args['pam'] = 'yes'
211
212     #FIXME: check instance is only alphanums
213
214     return args
215
216 if __name__ == '__main__':
217     opts = []
218     out = 0
219     openlogs()
220     try:
221         fplugins = find_plugins()
222         opts = parse_args(fplugins)
223
224         logger.setLevel(logging.DEBUG)
225
226         logger.info('Intallation arguments:')
227         for k in sorted(opts.iterkeys()):
228             logger.info('%s: %s', k, opts[k])
229
230         if 'uninstall' in opts and opts['uninstall'] is True:
231             uninstall(fplugins, opts)
232
233         install(fplugins, opts)
234     except Exception, e:  # pylint: disable=broad-except
235         logger.exception(e)
236         if 'uninstall' in opts and opts['uninstall'] is True:
237             print 'Uninstallation aborted.'
238         else:
239             print 'Installation aborted.'
240         print 'See log file %s for details' % LOGFILE
241         out = 1
242     finally:
243         if out == 0:
244             if 'uninstall' in opts and opts['uninstall'] is True:
245                 print 'Uninstallation complete.'
246             else:
247                 print 'Installation complete.'
248     sys.exit(out)