diff --git a/basic/srv/vm/mgr/__init__.py b/basic/srv/vm/mgr/__init__.py index 216b4e2..36326f5 100644 --- a/basic/srv/vm/mgr/__init__.py +++ b/basic/srv/vm/mgr/__init__.py @@ -129,7 +129,7 @@ class VMMgr: def update_login(self, app, login, password): # Update login and password for an app in the configuration - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) if login is not None: self.conf['apps'][app]['login'] = login @@ -139,27 +139,27 @@ class VMMgr: def show_tiles(self, app): # Update visibility for the app in the configuration - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) self.conf['apps'][app]['visible'] = True self.conf.save() def hide_tiles(self, app): # Update visibility for the app in the configuration - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) self.conf['apps'][app]['visible'] = False self.conf.save() def start_app(self, app): # Start the actual app service - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) tools.start_service(app) def stop_app(self, app): # Stop the actual app service - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) tools.stop_service(app) # Stop the app service's dependencies if they are not used by another running app @@ -193,13 +193,13 @@ class VMMgr: def enable_autostart(self, app): # Add the app to OpenRC default runlevel - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) subprocess.run(['/sbin/rc-update', 'add', app]) def disable_autostart(self, app): # Remove the app from OpenRC default runlevel - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) subprocess.run(['/sbin/rc-update', 'del', app]) @@ -240,7 +240,7 @@ class VMMgr: def register_proxy(self, app): # Setup proxy configuration and reload nginx - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f: f.write(NGINX_TEMPLATE.format(app=app, host=self.conf['apps'][app]['host'], domain=self.domain, port=self.port)) @@ -248,7 +248,7 @@ class VMMgr: def unregister_proxy(self, app): # Remove proxy configuration and reload nginx - if not validator.is_valid_app(app, self.conf): + if app not in self.conf['apps']: raise validator.InvalidValueException('app', app) os.unlink(os.path.join(NGINX_DIR, '{}.conf'.format(app))) tools.reload_nginx() diff --git a/basic/srv/vm/mgr/pkgmgr.py b/basic/srv/vm/mgr/pkgmgr.py index 452e048..d5bf899 100644 --- a/basic/srv/vm/mgr/pkgmgr.py +++ b/basic/srv/vm/mgr/pkgmgr.py @@ -22,7 +22,7 @@ class PackageManager: # Load JSON configuration self.conf = conf self.online_packages = {} - self.pending = 0 + self.bytes_downloaded = 0 def get_repo_resource(self, url, stream=False): return requests.get('{}/{}'.format(self.conf['repo']['url'], url), auth=(self.conf['repo']['user'], self.conf['repo']['pwd']), stream=stream) @@ -40,7 +40,7 @@ class PackageManager: def register_pending_installation(self, name): # Registers pending installation. Fetch online packages here instead of install_pacakges() to fail early if the repo isn't reachable self.fetch_online_packages() - self.pending = 1 + self.bytes_downloaded = 1 # Return total size for download deps = [d for d in self.get_install_deps(name) if d not in self.conf['packages']] return sum(self.online_packages[d]['size'] for d in deps) @@ -48,11 +48,15 @@ class PackageManager: def install_package(self, name): # Main installation function. Wrapper for download, registration and install script deps = [d for d in self.get_install_deps(name) if d not in self.conf['packages']] - for dep in deps: - self.download_package(dep) - self.register_package(dep) - self.run_install_script(dep) - self.pending = 0 + try: + for dep in deps: + self.download_package(dep) + self.register_package(dep) + self.run_install_script(dep) + self.bytes_downloaded = 0 + except: + # Store exception state for retrieval via get_install_progress_action() + self.bytes_downloaded = -1 def uninstall_package(self, name): # Main uninstallation function. Wrapper for uninstall script, filesystem purge and unregistration @@ -70,12 +74,12 @@ class PackageManager: with open(tmp_archive, 'wb') as f: for chunk in r.iter_content(chunk_size=65536): if chunk: - self.pending += f.write(chunk) + self.bytes_downloaded += f.write(chunk) # Verify hash if self.online_packages[name]['sha512'] != hash_file(tmp_archive): raise InvalidSignature(name) # Unpack - subprocess.run(['tar', 'xJf', tmp_archive], cwd='/') + subprocess.run(['tar', 'xJf', tmp_archive], cwd='/', check=True) os.unlink(tmp_archive) def purge_package(self, name): @@ -85,22 +89,6 @@ class PackageManager: if os.path.exists(srv_dir): shutil.rmtree(srv_dir) - def run_install_script(self, name): - # Runs install.sh for a package, if the script is present - install_dir = os.path.join('/srv/', name, 'install') - install_script = os.path.join('/srv/', name, 'install.sh') - if os.path.exists(install_script): - subprocess.run(install_script) - os.unlink(install_script) - if os.path.exists(install_dir): - shutil.rmtree(install_dir) - - def run_uninstall_script(self, name): - # Runs uninstall.sh for a package, if the script is present - uninstall_script = os.path.join('/srv/', name, 'uninstall.sh') - if os.path.exists(uninstall_script): - subprocess.run(uninstall_script) - def register_package(self, name): # Registers a package in local configuration metadata = self.online_packages[name] @@ -127,6 +115,22 @@ class PackageManager: del self.conf['apps'][name] self.conf.save() + def run_install_script(self, name): + # Runs install.sh for a package, if the script is present + install_dir = os.path.join('/srv/', name, 'install') + install_script = os.path.join('/srv/', name, 'install.sh') + if os.path.exists(install_script): + subprocess.run(install_script, check=True) + os.unlink(install_script) + if os.path.exists(install_dir): + shutil.rmtree(install_dir) + + def run_uninstall_script(self, name): + # Runs uninstall.sh for a package, if the script is present + uninstall_script = os.path.join('/srv/', name, 'uninstall.sh') + if os.path.exists(uninstall_script): + subprocess.run(uninstall_script, check=True) + def get_install_deps(self, name, online=True): # Flatten dependency tree for a package while preserving the dependency order packages = self.online_packages if online else self.conf['packages'] diff --git a/basic/srv/vm/mgr/validator.py b/basic/srv/vm/mgr/validator.py index 731f6e5..14571d4 100644 --- a/basic/srv/vm/mgr/validator.py +++ b/basic/srv/vm/mgr/validator.py @@ -18,9 +18,6 @@ def is_valid_port(port): except: return False -def is_valid_app(app, conf): - return app in conf['apps'] - def is_valid_email(email): parts = email.split('@') if len(parts) != 2: diff --git a/basic/srv/vm/mgr/wsgiapp.py b/basic/srv/vm/mgr/wsgiapp.py index 0c925b9..0f8f574 100644 --- a/basic/srv/vm/mgr/wsgiapp.py +++ b/basic/srv/vm/mgr/wsgiapp.py @@ -154,9 +154,9 @@ class WSGIApp(object): all_apps = sorted(set([k for k,v in self.pkgmgr.online_packages.items() if 'host' in v] + list(self.conf['apps'].keys()))) return self.render_template('setup-apps.html', request, all_apps=all_apps, online_packages=self.pkgmgr.online_packages) - def render_setup_apps_row(self, app, app_title, total_size=None): + def render_setup_apps_row(self, request, app, app_title, total_size=None, install_error=False): t = self.jinja_env.get_template('setup-apps-row.html') - return t.render({'app': app, 'app_title': app_title, 'conf': self.conf, 'total_size': total_size}) + return t.render({'conf': self.conf, 'session': request.session, 'app': app, 'app_title': app_title, 'total_size': total_size, 'install_error': install_error}) def update_host_action(self, request): # Update domain and port, then restart nginx @@ -287,7 +287,7 @@ class WSGIApp(object): except: return self.render_json({'error': request.session.lang.stop_start_error()}) app_title = self.conf['apps'][app]['title'] - return self.render_json({'ok': self.render_setup_apps_row(app, app_title)}) + return self.render_json({'ok': self.render_setup_apps_row(request, app, app_title)}) def stop_app_action(self, request): # Stops application along with its dependencies @@ -300,11 +300,11 @@ class WSGIApp(object): except: return self.render_json({'error': request.session.lang.stop_start_error()}) app_title = self.conf['apps'][app]['title'] - return self.render_json({'ok': self.render_setup_apps_row(app, app_title)}) + return self.render_json({'ok': self.render_setup_apps_row(request, app, app_title)}) def install_app_action(self, request): # Registers the application installation as pending - if self.pkgmgr.pending: + if self.pkgmgr.bytes_downloaded > 0: return self.render_json({'error': request.session.lang.installation_in_progress()}) try: app = request.form['app'] @@ -314,21 +314,23 @@ class WSGIApp(object): except: return self.render_json({'error': request.session.lang.package_manager_error()}) app_title = self.pkgmgr.online_packages[app]['title'] - response = self.render_json({'ok': self.render_setup_apps_row(app, app_title, total_size)}) + response = self.render_json({'ok': self.render_setup_apps_row(request, app, app_title, total_size)}) response.call_on_close(lambda: self.pkgmgr.install_package(app)) return response def get_install_progress_action(self, request): # Gets pending installation status - if self.pkgmgr.pending: - return self.render_json({'progress': self.pkgmgr.pending}) + if self.pkgmgr.bytes_downloaded > 0: + return self.render_json({'progress': self.pkgmgr.bytes_downloaded}) app = request.form['app'] - app_title = self.conf['apps'][app]['title'] - return self.render_json({'ok': self.render_setup_apps_row(app, app_title)}) + # In case of installation error, we need to get the name from online_packages as the app is not yet registered + app_title = self.conf['apps'][app]['title'] if app in self.conf['apps'] else self.pkgmgr.online_packages[app]['title'] + install_error = True if self.pkgmgr.bytes_downloaded == -1 else False + return self.render_json({'ok': self.render_setup_apps_row(request, app, app_title, None, install_error)}) def uninstall_app_action(self, request): # Uninstalls application - if self.pkgmgr.pending: + if self.pkgmgr.bytes_downloaded > 0: return self.render_json({'error': request.session.lang.installation_in_progress()}) try: app = request.form['app'] @@ -337,9 +339,8 @@ class WSGIApp(object): except (BadRequest, InvalidValueException): return self.render_json({'error': request.session.lang.malformed_request()}) except: - raise - # return self.render_json({'error': request.session.lang.package_manager_error()}) - return self.render_json({'ok': self.render_setup_apps_row(app, app_title)}) + return self.render_json({'error': request.session.lang.package_manager_error()}) + return self.render_json({'ok': self.render_setup_apps_row(request, app, app_title)}) def update_password_action(self, request): # Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account diff --git a/basic/srv/vm/mgr/wsgilang.py b/basic/srv/vm/mgr/wsgilang.py index 353916c..8785643 100644 --- a/basic/srv/vm/mgr/wsgilang.py +++ b/basic/srv/vm/mgr/wsgilang.py @@ -20,7 +20,7 @@ class WSGILang: 'common_updated': 'Nastavení aplikací bylo úspěšně změněno.', 'stop_start_error': 'Došlo k chybě při spouštění/zastavování. Zkuste akci opakovat nebo restartuje virtuální stroj.', 'installation_in_progress': 'Probíhá instalace jiného balíku. Vyčkejte na její dokončení.', - 'package_manager_error': 'Došlo k chybě při instalaci aplikace', + 'package_manager_error': 'Došlo k chybě při instalaci aplikace. Zkuste akci opakovat nebo restartuje virtuální stroj.', 'bad_password': 'Nesprávné heslo', 'password_mismatch': 'Zadaná hesla se neshodují', 'password_empty': 'Nové heslo nesmí být prázdné', diff --git a/basic/srv/vm/static/js/admin.js b/basic/srv/vm/static/js/admin.js index f91ea51..e5f5335 100644 --- a/basic/srv/vm/static/js/admin.js +++ b/basic/srv/vm/static/js/admin.js @@ -6,9 +6,9 @@ $(function() { $('#cert-method').on('change', toggle_cert_method); $('#update-cert').on('submit', update_cert); $('#update-common').on('submit', update_common); - $('.app-visible').on('click', update_app_visibility); - $('.app-autostart').on('click', update_app_autostart); $('#app-manager') + .on('click', '.app-visible', update_app_visibility) + .on('click', '.app-autostart', update_app_autostart) .on('click', '.app-start', start_app) .on('click', '.app-stop', stop_app) .on('click', '.app-install', install_app) diff --git a/basic/srv/vm/templates/setup-apps-row.html b/basic/srv/vm/templates/setup-apps-row.html index 3edcea3..14feb8f 100644 --- a/basic/srv/vm/templates/setup-apps-row.html +++ b/basic/srv/vm/templates/setup-apps-row.html @@ -1,7 +1,22 @@