Skip to content

Commit

Permalink
Support XDG Base Directory Specification, fixes twpayne#19
Browse files Browse the repository at this point in the history
.chezmoi for data and .chezmoi.* for config remain supported for
backwards compatibility.
  • Loading branch information
twpayne committed Dec 13, 2018
1 parent 170548f commit 6449e9c
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 59 deletions.
81 changes: 43 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ Manage an existing file with `chezmoi`:

$ chezmoi add ~/.bashrc

This will create a directory called `~/.chezmoi` with permissions `0600` where
`chezmoi` will store its state, if it does not already exist, and copy
`~/.bashrc` to `~/.chezmoi/dot_bashrc`.
This will create a directory called `~/.local/share/chezmoi` with permissions
`0600` where `chezmoi` will store its state, if it does not already exist, and
copy `~/.bashrc` to `~/.local/share/chezmoi/dot_bashrc`.

You should manage your `~/.chezmoi` directory with the version control system
of your choice. `chezmoi` will ignore all files and directories beginning with
a `.` in this directory, so directories like `.git` and `.hg` will not pollute
your home directory.
You should manage your `~/.local/share/chezmoi` directory with the version
control system of your choice. `chezmoi` will ignore all files and directories
beginning with a `.` in this directory, so directories like `.git` and `.hg`
will not pollute your home directory.

Edit the desired state:

Expand Down Expand Up @@ -119,16 +119,17 @@ Whereas at work it might be:
name = John Smith
email = john@company.com

To handle this, on each machine create a file called `~/.chezmoi.yaml` defining
what might change. For your home machine:
To handle this, on each machine create a file called
`~/.config/chezmoi/chezmoi.yaml` defining what might change. For your home
machine:

data:
name: John Smith
email: john@home.org

If you intend to store private data (e.g. access tokens) in `~/.chezmoi.yaml`,
make sure it has permissions `0600`. See "Keeping data private" below for more
discussion on this.
If you intend to store private data (e.g. access tokens) in
`~/.config/chezmoi/chezmoi.yaml`, make sure it has permissions `0600`. See
"Keeping data private" below for more discussion on this.

If you prefer, you can use any format supported by
[Viper](https://github.com/spf13/viper) for your configuration file. This
Expand All @@ -140,7 +141,7 @@ it in to a template:
$ chezmoi add -T ~/.gitconfig

You can then open the template (which will be saved in the file
`~/.chezmoi/dot_gitconfig.tmpl`):
`~/.local/share/chezmoi/dot_gitconfig.tmpl`):

$ chezmoi edit ~/.gitconfig

Expand All @@ -151,7 +152,8 @@ The file should look something like:
email = {{ .email }}

`chezmoi` will substitute the variables from the `data` section of your
`~/.chezmoi.yaml` file when calculating the desired state of `.gitconfig`.
`~/.config/chezmoi/chezmoi.yaml` file when calculating the desired state of
`.gitconfig`.

For more advanced usage, you can use the full power of the
[`text/template`](https://godoc.org/text/template) language to include or
Expand All @@ -167,7 +169,7 @@ populated variables:
| `chezmoi.os` | Operating system, e.g. `darwin`, `linux`, etc. as returned by [runtime.GOOS](https://godoc.org/runtime#pkg-constants). |
| `chezmoi.username` | The username of the user running `chezmoi`. |

For example, in your `~/.chezmoi/dot_bashrc.tmpl` you might have:
For example, in your `~/.local/share/chezmoi/dot_bashrc.tmpl` you might have:

# common config
export EDITOR=vi
Expand All @@ -187,16 +189,16 @@ to give it an `empty_` prefix. See "Under the hood" below.

`chezmoi` automatically detects when files and directories are private when
adding them by inspecting their permissions. Private files and directories are
stored in `~/.chezmoi` as regular, public files with permissions `0644` and the
name prefix `private_`. For example:
stored in `~/.local/share/chezmoi` as regular, public files with permissions
`0644` and the name prefix `private_`. For example:

$ chezmoi add ~/.netrc

will create `~/.chezmoi/private_dot_netrc` (assuming `~/.netrc` is not world-
or group- readable, as it should be). This file is still private because
`~/.chezmoi` is not group- or world- readable or executable. `chezmoi` checks
that the permissions of `~/.chezmoi` are `0700` on every run and will print a
warning if they are not.
will create `~/.local/share/chezmoi/private_dot_netrc` (assuming `~/.netrc` is
not world- or group- readable, as it should be). This file is still private
because `~/.local/share/chezmoi` is not group- or world- readable or
executable. `chezmoi` checks that the permissions of `~/.local/share/chezmoi`
are `0700` on every run and will print a warning if they are not.

It is common that you need to store access tokens in config files, e.g. a
[Github access
Expand All @@ -206,16 +208,17 @@ your machine.

### Using templates variables

Typically, `~/.chezmoi.yaml` is not checked in to version control and has
permissions 0600. You can store tokens as template values in the `data`
section. For example, if your `~/.chezmoi.yaml` contains:
Typically, `~/.config/chezmoi/chezmoi.yaml` is not checked in to version
control and has permissions 0600. You can store tokens as template values in
the `data` section. For example, if your `~/.config/chezmoi/chezmoi.yaml`
contains:

data:
github:
user: <your-github-username>
token: <your-github-token>

Your `~/.chezmoi/private_dot_gitconfig.tmpl` can then contain:
Your `~/.local/share/chezmoi/private_dot_gitconfig.tmpl` can then contain:

{{- if .github }}
[github]
Expand Down Expand Up @@ -285,20 +288,22 @@ You can query the keyring from the command line:
`chezmoi` takes a `-c` flag specifying the file to read its configuration from.
You can encrypt your configuration and then only decrypt it when needed:

$ gpg -d ~/.chezmoi.yaml.gpg | chezmoi -c /dev/stdin apply
$ gpg -d ~/.config/chezmoi/chezmoi.yaml.gpg | chezmoi -c /dev/stdin apply


## Managing your `~/.chezmoi` directory with version control

`chezmoi` has some helper commands to assist managing your source directory
with version control. The default version control system is `git` but you can
change this by setting `sourceVCSCommand` in your `.chezmoi.yaml` file, for
example, if you want to use Mercurial:
change this by setting `sourceVCSCommand` in your
`~/.config/chezmoi/chezmoi.yaml` file, for example, if you want to use
Mercurial:

sourceVCSCommand: hg

`chezmoi source` is then a shortcut to running `sourceVCSCommand` in your
`~/.chezmoi` directory. For example you can push the current branch with:
`~/.local/share/chezmoi` directory. For example you can push the current branch
with:

$ chezmoi source push

Expand Down Expand Up @@ -332,11 +337,11 @@ to update your home directory.
## Under the hood

`chezmoi` stores the desired state of files, symbolic links, and directories in
regular files and directories in `~/.chezmoi`. This location can be overridden
with the `-s` flag or by giving a value for `sourceDir` in `~/.chezmoi.yaml`.
Some state is encoded in the source names. `chezmoi` ignores all files and
directories in the source directory that begin with a `.`. The following
prefixes and suffixes are special.
regular files and directories in `~/.local/share/chezmoi`. This location can be
overridden with the `-s` flag or by giving a value for `sourceDir` in
`~/.config/chezmoi/chezmoi.yaml`. Some state is encoded in the source names.
`chezmoi` ignores all files and directories in the source directory that begin
with a `.`. The following prefixes and suffixes are special.

| Prefix | Effect |
| -------------------- | ----------------------------------------------------------------------------------|
Expand All @@ -363,9 +368,9 @@ Different target types allow different prefixes and suffixes.

`chezmoi`, by default, operates on your home directory, but this can be
overridden with the `--target` command line flag or by specifying `targetDir`
in your `~/.chezmoi.yaml`. In theory, you could use `chezmoi` to manage any
aspect of your filesystem. That said, although you can do this, you probably
shouldn't. Existing configuration management tools like
in your `~/.config/chezmoi/chezmoi.yaml`. In theory, you could use `chezmoi` to
manage any aspect of your filesystem. That said, although you can do this, you
probably shouldn't. Existing configuration management tools like
[Puppet](https://puppet.com/), [Chef](https://www.chef.io/chef/),
[Ansible](https://www.ansible.com/), and [Salt](https://www.saltstack.com/) are
much better suited to whole system configuration management.
Expand Down
73 changes: 52 additions & 21 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
vfs "github.com/twpayne/go-vfs"
xdg "github.com/twpayne/go-xdg"
)

var (
Expand All @@ -30,14 +31,19 @@ func init() {
printErrorAndExit(err)
}

x, err := xdg.NewXDG()
if err != nil {
printErrorAndExit(err)
}

persistentFlags := rootCommand.PersistentFlags()

persistentFlags.StringVarP(&configFile, "config", "c", filepath.Join(homeDir, ".chezmoi"), "config file")
persistentFlags.StringVarP(&configFile, "config", "c", getDefaultConfigFile(x, homeDir), "config file")

persistentFlags.BoolVarP(&config.DryRun, "dry-run", "n", false, "dry run")
viper.BindPFlag("dry-run", persistentFlags.Lookup("dry-run"))

persistentFlags.StringVarP(&config.SourceDir, "source", "s", filepath.Join(homeDir, ".chezmoi"), "source directory")
persistentFlags.StringVarP(&config.SourceDir, "source", "s", getDefaultSourceDir(x, homeDir), "source directory")
viper.BindPFlag("source", persistentFlags.Lookup("source"))

persistentFlags.StringVarP(&config.TargetDir, "target", "t", homeDir, "target directory")
Expand All @@ -51,25 +57,12 @@ func init() {
viper.BindPFlag("verbose", persistentFlags.Lookup("verbose"))

cobra.OnInitialize(func() {
// FIXME once https://github.com/spf13/viper/pull/601 is merged, we can
// use viper.SetConfigName instead of looping over possible config file
// names ourself.
for _, extension := range append([]string{""}, viper.SupportedExts...) {
configFileName := configFile
if extension != "" {
configFileName += "." + extension
}
if info, err := os.Stat(configFileName); err != nil || !info.Mode().IsRegular() {
continue
}
viper.SetConfigFile(configFileName)
if err := viper.ReadInConfig(); err != nil {
printErrorAndExit(err)
}
if err := viper.Unmarshal(&config); err != nil {
printErrorAndExit(err)
}
return
viper.SetConfigFile(configFile)
if err := viper.ReadInConfig(); err != nil {
printErrorAndExit(err)
}
if err := viper.Unmarshal(&config); err != nil {
printErrorAndExit(err)
}
})
}
Expand All @@ -95,3 +88,41 @@ func (c *Config) persistentPreRunRootE(fs vfs.FS, command *cobra.Command, args [
}
return nil
}

func getDefaultConfigFile(x *xdg.XDG, homeDir string) string {
// Search XDG config directories first.
for _, configDir := range x.ConfigDirs {
for _, extension := range viper.SupportedExts {
configFilePath := filepath.Join(configDir, "chezmoi", "chezmoi."+extension)
if _, err := os.Stat(configFilePath); err == nil {
return configFilePath
}
}
}
// Search for ~/.chezmoi.* for backwards compatibility.
for _, extension := range viper.SupportedExts {
configFilePath := filepath.Join(homeDir, ".chezmoi."+extension)
if _, err := os.Stat(configFilePath); err == nil {
return configFilePath
}
}
// Fallback to XDG default.
return filepath.Join(x.ConfigHome, "chezmoi", "chezmoi.yaml")
}

func getDefaultSourceDir(x *xdg.XDG, homeDir string) string {
// Check for XDG data directories first.
for _, dataDir := range x.DataDirs {
sourceDir := filepath.Join(dataDir, "chezmoi")
if _, err := os.Stat(sourceDir); err == nil {
return sourceDir
}
}
// Check for ~/.chezmoi for backwards compatibility.
sourceDir := filepath.Join(homeDir, ".chezmoi")
if _, err := os.Stat(sourceDir); err == nil {
return sourceDir
}
// Fallback to XDG default.
return filepath.Join(x.DataHome, "chezmoi")
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/stretchr/testify v1.2.2 // indirect
github.com/twpayne/go-shell v0.0.1
github.com/twpayne/go-vfs v0.1.5
github.com/twpayne/go-xdg v1.0.0
github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
gopkg.in/yaml.v2 v2.2.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ github.com/twpayne/go-shell v0.0.1 h1:Ako3cUeuULhWadYk37jM3FlJ8lkSSW4INBjYj9K60G
github.com/twpayne/go-shell v0.0.1/go.mod h1:QCjEvdZndTuPObd+11NYAI1UeNLSuGZVxJ+67Wl+IU4=
github.com/twpayne/go-vfs v0.1.5 h1:bhND91u3uNgs5cUedHfmZDVXqa+WDqpmOc5n4A8T+hI=
github.com/twpayne/go-vfs v0.1.5/go.mod h1:OIXA6zWkcn7Jk46XT7ceYqBMeIkfzJ8WOBhGJM0W4y8=
github.com/twpayne/go-xdg v1.0.0 h1:k+QM2LL00/zx/gvxKsCMRBJ8nxCBWDBe2LU9y3Xo7x0=
github.com/twpayne/go-xdg v1.0.0/go.mod h1:SHOoqWXTQT0rcQM4isbONZ6bH6uwIBgXuymEVgTc+Ao=
github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb h1:tXbazu9ZlecQbyCczvA22mWj+lw/36Bdwxapk8v7e7s=
github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb/go.mod h1:XlXBIfkGawHNVOHlenOaBW7zlfCh8LovwjOgjamYnkQ=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
Expand Down

0 comments on commit 6449e9c

Please sign in to comment.