Fun with tmux

2 Aug 2021

I finally got around to teaching myself tmux.

I had a bunch of microservices that had to start up in a certain order, and I wanted each microservice to be in its own tmux window, and I wanted each window to show a tail of that microservice's logs (using -f).

First, I created some scripts that pretended they were processes that had a long boot-up time.

Here is the script that pretends it sets up a kind cluster (which generally tends to take a while), named z_init.sh:

#!/bin/bash

set -e
set -o pipefail
set -u

echo init kind cluster
sleep 5
echo init some other thing
sleep 5
echo init db
sleep 5
echo done initing

Here's a script that pretends to launch a microservice that can only be launched after the init step above has happened, named z_events.sh:

#!/bin/bash

set -e
set -o pipefail
set -u

echo init events
sleep 10
echo done initing

There are two more scripts, z_billing.sh, and z_admin.sh, not shown, which are pretty much exactly the same as the above script; they pretend to be other microservices.

Here's a script that pretends to tail the logs of a microservice after it has started up, named z_heartbeat.sh:

#!/bin/bash

set -e
set -o pipefail
set -u

while true; do
	echo pretend I am a log file for service ${1}
	sleep 2
done

Here is a shell script named z_dev.sh that will:

  1. create a tmux session named mydevsession
  2. launch z_init.sh in the first window and wait until it is done
  3. launch z_events.sh in a new window named "events" and wait until it is running
  4. tail (forever) the event service's logs (by using z_heartbeat.sh events to pretend log tailing is happening)
  5. launch z_billing.sh in a new window named "billing" and wait until it is running
  6. tail (forever) the billing service's logs (by using z_heartbeat.sh billing to pretend log tailing is happening)
  7. launch z_admin.sh in a new window named "admin" and wait until it is running
  8. tail (forever) the admin service's logs (by using z_heartbeat.sh admin to pretend log tailing is happening)
  9. attach to the tmux session

Here is the script that does that:

#!/bin/bash

set -e
set -o pipefail
set -u

SESSION_NAME=mydevsession

tmux new-session -s ${SESSION_NAME} -d
tmux renamew 'init'
tmux send-keys -t ${SESSION_NAME} 'z_init.sh; tmux wait-for -S init-done' Enter\; wait-for init-done

tmux new-window -t ${SESSION_NAME} -n events -d
tmux select-window -t events
tmux send-keys -t ${SESSION_NAME} 'z_events.sh; tmux wait-for -S events-done' Enter\; wait-for events-done
tmux send-keys -t ${SESSION_NAME} 'z_heartbeat.sh events' Enter

tmux new-window -t ${SESSION_NAME}  -n billing -d
tmux select-window -t billing
tmux send-keys -t ${SESSION_NAME} 'z_billing.sh; tmux wait-for -S billing-done' Enter\; wait-for billing-done
tmux send-keys -t ${SESSION_NAME} 'z_heartbeat.sh billing' Enter

tmux new-window -t ${SESSION_NAME}  -n admin -d
tmux select-window -t admin
tmux send-keys -t ${SESSION_NAME} 'z_admin.sh; tmux wait-for -S admin-done' Enter\; wait-for admin-done
tmux send-keys -t ${SESSION_NAME} 'z_heartbeat.sh admin' Enter

tmux attach -t ${SESSION_NAME}

One fun thing to do is to leave out the last line of the above script, and run it in a different terminal instead, so that you can watch tmux start things up in real-time, and see that tmux really is waiting for each dependency to start before proceeding to the next.

The real secret seems to be this combo:

tmux wait-for -S [my-signal] // wait-for [my-signal]

What we're doing is firing off keystrokes (tmux send-keys) into our running tmux session. Usually this happens right away, and tmux rushes on to the next thing.

So what we do instead is run a microservice (e.g. z_events.sh) and then follow up that command with a tmux command that fires a named event (tmux wait-for -S events-done). Meanwhile, in our shell script, we also have a tmux command, wait-for events-done. What this does is block our shell script until it receives that signal. This prevents our shell script from hurring ahead and launching later microservices before the earlier microservices they depend on are running.