I run a shared hosting web server for a small number of clients. This web server is home to multiple content management systems including several different instances of Drupal.
To help myself and my users maintain their Drupal sites, every user on the server should be able to access Drush and use it on their own Drupal sites. Ideally this would be accomplished by simply typing drush
in the command line ensuring it is easily accessible to both myself and my clients (though, mostly just for me).
Historically, the standard way of installing Drush required users to download Drush (the drush.phar
file), set its execute permissions and then move it to /usr/local/bin/drush
. This enabled the Drush
command to be automatically globally accessible from the command line for every user. When Drupal shifted to using Composer to manage dependencies including Drush itself, Drush—or more accurately, the Drush Launcher—would detect the presence of Drush in the Composer vendor directory and use that binary instead. This made managing multiple Drupal sites with multiple different versions of Drupal and Drush easy.
Then Drush 12 came out, dropping support for Drush Launcher, resulting in errors like:
The Drush launcher could not find a local Drush in your Drupal site.
Please add Drush with Composer to your project.
This change in compatibility was likely due to the community desperately doing what it could to move on from Drupal 7 and the older versions of Drush. I appreciate the motivations and believe that we are better for it. However this approach and recommended means of "installation," or, more aptly, means of making Drush globally accessible seemed not very globally accessible to my use case at all. The use case and installation process for a beloved and arguably mandatory tool should be consistent—not being radically different between major versions without providing a means to ensure that expected operations and functionality continue as such. One should not be required to type in ./vendor/bin/drush
just to use Drush. One should also not be required to edit their $PATH
variable in either their ~/.bashrc
, ~/.bash_profile
, or ~/.zshrc
files to point to a binary installed by Composer for a specific site which could be one of many sites a single user could have, for all users who needs Drush access. (Link) This greatly reduces the flexibility that the previous implementation of Drush afforded those of us who run or use shared web hosting services. It simply isn't compatible with a user having more than one Drupal site.
One might say, "If you keep your Drupal instances up to date, you won't have this problem." While this is true, I believe this recommended approach contradicts the recommended process of having one Drush binary per Drupal site. The whole point of this is to ensure that your site is in alignment and stays in alignment with its dependencies, all of its dependencies, including Drush.
Consider performing a major upgrade to Drupal. In order to succeed at this, one typically must fully remove Drush from the project otherwise Composer will fail with an ambiguous error about dependencies. If one were to complete the upgrade of the site whose Drush binary is included in $PATH
variable, and move onto another site one may find themselves running into issues with compatibility between the new version of Drush and the Drupal site they are trying to update. This makes the whole process crazy-making, especially when one starts to troubleshoot the Drush issues, checking Composer to find out that the correct version of Drush for the respective Drupal version is already installed. To overcome this, the user would have to play musical $PATH
variables, and close and re-open their terminal to switch between Drush versions. This would also further complicate any upgrade troubleshooting processes that require users to switch back and forth between sites for more complex setups.
Googling this problem, the answers I could find were generally unhelpful. It seemed like guidance and recommendations from the community were generally geared towards single-user, single-site hosting circumstances. To overcome this, I produced a bash script that acts as a wrapper for Drush, emulating the previous functionality of the Drush Launcher's ability to select and use the site's respective instance of the Drush binary.
This wrapper will traverse the file system upwards looking for an instance of /vendor/bin/drush
stopping at /
and showing an error if it does not find an instance of the Drush binary. This ensures that the drush
command will work regardless of where you are within your project's directories.
To install this "Lite Drush Launcher" simply create a file at /usr/local/bin/drush
, with the following contents:
#!/usr/bin/env bash
SEARCH_DIR="$(pwd)"
while [ "$SEARCH_DIR" != "/" ]; do
DRUSH_EXEC="$SEARCH_DIR/vendor/bin/drush"
if [ -x "$DRUSH_EXEC" ]; then
if [[ "$1" == "completion" || "$1" == "_completion" ]]; then
exec "$DRUSH_EXEC" "$@"
fi
exec "$DRUSH_EXEC" "$@"
fi
SEARCH_DIR="$(dirname "$SEARCH_DIR")"
done
# Suppress error output if being sourced for shell completion
if [[ "$1" == "completion" || "$1" == "_completion" ]]; then
exit 0
fi
echo "Error: No local Drush instance found." >&2
exit 1
Once you've created the file, make use it is executable with the command sudo chmod +x /usr/local/bin/drush
.
BONUS: Drush Completion in Bash
If you were paying attention to the previous block of code you may have noticed that there was a line in there specific to error suppression for shell completion.
While researching this issue, I stumbled across the ability for Drush to support command line completion. Not a game changer per-se because the completion seems, incomplete, but definitely welcome none the less. The practice of relying on one Drush binary included for a single site could also have negative consequences for this functionality as well as it relates to other sites.
To enable autocompletion in bash, make sure that bash-completion
is installed on your system and create a file at /etc/bash_completion.d/drush
with the following contents:
# Dynamic Drush completion with prompt-based rebinding and file completion support
# Track the last known Drush exec path and completion function
__drush_last_fn=""
__drush_last_path=""
# Find nearest vendor/bin/drush upward from current directory
_find_drush_exec() {
local dir="$PWD"
while [ "$dir" != "/" ]; do
if [ -x "$dir/vendor/bin/drush" ]; then
echo "$dir/vendor/bin/drush"
return
fi
dir="$(dirname "$dir")"
done
}
# Dynamically (re)bind or unbind Drush completion based on current directory
_drush_auto_bind_completion() {
local drush_exec=$(_find_drush_exec)
if [ -z "$drush_exec" ]; then
if [ -n "$__drush_last_fn" ]; then
complete -r drush 2>/dev/null
__drush_last_fn=""
__drush_last_path=""
fi
return
fi
if [ "$__drush_last_path" = "$drush_exec" ]; then
return
fi
local script
script="$("$drush_exec" completion bash 2>/dev/null)"
local fn_name
fn_name=$(echo "$script" | grep -oP '^_[^()]+(?=\(\))' | head -n1)
if [[ -n "$fn_name" ]]; then
eval "$script"
if declare -F "$fn_name" >/dev/null 2>&1; then
complete -o default -F "$fn_name" drush
__drush_last_fn="$fn_name"
__drush_last_path="$drush_exec"
fi
fi
}
# Bootstrap once at shell load
_drush_auto_bind_completion
# Re-evaluate after each command prompt (e.g., after 'cd')
if [[ "$PROMPT_COMMAND" != *"_drush_auto_bind_completion"* ]]; then
PROMPT_COMMAND="_drush_auto_bind_completion${PROMPT_COMMAND:+; $PROMPT_COMMAND}"
fi
After doing this, when you are on the project directory of a site that has Drush installed, pressing tab while typing in a command will cause bash to complete the command for you.
For example: when you type drush sta <tab><tab>
it will autocomplete to drush status
. Nice!