322 lines
8.1 KiB
Bash
Executable File
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
|
|
|