Last active
February 7, 2024 07:19
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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