diff --git a/usr/local/share/bastille/clone.sh b/usr/local/share/bastille/clone.sh index 074c3c95..f3f1d8d2 100644 --- a/usr/local/share/bastille/clone.sh +++ b/usr/local/share/bastille/clone.sh @@ -34,33 +34,84 @@ . /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" IP6_MODE="disable" ip6=$(echo "${IP}" | grep -E '^(([a-fA-F0-9:]+$)|([a-fA-F0-9:]+\/[0-9]{1,3}$))') if [ -n "${ip6}" ]; then info "Valid: (${ip6})." - IPX_ADDR="ip6.addr" - # shellcheck disable=SC2034 IP6_MODE="new" else local IFS @@ -89,97 +140,158 @@ 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 + if echo ${_ip} | grep -q "|"; then + _ip="$(echo ${_ip} | awk -F"|" '{print $2}')" + fi + sed -i '' "/ip4.addr = .*/ s/${_ip}/${IP}/" "${JAIL_CONFIG}" + sed -i '' "/ip4.addr += .*/ s/${_ip}/127.0.0.1/" "${JAIL_CONFIG}" + done + fi + # IP6 + if [ "${_ip6}" != "not set" ]; then + for _ip in ${_ip6}; do + if echo ${_ip} | grep -q "|"; then + _ip="$(echo ${_ip} | awk -F"|" '{print $2}')" + fi + sed -i '' "/ip6.addr = .*/ s/${_ip}/${IP}/" "${JAIL_CONFIG}" + sed -i '' "/ip6.addr += .*/ s/${_ip}/127.0.0.1/" "${JAIL_CONFIG}" + sed -i '' "s/ip6 = .*/ip6 = ${IP6_MODE};/" "${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 _if_count="$(grep -Eo 'epair[0-9]+|bastille[0-9]+' ${bastille_jailsdir}/*/jail.conf 2>/dev/null | sort -u | wc -l | awk '{print $1}')" - local num_range=$((_if_count + 1)) - for _num in $(seq 0 "${num_range}"); do - if ! grep -Eoq "epair${_num}|bastille${_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 ${_if} | 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 interfaces - if echo ${_if} 2>/dev/null | grep -Eoq 'bastille[0-9]+'; then - local _if_vnet="$(grep ${_if} "${bastille_jail_rc_conf}" | grep -Eo "vnet[0-9]+")" - sed -i '' "s|${_if}|${uniq_epair}|g" "${JAIL_CONFIG}" - 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|exec.prestart += \"ifconfig.*${uniq_epair}.*description.*|exec.prestart += \"ifconfig e0a_${uniq_epair} description \\\\\"vnet host interface for Bastille jail ${NEWNAME}\\\\\"\";|" "${JAIL_CONFIG}" + 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 - 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 + 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" - fi - # for bridged VNET interfaces - elif echo ${_if} 2>/dev/null | grep -Eoq 'epair[0-9]+'; then - local _if_epaira="$(grep "${_if}" ${JAIL_CONFIG} | grep -Eo "e[0-9]+a_${TARGET}")" - local _if_epairb="$(grep "${_if}" ${JAIL_CONFIG} | grep -Eo "e[0-9]+b_${TARGET}")" - local _if_vnet="$(grep ${_if_epairb} "${bastille_jail_rc_conf}" | grep -Eo "vnet[0-9]+")" - sed -i '' "s|${_if}|epair${uniq_epair_bridge}|g" "${JAIL_CONFIG}" - sed -i '' "s|${_if_epaira}|e${uniq_epair_bridge}a_${NEWNAME}|g" "${JAIL_CONFIG}" - sed -i '' "s|${_if_epairb}|e${uniq_epair_bridge}b_${NEWNAME}|g" "${JAIL_CONFIG}" - sed -i '' "s|e${uniq_epair}a_${TARGET} ether.*:.*:.*:.*:.*:.*a\";|e${uniq_epair}a_${NEWNAME} ether ${macaddr}a\";|" "${JAIL_CONFIG}" - sed -i '' "s|e${uniq_epair}b_${TARGET} ether.*:.*:.*:.*:.*:.*b\";|e${uniq_epair}b_${NEWNAME} ether ${macaddr}b\";|" "${JAIL_CONFIG}" - # Update /etc/rc.conf - sed -i '' "s|${_if_epairb}_name|e${uniq_epair_bridge}b_${NEWNAME}_name|" "${bastille_jail_rc_conf}" - if grep "vnet0" "${bastille_jail_rc_conf}" | grep -q "e${uniq_epair_bridge}b_${NEWNAME}_name"; then - 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 - else - sysrc -f "${bastille_jail_rc_conf}" ifconfig_${_if_vnet}="SYNCDHCP" + sysrc -f "${_rc_conf}" ifconfig_${_jail_vnet}="SYNCDHCP" fi + break fi - break - fi - done + 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 } -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) @@ -195,13 +307,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 @@ -210,7 +322,7 @@ clone_jail() { # Generate jail configuration files update_jailconf - update_fstab + update_fstab "${TARGET}" "${NEWNAME}" # Display the exist status if [ "$?" -ne 0 ]; then @@ -218,18 +330,14 @@ 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 usage -fi - -clone_jail +fi \ No newline at end of file