My Own Configuration Manager

Jan 21, 2018

I have been using Linux since I was in my second year of undergraduate. My experiments with the dotfiles (configuration files) also started at the same time. For the uninformed, in Linux, it is common to configure a lot of settings and configurations within dotfiles. Dotfiles are files in a Linux user's home directory that begin with a dot or a full-stop character. This dot indicates to the operating system that these files are used to store the settings of programs like vimvim or shells like bashbash or fishfish to name a few.

In the beginning, I was keeping a manual backup of my dotfiles by copying them to a folder from time to time. But the list soon started getting huge, that it became complicated for me to keep track of the changes. Then I moved to symlinks. I started symlinking all the dotfiles from my folder to their usual locations. This setup worked perfectly fine, but as my collection of dotfiles grew, It became very cumbersome for me to symlink every dotfile manually.

I also tried a few tools built for this particular purpose. Some of them are vcshvcsh, mrmr, and stowstow. These tools work just fine, but I was not willing to learn new tools just for maintaining my dotfiles. At last, I decided to write my tool to solve this problem. This way, there will not be any external dependency, and this tool will also become part of my dotfiles.

Design

The tool, in itself, is inspired by the UNIX tradition of keeping configuration files for the settings of the programs. This configuration system uses a JSON formatted dotfile.

Here is the source code for the configuration system. Let's have a look at the file structure of the repository.

|-- .backups
|   |-- 08-01-2018-15:47
|   |-- 08-01-2018-19:30
|   |-- ......
|-- configure.py
|-- current_status
|-- dotfiles
|   |-- dunst
|   |-- gtk-3.0
|   |-- i3
|   |-- ......
|-- dotfiles.json
|-- LICENSE
|-- README.md
|-- .backups
|   |-- 08-01-2018-15:47
|   |-- 08-01-2018-19:30
|   |-- ......
|-- configure.py
|-- current_status
|-- dotfiles
|   |-- dunst
|   |-- gtk-3.0
|   |-- i3
|   |-- ......
|-- dotfiles.json
|-- LICENSE
|-- README.md

During the initial setup, you need to edit the dotfiles.jsondotfiles.json file to suit your setup. A relevant section of the JSON file is given below.

{
  "pre": [
    {
      "name": "cloning repository",
      "command": "git",
      "subcommand": "clone",
      "argument": "https://github.com/yashhere/dotfiles.git"
    }
  ],
  "linking": [
    {
      "name": "bashrc",
      "src": "dotfiles/.bashrc",
      "dest": ".bashrc"
    },
    {
      "name": "bash_profile",
      "src": "dotfiles/.bash_profile",
      "dest": ".bash_profile"
    },
    {
      "name": "profile",
      "src": "dotfiles/.profile",
      "dest": ".profile"
    },
    {
      "name": "i3",
      "src": "dotfiles/i3",
      "dest": ".config/i3"
    }
  ]
}
{
  "pre": [
    {
      "name": "cloning repository",
      "command": "git",
      "subcommand": "clone",
      "argument": "https://github.com/yashhere/dotfiles.git"
    }
  ],
  "linking": [
    {
      "name": "bashrc",
      "src": "dotfiles/.bashrc",
      "dest": ".bashrc"
    },
    {
      "name": "bash_profile",
      "src": "dotfiles/.bash_profile",
      "dest": ".bash_profile"
    },
    {
      "name": "profile",
      "src": "dotfiles/.profile",
      "dest": ".profile"
    },
    {
      "name": "i3",
      "src": "dotfiles/i3",
      "dest": ".config/i3"
    }
  ]
}

As can be seen, the JSON file has an array variable linkinglinking, which can be used to set the paths for each configuration file and folder. The configure.pyconfigure.py script also requires a dotfilesdotfiles folder to be present in the current directory. The folder can be created manually, or if it is already version controlled on GitHub, then the script can clone it. For that, you can edit the prepre section in the dotfiles.jsondotfiles.json.

Your dotfiles and config folders go inside the dotfilesdotfiles folder. You need to copy all your current configurations to this folder to get started.

So, how does the script know where a file or folder will be linked? Simple, you need to edit the dotfiles.jsondotfiles.json file and add source and destination locations. For example, if you want to set up configurations of i3i3 to its original location (which is, $HOME/.config/i3$HOME/.config/i3), then you need to create a new JSON object in the linkinglinking array, like this.

{
  "name": "i3",
  "src": "dotfiles/i3",
  "dest": ".config/i3"
}
{
  "name": "i3",
  "src": "dotfiles/i3",
  "dest": ".config/i3"
}

Here the namename is used to identify the configuration file, the srcsrc parameter is the location of your config file/folder in the dotfiles directory, and the destdest parameter is the final destination of the file/folder. Keen observers would notice that I have not used $HOME$HOME anywhere. It is understood that the configuration will go to the current user's home directory. So the destdest is relative to the user's home directory, and srcsrc is relative to the directory from which the configure.pyconfigure.py script is executed.

And you are done! Now, run configure.pyconfigure.py, and all your dotfiles and folders will be symlinked to their correct place.

The current_statuscurrent_status file saves all the symlink locations that are being managed by the script, for your easy reference and to debug any error.

Behind the Scenes

A lot to cool things happen behind the scenes. The script will check if any previous symlink exists at the given destdest location. It removes any symlinks to avoid redundancy. If the dest already has any dotfile or folder, then it backs it up in the .backups.backups under today's date and time before replacing it with a symlink to avoid any potential data loss.

I hope the article was useful. Cheers 😄