Skip to main content
  1. Posts/
  2. The Solo Stack/
  3. Dotfiles/

Automating age Installation with chezmoi

·940 words·5 mins
Dotfiles - This article is part of a series.
Part 5: This Article

In the previous article, we encrypted our SSH keys with age and stored them in the chezmoi repo. That’s great — but there’s a gap. When we set up a new machine and run chezmoi init, chezmoi needs age to decrypt those keys. And as we saw earlier in the series, the version of age available via apt on Debian is too old for post-quantum support.

Right now, installing the correct version of age is still a manual step. Time to fix that.

chezmoi’s run_once Scripts
#

Chezmoi has a concept called run_once scripts. These are shell scripts that chezmoi executes exactly once per machine — on the first chezmoi apply. If the script’s contents haven’t changed since the last run, chezmoi skips it. Change the script, and it runs again on the next apply.

This is perfect for package installation. We want age (and eventually other tools) to be installed automatically when setting up a new machine, but we don’t want the script to re-run on every chezmoi apply.

The naming convention is important: the script must start with run_once_ for chezmoi to treat it correctly. I’m calling mine run_once_install-packages.sh.

The Script
#

Let’s create the script in the chezmoi source directory:

chezmoi cd
touch run_once_install-packages.sh
chmod +x run_once_install-packages.sh

Here’s the full script:

#!/bin/bash
set -euo pipefail

# Use sudo if available and not root
SUDO=""
if [ "$(id -u)" -ne 0 ] && command -v sudo &> /dev/null; then
    SUDO="sudo"
fi

AGE_VERSION="1.3.1"

# Detect OS
if [ -f /etc/debian_version ]; then
    $SUDO apt-get update -y
    $SUDO apt-get upgrade -y
    $SUDO apt-get install -y \
        zsh
    cd /tmp
    curl -LO "https://github.com/FiloSottile/age/releases/download/v${AGE_VERSION}/age-v${AGE_VERSION}-linux-amd64.tar.gz"
    tar xzf "age-v${AGE_VERSION}-linux-amd64.tar.gz"
    $SUDO mv age/age age/age-keygen /usr/local/bin/
    rm -rf age "age-v${AGE_VERSION}-linux-amd64.tar.gz"
    cd ~
fi

Let’s walk through the key parts.

Handling sudo
#

SUDO=""
if [ "$(id -u)" -ne 0 ] && command -v sudo &> /dev/null; then
    SUDO="sudo"
fi

Not every environment has sudo, and sometimes you’re already root — like in a Docker container. This block checks both conditions and sets a $SUDO variable accordingly. Throughout the rest of the script, commands that need elevated privileges use $SUDO instead of hardcoding sudo.

OS Detection
#

if [ -f /etc/debian_version ]; then
    # ...
fi

The script checks for /etc/debian_version to confirm we’re on a Debian-based system. Right now, that’s the only OS I’m targeting. If I ever need to support macOS or another distro, I can add an elif branch later. For now, keeping it simple.

apt Packages
#

$SUDO apt-get update -y
$SUDO apt-get upgrade -y
$SUDO apt-get install -y \
    zsh

Before installing age from source, we update the package index and install any tools that are fine to get from apt. Right now that’s just zsh, but this list will grow as the dotfiles setup matures — tmux, neovim, and other tools can be added here.

Installing age From GitHub Releases
#

AGE_VERSION="1.3.1"

cd /tmp
curl -LO "https://github.com/FiloSottile/age/releases/download/v${AGE_VERSION}/age-v${AGE_VERSION}-linux-amd64.tar.gz"
tar xzf "age-v${AGE_VERSION}-linux-amd64.tar.gz"
$SUDO mv age/age age/age-keygen /usr/local/bin/
rm -rf age "age-v${AGE_VERSION}-linux-amd64.tar.gz"
cd ~

This is the same manual process from the earlier post, but now it’s automated. The version is pinned at the top of the script as a variable, making it easy to bump when a new release comes out. And because this is a run_once script, chezmoi will re-run it when the version number changes — so updating age across all machines is as simple as changing one line and running chezmoi apply.

Adding the Script to chezmoi
#

Since we created the script directly in the chezmoi source directory, it’s already in the right place. Let’s commit it:

chezmoi cd
git add run_once_install-packages.sh
git commit -m "add run_once script for automated package installation"
git push

Now, on any new machine, running chezmoi apply will automatically install zsh and the correct version of age before anything else happens.

Testing It
#

The best way to test this is with the disposable container setup from earlier in the series. Spin up a fresh container, install chezmoi, and run chezmoi init --apply. The script should install everything automatically, and age-keygen --version should report v1.3.1.

What We’ve Achieved So Far
#

Let’s take a step back and look at where we are. Testing the full dotfile setup inside a disposable container now comes down to a handful of manual steps:

podman run -it --rm debian:bookworm-slim bash
apt update -y && apt upgrade -y && apt install curl git nano -y
mkdir -p ~/.config/age
chmod 700 ~/.config/age
touch ~/.config/age/dotfiles-repo-key.txt
chmod 600 ~/.config/age/dotfiles-repo-key.txt
nano ~/.config/age/dotfiles-repo-key.txt  # (paste in key from secure location)
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply knuth-info
zsh

We start a fresh Debian container, install the bare minimum — curl, git, and nano — then create the age key directory and paste in the private key from a secure location. After that, a single chezmoi command pulls down the repo, runs the run_once script to install zsh and the correct version of age, decrypts the SSH keys, and applies everything. The last step is just switching to zsh.

That’s a lot less than where we started. No more manually downloading age binaries, no more copying SSH keys into containers by hand. The only truly manual part left is placing the age key — which is intentional, since that secret should never be automated into a repo.

What’s Next
#

With package installation automated, the setup flow for a new machine is getting tighter. We still need to handle a few things — like configuring git with the right name and email, and switching the chezmoi remote from HTTPS to SSH once the keys are decrypted. We’ll tackle that in the next post.

Marcus Knuth
Author
Marcus Knuth
Dotfiles - This article is part of a series.
Part 5: This Article

Related

Why apt install age Isn't Enough

·612 words·3 mins
In the previous article, I set up disposable containers to test my chezmoi dotfiles. That works, but there’s still too much manual effort involved — especially the SSH key. Every new container means copying it in by hand. So I decided to encrypt the SSH key with age and store it directly in the dotfiles repo. Chezmoi supports age-based decryption out of the box, which makes this a natural fit. On top of that, I want future tool installations — zsh, tmux, neovim — to be handled automatically too, not typed in manually every time.

Getting Started with Dotfile Management

·1225 words·6 mins
Getting Started with Dotfile Management # Up until now, I’ve mostly been working with GUI-driven IDEs on Windows. I wrote about why I’d like to shift to the terminal — the short version is that a portable, keyboard-driven workflow matters when you’re building solo. But alongside that shift, something else has been happening. Since Docker entered my daily work, I’ve been spending more and more time on Linux again — something I hadn’t done seriously since studying computer science. Linux stepped back into my life gradually, and for a long time I just used it without thinking much about it. I never cared about the fundamentals.