diff --git a/usr/bin/lxchelper b/usr/bin/lxchelper new file mode 100755 index 0000000..f5ba5cb --- /dev/null +++ b/usr/bin/lxchelper @@ -0,0 +1,68 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import argparse +import os +import shutil +import sys +import tempfile + +from lxcmgr import lxcmgr +from lxcmgr.paths import LXC_ROOT + +def get_layers(container): + with open(os.path.join(LXC_ROOT, container, 'config')) as f: + for line in f.read().splitlines(): + if line.startswith('lxc.hook.pre-start'): + return line.split()[-1].split(',') + +def copy(source, destination): + if os.path.isdir(source): + shutil.copytree(source, destination, True) + else: + shutil.copy2(source, destination) + +def extract(args): + with tempfile.TemporaryDirectory() as tmp_rootfs: + layers = get_layers(args.container) + lxcmgr.mount_rootfs(args.container, layers, tmp_rootfs) + source = os.path.join(tmp_rootfs, args.source.lstrip('/')) + try: + copy(source, args.destination) + except: + lxcmgr.unmount_rootfs(tmp_rootfs) + raise + lxcmgr.unmount_rootfs(tmp_rootfs) + +def main(args): + if args.action == 'prepare': + # Used with LXC hooks on container startup + lxcmgr.prepare_container(args.container, args.layers) + elif args.action == 'cleanup': + # Used with LXC hooks on container stop + lxcmgr.cleanup_container(args.container) + elif args.action == 'extract': + # Used in install.sh scripts to get files or directories from containers rootfs (excluding persistent mounts) + extract(args) + +parser = argparse.ArgumentParser(description='Collection of auxiliary LXC tools') +subparsers = parser.add_subparsers() + +parser_prepare = subparsers.add_parser('prepare', help='Perform pre-start steps for LXC') +parser_prepare.set_defaults(action='prepare') +parser_prepare.add_argument('layers', help='OverlayFS LXC rootfs layers') +parser_prepare.add_argument('container', help='Container name') +parser_prepare.add_argument('lxc', nargs=argparse.REMAINDER) + +parser_cleanup = subparsers.add_parser('cleanup', help='Perform post-stop steps for LXC') +parser_cleanup.set_defaults(action='cleanup') +parser_cleanup.add_argument('container', help='Container name') +parser_cleanup.add_argument('lxc', nargs=argparse.REMAINDER) + +parser_extract = subparsers.add_parser('extract', help='Extracts files or directories from containers rootfs (excluding persistent mounts)') +parser_cleanup.set_defaults(action='extract') +parser_extract.add_argument('container', help='Container name') +parser_extract.add_argument('source', help='Source file or directory within the container') +parser_extract.add_argument('destination', help='Destination file or directory on the host') + +main(parser.parse_args()) diff --git a/usr/bin/lxcmgr b/usr/bin/lxcmgr index 95dd3e1..8f3827f 100755 --- a/usr/bin/lxcmgr +++ b/usr/bin/lxcmgr @@ -10,44 +10,6 @@ from concurrent.futures import ThreadPoolExecutor from lxcmgr import lxcmgr from lxcmgr.pkgmgr import App, Stage, PkgMgr -parser = argparse.ArgumentParser(description='LXC container and package manager') -subparsers = parser.add_subparsers() - -parser_list = subparsers.add_parser('list') -subparsers_list = parser_list.add_subparsers() -parser_list_installed = subparsers_list.add_parser('installed') -parser_list_installed.set_defaults(action='list-installed') -parser_list_online = subparsers_list.add_parser('online') -parser_list_online.set_defaults(action='list-online') -parser_list_updates = subparsers_list.add_parser('updates') -parser_list_updates.set_defaults(action='list-updates') - -parser_install = subparsers.add_parser('install') -parser_install.set_defaults(action='install') -parser_install.add_argument('app', help='Application to install') - -parser_update = subparsers.add_parser('update') -parser_update.set_defaults(action='update') -parser_update.add_argument('app', help='Application to update') - -parser_uninstall = subparsers.add_parser('uninstall') -parser_uninstall.set_defaults(action='uninstall') -parser_uninstall.add_argument('app', help='Application to uninstall') - -parser_container = subparsers.add_parser('container') -subparsers_container = parser_container.add_subparsers() - -parser_container_prepare = subparsers_container.add_parser('prepare') -parser_container_prepare.set_defaults(action='container-prepare') -parser_container_prepare.add_argument('layers', help='OverlayFS LXC rootfs layers') -parser_container_prepare.add_argument('container', help='Container name') -parser_container_prepare.add_argument('lxc', nargs=argparse.REMAINDER) - -parser_container_cleanup = subparsers_container.add_parser('cleanup') -parser_container_cleanup.set_defaults(action='container-cleanup') -parser_container_cleanup.add_argument('container', help='Container name') -parser_container_cleanup.add_argument('lxc', nargs=argparse.REMAINDER) - def print_apps(packages): for app, meta in packages.items(): print(app, meta['version']) @@ -125,26 +87,42 @@ def uninstall_app(app): app = App(app) run_install_action(pm.uninstall_app, app) -args = parser.parse_args() -if not hasattr(args, 'action'): - parser.print_usage() - sys.exit(1) +def main(args): + if args.action == 'list-installed': + list_installed() + elif args.action == 'list-online': + list_online() + elif args.action == 'list-updates': + list_updates() + elif args.action == 'install': + install_app(args.app) + elif args.action == 'update': + update_app(args.app) + elif args.action == 'uninstall': + uninstall_app(args.app) -if args.action == 'list-installed': - list_installed() -elif args.action == 'list-online': - list_online() -elif args.action == 'list-updates': - list_updates() -elif args.action == 'install': - install_app(args.app) -elif args.action == 'update': - update_app(args.app) -elif args.action == 'uninstall': - uninstall_app(args.app) -elif args.action == 'container-prepare': - # Used with LXC hooks on container startup - lxcmgr.prepare_container(args.container, args.layers) -elif args.action == 'container-cleanup': - # Used with LXC hooks on container stop - lxcmgr.cleanup_container(args.container) +parser = argparse.ArgumentParser(description='LXC container and package manager') +subparsers = parser.add_subparsers() + +parser_list = subparsers.add_parser('list') +subparsers_list = parser_list.add_subparsers() +parser_list_installed = subparsers_list.add_parser('installed') +parser_list_installed.set_defaults(action='list-installed') +parser_list_online = subparsers_list.add_parser('online') +parser_list_online.set_defaults(action='list-online') +parser_list_updates = subparsers_list.add_parser('updates') +parser_list_updates.set_defaults(action='list-updates') + +parser_install = subparsers.add_parser('install') +parser_install.set_defaults(action='install') +parser_install.add_argument('app', help='Application to install') + +parser_update = subparsers.add_parser('update') +parser_update.set_defaults(action='update') +parser_update.add_argument('app', help='Application to update') + +parser_uninstall = subparsers.add_parser('uninstall') +parser_uninstall.set_defaults(action='uninstall') +parser_uninstall.add_argument('app', help='Application to uninstall') + +main(parser.parse_args()) diff --git a/usr/lib/python3.6/lxcmgr/lxcmgr.py b/usr/lib/python3.6/lxcmgr/lxcmgr.py index e21a1b0..0af3fe6 100644 --- a/usr/lib/python3.6/lxcmgr/lxcmgr.py +++ b/usr/lib/python3.6/lxcmgr/lxcmgr.py @@ -16,14 +16,20 @@ def prepare_container(container, layers): # 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]) + unmount_rootfs(rootfs) layers = layers.split(',') + mount_rootfs(container, layers, rootfs) + +def mount_rootfs(container, layers, mountpoint) if len(layers) == 1: # We have only single layer, no overlay needed - subprocess.run(['mount', '--bind', layers[0], rootfs]) + subprocess.run(['mount', '--bind', layers[0], mountpoint]) else: olwork = os.path.join(LXC_ROOT, container, 'olwork') - subprocess.run(['mount', '-t', 'overlay', '-o', 'upperdir={},lowerdir={},workdir={}'.format(layers[0], ':'.join(layers[1:]), olwork), 'none', rootfs]) + subprocess.run(['mount', '-t', 'overlay', '-o', 'upperdir={},lowerdir={},workdir={}'.format(layers[0], ':'.join(layers[1:]), olwork), 'none', mountpoint]) + +def unmount_rootfs(mountpoint): + subprocess.run(['umount', '--quiet', mountpoint]) def clean_ephemeral_layer(container): # Cleans containers ephemeral layer. Called in lxc.hook.post-stop and lxc.hook.pre-start in case of unclean shutdown @@ -35,7 +41,7 @@ def clean_ephemeral_layer(container): def cleanup_container(container): # Unmount rootfs rootfs = os.path.join(LXC_ROOT, container, 'rootfs') - subprocess.run(['umount', rootfs]) + unmount_rootfs(rootfs) # Remove ephemeral layer data clean_ephemeral_layer(container) diff --git a/usr/lib/python3.6/lxcmgr/templates.py b/usr/lib/python3.6/lxcmgr/templates.py index ce3258e..8bda2ab 100644 --- a/usr/lib/python3.6/lxcmgr/templates.py +++ b/usr/lib/python3.6/lxcmgr/templates.py @@ -39,8 +39,8 @@ lxc.idmap = u 0 100000 65536 lxc.idmap = g 0 100000 65536 # Hooks -lxc.hook.pre-start = /usr/bin/lxcmgr container prepare {layers} -lxc.hook.post-stop = /usr/bin/lxcmgr container cleanup +lxc.hook.pre-start = /usr/bin/lxchelper prepare {layers} +lxc.hook.post-stop = /usr/bin/lxchelper cleanup # Other lxc.arch = linux64