Configure Your New Machine with a Shell Script Cover Image

Configure Your New Machine with a Shell Script

6 Minute read

Utilise 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

  1. ANSI escape codes are a standard for various options in terminal emulators.

  2. the printf function is recommended as echo can behave differently across systems.