Files
cis/module/composition.module.sh
T

217 lines
8.4 KiB
Bash
Executable File

#!/bin/bash
source /cis/core/base.module.sh
base.loadModule print
# composition.printAll
#
# This function prints a list of all compositions, one per line.
function composition.printAll() {
local _COMPOSITIONS
_COMPOSITIONS="${CIS[COMPOSITIONS]:?"composition.printAll(): Missing CIS_COMPOSITIONS"}"
readonly _COMPOSITIONS
local _COMPOSITION_PATH
ls -d1 "${_COMPOSITIONS}"*/ | while read -r _COMPOSITION_PATH; do
# Like dirname: removes tailing '/'
local _COMPOSITION="${_COMPOSITION_PATH%/}"
# Like basename
echo "${_COMPOSITION##*/}"
done
}
# composition.printAllRunningOnThisHost
#
# This function prints a list of all compositions which run or should run on this host, one per line.
# see also: composition.shouldRunOnThisHost
function composition.printAllRunningOnThisHost() {
composition.printAll | while read -r _COMPOSITION; do
composition.shouldRunOnThisHost "${_COMPOSITION}" \
&& echo "${_COMPOSITION}"
done
}
# composition.printRunningHost
#
# This function prints the name of the host the given composition is running on.
function composition.printRunningHost() {
local _COMPOSITION _RUNNINGHOST_FILE
_COMPOSITION="${1:?"composition.printRunningHost(): Missing first parameter COMPOSITION"}"
_RUNNINGHOST_FILE="${CIS[COMPOSITIONS]:?"composition.printRunningHost(): Missing CIS_COMPOSITIONS"}${_COMPOSITION}/running-host"
readonly _COMPOSITION _RUNNINGHOST_FILE
[ -f "${_RUNNINGHOST_FILE}" ] \
&& head -n 1 -- "${_RUNNINGHOST_FILE}" \
&& return 0
return 1
}
# composition.printAllSyncedByThisHost
#
# This function prints a list of all compositions which should be synced to this host, one per line.
# see also: composition.shouldBeSyncedByGivenHost
function composition.printAllSyncedByThisHost() {
composition.printAll | while read -r _COMPOSITION; do
composition.shouldBeSyncedByGivenHost "${_COMPOSITION}" "${CIS[HOST]}" \
&& echo "${_COMPOSITION}"
done
}
# composition.printAllSyncingHosts
#
# This function prints the name of all hosts the given composition is syncted to.
function composition.printAllSyncingHosts() {
local _COMPOSITION _SYNCHOSTS_FILE
_COMPOSITION="${1:?"composition.printAllSyncingHosts(): Missing first parameter COMPOSITION"}"
_SYNCHOSTS_FILE="${CIS[COMPOSITIONS]:?"composition.printAllSyncingHosts(): Missing CIS_COMPOSITIONS"}${_COMPOSITION}/zfssync-hosts"
readonly _COMPOSITION _SYNCHOSTS_FILE
[ -f "${_SYNCHOSTS_FILE}" ] \
&& cat "${_SYNCHOSTS_FILE}" \
&& return 0
return 1
}
# composition.printZFS
# - COMPOSITION mandatory: "uptime-kuma-prod"
#
# This function prints zhe matching ZFS of zje given composition which stores the data or is synced.
function composition.printZFS() {
local _COMPOSITION _ZFS_BRANCH _ZFS _ZFS_VERIFIED
_COMPOSITION="${1:?"composition.printZfsOfComposition(): Missing first parameter COMPOSITION"}"
_ZFS_BRANCH="$(cat "${CIS[COMPOSITIONS]}${_COMPOSITION}/zfs-branch" 2> /dev/null)"
_ZFS_BRANCH="${_ZFS_BRANCH%/}" #Remove tailing '/' if exists
_ZFS="${_ZFS_BRANCH:-"zpool1/persistent"}/${_COMPOSITION}"
readonly _COMPOSITION _ZFS_BRANCH _ZFS
if composition.shouldRunOnThisHost "${_COMPOSITION}"; then
_ZFS_VERIFIED="$(zfs list -H -o name "${_ZFS}" 2> /dev/null)"
else
_ZFS_VERIFIED="$(zfs list -H -o name "${_ZFS}-BACKUP" 2> /dev/null)"
fi
[ -n "${_ZFS_VERIFIED}" ] \
&& echo "${_ZFS_VERIFIED}" \
&& return 0
print.failure "ZFS not found:" "${_ZFS}"
return 1
}
# composition.shouldRunOnThisHost COMPOSITION
# - COMPOSITION mandatory: "uptime-kuma-prod"
#
# This function checks if the given composition runs or should run on this host.
# Therefore a file 'running-host' defines the the current host where the composition runs.
function composition.shouldRunOnThisHost() {
local _COMPOSITION _RUNNINGHOST_FILE
_COMPOSITION="${1:?"composition.shouldRunOnThisHost(): Missing first parameter COMPOSITION"}"
_RUNNINGHOST_FILE="${CIS[COMPOSITIONS]:?"composition.shouldRunOnThisHost(): Missing CIS_COMPOSITIONS"}${_COMPOSITION}/running-host"
readonly _COMPOSITION _RUNNINGHOST_FILE
! [ -f "${_RUNNINGHOST_FILE}" ] \
&& print.failure "Missing file running-host for composition: '${_COMPOSITION}'" \
&& return 1
[ -n "${CIS[HOST]}" ] \
&& head -n 1 -- "${_RUNNINGHOST_FILE}" | grep -q -E -- "^${CIS[HOST]}" \
&& return 0
return 1
}
# composition.shouldBeSyncedByGivenHost COMPOSITION
# - COMPOSITION mandatory: "uptime-kuma-prod"
# - HOST mandatory: "server.your-domain.net"
#
# This function checks if the given composition is or should be synced to the given host.
# Therefore a file 'zfssync-hosts' defines a list of host, one per line, where the composition should be synced to.
# The given host either runs the composition or syncs it.
function composition.shouldBeSyncedByGivenHost() {
local _COMPOSITION _HOST _COMPOSITION_PATH _RUNNINGHOST_FILE _SYNCHOSTS_FILE
_COMPOSITION="${1:?"composition.shouldBeSyncedByGivenHost(): Missing first parameter COMPOSITION"}"
_HOST="${2:?"composition.shouldBeSyncedByGivenHost(): Missing second parameter HOST"}"
_COMPOSITION_PATH="${CIS[COMPOSITIONS]:?"composition.shouldBeSyncedByGivenHost(): Missing CIS_COMPOSITIONS"}${_COMPOSITION}/"
_RUNNINGHOST_FILE="${_COMPOSITION_PATH}running-host"
_SYNCHOSTS_FILE="${_COMPOSITION_PATH}zfssync-hosts"
readonly _COMPOSITION _HOST _COMPOSITION_PATH _RUNNINGHOST_FILE _SYNCHOSTS_FILE
# This host either runs the composition or syncs it.
# If there is no _RUNNINGHOST_FILE than the definition is invalid and should not be synced.
[ -f "${_RUNNINGHOST_FILE}" ] \
&& head -n 1 -- "${_RUNNINGHOST_FILE}" | grep -q -v -E -- "^${_HOST}" \
&& grep -q -E -- "^${_HOST}" "${_SYNCHOSTS_FILE}" \
&& return 0
return 1
}
# composition.start COMPOSITION
# - COMPOSITION mandatory: "uptime-kuma-prod"
#
# This function starts the given composition on this host, provided that this is the defined result.
function composition.start() {
local _COMPOSITION _COMPOSITION_HOME_FILE _COMPOSITION_HOME _COMPOSITION_FILE
_COMPOSITION="${1:?"composition.start(): Missing first parameter COMPOSITION"}"
_COMPOSITION_HOME_FILE="${CIS[COMPOSITIONS]:?"composition.start(): Missing CIS_COMPOSITIONS"}${_COMPOSITION}/home"
# The regex should ensure a path starts and ends with a '/' and it should limit the allowed set of characters
base.set _COMPOSITION_HOME "$(head -n 1 "${_COMPOSITION_HOME_FILE}" 2> /dev/null)" "${REGEX[DIRPATH]}" optional
_COMPOSITION_FILE="${_COMPOSITION_HOME}docker-compose.yml"
readonly _COMPOSITION _COMPOSITION_HOME_FILE _COMPOSITION_HOME _COMPOSITION_FILE
! composition.shouldRunOnThisHost "${_COMPOSITION}" \
&& echo "SKIPPED: The composition '${_COMPOSITION}' should not run on this host." >&2 \
&& return 0
[ -z "${_COMPOSITION_HOME}" ] \
&& print.failure "There was no file 'home' defined." \
&& return 1
[ ! -f "${_COMPOSITION_FILE}" ] \
&& print.failure "No composition file found: '${_COMPOSITION_FILE}'" \
&& return 1
printf -- "Starting composition: '%s'\n" "${_COMPOSITION}" >&2
if [ "$(docker compose version 2> /dev/null)" ]; then
docker compose --file "${_COMPOSITION_FILE}" start \
&& echo "SUCCESS" >&2 \
&& return 0
elif [ "$(docker-compose version 2> /dev/null)" ]; then
docker-compose --file "${_COMPOSITION_FILE}" start \
&& echo "SUCCESS" >&2 \
&& return 0
fi
print.failure "Missing command: 'docker compose'" \
"(maybe you have to install it via: 'apt install docker-compose-v2')"
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 'composition.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 composition'
echo
base.explain 'composition' "${1}" "${2}"
echo
exit 1
fi