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/usr/local/bin/bastille b/usr/local/bin/bastille index 4fdf24c8..4c4c56ec 100755 --- a/usr/local/bin/bastille +++ b/usr/local/bin/bastille @@ -164,10 +164,10 @@ version|-v|--version) help|-h|--help) usage ;; -bootstrap|console|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|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' diff --git a/usr/local/share/bastille/clone.sh b/usr/local/share/bastille/clone.sh index 428bf9c8..998f4674 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 [-f|--force]. + -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,57 +143,100 @@ 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" + 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_CONFIG} | sort -u)" + 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 | sort -u | wc -l | awk '{print $1}')" + 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 -oq "epair${_num}" ${bastille_jailsdir}/*/jail.conf; then - # Update jail.conf epair name - local uniq_epair_bridge="${_num}" - local _if_epaira="${_if}a" - local _if_epairb="${_if}b" - local _if_vnet="$(grep ${_if_epairb} "${bastille_jail_rc_conf}" | grep -Eo -m 1 "vnet[0-9]+")" - sed -i '' "s|${_if}|epair${uniq_epair_bridge}|g" "${JAIL_CONFIG}" - # If jail had a static MAC, generate one for clone - if grep ether ${JAIL_CONFIG} | grep -qoc epair${uniq_epair_bridge}; then - local external_interface="$(grep "epair${uniq_epair_bridge}" ${JAIL_CONFIG} | grep -o '[^ ]* addm' | awk '{print $1}')" - generate_static_mac "${NEWNAME}" "${external_interface}" - sed -i '' "s|epair${uniq_epair_bridge}a ether.*:.*:.*:.*:.*:.*a\";|epair${uniq_epair_bridge}a ether ${macaddr}a\";|" "${JAIL_CONFIG}" - sed -i '' "s|epair${uniq_epair_bridge}b ether.*:.*:.*:.*:.*:.*b\";|epair${uniq_epair_bridge}b ether ${macaddr}b\";|" "${JAIL_CONFIG}" + 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 - sed -i '' "s|vnet host interface for Bastille jail ${TARGET}|vnet host interface for Bastille jail ${NEWNAME}|g" "${JAIL_CONFIG}" + # 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 - sed -i '' "s|${_if_epairb}_name|epair${uniq_epair_bridge}b_name|" "${bastille_jail_rc_conf}" - if grep "vnet0" "${bastille_jail_rc_conf}" | grep -q "epair${uniq_epair_bridge}b_name"; then + 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 "${bastille_jail_rc_conf}" ifconfig_vnet0="SYNCDHCP" + sysrc -f "${_rc_conf}" ifconfig_vnet0="SYNCDHCP" else - sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="inet ${IP}" + sysrc -f "${_rc_conf}" ifconfig_vnet0="inet ${IP}" fi else - sysrc -f "${bastille_jail_rc_conf}" ifconfig_${_if_vnet}="SYNCDHCP" + sysrc -f "${_rc_conf}" ifconfig_${_jail_vnet}="SYNCDHCP" fi break fi @@ -150,26 +247,26 @@ update_jailconf_vnet() { 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} "${bastille_jail_rc_conf}" | grep -Eo -m 1 "vnet[0-9]+")" - sed -i '' "s|${_if}|${uniq_epair}|g" "${JAIL_CONFIG}" + 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_CONFIG} | grep -qoc ${uniq_epair}; then - local external_interface="$(grep ${uniq_epair} ${JAIL_CONFIG} | grep -o 'addm.*' | awk '{print $3}' | sed 's/["|;]//g')" + 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_CONFIG}" - sed -i '' "s|${uniq_epair} ether.*:.*:.*:.*:.*:.*b\";|${uniq_epair} ether ${macaddr}b\";|" "${JAIL_CONFIG}" + 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_CONFIG}" + 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|" "${bastille_jail_rc_conf}" - if grep "vnet0" "${bastille_jail_rc_conf}" | grep -q ${uniq_epair}; then + 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 "${bastille_jail_rc_conf}" ifconfig_vnet0="SYNCDHCP" + sysrc -f "${_rc_conf}" ifconfig_vnet0="SYNCDHCP" else - sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0=" inet ${IP} " + sysrc -f "${_rc_conf}" ifconfig_vnet0=" inet ${IP} " fi else - sysrc -f "${bastille_jail_rc_conf}" ifconfig_${_if_vnet}="SYNCDHCP" + sysrc -f "${_rc_conf}" ifconfig_${_if_vnet}="SYNCDHCP" fi break fi @@ -178,20 +275,21 @@ update_jailconf_vnet() { done } -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) @@ -207,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 @@ -222,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 @@ -230,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 c2017841..fd62d6e2 100644 --- a/usr/local/share/bastille/common.sh +++ b/usr/local/share/bastille/common.sh @@ -213,9 +213,7 @@ target_all_jails() { export JAILS } -# Moving fstab function to common.sh -# Not in use yet, so keeping the name different -update_fstab_new() { +update_fstab() { local _oldname="${1}" local _newname="${2}" local _fstab="${bastille_jailsdir}/${_newname}/fstab" @@ -249,14 +247,14 @@ generate_vnet_jail_netblock() { ## 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 | sort -u | wc -l | awk '{print $1}')" + 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; then + 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} @@ -367,4 +365,5 @@ checkyesno() { return 1 ;; esac -} \ No newline at end of file +} + diff --git a/usr/local/share/bastille/mount.sh b/usr/local/share/bastille/mount.sh index 8aa3cb78..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/rename.sh b/usr/local/share/bastille/rename.sh index 20fb8021..52e206f7 100644 --- a/usr/local/share/bastille/rename.sh +++ b/usr/local/share/bastille/rename.sh @@ -1,8 +1,6 @@ #!/bin/sh # -# SPDX-License-Identifier: BSD-3-Clause -# -# Copyright (c) 2018-2025, Christer Edwards +# Copyright (c) 2018-2024, Christer Edwards # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -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/setup.sh b/usr/local/share/bastille/setup.sh index 020d2cf4..8e609d95 100644 --- a/usr/local/share/bastille/setup.sh +++ b/usr/local/share/bastille/setup.sh @@ -1,7 +1,5 @@ #!/bin/sh # -# SPDX-License-Identifier: BSD-3-Clause -# # Copyright (c) 2018-2025, Christer Edwards # All rights reserved. # @@ -30,41 +28,488 @@ # 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. -bastille_config="/usr/local/etc/bastille/bastille.conf" +# Let's set some predefined/fallback variables. +bastille_config_path="/usr/local/etc/bastille" +bastille_config="${bastille_config_path}/bastille.conf" +bastille_prefix_default="/usr/local/bastille" +bastille_zfsprefix_default="bastille" +bastille_ifbridge_name="bastille1" + . /usr/local/share/bastille/common.sh # shellcheck source=/usr/local/etc/bastille/bastille.conf . ${bastille_config} usage() { - error_exit "Usage: bastille setup [pf|network|zfs|vnet]" + # Build an independent usage for the `setup` command. + # No short options here for the special purpose --long-options, + # so we can reserve short options for future adds, also the user + # must genuinely agreed on configuration reset/restore so let them type for it. + error_notify "Usage: bastille setup [option]" + + cat << EOF + Options: + + -p | --firewall -- Attempt to configure bastille PF firewall. + -n | --network -- Attempt to configure network loopback interface. + -e | --ethernet -- Attempt to configure the network shared interface. + -v | --vnet -- Attempt to configure VNET bridge interface [bastille1]. + -z | --zfs -- Activates ZFS storage features and benefits for bastille. + --conf-network-reset -- Restore bastille default Network options on the config file. + --conf-storage-reset -- Restore bastille default ZFS storage options on the config file. + --conf-restore-clean -- Restore bastille default config file from bastille.conf.sample file. + +EOF + exit 1 } -# Check for too many args +input_error() { + error_exit "Invalid user input, aborting!" +} + +config_runtime() { + # Run here variables considered to be required by bastille by default silently. + if ! sysrc -qn bastille_enable | grep -qi "yes"; then + sysrc bastille_enable="YES" >/dev/null 2>&1 + fi +} + +# Check for too many args. if [ $# -gt 1 ]; then usage fi -# Configure bastille loopback network interface -configure_network() { - info "Configuring ${bastille_network_loopback} loopback interface" - sysrc cloned_interfaces+=lo1 - sysrc ifconfig_lo1_name="${bastille_network_loopback}" +# Handle special-case commands first. +case "${1}" in + help|--help|-h) + usage + ;; +esac - info "Bringing up new interface: ${bastille_network_loopback}" +user_canceled() { + # Don't use 'error_exit' here as this only should inform the user, not panic them. + info "Cancelled by user, exiting!" + exit 1 +} + +config_backup() { + # Create bastille configuration backup with system time appended. + # This should be called each time `bastille setup` attempts to + # write to bastille configuration file. + BACKUP_DATE=$(date +%Y%m%d-%H%M%S) + cp "${bastille_config}" "${bastille_config}.${BACKUP_DATE}" + BACKUP_NAME="${bastille_config}.${BACKUP_DATE}" + info "Config backup created in: [${BACKUP_NAME}]" +} + +config_network_reset() { + # Restore bastille default network options. + warn "Performing Network configuration reset, requested by the user..." + warn "Do you really want to reset 'bastille' network configuration? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + config_backup + local VAR_ITEMS="bastille_network_loopback=bastille0 bastille_network_pf_ext_if=ext_if + bastille_network_pf_table=jails bastille_network_shared= bastille_network_gateway= bastille_network_gateway6=" + for _item in ${VAR_ITEMS}; do + sysrc -f "${bastille_config}" ${_item} + done + info "Network configuration has been reset successfully!" + exit 0 + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac +} + +config_storage_reset() { + # Restore bastille default ZFS storage options. + warn "Performing ZFS configuration reset, requested by the user..." + warn "Do you really want to reset 'bastille' ZFS configuration? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + config_backup + local VAR_ITEMS="bastille_zfs_enable= bastille_zfs_zpool= bastille_zfs_prefix=bastille" + for _item in ${VAR_ITEMS}; do + sysrc -f "${bastille_config}" ${_item} + done + + # Let's configure variables with complex values individually to keep it simple/readable for everyone. + sysrc -f "${bastille_config}" bastille_zfs_options="-o compress=lz4 -o atime=off" + sysrc -f "${bastille_config}" bastille_prefix="${bastille_prefix_default}" + info "ZFS configuration has been reset successfully!" + exit 0 + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac +} + +config_restore_global() { + local _response + # This will restore bastille default configuration file from the sample config file. + # Be aware that if the sample configuration file is missing, we can generate a new one, + # but that's highly unlikely to happen so will keep the code smaller here. + warn "Performing Bastille default configuration restore, requested by the user..." + warn "Do you really want to restore 'bastille' default configuration file and start over? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + config_backup + if [ -f "${bastille_config}.sample" ]; then + mv "${bastille_config}" "${bastille_config}.${BACKUP_DATE}" + cp "${bastille_config}.sample" "${bastille_config}" + else + error_exit "Bastille sample configuration file is missing, exiting." + fi + info "Bastille configuration file restored successfully!" + exit 0 + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac +} + +get_zfs_params() { + info "Reading on-disk and bastille ZFS config parameters..." + # Always try to detect and recover on-disk ZFS bastille configuration first. + # Bastille ZFS prefix is always set to "bastille" in the config file by default, + # so will keep things simple here, or considered custom setup if this variable is changed. + BARTILLE_ROOTFS=$(mount | awk '/ \/ / {print $1}') + BASTILLE_UFSBOOT= + BASTILLE_ZFSPOOL= + BASTILLE_PREFIXDEF= + BASTILLE_ZFSENABLE= + BASTILLE_PREFIX_MATCH= + + # Check if the system boots from ZFS. + if echo "${BARTILLE_ROOTFS}" | grep -q -m 1 -E "^/dev/"; then + # Assume the host is running from UFS. + info "This system doesn't boot from ZFS, looking for alternate configuration." + BASTILLE_UFSBOOT="1" + fi + + BASTILLE_PREFIXCONF=$(sysrc -qn -f "${bastille_config}" bastille_prefix) + BASTILLE_PREFIXZFS=$(sysrc -qn -f "${bastille_config}" bastille_zfs_prefix) + + if [ -z "${BASTILLE_PREFIXZFS}" ]; then + BASTILLE_PREFIXZFS="${bastille_zfsprefix_default}" + fi + + if [ -z "${BASTILLE_UFSBOOT}" ]; then + if [ "${BASTILLE_PREFIXZFS}" != "${bastille_zfsprefix_default}" ]; then + BASTILLE_CUSTOM_CONFIG="1" + fi + fi + + # Try to determine "zroot" pool name as it may happens that the user + # customized the "zroot" pool name during the initial FreeBSD installation. + if [ -z "${BASTILLE_UFSBOOT}" ]; then + #BASTILLE_ZFSPOOL=$(df ${bastille_config_path} 2>/dev/null | sed 1d | awk -F '/' '{print $1}') + BASTILLE_ZFSPOOL=$(zfs list -H ${bastille_config_path} 2>/dev/null | awk -F '/' '{print $1}') + fi + + if [ -z "${BASTILLE_UFSBOOT}" ]; then + BASTILLE_PREFIXDEF=$(zfs list -H "${BASTILLE_ZFSPOOL}/${BASTILLE_PREFIXZFS}" 2>/dev/null | awk '{print $5}') + fi + + if [ -n "${BASTILLE_UFSBOOT}" ]; then + # Make sure bastille_prefix is listed by ZFS then try to get bastille_zfs_pool from it. + # Make some additional checks for non ZFS boot systems, also rely on some 'bastille.conf' ZFS parameters. + BASTILLE_PREFIXLOOK=$(zfs list -H "${BASTILLE_PREFIXCONF}" 2>/dev/null | awk '{print $1}') + BASTILLE_ZFSPOOL=$(zfs list -H "${BASTILLE_PREFIXLOOK}" 2>/dev/null | awk -F '/' '{print $1}') + BASTILLE_PREFIXDEF=$(zfs list -H "${BASTILLE_PREFIXCONF}" 2>/dev/null | awk '{print $5}') + + else + # Fallback to default config. + if [ -z "${BASTILLE_PREFIXDEF}" ]; then + BASTILLE_PREFIXDEF="${bastille_prefix_default}" + fi + fi + + if [ "${BASTILLE_PREFIXDEF}" = "${BASTILLE_PREFIXCONF}" ]; then + BASTILLE_PREFIX_MATCH="1" + fi + + # Update 'bastille_prefix' if a custom dataset is detected while reading on-disk configuration. + if [ ! -d "${bastille_prefix}" ] || [ -n "${ZFS_DATASET_DETECT}" ] || [ -n "${BASTILLE_PREFIXDEF}" ]; then + BASTILLE_ZFSENABLE="YES" + bastille_prefix="${BASTILLE_PREFIXDEF}" + else + BASTILLE_ZFSENABLE="NO" + if [ -z "${BASTILLE_UFSBOOT}" ]; then + BASTILLE_PREFIXZFS="" + fi + fi +} + +config_validation(){ + # Perform a basic bastille ZFS configuration check, + if [ -d "${bastille_prefix}" ] && [ -n "${BASTILLE_PREFIX_MATCH}" ] && echo "${bastille_zfs_enable}" | grep -qi "yes" \ + && zfs list "${bastille_zfs_zpool}/${bastille_zfs_prefix}" >/dev/null 2>&1; then + info "Looks like Bastille ZFS storage features has been activated successfully!." + exit 0 + else + if [ ! -d "${bastille_prefix}" ] && [ -z "${BASTILLE_ZFSPOOL}" ]; then + zfs_initial_activation + else + if ! echo "${bastille_zfs_enable}" | grep -qi "no"; then + + # Inform the user bastille ZFS configuration has been tampered and/or on-disk ZFS config has changed. + error_exit "Bastille ZFS misconfiguration detected, please refer to 'bastille.conf' or see 'bastille setup --config-reset'." + fi + fi + fi +} + +show_zfs_params() { + # Show a brief info of the detected and/or pending bastille ZFS configuration parameters. + # Don't need to show bastille zfs enable as this will be enabled by default. + info "*************************************" + info "Bastille Storage Prefix: [${BASTILLE_PREFIXDEF}]" + info "Bastille ZFS Pool: [${BASTILLE_ZFSPOOL}]" + info "Bastille ZFS Prefix: [${BASTILLE_PREFIXZFS}]" + info "*************************************" +} + +write_zfs_opts() { + # Write/update to bastille config file the required and/or misssing parameters. + if [ -z "${bastille_prefix}" ] || [ "${BASTILLE_PREFIXDEF}" != "${bastille_prefix_default}" ]; then + if [ -z "${BASTILLE_PREFIX_MATCH}" ]; then + sysrc -f "${bastille_config}" bastille_prefix="${BASTILLE_PREFIXDEF}" + fi + else + if [ -z "${BASTILLE_PREFIXCONF}" ] && [ -n "${BASTILLE_PREFIXDEF}" ]; then + sysrc -f "${bastille_config}" bastille_prefix="${BASTILLE_PREFIXDEF}" + fi + fi + + if [ -z "${bastille_zfs_enable}" ]; then + sysrc -f "${bastille_config}" bastille_zfs_enable="${BASTILLE_ZFSENABLE}" + fi + if [ -z "${bastille_zfs_zpool}" ]; then + sysrc -f "${bastille_config}" bastille_zfs_zpool="${BASTILLE_ZFSPOOL}" + fi + if [ -z "${bastille_zfs_prefix}" ] || [ "${BASTILLE_PREFIXDEF}" != "${bastille_zfs_prefix}" ]; then + sysrc -f "${bastille_config}" bastille_zfs_prefix="${BASTILLE_PREFIXZFS}" + fi + info "ZFS has been enabled in bastille configuration successfully!" +} + +create_zfs_dataset(){ + info "Creating ZFS dataset [${BASTILLE_ZFSPOOL}/${BASTILLE_PREFIXZFS}] for bastille..." + + if [ -n "${BASTILLE_CONFIG_USER}" ]; then + bastille_prefix="${BASTILLE_PREFIXDEF}" + fi + + # shellcheck disable=SC1073 + if zfs list "${BASTILLE_ZFSPOOL}/${BASTILLE_PREFIXZFS}" >/dev/null 2>&1; then + info "Dataset ${BASTILLE_ZFSPOOL}/${BASTILLE_PREFIXZFS} already exist, skipping." + else + if ! zfs create -p "${bastille_zfs_options}" -o mountpoint="${bastille_prefix}" "${BASTILLE_ZFSPOOL}/${BASTILLE_PREFIXZFS}"; then + error_exit "Failed to create 'bastille_prefix' dataset, exiting." + fi + fi + chmod 0750 "${bastille_prefix}" + info "Bastille ZFS storage features has been activated successfully!" + exit 0 +} + +write_zfs_disable() { + # Explicitly disable ZFS in 'bastille_zfs_enable' + sysrc -f "${bastille_config}" bastille_zfs_enable="NO" + info "ZFS has been disabled in bastille configuration successfully!" +} + +write_zfs_enable() { + # Explicitly enable ZFS in 'bastille_zfs_enable' + # Just empty the 'bastille_zfs_enable' variable so the user can re-run the ZFS activation helper. + # Don't put "YES" here as it will trigger the ZFS validation and failing due missing and/or invalid configuration. + sysrc -f "${bastille_config}" bastille_zfs_enable="" + info "ZFS activation helper enabled!" +} + +zfs_initial_activation() { + local _response= + + # Just let the user interactively select the ZFS items manually from a list for the initial activation. + # This should be performed before `bastille bootstrap` as we already know. + info "Initial bastille ZFS activation helper invoked." + info "Would you like to configure the bastille ZFS options interactively? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # Assume the user knows what hes/she doing and want to configure ZFS parameters interactively. + configure_zfs_manually + ;; + [Nn]|[Nn][Oo]) + # Assume the user will manually edit the ZFS parameters in the config file. + user_canceled + ;; + *) + input_error + ;; + esac +} + +configure_ethernet() { + # This will attempt to configure the physical ethernet interface for 'bastille_network_shared', + # commonly used with shared IP jails and/or simple jail network configurations. + + local ETHIF_COUNT="0" + local _ethernet_choice= + local _ethernet_select= + local _response= + + # Try to get a list of the available physical network/ethernet interfaces. + local ETHERNET_PHY_ADAPTERS="$(pciconf -lv | grep 'ethernet' -B4 | grep 'class=0x020000' | awk -F '@' '{print $1}')" + if [ -z "${ETHERNET_PHY_ADAPTERS}" ]; then + error_exit "Unable to detect for any physical ethernet interfaces, exiting." + fi + + info "This will attempt to configure the physical ethernet interface for [bastille_network_shared]." + warn "Would you like to configure the physical ethernet interface now? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # shellcheck disable=SC2104 + break + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + + info "Listing available physical ethernet interfaces..." + for _ethernetif in ${ETHERNET_PHY_ADAPTERS}; do + echo "[${ETHIF_COUNT}] ${_ethernetif}" + ETHIF_NUM="${ETHIF_NUM} [${ETHIF_COUNT}]${_ethernetif}" + ETHIF_COUNT=$(expr ${ETHIF_COUNT} + 1) + done + + info "Please select the wanted physical ethernet adapter [NUM] to be used as 'bastille_network_shared': " + read _ethernet_choice + if ! echo "${_ethernet_choice}" | grep -Eq "^[0-9]{1,3}$"; then + error_exit "Invalid input number, aborting!" + else + _ethernet_select=$(echo "${ETHIF_NUM}" | grep -wo "\[${_ethernet_choice}\][^ ]*" | sed 's/\[.*\]//g') + # If the user is unsure here, just abort as no input validation will be performed after. + if [ -z "${_ethernet_select}" ]; then + error_exit "No physical ethernet interface selected, aborting!" + else + info "Selected physical ethernet interface: [${_ethernet_select}]" + # Ask again to make sure the user is confident with the election. + warn "Are you sure '${_ethernet_select}' is the correct physical ethernet interface [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + if ! sysrc -f "${bastille_config}" bastille_network_shared | grep -qi "${_ethernet_select}"; then + config_backup + sysrc -f "${bastille_config}" bastille_network_shared="${_ethernet_select}" + fi + exit 0 + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + fi + fi +} + +configure_network() { + local _response + + # Configure bastille loopback network interface. + # This is an initial attempt to make this function interactive, + # however this may be enhanced in the future by advanced contributors in this topic. + info "This will attempt to configure the loopback network interface [${bastille_network_loopback}]." + warn "Would you like to configure the loopback network interface now? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # shellcheck disable=SC2104 + break + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + + info "Configuring ${bastille_network_loopback} loopback interface..." + if ! sysrc -qn cloned_interfaces | grep -qi "lo1"; then + sysrc cloned_interfaces+="lo1" + fi + if ! sysrc -qn ifconfig_lo1_name | grep -qi "${bastille_network_loopback}"; then + sysrc ifconfig_lo1_name="${bastille_network_loopback}" + fi + + info "Bringing up new interface: ${bastille_network_loopback}..." service netif cloneup } configure_vnet() { - info "Configuring bridge interface" - sysrc cloned_interfaces+=bridge1 - sysrc ifconfig_bridge1_name=bastille1 + local _response - info "Bringing up new interface: bastille1" + # This is an initial attempt to make this function interactive, + # however this may be enhanced in the future by advanced contributors in this topic. + info "This will attempt to configure the VNET bridge interface [${bastille_ifbridge_name}]." + warn "Would you like to configure the VNET bridge interface now? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # shellcheck disable=SC2104 + break + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + + info "Configuring bridge interface [${bastille_ifbridge_name}]..." + + if ! sysrc -qn cloned_interfaces | grep -qi "${bastille_ifbridge_name}"; then + sysrc cloned_interfaces+="${bastille_ifbridge_name}" + fi + if ! sysrc -qn ifconfig_bridge1_name | grep -qi "${bastille_ifbridge_name}"; then + sysrc ifconfig_bridge1_name="${bastille_ifbridge_name}" + fi + + info "Bringing up new interface: ${bastille_ifbridge_name}..." service netif cloneup - if [ ! -f /etc/devfs.rules ]; then - info "Creating bastille_vnet devfs.rules" + if [ ! -f "/etc/devfs.rules" ]; then + info "Creating bastille_vnet devfs.rules..." cat << EOF > /etc/devfs.rules +# Auto-generated file from `bastille setup` +# devfs configuration information + [bastille_vnet=13] add include \$devfsrules_hide_all add include \$devfsrules_unhide_basic @@ -73,22 +518,48 @@ add include \$devfsrules_jail add include \$devfsrules_jail_vnet add path 'bpf*' unhide EOF + else + warn "File [/etc/devfs.rules] already exist, skipping." + exit 1 fi + exit 0 } -# Configure pf firewall configure_pf() { -# shellcheck disable=SC2154 -if [ ! -f "${bastille_pf_conf}" ]; then - # shellcheck disable=SC3043 - local ext_if - ext_if=$(netstat -rn | awk '/default/ {print $4}' | head -n1) - info "Determined default network interface: ($ext_if)" - info "${bastille_pf_conf} does not exist: creating..." + local _response + + # Configure the PF firewall. + # This is an initial attempt to make this function interactive, + # however this may be enhanced in the future by advanced contributors in this topic. + info "This will attempt to configure the PF firewall parameters in [${bastille_pf_conf}]." + warn "Would you like to configure the PF firewall parameters now? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # shellcheck disable=SC2104 + break + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + + # shellcheck disable=SC2154 + if [ ! -f "${bastille_pf_conf}" ]; then + # shellcheck disable=SC3043 + local ext_if + ext_if=$(netstat -rn | awk '/default/ {print $4}' | head -n1) + info "Determined default network interface: ($ext_if)" + info "${bastille_pf_conf} does not exist: creating..." + + # Creating pf.conf file. + cat << EOF > "${bastille_pf_conf}" +# Auto-generated file from `bastille setup` +# packet filter configuration file - ## creating pf.conf - cat << EOF > "${bastille_pf_conf}" -## generated by bastille setup ext_if="$ext_if" set block-policy return @@ -104,58 +575,417 @@ pass out quick keep state antispoof for \$ext_if inet pass in inet proto tcp from any to any port ssh flags S/SA keep state EOF - sysrc pf_enable=YES - warn "pf ruleset created, please review ${bastille_pf_conf} and enable it using 'service pf start'." -else - error_exit "${bastille_pf_conf} already exists. Exiting." -fi + + if ! sysrc -qn pf_enable | grep -qi "yes"; then + sysrc pf_enable="YES" + fi + warn "The pf ruleset file has been created, please review '${bastille_pf_conf}' and enable it using 'service pf start'." + else + warn "${bastille_pf_conf} already exists, skipping." + exit 1 + fi + exit 0 } -# Configure ZFS configure_zfs() { - if [ ! "$(kldstat -m zfs)" ]; then - info "ZFS module not loaded; skipping..." + # Attempt to detect and setup either new or an existing bastille ZFS on-disk configuration. + # This is useful for new users to easily activate the bastille ZFS parameters on a standard installation, + # or to recover an existing on-disk ZFS bastille configuration in case the config file has been borked/reset by the user, + # also a config backup will be created each time the config needs to be modified in the following format: bastille.conf.YYYYMMDD-HHMMSS + # Be aware that the users now need to explicitly enable ZFS in the config file due later config file changes, failing to do so + # before initial `bastille bootstrap` will private the user from activating ZFS storage features without manual intervention. + + ZFS_DATASET_DETECT= + BASTILLE_CUSTOM_CONFIG= + local _response= + + if ! kldstat -qm zfs; then + warn "Looks like the ZFS module is not loaded." + warn "If this is not a dedicated ZFS system you can ignore this warning." + exit 1 else - ## attempt to determine bastille_zroot from `zpool list` - bastille_zroot=$(zpool list | grep -v NAME | awk '{print $1}') - if [ "$(echo "${bastille_zroot}" | wc -l)" -gt 1 ]; then - error_notify "Error: Multiple ZFS pools available:\n${bastille_zroot}" - error_notify "Set desired pool using \"sysrc -f ${bastille_config} bastille_zfs_zpool=ZPOOL_NAME\"" - error_exit "Don't forget to also enable ZFS using \"sysrc -f ${bastille_config} bastille_zfs_enable=YES\"" + # If the below statement becomes true, will assume that the user do not want ZFS activation at all regardless of the + # host filesystem, or the default configuration file has been changed officially and set to "NO" by default. + if echo "${bastille_zfs_enable}" | grep -qi "no"; then + info "Looks like Bastille ZFS has been disabled in 'bastille.conf', ZFS activation helper disabled." + warn "Would you like to enable the ZFS activation helper now? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # Assume the user wants to configure the ZFS parameters. + if config_backup; then + write_zfs_enable + warn "Please run 'bastille setup' again or consult bastille.conf for further configuration." + exit 0 + else + error_exit "Config backup creation failed, exiting." + fi + ;; + [Nn]|[Nn][Oo]) + # Assume the user will manually configure the ZFS parameters, or skip ZFS configuration. + user_canceled + ;; + esac + else + # Attempt to detect if bastille was installed with sane defaults(ports/pkg) and hasn't been bootstrapped yet, + # then offer the user initial ZFS activation option to gain all of the ZFS storage features and benefits. + # This should be performed before `bastille` initial bootstrap because several ZFS datasets will be + # created/configured during the bootstrap process by default. + get_zfs_params + if [ ! -d "${bastille_prefix}" ] && [ -n "${BASTILLE_ZFSPOOL}" ]; then + if [ "${bastille_prefix}" = "${bastille_prefix_default}" ] && [ -z "${BASTILLE_CUSTOM_CONFIG}" ]; then + show_zfs_params + info "Looks like bastille has been installed and hasn't been bootstrapped yet." + warn "Would you like to activate ZFS now to get the features and benefits? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + if [ -n "${BASTILLE_ZFSPOOL}" ]; then + info "Attempting to create a backup file of the current bastille.conf file..." + if config_backup; then + write_zfs_opts + create_zfs_dataset + else + error_exit "Config backup creation failed, exiting." + fi + else + error_exit "Unable to determine the [zroot] pool name, exiting" + fi + ;; + [Nn]|[Nn][Oo]) + info "Looks like you cancelled the ZFS activation." + # Offer the user option to disable ZFS in the configuration file. + # Maybe the user wants to use UFS or ZFS with legacy directories instead. + warn "Would you like to explicitly disable ZFS in the configuration file? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + if config_backup; then + # Assume the user want to skip ZFS configuration regardless. + write_zfs_disable + exit 0 + else + error_exit "Config backup creation failed, exiting." + fi + ;; + [Nn]|[Nn][Oo]) + # Assume the user will manually configure the ZFS parameters by itself. + user_canceled + ;; + *) + input_error + ;; + esac + ;; + *) + input_error + ;; + esac + else + config_validation + fi + else + if [ -d "${bastille_prefix}" ] && [ -z "${bastille_zfs_enable}" ] && [ -z "${bastille_zfs_zpool}" ] && [ -z "${BASTILLE_CUSTOM_CONFIG}" ] && [ -z "${BASTILLE_UFSBOOT}" ]; then + show_zfs_params + # This section is handy if the user has reset the bastille configuration file after a successful ZFS activation. + info "Looks like bastille has been bootstrapped already, but ZFS options are not configured." + info "Attempting to configure default ZFS options for you..." + if zfs list | grep -qw "${bastille_prefix}"; then + ZFS_DATASET_DETECT="1" + warn "Would you like to auto-configure the detected ZFS parameters now? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + if config_backup; then + write_zfs_opts + exit 0 + else + error_exit "Config backup creation failed, exiting." + fi + ;; + [Nn]|[Nn][Oo]) + # Assume the user will manually configure the ZFS parameters by itself. + user_canceled + ;; + *) + input_error + ;; + esac + else + if [ -d "${bastille_prefix}" ]; then + if [ ! "$(ls -A ${bastille_prefix})" ]; then + if ! zfs list | grep -qw "${bastille_prefix}"; then + # If the user want to use ZFS he/she need to remove/rename the existing 'bastille_prefix' directory manually. + # We do not want to cause existing data lost at all due end-user errors. + warn "Looks like bastille prefix is not a ZFS dataset, thus ZFS storage options are not required." + warn "Please refer to 'bastille.conf' and/or verify for alreay existing 'bastille_prefix' directory." + warn "Would you like to explicitly disable ZFS in the configuration file so we don't ask again? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + if config_backup; then + write_zfs_disable + exit 0 + else + error_exit "Config backup creation failed, exiting." + fi + ;; + [Nn]|[Nn][Oo]) + # Assume the user will manually configure the ZFS parameters by itself. + user_canceled + ;; + *) + input_error + ;; + esac + fi + else + error_exit "Looks like 'bastille_prefix' is not a ZFS dataset and is not empty, aborting." + fi + fi + fi + fi + if [ -n "${BASTILLE_CUSTOM_CONFIG}" ]; then + # Attempt to detect an existing on-disk bastille ZFS configuration and let the user interactively select the items manually from a list. + # This should be performed if the user has borked/reset the config file or in the event the setup detected an unusual/customized bastille install. + warn "A custom bastille ZFS configuration has been detected and/or unable to read ZFS configuration properly." + warn "Please refer to 'bastille.conf' config file and/or 'bastille setup -help' for additional info." + zfs_initial_activation + else + config_validation + fi + fi fi - sysrc -f "${bastille_config}" bastille_zfs_enable=YES - sysrc -f "${bastille_config}" bastille_zfs_zpool="${bastille_zroot}" fi } -# Run all base functions (w/o vnet) if no args -if [ $# -eq 0 ]; then - sysrc bastille_enable=YES - configure_network - configure_pf - configure_zfs -fi +configure_zfs_manually() { + BASTILLE_CONFIG_USER= + local ZFSPOOL_COUNT="0" + local ZFSDATA_COUNT="0" + local MPREFIX_COUNT="0" + local _zfsprefix_trim= + local _zfspool_choice= + local _zfspool_select= + local _zfsprefix_choice= + local _zfsprefix_select= + local _zfsmount_choice= + local _zfsmount_select= + local _response= -# Handle special-case commands first. -case "$1" in -help|-h|--help) - usage - ;; -pf|firewall) - configure_pf - ;; -bastille0) - # TODO remove in future release 0.13 - warn "'bastille setup bastille0' will be deprecated in the next 0.13 version." - configure_network - ;; -network|loopback) - configure_network - ;; -zfs|storage) - configure_zfs - ;; -bastille1|vnet|bridge) - configure_vnet - ;; + info "Would you like to configure the ZFS parameters entirely by hand? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # We will assume the user knows what hes/she doing and want to configure ZFS parameters entirely by hand. + warn "Please enter the desired ZFS pool for bastille: " + read _zfspool_select + warn "Please enter the ZFS dataset for bastille: " + read _zfsprefix_select + warn "Please enter the ZFS mountpoint for bastille: " + read _zfsmount_select + + # Set the parameters and show the user a preview. + BASTILLE_PREFIXDEF="${_zfsmount_select}" + BASTILLE_ZFSPOOL="${_zfspool_select}" + BASTILLE_PREFIXZFS="${_zfsprefix_select}" + show_zfs_params + + # Ask again to make sure the user is confident with the entered parameters. + warn "Are you sure the above bastille ZFS configuration is correct?" + warn "Once bastille is activated it can't be easily undone, do you really want to activate ZFS now? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + BASTILLE_CONFIG_USER="1" + write_zfs_opts + create_zfs_dataset + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + ;; + [Nn]|[Nn][Oo]) + # shellcheck disable=SC2104 + break + ;; + *) + input_error + ;; + esac + + # Ask here several times as we want the user to be really sure of what they doing, + # We do not want to cause existing data lost at all due end-user errors. + info "Listing available ZFS pools..." + bastille_zpool=$(zpool list -H | awk '{print $1}') + for _zpool in ${bastille_zpool}; do + echo "[${ZFSPOOL_COUNT}] ${_zpool}" + ZFSPOOL_NUM="${ZFSPOOL_NUM} [${ZFSPOOL_COUNT}]${_zpool}" + ZFSPOOL_COUNT=$(expr ${ZFSPOOL_COUNT} + 1) + done + + info "Please select the ZFS pool [NUM] for bastille: " + read _zfspool_choice + if ! echo "${_zfspool_choice}" | grep -Eq "^[0-9]{1,3}$"; then + error_exit "Invalid input number, aborting!" + else + _zfspool_select=$(echo "${ZFSPOOL_NUM}" | grep -wo "\[${_zfspool_choice}\][^ ]*" | sed 's/\[.*\]//g') + # If the user is unsure here, just abort as no input validation will be performed after. + if [ -z "${_zfspool_select}" ]; then + error_exit "No ZFS pool selected, aborting!" + else + info "Selected ZFS pool: [${_zfspool_select}]" + # Ask again to make sure the user is confident with the election. + warn "Are you sure '${_zfspool_select}' is the correct ZFS pool [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # shellcheck disable=SC2104 + continue + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + fi + fi + + # Ask on what zfs dataset `bastille` is installed. + info "Listing available ZFS datasets from the selected ZFS pool..." + bastille_zprefix=$(zfs list -H -r "${_zfspool_select}" | awk '{print $1}') + for _zprefix in ${bastille_zprefix}; do + echo "[${ZFSDATA_COUNT}] ${_zprefix}" + ZFSDATA_NUM="${ZFSDATA_NUM} [${ZFSDATA_COUNT}]${_zprefix}" + ZFSDATA_COUNT=$(expr ${ZFSDATA_COUNT} + 1) + done + info "Please select the ZFS dataset [NUM] for bastille: " + read _zfsprefix_choice + if ! echo "${_zfsprefix_choice}" | grep -Eq "^[0-9]{1,3}$"; then + error_exit "Invalid input number, aborting!" + else + _zfsprefix_select=$(echo "${ZFSDATA_NUM}" | grep -wo "\[${_zfsprefix_choice}\][^ ]*" | sed 's/\[.*\]//g') + if [ -z "${_zfsprefix_select}" ]; then + # If the user is unsure here, just abort as no input validation will be performed after. + error_exit "No ZFS dataset selected, aborting!" + else + _zfsprefix_select=$(echo ${ZFSDATA_NUM} | grep -wo "\[${_zfsprefix_choice}\][^ ]*" | sed 's/\[.*\]//g') + _zfsprefix_trim=$(echo ${ZFSDATA_NUM} | grep -wo "\[${_zfsprefix_choice}\][^ ]*" | awk -F "${_zfspool_select}/" 'NR==1{print $2}') + info "Selected ZFS prefix: [${_zfsprefix_select}]" + # Ask again to make sure the user is confident with the election. + warn "Are you sure '${_zfsprefix_select}' is the correct ZFS dataset [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # shellcheck disable=SC2104 + continue + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + fi + fi + + _zfsmount_select="${_zfsprefix_select}" + # Ask what zfs mountpoint `bastille` will use. + info "Listing ZFS mountpoints from the selected ZFS dataset: [${_zfsmount_select}]..." + bastille_prefix=$(zfs list -H "${_zfsmount_select}" | awk '{print $5}') + for _zfsmount_choice in ${bastille_prefix}; do + echo "[${MPREFIX_COUNT}] ${_zfsmount_choice}" + MPREFIX_NUM="${MPREFIX_NUM} [${MPREFIX_COUNT}]${_zfsmount_choice}" + MPREFIX_COUNT=$(expr ${MPREFIX_COUNT} + 1) + done + info "Please select the ZFS mountpoint [NUM] for bastille: " + read _zfsmount_choice + if ! echo "${_zfsmount_choice}" | grep -Eq "^[0-9]{1,3}$"; then + error_exit "Invalid input number, aborting!" + else + _zfsmount_select=$(echo ${MPREFIX_NUM} | grep -wo "\[${_zfsmount_choice}\][^ ]*" | sed 's/\[.*\]//g') + if [ -z "${_zfsmount_select}" ]; then + # If the user is unsure here, just abort as no input validation will be performed after. + error_exit "No ZFS mountpoint selected, aborting!" + else + info "Selected bastille storage mountpoint: [${_zfsmount_select}]" + # Ask again to make sure the user is confident with the election. + warn "Are you sure '${_zfsmount_select}' is the correct bastille prefix [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + # Set the parameters and show the user a preview. + BASTILLE_PREFIXDEF="${_zfsmount_select}" + BASTILLE_ZFSPOOL="${_zfspool_select}" + BASTILLE_PREFIXZFS="${_zfsprefix_trim}" + show_zfs_params + warn "Are you sure the above bastille ZFS configuration is correct?" + warn "Once bastille is activated it can't be easily undone, do you really want to activate ZFS now? [y/N]: " + read _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + write_zfs_opts + create_zfs_dataset + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + ;; + [Nn]|[Nn][Oo]) + user_canceled + ;; + *) + input_error + ;; + esac + fi + fi +} + +# Runtime required variables. +config_runtime + +# Handle options one at a time per topic, we don't want users to select/process +# multiple options at once just to end with a broked and/or unwanted configuration. +case "${1}" in + --firewall|-p) + configure_pf + ;; + --ethernet|-e) + configure_ethernet + ;; + --network|-n|bastille0) + # TODO remove in future release 0.13 + warn "Notice: 'bastille setup bastille0' will be deprecated in the next 0.13 version." + configure_network + ;; + --vnet|-v|bridge) + configure_vnet + ;; + --zfs|-z) + configure_zfs + ;; + --conf-network-reset) + config_network_reset + ;; + --conf-storage-reset) + config_storage_reset + ;; + --conf-restore-clean) + config_restore_global + ;; + *) + usage + ;; esac 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