Originally published at blog.mphomphego.co.za on February 28, 2019.
Updated: 17 March 2019
Added demonstrational video for my new-computer set-up script.
Click Gif:
How I increased my productivity using dotfiles
TLDR; You can set up a new system using dotfiles and an installation script in minutes. It’s not hard to create your own repository, and you’ll learn a ton along the road. This is truly more about the journey than the destination!
There is no frustrating feeling like the feeling of starting all over again especially when it comes to a fresh OS install or having your hard-drive crashing on you which then results in reinstalling your OS all over again!
And you think to yourself, OMG! my aliases, system settings and configuration files, every little helper file and script GONE, just like that!
That new fresh OS install on your new computer is a shell of its former self, everything fresh out of the box - How annoyed you will/would be.
Your heart warms as you think back to the comfort and productivity that came with your computer before. If only there was a way to take everything you know and love on the go and never to worry about the agony of reinstalling everything...
Thankfully, there is!
Overtime I got annoyed and frustrated to the point where I created my own linux setting up script that auto-installs everything I needed from packages, dotfiles, configuration files and settings.
If you would like to check it out, [new-computer] - should you have nice ideas to share or want to collaborate, feel free to send me a tweet @orifhampho or fork and send me a PR!
Automate Everything!
There are two parts to this:
-
The first is a repository/backup of my dotfiles & configs.
This repository contains, and track changes for most of my important files in my
/home
partition, such as.bashrc
,.bash_aliases
, and other related files.Dotfiles are used to customize your system. The
"dotfiles"
name is derived from the configuration files in Unix-like systems that start with a dot (e.g..bash_profile
and.gitconfig
). For normal users, this indicates these are not regular documents, and by default are hidden in directory listings. For power users, however, they are a core tool belt.
Backing my dotfiles to GitHub keeps everything neatly tracked and version controlled. The second repository packs a punch, it contains a script which when ran it will install every little package, configs, githooks and dotfiles, I need for my day-to-day productivity from academic, work-related to entertainment.
installer.sh
is what makes this all magically work.
I will document it in detail on the next post.
My Dotfiles
An Example Dotfiles Repository
For this post, I’m just going to list a subset of my own dotfiles repo.
Current Structure
Below is the structure of my dotfiles repository. It’s also what we’ll use in our walk-through below.
.
├── .config
│ ├── Code
│ │ └── User
│ ├── gummi
│ │ ├── gummi.cfg
│ │ ├── snippets.cfg
│ │ └── welcome.tex
│ ├── Mendeley Ltd.
│ │ └── Mendeley Desktop.conf
│ ├── ranger
│ │ ├── bookmarks
│ │ ├── commands_full.py
│ │ ├── commands.py
│ │ ├── history
│ │ ├── rc.conf
│ │ ├── rifle.conf
│ │ ├── scope.sh
│ │ └── tagged
│ ├── redshift.conf
│ ├── Slack
│ │ └── local-settings.json
│ ├── sublime-text-3
│ │ ├── Packages
│ │ ├── TrailingSpaces
│ │ └── WordHighlight
│ ├── terminator
│ │ └── config
│ └── xfce4
│ ├── helpers.rc
│ ├── help.rc
│ ├── panel
│ ├── terminal
│ ├── xfce4-screenshooter
│ ├── xfce4-taskmanager.rc
│ └── xfconf
├── .dotfiles
│ ├── .bash_aliases
│ ├── .bash_functions
│ ├── .bashrc
│ ├── .docker_aliases
│ ├── .dotfiles_setup.sh
│ ├── .git-completion.bash
│ ├── .gitconfig
│ ├── .gitignore
│ ├── .nanorc
│ ├── .profile
│ └── .travis.conf
├── .ipython
│ └── profile_default
│ ├── db
│ ├── ipython_config.py
│ ├── ipython_kernel_config.py
│ ├── log
│ ├── pid
│ └── startup
├── LICENSE
├── My-Git-Repos.txt
├── Pictures
│ ├── glasses-and-computer-screen.jpg
│ ├── vertical_background.jpeg
│ └── wallpaper.jpg
├── README.md
└── .travis.yml
Diving deep into the dotfiles
We will take a look at the following examples:
- .dotfiles/.profile
- .dotfiles/.bashrc
- .dotfiles/.bash_aliases
- .dotfiles/.bash_functions
- .dotfiles/.docker_aliases
.profile
This file is located in your /home
directory is loaded first upon login, it is either called .profile
or .bash_profile
.
What to put in the .profile
is truly up-to the individual and it can be expanded significantly. I personally like to keep my .profile
as small possible and only have things I need to be ran once.
For example, I define all my colours and colour functions in my .profile
case ${TERM} in
'') ;;
*)
# Define a few Colours
BLACK="$(tput -T xterm setaf 0)"
BLACKBG="$(tput -T xterm setab 0)"
# ....
esac
# if running bash
if [ -n "$BASH_VERSION" ]; then
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
# set PATH so it includes user's private bin directories
PATH="$HOME/.venv/bin:$HOME/bin:$HOME/.local/bin:$PATH"
export PATH="$HOME/.poetry/bin:$PATH"
recho() {
echo "${RED}$1${NC}"
}
gecho() {
echo "${GREEN}$1${NC}"
}
export -f recho
export -f gecho
Full example: my .profile
If you want to dive into start-up scripts a bit more, Peter Ward explains about Shell startup scripts.
.bashrc
.bashrc
is a shell script that Bash runs whenever it is started interactively i.e. when you open a new terminal. It initializes an interactive shell session.
For example, I define my PS1, shell options, key-bindings, aliases, functions and custom message.
__git_status_info() {
STATUS=$(git status 2>/dev/null |
awk '
/^On branch / {printf($3)}
/^Changes not staged / {printf("|?Changes unstaged!")}
/^Changes to be committed/ {printf("|*Uncommitted changes!")}
/^Your branch is ahead of/ {printf("|^Push changes!")}
')
if [ -n "${STATUS}" ]; then
echo -ne " (${STATUS}) [Last updated: $(git show -1 --stat | grep ^Date | cut -f4- -d' ')]"
fi
}
__disk_space=$(/bin/df --output=pcent /home | tail -1)
_ip_add=$(ip addr | grep -w inet | gawk '{if (NR==2) {$0=$2; gsub(/\//," "); print $1;}}')
__ps1_startline="\[\033[0;32m\]\[\033[0m\033[0;38m\]\u\[\033[0;36m\]@\[\033[0;36m\]\h on ${_ip_add}:\w\[\033[0;32m\] \[\033[0;34m\] [disk:${__disk_space}] \[\033[0;32m\]"
__ps1_endline="\[\033[0;32m\]└─\[\033[0m\033[0;31m\] [\D{%F %T}] \$\[\033[0m\033[0;32m\] >>>\[\033[0m\] "
export PS1="${__ps1_startline}\$(__git_status_info)\n${__ps1_endline}"
# ------------
IP_ADD=`ip addr | grep -w inet | gawk '{if (NR==2) {$0=$2; gsub(/\//," "); print $1;}}'`
printf "${LIGHTGREEN}Hello, ${USER}@${IP_ADD}\n"
printf "Today is, $(date)\n";
printf "Sysinfo: $(uptime)\n"
printf "\n$(fortune | cowsay)${NC}\n"
Full example: my .bashrc
.bash_aliases
A Bash alias is essentially nothing more than a keyboard shortcut, an abbreviation, a means of avoiding typing a long command sequence. Read more. Here are some examples:
alias update='sudo apt-get -y update'
alias upgrade='sudo apt-get -y --allow-unauthenticated upgrade && sudo apt-get autoclean && sudo apt-get autoremove'
alias hist='history --color=always'
alias hist-grep='history | grep --color=always'
alias youtube="$(command -v youtube-dl)"
alias youtube-mp3="$(command -v youtube-dl) -x --audio-format mp3"
alias rsync='rsync --progress'
alias less='less -N'
Full example: my .bash_aliases
.bash_functions
Commands that are too complex for an alias (and perhaps too small for a stand-alone script) can be defined in a function. Functions can take arguments, making them more powerful.
cd() {
# The 'builtin' keyword allows you to redefine a Bash builtin without
# creating a recursion. Quoting the parameter makes it work in case there are spaces in
# directory names.
builtin cd "$@" && ls -thor;
}
compile() {
if [ -f $1 ] ; then
case $1 in
*.tex) latexmk -pdf $1 ;;
*.c) gcc -Wall "$1" -o "main" -lm ;;
# List should be expanded.
*) recho "'$1' cannot opened via ${FUNCNAME[0]}" ;;
esac
else
recho "'$1' is not a valid file"
fi
}
committer() {
# Add file(s), commit and push
FILE=$(git status | $(which grep) "modified:" | cut -f2 -d ":" | xargs)
for file in $FILE; do git add -f "$file"; done
if [ "$1" == "" ]; then
# SignOff by username & email, SignOff with PGP and ignore hooks
git commit -s -S -n -m"Updated $FILE";
else
git commit -s -S -n -m"$2";
fi;
read -t 5 -p "Hit ENTER if you want to push else wait 5 seconds"
[ $? -eq 0 ] && bash -c "git push --no-verify -q &"
}
createpr() {
# Push changes and create Pull Request on GitHub
REMOTE="devel";
if ! git show-ref --quiet refs/heads/devel; then REMOTE="master"; fi
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
git push -u origin "${BRANCH}" || true;
if [ -f /usr/local/bin/hub ]; then
/usr/local/bin/hub pull-request -b "${REMOTE}" -h "${BRANCH}" --no-edit || true
else
recho "Failed to create PR, create it Manually"
recho "If you would like to continue install hub: https://github.com/github/hub/"
fi
}
Full example: my .bash_functions
.docker_aliases
Similar to .bash_aliases
, in this file I defined all my docker run
shortcut
# bat supports syntax highlighting for a large number of programming and markup languages
# see: https://github.com/sharkdp/bat
alias bat='docker run -it --rm -e BAT_THEME -e BAT_STYLE -e BAT_TABS -v "$(pwd):/myapp" danlynn/bat'
# A collection of simplified and community-driven man pages.
# see: https://github.com/sharkdp/tldr
alias tldr='docker run --rm -it -v ~/.tldr/:/root/.tldr/ nutellinoit/tldr'
# Simplified docker based markdown linter
# see: https://github.com/mmphego/my-dockerfiles/markdownlint
alias markdownlint='docker run --rm -it -v "$(pwd):/app" mmphego/markdownlint'
# Simplified docker based latexmk
# see: https://github.com/mmphego/my-dockerfiles/latex-full
alias mklatex='docker run --rm -i -v "$PWD":/data --user="$(id -u):$(id -g)" mmphego/latex:ubuntu'
Full example: my .docker_aliases
Other dotfiles
I only listed a subset of dotfiles above, many packages also store their settings in a dotfile, for example
-
.gitconfig
for Git- Full example: my .gitconfig
-
.nanorc
for nano- Full example: my .nanorc
Installing the Dotfiles
NOTE: Be careful you should have some dotfiles defined in your /home directory. Before you continue you need to ensure that they are backed up somewhere.
Remember with great power comes great responsibility.
I wrote a dotfiles installation script to automate symlinking the dotfiles in the repo to your /home
directory. See this .dotfiles_setup.sh for an example.
Note that the script uses 'ln' instead of GNU stow
for symlinking - I will update the script soon.
To install the dotfiles on a new system, we can do so easily by cloning the repo:
cd $HOME
git clone --depth 1 https://github.com/mmphego/dot-files
rsync -uar --delete-after dot-files/{.,}* "${HOME}"
bash .dotfiles/.dotfiles_setup.sh install
# To delete
# bash .dotfiles/.dotfiles_setup.sh delete
This script will backup existing dotfiles and then do the symlinking.
When done, you should just run the test to ensure that the installation was successful.
bash .dotfiles/.dotfiles_setup.sh test
Conclusion
That's it for this blog post, I hope you picked up a thing or 2 from this post.
dotfiles are a very personal thing, Look around at other repositories and start building your dotfiles the way you like. I have always had a knack for searching for various repositories containing dotfiles on GitHub and other blogs, and I have often found something useful. It is always tricky to get everything to work according to your specs but once you have everything under-control you are good to go.
Go build set up your own dotfiles and the next time when your computer crashes/reinstall OS, it won't be that bad!
Feedback
If you have nice ideas to share or want to collaborate, feel free to send me a tweet @orifhampho or fork and send me a PR!
Top comments (9)
Nice article. It definitely helps so much when you can git clone your configs onto a new machine and you are ready to go. Also helps with having the same setup on each of your machines.
Personally I use GNU Stow to manage my dotfiles. So I don't need to write my own script for that. GNU Stow is really convenient.
A colleague of mine told me about GNU Stow, I think it's about time I look into it, instead of symlinking my files with ln.
Thanks
Really good idea!
I used to use something similar where I had a git repo of my symlinked dot files. I had a work branch that had all the work proxy settings and a home branch for when I was working from home direct to the WiFi without a proxy server.
I sync all my dotfiles across my work and home machine, it's like using the same machine!!! flawless
Nice post :) Thanks for sharing!
I shared an article a while ago explaining how I evolved my install script into an update script to never run into the issue again of having a stale script that doesn't work on a new machine:
dev.to/jorinvo/automate-your-mac-s...
Also, my personal preference is to keep, both, my setup script and my bash config so simple that I'm not afraid of breaking it. I put all bash configs in a single .bashrc file to make it straight forward to find anything and change anything when I feel like.
No matter how we organize the files, I think the point I'm trying to make is, that dotfiles are a living thing which we adjust constantly and we should minimize the friction to change as much as we can :)
Thank you very much for this article, well written and a great approach!
You are welcome.
I love dotfiles!
There is something so personal in sharing your configuration with others.
Maybe you will like my setup as well: github.com/sobolevn/dotfiles
Your dotfiles, are life.
I found some gems in there.
Thank you for sharing.