In my previous post on chezmoi I gave an introduction on the basic setup and usage. There is a lot more you can do with chezmoi, particularly if you’re using it across different computers you use. For example, maybe you have separate computers for work and at home, or maybe you have one computer that’s a Windows machine and one that’s MacOS and another one that’s Linux. Perhaps you have multiple Linux machines but you want the config to be slightly different. chezmoihas you covered.

The full documentation for chezmoi templates is here.

Template Basics

chezmoi keeps templates in a directory .chezmoitemplates or as files with a .tmpl extension. In practice, I’d say the former is for pieces of templates you want to include in other templates (I’ll show that later) whereas the latter is used for the “final” file that’s your actual config file e.g. .zshrc.tmpl (or really dot_zshrc.tmpl as it’s managed by chezmoi in it’s source directory.

Templates in chezmoi use the Go language template syntax. chezmoi also has custom functions it’s created as well as using functions from sprig. You can use branching logic with if/else and there are various boolean logic comparisons like ‘not equal’, ‘greater than’, etc.

Creating A Template

There are a few ways to create a template.

Empty Template

If you want to create a new empty template just do so manually in the .chezmoitemplates directory:

$ chezmoi cd
# if needed, `mkdir .chezmoitemplates`
$ cd .chezmoitemplates
$ vi mytemplate

Template files in .chezmoitemplates do not need any special extension.

Adding A New File To Manage

If you’re adding a new file for chezmoi to manage, pass --template when adding the file:

chezmoi add --template ~/.gitconfig

This will create a file dot_gitconfig.tmpl in the chezmoi source directory.

Turning An Existing File Into A Template

If you want to make a file that is already managed by chezmoi into a template, you use the chezmoi chattr command:

chezmoi chattr +template ~/.zshrc

This will change dot_zshrc into dot_zshrc.tmpl.

Using Template Data And Syntax

Now that we have a template file, let’s see some examples of leveraging the template language and available data and functions to customize our configuration.

Template Data

chezmoi exposes many data attributes from your system. For a full list, use the chezmoi data command:

chezmoi data

This will output a lot of content in JSON format. Among it all, things like the host name, operating system, properties of the operating system (name, version, etc) are probably the most useful, but use cases vary. To access any of these data values, you use a path-like syntax, similar to what you might use with jq.

{{ .chezmoi.hostname }} {{ .chezmoi.os }}

Tip

A quick and easy way to test your template expressions with with the chezmoi execute-template command.

$ chezmoi execute-template "host: {{ .chezmoi.hostname }} os: {{ .chezmoi.os }}"
host: kidoni os: linux

Custom (user-defined) Data

You can also create your own data values to use in your templates. You place these in your chezmoi configuration file e.g. ~/.config/chezmoi/chezmoi.toml. Place any custom data attributes you want in a [data] section in chezmoi.toml. You then access them in the template with the same dot syntax.

[data]
myvar = "foo"
mycnt = 10
$ chezmoi execute-template "var: {{ .myvar }}  cnt: {{ .mycnt }}"
var: foo  cnt: 10

You can also “nest” your custom data fields:

[data.foo]
cnt = 10
[data.bar]
name = "ray"
$ chezmoi execute-template "cnt: {{ .foo.cnt }}  name: {{ .bar.name }}"
cnt: 10  name: ray

Template Logic And Functions

We can create our templates and we can access data, so now let’s put in some logic to control what we configure based on the various data available.

First, I’m going to configure my ~/.gitconfig global Git configuration so that on Windows I sign my commits using SSH while on my MacOS and Linux systems I use PGP.

chezmoi chattr +template ~/.gitconfig
chezmoi edit ~/.gitconfig

Note

Obviously leave off the chattr if you’ve already made the file into a template

In the .gitconfig edit the [user] section:

[user]
{{ if eq .chezmoi.os "windows" }}
    signingkey = ssh-ed25519 xxx
{{ else if eq .chezmoi.os "linux" "darwin" }}
    signingkey = 25Bxxxxxxxxxxxx
{{ end }}

The branching is done using if/else if and if needed there is also just else. The branching is terminated by the end statement. You can see the boolean logic operator eq for comparing the variable .chezmoi.os with the values. One thing to call out is the else if branch logic. Note that it is checking for both Linux and MacOS in the one condition. The chezmoi template language will test the variable against the provided values with an implicit or.

Speaking of or you can use both and and or operators. For example, if I have two Linux systems with different host names, I can do something like

[user]
{{ if (and (eq .chezmoi.os "linux") (eq .chezmoi.hostname "kidoni")) }}
    email = "ray@kidoni.dev"
{{ else if (and (eq .chezmoi.os "linux") (eq .chezmoi.hostname "work")) }}
    email = "ray@bogus.com
{{ end }}

Using Sprig Functions

As mentioned, you can use any of the sprig functions in your templates. There are a lot of functions and a lot of use cases, so I won’t spend time trying to come up with anything fancy. It’s easy to test the functions using chezmoi execute-template as mentioned earlier. Just a few quick examples …

$ chezmoi execute-template "{{ upper .chezmoi.arch }}"
AMD64
$ chezmoi execute-template "{{ trunc 6 .chezmoi.version.commit }}"
48865
$ chezmoi execute-template '{{ replace  "/" "\\" .chezmoi.sourceDir}}'
\home\ray\.local\share\chezmoi

Combining Fragments Into A Larger Template

As mentioned at the start, you can put files in .chezmoitemplates and one use for that is to store pieces of templates that you want to include into another template, perhaps for re-use.

To do this, name the file whatever you want, just put it in .chezmoitemplates. To include a file into a template, you use the template command.

Let’s go back to the example of different home and work systems, differentiated by host name. I have some shell configuration I want only at home, some only at work, and some in both. So I create three files in .chezmoitemplates

$ ls -1 .chezmoitemplates
 common
 home
 work
 
$ bat *
───────┬──────────────────────────────────────────
 File: common
───────┼──────────────────────────────────────────
   1 # starship https://starship.rs/
   2 eval "$(starship init zsh)"
───────┴──────────────────────────────────────────
───────┬──────────────────────────────────────────
 File: home
───────┼──────────────────────────────────────────
   1 # atuin configuration
   2 if [ -e $HOME/.atuin/bin/env ]; then
   3   . "$HOME/.atuin/bin/env"
   4 fi
   5 eval "$(atuin init zsh)"
   6
───────┴──────────────────────────────────────────
───────┬──────────────────────────────────────────
 File: work
───────┼──────────────────────────────────────────
   1 . "$HOME/.deno/env"
───────┴──────────────────────────────────────────

Now I can create a template that includes these, based on which system I’m on.

$ bat -p setup.sh.tmpl
{{ if eq .chezmoi.hostname "kidoni" }}
{{ template "home" }}
{{ else if eq .chezmoi.hostname "work" }}
{{ template "work" }}
{{end}}
{{ template "common" }}
 
$ chezmoi execute-template < setup.sh.tmpl
 
# atuin configuration
 
if [ -e $HOME/.atuin/bin/env ]; then
. "$HOME/.atuin/bin/env"
fi
eval "$(atuin init zsh)"
 
# starship <https://starship.rs/>
 
eval "$(starship init zsh)"

Wrapping Up

The templating system in chezmoi is pretty robust and with custom data fields via the chezmoi config file, you can do quite a lot.

There’s also much more you can do with chezmoi. One of those is its ability to integrate with password managers. This is something I will cover in a subsequent blog post.