Three years ago MacBooks were in a pretty bad spot for me and I switched to Ubuntu, later Pop!_OS. It was a fun ride. While coding I felt very productive because the OS is so low in distractions and just feels incredibly responsive. Installing the world via apt beats brew 10x, and native Docker on Linux is so much faster it isn't even funny. We had to abandon Docker because we had folks with macOS on the team. But, other tasks (email, conference calling, scanning, word, upgrading without breakage) came with more friction, and those tend to fill up ever larger shares of my day.

With MacBooks in a better place, I'm back on a Mac. That is, I'll keep Linux close and I'm very grateful to have options now should Apple pull more shenanigans (or we don't manage to port our stack to ARM before second hand Intel macs run out (following this closely too)), but macOS will be my main driver again for some time.

Things to like after my first few days again:

  • easy access to messages, reminders, notes, photos from my phone
  • trackpad support is on another level and this directly translates to productivity for me
  • copy-paste working intuitively and reliably out of the box
  • no more "sorry audio device is gone rebooting i'm on linux" when videoconferencing
  • some apps are more accessible, or more polished (like Word, or GitHub Desktop)
  • unlocking and paying with fingerprint scanner (touchid) works very well, same for getting past sudo auth. And when the lid is closed, an my apple watch will unlock the machine when i'm close
  • i did try gnome-sushi but it's no match for preview, and allows me to plow through administration much more effortlessly. copying from previewed pdfs works out of the box, with sushi i had to first open the file for example
  • searching in finder works much better, e.g. it also searches for a pdf's contents by default, which is great for me

From experience I've learned that if you can, best set up an OS from scratch and only then apply any hacks and apps that you still find relevant, vs dragging a long tail of experiments into every new release via cloning/upgrading from old machines. It's good to let apps & hacks deserve their own place again by starting fresh some times.

So I reserved a day and documented the steps to make macOS suitable for my use again. This will be highly subjective, but who knows, maybe you can draw some inspiration from it, or spot something I'm clearly doing wrong and I learn a new trick or two. And if not, I at least have repeatable steps for a Mac after this or some reinstall after I screw up.

Without further ado..

Update and Configure macOS:

First let's

  • Upgrade to the latest OS version available via System Preferences » Software Update
  • Set hostname System Preferences » Sharing » Local hostname
  • Set up Text Replacements for often use emoji sequences System Preferences » Text Replacements » hhhh: ✨✨💖✨ (cool: adding this on macOS immediately made this available on my iPhone)
  • Set up Hot corners. Weirdly enough you can find this under System Preferences » Desktop » Screen Saver » Hot Corners... . I bind Upper Left to Mission Control, Upper Right to Desktop. Note btw that the Bottom Right can create Notes since macOS 12 Monterey
  • Drag the Desktop folder into the Dock and set it up as a Fan, sort by Last Modified for easy access. For Downloads this is already the case.
  • Unlock your with Apple Watch: System Preferences » Click Security & Privacy » General » Use Apple Watch to unlock apps and your Mac. If you have more than one Apple Watch, select the watches you want to use to unlock your apps and Mac.
  • Exclude some garbage locations from Spotlight. System Preferences » Spotlight » Privacy » Add. Consider excluding the entire ~/code directory to get rid of node_modules. Code is not typically found via Spotlight anyway. Accounting/automation is another good candidate.
  • Finder:
    • Finder »
      • Preferences »
      • General »
        • New Finder windows show: $USER
      • Advanced »
        • Show all filename extensions
        • Remove items from the Trash after 30 days
        • Keep folders on top: In windows when sorting by name
        • Keep folders on top: On Desktop
        • When performing a search: Search the Current Folder
      • View »
        • Show Path Bar
        • Show Status Bar

Install Apps from the AppStore

From the AppStore let's install:

  • 1Password
  • Sequel Ace (this replaces the now unmaintained Sequel Pro)
  • Slack
  • Pixelmator
  • Reeder
  • Word (I've tried to avoid but concluded that a .docx this is a lawyer's self-contained emailable git repo and really the only way I can collaborate with them)

Open Photos. Set it up to: Save Originals on Mac (this may be a lot of data. What makes it worth it for me is that I also backup this drive and then I can trust my life's photos won't be wiped out if there ever is some iCloud malfunction)

Install Apps from the web

  • Install Rectangle Pro (so you drag windows by moving the pointer anywhere in them with Option, and resize them with Option + Shift). Requires a $10 license. Go to Settings » Sync configuration over iCloud
  • Install Brave. Set it up to: Sync Chain from mobile phone, disable wallet, ads, etc
  • Install Dropbox. Set it up to keep files off-line, but add Selective Sync just on the important folders (like Config).
  • Install iTerm2. Set it up to load profile from ~/Dropbox/Configs/iTerm2/~ <-- I tried regular Terminal as a replacement but I couldn't configure it to open source files at the correct line by clicking on them. Saves me a lot of time interfacing with tsc and eslint so staying on Iterm2 for now, even if it's a bit slower (and an extra install)
  • Install WhatsApp
  • Install Signal
  • Install Spotify
  • Install VS Code, enable sync, instruct to install code in PATH
  • Install VirtualBox
  • Install GitHub Desktop
  • Install Zoom
  • Install Cleanshot
  • Install Fujitsu ScanSnap Home for the iX500 and ABBY FineReader for ScanSnap (you can enable "Check for available software as well" in SnanSnap Online Update settings and it will propose to install). Set it up to "Scan to Abby". To pair the scanner you must first use a USB cable, then wifi later. Signing in with a ScanSnap Cloud account in ScanSnap Home -> Settings -> Account, will enable the license.

Install Apps from Homebrew

In iTerm2:

# Switch prompt to Bash
chsh -s /bin/bash

# Install XCode Command Line Tools
xcode-select --install

# Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Install CLI software
brew install \
  awscli \
  bash-completion@1 \
  bat \
  corepack \
  coreutils \
  curl \
  diffutils \
  exiftool \
  gh \
  git \
  git-lfs \
  google-cloud-sdk \
  gpg \
  html2text \
  htop \
  imagemagick \
  ipcalc \
  jq \
  mc \
  mosh \
  netcat \
  nodejs \
  pinentry-mac \
  poppler \
  shellcheck \
  starship \
  telnet \
  tmux \
  unzip \
  vagrant \
  watch \
  wget \
  yarn-completion \
;

# Powerline fonts for Starship prompt
(
  cd "${TMPDIR}" \
    && git clone https://github.com/powerline/fonts.git --depth=1 \
    && cd fonts \
    && ./install.sh \
    && cd .. \
    && rm -rf fonts \
    ;
)

Setup CLI & dev tools

In iTerm2:

# Save any custom script here
mkdir ~/bin

# You may need to occasionally rerun this, but caching 'gh' completions like this saves
# noticeable latency when opening a new prompt/tab:
gh completion -s bash > ~/.bash_completions_gh

# Avoid VirtualBox Valid ranges error
sudo mkdir -p /etc/vbox/
sudo tee "/etc/vbox/networks.conf" > /dev/null <<'EOF'
* 10.0.0.0/8
* 192.168.33.0/24
* 2001::/64
EOF

# Allow Touch ID to unlock sudo (from: https://news.ycombinator.com/item?id=32611340)
if ! grep -q "pam_tid.so" /etc/pam.d/sudo; then
  echo "Touch ID not enabled for sudo. Inserting in /etc/pam.d/sudo .."
  sudo perl -pi -e 's/(pam_smartcard.so)/$1\nauth sufficient pam_tid.so/' /etc/pam.d/sudo
fi

cat << 'EOF' > ~/.config/starship.toml
[aws]
disabled=true
[gcloud]
disabled=true
[php]
disabled=true
EOF

cat << 'EOF' > ~/.bash_profile
# PATH
export PATH=/opt/homebrew/bin:/usr/local/opt/coreutils/libexec/gnubin:${HOME}/bin:${HOME}/code/dotfiles/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

# Starship Prompt
eval "$(starship init bash)" || true

# Do not mention we should switch to ZSH
export BASH_SILENCE_DEPRECATION_WARNING=1

# Add location to tabs in iterm
function set_win_title(){
  echo -ne "\033]0;${PWD/#$HOME/~}\007"
}
starship_precmd_user_func="set_win_title"

# https://stackoverflow.com/a/52901834/151666
export LC_CTYPE="en_US.UTF-8"

# Append to the Bash history file, rather than overwriting it
shopt -s histappend

# Bash aliases
alias yarn=corepack yarn
alias ..="cd .."
alias upb="yarn add \$(npm outdated |grep -E '^(@types/)?(eslint|sucrase|ts-fly|jest|babel|typescript|@babel|@sucrase|@typescript)'|awk '{print \$1}')"
alias e="vscode-workspace.sh"
# ^-- that file is in my ~/dotfiles/bin and among other things does:
# [ -f ".vscode/$(basename "${PWD}").code-workspace" ] && code ".vscode/$(basename "${PWD}").code-workspace" || code .
# this way I can enter any project and just execute `e` to open it correctly in VS Code

# Load completions
# [[ -r /usr/local/etc/profile.d/bash_completion.sh ]] && source /usr/local/etc/profile.d/bash_completion.sh
# ^-- commented out for latency reasons
[[ -f /usr/local/etc/bash_completion ]] && source /usr/local/etc/bash_completion
[[ -f /opt/homebrew/etc/bash_completion ]] && source /opt/homebrew/etc/bash_completion
[[ -f ~/.bash_completions_gh ]] && source ~/.bash_completions_gh
[[ -f /usr/local/etc/bash_completion.d/yarn ]] && source /usr/local/etc/bash_completion.d/yarn
[[ -f /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.bash.inc ]] && source /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.bash.inc
EOF

Node.js

Already installed higher up in the Homebrew command, but these will also come in handy:

yarn global add updtr sucrase @transloadit/ts-fly
npm login

More responsive typing

Setup Git + LFS

git lfs install
sudo git lfs install --system
git config --global pull.rebase true
git config --global push.autoSetupRemote true
git config --global alias.conflicts "diff --name-only --diff-filter=U"
git config --global alias.lol "log --pretty=oneline --abbrev-commit --graph --decorate"
git config --global alias.co "checkout"
git config --global alias.st "status -sb"
git config --global alias.down "! git pull && git checkout main && git pull && git checkout - && git merge main"

Setup Git SSH Keys for cloning

ssh-keygen -t rsa -b 4096 -C "${USER}@${HOSTNAME}" # enter, enter, enter
cat ~/.ssh/id_rsa.pub
# and add the contents as a key to https://github.com/settings/keys

Setup Git Verified commits

This assumes you have keys already. If you do not check out a full walkthrough

# Get key id from https://github.com/settings/keys
keyID="xxxxx"

mkdir -p ~/.gnupg
ln -nfs ~/Dropbox/Configs/gnupg ~/.gnupg
chmod 700 ~/.gnupg
git config --global user.signingkey "${keyID}"
git config --global commit.gpgsign true
git config --global gpg.program $(which gpg)
git config --global user.email kevin@vanzonneveld.net

if ! grep -q "pinentry-program /usr/local/bin/pinentry-mac" ~/.gnupg/gpg-agent.conf; then
  echo "pinentry-program /usr/local/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
fi

gpgconf --kill gpg-agent

Cloud tools

gcloud init
aws configure

Setup Github CLI + aliases

brew upgrade gh
gh auth login
gh auth refresh -s project
gh alias set todo-meta 'issue create --repo=transloadit/team-internals --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-founder 'issue create --repo=transloadit/founder-internals --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-accounting 'issue create --repo=transloadit/accounting --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-legal 'issue create --repo=transloadit/legal --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-website 'issue create --repo=transloadit/content --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-content 'issue create --repo=transloadit/content --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-nix 'issue create --repo=transloadit/api2 --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-api2 'issue create --repo=transloadit/api2 --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-growth 'issue create --repo=transloadit/growth --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
  && gh alias set todo-botty 'issue create --repo=transloadit/botty --project="🤖 The Board" --body="n/a" --assignee="@me"' --clobber \
;

Now when I think of a new Website todo I type gh todo-website, fill out a title, and done. Optionally I CMD+Click the link which takes me to the issue on Github.com to fill out more details.

Setup Git Repos

mkdir ~/code
# sync code + env files from existing machine via ssh
rsync \
  -a \
  --progress \
  --ignore-existing \
  --exclude='node_modules/' \
  --exclude='.Trash-*' \
  --exclude='.vagrant' \
192.168.68.92:/Users/kvz/code/ ~/code \
;

cd ~/code/api2
vagrant plugin install vagrant-vbguest
ln -nfs ${HOME}/code/api2/core/bin/tlc.sh ~/bin/tlc
chmod 755 ~/bin/tlc