Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specify jailmaker directory irrespective of script location #238

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 64 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,65 +21,72 @@ TrueNAS SCALE can create persistent Linux 'jails' with systemd-nspawn. This app
- Optional: GPU passthrough (including nvidia GPU with the drivers bind mounted from the host)
- Starting the jail with your config applied

## Requirements

Beginning with 24.04 (Dragonfish), TrueNAS SCALE officially includes the systemd-nspawn containerization program in the base system. Technically there's nothing to install. You can run the `jlmkr` tool directly, or put it somewhere convenient in your search path.

Your user account must have administrative access (i.e. the ability to use `sudo`), and the `jlmkr` tool must be owned by the root user.

## Installation

Beginning with 24.04 (Dragonfish), TrueNAS SCALE officially includes the systemd-nspawn containerization program in the base system. Technically there's nothing to install. You only need the `jlmkr` app in the right place. [Instructions 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`.
TL/DR: [Instructions with screenshots](https://www.truenas.com/docs/scale/scaletutorials/apps/sandboxes/) are provided on the TrueNAS website.

TODO: update install instructions. For now one may clone or download the repo and run the below commands to create the `jlmkr` zipapp.
> A note on installing *any* command-line tool or script: TrueNAS SCALE is a sealed storage appliance. It does [not allow](https://www.truenas.com/docs/scale/scaletutorials/systemsettings/advanced/developermode/) installing systemwide packages, nor making any other changes to system directories. Not even to `/usr/local/bin`. *\[You probably know this; creating your own customizable user environment is one of the top reasons to create a jail!]*
>
> The platform [standard location](https://www.freedesktop.org/software/systemd/man/latest/file-hierarchy.html#Home%20Directory) for your own tools and scripts is inside your home directory at `~/.local/bin`. This directory is not included in your shell's search path by default, so by default your shell will not find things there. Also it may not yet exist, so first create it if necessary.
>
> mkdir -p ~/.local/bin
>
> Add this directory to your search path with the following command. Consider appending the same command to the end of your `~/.bashrc` and/or `~/.zshrc` files, so that the same change will load and apply to your *future* login sessions.
>
> export PATH=~/.local/bin:"$PATH"

```shell
rm -rf /tmp/jlmkr-build
mkdir -p /tmp/jlmkr-build
cd /tmp/jlmkr-build
curl -L https://github.com/Jip-Hop/jailmaker/archive/refs/heads/v3.0.0.tar.gz | tar xvz --strip-components=1
python3 -m zipapp src/jlmkr -p "/usr/bin/env python3" -o jlmkr
cp jlmkr /mnt/mypool/jailmaker/
```
*Until stable release builds are available, you may need to build `jlmkr` from source code using the developer instructions below. As an alternative: you could download and extract `jlmkr` from the latest experimental [build artifacts](https://github.com/Jip-Hop/jailmaker/actions). But then soon…*

Alternatively one may download and extract `jlmkr` from the build artifacts of the [GitHub Actions](https://github.com/Jip-Hop/jailmaker/actions).
Download the latest `jlmkr` tool from the project [release page](https://github.com/Jip-Hop/jailmaker/releases) *\[coming soon\]* and extract its `jlmkr` file from the archive. The following command will copy it into `~/.local/bin` with the necessary root ownership and permissions.

The `jlmkr` app (and the jails + config it creates) are now stored on the `jailmaker` dataset and will survive updates of TrueNAS SCALE. If the automatically created `jails` directory is also a ZFS dataset (which is true for new users), then the `jlmkr` app will automatically create a new dataset for every jail created. This allows you to snapshot individual jails. For legacy users (where the `jails` directory is not a dataset) each jail will be stored in a plain directory.
sudo install ./jlmkr ~/.local/bin/

### Alias
## First-time setup

TODO: explain how to run `jlmkr` without using the absolute path. This probably involves building and releasing the `zipapp` on GitHub, downloading it into a directory added to the `PATH`. But this also requires the `jailmaker` directory to be configurable (instead of using the directory the `jlmkr` app itself is in) by using the `JAILMAKER_DIR` env variable.
Create a single common ZFS dataset in which to store your jails. You can use the TrueNAS web interface, and accept its suggested defaults. We will refer to this as the **jailmaker directory** throughout documentation.

```bash
mkdir /root/bin
cd /root/bin
curl -o jlmkr --location --remote-name https://some_url
chmod +x jlmkr
cd ../
echo 'export PATH="/root/bin:$PATH"' | tee -a .bashrc .zshrc
echo 'export JAILMAKER_DIR=/mnt/tank/path/to/desired/jailmaker/dir' | tee -a .bashrc .zshrc
```
> A note on datasets and directories: The jailmaker directory is *not required* to be a ZFS dataset, but is recommended. Jails being created inside a jailmaker *dataset* will themselves be created as datasets. This gives them independent snapshot histories, and the opportunity for rollback.

The `jlmkr` tool needs to know where to find its jailmaker directory. For now, pass that setting through an environment variable named `JAILMAKER_DIR`. For example: if your jailmaker directory is at `/mnt/pool/jailmaker` in the filesystem, you should enter the following command.

export JAILMAKER_DIR=/mnt/pool/jailmaker

Consider also appending this command to your `~/.bashrc` and/or `~/.zshrc` files, so that the same change will load and apply to your *future* login sessions.

## Usage

If you have not yet done so, set the `JAILMAKER_DIR` environment variable as described above. The following commands will rely on that setting, to know where to find the *jailmaker directory*.

### Create Jail

Creating a jail with the default settings is as simple as:

```shell
./jlmkr create --start myjail
jlmkr create --start myjail
```

You may also specify a path to a config template, for a quick and consistent jail creation process.

```shell
./jlmkr create --start --config /path/to/config/template myjail
jlmkr create --start --config /path/to/config/template myjail
```

Or you can override the default config by using flags. See `./jlmkr create --help` for the available options. Anything passed after the jail name will be passed to `systemd-nspawn` when starting the jail. See the `systemd-nspawn` manual for available options, specifically [Mount Options](https://manpages.debian.org/bookworm/systemd-container/systemd-nspawn.1.en.html#Mount_Options) and [Networking Options](https://manpages.debian.org/bookworm/systemd-container/systemd-nspawn.1.en.html#Networking_Options) are frequently used.
Or you can override the default config by using flags. See `jlmkr create --help` for the available options. Anything passed after the jail name will be passed to `systemd-nspawn` when starting the jail. See the `systemd-nspawn` manual for available options, specifically [Mount Options](https://manpages.debian.org/bookworm/systemd-container/systemd-nspawn.1.en.html#Mount_Options) and [Networking Options](https://manpages.debian.org/bookworm/systemd-container/systemd-nspawn.1.en.html#Networking_Options) are frequently used.

```shell
./jlmkr create --start --distro=ubuntu --release=jammy myjail --bind-ro=/mnt
jlmkr create --start --distro=ubuntu --release=jammy myjail --bind-ro=/mnt
```

If you omit the jail name, the create process is interactive. You'll be presented with questions which guide you through the process.

```shell
./jlmkr create
jlmkr create
```

After answering some questions you should have created your first jail (and it should be running if you chose to start it after creating)!
Expand All @@ -96,79 +103,79 @@ In order to start jails automatically after TrueNAS boots, run `/mnt/mypool/jail
### Start Jail

```shell
./jlmkr start myjail
jlmkr start myjail
```

### List Jails

See list of jails (including running, startup state, GPU passthrough, distro, and IP).

```shell
./jlmkr list
jlmkr list
```

### Execute Command in Jail

You may want to execute a command inside a jail, for example manually from the TrueNAS shell, a shell script or a CRON job. The example below executes the `env` command inside the jail.

```shell
./jlmkr exec myjail env
jlmkr exec myjail env
```

This example executes bash inside the jail with a command as additional argument.

```shell
./jlmkr exec myjail bash -c 'echo test; echo $RANDOM;'
jlmkr exec myjail bash -c 'echo test; echo $RANDOM;'
```

### Edit Jail Config

```shell
./jlmkr edit myjail
jlmkr edit myjail
```

Once you've created a jail, it will exist in a directory inside the `jails` dir next to `jlmkr`. For example `/mnt/mypool/jailmaker/jails/myjail` if you've named your jail `myjail`. You may edit the jail configuration file using the `./jlmkr edit myjail` command. This opens the config file in your favorite editor, as determined by following [Debian's guidelines](https://www.debian.org/doc/debian-policy/ch-customized-programs.html#editors-and-pagers) on the matter. You'll have to stop the jail and start it again with `jlmkr` for these changes to take effect.
Once you've created a jail, it will exist in a directory inside the `jails` dir next to `jlmkr`. For example `/mnt/mypool/jailmaker/jails/myjail` if you've named your jail `myjail`. You may edit the jail configuration file using the `jlmkr edit myjail` command. This opens the config file in your favorite editor, as determined by following [Debian's guidelines](https://www.debian.org/doc/debian-policy/ch-customized-programs.html#editors-and-pagers) on the matter. You'll have to stop the jail and start it again with `jlmkr` for these changes to take effect.

### Remove Jail

Delete a jail and remove it's files (requires confirmation).

```shell
./jlmkr remove myjail
jlmkr remove myjail
```

### Stop Jail

```shell
./jlmkr stop myjail
jlmkr stop myjail
```

### Restart Jail

```shell
./jlmkr restart myjail
jlmkr restart myjail
```

### Jail Shell

Switch into the jail's shell.

```shell
./jlmkr shell myjail
jlmkr shell myjail
```

### Jail Status

```shell
./jlmkr status myjail
jlmkr status myjail
```

### Jail Logs

View a jail's logs.

```shell
./jlmkr log myjail
jlmkr log myjail
```

### Additional Commands
Expand Down Expand Up @@ -221,6 +228,23 @@ TODO: write comparison between systemd-nspawn (without `jailmaker`), LXC, VMs, D

The rootfs image `jlmkr` downloads comes from the [Linux Containers Image server](https://images.linuxcontainers.org). These images are made for LXC. We can use them with systemd-nspawn too, although not all of them work properly. For example, the `alpine` image doesn't work well. If you stick with common systemd based distros (Debian, Ubuntu, Arch Linux...) you should be fine.

## Development

This is really all it takes at the moment to get started.

```shell
git clone -b v3.0.0 https://github.com/Jip-Hop/jailmaker jailmaker-src
python3 -m zipapp -o jlmkr -p /usr/bin/python3 jailmaker-src/src/jlmkr
```

You can take the resulting `jlmkr` file and install it as described more thoroughly under Installation, above.

```shell
sudo install ./jlmkr ~/.local/bin/
```

We hope you'll join us on [the project](https://github.com/Jip-Hop/jailmaker) and look forward to working with you on any future pull requests.

## Filing Issues and Community Support

When in need of help or when you think you've found a bug in `jailmaker`, [please start with reading this](https://github.com/Jip-Hop/jailmaker/discussions/135).
Expand Down
12 changes: 6 additions & 6 deletions src/jlmkr/actions/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from textwrap import dedent

from data import DISCLAIMER
from paths import COMMAND_NAME, JAILS_DIR_PATH, SCRIPT_DIR_PATH, SCRIPT_NAME
from paths import COMMAND_NAME, JAILS_DIR_PATH, JAILMAKER_DIR_PATH, SCRIPT_NAME
from utils.chroot import Chroot
from utils.config_parser import DEFAULT_CONFIG, KeyValueParser
from utils.console import BOLD, NORMAL, YELLOW, eprint
Expand All @@ -33,19 +33,19 @@
def create_jail(**kwargs):
print(DISCLAIMER)

if os.path.basename(SCRIPT_DIR_PATH) != "jailmaker":
if os.path.basename(JAILMAKER_DIR_PATH) != "jailmaker":
eprint(
dedent(
f"""
{COMMAND_NAME} needs to create files.
Currently it can not decide if it is safe to create files in:
{SCRIPT_DIR_PATH}
{JAILMAKER_DIR_PATH}
Please create a dedicated dataset called "jailmaker", store {SCRIPT_NAME} there and try again."""
)
)
return 1

if not PurePath(get_mount_point(SCRIPT_DIR_PATH)).is_relative_to("/mnt"):
if not PurePath(get_mount_point(JAILMAKER_DIR_PATH)).is_relative_to("/mnt"):
print(
dedent(
f"""
Expand All @@ -54,7 +54,7 @@ def create_jail(**kwargs):
{SCRIPT_NAME} should be on a dataset mounted under /mnt (it currently is not).
Storing it on the boot-pool means losing all jails when updating TrueNAS.
Jails will be stored under:
{SCRIPT_DIR_PATH}
{JAILMAKER_DIR_PATH}
"""
)
)
Expand Down Expand Up @@ -118,7 +118,7 @@ def create_jail(**kwargs):
try:
# Create the dir or dataset where to store the jails
if not os.path.exists(JAILS_DIR_PATH):
if get_zfs_dataset(SCRIPT_DIR_PATH):
if get_zfs_dataset(JAILMAKER_DIR_PATH):
# Creating "jails" dataset if "jailmaker" is a ZFS Dataset
create_zfs_dataset(JAILS_DIR_PATH)
else:
Expand Down
67 changes: 55 additions & 12 deletions src/jlmkr/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,63 @@
#
# SPDX-License-Identifier: LGPL-3.0-only

import os.path
import os

# When running as a zipapp, the script file is a parent
ZIPAPP_PATH = os.path.realpath(__file__)
while not os.path.exists(ZIPAPP_PATH):
ZIPAPP_PATH = os.path.dirname(ZIPAPP_PATH)
from configparser import ConfigParser
from pathlib import Path
from utils.console import fail

SCRIPT_PATH = os.path.realpath(ZIPAPP_PATH)
SCRIPT_NAME = os.path.basename(SCRIPT_PATH)
SCRIPT_DIR_PATH = os.path.dirname(SCRIPT_PATH)
COMMAND_NAME = os.path.basename(ZIPAPP_PATH)

JAILS_DIR_PATH = os.path.join(SCRIPT_DIR_PATH, "jails")
JAIL_CONFIG_NAME = "config"
JAIL_ROOTFS_NAME = "rootfs"
def _get_selected_jailmaker_directory() -> Path:
'''
Determine the user's affirmative choice of parent jailmaker directory
'''
# first choice: global --dir/-D argument
#TODO

# next: JAILMAKER_DIR environment variable
envname = 'JAILMAKER_DIR'
if envname in os.environ:
return Path(os.environ[envname])

# next: ~/.local/share/jailmaker.conf
secname = 'DEFAULT'
cfgname = 'jailmaker_dir'
username = ''
if os.getuid() == 0 and 'SUDO_USER' in os.environ:
username = os.environ['SUDO_USER']
cfgpath = Path(f'~{username}/.local/share/jailmaker.conf').expanduser()
cfg = ConfigParser()
cfg.read(cfgpath)
if cfgname in cfg[secname]:
return Path(cfg[secname][cfgname])

# reluctantly: current directory
# ... if it's named jailmaker
# ... seems to be structured correctly
#TODO: ... iff

fail("Please specify a jailmaker directory path (JAILMAKER_DIR)")


def get_tool_path_on_disk() -> Path:
'''
Determine the script's location on disk
'''
# When running as a zipapp, the script file is an ancestor
path = Path(__file__).resolve(strict=False)
while path and not path.is_file():
path = path.parent
return path


SCRIPT_PATH = get_tool_path_on_disk()
SCRIPT_NAME = SCRIPT_PATH.name
COMMAND_NAME = SCRIPT_NAME
SHORTNAME = "jlmkr"

JAILMAKER_DIR_PATH = _get_selected_jailmaker_directory()

JAILS_DIR_PATH = JAILMAKER_DIR_PATH.joinpath("jails")
JAIL_CONFIG_NAME = "config"
JAIL_ROOTFS_NAME = "rootfs"
6 changes: 3 additions & 3 deletions src/jlmkr/utils/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
import subprocess
from pathlib import PurePath

from paths import SCRIPT_DIR_PATH
from paths import JAILMAKER_DIR_PATH

from utils.console import eprint, fail


def _get_relative_path_in_jailmaker_dir(absolute_path):
return PurePath(absolute_path).relative_to(SCRIPT_DIR_PATH)
return PurePath(absolute_path).relative_to(JAILMAKER_DIR_PATH)


def get_zfs_dataset(path):
Expand All @@ -37,7 +37,7 @@ def get_zfs_base_path():
"""
Get ZFS dataset path for jailmaker directory.
"""
zfs_base_path = get_zfs_dataset(SCRIPT_DIR_PATH)
zfs_base_path = get_zfs_dataset(JAILMAKER_DIR_PATH)
if not zfs_base_path:
fail("Failed to get dataset path for jailmaker directory.")

Expand Down
Loading