diff --git a/jlmkr.py b/jlmkr.py old mode 100644 new mode 100755 index 4bbd66b..3d842a9 --- a/jlmkr.py +++ b/jlmkr.py @@ -13,6 +13,7 @@ import shutil import stat import subprocess import sys +import time import urllib.request from inspect import cleandoc @@ -59,12 +60,16 @@ def fail(*args, **kwargs): sys.exit(1) +def get_jail_path(jail_name): + return os.path.join(JAILS_DIR_PATH, jail_name) + + def start_jail(jail_name): """ Start jail with given name. """ - jail_path = os.path.join(JAILS_DIR_PATH, jail_name) + jail_path = get_jail_path(jail_name) jail_config_path = os.path.join(jail_path, JAIL_CONFIG_NAME) config = configparser.ConfigParser() @@ -188,7 +193,7 @@ def start_jail(jail_name): print(dedent(f""" Starting jail with the following command: - + {shlex.join(cmd)} Starting jail with name: {jail_name} @@ -284,6 +289,38 @@ def get_mount_point(path): return path +def check_jail_name_valid(jail_name, warn=True): + """ + Return True if jail name matches the required format. + """ + if re.match(r"^[.a-zA-Z0-9-]{1,64}$", jail_name) and not jail_name.startswith(".") and ".." not in jail_name: + return True + + if warn: + eprint(dedent(f""" + {YELLOW}{BOLD}WARNING: INVALID NAME{NORMAL} + + A valid name consists of: + - allowed characters (alphanumeric, dash, dot) + - no leading or trailing dots + - no sequences of multiple dots + - max 64 characters""")) + return False + + +def check_jail_name_available(jail_name, warn=True): + """ + Return True if jail name is not yet taken. + """ + if not os.path.exists(get_jail_path(jail_name)): + return True + + if warn: + print() + eprint("A jail with this name already exists.") + return False + + def create_jail(jail_name): """ Create jail with given name. @@ -365,29 +402,14 @@ def create_jail(jail_name): release = input("Release: ") - jail_path = None - - while jail_path == None: + while True: print() jail_name = input_with_default("Enter jail name: ", jail_name).strip() - if not re.match(r"^[.a-zA-Z0-9-]{1,64}$", jail_name) or jail_name.startswith(".") or ".." in jail_name: - eprint(dedent(f""" + if check_jail_name_valid(jail_name): + if check_jail_name_available(jail_name): + break - {YELLOW}{BOLD}WARNING: INVALID NAME{NORMAL} - - A valid name consists of: - - allowed characters (alphanumeric, dash, dot) - - no leading or trailing dots - - no sequences of multiple dots - - max 64 characters - - """)) - else: - jail_path = os.path.join(JAILS_DIR_PATH, jail_name) - if os.path.exists(jail_path): - print() - eprint("A jail with this name already exists.") - jail_path = None + jail_path = get_jail_path(jail_name) # Cleanup in except, but only once the jail_path is final # Otherwise we may cleanup the wrong directory @@ -412,7 +434,7 @@ def create_jail(jail_name): print(dedent(f""" {YELLOW}{BOLD}WARNING: CHECK SYNTAX{NORMAL} - + You may pass additional flags to systemd-nspawn. With incorrect flags the jail may not start. It is possible to correct/add/remove flags post-install. @@ -500,7 +522,7 @@ def create_jail(jail_name): os.path.join(jail_rootfs_path, 'sbin/init'))) != "systemd": print(dedent(f""" {YELLOW}{BOLD}WARNING: DISTRO NOT SUPPORTED{NORMAL} - + Chosen distro appears not to use systemd... You probably will not get a shell with: @@ -616,6 +638,44 @@ def create_jail(jail_name): start_jail(jail_name) +def delete_jail(jail_name): + """ + Delete jail with given name. + """ + + if check_jail_name_valid(jail_name): + if check_jail_name_available(jail_name, False): + eprint(f"A jail with name {jail_name} does not exist.") + else: + check = input(f'\nCAUTION: Type "{jail_name}" to confirm jail deletion!\n\n') or "" + if check == jail_name: + jail_path = get_jail_path(jail_name) + print(f"\nTrying to stop {jail_name} if it was running...") + subprocess.run(['machinectl', 'stop', jail_name]) + # Need to sleep since deleting immediately after stop causes problems... + time.sleep(1) + print(f"Cleaning up: {jail_path}") + shutil.rmtree(jail_path) + else: + eprint("Wrong name, nothing happened.") + +def list_jails(): + """ + List all available and running jails. + """ + + jails = next(os.walk('jails'))[1] + + print("Available jails:\n") + if not len(jails): + print("No jails.") + else: + for jail in jails: + print(f"{jail}") + + print("\nCurrently running:\n") + os.system(f"machinectl list") + def main(): if os.stat(__file__).st_uid != 0: fail("This script should be owned by the root user...") @@ -627,10 +687,16 @@ def main(): subparsers = parser.add_subparsers(title='commands', dest='subcommand') create_parser = subparsers.add_parser(name='create', epilog=DISCLAIMER) - create_parser.add_argument('name', nargs='?', help='name of the jail') + create_parser.add_argument( + 'name', nargs='?', help='name of the jail to create') start_parser = subparsers.add_parser(name='start', epilog=DISCLAIMER) - start_parser.add_argument('name', help='name of the jail') + start_parser.add_argument('name', help='name of the jail to start') + + start_parser = subparsers.add_parser(name='delete', epilog=DISCLAIMER) + start_parser.add_argument('name', help='name of the jail to delete') + + start_parser = subparsers.add_parser(name='list', epilog=DISCLAIMER) parser.usage = f"{parser.format_usage()[7:]}{create_parser.format_usage()}{start_parser.format_usage()}" @@ -653,6 +719,12 @@ def main(): elif args.subcommand == 'create': create_jail(args.name) + elif args.subcommand == 'delete': + delete_jail(args.name) + + elif args.subcommand == 'list': + list_jails() + elif args.subcommand: parser.print_usage()