I run a bunch of software on my Macbook. I'd like to keep that software up to date and patched, so it doesn't have security vulnerabilities. Trouble is, I install it from lots of places:

  • Random websites (for the most part)
  • Homebrew (for most commandline programs)
  • Homebrew Cask (a somehow different tool for managing GUI programs)
  • npm (Node.js package manager - used for Typescript mostly)
  • pip (Python package manager - for all sorts)
  • cargo (Rust package manager - mostly for ripgrep)
  • And probably more.

My approach to keeping these up to date has been pretty haphazard: mostly just running brew update and brew upgrade once in a while, missing the other ones. I'd like something a bit more formalized.

The usual way to solve this would be a big shell-script, something like

#!/bin/sh
brew update && \
brew upgrade && \
brew cask update && \
brew cask upgrade && \
npm update -g npm && \
npm update -g typescript && .....

I'd had decent success with using Ansible to automate deploying and updating my home fleet of Raspberry Pi air quality sensors, and one day I saw that there was a local mode of Ansbile too: that it's not just for SSH'ing into remote machines, but can also work on localhost.

That's interesting - I wondered if anybody had used Ansible for laptop maintenance. Adam Johnson had a fantastic writeup about how he uses Ansible for deploying and updating his mac, complete with a public domain GitHub repo to crib off. His config is a lot more complex than mine, but was a great starting point.

My result is up at https://github.com/mhansen/mac-ansible. It automates:

Python Packages

- name: install pip packages
  pip:
    name:
    - ansible
    - beancount
    - deepdiff
    - fava
    ...

Homebrew Formulae

- name: Update homebrew
  homebrew:
    name:
    - ack
    - bazel
    - cmake
    - fish
    - git
    - go
    ...

Vimrc

- name: update vimfiles to latest
  git:
    repo: git@github.com:mhansen/vimfiles.git
    dest: ~/.vim
    update: yes

NPM

- name: update npm
  npm:
    name: '{{ item }}'
    global: true
    state: latest
  loop:
    - npm
    - typescript

And a few more. All in all, I feel like this was a big success for being able to run maintenance with one command, and keep my set of packages I use in source control for if I get a new laptop. In the usual infrastructure-as-code fashion, I'm a big fan of being able to add comments explaining any hacks too :-)

It'll be a challenge to keep it in sync with ad-hoc installs, but hopefully when I get a new laptop I can update the config with anything on the old laptop.

So far there's nothing very secret in the repo, so I'm comfortable making it public. Not sure how long this'll keep. Hopefully we'll just .gitignore any secrets out of the public eye.

If you're interested in doing this yourself, please feel free to crib off my GitHub repo: https://github.com/mhansen/mac-ansible.