Whenever I want to upgrade any one of my systems, I run sd-world
.
You can find the current version of sd-world
here
in my dotfiles.
Here’s a snapshot1:
#!/usr/bin/env bash
# perform a full system upgrade
set -euo pipefail
log() {
local bold=$(tput bold) normal=$(tput sgr0)
echo "${bold}$*${normal}"
}
run_if_exists() {
if command -v "$1" >/dev/null 2>&1; then
if [[ $# -eq 1 ]]; then
log "Running $1..."
"$1"
else
shift
log "Running $*..."
"$@"
fi
elif [[ -d "$1" ]]; then
shift
log "Running $*..."
"$@"
fi
}
# usage: do_git <path/to/git/repo>
do_git() {
if [[ -d "$1" ]]; then
run_if_exists "$1" git -C "$1" pull origin master
fi
}
case "$(uname)" in
# linux
Linux)
# alpine linux
run_if_exists "apk" doas apk upgrade
# arch linux
run_if_exists "pacman" sudo pacman -Syu
# debian linux
# warning: macos has /usr/bin/apt which is a Java thing
run_if_exists "apt-get" sudo apt-get upgrade -y
run_if_exists "apt-get" sudo apt-get autoremove
;;
# macOS
Darwin)
# homebrew
run_if_exists "brew" brew upgrade
run_if_exists "brew" brew cleanup
# system update and app store
# run_if_exists "softwareupdate" softwareupdate --install --all
run_if_exists "mas" mas upgrade
;;
# windows
MINGW*)
# third-party package manager
run_if_exists "scoop" scoop update
;;
esac
# flatpaks
run_if_exists "flatpak" flatpak update
# nix
run_if_exists "nix-channel" nix-channel --update
run_if_exists "nix-env" nix-env -u
# pihole
# update pihole itself and gravity lists
run_if_exists "pihole" pihole -up
# dotfiles
do_git "$HOME/.dotfiles"
do_git "$HOME/.dotfiles_corp"
There’s a lot to unpack here.
Why is it called sd-world
?
world
is an inspiration taken from Gentoo Linux.
To upgrade a typical gentoo system, you usually run:
emerge --ask --quiet --update --changed-use --deep @world
There’s something deeply inspiring about saying it out loud: “emerge the world”. As if the whole world is at your fingertips.
sd
stands for “script directory”, it’s an inspiration taken from Ian
Henry.
Rationale: I tend to put scripts I run semi-frequently in a .bin
directory that is in my system $PATH
. However, there’s always a chance their
name could clash with a built-in one (e.g. in /usr/bin/
). In order to prevent
(or mitigate) it from happening, a prefix is added. For a long time in my life
I used the t-
prefix, merely because of my first name initial. At some point
I migrated to sd-
. That’s all, nothing fancy about it.
Why bash
?
bash
is the de-facto standard shell in most Linux distributions I care about.
And it’s also easily available in macOS and BSDs. And it’s POSIX compliant.
Therefore: availability, portability and compatibility.
Why /usr/bin/env bash
instead of /bin/bash
?
Because the env
shebang is more portable. This is more relevant when working
with BSDs. On Linux /bin/bash
should be mostly fine.
Why set -euo pipefail
?
A well-established good practice.
Why use a separare log
function?
- Old habits die hard.
- Consistent formatting.
Why run_if_exists
?
Since the script attempts to upgrade (potentially) many package managers, at
the very least we try to skip the ones that aren’t installed. For example,
there’s no need to attempt to run pacman
in a macOS system.
What else?
The rest should be quite straightforward to understand. Some design decisions:
sudo
permissions are not asked upfront, because not every system usessudo
. Notably, Alpine Linux and OpenBSD usedoas
by default. Also, laziness is OK as the script is intended for interactive use.git
is there merely for convenience. Updating my dotfiles could be done from a separate script, but that would be overkill for my simple use case.- There’s no concurrency / parallelism, and that’s intentional. I prefer output readability and system stability in this case.