diff --git a/usr/bin/lxcmgr b/usr/bin/lxcmgr index 42d9f29..98aa4ec 100755 --- a/usr/bin/lxcmgr +++ b/usr/bin/lxcmgr @@ -97,6 +97,7 @@ def install_app(app): print_install_status(app) def print_install_status(app): + # Prints current status of the installation. Uses ANSI "erase line" and "carriage return" to rewrite the status on single line. if app.stage == Stage.QUEUED: print('\x1b[KQueued...', end='\r') elif app.stage == Stage.DOWNLOAD: diff --git a/usr/lib/python3.6/lxcmgr/crypto.py b/usr/lib/python3.6/lxcmgr/crypto.py index 02acd43..5a169e3 100644 --- a/usr/lib/python3.6/lxcmgr/crypto.py +++ b/usr/lib/python3.6/lxcmgr/crypto.py @@ -7,8 +7,6 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec -from .paths import REPO_SIG_FILE - def verify_signature(public_key_path, input_data, signature_data): # Verifies ECDSA HMAC SHA512 signature of a file with open(public_key_path, 'rb') as f: diff --git a/usr/lib/python3.6/lxcmgr/lxcmgr.py b/usr/lib/python3.6/lxcmgr/lxcmgr.py index f066a41..3e6b613 100644 --- a/usr/lib/python3.6/lxcmgr/lxcmgr.py +++ b/usr/lib/python3.6/lxcmgr/lxcmgr.py @@ -12,7 +12,8 @@ from .templates import LXC_CONTAINER def prepare_container(container, layers): # Remove ephemeral layer data clean_ephemeral_layer(container) - # Prepare and mount overlayfs + # Prepare and mount overlayfs. This needs to be done before handing over control to LXC as we use unprivileged containers + # which don't have the capability to mount overlays - https://www.spinics.net/lists/linux-fsdevel/msg105877.html rootfs = os.path.join(LXC_ROOT, container, 'rootfs') # Unmount rootfs in case it remained mounted for whatever reason subprocess.run(['umount', rootfs]) diff --git a/usr/lib/python3.6/lxcmgr/paths.py b/usr/lib/python3.6/lxcmgr/paths.py index 1ed139b..e7bc1eb 100644 --- a/usr/lib/python3.6/lxcmgr/paths.py +++ b/usr/lib/python3.6/lxcmgr/paths.py @@ -13,3 +13,8 @@ HOSTS_LOCK = '/var/lock/lxcmgr-hosts.lock' LXC_LOGS = '/var/log/lxc' LXC_ROOT = '/var/lib/lxc' LXC_STORAGE_DIR = '/var/lib/lxcmgr/storage' + +# Services +AUTOSTART_SVC_DIR = '/etc/runlevels/default' +SERVICE_DIR = '/etc/init.d' +STARTED_SVC_DIR = '/run/openrc/started' diff --git a/usr/lib/python3.6/lxcmgr/pkgmgr.py b/usr/lib/python3.6/lxcmgr/pkgmgr.py index 75f7318..6af1aad 100644 --- a/usr/lib/python3.6/lxcmgr/pkgmgr.py +++ b/usr/lib/python3.6/lxcmgr/pkgmgr.py @@ -12,6 +12,7 @@ from pkg_resources import parse_version from . import crypto from . import flock from . import lxcmgr +from . import svcmgr from .paths import LXC_STORAGE_DIR, REPO_CACHE_DIR, REPO_CONF_FILE, REPO_LOCAL_FILE, REPO_LOCK, REPO_SIG_FILE class Stage(Enum): @@ -99,7 +100,7 @@ class PkgMgr: images.extend(self.online_packages['images'][image]['layers']) images = [image for image in set(images) if image not in self.installed_packages['images']] # Calculate bytes to download - app.bytes_total = sum(self.online_packages['images'][image]['size'] for image in images) + self.online_packages['apps'][app.name]['size'] + app.bytes_total = sum(self.online_packages['images'][image]['pkgsize'] for image in images) + self.online_packages['apps'][app.name]['pkgsize'] # Download layers and setup script files app.stage = Stage.DOWNLOAD for image in images: @@ -214,8 +215,10 @@ class PkgMgr: image = self.online_packages['images'][image].copy() if 'mounts' in self.online_packages['apps'][app]['containers'][container]: image['mounts'] = self.online_packages['apps'][app]['containers'][container]['mounts'] + if 'depends' in self.online_packages['apps'][app]['containers'][container] + image['depends'] = self.online_packages['apps'][app]['containers'][container]['depends'] lxcmgr.create_container(container, image) - # TODO: Create services + svcmgr.create_service(app, container, image) @flock.flock_ex(REPO_LOCK) def uninstall_app(self, app): @@ -225,11 +228,13 @@ class PkgMgr: self.run_uninstall_script(app) self.destroy_containers(app) self.purge_scripts(app) + self.unregister_app(app) self.purge_unused_layers() def destroy_containers(self, app): # Destroy LXC containers for container in self.installed_packages['apps'][app]['containers']: + svcmgr.delete_service(container) lxcmgr.destroy_container(container) def purge_unused_layers(self): diff --git a/usr/lib/python3.6/lxcmgr/svcmgr.py b/usr/lib/python3.6/lxcmgr/svcmgr.py new file mode 100644 index 0000000..2f3f9ab --- /dev/null +++ b/usr/lib/python3.6/lxcmgr/svcmgr.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +import os +import subprocess + +from .paths import AUTOSTART_SVC_DIR, SERVICE_DIR, STARTED_SVC_DIR +from .templates import SERVICE + +def create_service(app, container, image): + depends = ' '.join(image['depends']) if 'depends' in image else '' + check = 'lxc-execute {} -- sh -c \'until $({}); do sleep 0.1; done\''.format(container, image['check']) if 'check' in image else '' + with open(os.path.join(SERVICE_DIR, container), 'w') as f: + f.write(SERVICE.format(app=app, container=container, depends=depends, check=check)) + update_services() + +def delete_service(service): + if is_service_started(service): + stop_service(service) + if is_service_autostarted(service): + update_service_autostart(service, False) + try: + os.unlink(os.path.join(SERVICE_DIR, service)) + except FileNotFoundError: + pass + update_services() + +def update_services(): + subprocess.run(['/sbin/rc-update', '-u'], check=True) + +def start_service(service): + if not is_service_started(service): + subprocess.run(['/sbin/service', service, 'stop'], check=True) + +def stop_service(service): + if is_service_started(service): + subprocess.run(['/sbin/service', service, 'stop'], check=True) + +def is_service_started(self, app): + # Check OpenRC service status without calling any binary + return os.path.exists(os.path.join(STARTED_SVC_DIR, app)) + +def is_service_autostarted(self, app): + # Check OpenRC service enablement + return os.path.exists(os.path.join(AUTOSTART_SVC_DIR, app)) + +def update_service_autostart(self, service, enabled): + # Add/remove the app to OpenRC default runlevel + subprocess.run(['/sbin/rc-update', 'add' if enabled else 'del', service]) diff --git a/usr/lib/python3.6/lxcmgr/templates.py b/usr/lib/python3.6/lxcmgr/templates.py index 7eeb32e..9787d19 100644 --- a/usr/lib/python3.6/lxcmgr/templates.py +++ b/usr/lib/python3.6/lxcmgr/templates.py @@ -49,3 +49,24 @@ lxc.cap.drop = sys_admin lxc.include = /usr/share/lxc/config/common.conf lxc.include = /usr/share/lxc/config/userns.conf ''' + +SERVICE = """#!/sbin/openrc-run + +description="{app} {container} LXC container" + +depend() {{ + need cgroups {depends} +}} + +start() {{ + lxc-start {container} +}} + +start_post() {{ + {check} +}} + +stop() {{ + lxc-stop {container} +}} +""" diff --git a/usr/lib/python3.6/vmmgr/paths.py b/usr/lib/python3.6/vmmgr/paths.py index e8dd368..6187d75 100644 --- a/usr/lib/python3.6/vmmgr/paths.py +++ b/usr/lib/python3.6/vmmgr/paths.py @@ -10,17 +10,6 @@ ACME_DIR = '/etc/acme.sh.d' CERT_KEY_FILE = '/etc/ssl/services.key' CERT_PUB_FILE = '/etc/ssl/services.pem' -# Package manager -REPO_LOCAL_FILE = '/var/lib/lxc-pkg/packages' -REPO_SIG_FILE = '/etc/vmmgr/packages.pub' -REPO_CACHE_DIR = '/var/lib/lxc-pkg/cache' - -# LXC -HOSTS_FILE = '/etc/hosts' -HOSTS_LOCK = '/var/lock/vmmgr-hosts.lock' -LXC_ROOT = '/var/lib/lxc' -LXC_STORAGE_DIR = '/var/lib/lxc-pkg/storage' - # OS ISSUE_FILE = '/etc/issue' MOTD_FILE = '/etc/motd'