Introducing modules and improvements

This commit is contained in:
m8in
2026-04-25 17:13:19 +02:00
parent e2e1845ce9
commit 61593da47c
5 changed files with 676 additions and 118 deletions
+348
View File
@@ -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
+215
View File
@@ -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
+49 -10
View File
@@ -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
+38 -63
View File
@@ -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
+26 -45
View File
@@ -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