#!/bin/sh

# ------------------------------------------------------------
# Company:  Software- und Organisations-Service GmbH
# Date:     2023-04-05
# Purpose:  enable/disable JS7 features
# Platform: AIX, Linux, MacOS: bash, ksh, zsh, dash
# ------------------------------------------------------------
#
# Example:  ./js7_set_feature.sh --home=/home/sos/agent --list
#
#           returns the list of features indicating an enabled or disabled status per feature
#
# Example:  ./js7_set_feature.sh --home=/home/sos/agent --disable=simple-xml --backup-dir=/home/sos/backups -confirm
#
#           disables the indicated package
#
# Example:  ./js7_set_feature.sh --home=/home/sos/agent --enable=simple-xml,snakeyaml --backup-dir=/home/sos/backups
#
#           performs a dry-run to enable the indicated packages

set -e

# ------------------------------
# Initialize variables
# ------------------------------

product_home=
disable_packages=
enable_packages=
list_packages=
force_packages=
features_file=
sbom_file=
backup_dir=
log_dir=
make_dirs=
show_logs=
confirm=

lib_home=
sbom_home=
hostname=$(hostname)
start_time=$(date +"%Y-%m-%dT%H-%M-%S")


Usage()
{
    >&2 echo ""
    >&2 echo "Usage: $(basename "$0") [Options] [Switches]"
    >&2 echo ""
    >&2 echo "  Options:"
    >&2 echo "    --home=<directory>                 | required: directory to which the JS7 product is installed"
    >&2 echo "    --features=<path>                  | optional: path to features.json file, default: <home>/features.json"
    >&2 echo "    --sbom=<path>                      | optional: path to sbom.json file, default: <home>/sbom.json"
    >&2 echo "    --enable=<package[,package]>       | optional: enables one or more software packages"
    >&2 echo "    --disable=<package[,package]>      | optional: disables one or more software packages"
    >&2 echo "    --backup-dir=<directory>           | optional: backup directory for disabled software packages"
    >&2 echo "    --log-dir=<directory>              | optional: log directory for log output of the script"
    >&2 echo ""
    >&2 echo "  Switches:"
    >&2 echo "    -h | --help                        | displays usage"
    >&2 echo "    --list                             | returns the list of disabled/enabled software packages"
    >&2 echo "    --show-logs                        | shows log output of the script"
    >&2 echo "    --make-dirs                        | creates the backup and logs directories if they do not exist"
    >&2 echo "    --force                            | forces disabling packages without option for later enabling from a backup directory"
    >&2 echo "    --confirm                          | confirms enabling or disabling of software packages"
    >&2 echo ""
}

Log()
{
    if [ -n "${log_file}" ] && [ -f "${log_file}" ]
    then
        echo "$@" >> "${log_file}"
    fi

    if [ -z "${show_logs}" ]
    then
        echo "$@"
    fi
}

LogError()
{
    if [ -n "${log_file}" ] && [ -f "${log_file}" ]
    then
        echo "[ERROR]" "$@" >> "${log_file}"
    fi
    
    >&2 echo "[ERROR]" "$@"
}

# ------------------------------
# Process command line options
# ------------------------------

Arguments()
{
    args=$*
    for option in "$@"
    do
        case "${option}" in
            -h|--help)              Usage
                                    exit
                                    ;;
            # Options
            --home=*)               product_home=$(echo "${option}" | sed 's/--home=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --features=*)           features_file=$(echo "${option}" | sed 's/--features=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --sbom=*)               sbom_file=$(echo "${option}" | sed 's/--sbom=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --enable=*)             enable_packages=$(echo "${option}" | sed 's/--enable=//' | sed 's/^"//' | sed 's/"$//')
                                    ;;
            --disable=*)            disable_packages=$(echo "${option}" | sed 's/--disable=//' | sed 's/^"//' | sed 's/"$//')

                                    ;;
            --backup-dir=*)         backup_dir=$(echo "${option}" | sed 's/--backup-dir=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            --log-dir=*)            log_dir=$(echo "${option}" | sed 's/--log-dir=//' | sed 's/^"//' | sed 's/"$//' | sed 's/^\(.*\)\/$/\1/')
                                    ;;
            # Switches
            --confirm)              confirm=1
                                    ;;
            --list)                 list_packages=1
                                    ;;
            --force)                force_packages=1
                                    ;;
            --make-dirs)            make_dirs=1
                                    ;;
            --show-logs)            show_logs=1
                                    ;;
            *)                      >&2 echo "unknown option: ${option}"
                                    Usage
                                    exit 1
                                    ;;
        esac
    done

    lib_home=${product_home}
    sbom_home=${product_home}

    if [ -n "${product_home}" ] && [ -d "${product_home}" ]
    then
        if [ -d "${product_home}"/install ]
        then
            if [ -L "${product_home}"/jetty_base ]
            then
                lib_home="${product_home}"/jetty_base/webapps/joc/WEB-INF
                sbom_home="${product_home}"/jetty_base/webapps/joc
            else
                lib_home="${product_home}"/webapps/joc/WEB-INF
                sbom_home="${product_home}"/webapps/joc
            fi
            
            if [ ! -d "${lib_home}" ]
            then
                LogError "could not find base directory for libraries: ${lib_home}"
                Usage
                exit 1
            fi

            if [ -f "${product_home}"/sbom.json ]
            then
                sbom_home=${product_home}
            fi
        fi        
    fi

    if [ -z "${features_file}" ]
    then
        features_file="${product_home}"/features.json
    fi

    if [ -z "${sbom_file}" ]
    then
        sbom_file="${sbom_home}"/sbom.json
    fi

    if [ -z "${list_packages}" ] && [ -z "${enable_packages}" ] && [ -z "${disable_packages}" ]
    then
        LogError "no options specified, use --enable, --disable or --list"
        Usage
        exit 1
    fi

    if [ -n "${list_packages}" ] && [ -n "${enable_packages}" ]
    then
        LogError "conflicting options --enable and --list specified"
        Usage
        exit 1
    fi

    if [ -n "${list_packages}" ] && [ -n "${disable_packages}" ]
    then
        LogError "conflicting options --disable and --list specified"
        Usage
        exit 1
    fi

    if [ -n "${enable_packages}${disable_packages}${list_packages}" ] && [ -z "${product_home}" ]
    then
        LogError "home directory not specified: --home=<directory>"
        Usage
        exit 1
    fi

    if [ -n "${product_home}" ] && [ ! -d "${product_home}" ]
    then
        LogError "home directory not found: --home=${product_home}"
        Usage
        exit 1
    fi

    if [ -n "${backup_dir}" ] && [ ! -d "${backup_dir}" ] && [ -z "${make_dirs}" ]
    then
        LogError "backup directory not found and --make-dirs switch not present: --backup-dir=${backup_dir}"
        Usage
        exit 1
    fi

    if [ -n "${enable_packages}" ] && [ -z "${backup_dir}" ]
    then
        LogError "Option --enable requires to specify a backup directory: --backup-dir=<directory>"
        Usage
        exit 1
    fi

    if [ -n "${disable_packages}" ] && [ -z "${backup_dir}" ] && [ -z "${force_packages}" ]
    then
        LogError "Option --disable without option --backup-dir requires to specify switch: --force"
        Usage
        exit 1
    fi

    if [ -n "${log_dir}" ] && [ ! -d "${log_dir}" ] && [ -z "${make_dirs}" ]
    then
        LogError "logs directory not found and --make-dirs switch not present: --log-dir=${log_dir}"
        Usage
        exit 1
    fi

    if [ ! -f "${sbom_file}" ]
    then
        LogError "sbom.json file not found: --sbom=${sbom_file}"
        Usage
        exit 1
    fi

    if [ ! -f "${features_file}" ]
    then
        if [ -n "${disable_packages}" ]
        then
            touch "${features_file}"
        else
            LogError "features.json file not found, it will be created when disabling packages: --features=${features_file}"
            Usage
            exit 1
        fi
    fi

    if [ -n "${enable_packages}" ]
    then
        set -- "$(echo "${enable_packages}" | sed -r 's/[,]+/ /g')"
        for package_name in $@; do
            package_name_found=$(< "${features_file}" jq -r ".packages[]? | select(.name == \"${package_name}\") | .name // empty" | sed 's/^"//' | sed 's/"$//')
            if [ ! "$package_name" = "${package_name_found}" ]
            then
                LogError "package not found: --enable=${package_name}"
                Usage
                exit 1
            fi

            package_enabled=$(< "${features_file}" jq -r ".packages[]? | select(.name == \"${package_name}\") | .enabled // empty" | sed 's/^"//' | sed 's/"$//')
            if [ "${package_enabled}" = "true" ]
            then
                LogError "package already enabled: --enable=${package_name}"
                Usage
                exit 1
            fi
        done
    fi

    if [ -n "${disable_packages}" ]
    then
        set -- "$(echo "${disable_packages}" | sed -r 's/[,]+/ /g')"
        for package_name in $@; do
            package_name_found=$(< "${features_file}" jq -r ".packages[]? | select(.name == \"${package_name}\") | .name // empty" | sed 's/^"//' | sed 's/"$//')
            if [ -n "${package_name_found}" ]
            then
                if [ ! "$package_name" = "${package_name_found}" ]
                then
                    Log "package not found: --disable=${package_name}"
                    Usage
                    exit 1
                fi

                package_enabled=$(< "${features_file}" jq -r ".packages[]? | select(.name == \"${package_name}\") | .enabled // empty" | sed 's/^"//' | sed 's/"$//')
                if [ -z "$package_enabled" ]
                then
                    LogError "package already disabled: --disable=${package_name}"
                    Usage
                    exit 1
                fi
            fi

            package_name_found=$(< "${sbom_file}" jq -r ".components[]? | select(.name == \"${package_name}\") | .\"name\" // empty" | sed 's/^"//' | sed 's/"$//')
            if [ -z "${package_name_found}" ]
            then
                Log "package not found: --disable=${package_name}"
                Usage
                exit 1
            fi
        done
    fi

    # initialize logging
    if [ -n "${log_dir}" ]
    then
        # create log directory if required
        if [ ! -d "${log_dir}" ] && [ -n "${make_dirs}" ]
        then
            mkdir -p "${log_dir}"
        fi
    
        log_file="${log_dir}"/js7_set_feature."${hostname}"."${start_time}".log
        while [ -f "${log_file}" ]
        do
            sleep 1
            start_time=$(date +"%Y-%m-%dT%H-%M-%S")
            log_file="${log_dir}"/js7_set_feature."${hostname}"."${start_time}".log
        done
        
        touch "${log_file}"
    fi

    if [ -z "${list_packages}" ]
    then
        Log "-- begin of log --------------"
        Log "$0" "${args}"
        Log "-- begin of output -----------"
    fi
}

# ------------------------------
# Perform installation
# ------------------------------

FindComponentDown()
(
    component_ref=$1
    indent_level=$(($2+1))
    
    indent_string=$(printf "%${indent_level}s" |tr " " ".")
    Log "${indent_string} recursing affected component reference: ${component_ref}"
    
    set -- "$(< "${sbom_file}" jq -r ".dependencies[]? | select(.ref == \"${component_ref}\") | .dependsOn[] // empty" | sed 's/^"//' | sed 's/"$//')"
    for dependent_ref in $@; do
        if [ -n "${dependent_ref}" ]
        then
            FindComponentDown "${dependent_ref}" "${indent_level}"
        fi
    done
)

FindComponentUp()
(
    component_ref=$1
    indent_level=$(($2+1))

    indent_string=$(printf "%${indent_level}s" |tr " " ".")
    Log "${indent_string} recursing affected component reference: ${component_ref}"

    set -- "$(< "${sbom_file}" jq -c ".dependencies[]? | select(.dependsOn[]? | contains(\"${component_ref}\")) | .ref // empty" | sed 's/^"//' | sed 's/"$//')"
    for dependent_ref in $@; do
        if [ -n "${dependent_ref}" ]
        then
            FindComponentUp "${dependent_ref}" "${indent_level}"
        fi
    done
)

Process()
{
    if [ -n "${make_dirs}" ] && [ -n "${backup_dir}" ] && [ ! -d "${backup_dir}" ]
    then
        Log "creating backup directory: ${backup_dir}"
        mkdir -p "${backup_dir}"
    fi

    if [ -n "${list_packages}" ]
    then
        < "${features_file}" jq ".packages[]"
        exit
    fi
    
    if [ -n "${enable_packages}" ]
    then
        set -- "$(echo "${enable_packages}" | sed -r 's/[,]+/ /g')"
        for package_name in $@; do
            Log ".. checking to enable package: ${package_name}"
            set -- "$(< "${sbom_file}" jq -r ".components[]? | select(.name == \"${package_name}\") | .\"bom-ref\" // empty" | sed 's/^"//' | sed 's/"$//')"
            for component_ref in $@; do
                component_name=$(< "${sbom_file}" jq -r ".components[]? | select(.\"bom-ref\" == \"${component_ref}\") | .name // empty")
                component_group=$(< "${sbom_file}" jq -r ".components[]? | select(.\"bom-ref\" == \"${component_ref}\") | .group // empty")
                component_version=$(< "${sbom_file}" jq -r ".components[]? | select(.\"bom-ref\" == \"${component_ref}\") | .version // empty")

                if [ -n "${confirm}" ]
                then
                    if [ -n "${backup_dir}" ]
                    then
                        tar_file=/tmp/js7_features.$$.tar
                        cd "${backup_dir}" > /dev/null                        
                        set -- "$(find ./lib -type f -name "${component_name}*${component_version}.jar")"
                        Log ".... restoring component files from backup directory: ${backup_dir}"
                        Log "$@"
                        tar -cf "${tar_file}" "$@"
                        cd "${lib_home}" > /dev/null
                        tar -xf "${tar_file}"
                        rm -f "${tar_file}"
                        cd - > /dev/null
                        cd - > /dev/null
                    fi
                    
                    Log ".... removing component files from backup directory: ${backup_dir}/lib/*/${component_name}*${component_version}.jar"
                    find "${backup_dir}"/lib -type f -name "${component_name}*${component_version}.jar" -exec rm -f {} \;
                else
                    cd "${backup_dir}" > /dev/null
                    set -- "$(find ./lib -type f -name "${component_name}*${component_version}.jar")"
                    cd - > /dev/null
                    Log ".... candidate component files to be restored from backup directory: ${backup_dir}"
                    Log "$@"
                fi

                # create/update features.json file
                if [ -n "${confirm}" ]
                then
                    packages_found=$(< "${features_file}" jq -r ".packages[]? // empty" | sed 's/^"//' | sed 's/"$//')
                    if [ -z "${packages_found}" ]
                    then
                        echo "{ \"packages\": [] }" | jq '.' > "${features_file}"
                    fi

                    package_name_found=$(< "${features_file}" jq -r ".packages[]? | select(.name == \"${package_name}\") | .name // empty" | sed 's/^"//' | sed 's/"$//')
                    if [ -n "${package_name_found}" ]
                    then
                        < "${features_file}" jq -r "(.packages[]? | select(.name == \"${package_name}\")).enabled |= true" > "${features_file}~"
                    else
                        < "${features_file}" jq ".packages[.packages | length] |= . + { \"group\": \"${component_group}\", \"name\": \"${component_name}\", \"version\": \"${component_version}\", \"enabled\": true }" > "${features_file}~"
                    fi
                
                    if [ -s "${features_file}~" ]
                    then
                        mv "${features_file}~" "${features_file}"
                    fi
                fi
            done

            if [ -n "${confirm}" ]
            then
                Log ".. package enabled: ${package_name}"
            fi
        done
    fi

    if [ -n "${disable_packages}" ]
    then
        set -- "$(echo "${disable_packages}" | sed -r 's/[,]+/ /g')"
        for package_name in $@; do
            Log ".. checking to disable package: ${package_name}"
            component_ref=""
            set -- "$(< "${sbom_file}" jq -r ".components[]? | select(.name == \"${package_name}\") | .\"bom-ref\" // empty" | sed 's/^"//' | sed 's/"$//')"
            for component_ref in $@; do
                component_name=$(< "${sbom_file}" jq -r ".components[]? | select(.\"bom-ref\" == \"${component_ref}\") | .name // empty")
                component_group=$(< "${sbom_file}" jq -r ".components[]? | select(.\"bom-ref\" == \"${component_ref}\") | .group // empty")
                component_version=$(< "${sbom_file}" jq -r ".components[]? | select(.\"bom-ref\" == \"${component_ref}\") | .version // empty")

                if [ -n "${confirm}" ]
                then
                    if [ -n "${backup_dir}" ]
                    then
                        tar_file=/tmp/js7_features.$$.tar
                        cd "${lib_home}" > /dev/null
                        set -- "$(find ./lib -type f -name "${component_name}*${component_version}.jar")"
                        Log ".... creating copies of component files in backup directory: ${backup_dir}"
                        Log "$@"
                        tar -cf "${tar_file}" $@
                        cd "${backup_dir}" > /dev/null
                        tar -xf "${tar_file}"
                        rm -f "${tar_file}"
                        cd - > /dev/null
                        cd - > /dev/null
                    fi
                    
                    Log ".... removing component files: ${lib_home}/lib/*/${component_name}*${component_version}.jar"
                    find "${lib_home}"/lib -type f -name "${component_name}*${component_version}.jar" -exec rm -f {} \;
                else
                    cd "${lib_home}" > /dev/null
                    set -- "$(find ./lib -type f -name "${component_name}*${component_version}.jar")"
                    cd - > /dev/null
                    Log ".... candidate component files for removal from directory: ${lib_home}"
                    Log "$@"
                fi

                # ceate/update features.json file
                if [ -n "${confirm}" ]
                then
                    packages_found=$(< "${features_file}" jq -r ".packages[] // empty" | sed 's/^"//' | sed 's/"$//')
                    if [ -z "${packages_found}" ]
                    then
                        echo "{ \"packages\": [] }" | jq '.' > "${features_file}"
                    fi

                    package_name_found=$(< "${features_file}" jq -r ".packages[]? | select(.name == \"${package_name}\") | .name // empty" | sed 's/^"//' | sed 's/"$//')
                    if [ -n "${package_name_found}" ]
                    then
                        < "${features_file}" jq -r "(.packages[]? | select(.name == \"${package_name}\")).enabled |= false" > "${features_file}~"
                    else
                        < "${features_file}" jq ".packages[.packages | length] |= . + { \"group\": \"${component_group}\", \"name\": \"${component_name}\", \"version\": \"${component_version}\", \"enabled\": false }" > "${features_file}~"
                    fi
    
                    if [ -s "${features_file}~" ]
                    then
                        mv "${features_file}~" "${features_file}"
                    fi
                fi

                FindComponentUp "${component_ref}" 4
            done

            if [ -z "${component_ref}" ]
            then
                Log ".. package not found: ${package_name}"
            else
                if [ -n "${confirm}" ]
                then
                    Log ".. package disabled: ${package_name}"
                fi
            fi
        done
    fi
}

# ------------------------------
# Cleanup temporary resources
# ------------------------------

End()
{
    if [ -n "${tar_file}" ] && [ -f "${tar_file}" ]
    then
        rm -f -r "${tar_file}"
    fi

    if [ "$1" = "EXIT" ]
    then
        if [ -z "${list_packages}" ]
        then
            Log "-- end of log ----------------"
        fi

        if [ -n "${show_logs}" ] && [ -f "${log_file}" ]
        then
            cat "${log_file}"
        fi        
    fi

    unset product_home
    unset disable_packages
    unset enable_packages
    unset list_packages
    unset force_packages
    unset features_file
    unset sbom_file
    unset backup_dir
    unset log_dir
    unset make_dirs
    unset show_logs

    unset lib_home
    unset sbom_home
    unset hostname
    unset start_time
    unset tar_file

    set +e
}

# ------------------------------
# Enable trap and start
# ------------------------------

trap 'End EXIT' EXIT
trap 'End SIGTERM' TERM
trap 'End SIGINT' INT

Arguments "$@"
Process
