chezmoi Documentation

Manage your dotfiles across multiple diverse machines, securely.

RAW Doc

File: README.md

chezmoi

Manage your dotfiles across multiple diverse machines, securely.

chezmoi's documentation is at chezmoi.io.

If you're contributing to chezmoi, then please read the developer guide.

Contributors

License

MIT


File: assets/chezmoi.io/docs/developer-guide/architecture.md

Architecture

This document gives a high-level overview of chezmoi's source code for anyone
interested in contributing to chezmoi.

You can generate Go documentation for chezmoi's source code with go doc, for
example:

sh
go doc -all -http -u chezmoi.io/chezmoi/v2

and then visit http://localhost:$PORT/chezmoi.io/chezmoi/v2 in your browser.

You can also browse chezmoi's generated documentation online.

Directory structure

The important directories in chezmoi are:

Directory Contents
assets/chezmoi.io/docs/ The documentation single source of truth. Help text, examples, and the chezmoi.io website are generated from the files in this directory
internal/chezmoi/ chezmoi's core functionality
internal/cmd/ Code for the chezmoi command
internal/cmd/testdata/scripts/ High-level tests of chezmoi's commands using testscript

Key concepts

As described in the reference manual, chezmoi evaluates the source state
to compute a target state for the destination directory (typically your home
directory). It then compares the target state to the actual state of the
destination directory and performs any changes necessary to update the
destination directory to match the target state. These concepts are represented
directly in chezmoi's code.

chezmoi uses the generic term entry to describe something that it manages.
Entries can be files, directories, symlinks, scripts, amongst other things.

`internal/chezmoi/` directory

All of chezmoi's interaction with the operating system is abstracted through the
System interface. A System includes functionality to read and write files
and directories and execute commands. chezmoi makes a distinction between
idempotent commands that can be run multiple times without modifying the
underlying system and arbitrary commands that may modify the underlying system.

The real underlying system is implemented via a RealSystem struct. Other
Systems are composed on top of this to provide further functionality. For
example, the --debug flag is implemented by wrapping the RealSystem with a
DebugSystem that logs all calls to the underlying RealSystem. --dry-run is
implemented by wrapping the RealSystem with a DryRunSystem that allows reads
to pass through but silently discards all writes.

The SourceState struct represents a source state, including reading a source
state from the source directory, executing templates, applying the source state
(i.e. updating a System to match the desired source state), and adding more
entries to the source state.

Entries in the source state are abstracted by the SourceStateEntry interface
implemented by the SourceStateFile and SourceStateDir structs, as the source
state only consists of regular files and directories.

A SourceStateFile includes a FileAttr struct describing the attributes
parsed from its file name. Similarly, a SourceStateDir includes a DirAttr
struct describing the directory attributes parsed from a directory name.

SourceStateEntrys can compute their target state entries, i.e. what the
equivalent entry should be in the target state, abstracted by the
TargetStateEntry interface.

Actual target state entries include TargetStateFile structs, representing a
file with contents and permissions, TargetStateDir structs, representing a
directory, TargetStateSymlink for symlinks, TargetStateRemove for entries
that should be removed, and TargetStateScript for scripts that should be run.

The actual state of an entry in the target state is abstracted via the
ActualStateEntry interface, with ActualStateAbsent, ActualStateDir,
ActualStateFile, ActualStateSymlink structs implementing this interface.

Finally, an EntryState struct represents a serialization of an
ActualEntryState for storage in and retrieval from chezmoi's persistent state.
It stores a SHA256 of the entry's contents, rather than the full contents, to
avoid storing secrets in the persistent state.

With these concepts, chezmoi's apply command is effectively:

  1. Read the source state from the source directory.

  2. For each entry in the source state (SourceStateEntry), compute its
    TargetStateEntry and read its actual state in the destination state
    (ActualStateEntry).

  3. If the ActualStateEntry is not equivalent to the TargetStateEntry then
    apply the minimal set of changes to the ActualStateEntry so that they are
    equivalent.

Furthermore, chezmoi stores the EntryState of each entry that it writes in its
persistent state. chezmoi can then detect if a third party has updated a target
since chezmoi last wrote it by comparing the actual state entry in the target
state with the entry state in the persistent state.

`internal/cmd/` directory

internal/cmd/*cmd.go files contain the code for each individual command.
internal/cmd/*templatefuncs.go files contain the template functions.

Commands are defined as methods on the Config struct. The Config struct is
large, containing all configuration values read from the config file, command
line arguments, and computed and cached values.

The Config.persistentPreRunRootE and Config.persistentPostRunRootE methods
set up and tear down state for individual commands based on the command's
Annotations field, which defines how the command interacts with the file
system and persistent state.

Path handling

chezmoi uses separate types for absolute paths (AbsPath) and relative paths
(RelPath) to avoid errors where paths are combined (e.g. joining two absolute
paths is an error). The type SourceRelPath is a relative path within the
source directory and handles file and directory attributes.

Internally, chezmoi normalizes all paths to use forward slashes with an optional
upper-cased Windows volume so they can be compared with string comparisons.
Paths read from the user may include tilde (~) to represent the user's home
directory, use forward or backward slashes, and are treated as external paths
(ExtPath). These are normalized to absolute paths. chezmoi is case-sensitive
internally and makes no attempt to handle case-insensitive or case-preserving
file systems.

Persistent state

Persistent state is treated as a two-level key-value store with the
pseudo-structure map[Bucket]map[Key]Value, where Bucket, Key, and Value
are all []bytes. The PersistentState interface defines interaction with
them. Sometimes temporary persistent states are used. For example, in dry run
mode (--dry-run) the actual persistent state is copied into a temporary
persistent state in memory which remembers writes but does not persist them to
disk.

Encryption

Encryption tools are abstracted by the Encryption interface that contains
methods of encrypting and decrypting files and []bytes. Implementations are
the AGEEncryption and GPGEncryption structs. A DebugEncryption struct
wraps an Encryption interface and logs the methods called.

`run_once_` and `run_onchange_` scripts

The execution of a run_once_ script is recorded by storing the SHA256 of its
contents in the scriptState bucket in the persistent state. On future
invocations the script is only run if no matching contents SHA256 is found in
the persistent state.

The execution of a run_onchange_ script is recorded by storing its target name
in the entryState bucket along with its contents SHA256 sum. On future
invocations the script is only run if its contents SHA256 sum has changed, and
its contents SHA256 sum is then updated in the persistent state.

Testing

chezmoi has a mix of unit, integration, and end-to-end tests. Unit and
integration tests use the github.com/alecthomas/assert/v2 framework.
End-to-end tests use github.com/rogpeppe/go-internal/testscript
with the test scripts themselves in
internal/cmd/testdata/scripts/$TEST_NAME.txtar.

You can run individual end-to-end tests with

sh
go test ./internal/cmd -run=TestScript/$TEST_NAME

Tests should, if at all possible, run unmodified on all operating systems tested
in CI (Linux, macOS, Windows, and FreeBSD). Windows will sometimes need special
handling due to its path separator and lack of POSIX-style file permissions.


File: assets/chezmoi.io/docs/developer-guide/building-on-top-of-chezmoi.md

Building on top of chezmoi

chezmoi is designed with UNIX-style composability in mind, and the command line
tool is semantically versioned. Building on top of chezmoi should primarily be
done by executing the chezmoi binary with arguments and the standard input and
output configured appropriately. The chezmoi dump and chezmoi state commands
allows the inspection of chezmoi's internal state.


File: assets/chezmoi.io/docs/developer-guide/contributing-changes.md

Contributing changes

!!! warning

text
chezmoi has a strict zero-tolerance policy on LLM contributions. If you use
an LLM (Large Language Model, like ChatGPT, Claude, Gemini, GitHub Copilot,
or Llama) to make any kind of contribution then you will immediately be
banned without recourse. See [`CODE_OF_CONDUCT.md`][coc] for more
information.

Bug reports, bug fixes, and documentation improvements are always welcome.
Please open an issue or create a pull request with your report,
fix, or improvement.

If you want to make a more significant change, please first
open an issue to discuss the change that you want to make. Dave Cheney
gives a good rationale as to why this is important.

All changes are made via pull requests. In your pull request, please make sure
that:

  • All existing tests pass. You can ensure this by running make test.

  • There are appropriate additional tests that demonstrate that your PR works as
    intended.

  • The documentation is updated, if necessary. For new features you should add an
    entry in assets/chezmoi.io/docs/user-guide/ and a complete description in
    assets/chezmoi.io/docs/reference/. See the website developer guide
    for instructions on how to build and view a local version of the documentation.
    By default, chezmoi will panic if a flag is undocumented or a long help is
    missing for a command. You can disable this panic during development by
    setting the environment variable CHEZMOIDEV to ignoreflags=1,ignorehelp=1.
    Once you have documented the command and its flags, run make generate to
    generate the embedded documentation.

  • All generated files are up to date. You can ensure this by running make generate and including any modified files in your commit.

  • The code is correctly formatted. You can ensure this by running make format.

  • The code passes golangci-lint. You can ensure this by
    running make lint.

  • The commit messages adhere to the conventional commits specification,
    with the following additional requirements:

    text
    * The first character of the commit message is uppercase, e.g:
    
        ```text
        chore: Fix typo in test
        ```
    
      The purpose of this is to maintain consistency in chezmoi's release
      notes, which are generated directly from the commit messages.
    
    * The commits do not have scopes (e.g. `chore(scope): Message` is invalid).

    The following criteria can be used to determine the commit type:

    text
    * `fix`: bug fixes in chezmoi code
    * `feat`: extending an existing feature or adding a new feature
    * `docs`: adding to or updating the documentation
    * `chore`: small changes, such as fixing a typo, correcting grammar
      (including in documentation), or anything not covered by the above

    Examples can be found in the commit history.

  • Commits are logically separate, with no merge or "fixup" commits.

  • The branch applies cleanly to master.


File: assets/chezmoi.io/docs/developer-guide/index.md

Developer guide

!!! warning

text
If you use an LLM (Large Language Model, like ChatGPT, Claude, Gemini,
GitHub Copilot, or Llama) to make any kind of contribution then you will
immediately be banned without recourse. See [`CODE_OF_CONDUCT.md`][coc]
for more information.

chezmoi is written in Go and development happens on GitHub.
chezmoi is a standard Go project, using standard Go tooling. chezmoi requires
Go 1.25 or later.

Checkout chezmoi:

sh
git clone https://github.com/twpayne/chezmoi.git
cd chezmoi

Build chezmoi:

sh
go build

Run all tests:

sh
go test ./...

chezmoi's tests include integration tests with other software. If the other
software is not found in $PATH the tests will be skipped. Running the full set
of tests requires age, base64, bash, bzip2, git, gpg, gzip,
perl, python3, rage, ruby, sed, sha256sum, tr, true, unzip,
xz, zip, and zstd.

Run chezmoi:

sh
go tool chezmoi

Run a set of smoke tests, including cross-compilation, tests, and linting:

sh
make smoke-test

Test building chezmoi for all architectures:

sh
make test-release

!!! hint

text
If you use `fish` as your primary shell, you may get warnings from Fish
during tests:

```text
error: can not save history
warning-path: Unable to locate data directory derived from $HOME: '/home/user/.local/share/fish'.
warning-path: The error was 'Operation not supported'.
warning-path: Please set $HOME to a directory where you have write access.
```

These can be avoided with by running tests with `SHELL=bash` or `SHELL=zsh`:

```sh
SHELL=bash make test
SHELL=zsh make smoke-test
SHELL=bash go test ./...
```

File: assets/chezmoi.io/docs/developer-guide/install-script.md

Install script

chezmoi generates the install script from a single source of truth.
You must run

sh
make generate

if your change includes any of the following:

  • Modifications to the install script template.

  • Additions or modifications to the list of supported operating systems and
    architectures.

chezmoi's continuous integration verifies that all generated files are up to
date. Changes to generated files should be included in the commit that modifies
the source of truth.


File: assets/chezmoi.io/docs/developer-guide/packaging.md

Packaging

If you're packaging chezmoi for an operating system or distribution:

chezmoi has no build dependencies other than the standard Go toolchain.

chezmoi has no runtime dependencies, but is usually used with git, so many
packagers choose to make git an install dependency or recommended package.

Please set the version number, git commit, and build time in the binary. This
greatly assists debugging when end users report problems or ask for help. You
can do this by passing the following flags to go build:

text
-ldflags "-X main.version=$VERSION
          -X main.commit=$COMMIT
          -X main.date=$DATE
          -X main.builtBy=$BUILT_BY"

$VERSION should be the chezmoi version, e.g. 1.7.3. Any v prefix is
optional and will be stripped, so you can pass the git tag in directly.

!!! hint

text
The command `git describe --abbrev=0 --tags` will return a suitable value
for `$VERSION`.

$COMMIT should be the full git commit hash at which chezmoi is built, e.g.
4d678ce6850c9d81c7ab2fe0d8f20c1547688b91.

!!! hint

text
The `internal/cmds/generate-commit/main.go` script will return a suitable value
for `$COMMIT`.

You can run it with `go tool generate-commit`.

!!! hint

text
The source archive contains a file called `COMMIT` containing the commit
hash.

$DATE should be the date of the build as a UNIX timestamp or in RFC3339
format.

!!! hint

text
The command `git show -s --format=%ct HEAD` returns the UNIX timestamp of
the last commit, e.g. `1636668628`.

The command `date -u +%Y-%m-%dT%H:%M:%SZ` returns the current time in
RFC3339 format, e.g. `2019-11-23T18:29:25Z`.

$BUILT_BY should be a string indicating what system was used to build the
binary. Typically it should be the name of your packaging system, e.g.
homebrew.

Please enable cgo, if possible. chezmoi can be built and run without cgo, but
the .chezmoi.username and .chezmoi.group template variables may not be set
correctly on some systems.

chezmoi includes an upgrade command which attempts to self-upgrade. You can
remove this command completely by building chezmoi with the noupgrade build
tag.

chezmoi includes shell completions in the completions directory. Please
include these in the package and install them in the shell-appropriate
directory, if possible.

If the instructions for installing chezmoi in chezmoi's install guide
are absent or incorrect, please open an issue or submit a PR to correct them.


File: assets/chezmoi.io/docs/developer-guide/releases.md

Releases

Releases are managed with goreleaser.

Testing

To build a test release, without publishing, (Ubuntu Linux only) first ensure
that the musl-tools and snapcraft packages are installed:

sh
sudo apt-get install musl-tools snapcraft

Then run:

sh
make test-release

Publishing

Publish a new release by creating and pushing a tag, for example:

sh
git tag v1.2.3
git push --tags

This triggers a GitHub Action that builds and publishes archives,
packages, and snaps, creates a new GitHub Release, and deploys the
website.

Snaps

Publishing Snaps requires a SNAPCRAFT_STORE_CREDENTIALS repository
secret
.

Snapcraft store credentials periodically expire. This is visible in the release
GitHub Action reporting:

text
Run snapcraft whoami
Store operation failed:
- macaroon-authorization-required: The request is missing an Authorization header field containing a valid macaroon

Create new snapcraft store credentials by running:

sh
snapcraft export-login --snaps=chezmoi --channels=stable,candidate,beta,edge --acls=package_upload -

This command requires a Ubuntu machine with snapcraft installed and a keyring,
which is typically not available over SSH connections. Login to a GNOME session
on a Ubuntu machine and run:

sh
sudo snap install --classic snapcraft
snapcraft login

Homebrew

Homebrew automation will automatically detect new releases of chezmoi within
a few hours and open a pull request in
github.com/Homebrew/homebrew-core to bump the version.

If needed, the pull request can be created with:

sh
brew bump-formula-pr --tag=v1.2.3 chezmoi

Scoop

chezmoi is in Scoop's Main bucket. Scoop's automation will
automatically detect new releases within a few hours.

Signing

chezmoi uses GoReleaser's support for signing to sign the checksums
of its release assets with cosign.

Details:

  • The cosign private key was generated with cosign v1.12.1 on a private
    recently-installed Ubuntu 22.04.1 system with a single user and all available
    updates applied.

  • The private key uses a long (more than 32 character) password generated
    locally by a password manager.

  • The password-protected private key is stored in chezmoi's public GitHub repo.

  • The private key's password is stored as a GitHub Actions secret
    and only available to the release step of release job of the main
    workflow.

  • The cosign public key is included in the release assets and also uploaded to
    https://chezmoi.io/cosign.pub. Since
    https://chezmoi.io is served by GitHub pages, it
    probably has equivalent security to chezmoi's GitHub Releases page,
    which is also managed by GitHub.


File: assets/chezmoi.io/docs/developer-guide/security.md

Security

Supported versions

Only the most recent version of chezmoi is supported with security updates.

Virus scanner false positives

Virus scanning software, especially on Windows machines, occasionally report
viruses or trojans in the chezmoi binary. This is almost certainly a false
positive.

For more information see Why does my virus-scanning software think my Go
distribution or compiled binary is infected?
in
the Go FAQ.

Reporting a vulnerability

Please report vulnerabilities by opening a GitHub issue or sending an
email to [email protected].

Security thanks

Security problems are reported privately and do not show up in the list of
contributors. chezmoi thanks:

Organizations

  • Secur0 who performed an extensive security analysis of
    chezmoi.

Individuals

  • eltiburon7
  • gueco
  • julichaan
  • Krypt3d (multiple vulnerabilities)
  • pulpo
  • reddev (multiple vulnerabilities)
  • TheRedP4nther (multiple vulnerabilities)

File: assets/chezmoi.io/docs/developer-guide/testing.md

Testing

chezmoi uses multiple levels of testing:

  1. Unit testing, using testing, and
    github.com/alecthomas/assert/v2, tests that functions and small
    components behave as expected for a wide range of inputs, especially edge
    cases. These are generally found in internal/chezmoi/*_test.go.

  2. File system integration tests, using testing and
    github.com/twpayne/go-vfs/v5, test chezmoi's effects on the file
    system. This include some tests in internal/chezmoi/*_test.go, and higher
    level command tests in internal/cmd/*cmd_test.go.

  3. High-level integration tests using
    github.com/rogpeppe/go-internal/testscript are in
    internal/cmd/testdata/scripts/*.txtar and are run by
    internal/cmd/main_test.go.

  4. Linux distribution and OS tests run the full test suite using Docker for
    different Linux distributions (in assets/docker) and Vagrant for different
    OSes (in assets/vagrant). Windows tests are run in GitHub Actions.