env_hooker is a shell environment switcher, similar to direnv, autoenv and others.
It helps to solve problems of the following type:
I've
cd'd to a directory containing a node project. I'd like to add.node_modules/binto myPATH.
or
I've
cd'd to a directory containing a ruby project. I'd like to switch to the correct ruby version for the project, and set up gems to install and run from a project-specific location.
In each case we want to modify the shell environment to support a particular project type's workflow. When we enter the project directory, some environment manipulation is performed, and when we leave, it's usually desirable to tear it down again.
env_hooker does this by allowing you to define hook functions that
will run when a directory is entered or exited that contains a specified
project file - for example, I might want to perform custom ruby setup
based on the presence of a .ruby-version file.
Installation is still pretty basic. First clone the repo:
$ git clone https://github.com/urbanautomaton/env_hooker
$ cd env_hooker
Then:
# installs to /usr/local/share/env_hooker/env_hooker.sh
$ make install
Optionally specify PREFIX (default: /usr/local) to control
installation location:
# installs to /opt/share/env_hooker/env_hooker.sh
$ PREFIX=/opt make install
Then source the file in your .bashrc (or wherever) and start defining hooks:
# ~/.bashrc
. /usr/local/share/env_hooker/env_hooker.sh
function enter_some_project_type() {
# ...
}
function exit_some_project_type() {
# ...
}
register_env_hook .somehookfile some_project_type- pick a hook file and function root name (e.g.
.ruby-versionandruby_project) - define entry and exit functions:
enter_<function_root>exit_<function_root>
- Register the hook in your shell setup:
register_env_hook <hook_file> <function_root>
This is easiest shown by example. The following uses
chruby to switch to the version
specified in a .ruby-version file, if one is present, and resets
chruby on leaving the directory.
# ~/.bashrc
. /usr/local/share/env_hooker/env_hooker.sh
function enter_ruby_project() {
local -r project_dir=$1
local version
read -r version < "${project_dir}/.ruby-version"
[[ -n "${version}" ]] && chruby "${version}"
}
function exit_ruby_project() {
chruby_reset
}
register_env_hook .ruby-version ruby_project(Note: I've used chruby as an example here because the pattern I've used is directly inspired by chruby's own auto-switching, and I found the testing for that project to be a fantastic learning resource. You can test bash scripts! Who knew?)
Before adding project-local directories to your PATH, there are some
security considerations to, uh, consider.
Adding any directory within a project to your PATH is a risk if an
attacker can control that location. This is always liable to be the case
when cloning git repositories.
For example, let's say I add a hook that, in the presence of a
.nodeproject file, adds node_modules/.bin to the PATH. An attacker
could construct a malicious repo of the following form:
.
├── .nodeproject
└── node_modules
└── .bin
└── ls
where ls is executable and contains:
#!/bin/bash
rm -rf /
to cause havoc on your machine if you cloned, entered and listed the contents of the repo.
For this reason I recommend being very careful when adding in-repo
directories to your PATH. Using env_hooker to do so is perhaps
slightly safer than, say, globally adding ./bin to your path, but it's
still a fairly big risk to take. If there's a way to get your package
manager to install executables outside the project directory, I
recommend that you do so (e.g. bundler's BUNDLE_BIN setting), since
such locations can safely be automatically added to your PATH.
See my rambly release article for a discussion of alternative projects that you should almost certainly prefer to this one.