A symlink farm manager similar to GNU Stow. It allows you to create symlinks
in a directory such as your home directory that all point to files under a
single directory, such as ~/dotfiles
. You can then check your ~/dotfiles
into version control, without fear of polluting the repository with other files
from your home directory.
- Zero dependencies, just download and run the binary
- Keeps track of which symlinks have been created
- Scriptable hooks to run before or after a package is installed
You can install it with go install
.
go install github.com/jamesbehr/stowaway
If you just want to download the binary, you can also do that. This is useful for bootstrapping scripts that will setup your dotfiles on a clean install. For example, you can download the 64-bit x86 Linux binaries like this. See the releases page for more information about which platforms are supported.
curl -LO https://github.com/jamesbehr/stowaway/releases/latest/download/stowaway-linux-amd64.tar.gz
curl -LO https://github.com/jamesbehr/stowaway/releases/latest/download/stowaway-linux-amd64.tar.gz.sha256sum
sha256sum -c stowaway-linux-amd64.tar.gz.sha256sum
tar -xzf stowaway-linux-amd64.tar.gz
rm stowaway-linux-amd64.tar.gz*
Like GNU Stow, Stowaway operates on packages. Packages are GNU Stow compatible, in that they are just directories containing a number of files. Each file inside the package will get a symlink in the target directory.
Assuming you've cloned the repository into a directory called stowaway
,
$ find stowaway/examples/bash
stowaway/examples/bash
stowaway/examples/bash/.bashrc
The package will be installed into a target directory. Stowaway will create a symlink to each file in the package in the target directory and create any missing directories along the way. The symlinks path in the target directory corresponds to its path in the package directory.
You can install a package by running the stow
command. You can specify
multiple packages paths to install into a target directory. If you do not
specify a target directory with the --target
flag, then the current working
directory will be used as the target. If the package is already installed it
will be uninstalled before being reinstalled.
$ pwd
/home/me
$ rm .bashrc
$ cp -avr stowaway/examples ~/dotfiles
$ stowaway stow ~/dotfiles/bash
After installing there will be a file called /home/me/.bashrc
pointing to the
file in the package ~/dotfiles/bash/.bashrc
.
$ ls -a .bash*
.bash_logout
.bashrc
$ readlink -f ~/.bashrc
/home/me/dotfiles/bash/.bashrc
If you want to uninstall a package you can provide the --delete
flag. This
will clear up symlinks even when the original package has been modified.
$ rm ~/dotfiles/bash/.bashrc
$ stowaway stow --delete ~/dotfiles/bash
$ ls -a .bash*
.bash_logout
You can also list the packages installed in a given directory. If you do not
override it with the --target
flag, then it lists packages installed into the
current working directory by default.
$ stowaway stow dotfiles/bash stowaway/examples/git
$ stowaway packages
/home/me/stowaway/examples/git
/home/me/dotfiles/bash
$ stowaway packages --prefix /home/me/stowaway
/home/me/stowaway/examples/git
$ stowaway stow --delete dotfiles/bash stowaway/examples/git
You can also pass the --interactive
flag to the stow
command, which will
prompt the user to select which packages they want to install or uninstall from
the list of packages you provide as arguments. This allows you to do things
like passing in all the available packages as arguments and have the user
select which ones they want to install.
Stowaway also supports some advanced features, such as installation hooks.
To use these advanced features, you'll need to use a different package structure to the normal, GNU Stow-compatible, packages. An package that wants to use these features might look like this:
$ find stowaway/examples/bash-advanced
stowaway/examples/bash-advanced
stowaway/examples/bash-advanced/src
stowaway/examples/bash-advanced/src/.bashrc
stowaway/examples/bash-advanced/stowaway.toml
stowaway/examples/bash-advanced/hooks
stowaway/examples/bash-advanced/hooks/after_install
Notice the stowaway.toml
in the root of the package. This is the package
manifest. The presence of the package manifest enables the advanced features.
All the files that will get symlinks created are now located under the src
directory in the package. You can change this package by setting the source
configuration option in the package manifest. All symlink names are derived
from the name of the file relative to this directory, that is to say the name
of the symlink pointing to src/.bashrc
will be $TARGET/.bashrc
, not
$TARGET/src/.bashrc
(where $TARGET
is the installation target directory).
Package manifests are written in TOML and currently support the following options.
name = "foobar" # Package name - defaults to the name of the package directory
source = "files" # The directory where all the files in the package are kept. Defaults to "src"
hooks = "scripts" # The directory where hooks are package. Defaults to "hooks"
The package can also specify hooks, which work similarly to Git hooks. A hook
is just a file with the executable flag set. This file will be executed at
certain points in the package life cycle. For example, you might have a package
that has code written in a compiled language. You could use a hook that runs
after the package is installed to run make
and compile the package.
Hooks by default are kept in the hooks
directory, but this can be changed
with the hooks
manifest option.
the packages installation state directory passed as their only argument. See the section on package state. The name of the hook specifies the life cycle event that will cause it to run.
The hooks also get called with the following environment variables set. This is more useful for hooks that run prior to installation, when no package state has been created.
STOWAWAY_SOURCE
is set the the absolute path containing the package's source files.STOWAWAY_TARGET
is set the the absolute path where the package will be installed.STOWAWAY_PACKAGE_ROOT
is set the the absolute path of package root.
The following hoooks are currently available, in the order they are run:
before_uninstall_all
: Run for each selected package in astow --delete
operation.before_uninstall
: Run for a package right before it is uninstalled. Only run if the package is installed.after_uninstall
: Run after uninstalling the package.after_uninstall_all
Likebefore_uninstall_all
, but run after every package was uninstalled.before_install_all
: Run for each selected package in astow
operation.before_install
: Run for a package right before it is installed.after_install
: Run after installing the package.after_install_all
Likebefore_install_all
, but run after every package was installed.
The example bash-advanced
uses an after install
hook. It creates a file
customfile
in the target directory.
$ stowaway stow stowaway/examples/bash-advanced
$ cat customfile
hello from after install hook
$ stowaway stow --delete stowaway/examples/bash-advanced
Stowaway keeps track of each package installed in the .stowaway
directory
inside the target directory. Inside this directory are a number of
subdirectories, each containing the state of an installed Stowaway package.
$ stowaway stow stowaway/examples/bash
$ find /home/me/.stowaway
/home/me/.stowaway
/home/me/.stowaway/37bc12
/home/me/.stowaway/37bc12/links
/home/me/.stowaway/37bc12/links/0
/home/me/.stowaway/37bc12/source
/home/me/.stowaway/37bc12/target
In the example above, /home/me/.stowaway/37bc12
is the package installation
state directory.
For each symlink that Stowaway creates, it creates another symlink pointing to
that symlink inside the links
directory. This enables Stowaway to keep track
of which symlinks it has created, even when the contents of the package have
been modified.
The target
and source
directories are symlinks to the installation target
and package source directories respectively. For packages with a manifest, this
defaults to the src
directory in the package root, and is the same as the
package root for packages without a manifest.
$ readlink /home/me/.stowaway/37bc12/links/0
/home/me/.stowaway/37bc12/target/.bashrc
$ readlink /home/me/.stowaway/37bc12/target/.bashrc
/home/me/.stowaway/37bc12/source/.bashrc
$ readlink -f /home/me/.stowaway/37bc12/source/.bashrc
/home/me/stowaway/examples/bash/.bashrc
You can run the unit tests by running make test
.
You can also verify that the examples in the README are correct by make doctest
. This requires Docker to be installed. The doctests validate that
every code fence in this markdown document that has console
selected as its
language actually outputs what is written down.