From 61593da47c015a828fb8a7f2f29a050d7bce2d80 Mon Sep 17 00:00:00 2001 From: m8in Date: Sat, 25 Apr 2026 17:13:19 +0200 Subject: [PATCH] Introducing modules and improvements --- core/base.module.sh | 348 ++++++++++++++++++++++++++++++++ module/log.module.sh | 215 ++++++++++++++++++++ prepareThisHostBeforeCloning.sh | 59 +++++- setupCoreOntoThisHost.sh | 101 ++++----- updateRepositories.sh | 71 +++---- 5 files changed, 676 insertions(+), 118 deletions(-) create mode 100755 core/base.module.sh create mode 100755 module/log.module.sh diff --git a/core/base.module.sh b/core/base.module.sh new file mode 100755 index 0000000..15c1ae7 --- /dev/null +++ b/core/base.module.sh @@ -0,0 +1,348 @@ +#!/bin/bash +[ "${BASH_VERSINFO[0]}" -lt 4 ] \ + && echo "Version 4 or newer is required, bash has version : '${BASH_VERSION}'." >&2 \ + && exit 1 + + + +function checkAllInputParameters() { + local _ALLOWED_CHARS _ARG _SUCCESS + # Global whitelist for all start-parameters ($1, $2, ...) + _ALLOWED_CHARS='-[:alnum:]_.:' + readonly _ALLOWED_CHARS + + _SUCCESS="true" + for _ARG in "${@}"; do + if [[ -n "${_ARG}" ]]; then + # Has to start with an alphanumeric char or -- + if [[ ! "${_ARG}" =~ ^[[:alnum:]] ]] && [[ ! "${_ARG}" =~ ^--[[:alnum:]] ]]; then + echo "❌ Security: No special character is allowed at the bginning of the parameter: '${_ARG}'" >&2 + _SUCCESS="false" + fi + # No forbidden character is allowed to remain + if [[ -n "${_ARG//[${_ALLOWED_CHARS}]/}" ]]; then + echo "❌ Security: Illegal character found in parameter: '${_ARG}'" >&2 + _SUCCESS="false" + fi + fi + done + + [ "${_SUCCESS}" == "true" ] \ + && return 0 + + return 1 +} + +function checkScriptforCorrectAssignments() { + local _LN=0 + local _SUCCESS="true" + + while IFS= read -r _line || [[ -n "${_line}" ]]; do + ((_LN++)) + + [[ ! "${_line}" =~ ^.*(=\$|=\"\$).*$ ]] && continue # Assignments only + + [[ "${_line}" =~ ^[[:space:]]*# ]] && continue # Comments are okay + + [[ "${_line}" =~ ^[[:space:]]+[a-zA-Z0-9_]+=[^\ ]+ ]] && continue # Allow assignments in functions + + [[ "${_line}" =~ ^[a-zA-Z0-9_]+=[^\ ]+ ]] && [[ ! "${_line}" =~ "base.set" ]] \ + && echo "❌ line ${_LN}: direct assignment prohibited! Use 'base.set VARNAME VALUE REGEX' instead." >&2 \ + && _SUCCESS="false" + + done < "${0}" + + [ "${_SUCCESS}" == "true" ] \ + && return 0 + + return 1 +} + +function prepare.setCIS(){ + # Check precondition + [[ "${CIS[SET]:+isset}" != "isset" ]] \ + && base.abort "Array CIS was not initialized correctly." + + # Retrieves the variables for this module using 'BASH_SOURCE[0]', the infos about the script using '$0'. + local _CISROOT _FULLBASENAME _FULLSCRIPTNAME + _FULLBASENAME=$(readlink -e "${BASH_SOURCE[0]}" 2> /dev/null) + _FULLSCRIPTNAME=$(readlink -e "${0}" 2> /dev/null) + _CISROOT=$(echo "${_FULLSCRIPTNAME}" | grep -o '^.*/cis/') + readonly _CISROOT _FULLBASENAME _FULLSCRIPTNAME + + # Folders always ends with an tailing '/' + CIS[ROOT]="${_CISROOT:?"Missing CISROOT"}" + CIS[COREROOT]="${CIS[ROOT]}core/" + CIS[SCRIPTSROOT]="${CIS[ROOT]}script/" + CIS[DOMAIN]=$("${CIS[COREROOT]}"printOwnDomain.sh) + CIS[MODULEDIR]="${CIS[ROOT]}module/" + + [ -z "${CIS[DOMAIN]}" ] \ + && echo \ + && echo "No domain could be found for this host:" \ + && echo " This may be due to an incorrect configuration." \ + && echo \ + && return 1 + + # Sets the valus of the global array 'CIS' and set it readonly + CIS[ARGS]="${@}" + CIS[HOME]="${HOME:-"/root"}/" + CIS[HOST]="$(hostname -b)" + CIS[USER]="$(whoami)" + + # Ensures each user is allowed to create 'his' folder. + CIS[LOGDIR]="/tmp/${CIS[USER]:-"UNKNOWN"}/cis/" + CIS[WORKDIR]="$(pwd)/" + + CIS[BASE]="${_FULLBASENAME:?"Missing FULLBASENAME"}" + CIS[FULLSCRIPTNAME]="${_FULLSCRIPTNAME:?"Missing FULLSCRIPTNAME"}" + + # Like 'dirname ${CIS[FULLSCRIPTNAME]}' + CIS[SCRIPTDIR]="${CIS[FULLSCRIPTNAME]%/*}/" + + # Like 'basename ${CIS[FULLSCRIPTNAME]}' + CIS[SCRIPTNAME]="${CIS[FULLSCRIPTNAME]##*/}" + + CIS[DEFAULTDEFINITIONS]="${CIS[ROOT]}definitions/default/" + CIS[DOMAINDEFINITIONS]="${CIS[ROOT]}definitions/${CIS[DOMAIN]}/" + CIS[DOMAINSTATES]="${CIS[ROOT]}states/${CIS[DOMAIN]}/" + + CIS[SET]="normal" + # Sets the write protection of array 'CIS' + declare -A -g -r CIS + return 0 +} + +function prepare.setCOLOR(){ + # Check the procondition, + [[ "${COLOR[SET]:+isset}" != "isset" ]] \ + && base.abort "Array COLOR was not initialized correctly." + + # set the values into the global array 'COLOR', + COLOR[NO]='\033[0m' + COLOR[RED]='\033[0;31m' + COLOR[GREEN]='\033[0;32m' + COLOR[DARKYELLOW]='\033[0;33m' + COLOR[BLUE]='\033[0;34m' + COLOR[PURPLE]='\033[0;35m' + COLOR[CYAN]='\033[0;36m' + COLOR[LIGHTGREY]='\033[0;37m' + COLOR[DARKGREY]='\033[1;30m' + COLOR[LIGHTRED]='\033[1;31m' + COLOR[LIGHTGREEN]='\033[1;32m' + COLOR[YELLOW]='\033[1;33m' + COLOR[LIGHTBLUE]='\033[1;34m' + COLOR[WHITE]='\033[1;37m' + + # and define the array 'COLOR' as readonly. + declare -A -g -r COLOR + return 0 +} + +function prepare.setPATH(){ + local _GREP_PATH + _GREP_PATH="${1:?"Missing parameter GREP_PATH"}" + readonly _GREP_PATH + # Fixes the paths, ... + if [ -x ${_GREP_PATH} ]; then + echo ":${PATH}:" | ${_GREP_PATH} -q ":/bin:" || export PATH="${PATH}:/bin" 2> /dev/null + echo ":${PATH}:" | ${_GREP_PATH} -q ":/sbin:" || export PATH="${PATH}:/sbin" 2> /dev/null + echo ":${PATH}:" | ${_GREP_PATH} -q ":/usr/bin:" || export PATH="${PATH}:/usr/bin" 2> /dev/null + echo ":${PATH}:" | ${_GREP_PATH} -q ":/usr/sbin:" || export PATH="${PATH}:/usr/sbin" 2> /dev/null + echo ":${PATH}:" | ${_GREP_PATH} -q ":/usr/local/bin:" || export PATH="${PATH}:/usr/local/bin" 2> /dev/null + echo ":${PATH}:" | ${_GREP_PATH} -q ":/usr/local/sbin:" || export PATH="${PATH}:/usr/local/sbin" 2> /dev/null + return 0 + fi + return 1 +} + +function base.abort(){ + # Minimalmode in case of emergency + [[ "${COLOR[SET]:+isset}" != "isset" ]] \ + && printf %b "\nScript aborted during preparation (State: '${CIS[SET]:-""}')!\n" >&2 \ + && printf %b " ${@}\n\n" >&2 \ + && exit 1 + + local _FULLSCRIPTNAME=$(readlink -e "${0}" 2> /dev/null) + local _SCRIPTNAME="${_FULLSCRIPTNAME##*/}" + + [ "${1:+isset}" != "isset" ] \ + && base.printWithColor LIGHTRED "\nScript ${_SCRIPTNAME} aborted!\n\n" >&2 \ + && exit 1 + + [ "${2:+isset}" != "isset" ] \ + && base.printWithColor LIGHTRED "\nScript ${_SCRIPTNAME} aborted!\n" >&2 \ + && base.printWithColor WHITE "${1:?"Missing parameter MESSAGE."}\n\n" >&2 \ + && exit 1 + + [ "${3:+isset}" != "isset" ] \ + && base.printWithColor LIGHTRED "\nScript ${_SCRIPTNAME} aborted!\n" >&2 \ + && base.printWithColor WHITE "${1:?"Missing parameter MESSAGE."}\n\n" >&2 \ + && base.printWithColor CYAN "TIP: ${2:?"Missing parameter TIP."}\n" >&2 \ + && exit 1 + + base.printWithColor LIGHTRED "\nScript ${_SCRIPTNAME} aborted!\n" >&2 + base.printWithColor WHITE "${1:?"Missing parameter MESSAGE."}\n\n" >&2 + base.printWithColor CYAN "TIP - ${2:?"Missing parameter TIP."}:\n" >&2 + while shift; do + [ -z "${2:-""}" ] && break + base.printWithColor LIGHTGREY " ${2}\n" >&2 + done + exit 1 +} + +function base.filterComments(){ + local _FILENAME + _FILENAME="${1:?"base.filterComments() Missing first parameter FILENAME"}" + readonly _FILENAME + + # Filters comments (# und ;) and empty lines, retuns the remaining content... + grep -o "^[[:blank:]]*[^[:blank:]#;].\+$" "${_FILENAME}" \ + && return 0 + + return 1 +} + +# Funktionen von Module, die mit dieser Funktion geladen werden, behalten ihren Name. +# Deshalb dürfen solche Funktionen auch nicht per '→ FunktionsName' aufgerufen werden! +function base.loadModule(){ + local _MODULENAME _MODULEFULLNAME + _MODULENAME="${1:?"Function base.loadModule(): Missing parameter MODULENAME."}" + _MODULEFULLNAME="${CIS[MODULEDIR]:?"Function base.loadModule(): Missing CISMODULEDIR."}/${_MODULENAME}.module.sh" + readonly _MODULENAME _MODULEFULLNAME + + #module already is loaded => return + declare -f "module.${_MODULENAME}" > /dev/null 2>&1 \ + && return 0 + + #Iterates each function and checks for name-collisions with other programms or functions + local _functionName _programPath + for _functionName in $(grep "^[[:space:]]*function" "${_MODULEFULLNAME}" | cut -d' ' -f2 | cut -d'(' -f1); do + _programPath="$(which "${_functionName}")" + echo "${_programPath}" | grep -q "/${_functionName}" \ + && echo "WARNING: Loading this module '${_MODULEFULLNAME}' hides the program '${_programPath}'." + + [ "${_functionName}" == "$(declare -F ${_functionName})" ] \ + && echo "WARNING: Loading this module '${_MODULEFULLNAME}' replaces the existing function '${_functionName}'." + + # Checks the convention of the function's names + echo "${_functionName}" | grep -q "${_MODULENAME}." \ + && continue + + base.abort "Module ${_MODULEFULLNAME} does not comply the convention." "All function names has to start with '${_MODULENAME}.'." + done + + #Command source actually loads the module. + # source <(sed 's/\bfunction \b/&.cis_/' "${_MODULEFULLNAME}") would rename the functions additionally... + #Command eval creates a function which is used to determine if the module already is loaded + source "${_MODULEFULLNAME}" \ + && eval "function module.${_MODULENAME}(){ declare -F | grep '${_MODULENAME}\.' >&2; }" \ + && return 0 + + base.abort "Unable to load module '${_MODULEFULLNAME}'." +} + +function base.log() { + local _LOGLEVEL _LOGLEVEL_UPPER + base.set _LOGLEVEL "${1}" '^(error|warn|info|debug)$' || exit 1 + _LOGLEVEL_UPPER="${_LOGLEVEL:?"base.log(): Missing valid first parameter LOGLEVEL"}" + _LOGLEVEL_UPPER="${_LOGLEVEL_UPPER^^}" + readonly _LOGLEVEL_UPPER + + case "${CIS[LOGLEVEL]:-warn}" in + debug) [ "${_LOGLEVEL_UPPER}" = "DEBUG" ] && echo "[${_LOGLEVEL_UPPER}] $(date +%H:%M:%S) - ${2}" >&2 ;& # Forces execution to continue in the next block + info) [ "${_LOGLEVEL_UPPER}" = "INFO" ] && echo "[${_LOGLEVEL_UPPER}] $(date +%H:%M:%S) - ${2}" >&2 ;& + warn) [ "${_LOGLEVEL_UPPER}" = "WARN" ] && echo "[${_LOGLEVEL_UPPER}] $(date +%H:%M:%S) - ${2}" >&2 ;& + error) [ "${_LOGLEVEL_UPPER}" = "ERROR" ] && echo "[${_LOGLEVEL_UPPER}] $(date +%H:%M:%S) - ${2}" >&2 ;; + esac +} + +function base.printModuleFunctions(){ + local _MODULENAME + _MODULENAME="${1:?"Function base.printModuleFunctions(): Missing parameter MODULENAME."}" + readonly _MODULENAME + + [ "${_MODULENAME}" = "base" ] \ + && declare -f $(declare -F | grep "${_MODULENAME}." | cut -d" " -f3) \ + && return 0 + + # If module is loaded => continue + declare -f "module.${_MODULENAME}" > /dev/null 2>&1 \ + && declare -f $(declare -F | grep "${_MODULENAME}." | cut -d" " -f3) \ + && return 0 + + return 1 +} + +function base.printWithColor() { + local _COLOR _COLOR_KEY _MESSAGE _NO_COLOR + _COLOR_KEY="${1:?"log.color(): Missing first parameter COLOR."}" + # It printing target is a terminal which supports more than 8 colors. + if [ -t 1 ] \ + && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ] \ + && [[ "$(declare -p COLOR 2>/dev/null)" == "declare -A"* ]] \ + && [ -n "${COLOR[${_COLOR_KEY}]}" ] + then + _COLOR="${COLOR[${_COLOR_KEY}]}" + _NO_COLOR="${COLOR[NO]}" + fi + shift + if [ $# -gt 0 ]; then + _MESSAGE="$*" + elif [ ! -t 0 ]; then + # Read from stdin, if there is something in the pipe only. + _MESSAGE=$(cat) + fi + + printf "%b%b%b" "${_COLOR:-""}" "${_MESSAGE}" "${_NO_COLOR:-""}" \ + && return 0 + + return 1 +} + +function base.set() { + local _VARNAME="${1:?"base.set(): Missing first parameter VARNAME"}" + local _VALUE="${2}" + local _REGEX="${3:?"base.set(): Missing third parameter REGEX"}" + + # Sets the value to a global variable with name $_VARNAME + [[ "${_VALUE}" =~ $_REGEX ]] \ + && printf -v "${_VARNAME}" "%s" "${_VALUE}" \ + && readonly "${_VARNAME}" \ + && return 0 + + echo "❌ Security: Validation '$_REGEX' failed for ${_VARNAME}" >&2 + exit 1 +} + + + +# Check if this module was started correctly using source +if [ "${BASH_SOURCE[0]}" == "${0}" ]; then + # Script was executed directly, e.g. by ./base.sh + echo "FAILURE: you are using this module 'base.sh' in a wrong way." + echo " It is intended as a utility library and should not be called directly." + echo + echo "Usage: Call the base module at the beginning of your script e.g. like this:" + echo + echo ' #!/bin/bash' + echo ' source /cis/core/base.module.sh' + echo + echo "Now you can use the functions provided by this module inside your script:" + echo "-------------------------------------------------------------------------" + declare -F | grep "base." | cut -d" " -f3 + exit 1 +else + # If not exists, define a global array 'COLOR' + trap "base.abort ' User-initiated termination.'" INT \ + && checkAllInputParameters "${@}" \ + && declare -A -g COLOR=([SET]=unprepared) \ + && prepare.setCOLOR \ + && prepare.setPATH "/bin/grep" \ + && declare -A -g CIS=([SET]=unprepared) \ + && prepare.setCIS \ + && checkScriptforCorrectAssignments \ + || base.abort "The necessary preparations have failed." + + base.log debug "Module '${BASH_SOURCE[0]}' loaded by script: ${0}" +fi diff --git a/module/log.module.sh b/module/log.module.sh new file mode 100755 index 0000000..84a955b --- /dev/null +++ b/module/log.module.sh @@ -0,0 +1,215 @@ + +#!/bin/bash + +#Function, to highlight bad messages. +function log.bad() { + local _MESSAGE="${@:?"log.bad(): Missing first parameter MESSAGE."}" + + base.printWithColor LIGHTRED "${_MESSAGE}" >&2 \ + && return 0 + + return 1 +} + +#Function, for data. +function log.data() { + local _MESSAGE="${@:?"log.data(): Missing first parameter MESSAGE."}" + + base.printWithColor LIGHTGREY "${_MESSAGE}" >&2 \ + && return 0 + + return 1 +} + +#Function, for uncorrectable errors. +function log.error() { + local _MESSAGE="${@:-""}" + + [ -z "${_MESSAGE:-""}" ] \ + && base.printWithColor LIGHTRED "ERROR!\n" >&2 \ + && return 0 + + base.printWithColor LIGHTRED "ERROR!\n" >&2 \ + && base.printWithColor WHITE " ${_MESSAGE}\n" >&2 \ + && return 0 + + return 1 +} + +#Function, for very important information. +function log.essential() { + local _MESSAGE="${@:?"log.essential(): Missing first parameter MESSAGE."}" + + base.printWithColor LIGHTRED "${_MESSAGE}" >&2 \ + && return 0 + + return 1 +} + +#Function, for failures. +function log.failure() { + local _MESSAGE="${@:-""}" + + [ -z "${_MESSAGE:-""}" ] \ + && base.printWithColor LIGHTRED "FAILURE!\n" >&2 \ + && return 0 + + base.printWithColor LIGHTRED "FAILURE!\n" >&2 \ + && base.printWithColor WHITE " ${_MESSAGE}\n" >&2 \ + && return 0 + + return 1 +} + +#Function, to finish a script. +function log.finish() { + local _SCRIPTNAME="${CIS[SCRIPTNAME]:?"log.finish(): Missing CIS[SCRIPTNAME]."}" + + base.printWithColor WHITE "\nScript ${_SCRIPTNAME}: " >&2 \ + && base.printWithColor LIGHTGREEN "successful!\n\n" >&2 \ + && return 0 + + return 1 +} + +#Function, to highlight good messages. +function log.good() { + local _MESSAGE="${@:?"log.good(): Missing first parameter MESSAGE."}" + + base.printWithColor LIGHTGREEN "${_MESSAGE}" >&2 \ + && return 0 + + return 1 +} + +#Function, for important information. +function log.important() { + local _MESSAGE="${@:?"log.important(): Missing first parameter MESSAGE."}" + + base.printWithColor YELLOW "${_MESSAGE}" >&2 \ + && return 0 + + return 1 +} + +#Function, for normal information. +function log.info(){ + local _MESSAGE="${1:?"log.info(): Missing first parameter MESSAGE."}" + shift + local _DECRIPTION="${@:-""}" + + [ -z "${_DECRIPTION:-""}" ] \ + && base.printWithColor LIGHTBLUE "INFO:\n" >&2 \ + && base.printWithColor WHITE " ${_MESSAGE}\n" >&2 \ + && return 0 + + base.printWithColor LIGHTBLUE "INFO - " >&2 \ + && base.printWithColor WHITE "${_MESSAGE}:\n" >&2 \ + && base.printWithColor LIGHTGREY "${_DECRIPTION}\n" >&2 \ + && return 0 + + return 1 +} + +#Function, for highlighted messages. +function log.message(){ + local _MESSAGE="${@:?"log.message(): Missing first parameter MESSAGE."}" + + base.printWithColor WHITE "${_MESSAGE}" >&2 \ + && return 0 + + return 1 +} + +#Function, for additional information. +function log.optional(){ + local _MESSAGE="${@:?"log.optional(): Missing first parameter MESSAGE."}" + + base.printWithColor LIGHTBLUE "${_MESSAGE}" >&2 \ + && return 0 + + return 1 +} + +#Function, to start a script. +function log.start(){ + local _MESSAGE="${@:-""}" + local _CISROOT="${CIS[ROOT]:?"log.start(): Missing CIS[ROOT]."}" + local _SCRIPTDIR="${CIS[SCRIPTDIR]:?"log.start(): Missing CIS[SCRIPTDIR]."}" + local _SERVICE="$(echo "${_SCRIPTDIR##${_CISROOT}/}" | tr '[:lower:]' '[:upper:]')" + local _COMMAND="${CIS[SCRIPTNAME]:?"log.start(): Missing CIS[SCRIPTNAME]."}" + + [ -z "${_MESSAGE:-""}" ] \ + && base.printWithColor YELLOW "${_SERVICE} ${_COMMAND}:\n\n" >&2 \ + && return 0 + + base.printWithColor YELLOW "${_SERVICE} ${_COMMAND}:\n" >&2 \ + && base.printWithColor LIGHTBLUE "${_MESSAGE}\n\n" >&2 \ + && return 0 + + return 1 +} + +#Function, for successful messages. +function log.success(){ + local _MESSAGE="${@:-""}" + + [ -z "${_MESSAGE:-""}" ] \ + && base.printWithColor LIGHTGREEN "SUCCESS!\n" >&2 \ + && return 0 + + base.printWithColor LIGHTGREEN "SUCCESS!\n" >&2 \ + && base.printWithColor WHITE " ${_MESSAGE}\n" >&2 \ + && return 0 + + return 1 +} + +#Function, for tips. +function log.tip(){ + local _MESSAGE="${1:?"log.tip(): Missing first parameter MESSAGE."}" + + base.printWithColor CYAN "TIP:\n" >&2 + while [ "${_MESSAGE:-""}" != "" ]; do + base.printWithColor LIGHTGREY " ${_MESSAGE}\n" >&2 \ + && shift \ + && _MESSAGE="${1:-""}" \ + && continue + return 1 + done + + return 0 +} + +#Function, for warnings. +function log.warn(){ + local _MESSAGE="${@:?"log.warn(): Missing first parameter MESSAGE."}" + + base.printWithColor YELLOW "WARNING:\n" >&2 \ + && base.printWithColor WHITE " ${_MESSAGE}\n" >&2 \ + && return 0 + + return 1 +} + + + +# Check if this module was started correctly using source +if [ "${BASH_SOURCE[0]}" == "${0}" ]; then + # Script was executed directly + echo "FAILURE: you are using this module 'log.module.sh' in a wrong way." + echo " It is intended as a utility library and should not be called directly." + echo + echo "Usage: Call this module at the beginning of your script e.g. like this:" + echo + echo ' #!/bin/bash' + echo ' source /cis/core/base.module.sh' + echo + echo ' #Loads this module' + echo ' base.loadModule log' + echo + echo "Now you can use the functions provided by this module inside your script:" + echo "-------------------------------------------------------------------------" + declare -F | grep "log." | cut -d" " -f3 + exit 1 +fi diff --git a/prepareThisHostBeforeCloning.sh b/prepareThisHostBeforeCloning.sh index 610d7e7..c5f39c6 100755 --- a/prepareThisHostBeforeCloning.sh +++ b/prepareThisHostBeforeCloning.sh @@ -5,10 +5,45 @@ && echo "so this script is allowed to be executed if you are root only." \ && exit 1 +function goOn() { + local _QUESTION="${1:?"goOn(): Mising first parameter QUESTION"}" + local _TIPP="${2}" + local _ANSWER + + read -p "${_QUESTION}: [y]es or [n]o : " _ANSWER + [ "${_ANSWER}" == "y" ] && return 0 + [ "${_ANSWER}" == "Y" ] && return 0 + [ "${_ANSWER}" == "yes" ] && return 0 + [ "${_ANSWER}" == "Yes" ] && return 0 + [ "${_ANSWER}" == "YES" ] && return 0 + + echo + echo "${_TIPP}" + echo + return 1 +} + function setNeededHostnameOrExit() { - _FQDN="${1:?"Missing unique long hostname (fqdn, eg.: host1.example.net) for this host as first parameter."}" + _FQDN="${1}" + + [ -z "${_FQDN}" ] \ + && ! echo "$(hostname -b)" | grep -q -F '.' \ + && echo "This host needs a unique long hostname (fqdn, eg.: host1.example.net)" \ + && echo "Call this script with a full qualified domain name as first parameter." \ + && exit 1 + + [ -z "${_FQDN}" ] \ + && echo "$(hostname -b)" | grep -q -F '.' \ + && echo "The name of this host is: $(hostname -b)" \ + && goOn "Is this correct?" "Restart this script with a full qualified domain name as first parameter." \ + && return 0 + + [ "${_FQDN}" == "$(hostname -b)" ] \ + && echo "Name of this host already is: $(hostname -b)" \ + && return 0 echo "${_FQDN}" | grep -F '.' &> /dev/null \ + && "Setting name of this host to: ${_FQDN}" \ && hostnamectl set-hostname "${_FQDN}" \ && return 0 @@ -17,12 +52,16 @@ function setNeededHostnameOrExit() { exit 1 } -function printOrGenerateSSHKeysForRoot() { +function printOrGenerateSSHKeys() { git --version > /dev/null || (apt update; apt upgrade -y; apt install git) + local _FULL_USERNAME="$(whoami)@$(hostname -b)" + local _PUBKEY_FILE=~/.ssh/id_ed25519.pub + echo - echo "Public SSH-Key for root@$(hostname -b):" - cat "/root/.ssh/id_ed25519.pub" \ + echo "Printing public SSH-Key of ${_FULL_USERNAME}:" + echo " - Content of '${_PUBKEY_FILE}':" + cat "${_PUBKEY_FILE}" \ && return 0 # -t type of the key pair @@ -32,19 +71,19 @@ function printOrGenerateSSHKeysForRoot() { # -C defines a comment ssh-keygen \ -t ed25519 \ - -f "/root/.ssh/id_ed25519" -q -N "" \ - -C "$(date +%Y%m%d)-root@$(hostname -b)" + -f "${_PUBKEY_FILE}" -q -N "" \ + -C "$(date +%Y%m%d)-${_FULL_USERNAME}" - cat "/root/.ssh/id_ed25519.pub" \ + cat "${_PUBKEY_FILE}" \ && return 0 echo - echo "FAILED: somthing went wrong during the generation the ssh keys." + echo "FAILED: somthing went wrong during the generation the ssh keys for '${_FULL_USERNAME}'." echo " These keys are mandantory. You can try to restart this script." echo } -function showFurtherSteps() {} +function showFurtherSteps() { echo echo "IMPORTANT: It is assumed that repositories for definitions and states already exist" echo " and comply with the naming convention." @@ -66,7 +105,7 @@ function showFurtherSteps() {} # sanitizes all parameters setNeededHostnameOrExit "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && printOrGenerateSSHKeysForRoot \ + && printOrGenerateSSHKeys \ && showFurtherSteps \ && exit 0 diff --git a/setupCoreOntoThisHost.sh b/setupCoreOntoThisHost.sh index fb59987..dc9b545 100755 --- a/setupCoreOntoThisHost.sh +++ b/setupCoreOntoThisHost.sh @@ -1,4 +1,7 @@ #!/bin/bash +source ${CUSTOM_CIS_ROOT:-/}./cis/core/base.module.sh + + [ "$(id -u)" != "0" ] \ && sudo "${0}" "${1}" \ @@ -6,13 +9,6 @@ -# Folders always ends with an tailing '/' -_SETUP="$(readlink -f "${0}" 2> /dev/null)" -_CIS_ROOT="${_SETUP%/setupCoreOntoThisHost.sh}/" #Removes shortest matching pattern '/setupCoreOntoThisHost.sh' from the end -_CORE_SCRIPTS="${_CIS_ROOT:?"Missing CIS_ROOT"}core/" - - - function checkPathsAreAvaiable() { grep --version &> /dev/null \ && [ "$(echo ${PATH} | tr ':' '\n' | grep -c /usr/local/sbin)" -ge 1 ] \ @@ -51,7 +47,7 @@ function checkPreconditions() { ! [ -z "${_DOMAIN}" ] \ && checkPathsAreAvaiable \ && checkGitIsAvailable \ - && git -C "${_CIS_ROOT:?"Missing CIS_ROOT"}" pull &> /dev/null \ + && git -C "${CIS[ROOT]:?"Missing CIS_ROOT"}" pull &> /dev/null \ && return 0 echo @@ -75,9 +71,9 @@ function checkPreconditions() { function getOrSetDomain() { local _CURRENT_DOMAIN _GIVEN_DOMAIN _OVERRIDE_DOMAIN_FILE - _CURRENT_DOMAIN="$("${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}printOwnDomain.sh")" + _CURRENT_DOMAIN="${CIS[DOMAIN]:?"Missing CIS_DOMAIN"}" _GIVEN_DOMAIN="${1}" # Optional parameter DOMAIN - _OVERRIDE_DOMAIN_FILE="${_CIS_ROOT:?"Missing CIS_ROOT"}overrideOwnDomain" + _OVERRIDE_DOMAIN_FILE="${CIS[ROOT]:?"Missing CIS_ROOT"}overrideOwnDomain" readonly _CURRENT_DOMAIN _GIVEN_DOMAIN _OVERRIDE_DOMAIN_FILE ! [ -z "${_CURRENT_DOMAIN}" ] \ @@ -109,8 +105,10 @@ function getOrSetDomain() { } function getRemoteRepositoryPath() { - _REPOSITORY="$(git -C "${_CIS_ROOT:?"Missing CIS_ROOT"}" config --get remote.origin.url 2> /dev/null | grep -i 'git@')" - _PATH="${_REPOSITORY%/*}" #Removes shortest matching pattern '/*' from the end + local _REPOSITORY="$(git -C "${CIS[ROOT]:?"Missing CIS_ROOT"}" config --get remote.origin.url 2> /dev/null | grep -i 'git@')" + local _PATH="${_REPOSITORY%/*}" #Removes shortest matching pattern '/*' from the end + readonly _REPOSITORY _PATH + ! [ -z "${_PATH}" ] \ && echo "${_PATH}/" \ && return 0 @@ -119,22 +117,21 @@ function getRemoteRepositoryPath() { } function addDefinition(){ - local _DEFINITIONS _REPOSITORY - _DEFINITIONS="${1:?"Missing first parameter DEFINITIONS"}" - _REPOSITORY="$(getRemoteRepositoryPath)cis-definition-${2:?"Missing second parameter DOMAIN"}.git" - readonly _DEFINITIONS _REPOSITORY + local _REPOSITORY + _REPOSITORY="$(getRemoteRepositoryPath)cis-definition-${CIS[DOMAIN]}.git" + readonly _REPOSITORY [ "$(id -u)" == "0" ] \ && echo \ && echo "Running setup as 'root' trying to add definition repository:" \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_DEFINITIONS}" readonly "${_REPOSITORY}" \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${CIS[DOMAINDEFINITIONS]}" readonly "${_REPOSITORY}" \ && echo " - definitions are usable for this host." \ && return 0 [ "$(id -u)" != "0" ] \ && echo \ && echo "Running setup as 'user' trying to add definition repository:" \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_DEFINITIONS}" writable "${_REPOSITORY}" \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${CIS[DOMAINDEFINITIONS]}" writable "${_REPOSITORY}" \ && echo " - definitions are usable, as working copy." \ && return 0 @@ -142,22 +139,21 @@ function addDefinition(){ } function addState() { - local _STATES _REPOSITORY - _STATES="${1:?"Missing first parameter STATES"}" - _REPOSITORY="$(getRemoteRepositoryPath)cis-state-${2:?"Missing second parameter DOMAIN"}.git" - readonly _STATES _REPOSITORY + local _REPOSITORY + _REPOSITORY="$(getRemoteRepositoryPath)cis-state-${CIS[DOMAIN]}.git" + readonly _REPOSITORY [ "$(id -u)" == "0" ] \ && echo \ && echo "Running setup as 'root' trying to add state repository:" \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_STATES}" writable "${_REPOSITORY}" \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${CIS[DOMAINSTATES]}" writable "${_REPOSITORY}" \ && echo " - states are usable for this host." \ && return 0 [ "$(id -u)" != "0" ] \ && echo \ && echo "Running setup as 'user' trying to add state repository:" \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_STATES}" writable "${_REPOSITORY}" \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${CIS[DOMAINSTATES]}" writable "${_REPOSITORY}" \ && echo " - states are usable, as working copy." \ && return 0 @@ -165,10 +161,9 @@ function addState() { } function setupCoreFunctionality() { - local _DEFINITIONS _MINUTE_FROM_OWN_IP - _DEFINITIONS="${1:?"Missing DEFINITIONS: 'ROOT/definitions/DOMAIN'"}" + local _MINUTE_FROM_OWN_IP _MINUTE_FROM_OWN_IP="$(hostname -I | xargs -n 1 | grep -F '.' | head -n 1 | cut -d. -f4 || echo 0)" #uses last value from first own ipv4 or 0 as minute value - readonly _DEFINITIONS _MINUTE_FROM_OWN_IP + readonly _MINUTE_FROM_OWN_IP [ "$(id -u)" != "0" ] \ && echo \ @@ -177,38 +172,34 @@ function setupCoreFunctionality() { [ "$(id -u)" == "0" ] \ && echo \ - && echo "Using definitions: '${_DEFINITIONS:?"Missing DEFINITIONS"}' ..." \ + && echo "Using definitions: '${CIS[DOMAINDEFINITIONS]:?"Missing DEFINITIONS"}' ..." \ && echo \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}defineAuthorizedKeysOfUser.sh" "${_DEFINITIONS}" root \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}defineAuthorizedKeysOfUser.sh" "${CIS[DOMAINDEFINITIONS]}" root \ && echo \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}ensureUsageOfDefinitions.sh" "${_DEFINITIONS}" /etc/adduser.conf \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}ensureUsageOfDefinitions.sh" "${CIS[DOMAINDEFINITIONS]}" /etc/adduser.conf \ && echo \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addNormalUser.sh" jenkins \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}addNormalUser.sh" jenkins \ && echo \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}defineAuthorizedKeysOfUser.sh" "${_DEFINITIONS}" jenkins \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}defineAuthorizedKeysOfUser.sh" "${CIS[DOMAINDEFINITIONS]}" jenkins \ && echo \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}ensureUsageOfDefinitions.sh" "${_DEFINITIONS}" /etc/sudoers.d/allow-jenkins-updateRepositories \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}ensureUsageOfDefinitions.sh" "${CIS[DOMAINDEFINITIONS]}" /etc/sudoers.d/allow-jenkins-updateRepositories \ && echo \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addToCrontabEveryHour.sh" "${_SETUP:?"Missing SETUP"}" "${_MINUTE_FROM_OWN_IP}" \ + && "${CIS[COREROOT]:?"Missing CORE_SCRIPTS"}addToCrontabEveryHour.sh" "${CIS[FULLSCRIPTNAME]:?"Missing FULLSCRIPTNAME"}" "${_MINUTE_FROM_OWN_IP}" \ && return 0 return 1 } function setup() { - local _DEFINITIONS _DOMAIN _STATES - _DOMAIN="$(getOrSetDomain "${1}")" + local _DOMAIN="$(getOrSetDomain "${1}")" + readonly _DOMAIN ! checkPreconditions "${_DOMAIN}" \ && return 1 - _DEFINITIONS="${_CIS_ROOT:?"Missing CIS_ROOT"}definitions/${_DOMAIN:?"Missing DOMAIN"}" - _STATES="${_CIS_ROOT:?"Missing CIS_ROOT"}states/${_DOMAIN:?"Missing DOMAIN"}" - readonly _DEFINITIONS _DOMAIN _STATES - - addDefinition "${_DEFINITIONS:?"Missing DEFINITIONS"}" "${_DOMAIN:?"Missing DOMAIN"}" \ - && addState "${_STATES:?"Missing STATES"}" "${_DOMAIN:?"Missing DOMAIN"}" \ - && setupCoreFunctionality "${_DEFINITIONS:?"Missing DEFINITIONS"}" \ + addDefinition \ + && addState \ + && setupCoreFunctionality \ && return 0 echo "FAIL: setup is incomplete: ("$(readlink -f ${0})")" >&2 @@ -216,27 +207,11 @@ function setup() { return 1 } -function isValid() { - # printf '%s' - # - always treats the contents of ${1} as pure plain text. - # grep -qE: checks RegExp, but quiet - printf '%s' "${1}" | grep -qE "${2:?"isValid(): Missing REGEXP"}" -} - -function isValidOptional() { - [ -z "${1}" ] || isValid "${1}" "${2}" -} - -# Parameter 1: Only alphanumeric characters allowed and [.-] if not leading (due to: -oProxyCommand=...). -if isValidOptional "${1}" '^[a-zA-Z0-9][a-zA-Z0-9.-]*$' -then - setup "${1}" \ - && exit 0 -else - echo "Failure: At least one parameter is invalid" >&2 - exit 1 -fi +# Parameter 1: is optional '()?' and only alphanumeric characters are allowed and [.-] if not leading (due to: -oProxyCommand=...). +base.set DOMAIN "${1}" '^([a-zA-Z0-9][a-zA-Z0-9.-]*)?$' || exit 1 +setup "${DOMAIN}" \ + && exit 0 exit 1 diff --git a/updateRepositories.sh b/updateRepositories.sh index 0825c89..2f46ca5 100755 --- a/updateRepositories.sh +++ b/updateRepositories.sh @@ -1,4 +1,7 @@ #!/bin/bash +source /cis/core/base.module.sh + + # No write permission, but terminal => restart as root using sudo, user jenkins can do this without password ! [ -w "${0}" ] \ @@ -14,58 +17,52 @@ # Still no write permission => was not called as root ! [ -w "${0}" ] \ - && echo "Host $HOSTNAME: insufficient rights." \ + && echo "Host ${CIS[HOST]:?"Missing HOST"}: insufficient rights." \ && exit 1 function update_repositories() { - local _CIS_ROOT _DEFINITIONS _DOMAIN _MODE _STATES _UPDATE_REPOSITORIES - _UPDATE_REPOSITORIES="$(readlink -f "${0}" 2> /dev/null)" - _CIS_ROOT="${_UPDATE_REPOSITORIES%/updateRepositories.sh}/" #Removes shortest matching pattern '/updateRepositories.sh' from the end - _MODE="${1:-"--core"}" - _DOMAIN="$(${_CIS_ROOT:?"Missing CIS_ROOT"}core/printOwnDomain.sh)" - _DEFINITIONS="${_CIS_ROOT}definitions/${_DOMAIN:?"Missing DOMAIN from file: ${_CIS_ROOT}domainOfHostOwner"}/" - _STATES="${_CIS_ROOT}states/${_DOMAIN:?"Missing DOMAIN from file: ${_CIS_ROOT}domainOfHostOwner"}/" - readonly _CIS_ROOT _DEFINITIONS _DOMAIN _MODE _STATES _UPDATE_REPOSITORIES + local _MODE="${1:-"--core"}" + readonly _MODE [ "${_MODE}" == "--repair" ] \ - && (git -C "${_CIS_ROOT}" reset --hard origin/main; \ - git -C "${_DEFINITIONS}" reset --hard origin/main; \ - git -C "${_STATES}" reset --hard origin/main; \ + && (git -C "${CIS[ROOT]:?"Missing CISROOT"}" reset --hard origin/main; \ + git -C "${CIS[DOMAINDEFINITIONS]:?"Missing DEFINITIONS"}" reset --hard origin/main; \ + git -C "${CIS[DOMAINSTATES]:?"Missing STATES"}" reset --hard origin/main; \ echo "Run repairs") \ && return 0 [ "${_MODE}" == "--test" ] \ - && git -C "${_CIS_ROOT}" pull \ - && git -C "${_DEFINITIONS}" pull \ - && git -C "${_STATES}" pull \ + && git -C "${CIS[ROOT]:?"Missing CISROOT"}" pull \ + && git -C "${CIS[DOMAINDEFINITIONS]:?"Missing DEFINITIONS"}" pull \ + && git -C "${CIS[DOMAINSTATES]:?"Missing STATES"}" pull \ && echo "Run in testMode successfully." \ && return 0 [ "${_MODE}" == "--scripts" ] \ - && printf "Host $HOSTNAME updating scripts: ${_CIS_ROOT} ... " \ - && (git -C "${_CIS_ROOT}" pull &> /dev/null) \ + && printf "Host $HOSTNAME updating scripts: ${CIS[ROOT]:?"Missing CISROOT"} ... " \ + && (git -C "${CIS[ROOT]:?"Missing CISROOT"}" pull &> /dev/null) \ && echo "(done)" \ && return 0 [ "${_MODE}" == "--definitions" ] \ - && echo "Host ${HOSTNAME} updating definitions: ${_DEFINITIONS} ... " \ - && (git -C "${_DEFINITIONS}" pull &> /dev/null) \ + && printf "Host ${HOSTNAME} updating definitions: ${CIS[DOMAINDEFINITIONS]:?"Missing DEFINITIONS"} ... " \ + && (git -C "${CIS[DOMAINDEFINITIONS]:?"Missing DEFINITIONS"}" pull &> /dev/null) \ && echo "(done)" \ && return 0 [ "${_MODE}" == "--states" ] \ - && echo "Host ${HOSTNAME} updating states: ${_STATES} ... " \ - && (git -C "${_STATES}" pull &> /dev/null) \ + && printf "Host ${HOSTNAME} updating states: ${CIS[DOMAINSTATES]:?"Missing STATES"} ... " \ + && (git -C "${CIS[DOMAINSTATES]:?"Missing STATES"}" pull &> /dev/null) \ && echo "(done)" \ && return 0 [ "${_MODE}" == "--core" ] \ - && echo "Host ${HOSTNAME} updating core including scripts, definitions and states: ${_STATES} ... " \ - && (git -C "${_CIS_ROOT}" pull &> /dev/null) \ - && (git -C "${_DEFINITIONS}" pull &> /dev/null) \ - && (git -C "${_STATES}" pull &> /dev/null) \ + && printf "Host ${HOSTNAME} updating core including scripts, definitions and states ... " \ + && (git -C "${CIS[ROOT]:?"Missing CISROOT"}" pull &> /dev/null) \ + && (git -C "${CIS[DOMAINDEFINITIONS]:?"Missing DEFINITIONS"}" pull &> /dev/null) \ + && (git -C "${CIS[DOMAINSTATES]:?"Missing STATES"}" pull &> /dev/null) \ && echo "(done)" \ && return 0 @@ -73,27 +70,11 @@ function update_repositories() { return 1 } -function isValid() { - # printf '%s' - # - always treats the contents of ${1} as pure plain text. - # grep -qE: checks RegExp, but quiet - printf '%s' "${1}" | grep -qE "${2:?"isValid(): Missing REGEXP"}" -} - -function isValidOptional() { - [ -z "${1}" ] || isValid "${1}" "${2}" -} - -# Parameter 1: Only one of these values are allowed (--core, --definitions, --repair, --scripts, --states, --test) -if isValidOptional "${1}" '^(--core|--definitions|--repair|--scripts|--states|--test)$' -then - update_repositories "${1}" \ - && exit 0 -else - echo "Failure: At least one parameter is invalid" >&2 - exit 1 -fi +# Parameter 1: Only one of these values are allowed, or empty (--core, --definitions, --repair, --scripts, --states, --test)? +base.set MODE "${1}" '^(--core|--definitions|--repair|--scripts|--states|--test)?$' || exit 1 +update_repositories "${MODE}" \ + && exit 0 exit 1