Skip to content

Instantly share code, notes, and snippets.

@b0o
Last active February 7, 2024 07:19
Show Gist options
  • Save b0o/8d85788598f502e2c9b1c2ffc1b5e441 to your computer and use it in GitHub Desktop.
Save b0o/8d85788598f502e2c9b1c2ffc1b5e441 to your computer and use it in GitHub Desktop.
Arch Linux update script which checks for Arch news, then updates the system via Yay or Pacman, plus support for updating user-space utilities such as NPM, Yarn, RubyGems, Cargo, and more.
#!/bin/bash
#
# Copyright (c) 2018 Maddison Hellstrom (github.com/b0o)
# Released under the MIT License
#
# Arch Linux update script which checks for Arch news, then updates the system
# via Yay or Pacman, with seamless support for updating user-space utilities
# such as NPM, Yarn, RubyGems, Cargo, and more. Plus it has pretty colors :)
#
# Screencast:
# https://asciinema.org/a/191154
#
# Dependencies:
# Required:
# - moment-cli: https://www.npmjs.com/package/moment-cli
# - termcolors: https://gist.github.com/08007d77853fcea5ca669e6800844c06
# - news: https://gist.github.com/2f4cf3ad681f46570715c8f381d7155f
# - git: https://git-scm.com
# Optional:
# - upgrade_system:
# - yay: https://github.com/Jguer/yay
# - upgrade_oh_my_zsh:
# - oh-my-zsh: https://github.com/robbyrussell/oh-my-zsh
# - upgrade_npm
# - npm: https://github.com/npm/npm
# - upgrade_yarn
# - yarn: https://github.com/yarnpkg/yarn
# - upgrade_pip
# - pip3: https://pip.pypa.io/en/stable/installing/
# - upgrade_gem
# - gem: https://rubygems.org/pages/download
# - upgrade_cargo
# - rust: https://github.com/rust-lang/rust
# - upgrade_cabal
# - cabal-install: https://github.com/haskell/cabal/blob/master/cabal-install/README.md
# - upgrade_xmobar
# - xmobar: git clone https://github.com/jaor/xmobar ~/git/xmobar
# - upgrade_xmonad
# - xmonad-conf: git clone https://github.com/b0o/xmonad-conf ~/.xmonad
# bash "strict mode"
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
IFS=$'\n\t'
# shellcheck source=./termcolors
source "$(command -v termcolors)"
sig_handled=0
export current_command=""
handle_exit() {
code=$?
if [[ $sig_handled == 0 ]]; then
if [[ $code != 0 ]]; then
local cmd=""
if [[ $current_command != "" ]]; then
cmd=" '${Cya}$upgrade${RCol}' "
fi
echo -e "${Err}Command${cmd:- }${Red}failed${RCol} with code ${Yel}${code}${RCol}."
exit $code
fi
fi
}
handle_sig() {
echo "handle_sig"
local sig=${1:-}
echo -e "\\n${Ifo}Caught ${Yel}SIG${sig}${RCol}. Exiting..."
sig_handled=1
exit 1
}
trap handle_exit EXIT
trap 'handle_sig TERM' TERM
trap 'handle_sig ILL' ILL
trap 'handle_sig HUP' HUP
trap 'handle_sig INT' INT
export start_time="$(date +%s)"
function confirm() {
local confmsg=$1
local exitmsg=$2
local flip=$3
local ynmsg="y/N"
if [[ $flip == 1 ]]; then
ynmsg="Y/n"
fi
while true; do
echo -e "${confmsg:-Are you sure? [${Yel}${ynmsg}${RCol}]} "
read -rp "> " yn
case $yn in
[Yy]*) break ;;
[Nn]*) echo -e "$exitmsg" && exit 1 ;;
"") [[ $flip == 1 ]] && break || echo -e "$exitmsg" && exit 1 ;;
*) echo -e "Please answer ${On_Yel}${BIBla}[Y|y]es${RCol}" \
"or ${On_Yel}${BIBla}[N|n]o${RCol}" ;;
esac
done
}
export updatecachedir="$HOME/.cache/update"
if [[ ${XDG_CACHE_HOME:-} != "" ]]; then
updatecachedir="$XDG_CACHE_HOME/update"
fi
if [[ ! -d $updatecachedir ]]; then
mkdir -p "$updatecachedir"
fi
newscachefile="$updatecachedir/news"
lastupdate="$updatecachedir/lastupdate"
if [[ ! -s $newscachefile ]]; then
touch "$newscachefile"
fi
if [[ ! -s $lastupdate ]]; then
touch "$lastupdate"
else
elapsed=$(moment -ti "unix" "$(cat "$lastupdate")")
cal=$(moment -ci "unix" "$(cat "$lastupdate")")
echo -e "${Ifo}Last update was ${Blu}$cal${RCol}" \
"(${Pur}$elapsed${RCol})"
fi
newsout="$(news 10)"
diff=$(diff --color=always -Z "$newscachefile" <(echo "$newsout")) || :
if [[ $diff != "" ]]; then
echo -e "${Wrn}New '${UCya}archlinux.org${RCol}' news item(s) since last update:"
echo "$diff"
confirm "Continue with system update? ${On_Yel}${BIBla}[Y/n]${RCol} " \
"${Wrn}Exiting without updating..." 1
else
echo -e "${Ifo}No new news."
fi
command_exists() {
local cmd="${1:-}"
if [[ -z "$cmd" ]]; then
echo -e "${Err}No command specified."
return 1
fi
command -v "${cmd}" >/dev/null || {
echo -e "${Wrn}Candidate not found: '${Yel}${cmd}${RCol}'."
return 1
}
echo -e "${Ifo}Using '${Cya}${cmd}${RCol}'."
}
export -f command_exists
upgrade_system() {
command_exists yay && {
yay -Syu || return 1
return 0
}
command_exists pacman && {
sudo pacman -Syu || return 1
return 0
}
return 2
}
upgrade_cabal() {
command_exists cabal && {
cabal update || return 1
} || return 2
}
upgrade_cargo() {
command_exists cargo && {
cargo install-update -a || return 1
} || return 2
}
upgrade_gem() {
command_exists gem && {
gem update || return 1
} || return 2
}
upgrade_npm() {
command_exists npm && {
npm --global update || return 1
} || return 2
}
upgrade_oh_my_zsh() {
command_exists zsh && {
zsh -c 'cd $HOME; . $HOME/.zshrc; upgrade_oh_my_zsh' || return 1
} || return 2
}
upgrade_pip() {
command_exists pip && {
local outdated
outdated="$(pip list --user --outdated --format=freeze)" || return 1
if [[ ! -z "$outdated" ]]; then
echo "$outdated" |
tee "$updatecachedir/upgrade_pip_${start_time}_freeze.txt" |
grep -v '^\-e' |
cut -d = -f 1 |
xargs -n1 pip install --user --upgrade || return 1
fi
} || return 2
}
upgrade_xmonad() {
command_exists ~/.xmonad/build.sh && {
~/.xmonad/build.sh -rw || return 1
} || return 2
}
upgrade_xmobar() {
lwd="$PWD"
if [[ ! -d "$HOME/git/xmobar" ]]; then
echo -e "${Wrn}Candidate not found: '${Yel}$HOME/git/xmobar${RCol}'."
return 2
fi
echo -e "${Ifo}Using '${Cya}$HOME/git/xmobar${RCol}'."
cd "$HOME/git/xmobar"
git pull origin master
cabal install \
-fwith_threaded -fwith_utf8 -fwith_xft -fwith_iwlib --reinstall \
--force-reinstalls --enable-debug-info=1 || return 1
cd "$lwd"
}
function upgrade_yarn() {
command_exists yarn && {
yarn global upgrade-interactive --latest || return 1
} || return 2
}
upgrades=()
if [[ ${#} -gt 0 ]]; then
while test ${#} -gt 0; do
u="upgrade_${1}"
r="${1}"
shift
if [[ -n "$(type -t "${u}")" ]] && [[ "$(type -t "${u}")" == "function" ]]; then
upgrades+=("${u}")
else
echo -e "${Err}Unknown upgrade type '${Yel}${r}${RCol}'."
exit 1
fi
done
else
upgrades+=(
upgrade_system
upgrade_oh_my_zsh
upgrade_npm
upgrade_yarn
upgrade_pip
upgrade_gem
upgrade_cargo
upgrade_cabal
upgrade_xmobar
upgrade_xmonad
)
fi
i=1
skipped=0
for upgrade in "${upgrades[@]}"; do
echo -e "${Ifo}(${Cya}${i}${RCol}/${Cya}${#upgrades[@]}${RCol}) Performing '${Cya}$upgrade${RCol}'..."
export -f "${upgrade?}"
current_command="$upgrade"
cmd=$(
cat <<EOF
set -euo pipefail
IFS=$'\\n\\t'
h () {
echo -e '${Err}Child process caught ${Yel}SIGINT${RCol}. Exiting...'
exit 1
}
trap h SIGINT
${upgrade}
EOF
)
set +e
SHELL="/bin/bash" script -qec "${cmd}" "${updatecachedir}/${upgrade}_${start_time}.log"
code="$?"
set -e
case ${code} in
0) ;;
1)
exit 1
;;
2)
# TODO: Add flag -s (strict) which prevents skipping if no utilities are
# found for any commands
((skipped += 1))
echo -e "${Wrn}No suitable utilities found for" \
"'${Cya}${current_command}${RCol}'. ${Yel}Skipping...${RCol}"
continue
;;
\?)
echo -e "${Err}Unknown error occurred! (${Red}code ${code}${RCol})"
exit ${code}
;;
esac
current_command=""
echo -e "${Ifo}Command '${Cya}${upgrade}${RCol}' was ${Gre}successful${RCol}"
printf ' ---\n'
((i += 1))
done
echo -e "${Ifo}Cleaning up..."
mapfile -t old < <(find "${updatecachedir}" \
-depth -maxdepth 1 -type f -mtime +30 \
-and \( -name 'upgrade_*.log' -or -name 'upgrade_pip_*_freeze.txt' \))
if [[ ${#old[@]} -gt 0 ]]; then
echo -e "${Ifo}Removing ${Cya}${#old[@]}${RCol} stale log files."
rm "${old[@]}"
fi
end_time="$(date +%s)"
echo "$end_time" >"$lastupdate"
echo "$newsout" >"$newscachefile"
elapsed="$(((end_time - start_time)))"
skipmsg=""
comma=""
if [[ ${skipped} -gt 0 ]]; then
skipmsg="${Yel}${skipped} skipped${RCol}"
comma=", "
fi
successmsg=""
if [[ ${i} -le 1 ]]; then
echo -e "${Wrn}Nothing was updated" \
"(${skipmsg})"
exit 0
else
successmsg="${Cya}$(((i - 1))) succeeded${RCol}${comma}"
fi
echo -e "${Ifo}Successfully completed update in ${Cya}${elapsed} seconds${RCol}" \
"(${successmsg}${skipmsg})"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment