diff --git a/docs/chapters/zfs-support.rst b/docs/chapters/zfs-support.rst index b00fcf62..e29690eb 100644 --- a/docs/chapters/zfs-support.rst +++ b/docs/chapters/zfs-support.rst @@ -58,3 +58,58 @@ dataset for bastille. Bastille will mount the datasets it creates at ``bastille_prefix`` which defaults to ``/usr/local/bastille`` If this is not desirable, you can change it at the top of the config file. + +Jailing a Dataset +----------------- + +It is possible to "jail" a dataset. This means mounting a datset into a jail, and being +able to fully manage it from within the jail. + +To add a dataset to a jail, we can run ``bastille zfs TARGET jail pool/dataset /path/inside/jail``. +This will mount ``pool/dataset`` into the jail at ``/path/inside/jail`` when the jail is started, and +unmount and unjail it when the jail is stopped. + +You can manually change the path where the dataset will be mounted by ``bastille edit TARGET zfs.conf`` and +adjusting the path after you have added it, bearing in mind the warning below. + +WARNING: Adding or removing datasets to the ``zfs.conf`` file can result in permission errors with your jail. It is +important that the jail is first stopped before attempting to manually configure this file. The format inside +the file is simple. + +.. code-block:: shell + + pool/dataset /path/in/jail + pool/other/dataset /other/path/in/jail + +To remove a dataset from being jailed, we can run ``bastille zfs TARGET unjail pool/dataset``. + +Template Approach +^^^^^^^^^^^^^^^^^ + +While it is possible to "jail" a dataset using a template, it is a bit more "hacky" than the above apporach. +Below is a template that you can use that will add the necessary bits to the ``jail.conf`` file to "jail" a +dataset. + +.. code-block:: shell + + ARG JAIL_NAME + ARG DATASET + ARG MOUNT + + CONFIG set allow.mount + CONFIG set allow.mount.devfs + CONFIG set allow.mount.zfs + CONFIG set enforce_statfs 1 + + CONFIG set "exec.created += '/sbin/zfs jail ${JAIL_NAME} ${DATASET}'" + CONFIG set "exec.start += '/sbin/zfs set mountpoint=${MOUNT} ${DATASET}'" + + RESTART + + CONFIG set "exec.prestop += 'jexec -l -U root ${JAIL_NAME} /sbin/zfs umount ${DATASET}'" + CONFIG set "exec.prestop += '/sbin/zfs unjail ${JAIL_NAME} ${DATASET}'" + + RESTART + +This template can be applied using ``bastille template TARGET project/template --arg DATASET=zpool/dataset --arg MOUNT=/path/inside/jail``. +We do not need the ``JAIL_NAME`` arg, as it will be auto-filled from the supplied ``TARGET`` name. diff --git a/usr/local/share/bastille/start.sh b/usr/local/share/bastille/start.sh index 81046c67..93805d99 100644 --- a/usr/local/share/bastille/start.sh +++ b/usr/local/share/bastille/start.sh @@ -186,6 +186,16 @@ for _jail in ${JAILS}; do # Start jail jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -c "${_jail}" + # Add ZFS jailed datasets + if [ -s "${bastille_jailsdir}/${_jail}/zfs.conf" ]; then + while read _dataset _mount; do + zfs set jailed=on "${_dataset}" + zfs jail ${_jail} "${_dataset}" + jexec -l -U root "${_jail}" zfs set mountpoint="${_mount}" "${_dataset}" + jexec -l -U root "${_jail}" zfs mount "${_dataset}" 2>/dev/null + done < "${bastille_jailsdir}/${_jail}/zfs.conf" + fi + # Add rctl limits if [ -s "${bastille_jailsdir}/${_jail}/rctl.conf" ]; then while read _limits; do @@ -215,4 +225,4 @@ for _jail in ${JAILS}; do bastille_running_jobs "${bastille_process_limit}" done -wait +wait \ No newline at end of file diff --git a/usr/local/share/bastille/stop.sh b/usr/local/share/bastille/stop.sh index 9d1329f8..680c4ec0 100644 --- a/usr/local/share/bastille/stop.sh +++ b/usr/local/share/bastille/stop.sh @@ -126,6 +126,14 @@ for _jail in ${JAILS}; do bastille limits "${_jail}" clear fi + # Unmount any jailed ZFS datasets + if [ -s "${bastille_jailsdir}/${_jail}/zfs.conf" ]; then + while read _dataset _mount; do + jexec -l -U root "${_jail}" zfs umount "${_dataset}" + zfs unjail "${_jail}" "${_dataset}" + done < "${bastille_jailsdir}/${_jail}/zfs.conf" + fi + # Stop jail jail ${OPTION} -f "${bastille_jailsdir}/${_jail}/jail.conf" -r "${_jail}" diff --git a/usr/local/share/bastille/zfs.sh b/usr/local/share/bastille/zfs.sh index bddd675a..fb030383 100644 --- a/usr/local/share/bastille/zfs.sh +++ b/usr/local/share/bastille/zfs.sh @@ -33,7 +33,10 @@ . /usr/local/share/bastille/common.sh usage() { - error_notify "Usage: bastille zfs TARGET [set|get|snap|destroy_snap|df|usage] [key=value|date]" + error_notify "Usage: bastille zfs TARGET [destroy_snap|df|get|set|(snap|snapshot)|usage] [key=value|date]" + error_notify " [jail pool/dataset /jail/path]" + error_notify " [unjail pool/dataset]" + cat << EOF Options: @@ -44,42 +47,127 @@ EOF exit 1 } +zfs_jail_dataset() { + + info "\n[${_jail}]:" + + # Exit if MOUNT or DATASET is empty + if [ -z "${MOUNT}" ] || [ -z "${DATASET}" ]; then + usage + # Exit if datset does not exist + elif ! zfs list "${DATASET}" >/dev/null 2>/dev/null; then + error_exit "[ERROR]: Dataset does not exist: ${DATASET}" + fi + + # Ensure dataset is not already present in *zfs.conf* + if grep -hoqsw "${DATASET}" ${bastille_jailsdir}/*/zfs.conf; then + error_exit "[ERROR]: Dataset already assigned." + fi + # Validate jail state + check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${_jail}" + else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." + fi + + # Add necessary config variables to jail + bastille config ${_jail} set enforce_statfs 1 >/dev/null + bastille config ${_jail} set allow.mount >/dev/null + bastille config ${_jail} set allow.mount.devfs >/dev/null + bastille config ${_jail} set allow.mount.zfs >/dev/null + + # Add dataset to zfs.conf + echo "${DATASET} ${MOUNT}" >> "${bastille_jailsdir}/${_jail}/zfs.conf" + + if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + fi +} + +zfs_unjail_dataset() { + + info "\n[${_jail}]:" + + # Exit if DATASET is empty + if [ -z "${DATASET}" ]; then + usage + # Warn if datset does not exist + elif ! zfs list "${DATASET}" >/dev/null 2>/dev/null; then + warn "[WARNING]: Dataset does not exist: ${DATASET}" + fi + + # Validate jail state + check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${_jail}" + else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." + fi + + # Remove dataset from zfs.conf + if ! grep -hoqsw "${DATASET}" ${bastille_jailsdir}/${_jail}/zfs.conf; then + error_exit "[ERROR]: Dataset not present in zfs.conf." + else + sed -i '' "\#.*${DATASET}.*#d" "${bastille_jailsdir}/${_jail}/zfs.conf" + fi + + if [ "${AUTO}" -eq 1 ]; then + bastille start "${_jail}" + fi +} + zfs_snapshot() { + info "\n[${_jail}]:" # shellcheck disable=SC2140 zfs snapshot -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}" } zfs_destroy_snapshot() { + info "\n[${_jail}]:" # shellcheck disable=SC2140 zfs destroy -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}"@"${TAG}" } zfs_set_value() { + info "\n[${_jail}]:" zfs "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" } zfs_get_value() { + info "\n[${_jail}]:" zfs get "${ATTRIBUTE}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" } zfs_disk_usage() { + info "\n[${_jail}]:" zfs list -t all -o name,used,avail,refer,mountpoint,compress,ratio -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${_jail}" } - # Handle options. +AUTO=0 while [ "$#" -gt 0 ]; do case "${1}" in -h|--help|help) usage ;; + -a|--auto) + AUTO=1 + shift + ;; -x|--debug) enable_debug shift ;; - -*) - error_notify "[ERROR]: Unknown Option: \"${1}\"" - usage + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "[ERROR]: Unknown Option: \"${1}\"" ;; + esac + done + shift ;; *) break @@ -87,7 +175,7 @@ while [ "$#" -gt 0 ]; do esac done -if [ "$#" -lt 2 ]; then +if [ "$#" -lt 2 ] || [ "$#" -gt 4 ]; then usage fi @@ -111,21 +199,7 @@ for _jail in ${JAILS}; do ( - info "\n[${_jail}]:" - case "${ACTION}" in - set) - ATTRIBUTE="${3}" - zfs_set_value - ;; - get) - ATTRIBUTE="${3}" - zfs_get_value - ;; - snap|snapshot) - TAG="${3}" - zfs_snapshot - ;; destroy_snap|destroy_snapshot) TAG="${3}" zfs_destroy_snapshot @@ -133,6 +207,27 @@ for _jail in ${JAILS}; do df|usage) zfs_disk_usage ;; + get) + ATTRIBUTE="${3}" + zfs_get_value + ;; + jail) + DATASET="${3}" + MOUNT="${4}" + zfs_jail_dataset + ;; + unjail) + DATASET="${3}" + zfs_unjail_dataset + ;; + set) + ATTRIBUTE="${3}" + zfs_set_value + ;; + snap|snapshot) + TAG="${3}" + zfs_snapshot + ;; *) usage ;; @@ -143,4 +238,4 @@ for _jail in ${JAILS}; do bastille_running_jobs "${bastille_process_limit}" done -wait \ No newline at end of file +wait