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
|
||||
- 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: GPU passthrough (including [nvidia GPU](README.md#nvidia-gpu) with the drivers bind mounted from the host)
|
||||
- Starting the jail with your config applied
|
||||
|
@ -31,7 +32,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. 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).
|
||||
|
||||
|
|
|
@ -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.
|
68
jlmkr.py
68
jlmkr.py
|
@ -4,7 +4,7 @@
|
|||
with full access to all files via bind mounts, \
|
||||
thanks to systemd-nspawn!"""
|
||||
|
||||
__version__ = "1.1.3"
|
||||
__version__ = "1.1.4"
|
||||
|
||||
__disclaimer__ = """USE THIS SCRIPT AT YOUR OWN RISK!
|
||||
IT COMES WITHOUT WARRANTY AND IS NOT SUPPORTED BY IXSYSTEMS."""
|
||||
|
@ -752,7 +752,11 @@ def cleanup(jail_path):
|
|||
"""
|
||||
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
|
||||
# Should be fixed in Python 3.13 https://stackoverflow.com/a/70549000
|
||||
def _onerror(func, path, exc_info):
|
||||
|
@ -891,6 +895,49 @@ def get_mount_point(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):
|
||||
"""
|
||||
Return True if jail name matches the required format.
|
||||
|
@ -1161,7 +1208,7 @@ def create_jail(**kwargs):
|
|||
{COMMAND_NAME} needs to create files.
|
||||
Currently it can not decide if it is safe to create files in:
|
||||
{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
|
||||
|
@ -1246,9 +1293,18 @@ def create_jail(**kwargs):
|
|||
# Cleanup in except, but only once the jail_path is final
|
||||
# Otherwise we may cleanup the wrong directory
|
||||
try:
|
||||
# Create the dir where to store the jails
|
||||
os.makedirs(JAILS_DIR_PATH, exist_ok=True)
|
||||
stat_chmod(JAILS_DIR_PATH, 0o700)
|
||||
# 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)
|
||||
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_rootfs_path = get_jail_rootfs_path(jail_name)
|
||||
|
|
Loading…
Reference in New Issue