A tool to manage and render dotfiles.
Define your dotfiles once as YAML, template them with Go, and render them for any OS or shell.
dotgen takes YAML files like this:
env:
EDITOR: nano
vars:
project: /work/myproject
commands:
- name: gs
cmd: git status
kind: alias
exclude: {{ notInPath "git" }}
- name: greet
kind: function
cmd: |
echo "Hello from {{ .OS }} on {{ .ARCHITECTURE }}!"
echo "Project at ${project}"and outputs shell code you can source:
# Environment variables
# ------------------------------------------------
export EDITOR="nano"
# ------------------------------------------------
# Variables
# ------------------------------------------------
project="/work/myproject"
# ------------------------------------------------
# Commands
# ------------------------------------------------
# name: gs
alias gs='git status'
# name: greet
greet() {
echo "Hello from linux on x86_64!"
echo "Project at ${project}"
}
# ------------------------------------------------Template with Go, filter by OS/shell, conditionally exclude commands, run setup scripts once - all from declarative config files.
curl -sSL https://raw.githubusercontent.com/idelchi/dotgen/refs/heads/main/install.sh | sh -s -- -d ~/.local/bin# Generate and source your dotfiles
eval "$(dotgen --shell zsh ~/.config/dotgen/**/*.dotgen)"or cache the output to avoid regenerating on every shell startup:
# In your .zshrc or .bashrc
if [ ! -f "${HOME}/.cache/dotgen/dotgen.rc" ]; then
mkdir -p "${HOME}/.cache/dotgen"
dotgen --shell zsh "/path/to/configs/**/*.dotgen" > "${HOME}/.cache/dotgen/dotgen.rc"
fi
source "${HOME}/.cache/dotgen/dotgen.rc"Config files are YAML with an optional header section for template variables and a body section for your actual dotfiles.
# Header (optional) - define template variables and global excludes
values:
BIN_DIR: ${HOME}/bin
dependencies:
executables:
- git
files:
- rc/**/*.rc
# Multiple exclude conditions are OR'ed together
exclude:
- {{ notInPath "git" }}
- {{ notInPath "gh" }}
---
# Body - your actual dotfiles
env:
PATH: "{{ .BIN_DIR }}:${PATH}"
EDITOR: nano
vars:
project: /work/src/myproject
commands:
- name: gs
cmd: git status
kind: alias
- name: greet
kind: function
cmd: echo "Hello!"Four command kinds, each with different output behavior:
alias - Shell alias
- name: ll
cmd: ls -al
# default, can be omitted
kind: aliasalias ll='ls -al'function - Shell function
- name: greet
kind: function
cmd: |
echo "Hello $1"greet() {
echo "Hello $1"
}raw - Raw shell code, no wrapping
- name: custom
kind: raw
cmd: |
# Direct shell code
if [ -f ${HOME}/.rc ]; then
source ${HOME}/.rc
firun - Execute command at generation time, capture stdout
- name: zoxide
kind: run
cmd: zoxide init "{{ .SHELL }}" --cmd cd
export_to: {{ .CACHE_DIR }}/zoxide.rc
timeout: 30sUse run for tool integrations (starship, zoxide, etc.) that need to execute once to generate shell code.
export_to takes three possible values:
"",nullor omitted: stdout (inserted in place)path: writes output to that file, and inserts a. <path>line instead/dev/null: discarded. Allows for just performing some operations without outputting anything.
timeout accepts Go duration format (e.g., 30s, 5m, 1h30m). Defaults to 1m if not specified.
Target specific operating systems or shells:
commands:
- name: pbcopy-alias
cmd: xclip -selection clipboard
kind: alias
os:
- linux
shell:
- zsh
- bashor use template logic to exclude conditionally:
commands:
- name: gs
cmd: git status
kind: alias
exclude: {{ notInPath "git" }}
- name: gp
cmd: git push
kind: alias
exclude:
- {{ notInPath "git" }}
- {{ notInPath "gh" }}Files named <name>_<os>.dotgen are automatically skipped if the OS doesn't match.
Files named <name>_wsl.dotgen are included only under Windows Subsystem for Linux.
Files named <name>_docker.dotgen are included only inside Docker containers.
On WSL, both _linux and _wsl suffixes match.
In Docker on WSL, _linux and _docker suffixes match, but _wsl does not.
Every template has access to these built-in variables:
- Platform:
OS,PLATFORM,ARCHITECTURE,EXTENSION,HOSTNAME - User:
USER,HOME,CACHE_DIR,CONFIG_DIR,TMP_DIR - Shell:
SHELL - File context:
DOTGEN_CURRENT_FILE,DOTGEN_CURRENT_DIR,CWD
Add your own variables in multiple ways:
# Inline
dotgen config.dotgen --set KEY=VALUE --set ANOTHER=thing
# From YAML files
dotgen config.dotgen --values values.yml
# From dotenv files
dotgen config.dotgen --env-file .env --env-file "env/*.env"
# In config header
values:
MY_VAR: some_valueVariables merge in this order (last wins):
- Built-in defaults
- Config file header
--valuesvalue files--set KEY=VALUEargs
Environment files passed with --env-file are loaded before rendering, so template functions such as env and mustEnv
can read them. Their values are also emitted as exported environment variables in the generated shell output.
Paths support the same directory expansion, doublestar globbing, platform suffix matching, and templating as dotgen files.
Env-file templates are rendered before imported env values are applied, so env and mustEnv read the process
environment rather than values from other --env-file files.
Declare files and executables that affect generated output but are not dotgen inputs themselves:
dependencies:
executables:
- starship
- zoxide
files:
- rc/01-task.rc
- rc/**/*.rcDependencies are declared in the optional header and contribute to --hash; they are not exposed as template values
or rendered into the generated shell output. Relative file paths resolve from the declaring configuration file,
doublestar globs are supported, and patterns with no matches are allowed.
Files are fingerprinted from their normalized path and SHA-256 content. Executables are resolved from PATH and
fingerprinted from their command name, resolved path, size, and modification time. Missing executables are recorded so
installing or removing one changes the hash. Dependencies of arbitrary shell commands and remote resources cannot be
discovered automatically and must be declared or pinned explicitly.
All config files are processed as Go templates with slim-sprig functions plus custom helpers:
inPath "cmd"- Check if command exists in PATHnotInPath "cmd"- Inverse of aboveisWSL- Check if running under Windows Subsystem for LinuxisDocker- Check if running inside a Docker containerexists "path"- Check if file/directory existswhich "cmd"- Get full path to a command if in PATH, empty string if not foundresolve "paths"...- Joins multiple path elements and returns the full path if it exists, empty string otherwisesize "path"- Get file size in bytes, 0 if missingfingerprint "path"- Get a SHA-256 fingerprint of a file's normalized path and contents, empty string if unreadablejoin "paths"...- Join multiple path elements into a single pathread "path"- Read file content, returns an error if the file doesn't exist or can't be readposixPath "path"- Convert Windows path (likeC:/...orC:\...) to Posix format (/c/...)windowsPath "path"- Convert Posix path (like/c/...) to Windows format (C:/...)mustEnv "KEY"- Return the value of an environment variable, or an error if not setenvIsSet "KEY"- Check whether an environment variable is set, even if empty
Examples:
commands:
- name: dp
cmd: docker ps
kind: alias
exclude: {{ notInPath "docker" }}
- name: setup
kind: raw
cmd: |
{{- if (join (env "HOME") ".rc" | exists) }}
source {{ join (env "HOME") ".rc" }}
{{ end }}Since the entire file is rendered, templates may be used anywhere.
All paths are rendered and returned with forward slashes (/), even on Windows.
Examples of various use-cases can be found at dotfiles.
dotgen [options] [patterns...]--shell- Target shell (default: basename ofSHELLenvironment variable)-f, --values- Additional YAML variable files--env-file- Dotenv files to load before rendering--set- AdditionalKEY=VALUEvariables, only string values supported--verbose- Increase verbosity in rendered output--debug- Show all variables and rendered templates without processing-I, --instrument- Add instrumentation to rendered output to time commands-j, --parallel- Number of concurrent command exports (1disables parallelism)--hash- Compute the hash of all included files, variables, and declared dependencies--dry- Show a list of files that would be processed without executing-v, --version- Show version--shell-completion- Generate shell completion script for specified shell (bash, zsh, fish, powershell)
The positional arguments are patterns supporting globbing (**), with the following special cases:
- when none are provided, defaults to
**/*.dotgen .expands to**/*.dotgen- a trailing
/expands to**/*.dotgenin that directory - if a directory is provided, it expands to
**/*.dotgenin that directory
Unified dotfiles across machines Define once, render differently per OS/shell.
Conditional tool integration Only set up starship/zoxide/fzf if they're installed. No errors on minimal systems.
DRY shell config
Use variables and templates instead of hardcoded paths. Change BIN_DIR once, update everywhere.
Cached generation
Generate once on login, source the cached output. Fast shell startup without losing flexibility.
Use --hash to further optimize caching by only regenerating when configuration or declared dependencies change.
