sync.sh uses module composition to centralize critical logic

This commit is contained in:
m8in
2026-06-12 22:18:37 +02:00
parent 389b97b5d8
commit 5136cfc24d
+91 -104
View File
@@ -1,51 +1,9 @@
#!/bin/bash #!/bin/bash
source /cis/core/base.module.sh source /cis/core/base.module.sh
base.loadModule composition
function stopObsoleteScreenSession() {
local _SCREEN_SESSION _COMPOSITION _PID
_SCREEN_SESSION="${1:?"stopObsoleteScreenSession(): Missing first parameter SCREEN_SESSION"}"
_COMPOSITION=$(echo "$_SCREEN_SESSION" | grep -oE "[^:]+$")
_PID=$(echo "$_SCREEN_SESSION" | grep -oE "^[0-9]+")
readonly _SCREEN_SESSION _COMPOSITION _PID
! isSyncHostForComposition "${_COMPOSITION}" \
&& echo "Stopping sync screen session of composition: ${_COMPOSITION}" \
&& screen -XS "${_PID}" quit
}
function cleanSessions() {
screen -ls | grep -oE "[0-9]+\.composition-sync\:[a-zA-Z0-9_-]+" | while read -r _SCREEN_SESSION; do
stopObsoleteScreenSession "${_SCREEN_SESSION}"
done
}
function startMissingScreenSession() {
local _COMPOSITION _SCRIPT
_COMPOSITION="${1:?"startMissingScreenSession(): Missing first parameter COMPOSITION"}"
_SCRIPT="${CIS[FULLSCRIPTNAME]:?"startMissingScreenSession(): Missing CIS_FULLSCRIPTNAME"}"
readonly _COMPOSITION _SCRIPT
! screen -ls | grep -qoE "[0-9]+\.compositionsync\.${_COMPOSITION}" \
&& echo "Starting screen sync session of composition: ${_COMPOSITION}" \
&& screen -dmS "composition-sync:${_COMPOSITION}" "${_SCRIPT}" --loopSingle "${_COMPOSITION}"
}
function addSessions() {
local _COMPOSITIONS
_COMPOSITIONS="${CIS[DOMAINDEFINITIONS]:?"Missing CIS_DOMAINDEFINITIONS"}compositions/"
readonly _COMPOSITIONS
local _composition
for _composition in "${_COMPOSITIONS}"*/; do
_composition="${_composition%/}"
_composition="${_composition##*/}"
isSyncHostForComposition "${_composition}" \
&& startMissingScreenSession "${_composition}"
done
}
function destroySyncSnapshot() { function destroySyncSnapshot() {
local _ZFS _SNAPSHOT local _ZFS _SNAPSHOT
_ZFS="${1:?"destroySyncSnapshot(): Missing first parameter ZFS"}" _ZFS="${1:?"destroySyncSnapshot(): Missing first parameter ZFS"}"
@@ -62,25 +20,6 @@ function destroySyncSnapshot() {
return 1 return 1
} }
function isSyncHostForComposition() {
local _COMPOSITION _CURRENTHOST_FILE _RECEIVERHOST _SYNCHOST_FILE
_COMPOSITION="${1:?"isSyncHostForComposition(): Missing first parameter COMPOSITION"}"
_CURRENTHOST_FILE="${CIS[DOMAINDEFINITIONS]:?"Missing CIS_DOMAINDEFINITIONS"}compositions/${_COMPOSITION}/current-host"
_RECEIVERHOST="${CIS[HOST]:?"Missing CIS_HOST"}"
_SYNCHOST_FILE="${CIS[DOMAINDEFINITIONS]:?"Missing CIS_DOMAINDEFINITIONS"}compositions/${_COMPOSITION}/composition-sync-hosts"
readonly _COMPOSITION _CURRENTHOST_FILE _RECEIVERHOST _SYNCHOST_FILE
# There has to be a host running the composition as current host (file exists),
# and this host cannot be sync-host also (file content is not this host).
# After that check if this host is listed as composition-sync-host.
[ -f "${_CURRENTHOST_FILE}" ] \
&& ! head -n 1 "${_CURRENTHOST_FILE}" 2> /dev/null | grep -q -F -- "${_RECEIVERHOST}" \
&& grep -q -F -- "${_RECEIVERHOST}" "${_SYNCHOST_FILE}" 2> /dev/null \
&& return 0
return 1
}
function protectZFS() { function protectZFS() {
local _ZFS local _ZFS
_ZFS="${1:?"protectZFS(): Missing first parameter ZFS"}" _ZFS="${1:?"protectZFS(): Missing first parameter ZFS"}"
@@ -127,6 +66,27 @@ function removeOutdatedSyncSnapshots() {
return 0 return 0
} }
function tryRollbackToRepair() {
local _COMPOSITION _RECEIVERHOST _ZFS _ROLLBACK_DAY _ROLLBACK_SNAPSHOT
_COMPOSITION="${1:?"tryRollbackToRepair(): Missing first parameter COMPOSITION"}"
_ZFS="${2:?"tryRollbackToRepair(): Missing second parameter ZFS"}"
_RECEIVERHOST="${CIS[HOST]:?"Missing CIS_HOST"}"
_ROLLBACK_DAY=$(head -n 1 "${CIS[DOMAINDEFINITIONS]:?"Missing CIS_DOMAINDEFINITIONS"}compositions/${_COMPOSITION:?"Missing COMPOSITION"}/rollback")
base.set _ROLLBACK_DAY "${_ROLLBACK_DAY:-'2020-01-01'}" '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'
_ROLLBACK_SNAPSHOT=$(zfs list -H -o name -S creation -t snapshot "${_ZFS}" | head -n 1 | grep -F -- "@SYNC_${_RECEIVERHOST}_${_ROLLBACK_DAY}_")
readonly _COMPOSITION _RECEIVERHOST _ZFS _ROLLBACK_SNAPSHOT
# Not allowed to do anything
[ -z "${_ROLLBACK_SNAPSHOT}" ] && return 1
# Remove at most the two newest sync snapshots, if the day matches with the rollback file
echo "Try to fix by removing: '${_ROLLBACK_SNAPSHOT}'" \
&& zfs destroy "${_ROLLBACK_SNAPSHOT:?"tryRollbackToRepair(): Missing _ROLLBACK_SNAPSHOT"}" \
&& return 0
return 1
}
function receive() { function receive() {
local _RECEIVERHOST _COMPOSITION _SOURCEHOST _SSH_PORT _SSH_COMMAND _SEND_SCRIPT _ZFS_BRANCH _ZFS local _RECEIVERHOST _COMPOSITION _SOURCEHOST _SSH_PORT _SSH_COMMAND _SEND_SCRIPT _ZFS_BRANCH _ZFS
_COMPOSITION="${1:?"receive(): Missing first parameter COMPOSITION"}" _COMPOSITION="${1:?"receive(): Missing first parameter COMPOSITION"}"
@@ -183,84 +143,111 @@ function receive() {
return 1 return 1
} }
function receiveLoopAll() {
local _SCRIPT
_SCRIPT="${CIS[FULLSCRIPTNAME]:?"startMissingScreenSession(): Missing CIS_FULLSCRIPTNAME"}"
readonly _SCRIPT
composition.printAllSyncedByThisHost | while read -r _COMPOSITION; do
! screen -ls | grep -qoE "[0-9]+\.compositionsync\.${_COMPOSITION}" \
&& echo "Starting screen sync session of composition: ${_COMPOSITION}" \
&& screen -dmS "composition-sync:${_COMPOSITION}" "${_SCRIPT}" --loopSingle "${_COMPOSITION}"
done
}
function receiveLoopSingle() { function receiveLoopSingle() {
local _COMPOSITION local _COMPOSITION
_COMPOSITION="${1:?"receiveLoopSingle(): Missing first parameter COMPOSITION"}" _COMPOSITION="${1:?"receiveLoopSingle(): Missing first parameter COMPOSITION"}"
readonly _COMPOSITION readonly _COMPOSITION
while true; do while composition.isSyncedByThisHost "${_COMPOSITION}"; do
receive "${_COMPOSITION}" \ receive "${_COMPOSITION}" \
&& echo "Sleep for 5s" \ && echo "Sleep for 5s" \
&& sleep 5 \ && sleep 5 \
&& echo \ && echo \
&& continue && continue
# If there is a screen session this keeps it alive in case of an error for debugging
echo echo
echo "Waiting 5min then ABORT!" echo "Waiting 5min then ABORT!"
sleep 300 sleep 300
break return 1
done done
! composition.isSyncedByThisHost "${_COMPOSITION}" \
&& echo "This host '${CIS[HOST]}' is no sync-host (anymore) for composition: '${_COMPOSITION}'"
} }
function receiveOnceAll() { function receiveOnceAll() {
local _COMPOSITIONS local _COMPOSITION
_COMPOSITIONS="${CIS[DOMAINDEFINITIONS]:?"Missing CIS_DOMAINDEFINITIONS"}compositions/"
readonly _COMPOSITIONS
for _composition in "${_COMPOSITIONS}"*/; do composition.printAllSyncedByThisHost | while read -r _COMPOSITION; do
_composition="${_composition%/}" receive "${_COMPOSITION}"
_composition="${_composition##*/}"
isSyncHostForComposition "${_composition}" \
&& receive "${_composition}"
done done
return 0 return 0
} }
function tryRollbackToRepair() { function receiveOnceSingle() {
local _COMPOSITION _RECEIVERHOST _ZFS _ROLLBACK_DAY _ROLLBACK_SNAPSHOT local _COMPOSITION
_COMPOSITION="${1:?"tryRollbackToRepair(): Missing first parameter COMPOSITION"}" _COMPOSITION="${1:?"receiveOnceSingle(): Missing first parameter COMPOSITION"}"
_ZFS="${2:?"tryRollbackToRepair(): Missing second parameter ZFS"}" readonly _COMPOSITION
_RECEIVERHOST="${CIS[HOST]:?"Missing CIS_HOST"}"
_ROLLBACK_DAY=$(head -n 1 "${CIS[DOMAINDEFINITIONS]:?"Missing CIS_DOMAINDEFINITIONS"}compositions/${_COMPOSITION:?"Missing COMPOSITION"}/rollback")
base.set _ROLLBACK_DAY "${_ROLLBACK_DAY:-'2020-01-01'}" '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'
_ROLLBACK_SNAPSHOT=$(zfs list -H -o name -S creation -t snapshot "${_ZFS}" | head -n 1 | grep -F -- "@SYNC_${_RECEIVERHOST}_${_ROLLBACK_DAY}_")
readonly _COMPOSITION _RECEIVERHOST _ZFS _ROLLBACK_SNAPSHOT
# Nothing to do ! composition.isSyncedByThisHost "${_COMPOSITION}" \
[ -z "${_ROLLBACK_SNAPSHOT}" ] && return 0 && echo "This host '${CIS[HOST]}' is no sync-host for composition: '${_COMPOSITION}'" \
&& return 1
# Remove at most the two newest sync snapshots, if the day matches with the rollback file receive "${_COMPOSITION}" \
echo "Try to fix by removing: '${_ROLLBACK_SNAPSHOT}'" \
&& zfs destroy "${_ROLLBACK_SNAPSHOT:?"tryRollbackToRepair(): Missing _ROLLBACK_SNAPSHOT"}" \
&& return 0 && return 0
return 1 return 1
}
function usage() {
echo
echo 'Commands:'
echo ' --loopAll : This will start one screen session per composition ("composition-sync-hosts"),'
echo ' and run the sync process in an endless loop.'
echo ' --onceAll : This will run the sync process once for each composition ("composition-sync-hosts").'
echo ' e.g.: you can use it in crontab as a daily backup.'
echo ' --loopSingle COMPOSITION : This will run the sync process in an endless loop but just for the specified COMPOSITION.'
echo ' --onceSingle COMPOSITION : This will run the sync process once just for the specified COMPOSITION.'
echo
echo 'Current environment:'
echo " Full name of this script : FULLSCRIPTNAME='${CIS[FULLSCRIPTNAME]}'"
echo " Configuration:"
echo " Receiving host (this host): RECEIVERHOST='${CIS[HOST]}'"
return 0
} }
# Parameter 1: only one of these values are allowed (--all, --once, --loop)
# Parameter 2: is optional '()?' and only a subset of alphanumeric characters are allowed and [_-] if not leading (due to: -oProxyCommand=...). # Parameter 2: is optional '()?' and only a subset of alphanumeric characters are allowed and [_-] if not leading (due to: -oProxyCommand=...).
base.set MODE "${1}" '^(--onceAll|--onceSingle|--loopAll|--loopSingle)$'
base.set COMPOSITION "${2}" '^([a-zA-Z0-9][a-zA-Z0-9_-]*)?$' base.set COMPOSITION "${2}" '^([a-zA-Z0-9][a-zA-Z0-9_-]*)?$'
[ "${MODE}" == "--onceAll" ] \ case "${1}" in
&& receiveOnceAll \ --onceAll)
&& exit 0 receiveOnceAll \
&& exit 0
[ "${MODE}" == "--onceSingle" ] \ ;;
&& receive "${COMPOSITION}" \ --onceSingle)
&& exit 0 receiveOnceSingle "${COMPOSITION}" \
&& exit 0
[ "${MODE}" == "--loopAll" ] \ ;;
&& cleanSessions \ --loopAll)
&& addSessions \ receiveLoopAll \
&& exit 0 && exit 0
;;
[ "${MODE}" == "--loopSingle" ] \ --loopSingle)
&& receiveLoopSingle "${COMPOSITION}" \ receiveLoopSingle "${COMPOSITION}" \
&& exit 0 && exit 0
;;
*)
echo "Unknown command '${1}' '${2}'"
usage
exit 1
;;
esac
exit 1 exit 1