diff --git a/basic/srv/vm/mgr/config.py b/basic/srv/vm/mgr/config.py index 294f794..8206b1c 100644 --- a/basic/srv/vm/mgr/config.py +++ b/basic/srv/vm/mgr/config.py @@ -6,6 +6,9 @@ CONF_FILE = '/srv/vm/config.json' class Config: def __init__(self): + self.load() + + def load(self): with open(CONF_FILE, 'r') as f: self.data = json.load(f) diff --git a/basic/srv/vm/mgr/wsgiapp.py b/basic/srv/vm/mgr/wsgiapp.py index 8158b06..5bb6335 100644 --- a/basic/srv/vm/mgr/wsgiapp.py +++ b/basic/srv/vm/mgr/wsgiapp.py @@ -20,7 +20,9 @@ SESSION_KEY = os.urandom(26) class WSGIApp(object): def __init__(self): + self.vmmgr = VMMgr() self.jinja_env = Environment(loader=FileSystemLoader('/srv/vm/templates'), autoescape=True, lstrip_blocks=True, trim_blocks=True) + self.jinja_env.globals.update(is_app_visible=self.is_app_visible) self.jinja_env.globals.update(is_service_autostarted=tools.is_service_autostarted) self.jinja_env.globals.update(is_service_started=tools.is_service_started) @@ -29,8 +31,9 @@ class WSGIApp(object): def wsgi_app(self, environ, start_response): request = Request(environ) + # Reload VM Manager config in case it has changed + self.vmmgr.conf.load() # Enhance request - request.mgr = VMMgr() request.session = WSGISession(request.cookies, SESSION_KEY) request.session.lang = WSGILang() # Dispatch request @@ -81,7 +84,7 @@ class WSGIApp(object): def render_template(self, template_name, request, **context): # Enhance context - context['conf'] = request.mgr.conf + context['conf'] = self.vmmgr.conf context['session'] = request.session # Render template t = self.jinja_env.get_template(template_name) @@ -95,7 +98,7 @@ class WSGIApp(object): def login_action(self, request): password = request.form['password'] - if tools.adminpwd_verify(password, request.mgr.conf['host']['adminpwd']): + if tools.adminpwd_verify(password, self.vmmgr.conf['host']['adminpwd']): request.session['admin'] = True return redirect('/') else: @@ -106,13 +109,22 @@ class WSGIApp(object): return redirect('/') def portal_view(self, request): - # Default view. If domain is set to the default dummy domain, redirects to first-run setup instead. + # Default portal view. If this is the first run, perform first-run setup. + if self.vmmgr.conf['host']['firstrun']: + # Set user as admin + request.session['admin'] = True + # Disable and save first-run flag + self.vmmgr.conf['host']['firstrun'] = False + self.vmmgr.conf.save() + # Redirect to host setup view + return redirect('/setup-host') + host = tools.compile_url(self.vmmgr.conf['host']['domain'], self.vmmgr.conf['host']['port'], None) if request.session['admin']: - return self.render_template('portal-admin.html', request) - return self.render_template('portal-user.html', request) + return self.render_template('portal-admin.html', request, host=host) + return self.render_template('portal-user.html', request, host=host) def setup_host_view(self, request): - # First-run setup view. + # Host setup view. ex_ipv4 = tools.get_external_ipv4() ex_ipv6 = tools.get_external_ipv6() in_ipv4 = tools.get_local_ipv4() @@ -130,7 +142,7 @@ class WSGIApp(object): try: domain = request.form['domain'] port = request.form['port'] - request.mgr.update_host(domain, port, False) + self.vmmgr.update_host(domain, port, False) server_name = request.environ['HTTP_X_FORWARDED_SERVER_NAME'] url = '{}/setup-host'.format(tools.compile_url(server_name, port)) response = self.render_json({'ok': request.session.lang.host_updated(url, url)}) @@ -146,8 +158,7 @@ class WSGIApp(object): def verify_dns_action(self, request): # Check if all FQDNs for all applications are resolvable and point to current external IP - mgr = request.mgr - domains = [mgr.domain]+['{}.{}'.format(mgr.conf['apps'][app]['host'], mgr.domain) for app in mgr.conf['apps']] + domains = [self.vmmgr.domain]+['{}.{}'.format(self.vmmgr.conf['apps'][app]['host'], self.vmmgr.domain) for app in self.vmmgr.conf['apps']] ipv4 = tools.get_external_ipv4() ipv6 = tools.get_external_ipv6() for domain in domains: @@ -167,9 +178,8 @@ class WSGIApp(object): def verify_http_action(self, request, **kwargs): # Check if all applications are accessible from the internet using 3rd party ping service proto = kwargs['proto'] - mgr = request.mgr - port = mgr.port if proto == 'https' else '80' - domains = [mgr.domain]+['{}.{}'.format(mgr.conf['apps'][app]['host'], mgr.domain) for app in mgr.conf['apps']] + port = self.vmmgr.port if proto == 'https' else '80' + domains = [self.vmmgr.domain]+['{}.{}'.format(self.vmmgr.conf['apps'][app]['host'], self.vmmgr.domain) for app in self.vmmgr.conf['apps']] for domain in domains: url = tools.compile_url(domain, port, proto) try: @@ -191,22 +201,22 @@ class WSGIApp(object): return self.render_json({'error': request.session.lang.key_file_missing()}) request.files['public'].save('/tmp/public.pem') request.files['private'].save('/tmp/private.pem') - request.mgr.install_cert('/tmp/public.pem', '/tmp/private.pem') + self.vmmgr.install_cert('/tmp/public.pem', '/tmp/private.pem') os.unlink('/tmp/public.pem') os.unlink('/tmp/private.pem') else: - request.mgr.request_cert() + self.vmmgr.request_cert() except BadRequest: return self.render_json({'error': request.session.lang.malformed_request()}) except: return self.render_json({'error': request.session.lang.cert_request_error()}) - url = tools.compile_url(request.mgr.domain, request.mgr.port) + url = tools.compile_url(self.vmmgr.domain, self.vmmgr.port) return self.render_json({'ok': request.session.lang.cert_installed(url, url)}) def update_common_action(self, request): # Update common settings shared between apps - admin e-mail address, Google Maps API key try: - request.mgr.update_common(request.form['email'], request.form['gmaps-api-key']) + self.vmmgr.update_common(request.form['email'], request.form['gmaps-api-key']) except BadRequest: return self.render_json({'error': request.session.lang.malformed_request()}) return self.render_json({'ok': request.session.lang.common_updated()}) @@ -215,9 +225,9 @@ class WSGIApp(object): # Update application visibility on portal page try: if request.form['value'] == 'true': - request.mgr.show_tiles(request.form['app']) + self.vmmgr.show_tiles(request.form['app']) else: - request.mgr.hide_tiles(request.form['app']) + self.vmmgr.hide_tiles(request.form['app']) except (BadRequest, InvalidValueException): return self.render_json({'error': request.session.lang.malformed_request()}) return self.render_json({'ok': 'ok'}) @@ -226,9 +236,9 @@ class WSGIApp(object): # Update value determining if the app should be automatically started after VM boot try: if request.form['value'] == 'true': - request.mgr.enable_autostart(request.form['app']) + self.vmmgr.enable_autostart(request.form['app']) else: - request.mgr.disable_autostart(request.form['app']) + self.vmmgr.disable_autostart(request.form['app']) except (BadRequest, InvalidValueException): return self.render_json({'error': request.session.lang.malformed_request()}) return self.render_json({'ok': 'ok'}) @@ -236,7 +246,7 @@ class WSGIApp(object): def start_app_action(self, request): # Starts application along with its dependencies try: - request.mgr.start_app(request.form['app']) + self.vmmgr.start_app(request.form['app']) except (BadRequest, InvalidValueException): return self.render_json({'error': request.session.lang.malformed_request()}) except: @@ -246,7 +256,7 @@ class WSGIApp(object): def stop_app_action(self, request): # Stops application along with its dependencies try: - request.mgr.stop_app(request.form['app']) + self.vmmgr.stop_app(request.form['app']) except (BadRequest, InvalidValueException): return self.render_json({'error': request.session.lang.malformed_request()}) except: @@ -254,14 +264,14 @@ class WSGIApp(object): return self.render_json({'ok': request.session.lang.app_stopped()}) def update_password_action(self, request): - # Updates password for both HDD encryption (LUKS-on-LVM) and admin account to vmmgr + # Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account try: if request.form['newpassword'] != request.form['newpassword2']: return self.render_json({'error': request.session.lang.password_mismatch()}) if request.form['newpassword'] == '': return self.render_json({'error': request.session.lang.password_empty()}) # No need to explicitly validate old password, update_luks_password will raise exception if it's wrong - request.mgr.update_password(request.form['oldpassword'], request.form['newpassword']) + self.vmmgr.update_password(request.form['oldpassword'], request.form['newpassword']) except: return self.render_json({'error': request.session.lang.bad_password()}) return self.render_json({'ok': request.session.lang.password_changed()}) @@ -278,5 +288,8 @@ class WSGIApp(object): response.call_on_close(tools.shutdown_vm) return response + def is_app_visible(self, app): + return app in self.vmmgr.conf['apps'] and self.vmmgr.conf['apps'][app]['visible'] and tools.is_service_started(app) + class InvalidRecordException(Exception): pass