Skip to content

{% raw %}

Tutorial: Create a Machine Profile

Machine profiles tell chezmoi how to render templates differently on each host — display scale, keyboard layout, performance preset, available features.

When to Use a Profile

  • You have more than one host (laptop + desktop, work + personal)
  • Your hardware differs materially (HiDPI laptop vs 1080p external, ARM vs x86)
  • Certain features only apply to specific setups (Niri on Wayland, AeroSpace on macOS)

Step 1: Choose or Create a Preset

Presets live in .chezmoidata/hardware.toml:

[hardware.macbook-t2]
display_scale = 2.0
kbd_layout = "qwerty"
modifier_mode = "left-cmd-control"
perf_profile = "laptop"
wm = "aerospace"
[hardware.macbook-t2.features]
retina = true
touchid = true

[hardware.surface-pro]
display_scale = 1.5
kbd_layout = "colemak-dh"
modifier_mode = "standard"
perf_profile = "laptop"
wm = "niri"
[hardware.surface-pro.features]
touch = true
pen = true

If an existing preset fits: use its name. Otherwise, add a new one.

Step 2: Select the Preset for This Host

Edit ~/.config/chezmoi/chezmoi.toml:

[data]
machine = "surface-pro"      # choose from .chezmoidata/hardware.toml
theme = "monterey-dark"
default_shell = "fish"
terminal_font_family = "JetBrainsMono Nerd Font"
terminal_font_size = 11       # larger for HiDPI, smaller for dense screens

chezmoi.toml is not in the repository — each host has its own. Generated by chezmoi init on first install.

Step 3: Reference the Preset in Templates

Inside any .tmpl file:

{{- $hw := index .hardware .machine }}

# Ghostty config  uses machine-specific font size
font-family = "{{ .terminal_font_family }}"
font-size = {{ .terminal_font_size }}

# DPI-aware settings
{{ if eq $hw.display_scale 2.0 }}
window-decoration = false
{{ else }}
window-decoration = true
{{ end }}

# Feature-gated config
{{ if $hw.features.touch }}
mouse-scroll-multiplier = 3
{{ end }}

After editing the template, run chezmoi apply or dot apply.

Step 4: Add a New Preset

Suppose you have a new mini PC. Add to .chezmoidata/hardware.toml:

[hardware.my-nuc]
display_scale = 1.0           # external 1440p
kbd_layout = "qwerty"
modifier_mode = "standard"
perf_profile = "desktop"
wm = "gnome"
[hardware.my-nuc.features]
multi-monitor = true
gaming = true

Then on that host:

# ~/.config/chezmoi/chezmoi.toml
[data]
machine = "my-nuc"

Apply:

dot apply

Templates automatically pick up the new preset data.

Step 5: Per-Preset Feature Flags

Feature flags in .chezmoidata.toml can be gated by preset:

# .chezmoidata.toml — defaults for all hosts
[features]
dms = false
linux_desktop = false
waybar = false

Override per-preset in .chezmoidata/hardware.toml:

[hardware.surface-pro.features]
touch = true
pen = true
dms = true                    # override feature default
waybar = true

In templates:

{{- $hw := index .hardware .machine }}
{{- $features := mergeOverwrite .features $hw.features }}

{{ if $features.waybar }}
# ... waybar-specific config ...
{{ end }}

Example: Font Size by Preset

A single Ghostty config that renders correctly on every host:

# dot_config/ghostty/config.tmpl
{{- $hw := index .hardware .machine }}

theme = {{ .theme }}
font-family = "{{ .terminal_font_family }}"

# DPI-aware font sizing
{{ if eq $hw.display_scale 2.0 }}
font-size = 12               # Retina MacBook
{{ else if eq $hw.display_scale 1.5 }}
font-size = 11               # Surface Pro
{{ else }}
font-size = 14               # External 1440p/4K
{{ end }}

On the MacBook: 12pt. On the Surface: 11pt. On the NUC: 14pt. One template.

Example: Platform × Preset

Combine .chezmoi.os with the preset:

{{- $hw := index .hardware .machine }}

{{ if and (eq .chezmoi.os "darwin") (eq $hw.wm "aerospace") }}
# macOS + AeroSpace specifics
{{ else if and (eq .chezmoi.os "linux") (eq $hw.wm "niri") }}
# Linux + Niri specifics
{{ end }}

Listing Known Presets

chezmoi execute-template '{{- range $name, $_ := .hardware }}{{ $name }}{{ "\n" }}{{ end }}'
# macbook-t2
# surface-pro
# geekom-a9
# my-nuc

Changing Presets on an Existing Host

If you reassign a host to a different preset:

# Edit ~/.config/chezmoi/chezmoi.toml
# machine = "my-nuc" (was "surface-pro")

dot apply --dry-run   # preview changes
dot apply             # apply

Chezmoi re-renders every template with the new preset data. No reboot needed.

Troubleshooting

"Machine preset 'foo' not found"

The machine value in ~/.config/chezmoi/chezmoi.toml doesn't match any key under [hardware.*] in .chezmoidata/hardware.toml. Check spelling.

Templates Silently Use Defaults

Make sure you're using {{- $hw := index .hardware .machine }} at the top of templates. If machine is empty, index returns nil — reference with {{- with $hw }}...{{- end }} guards.

Feature Flag Not Taking Effect

Verify the merge order:

chezmoi execute-template '{{- $hw := index .hardware .machine -}}{{- $f := mergeOverwrite .features $hw.features -}}{{ $f | toToml }}'

Per-preset features must override global defaults. If the output doesn't reflect that, check template syntax.

Summary

Profiles let one source tree produce correctly-configured output on every host you own. The machinery:

  1. .chezmoidata/hardware.toml defines presets (declarative)
  2. ~/.config/chezmoi/chezmoi.toml selects a preset per host (per-host override)
  3. .tmpl files read .hardware + .machine (template-time)

Next