Skip to content

A minimalist, transparent flexible approach to dotfiles management. Written with fp-ts

License

Notifications You must be signed in to change notification settings

OlaoluwaM/dotfilers

Repository files navigation

Introduction

Welcome to Dotfilers! A CLI tool that automates the management of system configuration files (dotfiles) simply and transparently with blazing fast efficiency.

📒 Index

🔰 About

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 with exclude 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.

⚡ Usage

🔌 Installation

Installation is simple. All you need is node and npm.

npm i -g dotfilers

🪜 Setup

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.

📦 Commands

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 the destinations.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 to link. 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 corresponding destinations.json as lookup reference for where the links are

  • create: Bootstraps a new configuration group with a default destinations.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.

🛠️ Configuration

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"
}

🔧 Development

📓 Pre-Requisites

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

🔩 Development Environment

Setup is easy, it's just like any other node project written in TypeScript with NPM as the package manager

🔨 Build

Nothing serious, just run the command:

npm run build

🚀 Deployment

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.

🌸 Community

🔥 Contribution

Your contributions are always welcome and appreciated. Following are the things you can do to contribute to this project.

  1. 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.

  2. Request a feature - You can also request for a feature here, and if it will viable, it will be picked for development.

  3. 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.

🌵 Branches

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

🔒 License

MIT License