loading...
Cover image for Automating multiple terminals with tmux and tmuxinator

Automating multiple terminals with tmux and tmuxinator

bigj1m profile image Jim Plourde ・5 min read

Photo by Sai Kiran Anagani on Unsplash

As part of my current job, I am working on a business-to-business e-commerce platform, on both the front-end and the back-end. This requires to open multiple terminals in order to use different tools. Some of them are running in the background while others are blocking the CLI. Here's the minimum required to spin up the local environment:

  1. A NPM watcher for the buyer app of the platform. Adds the ability for the browser to pick up changes in the code and rebuild the app partly.
  2. A NPM watcher for the seller side of the app, which is also blocking like the first one.
  3. Pycharm, my IDE that I run through a terminal that is dedicated to it.
  4. A docker image of the local database that runs in the background. I reuse this terminal for Git.
  5. An SSH connection to a local Vagrant VM that runs the back-end. The initial setup requires to SSH into the VM, to cd to the app folders, source the virtual environment and load up the environement variables. Commands to load fixtures, restart the database and others are sent into that session. It's also the starting process that takes up the most time.

There is a minimum of five terminal instances involved, some requiring a couple of manipulations while others require no attention but are dedicated to one job. It takes 2-3 minutes or more sometimes, each day to setup everything so that I can work. Seems not like a lot, but let's say I do it only once per day, five days a week for 50 weeks a year. That's minimally ~8 hours a year lost to a boring and very repetitive task. For a time, I was wondering if there was something I could do to automate the whole thing. At first, I considered using a small bash script to automate certain portions of my routine, but that wasn't enough. That's when a colleague showed me tmux and tmuxinator.

tmux

tmux stands for Terminal Multiplexer. It lets you switch between several programs inside one terminal, detach and reattach them to a different terminal at will. It will work with multiple programs and shells in one terminal, like a window manager. tmux protects your remote connections if installed on a remote server. It's ideal for SSH, PuTTy and other connections. Programs can be remotely accessed by many local computers. To better understand the purpose of tmux, let's look at some core concepts.

tmux Server and client

tmux keeps all its states in one single main process called a tmux server. It runs in the backgroup, manages all the programs and keep track of their outputs. It launches automagically when a tmux command is used and stops by itself when there is no program left to run.

When a user starts a tmux client, it takes over his terminal and attach to the server so they can talk via a socket file in /tmp.

Commands

Every command is prefixed with ctrl+b. When entering a semicolon, you can enter commands much like the vi/vim command mode.

Status bar

At the bottom of the terminal, there is a status bar. It indicates which session, pane and window is currently active.

Pane

Inside tmux, every terminal belongs to a pane, that is a rectangular area much like a tile in a windows manager. You can navigate between panes with ctrl+b ←/↑/↓/→, split panes vertically with ctrl+b % and horizontally with ctrl+b %.

Window

Every pane is contained inside a window, which is the whole area of the terminal. There can be many window per session. These windows can be reordered and have a name. You can switch from a window to another either with its number ctrl+b <0-9> or ctrl+b n and ctrl+b p for next and previous. Windows have a layout which is how each panes appear inside a window. In a layout, panes are seperated by a line called a pane border. You can either use one of the 4 preset layouts or specify your own.

Session

One or many windows are in a session. Each session window has a numbered index and a name, at the bottom in the status bar. A window can be part, or attached, to many sessions. A session can be attached to one or more clients and has a unique name.

tmuxinator

tmux is a great and powerful tool that can save a lot of trouble. But our process is still manual and it takes too much time. One of the good feature of tmux is that it works well with scripts and automation.

tmuxinator is a tool to boost how tmux create and manage sessions. We create a new project, which opens up a YAML file. In this file, we specify how many windows and panes we want and their layouts. There is many hooks to run commands at certain moments of the tmux run: when a project start, when it stops, etc.

tmux executes any command as if you typed it. It waits for a command to finish before launching the next. This is perfect for waiting on an SSH connection to run your next commands.

Let's have a look at the actual project I'm using for my local dev env:

name: myproject
root: ~/

# Specifies (by name or index) which window will be selected on project startup. If not set, the first window is used.
startup_window: vagrant and docker-git

# Specifies (by index) which pane of the specified window will be selected on project startup. If not set, the first pane is used.
startup_pane: 1

# Controls whether the tmux session should be attached to automatically. Defaults to true.
# attach: false

windows: # first level specify that it's a list of windows
  # Second level is all the windows with their name
  - ide: /usr/local/pycharm-2020.1.3/bin/pycharm.sh # Commands can be specified inline 
                                                    # with the window name
  - watchers:
      layout: even-horizontal # This is the pane layout option, the man page explain                            
                              # them. 
      panes:                  # This specifies all the panes inside the current window
        - vendor watcher:     # Pane names don't appear but it's for clarity
          - j myproject-app         # 4th level is a list of all commands to execute
          - clear
          - npm run watch:vendor
        - customer watcher:
          - j myproject-app         # Every command is fair game, even zsh plugins
          - clear             # Every command wait for the previous one to finish
          - npm run watch:customer
  - vagrant and docker-git:
      layout: even-vertical
      panes:
        - vagrant:
          - j MyProjectFiles
          - clear
          - workon MyProject
          - vagrant up
          - vagrant ssh      # I SSH into vagrant which takes some time to complete
                             # When SSH is completed, other commands run
          - source /usr/local/virtualenvs/myproject374/bin/activate
          - cd /srv/myproject-api
          - export $(cat .env | xargs)
          - sudo service supervisor restart
          - clear
        - docker:
          - j myproject
          - clear
          - docker stop myproject_mysql
          - docker rm myproject_mysql
          - docker run --name myproject_mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=true -p 3306:3306 -d mysql:5.6.35
          - clear
          - docker ps

To spin up my environment, I type tmuxinator start myproject and the whole thing kicks in in seconds. When running, the script creates every windows and panes and run every command at the same time. It reduces the previous 2-3 minutes of setup time to ~10 seconds. The "bottleneck" of this script is the vagrant setup. It takes time to spin up a VM and SSH into it. But since everything is automatic, I can start to code while it finishes in the background.

Bonus: Here is a tmux cheatsheet that is super helpful to get into tmux and help tmuxinator scripting

Posted on by:

bigj1m profile

Jim Plourde

@bigj1m

Canadian computer engineering student and Python developer. I'm curious about a broad variety of subjects.

Discussion

pic
Editor guide