Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What is Tuckr

Tuckr is a dotfile manager as well as a symlink farm. It aims to reduce the amount of work necessary to get up and running on a new machine. It aims to be a simple tool that extends stow by taking inspiration from other tools such as yadm, chezmoi, odin and go.

Since simplicity is one of the main goals of this project, the tool tries as much as possible to avoid any sort of configuration. The dotfiles repository is the configuration. This makes it easy to learn how to use and setup everything by just understanding a couple of concepts that are reused everywhere. The consistency of the repo also allows to make assumptions annd be able to validate that everything is being setup correctly.

How Tuckr works

Tuckr mandates that all dotfiles are under a dotfiles directory. This directory should be on your systems standard location for configuration files. On Unix-like OSes $HOME/.dotfiles is also permitted, but is discouraged.

Tuckr looks up the following paths for dotfiles:

PlatformConfig PathHome Path
Linux/BSDs/etc$HOME/.config/dotfiles$HOME/.dotfiles
MacOS$HOME/Library/Application Support/dotfiles$HOME/.dotfiles
Windows%HomePath%\AppData\Roaming\dotfiles%HomePath%\\.dotfiles

The dotfiles directory itself has the following structure:

dotfiles
├── Configs # Stores all configuration files
├── Secrets # Stores encrypted files
└── Hooks # Stores scripts that configure the system

Dotfile validation

All dotfiles are stored inside of the Configs directory. Inside this directory you will find a directory for each program or group of dotfiles (I recommend making each group the dotfiles for a specific program, but you can group them however you want). Tuckr will use these groups to same files that are already in your $HOME directory:

  • if it points to the dotfile in the repo it's deemed as symlinked
  • if it's not symlinked it's deemed as unsymlinked
  • if it's a symlink but points somewhere else it's put on the not_owned bucket to indicate that it's in conflict

All these checks are done right after the program's execution starts. Once the entire repo is mapped to either one of those states. The actual command execution starts.

Some commands work only on dotfiles with certain statuses, such as:

  • add: works on not_symlinked and not_owned if conflicts are being resolved
  • rm: works on symlinked

Hooks

Hooks are scripts used to configure and clean up your dotfiles. You can run hooks before and after adding a dotfile, you can also run a hook when removing a dotfile. These hooks are only called if using the set and unset commands.

The hooks are stored in dotfiles/Hooks and when they should be run is determined by their suffixes:

  • pre_: run before adding dotfiles
  • post_: run after adding dotfiles
  • rm_: run when removing dotfiles

Most scripts will likely not matter if they're run before or after having the dotfiles symlinked, but sometimes this is useful.

Secrets

Secrets use is discouraged right now, they're the least maintained part of the program as I've spent most of the time making sure the other parts of the program are correct and useful.

Secrets might end up being removed later on or improved enough that I might start recommending people to use it.

Profiles

A profile is a way to keep dotfiles in different repos in the same machine. Say you have dotfiles that are personal and others that should only be for work and is on a private repo.

You could attempt to deploy it with tuckr, but you would either have to:

  1. make the private work repo a submodule and have tuckr dump every dotfile as one group
  2. make your work dotfiles part of your personal one (which might not be feasible)

Profiles fix this by allowing you to be able to work with multiple repos at once.

How it works

Profiles are essentially just a dotfiles directory with a suffix.

It follows this structure: dotfiles_<profile> If you have a dotfiles_work directory in the lookup path, you can use work as a profile:

tuckr -p work status

And it will give you the status for dotfiles in this repo

If you're using the unsuffixed dotfiles, no profile flag is needed. So you can consider that one your default profile.

Listing profiles

This command lists every profiles that are available in the machine

$ tuckr ls profiles

Conditional groups

Conditional deployment is used when a dotfile should only be deployed on a specific platform. All you have to do is add a suffix for the platform where the dotfile is valid on and it will be ignored on every other platform by tuckr.

Example:

Configs
├── config
├── config_unix          // deployed on any unix system
├── config_linux         // only on linux
├── config_macos         // only on macos
├── config_windows       // only on windows
└── config_wsl           // only on Windows Subsystem for Linux (WSL)

The conditional groups are treated as if they were files in the regular base group. So if you were on windows the config and config_windows groups are treated as a single group, if you had a config_linux, it would be as if the files in this group didn't exist at all.

If use only the conditional group explictly, you can do that as well, for example: tuckr rm config_linux (this won't remove config)

Dotfile fallback

A nice thing about conditional dotfiles is that they can fallback. You can have the same config file multiple times in different conditional groups and tuckr won't complain. It will just choose the most specific one for your platform.

This is possible because the suffixes have a priority order. The order is defined like this:

  1. Custom targets
  2. Windows Subsystem for Linux
  3. OS name (e.g: linux, freebsd, macos)
  4. OS family: unix, windows
  5. Non conditional groups (groups without a conditional suffix)

Any of the options available on Rust's target_family and target_os are valid targets.

Custom targets

Custom targets are targets that has a suffix that starts with a #. These are user defined targets that can be enabled programmatically. Custom targets are the highest priority targets in Tuckr, so they will allows override any other group.

They were created to be used as a way to deploy different variants of a file under the same platform. But you can use it for whatever application you may find for them.

For example if you're running on Linux but have a raspberry pi and a desktop and want different configs to be used for a certain program you can create a group_#raspberrypi and group_#dekstop. Then you can pick which to choose by using tuckr -t raspberrypi add group to tell tuckr that raspberry pi is a valid target right now.

If your custom target is a more permanent target you can use the TUCKR_CUSTOM TARGETS environment variable or create a tuckr alias in your shell that adds the flag.

Both the flag and the env variable support defining multiple targets at once by separating them with a comma:

$ tuckr -t raspberrypi,desktop add group
$ TUCKR_CUSTOM_TARGETS="raspberrypi,desktop" tuckr add group

Dotfiles paths

By default tuckr will use your $HOME to symlink things and your dotfiles have to be on the standard config directory for your platform. This is generally the most desirable behavior. But if you don't like this and/or need to use other directories, you can set the TUCKR_HOME and TUCKR_TARGET environment variables to override the default behavior.

  • TUCKR_HOME: parent directory for your dotfiles. It assumes that your dotfiles directory is named as dotfiles.
  • TUCKR_TARGET: the base directory from which all dotfiles will be deployed (with the exception of root and environment variable paths). By default this is $HOME on UNIX-like systems and %USERPROFILE% on Windows.

Another use case for overriding these is if you're managing dotfiles for many users, you can then set these variables to point to those users' directories.

Path from environment variable

Certain programs depend on environment variables to know where to put things in. Sometimes you might also just want to script the deployment of your dotfiles. For both cases you can expand your path through an enviroment variables.

You can tell Tuckr to expand an environment variable by using %.

For example if you set an environment variable PROGRAM_PATH to /home/user/Documents/program and you have a dotfile with this file structure:

program
└── %PROGRAM_PATH
    └── config.txt

Tuckr will expand it to /home/user/Documents/program/config.txt.

Path from root

If you're managing a global configuration from Tuckr, you need to tell if that the configuration is global. Otherwise it will attempt to deploy it to $HOME. The way to tell if is just by using ^, it will then expand your dotfile path starting from that path into root.

If you have a dotfile:

xorg
└── ^etc
    └── X11

This will be expanded to /etc/X11.

Bear in mind that this can be anywhere inside your dotfile group, wherever it is the expansion will always be the same. You can even put one inside the other and the last one to occur is the one that takes precedence. For example if you have:

xorg
└── ^etc
    └── %PROGRAM_PATH
        └── some_file

Then some_file will end up in whatever path was in this variable and not in root. If it were the other way around (xorg/%PROGRAM_PATH/^etc/some_file) then it would be in root but not in the path in the environment variable.

Debugging

If you're trying to figure out what will happen or where things will go. You can enable dry-running commands.

Dry running means that Tuckr will tell only you what actions it would take without carrying them out. If you do dry run and nothing is printed. That just means that there was nothing to do.

You can enable dry running by using the -n or --dry-run flags after tuckr. Examples:

$ tuckr -n add \*
$ tuckr -n add -f zsh
$ tuckr -n set neovim

Globbing and excluding groups

Globbing

Globbing permits you to select every group at once. Using * is the same as selecting every group that match the following criteria:

  • Is not currently symlinked
  • Does not contain conflicting files
  • Is supported by the current platform (for example group_windows would only work on windows, group_linux only on linux, group everywhere)

If you try to use tuckr add -f \* to try to override every conflict, this won't work. You need to manually name every group you want to override like so tuckr add -f group1 group2. It's recommended that you don't blindly just override everything, check tuckr status <groups> to see what files are causing conflict and if you want to delete or adopt certain files or if you you're 100% sure that is the case for all the files you can then use the -a or -f flags, otherwise you could always just manually resolve them, depending on your needs.

$ tuckr add \*

Note: on some systems the glob symbol * is not part of the shell's operators and thus do not need to be escaped.

Exclusion

Exclusion permits you to exclude specified groups

$ tuckr a firefox nvim hyprland -e firefox
$ tuckr a \* -e firefox,nvim,hyperland

Exclusion is most useful when globbing, it allows you to add and remove groups en masse.

Dotfile commands

Status

When status is run with no arguments:

$ tuckr status

it checks the status of all programs and displays it, returning either 0 or 1 depending on whether there were any remaining unsymlinked dotfiles.
If one wishes to check the status of individual or multiple programs that is achieved by running:

tuckr status program...

When returning tuckr returns an error code pertaining to the specified programs.

Add

The add command symlinks unsymlinked groups, returning an error code in case of failure symlinking. By default add ignores all conflicting files, conflicts should be handled by checking the status command and deciding whether to adopt or override conflicts.

Say there's a group foo in conflict, to adopt the system's dotfile:

$ tuckr add -a foo

To override conflicts:

$ tuckr add -f foo

Rm

The remove command removes dotfiles marked as symlinked, it only returns an error if there was a failure to remove files. It accepts either a single program or multiple

$ tuckr rm foo
$ tuckr rm foo bar

Hook commands

Hooks are scripts that are run to set up your environment. A hook is essentially directory in the dotfiles/Hooks directory. To create a hook foo, one creates a directory dotfiles/Hooks/foo and put scripts there. Those scripts have to be prefixed with either pre_, post_ or rm_, if they're not prefixed they're ignored by tuckr.

Hooks are run in 3 step: pre-hooking -> symlinking -> post-hooking. They are associated with Configs groups of the same name. So if one has a Configs/foo and a Hooks/foo. So the Configs/foo group is symlinked after a successful run of the pre-hook.

Running setup hooks

Hooks are run by using the following command:

$ tuckr set foo

All setup hooks are run if the following command is used:

$ tuckr set \*

A few flags can be used to change the behavior of the hooking and symlinking steps, check tuckr help set to know more.

Running cleanup hooks

Just like setup hooks they follow these 2 steps: run-cleanup -> remove-symlinks

$ tuckr unset \*

A practical example

If one has a neovim config, where one needs to download a plugin manager (packer), the npm and pip neovim libraries, and after installing them you need to download all of your plugins and LSPs, this could be achieved with hooks:

  1. Prehook: install the neovim libraries for npm and pip and download packer
  2. Symlink: symlink neovim configs (if previous step was successful)
  3. Posthook: run command to install plugins with packer and install all LSPs with mason

Listing hooks

If you want to know which hooks are available run:

$ tuckr ls hooks

This will print a table informing you about which hooks are available and you'll either have a tick or an X.

  • A tick means that there's a pre-hook or post-hook available
  • An X means that there's no pre-hook or post-hook set up
                                      
    ╭───────┬─────────┬──────────╮    
    │ Group │ Prehook │ Posthook │    
    ├───────┼─────────┼──────────┤    
    │ nvim  │    ✗    │    ✓     │    
    │ tmux  │    ✓    │    ✗     │    
    │ zsh   │    ✓    │    ✗     │    
    ╰───────┴─────────┴──────────╯