2022.07.07

Save Development Time with tmux and tmuxinator

こんにちは、次世代システム研究室のN.M.です。

If you are not already familiar with tmux, it is a terminal multiplexer. Take a quick look here, then come back.

If you are developing a project where you have multiple terminal windows open, then this may help you.

tmux, is a great terminal management tool. You can create your session, with multiple windows, and panes inside your windows, run long-running commands, detach from your session, re-attach later and be able to view your terminals and commands as you left them.

But sometimes we want the ability to stop our session completely and then start the session with the same window configuration, and same commands executed. For example, when the server running tmux is restarted. Or you may want to stop your tmux session because it uses a lot of resources (such as when running docker-compose or local Kubernetes). When restarting you want your terminal environment to be the same as when you left your session, with the same windows, panes and same environment setup commands having run.

If you use raw tmux, when you stop your session you lose everything. This is why we need a tmux manager such as tmuxinator. Other tmux managers exist such as tmux-resurrect or tmuxp. I like tmuxinator because of it’s yaml configuration and ability to configure lifecycle hooks and commands.

 

tmuxinator, is a ruby gem that allows you to create and manage your tmux sessions, called projects in tmuxinator.

❯ tmuxinator --help
tmuxinator commands:
  tmuxinator commands                          # Lists commands available in tmuxinator
  tmuxinator completions [arg1 arg2]           # Used for shell completion
  tmuxinator copy [EXISTING] [NEW]             # Copy an existing project to a new project and open it in your editor
  tmuxinator debug [PROJECT] [ARGS]            # Output the shell commands that are generated by tmuxinator
  tmuxinator delete [PROJECT1] [PROJECT2] ...  # Deletes given project
  tmuxinator doctor                            # Look for problems in your configuration
  tmuxinator help [COMMAND]                    # Describe available commands or one specific command
  tmuxinator implode                           # Deletes all tmuxinator projects
  tmuxinator list                              # Lists all tmuxinator projects
  tmuxinator local                             # Start a tmux session using ./.tmuxinator.y[a]ml
  tmuxinator new [PROJECT] [SESSION]           # Create a new project file and open it in your editor
  tmuxinator start [PROJECT] [ARGS]            # Start a tmux session using a project's name (with an optional [ALIAS] for project reuse) or a path to a p...
  tmuxinator stop [PROJECT]                    # Stop a tmux session using a project's tmuxinator config
  tmuxinator version                           # Display installed tmuxinator version
Each of the commands provides functionality for creating a tmuxinator project.
Project configuration files are saved as yaml, you can edit these files using:
❯ tmuxinator open diesel-demo
This command will open up a project configuration yaml file for the diesel-demo project.
# /Users/USERNAME/.config/tmuxinator/diesel-demo.yml
name: diesel-demo
root: ~/projects/rust/diesel_demo

# Runs on project start, always
on_project_start: vagrant up

# Run on project stop
on_project_stop: vagrant halt

windows:
  - editor:
      layout: main-vertical
      panes:
        - code .
  - server:
     panes:
       - postgres:
         - vagrant ssh
         - sudo -u postgres psql
If this is the first time using tmuxinator open, you will see the default project configuration. The default has an example config for windows, panes, and lifecycle hooks.

I have already edited the configuration. On macOS, the file location is /Users/USERNAME/.config/tmuxinator/diesel-demo.yml.
The root config defines the default project directory. You can change this per window (using a window level root setting), or per pane (using the cd command).

There are two lifecycle hooks, that take care of starting and stopping my development vagrant box. The use of vagrant here is simply to demonstrate the use of lifecycle hooks, and to set up an example remote server.

The first window, editor, contains one pane, that starts vscode in the current directory.

The second window, server, contains one pane called postgres. Inside this pane, two commands are invoked, vagrant ssh and sudo -u postgres psql

Consequently, in the server window, we connect to our remote server, then run psql at the server.

If you run tmuxinator start diesel-demo, you will see the vagrant box startup, then when vagrant has started you will see a window similar to the following image.

tmuxinator local
Here we see the editor tmux window. This contains a terminal prompt into my local environment within the /Users/USERNAME/projects/rust/diesel_demo folder. The command code . opened the vscode editor.

In the tmux status bar, we can see there are two windows, editor (* denotes the current window) and server. If we change to the server window, we can see the psql prompt running in our vagrant instance.

(Note, that my status bar has been customized, so it looks a little different, more on that later)

tmuxinator remote
We can see the two commands defined for the server window, vagrant ssh and sudo -u postgres psql

So we have two windows, one local and one remote. We can create as many windows and panes as we want, each containing as many commands as we need. I hope you are beginning to see the power of tmuxinator.

 

Nested tmux Sessions

It is common to run tmux from a remote server.

For example, many of you may have a bastion server as your remote server, that you connect to first and then open a terminal on multiple app servers from the bastion. It also, is common to want to use tmux to manage each of these terminals.

In this case we would have nested tmux sessions. One session running locally and one from our remote server. As you know, tmux uses a prefix command (default C-b) to invoke tmux shortcuts, for example, the key combination to go to the next window is C-b n

When running nested tmux sessions, the prefix command is caught by the outer tmux session. By default, the prefix command for the inner tmux session is the prefix twice (default C-b C-b)

To make this easier, some tmux users have figured out how to toggle on/off the outer tmux session key bindings. When off, the prefix command is handled by the inner (remote) tmux session.

The code to do this shown in this gist from this medium post.

Just put it into your tmux.conf, usually located at ~/.tmux.conf, and run tmux source ~/.tmux.conf to reload the new configuration.

Once you have reloaded your tmux.conf, the status bar will still look a little strange. This is because the new tmux.conf uses a tmux plugin, which is not yet installed.

Now, you will be able to invoke a tmux session from within your tmuxinator project yaml and enter f12 to toggle off your outer tmux session and use C-b (or your  configured prefix) commands for the remote tmux session.

Let’s change our tmuxinator project config with tmuxinator open diesel-demo.
# /Users/USERNAME/.config/tmuxinator/diesel-demo.yml

name: diesel-demo
root: ~/projects/rust/diesel_demo

# Runs on project start, always
on_project_start: vagrant up

# Run on project stop
on_project_stop: vagrant halt

windows:
  - editor:
      layout: main-vertical
      panes:
        - code .
  - server:
     panes:
       - remote:
         - vagrant ssh
         - tmux new-session -A -s main
Note that the server window creates a new remote tmux session or attaches to an existing session called main.

nested tmux
After hitting f12, the status bar for the outer (local) tmux session shows OFF in red, and I am able to use the C-b prefix remotely.

Now I can then change main as I wish.

I have opened a source file with vim and added another pane to the remote tmux session using C-b %. In the second pane I invoked the psql command previously invoked from the tmuxinator project config.

Again toggling the outer tmux command prefix by hitting f12, I can now detach from my tmuxinator session, using C-b d, and re-attach, using tmuxinator start diesel-demo, and my remote session will still have the psql pane. This is because the remote session will last as long as the remote server is not restarted.

If I run tmuxinator stop diesel-demo, vagrant halt would be called by the tmuxinator lifecycle on_project_stop hook and I would lose my remote psql pane.

To have remote tmux sessions that survive remote server restarts, I could use a remote tmuxinator or use another tmux session management tool remotely.

tmux Plugins

tmux functionality is commonly extended using tmux plugins. There is a tmux plugin manager (tpm) for managing these.

This plugin manager takes care of downloading your plugins, and applying them to your tmux.

All you need to do is add two lines to tmux.conf:
  • the plugin to install (in our case we want the tmux status bar to work properly so we need this plugin):
set -g @plugin 'samoshkin/tmux-plugin-sysstat'
  • activate tpm (should always the last line of tmux.conf):
run '~/.tmux/plugins/tpm/tpm'
You may then do C-b I from a tmux session and tpm will download the new plugin. Enter return, when prompted, and tpm will install the plugin.

There are many other tmux plugins available.

After having installed the tmux-plugin-sysstat plugin your status bar should look more like the ones shown in this article. You may have to define variables for colors as shown in samoshikin’s article, or go directly to his tmux.conf.

 

Summary

  • Here is have introduced tmuxinator for tmux, and explained how to set it up with a realistic example
  • Also a solution is presented on how to handle nested tmux sessions for opening remote tmux sessions inside local sessions
  • tmux plugins and tpm are expained
Of course, all this requires more work initially, as well as a learning curve. But this can make development easier, less stressful, and fun, so is worth it in the long run.

Resources and links:

 

次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事