1
0

feat: make SSH user configurable and harden remote actions

This commit is contained in:
2026-04-26 00:19:39 +02:00
parent f3c649341a
commit a25caf6f3d
3 changed files with 118 additions and 78 deletions
+99 -63
View File
@@ -58,23 +58,37 @@ loadConfig(){
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
# shellcheck disable=SC2206
local FIELDS=(${NODE//;/ })
local DESKSKIP=0
local DESC=""
for FIELD in "${FIELDS[@]}"; do
if [ ${DESKSKIP} -gt 1 ]; then
if [ "${DESC}" == "" ]; then
DESC="${FIELD/:*/}"
else
DESC="${DESC}|${FIELD/:*/}"
fi
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
DESKSKIP=$(( DESKSKIP + 1))
done
OPTIONS+=("${INDEX}:${FIELDS[0]}" "${FIELDS[1]} [${DESC}]" "${FULL}")
INDEX+=1
@@ -97,14 +111,14 @@ selectNodes(){
for ITM in ${SEL}; do
INDEX=1
for NODE in "${NODES[@]}"; do
# shellcheck disable=SC2206
local FIELDS=(${NODE//;/ })
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"
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"
RESULT+="Error: ${FIELDS[1]} @ ${FIELDS[0]} : ${FIELDS[I]}\n"
fi
done
fi
@@ -127,136 +141,157 @@ selectNodes(){
echo -e "${RESULT}"
}
runCmd() { #$1=host $2=name #3=cmd
local -r HOST=${1}
local -r NAME=${2}
local -r CMD=${3//:*}
local -r CMDVAL=${3//*:}
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
echo "${NAME} @ ${HOST} : ${CMD}" | tee -a "${LOGFILENAME}"
local TITLELENGTH=$((${#NAME} + ${#HOST} + ${#CMD} + 6))
local TITLELENGTH=0
local SUBTITLE="-----------------------------------------------------------------------------"
echo ${SUBTITLE:0:${TITLELENGTH}} | tee -a "${LOGFILENAME}"
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
local YESARG=""
case ${CMD} in
reboot)
ssh root@"${HOST}" reboot | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" reboot | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
apt)
if [ ${YES} == 1 ]; then
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
echo "apt-get ${YESARG} update" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" apt-get ${YESARG} update | tee -a "${LOGFILENAME}"; then
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 ! ssh root@"${HOST}" apt-get ${YESARG} dist-upgrade | tee -a "${LOGFILENAME}"; then
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}"
ssh root@"${HOST}" 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}"
ssh root@"${HOST}" 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}"
ssh root@"${HOST}" 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}"
ssh root@"${HOST}" apt-get ${YESARG} purge | tee -a "${LOGFILENAME}"
runSSH "${HOST}" apt-get ${YESARG} purge | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
fi
;;
yum)
if [ ${YES} == 1 ]; then
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
echo "yum ${YESARG} update" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" yum ${YESARG} update | tee -a "${LOGFILENAME}"; then
if ! runSSH "${HOST}" yum ${YESARG} update | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
pkg)
if [ ${YES} == 1 ]; then
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
echo "pkg ${YESARG} update" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" pkg ${YESARG} update | tee -a "${LOGFILENAME}"; then
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 ! ssh root@"${HOST}" pkg upgrade ${YESARG} | tee -a "${LOGFILENAME}"; then
ERROR=1
if ! runSSH "${HOST}" pkg upgrade ${YESARG} | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "" | tee -a "${LOGFILENAME}"
echo "pkg autoremove ${YESARG}" | tee -a "${LOGFILENAME}"
ssh root@"${HOST}" 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}"
ssh root@"${HOST}" pkg clean ${YESARG} | tee -a "${LOGFILENAME}"
runSSH "${HOST}" pkg clean ${YESARG} | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
fi
;;
pacman)
if [ ${YES} == 1 ]; then
if [ "${YES}" -eq 1 ]; then
YESARG="--noconfirm"
fi
echo "pacman -Sy ${YESARG} archlinux-keyring" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" pacman -Sy ${YESARG} archlinux-keyring | tee -a "${LOGFILENAME}"; then
if ! runSSH "${HOST}" pacman -Sy ${YESARG} archlinux-keyring | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "pacman -Syu ${YESARG}" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" pacman -Syu ${YESARG} | tee -a "${LOGFILENAME}"; then
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
# shellcheck disable=SC2046
ssh root@"${HOST}" pacman -Rns $(pacman -Qqtd) ${YESARG} | tee -a "${LOGFILENAME}"
ssh root@"${HOST}" pacman -Sc ${YESARG} | tee -a "${LOGFILENAME}"
;;
apk)
if [ ${YES} == 1 ]; then
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
echo "apk update" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" apk update | tee -a "${LOGFILENAME}"; then
if ! runSSH "${HOST}" apk update | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "apk upgrade" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" apk upgrade | tee -a "${LOGFILENAME}"; then
if ! runSSH "${HOST}" apk upgrade | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
cmd)
echo "cmd: ${CMDVAL}" | tee -a "${LOGFILENAME}"
# shellcheck disable=SC2029
if ! ssh root@"${HOST}" "${CMDVAL}" | tee -a "${LOGFILENAME}"; then
if ! runSSH "${HOST}" sh -c "${CMDVAL}" | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
;;
docker-stacks)
echo "docker stacks update" | tee -a "${LOGFILENAME}"
echo "for each" | tee -a "${LOGFILENAME}"
echo " docker compose pull; docker compose up -d" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" 'for dir in '"${CMDVAL}"'/*; do (cd "${dir}"; docker compose pull; docker compose up -d); done; docker image prune -f' | tee -a "${LOGFILENAME}"; then
ERROR=1
fi
echo "docker image prune -a -f" | tee -a "${LOGFILENAME}"
if ! ssh root@"${HOST}" docker image prune -f | tee -a "${LOGFILENAME}"; then
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}";;
*)
echo "Error: Command ${CMD} unknown" | tee -a "${LOGFILENAME}"
ERROR=1
;;
esac
echo "" | tee -a "${LOGFILENAME}"
echo "" | tee -a "${LOGFILENAME}"
if [ ${ERROR} == 1 ]; then
if [ "${ERROR}" -eq 1 ]; then
return 1
fi
}
@@ -267,6 +302,7 @@ 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