diff --git a/usr/local/share/bastille/setup.sh b/usr/local/share/bastille/setup.sh index 020d2cf4..bd22d4fa 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,410 @@ # 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. + -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..." + read -p "$(warn "Do you really want to reset 'bastille' network configuration? [y/N]: ")" _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + 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..." + read -p "$(warn "Do you really want to reset 'bastille' ZFS configuration? [y/N]: ")" _response + case "${_response}" in + [Yy]|[Yy][Ee][Ss]) + 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..." + read -p "$(warn "Do you really want to restore 'bastille' default configuration file and start over? [y/N]: ")" _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." + #read -p "$(info "Would you like bastille attempt to auto-detect/activate ZFS for you?, (assuming a standard install was performed) [y/N]: ")" _response + read -p "$(info "Would you like to configure the bastille ZFS options interactively? [y/N]: ")" _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_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}]." + read -p "$(warn "Would you like to configure the loopback network interface now? [y/N]: ")" _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}]." + read -p "$(warn "Would you like to configure the VNET bridge interface now? [y/N]: ")" _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 +440,47 @@ 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}]." + read -p "$(warn "Would you like to configure the PF firewall parameters now? [y/N]: ")" _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 +496,399 @@ 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= + BASTILLE_INITIAL_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." + read -p "$(warn "Would you like to enable the ZFS activation helper now? [y/N]: ")" _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." + read -p "$(warn "Would you like to activate ZFS now to get the features and benefits? [y/N]"): " _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. + read -p "$(warn "Would you like to explicitly disable ZFS in the configuration file? [y/N]: ")" _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" + read -p "$(warn "Would you like to auto-configure the detected ZFS parameters now? [y/N]: ")" _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." + read -p "$(warn "Would you like to explicitly disable ZFS in the configuration file so we don't ask again? [y/N]: ")" _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 - ;; + read -p "$(info "Would you like to configure the ZFS parameters entirely by hand? [y/N]: ")" _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. + read -p "$(warn "Please enter the desired ZFS pool for bastille: ")" _zfspool_select + read -p "$(warn "Please enter the ZFS dataset for bastille: ")" _zfsprefix_select + read -p "$(warn "Please enter the ZFS mountpoint for bastille: ")" _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?" + read -p "$(warn "Once bastille is activated it can't be easily undone, do you really want to activate ZFS now? [y/N]: ")" _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 + + read -p "$(info "Please select the ZFS pool [NUM] for bastille: ")" _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 in his election. + read -p "$(warn "Are you sure '${_zfspool_select}' is the correct ZFS pool [y/N]: ")" _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 + read -p "$(info "Please select the ZFS dataset [NUM] for bastille: ")" _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 in his election. + read -p "$(warn "Are you sure '${_zfsprefix_select}' is the correct ZFS dataset [y/N]: ")" _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 + read -p "$(info "Please select the ZFS mountpoint [NUM] for bastille: ")" _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 in his election. + read -p "$(warn "Are you sure '${_zfsmount_select}' is the correct bastille prefix [y/N]: ")" _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?" + read -p "$(warn "Once bastille is activated it can't be easily undone, do you really want to activate ZFS now? [y/N]: ")" _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 + ;; + --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