Motivation
Dotfile management is hard. Most of us are doing it with complicated
series of batch scripts, and some of the smarter among us are using
ansible to do the job. Ansible is, however, quite heavy weight for this
task, and not really well suited to the simple set-and-forget nature of
dotfile directories, however well it does work.
dotfile-playbook should DWIM, and should be idempotent (i.e., it should
be safe to run as many times as the user wishes without causing issues).
This blog post serves as a rough outline for the project, and may be
edited as time goes on.
Layout
.
|-- data
| |-- bar
| |-- baz
| `-- foo
|-- files
| |-- Xmodmap
| |-- bashrc
| |-- config
| | |-- fish
| | | `-- config.fish
| | |-- i3
| | | `-- config
| | `-- riot-web
| | `-- config.json
| |-- data
| | `-- exampleapp
| | `-- example.db
| |-- emacs.el
| `-- my-x-profile
|-- packages.yml
`-- recipies.yml
Phases
Package installation
dotfile-playbook should first check packages.yml
and attempt to
install any packages that don't already exist in some sort of state
saving directory.
Dotfile symlinking
dotfile-playbook should, mostly automatically based on the structure of
the repository, generate symlinks for the user. It should automatically
create any parent directories that don't already exist.
It should try to respect the XDG Base Directory environment variables,
but if they aren't set, it should default to the 'normal' locations
(e.g. ~/.config/
{.verbatim} for $XDG_CONFIG_HOME
{.verbatim})
It should generally not create symlinks to directories, instead creating
the directory and symlinking to each file in it individually.
DWIM linking
-
XDG dir linking
dotfile-playbook should first iterate through each of the folders in
the files
directory, and associate each one with an XDG base
directory (e.g. files/conifg
would be associated with
$XDG_CONFIG_HOME
).
Each XDG folder would then have its contents 'overlayed' with
symlinks to those contents in their associated folder. (e.g. in the
[above example]{.spurious-link target=”*Layout”}
$XDG_CONFIG_HOME/fish/config.fish
would become a symlink to
files/config/fish/config.fish
).
-
Direct file linking
Each file inside of files/
, but not inside a subfolder (e.g.
bashrc
) will have a symlink generated to it in the users
homefolder, with the name of that symlink being the name of the
file, but prefixed with a dot (e.g. files/bashrc
becomes
~/.bashrc
{.verbatim}).
Override linking
dotfile-playbook should parse the recipes.yml
{.verbatim} file, and
look for any applicable linkOverride
{.verbatim} map, with a format
like so
linkOverride:
"my-x-profile": "~/.Xprofile"
"emacs.el":
- "~/.emacs.el"
- "~/.emacs-2.d/init.el"
"config/riot-web": "$XDG_CONFIG_HOME/Riot"
This allows overriding the default link location for files or folders.
Keys that are directories should "overlay" the source onto the
destination, in the same way that the DWIM linking does. Keys that are
files should create a direct link. All keys are paths relative to the
files
{.verbatim} directory. Keys that show up in an applicable
linkOverride
{.verbatim} should not have the DWIM linking applied to
them.
A list may be used as the value, in this case, all the specified links
are created.
In this example (assuming .config
{.verbatim} for the xdg config
directory):
~/.Xprofile
{.verbatim} is a symlink to
files/my-x-profile
{.verbatim}
~/.emacs.el
{.verbatim} is a symlink to files/emacs.el
{.verbatim}
~/.emacs-2.d/init.el
{.verbatim} is a symlink to
files/emacs.el
{.verbatim}
~/.config/Riot/config.json
{.verbatim} is a symlink to
files/riot-web/config.json
{.verbatim}
Recipes
The recipes.yml
{.verbatim} can contain link overrides and additional
conditional actions.
Conditionals
The two conditional blocks are as follows, executing the described
actions/overrides when the value of the key is equal to the value given,
or when they are not equal, as follows. Multiple conditions may be
specified, and will be combined with an 'and' operation.
Conditionals may be arbitrarily nested.
When:
when:
(condition): (value)
commands:
- (action1):
- a
- b
- c
- (action2):
- d
- e
- f
- linkOverrides:
"a": "b"
"c": "d"
when-not:
when:
(condition): (value)
commands:
- (action1):
- a
- b
- c
- (action2):
- d
- e
- f
- linkOverrides:
"a": "b"
"c": "d"
Supported conditions:
-
distro
{.verbatim}
the distro key will either be the operating system, for general
unixen (e.g. darwin
{.verbatim} for macOS, freebsd
{.verbatim},
netbsd
{.verbatim}, etc.), or the distribution if it is linux
(arch
{.verbatim}, ubuntu
{.verbatim}, fedora
{.verbatim})
-
linux
{.verbatim}
Will be true
{.verbatim} if dotfile-playbook is being run on linux,
false
{.verbatim} otherwise
-
release
{.verbatim}
Will be the VERSION_ID
{.verbatim} or equivalent from
/etc/os-release
{.verbatim} if it exists, or the OS version number
on non-linux unixen
-
hostname
{.verbatim}
Will be the output of hostname -s
{.verbatim}
-
hostname-full
{.verbatim}
Will be the output of hostname -f
{.verbatim}
-
domain
{.verbatim}
Will be the output of hostname -d
{.verbatim}
Actions:
Actions can either be a key in a conditional, or can be free floating in
the recipes.yml
{.verbatim}
Link
Manually links a file or directory. This is independent of the DWIM
linking, and, unlike the DWIM linking, will create a symlink to a
directory if asked.
It will perform this action for all keys specified under it.
Example:
link:
"data/foo": "some/random/directory/foo"
Copy
Unconditionally copies a file, overriding it if it exists.
Will overwrite the file every time dotfile-playbook is ran.
Example:
copy:
"data/foo": "some/random/directory/foo"
Copy-Once
Same as copy
{.verbatim}, but will not overwrite the file if it exists.
Example:
copy-once:
"data/bar": "some/random/directory/bar"