Remove wizard
Let's hope iX will introduce web GUI support for Sandboxes so users won't need an interactive CLI wizard.
This commit is contained in:
parent
b221bf04ac
commit
5a29d4ad26
269
jlmkr.py
269
jlmkr.py
|
@ -20,7 +20,6 @@ import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import readline
|
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
|
@ -817,17 +816,6 @@ def cleanup(jail_path):
|
||||||
shutil.rmtree(jail_path, onerror=_onerror)
|
shutil.rmtree(jail_path, onerror=_onerror)
|
||||||
|
|
||||||
|
|
||||||
def input_with_default(prompt, default):
|
|
||||||
"""
|
|
||||||
Ask user for input with a default value already provided.
|
|
||||||
"""
|
|
||||||
readline.set_startup_hook(lambda: readline.insert_text(default))
|
|
||||||
try:
|
|
||||||
return input(prompt)
|
|
||||||
finally:
|
|
||||||
readline.set_startup_hook()
|
|
||||||
|
|
||||||
|
|
||||||
def validate_sha256(file_path, digest):
|
def validate_sha256(file_path, digest):
|
||||||
"""
|
"""
|
||||||
Validates if a file matches a sha256 digest.
|
Validates if a file matches a sha256 digest.
|
||||||
|
@ -903,6 +891,8 @@ def run_lxc_download_script(
|
||||||
r"^(alpine|amazonlinux|busybox|devuan|funtoo|openwrt|plamo|voidlinux)\s",
|
r"^(alpine|amazonlinux|busybox|devuan|funtoo|openwrt|plamo|voidlinux)\s",
|
||||||
line,
|
line,
|
||||||
):
|
):
|
||||||
|
# TODO: check if output matches expected output, if it does then return 0
|
||||||
|
# Else treat this as an error and return 1
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
rc = p1.wait()
|
rc = p1.wait()
|
||||||
|
@ -923,21 +913,6 @@ def stat_chmod(file_path, mode):
|
||||||
os.chmod(file_path, mode)
|
os.chmod(file_path, mode)
|
||||||
|
|
||||||
|
|
||||||
def agree(question, default=None):
|
|
||||||
"""
|
|
||||||
Ask user a yes/no question.
|
|
||||||
"""
|
|
||||||
hint = "[Y/n]" if default == "y" else ("[y/N]" if default == "n" else "[y/n]")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
user_input = input(f"{question} {hint} ") or default
|
|
||||||
|
|
||||||
if user_input.lower() in ["y", "n"]:
|
|
||||||
return user_input.lower() == "y"
|
|
||||||
|
|
||||||
eprint("Invalid input. Please type 'y' for yes or 'n' for no and press enter.")
|
|
||||||
|
|
||||||
|
|
||||||
def get_mount_point(path):
|
def get_mount_point(path):
|
||||||
"""
|
"""
|
||||||
Return the mount point on which the given path resides.
|
Return the mount point on which the given path resides.
|
||||||
|
@ -1043,20 +1018,6 @@ def check_jail_name_available(jail_name, warn=True):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def ask_jail_name(jail_name=""):
|
|
||||||
while True:
|
|
||||||
print()
|
|
||||||
jail_name = input_with_default("Enter jail name: ", jail_name).strip()
|
|
||||||
if check_jail_name_valid(jail_name):
|
|
||||||
if check_jail_name_available(jail_name):
|
|
||||||
return jail_name
|
|
||||||
|
|
||||||
|
|
||||||
def agree_with_default(config, key, question):
|
|
||||||
default_answer = "y" if config.my_getboolean(key) else "n"
|
|
||||||
config.my_set(key, agree(question, default_answer))
|
|
||||||
|
|
||||||
|
|
||||||
def get_text_editor():
|
def get_text_editor():
|
||||||
def get_from_environ(key):
|
def get_from_environ(key):
|
||||||
if editor := os.environ.get(key):
|
if editor := os.environ.get(key):
|
||||||
|
@ -1071,176 +1032,6 @@ def get_text_editor():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def interactive_config():
|
|
||||||
config = KeyValueParser()
|
|
||||||
config.read_string(DEFAULT_CONFIG)
|
|
||||||
|
|
||||||
recommended_distro = config.my_get("distro")
|
|
||||||
recommended_release = config.my_get("release")
|
|
||||||
|
|
||||||
#################
|
|
||||||
# Config handling
|
|
||||||
#################
|
|
||||||
jail_name = ""
|
|
||||||
|
|
||||||
print()
|
|
||||||
if agree("Do you wish to create a jail from a config template?", "n"):
|
|
||||||
print(
|
|
||||||
dedent(
|
|
||||||
"""
|
|
||||||
A text editor will open so you can provide the config template.
|
|
||||||
|
|
||||||
1. Please copy your config
|
|
||||||
2. Paste it into the text editor
|
|
||||||
3. Save and close the text editor
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
input("Press Enter to open the text editor.")
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(mode="w+t") as f:
|
|
||||||
subprocess.call([get_text_editor(), f.name])
|
|
||||||
f.seek(0)
|
|
||||||
# Start over with a new KeyValueParser to parse user config
|
|
||||||
config = KeyValueParser()
|
|
||||||
config.read_file(f)
|
|
||||||
|
|
||||||
# Ask for jail name
|
|
||||||
jail_name = ask_jail_name(jail_name)
|
|
||||||
else:
|
|
||||||
print()
|
|
||||||
if not agree(
|
|
||||||
f"Install the recommended image ({recommended_distro} {recommended_release})?",
|
|
||||||
"y",
|
|
||||||
):
|
|
||||||
print(
|
|
||||||
dedent(
|
|
||||||
f"""
|
|
||||||
{YELLOW}{BOLD}WARNING: ADVANCED USAGE{NORMAL}
|
|
||||||
|
|
||||||
You may now choose from a list which distro to install.
|
|
||||||
But not all of them may work with {COMMAND_NAME} since these images are made for LXC.
|
|
||||||
Distros based on systemd probably work (e.g. Ubuntu, Arch Linux and Rocky Linux).
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
input("Press Enter to continue...")
|
|
||||||
print()
|
|
||||||
|
|
||||||
if run_lxc_download_script() != 0:
|
|
||||||
fail("Failed to list images. Aborting...")
|
|
||||||
|
|
||||||
print(
|
|
||||||
dedent(
|
|
||||||
"""
|
|
||||||
Choose from the DIST column.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
config.my_set("distro", input("Distro: "))
|
|
||||||
|
|
||||||
print(
|
|
||||||
dedent(
|
|
||||||
"""
|
|
||||||
Choose from the RELEASE column (or ARCH if RELEASE is empty).
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
config.my_set("release", input("Release: "))
|
|
||||||
|
|
||||||
jail_name = ask_jail_name(jail_name)
|
|
||||||
|
|
||||||
print()
|
|
||||||
agree_with_default(
|
|
||||||
config, "gpu_passthrough_intel", "Passthrough the intel GPU (if present)?"
|
|
||||||
)
|
|
||||||
print()
|
|
||||||
agree_with_default(
|
|
||||||
config, "gpu_passthrough_nvidia", "Passthrough the nvidia GPU (if present)?"
|
|
||||||
)
|
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if agree("Show the man page for systemd-nspawn?", "n"):
|
|
||||||
subprocess.run(["man", "systemd-nspawn"])
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
base_os_version = platform.freedesktop_os_release().get(
|
|
||||||
"VERSION_CODENAME", recommended_release
|
|
||||||
)
|
|
||||||
except AttributeError:
|
|
||||||
base_os_version = recommended_release
|
|
||||||
print(
|
|
||||||
dedent(
|
|
||||||
f"""
|
|
||||||
You may read the systemd-nspawn manual online:
|
|
||||||
https://manpages.debian.org/{base_os_version}/systemd-container/systemd-nspawn.1.en.html"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Backslashes and colons need to be escaped in bind mount options:
|
|
||||||
# e.g. to bind mount a file called:
|
|
||||||
# weird chars :?\"
|
|
||||||
# the corresponding command would be:
|
|
||||||
# --bind-ro='/mnt/data/weird chars \:?\\"'
|
|
||||||
|
|
||||||
print(
|
|
||||||
dedent(
|
|
||||||
"""
|
|
||||||
Would you like to add additional systemd-nspawn flags?
|
|
||||||
For example to mount directories inside the jail you may:
|
|
||||||
Mount the TrueNAS location /mnt/pool/dataset to the /home directory of the jail with:
|
|
||||||
--bind='/mnt/pool/dataset:/home'
|
|
||||||
Or the same, but readonly, with:
|
|
||||||
--bind-ro='/mnt/pool/dataset:/home'
|
|
||||||
Or create macvlan interface with:
|
|
||||||
--network-macvlan=eno1 --resolv-conf=bind-host
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
config.my_set(
|
|
||||||
"systemd_nspawn_user_args",
|
|
||||||
"\n ".join(shlex.split(input("Additional flags: ") or "")),
|
|
||||||
)
|
|
||||||
|
|
||||||
print(
|
|
||||||
dedent(
|
|
||||||
f"""
|
|
||||||
The `{COMMAND_NAME} startup` command can automatically start a selection of jails.
|
|
||||||
This comes in handy when you want to automatically start multiple jails after booting TrueNAS SCALE (e.g. from a Post Init Script).
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
config.my_set(
|
|
||||||
"startup",
|
|
||||||
agree(
|
|
||||||
f"Do you want to start this jail when running: {COMMAND_NAME} startup?",
|
|
||||||
"n",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
print()
|
|
||||||
start_now = agree("Do you want to start this jail now (when create is done)?", "y")
|
|
||||||
|
|
||||||
print()
|
|
||||||
|
|
||||||
return jail_name, config, start_now
|
|
||||||
|
|
||||||
|
|
||||||
def create_jail(**kwargs):
|
def create_jail(**kwargs):
|
||||||
print(DISCLAIMER)
|
print(DISCLAIMER)
|
||||||
|
|
||||||
|
@ -1270,11 +1061,9 @@ def create_jail(**kwargs):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
jail_name = kwargs.pop("jail_name", None)
|
jail_name = kwargs.pop("jail_name")
|
||||||
start_now = False
|
start_now = False
|
||||||
|
|
||||||
# Non-interactive create
|
|
||||||
if jail_name:
|
|
||||||
if not check_jail_name_valid(jail_name):
|
if not check_jail_name_valid(jail_name):
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
@ -1289,14 +1078,10 @@ def create_jail(**kwargs):
|
||||||
if jail_config_path:
|
if jail_config_path:
|
||||||
# TODO: fallback to default values for e.g. distro and release if they are not in the config file
|
# TODO: fallback to default values for e.g. distro and release if they are not in the config file
|
||||||
if jail_config_path == "-":
|
if jail_config_path == "-":
|
||||||
print(
|
print(f"Creating jail {jail_name} from config template passed via stdin.")
|
||||||
f"Creating jail {jail_name} from config template passed via stdin."
|
|
||||||
)
|
|
||||||
config.read_string(sys.stdin.read())
|
config.read_string(sys.stdin.read())
|
||||||
else:
|
else:
|
||||||
print(
|
print(f"Creating jail {jail_name} from config template {jail_config_path}.")
|
||||||
f"Creating jail {jail_name} from config template {jail_config_path}."
|
|
||||||
)
|
|
||||||
if jail_config_path not in config.read(jail_config_path):
|
if jail_config_path not in config.read(jail_config_path):
|
||||||
eprint(f"Failed to read config template {jail_config_path}.")
|
eprint(f"Failed to read config template {jail_config_path}.")
|
||||||
return 1
|
return 1
|
||||||
|
@ -1304,8 +1089,6 @@ def create_jail(**kwargs):
|
||||||
print(f"Creating jail {jail_name} with default config.")
|
print(f"Creating jail {jail_name} with default config.")
|
||||||
config.read_string(DEFAULT_CONFIG)
|
config.read_string(DEFAULT_CONFIG)
|
||||||
|
|
||||||
user_overridden = False
|
|
||||||
|
|
||||||
for option in [
|
for option in [
|
||||||
"distro",
|
"distro",
|
||||||
"gpu_passthrough_intel",
|
"gpu_passthrough_intel",
|
||||||
|
@ -1326,20 +1109,6 @@ def create_jail(**kwargs):
|
||||||
# Should there be an option to append them instead?
|
# Should there be an option to append them instead?
|
||||||
print(f"Overriding {option} config value with {value}.")
|
print(f"Overriding {option} config value with {value}.")
|
||||||
config.my_set(option, value)
|
config.my_set(option, value)
|
||||||
user_overridden = True
|
|
||||||
|
|
||||||
if not user_overridden:
|
|
||||||
print(
|
|
||||||
dedent(
|
|
||||||
f"""
|
|
||||||
Hint: run `{COMMAND_NAME} create` without any arguments for interactive config.
|
|
||||||
Or use CLI args to override the default options.
|
|
||||||
For more info, run: `{COMMAND_NAME} create --help`
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
jail_name, config, start_now = interactive_config()
|
|
||||||
|
|
||||||
jail_path = get_jail_path(jail_name)
|
jail_path = get_jail_path(jail_name)
|
||||||
|
|
||||||
|
@ -1801,6 +1570,7 @@ def add_parser(subparser, **kwargs):
|
||||||
add_help = True
|
add_help = True
|
||||||
|
|
||||||
kwargs["epilog"] = DISCLAIMER
|
kwargs["epilog"] = DISCLAIMER
|
||||||
|
kwargs["formatter_class"] = argparse.RawDescriptionHelpFormatter
|
||||||
kwargs["exit_on_error"] = False
|
kwargs["exit_on_error"] = False
|
||||||
func = kwargs.pop("func")
|
func = kwargs.pop("func")
|
||||||
parser = subparser.add_parser(**kwargs)
|
parser = subparser.add_parser(**kwargs)
|
||||||
|
@ -1825,7 +1595,10 @@ def main():
|
||||||
)
|
)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description=__doc__, epilog=DISCLAIMER, allow_abbrev=False
|
description=__doc__,
|
||||||
|
allow_abbrev=False,
|
||||||
|
epilog=f"For more info on some command, run: {COMMAND_NAME} some_command --help.\n{DISCLAIMER}",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument("--version", action="version", version=__version__)
|
parser.add_argument("--version", action="version", version=__version__)
|
||||||
|
@ -1907,7 +1680,17 @@ def main():
|
||||||
]:
|
]:
|
||||||
commands[d["name"]] = add_parser(subparsers, **d)
|
commands[d["name"]] = add_parser(subparsers, **d)
|
||||||
|
|
||||||
for cmd in ["edit", "exec", "log", "remove", "restart", "start", "status", "stop"]:
|
for cmd in [
|
||||||
|
"create",
|
||||||
|
"edit",
|
||||||
|
"exec",
|
||||||
|
"log",
|
||||||
|
"remove",
|
||||||
|
"restart",
|
||||||
|
"start",
|
||||||
|
"status",
|
||||||
|
"stop",
|
||||||
|
]:
|
||||||
commands[cmd].add_argument("jail_name", help="name of the jail")
|
commands[cmd].add_argument("jail_name", help="name of the jail")
|
||||||
|
|
||||||
commands["exec"].add_argument(
|
commands["exec"].add_argument(
|
||||||
|
@ -1934,11 +1717,6 @@ def main():
|
||||||
help="args to pass to systemctl",
|
help="args to pass to systemctl",
|
||||||
)
|
)
|
||||||
|
|
||||||
commands["create"].add_argument(
|
|
||||||
"jail_name", #
|
|
||||||
nargs="?",
|
|
||||||
help="name of the jail",
|
|
||||||
)
|
|
||||||
commands["create"].add_argument("--distro")
|
commands["create"].add_argument("--distro")
|
||||||
commands["create"].add_argument("--release")
|
commands["create"].add_argument("--release")
|
||||||
commands["create"].add_argument(
|
commands["create"].add_argument(
|
||||||
|
@ -2037,11 +1815,6 @@ def main():
|
||||||
if not command:
|
if not command:
|
||||||
# Parse args and show error for unknown args
|
# Parse args and show error for unknown args
|
||||||
parser.parse_args(args_to_parse)
|
parser.parse_args(args_to_parse)
|
||||||
|
|
||||||
if agree("Create a new jail?", "y"):
|
|
||||||
print()
|
|
||||||
sys.exit(create_jail())
|
|
||||||
else:
|
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue