Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Templates

Some dotfiles contain machine-specific values — a hostname in a Nix flake, a username in a config, a path that differs per machine. dotling supports opt-in templating to handle this.

How it works

Any file tracked with --template is marked with template: true in dotling.toml. On every sync, dotling renders the template and writes the output to the deploy target — the repo source is never deployed directly.

# 1. Set your machine-local variables
dotling vars set hostname "Some hostname"
dotling vars set primary_user "someuser"

# 2. Add a file as a template
dotling add ~/.config/nix-darwin/flake.nix --template

# 3. On another machine, sync will detect missing vars and prompt for them
dotling sync

Template syntax

Variables are wrapped in double curly braces:

{{ var.key }}
{{ dotling.hostname }}
{{ env.VAR }}

Namespaces

ExpressionDescription
{{ var.key }}User-defined variable (local or config default)
{{ dotling.hostname }}Current machine hostname
{{ dotling.username }}Current OS username
{{ dotling.os }}macos, linux, or windows
{{ dotling.arch }}x86_64, aarch64, or arm
{{ dotling.home }}Home directory path
{{ dotling.repo }}Dotfiles repo root path
{{ env.VAR }}Environment variable

Filters

Filters are applied with the pipe (|) syntax:

{{ var.key | upper }}          Convert to uppercase
{{ var.key | lower }}          Convert to lowercase
{{ var.key | trim }}           Strip leading/trailing whitespace
{{ var.key | quote }}          Wrap in double quotes: "value"
{{ var.key | squote }}         Wrap in single quotes: 'value'
{{ var.key | default "foo" }}  Use fallback if variable is not set

Filters can be chained:

{{ var.name | trim | upper }}

Whitespace control

Use - to strip surrounding whitespace:

{{- var.key -}}     Strip whitespace on both sides
{{- var.key }}      Strip whitespace on the left only
{{ var.key -}}      Strip whitespace on the right only

This is useful for controlling indentation in generated files:

{{- if var.enable_feature -}}
  feature = true;
{{- end -}}

Scripts

Templates can execute inline shell commands and insert their standard output into the document using backticks inside template tags:

{{ `uname -s` | lower }}
{{ `curl -sSf https://api.ipify.org` | trim }}
  • Interpreter: Commands are executed using sh -c on Unix and cmd /C on Windows.
  • Environment: Internal shell pipes, redirects, and environment variables work as expected (e.g. {{ `echo $USER` }}).
  • Whitespace: A command’s output usually ends with a trailing newline. Dotling trims trailing whitespace automatically before the value is processed by filters, so it substitutes cleanly inline.
  • Failures: A non-zero exit code or an execution failure fails the template render immediately. You can use the `default` filter to catch command failures and provide a fallback value: {{ `brew --prefix` | default "/opt/homebrew" }}.

Script Security

Arbitrary command execution during config syncing is a security risk for shared dotfiles. Dotling reuses the HookSession trust system to protect against malicious template scripts.

When an untrusted script is encountered during dotling sync or dotling add, the user is interactively prompted to trust it. Trusted scripts are hashed and remembered in ~/.dotling/trusted_hooks. In non-interactive environments (e.g. CI), untrusted scripts are skipped (which fails the template render unless caught by a default filter) unless explicitly allowed by setting DOTLING_ALLOW_HOOKS=1.

Variable sources

Variables are resolved in priority order:

  1. Local store~/.dotling/vars.toml (machine-specific, never committed)
  2. Config defaults[vars] in dotling.toml (shared, committed)

Built-in variables (dotling.*) and environment variables (env.*) are separate namespaces resolved directly.

Shared defaults

Shared defaults in dotling.toml act as documentation and fallbacks — use placeholders, not real values:

# dotling.toml
[vars]
hostname = "my-mac"       # placeholder — override in ~/.dotling/vars.toml
primary_user = "user"     # placeholder

Machine-local variables

Machine-specific values are stored in ~/.dotling/vars.toml and are never committed to git:

dotling vars set hostname "work-laptop"
dotling vars set primary_user "alice"

dotling vars reference

dotling vars list                    # show all resolved variables
dotling vars set hostname "my-mac"   # set a machine-local variable
dotling vars get hostname            # print the resolved value
dotling vars unset hostname          # remove from local store
dotling vars check                   # validate all templates for unresolved variables
dotling vars import ~/.env           # bulk-import from .env or TOML file
dotling vars export                  # print local variables as TOML

Encrypted templates

Sensitive templates (e.g. a config containing tokens) can be both templated and encrypted:

dotling add ~/.config/secret.conf --template --encrypt

The pipeline on sync is: vault decrypt -> render with vars -> deploy.

Example

Given this template at config/nix-darwin/flake.nix:

{
  description = "Nix darwin config for {{ var.hostname }}";

  darwinConfigurations = {
    "{{ var.hostname }}" = darwin.lib.darwinSystem {
      system = "{{ var.arch }}-darwin";
      modules = [ ./configuration.nix ];
    };
  };
}

And these local variables:

dotling vars set hostname "work-mbp"

The rendered output on an Apple Silicon Mac would be:

{
  description = "Nix darwin config for work-mbp";

  darwinConfigurations = {
    "work-mbp" = darwin.lib.darwinSystem {
      system = "aarch64-darwin";
      modules = [ ./configuration.nix ];
    };
  };
}