Skip to content

Commit ba708eb

Browse files
authored
Merge pull request #71 from twpayne/xdg
Support XDG Base Directory Specification
2 parents a623863 + 13edec3 commit ba708eb

File tree

4 files changed

+105
-59
lines changed

4 files changed

+105
-59
lines changed

README.md

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ Manage an existing file with `chezmoi`:
6363

6464
$ chezmoi add ~/.bashrc
6565

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

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

7575
Edit the desired state:
7676

@@ -119,16 +119,17 @@ Whereas at work it might be:
119119
name = John Smith
120120
email = john@company.com
121121

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

125126
data:
126127
name: John Smith
127128
email: john@home.org
128129

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

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

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

145146
$ chezmoi edit ~/.gitconfig
146147

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

153154
`chezmoi` will substitute the variables from the `data` section of your
154-
`~/.chezmoi.yaml` file when calculating the desired state of `.gitconfig`.
155+
`~/.config/chezmoi/chezmoi.yaml` file when calculating the desired state of
156+
`.gitconfig`.
155157

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

170-
For example, in your `~/.chezmoi/dot_bashrc.tmpl` you might have:
172+
For example, in your `~/.local/share/chezmoi/dot_bashrc.tmpl` you might have:
171173

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

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

193195
$ chezmoi add ~/.netrc
194196

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

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

207209
### Using templates variables
208210

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

213216
data:
214217
github:
215218
user: <your-github-username>
216219
token: <your-github-token>
217220

218-
Your `~/.chezmoi/private_dot_gitconfig.tmpl` can then contain:
221+
Your `~/.local/share/chezmoi/private_dot_gitconfig.tmpl` can then contain:
219222

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

288-
$ gpg -d ~/.chezmoi.yaml.gpg | chezmoi -c /dev/stdin apply
291+
$ gpg -d ~/.config/chezmoi/chezmoi.yaml.gpg | chezmoi -c /dev/stdin apply
289292

290293

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

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

298302
sourceVCSCommand: hg
299303

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

303308
$ chezmoi source push
304309

@@ -310,6 +315,13 @@ stop `chezmoi` from interpreting extra flags. For example:
310315
The `source` command accepts the usual `-n` and `-v` flags, so you can see
311316
exactly what it will run without executing it.
312317

318+
As a shortcut,
319+
320+
$ chezmoi cd
321+
322+
starts a shell in your source directory, which can be very useful when
323+
performing multiple VCS operations.
324+
313325

314326
## Importing archives
315327

@@ -343,11 +355,11 @@ which lists all the files in the target state.
343355
## Under the hood
344356

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

352364
| Prefix | Effect |
353365
| -------------------- | ----------------------------------------------------------------------------------|
@@ -374,9 +386,9 @@ Different target types allow different prefixes and suffixes.
374386

375387
`chezmoi`, by default, operates on your home directory, but this can be
376388
overridden with the `--target` command line flag or by specifying `targetDir`
377-
in your `~/.chezmoi.yaml`. In theory, you could use `chezmoi` to manage any
378-
aspect of your filesystem. That said, although you can do this, you probably
379-
shouldn't. Existing configuration management tools like
389+
in your `~/.config/chezmoi/chezmoi.yaml`. In theory, you could use `chezmoi` to
390+
manage any aspect of your filesystem. That said, although you can do this, you
391+
probably shouldn't. Existing configuration management tools like
380392
[Puppet](https://puppet.com/), [Chef](https://www.chef.io/chef/),
381393
[Ansible](https://www.ansible.com/), and [Salt](https://www.saltstack.com/) are
382394
much better suited to whole system configuration management.

cmd/root.go

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/spf13/cobra"
1010
"github.com/spf13/viper"
1111
vfs "github.com/twpayne/go-vfs"
12+
xdg "github.com/twpayne/go-xdg"
1213
)
1314

1415
var (
@@ -30,14 +31,19 @@ func init() {
3031
printErrorAndExit(err)
3132
}
3233

34+
x, err := xdg.NewXDG()
35+
if err != nil {
36+
printErrorAndExit(err)
37+
}
38+
3339
persistentFlags := rootCommand.PersistentFlags()
3440

35-
persistentFlags.StringVarP(&configFile, "config", "c", filepath.Join(homeDir, ".chezmoi"), "config file")
41+
persistentFlags.StringVarP(&configFile, "config", "c", getDefaultConfigFile(x, homeDir), "config file")
3642

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

40-
persistentFlags.StringVarP(&config.SourceDir, "source", "s", filepath.Join(homeDir, ".chezmoi"), "source directory")
46+
persistentFlags.StringVarP(&config.SourceDir, "source", "s", getDefaultSourceDir(x, homeDir), "source directory")
4147
viper.BindPFlag("source", persistentFlags.Lookup("source"))
4248

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

5359
cobra.OnInitialize(func() {
54-
// FIXME once https://github.com/spf13/viper/pull/601 is merged, we can
55-
// use viper.SetConfigName instead of looping over possible config file
56-
// names ourself.
57-
for _, extension := range append([]string{""}, viper.SupportedExts...) {
58-
configFileName := configFile
59-
if extension != "" {
60-
configFileName += "." + extension
61-
}
62-
if info, err := os.Stat(configFileName); err != nil || !info.Mode().IsRegular() {
63-
continue
64-
}
65-
viper.SetConfigFile(configFileName)
66-
if err := viper.ReadInConfig(); err != nil {
67-
printErrorAndExit(err)
68-
}
69-
if err := viper.Unmarshal(&config); err != nil {
70-
printErrorAndExit(err)
71-
}
72-
return
60+
viper.SetConfigFile(configFile)
61+
if err := viper.ReadInConfig(); err != nil {
62+
printErrorAndExit(err)
63+
}
64+
if err := viper.Unmarshal(&config); err != nil {
65+
printErrorAndExit(err)
7366
}
7467
})
7568
}
@@ -95,3 +88,41 @@ func (c *Config) persistentPreRunRootE(fs vfs.FS, command *cobra.Command, args [
9588
}
9689
return nil
9790
}
91+
92+
func getDefaultConfigFile(x *xdg.XDG, homeDir string) string {
93+
// Search XDG config directories first.
94+
for _, configDir := range x.ConfigDirs {
95+
for _, extension := range viper.SupportedExts {
96+
configFilePath := filepath.Join(configDir, "chezmoi", "chezmoi."+extension)
97+
if _, err := os.Stat(configFilePath); err == nil {
98+
return configFilePath
99+
}
100+
}
101+
}
102+
// Search for ~/.chezmoi.* for backwards compatibility.
103+
for _, extension := range viper.SupportedExts {
104+
configFilePath := filepath.Join(homeDir, ".chezmoi."+extension)
105+
if _, err := os.Stat(configFilePath); err == nil {
106+
return configFilePath
107+
}
108+
}
109+
// Fallback to XDG default.
110+
return filepath.Join(x.ConfigHome, "chezmoi", "chezmoi.yaml")
111+
}
112+
113+
func getDefaultSourceDir(x *xdg.XDG, homeDir string) string {
114+
// Check for XDG data directories first.
115+
for _, dataDir := range x.DataDirs {
116+
sourceDir := filepath.Join(dataDir, "chezmoi")
117+
if _, err := os.Stat(sourceDir); err == nil {
118+
return sourceDir
119+
}
120+
}
121+
// Check for ~/.chezmoi for backwards compatibility.
122+
sourceDir := filepath.Join(homeDir, ".chezmoi")
123+
if _, err := os.Stat(sourceDir); err == nil {
124+
return sourceDir
125+
}
126+
// Fallback to XDG default.
127+
return filepath.Join(x.DataHome, "chezmoi")
128+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
github.com/stretchr/testify v1.2.2 // indirect
1616
github.com/twpayne/go-shell v0.0.1
1717
github.com/twpayne/go-vfs v0.1.5
18+
github.com/twpayne/go-xdg v1.0.0
1819
github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb
1920
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
2021
gopkg.in/yaml.v2 v2.2.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ github.com/twpayne/go-shell v0.0.1 h1:Ako3cUeuULhWadYk37jM3FlJ8lkSSW4INBjYj9K60G
4646
github.com/twpayne/go-shell v0.0.1/go.mod h1:QCjEvdZndTuPObd+11NYAI1UeNLSuGZVxJ+67Wl+IU4=
4747
github.com/twpayne/go-vfs v0.1.5 h1:bhND91u3uNgs5cUedHfmZDVXqa+WDqpmOc5n4A8T+hI=
4848
github.com/twpayne/go-vfs v0.1.5/go.mod h1:OIXA6zWkcn7Jk46XT7ceYqBMeIkfzJ8WOBhGJM0W4y8=
49+
github.com/twpayne/go-xdg v1.0.0 h1:k+QM2LL00/zx/gvxKsCMRBJ8nxCBWDBe2LU9y3Xo7x0=
50+
github.com/twpayne/go-xdg v1.0.0/go.mod h1:SHOoqWXTQT0rcQM4isbONZ6bH6uwIBgXuymEVgTc+Ao=
4951
github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb h1:tXbazu9ZlecQbyCczvA22mWj+lw/36Bdwxapk8v7e7s=
5052
github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb/go.mod h1:XlXBIfkGawHNVOHlenOaBW7zlfCh8LovwjOgjamYnkQ=
5153
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=

0 commit comments

Comments
 (0)