feat: make SSH user configurable and harden remote actions
This commit is contained in:
@@ -84,6 +84,8 @@ host;display-name;action1;action2;...
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash README.md
|
```bash README.md
|
||||||
|
SSH_USER="root"
|
||||||
|
|
||||||
NODES=(
|
NODES=(
|
||||||
"192.168.1.10;web-01;apt;reboot"
|
"192.168.1.10;web-01;apt;reboot"
|
||||||
"192.168.1.11;db-01;apt;cmd:systemctl restart postgresql"
|
"192.168.1.11;db-01;apt;cmd:systemctl restart postgresql"
|
||||||
@@ -106,7 +108,8 @@ Options:
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- SSH connections currently use `root@host`
|
- SSH connections use `root@host` by default and can be changed with `SSH_USER` in the config file
|
||||||
|
- `cmd:<remote command>` is executed through a remote shell, so shell operators such as pipes, redirections, `&&`, and `||` are supported
|
||||||
- The tool is interactive and intended for manual administration workflows
|
- The tool is interactive and intended for manual administration workflows
|
||||||
- After execution, the log file is opened with `$EDITOR` when available, otherwise with `nano`, `vi`, or `less`
|
- After execution, the log file is opened with `$EDITOR` when available, otherwise with `nano`, `vi`, or `less`
|
||||||
- If no supported log viewer is available, the script keeps running and prints the log file path
|
- If no supported log viewer is available, the script keeps running and prints the log file path
|
||||||
|
|||||||
+97
-61
@@ -58,23 +58,37 @@ loadConfig(){
|
|||||||
fi
|
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(){
|
selectNodes(){
|
||||||
local -a OPTIONS=()
|
local -a OPTIONS=()
|
||||||
local -i INDEX=1
|
local -i INDEX=1
|
||||||
for NODE in "${NODES[@]}"; do
|
local -i I=0
|
||||||
# shellcheck disable=SC2206
|
local -a FIELDS=()
|
||||||
local FIELDS=(${NODE//;/ })
|
|
||||||
local DESKSKIP=0
|
|
||||||
local DESC=""
|
local DESC=""
|
||||||
for FIELD in "${FIELDS[@]}"; do
|
local FIELD=""
|
||||||
if [ ${DESKSKIP} -gt 1 ]; then
|
|
||||||
if [ "${DESC}" == "" ]; then
|
for NODE in "${NODES[@]}"; do
|
||||||
DESC="${FIELD/:*/}"
|
FIELDS=()
|
||||||
|
DESC=""
|
||||||
|
parseNode "${NODE}" FIELDS
|
||||||
|
for ((I = 2; I < ${#FIELDS[@]}; ++I)); do
|
||||||
|
FIELD="${FIELDS[I]}"
|
||||||
|
if [ -z "${DESC}" ]; then
|
||||||
|
DESC="${FIELD%%:*}"
|
||||||
else
|
else
|
||||||
DESC="${DESC}|${FIELD/:*/}"
|
DESC="${DESC}|${FIELD%%:*}"
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
DESKSKIP=$(( DESKSKIP + 1))
|
|
||||||
done
|
done
|
||||||
OPTIONS+=("${INDEX}:${FIELDS[0]}" "${FIELDS[1]} [${DESC}]" "${FULL}")
|
OPTIONS+=("${INDEX}:${FIELDS[0]}" "${FIELDS[1]} [${DESC}]" "${FULL}")
|
||||||
INDEX+=1
|
INDEX+=1
|
||||||
@@ -97,14 +111,14 @@ selectNodes(){
|
|||||||
for ITM in ${SEL}; do
|
for ITM in ${SEL}; do
|
||||||
INDEX=1
|
INDEX=1
|
||||||
for NODE in "${NODES[@]}"; do
|
for NODE in "${NODES[@]}"; do
|
||||||
# shellcheck disable=SC2206
|
FIELDS=()
|
||||||
local FIELDS=(${NODE//;/ })
|
parseNode "${NODE}" FIELDS
|
||||||
if [ "${ITM}" = "\"${INDEX}:${FIELDS[0]}\"" ]; then
|
if [ "${ITM}" = "\"${INDEX}:${FIELDS[0]}\"" ]; then
|
||||||
for ((I = 2; I < ${#FIELDS[@]}; ++I)); do
|
for ((I = 2; I < ${#FIELDS[@]}; ++I)); do
|
||||||
if runCmd "${FIELDS[0]}" "${FIELDS[1]}" "${FIELDS[${I}]}"; then
|
if runCmd "${FIELDS[0]}" "${FIELDS[1]}" "${FIELDS[I]}"; then
|
||||||
RESULT+="Ok: ${FIELDS[1]} @ ${FIELDS[0]} : ${FIELDS[${I}]}\n"
|
RESULT+="Ok: ${FIELDS[1]} @ ${FIELDS[0]} : ${FIELDS[I]}\n"
|
||||||
else
|
else
|
||||||
RESULT+="Error: ${FIELDS[1]} @ ${FIELDS[0]} : ${FIELDS[${I}]}\n"
|
RESULT+="Error: ${FIELDS[1]} @ ${FIELDS[0]} : ${FIELDS[I]}\n"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@@ -127,136 +141,157 @@ selectNodes(){
|
|||||||
echo -e "${RESULT}"
|
echo -e "${RESULT}"
|
||||||
}
|
}
|
||||||
|
|
||||||
runCmd() { #$1=host $2=name #3=cmd
|
runCmd() { # $1=host $2=name $3=cmd
|
||||||
local -r HOST=${1}
|
local -r HOST="${1}"
|
||||||
local -r NAME=${2}
|
local -r NAME="${2}"
|
||||||
local -r CMD=${3//:*}
|
local -r ACTION="${3}"
|
||||||
local -r CMDVAL=${3//*:}
|
local -r CMD="${ACTION%%:*}"
|
||||||
|
local -r CMDVAL="${ACTION#*:}"
|
||||||
local -i ERROR=0
|
local -i ERROR=0
|
||||||
echo "${NAME} @ ${HOST} : ${CMD}" | tee -a "${LOGFILENAME}"
|
local TITLELENGTH=0
|
||||||
local TITLELENGTH=$((${#NAME} + ${#HOST} + ${#CMD} + 6))
|
|
||||||
local SUBTITLE="-----------------------------------------------------------------------------"
|
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}"
|
date +'%Y-%m-%d %H:%M:%S %A' | tee -a "${LOGFILENAME}"
|
||||||
echo "" | tee -a "${LOGFILENAME}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
local YESARG=""
|
|
||||||
case ${CMD} in
|
case ${CMD} in
|
||||||
reboot)
|
reboot)
|
||||||
ssh root@"${HOST}" reboot | tee -a "${LOGFILENAME}"
|
if ! runSSH "${HOST}" reboot | tee -a "${LOGFILENAME}"; then
|
||||||
|
ERROR=1
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
apt)
|
apt)
|
||||||
if [ ${YES} == 1 ]; then
|
if [ "${YES}" -eq 1 ]; then
|
||||||
YESARG="-y"
|
YESARG="-y"
|
||||||
fi
|
fi
|
||||||
echo "apt-get ${YESARG} update" | tee -a "${LOGFILENAME}"
|
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
|
ERROR=1
|
||||||
else
|
else
|
||||||
echo "" | tee -a "${LOGFILENAME}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
echo "apt-get ${YESARG} dist-upgrade" | 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
|
ERROR=1
|
||||||
fi
|
fi
|
||||||
echo "" | tee -a "${LOGFILENAME}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
echo "apt-get ${YESARG} autoremove" | 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 "" | tee -a "${LOGFILENAME}"
|
||||||
echo "apt-get ${YESARG} autoclean" | 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 "" | tee -a "${LOGFILENAME}"
|
||||||
echo "apt-get ${YESARG} clean" | 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 "" | tee -a "${LOGFILENAME}"
|
||||||
echo "apt-get ${YESARG} purge" | 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}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
yum)
|
yum)
|
||||||
if [ ${YES} == 1 ]; then
|
if [ "${YES}" -eq 1 ]; then
|
||||||
YESARG="-y"
|
YESARG="-y"
|
||||||
fi
|
fi
|
||||||
echo "yum ${YESARG} update" | tee -a "${LOGFILENAME}"
|
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
|
ERROR=1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
pkg)
|
pkg)
|
||||||
if [ ${YES} == 1 ]; then
|
if [ "${YES}" -eq 1 ]; then
|
||||||
YESARG="-y"
|
YESARG="-y"
|
||||||
fi
|
fi
|
||||||
echo "pkg ${YESARG} update" | tee -a "${LOGFILENAME}"
|
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
|
ERROR=1
|
||||||
else
|
else
|
||||||
echo "" | tee -a "${LOGFILENAME}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
echo "pkg upgrade ${YESARG}" | tee -a "${LOGFILENAME}"
|
echo "pkg upgrade ${YESARG}" | tee -a "${LOGFILENAME}"
|
||||||
if ! ssh root@"${HOST}" pkg upgrade ${YESARG} | tee -a "${LOGFILENAME}"; then
|
if ! runSSH "${HOST}" pkg upgrade ${YESARG} | tee -a "${LOGFILENAME}"; then
|
||||||
ERROR=1
|
ERROR=1
|
||||||
fi
|
fi
|
||||||
echo "" | tee -a "${LOGFILENAME}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
echo "pkg autoremove ${YESARG}" | 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 "" | tee -a "${LOGFILENAME}"
|
||||||
echo "pkg clean ${YESARG}" | 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}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
pacman)
|
pacman)
|
||||||
if [ ${YES} == 1 ]; then
|
if [ "${YES}" -eq 1 ]; then
|
||||||
YESARG="--noconfirm"
|
YESARG="--noconfirm"
|
||||||
fi
|
fi
|
||||||
echo "pacman -Sy ${YESARG} archlinux-keyring" | tee -a "${LOGFILENAME}"
|
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
|
ERROR=1
|
||||||
fi
|
fi
|
||||||
echo "pacman -Syu ${YESARG}" | tee -a "${LOGFILENAME}"
|
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
|
ERROR=1
|
||||||
fi
|
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)
|
apk)
|
||||||
if [ ${YES} == 1 ]; then
|
if [ "${YES}" -eq 1 ]; then
|
||||||
YESARG="-y"
|
YESARG="-y"
|
||||||
fi
|
fi
|
||||||
echo "apk update" | tee -a "${LOGFILENAME}"
|
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
|
ERROR=1
|
||||||
fi
|
fi
|
||||||
echo "apk upgrade" | tee -a "${LOGFILENAME}"
|
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
|
ERROR=1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
cmd)
|
cmd)
|
||||||
echo "cmd: ${CMDVAL}" | tee -a "${LOGFILENAME}"
|
echo "cmd: ${CMDVAL}" | tee -a "${LOGFILENAME}"
|
||||||
# shellcheck disable=SC2029
|
if ! runSSH "${HOST}" sh -c "${CMDVAL}" | tee -a "${LOGFILENAME}"; then
|
||||||
if ! ssh root@"${HOST}" "${CMDVAL}" | tee -a "${LOGFILENAME}"; then
|
|
||||||
ERROR=1
|
ERROR=1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
docker-stacks)
|
docker-stacks)
|
||||||
echo "docker stacks update" | tee -a "${LOGFILENAME}"
|
echo "docker stacks update in ${CMDVAL}" | tee -a "${LOGFILENAME}"
|
||||||
echo "for each" | tee -a "${LOGFILENAME}"
|
if ! runSSH "${HOST}" sh -s -- "${CMDVAL}" <<'EOF' | tee -a "${LOGFILENAME}"
|
||||||
echo " docker compose pull; docker compose up -d" | tee -a "${LOGFILENAME}"
|
stack_root="$1"
|
||||||
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
|
for dir in "$stack_root"/*; do
|
||||||
fi
|
[ -d "$dir" ] || continue
|
||||||
echo "docker image prune -a -f" | tee -a "${LOGFILENAME}"
|
(
|
||||||
if ! ssh root@"${HOST}" docker image prune -f | tee -a "${LOGFILENAME}"; then
|
cd "$dir" || exit 1
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
) || exit 1
|
||||||
|
done
|
||||||
|
|
||||||
|
docker image prune -f
|
||||||
|
EOF
|
||||||
|
then
|
||||||
ERROR=1
|
ERROR=1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
*) echo "Error: Command ${CMD} unknown" | tee -a "${LOGFILENAME}";;
|
*)
|
||||||
|
echo "Error: Command ${CMD} unknown" | tee -a "${LOGFILENAME}"
|
||||||
|
ERROR=1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
echo "" | tee -a "${LOGFILENAME}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
echo "" | tee -a "${LOGFILENAME}"
|
echo "" | tee -a "${LOGFILENAME}"
|
||||||
if [ ${ERROR} == 1 ]; then
|
if [ "${ERROR}" -eq 1 ]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -267,6 +302,7 @@ declare -i FULL=0
|
|||||||
declare CONFIGFILENAME="${HOME}/.config/netupgrade/index.cfg"
|
declare CONFIGFILENAME="${HOME}/.config/netupgrade/index.cfg"
|
||||||
declare LOGFILENAME="${HOME}/netupgrade.log"
|
declare LOGFILENAME="${HOME}/netupgrade.log"
|
||||||
declare LOGVIEWER=""
|
declare LOGVIEWER=""
|
||||||
|
declare SSH_USER="root"
|
||||||
declare -a NODES=()
|
declare -a NODES=()
|
||||||
|
|
||||||
while [[ ${#} -gt 0 ]]; do
|
while [[ ${#} -gt 0 ]]; do
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ The tool:
|
|||||||
2. Expects a `NODES` array populated with entries formatted like:
|
2. Expects a `NODES` array populated with entries formatted like:
|
||||||
`host;display-name;action1;action2;...`
|
`host;display-name;action1;action2;...`
|
||||||
3. Displays an interactive multi-select checklist using `whiptail`
|
3. Displays an interactive multi-select checklist using `whiptail`
|
||||||
4. Executes the selected actions on each selected host through `ssh root@host`
|
4. Executes the selected actions on each selected host through SSH, using `root@host` by default or `SSH_USER@host` when configured
|
||||||
5. Writes execution logs to `~/netupgrade.log`
|
5. Writes execution logs to `~/netupgrade.log`
|
||||||
6. Opens the log with `$EDITOR` when available, otherwise `nano`, `vi`, or `less`; if none is available, it prints the log path, then optionally removes the log file
|
6. Opens the log with `$EDITOR` when available, otherwise `nano`, `vi`, or `less`; if none is available, it prints the log path, then optionally removes the log file
|
||||||
|
|
||||||
@@ -57,18 +57,15 @@ Supported action types currently include:
|
|||||||
- The configuration format and supported actions are now documented in more detail
|
- The configuration format and supported actions are now documented in more detail
|
||||||
|
|
||||||
### 2. Shell robustness concerns
|
### 2. Shell robustness concerns
|
||||||
- Node parsing relies on replacing `;` with spaces and re-splitting:
|
|
||||||
this is fragile if values contain spaces or special characters
|
|
||||||
- Quoting is inconsistent across the script
|
|
||||||
- Config files are sourced directly, which is flexible but implies arbitrary code execution
|
- Config files are sourced directly, which is flexible but implies arbitrary code execution
|
||||||
- The script does not use a stricter shell safety baseline
|
- The script still has some quoting-sensitive areas and does not use a stricter shell safety baseline
|
||||||
|
- The `NODES` parsing was hardened to split on `;` with `IFS`/`read -r -a`, which now preserves spaces in action values such as `cmd:...`
|
||||||
|
|
||||||
### 3. Remote execution correctness and safety
|
### 3. Remote execution correctness and safety
|
||||||
- SSH user is hardcoded to `root`
|
- SSH execution now goes through a dedicated `runSSH` helper and the SSH user is configurable via `SSH_USER`, defaulting to `root`
|
||||||
- `cmd:<...>` intentionally allows arbitrary remote command execution, which should be treated as a powerful unsafe feature
|
- `cmd:<...>` intentionally allows arbitrary remote command execution and is now executed through a remote shell, which improves support for shell operators but remains a powerful unsafe feature
|
||||||
- Some remote command constructions are brittle
|
- The `pacman` orphan-removal command was corrected so orphan detection happens on the remote host instead of locally
|
||||||
- The `pacman` orphan-removal command appears incorrect because command substitution is evaluated locally instead of remotely
|
- The `docker-stacks` remote loop was rewritten to pass the stack root as an argument to a remote shell script, improving quoting and path handling
|
||||||
- The `docker-stacks` remote loop should be reviewed carefully for quoting and path safety
|
|
||||||
|
|
||||||
### 4. UX and dependency issues
|
### 4. UX and dependency issues
|
||||||
- Required runtime dependencies are now checked at startup (`ssh`, `whiptail`, `sed`, `tee`, `rm`, `touch`)
|
- Required runtime dependencies are now checked at startup (`ssh`, `whiptail`, `sed`, `tee`, `rm`, `touch`)
|
||||||
@@ -92,10 +89,9 @@ Supported action types currently include:
|
|||||||
## Recommended direction
|
## Recommended direction
|
||||||
|
|
||||||
### Short term
|
### Short term
|
||||||
- Correct the remote command bugs, especially for `pacman`
|
- Review the remaining quoting-sensitive areas, especially around remote shell command construction
|
||||||
- Improve parsing of node entries to avoid whitespace-splitting issues
|
- Consider making log path and editor configurable
|
||||||
- Review `docker-stacks` remote command quoting and behavior
|
- Revisit the log summary insertion method, which still relies on `sed -i` string interpolation
|
||||||
- Consider making SSH user, log path, and editor configurable
|
|
||||||
|
|
||||||
### Medium term
|
### Medium term
|
||||||
- Improve parsing of node entries to avoid whitespace-splitting issues
|
- Improve parsing of node entries to avoid whitespace-splitting issues
|
||||||
@@ -116,6 +112,11 @@ Supported action types currently include:
|
|||||||
- Log viewer selection was made more flexible: `$EDITOR` is preferred, then `nano`, `vi`, or `less`
|
- Log viewer selection was made more flexible: `$EDITOR` is preferred, then `nano`, `vi`, or `less`
|
||||||
- `nano` is no longer a strict runtime dependency
|
- `nano` is no longer a strict runtime dependency
|
||||||
- The unsupported `-b` option remains unimplemented and is no longer shown in help output
|
- The unsupported `-b` option remains unimplemented and is no longer shown in help output
|
||||||
|
- `NODES` parsing was hardened to preserve spaces in action values by splitting on `;` with `IFS` and `read -r -a`
|
||||||
|
- SSH calls were centralized through a `runSSH` helper and `SSH_USER` is now configurable, defaulting to `root`
|
||||||
|
- The `pacman` orphan cleanup now runs entirely on the remote host instead of evaluating orphan detection locally
|
||||||
|
- The `docker-stacks` action was rewritten to use a remote shell script with the stack directory passed as an argument
|
||||||
|
- Unknown actions and reboot SSH failures now propagate error status more consistently
|
||||||
|
|
||||||
## Change guidance
|
## Change guidance
|
||||||
- Preserve backward compatibility for existing config files where possible
|
- Preserve backward compatibility for existing config files where possible
|
||||||
|
|||||||
Reference in New Issue
Block a user