diff --git a/basic/srv/vm/mgr/pkgmgr.py b/basic/srv/vm/mgr/pkgmgr.py index 56d887d..122168d 100644 --- a/basic/srv/vm/mgr/pkgmgr.py +++ b/basic/srv/vm/mgr/pkgmgr.py @@ -36,13 +36,20 @@ class PackageManager: self.online_packages = json.loads(packages) def install_package(self, name): - # Main installation function. Wrapper for download, registration and setup + # Main installation function. Wrapper for download, install script and registration self.fetch_online_packages() for dep in self.get_deps(name): if dep not in self.conf['packages']: self.download_package(dep) + self.run_install_script(dep) self.register_package(dep) - self.setup_package() + + def uninstall_package(self, name): + # Main uninstallation function. Wrapper for uninstall script, filesystem purge and unregistration + # TODO: Get dependencies which can be uninstalled + self.run_uninstall_script(name) + self.purge_package(name) + self.unregister_package(name) def download_package(self, name): # Downloads, verifies, unpacks and sets up a package @@ -56,14 +63,39 @@ class PackageManager: if self.online_packages[name]['sha512'] != hash_file(tmp_archive): raise InvalidSignature(name) # Unpack - subprocess.run(['tar', 'xJf', tmp_archive], cwd=LXC_ROOT) + subprocess.run(['tar', 'xJf', tmp_archive], cwd='/') os.unlink(tmp_archive) + def purge_package(self, name): + # Removes package and shared data from filesystem + shutil.rmtree(os.path.join(LXC_ROOT, self.conf['packages'][name]['lxcpath'])) + srv_dir = os.path.join('/srv/', name) + if os.path.exsit(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] self.conf['packages'][name] = { - 'version': metadata['version'], + 'deps': metadata['deps'], + 'lxcpath': metadata['lxcpath'], + 'version': metadata['version'] } # If host definition is present, register the package as application if 'host' in metadata: @@ -76,15 +108,12 @@ class PackageManager: } self.conf.save() - def setup_package(self): - # Runs setup.sh for a package, if the script is present - setup_dir = os.path.join(LXC_ROOT, 'setup') - setup_script = os.path.join(LXC_ROOT, 'setup.sh') - if os.path.exists(setup_script): - subprocess.run(setup_script) - os.unlink(setup_script) - if os.path.exists(setup_dir): - shutil.rmtree(setup_dir) + def unregister_package(self, name): + # Removes a package from local configuration + del self.conf['packages'][name] + if name in self.conf['apps']: + del self.conf['apps'][name] + self.conf.save() def get_deps(self, name): # Flatten dependency tree for a package diff --git a/basic/srv/vm/mgr/wsgiapp.py b/basic/srv/vm/mgr/wsgiapp.py index 2566798..734daa8 100644 --- a/basic/srv/vm/mgr/wsgiapp.py +++ b/basic/srv/vm/mgr/wsgiapp.py @@ -78,6 +78,7 @@ class WSGIApp(object): Rule('/start-app', endpoint='start_app_action'), Rule('/stop-app', endpoint='stop_app_action'), Rule('/install-app', endpoint='install_app_action'), + Rule('/uninstall-app', endpoint='uninstall_app_action'), Rule('/update-password', endpoint='update_password_action'), Rule('/shutdown-vm', endpoint='shutdown_vm_action'), Rule('/reboot-vm', endpoint='reboot_vm_action'), @@ -286,9 +287,27 @@ class WSGIApp(object): return self.render_json({'error': request.session.lang.malformed_request()}) except: return self.render_json({'error': request.session.lang.package_manager_error()}) + # Reload config and get fresh data + self.vmmgr.conf.load() app_title = self.vmmgr.conf['apps'][app]['title'] return self.render_json({'ok': self.render_setup_apps_row(app, app_title)}) + def uninstall_app_action(self, request): + # Uninstalls application + try: + app = request.form['app'] + self.vmmgr.stop_app(app) + pkgmgr = PackageManager() + pkgmgr.uninstall_package(app) + except (BadRequest, InvalidValueException): + return self.render_json({'error': request.session.lang.malformed_request()}) + except: + return self.render_json({'error': request.session.lang.package_manager_error()}) + # Get title from old data, then reload config + app_title = self.vmmgr.conf['apps'][app]['title'] + self.vmmgr.conf.load() + return self.render_json({'ok': self.render_setup_apps_row(app, app_title)}) + def update_password_action(self, request): # Updates password for both HDD encryption (LUKS-on-LVM) and web interface admin account try: diff --git a/basic/srv/vm/static/js/admin.js b/basic/srv/vm/static/js/admin.js index e2b3a24..3bf8175 100644 --- a/basic/srv/vm/static/js/admin.js +++ b/basic/srv/vm/static/js/admin.js @@ -11,7 +11,8 @@ $(function() { $('#app-manager') .on('click', '.app-start', start_app) .on('click', '.app-stop', stop_app) - .on('click', '.app-install', install_app); + .on('click', '.app-install', install_app) + .on('click', '.app-uninstall', install_app); $('#update-password').on('submit', update_password); $('#reboot-vm').on('click', reboot_vm); $('#shutdown-vm').on('click', shutdown_vm); @@ -116,11 +117,11 @@ function update_common() { return false; } -function update_app_visibility(ev) { +function _update_app(item, ev) { var el = $(ev.target); var app = el.closest('tr').data('app'); var value = el.is(':checked') ? 'true' : ''; - $.post('/update-app-visibility', {'app': app, 'value': value}, function(data) { + $.post('/update-app-'+item, {'app': app, 'value': value}, function(data) { if (data.error) { el.prop('checked', !value); alert(data.error); @@ -128,60 +129,45 @@ function update_app_visibility(ev) { }); } +function update_app_visibility(ev) { + return _update_app('visibility', ev); +} + function update_app_autostart(ev) { + return _update_app('autostart', ev); +} + +function _do_app(action, ev) { var el = $(ev.target); - var app = el.closest('tr').data('app'); - var value = el.is(':checked') ? 'true' : ''; - $.post('/update-app-autostart', {'app': app, 'value': value}, function(data) { + var tr = el.closest('tr'); + var td = el.closest('td'); + td.html('
'); + $.post('/'+action+'-app', {'app': tr.data('app')}, function(data) { if (data.error) { - el.prop('checked', !value); - alert(data.error); + td.attr('class','error').html(data.error); + } else { + tr.replaceWith(data.ok); } }); + return false; } function start_app(ev) { - var el = $(ev.target); - var tr = el.closest('tr'); - var td = el.closest('td'); - td.html('
'); - $.post('/start-app', {'app': tr.data('app')}, function(data) { - if (data.error) { - td.attr('class','error').html(data.error); - } else { - tr.replaceWith(data.ok); - } - }); - return false; + return _do_app('start', ev); } function stop_app(ev) { - var el = $(ev.target); - var tr = el.closest('tr'); - var td = el.closest('td'); - td.html('
'); - $.post('/stop-app', {'app': tr.data('app')}, function(data) { - if (data.error) { - td.attr('class','error').html(data.error); - } else { - tr.replaceWith(data.ok); - } - }); - return false; + return _do_app('stop', ev); } function install_app(ev) { - var el = $(ev.target); - var tr = el.closest('tr'); - var td = el.closest('td'); - td.html('
'); - $.post('/install-app', {'app': tr.data('app')}, function(data) { - if (data.error) { - td.attr('class','error').html(data.error); - } else { - tr.replaceWith(data.ok); - } - }); + return _do_app('install', ev); +} + +function uninstall_app(ev) { + if (confirm('Do you really want to uninstall this applition?')) { + return _do_app('uninstall', ev); + } return false; } @@ -201,20 +187,22 @@ function update_password() { return false; } +function _do_vm(action) { + $.get('/'+action+'-vm', function(data) { + $('#vm-message').attr('class','info').html(data.ok).show(); + }); +} + function reboot_vm() { if (confirm('Do you really want to reboot VM?')) { - $.get('/reboot-vm', function(data) { - $('#vm-message').attr('class','info').html(data.ok).show(); - }); + _do_vm('reboot'); } return false; } function shutdown_vm() { if (confirm('Do you really want to shutdown VM?')) { - $.get('/shutdown-vm', function(data) { - $('#vm-message').attr('class','info').html(data.ok).show(); - }); + _do_vm('shutdown'); } return false; } diff --git a/basic/srv/vm/templates/setup-apps-row.html b/basic/srv/vm/templates/setup-apps-row.html index 378e1de..a6eca95 100644 --- a/basic/srv/vm/templates/setup-apps-row.html +++ b/basic/srv/vm/templates/setup-apps-row.html @@ -2,6 +2,6 @@ {{ app_title }} - {% if app not in conf['apps'] %} Není nainstalována{% elif is_service_started(app) %}Spuštěna{% else %}Zastavena{% endif %} + {% if app not in conf['apps'] %} N/A{% elif is_service_started(app) %}Spuštěna{% else %}Zastavena{% endif %} {% if app not in conf['apps'] %}Instalovat{% else %}{% if is_service_started(app) %}Zastavit{% else %}Spustit{% endif %}, Odinstalovat{% endif %} diff --git a/zz-build/usr/bin/lxc-pack b/zz-build/usr/bin/lxc-pack index ac788cc..907b1f6 100755 --- a/zz-build/usr/bin/lxc-pack +++ b/zz-build/usr/bin/lxc-pack @@ -39,12 +39,13 @@ def pack(pkg_file): # Create archive print('Archiving', meta['lxcpath']) - subprocess.run(['tar', 'cp', '--xattrs', '-f', tar_path, meta['lxcpath']], cwd=LXC_ROOT) + subprocess.run(['tar', '--xattrs', '-cpf', tar_path, os.path.join(LXC_ROOT, meta['lxcpath'])], cwd='/') if '/' not in meta['lxcpath']: - print('Archiving setup files') + # If lxcpath doesn't point to layer but is a full-featured container, pack also scripts + print('Archiving install/upgrade/uninstall scripts and related files') cwd = os.path.dirname(os.path.abspath(pkg_file)) - subprocess.run(['tar', 'rpf', tar_path, 'setup', 'setup.sh'], cwd=cwd) - print('Compressing', tar_path) + subprocess.run(['tar', '--transform' 's,^\.,/srv/{},'.format(pkg_name), '-rpf', tar_path, 'install', 'install.sh', 'upgrade', 'upgrade.sh', 'uninstall', 'uninstall.sh'], cwd=cwd) + print('Compressing', tar_path, '({} MB)'.format(os.path.getsize(tar_path)/1048576)) subprocess.run(['xz', '-9', tar_path]) # Register package