One of the beautiful things about Linux, is how easily customizable everything is. Usually these custom configurations are stored in files that start with a dot (hence dotfiles!), and typically located in your users home `~`, or better yet `~/.config` (even this can be customized, for apps that respect the XDG Base Directory spec). Some examples of dotfiles that you're likely already familiar with include `.gitconfig`, `.zshrc` or `.vimrc`.
You will often find yourself tweaking your configs over time, so that your system perfectly matches your needs. It makes sense to back these files up, so that you don't need to set everything up from scratch each time you enter a new environment. Git is a near-perfect system for this, as it allows for easy roll-backs, branches and it's well supported with plenty of hosting options (like here on GitHub).
Once everything's setup, you'll be able to SSH into a fresh system or reinstall your OS, then just run your script and go from zero to feeling at right at home within a minute or two.
It's not hard to create your own dotfile repo, it's great fun and you'll learn a ton along the way!
By using a dotfile system, you can set up a brand new machine in minutes, keep settings synced across multiple environments, easily roll-back changes, and never risk loosing your precious config files.
This is important, because as a developer, we usually have multiple machines (work / personal laptops, cloud servers, virtual machines, some GH codespaces, maybe a few Pis, etc). And you're much more productive when working from a familiar environment, with all your settings applied just how you like them. But it would be a pain to have to set each of these machines up manually. Even if you've only got a single device, how much time would you loose if your data became lost or corrupted?
The location of most config files can be defined using the [XDG base directory specification](https://specifications.freedesktop.org/basedir-spec), which is honored by most apps. This lets you specify where config, log, cache and data files are stored, keeping your top-level home directory free from clutter. You can do this by setting environmental variables, usually within the [`.zshenv`](https://github.com/Lissy93/dotfiles/blob/master/config/zsh.zshenv) file.
You can also containerize your dotfiles, meaning with a single command, you can spin up a fresh virtual environment on any system, and immediately feel right at home with all your configurations, packages, aliases and utils.
This is awesome for a number of reasons: 1) Super minimal dependency installation on the host 2) Blazing fast, as you can pull your built image from a registry, instead of compiling everything locally 3) Cross-platform compatibility, whatever your host OS is, you can always have a familiar Linux system in the container 4) Security, you can control which host resources are accessible within each container
For this, I'm using an Alpine-based Docker container defined in the [`Dockerfile`](https://github.com/Lissy93/dotfiles/blob/master/Dockerfile), to try it out, just run `docker run lissy93/dotfiles`.
Other options could include spinning up VMs with a predefined config, either using something like [Vagrant](https://www.vagrantup.com/) or a [NixOS](https://nixos.org/)-based config.
---
### Security
Something that is important to keep in mind, is security. Often you may have some personal info included in some of your dotfiles. Before storing anything on the internet, double check there's no sensitive info, SSH keys, API keys or plaintext passwords. If you're using git, then any files you wouldn't want to be commited, can just be listed in your [`.gitignore`](https://git-scm.com/docs/gitignore). If any .gitignore'd files are imported by other files, be sure to check they exist, so you don't get errors when cloning onto a fresh system.
Another solution, is to encrypt sensitive info. A great tool for this is [`pass`](https://www.passwordstore.org/) as it makes GPG-encrypting passwords very easy ([this article](https://www.outcoldman.com/en/archive/2015/09/17/keep-sensitive-data-encrypted-in-dotfiles/) outlines how), or you could also just use plain old GPG (as outlined in [this article](https://www.abdullah.today/encrypted-dotfiles/)).
The two most common approaches are be either [symlinking](#option-1---symlinking), or using [git bare repo](#option-2---git-bare-repo), but you could also do things manually by writing a simple script.
Symlinks let you maintain all your dotfiles in a working directory, and then link them to the appropriate places on disk, sort of like shortcuts.
For example, if your dotfiles are in `~/Documents/dotfiles`, you could create a zshrc file there, and link it with:
```bash
ln -s ~/Documents/dotfiles/zsh/.zshrc ~/.zshrc
```
This would obviously get cumbersome very quickly if you had a lot of files, so you would really want to automate this process. You could either create your own script to do this, or use a tool specifically designed for this.
I personally use [Dotbot](https://github.com/anishathalye/dotbot), as it doesn't have any dependencies - just include it as a sub-module, define a list of links in a simple YAML file, and hit go.
[GNU Stow](https://www.gnu.org/software/stow/) is also a popular choice, and it's usage is explained well in [this article](https://alexpearce.me/2016/02/managing-dotfiles-with-stow/) by Alex Pearce.
There's many other tools which do a similar thing, like [Homesick](https://github.com/technicalpickles/homesick), [Rcm](https://github.com/thoughtbot/rcm), [dotdrop](https://github.com/deadc0de6/dotdrop) or [mackup](https://github.com/lra/mackup).
Bare repositories let you add files from anywhere on your system, maintaining the original directory structure, and without the need for symlinks ([learn more](https://www.saintsjd.com/2011/01/what-is-a-bare-git-repository/)). Just initiialize or clone using the [`--bare`](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---bare) flag, then add a global alias to manage files with git.
```bash
# Initialise a new repo, or clone an existing one with the --bare flag
git init --bare $HOME/dotfiles
# Next create an alias that sets the directory to your dotfile (add to .zshrc/ .bashrc)
alias dotfiles='$(where git) --git-dir=$HOME/dotfiles/ --work-tree=$HOME'
# Hide untracked files
dotfiles config --local status.showUntrackedFiles no
Then, from anywhere in your system you can use your newly created alias to add, commit and push files to your repo using all the normal git commands, as well as pull them down onto another system.
```bash
dotfiles add ~/.config/my-file
dotfiles commit -m "A short message"
dotfiles push
```
Both [Chezmoi](https://github.com/twpayne/chezmoi/) and [YADM](https://github.com/TheLocehiliosan/yadm) are a dotfile management tools, which wrap bare git repo functionality, adding some additional QoL features.
To learn more, DistroTube made an excellent [video about bare git repos](https://www.youtube.com/watch?v=tBoLDpTWVOM), and Marcel Krčah has written [a post](https://marcel.is/managing-dotfiles-with-git-bare-repo/) outlining the benefits.
In terms of managing dependencies, using either [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) or [git subtree](https://github.com/git/git/blob/master/contrib/subtree/git-subtree.txt) will let you keep dependencies in your project, while also separate from your own code and easily updatable.
Zach Holman wrote a great article titled [Dotfiles Are Meant to Be Forked](https://zachholman.com/2010/08/dotfiles-are-meant-to-be-forked/). I personally disagree with this, since your dotfiles are usually highly personalized, so what's right for one developer, likely won't be what someone else is looking for. They're also typically something you build up over time, and although some repos may provide a great starting point, it's really important to know what everything does, and how it works.
By all means feel free to take what you want from mine. I've taken care to ensure that each file is standalone, and well documented so that certain files can just be dropped into any system. But I cannot stress enough the importance of reading through files to ensure it's actually what you want.
If you're looking for some more example dotfile repos to get you started, I can highly recommend taking a look at: [@holman/dotfiles](https://github.com/holman/dotfiles), [@nickjj/dotfiles](https://github.com/nickjj/dotfiles), [@caarlos0/dotfiles](https://github.com/caarlos0/dotfiles), [@cowboy/dotfiles](https://github.com/cowboy/dotfiles), [@drduh/config](https://github.com/drduh/config).
There's even more to check out at [webpro/awesome-dotfiles](https://github.com/webpro/awesome-dotfiles), [dotfiles.github.io](https://dotfiles.github.io/) and [r/unixporn](https://www.reddit.com/r/unixporn/).
This will execute the quick setup script (in [`lets-go.sh`](https://github.com/Lissy93/dotfiles/blob/master/lets-go.sh)), which just clones the repo (if not yet present), then executes the [`install.sh`](https://github.com/Lissy93/dotfiles/blob/master/install.sh) script. You can re-run this at anytime to update the dotfiles. You can also optionally pass in some variables to change the install location (`DOTFILES_DIR`) and source repo (`DOTFILES_REPO`) to use your fork.
The install script [does several things](#install-script), it takes care of checking dependencies are met, updating dotfiles and symlinks, configuring CLI (Vim, Tmux, ZSH, etc), and will prompt the user to install listed packages, update the OS and apply any system preferences. The script is idempotent, so it can be run multiple times without changing the result, beyond the initial application.
_Alternatively, you can clone the repo yourself, cd into it, allow execution of [`install.sh`](https://github.com/Lissy93/dotfiles/blob/master/install.sh) then run it to install or update._
You'll probably want to fork the repo, then clone your fork instead, so update the above commands with the path to your repo, and optionally change the clone location on your disk.
Once the repo is cloned, you can modify whatever files you like before running the install script. The [Directory Structure](#directory-structure) section provides an overview of where each file is located. Then see the [Configuring](#configuring) section for setting file paths and symlink locations.
├── <ahref="https://github.com/Lissy93/dotfiles/tree/master/scripts">scripts/</a> # Bash scripts for automating tasks
│ ├── <ahref="https://github.com/Lissy93/dotfiles/tree/master/scripts/installs">installs/</a> # Scripts for software installation
│ │ ├── <ahref="https://github.com/Lissy93/dotfiles/blob/master/scripts/installs/Brewfile">Brewfile</a> # Package installs for MacOS via Homebrew
│ │ ├── <ahref="https://github.com/Lissy93/dotfiles/blob/master/scripts/installs/arch-pacman.sh">arch-pacman.sh</a> # Package installs for Arch via Pacman
│ │ └── <ahref="https://github.com/Lissy93/dotfiles/blob/master/scripts/installs/flatpak.sh">flatpak.sh</a> # Package installs for Linux desktops via Flatpak
- Set variables by reading any passed parameters, or fallback to sensible defaults (see [`.zshenv`](https://github.com/Lissy93/dotfiles/blob/master/config/zsh/.zshenv))
The locations for all symlinks are defined in [`symlinks.yaml`](https://github.com/Lissy93/dotfiles/blob/master/symlinks.yaml). These are managed using [Dotbot](https://github.com/anishathalye/dotbot), and will be applied whenever you run the [`install.sh`](https://github.com/Lissy93/dotfiles/blob/master/install.sh) script. The symlinks set locations based on XDG paths, all of which are defined in [`.zshenv`](https://github.com/Lissy93/dotfiles/blob/master/config/zsh/.zshenv).
For example, if you often find yourself typing `git add .` you could add an alias like `alias gaa='git add .'`, then just type `gaa`. You can also override existing commands, for example to always show hidden files with `ls` you could set `alias ls='ls -a'`.
Aliases should almost always be created at the user-level, and then sourced from your shell config file (usually `.bashrc` or `.zshrc`). System-wide aliases would be sourced from `/etc/profile`. Don't forget that for your changes to take effect, you'll need to restart your shell, or re-source the file containing your aliases, e.g. `source ~/.zshrc`.
You can view a list of defined aliases by running `alias`, or search for a specific alias with `alias | grep 'search-term'`. The `unalias` command is used for removing aliases.
All aliases in my dotfiles are categorised into files located in [`zsh/aliases/`](https://github.com/Lissy93/dotfiles/blob/master/config/zsh/aliases/) which are imported in [`zsh/.zshrc`](https://github.com/Lissy93/dotfiles/blob/master/config/zsh/.zshrc#L9-L14).
The dotfile installation script can also, detect which system and environemnt you're running, and optionally prompt to install packages and applications.
Package lists are stored in [`scripts/installs/`](https://github.com/Lissy93/dotfiles/tree/master/installs) directory, with separate files for different OSs. The install script will [pick the appropriate file](https://github.com/Lissy93/dotfiles/blob/22c6a04fdb22c140448b7d15ef8187c3a424ab47/install.sh#L243-L260) based on your distro.
- Linux (desktop): [`flatpak.sh`](https://github.com/Lissy93/dotfiles/blob/master/scripts/installs/flatpak.sh) - Desktop apps can be installed on Linux systems via [Flatpack](https://flatpak.org/)
- Mac OS: [`.Brewfile`](https://github.com/Lissy93/dotfiles/blob/master/scripts/installs/Brewfile) - Mac apps installed via [Homebrew](https://brew.sh/)
- Arch (and Arch-based systems, like Manjaro): [`arch-pacman.sh`](https://github.com/Lissy93/dotfiles/blob/master/scripts/installs/arch-pacman.sh) - Arch CLI apps installed via [pacman](https://wiki.archlinux.org/title/Pacman)
- Debian (and Debian-based systems, like Ubuntu): [`apt.sh`](https://github.com/Lissy93/dotfiles/blob/master/scripts/installs/apt.sh) - Debian CLI apps installed via [apt](https://wiki.debian.org/Apt)
- Alpine: [`apk.sh`](https://github.com/Lissy93/dotfiles/blob/master/scripts/installs/apk.sh) - Alpine CLI apps installed via [apk](https://docs.alpinelinux.org/user-handbook/0.1a/Working/apk.html)
The installation script can also prompt you to confiture system settings and user preferences. This is useful for setting up a completely fresh system in just a few seconds.
MacOS includes a built-in utility named [`defaults`](https://real-world-systems.com/docs/defaults.1.html), which lets you configure all system and app preferences programatically through the command line. This is very powerful, as you can write a script that configures every aspect of your system enabling you to setup a brand new machine in seconds.
All settings are then updated in the `.plist` files stored in `~/Library/Preferences`. This can also be used to configure preferences for any installed app on your system, where the application is specified by its domain identifier - you can view a full list of your configurable apps by running `defaults domains`.
The Mac settings are located in [`scripts/macos-setup/`](https://github.com/Lissy93/dotfiles/tree/master/scripts/macos-setup), and are split into three files:
- [`macos-security.sh`](https://github.com/Lissy93/dotfiles/blob/master/scripts/macos-setup/macos-security.sh) - Sets essential security settings, disables telementry, disconnects unused ports, enforces signing, sets logout timeouts, and much more
- [`macos-preferences.sh`](https://github.com/Lissy93/dotfiles/blob/master/scripts/macos-setup/macos-preferences.sh) - Configures all user preferences, including computer name, highlight color, finder options, spotlight settings, hardware preferences and more
- [`macos-apps.sh`](https://github.com/Lissy93/dotfiles/blob/master/scripts/macos-setup/macos-apps.sh) - Applies preferences to any installed desktop apps, such as Terminal, Time Machine, Photos, Spotify, and many others
Upon running each script, a summary of what will be changed will be shown, and you'll be prompted as to weather you'd like to continue. Each script also handles permissions, compatibility checking, and graceful fallbacks. Backup of original settings will be made, and a summary of all changes made will be logged as output when the script is complete.
If you choose to run any of these scripts, take care to read it through first, to ensure you understand what changes will be made, and optionally update or remove anything as you see fit.
The entry point for the Vim config is the [`vimrc`](https://github.com/Lissy93/dotfiles/blob/master/config/vim/vimrc), but the main editor settings are defined in [`vim/editor.vim`](https://github.com/Lissy93/dotfiles/blob/master/config/vim/editor.vim)
Vim plugins are managed using [Plug](https://github.com/junegunn/vim-plug) defined in [`vim/plugins.vim`](https://github.com/Lissy93/dotfiles/blob/master/config/vim/setup-vim-plug.vim).
To install them from GitHub, run `:PlugInstall` (see [options](https://github.com/junegunn/vim-plug#commands)) from within Vim. They will also be installed or updated when you run the main dotfiles setup script ([`install.sh`](https://github.com/Lissy93/dotfiles/blob/d4b8426629e7fbbd6d17d0b87f0bb863d6618bfd/install.sh#L132-L134)).
- **[Airline](https://github.com/vim-airline/vim-airline)**: `vim-airline/vim-airline` - A very nice status line at the bottom of each window, displaying useful info
- **[Nerd-tree](https://github.com/preservim/nerdtree)**: `preservim/nerdtree` - Alter files in larger projects more easily, with a nice tree-view pain
- **[Matchup](https://github.com/andymass/vim-matchup)**: `andymass/vim-matchup` - Better % naviagtion, to highlight and jump between open and closing blocks
- **[TagBar](https://github.com/preservim/tagbar)**: `preservim/tagbar` - Provides an overview of the structure of a long file, shows tags ordered by scope
- **[Gutentags](https://github.com/ludovicchabant/vim-gutentags)**: `ludovicchabant/vim-gutentags` - Manages tag files
- **[Fzf](https://github.com/junegunn/fzf.vim)**: `junegunn/fzf` and `junegunn/fzf.vim` - Command-line fuzzy finder and corresponding vim bindings
- **[Deoplete.nvim](https://github.com/Shougo/deoplete.nvim)**: `Shougo/deoplete.nvim` - Extensible and asynchronous auto completion framework
- **[Nerd-Commenter](https://github.com/preservim/nerdcommenter)**: `preservim/nerdcommenter` - For auto-commenting code blocks
- **[Ale](https://github.com/dense-analysis/ale)**: `dense-analysis/ale` - Checks syntax asynchronously, with lint support
- **[Surround](https://github.com/tpope/vim-surround)**: `tpope/vim-surround` - Easily surround selected text with brackets, quotes, tags etc
- **[IncSearch](https://github.com/haya14busa/incsearch.vim)**: `haya14busa/incsearch.vim` - Efficient incremental searching within files
- **[Vim-Visual-Multi](https://github.com/mg979/vim-visual-multi)**: `mg979/vim-visual-multi` - Allows for inserting/ deleting in multiple places simultaneously
- **[Visual-Increment](https://github.com/triglav/vim-visual-increment)**: `triglav/vim-visual-increment` - Create an increasing sequence of numbers/ letters with `Ctrl` + `A`/`X`
- **[Vim-Test](https://github.com/janko/vim-test)**: `janko/vim-test` - A wrapper for running tests on different granularities
- **[Syntastic](https://github.com/vim-syntastic/syntastic)**: `vim-syntastic/syntastic` - Syntax checking that warns in the gutter when there's an issue
- **[Git-Gutter](https://github.com/airblade/vim-gitgutter)**: `airblade/vim-gitgutter` - Shows git diff markers in the gutter column
- **[Vim-fugitive](https://github.com/tpope/vim-fugitive)**: `tpope/vim-fugitive` - A git wrapper for git that lets you call a git command using `:Git`
- **[Committia](https://github.com/rhysd/committia.vim)**: `rhysd/committia.vim` - Shows a diff, status and edit window for git commits
- **[Vim-Git](https://github.com/tpope/vim-git)**: `tpope/vim-git` - Runtime files for git in vim, for git, gitcommit, gitconfig, gitrebase, and gitsendemail
- **[Vim-JavaScript](https://github.com/pangloss/vim-javascript)**: `pangloss/vim-javascript`*(JavaScript)* - Syntax highlighting and improved indentation for JS files
- **[Yats](https://github.com/HerringtonDarkholme/yats.vim)**: `HerringtonDarkholme/yats.vim`*(TypeScript)* - Syntax highlighting and snippets for TypeScript files
- **[Vim-jsx-pretty](https://github.com/MaxMEllon/vim-jsx-pretty)**: `MaxMEllon/vim-jsx-pretty`*(React)* - Highlighting and indentation for React .tsx and .jsx files
- **[Vim-CSS-Color](https://github.com/ap/vim-css-color)**: `ap/vim-css-color`*(CSS/ SASS)* - Previews colors as text highlight, where hex codes are present
- **[Mustache and Handlebars](https://github.com/mustache/vim-mustache-handlebars)**: `mustache/vim-mustache-handlebars`*(Mustache/ Handlebars)* - Auto handles braces
- **[Vim-Go](https://github.com/fatih/vim-go)**: `fatih/vim-go`*(GoLang)* - Go support, with syntax highlighting, quick execute, imports, formatting etc
- **[Indentpython](https://github.com/vim-scripts/indentpython.vim)**: `vim-scripts/indentpython.vim`*(Python)* - Correct indentation for Python files
- **[Semshi](https://github.com/numirias/semshi)**: `numirias/semshi`*(Python)* - Advanced syntax highlighting for Python files
- **[SimpylFold](https://github.com/tmhedberg/SimpylFold)**: `tmhedberg/SimpylFold`*(Python)* - Code-folding for Python
- **[Vimtex](https://github.com/lervag/vimtex)**: `lervag/vimtex`*(LaTex)* - Completion of citations, labels, commands and glossary entries
- **[Dockerfile.vim](https://github.com/ekalinin/Dockerfile.vim)**: `ekalinin/Dockerfile.vim`*(Docker)* - Syntax highlighting and snippets for Dockerfiles
- **[Requirements](https://github.com/raimon49/requirements.txt.vim)**: `raimon49/requirements.txt.vim`*(Requirements)* - Syntax highlighting for the requirements file format
- **[Vim-Markdown](https://github.com/gabrielelana/vim-markdown)**: `gabrielelana/vim-markdown`*(Markdown)* - Syntax highlighting, auto format, easy tables and more
- **[Zinit](https://github.com/zinit-zsh/zinit-vim-syntax)**: `zinit-zsh/zinit-vim-syntax`*(ZSH)* - syntax definition for Zinit commands in any file of type zsh
- **[Nginx](https://github.com/chr4/nginx.vim)**:`chr4/nginx.vim` *(Nginx)* - Integer matching, hichlight syntax and IPv4/ IPv6, mark insecure protocols and more
Fairly standard Tmux configuration, strongly based off Tmux-sensible. Configuration is defined in [`.tmux.conf`](https://github.com/Lissy93/dotfiles/blob/master/config/tmux/tmux.conf)
Tmux plugins are managed using [TMP](https://github.com/tmux-plugins/tpm) and defined in [`.tmux.conf`](https://github.com/Lissy93/dotfiles/blob/master/config/tmux/tmux.conf). To install them from GitHub, run `prefix` + <kbd>I</kbd> from within Tmux, and they will be cloned int `~/.tmux/plugins/`.
Git aliases for ZSH are located in [`/zsh/aliases/git.zsh`](https://github.com/Lissy93/dotfiles/blob/master/config/zsh/aliases/git.zsh), and are documented under the [Aliases](https://github.com/lissy93/dotfiles#my-aliases) section, above.
Quickly open web search results for a given query using a selected search engine. To get started, run `web-search`, or `web-search --help` for more info.
Usage:
All parameters are optional, to get started just run `web-search` or `web-search <search provider (optional)> <query (optional)>`, the `ws` alias can also be used. If a search engine isn't specified, you'll be prompted to select one from the list. Similarly, if a query hasn't been included you'll be asked for that too.
-`web-search` - Opens interactive menu, you'll be prompted to select a search engine from the list then enter your query
-`web-search <search term>` - Specify a search term, and you'll be prompted to select the search engine
- For example, `web-search Hello World!`
-`web-search <search engine>` - Specify a search engine, and you'll be prompted for your search term
- For example, `web-search duckduckgo`
-`web-search <search engine> <search engine>` - Specify both a search engine and query, and results will open immediately
- For example, `web-search wikipedia Matrix Defense`
The following search engines are supported by default:
- DuckDuckGo: `ws duckduckgo` (or `wsddg`)
- Wikipedia: `ws wikipedia` or (`wswiki`)
- GitHub: `ws github` (or `wsgh`)
- StackOverflow: `ws stackoverflow` (or `wsso`)
- Wolframalpha: `ws wolframalpha` (or `wswa`)
- Reddit: `ws reddit` (or `wsrdt`)
- Maps: `ws maps` (or `wsmap`)
- Google: `ws google` (or `wsggl`)
- Grep App: `ws grepapp` (or `wsgra`)
</details>
The alias `ws` will also resolve to `web-search`, if it's not already in use. You can either run the script directly, e.g.`~/.config/utils/web-search.sh` (don't forget to `chmod +x` the file first, to make it executable), or use the `web-search` / `ws` alias anywhere, once it has been source'd from your .zshrc.