In a previous post, I mentioned we would revisit chezmoi templates. Today, let’s explore how to use these templates to manage your tool versions with asdf.

Data

Chezmoi lets you define a mini database to store whatever you want! These data must be defined in the .chezmoidata/ folder.

Let’s start by creating an asdf.yaml file in this folder to store our tool versions.

.chezmoidata/asdf.yaml :

asdf:
  version: 0.18.0
  tools:
    - name: age
      version: 1.2.1
      plugin_url:
    - name: bat
      version: 0.25.0
      plugin_url:
    - name: chezmoi
      version: 2.64.0
      plugin_url:
    - name: github-cli
      version: 2.76.2
      plugin_url:
    - name: golang
      version: 1.25.0
      plugin_url:
    - name: hugo
      version: 0.148.2
      plugin_url:
    - name: lazygit
      version: 0.54.2
      plugin_url:
    - name: lsd
      plugin_url: https://github.com/ossareh/asdf-lsd.git
      version: 1.1.5
    - name: neovim
      version: 0.11.3
      plugin_url:
    - name: nodejs
      version: 24.6.0
      plugin_url:
    - name: rclone
      version: 1.70.3
      plugin_url:
    - name: shellcheck
      version: 0.11.0
      plugin_url:
    - name: shfmt
      version: 3.12.0
      plugin_url:
    - name: upx
      version: 5.0.2
      plugin_url:
    - name: yq
      version: 4.47.1
      plugin_url:
    - name: gum
      version: 0.16.2
      plugin_url:
    - name: starship
      version: 1.23.0
      plugin_url:

Templates

Chezmoi allows you to use templates to customize your configuration files. You can use variables to replace values in your files.

Chezmoi templates use the Go template language. More precisely, they use Go’s text/template library for rendering. This means you can use all the features of this library, including functions, pipelines, and conditions. See the documentation

Here, we’ll generate the ~/.tool-versions file from the data defined in asdf.yaml.

Quick reminder: the file starts with a dot 👉 so we need to use dot to symbolize it. Add .tmpl to indicate it’s a template.

~/dot_tool-versions.tmpl :

{{ range .asdf.tools -}}
{{ .name }} {{ .version }}
{{ end -}}

Simple, right?

Template explanation

  • {{ range .asdf.tools -}} creates a loop to iterate over each tool defined in asdf.yaml.
  • {{ .name }} {{ .version }} displays the name and version of each tool.
  • {{ end }} ends the loop.

Scripts

Where chezmoi excels is in script management. You can easily define scripts to run when certain files are modified.

In our case, chezmoi will update our .tool-versions, but it can also run the necessary commands for asdf to install the tools!

Even better, you can combine templates and scripts to further automate your workflow.

Let’s create the file run_onchange_dot_tool-versions.sh.tmpl in the .chezmoiscripts/ folder.

.chezmoiscripts/run_onchange_dot_tool-versions.sh.tmpl :

{{- if eq .chezmoi.os "linux" -}}
#!/bin/bash

# function install_asdf

function install_asdf() {
	base_url="https://github.com/asdf-vm/asdf/releases/download/"
	rm -f asdf-v{{ .asdf.version }}-{{ .chezmoi.os }}-{{ .chezmoi.arch }}.tar.gz*
	wget "${base_url}v{{ .asdf.version }}/asdf-v{{ .asdf.version }}-{{ .chezmoi.os }}-{{ .chezmoi.arch }}.tar.gz"
	tar -xvzf asdf-v{{ .asdf.version }}-{{ .chezmoi.os }}-{{ .chezmoi.arch }}.tar.gz -C ~/.asdf/bin
	# clean up
	rm -f asdf-v{{ .asdf.version }}-{{ .chezmoi.os }}-{{ .chezmoi.arch }}.tar.gz*
	export ASDF_DATA_DIR={{ .chezmoi.config.destDir }}/.asdf
	export PATH="$ASDF_DATA_DIR/shims:$ASDF_DATA_DIR/bin:$PATH"
}

# install asdf
# if ~/.asdf not exist then install asdf
if [ ! -d ~/.asdf ]; then
	mkdir -p ~/.asdf/bin
fi
# if ~/.asdf/bin/asdf not exist or not executable then install asdf
if [ ! -x ~/.asdf/bin/asdf ]; then
	install_asdf
fi
# if asdf version is not the right one then install asdf
if [ "$(~/.asdf/bin/asdf --version | cut -d ' ' -f 3)" != "v{{ .asdf.version }}" ]; then
	install_asdf
fi

{{ range .asdf.tools -}}
# install plugins
# if .plugin_url does not exist then use ""
if [ -z "$(~/.asdf/bin/asdf plugin list | grep {{ .name }})" ]; then
	echo "installing {{.name}} plugin"
	~/.asdf/bin/asdf plugin add {{ .name }} {{ .plugin_url | default "" }}
else
	# if plugin is already installed then update it
	~/.asdf/bin/asdf plugin update {{ .name }}
fi
{{ end -}}

{{ range .asdf.tools -}}
# install tool
# if tool is not installed then install it
echo "installing {{.name}} {{.version}}"
if [ -z "$(~/.asdf/bin/asdf list {{ .name }} | grep {{ .version }})" ]; then
	~/.asdf/bin/asdf install {{ .name }} {{ .version }}
fi
# remove old versions
for mytool in $(asdf list {{ .name }} | grep -v {{ .version }} | sed -e s/\*//); do 
	echo "removing {{ .name }} $mytool"
	asdf uninstall {{ .name }} $mytool; 
done;
{{ end -}}
{{ end -}}

Script explanation

I’ll just cover the main points:

  • The script starts by checking if the operating system is Linux.
  • It defines a function to install asdf.
  • It ensures the folder needed for asdf installation exists.
  • It checks if asdf is already installed, and if not, installs it.
  • For each tool, it checks that the plugin is installed and up to date.
  • For each tool, it ensures the version specified in ~/.tool-versions is installed.
  • Finally, for each tool, it cleans up old versions.

Conclusion

We’ve seen how to use chezmoi to manage tool versions with asdf. By combining templates and scripts, you can automate your workflow and ensure your tools are always up to date. Feel free to explore more chezmoi features and adapt them to your needs.

Note: data files cannot be templated (at least for now).

Once again, you can get inspiration from my dotfiles.