Configure Your New Machine with a Shell Script
6 Minute readUtilise POSIX Shell to take the hassle out of configuring your system each time you get a new computer or provision a server.
This script will walk through your standard system setup, installing the tools you need, whether that be Homebrew and various packages or Deno as a TypeScript runtime. It will also ensure that your configuration or dotfiles are set up in their correct places.
Why POSIX Shell?
It's ubiquitous among UNIX and most UNIX-like systems and requires no dependencies to run, making it perfect for setting up your system with the tools and options you need.
Writing The Script
Here marks where the actual writing of the script takes place, open up your favourite code editor and follow along. Be sure to have a terminal window open so you can test the sections as you go along.
Informing the User
Scripts on UNIX & UNIX-like systems require a
shebang line this ensures your
machine knows what binary to use when running the script, in this case it will
be /bin/sh
.
Just below the shebang line, I like to start my POSIX Shell scripts with a message function that allows me to print detailed and formatted messages to the console so that you can clearly read what's going on.
# !/bin/sh -e
message() {
ansi_pre=$(printf "\033")
[ "$1" = "info" ] && color="${ansi_pre}[34m" # Blue
[ "$1" = "success" ] && color="${ansi_pre}[1A${ansi_pre}[K${ansi_pre}[32m" # Clear ^ & Green
[ "$1" = "error" ] && color="${ansi_pre}[31m" # Red
printf "%s[%s] %s%s\n" "$color" "$1" "${ansi_pre}[0m" "$2"
}
The ANSI prefix1 is just the prefix for the
escape code required to change the colour of shell output, so in this case the code ${ansi_pre}[34m
becomes \033[34m
and changes the text to blue. The
final use of the ANSI prefix, in the printf
statement, resets the text to the
default colour.
The only one that's different is the success
message, what this does is clear
the previous line, which will be the info
message letting the user know what
job is starting. This just means there is no need to sift through an array of
alternating info/success
.
The function then prints the message specified using make sure to avoid using echo2 in POSIX sh. Below, I will show an example usage for each type of message.
message "info" "Running Section..." # [info] Running Section...
message "success" "Section Complete" # [success] Section Complete
message "error" "$?" # [error] {output of previous command}
So that's how we will inform the person running the script, likely yourself, of what's going on.
Note
I differentiate between my variables scope using capitalisation, CAPS=""
is global whereas lower=""
is local to the function or flow control it is defined in.
Installing Dependencies & Tooling
This section is where we will install all that's needed, for macOS it will likely look something like this:
install_xcli() {
if ! command -v git > /dev/null && [ "$(uname)" = "Darwin" ]; then
message "info" "Installing Xcode Developer Tools..."
xcode-select --install
sudo xcodebuild -license accept
message "success" "Xcode Developer Tools installed"
fi
}
This will install Xcode Developer Tools, allowing you to run git
. curl
is
installed as a default tool on macOS, so no need to install it.
If you are on Linux or BSD, it will probably look more like the following:
if ! command -v git > /dev/null && [ "$(uname)" = "Darwin" ]; then
message "info" "Installing Git..."
pkg_add git curl
message "info" "Git Installed"
fi
Warning
Make sure to change pkg_add
for the package manager that your system uses, such as xbps-install
for Void Linux
You can also install any other tooling you want here, for example below I
install Deno however you could install Node.js or Homebrew or run your package
manager to install a list of programs like vim
, bat
and eza
.
install_tooling() {
if ! command -v deno > /dev/null; then
message "info" "Installing Tooling..."
curl -SLs "https://deno.land/install.sh" | sh >/dev/null 2 >& 1
message "success" "Tooling installed"
fi
}
Cloning & Linking Configuration Files
This section involves running git clone
to ensure your dotfiles repository is
cloned, I personally store mine on GitHub, however you
just need to alter the URL to match your repository.
clone_configuration() {
if [ -d "$HOME/.config" ]; then
message "info" "$HOME/.config already exists"
else
message "info" "Cloning configuration..."
git clone -q "https://github.com/maclong9/dotfiles" "$HOME/.config"
message "success" "Configuration cloned"
fi
}
Warning
If you are running a Desktop Environment on Linux/BSD such as xfce you may find that it stores a lot of files in .config
already, this means the script will fail to clone the repository. In this case I would replace .config
with a different location, .dots
maybe, make sure to also change the location in the link_configuration
function below.
This next function will symbolically link the files in your configuration folder
to their respective locations in the $HOME
directory, this means you can keep
your configuration files source controlled easily.
link_configuration() {
message "info" "Linking configuration files..."
for file in "gitconfig" "gitignore" "vimrc" "zshrc"; do
ln -s "$HOME/.config/$file" "$HOME/.$file"
done
message "success" "Configuration linked"
}
Tying It All Together
Finally, you just need to call the functions previously specified, I prefer to
wrap mine in a main
function which keeps things tidy and allows for more
complex operations should I ever require them.
main() {
message "info" "System Initialising"
install_xcli
install_tooling
clone_configuration
link_configuration
message "success" "System configuration complete, enjoy."
}
main
You can then run the script directly from the git repository using curl, just make sure to replace my username and repository name with yours in the script below, and you're good to go:
curl https://raw.githubusercontent.com/maclong9/dotfiles/main/setup | sh
For inspiration and more context on what the script is doing, you can view my dotfiles here.
Footnotes
-
ANSI escape codes are a standard for various options in terminal emulators. ↩
-
the
printf
function is recommended asecho
can behave differently across systems. ↩