Compare commits
20 Commits
09d4815a88
..
ai
| Author | SHA1 | Date | |
|---|---|---|---|
| 40670bd1f7 | |||
| 8229b06cc1 | |||
| 5728e8769a | |||
| 0360323c10 | |||
| faeac57bf4 | |||
| 8b566f0793 | |||
| 76101e77c9 | |||
| c138d9201e | |||
| ba5a8c9397 | |||
| a08efd54c4 | |||
| 3e78f8afe6 | |||
| afea447887 | |||
| e2b3a0a88d | |||
| 3fe7959850 | |||
| 46f42c8893 | |||
| f9af0f4823 | |||
| a25caf6f3d | |||
| f3c649341a | |||
| 2ed34b97be | |||
| 9de65f9aa5 |
@@ -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
|
||||
@@ -1,32 +1,118 @@
|
||||
# netupgrade
|
||||
|
||||
Servers full upgrade script
|
||||
Interactive CLI tool to run upgrade and maintenance actions on multiple remote hosts over SSH.
|
||||
|
||||
## 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 (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
|
||||
|
||||
### Bin as root
|
||||
### Install the executable
|
||||
|
||||
``` bash
|
||||
cp bin/netupgrade to /usr/local/bin
|
||||
```bash README.md
|
||||
cp bin/netupgrade /usr/local/bin/netupgrade
|
||||
chmod +x /usr/local/bin/netupgrade
|
||||
```
|
||||
|
||||
### Config as user
|
||||
### Create the config directory
|
||||
|
||||
``` bash
|
||||
mkdir -p ~/.config/netuprade
|
||||
touch ~/.config/netuprade/index.cfg
|
||||
```bash README.md
|
||||
mkdir -p ~/.config/netupgrade
|
||||
touch ~/.config/netupgrade/index.cfg
|
||||
```
|
||||
|
||||
### 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'
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
+205
-79
@@ -1,12 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
showHelp() {
|
||||
echo "netupgrade [-f] [-y] [configfilename]"
|
||||
echo "netupgrade [--help] [-f] [-y] [configfilename]"
|
||||
echo ""
|
||||
echo " -f : Select all nodes"
|
||||
echo " -y : No confirmation"
|
||||
echo " -b : Breack on error"
|
||||
echo " configfilename : a cfg filename"
|
||||
echo " --help Show this help message"
|
||||
echo " -f Preselect all nodes in the checklist"
|
||||
echo " -y No confirmation for supported package managers"
|
||||
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(){
|
||||
@@ -23,25 +58,76 @@ loadConfig(){
|
||||
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(){
|
||||
local -a OPTIONS=()
|
||||
local -a SELECTED_ITEMS=()
|
||||
local -i INDEX=1
|
||||
for NODE in "${NODES[@]}"; do
|
||||
# shellcheck disable=SC2206
|
||||
local FIELDS=(${NODE//;/ })
|
||||
local DESKSKIP=0
|
||||
local -i I=0
|
||||
local -a FIELDS=()
|
||||
local DESC=""
|
||||
for FIELD in "${FIELDS[@]}"; do
|
||||
if [ ${DESKSKIP} -gt 1 ]; then
|
||||
if [ "${DESC}" == "" ]; then
|
||||
DESC="${FIELD/:*/}"
|
||||
local FIELD=""
|
||||
local SEL=""
|
||||
local DEFAULT_STATE="OFF"
|
||||
|
||||
if [ "${FULL}" -eq 1 ]; then
|
||||
DEFAULT_STATE="ON"
|
||||
fi
|
||||
|
||||
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/:*/}"
|
||||
DESC="${DESC}|${FIELD%%:*}"
|
||||
fi
|
||||
fi
|
||||
DESKSKIP=$(( DESKSKIP + 1))
|
||||
done
|
||||
OPTIONS+=("${INDEX}:${FIELDS[0]}" "${FIELDS[1]} [${DESC}]" "${FULL}")
|
||||
OPTIONS+=("${INDEX}:${FIELDS[0]}" "${FIELDS[1]} [${DESC}]" "${DEFAULT_STATE}")
|
||||
INDEX+=1
|
||||
done
|
||||
if ! SEL=$(whiptail --title "NetUpgrade" --checklist "" 0 0 0 \
|
||||
@@ -49,7 +135,12 @@ selectNodes(){
|
||||
3>&1 1>&2 2>&3); then
|
||||
exit 0
|
||||
fi
|
||||
if [ ${#SEL} == 0 ]; then
|
||||
if [ -z "${SEL}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
parseSelection "${SEL}" SELECTED_ITEMS
|
||||
if [ ${#SELECTED_ITEMS[@]} -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -59,17 +150,17 @@ selectNodes(){
|
||||
touch "${LOGFILENAME}"
|
||||
local RESULT="\n"
|
||||
|
||||
for ITM in ${SEL}; do
|
||||
for ITM in "${SELECTED_ITEMS[@]}"; do
|
||||
INDEX=1
|
||||
for NODE in "${NODES[@]}"; do
|
||||
# shellcheck disable=SC2206
|
||||
local FIELDS=(${NODE//;/ })
|
||||
if [ "${ITM}" = "\"${INDEX}:${FIELDS[0]}\"" ]; then
|
||||
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
|
||||
@@ -77,10 +168,12 @@ selectNodes(){
|
||||
done
|
||||
done
|
||||
|
||||
sed -i "1s/^/${RESULT//\//\\\/}\n\n\n\n/" "${LOGFILENAME}"
|
||||
sed -i "1s/^/---------\n\n/" "${LOGFILENAME}"
|
||||
sed -i "1s/^/Results :\n/" "${LOGFILENAME}"
|
||||
nano "${LOGFILENAME}"
|
||||
prependLogSummary "${RESULT}"
|
||||
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 :"
|
||||
@@ -88,136 +181,165 @@ 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}"
|
||||
echo "reboot" | tee -a "${LOGFILENAME}"
|
||||
runSSH "${HOST}" reboot | tee -a "${LOGFILENAME}"
|
||||
;;
|
||||
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}"
|
||||
echo "apt-get ${YESARG} autoremove --purge" | tee -a "${LOGFILENAME}"
|
||||
runSSH "${HOST}" apt-get ${YESARG} autoremove --purge | 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}"
|
||||
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} clean | 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
|
||||
;;
|
||||
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
|
||||
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
|
||||
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 [ -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
|
||||
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
|
||||
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 ! ssh "${SSH_USER}@${HOST}" "STACK_ROOT=$(printf '%q' "${CMDVAL}") bash -s" <<'EOF' | tee -a "${LOGFILENAME}"
|
||||
stack_root="${STACK_ROOT}"
|
||||
|
||||
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
|
||||
}
|
||||
@@ -227,6 +349,8 @@ 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
|
||||
@@ -238,6 +362,8 @@ while [[ ${#} -gt 0 ]]; do
|
||||
esac
|
||||
done
|
||||
|
||||
checkDependencies
|
||||
resolveLogViewer
|
||||
loadConfig
|
||||
selectNodes
|
||||
|
||||
|
||||
@@ -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.103;alpine-01;apk;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.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")
|
||||
|
||||
Reference in New Issue
Block a user