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:
go doc -all -http -u chezmoi.io/chezmoi/v2and 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 theSystem 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. OtherSystems are composed on top of this to provide further functionality. For
example, the --debug flag is implemented by wrapping the RealSystem with aDebugSystem 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 theTargetStateEntry 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 theActualStateEntry interface, with ActualStateAbsent, ActualStateDir,ActualStateFile, ActualStateSymlink structs implementing this interface.
Finally, an EntryState struct represents a serialization of anActualEntryState 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:
Read the source state from the source directory.
For each entry in the source state (
SourceStateEntry), compute itsTargetStateEntryand read its actual state in the destination state
(ActualStateEntry).If the
ActualStateEntryis not equivalent to theTargetStateEntrythen
apply the minimal set of changes to theActualStateEntryso 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'sAnnotations 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 ininternal/cmd/testdata/scripts/$TEST_NAME.txtar.
You can run individual end-to-end tests with
go test ./internal/cmd -run=TestScript/$TEST_NAMETests 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
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 inassets/chezmoi.io/docs/user-guide/and a complete description inassets/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 variableCHEZMOIDEVtoignoreflags=1,ignorehelp=1.
Once you have documented the command and its flags, runmake generateto
generate the embedded documentation.All generated files are up to date. You can ensure this by running
make generateand 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
runningmake 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 aboveExamples 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
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:
git clone https://github.com/twpayne/chezmoi.git
cd chezmoiBuild chezmoi:
go buildRun all tests:
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:
go tool chezmoiRun a set of smoke tests, including cross-compilation, tests, and linting:
make smoke-testTest building chezmoi for all architectures:
make test-release!!! hint
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
make generateif 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:
-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
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
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
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
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:
sudo apt-get install musl-tools snapcraftThen run:
make test-releasePublishing
Publish a new release by creating and pushing a tag, for example:
git tag v1.2.3
git push --tagsThis 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:
Run snapcraft whoami
Store operation failed:
- macaroon-authorization-required: The request is missing an Authorization header field containing a valid macaroonCreate new snapcraft store credentials by running:
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:
sudo snap install --classic snapcraft
snapcraft loginHomebrew
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:
brew bump-formula-pr --tag=v1.2.3 chezmoiScoop
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 thereleasestep ofreleasejob of themain
workflow.The cosign public key is included in the release assets and also uploaded to
https://chezmoi.io/cosign.pub. Sincehttps://chezmoi.iois 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:
Unit testing, using
testing, andgithub.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 ininternal/chezmoi/*_test.go.File system integration tests, using
testingandgithub.com/twpayne/go-vfs/v5, test chezmoi's effects on the file
system. This include some tests ininternal/chezmoi/*_test.go, and higher
level command tests ininternal/cmd/*cmd_test.go.High-level integration tests using
github.com/rogpeppe/go-internal/testscriptare ininternal/cmd/testdata/scripts/*.txtarand are run byinternal/cmd/main_test.go.Linux distribution and OS tests run the full test suite using Docker for
different Linux distributions (inassets/docker) and Vagrant for different
OSes (inassets/vagrant). Windows tests are run in GitHub Actions.