1
0
Files
netupgrade/bin/netupgrade
T

322 lines
8.1 KiB
Bash
Executable File

#!/bin/bash
showHelp() {
echo "netupgrade [--help] [-f] [-y] [configfilename]"
echo ""
echo " --help Show this help message"
echo " -f Select all nodes"
echo " -y No confirmation for supported package managers"
echo " configfilename Path to a cfg file"
}
checkDependencies() {
local -a REQUIRED_CMDS=(ssh whiptail sed tee rm touch)
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}"
}
runSSH() {
local HOST="${1}"
shift
ssh "${SSH_USER}@${HOST}" "$@"
}
selectNodes(){
local -a OPTIONS=()
local -i INDEX=1
local -i I=0
local -a FIELDS=()
local DESC=""
local FIELD=""
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}]" "${FULL}")
INDEX+=1
done
if ! SEL=$(whiptail --title "NetUpgrade" --checklist "" 0 0 0 \
"${OPTIONS[@]}" \
3>&1 1>&2 2>&3); then
exit 0
fi
if [ ${#SEL} == 0 ]; then
exit 0
fi
if [ -f "${LOGFILENAME}" ]; then
rm "${LOGFILENAME}"
fi
touch "${LOGFILENAME}"
local RESULT="\n"
for ITM in ${SEL}; 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
sed -i "1s/^/${RESULT//\//\\\/}\n\n\n\n/" "${LOGFILENAME}"
sed -i "1s/^/---------\n\n/" "${LOGFILENAME}"
sed -i "1s/^/Results :\n/" "${LOGFILENAME}"
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)
if ! runSSH "${HOST}" reboot | tee -a "${LOGFILENAME}"; then
ERROR=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" | tee -a "${LOGFILENAME}"
runSSH "${HOST}" apt-get ${YESARG} autoremove | 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}"
echo "apt-get ${YESARG} purge" | tee -a "${LOGFILENAME}"
runSSH "${HOST}" apt-get ${YESARG} purge | 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 ! runSSH "${HOST}" sh -c 'yesarg="$1"; orphans=$(pacman -Qqtd 2>/dev/null || true); if [ -n "$orphans" ]; then pacman -Rns $yesarg $orphans; fi' sh "${YESARG}" | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "pacman -Sc ${YESARG}" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" pacman -Sc ${YESARG} | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
apk)
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
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 ! runSSH "${HOST}" sh -s -- "${CMDVAL}" <<'EOF' | tee -a "${LOGFILENAME}"
stack_root="$1"
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