1
0

Compare commits

..

20 Commits

Author SHA1 Message Date
matmoul 40670bd1f7 chore: replace legacy repo state docs with project memory 2026-05-02 01:21:37 +02:00
matmoul 8229b06cc1 refactor: stop marking reboot command failures in netupgrade 2026-05-02 01:12:12 +02:00
matmoul 5728e8769a fix: simplify reboot to direct SSH invocation 2026-05-01 22:00:32 +02:00
matmoul 0360323c10 docs: update project state overview 2026-04-26 03:09:30 +02:00
matmoul faeac57bf4 feat: add dnf action support 2026-04-26 02:57:22 +02:00
matmoul 8b566f0793 docs: reorganize TODO priorities 2026-04-26 02:43:51 +02:00
matmoul 76101e77c9 chore: rename repository state file to STATE.md 2026-04-26 02:35:38 +02:00
matmoul c138d9201e fix: detach reboot command to avoid false SSH failures 2026-04-26 02:08:34 +02:00
matmoul ba5a8c9397 docs: update dependency and sample config notes 2026-04-26 01:36:02 +02:00
matmoul a08efd54c4 fix: stop passing -y to apk upgrade
The Alpine apk upgrade command does not accept -y, so the flag is no longer appended there. The CLI -y option remains available for other package managers.
2026-04-26 01:17:50 +02:00
matmoul 3e78f8afe6 fix: simplify pacman orphan cleanup command construction 2026-04-26 01:08:09 +02:00
matmoul afea447887 docs: update roadmap with review findings and next priorities 2026-04-26 00:45:03 +02:00
matmoul e2b3a0a88d fix: correct package cleanup commands in netupgrade
Use apt-get autoremove --purge instead of a separate empty purge step, and pass the CLI yes flag through to apk upgrade so the logged commands match actual behavior.
2026-04-26 00:40:24 +02:00
matmoul 3fe7959850 fix: avoid sed interpolation when prepending log summary
Use a temporary file to write the summary header and existing log content, then replace the original log atomically. This removes the dependency on sed for summary insertion and avoids unsafe string interpolation.
2026-04-26 00:36:01 +02:00
matmoul 46f42c8893 fix: harden checklist selection parsing and clarify -f help text 2026-04-26 00:33:27 +02:00
matmoul f9af0f4823 docs: update roadmap with next hardening priorities 2026-04-26 00:26:09 +02:00
matmoul a25caf6f3d feat: make SSH user configurable and harden remote actions 2026-04-26 00:19:39 +02:00
matmoul f3c649341a fix: make log viewer optional and document runtime dependencies 2026-04-26 00:08:02 +02:00
matmoul 2ed34b97be docs: expand netupgrade usage and configuration docs
Update the README with installation, requirements, supported actions, config format, and usage details. Align the CLI help text with current behavior and add startup checks for required runtime dependencies.
2026-04-25 23:59:28 +02:00
matmoul 9de65f9aa5 chore: add repository guidance and state context files 2026-04-25 23:51:29 +02:00
4 changed files with 337 additions and 94 deletions
+30
View File
@@ -0,0 +1,30 @@
# Project memory
- Repo: `netupgrade`
- Language: Bash
- Entry point: `bin/netupgrade`
- Purpose: interactive SSH-based maintenance/upgrade CLI
## Rules
- Keep it lightweight and shell-based
- Preserve backward compatibility when possible
- Treat config files as trusted shell input
- Be careful with SSH quoting and remote command construction
- Prefer small, reviewable changes
- Keep docs aligned with runtime behavior
- Avoid introducing `set -euo pipefail` without validating failure semantics
## Current behavior
- Loads configs from `~/.config/netupgrade/*.cfg`
- Uses `NODES` entries: `host;display-name;action1;action2;...`
- Runs selected actions sequentially over SSH
- Defaults to `root@host`, or `SSH_USER@host`
- Logs to `~/netupgrade.log`
- Opens the log with `$EDITOR`, then `nano`, `vi`, or `less`
## Sensitive areas
- SSH quoting
- `cmd:<...>` execution
- `docker-stacks:<...>` handling
- package-manager cleanup behavior
- error propagation
+98 -12
View File
@@ -1,32 +1,118 @@
# netupgrade # netupgrade
Servers full upgrade script Interactive CLI tool to run upgrade and maintenance actions on multiple remote hosts over SSH.
## Where to use ## Where to use
- On a dedicated server (bastion, ...) - On a dedicated server (bastion, jump host, ...)
- On your computer with an alias to your dedicated server - On your computer with an alias to your dedicated server
- On your computer (not recommended) - On your computer directly (not recommended)
## Features
- Select one or more hosts from an interactive checklist
- Run predefined actions on each selected host
- Write execution logs to `~/netupgrade.log`
Supported actions:
- `apt`
- `yum`
- `dnf`
- `pkg`
- `pacman`
- `apk`
- `reboot`
- `cmd:<remote command>`
- `docker-stacks:<directory>`
## Requirements
Required locally:
- `bash`
- `ssh`
- `whiptail`
- core utilities such as `cat`, `tee`, `rm`, `touch`, and `mv`
Optional log viewer:
- `$EDITOR` if it points to an installed command
- otherwise one of: `nano`, `vi`, `less`
Remote hosts must also provide the commands needed by the configured actions, such as:
`apt-get`, `yum`, `dnf`, `pkg`, `pacman`, `apk`, `docker`, `docker compose`, or `reboot`.
## Install ## Install
### Bin as root ### Install the executable
``` bash ```bash README.md
cp bin/netupgrade to /usr/local/bin cp bin/netupgrade /usr/local/bin/netupgrade
chmod +x /usr/local/bin/netupgrade
``` ```
### Config as user ### Create the config directory
``` bash ```bash README.md
mkdir -p ~/.config/netuprade mkdir -p ~/.config/netupgrade
touch ~/.config/netuprade/index.cfg touch ~/.config/netupgrade/index.cfg
``` ```
### Alias on your computer with a dedicated server ### Alias on your computer with a dedicated server
You can save it in your .bashrc You can save it in your `.bashrc`
``` bash ```bash README.md
alias netupgrade='ssh -t user@10.0.0.10 netupgrade' alias netupgrade='ssh -t user@10.0.0.10 netupgrade'
``` ```
## Configuration
The default config file is:
```text README.md
~/.config/netupgrade/index.cfg
```
The script sources this file as Bash code. It must define a `NODES` array.
Each entry uses this format:
```text README.md
host;display-name;action1;action2;...
```
Example:
```bash README.md
SSH_USER="root"
NODES=(
"192.168.1.10;web-01;apt;reboot"
"192.168.1.11;db-01;apt;cmd:systemctl restart postgresql"
"192.168.1.12;docker-01;docker-stacks:/opt/stacks"
)
```
## Usage
```bash README.md
netupgrade [--help] [-f] [-y] [configfilename]
```
Options:
- `--help`: show help
- `-f`: preselect all nodes in the interactive checklist
- `-y`: pass non-interactive confirmation flags to supported package managers
- `configfilename`: path to a config file
## Notes
- 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
- 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
- The configuration file is sourced as shell code, so only use trusted config files
+207 -81
View File
@@ -1,12 +1,47 @@
#!/bin/bash #!/bin/bash
showHelp() { showHelp() {
echo "netupgrade [-f] [-y] [configfilename]" echo "netupgrade [--help] [-f] [-y] [configfilename]"
echo "" echo ""
echo " -f : Select all nodes" echo " --help Show this help message"
echo " -y : No confirmation" echo " -f Preselect all nodes in the checklist"
echo " -b : Breack on error" echo " -y No confirmation for supported package managers"
echo " configfilename : a cfg filename" 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(){ pressAnyKey(){
@@ -23,25 +58,76 @@ loadConfig(){
fi 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(){ selectNodes(){
local -a OPTIONS=() local -a OPTIONS=()
local -a SELECTED_ITEMS=()
local -i INDEX=1 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 for NODE in "${NODES[@]}"; do
# shellcheck disable=SC2206 FIELDS=()
local FIELDS=(${NODE//;/ }) DESC=""
local DESKSKIP=0 parseNode "${NODE}" FIELDS
local DESC="" for ((I = 2; I < ${#FIELDS[@]}; ++I)); do
for FIELD in "${FIELDS[@]}"; do FIELD="${FIELDS[I]}"
if [ ${DESKSKIP} -gt 1 ]; then if [ -z "${DESC}" ]; then
if [ "${DESC}" == "" ]; then DESC="${FIELD%%:*}"
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}]" "${DEFAULT_STATE}")
INDEX+=1 INDEX+=1
done done
if ! SEL=$(whiptail --title "NetUpgrade" --checklist "" 0 0 0 \ if ! SEL=$(whiptail --title "NetUpgrade" --checklist "" 0 0 0 \
@@ -49,7 +135,12 @@ selectNodes(){
3>&1 1>&2 2>&3); then 3>&1 1>&2 2>&3); then
exit 0 exit 0
fi fi
if [ ${#SEL} == 0 ]; then if [ -z "${SEL}" ]; then
exit 0
fi
parseSelection "${SEL}" SELECTED_ITEMS
if [ ${#SELECTED_ITEMS[@]} -eq 0 ]; then
exit 0 exit 0
fi fi
@@ -59,17 +150,17 @@ selectNodes(){
touch "${LOGFILENAME}" touch "${LOGFILENAME}"
local RESULT="\n" local RESULT="\n"
for ITM in ${SEL}; do for ITM in "${SELECTED_ITEMS[@]}"; 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
@@ -77,10 +168,12 @@ selectNodes(){
done done
done done
sed -i "1s/^/${RESULT//\//\\\/}\n\n\n\n/" "${LOGFILENAME}" prependLogSummary "${RESULT}"
sed -i "1s/^/---------\n\n/" "${LOGFILENAME}" if [ -n "${LOGVIEWER}" ]; then
sed -i "1s/^/Results :\n/" "${LOGFILENAME}" "${LOGVIEWER}" "${LOGFILENAME}"
nano "${LOGFILENAME}" else
echo "Warning: no log viewer found, showing log path instead: ${LOGFILENAME}"
fi
rm -i "${LOGFILENAME}" rm -i "${LOGFILENAME}"
echo "" echo ""
echo "Results :" echo "Results :"
@@ -88,136 +181,165 @@ 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}" echo "reboot" | tee -a "${LOGFILENAME}"
runSSH "${HOST}" reboot | tee -a "${LOGFILENAME}"
;; ;;
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 --purge" | tee -a "${LOGFILENAME}"
ssh root@"${HOST}" apt-get ${YESARG} autoremove | tee -a "${LOGFILENAME}" runSSH "${HOST}" apt-get ${YESARG} autoremove --purge | 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 "apt-get ${YESARG} purge" | tee -a "${LOGFILENAME}"
ssh root@"${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
fi
;;
dnf)
if [ "${YES}" -eq 1 ]; then
YESARG="-y"
fi
echo "dnf ${YESARG} upgrade --refresh" | tee -a "${LOGFILENAME}"
if ! runSSH "${HOST}" dnf ${YESARG} upgrade --refresh | 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 [ -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 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
YESARG="-y"
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 ! ssh "${SSH_USER}@${HOST}" "STACK_ROOT=$(printf '%q' "${CMDVAL}") bash -s" <<'EOF' | tee -a "${LOGFILENAME}"
echo " docker compose pull; docker compose up -d" | tee -a "${LOGFILENAME}" stack_root="${STACK_ROOT}"
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
} }
@@ -227,6 +349,8 @@ declare -i YES=0
declare -i FULL=0 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 SSH_USER="root"
declare -a NODES=() declare -a NODES=()
while [[ ${#} -gt 0 ]]; do while [[ ${#} -gt 0 ]]; do
@@ -238,6 +362,8 @@ while [[ ${#} -gt 0 ]]; do
esac esac
done done
checkDependencies
resolveLogViewer
loadConfig loadConfig
selectNodes selectNodes
+2 -1
View File
@@ -4,6 +4,7 @@ NODES+=("10.0.0.101;debian-01;apt;reboot")
NODES+=("10.0.0.102;archlinux-01;pacman;reboot") NODES+=("10.0.0.102;archlinux-01;pacman;reboot")
NODES+=("10.0.0.103;alpine-01;apk;reboot") NODES+=("10.0.0.103;alpine-01;apk;reboot")
NODES+=("10.0.0.104;redhat-01;yum;reboot") NODES+=("10.0.0.104;redhat-01;yum;reboot")
NODES+=("10.0.0.106;rocky-01;dnf;reboot")
NODES+=("10.0.0.105;freebsd-01;pkg;reboot") NODES+=("10.0.0.105;freebsd-01;pkg;reboot")
NODES+=("10.0.0.211;docker-01;docker-stacks:/srv/stacks") NODES+=("10.0.0.211;docker-01;docker-stacks:/srv/stacks")
NODES+=("10.0.0.105;docker-01;cmd:reboot") #NODES+=("10.0.0.211;docker-01;cmd:reboot")