Add startup command
This commit is contained in:
parent
dd9d610178
commit
645c758e50
32
README.md
32
README.md
|
@ -26,21 +26,12 @@ Create a new dataset called `jailmaker` with the default settings (from TrueNAS
|
||||||
cd /mnt/mypool/jailmaker
|
cd /mnt/mypool/jailmaker
|
||||||
curl --location --remote-name https://raw.githubusercontent.com/Jip-Hop/jailmaker/main/jlmkr.py
|
curl --location --remote-name https://raw.githubusercontent.com/Jip-Hop/jailmaker/main/jlmkr.py
|
||||||
chmod +x jlmkr.py
|
chmod +x jlmkr.py
|
||||||
```
|
|
||||||
|
|
||||||
The `jlmkr.py` script (and the jails + config it creates) are now stored on the `jailmaker` dataset and will survive updates of TrueNAS SCALE.
|
|
||||||
|
|
||||||
### Install Jailmaker Dependencies
|
|
||||||
|
|
||||||
Unfortunately since version 22.12.3 TrueNAS SCALE no longer includes systemd-nspawn. In order to use jailmaker, we need to first install systemd-nspawn using the command below.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
./jlmkr.py install
|
./jlmkr.py install
|
||||||
```
|
```
|
||||||
|
|
||||||
We need to do this again after each update of TrueNAS SCALE. So it is recommended to schedule this command as Post Init Script (see [Autostart Jail on Boot](#autostart-jail-on-boot)).
|
The `jlmkr.py` script (and the jails + config it creates) are now stored on the `jailmaker` dataset and will survive updates of TrueNAS SCALE. Additionally a symlink has been created so you can call `jlmkr` from anywhere.
|
||||||
|
|
||||||
Additionally the install command will create a symlink from `/usr/local/sbin/jlmkr` to `jlmkr.py`. Thanks this this you can now run the `jlmkr` command from anywhere (instead of having to run `./jlmkr.py` from inside the directory where you've placed it).
|
After an update of TrueNAS SCALE the symlink will be lost and `systemd-nspawn` (the core package which makes `jailmaker` work) may be gone too. Not to worry, just run `./jlmkr.py install` again or use [the `./jlmkr.py startup` command](#startup-jails-on-boot).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
@ -54,9 +45,18 @@ jlmkr create myjail
|
||||||
|
|
||||||
After answering a few questions you should have your first jail up and running!
|
After answering a few questions you should have your first jail up and running!
|
||||||
|
|
||||||
#### Autostart Jail on Boot
|
### Startup Jails on Boot
|
||||||
|
|
||||||
In order to start a jail automatically after TrueNAS boots, run `jlmkr start myjail` as Post Init Script with Type `Command` from the TrueNAS web interface. If you want to automatically install systemd-nspawn if it's not already installed (recommended to keep working after a TrueNAS SCALE update) then you may use a command such as this instead: `/mnt/mypool/jailmaker/jlmkr.py install && jlmkr start myjail`.
|
```shell
|
||||||
|
# Best to call startup directly (not through the jlmkr symlink)
|
||||||
|
/mnt/mypool/jailmaker/jlmkr.py startup
|
||||||
|
|
||||||
|
# Can be called from the symlink too...
|
||||||
|
# But this may not be available after a TrueNAS SCALE update
|
||||||
|
jlmkr startup
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to start jails automatically after TrueNAS boots, run `/mnt/mypool/jailmaker/jlmkr.py startup` as Post Init Script with Type `Command` from the TrueNAS web interface. This will automatically fix the installation of `systemd-nspawn` and setup the `jlmkr` symlink, as well as start all the jails with `startup=1` in the config file. Running the `startup` command Post Init is recommended to keep `jailmaker` working after a TrueNAS SCALE update.
|
||||||
|
|
||||||
### Start Jail
|
### Start Jail
|
||||||
|
|
||||||
|
@ -134,11 +134,11 @@ See [Advanced Networking](./NETWORKING.md) for more.
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
Jailmaker won't install Docker for you, but it can setup the jail with the capabilities required to run docker. You can manually install Docker inside the jail using the [official installation guide](https://docs.docker.com/engine/install/#server) or use [convenience script](https://get.docker.com).
|
The `jailmaker` script won't install Docker for you, but it can setup the jail with the capabilities required to run docker. You can manually install Docker inside the jail using the [official installation guide](https://docs.docker.com/engine/install/#server) or use [convenience script](https://get.docker.com).
|
||||||
|
|
||||||
## Nvidia GPU
|
## Nvidia GPU
|
||||||
|
|
||||||
To make passthrough of the nvidia GPU work, you need to schedule a Pre Init command. The reason is that TrueNAS SCALE by default doesn't load the nvidia kernel modules (and jailmaker doesn't do that either). [This screenshot](https://user-images.githubusercontent.com/1704047/222915803-d6dd51b0-c4dd-4189-84be-a04d38cca0b3.png) shows what the configuration should look like.
|
To make passthrough of the nvidia GPU work, you need to schedule a Pre Init command. The reason is that TrueNAS SCALE by default doesn't load the nvidia kernel modules (and `jailmaker` doesn't do that either). [This screenshot](https://user-images.githubusercontent.com/1704047/222915803-d6dd51b0-c4dd-4189-84be-a04d38cca0b3.png) shows what the configuration should look like.
|
||||||
|
|
||||||
```
|
```
|
||||||
[ ! -f /dev/nvidia-uvm ] && modprobe nvidia-current-uvm && /usr/bin/nvidia-modprobe -c0 -u
|
[ ! -f /dev/nvidia-uvm ] && modprobe nvidia-current-uvm && /usr/bin/nvidia-modprobe -c0 -u
|
||||||
|
@ -146,7 +146,7 @@ To make passthrough of the nvidia GPU work, you need to schedule a Pre Init comm
|
||||||
|
|
||||||
## Comparison
|
## Comparison
|
||||||
|
|
||||||
TODO: write comparison between systemd-nspawn (without jailmaker), LXC, VMs, Docker (on the host).
|
TODO: write comparison between systemd-nspawn (without `jailmaker`), LXC, VMs, Docker (on the host).
|
||||||
|
|
||||||
### Incompatible Distros
|
### Incompatible Distros
|
||||||
|
|
||||||
|
|
130
jlmkr.py
130
jlmkr.py
|
@ -210,28 +210,48 @@ def stop_jail(jail_name):
|
||||||
subprocess.run(["machinectl", "poweroff", jail_name])
|
subprocess.run(["machinectl", "poweroff", jail_name])
|
||||||
|
|
||||||
|
|
||||||
def start_jail(jail_name):
|
def parse_config(jail_config_path):
|
||||||
"""
|
|
||||||
Start jail with given name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if jail_is_running(jail_name):
|
|
||||||
fail(
|
|
||||||
f"Skipped starting jail {jail_name}. It appears to be running already...")
|
|
||||||
|
|
||||||
jail_path = get_jail_path(jail_name)
|
|
||||||
jail_config_path = get_jail_config_path(jail_name)
|
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
try:
|
try:
|
||||||
# Workaround to read config file without section headers
|
# Workaround to read config file without section headers
|
||||||
config.read_string('[DEFAULT]\n'+Path(jail_config_path).read_text())
|
config.read_string('[DEFAULT]\n'+Path(jail_config_path).read_text())
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
fail(f'Unable to find: {jail_config_path}.')
|
eprint(f'Unable to find config file: {jail_config_path}.')
|
||||||
|
return
|
||||||
|
|
||||||
config = dict(config['DEFAULT'])
|
config = dict(config['DEFAULT'])
|
||||||
|
|
||||||
print("Config loaded!")
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def start_jail(jail_name, check_startup_enabled=False):
|
||||||
|
"""
|
||||||
|
Start jail with given name.
|
||||||
|
"""
|
||||||
|
skip_start_message = f"Skipped starting jail {jail_name}. It appears to be running already..."
|
||||||
|
|
||||||
|
if not check_startup_enabled and jail_is_running(jail_name):
|
||||||
|
fail(skip_start_message)
|
||||||
|
|
||||||
|
jail_path = get_jail_path(jail_name)
|
||||||
|
jail_config_path = get_jail_config_path(jail_name)
|
||||||
|
|
||||||
|
config = parse_config(jail_config_path)
|
||||||
|
|
||||||
|
if not config:
|
||||||
|
fail(f'Aborting...')
|
||||||
|
|
||||||
|
# Only start if the startup setting is enabled in the config
|
||||||
|
if check_startup_enabled:
|
||||||
|
if config.get('startup') == '1':
|
||||||
|
# We should start this jail based on the startup config...
|
||||||
|
if jail_is_running(jail_name):
|
||||||
|
# ...but we can skip if it's already running
|
||||||
|
eprint(skip_start_message)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# Skip starting this jail since the startup config setting isnot enabled
|
||||||
|
return
|
||||||
|
|
||||||
systemd_run_additional_args = [
|
systemd_run_additional_args = [
|
||||||
f"--unit={SYMLINK_NAME}-{jail_name}",
|
f"--unit={SYMLINK_NAME}-{jail_name}",
|
||||||
|
@ -327,27 +347,20 @@ def start_jail(jail_name):
|
||||||
]
|
]
|
||||||
|
|
||||||
print(dedent(f"""
|
print(dedent(f"""
|
||||||
Starting jail with the following command:
|
Starting jail {jail_name} with the following command:
|
||||||
|
|
||||||
{shlex.join(cmd)}
|
{shlex.join(cmd)}
|
||||||
|
|
||||||
Starting jail with name: {jail_name}
|
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(cmd, check=True)
|
subprocess.run(cmd, check=True)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
fail(dedent(f"""
|
fail(dedent(f"""
|
||||||
Failed to start the jail...
|
Failed to start jail {jail_name}...
|
||||||
In case of a config error, you may fix it with:
|
In case of a config error, you may fix it with:
|
||||||
{SYMLINK_NAME} edit {jail_name}
|
{SYMLINK_NAME} edit {jail_name}
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
print(dedent(f"""
|
|
||||||
Get a shell:
|
|
||||||
{COMMAND_NAME} shell {jail_name}
|
|
||||||
"""))
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup(jail_path):
|
def cleanup(jail_path):
|
||||||
"""
|
"""
|
||||||
|
@ -651,6 +664,15 @@ def create_jail(jail_name, distro='debian', release='bullseye'):
|
||||||
systemd_nspawn_user_args = input("Additional flags: ") or ""
|
systemd_nspawn_user_args = input("Additional flags: ") or ""
|
||||||
# Disable tab auto completion
|
# Disable tab auto completion
|
||||||
readline.parse_and_bind('tab: self-insert')
|
readline.parse_and_bind('tab: self-insert')
|
||||||
|
|
||||||
|
print(dedent(f"""
|
||||||
|
The `{COMMAND_NAME} startup` command can automatically ensure {COMMAND_NAME} is installed properly and 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).
|
||||||
|
"""))
|
||||||
|
|
||||||
|
startup = int(agree(
|
||||||
|
f"Do you want to start this jail when running: {COMMAND_NAME} startup?", 'n'))
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
jail_config_path = get_jail_config_path(jail_name)
|
jail_config_path = get_jail_config_path(jail_name)
|
||||||
|
@ -786,6 +808,7 @@ def create_jail(jail_name, distro='debian', release='bullseye'):
|
||||||
]
|
]
|
||||||
|
|
||||||
config = cleandoc(f"""
|
config = cleandoc(f"""
|
||||||
|
startup={startup}
|
||||||
docker_compatible={docker_compatible}
|
docker_compatible={docker_compatible}
|
||||||
gpu_passthrough_intel={gpu_passthrough_intel}
|
gpu_passthrough_intel={gpu_passthrough_intel}
|
||||||
gpu_passthrough_nvidia={gpu_passthrough_nvidia}
|
gpu_passthrough_nvidia={gpu_passthrough_nvidia}
|
||||||
|
@ -805,7 +828,7 @@ def create_jail(jail_name, distro='debian', release='bullseye'):
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
print()
|
print()
|
||||||
if agree("Do you want to start the jail?", 'y'):
|
if agree(f"Do you want to start jail {jail_name} right now?", 'y'):
|
||||||
start_jail(jail_name)
|
start_jail(jail_name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -890,6 +913,15 @@ def run_command_and_parse_json(command):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_jail_names():
|
||||||
|
try:
|
||||||
|
jail_names = os.listdir(JAILS_DIR_PATH)
|
||||||
|
except FileNotFoundError:
|
||||||
|
jail_names = []
|
||||||
|
|
||||||
|
return jail_names
|
||||||
|
|
||||||
|
|
||||||
def list_jails():
|
def list_jails():
|
||||||
"""
|
"""
|
||||||
List all available and running jails.
|
List all available and running jails.
|
||||||
|
@ -898,16 +930,13 @@ def list_jails():
|
||||||
jails = {}
|
jails = {}
|
||||||
empty_value_indicator = '-'
|
empty_value_indicator = '-'
|
||||||
|
|
||||||
try:
|
jail_names = get_all_jail_names()
|
||||||
jail_dirs = os.listdir(JAILS_DIR_PATH)
|
|
||||||
except FileNotFoundError:
|
|
||||||
jail_dirs = []
|
|
||||||
|
|
||||||
if not jail_dirs:
|
if not jail_names:
|
||||||
print('No jails.')
|
print('No jails.')
|
||||||
return
|
return
|
||||||
|
|
||||||
for jail in jail_dirs:
|
for jail in jail_names:
|
||||||
jails[jail] = {"name": jail, "running": False}
|
jails[jail] = {"name": jail, "running": False}
|
||||||
|
|
||||||
# Get running jails from machinectl
|
# Get running jails from machinectl
|
||||||
|
@ -937,7 +966,19 @@ def list_jails():
|
||||||
|
|
||||||
# TODO: add additional properties from the jails config file
|
# TODO: add additional properties from the jails config file
|
||||||
|
|
||||||
print_table(["name", "running", "os", "version", "addresses"],
|
for jail_name in jails:
|
||||||
|
|
||||||
|
config = parse_config(get_jail_config_path(jail_name))
|
||||||
|
|
||||||
|
startup = False
|
||||||
|
if config:
|
||||||
|
startup = bool(int(config.get('startup', '0')))
|
||||||
|
# TODO: in case config is missing or parsing fails,
|
||||||
|
# should an error message be thrown here?
|
||||||
|
|
||||||
|
jails[jail_name]['startup'] = startup
|
||||||
|
|
||||||
|
print_table(["name", "running", "startup", "os", "version", "addresses"],
|
||||||
sorted(jails.values(), key=lambda x: x['name']), empty_value_indicator)
|
sorted(jails.values(), key=lambda x: x['name']), empty_value_indicator)
|
||||||
|
|
||||||
|
|
||||||
|
@ -983,6 +1024,12 @@ def install_jailmaker():
|
||||||
print("Done installing jailmaker.")
|
print("Done installing jailmaker.")
|
||||||
|
|
||||||
|
|
||||||
|
def startup_jails():
|
||||||
|
install_jailmaker()
|
||||||
|
for jail_name in get_all_jail_names():
|
||||||
|
start_jail(jail_name, True)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if os.stat(SCRIPT_PATH).st_uid != 0:
|
if os.stat(SCRIPT_PATH).st_uid != 0:
|
||||||
fail(
|
fail(
|
||||||
|
@ -1042,6 +1089,9 @@ def main():
|
||||||
subparsers.add_parser(name='images', epilog=DISCLAIMER,
|
subparsers.add_parser(name='images', epilog=DISCLAIMER,
|
||||||
help='list available images to create jails from')
|
help='list available images to create jails from')
|
||||||
|
|
||||||
|
subparsers.add_parser(name='startup', epilog=DISCLAIMER,
|
||||||
|
help=f'install {SYMLINK_NAME} and startup selected jails')
|
||||||
|
|
||||||
if os.getuid() != 0:
|
if os.getuid() != 0:
|
||||||
parser.print_usage()
|
parser.print_usage()
|
||||||
fail("Run this script as root...")
|
fail("Run this script as root...")
|
||||||
|
@ -1054,7 +1104,13 @@ def main():
|
||||||
|
|
||||||
args, additional_args = parser.parse_known_args()
|
args, additional_args = parser.parse_known_args()
|
||||||
|
|
||||||
if args.subcommand == 'start':
|
if args.subcommand == 'install':
|
||||||
|
install_jailmaker()
|
||||||
|
|
||||||
|
elif args.subcommand == 'create':
|
||||||
|
create_jail(args.name)
|
||||||
|
|
||||||
|
elif args.subcommand == 'start':
|
||||||
start_jail(args.name)
|
start_jail(args.name)
|
||||||
|
|
||||||
elif args.subcommand == 'shell':
|
elif args.subcommand == 'shell':
|
||||||
|
@ -1072,9 +1128,6 @@ def main():
|
||||||
elif args.subcommand == 'stop':
|
elif args.subcommand == 'stop':
|
||||||
stop_jail(args.name)
|
stop_jail(args.name)
|
||||||
|
|
||||||
elif args.subcommand == 'create':
|
|
||||||
create_jail(args.name)
|
|
||||||
|
|
||||||
elif args.subcommand == 'edit':
|
elif args.subcommand == 'edit':
|
||||||
edit_jail(args.name)
|
edit_jail(args.name)
|
||||||
|
|
||||||
|
@ -1084,14 +1137,11 @@ def main():
|
||||||
elif args.subcommand == 'list':
|
elif args.subcommand == 'list':
|
||||||
list_jails()
|
list_jails()
|
||||||
|
|
||||||
elif args.subcommand == 'install':
|
|
||||||
install_jailmaker()
|
|
||||||
|
|
||||||
elif args.subcommand == 'images':
|
elif args.subcommand == 'images':
|
||||||
run_lxc_download_script()
|
run_lxc_download_script()
|
||||||
|
|
||||||
elif args.subcommand:
|
elif args.subcommand == 'startup':
|
||||||
parser.print_usage()
|
startup_jails()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if agree("Create a new jail?", 'y'):
|
if agree("Create a new jail?", 'y'):
|
||||||
|
|
Loading…
Reference in New Issue