Added Full ZFS Dataset Support (#118)
Added Full ZFS Dataset Support: - The script will now create a ZFS dataset for each jail if the 'jailmaker' directory is a ZFS dataset - The script will create the 'jails' directory as a dataset if the 'jailmaker' directory is a ZFS dataset - The script will now remove the ZFS dataset (including snapshots) when deleting the jail - Dual mode: For legacy use without datasets, it will continue to work as previously Added a guide to migrate from using directories to using ZFS datasets. Closes #80. --------- Co-authored-by: Jip-Hop <2871973+Jip-Hop@users.noreply.github.com>
This commit is contained in:
parent
f046dd3d32
commit
a7c4b9dbad
|
@ -12,6 +12,7 @@ TrueNAS SCALE can create persistent Linux 'jails' with systemd-nspawn. This scri
|
||||||
|
|
||||||
- Setting up the jail so it won't be lost when you update SCALE
|
- 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)
|
- Choosing a distro (Debian 12 strongly recommended, but Ubuntu, Arch Linux or Rocky Linux seem good choices too)
|
||||||
|
- Will create a ZFS Dataset for each jail if the `jailmaker` directory is a dataset (easy snapshotting)
|
||||||
- Optional: configuring the jail so you can run Docker inside it
|
- Optional: configuring the jail so you can run Docker inside it
|
||||||
- Optional: GPU passthrough (including [nvidia GPU](README.md#nvidia-gpu) with the drivers bind mounted from the host)
|
- Optional: GPU passthrough (including [nvidia GPU](README.md#nvidia-gpu) with the drivers bind mounted from the host)
|
||||||
- Starting the jail with your config applied
|
- Starting the jail with your config applied
|
||||||
|
@ -31,7 +32,9 @@ chmod +x jlmkr.py
|
||||||
./jlmkr.py install
|
./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. A symlink has been created so you can call `jlmkr` from anywhere (unless the boot pool is readonly, which is the default since SCALE 24.04). Additionally shell aliases have been setup, so you can still call `jlmkr` in an interactive shell (even if the symlink couldn't be created).
|
The `jlmkr.py` script (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.py` script 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.
|
||||||
|
|
||||||
|
A symlink has been created so you can call `jlmkr` from anywhere (unless the boot pool is readonly, which is the default since SCALE 24.04). Additionally shell aliases have been setup, so you can still call `jlmkr` in an interactive shell (even if the symlink couldn't be created).
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
# ZFS Datasets Migration
|
||||||
|
|
||||||
|
From version 1.1.4 ZFS Datasets support was added to jailmaker.
|
||||||
|
By default starting in v1.1.4, jailmaker will create a separate dataset for each jail if possible. This allows the user to configure snapshots, rollbacks, replications etc.
|
||||||
|
|
||||||
|
Jailmaker operates in dual-mode: it supports using both directories and datasets. If the 'jailmaker' directory is a dataset, it will use datasets, if it is a directory, it will use directories.
|
||||||
|
___
|
||||||
|
## Procedure to migrate from directories to ZFS Datasets
|
||||||
|
|
||||||
|
### Stop all jails
|
||||||
|
|
||||||
|
`jlmkr stop jail1`
|
||||||
|
|
||||||
|
`jlmkr stop jail2`
|
||||||
|
etc..
|
||||||
|
|
||||||
|
### Move/rename the 'jailmaker' directory
|
||||||
|
|
||||||
|
`mv jailmaker orig_jailmaker`
|
||||||
|
|
||||||
|
### Create the ZFS datasets for jailmaker
|
||||||
|
|
||||||
|
Create all the required datasets via GUI or CLI.
|
||||||
|
|
||||||
|
You need to create the following datasets:
|
||||||
|
|
||||||
|
`jailmaker`
|
||||||
|
|
||||||
|
`jailmaker/jails`
|
||||||
|
|
||||||
|
And one for each existing jail:
|
||||||
|
|
||||||
|
`jailmaker/jails/jail1`
|
||||||
|
|
||||||
|
`jailmaker/jails/jail2`
|
||||||
|
etc.
|
||||||
|
|
||||||
|
|
||||||
|
Via CLI:
|
||||||
|
```
|
||||||
|
zfs create mypool/jailmaker
|
||||||
|
zfs create mypool/jailmaker/jails
|
||||||
|
zfs create mypool/jailmaker/jails/jail1
|
||||||
|
zfs create mypool/jailmaker/jails/jail2
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Move the existing jail data into the newly created datasets
|
||||||
|
|
||||||
|
Now move all the jail data:
|
||||||
|
|
||||||
|
`rsync -av orig_jailmaker/ jailmaker/`
|
||||||
|
|
||||||
|
Warning! It's important that both directories have the `/` at the end to make sure contents are copied correctly. Otherwise you may end up with `jailmaker/jailmaker`
|
||||||
|
|
||||||
|
### Test everything works
|
||||||
|
|
||||||
|
If everything works, you should be able to use the `jlmkr` command directly. Try doing a `jlmkr list` to check if the jails are correctly recognized
|
||||||
|
|
||||||
|
You can also try creating a new jail and see that the dataset is created automatically.
|
64
jlmkr.py
64
jlmkr.py
|
@ -4,7 +4,7 @@
|
||||||
with full access to all files via bind mounts, \
|
with full access to all files via bind mounts, \
|
||||||
thanks to systemd-nspawn!"""
|
thanks to systemd-nspawn!"""
|
||||||
|
|
||||||
__version__ = "1.1.3"
|
__version__ = "1.1.4"
|
||||||
|
|
||||||
__disclaimer__ = """USE THIS SCRIPT AT YOUR OWN RISK!
|
__disclaimer__ = """USE THIS SCRIPT AT YOUR OWN RISK!
|
||||||
IT COMES WITHOUT WARRANTY AND IS NOT SUPPORTED BY IXSYSTEMS."""
|
IT COMES WITHOUT WARRANTY AND IS NOT SUPPORTED BY IXSYSTEMS."""
|
||||||
|
@ -752,7 +752,11 @@ def cleanup(jail_path):
|
||||||
"""
|
"""
|
||||||
Cleanup jail.
|
Cleanup jail.
|
||||||
"""
|
"""
|
||||||
if os.path.isdir(jail_path):
|
if get_zfs_dataset(jail_path):
|
||||||
|
eprint(f"Cleaning up: {jail_path}.")
|
||||||
|
remove_zfs_dataset(jail_path)
|
||||||
|
|
||||||
|
elif os.path.isdir(jail_path):
|
||||||
# Workaround for https://github.com/python/cpython/issues/73885
|
# Workaround for https://github.com/python/cpython/issues/73885
|
||||||
# Should be fixed in Python 3.13 https://stackoverflow.com/a/70549000
|
# Should be fixed in Python 3.13 https://stackoverflow.com/a/70549000
|
||||||
def _onerror(func, path, exc_info):
|
def _onerror(func, path, exc_info):
|
||||||
|
@ -891,6 +895,49 @@ def get_mount_point(path):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_zfs_dataset(path):
|
||||||
|
"""
|
||||||
|
Get ZFS dataset path.
|
||||||
|
"""
|
||||||
|
path = os.path.realpath(path)
|
||||||
|
with open("/proc/mounts", "r") as f:
|
||||||
|
for line in f:
|
||||||
|
fields = line.split()
|
||||||
|
if fields[1] == path and fields[2] == "zfs":
|
||||||
|
return fields[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_zfs_base_path():
|
||||||
|
"""
|
||||||
|
Get ZFS dataset path for jailmaker directory.
|
||||||
|
"""
|
||||||
|
zfs_base_path = get_zfs_dataset(SCRIPT_DIR_PATH)
|
||||||
|
if not zfs_base_path:
|
||||||
|
fail("Failed to get dataset path for jailmaker directory.")
|
||||||
|
|
||||||
|
return zfs_base_path
|
||||||
|
|
||||||
|
|
||||||
|
def create_zfs_dataset(relative_path):
|
||||||
|
"""
|
||||||
|
Create a ZFS Dataset.
|
||||||
|
Receives the dataset to be created relative to the jailmaker script (e.g. "jails" or "jails/newjail").
|
||||||
|
"""
|
||||||
|
dataset_to_create = os.path.join(get_zfs_base_path(), relative_path)
|
||||||
|
eprint(f"Creating ZFS Dataset {dataset_to_create}")
|
||||||
|
subprocess.run(["zfs", "create", dataset_to_create], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_zfs_dataset(relative_path):
|
||||||
|
"""
|
||||||
|
Remove a ZFS Dataset.
|
||||||
|
Receives the dataset to be created relative to the jailmaker script (e.g. "jails/oldjail").
|
||||||
|
"""
|
||||||
|
dataset_to_remove = os.path.join((get_zfs_base_path()), relative_path)
|
||||||
|
eprint(f"Removing ZFS Dataset {dataset_to_remove}")
|
||||||
|
subprocess.run(["zfs", "destroy", "-r", dataset_to_remove], check=True)
|
||||||
|
|
||||||
|
|
||||||
def check_jail_name_valid(jail_name, warn=True):
|
def check_jail_name_valid(jail_name, warn=True):
|
||||||
"""
|
"""
|
||||||
Return True if jail name matches the required format.
|
Return True if jail name matches the required format.
|
||||||
|
@ -1161,7 +1208,7 @@ def create_jail(**kwargs):
|
||||||
{COMMAND_NAME} needs to create files.
|
{COMMAND_NAME} needs to create files.
|
||||||
Currently it can not decide if it is safe to create files in:
|
Currently it can not decide if it is safe to create files in:
|
||||||
{SCRIPT_DIR_PATH}
|
{SCRIPT_DIR_PATH}
|
||||||
Please create a dedicated directory called 'jailmaker', store {SCRIPT_NAME} there and try again."""
|
Please create a dedicated dataset called "jailmaker", store {SCRIPT_NAME} there and try again."""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
|
@ -1246,10 +1293,19 @@ def create_jail(**kwargs):
|
||||||
# Cleanup in except, but only once the jail_path is final
|
# Cleanup in except, but only once the jail_path is final
|
||||||
# Otherwise we may cleanup the wrong directory
|
# Otherwise we may cleanup the wrong directory
|
||||||
try:
|
try:
|
||||||
# Create the dir where to store the jails
|
# 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):
|
||||||
|
# Creating "jails" dataset if "jailmaker" is a ZFS Dataset
|
||||||
|
create_zfs_dataset(JAILS_DIR_PATH)
|
||||||
|
else:
|
||||||
os.makedirs(JAILS_DIR_PATH, exist_ok=True)
|
os.makedirs(JAILS_DIR_PATH, exist_ok=True)
|
||||||
stat_chmod(JAILS_DIR_PATH, 0o700)
|
stat_chmod(JAILS_DIR_PATH, 0o700)
|
||||||
|
|
||||||
|
# Creating a dataset for the jail if the jails dir is a dataset
|
||||||
|
if get_zfs_dataset(JAILS_DIR_PATH):
|
||||||
|
create_zfs_dataset(jail_path)
|
||||||
|
|
||||||
jail_config_path = get_jail_config_path(jail_name)
|
jail_config_path = get_jail_config_path(jail_name)
|
||||||
jail_rootfs_path = get_jail_rootfs_path(jail_name)
|
jail_rootfs_path = get_jail_rootfs_path(jail_name)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue