Skip to main content

is: an inspector for your environment

·1273 words·6 mins·
Go bash dotfiles
Table of Contents
❤️ It's great to see you here! I'm currently available on evenings and weekends for consulting and freelance work. Let's chat about how I can help you achieve your goals.

$ (is os name eq darwin && echo 🍏 ) ||
  (is os name eq linux && echo 🐧) ||
  echo 💣
$ 🍏

In a hurry? Jump straight to the README or install is now.

Motivation
#

Many years ago, before I switched to 100% remote work, I sat beside a young kid who had recently graduated from University. I picked up a lot of command line tricks from him, mostly by looking over his shoulder. I learned two big things from him:

  1. vim is highly configurable
  2. It’s helpful to keep your config files in a git repository

The kid had a neat way of organizing his stuff and by stuff I mean “dot files”. I copied some of his setup and, over the years, I have customized it a lot. My dot files get installed on Macs (sometimes with Apple silicon and sometimes not), Ubuntu VMs, Raspberry Pi and most recently on a Chromebook (so, Debian). I have different needs for different environments, but I want my dot files to handle as much of this as possible. Also, I’d like to keep them in public and on GitHub.

All this means that I have to do a few things to ensure everything gets installed in the right way and on the right machine.

So, you’re probably thinking that I just moved my dot files to Ansible configuration files.

I did not.

Right, but if I were sensible I would use one of the cool dot file frameworks to deploy to my various machines.

I did not.

Surely I’m not just using a bunch of bash scripts?

Yes, I am.

Now, this may not seem sensible to you, but my reasoning is:

  1. I want the files to be installable everywhere and I want the repo to basically bootstrap itself, so if bash and git are available, things should mostly just work
  2. I want my logic to run quickly, which can be hard to do with Ansible
  3. I do not want to be constrained by someone else’s way of doing things
  4. I do want to mess around with shell scripts
  5. Don’t tell me what to do

Working within these parameters I’m able to do most of the things that I need to do, but some things seem hard. For instance “do A if we’re on a Mac but do B on Ubuntu (but only certain versions) or do B on Debian, but also certain versions. At some point doing all of this in bash feels kind of tedious. I wondered if I could write a tool to give me some of these answers in a more elegant way.

I eventually settled on Go as the implementation language for the tool and kong as the CLI arg parser. After messing around with kong, I realized that I could probably abuse commands and subcommands to come up with a CLI that reads (at least a little bit) like English. The end result is: is.

Recipes
#

Let’s dive right in.

Sometimes you don’t know if a binary is available. Let’s do a git clone only if git is available.

is there git && git clone https://github.com/oalders/is.git

Likewise, let’s install bat via homebrew, but only if we’re on a Mac.

is os name eq darwin && brew install bat

We could further expand this and only install via brew if it’s available:

is os name eq darwin && is there brew && brew install bat

Do we have a perl with a version >= 5.34 in our $PATH?

is cli version perl gte 5.34 && perl -E 'say "TIMTOWDI"'

Let’s express this by checking the minor version specifically:

is cli version perl --minor gte 34

Maybe we can support a range of versions:

is cli version perl --minor in 34,36,38

Just print the codename of this OS version:

$ is known os version-codename
ventura

Just print the system architecture:

$ is known arch
amd64

Try to upgrade ubi if it’s older than 7 days.

is cli age ubi gt 7 days && ubi --self-upgrade

Am I running as the correct user?

is cli output stdout whoami eq olaf

Is today Tuesday?

is cli output stdout date like Tue

Same, but without a regex:

is cli output stdout date --arg='+%a' eq Tue

Is today within Mon-Wed?

is cli output stdout date --arg='+%a' like 'Mon|Tue|Wed'

Same, but without a regex:

is cli output stdout date --arg='+%a' in Mon,Tue,Wed

Is this README.md greater than 800 lines long?

is cli output stdout 'bash -c' -a 'cat README.md|wc -l' gte 800

Are we on the main branch in this git repository?

is cli output stdout git --arg=branch --arg='--show-current' eq main

Check if git supports branch --show-current before running the previous command:

is cli version git gte 2.22 &&
  is cli output stdout git --arg=branch --arg='--show-current' eq main

Use a regex to match an SSH version. Note that ssh -V prints to STDERR.

is cli output stderr ssh --arg='-V' like 9.0p1

The above is essentially the same as the much shorter:

is cli version ssh like 9.0p1

Get the version of a CLI and do something with it.

$ version=$(is known cli version go) && echo $version
1.20.6

This is somewhat easier than parsing a version out of:

$ go version
go version go1.20.6 linux/amd64

In a script, exit early if it’s a Raspberry Pi.

if is os id eq raspbian; then
    exit 0
fi

At the command line, update the go binaries that vim-go needs:

is there go && nvim +:GoUpdateBinaries +qa

Same, but ensure nvim is installed:

is there go &&
  is there nvim &&
  nvim +:GoUpdateBinaries +qa

Same, but install plugins before :GoUpdateBinaries in order to ensure that vim-go has been installed:

is there go &&
  is there nvim &&
  nvim +:PlugInstall +:GoUpdateBinaries +qa

Same, but ensure we have at least version 1.20 of go:

is there go &&
  is cli version go gte 1.20 &&
  is there nvim &&
  nvim +:PlugInstall +:GoUpdateBinaries +qa

Now a more complex example. I want to install the nightly wezterm build. On macOS, I tend to use homebrew, but on Linux I’ve chosen to install the .deb files. To complicate the Linux example, I don’t want to re-install the nightly wezterm if I (for whatever reason) re-run my dot files installer. So, if wezterm has been installed within the last 18 hours, we won’t try installing it again. (See if is cli age wezterm lt 18 hours.)

The download URLs look like:

https://github.com/wez/wezterm/releases/download/nightly/wezterm-nightly.Ubuntu20.04.deb

and

https://github.com/wez/wezterm/releases/download/nightly/wezterm-nightly.Ubuntu22.04.deb

 1#!/usr/bin/env bash
 2
 3set -e -u -o pipefail
 4
 5install_for_linux() (
 6    sudo apt install -y libxcb-image0 libxkbcommon-x11-0
 7    version=$(is known os version)
 8    file=wezterm-nightly.${1}${version}.deb
 9
10    cd /tmp || exit 1
11    url=https://github.com/wez/wezterm/releases/download/nightly/$file
12    curl --location --output "$file" "$url"
13    sudo dpkg -i "$file"
14)
15
16if is os name eq darwin; then
17    if is there wezterm; then
18        brew upgrade homebrew/cask/wezterm
19    else
20        brew tap wez/wezterm
21        brew install --cask wezterm --no-quarantine
22    fi
23elif is os name eq linux; then
24    if ! is user sudoer; then
25        exit 0
26    fi
27    is there wezterm && is cli age wezterm lt 18 hours && exit
28    if is os id eq ubuntu && is os version in 20.04,22.04; then
29        install_for_linux Ubuntu
30    elif is os id eq debian && is os version --major in 10,11; then
31        install_for_linux Debian
32    fi
33fi

Hopefully this brief tutorial gives you an idea of what I had in mind with is. I wrote it entirely for my own needs. I’ve been living with it for a couple of months. For my purposes it has been just fine. Maybe you’ll find it useful as well.


Related

Autocorrecting my Git Commands
·598 words·3 mins
git bash autocorrect
About Me
·167 words·1 min
Closing Duplicate Tabs With AppleScript
·745 words·4 mins
AppleScript Hammerspoon