#!/bin/sh # beminit # Boot Environments Manager add-on for XigmaNAS Embedded x64 11.x and later. # (https://www.xigmanas.com/forums) # License: BSD2CLAUSE (BSD 2-clause Simplified License). # Debug script #set -x # Copyright (c) 2019-2021 José Rivera (JoseMR) # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that following conditions are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. 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. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. # Set environment. PATH=${PATH}:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin # Determine full working directory. CWDIR=$(dirname $(realpath $0)) # Global variables. PLATFORM=$(uname -m) PRODUCT=$(uname -i) PRDVERSION=$(uname -r | cut -d '-' -f1 | tr -d '.') PRDPLATFORM=$(cat /etc/platform) SCRIPTNAME=$(basename $0) ROOTFS=$(mount | awk '/ \/ / {print $1}') ZROOT=$(mount | awk -F '/' '/ \/ / {print $1}') CONFIG="/cf/conf/config.xml" APPNAME="bem" BEADM="beadm" ZFS_SENDPARAMS="" ZFS_RECVPARAM="" DEFAULT_COMPRESS="xz -0 -v --threads=0" DEFAULT_DECOMPRESS="xz -c -d -v --threads=0" DEFAULT_COMPRESS_GZ="gzip -1 -v" DEFAULT_DECOMPRESS_GZ="gzip -k -d -c -v" EXTLOGFILE="${CWDIR}/log/bem_ext.log" FULLAPPNAME="BE Manager" LONGAPPNAME="Boot Environments Manager" USRLOCAL="/usr/local" BECTLPATH="/sbin/bectl" BEADMPATH="${USRLOCAL}/sbin" BEADMAPP="${BEADMPATH}/${BEADM}" LOCALSHAREPATH="${USRLOCAL}/share" WWWPATH="/usr/local/www" BEMCONF="/conf/bem_config" BEMCONFLINK="/var/etc/bemconf" BRANCH="master" BEADMURL="https://github.com/vermaden/beadm/archive/master.zip" GITURL="https://github.com/JRGTH/xigmanas-${APPNAME}-extension/archive/${BRANCH}.zip" VERFILE="https://raw.githubusercontent.com/JRGTH/xigmanas-${APPNAME}-extension/${BRANCH}/version" error_notify() { # Log/notify message on error and exit. MSG="$*" logger -t "${SCRIPTNAME}" "${MSG}" echo -e "$*" >&2; exit 1 } runtime_config() { # Check and set be manager utility. if [ -f "${BEADMAPP}" ] && [ ! -f "${BECTLPATH}" ]; then ln -sf ${BEADMAPP} ${BECTLPATH} elif [ -f "${BECTLPATH}" ] && [ ! -f "${BEADMAPP}" ]; then ln -sf ${BECTLPATH} ${BEADMAPP} elif [ ! -f "${BECTLPATH}" ] && [ ! -f "${BEADMAPP}" ]; then beadm_initial_download ln -sf ${BEADMAPP} ${BECTLPATH} else # Make sure command is installed. if [ ! -f "${BEADMAPP}" ]; then error_notify "Error: A suitable command for boot environments management was not found!" fi fi # Create required directories if missing. if [ ! -d ${CWDIR}/conf ]; then mkdir -p ${CWDIR}/conf fi if [ ! -d ${CWDIR}/locale-bem ]; then mkdir -p ${CWDIR}/locale-bem fi if [ ! -d ${CWDIR}/log ]; then mkdir -p ${CWDIR}/log fi # Restore default variables if missing. if ! grep -qw "GUI_ENABLE=" ${CWDIR}${BEMCONF} >/dev/null 2>&1; then sysrc -f ${CWDIR}${BEMCONF} GUI_ENABLE="YES" >/dev/null 2>&1 fi if ! grep -qw "INSTALL_DIR=" ${CWDIR}${BEMCONF} >/dev/null 2>&1; then sysrc -f ${CWDIR}${BEMCONF} INSTALL_DIR="${CWDIR}" >/dev/null 2>&1 fi if ! grep -qw "ZFS_SENDPARAMS=" ${CWDIR}${BEMCONF} >/dev/null 2>&1; then sysrc -f ${CWDIR}${BEMCONF} ZFS_SENDPARAMS="${ZFS_SENDPARAMS}" >/dev/null 2>&1 fi if ! grep -qw "ZFS_RECVPARAM=" ${CWDIR}${BEMCONF} >/dev/null 2>&1; then sysrc -f ${CWDIR}${BEMCONF} ZFS_RECVPARAM="${ZFS_RECVPARAM}" >/dev/null 2>&1 fi if ! grep -qw "DEFAULT_COMPRESS=" ${CWDIR}${BEMCONF} >/dev/null 2>&1; then sysrc -f ${CWDIR}${BEMCONF} DEFAULT_COMPRESS="${DEFAULT_COMPRESS}" >/dev/null 2>&1 fi if ! grep -qw "DEFAULT_DECOMPRESS=" ${CWDIR}${BEMCONF} >/dev/null 2>&1; then sysrc -f ${CWDIR}${BEMCONF} DEFAULT_DECOMPRESS="${DEFAULT_DECOMPRESS}" >/dev/null 2>&1 fi if ! grep -qw "DEFAULT_COMPRESS_GZ=" ${CWDIR}${BEMCONF} >/dev/null 2>&1; then sysrc -f ${CWDIR}${BEMCONF} DEFAULT_COMPRESS_GZ="${DEFAULT_COMPRESS_GZ}" >/dev/null 2>&1 fi if ! grep -qw "DEFAULT_DECOMPRESS_GZ=" ${CWDIR}${BEMCONF} >/dev/null 2>&1; then sysrc -f ${CWDIR}${BEMCONF} DEFAULT_DECOMPRESS_GZ="${DEFAULT_DECOMPRESS_GZ}" >/dev/null 2>&1 fi } beadm_initial_download() { if [ ! -f "${BEADMAPP}" ]; then # Fetch latest beadm package from Git. echo "Fetching latest ${BEADM} files..." fetch -ao ${CWDIR}/master.zip --no-verify-peer --timeout=30 ${BEADMURL} || \ error_notify "Error: A problem has occurred while fetching ${BEADM}." # Extract beadm from package. echo "Extracting ${BEADM} package..." tar -xf ${CWDIR}/master.zip -C ${BEADMPATH} --strip-components 1 beadm-master/beadm || \ error_notify "Error: A problem has occurred while extractig ${BEADM} files." chmod 555 ${BEADMPATH}/${BEADM} rm -f ${CWDIR}/master.zip echo "Done!" fi } beadm_update_download() { # Check if beadm meets minimum requirements. if [ -f ${BEADMAPP} ]; then if ! grep -qw "{BOOTPOOL}" ${BEADMAPP}; then # Fetch latest beadm package. echo "Fetching latest ${BEADM} files..." fetch -ao ${CWDIR}/master.zip --no-verify-peer --timeout=30 ${BEADMURL} || \ error_notify "Error: A problem has occurred while fetching ${BEADM}." beadm_pkg_extract fi fi } beadm_pkg_extract() { # Extract beadm script from package. if [ -f ${CWDIR}/master.zip ]; then if [ -f ${BEADMAPP} ]; then echo "Extracting ${BEADM} package..." tar -xf ${CWDIR}/master.zip -C ${BEADMPATH} --strip-components 1 beadm-master/beadm || \ error_notify "Error: A problem has occurred while extractig ${BEADM} files." chmod 555 ${BEADMPATH}/${BEADM} rm -f ${CWDIR}/master.zip echo "Done!" else error_notify "Error: ${BEADMAPP} file not found!" fi fi } ext_initial_download() { # Always ensure the version file is present, otherwise update the extension files on startup. if [ ! -f ${CWDIR}/version ]; then echo "Fetching and extracting extension files..." mkdir -p ${CWDIR}/update fetch -ao ${CWDIR}/update --no-verify-peer --timeout=30 ${GITURL} || \ error_notify "Error: A problem has occurred while fetching extension package." tar -xf ${CWDIR}/update/*${BRANCH}.zip --exclude='.git*' --strip-components 1 -C ${CWDIR}/update chmod +x ${CWDIR}/update/${SCRIPTNAME} cp -rf ${CWDIR}/update/* ${CWDIR}/ rm -r ${CWDIR}/update rm -f ${CWDIR}/master.zip echo "Done!" fi } extension_upgrade() { # Perform an online extension upgrade. DATE=$(date +"%a %b %d %T %Y") echo "Looking for new ${FULLAPPNAME} Extension package!" mkdir -p ${CWDIR}/update fetch -ao ${CWDIR}/update --no-verify-peer --timeout=30 ${VERFILE} || \ error_notify "Error: A problem has occurred while fetching version file." # Compare version files and fetch latest package if available. if [ -f ${CWDIR}/update/version ]; then UPDATEVER=$(cat ${CWDIR}/update/version | tr -d .) CURRENTVER=$(cat ${CWDIR}/version | tr -d .) if [ "${UPDATEVER}" -gt "${CURRENTVER}" ]; then echo "New ${FULLAPPNAME} Extension package found, performing upgrade..." fetch -ao ${CWDIR}/update --no-verify-peer --timeout=30 ${GITURL} || \ error_notify "Error: A problem has occurred while fetching extension package." tar -xf ${CWDIR}/update/*${BRANCH}.zip --exclude='.git*' --strip-components 1 -C ${CWDIR}/update chmod +x ${CWDIR}/update/${SCRIPTNAME} cp -Rf ${CWDIR}/update/* ${CWDIR}/ rm -r ${CWDIR}/update rm -f ${CWDIR}/master.zip # Logging the update event. UPDATEVERSION=$(cat ${CWDIR}/version) echo "${DATE}: ${FULLAPPNAME} Extension upgraded to ${UPDATEVERSION}" >> ${EXTLOGFILE} echo "${FULLAPPNAME} upgraded to version ${UPDATEVERSION}" echo "${FULLAPPNAME} Extension package upgrade completed!" else echo "${FULLAPPNAME} Extension is on the latest version!" rm -r ${CWDIR}/update fi fi exit $? } product_check() { # Check for the working product. if [ "${PRODUCT}" = "NAS4FREE-x64" ] || [ "${PRODUCT}" = "XIGMANAS-x64" ]; then create_addon_env ext_initial_download #beadm_update_download postinit_cmd gui_start fi } create_addon_env() { # Create required directories. if [ ! -d ${CWDIR}/conf ]; then mkdir -p ${CWDIR}/conf fi if [ ! -d ${CWDIR}/locale-bem ]; then mkdir -p ${CWDIR}/locale-bem fi if [ ! -d ${CWDIR}/log ]; then mkdir -p ${CWDIR}/log fi # Link beminit to /usr/local/sbin. if [ ! -f ${USRLOCAL}/sbin/${SCRIPTNAME} ]; then ln -fs ${CWDIR}/${SCRIPTNAME} ${USRLOCAL}/sbin/${SCRIPTNAME} fi } postinit_cmd() { # Check and generate temporary php script for postinit command. if ! grep -qw ${CWDIR}/${SCRIPTNAME} ${CONFIG}; then touch ${CWDIR}/postinit || error_notify "Error: A problem has occurred while creating the postinit file." chmod +x ${CWDIR}/postinit if [ ! "${PRDVERSION}" -ge "110" ]; then # Generate php script for previous versions. cat << EOF > ${CWDIR}/postinit EOF else # Generate php script for XigmaNAS 11.x versions. cat << EOF > ${CWDIR}/postinit EOF fi # Execute temporary php script. if [ "${OBI_INSTALL}" != "ON" ]; then echo "Creating postinit command..." php-cgi -f ${CWDIR}/postinit && rm ${CWDIR}/postinit || \ error_notify "Error: A problem has occurred while executing postinit file." echo "Done!" fi sysrc -f ${CWDIR}${BEMCONF} GUI_ENABLE="YES" INSTALL_DIR="${CWDIR}" ZFS_SENDPARAMS="${ZFS_SENDPARAMS}" DEFAULT_COMPRESS="${DEFAULT_COMPRESS}" \ DEFAULT_DECOMPRESS="${DEFAULT_DECOMPRESS}" >/dev/null 2>&1 fi } gui_start() { # Initialize the extension gui. if [ -d "${CWDIR}/gui" ]; then # Always ensure the config directory/file exist. if [ ! -f "${CWDIR}${BEMCONF}" ]; then # Try to restore default configuration. runtime_config # Set default config. sysrc -f ${CWDIR}${BEMCONF} GUI_ENABLE=YES INSTALL_DIR=${CWDIR} >/dev/null 2>&1 fi GUI_STATUS=$(sysrc -f ${CWDIR}${BEMCONF} -qn GUI_ENABLE) if [ "${GUI_STATUS}" = "YES" ]; then # Store the installation path and link conf. if ! sysrc -f ${CWDIR}${BEMCONF} -n INSTALL_DIR | grep -q "${CWDIR}"; then sysrc -f ${CWDIR}${BEMCONF} INSTALL_DIR=${CWDIR} >/dev/null 2>&1 fi mkdir -p ${BEMCONFLINK} ln -Ffhs ${CWDIR}/conf ${BEMCONFLINK}/conf # Copy the gui files. ln -fhs ${CWDIR}/gui/ext/bem-gui ${WWWPATH}/ext/ || error_notify "Error: A problem has occurred while copying extension gui files." ln -fhs ${CWDIR}/gui/zfs_bootenv_*.php ${WWWPATH}/ || error_notify "Error: A problem has occurred while copying extension gui files." ln -fhs ${CWDIR}/gui/zfs_bootenv_*.inc ${WWWPATH}/ || error_notify "Error: A problem has occurred while copying extension gui files." fi fi } gui_enable() { # Relink conf and copy the gui files. if [ -d "${CWDIR}/gui" ]; then mkdir -p ${BEMCONFLINK} ln -Ffhs ${CWDIR}/conf ${BEMCONFLINK}/conf sysrc -f ${CWDIR}${BEMCONF} GUI_ENABLE=YES >/dev/null 2>&1 ln -fhs ${CWDIR}/gui/ext/bem-gui ${WWWPATH}/ext/ || error_notify "Error: A problem has occurred while copying extension gui files." ln -fhs ${CWDIR}/gui/zfs_bootenv_*.php ${WWWPATH}/ || error_notify "Error: A problem has occurred while copying extension gui files." ln -fhs ${CWDIR}/gui/zfs_bootenv_*.inc ${WWWPATH}/ || error_notify "Error: A problem has occurred while copying extension gui files." exit 0 else error_notify "Error: Extension gui files not found." fi } gui_disable() { # Disable gui if -t option specified. if [ -d "${CWDIR}/gui" ]; then remove_gui_files sysrc -f ${CWDIR}${BEMCONF} GUI_ENABLE=NO >/dev/null 2>&1 || error_notify "Error: A problem while removing extension gui files." exit 0 else error_notify "Error: Extension gui files not found." fi # Remove empty ext folder to prevent empty "Extensions" tab. if [ -d "${WWWPATH}/ext" ]; then if [ ! "$(ls -A ${WWWPATH}/ext)" ]; then rm -r ${WWWPATH}/ext fi fi } pkg_upgrade() { # Re-fetch beadm package and extract. #beadm_update_download # Check for extension updates. extension_upgrade } remove_gui_files() { if [ -f "${WWWPATH}/zfs_bootenv_gui.php" ]; then GUIFILES="zfs_bootenv_add_gui.php zfs_bootenv_edit_gui.php zfs_bootenv_gui.php zfs_bootenv_info_gui.php zfs_bootenv_maintain_gui.php zfs_bootenv_gui-lib.inc" for file in ${GUIFILES} do rm -f ${WWWPATH}/${file} done fi rm -rf ${WWWPATH}/ext/bem-gui rm -f ${LOCALSHAREPATH}/locale-bem rm -rf ${BEMCONFLINK} } remove_addon() { # Confirm for addon removal. while : do read -p "Do you wish to proceed with the ${FULLAPPNAME} removal? [y/N]:" yn case ${yn} in [Yy]) break;; [Nn]) exit 0;; esac done echo "Proceeding..." remove_gui_files # Remove addon related files and folders only- # to protect any user-created custom files. EXTFILES="conf download gui locale-bem log README.md postinit release_notes update version beminit" for file in ${EXTFILES}; do if [ -f ${CWDIR}/${file} ] || [ -d ${CWDIR}/${file} ]; then rm -rf ${CWDIR}/${file} fi done if [ ! -f ${USRLOCAL}/sbin/${SCRIPTNAME} ]; then rm ${USRLOCAL}/sbin/${SCRIPTNAME} fi echo "Done!" echo "Please manually remove the BE Manager Extension Command Script from the WebGUI." exit 0 } reset_install() { # Reset the extension environment. echo "Removing extension files..." if [ -d ${CWDIR}/conf ]; then rm -rf ${CWDIR}/conf fi if [ -d ${CWDIR}/log ]; then rm -rf ${CWDIR}/log fi if [ -d ${CWDIR}/locale-bem ]; then rm -rf ${CWDIR}/locale-bem fi if [ -f ${CWDIR}/version ]; then rm -f ${CWDIR}/version fi remove_gui_files runtime_config } get_versions() { # Get bem extension version. if [ -f "${CWDIR}/version" ]; then APPVERSION=$(cat ${CWDIR}/version) else APPVERSION="version file not found!" fi # Display product versions. if [ -f "${BECTLPATH}" ]; then echo "currently using bectl" else if [ -f "${BEADMAPP}" ]; then beadm version fi fi echo "extension version: ${APPVERSION}" exit 0 } bem_rc_start() { # Log on startup success, else logging with faults. if [ $? -eq 0 ]; then MSG="script has been started successfully!" logger -t ${SCRIPTNAME} "${MSG}" else MSG="script started with faults" logger -t ${SCRIPTNAME} "${MSG}" fi } bem_init() { # Check for system compatibility. if [ ! "${PLATFORM}" = "amd64" ]; then echo "Unsupported platform!"; exit 1 fi # Check for product compatibility. if [ ! "${PRDVERSION}" -ge "110" ]; then echo "Unsupported version!"; exit 1 fi # Ensure the system is configured for boot environments. if ! echo ${ROOTFS} | grep -qw "${ZROOT}/ROOT"; then MSG="ERROR: This system does not boot from ZFS pool." error_notify "${MSG}" elif ! zpool list -H -o bootfs | grep -qw "${ZROOT}/ROOT"; then MSG="ERROR: This system is not configured for boot environments." error_notify "${MSG}" fi echo "Initializing ${FULLAPPNAME}..." # Function calls. product_check bem_rc_start } # Run-time configuration. runtime_config while getopts ":ougtxdvh" option; do case ${option} in [h]) echo "Usage: ${SCRIPTNAME} -[option]"; echo "Options:" echo " -u Upgrade ${FULLAPPNAME}/Extension packages." echo " -g Enables the addon GUI." echo " -t Disable the addon GUI." echo " -x Reset ${FULLAPPNAME}." echo " -d Uninstall ${FULLAPPNAME}." echo " -v Display product version." echo " -h Display this help message."; exit 0;; [o]) OBI_INSTALL="ON";; # To prevent nested PHP-CGI call for installation with OBI. [u]) pkg_upgrade;; [g]) gui_enable;; [t]) gui_disable;; [x]) reset_install;; [d]) remove_addon;; [v]) get_versions;; [?]) echo "Invalid option, -h for usage."; exit 1;; esac done bem_init