#!@PMODULES_HOME@/bin/bash unset CDPATH shopt -s expand_aliases declare -r bindir=$(cd $(dirname "$0") && pwd) declare -r libdir=$(cd "${bindir}/../lib" && pwd) source "${libdir}/libpmodules.bash" print_version() { echo " Pmodules ${version} using Tcl Environment Modules @MODULES_VERSION@ Copyright GNU GPL v2 " 1>&2 } usage() { local -r prog=$(basename $0) print_version echo " Usage: ${prog} [ switches ] [ subcommand ] [subcommand-args ] Switches: --dry-run do nothing --force force overwrite Available SubCommands and Args: init [--src=] [--user=] Initialize a new minimal Pmodule environment. install [--with=...] Install matching modules sync [--delete] [--dst=] Synchronize modules. " } declare force='no' declare dry_run='no' declare DRY='' declare subcommand='' declare sargs=() subcommand_help_init() { echo " init [--src=] [--user=] Initialize a new minimal Pmodule environment in directory . The parameter must only be present if ${prog} is executed as root. " 1>&2 } subcommand_help_install() { echo " install ... [--with=...] [--release=...] [--src=] Install matching modules " 1>&2 } subcommand_help_sync() { echo " sync [--delete] [--dst=] Synchronize environment modules and configuration files from Pmodule environment to Pmodule environment (default: currently active Pmodule environment). Not yet implemented: If --delete is given, unmarked modules present in will be deleted. " 1>&2 } subcommand_help() { if [[ $# == 0 ]]; then usage elif typeset -F subcommand_help_$1 > /dev/null 2>&1 ; then # help for sub-command subcommand_help_$1 else usage fi } # # Derive the relative module installation path # from the relative modulefile path # # $1: relative module file path # get_module_prefix() { local -a comp=( ${1//\// } ) # split rel.path into components local path="${comp[0]}" # result path local -i i for ((i=1; i<${#comp[@]}; i+=2)); do path+="/${comp[$((-i-1))]}/${comp[$((-i))]}" done echo "${path}" } # # Derive the relative module release file path # from the relative module file path # # $1: relative module file path # get_releasefile_name() { echo "$(dirname "$1")/.release-$(basename "$1")" } # # Sync a module from one Pmodules environment to another: # - sync module installation # - sync modulefile # - sync release file # # $1: relative modulefile path # $2: source prefix of Pmodule environment # $3: target prefix of Pmodule environment # sync_module() { local -r rel_modulefile=$1 local -r src_prefix=$2 local -r target_prefix=$3 local -r rel_module_prefix=$( get_module_prefix "${rel_modulefile}" ) local -r rel_releasefile=$( get_releasefile_name "${rel_modulefile}" ) if [[ ! -d "${target_prefix}/${rel_module_prefix}" ]] || [[ "${force}" == 'yes' ]]; then $DRY mkdir -p "${target_prefix}/${rel_module_prefix}" || return $? $DRY rsync --links --perms --recursive --delete \ "${src_prefix}/${rel_module_prefix}/" \ "${target_prefix}/${rel_module_prefix}/" || return $? fi local -r src_modulefile="${src_prefix}/${PSI_MODULES_ROOT}/${rel_modulefile}" local -r src_releasefile="${src_prefix}/${PSI_MODULES_ROOT}/${rel_releasefile}" local -r target_modulefile="${target_prefix}/${PSI_MODULES_ROOT}/${rel_modulefile}" local -r target_releasefile="${target_prefix}/${PSI_MODULES_ROOT}/${rel_releasefile}" if [[ -e "${src_modulefile}" ]] || [[ -e "${src_releasefile}" ]]; then local dir=$( dirname "${target_modulefile}" ) $DRY mkdir -p "${dir}" || return $? fi if [[ -e "${src_modulefile}" ]]; then $DRY rsync --links --perms --recursive \ "${src_modulefile}" "${target_modulefile}" || return $? fi if [[ -e "${src_releasefile}" ]]; then $DRY rsync --links --perms --recursive \ "${src_releasefile}" "${target_releasefile}" || return $? fi } # # Sync the Pmodules configuration and templates # # $1: source prefix of Pmodule environment # $2: target prefix of Pmodule environment # sync_config() { src="$1/${PSI_CONFIG_DIR}/" dst="$2/${PSI_CONFIG_DIR}/" $DRY rsync --recursive --links --perms --delete \ "${src}" "${dst}" || die 1 "Error: synch operation failed!" echo src="$1/${PSI_TEMPLATES_DIR}/" dst="$2/${PSI_TEMPLATES_DIR}/" $DRY rsync --recursive --links --perms --delete \ "${src}" "${dst}" || die 1 "Error: synch operation failed!" echo } # # Delete a module # # $1: relative modulefile path # $2: target prefix of Pmodule environment # delete_module() { echo "Not implemented yet!" } subcommand_init() { local src='' local target_prefixes=() local user='' local opts='' opts=$(get_options -o h -l src: -l user: -l help -l version: -- "$@") if [[ $? != 0 ]]; then subcommand_help_init exit 1 fi eval set -- "${opts}" while (($# > 0)); do case $1 in --src ) src=$2 shift ;; --user ) user=$2 shift ;; --version ) PMODULES_VERSION=$2 shift ;; -- ) : ;; -* | -h | --help ) echo "$1: illegal option" 1>&2 subcommand_help_init exit 1 ;; * ) target_prefixes+=( "$1" ) ;; esac shift done (( ${#target_prefixes[@]} != 0 )) || die 1 "Error: no target directory specified!" if [[ -n "${src}" ]]; then [[ -d "${src}" ]] || die 1 "Error: ${src}: source directory does not exist!" [[ -r "${src}/config/profile.bash" ]] || \ die 1 "Error: ${src}: shell profile does not exist or is not readable!" source "${src}/config/profile.bash" fi local -i euid=$(id -u) if (( euid == 0 )); then [[ -n "${user}" ]] || \ die 1 "Error: --user parameter is required!" id -u "${user}" > /dev/null 2>&1 || \ die 1 "Error: Unable to retrieve user id of user '${user}'" else [[ -z "${user}" ]] || \ die 1 "Error: --user option is only allowed if running as root!" fi check_pmodules_env || die 1 "Giving up..." echo " Attempting to create a minimal module environment from the environment at '${PSI_PREFIX}' " init_pmodules_environment() { local -r src_prefix="${PSI_PREFIX}" local -r target_prefix=$1 local src='' local dst='' echo "Initializing target directory '${target_prefix}' ..." echo if [[ -d "${target_prefix}" ]] && [[ ${force} == no ]]; then echo "Warning: ${target_prefix} already exists." get_YN_answer "Do you really want to re-run the initialization? (y/N) " || \ die 1 "Abort ..." fi echo "Creating target directory '${target_prefix}'..." $DRY mkdir -p "${target_prefix}" || die 1 "Error: make directory failed!" echo echo "Syncing configuration ..." sync_config "${PSI_PREFIX}" \ "${target_prefix}" || die 1 "Error: configuration synchronization failed!" dst="${target_prefix}/${PSI_MODULES_ROOT}/" echo "Creating root directory '${dst}' for module hierarchy ..." $DRY mkdir -p "${dst}" echo echo "Syncing Pmodules ..." sync_module "Tools/Pmodules/${PMODULES_VERSION}" \ "${src_prefix}" \ "${target_prefix}" || die 1 "Error: sync Pmodules failed!" echo dst="${target_prefix}/${PSI_CONFIG_DIR}/environment.bash" echo "Adding installation source '${src_prefix}' to '${dst}'..." sed -i .bak '/PMODULES_INSTALL_SOURCE/d' "${dst}" echo "declare -x PMODULES_INSTALL_SOURCE=\"${src_prefix}\"" >> "${dst}" echo if [[ -n "${user}" ]]; then echo "Changing user of new module environment to '${user}'..." $DRY chown -R "${user}" "${target_prefix}" || die 1 "Error: changing owner failed!" echo fi echo "New minimal module environment created at '${target_prefix}'." echo "To use this environment, execute" echo " sudo ln -fs ${target_prefix} /opt/psi" echo " source /opt/psi/${PSI_CONFIG_DIR}/profile.bash" } umask 022 for target_prefix in "${target_prefixes[@]}"; do init_pmodules_environment "${target_prefix}" done } subcommand_install() { local opts='' local -a with=() local -a releases=() local -a module_pattern=() local -r src_prefix="${PMODULES_INSTALL_SOURCE}" local -r target_prefix="${PSI_PREFIX}" opts=$(get_options -o hf -l dry-run -l force -l with: -l release: -l help -l src: -- "$@") if [[ $? != 0 ]]; then subcommand_help_install exit 1 fi eval set -- "${opts}" while (($# > 0)); do case $1 in --dry-run ) DRY='echo' ;; --force | -f ) force='yes' ;; --release ) releases+=( "$2" ) shift ;; --src ) src_prefix="$2" shift ;; --with ) with+=( "$2" ) shift ;; -- ) : ;; -h | --help ) subcommand_help_install exit 1 ;; -* ) echo "$1: illegal option" 1>&2 subcommand_help_init exit 1 ;; * ) module_pattern+=( "$1" ) ;; esac shift done local -A modules_to_install local -i n=0 while read rel_modulefile; do modules_to_install["${rel_modulefile}"]+='.' let n+=1 done < <(${PMODULES_HOME}/bin/modulecmd bash search \ "${module_pattern[@]}" \ "${with[@]/#/--with=}" \ "${releases[@]/#/--release=}" \ --no-header --print-modulefiles \ --src="${src_prefix}" 2>&1) (( n == 0 )) && die 0 "Nothing to install..." echo -e "The following modules will be installed/updated:\n" 1>&2 for key in "${!modules_to_install[@]}"; do echo " ${key}" 1>&2 done echo 1>&2 get_YN_answer "Do you want to continue? [n] " || die 1 "Aborting..." echo 1>&2 for rel_modulefile in "${!modules_to_install[@]}"; do if [[ -d "${target_prefix}/${rel_modulefile}" ]]; then echo " Updating; ${rel_modulefile}..." 1>&2 else echo " Installing: ${rel_modulefile}..." fi sync_module "${rel_modulefile}" \ "${src_prefix}" \ "${target_prefix}" done echo -e "\nDone!\n" 1>&2 } subcommand_sync() { [[ -z "${PSI_PREFIX}" ]] && die 1 "Error: No current module environment is configured!" local delete=false local opts='' local dst_prefix='' local src_prefix='' opts=$(get_options -o h -l dst: -l delete -l help -- "$@") if [[ $? != 0 ]]; then subcommand_help_sync exit 1 fi eval set -- "${opts}" while (($# > 0)); do case $1 in --dst ) dst_prefix="$2" shift ;; --delete ) delete=true ;; -- ) : ;; -* | -h | --help ) echo "$1: illegal option" 1>&2 subcommand_help_init exit 1 ;; * ) [[ -n "${src_prefix}" ]] && die 1 "Error: Only one source is allowed!" src_prefix="$1" ;; esac shift done unset -v opts if [[ -z "${dst_prefix}" ]]; then dst_prefix="${PSI_PREFIX}" fi ( PSI_PREFIX="${dst_prefix}" check_pmodules_env || die 1 "Error: invalid destination modules environment!" ) || die 1 "Giving up..." : ${src_prefix:=${PMODULES_INSTALL_SOURCE}} if [[ -z "${src_prefix}" ]]; then die 1 "Error: no source module environment was specified!" fi ( PSI_PREFIX="${src_prefix}" check_pmodules_env || die 1 "Error: invalid source modules environment!" ) || die 1 "Giving up..." [[ "$( cd "$src_prefix"; pwd -P )" == "$( cd "$dst_prefix"; pwd -P )" ]] && die 1 "Error: source and destination are equal!" local modbin=${PMODULES_HOME#"${PSI_PREFIX}/"}/bin/modulecmd.tcl local file_type_src=$( file -b "${src_prefix}/${modbin}" 2>&1 || echo err1 ) local file_type_dst=$( file -b "${dst_prefix}/${modbin}" 2>&1 || echo err2 ) [[ "${file_type_src}" == "${file_type_dst}" ]] || die 1 "Error: The file signatures in the source and destination installation do not match!" unset -v file_type_src file_type_dst local dialog_script="${PMODULES_HOME}/bin/dialog.bash" [[ -r "$dialog_script" ]] || die 1 "Error: Unable to find dialog script of installation $dialog_script"; DIALOG_LIB=1 # use dialog script as a library source "$dialog_script" # dialog functions # Redefine module_out to append modules to the selected_modules variable local -a selected_modules function module_out() { local -a args=(${modlist[$1]}) local path="" IFS=/ [[ -n "${args[3]}" ]] && path="/${args[*]:3}" unset IFS selected_modules+=( "${args[2]}${path}/${args[0]}" ) } module_picker "${dst_prefix}" "${src_prefix}" || { # this calls module_out for each selected module, filling up the selected_modules array echo "Abort!" exit 1 } local -a destination_modules=( $(cd "${dst_prefix}/${PSI_MODULES_ROOT}"; find -L . -type f | while read f; do n=${f##*/}; [[ "${n:0:1}" == "." ]] || echo ${f#./}; done) ) # redefine set difference, the version in dialog.bash only handles integers function set_difference() { # $1 \ $2 local -a operand1=($1) local -a operand2=($2) local -A members local elem for elem in "${operand1[@]}"; do members[$elem]=1 done for elem in "${operand2[@]}"; do unset members[$elem] done echo ${!members[@]} } if [[ "$delete" == "true" ]]; then local -a modules_delete=( $(set_difference "${destination_modules[*]}" "${selected_modules[*]}") ) for m in "${modules_delete[@]}"; do echo "Deleting module $m ..." delete_module "$m" "$dst_prefix" done unset modules_delete fi local -a modules_copy=( $(set_difference "${selected_modules[*]}" "${destination_modules[*]}") ) if [[ -n $modules_copy ]]; then echo "Syncing configuration ..." sync_config "$src_prefix" "$dst_prefix" || die 1 "Error: syncing the configuration failed" fi for m in "${modules_copy[@]}"; do echo "Copying module $m ..." sync_module "$m" "$src_prefix" "$dst_prefix" || die 1 "Error: syncing of module $m failed!" done unset modules_copy } while (($# > 0)); do case $1 in -h | -H | -\? | --help | -help ) usage exit 1 ;; -V | --version ) print_version exit 1 ;; -f | --force ) force='yes' ;; --dry-run ) dry_run='yes' DRY='echo' ;; -* ) echo "$1: unknown switch.\n" 1>&2 exit 1 ;; init|install|sync|help ) subcommand="subcommand_$1" shift sargs=( $* ) shift $# ;; * ) echo "$1: unknown sub-command" 1>&2 exit 1 esac shift done $subcommand "${sargs[@]}" # Local Variables: # mode: sh # sh-basic-offset: 8 # tab-width: 8 # End: