From f0c3620fac4ac64946131a55980afa6e42bb3a7f Mon Sep 17 00:00:00 2001 From: JRGTH Date: Sat, 10 Jul 2021 08:35:50 -0400 Subject: [PATCH] Add export/import standard I/O redirection This update enhances the export and import command to fully support jail export/import user standard input/output redirection --- usr/local/share/bastille/export.sh | 102 +++++++++++++++++++---------- usr/local/share/bastille/import.sh | 55 ++++++++++++---- 2 files changed, 107 insertions(+), 50 deletions(-) diff --git a/usr/local/share/bastille/export.sh b/usr/local/share/bastille/export.sh index a8f678ce..af1d284a 100644 --- a/usr/local/share/bastille/export.sh +++ b/usr/local/share/bastille/export.sh @@ -33,8 +33,9 @@ usage() { # Build an independent usage for the export command - # Valid compress/options for ZFS systems are raw, .gz, .tgz, .txz and .xz(default) - # Valid compress/options for non ZFS configured systems are .tgz and .txz(default) + # Valid compress/options for ZFS systems are raw, .gz, .tgz, .txz and .xz + # Valid compress/options for non ZFS configured systems are .tgz and .txz + # If no compression option specified, user must redirect standard output echo -e "${COLOR_RED}Usage: bastille export | option(s) | TARGET | PATH${COLOR_RESET}" cat << EOF @@ -46,6 +47,7 @@ usage() { --tgz -- Export a jail using simple .tgz compressed archive instead. --txz -- Export a jail using simple .txz compressed archive instead. -v | --verbose -- Be more verbose during the ZFS send operation. + --xz -- Export a ZFS jail using XZ(.xz) compressed image. EOF exit 1 @@ -77,6 +79,7 @@ zfs_enable_check() { TARGET="${1}" GZIP_EXPORT= SAFE_EXPORT= +USER_EXPORT= RAW_EXPORT= DIR_EXPORT= TXZ_EXPORT= @@ -97,6 +100,12 @@ while [ $# -gt 0 ]; do opt_count shift ;; + --xz) + XZ_EXPORT="1" + TARGET="${2}" + opt_count + shift + ;; --tgz) TGZ_EXPORT="1" TARGET="${2}" @@ -186,35 +195,45 @@ fi create_zfs_snap() { # Take a recursive temporary snapshot - info "Creating temporary ZFS snapshot for export..." + if [ -z "${USER_EXPORT}" ]; then + info "Creating temporary ZFS snapshot for export..." + fi zfs snapshot -r "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" } +clean_zfs_snap() { + # Cleanup the recursive temporary snapshot + zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}/root@bastille_export_${DATE}" + zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" +} + export_check() { # Inform the user about the exporting method - if [ -n "$(jls name | awk "/^${TARGET}$/")" ]; then - if [ -n "${SAFE_EXPORT}" ]; then - EXPORT_AS="Safely exporting" + if [ -z "${USER_EXPORT}" ]; then + if [ -n "$(jls name | awk "/^${TARGET}$/")" ]; then + if [ -n "${SAFE_EXPORT}" ]; then + EXPORT_AS="Safely exporting" + else + EXPORT_AS="Hot exporting" + fi else - EXPORT_AS="Hot exporting" + EXPORT_AS="Exporting" fi - else - EXPORT_AS="Exporting" - fi - if [ "${FILE_EXT}" = ".xz" -o "${FILE_EXT}" = ".gz" -o "${FILE_EXT}" = "" ]; then - EXPORT_TYPE="image" - else - EXPORT_TYPE="archive" - fi + if [ "${FILE_EXT}" = ".xz" -o "${FILE_EXT}" = ".gz" -o "${FILE_EXT}" = "" ]; then + EXPORT_TYPE="image" + else + EXPORT_TYPE="archive" + fi - if [ -n "${RAW_EXPORT}" ]; then - EXPORT_INFO="to a raw ${EXPORT_TYPE}" - else - EXPORT_INFO="to a compressed ${FILE_EXT} ${EXPORT_TYPE}" - fi + if [ -n "${RAW_EXPORT}" ]; then + EXPORT_INFO="to a raw ${EXPORT_TYPE}" + else + EXPORT_INFO="to a compressed ${FILE_EXT} ${EXPORT_TYPE}" + fi - info "${EXPORT_AS} '${TARGET}' ${EXPORT_INFO}..." + info "${EXPORT_AS} '${TARGET}' ${EXPORT_INFO}..." + fi # Safely stop and snapshot the jail if [ -n "${SAFE_EXPORT}" ]; then @@ -226,7 +245,9 @@ export_check() { fi if [ "${bastille_zfs_enable}" = "YES" ]; then - info "Sending ZFS data stream..." + if [ -z "${USER_EXPORT}" ]; then + info "Sending ZFS data stream..." + fi fi } @@ -242,8 +263,7 @@ jail_export() { # Export the raw container recursively and cleanup temporary snapshots zfs send ${OPT_ZSEND} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" \ > "${bastille_backupsdir}/${TARGET}_${DATE}" - zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}/root@bastille_export_${DATE}" - zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" + clean_zfs_snap elif [ -n "${GZIP_EXPORT}" ]; then FILE_EXT=".gz" export_check @@ -251,17 +271,23 @@ jail_export() { # Export the raw container recursively and cleanup temporary snapshots zfs send ${OPT_ZSEND} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" | \ gzip ${bastille_compress_gz_options} > "${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}" - zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}/root@bastille_export_${DATE}" - zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" - else + clean_zfs_snap + elif [ -n "${XZ_EXPORT}" ]; then FILE_EXT=".xz" export_check - # Export the container recursively and cleanup temporary snapshots(default) + # Export the container recursively and cleanup temporary snapshots zfs send ${OPT_ZSEND} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" | \ xz ${bastille_compress_xz_options} > "${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}" - zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}/root@bastille_export_${DATE}" - zfs destroy "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" + clean_zfs_snap + else + FILE_EXT="" + USER_EXPORT="1" + export_check + + # Quietly export the container recursively, user must redirect standard output + zfs send ${OPT_ZSEND} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}@bastille_export_${DATE}" + clean_zfs_snap fi fi else @@ -271,22 +297,26 @@ jail_export() { # Create standard tgz backup archive info "Exporting '${TARGET}' to a compressed ${FILE_EXT} archive..." cd "${bastille_jailsdir}" && tar -cf - "${TARGET}" | gzip ${bastille_compress_gz_options} > "${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}" - else + elif [ -n "${TXZ_EXPORT}" ]; then FILE_EXT=".txz" - # Create standard txz backup archive(default) + # Create standard txz backup archive info "Exporting '${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" fi fi if [ "$?" -ne 0 ]; then error_exit "Failed to export '${TARGET}' container." else - # Generate container checksum file - cd "${bastille_backupsdir}" - sha256 -q "${TARGET}_${DATE}${FILE_EXT}" > "${TARGET}_${DATE}.sha256" - info "Exported '${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}' successfully." + if [ -z "${USER_EXPORT}" ]; then + # Generate container checksum file + cd "${bastille_backupsdir}" + sha256 -q "${TARGET}_${DATE}${FILE_EXT}" > "${TARGET}_${DATE}.sha256" + info "Exported '${bastille_backupsdir}/${TARGET}_${DATE}${FILE_EXT}' successfully." + fi exit 0 fi } diff --git a/usr/local/share/bastille/import.sh b/usr/local/share/bastille/import.sh index fb9b1a98..5c649387 100644 --- a/usr/local/share/bastille/import.sh +++ b/usr/local/share/bastille/import.sh @@ -33,6 +33,7 @@ usage() { # Build an independent usage for the import command + # If no file/extension specified, will import from standard input echo -e "${COLOR_RED}Usage: bastille import [option(s)] FILE${COLOR_RESET}" cat << EOF @@ -53,11 +54,12 @@ help|-h|--help) esac if [ $# -gt 3 ] || [ $# -lt 1 ]; then - usage + #usage fi TARGET="${1}" OPT_FORCE= +USER_IMPORT= OPT_ZRECV="-u" # Handle and parse option args @@ -113,7 +115,7 @@ validate_archive() { if [ -n "${OPT_FORCE}" ]; then warn "Warning: Skipping archive validation!" else - error_exit "Checksum file not found. See 'bastille import TARGET -f'." + error_exit "Checksum file not found. See 'bastille import [option(s)] FILE'." fi fi fi @@ -359,11 +361,12 @@ jail_import() { # Attempt to import container from file FILE_TRIM=$(echo "${TARGET}" | sed 's/\.xz//g;s/\.gz//g;s/\.tgz//g;s/\.txz//g;s/\.zip//g;s/\.tar\.gz//g;s/\.tar//g') FILE_EXT=$(echo "${TARGET}" | sed "s/${FILE_TRIM}//g") - validate_archive + #validate_archive if [ -d "${bastille_jailsdir}" ]; then if [ "${bastille_zfs_enable}" = "YES" ]; then if [ -n "${bastille_zfs_zpool}" ]; then if [ "${FILE_EXT}" = ".xz" ]; then + validate_archive # Import from compressed xz on ZFS systems info "Importing '${TARGET_TRIM}' from compressed ${FILE_EXT} image." info "Receiving ZFS data stream..." @@ -373,6 +376,7 @@ jail_import() { # Update ZFS mountpoint property if required update_zfsmount elif [ "${FILE_EXT}" = ".gz" ]; then + validate_archive # Import from compressed xz on ZFS systems info "Importing '${TARGET_TRIM}' from compressed ${FILE_EXT} image." info "Receiving ZFS data stream..." @@ -383,6 +387,7 @@ jail_import() { update_zfsmount elif [ "${FILE_EXT}" = ".txz" ]; then + validate_archive # Prepare the ZFS environment and restore from existing .txz file create_zfs_datasets @@ -394,6 +399,7 @@ jail_import() { remove_zfs_datasets fi elif [ "${FILE_EXT}" = ".tgz" ]; then + validate_archive # Prepare the ZFS environment and restore from existing .tgz file create_zfs_datasets @@ -405,6 +411,7 @@ jail_import() { remove_zfs_datasets fi elif [ "${FILE_EXT}" = ".zip" ]; then + validate_archive # Attempt to import a foreign/iocage container info "Importing '${TARGET_TRIM}' from foreign compressed ${FILE_EXT} archive." # Sane bastille ZFS options @@ -469,14 +476,24 @@ jail_import() { fi elif [ -z "${FILE_EXT}" ]; then if echo "${TARGET}" | grep -q '_[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{6\}$'; then - # Based on the file name, looks like we are importing a raw bastille image - # Import from uncompressed image file - info "Importing '${TARGET_TRIM}' from uncompressed image archive." - info "Receiving ZFS data stream..." - zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" < "${bastille_backupsdir}/${TARGET}" + validate_archive + # Based on the file name, looks like we are importing a raw bastille image + # Import from uncompressed image file + info "Importing '${TARGET_TRIM}' from uncompressed image archive." + info "Receiving ZFS data stream..." + zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET_TRIM}" < "${bastille_backupsdir}/${TARGET}" - # Update ZFS mountpoint property if required - update_zfsmount + # Update ZFS mountpoint property if required + update_zfsmount + else + # Based on the file name, looks like we are importing from previous redirected bastille image + # Quietly import from previous redirected bastille image + if ! zfs receive ${OPT_ZRECV} "${bastille_zfs_zpool}/${bastille_zfs_prefix}/jails/${TARGET}"; then + exit 1 + else + # Update ZFS mountpoint property if required + update_zfsmount + fi fi else error_exit "Unknown archive format." @@ -520,7 +537,9 @@ jail_import() { # This is required on foreign imports only update_jailconf update_fstab - info "Container '${TARGET_TRIM}' imported successfully." + if [ -z "${USER_IMPORT}" ]; then + info "Container '${TARGET_TRIM}' imported successfully." + fi exit 0 fi else @@ -551,14 +570,22 @@ if [ -f "${bastille_backupsdir}/${TARGET}" ]; then error_exit "Unrecognized archive name." fi else - error_exit "Archive '${TARGET}' not found." + if echo "${TARGET}" | grep -q '_[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{6\}.*$'; then + error_exit "Archive '${TARGET}' not found." + else + # Assume user will import from standard input + TARGET_TRIM=${TARGET} + USER_IMPORT="1" + fi fi # Check if a running jail matches name or already exist if [ -n "$(jls name | awk "/^${TARGET_TRIM}$/")" ]; then error_exit "A running jail matches name." -elif [ -d "${bastille_jailsdir}/${TARGET_TRIM}" ]; then - error_exit "Container: ${TARGET_TRIM} already exists." +elif [ -n "${TARGET_TRIM}" ]; then + if [ -d "${bastille_jailsdir}/${TARGET_TRIM}" ]; then + error_exit "Container: ${TARGET_TRIM} already exists." + fi fi if [ -n "${TARGET}" ]; then