1
0
Files
netupgrade/bin/netupgrade
T

365 lines
9.1 KiB
Bash
Executable File

#!/bin/bash
showHelp() {
echo "netupgrade [--help] [-f] [-y] [configfilename]"
echo ""
echo " --help Show this help message"
echo " -f Preselect all nodes in the checklist"
echo " -y No confirmation for supported package managers"
echo " configfilename Path to a cfg file"
}
checkDependencies() {
local -a REQUIRED_CMDS=(ssh whiptail cat tee rm touch mv)
local -a MISSING_CMDS=()
local CMD
for CMD in "${REQUIRED_CMDS[@]}"; do
if ! command -v "${CMD}" >/dev/null 2>&1; then
MISSING_CMDS+=("${CMD}")
fi
done
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
echo "Error: missing required dependencies: ${MISSING_CMDS[*]}"
exit 1
fi
}
resolveLogViewer() {
if [ -n "${EDITOR}" ] && command -v "${EDITOR}" >/dev/null 2>&1; then
LOGVIEWER="${EDITOR}"
return
fi
local -a CANDIDATES=(nano vi less)
local CANDIDATE
for CANDIDATE in "${CANDIDATES[@]}"; do
if command -v "${CANDIDATE}" >/dev/null 2>&1; then
LOGVIEWER="${CANDIDATE}"
return
fi
done
LOGVIEWER=""
}
pressAnyKey(){
read -n1 -r -p "Press any key to continue."
}
loadConfig(){
if [ -e "${CONFIGFILENAME}" ]; then
. "${CONFIGFILENAME}"
else
echo "Error : Config file not found :"
echo "${CONFIGFILENAME}"
exit 1
fi
}
parseNode() {
local NODE_VALUE="${1}"
local -n NODE_FIELDS_REF="${2}"
IFS=';' read -r -a NODE_FIELDS_REF <<< "${NODE_VALUE}"
}
parseSelection() {
local SELECTION_RAW="${1}"
local -n SELECTION_REF="${2}"
local ITEM_INDEX=0
SELECTION_REF=()
read -r -a SELECTION_REF <<< "${SELECTION_RAW}"
for ITEM_INDEX in "${!SELECTION_REF[@]}"; do
SELECTION_REF[ITEM_INDEX]="${SELECTION_REF[ITEM_INDEX]%\"}"
SELECTION_REF[ITEM_INDEX]="${SELECTION_REF[ITEM_INDEX]#\"}"
done
}
runSSH() {
local HOST="${1}"
shift
ssh "${SSH_USER}@${HOST}" "$@"
}
prependLogSummary() {
local SUMMARY_CONTENT="${1}"
local TEMP_LOGFILENAME="${LOGFILENAME}.tmp"
{
echo "Results :"
echo "---------"
echo ""
echo -e "${SUMMARY_CONTENT}"
echo ""
echo ""
cat "${LOGFILENAME}"
} > "${TEMP_LOGFILENAME}"
mv "${TEMP_LOGFILENAME}" "${LOGFILENAME}"
}
selectNodes(){
local -a OPTIONS=()
local -a SELECTED_ITEMS=()
local -i INDEX=1
local -i I=0
local -a FIELDS=()
local DESC=""
local FIELD=""
local SEL=""
local DEFAULT_STATE="OFF"
if [ "${FULL}" -eq 1 ]; then
DEFAULT_STATE="ON"
fi
for NODE in "${NODES[@]}"; do
FIELDS=()
DESC=""
parseNode "${NODE}" FIELDS
for ((I = 2; I < ${#FIELDS[@]}; ++I)); do
FIELD="${FIELDS[I]}"
if [ -z "${DESC}" ]; then
DESC="${FIELD%%:*}"
else
DESC="${DESC}|${FIELD%%:*}"
fi
done
OPTIONS+=("${INDEX}:${FIELDS[0]}" "${FIELDS[1]} [${DESC}]" "${DEFAULT_STATE}")
INDEX+=1
done
if ! SEL=$(whiptail --title "NetUpgrade" --checklist "" 0 0 0 \
"${OPTIONS[@]}" \
3>&1 1>&2 2>&3); then
exit 0
fi
if [ -z "${SEL}" ]; then
exit 0
fi
parseSelection "${SEL}" SELECTED_ITEMS
if [ ${#SELECTED_ITEMS[@]} -eq 0 ]; then
exit 0
fi
if [ -f "${LOGFILENAME}" ]; then
rm "${LOGFILENAME}"
fi
touch "${LOGFILENAME}"
local RESULT="\n"
for ITM in "${SELECTED_ITEMS[@]}"; do
INDEX=1
for NODE in "${NODES[@]}"; do
FIELDS=()
parseNode "${NODE}" FIELDS
if [ "${ITM}" = "${INDEX}:${FIELDS[0]}" ]; then
for ((I = 2; I < ${#FIELDS[@]}; ++I)); do
if runCmd "${FIELDS[0]}" "${FIELDS[1]}" "${FIELDS[I]}"; then
RESULT+="Ok: ${FIELDS[1]} @ ${FIELDS[0]} : ${FIELDS[I]}\n"
else
RESULT+="Error: ${FIELDS[1]} @ ${FIELDS[0]} : ${FIELDS[I]}\n"
fi
done
fi
INDEX+=1
done
done
prependLogSummary "${RESULT}"
if [ -n "${LOGVIEWER}" ]; then
"${LOGVIEWER}" "${LOGFILENAME}"
else
echo "Warning: no log viewer found, showing log path instead: ${LOGFILENAME}"
fi
rm -i "${LOGFILENAME}"
echo ""
echo "Results :"
echo "---------"
echo -e "${RESULT}"
}
runCmd() { # $1=host $2=name $3=cmd
local -r HOST="${1}"
local -r NAME="${2}"
local -r ACTION="${3}"
local -r CMD="${ACTION%%:*}"
local -r CMDVAL="${ACTION#*:}"
local -i ERROR=0
local TITLELENGTH=0
local SUBTITLE="-----------------------------------------------------------------------------"
local YESARG=""
echo "${NAME} @ ${HOST} : ${CMD}" | tee -a "${LOGFILENAME}"
TITLELENGTH=$((${#NAME} + ${#HOST} + ${#CMD} + 6))
echo "${SUBTITLE:0:${TITLELENGTH}}" | tee -a "${LOGFILENAME}"
date +'%Y-%m-%d %H:%M:%S %A' | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
set -o pipefail
case ${CMD} in
reboot)
echo "reboot (detached)" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" sh -c 'nohup sh -c "reboot || /sbin/reboot || shutdown -r now" >/dev/null 2>&1 </dev/null &' | tee -a "${LOGFILENAME}"; then
ERROR=1
else
sleep 1
fi
;;
apt)
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
echo "apt-get ${YESARG} update" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" apt-get ${YESARG} update | tee -a "${LOGFILENAME}"; then
ERROR=1
else
echo "" | tee -a "${LOGFILENAME}"
echo "apt-get ${YESARG} dist-upgrade" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" apt-get ${YESARG} dist-upgrade | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "" | tee -a "${LOGFILENAME}"
echo "apt-get ${YESARG} autoremove --purge" | tee -a "${LOGFILENAME}"
runSSH "${HOST}" apt-get ${YESARG} autoremove --purge | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
echo "apt-get ${YESARG} autoclean" | tee -a "${LOGFILENAME}"
runSSH "${HOST}" apt-get ${YESARG} autoclean | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
echo "apt-get ${YESARG} clean" | tee -a "${LOGFILENAME}"
runSSH "${HOST}" apt-get ${YESARG} clean | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
fi
;;
yum)
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
echo "yum ${YESARG} update" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" yum ${YESARG} update | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
pkg)
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
echo "pkg ${YESARG} update" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" pkg ${YESARG} update | tee -a "${LOGFILENAME}"; then
ERROR=1
else
echo "" | tee -a "${LOGFILENAME}"
echo "pkg upgrade ${YESARG}" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" pkg upgrade ${YESARG} | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "" | tee -a "${LOGFILENAME}"
echo "pkg autoremove ${YESARG}" | tee -a "${LOGFILENAME}"
runSSH "${HOST}" pkg autoremove ${YESARG} | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
echo "pkg clean ${YESARG}" | tee -a "${LOGFILENAME}"
runSSH "${HOST}" pkg clean ${YESARG} | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
fi
;;
pacman)
if [ "${YES}" -eq 1 ]; then
YESARG="--noconfirm"
fi
echo "pacman -Sy ${YESARG} archlinux-keyring" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" pacman -Sy ${YESARG} archlinux-keyring | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "pacman -Syu ${YESARG}" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" pacman -Syu ${YESARG} | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "pacman orphan cleanup" | tee -a "${LOGFILENAME}"
if [ -n "${YESARG}" ]; then
if ! runSSH "${HOST}" sh -c 'orphans=$(pacman -Qqtd 2>/dev/null || true); if [ -n "$orphans" ]; then pacman -Rns --noconfirm $orphans; fi' | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
else
if ! runSSH "${HOST}" sh -c 'orphans=$(pacman -Qqtd 2>/dev/null || true); if [ -n "$orphans" ]; then pacman -Rns $orphans; fi' | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
fi
echo "pacman -Sc ${YESARG}" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" pacman -Sc ${YESARG} | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
apk)
echo "apk update" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" apk update | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "apk upgrade" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" apk upgrade | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
cmd)
echo "cmd: ${CMDVAL}" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" sh -c "${CMDVAL}" | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
docker-stacks)
echo "docker stacks update in ${CMDVAL}" | tee -a "${LOGFILENAME}"
if ! ssh "${SSH_USER}@${HOST}" "STACK_ROOT=$(printf '%q' "${CMDVAL}") bash -s" <<'EOF' | tee -a "${LOGFILENAME}"
stack_root="${STACK_ROOT}"
for dir in "$stack_root"/*; do
[ -d "$dir" ] || continue
(
cd "$dir" || exit 1
docker compose pull
docker compose up -d
) || exit 1
done
docker image prune -f
EOF
then
ERROR=1
fi
;;
*)
echo "Error: Command ${CMD} unknown" | tee -a "${LOGFILENAME}"
ERROR=1
;;
esac
echo "" | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
if [ "${ERROR}" -eq 1 ]; then
return 1
fi
}
declare -i YES=0
declare -i FULL=0
declare CONFIGFILENAME="${HOME}/.config/netupgrade/index.cfg"
declare LOGFILENAME="${HOME}/netupgrade.log"
declare LOGVIEWER=""
declare SSH_USER="root"
declare -a NODES=()
while [[ ${#} -gt 0 ]]; do
case ${1} in
--help) showHelp; exit 0;;
-y) YES=1; shift;;
-f) FULL=1; shift;;
*) CONFIGFILENAME=${1}; shift;;
esac
done
checkDependencies
resolveLogViewer
loadConfig
selectNodes