diff --git a/AUTHORS.md b/AUTHORS.md index 3a777ac1..353f1961 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -28,6 +28,7 @@ Christer Edwards [christer.edwards@gmail.com] - Niketh Murali - Eric Borisch - Kevet Duncombe +- Victor Tschetter ### Special thanks Software doesn't happen in a vacuum. Thank you to the following people who may diff --git a/README.md b/README.md index 21746469..ca2d2d38 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Available Commands: config Get or set a config value for the targeted container(s). console Console into a running container. convert Convert a Thin container into a Thick container. - cp cp(1) files from host to targeted container(s). + cp cp(1) files from host or container to host or targeted container(s). create Create a new thin container or a thick container if -T|--thick option specified. destroy Destroy a stopped container or a FreeBSD release. edit Edit container configuration files (advanced). @@ -70,12 +70,13 @@ Available Commands: help Help about any command. htop Interactive process viewer (requires htop). import Import a specified container. + jcp cp(1) files from a jail to jail(s). limits Apply resources limits to targeted container(s). See rctl(8). list List containers (running). mount Mount a volume inside the targeted container(s). pkg Manipulate binary packages within targeted container(s). See pkg(8). + rcp cp(1) files from a jail to host. rdr Redirect host port to container port. - rcp reverse cp(1) files from a single container to the host. rename Rename a container. restart Restart a running container. service Manage services within targeted container(s). @@ -97,7 +98,7 @@ Use "bastille command -h|--help" for more information about a command. ``` -## 0.12-beta +## 0.13-beta This document outlines the basic usage of the Bastille container management framework. This release is still considered beta. diff --git a/docs/chapters/installation.rst b/docs/chapters/installation.rst index 232988c9..1596813b 100644 --- a/docs/chapters/installation.rst +++ b/docs/chapters/installation.rst @@ -4,7 +4,7 @@ Bastille is available in the official FreeBSD ports tree at `sysutils/bastille`. Binary packages available in `quarterly` and `latest` repositories. -Current version is `0.12.20250111`. +Current version is `0.13.20250126`. To install from the FreeBSD package repository: diff --git a/docs/chapters/subcommands/config.rst b/docs/chapters/subcommands/config.rst new file mode 100644 index 00000000..aa583ca4 --- /dev/null +++ b/docs/chapters/subcommands/config.rst @@ -0,0 +1,32 @@ +======= +config +======= + +Gets or sets properties for a target container. + +.. code-block:: shell + + Usage: bastille config TARGET get|set propertyName [newValue] + +Getting a property that *is* defined in jail.conf: + +.. code-block:: shell + + ishmael ~ # bastille config azkaban get ip4.addr + 192.168.2.23 + +Getting a property that *is not* defined in jail.conf + +.. code-block:: shell + + ishmael ~ # bastille config azkaban get notaproperty + not set + +Setting a property: + +.. code-block:: shell + + ishmael ~ # bastille config azkaban set ip4.addr 192.168.2.24 + A restart is required for the changes to be applied. See 'bastille restart azkaban'. + +The restart message will appear every time a property is set. diff --git a/docs/chapters/subcommands/console.rst b/docs/chapters/subcommands/console.rst index b1a218eb..264a87b5 100644 --- a/docs/chapters/subcommands/console.rst +++ b/docs/chapters/subcommands/console.rst @@ -2,8 +2,7 @@ console ======= -This sub-command launches a login shell into the container. Default is password-less -root login. +This sub-command launches a login shell into the container. Default is password-less root login. .. code-block:: shell @@ -11,6 +10,25 @@ root login. [folsom]: root@folsom:~ # +TARGET can also be a running jails JID value. + +.. code-block:: shell + + ishmael ~ # bastille list + JID IP Address Hostname Path + 1 10.1.2.3 ishmael /usr/local/bastille/jails/ishmael/root + ishmael ~ # bastille console 1 + [ishmael]: + root@ishmael:~ # + At this point you are logged in to the container and have full shell access. The system is yours to use and/or abuse as you like. Any changes made inside the container are limited to the container. + +.. code-block:: shell + + "Usage: bastille console [option(s)] TARGET [user]" + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. diff --git a/docs/chapters/subcommands/cp.rst b/docs/chapters/subcommands/cp.rst index 88c69cfb..57b610b9 100644 --- a/docs/chapters/subcommands/cp.rst +++ b/docs/chapters/subcommands/cp.rst @@ -2,21 +2,31 @@ cp == -This command allows efficiently copying files from host to container(s). +This command allows copying files from host to jail(s). .. code-block:: shell - ishmael ~ # bastille cp ALL /tmp/resolv.conf-cf etc/resolv.conf + ishmael ~ # bastille cp ALL /tmp/resolv.conf-cf /etc/resolv.conf [bastion]: - + /tmp/resolv.conf-cf -> /usr/local/bastille/jails/bastion/root/etc/resolv.conf [unbound0]: - + /tmp/resolv.conf-cf -> /usr/local/bastille/jails/unbound0/root/etc/resolv.conf [unbound1]: - + /tmp/resolv.conf-cf -> /usr/local/bastille/jails/unbound1/root/etc/resolv.conf [squid]: - + /tmp/resolv.conf-cf -> /usr/local/bastille/jails/squid/root/etc/resolv.conf [nginx]: - + /tmp/resolv.conf-cf -> /usr/local/bastille/jails/nginx/root/etc/resolv.conf [folsom]: + /tmp/resolv.conf-cf -> /usr/local/bastille/jails/folsom/root/etc/resolv.conf Unless you see errors reported in the output the `cp` was successful. + +.. code-block:: shell + + ishmael ~ # bastille cp help + Usage: bastille cp [option(s)] TARGET HOST_PATH JAIL_PATH + Options: + + -q | --quiet Suppress output. + -x | --debug Enable debug mode. diff --git a/docs/chapters/subcommands/htop.rst b/docs/chapters/subcommands/htop.rst index d3493be2..1fcb8bdb 100644 --- a/docs/chapters/subcommands/htop.rst +++ b/docs/chapters/subcommands/htop.rst @@ -2,8 +2,8 @@ htop ==== -This one runs `htop` inside the container. -note: won't work if you don't have htop installed in the container. +This command runs `htop` in the targeted jail. +Requires htop to be installed in the jail. .. image:: ../../images/htop.png diff --git a/docs/chapters/subcommands/jcp.rst b/docs/chapters/subcommands/jcp.rst new file mode 100644 index 00000000..6dca3c3d --- /dev/null +++ b/docs/chapters/subcommands/jcp.rst @@ -0,0 +1,30 @@ +=== +jcp +=== + +This command allows copying files from jail to jail(s). + +.. code-block:: shell + + ishmael ~ # bastille jcp bastion /tmp/resolv.conf-cf ALL /etc/resolv.conf + [unbound0]: + /usr/local/bastille/jails/bastion/root/tmp/resolv.conf-cf -> /usr/local/bastille/jails/unbound0/root/etc/resolv.conf + [unbound1]: + /usr/local/bastille/jails/bastion/root/tmp/resolv.conf-cf -> /usr/local/bastille/jails/unbound1/root/etc/resolv.conf + [squid]: + /usr/local/bastille/jails/bastion/root/tmp/resolv.conf-cf -> /usr/local/bastille/jails/squid/root/etc/resolv.conf + [nginx]: + /usr/local/bastille/jails/bastion/root/tmp/resolv.conf-cf -> /usr/local/bastille/jails/nginx/root/etc/resolv.conf + [folsom]: + /usr/local/bastille/jails/bastion/root/tmp/resolv.conf-cf -> /usr/local/bastille/jails/folsom/root/etc/resolv.conf + +Unless you see errors reported in the output the `jcp` was successful. + +.. code-block:: shell + + ishmael ~ # bastille jcp help + Usage: bastille jcp [option(s)] SOURCE_JAIL JAIL_PATH DEST_JAIL JAIL_PATH + Options: + + -q | --quiet Suppress output. + -x | --debug Enable debug mode. diff --git a/docs/chapters/subcommands/mount.rst b/docs/chapters/subcommands/mount.rst index cabe779c..2f37f47b 100644 --- a/docs/chapters/subcommands/mount.rst +++ b/docs/chapters/subcommands/mount.rst @@ -10,7 +10,7 @@ Syntax follows standard `/etc/fstab` format: Usage: bastille mount TARGET HOST_PATH JAIL_PATH [filesystem_type options dump pass_number] -The 'options' string can include a comma-separated list of mount options, but must start with 'ro' or 'rw'. +The 'options' string can include a comma-separated list of mount options, but must include one of (rw,ro,rq,sw,xx) according to fstab documentation. Example: Mount a tmpfs filesystem with options. .. code-block:: shell diff --git a/docs/chapters/subcommands/rcp.rst b/docs/chapters/subcommands/rcp.rst new file mode 100644 index 00000000..fd4ab86f --- /dev/null +++ b/docs/chapters/subcommands/rcp.rst @@ -0,0 +1,22 @@ +=== +rcp +=== + +This command allows copying files from jail to host. + +.. code-block:: shell + + ishmael ~ # bastille rcp bastion /test/testfile.txt /tmp/testfile.txt + [bastion]: + /usr/local/bastille/jails/bastion/root/test/testfile.txt -> /tmp/testfile.txt + +Unless you see errors reported in the output the `rcp` was successful. + +.. code-block:: shell + + ishmael ~ # bastille rcp help + Usage: bastille rcp [option(s)] TARGET JAIL_PATH HOST_PATH + Options: + + -q | --quiet Suppress output. + -x | --debug Enable debug mode. diff --git a/docs/chapters/subcommands/top.rst b/docs/chapters/subcommands/top.rst index 16df8682..77e7f831 100644 --- a/docs/chapters/subcommands/top.rst +++ b/docs/chapters/subcommands/top.rst @@ -2,7 +2,7 @@ top === -This one runs `top` in that container. +This command runs `top` in the targeted jail. .. image:: ../../images/top.png diff --git a/docs/conf.py b/docs/conf.py index 96451510..c2f36b89 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,9 +12,9 @@ copyright = '2018-2025, Christer Edwards' author = 'Christer Edwards' # The short X.Y version -version = '0.12.20250111' +version = '0.13.20250126' # The full version, including alpha/beta/rc tags -release = '0.12.20250111-beta' +release = '0.13.20250126-beta' # -- General configuration --------------------------------------------------- diff --git a/usr/local/bin/bastille b/usr/local/bin/bastille index c5442daf..d8439beb 100755 --- a/usr/local/bin/bastille +++ b/usr/local/bin/bastille @@ -78,7 +78,7 @@ bastille_perms_check() { bastille_perms_check ## version -BASTILLE_VERSION="0.12.20250111" +BASTILLE_VERSION="0.13.20250126" usage() { cat << EOF @@ -95,19 +95,21 @@ Available Commands: config Get or set a config value for the targeted container(s). console Console into a running container. convert Convert a Thin container into a Thick container. - cp cp(1) files from host to targeted container(s). + cp cp(1) files from host to jail(s). create Create a new thin container or a thick container if -T|--thick option specified. destroy Destroy a stopped container or a FreeBSD release. edit Edit container configuration files (advanced). + etcupdate Update /etc directory to specified release. export Exports a specified container. help Help about any command. htop Interactive process viewer (requires htop). + jcp cp(1) files from a jail to jail(s). import Import a specified container. limits Apply resources limits to targeted container(s). See rctl(8). list List containers (running). mount Mount a volume inside the targeted container(s). pkg Manipulate binary packages within targeted container(s). See pkg(8). - rcp reverse cp(1) files from a single container to the host. + rcp cp(1) files from a jail to host. rdr Redirect host port to container port. rename Rename a container. restart Restart a running container. @@ -137,7 +139,7 @@ EOF CMD=$1 shift -target_all_jails() { +target_all_jails_old() { _JAILS=$(/usr/sbin/jls name) JAILS="" for _jail in ${_JAILS}; do @@ -148,7 +150,7 @@ target_all_jails() { done } -check_target_is_running() { +check_target_is_running_old() { if [ ! "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then error_exit "[${TARGET}]: Not started. See 'bastille start ${TARGET}'." fi @@ -163,10 +165,11 @@ version|-v|--version) help|-h|--help) usage ;; -bootstrap|create|destroy|export|htop|import|list|mount|rdr|restart|setup|start|top|umount|update|upgrade|verify) + +bootstrap|clone|console|create|cp|destroy|etcupdate|export|htop|import|jcp|list|mount|rcp|rdr|rename|restart|setup|start|top|umount|update|upgrade|verify) # Nothing "extra" to do for these commands. -- cwells ;; -clone|config|cmd|console|convert|cp|edit|limits|pkg|rcp|rename|service|stop|sysrc|tags|template|zfs) +config|cmd|convert|edit|limits|pkg|service|stop|sysrc|tags|template|zfs) # Parse the target and ensure it exists. -- cwells if [ $# -eq 0 ]; then # No target was given, so show the command's help. -- cwells PARAMS='help' @@ -187,15 +190,15 @@ clone|config|cmd|console|convert|cp|edit|limits|pkg|rcp|rename|service|stop|sysr fi if [ "${TARGET}" = 'ALL' ]; then - target_all_jails + target_all_jails_old elif [ "${CMD}" = "pkg" ] && [ "${TARGET}" = '-H' ] || [ "${TARGET}" = '--host' ]; then TARGET="${1}" USE_HOST_PKG=1 if [ "${TARGET}" = 'ALL' ]; then - target_all_jails + target_all_jails_old else JAILS="${TARGET}" - check_target_is_running + check_target_is_running_old fi shift elif [ "${CMD}" = 'template' ] && [ "${TARGET}" = '--convert' ]; then @@ -211,8 +214,8 @@ clone|config|cmd|console|convert|cp|edit|limits|pkg|rcp|rename|service|stop|sysr fi case "${CMD}" in - cmd|console|pkg|service|stop|sysrc|template) - check_target_is_running + cmd|pkg|service|stop|sysrc|template) + check_target_is_running_old ;; convert|rename) # Require the target to be stopped. -- cwells diff --git a/usr/local/share/bastille/clone.sh b/usr/local/share/bastille/clone.sh index 9dae7f44..2d2dee9e 100644 --- a/usr/local/share/bastille/clone.sh +++ b/usr/local/share/bastille/clone.sh @@ -34,24 +34,78 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille clone TARGET NEW_NAME IPADDRESS" + error_notify "Usage: bastille clone [option(s)] TARGET NEW_NAME IP_ADDRESS" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. Cannot be used with [-l|--live]. + -l | --live Clone a running jail. ZFS only. Jail must be running. Cannot be used with [-a|--auto]. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first -case "$1" in -help|-h|--help) - usage - ;; -esac +# Handle options. +AUTO=0 +LIVE=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -l|--live) + if ! checkyesno bastille_zfs_enable; then + error_exit "[-l|--live] can only be used with ZFS." + else + LIVE=1 + shift + fi + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + l) LIVE=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -ne 2 ]; then +if [ "${AUTO}" -eq 1 ] && [ "${LIVE}" -eq 1 ]; then + error_exit "[-a|--auto] cannot be used with [-l|--live]" +fi + +if [ $# -ne 3 ]; then usage fi -bastille_root_check +TARGET="${1}" +NEWNAME="${2}" +IP="${3}" -NEWNAME="${1}" -IP="${2}" +bastille_root_check +set_target_single "${TARGET}" + +## don't allow for dots(.) in container names +if echo "${NEWNAME}" | grep -q "[.]"; then + error_exit "Container names may not contain a dot(.)!" +fi validate_ip() { IPX_ADDR="ip4.addr" @@ -89,79 +143,153 @@ update_jailconf() { JAIL_CONFIG="${bastille_jailsdir}/${NEWNAME}/jail.conf" if [ -f "${JAIL_CONFIG}" ]; then if ! grep -qw "path = ${bastille_jailsdir}/${NEWNAME}/root;" "${JAIL_CONFIG}"; then - sed -i '' "s|host.hostname = ${TARGET};|host.hostname = ${NEWNAME};|" "${JAIL_CONFIG}" + sed -i '' "s|host.hostname = ${TARGET};|host.hostname = ${NEWNAME};|" "${JAIL_CONFIG}" sed -i '' "s|exec.consolelog = .*;|exec.consolelog = ${bastille_logsdir}/${NEWNAME}_console.log;|" "${JAIL_CONFIG}" sed -i '' "s|path = .*;|path = ${bastille_jailsdir}/${NEWNAME}/root;|" "${JAIL_CONFIG}" sed -i '' "s|mount.fstab = .*;|mount.fstab = ${bastille_jailsdir}/${NEWNAME}/fstab;|" "${JAIL_CONFIG}" sed -i '' "s|${TARGET} {|${NEWNAME} {|" "${JAIL_CONFIG}" - sed -i '' "s|${IPX_ADDR} = .*;|${IPX_ADDR} = ${IP};|" "${JAIL_CONFIG}" fi fi if grep -qw "vnet;" "${JAIL_CONFIG}"; then update_jailconf_vnet + else + _ip4="$(bastille config ${TARGET} get ip4.addr | sed 's/,/ /g')" + _ip6="$(bastille config ${TARGET} get ip6.addr | sed 's/,/ /g')" + # IP4 + if [ "${_ip4}" != "not set" ]; then + for _ip in ${_ip4}; do + _ip="$(echo ${_ip} | awk -F"|" '{print $2}')" + sed -i '' "/${IPX_ADDR} = .*/ s/${_ip}/${IP}/" "${JAIL_CONFIG}" + sed -i '' "/${IPX_ADDR} += .*/ s/${_ip}/127.0.0.1/" "${JAIL_CONFIG}" + done + fi + # IP6 + if [ "${_ip6}" != "not set" ]; then + for _ip in ${_ip6}; do + _ip="$(echo ${_ip} | awk -F"|" '{print $2}')" + sed -i '' "/${IPX_ADDR} = .*/ s/${_ip}/${IP}/" "${JAIL_CONFIG}" + sed -i '' "/${IPX_ADDR} += .*/ s/${_ip}/127.0.0.1/" "${JAIL_CONFIG}" + done + fi fi } update_jailconf_vnet() { - bastille_jail_rc_conf="${bastille_jailsdir}/${NEWNAME}/root/etc/rc.conf" - - # Determine number of containers and define an uniq_epair - local list_jails_num="$(bastille list jails | wc -l | awk '{print $1}')" - local num_range="$(expr "${list_jails_num}" + 1)" - jail_list=$(bastille list jail) - for _num in $(seq 0 "${num_range}"); do - if [ -n "${jail_list}" ]; then - if ! grep -q "e0b_bastille${_num}" "${bastille_jailsdir}"/*/jail.conf; then - if ! grep -q "epair${_num}" "${bastille_jailsdir}"/*/jail.conf; then - local uniq_epair="bastille${_num}" - local uniq_epair_bridge="${_num}" - # since we don't have access to the external_interface variable, we cat the jail.conf file to retrieve the mac prefix - # we also do not use the main generate_static_mac function here - local macaddr_prefix="$(cat ${JAIL_CONFIG} | grep -m 1 ether | grep -oE '([0-9a-f]{2}(:[0-9a-f]{2}){5})' | awk -F: '{print $1":"$2":"$3}')" - local macaddr_suffix="$(echo -n ${NEWNAME} | sha256 | cut -b -5 | sed 's/\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F]\)/\1:\2:\3/')" - local macaddr="${macaddr_prefix}:${macaddr_suffix}" - # Update the exec.* with uniq_epair when cloning jails. - # for VNET jails - sed -i '' "s|bastille\([0-9]\{1,\}\)|${uniq_epair}|g" "${JAIL_CONFIG}" - sed -i '' "s|e\([0-9]\{1,\}\)a_${NEWNAME}|e${uniq_epair_bridge}a_${NEWNAME}|g" "${JAIL_CONFIG}" - sed -i '' "s|e\([0-9]\{1,\}\)b_${NEWNAME}|e${uniq_epair_bridge}b_${NEWNAME}|g" "${JAIL_CONFIG}" - sed -i '' "s|epair\([0-9]\{1,\}\)|epair${uniq_epair_bridge}|g" "${JAIL_CONFIG}" - sed -i '' "s|exec.prestart += \"ifconfig e0a_bastille\([0-9]\{1,\}\).*description.*|exec.prestart += \"ifconfig e0a_${uniq_epair} description \\\\\"vnet host interface for Bastille jail ${NEWNAME}\\\\\"\";|" "${JAIL_CONFIG}" - sed -i '' "s|ether.*:.*:.*:.*:.*:.*a\";|ether ${macaddr}a\";|" "${JAIL_CONFIG}" - sed -i '' "s|ether.*:.*:.*:.*:.*:.*b\";|ether ${macaddr}b\";|" "${JAIL_CONFIG}" + local _jail_conf="${bastille_jailsdir}/${NEWNAME}/jail.conf" + local _rc_conf="${bastille_jailsdir}/${NEWNAME}/root/etc/rc.conf" + # Determine number of interfaces and define a uniq_epair + local _if_list="$(grep -Eo 'epair[0-9]+|bastille[0-9]+' ${_jail_conf} | sort -u)" + for _if in ${_if_list}; do + local _epair_if_count="$( (grep -Eo 'epair[0-9]+' ${bastille_jailsdir}/*/jail.conf; ifconfig | grep -Eo '(e[0-9]+a|epair[0-9]+a)' ) | sort -u | wc -l | awk '{print $1}')" + local _bastille_if_count="$(grep -Eo 'bastille[0-9]+' ${bastille_jailsdir}/*/jail.conf | sort -u | wc -l | awk '{print $1}')" + local epair_num_range=$((_epair_if_count + 1)) + local bastille_num_range=$((_bastille_if_count + 1)) + if echo ${_if} | grep -Eoq 'epair[0-9]+'; then + # Update bridged VNET config + for _num in $(seq 0 "${epair_num_range}"); do + if ! grep -Eoq "epair${_num}" ${bastille_jailsdir}/*/jail.conf && ! ifconfig | grep -Eoq "(e${_num}a|epair${_num}a)"; then + # Generate new epair name + if [ "$(echo -n "e${_num}a_${NEWNAME}" | awk '{print length}')" -lt 16 ]; then + local _new_host_epair="e${_num}a_${NEWNAME}" + local _new_jail_epair="e${_num}b_${NEWNAME}" + else + local _new_host_epair="epair${_num}a" + local _new_jail_epair="epair${_num}b" + fi + # Get epair name from TARGET + if grep -Eoq "e[0-9]+a_${TARGET}" "${_jail_conf}"; then + _target_host_epair="$(grep -Eo -m 1 "e[0-9]+a_${TARGET}" "${_jail_conf}")" + _target_jail_epair="$(grep -Eo -m 1 "e[0-9]+b_${TARGET}" "${_jail_conf}")" + else + _target_host_epair="${_if}a" + _target_jail_epair="${_if}b" + fi + # Replace host epair name in jail.conf + sed -i '' "s|up name ${_target_host_epair}|up name ${_new_host_epair}|g" "${_jail_conf}" + sed -i '' "s|${_target_host_epair} ether|${_new_host_epair} ether|g" "${_jail_conf}" + sed -i '' "s|deletem ${_target_host_epair}|deletem ${_new_host_epair}|g" "${_jail_conf}" + sed -i '' "s|${_target_host_epair} destroy|${_new_host_epair} destroy|g" "${_jail_conf}" + sed -i '' "s|${_target_host_epair} description|${_new_host_epair} description|g" "${_jail_conf}" + # Replace jail epair name in jail.conf + sed -i '' "s|= ${_target_jail_epair};|= ${_new_jail_epair};|g" "${_jail_conf}" + sed -i '' "s|up name ${_target_jail_epair}|up name ${_new_jail_epair}|g" "${_jail_conf}" + sed -i '' "s|${_target_jail_epair} ether|${_new_jail_epair} ether|g" "${_jail_conf}" + # Replace epair name in jail.conf + sed -i '' "s|${_if}|epair${_num}|g" "${_jail_conf}" + # If jail had a static MAC, generate one for clone + if grep -q ether ${_jail_conf}; then + local external_interface="$(grep "epair${_num}a" ${_jail_conf} | grep -o '[^ ]* addm' | awk '{print $1}')" + generate_static_mac "${NEWNAME}" "${external_interface}" + sed -i '' "s|${_new_host_epair} ether.*:.*:.*:.*:.*:.*a\";|${_new_host_epair} ether ${macaddr}a\";|" "${_jail_conf}" + sed -i '' "s|${_new_jail_epair} ether.*:.*:.*:.*:.*:.*b\";|${_new_jail_epair} ether ${macaddr}b\";|" "${_jail_conf}" + fi + # Replace epair description + sed -i '' "s|vnet host interface for Bastille jail ${TARGET}|vnet host interface for Bastille jail ${NEWNAME}|g" "${_jail_conf}" + # Update /etc/rc.conf + local _jail_vnet="$(grep ${_target_jail_epair} "${_rc_conf}" | grep -Eo -m 1 "vnet[0-9]+")" + sed -i '' "s|${_target_jail_epair}_name|${_new_jail_epair}_name|" "${_rc_conf}" + if grep "vnet0" "${_rc_conf}" | grep -q "${_new_jail_epair}_name"; then + if [ "${IP}" = "0.0.0.0" ]; then + sysrc -f "${_rc_conf}" ifconfig_vnet0="SYNCDHCP" + else + sysrc -f "${_rc_conf}" ifconfig_vnet0="inet ${IP}" + fi + else + sysrc -f "${_rc_conf}" ifconfig_${_jail_vnet}="SYNCDHCP" + fi break fi - fi + done + elif echo ${_if} | grep -Eoq 'bastille[0-9]+'; then + # Update VNET config + for _num in $(seq 0 "${bastille_num_range}"); do + if ! grep -oq "bastille${_num}" ${bastille_jailsdir}/*/jail.conf; then + # Update jail.conf epair name + local uniq_epair="bastille${_num}" + local _if_vnet="$(grep ${_if} "${_rc_conf}" | grep -Eo -m 1 "vnet[0-9]+")" + sed -i '' "s|${_if}|${uniq_epair}|g" "${_jail_conf}" + # If jail had a static MAC, generate one for clone + if grep ether ${_jail_conf} | grep -qoc ${uniq_epair}; then + local external_interface="$(grep ${uniq_epair} ${_jail_conf} | grep -o 'addm.*' | awk '{print $3}' | sed 's/["|;]//g')" + generate_static_mac "${NEWNAME}" "${external_interface}" + sed -i '' "s|${uniq_epair} ether.*:.*:.*:.*:.*:.*a\";|${uniq_epair} ether ${macaddr}a\";|" "${_jail_conf}" + sed -i '' "s|${uniq_epair} ether.*:.*:.*:.*:.*:.*b\";|${uniq_epair} ether ${macaddr}b\";|" "${_jail_conf}" + fi + sed -i '' "s|vnet host interface for Bastille jail ${TARGET}|vnet host interface for Bastille jail ${NEWNAME}|g" "${_jail_conf}" + # Update /etc/rc.conf + sed -i '' "s|ifconfig_e0b_${_if}_name|ifconfig_e0b_${uniq_epair}_name|" "${_rc_conf}" + if grep "vnet0" "${_rc_conf}" | grep -q ${uniq_epair}; then + if [ "${IP}" = "0.0.0.0" ]; then + sysrc -f "${_rc_conf}" ifconfig_vnet0="SYNCDHCP" + else + sysrc -f "${_rc_conf}" ifconfig_vnet0=" inet ${IP} " + fi + else + sysrc -f "${_rc_conf}" ifconfig_${_if_vnet}="SYNCDHCP" + fi + break + fi + done fi done - - # Rename interface to new uniq_epair - sed -i '' "s|ifconfig_e0b_bastille.*_name|ifconfig_e0b_${uniq_epair}_name|" "${bastille_jail_rc_conf}" - sed -i '' "s|ifconfig_e.*b_${TARGET}_name|ifconfig_e${uniq_epair_bridge}b_${NEWNAME}_name|" "${bastille_jail_rc_conf}" - - # If 0.0.0.0 set DHCP, else set static IP address - if [ "${IP}" = "0.0.0.0" ]; then - sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="SYNCDHCP" - else - sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="inet ${IP}" - fi -} - -update_fstab() { - # Update fstab to use the new name - FSTAB_CONFIG="${bastille_jailsdir}/${NEWNAME}/fstab" - if [ -f "${FSTAB_CONFIG}" ]; then - # Update additional fstab paths with new jail path - sed -i '' "s|${bastille_jailsdir}/${TARGET}/root/|${bastille_jailsdir}/${NEWNAME}/root/|" "${FSTAB_CONFIG}" - fi } clone_jail() { - # Attempt container clone - info "Attempting to clone '${TARGET}' to ${NEWNAME}..." + + info "Attempting to clone ${TARGET} to ${NEWNAME}..." + if ! [ -d "${bastille_jailsdir}/${NEWNAME}" ]; then if checkyesno bastille_zfs_enable; then + if [ "${LIVE}" -eq 1 ]; then + check_target_is_running "${TARGET}" || error_exit "[-l|--live] can only be used with a running jail." + else check_target_is_stopped "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${TARGET}" + else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to force stop the jail, or [-l|--live] (ZFS only) to clone a running jail." + fi + fi if [ -n "${bastille_zfs_zpool}" ]; then # Replicate the existing container DATE=$(date +%F-%H%M%S) @@ -177,13 +305,13 @@ clone_jail() { zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${NEWNAME}@bastille_clone_${DATE}" fi else - # Just clone the jail directory - # Check if container is running - if [ -n "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "${TARGET} is running. See 'bastille stop ${TARGET}'." + # Perform container file copy (archive mode) + check_target_is_stopped "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${TARGET}" + else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to force stop the jail." fi - - # Perform container file copy(archive mode) cp -a "${bastille_jailsdir}/${TARGET}" "${bastille_jailsdir}/${NEWNAME}" fi else @@ -192,7 +320,7 @@ clone_jail() { # Generate jail configuration files update_jailconf - update_fstab + update_fstab "${TARGET}" "${NEWNAME}" # Display the exist status if [ "$?" -ne 0 ]; then @@ -200,14 +328,12 @@ clone_jail() { else info "Cloned '${TARGET}' to '${NEWNAME}' successfully." fi + if [ "${AUTO}" -eq 1 ] || [ "${LIVE}" -eq 1 ]; then + bastille start "${NEWNAME}" + fi } -## don't allow for dots(.) in container names -if echo "${NEWNAME}" | grep -q "[.]"; then - error_exit "Container names may not contain a dot(.)!" -fi - -## check if ip address is valid +# Check if IP address is valid. if [ -n "${IP}" ]; then validate_ip else diff --git a/usr/local/share/bastille/common.sh b/usr/local/share/bastille/common.sh index 4189f07b..fd62d6e2 100644 --- a/usr/local/share/bastille/common.sh +++ b/usr/local/share/bastille/common.sh @@ -31,7 +31,9 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Source config file -. /usr/local/etc/bastille/bastille.conf +if [ -f /usr/local/etc/bastille/bastille.conf ]; then + . /usr/local/etc/bastille/bastille.conf +fi COLOR_RED= COLOR_GREEN= @@ -50,24 +52,30 @@ enable_color() { . /usr/local/share/bastille/colors.pre.sh } +enable_debug() { + # Enable debug mode. + warn "***DEBUG MODE***" + set -x +} + # If "NO_COLOR" environment variable is present, or we aren't speaking to a # tty, disable output colors. if [ -z "${NO_COLOR}" ] && [ -t 1 ]; then enable_color fi -# Error/Info functions -error_notify() { - echo -e "${COLOR_RED}$*${COLOR_RESET}" 1>&2 -} - +# Notify message on error, and continue to next jail error_continue() { error_notify "$@" - # Disabling this shellcheck as we only ever call it inside of a loop # shellcheck disable=SC2104 continue } +# Notify message on error, but do not exit +error_notify() { + echo -e "${COLOR_RED}$*${COLOR_RESET}" 1>&2 +} + # Notify message on error and exit error_exit() { error_notify "$@" @@ -84,7 +92,8 @@ warn() { check_target_exists() { local _TARGET="${1}" - if [ ! -d "${bastille_jailsdir}"/"${_TARGET}" ]; then + local _jaillist="$(bastille list jails)" + if ! echo "${_jaillist}" | grep -Eq "^${_TARGET}$"; then return 1 else return 0 @@ -93,7 +102,7 @@ check_target_exists() { check_target_is_running() { local _TARGET="${1}" - if [ ! "$(/usr/sbin/jls name | awk "/^${_TARGET}$/")" ]; then + if ! jls name | grep -Eq "^${_TARGET}$"; then return 1 else return 0 @@ -102,89 +111,67 @@ check_target_is_running() { check_target_is_stopped() { local _TARGET="${1}" - if [ "$(/usr/sbin/jls name | awk "/^${_TARGET}$/")" ]; then + if jls name | grep -Eq "^${_TARGET}$"; then return 1 else return 0 fi } -generate_static_mac() { - local jail_name="${1}" - local external_interface="${2}" - local external_interface_mac="$(ifconfig ${external_interface} | grep ether | awk '{print $2}' | sed 's#:##g')" - local macaddr_prefix="$(echo -n "${external_interface_mac}" | sha256 | cut -b -6 | sed 's/\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F]\)/\1:\2:\3/')" - local macaddr_suffix="$(echo -n "${jail_name}" | sha256 | cut -b -5 | sed 's/\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F]\)/\1:\2:\3/')" - if [ -z "${macaddr_prefix}" ] || [ -z "${macaddr_suffix}" ]; then - error_notify "Failed to generate MAC address." +get_jail_name() { + local _JID="${1}" + local _jailname="$(jls -j ${_JID} name 2>/dev/null)" + if [ -z "${_jailname}" ]; then + return 1 + else + echo "${_jailname}" fi - macaddr="${macaddr_prefix}:${macaddr_suffix}" - export macaddr } -generate_vnet_jail_netblock() { - local jail_name="$1" - local use_unique_bridge="$2" - local external_interface="$3" - generate_static_mac "${jail_name}" "${external_interface}" - ## determine number of containers + 1 - ## iterate num and grep all jail configs - ## define uniq_epair - local jail_list="$(bastille list jails)" - if [ -n "${jail_list}" ]; then - local list_jails_num="$(echo "${jail_list}" | wc -l | awk '{print $1}')" - local num_range=$((list_jails_num + 1)) - for _num in $(seq 0 "${num_range}"); do - if ! grep -q "e[0-9]b_bastille${_num}" "${bastille_jailsdir}"/*/jail.conf; then - if ! grep -q "epair${_num}" "${bastille_jailsdir}"/*/jail.conf; then - local uniq_epair="bastille${_num}" - local uniq_epair_bridge="${_num}" - break - fi - fi - done +jail_autocomplete() { + local _TARGET="${1}" + local _jaillist="$(bastille list jails)" + local _AUTOTARGET="$(echo "${_jaillist}" | grep -E "^${_TARGET}")" + if [ -n "${_AUTOTARGET}" ]; then + if [ "$(echo "${_AUTOTARGET}" | wc -l)" -eq 1 ]; then + echo "${_AUTOTARGET}" + else + error_continue "Multiple jails found for ${_TARGET}:\n${_AUTOTARGET}" + return 1 + fi else - local uniq_epair="bastille0" - local uniq_epair_bridge="0" - fi - if [ -n "${use_unique_bridge}" ]; then - ## generate bridge config - cat <<-EOF - vnet; - vnet.interface = e${uniq_epair_bridge}b_${jail_name}; - exec.prestart += "ifconfig epair${uniq_epair_bridge} create"; - exec.prestart += "ifconfig ${external_interface} addm epair${uniq_epair_bridge}a"; - exec.prestart += "ifconfig epair${uniq_epair_bridge}a up name e${uniq_epair_bridge}a_${jail_name}"; - exec.prestart += "ifconfig epair${uniq_epair_bridge}b up name e${uniq_epair_bridge}b_${jail_name}"; - exec.prestart += "ifconfig e${uniq_epair_bridge}a_${jail_name} ether ${macaddr}a"; - exec.prestart += "ifconfig e${uniq_epair_bridge}b_${jail_name} ether ${macaddr}b"; - exec.poststop += "ifconfig ${external_interface} deletem e${uniq_epair_bridge}a_${jail_name}"; - exec.poststop += "ifconfig e${uniq_epair_bridge}a_${jail_name} destroy"; -EOF - else - ## generate config - cat <<-EOF - vnet; - vnet.interface = e0b_${uniq_epair}; - exec.prestart += "jib addm ${uniq_epair} ${external_interface}"; - exec.prestart += "ifconfig e0a_${uniq_epair} ether ${macaddr}a"; - exec.prestart += "ifconfig e0b_${uniq_epair} ether ${macaddr}b"; - exec.prestart += "ifconfig e0a_${uniq_epair} description \"vnet host interface for Bastille jail ${jail_name}\""; - exec.poststop += "jib destroy ${uniq_epair}"; -EOF + return 2 fi } set_target() { - local _TARGET="${1}" + local _TARGET=${1} + JAILS="" + TARGET="" if [ "${_TARGET}" = ALL ] || [ "${_TARGET}" = all ]; then target_all_jails else - check_target_exists "${_TARGET}" || error_exit "Jail not found \"${_TARGET}\"" - JAILS="${_TARGET}" - TARGET="${_TARGET}" - export JAILS + for _jail in ${_TARGET}; do + if [ ! -d "${bastille_jailsdir}/${_TARGET}" ] && echo "${_jail}" | grep -Eq '^[0-9]+$'; then + if get_jail_name "${_jail}" > /dev/null; then + _jail="$(get_jail_name ${_jail})" + else + error_continue "Error: JID \"${_jail}\" not found. Is jail running?" + fi + elif ! check_target_exists "${_jail}"; then + if jail_autocomplete "${_jail}" > /dev/null; then + _jail="$(jail_autocomplete ${_jail})" + elif [ $? -eq 2 ]; then + error_continue "Jail not found \"${_jail}\"" + else + exit 1 + fi + fi + TARGET="${TARGET} ${_jail}" + JAILS="${JAILS} ${_jail}" + done export TARGET + export JAILS fi } @@ -192,13 +179,27 @@ set_target_single() { local _TARGET="${1}" if [ "${_TARGET}" = ALL ] || [ "${_TARGET}" = all ]; then error_exit "[all|ALL] not supported with this command." - else - check_target_exists "${_TARGET}" || error_exit "Jail not found \"${_TARGET}\"" - JAILS="${_TARGET}" - TARGET="${_TARGET}" - export JAILS - export TARGET + elif [ "$(echo ${_TARGET} | wc -w)" -gt 1 ]; then + error_exit "Error: Command only supports a single TARGET." + elif [ ! -d "${bastille_jailsdir}/${_TARGET}" ] && echo "${_TARGET}" | grep -Eq '^[0-9]+$'; then + if get_jail_name "${_TARGET}" > /dev/null; then + _TARGET="$(get_jail_name ${_TARGET})" + else + error_exit "Error: JID \"${_TARGET}\" not found. Is jail running?" + fi + elif ! check_target_exists "${_TARGET}"; then + if jail_autocomplete "${_TARGET}" > /dev/null; then + _TARGET="$(jail_autocomplete ${_TARGET})" + elif [ $? -eq 2 ]; then + error_exit "Jail not found \"${_TARGET}\"" + else + exit 1 + fi fi + TARGET="${_TARGET}" + JAILS="${_TARGET}" + export TARGET + export JAILS } target_all_jails() { @@ -212,6 +213,139 @@ target_all_jails() { export JAILS } +update_fstab() { + local _oldname="${1}" + local _newname="${2}" + local _fstab="${bastille_jailsdir}/${_newname}/fstab" + if [ -f "${_fstab}" ]; then + sed -i '' "s|${bastille_jailsdir}/${_oldname}/root/|${bastille_jailsdir}/${_newname}/root/|" "${_fstab}" + else + error_notify "Error: Failed to update fstab: ${_newmane}" + fi +} + +generate_static_mac() { + local jail_name="${1}" + local external_interface="${2}" + local external_interface_mac="$(ifconfig ${external_interface} | grep ether | awk '{print $2}')" + # Use FreeBSD vendor MAC prefix (58:9c:fc) for jail MAC prefix + local macaddr_prefix="58:9c:fc" + # Use hash of interface+jailname for jail MAC suffix + local macaddr_suffix="$(echo -n "${external_interface_mac}${jail_name}" | sed 's#:##g' | sha256 | cut -b -5 | sed 's/\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F][0-9a-fA-F]\)\([0-9a-fA-F]\)/\1:\2:\3/')" + if [ -z "${macaddr_prefix}" ] || [ -z "${macaddr_suffix}" ]; then + error_notify "Failed to generate MAC address." + fi + macaddr="${macaddr_prefix}:${macaddr_suffix}" + export macaddr +} + +generate_vnet_jail_netblock() { + local jail_name="${1}" + local use_unique_bridge="${2}" + local external_interface="${3}" + local static_mac="${4}" + ## determine number of interfaces + 1 + ## iterate num and grep all jail configs + ## define uniq_epair + local _epair_if_count="$( (grep -Eos 'epair[0-9]+' ${bastille_jailsdir}/*/jail.conf; ifconfig | grep -Eo '(e[0-9]+a|epair[0-9]+a)' ) | sort -u | wc -l | awk '{print $1}')" + local _bastille_if_count="$(grep -Eos 'bastille[0-9]+' ${bastille_jailsdir}/*/jail.conf | sort -u | wc -l | awk '{print $1}')" + local epair_num_range=$((_epair_if_count + 1)) + local bastille_num_range=$((_bastille_if_count + 1)) + if [ -n "${use_unique_bridge}" ]; then + if [ "${_epair_if_count}" -gt 0 ]; then + for _num in $(seq 0 "${epair_num_range}"); do + if ! grep -Eosq "epair${_num}" ${bastille_jailsdir}/*/jail.conf && ! ifconfig | grep -Eosq "(e${_num}a|epair${_num}a)"; then + if [ "$(echo -n "e${_num}a_${jail_name}" | awk '{print length}')" -lt 16 ]; then + local host_epair=e${_num}a_${jail_name} + local jail_epair=e${_num}b_${jail_name} + else + local host_epair=epair${_num}a + local jail_epair=epair${_num}b + fi + break + fi + done + else + if [ "$(echo -n "e0a_${jail_name}" | awk '{print length}')" -lt 16 ]; then + local _num=0 + local host_epair=e${_num}a_${jail_name} + local jail_epair=e${_num}b_${jail_name} + else + local _num=0 + local host_epair=epair${_num}a + local jail_epair=epair${_num}b + fi + fi + else + if [ "${_bastille_if_count}" -gt 0 ]; then + for _num in $(seq 0 "${bastille_num_range}"); do + if ! grep -Eosq "bastille${_num}" ${bastille_jailsdir}/*/jail.conf; then + local uniq_epair="bastille${_num}" + break + fi + done + else + local uniq_epair="bastille0" + fi + fi + ## If BRIDGE is enabled, generate bridge config, else generate VNET config + if [ -n "${use_unique_bridge}" ]; then + if [ -n "${static_mac}" ]; then + ## Generate bridged VNET config with static MAC address + generate_static_mac "${jail_name}" "${external_interface}" + cat <<-EOF + vnet; + vnet.interface = ${jail_epair}; + exec.prestart += "ifconfig epair${_num} create"; + exec.prestart += "ifconfig ${external_interface} addm epair${_num}a"; + exec.prestart += "ifconfig epair${_num}a up name ${host_epair}"; + exec.prestart += "ifconfig epair${_num}b up name ${jail_epair}"; + exec.prestart += "ifconfig ${host_epair} ether ${macaddr}a"; + exec.prestart += "ifconfig ${jail_epair} ether ${macaddr}b"; + exec.prestart += "ifconfig ${host_epair} description \"vnet host interface for Bastille jail ${jail_name}\""; + exec.poststop += "ifconfig ${external_interface} deletem ${host_epair}"; + exec.poststop += "ifconfig ${host_epair} destroy"; +EOF + else + ## Generate bridged VNET config without static MAC address + cat <<-EOF + vnet; + vnet.interface = ${jail_epair}; + exec.prestart += "ifconfig epair${_num} create"; + exec.prestart += "ifconfig ${external_interface} addm epair${_num}a"; + exec.prestart += "ifconfig epair${_num}a up name ${host_epair}"; + exec.prestart += "ifconfig epair${_num}b up name ${jail_epair}"; + exec.prestart += "ifconfig ${host_epair} description \"vnet host interface for Bastille jail ${jail_name}\""; + exec.poststop += "ifconfig ${external_interface} deletem ${host_epair}"; + exec.poststop += "ifconfig ${host_epair} destroy"; +EOF + fi + else + if [ -n "${static_mac}" ]; then + ## Generate VNET config with static MAC address + generate_static_mac "${jail_name}" "${external_interface}" + cat <<-EOF + vnet; + vnet.interface = e0b_${uniq_epair}; + exec.prestart += "jib addm ${uniq_epair} ${external_interface}"; + exec.prestart += "ifconfig e0a_${uniq_epair} ether ${macaddr}a"; + exec.prestart += "ifconfig e0b_${uniq_epair} ether ${macaddr}b"; + exec.prestart += "ifconfig e0a_${uniq_epair} description \"vnet host interface for Bastille jail ${jail_name}\""; + exec.poststop += "jib destroy ${uniq_epair}"; +EOF + else + ## Generate VNET config without static MAC address + cat <<-EOF + vnet; + vnet.interface = e0b_${uniq_epair}; + exec.prestart += "jib addm ${uniq_epair} ${external_interface}"; + exec.prestart += "ifconfig e0a_${uniq_epair} description \"vnet host interface for Bastille jail ${jail_name}\""; + exec.poststop += "jib destroy ${uniq_epair}"; +EOF + fi + fi +} + checkyesno() { ## copied from /etc/rc.subr -- cedwards (20231125) ## issue #368 (lowercase values should be parsed) diff --git a/usr/local/share/bastille/console.sh b/usr/local/share/bastille/console.sh index 9131b221..33851a05 100644 --- a/usr/local/share/bastille/console.sh +++ b/usr/local/share/bastille/console.sh @@ -34,30 +34,70 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille console TARGET [user]" + error_notify "Usage: bastille console [option(s)] TARGET [user]" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# 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 + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + x) enable_debug ;; + a) AUTO=1 ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -gt 1 ]; then +if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then usage fi -bastille_root_check +TARGET="${1}" +USER="${2}" -USER="${1}" +bastille_root_check +set_target_single "${TARGET}" +check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" +else + error_notify "Jail is not running." + error_exit "Use [-a|--auto] to auto-start the jail." +fi validate_user() { - if jexec -l "${_jail}" id "${USER}" >/dev/null 2>&1; then - USER_SHELL="$(jexec -l "${_jail}" getent passwd "${USER}" | cut -d: -f7)" + if jexec -l "${TARGET}" id "${USER}" >/dev/null 2>&1; then + USER_SHELL="$(jexec -l "${TARGET}" getent passwd "${USER}" | cut -d: -f7)" if [ -n "${USER_SHELL}" ]; then - if jexec -l "${_jail}" grep -qwF "${USER_SHELL}" /etc/shells; then - jexec -l "${_jail}" $LOGIN -f "${USER}" + if jexec -l "${TARGET}" grep -qwF "${USER_SHELL}" /etc/shells; then + jexec -l "${TARGET}" $LOGIN -f "${USER}" else echo "Invalid shell for user ${USER}" fi @@ -70,7 +110,7 @@ validate_user() { } check_fib() { - fib=$(grep 'exec.fib' "${bastille_jailsdir}/${_jail}/jail.conf" | awk '{print $3}' | sed 's/\;//g') + fib=$(grep 'exec.fib' "${bastille_jailsdir}/${TARGET}/jail.conf" | awk '{print $3}' | sed 's/\;//g') if [ -n "${fib}" ]; then _setfib="setfib -F ${fib}" else @@ -78,15 +118,12 @@ check_fib() { fi } -for _jail in ${JAILS}; do - info "[${_jail}]:" - LOGIN="$(jexec -l "${_jail}" which login)" - if [ -n "${USER}" ]; then - validate_user - else - check_fib - LOGIN="$(jexec -l "${_jail}" which login)" - ${_setfib} jexec -l "${_jail}" $LOGIN -f root - fi - echo -done +info "[${TARGET}]:" +LOGIN="$(jexec -l "${TARGET}" which login)" +if [ -n "${USER}" ]; then + validate_user +else + check_fib + LOGIN="$(jexec -l "${TARGET}" which login)" + ${_setfib} jexec -l "${TARGET}" $LOGIN -f root +fi diff --git a/usr/local/share/bastille/cp.sh b/usr/local/share/bastille/cp.sh index d7fc174b..0d1b53a1 100644 --- a/usr/local/share/bastille/cp.sh +++ b/usr/local/share/bastille/cp.sh @@ -34,49 +34,64 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille cp [OPTION] TARGET HOST_PATH CONTAINER_PATH" + error_notify "Usage: bastille cp [option(s)] TARGET HOST_PATH JAIL_PATH" + cat << EOF + Options: + + -q | --quiet Suppress output. + -x | --debug Enable debug mode. + +EOF + exit 1 } -CPSOURCE="${1}" -CPDEST="${2}" +# Handle options. +OPTION="-av" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -q|--quiet) + OPTION="-a" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + q) OPTION="-a" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; --q|--quiet) - OPTION="${1}" - CPSOURCE="${2}" - CPDEST="${3}" - ;; -esac - -if [ $# -ne 2 ]; then +if [ "$#" -ne 3 ]; then usage fi -bastille_root_check +TARGET="${1}" +HOST_PATH="${2}" +JAIL_PATH="${3}" -case "${OPTION}" in - -q|--quiet) - OPTION="-a" - ;; - *) - OPTION="-av" - ;; -esac +bastille_root_check +set_target "${TARGET}" for _jail in ${JAILS}; do info "[${_jail}]:" - bastille_jail_path="${bastille_jailsdir}/${_jail}/root" - cp "${OPTION}" "${CPSOURCE}" "${bastille_jail_path}/${CPDEST}" - RETURN="$?" - if [ "${TARGET}" = "ALL" ]; then - # Display the return status for reference - echo -e "Returned: ${RETURN}\n" - else - echo - return "${RETURN}" + host_path="${HOST_PATH}" + jail_path="$(echo ${bastille_jailsdir}/${_jail}/root/${JAIL_PATH} | sed 's#//#/#g')" + if ! cp "${OPTION}" "${host_path}" "${jail_path}"; then + error_continue "CP failed: ${host_path} -> ${jail_path}" fi -done +done \ No newline at end of file diff --git a/usr/local/share/bastille/create.sh b/usr/local/share/bastille/create.sh index 77169e8f..bfc62d06 100644 --- a/usr/local/share/bastille/create.sh +++ b/usr/local/share/bastille/create.sh @@ -41,12 +41,13 @@ usage() { cat << EOF Options: - -E | --empty -- Creates an empty container, intended for custom jail builds (thin/thick/linux or unsupported). - -L | --linux -- This option is intended for testing with Linux jails, this is considered experimental. - -T | --thick -- Creates a thick container, they consume more space as they are self contained and independent. - -V | --vnet -- Enables VNET, VNET containers are attached to a virtual bridge interface for connectivity. - -C | --clone -- Creates a clone container, they are duplicates of the base release, consume low space and preserves changing data. - -B | --bridge -- Enables VNET, VNET containers are attached to a specified, already existing external bridge. + -M | --static-mac -- Generate a static MAC address for jail (VNET only). + -E | --empty -- Creates an empty container, intended for custom jail builds (thin/thick/linux or unsupported). + -L | --linux -- This option is intended for testing with Linux jails, this is considered experimental. + -T | --thick -- Creates a thick container, they consume more space as they are self contained and independent. + -V | --vnet -- Enables VNET, VNET containers are attached to a virtual bridge interface for connectivity. + -C | --clone -- Creates a clone container, they are duplicates of the base release, consume low space and preserves changing data. + -B | --bridge -- Enables VNET, VNET containers are attached to a specified, already existing external bridge. EOF exit 1 @@ -67,6 +68,8 @@ validate_name() { error_exit "Container names may not begin with (-|_) characters!" elif [ "${NAME_VERIFY}" != "${NAME_SANITY}" ]; then error_exit "Container names may not contain special characters!" + elif echo "${NAME_VERIFY}" | grep -qE '^[0-9]+$'; then + error_exit "Container names may not contain only digits." fi } @@ -229,7 +232,7 @@ generate_vnet_jail_conf() { else devfs_ruleset_value=13 fi - NETBLOCK=$(generate_vnet_jail_netblock "$NAME" "${VNET_JAIL_BRIDGE}" "${bastille_jail_conf_interface}") + NETBLOCK=$(generate_vnet_jail_netblock "$NAME" "${VNET_JAIL_BRIDGE}" "${bastille_jail_conf_interface}" "${STATIC_MAC}") cat << EOF > "${bastille_jail_conf}" ${NAME} { enforce_statfs = 2; @@ -630,10 +633,15 @@ THICK_JAIL="" CLONE_JAIL="" VNET_JAIL="" LINUX_JAIL="" +STATIC_MAC="" # Handle and parse options while [ $# -gt 0 ]; do case "${1}" in + -M|--static-mac) + STATIC_MAC="1" + shift + ;; -E|--empty) EMPTY_JAIL="1" shift diff --git a/usr/local/share/bastille/etcupdate.sh b/usr/local/share/bastille/etcupdate.sh new file mode 100644 index 00000000..1f0979be --- /dev/null +++ b/usr/local/share/bastille/etcupdate.sh @@ -0,0 +1,197 @@ +#!/bin/sh +# Copyright (c) 2018-2025, Christer Edwards +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +. /usr/local/share/bastille/common.sh +. /usr/local/etc/bastille/bastille.conf + +usage() { + error_notify "Usage: bastille etcupdate [option(s)] [bootstrap|TARGET] [diff|resolve|update RELEASE]" + cat << EOF + Options: + + -d | --dry-run Show output, but do not apply. + -f | --force Force a re-bootstrap of a RELEASE. + -x | --debug Enable debug mode. + +EOF + exit 1 +} + +bootstrap_etc_release() { + local _release="${1}" + local _current="$(sysrc -f /usr/local/etc/bastille/bastille.conf bastille_bootstrap_archives | awk -F': ' '{print $2}')" + if ! ls -A "${bastille_releasesdir}/${_release}/usr/src" 2>/dev/null; then + sysrc -f /usr/local/etc/bastille/bastille.conf bastille_bootstrap_archives=src + if ! bastille bootstrap "${_release}" > /dev/null; then + sysrc -f /usr/local/etc/bastille/bastille.conf bastille_bootstrap_archives="${_current}" + error_exit "Failed to bootstrap etcupdate: ${_release}" + else + sysrc -f /usr/local/etc/bastille/bastille.conf bastille_bootstrap_archives="${_current}" + fi + fi +} + +bootstrap_etc_tarball() { + local _release="${1}" + if [ ! -f ${bastille_cachedir}/${_release}.tbz2 ]; then + echo "Building tarball, please wait..." + if ! etcupdate build -d /tmp/etcupdate -s ${bastille_releasesdir}/${_release}/usr/src ${bastille_cachedir}/${_release}.tbz2; then + error_exit "Failed to build etcupdate tarball \"${_release}.tbz2\"" + else + info "Etcupdate bootstrap complete: ${_release}" + fi + elif [ -f ${bastille_cachedir}/${_release}.tbz2 ] && [ "${FORCE}" -eq 1 ]; then + rm -f "${bastille_cachedir}/${_release}.tbz2" + echo "Building tarball, please wait..." + if ! etcupdate build -d /tmp/etcupdate -s ${bastille_releasesdir}/${_release}/usr/src ${bastille_cachedir}/${_release}.tbz2; then + error_exit "Failed to build etcupdate tarball: ${_release}.tbz2" + else + info "Etcupdate bootstrap complete: ${_release}" + fi + else + info "Etcupdate release has already been prepared for application: ${_release}" + fi +} + +diff_review() { + local _jail="${1}" + if [ "${DRY_RUN}" -eq 1 ]; then + warn "Warning: diff mode does not support [-d|--dryrun]" + fi + info "[${_jail}]: etcupdate --diff mode" + etcupdate diff -D "${bastille_jailsdir}/${_jail}/root" +} + +resolve_conflicts() { + local _jail="${1}" + if [ "${DRY_RUN}" -eq 1 ]; then + warn "Warning: resolve mode does not support [-d|--dryrun]" + fi + info "[${_jail}]: etcupdate resolve" + etcupdate resolve -D "${bastille_jailsdir}/${_jail}/root" +} + +update_jail_etc() { + local _jail="${1}" + local _release="${2}" + if [ ! -f ${bastille_cachedir}/${_release}.tbz2 ]; then + error_exit "Error: Please run \"bastille etcupdate bootstrap RELEASE\" first." + fi + if [ "${DRY_RUN}" -eq 1 ]; then + info "[${_jail}]: etcupdate update --dry-run" + etcupdate -n -D "${bastille_jailsdir}/${_jail}/root" -t ${bastille_cachedir}/${_release}.tbz2 + else + info "[${_jail}]: etcupdate update" + etcupdate -D "${bastille_jailsdir}/${_jail}/root" -t ${bastille_cachedir}/${_release}.tbz2 + fi +} + +# Handle options. +DRY_RUN=0 +FORCE=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -d|--dry-run) + DRY_RUN=1 + shift + ;; + -f|--force) + FORCE=1 + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + d) DRY_RUN=1 ;; + f) FORCE=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done + +if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then + usage +fi + +# Main commands +while [ "$#" -gt 0 ]; do + case "${1}" in + bootstrap) + if [ -z "${2}" ]; then + usage + else + RELEASE="${2}" + bootstrap_etc_release "${RELEASE}" + bootstrap_etc_tarball "${RELEASE}" + shift "$#" + fi + ;; + *) + TARGET="${1}" + ACTION="${2}" + RELEASE="${3}" + set_target_single "${TARGET}" + case "${ACTION}" in + diff) + diff_review "${TARGET}" + shift "$#" + ;; + resolve) + resolve_conflicts "${TARGET}" + shift "$#" + ;; + update) + if [ -z "${RELEASE}" ]; then + usage + else + update_jail_etc "${TARGET}" "${RELEASE}" + shift "$#" + fi + ;; + *) + error_exit "Unknown action: \"${ACTION}\"" + ;; + esac + ;; + esac +done diff --git a/usr/local/share/bastille/htop.sh b/usr/local/share/bastille/htop.sh index 4449edef..d6b108d8 100644 --- a/usr/local/share/bastille/htop.sh +++ b/usr/local/share/bastille/htop.sh @@ -34,29 +34,41 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille htop [option(s)] TARGET" + error_notify "Usage: bastille htop [option(s)] TARGET" cat << EOF Options: - -f | --force -- Start the jail if it is stopped. + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. EOF exit 1 } # Handle options. -FORCE=0 +AUTO=0 while [ "$#" -gt 0 ]; do case "${1}" in -h|--help|help) usage ;; - -f|--force) - FORCE=1 + -a|--auto) + AUTO=1 + shift + ;; + -x|--debug) + enable_debug shift ;; -*) - error_exit "Unknown option: \"${1}\"" + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" + esac + done + shift ;; *) break @@ -74,15 +86,14 @@ bastille_root_check set_target_single "${TARGET}" info "[${TARGET}]:" -check_target_is_running "${TARGET}" || if [ "${FORCE}" -eq 1 ]; then +check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then bastille start "${TARGET}" else error_notify "Jail is not running." - error_continue "Use [-f|--force] to force start the jail." + error_continue "Use [-a|--auto] to auto-start the jail." fi -bastille_jail_path="${bastille_jailsdir}/${TARGET}/root" -if [ ! -x "${bastille_jail_path}/usr/local/bin/htop" ]; then +if [ ! -x "${bastille_jailsdir}/${TARGET}/root/usr/local/bin/htop" ]; then error_notify "htop not found on ${TARGET}." elif [ -x "${bastille_jail_path}/usr/local/bin/htop" ]; then jexec -l ${TARGET} /usr/local/bin/htop diff --git a/usr/local/share/bastille/jcp.sh b/usr/local/share/bastille/jcp.sh new file mode 100644 index 00000000..fc8cf05e --- /dev/null +++ b/usr/local/share/bastille/jcp.sh @@ -0,0 +1,103 @@ +#!/bin/sh +# +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2018-2025, Christer Edwards +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +. /usr/local/share/bastille/common.sh +. /usr/local/etc/bastille/bastille.conf + +usage() { + error_notify "Usage: bastille jcp [option(s)] SOURCE_JAIL JAIL_PATH DEST_JAIL JAIL_PATH" + cat << EOF + Options: + + -q | --quiet Suppress output. + -x | --debug Enable debug mode. + +EOF + exit 1 +} + +# Handle options. +OPTION="-av" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -q|--quiet) + OPTION="-a" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + q) OPTION="-a" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done + +if [ "$#" -ne 4 ]; then + usage +fi + +SOURCE_TARGET="${1}" +SOURCE_PATH="${2}" +DEST_TARGET="${3}" +DEST_PATH="${4}" + +bastille_root_check +set_target_single "${SOURCE_TARGET}" && SOURCE_TARGET="${TARGET}" +set_target "${DEST_TARGET}" && DEST_TARGET="${JAILS}" + +for _jail in ${DEST_TARGET}; do + if [ "${_jail}" = "${SOURCE_TARGET}" ]; then + continue + else + info "[${_jail}]:" + source_path="$(echo ${bastille_jailsdir}/${SOURCE_TARGET}/root/${SOURCE_PATH} | sed 's#//#/#g')" + dest_path="$(echo ${bastille_jailsdir}/${_jail}/root/${DEST_PATH} | sed 's#//#/#g')" + if ! cp "${OPTION}" "${source_path}" "${dest_path}"; then + error_continue "JCP failed: ${source_path} -> ${dest_path}" + fi + fi +done \ No newline at end of file diff --git a/usr/local/share/bastille/list.sh b/usr/local/share/bastille/list.sh index 1c6654cc..b05f04f5 100644 --- a/usr/local/share/bastille/list.sh +++ b/usr/local/share/bastille/list.sh @@ -64,7 +64,8 @@ list_all(){ MAX_LENGTH_JID=${MAX_LENGTH_JID:-3} MAX_LENGTH_JAIL_IP=$(find ${bastille_jailsdir}/*/jail.conf -maxdepth 1 -type f -print0 2> /dev/null | xargs -r0 -P0 sed -n "s/^[ ]*ip[4,6].addr[ ]*=[ ]*\(.*\);$/\1 /p" | sed 's/\// /g' | awk '{ print length($1) }' | sort -nr | head -n 1) MAX_LENGTH_JAIL_IP=${MAX_LENGTH_JAIL_IP:-10} - MAX_LENGTH_JAIL_VNET_IP=$(find ${bastille_jailsdir}/*/jail.conf -maxdepth 1 -type f -print0 2> /dev/null | xargs -r0 -P0 grep -l "vnet;" | grep -h "ifconfig_vnet0=" "$(sed -n "s/\(.*\)jail.conf$/\1root\/etc\/rc.conf/p")" | sed -n "s/^ifconfig_vnet0=\"\(.*\)\"$/\1/p"| sed "s/\// /g" | awk '{ if ($1 ~ /^[inet|inet6]/) print length($2); else print 15 }' | sort -nr | head -n 1) + # shellcheck disable=SC2046 + MAX_LENGTH_JAIL_VNET_IP="$(find ${bastille_jailsdir}/*/jail.conf -maxdepth 1 -type f -print0 2> /dev/null | xargs -r0 -P0 grep -l "vnet;" | grep -h "ifconfig_vnet0=" $(sed -n "s/\(.*\)jail.conf$/\1root\/etc\/rc.conf/p") | sed -n "s/^ifconfig_vnet0=\"\(.*\)\"$/\1/p"| sed "s/\// /g" | awk '{ if ($1 ~ /^[inet|inet6]/) print length($2); else print 15 }' | sort -nr | head -n 1)" MAX_LENGTH_JAIL_VNET_IP=${MAX_LENGTH_JAIL_VNET_IP:-10} if [ "${MAX_LENGTH_JAIL_VNET_IP}" -gt "${MAX_LENGTH_JAIL_IP}" ]; then MAX_LENGTH_JAIL_IP=${MAX_LENGTH_JAIL_VNET_IP}; fi if [ "${MAX_LENGTH_JAIL_IP}" -lt 10 ]; then MAX_LENGTH_JAIL_IP=10; fi @@ -75,11 +76,13 @@ list_all(){ MAX_LENGTH_JAIL_PORTS=${MAX_LENGTH_JAIL_PORTS:-15} if [ "${MAX_LENGTH_JAIL_PORTS}" -lt 15 ]; then MAX_LENGTH_JAIL_PORTS=15; fi if [ "${MAX_LENGTH_JAIL_PORTS}" -gt 30 ]; then MAX_LENGTH_JAIL_PORTS=30; fi - MAX_LENGTH_JAIL_RELEASE=$(find ${bastille_jailsdir}/*/fstab -maxdepth 1 -type f -print0 2> /dev/null | xargs -r0 -P0 grep -h "/releases/.*/root/.bastille.*nullfs" | grep -hE "^USERLAND_VERSION=" "$(sed -n "s/^\(.*\) \/.*$/\1\/bin\/freebsd-version/p" | awk '!_[$0]++')" | sed "s/[\"\'\^]//g;s/ .*$//g" | sed -n "s/^USERLAND_VERSION=\(.*\)$/\1/p" | awk '{ print length($0) }' | sort -nr | head -n 1) + # shellcheck disable=SC2046 + MAX_LENGTH_JAIL_RELEASE="$(find ${bastille_jailsdir}/*/fstab -maxdepth 1 -type f -print0 2> /dev/null | xargs -r0 -P0 grep -h "/releases/.*/root/.bastille.*nullfs" | grep -hE "^USERLAND_VERSION=" $(sed -n "s/^\(.*\) \/.*$/\1\/bin\/freebsd-version/p" | awk '!_[$0]++') | sed "s/[\"\'\^]//g;s/ .*$//g" | sed -n "s/^USERLAND_VERSION=\(.*\)$/\1/p" | awk '{ print length($0) }' | sort -nr | head -n 1)" MAX_LENGTH_JAIL_RELEASE=${MAX_LENGTH_JAIL_RELEASE:-7} MAX_LENGTH_THICK_JAIL_RELEASE=$(find ${bastille_jailsdir}/*/root/bin/freebsd-version -maxdepth 1 -type f -print0 2> /dev/null | xargs -r0 -P0 grep -hE "^USERLAND_VERSION=" | sed "s/[\"\'\^]//g;s/ .*$//g" | sed -n "s/^USERLAND_VERSION=\(.*\)$/\1/p" | awk '{ print length($0) }' | sort -nr | head -n 1) MAX_LENGTH_THICK_JAIL_RELEASE=${MAX_LENGTH_THICK_JAIL_RELEASE:-7} - MAX_LENGTH_LINUX_JAIL_RELEASE=$(find ${bastille_jailsdir}/*/fstab -maxdepth 1 -type f -print0 2> /dev/null | xargs -r0 -P0 grep -h "/jails/.*/root/proc.*linprocfs" | grep -hE "^NAME=|^VERSION_ID=|^VERSION_CODENAME=" "$(sed -n "s/^linprocfs *\(.*\)\/.*$/\1\/etc\/os-release/p")" 2> /dev/null | sed "s/\"//g" | sed "s/ GNU\/Linux//g" | sed "N;N;s/\n/;/g" | sed -n "s/^NAME=\(.*\);VERSION_ID=\(.*\);VERSION_CODENAME=\(.*\)$/\1 \2 (\3)/p" | awk '{ print length($0) }' | sort -nr | head -n 1) + # shellcheck disable=SC2046 + MAX_LENGTH_LINUX_JAIL_RELEASE="$(find ${bastille_jailsdir}/*/fstab -maxdepth 1 -type f -print0 2> /dev/null | xargs -r0 -P0 grep -h "/jails/.*/root/proc.*linprocfs" | grep -hE "^NAME=|^VERSION_ID=|^VERSION_CODENAME=" $(sed -n "s/^linprocfs *\(.*\)\/.*$/\1\/etc\/os-release/p") 2> /dev/null | sed "s/\"//g" | sed "s/ GNU\/Linux//g" | sed "N;N;s/\n/;/g" | sed -n "s/^NAME=\(.*\);VERSION_ID=\(.*\);VERSION_CODENAME=\(.*\)$/\1 \2 (\3)/p" | awk '{ print length($0) }' | sort -nr | head -n 1)" MAX_LENGTH_LINUX_JAIL_RELEASE=${MAX_LENGTH_LINUX_JAIL_RELEASE:-7} if [ "${MAX_LENGTH_THICK_JAIL_RELEASE}" -gt "${MAX_LENGTH_JAIL_RELEASE}" ]; then MAX_LENGTH_JAIL_RELEASE=${MAX_LENGTH_THICK_JAIL_RELEASE}; fi if [ "${MAX_LENGTH_LINUX_JAIL_RELEASE}" -gt "${MAX_LENGTH_JAIL_RELEASE}" ]; then MAX_LENGTH_JAIL_RELEASE=${MAX_LENGTH_LINUX_JAIL_RELEASE}; fi diff --git a/usr/local/share/bastille/mount.sh b/usr/local/share/bastille/mount.sh index b2aeb438..e2c060e6 100644 --- a/usr/local/share/bastille/mount.sh +++ b/usr/local/share/bastille/mount.sh @@ -34,15 +34,24 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille mount TARGET HOST_PATH JAIL_PATH [filesystem_type options dump pass_number]" + error_exit "Usage: bastille mount [option(s)] TARGET HOST_PATH JAIL_PATH [filesystem_type options dump pass_number]" } -# Handle special-case commands first. -case "${1}" in - help|-h|--help) - usage - ;; -esac +# Handle options. +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + --*|-*) + error_notify "Unknown Option." + usage + ;; + *) + break + ;; + esac +done if [ "$#" -lt 3 ] || [ "$#" -gt 7 ]; then usage @@ -91,8 +100,8 @@ elif [ ! -e "${_hostpath}" ] || [ "${_type}" != "nullfs" ]; then usage fi -# Mount permissions,options need to start with "ro" or "rw" -if ! echo "${_perms}" | grep -Eq 'r[w|o],.*$'; then +# Mount permissions,options must include one of "ro, rw, rq, sw, xx" +if ! echo "${_perms}" | grep -Eq '(ro|rw|rq|sw|xx)(,.*)?$'; then error_notify "Detected invalid mount permissions in FSTAB." warn "Format: /host/path /jail/path nullfs ro 0 0" warn "Read: ${_fstab}" @@ -117,7 +126,7 @@ for _jail in ${JAILS}; do # Check if mount point has already been added _existing_mount="$(echo ${_fullpath_fstab} 2>/dev/null | sed 's#\\#\\\\#g')" - if grep -Eq "[[:blank:]]${_existing_mount}.*[[:blank:]]" "${bastille_jailsdir}/${_jail}/fstab"; then + if grep -Eq "[[:blank:]]${_existing_mount}[[:blank:]]" "${bastille_jailsdir}/${_jail}/fstab"; then warn "Mountpoint already present in ${bastille_jailsdir}/${_jail}/fstab" grep -E "[[:blank:]]${_existing_mount}" "${bastille_jailsdir}/${_jail}/fstab" continue diff --git a/usr/local/share/bastille/rcp.sh b/usr/local/share/bastille/rcp.sh index a3b1cda1..f3880a0f 100644 --- a/usr/local/share/bastille/rcp.sh +++ b/usr/local/share/bastille/rcp.sh @@ -34,46 +34,64 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille rcp [OPTION] TARGET CONTAINER_PATH HOST_PATH" + error_notify "Usage: bastille rcp [option(s)] TARGET JAIL_PATH HOST_PATH" + cat << EOF + Options: + + -q | --quiet Suppress output. + -x | --debug Enable debug mode. + +EOF + exit 1 } -CPSOURCE="${1}" -CPDEST="${2}" - -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; --q|--quiet) - OPTION="${1}" - CPSOURCE="${2}" - CPDEST="${3}" - ;; -esac - -if [ $# -ne 2 ]; then - usage -fi - -if [ "${TARGET}" = "ALL" ]; then - usage -fi - -case "${OPTION}" in - -q|--quiet) - OPTION="-a" - ;; - *) - OPTION="-av" - ;; -esac - -for _jail in ${JAILS}; do - info "[${_jail}]:" - bastille_jail_path="${bastille_jailsdir}/${_jail}/root" - cp "${OPTION}" "${bastille_jail_path}/${CPSOURCE}" "${CPDEST}" - RETURN="$?" - echo - return "${RETURN}" +# Handle options. +OPTION="-av" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -q|--quiet) + OPTION="-a" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + q) OPTION="-a" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac done + +if [ "$#" -ne 3 ]; then + usage +fi + +TARGET="${1}" +JAIL_PATH="${2}" +HOST_PATH="${3}" + +bastille_root_check +set_target_single "${TARGET}" + +info "[${TARGET}]:" + +host_path="${HOST_PATH}" +jail_path="$(echo ${bastille_jailsdir}/${TARGET}/root/${JAIL_PATH} | sed 's#//#/#g')" + +if ! cp "${OPTION}" "${jail_path}" "${host_path}"; then + error_exit "RCP failed: ${jail_path} -> ${host_path}" +fi diff --git a/usr/local/share/bastille/rename.sh b/usr/local/share/bastille/rename.sh index 20fb8021..7aa887b8 100644 --- a/usr/local/share/bastille/rename.sh +++ b/usr/local/share/bastille/rename.sh @@ -1,7 +1,5 @@ #!/bin/sh # -# SPDX-License-Identifier: BSD-3-Clause -# # Copyright (c) 2018-2025, Christer Edwards # All rights reserved. # @@ -34,11 +32,62 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille rename TARGET NEW_NAME" + error_notify "Usage: bastille rename [option(s)] TARGET NEW_NAME" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. + +EOF + exit 1 } +# Handle options. +AUTO=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" + esac + done + shift + ;; + *) + break + ;; + esac +done + +if [ "$#" -ne 2 ]; then + usage +fi + +TARGET="${1}" +NEWNAME="${2}" + +bastille_root_check +set_target_single "${TARGET}" +check_target_is_stopped "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${TARGET}" +else + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." +fi + validate_name() { - local NAME_VERIFY=${NEWNAME} + local NAME_VERIFY="${NEWNAME}" local NAME_SANITY="$(echo "${NAME_VERIFY}" | tr -c -d 'a-zA-Z0-9-_')" if [ -n "$(echo "${NAME_SANITY}" | awk "/^[-_].*$/" )" ]; then error_exit "Container names may not begin with (-|_) characters!" @@ -47,44 +96,64 @@ validate_name() { fi } -# Handle special-case commands first -case "$1" in -help|-h|--help) - usage - ;; -esac - -if [ $# -ne 1 ]; then - usage -fi - -bastille_root_check - -NEWNAME="${1}" - update_jailconf() { # Update jail.conf - JAIL_CONFIG="${bastille_jailsdir}/${NEWNAME}/jail.conf" - if [ -f "${JAIL_CONFIG}" ]; then - if ! grep -qw "path = ${bastille_jailsdir}/${NEWNAME}/root;" "${JAIL_CONFIG}"; then - sed -i '' "s|host.hostname.*=.*${TARGET};|host.hostname = ${NEWNAME};|" "${JAIL_CONFIG}" - sed -i '' "s|exec.consolelog.*=.*;|exec.consolelog = ${bastille_logsdir}/${NEWNAME}_console.log;|" "${JAIL_CONFIG}" - sed -i '' "s|path.*=.*;|path = ${bastille_jailsdir}/${NEWNAME}/root;|" "${JAIL_CONFIG}" - sed -i '' "s|mount.fstab.*=.*;|mount.fstab = ${bastille_jailsdir}/${NEWNAME}/fstab;|" "${JAIL_CONFIG}" - sed -i '' "s|${TARGET}.*{|${NEWNAME} {|" "${JAIL_CONFIG}" - # Rename vnet interface - sed -i '' "/vnet.interface/s|_${TARGET}\";|_${NEWNAME}\";|" "${JAIL_CONFIG}" - sed -i '' "/ifconfig/s|_${TARGET}|_${NEWNAME}|" "${JAIL_CONFIG}" + local _jail_conf="${bastille_jailsdir}/${NEWNAME}/jail.conf" + local _rc_conf="${bastille_jailsdir}/${NEWNAME}/root/etc/rc.conf" + if [ -f "${_jail_conf}" ]; then + if ! grep -qw "path = ${bastille_jailsdir}/${NEWNAME}/root;" "${_jail_conf}"; then + sed -i '' "s|host.hostname.*=.*${TARGET};|host.hostname = ${NEWNAME};|" "${_jail_conf}" + sed -i '' "s|exec.consolelog.*=.*;|exec.consolelog = ${bastille_logsdir}/${NEWNAME}_console.log;|" "${_jail_conf}" + sed -i '' "s|path.*=.*;|path = ${bastille_jailsdir}/${NEWNAME}/root;|" "${_jail_conf}" + sed -i '' "s|mount.fstab.*=.*;|mount.fstab = ${bastille_jailsdir}/${NEWNAME}/fstab;|" "${_jail_conf}" + sed -i '' "s|${TARGET}.*{|${NEWNAME} {|" "${_jail_conf}" + fi + if grep -qo "vnet;" "${_jail_conf}"; then + update_jailconf_vnet fi fi } -update_fstab() { - # Update fstab to use the new name - FSTAB_CONFIG="${bastille_jailsdir}/${NEWNAME}/fstab" - if [ -f "${FSTAB_CONFIG}" ]; then - sed -i '' "s|${bastille_jailsdir}/${TARGET}|${bastille_jailsdir}/${NEWNAME}|g" "${FSTAB_CONFIG}" - fi +update_jailconf_vnet() { + local _jail_conf="${bastille_jailsdir}/${NEWNAME}/jail.conf" + local _rc_conf="${bastille_jailsdir}/${NEWNAME}/root/etc/rc.conf" + # Change epair name (if needed) + local _if_list="$(grep -Eo 'epair[0-9]+|bastille[0-9]+' ${_jail_conf} | sort -u)" + for _if in ${_if_list}; do + if echo ${_if} | grep -Eoq 'epair[0-9]+'; then + # Check if epair name = jail name + local _epair_num="$(grep -Eo -m 1 "epair[0-9]+" "${_jail_conf}" | grep -Eo "[0-9]+")" + if grep -E "epair[0-9]+a" "${_jail_conf}" | grep -Eo "e[0-9]+a_${TARGET}"; then + local _target_host_epair="$(grep -Eo -m 1 "e[0-9]+a_${TARGET}" "${_jail_conf}")" + local _target_jail_epair="$(grep -Eo -m 1 "e[0-9]+b_${TARGET}" "${_jail_conf}")" + else + local _target_host_epair="$(grep -Eo -m 1 "epair[0-9]+a" "${_jail_conf}")" + local _target_jail_epair="$(grep -Eo -m 1 "epair[0-9]+b" "${_jail_conf}")" + fi + if [ "$(echo -n "e${_epair_num}a_${NEWNAME}" | awk '{print length}')" -lt 16 ]; then + # Generate new epair name + local _new_host_epair="e${_epair_num}a_${NEWNAME}" + local _new_jail_epair="e${_epair_num}b_${NEWNAME}" + else + local _new_host_epair="epair${_epair_num}a" + local _new_jail_epair="epair${_epair_num}b" + fi + # Replace host epair name in jail.conf + sed -i '' "s|up name ${_target_host_epair}|up name ${_new_host_epair}|g" "${_jail_conf}" + sed -i '' "s|${_target_host_epair} ether|${_new_host_epair} ether|g" "${_jail_conf}" + sed -i '' "s|deletem ${_target_host_epair}|deletem ${_new_host_epair}|g" "${_jail_conf}" + sed -i '' "s|${_target_host_epair} destroy|${_new_host_epair} destroy|g" "${_jail_conf}" + sed -i '' "s|${_target_host_epair} description|${_new_host_epair} description|g" "${_jail_conf}" + # Replace jail epair name in jail.conf + sed -i '' "s|= ${_target_jail_epair};|= ${_new_jail_epair};|g" "${_jail_conf}" + sed -i '' "s|up name ${_target_jail_epair}|up name ${_new_jail_epair}|g" "${_jail_conf}" + sed -i '' "s|${_target_jail_epair} ether|${_new_jail_epair} ether|g" "${_jail_conf}" + # Replace epair description + sed -i '' "s|vnet host interface for Bastille jail ${TARGET}|vnet host interface for Bastille jail ${NEWNAME}|g" "${_jail_conf}" + # Replace epair name in /etc/rc.conf + sed -i '' "/ifconfig/ s|${_target_jail_epair}|${_new_jail_epair}|g" "${_rc_conf}" + fi + done } change_name() { @@ -124,24 +193,27 @@ change_name() { fi fi - # Update jail configuration files accordingly + # Update jail conf files update_jailconf - update_fstab + update_fstab "${TARGET}" "${NEWNAME}" # Check exit status and notify if [ "$?" -ne 0 ]; then error_exit "An error has occurred while attempting to rename '${TARGET}'." else info "Renamed '${TARGET}' to '${NEWNAME}' successfully." + if [ "${AUTO}" -eq 1 ]; then + bastille start "${NEWNAME}" + fi fi } -## validate jail name +# Validate NEW_NAME if [ -n "${NEWNAME}" ]; then validate_name fi -## check if a jail already exists with the new name +# Check if a jail already exists with NEW_NAME if [ -d "${bastille_jailsdir}/${NEWNAME}" ]; then error_exit "Jail: ${NEWNAME} already exists." fi diff --git a/usr/local/share/bastille/top.sh b/usr/local/share/bastille/top.sh index bd82d153..ef2a8bcb 100644 --- a/usr/local/share/bastille/top.sh +++ b/usr/local/share/bastille/top.sh @@ -38,25 +38,37 @@ usage() { cat << EOF Options: - -f | --force -- Start the jail if it is stopped. + -a | --auto Auto mode. Start/stop jail(s) if required. + -x | --debug Enable debug mode. EOF exit 1 } # Handle options. -FORCE=0 +AUTO=0 while [ "$#" -gt 0 ]; do case "${1}" in -h|--help|help) usage ;; - -f|--force) - FORCE=1 + -a|--auto) + AUTO=1 + shift + ;; + -x|--debug) + enable_debug shift ;; -*) - error_exit "Unknown option: \"${1}\"" + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" + esac + done + shift ;; *) break @@ -74,10 +86,10 @@ bastille_root_check set_target_single "${TARGET}" info "[${TARGET}]:" -check_target_is_running "${TARGET}" || if [ "${FORCE}" -eq 1 ]; then +check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then bastille start "${TARGET}" else error_notify "Jail is not running." - error_continue "Use [-f|--force] to force start the jail." + error_continue "Use [-a|--auto] to auto-start the jail." fi jexec -l "${TARGET}" /usr/bin/top diff --git a/usr/local/share/bastille/update.sh b/usr/local/share/bastille/update.sh index 85d632c0..1a387b36 100644 --- a/usr/local/share/bastille/update.sh +++ b/usr/local/share/bastille/update.sh @@ -34,39 +34,62 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille update [release|container|template] | [force]" -} + error_notify "Usage: bastille update [option(s)] TARGET" + cat << EOF + Options: -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac + -a | --auto Auto mode. Start/stop jail(s) if required. + -f | --force Force update a release. + -x | --debug Enable debug mode. + +EOF + exit 1 +} if [ $# -gt 2 ] || [ $# -lt 1 ]; then usage fi -bastille_root_check +# Handle options. +OPTION="" +AUTO=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -f|--force) + OPTION="-F" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + f) OPTION="-F" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done TARGET="${1}" -OPTION="${2}" -# Handle options -case "${OPTION}" in - -f|--force) - OPTION="-F" - ;; - *) - OPTION= - ;; -esac - -# Check for unsupported actions -if [ "${TARGET}" = "ALL" ]; then - error_exit "Batch upgrade is unsupported." -fi +bastille_root_check if [ -f "/bin/midnightbsd-version" ]; then echo -e "${COLOR_RED}Not yet supported on MidnightBSD.${COLOR_RESET}" @@ -86,45 +109,64 @@ arch_check() { jail_check() { # Check if the jail is thick and is running - if [ ! "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "[${TARGET}]: Not started. See 'bastille start ${TARGET}'." - else - if grep -qw "${bastille_jailsdir}/${TARGET}/root/.bastille" "${bastille_jailsdir}/${TARGET}/fstab"; then - error_exit "${TARGET} is not a thick container." - fi + set_target_single "${TARGET}" + check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." + fi + if grep -qw "${bastille_jailsdir}/${TARGET}/root/.bastille" "${bastille_jailsdir}/${TARGET}/fstab"; then + error_notify "${TARGET} is not a thick container." + error_exit "See 'bastille update RELEASE' to update thin jails." fi } jail_update() { + local _jailname="${1}" + local _jailpath="${bastille_jailsdir}/${TARGET}/root" + local _freebsd_update_conf="${_jailpath}/etc/freebsd-update.conf" + local _workdir="${_jailpath}/var/db/freebsd-update" # Update a thick container - if [ -d "${bastille_jailsdir}/${TARGET}" ]; then - jail_check + if [ -d "${bastille_jailsdir}/${TARGET}" ]; then CURRENT_VERSION=$(/usr/sbin/jexec -l "${TARGET}" freebsd-version 2>/dev/null) if [ -z "${CURRENT_VERSION}" ]; then error_exit "Can't determine '${TARGET}' version." else - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_jailsdir}/${TARGET}/root" \ - fetch install --currently-running "${CURRENT_VERSION}" + env PAGER="/bin/cat" freebsd-update ${OPTION} \ + --not-running-from-cron \ + -j "${_jailname}" \ + -d "${_workdir}" \ + -f "${_freebsd_update_conf}" \ + fetch install fi - else - error_exit "${TARGET} not found. See 'bastille bootstrap'." fi } release_update() { + local _releasepath="${bastille_releasesdir}/${TARGET}" + local _freebsd_update_conf="${_releasepath}/etc/freebsd-update.conf" + local _workdir="${_releasepath}/var/db/freebsd-update" # Update a release base(affects child containers) - if [ -d "${bastille_releasesdir}/${TARGET}" ]; then + if [ -d "${_releasepath}" ]; then TARGET_TRIM="${TARGET}" if [ -n "${ARCH_I386}" ]; then TARGET_TRIM=$(echo "${TARGET}" | sed 's/-i386//') fi - - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_releasesdir}/${TARGET}" \ + env PAGER="/bin/cat" freebsd-update ${OPTION} \ + --not-running-from-cron \ + -b "${_releasepath}" \ + -d "${_workdir}" \ + -f "${_freebsd_update_conf}" \ fetch --currently-running "${TARGET_TRIM}" - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_releasesdir}/${TARGET}" \ + env PAGER="/bin/cat" freebsd-update ${OPTION} \ + --not-running-from-cron \ + -b "${_releasepath}" \ + -d "${_workdir}" \ + -f "${_freebsd_update_conf}" \ install --currently-running "${TARGET_TRIM}" else - error_exit "${TARGET} not found. See 'bastille bootstrap'." + error_exit "${TARGET} not found. See 'bastille bootstrap RELEASE'." fi } @@ -145,10 +187,10 @@ template_update() { templates_update() { # Update all templates _updated_templates=0 - if [ -d "${bastille_templatesdir}" ]; then - # shellcheck disable=SC2045 - for _template_path in $(ls -d "${bastille_templatesdir}"/*/*); do - if [ -d "$_template_path"/.git ]; then + if [ -d ${bastille_templatesdir} ]; then + # shellcheck disable=SC2045 + for _template_path in $(ls -d ${bastille_templatesdir}/*/*); do + if [ -d $_template_path/.git ]; then BASTILLE_TEMPLATE=$(echo "$_template_path" | awk -F / '{ print $(NF-1) "/" $NF }') template_update @@ -174,5 +216,6 @@ elif echo "${TARGET}" | grep -q "[0-9]\{2\}.[0-9]-RELEASE"; then arch_check release_update else - jail_update + jail_check + jail_update "${TARGET}" fi diff --git a/usr/local/share/bastille/upgrade.sh b/usr/local/share/bastille/upgrade.sh index 5aa06905..7df54354 100644 --- a/usr/local/share/bastille/upgrade.sh +++ b/usr/local/share/bastille/upgrade.sh @@ -34,31 +34,64 @@ . /usr/local/etc/bastille/bastille.conf usage() { - error_exit "Usage: bastille upgrade release newrelease | target newrelease | target install | [force]" + error_notify "Usage: bastille upgrade [option(s)] TARGET [NEWRELEASE|install]" + cat << EOF + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -f | --force Force upgrade a release. + -x | --debug Enable debug mode. + +EOF + exit 1 } -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -esac +# Handle options. +OPTION="" +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -f|--force) + OPTION="-F" + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + f) OPTION="-F" ;; + x) enable_debug ;; + *) error_exit "Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done -if [ $# -gt 3 ] || [ $# -lt 2 ]; then +if [ $# -lt 2 ] || [ $# -gt 3 ]; then usage fi +TARGET="${1}" +NEWRELEASE="${2}" + bastille_root_check -TARGET="$1" -NEWRELEASE="$2" -OPTION="$3" - -# Check for unsupported actions -if [ "${TARGET}" = "ALL" ]; then - error_exit "Batch upgrade is unsupported." -fi - +# Check for unsupported actions if [ -f "/bin/midnightbsd-version" ]; then echo -e "${COLOR_RED}Not yet supported on MidnightBSD.${COLOR_RESET}" exit 1 @@ -68,24 +101,14 @@ if freebsd-version | grep -qi HBSD; then error_exit "Not yet supported on HardenedBSD." fi -# Handle options -case "${OPTION}" in - -f|--force) - OPTION="-F" - ;; - *) - OPTION= - ;; -esac - jail_check() { # Check if the jail is thick and is running - if [ ! "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "[${TARGET}]: Not started. See 'bastille start ${TARGET}'." - else - if grep -qw "${bastille_jailsdir}/${TARGET}/root/.bastille" "${bastille_jailsdir}/${TARGET}/fstab"; then - error_exit "${TARGET} is not a thick container." - fi + set_target_single "${TARGET}" + check_target_is_running "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille start "${TARGET}" + else + error_notify "Jail is not running." + error_continue "Use [-a|--auto] to auto-start the jail." fi } @@ -96,60 +119,64 @@ release_check() { fi } -release_upgrade() { - # Upgrade a release - if [ -d "${bastille_releasesdir}/${TARGET}" ]; then - release_check - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_releasesdir}/${TARGET}" --currently-running "${TARGET}" -r "${NEWRELEASE}" upgrade - echo - echo -e "${COLOR_YELLOW}Please run 'bastille upgrade ${TARGET} install' to finish installing updates.${COLOR_RESET}" - else - error_exit "${TARGET} not found. See 'bastille bootstrap'." - fi -} - jail_upgrade() { - # Upgrade a thick container - if [ -d "${bastille_jailsdir}/${TARGET}" ]; then - jail_check - release_check - CURRENT_VERSION=$(jexec -l ${TARGET} freebsd-version) - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_jailsdir}/${TARGET}/root" --currently-running "${CURRENT_VERSION}" -r ${NEWRELEASE} upgrade - echo - echo -e "${COLOR_YELLOW}Please run 'bastille upgrade ${TARGET} install' to finish installing updates.${COLOR_RESET}" + local _jailname="${1}" + local _oldrelease="$(jexec -l ${TARGET} freebsd-version)" + local _newrelease="${2}" + local _jailpath="${bastille_jailsdir}/${TARGET}/root" + local _workdir="${_jailpath}/var/db/freebsd-update" + local _freebsd_update_conf="${_jailpath}/etc/freebsd-update.conf" + + jail_check + release_check + + # Upgrade a thin jail + if grep -qw "${bastille_jailsdir}/${TARGET}/root/.bastille" "${bastille_jailsdir}/${TARGET}/fstab"; then + local _oldrelease="$(grep osrelease ${bastille_jailsdir}/${TARGET}/jail.conf | awk -F"= " '{print $2}' | sed 's/;//g')" + local _newrelease="${NEWRELEASE}" + # Update "osrelease" entry inside jail.conf + sed -i '' "/.bastille/ s|${_oldrelease}|${_newrelease}|g" "${bastille_jailsdir}/${TARGET}/fstab" + # Update "fstab" entry + sed -i '' "/osrelease/ s|${_oldrelease}|${_newrelease}|g" "${bastille_jailsdir}/${TARGET}/jail.conf" + info "Upgraded ${TARGET}: ${_oldrelease} -> ${_newrelease}" + info "See 'bastille etcupdate TARGET' to update /etc/rc.conf" else - error_exit "${TARGET} not found. See 'bastille bootstrap'." + # Upgrade a thick jail + env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron \ + --currently-running "${_oldrelease}" \ + -j "${_jailname}" \ + -d "${_workdir}" \ + -f "${_freebsd_update_conf}" \ + -r "${_newrelease}" upgrade + + # Update "osrelease" entry inside jail.conf + sed -i '' "/osrelease/ s|${_oldrelease}|${_newrelease}|g" "${bastille_jailsdir}/${TARGET}/jail.conf" + echo + echo -e "${COLOR_YELLOW}Please run 'bastille upgrade ${TARGET} install', restart the jail, then run 'bastille upgrade ${TARGET} install' again to finish installing updates.${COLOR_RESET}" fi } jail_updates_install() { + local _jailname="${1}" + local _jailpath="${bastille_jailsdir}/${TARGET}/root" + local _workdir="${_jailpath}/var/db/freebsd-update" + local _freebsd_update_conf="${_jailpath}/etc/freebsd-update.conf" # Finish installing upgrade on a thick container if [ -d "${bastille_jailsdir}/${TARGET}" ]; then jail_check - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_jailsdir}/${TARGET}/root" install + env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron \ + -j "${_jailname}" \ + -d "${_workdir}" \ + -f "${_freebsd_update_conf}" \ + install else - error_exit "${TARGET} not found. See 'bastille bootstrap'." - fi -} - -release_updates_install() { - # Finish installing upgrade on a release - if [ -d "${bastille_releasesdir}/${TARGET}" ]; then - env PAGER="/bin/cat" freebsd-update ${OPTION} --not-running-from-cron -b "${bastille_releasesdir}/${TARGET}" install - else - error_exit "${TARGET} not found. See 'bastille bootstrap'." + error_exit "${TARGET} not found. See 'bastille bootstrap RELEASE'." fi } # Check what we should upgrade -if echo "${TARGET}" | grep -q "[0-9]\{2\}.[0-9]-RELEASE"; then - if [ "${NEWRELEASE}" = "install" ]; then - release_updates_install - else - release_upgrade - fi -elif [ "${NEWRELEASE}" = "install" ]; then - jail_updates_install +if [ "${NEWRELEASE}" = "install" ]; then + jail_updates_install "${TARGET}" else - jail_upgrade + jail_upgrade "${TARGET}" "${NEWRELEASE}" fi