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

×
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

×
bash
$ 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:

×
Procfile.dev
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:

×
tmux_guard_runner.rb
#!/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.

×
Procfile.dev
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