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:
- create a tmux session named mydevsession
- launch
z_init.sh
in the first window and wait until it is done - launch
z_events.sh
in a new window named "events" and wait until it is running - tail (forever) the event service's logs (by using
z_heartbeat.sh events
to pretend log tailing is happening) - launch
z_billing.sh
in a new window named "billing" and wait until it is running - tail (forever) the billing service's logs (by using
z_heartbeat.sh billing
to pretend log tailing is happening) - launch
z_admin.sh
in a new window named "admin" and wait until it is running - tail (forever) the admin service's logs (by using
z_heartbeat.sh admin
to pretend log tailing is happening) - 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.