diff --git a/README.md b/README.md index 7a23c58d..3ca56610 100644 --- a/README.md +++ b/README.md @@ -59,28 +59,29 @@ Available Commands: clone Clone an existing jail. cmd Execute arbitrary command on targeted jail(s). config Get, set or remove a config value for the targeted jail(s). - console Console into a running jail. + console Console into a jail. convert Convert thin jail to thick jail, or convert a jail to a custom release. - cp cp(1) files from host or jail to host or targeted jail(s). + cp cp(1) files from host to targeted jail(s). create Create a jail. destroy Destroy a jail or release. edit Edit jail configuration files (advanced). - export Exports a jail. + export Export a jail. help Help about any command. htop Interactive process viewer (requires htop). import Import a jail. jcp cp(1) files from a jail to jail(s). - limits Apply resources limits to targeted jail(s). See rctl(8). - list List jails, releases, templates etc... + limits Apply resources limits to targeted jail(s). See rctl(8) and cpuset(1). + list List jails, releases, templates and more... + migrate Migrate targeted jail(s) to a remote system. mount Mount a volume inside targeted jail(s). network Add or remove interfaces from targeted jail(s). pkg Manipulate binary packages within targeted jail(s). See pkg(8). rcp cp(1) files from a jail to host. - rdr Redirect host port to container port. + rdr Redirect host port to jail port. rename Rename a jail. restart Restart a running jail. service Manage services within targeted jail(s). - setup Attempt to auto-configure network, firewall and storage on new installs. + setup Attempt to auto-configure network, firewall and storage and more... start Start a stopped jail. stop Stop a running jail. sysrc Safely edit rc files within targeted jail(s). @@ -95,8 +96,8 @@ Available Commands: Use "bastille -v|--version" for version information. Use "bastille command -h|--help" for more information about a command. -Use "bastille [-c|--config config.conf] command" to specify a non-default config file. -Use "bastille [-p|--parallel VALUE] command" to run bastille in parallel mode. +Use "bastille -c|--config config.conf command" to specify a non-default config file. +Use "bastille -p|--parallel VALUE command" to run bastille in parallel mode. ``` diff --git a/docs/chapters/migration.rst b/docs/chapters/migration.rst index 8fd05f4f..d605f8d8 100644 --- a/docs/chapters/migration.rst +++ b/docs/chapters/migration.rst @@ -1,7 +1,50 @@ -========= Migration ========= +Bastille +-------- + +Bastille supports migrations to a remote system using the ``migrate`` subcommand. + +Prerequisites +^^^^^^^^^^^^^ + +There are a couple of things that need to be in place before running the ``migrate`` command. + +First, you must have bastille configured both locally and remotely to use the same filesystem +configuration. ZFS on both, or UFS on both. + +Second, you must create a user on the remote system that will be used to migrate the jail. The user +must be able to log in via SSH using either key-based authentication, or password based authentication. +The user also needs ``sudo`` permissions on the remote system. This user should then be given as the +``USER`` arg in the ``migrate`` command. + +If you are using key-based auth, the keys should be stored in the default location at ``$HOME/.ssh/id_rsa``, +where ``$HOME`` is the users home directory. This is the default location for ssh keys, and where Bastille +will try to load them from. + +If you want to use password based authentication, simply run ``bastille migrate -p TARGET USER HOST``. This +will prompt you to enter the password for the remote system, which Bastille will then use during the migration +process. + +Migration +^^^^^^^^^ + +To migrate a jail (or multiple jails) we can simply run +``bastille migrate TARGET USER HOST``. This will export the jail(s), send them to the +remote system, and import them. + +The ``migrate`` sub-command includes the ``-a|--auto`` option, which will auto-stop the old jail, +migrate it, and attempt to start the migrated jail on the remote system after importing it. See the +warning below about auto-starting the migrated jail. + +WARNING: Every system is unique, has different interfaces, bridges, and network configurations. +It is possible, with the right configuration, for jails to start and work normally. But for some +systems, it will be necessary to edit the ``jail.conf`` file of the migrated jail to get it working +properly. + +You can optionally set ``-d|--destroy`` to have Bastille destroy the old jail on completion. + iocage ------ diff --git a/docs/chapters/subcommands/migrate.rst b/docs/chapters/subcommands/migrate.rst new file mode 100644 index 00000000..36365c18 --- /dev/null +++ b/docs/chapters/subcommands/migrate.rst @@ -0,0 +1,19 @@ +migrate +======= + +The ``migrate`` sub-command allows migrating the targeted jail(s) to +another remote system. See the chapter on Migration. + +This sub-command supports multiple targets. + +.. code-block:: shell + + ishmael ~ # bastille migrate help + Usage: bastille migrate [option(s)] TARGET USER HOST + + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -d | --destroy Destroy local jail after migration. + -p | --password Use password based authentication. + -x | --debug Enable debug mode. diff --git a/docs/chapters/subcommands/setup.rst b/docs/chapters/subcommands/setup.rst index 311c617a..40987f98 100644 --- a/docs/chapters/subcommands/setup.rst +++ b/docs/chapters/subcommands/setup.rst @@ -13,12 +13,13 @@ Below is a list of available options that can be used with the ``setup`` command .. code-block:: shell ishmael ~ # bastille setup -h ## display setup help - ishmael ~ # bastille setup -l ## configure loopback interface - ishmael ~ # bastille setup -s ## configure shared interface - ishmael ~ # bastille setup -p ## configure default pf firewall - ishmael ~ # bastille setup -z ## configure ZFS storage - ishmael ~ # bastille setup -v ## configure VNET ishmael ~ # bastille setup -b ## configure bridge interface + ishmael ~ # bastille setup -f ## configure filesystem/structure + ishmael ~ # bastille setup -l ## configure loopback interface + ishmael ~ # bastille setup -p ## configure default pf firewall + ishmael ~ # bastille setup -s ## configure shared interface + ishmael ~ # bastille setup -v ## configure VNET + ishmael ~ # bastille setup -z ## configure ZFS storage ishmael ~ # bastille setup ## configure -l -p and -z The ``-l|loopback`` option will configure a loopback interface called ``bastille0`` that @@ -36,6 +37,9 @@ networking option. The ``-l|loopback`` and ``-s|shared`` options are only for ca is not specified during the ``create`` command. If an interface is specified, these options have no effect. Instead, the specified interface will be used. +The ``-f|--filesystem`` option is to ensure the proper datasets/directories are in place +for using Bastille. This should only have to be run once on a new system. + The ``-s|shared`` option is for cases where you want an actual interface to use with bastille as opposed to a loopback. Jails will be linked to the shared interface on creation. @@ -51,10 +55,10 @@ The ``-v|vnet`` option will configure your system for use with VNET ``-V`` jails The ``-b|bridge`` options will attempt to configure a bridge interface for use with bridged VNET ``-B`` jails. -Running ``bastille setup`` without any options will attempt to auto-configure the ``-l``, ``-p`` and +Running ``bastille setup`` without any options will attempt to auto-configure the ``-f``, ``-l``, ``-p`` and ``-z`` options. .. code-block:: shell ishmael ~ # bastille setup help - Usage: bastille setup [-p|pf|firewall] [-l|loopback] [-s|shared] [-z|zfs|storage] [-v|vnet] [-b|bridge] + Usage: bastille setup [-b|bridge] [-f|--filesystem] [-l|loopback] [-p|pf|firewall] [-s|shared] [-v|vnet] [-z|zfs|storage] diff --git a/docs/chapters/usage.rst b/docs/chapters/usage.rst index 2086d6db..b6623a8f 100644 --- a/docs/chapters/usage.rst +++ b/docs/chapters/usage.rst @@ -8,46 +8,49 @@ Usage containerized applications on FreeBSD. Usage: - bastille command TARGET [args] + bastille command [options(s)] TARGET [option(s)] [args] Available Commands: - bootstrap Bootstrap a FreeBSD release for container base. - clone Clone an existing container. - cmd Execute arbitrary command on targeted container(s). - config Get or set a config value for the targeted container(s). - console Console into a running container. - convert Convert a Thin container into a Thick container. - cp cp(1) files from host or container to host or targeted container(s). - create Create a new thin container or a thick container if -T|--thick option specified. - destroy Destroy a stopped container or a FreeBSD release. - edit Edit container configuration files (advanced). - export Exports a specified container. + bootstrap Bootstrap a release for jail base. + clone Clone an existing jail. + cmd Execute arbitrary command on targeted jail(s). + config Get, set or remove a config value for the targeted jail(s). + console Console into a jail. + convert Convert thin jail to thick jail, or convert a jail to a custom release. + cp cp(1) files from host to targeted jail(s). + create Create a jail. + destroy Destroy a jail or release. + edit Edit jail configuration files (advanced). + export Export a jail. help Help about any command. htop Interactive process viewer (requires htop). - import Import a specified container. - jcp cp(1) files from a jail to targeted jail(s). - limits Apply resources limits to targeted container(s). See rctl(8). - list List containers (running). - mount Mount a volume inside the targeted container(s). - pkg Manipulate binary packages within targeted container(s). See pkg(8). + import Import a jail. + jcp cp(1) files from a jail to jail(s). + limits Apply resources limits to targeted jail(s). See rctl(8) and cpuset(1). + list List jails, releases, templates and more... + migrate Migrate targeted jail(s) to a remote system. + mount Mount a volume inside targeted jail(s). + network Add or remove interfaces from targeted jail(s). + pkg Manipulate binary packages within targeted jail(s). See pkg(8). rcp cp(1) files from a jail to host. - rdr Redirect host port to container port. - rename Rename a container. - restart Restart a running container. - service Manage services within targeted container(s). - setup Attempt to auto-configure network, firewall and storage on new installs. - start Start a stopped container. - stop Stop a running container. - sysrc Safely edit rc files within targeted container(s). - tags Add or remove tags to targeted container(s). - template Apply file templates to targeted container(s). + rdr Redirect host port to jail port. + rename Rename a jail. + restart Restart a running jail. + service Manage services within targeted jail(s). + setup Attempt to auto-configure network, firewall and storage and more... + start Start a stopped jail. + stop Stop a running jail. + sysrc Safely edit rc files within targeted jail(s). + tags Add or remove tags to targeted jail(s). + template Apply file templates to targeted jail(s). top Display and update information about the top(1) cpu processes. - umount Unmount a volume from within the targeted container(s). - update Update container base -pX release. - upgrade Upgrade container release to X.Y-RELEASE. + umount Unmount a volume from targeted jail(s). + update Update jail base -pX release. + upgrade Upgrade jail release to X.Y-RELEASE. verify Compare release against a "known good" index. zfs Manage (get|set) ZFS attributes on targeted container(s). Use "bastille -v|--version" for version information. Use "bastille command -h|--help" for more information about a command. - Use "bastille [-c|--config FILE] command" to specify a non-default config file. + Use "bastille -c|--config config.conf command" to specify a non-default config file. + Use "bastille -p|--parallel VALUE command" to run bastille in parallel mode. \ No newline at end of file diff --git a/usr/local/bin/bastille b/usr/local/bin/bastille index b162813f..cb97e833 100755 --- a/usr/local/bin/bastille +++ b/usr/local/bin/bastille @@ -96,6 +96,7 @@ Available Commands: jcp cp(1) files from a jail to jail(s). limits Apply resources limits to targeted container(s). See rctl(8). list List containers (running). + migrate Migrate targetted jail(s) to a remote system. mount Mount a volume inside the targeted container(s). network Add/remove network interfaces from targeted container. pkg Manipulate binary packages within targeted container(s). See pkg(8). @@ -213,6 +214,7 @@ case "${CMD}" in import| \ limits| \ list| \ + migrate| \ network| \ rcp| \ rdr| \ diff --git a/usr/local/etc/bastille/bastille.conf.sample b/usr/local/etc/bastille/bastille.conf.sample index 5287c8eb..ea4c3a50 100644 --- a/usr/local/etc/bastille/bastille.conf.sample +++ b/usr/local/etc/bastille/bastille.conf.sample @@ -9,6 +9,7 @@ bastille_cachedir="${bastille_prefix}/cache" ## default bastille_jailsdir="${bastille_prefix}/jails" ## default: "${bastille_prefix}/jails" bastille_releasesdir="${bastille_prefix}/releases" ## default: "${bastille_prefix}/releases" bastille_templatesdir="${bastille_prefix}/templates" ## default: "${bastille_prefix}/templates" +bastille_migratedir="${bastille_prefix}/migrate" ## default: "${bastille_prefix}/migrate" bastille_logsdir="/var/log/bastille" ## default: "/var/log/bastille" ## pf configuration path diff --git a/usr/local/share/bastille/bootstrap.sh b/usr/local/share/bastille/bootstrap.sh index 8556421a..f55c3ad6 100644 --- a/usr/local/share/bastille/bootstrap.sh +++ b/usr/local/share/bastille/bootstrap.sh @@ -108,6 +108,18 @@ bootstrap_directories() { chmod 0750 "${bastille_backupsdir}" fi + ## ${bastille_migratedir} + if [ ! -d "${bastille_migratedir}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_migratedir}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/migrate" + fi + else + mkdir -p "${bastille_migratedir}" + fi + chmod 0750 "${bastille_migratedir}" + fi + ## ${bastille_cachedir} if [ ! -d "${bastille_cachedir}" ]; then if checkyesno bastille_zfs_enable; then @@ -182,7 +194,6 @@ bootstrap_directories() { else mkdir -p "${bastille_releasesdir}/${RELEASE}" fi - ## create subsequent releases/XX.X-RELEASE datasets elif [ ! -d "${bastille_releasesdir}/${RELEASE}" ]; then if checkyesno bastille_zfs_enable; then diff --git a/usr/local/share/bastille/common.sh b/usr/local/share/bastille/common.sh index 21dcf6f8..8c395bed 100644 --- a/usr/local/share/bastille/common.sh +++ b/usr/local/share/bastille/common.sh @@ -247,6 +247,10 @@ set_target() { TARGET="${TARGET} ${_jail}" JAILS="${JAILS} ${_jail}" done + # Exit if no jails + if [ -z "${TARGET}" ] && [ -z "${JAILS}" ]; then + exit 1 + fi if [ "${_order}" = "forward" ]; then TARGET="$(list_jail_priority "${TARGET}" | sort -k2 -n | awk '{print $1}')" JAILS="$(list_jail_priority "${TARGET}" | sort -k2 -n | awk '{print $1}')" @@ -281,6 +285,10 @@ set_target_single() { exit 1 fi fi + # Exit if no jails + if [ -z "${TARGET}" ] && [ -z "${JAILS}" ]; then + exit 1 + fi TARGET="${_TARGET}" JAILS="${_TARGET}" export TARGET @@ -295,6 +303,10 @@ target_all_jails() { JAILS="${JAILS} ${_jail}" fi done + # Exit if no jails + if [ -z "${JAILS}" ]; then + exit 1 + fi if [ "${_order}" = "forward" ]; then JAILS="$(list_jail_priority "${JAILS}" | sort -k2 -n | awk '{print $1}')" elif [ "${_order}" = "reverse" ]; then diff --git a/usr/local/share/bastille/export.sh b/usr/local/share/bastille/export.sh index 34bbeb9b..ff2de4db 100644 --- a/usr/local/share/bastille/export.sh +++ b/usr/local/share/bastille/export.sh @@ -50,7 +50,7 @@ usage() { -v | --verbose Be more verbose during the ZFS send operation. --xz Export a ZFS jail using XZ(.xz) compressed image. -Note: If no export option specified, the container should be redirected to standard output. +Note: If no export option specified, the jail should be redirected to standard output. EOF exit 1 @@ -87,6 +87,7 @@ if [ -n "${bastille_export_options}" ]; then # Reference "/bastille/issues/443" DEFAULT_EXPORT_OPTS="${bastille_export_options}" + info "Default export option(s): '${DEFAULT_EXPORT_OPTS}'" for opt in ${DEFAULT_EXPORT_OPTS}; do @@ -123,7 +124,9 @@ if [ -n "${bastille_export_options}" ]; then usage;; esac done + else + # Handle options while [ $# -gt 0 ]; do case "${1}" in @@ -194,8 +197,6 @@ fi bastille_root_check set_target_single "${TARGET}" -info "\n[${_jail}]:" - # Validate for combined options if [ "${COMP_OPTION}" -gt "1" ]; then error_exit "[ERROR]: Only one compression format can be used during export." @@ -254,7 +255,7 @@ clean_zfs_snap() { zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_${TARGET}_${DATE}" } -export_check() { +export_check() { # Inform the user about the exporting method if [ -z "${USER_EXPORT}" ]; then if [ -n "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then @@ -302,35 +303,52 @@ jail_export() { # Attempt to export the container DATE=$(date +%F-%H%M%S) + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + if [ -n "${RAW_EXPORT}" ]; then + FILE_EXT="" + export_check # Export the raw container recursively and cleanup temporary snapshots zfs send ${OPT_ZSEND} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_${TARGET}_${DATE}" \ > "${bastille_backupsdir}/${TARGET}_${DATE}" + clean_zfs_snap + elif [ -n "${GZIP_EXPORT}" ]; then + FILE_EXT=".gz" + export_check # Export the raw container recursively and cleanup temporary snapshots zfs send ${OPT_ZSEND} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_${TARGET}_${DATE}" | \ gzip ${bastille_compress_gz_options} > "${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}" + clean_zfs_snap + elif [ -n "${XZ_EXPORT}" ]; then + FILE_EXT=".xz" + export_check # Export the container recursively and cleanup temporary snapshots zfs send ${OPT_ZSEND} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_${TARGET}_${DATE}" | \ xz ${bastille_compress_xz_options} > "${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}" + clean_zfs_snap + else + FILE_EXT="" USER_EXPORT="1" + export_check # Quietly export the container recursively, user must redirect standard output @@ -342,16 +360,21 @@ jail_export() { fi else if [ -n "${TGZ_EXPORT}" ]; then + FILE_EXT=".tgz" # Create standard tgz backup archive info "\nExporting '${TARGET}' to a compressed ${FILE_EXT} archive..." + cd "${bastille_jailsdir}" && tar -cf - "${TARGET}" | gzip ${bastille_compress_gz_options} > "${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}" + elif [ -n "${TXZ_EXPORT}" ]; then + FILE_EXT=".txz" # Create standard txz backup archive info "\nExporting '${TARGET}' to a compressed ${FILE_EXT} archive..." + cd "${bastille_jailsdir}" && tar -cf - "${TARGET}" | xz ${bastille_compress_xz_options} > "${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}" else error_exit "[ERROR]: export option required" @@ -363,10 +386,13 @@ jail_export() { error_exit "[ERROR]: Failed to export '${TARGET}' container." else if [ -z "${USER_EXPORT}" ]; then + # Generate container checksum file cd "${bastille_backupsdir}" || error_exit "Failed to change directory." sha256 -q "${TARGET}_${DATE}${FILE_EXT}" > "${TARGET}_${DATE}.sha256" + info "\nExported '${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}' successfully." + fi exit 0 fi @@ -379,18 +405,22 @@ fi if [ -n "${TARGET}" ]; then + # Validate jail existence if [ ! -d "${bastille_jailsdir}/${TARGET}" ]; then error_exit "[ERROR]: Jail not found: ${TARGET}" fi - # Check if is a ZFS system + # Jail needs to be stopped on non-ZFS systems if ! checkyesno bastille_zfs_enable; then - # Check if container is running and ask for stop in non ZFS systems - if [ -n "$(/usr/sbin/jls name | awk "/^${TARGET}$/")" ]; then - error_exit "[ERROR]: ${TARGET} is running. See 'bastille stop'." + # Validate jail state + check_target_is_stopped "${TARGET}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${TARGET}" + else + info "\n[${TARGET}]:" + error_notify "Jail is running." + error_exit "Use [-a|--auto] to auto-stop the jail." fi fi + jail_export fi - -echo \ No newline at end of file diff --git a/usr/local/share/bastille/import.sh b/usr/local/share/bastille/import.sh index cf9abda3..9d004840 100644 --- a/usr/local/share/bastille/import.sh +++ b/usr/local/share/bastille/import.sh @@ -716,8 +716,8 @@ jail_import() { # Check for user specified file location if echo "${TARGET}" | grep -q '\/'; then GETDIR="${TARGET}" - TARGET=$(echo ${TARGET} | awk -F '\/' '{print $NF}') - bastille_backupsdir=$(echo ${GETDIR} | sed "s/${TARGET}//") + TARGET="$(basename ${TARGET})" + bastille_backupsdir="$(dirname ${GETDIR})" fi # Check if backups directory/dataset exist @@ -740,15 +740,17 @@ else error_exit "[ERROR]: Archive '${TARGET}' not found." else # Assume user will import from standard input - TARGET_TRIM=${TARGET} + TARGET_TRIM="${TARGET}" USER_IMPORT="1" fi fi # Check if a running jail matches name or already exist -check_target_exists || error_exit "[ERROR]: Jail: ${TARGET_TRIM} already exists." +if check_target_exists "${TARGET_TRIM}"; then + error_exit "[ERROR]: Jail: ${TARGET_TRIM} already exists." +fi if [ -n "${TARGET}" ]; then - info "\nAttempting to import jail: ${TARGET}..." + info "\nAttempting to import jail: ${TARGET_TRIM}..." jail_import -fi \ No newline at end of file +fi diff --git a/usr/local/share/bastille/migrate.sh b/usr/local/share/bastille/migrate.sh new file mode 100644 index 00000000..9996d7fb --- /dev/null +++ b/usr/local/share/bastille/migrate.sh @@ -0,0 +1,308 @@ +#!/bin/sh +# +# SPDX-License-Identifier: BSD-3-Clause +# +# Copyright (c) 2018-2025, Christer Edwards +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +. /usr/local/share/bastille/common.sh + +usage() { + error_notify "Usage: bastille migrate [option(s)] TARGET USER HOST" + cat << EOF + + Options: + + -a | --auto Auto mode. Start/stop jail(s) if required. + -d | --destroy Destroy local jail after migration. + -p | --password Use password based authentication. + -x | --debug Enable debug mode. + +EOF + exit 1 +} + +# Handle options. +AUTO=0 +OPT_DESTROY=0 +OPT_PASSWORD=0 +while [ "$#" -gt 0 ]; do + case "${1}" in + -h|--help|help) + usage + ;; + -a|--auto) + AUTO=1 + shift + ;; + -d|--destroy) + OPT_DESTROY=1 + shift + ;; + -p|--password) + OPT_PASSWORD=1 + shift + ;; + -x|--debug) + enable_debug + shift + ;; + -*) + for _opt in $(echo ${1} | sed 's/-//g' | fold -w1); do + case ${_opt} in + a) AUTO=1 ;; + d) OPT_DESTROY=1 ;; + p) OPT_PASSWORD=1 ;; + x) enable_debug ;; + *) error_exit "[ERROR]: Unknown Option: \"${1}\"" ;; + esac + done + shift + ;; + *) + break + ;; + esac +done + +if [ "$#" -ne 3 ]; then + usage +fi + +TARGET="${1}" +USER="${2}" +HOST="${3}" + +bastille_root_check +set_target "${TARGET}" + +validate_host_status() { + + local _user="${1}" + local _host="${2}" + + info "\nChecking remote host status..." + + # Host uptime + if ! ping -c 1 ${_host} >/dev/null 2>/dev/null; then + error_exit "[ERROR]: Host appears to be down" + fi + + # Host SSH check + if [ "${OPT_PASSWORD}" -eq 1 ]; then + if ! ${_sshpass_cmd} ssh ${_user}@${_host} exit >/dev/null 2>/dev/null; then + error_notify "[ERROR]: Could not establish ssh connection to host." + error_notify "Please make sure the remote host supports password based authentication" + error_exit "and you are using the correct password for user: '${_user}'" + fi + elif ! ${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} exit >/dev/null 2>/dev/null; then + error_notify "[ERROR]: Could not establish ssh connection to host." + error_notify "Please make sure user '${_user}' has password-less access" + error_exit "or use '-p|--password' for password based authentication." + fi + + echo "Host check successful." +} + +migrate_cleanup() { + + local _jail="${1}" + local _user="${2}" + local _host="${3}" + + # Remove archive files from local and remote system + ${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} sudo rm -f "${_remote_bastille_migratedir}/${_jail}_*.*" + rm -f ${bastille_migratedir}/${_jail}_*.* +} + +migrate_create_export() { + + local _jail="${1}" + local _user="${2}" + local _host="${3}" + + info "\nPreparing jail for migration..." + + # Ensure new migrate directory is created + bastille setup -f + ${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} sudo bastille setup -f + + # --xz for ZFS, otherwise --txz + if checkyesno bastille_zfs_enable; then + bastille export --xz ${_jail} ${bastille_migratedir} + else + bastille export --txz ${_jail} ${_bastille_migratedir} + fi +} + +migrate_jail() { + + local _jail="${1}" + local _user="${2}" + local _host="${3}" + + local _remote_bastille_zfs_enable="$(${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} sysrc -f /usr/local/etc/bastille/bastille.conf -n bastille_zfs_enable)" + local _remote_bastille_jailsdir="$(${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} sysrc -f /usr/local/etc/bastille/bastille.conf -n bastille_jailsdir)" + local _remote_bastille_migratedir="$(${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} sysrc -f /usr/local/etc/bastille/bastille.conf -n bastille_migratedir)" + local _remote_jail_list="$(${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} bastille list jails)" + + # Verify jail does not exist remotely + if echo "${_remote_jail_list}" | grep -Eoqw "${_jail}"; then + error_exit "[ERROR]: Jail already exists on remote system: ${_jail}" + fi + + # Verify ZFS on both systems + if checkyesno bastille_zfs_enable; then + if ! checkyesno _remote_bastille_zfs_enable; then + error_notify "[ERROR]: ZFS is enabled locally, but not remotely." + error_exit "Enable ZFS remotely to continue." + else + + migrate_create_export "${_jail}" "${_user}" "${_host}" + + info "\nAttempting to migrate jail to remote system..." + + local _file="$(find "${bastille_migratedir}" -maxdepth 1 -type f | grep -Eo "${_jail}_.*\.xz$" | head -n1)" + local _file_sha256="$(echo ${_file} | sed 's/\..*/.sha256/')" + + # Send sha256 + if ! ${_sshpass_cmd} scp ${_opt_ssh_key} ${bastille_migratedir}/${_file_sha256} ${_user}@${_host}:${_remote_bastille_migratedir}; then + migrate_cleanup "${_jail}" "${_user}" "${_host}" + error_exit "[ERROR]: Failed to send jail to remote system." + fi + + # Send jail export + if ! ${_sshpass_cmd} scp ${_opt_ssh_key} ${bastille_migratedir}/${_file} ${_user}@${_host}:${_remote_bastille_migratedir}; then + migrate_cleanup "${_jail}" "${_user}" "${_host}" + error_exit "[ERROR]: Failed to send jail to remote system." + fi + fi + else + if checkyesno _remote_bastille_zfs_enable; then + error_notify "[ERROR]: ZFS is enabled remotely, but not locally." + error_exit "Enable ZFS locally to continue." + else + + info "\nAttempting to migrate jail to remote system..." + + migrate_create_export "${_jail}" "${_user}" "${_host}" + + local _file="$(find "${bastille_migratedir}" -maxdepth 1 -type f | grep -Eo "${_jail}_.*\.txz$" | head -n1)" + local _file_sha256="$(echo ${_file} | sed 's/\..*/.sha256/')" + + # Send sha256 + if ! ${_sshpass_cmd} scp ${_opt_ssh_key} ${bastille_migratedir}/${_file_sha256} ${_user}@${_host}:${_remote_bastille_migratedir}; then + migrate_cleanup "${_jail}" "${_user}" "${_host}" + error_exit "[ERROR]: Failed to migrate jail to remote system." + fi + + # Send jail export + if ! ${_sshpass_cmd} scp ${_opt_ssh_key} ${bastille_migratedir}/${_file} ${_user}@${_host}:${_remote_bastille_migratedir}; then + migrate_cleanup "${_jail}" "${_user}" "${_host}" + error_exit "[ERROR]: Failed to migrate jail to remote system." + fi + fi + fi + + # Import the jail remotely + if ! ${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} sudo bastille import ${_remote_bastille_migratedir}/${_file}; then + migrate_cleanup "${_jail}" "${_user}" "${_host}" + error_exit "[ERROR]: Failed to import jail on remote system." + fi + + # Destroy old jail if OPT_DESTROY=1 + if [ "${OPT_DESTROY}" -eq 1 ]; then + bastille destroy -af "${_jail}" + fi + + # Remove archives + migrate_cleanup "${_jail}" "${_user}" "${_host}" + + # Start new jail if AUTO=1 + if [ "${AUTO}" -eq 1 ]; then + ${_sshpass_cmd} ssh ${_opt_ssh_key} ${_user}@${_host} sudo bastille start "${_jail}" + fi +} + +# Determine if user wants to authenticate via password +if [ "${OPT_PASSWORD}" -eq 1 ]; then + if ! which sshpass >/dev/null 2>/dev/null; then + error_exit "[ERROR]: Please install 'sshpass' to use password based authentication." + else + warn "[WARNING]: Password based authentication can be insecure." + printf "Please enter your password: " + # We disable terminal output for the password + stty -echo + read _password + stty echo + printf "\n" + _sshpass_cmd="sshpass -p ${_password}" + fi +else + _sshpass_cmd= +fi + +# Get user we want to migrate as +# We need this to pass the ssh keys properly +if [ "${OPT_PASSWORD}" -eq 1 ]; then + _opt_ssh_key= +else + _migrate_user="$(sudo -u ${USER} whoami)" + _migrate_user_home="$(getent passwd migrate | cut -d: -f6)" + _migrate_user_ssh_key="${_migrate_user_home}/.ssh/id_rsa" + _opt_ssh_key="-i ${_migrate_user_ssh_key}" +fi + +# Validate host uptime +validate_host_status "${USER}" "${HOST}" + +for _jail in ${JAILS}; do + + ( + + # Validate jail state + check_target_is_stopped "${_jail}" || if [ "${AUTO}" -eq 1 ]; then + bastille stop "${_jail}" + else + info "\n[${_jail}]:" + error_notify "Jail is running." + error_continue "Use [-a|--auto] to auto-stop the jail." + fi + + info "\nAttempting to migrate '${_jail}' to '${HOST}'..." + + migrate_jail "${_jail}" "${USER}" "${HOST}" + + info "\nSuccessfully migrated '${_jail}' to '${HOST}'.\n" + + ) & + + bastille_running_jobs "${bastille_process_limit}" + +done +wait diff --git a/usr/local/share/bastille/setup.sh b/usr/local/share/bastille/setup.sh index ecb954ff..453632b3 100644 --- a/usr/local/share/bastille/setup.sh +++ b/usr/local/share/bastille/setup.sh @@ -33,14 +33,128 @@ . /usr/local/share/bastille/common.sh usage() { - error_exit "Usage: bastille setup [-p|pf|firewall] [-l|loopback] [-s|shared] [-z|zfs|storage] [-v|vnet] [-b|bridge]" + error_exit "Usage: bastille setup [-b|bridge] [-f|--filesystem] [-l|loopback] [-p|pf|firewall] [-s|shared] [-v|vnet] [-z|zfs|storage]" } # Check for too many args -if [ $# -gt 1 ]; then +if [ "$#" -gt 1 ]; then usage fi +configure_filesystem() { + + # This is so we dont have to introduce breaking + # changes on new variables added to bastille.conf + + # Ensure migrate directory is in place + ## ${bastille_migratedir} + if [ -z "${bastille_migratedir}" ]; then + if ! grep -oq "bastille_migratedir=" "${BASTILLE_CONFIG}"; then + sed -i '' 's|bastille_backupsdir=.*|&\nbastille_migratedir=\"${bastille_prefix}/migrate\" ## default: \"${bastille_prefix}/migrate\"|' ${BASTILLE_CONFIG} + # shellcheck disable=SC1090 + . ${BASTILLE_CONFIG} + fi + fi + + ## ${bastille_prefix} + if [ ! -d "${bastille_prefix}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_prefix}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}" + fi + else + mkdir -p "${bastille_prefix}" + fi + chmod 0750 "${bastille_prefix}" + # Make sure the dataset is mounted in the proper place + elif [ -d "${bastille_prefix}" ]; then + if ! zfs list "${bastille_zfs_zpool}/${bastille_zfs_prefix}" >/dev/null; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_prefix}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}" + elif [ "$(zfs get -H -o value mountpoint ${bastille_zfs_zpool}/${bastille_zfs_prefix})" != "${bastille_prefix}" ]; then + zfs set mountpoint="${bastille_prefix}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}" + fi + fi + + ## ${bastille_backupsdir} + if [ ! -d "${bastille_backupsdir}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_backupsdir}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/backups" + fi + else + mkdir -p "${bastille_backupsdir}" + fi + chmod 0750 "${bastille_backupsdir}" + fi + + ## ${bastille_cachedir} + if [ ! -d "${bastille_cachedir}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_cachedir}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/cache" + fi + else + mkdir -p "${bastille_cachedir}" + fi + fi + + ## ${bastille_jailsdir} + if [ ! -d "${bastille_jailsdir}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_jailsdir}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails" + fi + else + mkdir -p "${bastille_jailsdir}" + fi + fi + + ## ${bastille_logsdir} + if [ ! -d "${bastille_logsdir}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_logsdir}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/logs" + fi + else + mkdir -p "${bastille_logsdir}" + fi + fi + + ## ${bastille_templatesdir} + if [ ! -d "${bastille_templatesdir}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_templatesdir}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/templates" + fi + else + mkdir -p "${bastille_templatesdir}" + fi + fi + + ## ${bastille_releasesdir} + if [ ! -d "${bastille_releasesdir}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_releasesdir}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/releases" + fi + else + mkdir -p "${bastille_releasesdir}" + fi + fi + + ## ${bastille_migratedir} + if [ ! -d "${bastille_migratedir}" ]; then + if checkyesno bastille_zfs_enable; then + if [ -n "${bastille_zfs_zpool}" ]; then + zfs create ${bastille_zfs_options} -o mountpoint="${bastille_migratedir}" "${bastille_zfs_zpool}/${bastille_zfs_prefix}/migrate" + fi + else + mkdir -p "${bastille_migratedir}" + fi + chmod 0750 "${bastille_migratedir}" + fi +} + # Configure netgraph configure_netgraph() { if [ ! "$(kldstat -m netgraph)" ]; then @@ -236,6 +350,7 @@ configure_zfs() { # Run all base functions (w/o vnet) if no args if [ $# -eq 0 ]; then sysrc bastille_enable=YES + configure_filesystem configure_loopback_interface configure_pf configure_zfs @@ -248,6 +363,9 @@ case "$1" in -h|--help|help) usage ;; + -f|--filesystem) + configure_filesystem + ;; -p|pf|firewall) configure_pf ;; @@ -320,4 +438,4 @@ case "$1" in *) error_exit "[ERROR]: Unknown option: \"${1}\"" ;; -esac +esac \ No newline at end of file