Foreman, Guard and Tmux Combo
If you ever wanted to use forman with guard but don’t want to go without interactive mode, this little helper might be useful for you.
I recently added react_on_rails
to one of my rails projects.
Since there’s now a second process you need to run in parallel in the background,
webpack, it initially sets up a Procfile.dev
to be used with foreman.
Foreman is a nice little tool helping to start up a development environment for complex projects – with complex starting at 2 processes like here. You can read more about it here.
The initial setup adds the following Procfile.dev
web: rails s -p 3000
client: sh -c 'rm app/assets/webpack/* || true && cd client && yarn run build:development'
However, my standard development environment invokes guard
in a tmux pane,
starting the rails server with the help of the guard-rails
gem.
Simply replacing rails s -p 3000
with guard
won’t do the trick since guard
has an interactive shell built in and to be honest, I don’t want to integrate the
output of guard to the foreman result either, because space is limited already and
adding something like 21:35:34 guard.1 |
at the beginning of each line makes
guard not more readable.
So I decided to start guard into another tmux pane - using foreman.
First trick is to not start guard itself but send a command to a tmux pane. You can achieve this with
$ tmux send-keys [options] 'guard' Enter
$
Unfortunately the tmux send-keys
command will return immediately letting foreman
also stop immediately, so we have to somehow wait for the guard command to terminate.
We achieve this by letting tmux
wait for some event and send it as soon as guard
terminates.
The first working version looks like this:
guard: tmux send-keys -t 2 'guard; tmux wait-for -S tmux-guard' Enter \; wait-for tmux-guard
client: sh -c 'rm app/assets/webpack/* || true && cd client && yarn run build:development'
This will do the trick, assuming you want to run guard in tmux pane 2.
However there’s a drawback: whenever you kill foreman, guard continues to run,
because foreman sends SIGTERM
and SIGINT
signals to the processes it started
by itself, i.e. to the tmux send-keys
command. So this won’t work.
We have to exit guard on our own.
For that I wrote a simple little script that does exactly that and takes over the tmux part:
#!/usr/bin/env ruby
$exit_sent = false
def send_exit
unless $exit_sent
system("tmux send-keys #{ARGV.join(' ')} 'e' Enter");
$exit_sent = true
end
end
Signal.trap("TERM") { send_exit }
Signal.trap("INT") { send_exit }
system("tmux send-keys #{ARGV.join(' ')} 'guard; tmux wait-for -S tmux-guard' Enter \\; wait-for tmux-guard")
puts("guard stopped")
Using this script in the Procfile.dev
helps me to start guard in a different tmux pane
and terminating it gracefully whenever foreman is stopping for whatever reason.
guard: /path/to/tmux_guard_runner.rb -t 2
client: sh -c 'rm app/assets/webpack/* || true && cd client && yarn run build:development'
No Comments