A simple and efficient dotfiles manager for Unix systems that not only syncs
your configuration files but also handles package installation, upgrades and
uninstallation. Created out of frustration with the original home-manager
nix
project (very slow, probably a skill issue, I know).
hm
helps you manage your dotfiles and system configurations by:
- Copying or symlinking configuration files from a source directory to their target locations
- Installing, uninstalling, and upgrading packages based on instruction files
- Tracking changes with a lockfile system
- Managing dependencies for your configurations
- Providing an interactive TUI for easier configuration management
# Clone the repository
git clone https://github.com/blanktiger/hm.git
# Build the binary
cd hm
# Make sure ~/.local/bin is in your `$PATH`
go build -o ~/.local/bin/hm
# Symlink all configs
hm
# Hard copy all configs
hm --copy
# Install packages along with copying configs
hm --install
# Only install packages without copying configs
hm --only-install
# Upgrade already installed packages
hm --upgrade
# Uninstall packages that you prefixed with `.`
hm --uninstall
# Only uninstall packages without modifying configs
hm --only-uninstall
# Acts like passing both --install and --uninstall at the same time
hm --manage
# Manage specific packages
hm --pkgs fish,ghostty
# Use the interactive TUI mode
hm --tui
# Enable debug output
hm --dbg
hm
includes an interactive Text User Interface (TUI) mode that allows you to:
- Select which configurations to symlink/copy
- Choose which global dependencies to install
- Persist your selections to disk
To use the TUI mode, run:
hm --tui
Navigation:
- Use arrow keys (or j/k) to move up and down
- Press
Space
to toggle selection - Press
Tab
to move to the next screen - Press
Shift+Tab
to move to the previous screen - Press
q
orCtrl+c
to exit
The TUI has four screens:
- Program flags - manage CLI flags interactively
- Config selection - choose which configs to copy/symlink
- Global dependencies - select which packages to install
- Additional options - decide whether to persist your selections
By default, hm
looks for configurations in:
- Source directory:
$HOME/.config/homecfg/config/
- Target directory:
$HOME/.config/
These directories can be overwritten by doing:
hm --sourcedir ~/my-dotfiles --targetdir ~/.local
Each configuration in the source directory should be a folder:
$HOME/.config/homecfg/
└── config/
├── DEPENDENCIES # Global installation instruction file (to install many common dependencies)
├── fish/ # Fish shell configuration
│ ├── config.fish
│ ├── INSTALL # Installation instructions
│ ├── UNINSTALL # Additional shell script run during uninstallation
│ └── DEPENDENCIES # Required dependencies
├── nvim/
│ ├── init.lua
│ ├── INSTALL
│ └── UNINSTALL
└── .tmux/ # Hidden dirs are skipped (notice the dot)
├── tmux.conf
├── INSTALL # Used to determine uninstallation method for dirs hidden for the first time
└── UNINSTALL # Shell script for cleanup (running once when dir is hidden for the first time)
The INSTALL
file contains an instruction for installing a package, format is method:package
. An example if you wanted to install fish
could be:
system:fish
Available methods are:
system
apt
pacman
dnf
brew
aur
yay
paru
pacaur
aurman
cargo
cargo-binstall
bash
, this executes what you write directly after the:
. This method doesn't provide automatic uninstall instruction generation, which means that you will not be able to use--uninstall
to remove a package installed this way.
An example using bash could be:
bash:curl -fsSL https://example.com/install.sh | bash
The system
method is particularly powerful as it dynamically detects your
operating system package manager and uses the appropriate installation command.
For example:
- On Debian/Ubuntu systems, it will use
apt
- On Arch Linux, it will use
pacman
- On Fedora, it will use
dnf
- On macOS with Homebrew installed, it will use
brew
This makes your configuration files more portable across different systems.
Similarly, the aur
method will detect which AUR helper is installed on your system
(paru, yay, pacaur, or aurman) and use it automatically.
The UNINSTALL
file is treated as a bash script that will be executed during uninstallation.
When INSTALL
contains packages installed via methods other than bash
, the UNINSTALL
file is
optional since hm
can generate uninstallation commands automatically.
However, for packages installed with the bash
method, you need to provide an UNINSTALL
file
to specify how to remove the package. You can also use this file as a cleanup script, since it
will always run during uninstallation regardless of installation method.
Example UNINSTALL
file:
#!/bin/bash
# Clean up configuration files
rm -rf ~/.cache/my-program
# Run the program's uninstaller
/opt/my-program/uninstall.sh
The DEPENDENCIES
file lists dependencies required for a configuration:
cargo:fd
system:fzf
system:git
Each line follows the same method:package
format as the INSTALL
file.
NOTE: Currently dependencies are only installed. They aren't uninstalled when
you uninstall the config that owns them if you don't pass the --uninstall
flag.
The config/DEPENDENCIES
file (at the root of your config directory) specifies global dependencies
that should be installed regardless of which configurations are active. These are installed before
any configuration-specific packages.
This is useful for installing tools that are required by the installation process itself or for packages that are common dependencies for multiple configurations.
Example:
system:git
system:curl
cargo:cargo-binstall
To show all available logs produced during execution:
hm --dbg
To install, uninstall, or upgrade only specific packages:
hm --pkgs fish,nvim --install
hm --pkgs tmux --uninstall
hm --pkgs fish,nvim --upgrade
Hidden Configurations
Configurations with directories prefixed with a dot (e.g., .tmux/
) are considered "hidden"
and won't be symlinked/copied or installed by default. This allows you to temporarily disable
configurations without completely removing them.
To uninstall packages associated with hidden configurations:
hm --uninstall
In TUI mode, you can toggle which configurations are active (not hidden) and choose whether to persist these selections to disk.
hm
creates a lockfile (hmlock.json
) in the target directory to track:
- What configurations have been deployed
- What packages have been installed
- Installation timestamps
- Hidden configurations
- Global dependencies
The lockfile format is JSON and includes:
version
: The version of the lockfile formatmode
: Whether configs are symlinked or copiedglobalDependencies
: List of globally installed packagesconfigs
: List of active configuration directorieshiddenConfigs
: List of configuration directories that have been hidden
Additionally, a diff file (hmlock_diff.json
) is generated to show changes between runs, including:
- Added/removed configurations
- Added/removed global dependencies
- Changes in mode or version
hm
scans the source directory for configuration folders- Parses
config/DEPENDENCIES
file (if exists) for global dependencies and installs them - For each configuration, it:
- Copies or symlinks the files to the target location
- Parses
INSTALL
,DEPENDENCIES
andUNINSTALL
files if present - Installs packages and dependencies when requested
- Uninstalls packages when requested
- Tracks all activities in a lockfile (
hmlock.json
) - Generates a diff file (
hmlock_diff.json
) to show changes
- While
hm
supports installing software, it doesn't install tools needed for the installation process itself. For example,hm
won't installcargo-binstall
automatically, even if you usecargo-binstall
as an installation method for some package. To do that the recommended way is to putsystem:cargo-binstall
instruction in theconfig/DEPENDENCIES
file. - If installation fails for any package,
hm
will continue processing other configurations. - Hidden directories (starting with a dot) are not managed by
hm
by default. - If you hide a configuration directory previously managed by
hm
, you can uninstall its dependencies by passing--uninstall
. - The
.git
directory is always ignored. - When running in TUI mode, the command-line flags still apply but can be modified in the first screen of the interface.