Crafting A Bash Script with Tmux

By Published On: April 6, 20244.9 min readViews: 241

The Background…

I have Django/Vue development environment running locally.

To streamline my Django development, I typically open six tmux windows 😎 :

  1. Celery window – It also check and start necessary local services, like mailpit and redis, then fianlly start Celery.
  2. Flower window – Start Flower
  3. Django window – Start Django runserver
  4. Django manager shell window – For Django manager operations
  5. Heroku window – Checking Heroku status and commit and other Heroku operations
  6. Vue window – Start npm run serve or build

I used one Tmux session to hold all above.

However, my laptop sometimes needs to reboot, after reboot, all of my windows are gone 😓

I have configured tmux-resurrect and tmux-continuum to try to handle this scenario, but they couldn’t re-run those commands even they could restore the windows correctly.

Let me show you the screenshots.

The problem…

Typically, my development windows look like this:

As you see, the services are running within the respective windows.

If I save them with tmux-resurrect, after reboot, of course tmux-resurrect and tmux-continuum could restore them, but services and all environment variables are gone.

To simulate, let me kill all sessions in tmux, check the output:

Now start tmux again, here are the status I can see, tmux restored the previous saved windows:

Let’s check the window now:

None of the services is running 🙉

The Complain…

As the supreme overlord of geekcoding101.com, I simply cannot let such imperfection slide.
Not on my watch.
Nope, not happening.
This ain’t it, chief.

Okay, let’s fix it!

The Fix…

….

Okay! I wrote a script.. oh no! Two scripts!

One is called start_tmux_dev_env.sh to create all windows, it will invoke prepare_dev_env.sh which export functions to initialize environment variables in specific windows.

A snippet of start_tmux_dev_env.sh:

#!/bin/bash

# Please note: don't use dot in session name.
SESSION_NAME="matrixlink_ai"


# Check if the tmux session already exists
tmux has-session -t $SESSION_NAME 2>/dev/null

if [ $? != 0 ]; then
  # Create a new detached tmux session named matrixlink.ai
  tmux new-session -d -s $SESSION_NAME

  # Set up the 'celery' window
  tmux rename-window -t $SESSION_NAME 'celery'
  echo "Starting celery window..."
  echo "Sleeping 5s to wait window celery finish initialization....."
  sleep 5
  echo "Checking psql in window celery..."
  tmux send-keys -t $SESSION_NAME 'psql -h localhost -p 5432 -d matrixlink_ai' C-m
  sleep 2
  tmux send-keys -t $SESSION_NAME '\q' C-m
  sleep 1

  # Can't use ENVIRONMENT variable for the script path be sourced.
  # Remember to put below command to background, otherwise it will wait here forever.
  tmux send-keys -t $SESSION_NAME '. ${YOUR_PATH_TO_PROJECT}/matrixlink.ai/utils/prepare_dev_env.sh && setup_celery_window' C-m &

  echo "Starting flower window..."
  tmux new-window -t $SESSION_NAME -n 'flower'
  echo "Sleeping 5s to wait window flower finish initialization....."
  sleep 5
  # $SESSION_NAME:flower is to specify the window
  # $SESSION_NAME.flower is to specify the pane
  tmux send-keys -t $SESSION_NAME:flower '. ${YOUR_PATH_TO_PROJECT}/matrixlink.ai/utils/prepare_dev_env.sh && setup_flower_window' C-m &

  echo "Starting nvm window..."
  ...

  echo "Starting Django window..."
  ...

  echo "Starting Django manager window..."
  ...

  echo "Starting Heroku window..."
  ...
fi

# Attach to the tmux session
tmux attach -t $SESSION_NAME

The prepare_dev_env.sh looks like:

#!/bin/sh


WORKDIR="${YOUR_PATH_TO_PROJECT}/github/matrixlink.ai/"
CONDA_ENV="matrixlinkai.django"

setup_celery_window() {
  conda activate ${CONDA_ENV}
  cd $WORKDIR
  brew services start mailpit
  brew services start redis
  export REDIS_URL=redis://localhost:6379/0
  export USE_DOCKER=no
  export CELERY_CONFIG_TASK_ALWAYS_EAGER=yes
  celery -A config.celery_app worker --loglevel=info
}

setup_flower_window() {
  conda activate ${CONDA_ENV}
  cd $WORKDIR
  export CELERY_BROKER_URL=$(echo "REDIS_URL")
  export REDIS_URL=redis://localhost:6379/0
  export CELERY_BROKER_URL=$REDIS_URL
  export CELERY_FLOWER_USER=debug
  export CELERY_FLOWER_PASSWORD=debug
  export USE_DOCKER=no
  export CELERY_CONFIG_TASK_ALWAYS_EAGER=yes
  celery -A config.celery_app -b ${CELERY_BROKER_URL} flower --basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"
}

setup_django_window() {
  conda activate ${CONDA_ENV}
  cd $WORKDIR
  export CELERY_BROKER_URL=$(echo "REDIS_URL")
  export REDIS_URL=redis://localhost:6379/0
  export CELERY_BROKER_URL=$REDIS_URL
  export CELERY_FLOWER_USER=debug
  export CELERY_FLOWER_PASSWORD=debug
  export USE_DOCKER=no
  export EMAIL_HOST=localhost
  export CELERY_CONFIG_TASK_ALWAYS_EAGER=yes
  
  # Please export sensitive information manually, like OPENAI key

  # You need to manually replace ${DB_USERNAME} here if not yet set environment variable.
  export DATABASE_URL=postgres://${DB_USERNAME}@127.0.0.1:5432/matrixlink_ai
  python manage.py migrate
  echo "Sleeping 10s to wait npm to start in another window..."
  sleep 10
  python manage.py runserver 0.0.0.0:8000
}

setup_django_manager_window() {
  conda activate ${CONDA_ENV}
  cd $WORKDIR
  export USE_DOCKER=no
  # You need to manually replace ${DB_USERNAME} here if not yet set environment variable.
  export DATABASE_URL=postgres://${DB_USERNAME}@127.0.0.1:5432/matrixlink_ai
  export REDIS_URL=redis://localhost:6379/0
  export CELERY_CONFIG_TASK_ALWAYS_EAGER=yes
  python manage.py shell
}

setup_nvm_window() {
  conda activate ${CONDA_ENV}
  cd $WORKDIR/frontend
  npm run serve
}

The End…

Now, after reboot, I can just invoke script start_tmux_dev_env.sh and it will spin up all windows for me in seconds!

I’ve recorded a video about how it looks like when running the script, please check out at my post Terminal Mastery: Crafting A Productivity Environment With ITerm, Tmux, And Beyond

Thanks for watching!

Share it:

5 2 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments