Welcome to Dotfilers! A CLI tool that automates the management of system configuration files (dotfiles) simply and transparently with blazing fast efficiency.
This project began as a simple script to help keep my dotfiles repository in check with minimal effort (laziness ftw :P). Eventually, as things grew, so did the sophistication of my script, to the point where I felt the idea it leveraged was robust enough to be generalized. Hence this project!
Dotfilers helps you manage your system configuration files with ease. It's implementation is also pretty straightforward! Essentially, it's all symbolic links 😃. Symbolic links are very powerful as they allow us keep files up to date with pointers that reference those files from any part of our file system. What's more, paths to these pointers can also be used in place of the path to the actual file being referred to because the pointer's path still resolves to the actual file.
However, for all this to work, the CLI imposes a minimal required structure on your dotfiles repository, so certain things can be kept track of.
The CLI requires that you encapsulate configuration files into special directories known as "configuration groups" or "config group" for short. A config group is a directory of system configuration files (preferably related, but not a hard requirement) with a special association file, called a destinations.json
file, that maps each configuration file to a destination path.
Although, it sounds very simple (because it is), this file is has added capabilities such as:
- The ability to configure a destination for all configuration files at once using the
all
key which also doubles as a value if you wanted to set all configuration files to be excluded withexclude
key. - We can direct sets of configuration files to a single destination path using globs as keys in the file
- Paths support all available shell variables, even the
~
. Destination paths must be absolute, not relative
Config groups can have any name, but names that collectively represent the nature or use case of the configuration files housed within them are best. For instance, if you had a config group that contained all your git
configuration, you wouldn't name it cherry
. The name git
is better because it is semantic.
Here is an example of what your dotfiles directory structure might look like
.
├── git/
│ ├── .gitconfig
│ ├── destinations.json
│ └── .gitignore
├── npm/
│ ├── destinations.json
│ └── .npmrc
├── shell/
│ ├── .zshrc
│ ├── destinations.json
│ └── .bash_aliases
└── cava/
└── .config
The git
, shell
, and npm
directories are all config groups, but not the cava
directory since it doesn't have the destinations.json
file.
Your structure can be flat (recommended), but it can also be nested, that is, you can have config groups within other config groups or regular directories within config groups. In the case of nested directories, you can reference nested files from the top level destinations.json
file.
However, if the nested directory is a config group, then the parent directory's destinations.json
file cannot reference any files within it. Nested config groups are isolated from their parents and function much like separate directories.
Installation is simple. All you need is node and npm.
npm i -g dotfilers
The CLI relies on one of two environment variables for the path to your dotfiles directory: $DOTS
or $DOTFILES
. Ideally, either one (or both) of these variables should be defined and available permanently. This can be achieved by defining them in your shell config file (like a .bashrc or .zshrc) or an alias file sourced on shell startup.
IMPORTANT: Either the
$DOTS
or$DOTFILES
shell variables must be set as the CLI depends on them to function properly.
The CLI has four commands
-
link
: Receives the names of configuration groups as arguments and, by default, creates symbolic links of the un-excluded files within each listed configuration group. The destination path of the ensuing symbolic links are determined by associations listed in thedestinations.json
file of the corresponding configuration group. If the destination path does not exist, it is created, regardless of how nested it is. -
unlink
: The opposite tolink
. Functions in much of the same way, except rather than placing symbolic links at a destination path, it deletes the symbolic links of the files in the configuration groups, using the correspondingdestinations.json
as lookup reference for where the links are -
create
: Bootstraps a new configuration group with a defaultdestinations.json
file -
sync
: Synchronizes dotfiles directory with corresponding remote repository. Note, your dotfiles directory must be a git repository for this command to work.
Each command also has a set of options that augment its behavior. Use the dfs --help
command to find out more.
Below is an example of a sample destinations.json
file for the shell
directory in the above illustration:
{
".zshrc": "~",
".bash_aliases": "$HOME"
}
Notice how we refer to files by their names and not by some relative path
When we invoke the link
command (link shell
), we would be creating symbolic links for the .zshrc
and .bash_aliases
files in the home directory. We could set a default destination for all files in the shell
configuration group with the following file disposition:
{
"all": "~"
}
The all
key is a reserved one that collectively refers to all files within a config group. It can also be used to specify a general default destination for files not explicitly associated with a destination path in the JSON. Implicitly, though, all files default to having their symbolic links placed in the home directory
To keep certain files from having symbolic links created and positioned somewhere in your file system, we can list them as part of the values for the exclude
key, as so:
{
"exclude": ["example.json", "*.toml", ".xresources"]
}
This is another reserved key that takes either an array of filenames and globs or the string "all
" as values.
{
"exclude": "all"
}
If it has a value of "all
", all config files in the configuration would be skipped over by both the link
and unlink
commands.
Finally, here is a full on sample destinations.json
file
{
"config-file-name.json":"$HOME/.local/app",
"inner/nested-file.rs": "$HOME",
"*.toml": "$CUSTOM_VAR/.local/toml-configs",
"exclude": ["*.js", "*.txt", ".gitconfig"],
"all": "~/default"
}
To work or contribute to the code of this project, you require the following:
- Knowledge of functional programming, beyond purity, array methods, and immutability. Knowledge of things like
- Modelling side effects with algebraic data structures
- Optics
- Knowledge of fp-ts
- Comfortable with Typescript
Setup is easy, it's just like any other node project written in TypeScript with NPM as the package manager
Nothing serious, just run the command:
npm run build
Deployments to the NPM registry occur automatically on successful pull requests to the main
branch. The nature of the commit determines the kind of release that occurs.
Your contributions are always welcome and appreciated. Following are the things you can do to contribute to this project.
-
Report a bug - If you think you have encountered a bug, and I should know about it, feel free to report it here and I will take care of it.
-
Request a feature - You can also request for a feature here, and if it will viable, it will be picked for development.
-
Create a pull request - It can't get better then this, your pull request will be appreciated by the community. You can get started by picking up any open issues from here and make a pull request.
If you are new to open-source, make sure to check read more about it here and learn more about creating a pull request here.
The main branch is the production/release branch. All other branches are feature branches and should be deleted when no longer needed. I initially had a development branch, but keeping it in sync with the master branch became a bit of a hassle, and it's a relatively small project