Create jlmkr shell aliases

This commit is contained in:
Jip-Hop 2024-02-10 13:04:18 +01:00
parent f9730d3a32
commit d7b30011b0
2 changed files with 94 additions and 21 deletions

View File

@ -10,7 +10,6 @@ Persistent Linux 'jails' on TrueNAS SCALE to install software (docker-compose, p
TrueNAS SCALE can create persistent Linux 'jails' with systemd-nspawn. This script helps with the following:
- Installing the systemd-container package (which includes systemd-nspawn)
- Setting up the jail so it won't be lost when you update SCALE
- Choosing a distro (Debian 12 strongly recommended, but Ubuntu, Arch Linux or Rocky Linux seem good choices too)
- Optional: configuring the jail so you can run Docker inside it
@ -23,7 +22,7 @@ Despite what the word 'jail' implies, jailmaker's intended use case is to create
## Installation
Create a new dataset called `jailmaker` with the default settings (from TrueNAS web interface). Then login as the root user and download `jlmkr.py`.
[Installation steps with screenshots](https://www.truenas.com/docs/scale/scaletutorials/apps/sandboxes/) are provided on the TrueNAS website. Start by creating a new dataset called `jailmaker` with the default settings (from TrueNAS web interface). Then login as the root user and download `jlmkr.py`.
```shell
cd /mnt/mypool/jailmaker
@ -32,9 +31,9 @@ chmod +x jlmkr.py
./jlmkr.py install
```
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.
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 (if the boot pool is not readonly) so you can call `jlmkr` from anywhere.
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).
After an update of TrueNAS SCALE the symlink will be lost (but the shell aliases will remain). To restore the symlink, just run `./jlmkr.py install` again or use [the `./jlmkr.py startup` command](#startup-jails-on-boot).
## Usage
@ -51,15 +50,12 @@ After answering a few questions you should have your first jail up and running!
### Startup Jails on Boot
```shell
# Best to call startup directly (not through the jlmkr symlink)
# Call startup using the absolute path to jlmkr.py
# The jlmkr shell alias doesn't work in Init/Shutdown Scripts
/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.
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 creates the `jlmkr` symlink (if possible), as well as start all the jails with `startup=1` in the config file.
### Start Jail

View File

@ -4,6 +4,7 @@ import argparse
import configparser
import contextlib
import ctypes
import errno
import glob
import hashlib
import json
@ -1073,7 +1074,9 @@ def create_jail(jail_name, distro="debian", release="bookworm"):
"""
)
config += f"\n\nsystemd_nspawn_user_args={systemd_nspawn_user_args_multiline}\n\n"
config += (
f"\n\nsystemd_nspawn_user_args={systemd_nspawn_user_args_multiline}\n\n"
)
config += cleandoc(
"""
@ -1314,6 +1317,58 @@ def list_jails():
return 0
def replace_or_add_string(file_path, regex, replacement_string):
"""
Replace all occurrences of a regular expression in a file with a given string.
Add the string to the end of the file if regex doesn't match.
Args:
file_path (str): The path to the file.
regex (str): The regular expression to search for.
replacement_string (str): The string to replace the matches with.
"""
with open(file_path, "a+") as f:
f.seek(0)
updated = False
found = False
new_text = ""
replacement_line = f"{replacement_string}\n"
for line in f:
if not re.match(regex, line):
new_text += line
continue
found = True
new_text += replacement_line
if replacement_line != line:
updated = True
if not new_text.strip():
# In case of an empty file just write the replacement_string
new_text = replacement_line
updated = True
elif not found:
# Add a newline to the end of the file in case it's not there
if not new_text.endswith("\n"):
new_text += "\n"
# Then add our replacement_string to the end of the file
new_text += replacement_line
updated = True
# Only overwrite in case there are change to the file
if updated:
f.seek(0)
f.truncate()
f.write(new_text)
return True
return False
def install_jailmaker():
# Check if command exists in path
if shutil.which("systemd-nspawn"):
@ -1341,19 +1396,41 @@ def install_jailmaker():
for file, original_permission in original_permissions.items():
stat_chmod(file, original_permission)
target = f"/usr/local/sbin/{SYMLINK_NAME}"
symlink = f"/usr/local/sbin/{SYMLINK_NAME}"
# Check if command exists in path
if shutil.which(SYMLINK_NAME):
print(f"The {SYMLINK_NAME} command is available.")
elif not os.path.lexists(target):
print(f"Creating symlink {target} to {SCRIPT_PATH}.")
os.symlink(SCRIPT_PATH, target)
else:
if os.path.lexists(symlink) and not os.path.islink(symlink):
print(
f"File {target} already exists... Maybe it's a broken symlink from a previous install attempt?"
f"Unable to create symlink at {symlink}. File already exists but is not a symlink."
)
print(f"Skipped creating new symlink {target} to {SCRIPT_PATH}.")
# Check if the symlink is already pointing to the desired destination
elif os.path.realpath(symlink) != SCRIPT_PATH:
try:
Path(symlink).unlink(missing_ok=True)
os.symlink(SCRIPT_PATH, symlink)
print(f"Created symlink {symlink} to {SCRIPT_PATH}.")
except OSError as e:
if e.errno != errno.EROFS:
raise e
print(
f"Cannot create symlink because {symlink} is on a readonly filesystem."
)
alias = f"alias jlmkr={shlex.quote(SCRIPT_PATH)} # managed by jailmaker"
alias_regex = re.compile(r"^\s*alias jlmkr=.*# managed by jailmaker\s*")
shell_env = os.getenv("SHELL")
for shell_type in ["bash", "zsh"]:
file = "/root/.bashrc" if shell_type == "bash" else "/root/.zshrc"
if replace_or_add_string(file, alias_regex, alias):
print(f"Created {shell_type} alias {SYMLINK_NAME}.")
if shell_env.endswith(shell_type):
print(
f"Please source {file} manually for the {SYMLINK_NAME} alias to become effective immediately."
)
else:
print(f"The {shell_type} alias {SYMLINK_NAME} is already present.")
print("Done installing jailmaker.")