dotling is a zero-dependency dotfiles management CLI. It moves your config files into a central git repository and replaces them with symlinks (or copies). It handles the tedious parts — path mapping, conflict detection, encryption, templating, hooks, and multi-OS support — so you can set up a new machine in seconds.
Table of Contents
- Features
- Installation
- Quick Start
- Commands
- How It Works
- Configuration
- Multi-OS Support
- Encryption Vault
- Dotfile Templating
- Lifecycle Hooks
- Conflict Resolution
- Sync Fingerprints
- Shell Completions
- Environment Variables
- File Structure
- Contributing
- License
Features
- Symlink & copy deployment — choose per file, switch anytime
- Bidirectional sync —
dotling syncpushes from repo → actual and pulls from actual → repo automatically - Automatic path mapping —
~/.config/nvim→config/nvim,~/.zshrc→shell/zshrc - Multi-OS support — tag entries as
linux,macos, orwindows; skip irrelevant files automatically - Secure Password Vault — encrypt sensitive files (API keys, .env) using an Argon2id + ChaCha20-Poly1305 Vault
- Encrypted sync — sync handles encrypted entries in both directions; modified plaintext is re-encrypted back into the repo automatically
- Portable Secrets — export your vault to easily unlock secrets on a new machine
- Native Git integration — dotling manages the symlinks, you manage the repo with native
gitcommands - Dotfile Templating — add machine-specific values (
hostname,username, custom vars) to any dotfile using{{ var.key }}syntax; render on every sync via~/.dotling/vars.toml - Health checks —
dotling doctoraudits broken links, orphaned entries, and repo issues - Conflict-safe — refuses to overwrite unmanaged files without explicit confirmation
- Lifecycle Hooks — run custom commands before/after syncing at a repository or entry level with safe trust verification
- Interactive 3-way Merge — cleanly merge changes between repo and local files with standard git-style conflict markers
- Fingerprint-based Status — speed up encrypted and copy-mode sync checks using lightweight Blake2s-256 fingerprints, without prompting for passwords
- Shell Completions — tab-completion for bash, zsh, fish, elvish, and powershell via
dotling completions <SHELL>
Installation
From crates.io (recommended)
Prebuilt binaries
Download a prebuilt binary from the latest GitHub release for your platform:
| Platform | Binary |
|---|---|
| Linux (x86_64, glibc) | dotling-x86_64-linux.tar.gz |
| Linux (x86_64, musl) | dotling-x86_64-linux-musl.tar.gz |
| Linux (aarch64) | dotling-aarch64-linux.tar.gz |
| macOS (Intel) | dotling-x86_64-macos.tar.gz |
| macOS (Apple Silicon) | dotling-aarch64-macos.tar.gz |
# Example: Linux x86_64
|
Homebrew (macOS & Linux)
Or install directly:
Nix
# or add to your flake inputs
Quick Start
# Initialize a new dotfiles repo
# Or clone an existing one (clones to ~/dotfiles)
# Track files
# Sync everything (repo → actual and actual → repo)
# Since dotling doesn't wrap git, you can commit and push directly!
Commands
| Command | Description |
|---|---|
dotling init <path|url> |
Initialize a new repo at the given path, or clone an existing repo from a URL (always clones to ~/dotfiles) |
dotling add <paths> |
Move files into the repo and deploy a symlink (or copy) back |
dotling remove <entries> |
Undeploy, safely restore tracked files/folders recursively to their original target locations (decrypting if encrypted), and remove from tracking |
dotling sync |
Bidirectional sync — push repo → actual and pull actual → repo |
dotling status |
Show deployment status of all tracked entries |
dotling edit <entry> |
Edit any tracked entry in $EDITOR — decrypts/re-encrypts automatically for encrypted entries |
dotling encrypt <paths> |
Encrypt tracked entries using your Vault |
dotling decrypt <paths> |
Decrypt encrypted entries back to plaintext |
dotling vault <action> |
Manage your password-protected encryption Vault |
dotling doctor |
Audit repository health and report issues |
dotling vars <action> |
Manage machine-local template variables |
dotling completions <SHELL> |
Generate shell completion scripts (bash, zsh, fish, elvish, powershell) |
Key Flags
| Command | Flag | Description |
|---|---|---|
all |
-v, --verbose |
Show hints and additional details |
add |
--copy |
Deploy as a copy instead of a symlink |
add |
--encrypt |
Encrypt the file(s) using the vault password |
add |
--template |
Track as a template (.dtmpl): rendered on each sync with machine-local variables |
add |
--os <platform> |
Target OS: linux (alias: darwin), macos, windows (alias: win). Omit for all platforms |
sync |
--dry-run |
Preview changes without modifying anything |
sync |
--force |
Overwrite conflicting files (repo wins) |
sync |
--prefer-actual |
When both sides conflict, prefer the actual file (alias: --prefer-local) |
sync |
--no-interactive |
Do not prompt for conflict resolution; skip conflicting entries and print a warning |
sync |
--allow-hooks |
Allow executing all hooks without prompting |
sync |
--no-hooks |
Disable executing any hooks |
status |
--diff |
Show inline diffs for modified copy entries |
How It Works
dotling moves your config files into a central git repository and replaces them with symlinks (or copies). Each tracked file is recorded in a dotling.toml config at the repo root.
Symlinks (default): the deployed file points to the repo — edits are instantly reflected in your repo. dotling sync ensures the symlink is present and correct. Symlink entries are always repo-authoritative: if the symlink is wrong or missing, sync always pushes from repo to actual.
Copies (--copy): the deployed file is a standalone copy. Useful for apps that don't support symlinks. dotling sync compares content fingerprints first, then modification times, and copies in whichever direction is newer.
Sync Direction
dotling sync decides the direction per entry:
| Entry type | Push (repo → actual) | Pull (actual → repo) |
|---|---|---|
| Symlink | Create/fix symlink | Never (symlink always reads repo) |
| Copy | Source newer or target missing | Target newer |
| Encrypted | .enc newer or target missing → decrypt |
Target newer → re-encrypt into .enc |
When both sides differ and timestamps are equal, dotling defaults to repo wins (push). Pass --prefer-actual to flip this.
Path Mapping
Files are organized into categories automatically:
| Home path | Repo path |
|---|---|
~/.config/nvim/init.lua |
config/nvim/init.lua |
~/.zshrc |
shell/zshrc |
~/.gitconfig |
git/gitconfig |
~/.vimrc |
vim/vimrc |
~/.tmux.conf |
tmux/tmux.conf |
~/.ssh/config |
ssh/config |
~/.gnupg/gpg.conf |
gnupg/gpg.conf |
~/.somerc |
home/somerc |
Configuration
Tracked entries and settings are stored in dotling.toml at the repo root:
# dotling.toml — managed by dotling, safe to hand-edit
[]
= "symlink" # Default sync method: "symlink" or "copy"
[]
= "echo 'Initializing repo...'"
= "echo 'Starting global before-sync hook...'"
= "echo 'Global after-sync hook completed.'"
[]
= "my-mac" # shared default — override in ~/.dotling/vars.toml
= "user" # placeholder
[[]]
= "shell/zshrc"
= "~/.zshrc"
= "echo 'Updating zshrc...'"
= "echo 'zshrc updated!'"
[[]]
= "config/nvim/init.lua"
= "~/.config/nvim/init.lua"
= "copy"
= "0600" # Apply octal permissions on sync
[[]]
= "shell/bashrc"
= "~/.bashrc"
= "linux"
Entry Fields
| Field | Type | Description |
|---|---|---|
source |
string | Repo-relative path (required) |
target |
string | Deploy target path with ~ support (required) |
method |
string | Override: "symlink" or "copy" |
encrypted |
boolean | true, 1, or yes — marks entry as encrypted |
os |
string | "all" (default), "linux", "macos", or "windows" |
permissions |
string | Octal permissions applied on sync, e.g. "0600" |
before |
string | Entry-level hook run before sync |
after |
string | Entry-level hook run after sync |
Note: Template entries don't have a
templatefield. A file is treated as a template when itssourcepath ends with.dtmpl(e.g.shell/zshrc.dtmpl). This suffix is added automatically bydotling add --template.
Multi-OS Support
Tag entries with --os to restrict them to a specific platform:
When deploying, dotling automatically skips entries that don't match the current OS. Entries without an --os flag (or tagged all) deploy everywhere.
Platform aliases: darwin = macos, win = windows.
Encryption Vault
dotling includes a built-in portable encryption Vault protected by Argon2id and ChaCha20-Poly1305. This lets you safely commit API keys, .env files, or ssh configs to your public dotfiles repo.
1. Initialize your Vault
You'll be prompted for a password (entered twice for confirmation). This creates a secure identity in ~/.dotling/vault/.
2. Add a file with encryption
dotling will read your local file, encrypt it, store the ciphertext (config.enc) in your git repo, and deploy the decrypted file locally with secure permissions.
3. Sync encrypted entries
dotling sync handles encrypted entries in both directions. If you edit the deployed plaintext file, running sync will re-encrypt it back into the repo:
# Edit your deployed file, then sync it back
4. Migrating to a new machine
Export your vault as a single encrypted bundle from your old machine:
Then import it on the new machine (you'll be prompted for your vault password) and sync:
5. Editing encrypted files
Use dotling edit to open an encrypted file in your $EDITOR without a manual decrypt/encrypt cycle:
# Opens the decrypted content in $EDITOR, re-encrypts on save
# Works with any tracked path: source path, target path, or partial name
After editing, run dotling sync to push the re-encrypted changes to your deployed file.
Editor resolution: $DOTLING_EDITOR → $VISUAL → $EDITOR → vim → nano → vi. GUI editors (code, subl, zed, pulsar, atom) automatically get --wait appended.
Secure temp files: Decrypted content is written to ~/.dotling/tmp/ with restricted permissions (mode 0600). Temp files are securely wiped (overwritten with zeros) before deletion.
6. Other vault commands
Dotfile Templating
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 cleanly.
How it works
Any file tracked with --template is stored in the repo as <name>.dtmpl. 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 (saved to ~/.dotling/vars.toml, never committed)
# 2. Add a file as a template
# 3. On another machine, sync will detect missing vars and prompt for them
Template syntax
# ~/.config/nix-darwin/flake.nix.dtmpl
darwinConfigurations = {
{{ var.hostname }} = darwin.lib.darwinSystem { ... };
};
# ~/.config/nix-darwin/configuration.nix.dtmpl
= "{{ var.primary_user }}";
| Expression | Description |
|---|---|
{{ 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 |
{{ var.key | upper }} |
Apply a filter (upper, lower, trim, quote, squote) |
{{ var.key | default "fallback" }} |
Use a fallback if the variable is not set |
{{- expr -}} |
Strip surrounding whitespace (also {{- expr }} and {{ expr -}} for one-sided trimming) |
Variable sources
Variables are resolved in priority order:
- Local store —
~/.dotling/vars.toml(machine-specific, never committed) - Config defaults —
[vars]indotling.toml(shared, committed)
Built-in variables (dotling.*) and environment variables (env.*) are separate namespaces resolved directly.
Shared defaults in dotling.toml act as documentation and fallbacks — use placeholders, not real values:
# dotling.toml
[]
= "my-mac" # placeholder — override in ~/.dotling/vars.toml
= "user" # placeholder
Encrypted templates
Sensitive templates (e.g. a config containing tokens) can be both templated and encrypted:
The pipeline on sync is: vault decrypt → render with vars → deploy.
dotling vars reference
Lifecycle Hooks
dotling supports executing hooks (shell commands) globally or per-entry during the sync process. Hooks can be used for actions like reloading your shell, compiling configurations, or running custom setup scripts.
Global Hooks
Global hooks run at the very beginning and very end of the dotling sync session:
init: Command run during repository initialization (also runs when adopting or cloning an existing repo).before: Command run before any entries are synced.after: Command run after all entries are successfully synced.
Entry-level Hooks
You can define hooks specific to individual tracked entries:
before: Command run before this entry is pushed or pulled.after: Command run after this entry is successfully pushed or pulled.
Execution Context
Hooks are executed in the repository root directory. The following environment variables are populated to provide rich runtime context:
| Variable | Description |
|---|---|
DOTLING_HOOK_TYPE |
Type of hook: global_init, global_before, global_after, entry_before, entry_after |
DOTLING_REPO_ROOT |
Absolute path to the dotfiles repository |
DOTLING_DRY_RUN |
"true" if running with --dry-run, otherwise "false" |
DOTLING_ENTRY_SOURCE |
(Entry hooks only) Repo-relative path of the entry's source file/folder |
DOTLING_ENTRY_TARGET |
(Entry hooks only) Target path of the entry's deployed file/folder |
DOTLING_ENTRY_ACTION |
(Entry hooks only) Current action being performed ("push" or "pull") |
Hook Trust System
To protect against malicious code in imported dotfile repositories, dotling prompts for user verification before running a hook for the first time:
⚡ Untrusted hook detected (type: entry_before):
echo "updating shell configuration"
? Do you want to run this hook? [y]es (once) / [n]o (skip) / [a]lways (trust) / [s]kip all >
Selecting always stores the Blake2s-256 hash of the command string in ~/.dotling/state/trusted_hooks so it runs seamlessly on subsequent syncs.
- Pass
--allow-hooks(or setDOTLING_ALLOW_HOOKS=1) to automatically execute all hooks without prompting. - Pass
--no-hooks(or setDOTLING_NO_HOOKS=1) to completely disable hook execution.
Hook Retry
If a hook command exits with a non-zero status, dotling automatically retries it up to 3 times (1 initial attempt + 2 retries) before aborting the sync. A warning is printed after each failed attempt so the failure is always visible.
Conflict Resolution
Three-way Merge
When sync detects a conflict between the repository and your local target, you can choose from the following interactive options:
[s]Show diff: Compare inline changes.[k]Keep Local: Overwrite the repository with your local file (pulls to repo).[r]Use Repo: Overwrite the local file with the repository version (pushes to local).[m]Merge: Performs a standard line-level three-way merge using the last-in-sync snapshot as the base, combining modifications from both the repo (ours) and local target (theirs). Non-overlapping changes are cleanly auto-merged, while overlapping conflicts are highlighted with standard git conflict markers:
The merge outcome is written back to both the local disk and the repository, resolving the conflict.<<<<<<< repo repo version content ======= actual local content >>>>>>> actual[x]Skip: Leave this entry unresolved and continue.
Conflict types dotling detects:
- First seen — a file exists at the target path but was never tracked by dotling (pre-existing file).
- Both modified — both the repo and local file changed since the last sync.
- Timestamp tie — files differ but modification times are identical.
Snapshots used as the merge base are stored in ~/.dotling/snapshots/<source> and are updated after each successful sync of a copy-mode plain file.
Sync Fingerprints
Previously, encrypted entries had to be decrypted to verify their sync state. dotling v0.5.0 introduces lightweight Blake2s-256 sync fingerprints stored in ~/.dotling/fingerprints.toml.
- After each successful sync, dotling records the content hashes of the
.encciphertext and the local plaintext target. - On subsequent
statusorsyncchecks, dotling compares current file hashes against the stored fingerprint. - Benefits: You can run
dotling statusordotling sync --dry-runto audit your system instantly, without entering your vault password. A password is only requested when actual file modifications need to be decrypted or re-encrypted! - For copy-mode plain files and template entries, fingerprints track both repo source and target file hashes, enabling deterministic detection of which side has changed.
Shell Completions
dotling ships with tab-completion support for bash, zsh, fish, elvish, and powershell. Completions cover all subcommands, nested actions, and flags.
Quick install (auto-detect shell)
Manual install
# Bash
# Zsh
# Add to ~/.zshrc if not already present:
# fpath=(~/.zfunc $fpath)
# autoload -Uz compinit && compinit
# Fish
Other shells
# Elvish
# PowerShell
# Then source from your $PROFILE
Environment Variables
dotling reads and respects the following environment variables:
| Variable | Description |
|---|---|
NO_COLOR |
Disables ANSI color output when set (follows the no-color.org standard) |
DOTLING_EDITOR |
Highest-priority editor override for dotling edit |
VISUAL |
Editor override (standard Unix convention) |
EDITOR |
Editor fallback |
DOTLING_ALLOW_HOOKS |
Set to 1 or true to auto-trust and execute all hooks without prompting |
DOTLING_NO_HOOKS |
Set to 1 or true to completely disable hook execution |
HOSTNAME |
Fallback hostname for dotling.hostname built-in if the syscall fails |
USER / USERNAME |
Used for dotling.username built-in (USERNAME is the Windows fallback) |
File Structure
dotling stores all state under ~/.dotling/:
| Path | Purpose |
|---|---|
state.toml |
Registered repo root path |
vars.toml |
Machine-local template variables (never committed) |
fingerprints.toml |
Sync fingerprint store for change detection |
vault/ |
Encryption vault (identity.enc, config.toml) |
snapshots/ |
Plaintext snapshots used as merge base for three-way merge |
state/trusted_hooks |
Blake2s-256 hashes of trusted hook commands |
tmp/ |
Secure temp files for encrypted file editing (auto-cleaned) |
Contributing
Contributions are welcome! See CONTRIBUTING.md for guidelines, development setup, and PR workflow.
License
Licensed under either of:
at your option.