diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff1f297 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Ignore the file '/overrideOwnDomain' because this is per host individually. +/overrideOwnDomain + +# Ignore the subfolders only, because their content are other git repositories. +# But 'definitions and 'states' should be prepared by cloning this repository. +/definitions/*/ +/states/*/ + +# Ignore environment files +.env diff --git a/README.md b/README.md index 5eb5fea..6e85076 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,109 @@ -Infrastructure System (ISS) -=========================== +Core Infrastructure System (CIS) +================================ -Setup a new host ----------------- +The main idea is to use git to keep scripts, definitions and state in sync across all hosts. +Currently an operating instance uses one repository for this core functionality and scripts, +another to distibute the definitions and a third one to share the state. -### Preconditions -To deploy the system you have to clone this repository to the host as root user. -Therefore you have to register the SSH public key of that root user as deploy key to allow readonly access to this repository. -We use the modern ed25519 keys, so the public key of root is stored at this location: +If a script or a definition has to be changed an independent working copy is needed to push the adaptions. +States can be changed by a host itself. Then we need a mechanism that informs all hosts to execute a `git pull`. + +We use a Git server as syncronisation point and use a web hook to send the notification. +Because the should not be an agent to be installed on each host, we use jenkins to execute an update script via ssh. + +This allows us to use standard software without having to program something that may contain a security problem. + + + +Setup the first or a new host +----------------------------- + +1. Update the host and ensure git is installed +2. Set the long hostname (fqdn) +3. Create ssh keys for user root (ssh key type ed25519) + +You can use this script to do so: [prepareThisHostBeforeCloning.sh](./prepareThisHostBeforeCloning.sh) + + + +### Ensure the existence of the repositories for your definitions and the state + +This should be necessary just if you set up the first host. +You can use the following scripts to assist the process: + +- [prepareDefinitionsRepository.sh](./prepareDefinitionsRepository.sh) +- [prepareStatesRepository.sh](./prepareStatesRepository.sh) + + + +### Register the public ssh key of user root + +This is an example for `example.net` as domain of the host. + +1. __Scripts:__ + The public ssh key of the root user must be registered as a deploy key for the this repository, + which grants __readonly access__. + + A root user of a host should only be able to update the local cloned repository (`cis`) to a new version via `git pull`. + +2. __Definitions:__ + The public ssh key of the root user must be registered as a deploy key for the definitions repository, + which grants __readonly access__. + + User root should only be able to update the local cloned repository (`cis-definition-example.net`) to a new version via `git pull`. + +3. __States:__ + The public ssh key of the root user must be registered as a deploy key for the states repository, + which grants __write access__. + + User root should be able to push new state to the cloned repository (`cis-state-example.net`) via `git push`. + + + +### Clone the Infrastructure System (cis) repository and complete the setup +After you registered the printed root's public key of this host you can clone the repository and execute the setup script: +```sh +# Note the tailing '/cis', because we want to clone the repository to that folder +git clone ssh://git@git.example.dev:22448/cis.git /cis + +# Execute the setup script +/cis/setupCoreOntoThisHost.sh +``` + +
+
+
+ + + +Setup a new host step by step manually +-------------------------------------- + +To deploy cis you have to clone this repository to the host as root user. +Therefore you have to set the correct long hostname (fqdn) create a pair of ssh keys (key type ed25519) for user root +and register the SSH public key of root as __deploy key__ to allow readonly access to this repository: 1. First become root: ```sh sudo -i ``` -2. Set the long hostname: - ```sh - hostnamectl set-hostname "the-new-unique-long-hostname (fqdn, eg.: host1.example.net)" - ``` - -3. Update Ubuntu: +2. Update Ubuntu: ```sh # DO NOT SKIP THIS STEP apt update; apt upgrade -y ``` -4. Install git if needed: +3. Install git if needed: ```sh git --version > /dev/null || apt install git ``` +4. Set the long hostname: + ```sh + hostnamectl set-hostname "the-new-unique-long-hostname (fqdn, eg.: host1.example.net)" + ``` + 5. If not exist generate the ssh key pair and print the public key of the user root: ```sh # -t type of the key pair @@ -41,40 +115,17 @@ We use the modern ed25519 keys, so the public key of root is stored at this loca || (ssh-keygen \ -t ed25519 \ -f "/root/.ssh/id_ed25519" -q -N "" \ - -C "$(date +%Y%m%d):root@$(hostname -b)" \ + -C "$(date +%Y%m%d)-root@$(hostname -b)" \ && cat "/root/.ssh/id_ed25519.pub") ``` - This key has to be registerd via gitea web ui as deploy key into the repositories as documented in chapter "Register public host key". + This key has to be registerd via gitea web ui as deploy key into this repository. -### Register public host key -This is an example for `example.net` as domain of the host owner. - -1. Repository `iss`, allow __readonly__ access only. -2. Repository `iss-definition-example.net`, allow __readonly__ access only. -3. Repository `iss-state-example.net`, allow __writable__ access. - - - -### Clone the Infrastructure System (iss) repository -After you registered the printed root's public key of this host you can clone the repository and execute the setup script: -```sh -# Note the tailing '/iss', because we want to clone the repository to that folder -git clone ssh://git@git.example.dev:22448/iss.git /iss - -# Execute the setup script -/iss/setupCoreOntoThisHost.sh -``` - -
-
-
- How it works ------------ -We add a webhook to each gitea repository that belongs to ISS: +We add a webhook to each gitea repository that belongs to CIS: - __Taget URL:__ https://YOUR.JENKINS.DOMAIN/generic-webhook-trigger/invoke?token=YOUR_TOKEN - __HTTP-Method:__ POST - __Trigger On:__ Push Events @@ -94,11 +145,11 @@ cat "${JENKINS_HOME}/.ssh/id_ed25519.pub" \ || (ssh-keygen \ -t ed25519 \ -f "${JENKINS_HOME}/.ssh/id_ed25519" -q -N "" \ - -C "$(date +%Y%m%d):$(whoami)@$(echo ${JENKINS_URL} | cut -d/ -f3)" \ + -C "$(date +%Y%m%d)-$(whoami)@$(echo ${JENKINS_URL} | cut -d/ -f3)" \ && cat "${JENKINS_HOME}/.ssh/id_ed25519.pub") # add your host here, note the tailing '&' to run it in parallel -ssh -o StrictHostKeyChecking=no jenkins@192.168.X.Y /iss/update_repositories.sh ( --scripts | --definitions | --states ) & +ssh -o StrictHostKeyChecking=no jenkins@192.168.X.Y /cis/updateRepositories.sh ( --scripts | --definitions | --states ) & #wait for all background processes to complete wait diff --git a/core/addAndCheckGitRepository.sh b/core/addAndCheckGitRepository.sh index 8c5ab80..ac6868f 100755 --- a/core/addAndCheckGitRepository.sh +++ b/core/addAndCheckGitRepository.sh @@ -6,10 +6,10 @@ function checkPermissions(){ - local _FOLDER _REPOSITORY + local _FOLDER _RIGHTS _FOLDER="${1:?"Missing first parameter FOLDER"}" _RIGHTS="${2:?"Missing second parameter RIGHTS"}" - readonly _FOLDER _REPOSITORY + readonly _FOLDER _RIGHTS [ "${_RIGHTS}" == "readonly" ] \ && [ -d "${_FOLDER}/.git" ] \ @@ -21,30 +21,9 @@ function checkPermissions(){ && git -C "${_FOLDER}" push --dry-run &> /dev/null \ && return 0 - echo "FAIL: The rights of the repository are incorrect: ("$(readlink -f ${0})")" - echo " - '${_FOLDER}' is not '${_RIGHTS}'" - echo " - check the settings of gitea." - return 1 -} - -function checkRemoteRepository() { - local _FOLDER _REPOSITORY - _FOLDER="${1:?"Missing first parameter FOLDER"}" - _REPOSITORY="${2:?"Missing second parameter REPOSITORY"}" - readonly _FOLDER _REPOSITORY - - #Should exist after successful clone only, therefore the remote repository exists and was accessible. - [ -d "${_FOLDER}/.git" ] \ - && return 0 - - #Checks if repository exists and is accessible. - ! [ -d "${_FOLDER}/.git" ] \ - && git ls-remote "${_REPOSITORY}" \ - && return 0 - - echo "FAIL: The remote repository is not accessible: ("$(readlink -f ${0})")" - echo " - '${_REPOSITORY}'" - echo " - check the settings of gitea." + echo "FAIL: The rights of the repository are incorrect: ("$(readlink -f ${0})")" >&2 + echo " - '${_FOLDER}' is not '${_RIGHTS}'" >&2 + echo " - check the settings of gitea." >&2 return 1 } @@ -54,42 +33,67 @@ function cloneOrPull { _REPOSITORY="${2:?"Missing second parameter REPOSITORY"}" readonly _FOLDER _REPOSITORY - ! [ -d "${_FOLDER}/.git" ] \ - && git clone "${_REPOSITORY}" "${_FOLDER}" &> /dev/null \ - && return 0 - [ -d "${_FOLDER}/.git" ] \ && git -C "${_FOLDER}" pull &> /dev/null \ && return 0 - echo "FAIL: The local repository is not updatable: ("$(readlink -f ${0})")" - echo " - '${_FOLDER}'" - echo " - check your network and the permissions in gitea." + ! [ -d "${_FOLDER}/.git" ] \ + && git clone "${_REPOSITORY}" "${_FOLDER}" &> /dev/null \ + && return 0 + + echo "FAIL: The local repository is not updatable: ("$(readlink -f ${0})")" >&2 + echo " - '${_FOLDER}'" >&2 + echo " - check your network and the permissions in gitea." >&2 + return 1 +} + +function printRepository(){ + local _FOLDER _CONFIGURED_REPOSITORY _SUGGESTED_REPOSITORY + _FOLDER="${1:?"Missing first parameter FOLDER"}" + _CONFIGURED_REPOSITORY="$(git -C "${_FOLDER:?"Missing FOLDER"}" config --get remote.origin.url 2> /dev/null)" + _SUGGESTED_REPOSITORY="${2}" + readonly _FOLDER _CONFIGURED_REPOSITORY _SUGGESTED_REPOSITORY + + ! [ -z "${_CONFIGURED_REPOSITORY}" ] \ + && echo "${_CONFIGURED_REPOSITORY}" \ + && return 0 + + while true; do + read -e -p "Enter ssh URL to clone Repository: " -i "${_SUGGESTED_REPOSITORY}" _REPOSITORY + echo "${_REPOSITORY}" | grep -F 'git@' &> /dev/null \ + && git ls-remote "${_REPOSITORY}" &> /dev/null \ + && echo "${_REPOSITORY:?"Missing REPOSITORY: e.g. ssh://git@your.domain.com/cis.git"}" \ + && return 0 + done + + echo "FAIL: The remote repository is not accessible: ("$(readlink -f ${0})")" >&2 + echo " - '${_REPOSITORY}'" >&2 + echo " - check the settings of gitea." >&2 return 1 } # Note that an unprivileged user can use this script successfully, # if no user has to be added to the host because it already exists. function addAndCheckGitRepository() { - local _FOLDER _REPOSITORY + local _FOLDER _REPOSITORY _RIGHTS _FOLDER="${1:?"Missing first parameter FOLDER"}" - _REPOSITORY="${2:?"Missing second parameter REPOSITORY: e.g. ssh://git@your.domain.com/iss.git "}" - _RIGHTS="${3:?"Missing third parameter RIGHTS: (readonly, writable) "}" - readonly _FOLDER _REPOSITORY + _RIGHTS="${2:?"Missing second parameter RIGHTS: (readonly, writable) "}" + _REPOSITORY="$(printRepository "${_FOLDER}" "${3}")" + readonly _FOLDER _REPOSITORY _RIGHTS - checkRemoteRepository "${_FOLDER}" "${_REPOSITORY}" \ - && cloneOrPull "${_FOLDER}" "${_REPOSITORY}" \ + echo \ + && cloneOrPull "${_FOLDER}" "${_REPOSITORY:?"Missing REPOSITORY: e.g. ssh://git@your.domain.com/cis.git"}" \ && checkPermissions "${_FOLDER}" "${_RIGHTS}" \ && echo "SUCCESS: The git repository is usable. ("$(readlink -f ${0})")" \ && echo " - remote repository: '${_REPOSITORY}'" \ && echo " - local repository: '${_FOLDER}' (${_RIGHTS})" \ && return 0 - echo "FAIL: The repository is not functional: ("$(readlink -f ${0})")" - echo " - remote repository: '${_REPOSITORY}'" - echo " - local repository: '${_FOLDER}'" - echo " - due to an error or insufficient rights or" - echo " - one check failed." + echo "FAIL: The repository is not functional: ("$(readlink -f ${0})")" >&2 + echo " - remote repository: '${_REPOSITORY}'" >&2 + echo " - local repository: '${_FOLDER}'" >&2 + echo " - due to an error or insufficient rights or" >&2 + echo " - one check failed." >&2 return 1 } @@ -98,4 +102,6 @@ addAndCheckGitRepository \ "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ "$(echo ${2} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ "$(echo ${3} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && exit 0 || exit 1 + && exit 0 + +exit 1 diff --git a/core/addNormalUser.sh b/core/addNormalUser.sh index 04716c1..7582033 100755 --- a/core/addNormalUser.sh +++ b/core/addNormalUser.sh @@ -18,6 +18,8 @@ function addNormalUser() { && echo " - '${_USER}'" \ && return 0 + # useradd is a low level utility ... use adduser(8) instead. + # See: https://askubuntu.com/questions/345974/what-is-the-difference-between-adduser-and-useradd [ "$(id -u)" == "0" ] \ && adduser --gecos 'Normal user' --disabled-password "${_USER}" \ && chown -R "${_USER}:${_USER}" "/home/${_USER}" \ @@ -27,13 +29,14 @@ function addNormalUser() { && echo " - existing home directories were taken over" \ && return 0 - echo "FAIL: The user could not be created: ("$(readlink -f ${0})")" - echo " - '${_USER}'" - echo " - due to an error or insufficient rights." + echo "FAIL: The user could not be created: ("$(readlink -f ${0})")" >&2 + echo " - '${_USER}'" >&2 + echo " - due to an error or insufficient rights." >&2 return 1 } # sanitizes all parameters -addNormalUser \ - "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && exit 0 || exit 1 +addNormalUser "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ + && exit 0 + +exit 1 diff --git a/core/addToCrontabEveryHour.sh b/core/addToCrontabEveryHour.sh index 492ad14..e7e7218 100755 --- a/core/addToCrontabEveryHour.sh +++ b/core/addToCrontabEveryHour.sh @@ -5,14 +5,17 @@ +# Folders always ends with an tailing '/' +_SCRIPT="$(readlink -f "${0}" 2> /dev/null)" +_CIS_ROOT="${_SCRIPT%%/core/*}/" #Removes longest matching pattern '/core/*' from the end + # Note that an unprivileged user can use this script successfully, # if no user has to be added to the host because it already exists. function addToCrontabEveryHour() { - local _ROOT _MINUTE_VALUE _STRING - _ROOT="${0%%/core/*}/" #Removes longest matching pattern '/core/*' from the end + local _MINUTE_VALUE _STRING ! [ -z "${2##*[!0-9]*}" ] && _MINUTE_VALUE=$((${2}%60)) # if second parameter is integer then (minute-value % 60) as safe guard _STRING="${_MINUTE_VALUE:?"Missing MINUTE_VALUE"} * * * * ${1:?"Missing first parameter COMMAND"} > /dev/null 2>&1" - readonly _ROOT _MINUTE_VALUE _STRING + readonly _MINUTE_VALUE _STRING [ "$(id -u)" == "0" ] \ && crontab -l | grep -qF "${_STRING:?"Missing CRON_STRING"}" \ @@ -21,11 +24,11 @@ function addToCrontabEveryHour() { && return 0 [ "$(id -u)" == "0" ] \ - && echo "${_ROOT:?"Missing ROOT"}" | grep "home" &> /dev/null \ + && echo "${_CIS_ROOT:?"Missing CIS_ROOT"}" | grep -F 'home' &> /dev/null \ && echo "SUCCESS: Although the entry will be skipped: ("$(readlink -f ${0})")" \ && echo " - '${_STRING}'" \ && echo " that is because the current environment is:" \ - && echo " - ${_ROOT}" \ + && echo " - ${_CIS_ROOT}" \ && return 0 [ "$(id -u)" == "0" ] \ @@ -37,9 +40,9 @@ function addToCrontabEveryHour() { && echo " - '${_STRING}'" \ && return 0 - echo "FAIL: Entry could not be registered to crontab: ("$(readlink -f ${0})")" - echo " - '${_STRING:?"Missing CRON_STRING"}'" - echo " - due to an error or insufficient rights." + echo "FAIL: Entry could not be registered to crontab: ("$(readlink -f ${0})")" >&2 + echo " - '${_STRING:?"Missing CRON_STRING"}'" >&2 + echo " - due to an error or insufficient rights." >&2 return 1 } @@ -47,4 +50,6 @@ function addToCrontabEveryHour() { addToCrontabEveryHour \ "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ "$(echo ${2} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && exit 0 || exit 1 + && exit 0 + +exit 1 diff --git a/core/default/etc/sudoers.d/allow-jenkins-updateRepositories b/core/default/etc/sudoers.d/allow-jenkins-updateRepositories new file mode 100644 index 0000000..804dba5 --- /dev/null +++ b/core/default/etc/sudoers.d/allow-jenkins-updateRepositories @@ -0,0 +1,6 @@ +Cmnd_Alias C_JENKINS = \ + /cis/updateRepositories.sh --core, \ + /cis/updateRepositories.sh --scripts, \ + /cis/updateRepositories.sh --definitions, \ + /cis/updateRepositories.sh --states +jenkins ALL = (root) NOPASSWD: C_JENKINS diff --git a/core/defineAuthorizedKeysOfUser.sh b/core/defineAuthorizedKeysOfUser.sh index 68b318e..6b9cb23 100755 --- a/core/defineAuthorizedKeysOfUser.sh +++ b/core/defineAuthorizedKeysOfUser.sh @@ -44,24 +44,24 @@ function prepareFolder() { && echo " - '${_SSH_FOLDER}'" \ && return 0 - echo "FAIL: The ssh folder could not be prepared: ("$(readlink -f ${0})")" - echo " - '${_SSH_FOLDER}'" - echo " - due to an error or insufficient rights." + echo "FAIL: The ssh folder could not be prepared: ("$(readlink -f ${0})")" >&2 + echo " - '${_SSH_FOLDER}'" >&2 + echo " - due to an error or insufficient rights." >&2 return 1 } function defineAuthorizedKeysOfUser() { - local _ROOT _CORE_SCRIPTS _DOMAIN _DEFINITIONS _USER + local _CIS_ROOT _CORE_SCRIPTS _DOMAIN _DEFINITIONS _USER _DEFINITIONS="$(realpath -s "${1:?"Missing first parameter DEFINITIONS: 'ROOT/definitions/DOMAIN'"}")" - _ROOT="${_DEFINITIONS%%/definitions/*}/" #Removes longest matching pattern '/definitions/*' from the end - _DOMAIN="${_DEFINITIONS##*/definitions/}" #Removes longest matching pattern '*/definitions/' from the begin - _DOMAIN="${_DOMAIN%/}" #Removes shortest matching pattern '/' from the end + _CIS_ROOT="${_DEFINITIONS%%/definitions/*}/" #Removes longest matching pattern '/definitions/*' from the end + _DOMAIN="${_DEFINITIONS##*/definitions/}" #Removes longest matching pattern '*/definitions/' from the begin + _DOMAIN="${_DOMAIN%/}" #Removes shortest matching pattern '/' from the end #Build from components for safety - _DEFINITIONS="${_ROOT:?"Missing ROOT"}definitions/${_DOMAIN:?"Missing DOMAIN"}" + _DEFINITIONS="${_CIS_ROOT:?"Missing ROOT"}definitions/${_DOMAIN:?"Missing DOMAIN"}" _USER="${2:?"Missing second parameter USER"}" - _CORE_SCRIPTS="${_ROOT:?"Missing ROOT"}core/" - readonly _ROOT _CORE_SCRIPTS _DOMAIN _DEFINITIONS _USER + _CORE_SCRIPTS="${_CIS_ROOT:?"Missing ROOT"}core/" + readonly _CIS_ROOT _CORE_SCRIPTS _DOMAIN _DEFINITIONS _USER case "${_USER:?"Missing USER"}" in root) @@ -83,4 +83,6 @@ function defineAuthorizedKeysOfUser() { defineAuthorizedKeysOfUser \ "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ "$(echo ${2} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && exit 0 || exit 1 + && exit 0 + +exit 1 diff --git a/core/ensureUsageOfDefinitions.sh b/core/ensureUsageOfDefinitions.sh index f9b53b1..05073e3 100755 --- a/core/ensureUsageOfDefinitions.sh +++ b/core/ensureUsageOfDefinitions.sh @@ -12,46 +12,76 @@ function printIfEqual() { } function isCoreDefinition() { - echo "${1:?"Missing first parameter FILE"}" | grep "/root/.ssh/authorized_keys" &> /dev/null \ + echo "${1:?"Missing first parameter FILE"}" | grep -F '/root/.ssh/authorized_keys' &> /dev/null \ && return 0 - echo "${1:?"Missing first parameter FILE"}" | grep "/home/jenkins/.ssh/authorized_keys" &> /dev/null \ + echo "${1:?"Missing first parameter FILE"}" | grep -F '/home/jenkins/.ssh/authorized_keys' &> /dev/null \ && return 0 - echo "${1:?"Missing first parameter FILE"}" | grep "/etc/sudoers.d/allow-jenkins-updateRepositories" &> /dev/null \ + echo "${1:?"Missing first parameter FILE"}" | grep -F '/etc/sudoers.d/allow-jenkins-updateRepositories' &> /dev/null \ && return 0 return 1 } +function filterInvalidAuthorizedKeysFilesOfRoot() { + local _FILE_DEFINED + _FILE_DEFINED="${1:?"Missing DEFINITION FILE"}" + readonly _FILE_DEFINED + + #If the full filename contains 'root/.ssh/authorized_keys' then check the content. + #Skip lines starting with '#' and if at least one remaining line contains 'ssh' and '@' then print the filename. + echo "${_FILE_DEFINED}" | grep -F 'root/.ssh/authorized_keys' &> /dev/null \ + && grep -vE '^[[:blank:]]*#' "${_FILE_DEFINED}" | grep -F 'ssh' | grep -F '@' &> /dev/null \ + && echo "${_FILE_DEFINED}" \ + && return 0 + + #If the full filename contains 'root/.ssh/authorized_keys' print nothing because the file has to be invalid. + echo "${_FILE_DEFINED}" | grep -F 'root/.ssh/authorized_keys' &> /dev/null \ + && echo \ + && return 0 + + #Print the full filename because it does not contain 'root/.ssh/authorized_keys' + echo "${_FILE_DEFINED}" + return 0 +} + function printSelectedDefinition() { - local _CORE_FILE_DEFINED_ALL_HOSTS _CORE_FILE_DEFINED_THIS_HOST _FILE_DEFINED_ALL_HOSTS _FILE_DEFINED_THIS_HOST - _CORE_FILE_DEFINED_ALL_HOSTS="${1:?"Missing DEFINITIONS"}/core/all${2:?"Missing CURRENT_FULLFILE"}" - _CORE_FILE_DEFINED_THIS_HOST="${1:?"Missing DEFINITIONS"}/core/$(hostname -s)${2:?"Missing CURRENT_FULLFILE"}" - _FILE_DEFINED_ALL_HOSTS="${1:?"Missing DEFINITIONS"}/hosts/all${2:?"Missing CURRENT_FULLFILE"}" - _FILE_DEFINED_THIS_HOST="${1:?"Missing DEFINITIONS"}/hosts/$(hostname -s)${2:?"Missing CURRENT_FULLFILE"}" - readonly _CORE_FILE_DEFINED_ALL_HOSTS _CORE_FILE_DEFINED_THIS_HOST _FILE_DEFINED_ALL_HOSTS _FILE_DEFINED_THIS_HOST + local _DEFINITIONS _CORE_FILE_DEFINED_ALL_HOSTS _CORE_FILE_DEFINED_THIS_HOST _FILE_DEFINED_ALL_HOSTS _FILE_DEFINED_THIS_HOST + _DEFINITIONS="${1:?"Missing CIS_ROOT"}definitions/${2:?"Missing DOMAIN"}/" + _CORE_DEFAULT_ALL_HOSTS="${1:?"Missing CIS_ROOT"}core/default${3:?"Missing CURRENT_FULLFILE"}" + _CORE_FILE_DEFINED_ALL_HOSTS="${_DEFINITIONS:?"Missing DEFINITIONS"}core/all${3:?"Missing CURRENT_FULLFILE"}" + _CORE_FILE_DEFINED_THIS_HOST="${_DEFINITIONS:?"Missing DEFINITIONS"}core/$(hostname -s)${3:?"Missing CURRENT_FULLFILE"}" + _FILE_DEFINED_ALL_HOSTS="${_DEFINITIONS:?"Missing DEFINITIONS"}hosts/all${3:?"Missing CURRENT_FULLFILE"}" + _FILE_DEFINED_THIS_HOST="${_DEFINITIONS:?"Missing DEFINITIONS"}hosts/$(hostname -s)${3:?"Missing CURRENT_FULLFILE"}" + readonly _DEFINITIONS _CORE_FILE_DEFINED_ALL_HOSTS _CORE_FILE_DEFINED_THIS_HOST _FILE_DEFINED_ALL_HOSTS _FILE_DEFINED_THIS_HOST #The following are special definitions that affect the core functionality. #Try this host first because it should be priorized. - isCoreDefinition "${2:?"Missing CURRENT_FULLFILE"}" \ + isCoreDefinition "${3:?"Missing CURRENT_FULLFILE"}" \ && [ -s "${_CORE_FILE_DEFINED_THIS_HOST}" ] \ - && echo "${_CORE_FILE_DEFINED_THIS_HOST}" \ + && filterInvalidAuthorizedKeysFilesOfRoot "${_CORE_FILE_DEFINED_THIS_HOST}" \ && return 0 #The following are special definitions that affect the core functionality. - isCoreDefinition "${2:?"Missing CURRENT_FULLFILE"}" \ + isCoreDefinition "${3:?"Missing CURRENT_FULLFILE"}" \ && [ -s "${_CORE_FILE_DEFINED_ALL_HOSTS}" ] \ - && echo "${_CORE_FILE_DEFINED_ALL_HOSTS}" \ + && filterInvalidAuthorizedKeysFilesOfRoot "${_CORE_FILE_DEFINED_ALL_HOSTS}" \ + && return 0 + + #The following are special definitions that affect the core functionality. + isCoreDefinition "${3:?"Missing CURRENT_FULLFILE"}" \ + && [ -s "${_CORE_DEFAULT_ALL_HOSTS}" ] \ + && filterInvalidAuthorizedKeysFilesOfRoot "${_CORE_DEFAULT_ALL_HOSTS}" \ && return 0 #Try this host first because it should be priorized. - ! isCoreDefinition "${2:?"Missing CURRENT_FULLFILE"}" \ + ! isCoreDefinition "${3:?"Missing CURRENT_FULLFILE"}" \ && [ -s "${_FILE_DEFINED_THIS_HOST}" ] \ && echo "${_FILE_DEFINED_THIS_HOST}" \ && return 0 - ! isCoreDefinition "${2:?"Missing CURRENT_FULLFILE"}" \ + ! isCoreDefinition "${3:?"Missing CURRENT_FULLFILE"}" \ && [ -s "${_FILE_DEFINED_ALL_HOSTS}" ] \ && echo "${_FILE_DEFINED_ALL_HOSTS}" \ && return 0 @@ -71,11 +101,6 @@ function createSymlinkToDefinition() { && [ "$(sha256sum "${_DEFINED_FULLFILE}" | cut -d' ' -f1)" == "$(sha256sum "${_CURRENT_FULLFILE}" | cut -d' ' -f1)" ] \ && echo "The content of the current file already matches the definition, but it will be replaced by a symlink..." - [ -f "${_CURRENT_FULLFILE}" ] \ - && [ "$(sha256sum "${_DEFINED_FULLFILE}" | cut -d' ' -f1)" == "$(sha256sum "${_CURRENT_FULLFILE}" | cut -d' ' -f1)" ] \ - && echo "The content of the current file already matches the definition, but it will be replaced by a symlink..." - - [ -f "${_CURRENT_FULLFILE}" ] \ && mv "${_CURRENT_FULLFILE:?"Missing CURRENT_FULLFILE"}" "${_SAVED_FULLFILE:?"Missing SAVED_FULLFILE"}" \ && echo "Current file has been backed up to: '${_SAVED_FULLFILE}'" @@ -92,17 +117,17 @@ function createSymlinkToDefinition() { } function ensureUsageOfDefinitions() { - local _ROOT _CURRENT_FILE _CURRENT_FOLDER _CURRENT_FULLFILE _DEFINITIONS _DOMAIN _DEFINED_FULLFILE _NOW _SAVED_FULLFILE - _DEFINITIONS="$(realpath -s "${1:?"Missing first parameter DEFINITIONS: 'ROOT/definitions/DOMAIN'"}")" - _ROOT="${_DEFINITIONS%%/definitions/*}/" #Removes longest matching pattern '/definitions/*' from the end - _DOMAIN="${_DEFINITIONS##*/definitions/}" #Removes longest matching pattern '*/definitions/' from the begin - _DOMAIN="${_DOMAIN%/}" #Removes shortest matching pattern '/' from the end + local _CIS_ROOT _CURRENT_FILE _CURRENT_FOLDER _CURRENT_FULLFILE _DEFINITIONS _DOMAIN _DEFINED_FULLFILE _NOW _SAVED_FULLFILE + _DEFINITIONS="$(realpath -s "${1:?"Missing first parameter DEFINITIONS: 'ROOT/definitions/DOMAIN'"}")/" + _CIS_ROOT="${_DEFINITIONS%%/definitions/*}/" #Removes longest matching pattern '/definitions/*' from the end + _DOMAIN="${_DEFINITIONS##*/definitions/}" #Removes longest matching pattern '*/definitions/' from the begin + _DOMAIN="${_DOMAIN%/}" #Removes shortest matching pattern '/' from the end #Build from components for safety - _DEFINITIONS="$(printIfEqual "${_DEFINITIONS}" "${_ROOT:?"Missing ROOT"}definitions/${_DOMAIN:?"Missing DOMAIN"}")" + _DEFINITIONS="$(printIfEqual "${_DEFINITIONS}" "${_CIS_ROOT:?"Missing ROOT"}definitions/${_DOMAIN:?"Missing DOMAIN"}/")" - _CURRENT_FOLDER="$(dirname "${2:?"Missing second parameter CURRENT_FULLFILE"}")" - _CURRENT_FOLDER="${_CURRENT_FOLDER%/}/" #Removes shortest matching pattern '/' from the end + _CURRENT_FULLFILE="${2:?"Missing second parameter CURRENT_FULLFILE"}" + _CURRENT_FOLDER="${_CURRENT_FULLFILE%/*}/" #Removes shortest matching pattern '/*' from the end ! [ -d "${_CURRENT_FOLDER}" ] \ && echo "FAIL: The folder cannot be read: ("$(readlink -f ${0})")" \ && echo " - '${_CURRENT_FOLDER}'" \ @@ -118,10 +143,16 @@ function ensureUsageOfDefinitions() { _CURRENT_FULLFILE="${_CURRENT_FOLDER:?"Missing CURRENT_FOLDER"}${_CURRENT_FILE:?"Missing CURRENT_FILE"}" - _DEFINED_FULLFILE="$(printSelectedDefinition "${_DEFINITIONS}" "${_CURRENT_FULLFILE}")" + _DEFINED_FULLFILE="$(printSelectedDefinition "${_CIS_ROOT}" "${_DOMAIN}" "${_CURRENT_FULLFILE}")" _NOW="$(date +%Y%m%d_%H%M)" - _SAVED_FULLFILE="${_CURRENT_FULLFILE}-backup@${_NOW:?"Missing NOW"}" - readonly _ROOT _CURRENT_FILE _CURRENT_FOLDER _CURRENT_FULLFILE _DEFINITIONS _DOMAIN _DEFINED_FULLFILE _NOW _SAVED_FULLFILE + _SAVED_FULLFILE="${_CURRENT_FULLFILE}.backup@${_NOW:?"Missing NOW"}" + readonly _CIS_ROOT _CURRENT_FILE _CURRENT_FOLDER _CURRENT_FULLFILE _DEFINITIONS _DOMAIN _DEFINED_FULLFILE _NOW _SAVED_FULLFILE + + [ -z "${_DEFINED_FULLFILE}" ] \ + && echo \ + && echo "URGENT WARNING: If an 'authorized_keys' file of root is replaced by an invalid version," \ + && echo " you may lose access to this host!" \ + && echo ! [ -f "${_DEFINED_FULLFILE}" ] \ && echo "FAIL: No definition available for this file: ("$(readlink -f ${0})")" \ @@ -138,11 +169,11 @@ function ensureUsageOfDefinitions() { && echo " - '${_DEFINED_FULLFILE}'" \ && return 0 - echo "${_ROOT:?"Missing ROOT"}" | grep "home" &> /dev/null \ + echo "${_CIS_ROOT:?"Missing CIS_ROOT"}" | grep -F 'home' &> /dev/null \ && echo "SUCCESS: Although this definition will be skipped: ("$(readlink -f ${0})")" \ && echo " - '${_DEFINED_FULLFILE}'" \ && echo " that is because the current environment is:" \ - && echo " - ${_ROOT}" \ + && echo " - ${_CIS_ROOT}" \ && echo " following file is in use:" \ && echo " - $(readlink -f "${_CURRENT_FULLFILE}")" \ && return 0 @@ -165,8 +196,8 @@ function ensureUsageOfDefinitions() { && echo "- '${_DEFINED_FULLFILE}'" \ && return 0 - echo "FAIL: The definition could not be ensured: ("$(readlink -f ${0})")" - echo " - due to an error or insufficient rights." + echo "FAIL: The definition could not be ensured: ("$(readlink -f ${0})")" >&2 + echo " - due to an error or insufficient rights." >&2 return 1 } @@ -174,4 +205,6 @@ function ensureUsageOfDefinitions() { ensureUsageOfDefinitions \ "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ "$(echo ${2} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && exit 0 || exit 1 + && exit 0 + +exit 1 diff --git a/core/printCisRoot.sh b/core/printCisRoot.sh new file mode 100755 index 0000000..67bc2db --- /dev/null +++ b/core/printCisRoot.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +_SCRIPT="$(readlink -f "${0}" 2> /dev/null)" +_CIS_ROOT="${_SCRIPT%%/core/*}/" #Removes longest matching pattern '/core/*' from the end + +[ -d "${_CIS_ROOT}" ] \ + && [ -d "${_CIS_ROOT}definitions/" ] \ + && [ -d "${_CIS_ROOT}states/" ] \ + && echo "${_CIS_ROOT}" \ + && exit 0 + +echo "FAIL: Unable to detect CIS_ROOT" >&2 +exit 1 diff --git a/core/printOwnDomain.sh b/core/printOwnDomain.sh new file mode 100755 index 0000000..a5e712d --- /dev/null +++ b/core/printOwnDomain.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +#WARNING: Used for core functionality in setup.sh +# DO NOT rename the script and test changes well! + + + +# Folders always ends with an tailing '/' +_SCRIPT="$(readlink -f "${0}" 2> /dev/null)" +_CIS_ROOT="${_SCRIPT%%/core/*}/" #Removes longest matching pattern '/core/*' from the end +_OVERRIDE_DOMAIN_FILE="${_CIS_ROOT:?"Missing CIS_ROOT"}overrideOwnDomain" + +# There has to be one dot at least. +_BOOT_DOMAIN="$(hostname -b | grep -F '.' | cut -d. -f2-)" + +# Take OVERRIDING_DOMAIN_FILE without empty lines and comments, then take the first line without leading spaces +_OVERRIDE_DOMAIN="$(grep -vE '^[[:space:]]*$|^[[:space:]]*#' "${_OVERRIDE_DOMAIN_FILE}" 2> /dev/null | head -n 1 | xargs)" + +! [ -z "${_OVERRIDE_DOMAIN}" ] \ + && [ "${_OVERRIDE_DOMAIN}" != "${_BOOT_DOMAIN}" ] \ + && echo "WARNING: Domain has been overridden by: ${_OVERRIDE_DOMAIN_FILE}" >&2 \ + && echo "${_OVERRIDE_DOMAIN}" \ + && exit 0 + +! [ -z "${_BOOT_DOMAIN}" ] \ + && echo "${_BOOT_DOMAIN}" \ + && exit 0 + +echo "It was impossible to find out the domain of this host, please prepare this host first." >&2 +exit 1 diff --git a/prepareDefinitionsRepository.sh b/prepareDefinitionsRepository.sh new file mode 100755 index 0000000..271f432 --- /dev/null +++ b/prepareDefinitionsRepository.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +[ "$(id -u)" != "0" ] \ + && echo "This script prepares the user 'root' of this host and the host itself," \ + && echo "so this script is allowed to be executed if you are root only." \ + && exit 1 + +# There has to be one dot at least. +_BOOT_DOMAIN="$(hostname -b | grep -F '.' | cut -d. -f2-)" + +[ -z "${_BOOT_DOMAIN}" ] \ + && echo "It was impossible to find out the domain of this host, please prepare this host first." \ + && exit 1 + +_REOPSITORY_NAME="cis-definition-${_BOOT_DOMAIN}" + + + +#Generate file 'README.md' +mkdir -p /tmp/skeleton/definition +cat << EOF > /tmp/skeleton/definition/README.md +This repository contains the definitions of the domain “$_BOOT_DOMAIN” by the Core Infrastructure System. +EOF + + + +#Use current file 'authorized_keys' of root as definition +mkdir -p /tmp/skeleton/definition/core/all/root/.ssh +cp /root/.ssh/authorized_keys /tmp/skeleton/definition/core/all/root/.ssh/authorized_keys + + + +#Generate file 'authorized_keys' for user jenkins +mkdir -p /tmp/skeleton/definition/core/all/home/jenkins/.ssh +cat << EOF > /tmp/skeleton/definition/core/all/home/jenkins/.ssh/authorized_keys +#------------------------------------------------------ +# Enter the public ssh key of your jenkins server here. +#------------------------------------------------------ +EOF + + + +cat << EOF + +The first content for your repository for the definitions of the '$_BOOT_DOMAIN' domain has been created. + +Please create a definition repository. +To follow the naming convention name it '$_REOPSITORY_NAME' + +Please DO NOT use the SSH key of root for this. +Maybe you can use https and user password for pushing the first commit. + +Go to folder '/tmp/skeleton/definition' and check the content of all 'authorized_keys' files, +correct them if required to prevent losing access to your hosts. + +The public ssh key of your jenkins server has to be added. + +Only now follow the instructions as our git server shows. +For example: + + cd /tmp/skeleton/definition + git init + git checkout -b main + git add . + git commit -m "first core definitions" + git remote add origin https://git.example.dev/[SOME_PATH/]$_REOPSITORY_NAME.git + git push -u origin main + +EOF diff --git a/prepareStatesRepository.sh b/prepareStatesRepository.sh new file mode 100755 index 0000000..a68e03e --- /dev/null +++ b/prepareStatesRepository.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +[ "$(id -u)" != "0" ] \ + && echo "This script prepares the user 'root' of this host and the host itself," \ + && echo "so this script is allowed to be executed if you are root only." \ + && exit 1 + +# There has to be one dot at least. +_BOOT_DOMAIN="$(hostname -b | grep -F '.' | cut -d. -f2-)" + +[ -z "${_BOOT_DOMAIN}" ] \ + && echo "It was impossible to find out the domain of this host, please prepare this host first." \ + && exit 1 + +_REOPSITORY_NAME="cis-state-${_BOOT_DOMAIN}" + + + +#Generate README.md +mkdir -p /tmp/skeleton/state +cat << EOF > /tmp/skeleton/state/README.md +This repository contains the states of the domain “$_BOOT_DOMAIN” by the Core Infrastructure System. +EOF + + + +cat << EOF + +The first content for your repository for the state of the '$_BOOT_DOMAIN' domain has been created. + +Please create a states repository. +To follow the naming convention name it '$_REOPSITORY_NAME' + +Please DO NOT use the SSH key of root for this. +Maybe you can use https and user password for pushing the first commit. + +Then go to folder '/tmp/skeleton/state' and follow the instructions as your git server shows. +For example: + + cd /tmp/skeleton/state + git init + git checkout -b main + git add . + git commit -m "first state" + git remote add origin https://git.example.dev/[SOME_PATH/]$_REOPSITORY_NAME.git + git push -u origin main + +EOF diff --git a/preparationBeforeCloning.sh b/prepareThisHostBeforeCloning.sh similarity index 52% rename from preparationBeforeCloning.sh rename to prepareThisHostBeforeCloning.sh index 74d60b4..58aaaa0 100755 --- a/preparationBeforeCloning.sh +++ b/prepareThisHostBeforeCloning.sh @@ -8,46 +8,66 @@ function setNeededHostnameOrExit() { _FQDN="${1:?"Missing unique long hostname (fqdn, eg.: host1.example.net) for this host as first parameter."}" - echo "${_FQDN}" | grep '\.' &> /dev/null \ + echo "${_FQDN}" | grep -F '.' &> /dev/null \ && hostnamectl set-hostname "${_FQDN}" \ && return 0 - echo "FAILED: setting full qualified domain name, given value was:" - echo " - ${_FQDN}" + echo "FAILED: setting full qualified domain name does not contain a domain," + echo " given value was: ${_FQDN}" exit 1 } -function prepare() { +function prepareThisHost() { git --version > /dev/null || (apt update; apt upgrade -y; apt install git) echo echo "Public SSH-Key for root@$(hostname -b):" + cat "/root/.ssh/id_ed25519.pub" \ + && return 0 + # -t type of the key pair # -f defines the filenames (we use the standard for the selected type here) # -q quiet, no output or interaction # -N "" means the private key will not be secured by a passphrase # -C defines a comment + ssh-keygen \ + -t ed25519 \ + -f "/root/.ssh/id_ed25519" -q -N "" \ + -C "$(date +%Y%m%d)-root@$(hostname -b)" + cat "/root/.ssh/id_ed25519.pub" \ - || (ssh-keygen \ - -t ed25519 \ - -f "/root/.ssh/id_ed25519" -q -N "" \ - -C "$(date +%Y%m%d)-root@$(hostname -b)" \ - && cat "/root/.ssh/id_ed25519.pub") + && return 0 echo - echo "Now you have to register the public ssh-key from above into your git-server to grant these access rights:" + echo "FAILED: somthing went wrong during the generation the ssh keys." + echo " These keys are mandantory. You can try to restart this script." + echo +} + +function showFurtherSteps() {} + echo + echo "IMPORTANT: It is assumed that repositories for definitions and states already exist" + echo " and comply with the naming convention." + echo " Otherwise, these repositories must be created first!" + echo + echo "To grant the correct access rights, you have to register the above-mentioned ssh key," + echo "as deploy key in these repositories of the Git server:" echo " - scripts repository (allow readonly access only)," echo " - definitions repository (allow readonly access only)," echo " - states repository (allow writable access)." echo - echo "After all access rights are granted you can clone the Infrastructure System:" - echo " e.g.: git clone ssh://git@git.example.dev:22448/iss.git /iss" + echo "After all access rights are granted you can clone the Core Infrastructure System:" + echo " e.g.: git clone ssh://git@git.example.dev:22448/cis.git /cis" echo - echo "Finally call 'setupCoreOntoThisHost.sh' from the root directory of the repository:" - echo " e.g.: /iss/setupCoreOntoThisHost.sh" + echo "Finally call 'setupCoreOntoThisHost.sh' from the root directory:" + echo " e.g.: /cis/setupCoreOntoThisHost.sh" echo } # sanitizes all parameters setNeededHostnameOrExit "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && prepare + && prepareThisHost \ + && showFurtherSteps \ + && exit 0 + +exit 1 diff --git a/setupCoreOntoThisHost.sh b/setupCoreOntoThisHost.sh index f745839..c7e5d82 100755 --- a/setupCoreOntoThisHost.sh +++ b/setupCoreOntoThisHost.sh @@ -6,6 +6,12 @@ +# 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 \ @@ -31,22 +37,21 @@ function checkGitIsAvailable() { } function checkPreconditions() { - local _ROOT _DOMAIN - _ROOT="${1:?"Missing parameter ROOT"}" - _DOMAIN="${2}" # Optional parameter DOMAIN - readonly _ROOT _DOMAIN + local _DOMAIN + _DOMAIN="${1}" # Optional parameter DOMAIN + readonly _DOMAIN ! [ -z "${_DOMAIN}" ] \ && [ "$(hostname -d)" != "${_DOMAIN}" ] \ && echo \ - && echo "WARNING: system-domain DOES NOT MATCH domainOfHostOwner: '$(hostname -d)' != '${_DOMAIN}'" \ + && echo "WARNING: system-domain DOES NOT MATCH overrideOwnDomain: '$(hostname -d)' != '${_DOMAIN}'" \ && echo # Given domain verfügbar (nicht leer) ! [ -z "${_DOMAIN}" ] \ && checkPathsAreAvaiable \ && checkGitIsAvailable \ - && git -C "${_ROOT}" pull &> /dev/null \ + && git -C "${_CIS_ROOT:?"Missing CIS_ROOT"}" pull &> /dev/null \ && return 0 echo @@ -69,63 +74,65 @@ function checkPreconditions() { } function getOrSetDomain() { - local _ROOT _DOMAIN_FILE _GIVEN_DOMAIN - _ROOT="${1:?"Missing parameter ROOT"}" - _DOMAIN_FILE="${_ROOT:?"Missing ROOT"}domainOfHostOwner" - _GIVEN_DOMAIN="${2}" # Optional parameter DOMAIN - readonly _ROOT _DOMAIN_FILE _GIVEN_DOMAIN + local _CURRENT_DOMAIN _GIVEN_DOMAIN _OVERRIDE_DOMAIN_FILE + _CURRENT_DOMAIN="$("${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}printOwnDomain.sh")" + _GIVEN_DOMAIN="${1}" # Optional parameter DOMAIN + _OVERRIDE_DOMAIN_FILE="${_CIS_ROOT:?"Missing CIS_ROOT"}overrideOwnDomain" + readonly _CURRENT_DOMAIN _GIVEN_DOMAIN _OVERRIDE_DOMAIN_FILE - # Wenn DOMAIN_FILE enhält lesbare Daten - grep '[^[:space:]]' "${_DOMAIN_FILE:?"Missing DOMAIN_FILE"}" &> /dev/null \ - && cat "${_DOMAIN_FILE}" \ + ! [ -z "${_CURRENT_DOMAIN}" ] \ + && [ -z "${_GIVEN_DOMAIN}" ] \ + && echo "${_CURRENT_DOMAIN}" \ && return 0 - # Der boot-hostname muss mindestens einen Punkt enthalten, dann wird die hintere Hälfte als Domain genommen - hostname -b | grep "\." | cut -d. -f2- > "${_DOMAIN_FILE}" - grep '[^[:space:]]' "${_DOMAIN_FILE}" &> /dev/null \ - && cat "${_DOMAIN_FILE}" \ + ! [ -z "${_CURRENT_DOMAIN}" ] \ + && [ "${_CURRENT_DOMAIN}" == "${_GIVEN_DOMAIN}" ] \ + && echo "${_CURRENT_DOMAIN}" \ && return 0 - # Given domain is set (nicht leer) - ! [ -z "${_GIVEN_DOMAIN}" ] \ + # If there is a given domain it will be set or it will override the current one + [ -z "${_CURRENT_DOMAIN}" ] \ + && ! [ -z "${_GIVEN_DOMAIN}" ] \ && [ "$(id -u)" == "0" ] \ + && echo "Setting hostname to: $(hostname -s).${_GIVEN_DOMAIN}" >&2 \ && hostnamectl set-hostname "$(hostname -s).${_GIVEN_DOMAIN}" \ - && hostname -b | grep "\." | cut -d. -f2- > "${_DOMAIN_FILE}" \ - && grep '[^[:space:]]' "${_DOMAIN_FILE}" &> /dev/null \ - && cat "${_DOMAIN_FILE}" \ + && echo "${_GIVEN_DOMAIN}" \ + && return 0 + + ! [ -z "${_GIVEN_DOMAIN}" ] \ + && echo "Overwriting domain to: ${_GIVEN_DOMAIN}" >&2 \ + && echo "${_GIVEN_DOMAIN}" > "${_OVERRIDE_DOMAIN_FILE}" \ + && echo "${_GIVEN_DOMAIN}" \ && return 0 return 1 } function getRemoteRepositoryPath() { - local _ROOT - _ROOT="${1:?"Missing parameter ROOT"}" - readonly _ROOT - - _RESULT="$(git -C "${_ROOT:?"Missing ROOT"}" remote show origin | grep -i 'fetch' | xargs -n 1 | grep -i 'ssh://')" - _RESULT="${_RESULT%/*}" #Removes shortest matching pattern '/*' from the end - ! [ -z "${_RESULT}" ] \ - && echo "${_RESULT}" \ + _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 + ! [ -z "${_PATH}" ] \ + && echo "${_PATH}/" \ && return 0 return 1 } function addDefinition(){ - local _ROOT _CORE_SCRIPTS _DEFINITIONS _REPOSITORY - _DEFINITIONS="${1:?"Missing parameter DEFINITIONS"}" - _REPOSITORY="${2:?"Missing parameter REPOSITORY"}" - _ROOT="${_DEFINITIONS%%/definitions/*}/" #Removes longest matching pattern '/definitions/*' from the end - _CORE_SCRIPTS="${_ROOT:?"Missing ROOT"}core/" - readonly _ROOT _CORE_SCRIPTS _DEFINITIONS _REPOSITORY + local _DEFINITIONS _REPOSITORY + _DEFINITIONS="${1:?"Missing first parameter DEFINITIONS"}" + _REPOSITORY="$(getRemoteRepositoryPath)cis-definition-${2:?"Missing second parameter DOMAIN"}.git" + readonly _DEFINITIONS _REPOSITORY + [ "$(id -u)" == "0" ] \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_DEFINITIONS}" "${_REPOSITORY}" readonly \ + && echo "Running setup as 'root' trying to add definition repository:" \ + && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_DEFINITIONS}" readonly "${_REPOSITORY}" \ && echo " - definitions are usable for this host." \ && return 0 [ "$(id -u)" != "0" ] \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_DEFINITIONS}" "${_REPOSITORY}" writable \ + && echo "Running setup as 'user' trying to add definition repository:" \ + && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_DEFINITIONS}" writable "${_REPOSITORY}" \ && echo " - definitions are usable, as working copy." \ && return 0 @@ -133,20 +140,22 @@ function addDefinition(){ } function addState() { - local _ROOT _CORE_SCRIPTS _STATES _REPOSITORY - _STATES="${1:?"Missing parameter STATES"}" - _REPOSITORY="${2:?"Missing parameter REPOSITORY"}" - _ROOT="${_STATES%%/states/*}/" #Removes longest matching pattern '/states/*' from the end - _CORE_SCRIPTS="${_ROOT:?"Missing ROOT"}core/" - readonly _ROOT _CORE_SCRIPTS _STATES _REPOSITORY + local _STATES _REPOSITORY + _STATES="${1:?"Missing first parameter STATES"}" + _REPOSITORY="$(getRemoteRepositoryPath)cis-state-${2:?"Missing second parameter DOMAIN"}.git" + readonly _STATES _REPOSITORY [ "$(id -u)" == "0" ] \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_STATES}" "${_REPOSITORY}" writable \ + && echo "Running setup as 'root' trying to add state repository:" \ + && echo \ + && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_STATES}" writable "${_REPOSITORY}" \ && echo " - states are usable for this host." \ && return 0 [ "$(id -u)" != "0" ] \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_STATES}" "${_REPOSITORY}" writable \ + && echo "Running setup as 'user' trying to add state repository:" \ + && echo \ + && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addAndCheckGitRepository.sh" "${_STATES}" writable "${_REPOSITORY}" \ && echo " - states are usable, as working copy." \ && return 0 @@ -154,13 +163,10 @@ function addState() { } function setupCoreFunctionality() { - local _ROOT _CORE_SCRIPTS _DEFINITIONS _MINUTE_FROM_OWN_IP _SETUP + local _DEFINITIONS _MINUTE_FROM_OWN_IP _DEFINITIONS="${1:?"Missing DEFINITIONS: 'ROOT/definitions/DOMAIN'"}" - _ROOT="${_DEFINITIONS%%/definitions/*}/" #Removes longest matching pattern '/definitions/*' from the end - _CORE_SCRIPTS="${_ROOT:?"Missing ROOT"}core/" - _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 - _SETUP="${2:?"Missing SETUP"}" - readonly _ROOT _CORE_SCRIPTS _DEFINITIONS _MINUTE_FROM_OWN_IP _SETUP + _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 [ "$(id -u)" != "0" ] \ && echo "Configuration of host skipped because of insufficient rights." \ @@ -176,45 +182,39 @@ function setupCoreFunctionality() { && echo \ && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}ensureUsageOfDefinitions.sh" "${_DEFINITIONS}" /etc/sudoers.d/allow-jenkins-updateRepositories \ && echo \ - && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addToCrontabEveryHour.sh" "${_SETUP}" "${_MINUTE_FROM_OWN_IP}" \ + && "${_CORE_SCRIPTS:?"Missing CORE_SCRIPTS"}addToCrontabEveryHour.sh" "${_SETUP:?"Missing SETUP"}" "${_MINUTE_FROM_OWN_IP}" \ && return 0 return 1 } function setup() { - local _ROOT _DEFINITIONS _DEFINITIONS_REPOSITORY _DOMAIN _REPOSITORY_PATH _SETUP _STATES _STATES_REPOSITORY - _SETUP="$(readlink -f "${0}" 2> /dev/null)" - _ROOT="$(dirname ${_SETUP:?"Missing SETUP"} 2> /dev/null || echo "/iss")/" - _DOMAIN="$(getOrSetDomain "${_ROOT:?"Missing ROOT"}" "${1}")" - _REPOSITORY_PATH="$(getRemoteRepositoryPath "${_ROOT:?"Missing ROOT"}")" + local _DEFINITIONS _DOMAIN _STATES + _DOMAIN="$(getOrSetDomain "${1}")" - ! checkPreconditions "${_ROOT:?"Missing ROOT"}" "${_DOMAIN}" \ + ! checkPreconditions "${_DOMAIN}" \ && return 1 - _DEFINITIONS="${_ROOT:?"Missing ROOT"}definitions/${_DOMAIN:?"Missing DOMAIN"}" - _DEFINITIONS_REPOSITORY="${_REPOSITORY_PATH:?"Missing REPOSITORY_PATH"}/iss-definition-${_DOMAIN:?"Missing DOMAIN"}.git" - _STATES="${_ROOT:?"Missing ROOT"}states/${_DOMAIN:?"Missing DOMAIN"}" - _STATES_REPOSITORY="${_REPOSITORY_PATH:?"Missing REPOSITORY_PATH"}/iss-state-${_DOMAIN:?"Missing DOMAIN"}.git" - readonly _ROOT _DEFINITIONS _DEFINITIONS_REPOSITORY _DOMAIN _REPOSITORY_PATH _SETUP _STATES _STATES_REPOSITORY + _DEFINITIONS="${_CIS_ROOT:?"Missing CIS_ROOT"}definitions/${_DOMAIN:?"Missing DOMAIN"}" + _STATES="${_CIS_ROOT:?"Missing CIS_ROOT"}states/${_DOMAIN:?"Missing DOMAIN"}" + readonly _DEFINITIONS _DOMAIN _STATES echo \ - && echo "Running setup using repositories of: '${_REPOSITORY_PATH:?"Missing REPOSITORY_PATH"}' ..." \ + && addDefinition "${_DEFINITIONS:?"Missing DEFINITIONS"}" "${_DOMAIN:?"Missing DOMAIN"}" \ && echo \ - && addDefinition "${_DEFINITIONS:?"Missing DEFINITIONS"}" "${_DEFINITIONS_REPOSITORY:?"Missing DEFINITIONS_REPOSITORY"}" \ - && echo \ - && addState "${_STATES:?"Missing STATES"}" "${_STATES_REPOSITORY:?"Missing STATES_REPOSITORY"}" \ + && addState "${_STATES:?"Missing STATES"}" "${_DOMAIN:?"Missing DOMAIN"}" \ && echo \ && echo "Using definitions: '${_DEFINITIONS:?"Missing DEFINITIONS"}' ..." \ - && setupCoreFunctionality "${_DEFINITIONS:?"Missing DEFINITIONS"}" "${_SETUP:?"Missing SETUP"}" \ + && setupCoreFunctionality "${_DEFINITIONS:?"Missing DEFINITIONS"}" \ && return 0 - echo "FAIL: setup is incomplete: ("$(readlink -f ${0})")" - echo " - due to an error or insufficient rights." + echo "FAIL: setup is incomplete: ("$(readlink -f ${0})")" >&2 + echo " - due to an error or insufficient rights." >&2 return 1 } # sanitizes all parameters -setup \ - "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && exit 0 || exit 1 +setup "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ + && exit 0 + +exit 1 diff --git a/updateRepositories.sh b/updateRepositories.sh index cf6c13e..bbad32e 100755 --- a/updateRepositories.sh +++ b/updateRepositories.sh @@ -20,54 +20,61 @@ function update_repositories() { - local _ROOT _DEFINITIONS _DOMAIN _MODE _STATES _UPDATE_REPOSITORIES + local _CIS_ROOT _DEFINITIONS _DOMAIN _MODE _STATES _UPDATE_REPOSITORIES _UPDATE_REPOSITORIES="$(readlink -f "${0}" 2> /dev/null)" - _MODE="${1:-"all"}" - _ROOT="$(dirname ${_UPDATE_REPOSITORIES:?"Missing UPDATE_REPOSITORIES"} 2> /dev/null || echo "/iss")/" - _DOMAIN="$(cat ${_ROOT:?"Missing ROOT"}domainOfHostOwner)" - _DEFINITIONS="${_ROOT}definitions/${_DOMAIN:?"Missing DOMAIN from file: ${_ROOT}domainOfHostOwner"}/" - _STATES="${_ROOT}states/${_DOMAIN:?"Missing DOMAIN from file: ${_ROOT}domainOfHostOwner"}/" - readonly _ROOT _DEFINITIONS _DOMAIN _MODE _STATES _UPDATE_REPOSITORIES + _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 [ "${_MODE}" == "--repair" ] \ - && (git -C "${_ROOT}" reset --hard origin/master; \ - git -C "${_DEFINITIONS}" reset --hard origin/master; \ - git -C "${_STATES}" reset --hard origin/master; \ + && (git -C "${_CIS_ROOT}" reset --hard origin/main; \ + git -C "${_DEFINITIONS}" reset --hard origin/main; \ + git -C "${_STATES}" reset --hard origin/main; \ echo "Run repairs") \ && return 0 [ "${_MODE}" == "--test" ] \ - && git -C "${_ROOT}" pull \ + && git -C "${_CIS_ROOT}" pull \ && git -C "${_DEFINITIONS}" pull \ && git -C "${_STATES}" pull \ && echo "Run in testMode successfully." \ && return 0 [ "${_MODE}" == "--scripts" ] \ - && echo "Host $HOSTNAME updating scripts: ${_ROOT} ..." \ - && (git -C "${_ROOT}" pull &> /dev/null &) \ + && printf "Host $HOSTNAME updating scripts: ${_CIS_ROOT} ... " \ + && (git -C "${_CIS_ROOT}" pull &> /dev/null) \ + && echo "(done)" \ && return 0 [ "${_MODE}" == "--definitions" ] \ - && echo "Host ${HOSTNAME} updating definitions: ${_DEFINITIONS} ..." \ - && (git -C "${_DEFINITIONS}" pull &> /dev/null &) \ + && echo "Host ${HOSTNAME} updating definitions: ${_DEFINITIONS} ... " \ + && (git -C "${_DEFINITIONS}" pull &> /dev/null) \ + && echo "(done)" \ && return 0 [ "${_MODE}" == "--states" ] \ - && echo "Host ${HOSTNAME} updating states: ${_STATES} ..." \ - && (git -C "${_STATES}" pull &> /dev/null &) \ + && echo "Host ${HOSTNAME} updating states: ${_STATES} ... " \ + && (git -C "${_STATES}" pull &> /dev/null) \ + && echo "(done)" \ && return 0 - echo "Host ${HOSTNAME} updating ${_MODE}:" \ - && echo " - ${_ROOT}" \ - && echo " - ${_DEFINITIONS}" \ - && echo " - ${_STATES}" - git -C "${_ROOT}" pull &> /dev/null - git -C "${_DEFINITIONS}" pull &> /dev/null - git -C "${_STATES}" pull &> /dev/null + [ "${_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) \ + && echo "(done)" \ + && return 0 + + echo "FAILED: an error occurred during an update." + return 1 } # sanitizes all parameters -update_repositories \ - "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ - && exit 0 || exit 1 +update_repositories "$(echo ${1} | sed -E 's|[^a-zA-Z0-9/:@._-]*||g')" \ + && exit 0 + +exit 1