Tmux - štartovacie skripty pre projekty

28.05.2021

Keď programujem, tak programujem v konzole. Okrem editora mám v jednej tmux session otvorených niekoľko ďalších tabov/okien pre projekt a v nich mám veci ako bežiaci dev server, pripojenie do databázy, shell v docker kontajneroch a pod. Skrátka, môže byť toho celkom dosť. Ale aj keby nebolo, tak by bola celkom otrava, keby som si túto session a jej rozloženie do tabov zakaždým spúšťal ručne. Tak som si namiesto toho napísal skripty.

S použitím tmux-u to bolo ľahké. Proste som si napísal shellovský skript, kde som najskôr vytvoril session (tmux new-session), pootváral taby (tmux new-window) a v každom tabe spustil to, čo som potreboval (tmux send-keys).

Pre každý projekt mám takto urobený samostatný spúšťací skript. Niektoré sú zložitejšie, iné jednoduchšie. Napríklad skript spúštaný vtedy, keď idem napísať článok pre tento blog je pomerne jednoduchý.

Blog je robený v Gatsby a keď s ním idem pracovat, tak môj setup spočíva v tom, že otvorím tmux session s tromi oknami. V jednom je neovim, v druhom spustím server pre gatsby a tretí tab je len terminál v adresári, odkiaľ spúštam deploy.

Skript vyzerá takto:

#!/bin/bash

# ako sa má volať tmux session
SESSION_NAME=hrad

# ak už session bola spustená, tak sa len pripojím
if tmux has-session -t=$SESSION_NAME 2>/dev/null; then
    tmux attach -t $SESSION_NAME
    exit
fi

# prepnem sa do adresáru s projektom
cd ~/projects/perunhq.org/hrad.perunhq.org

# spustím session s prvým tabom, ale detachnem sa
tmux new-session -d -s $SESSION_NAME -n src

# otvorím ďalšie taby
tmux new-window -n gatsby
tmux new-window -n deploy

# v tabe `src` spustím nvim
tmux send-keys -t $SESSION_NAME:src "cd web" Enter "nvim" Enter
# v tabe `gatsby` spustím dev server
tmux send-keys -t $SESSION_NAME:gatsby "cd web" Enter "gatsby develop" Enter
# v tabe `deploy` nerobím nič, len sa prepnem do adresáru, odkiaľ neskôr spustím deploy
tmux send-keys -t $SESSION_NAME:deploy "cd deploy" Enter

# keď je hotovo, tak sa pripojím do session a môžem písať
tmux attach -t $SESSION_NAME:src

Bez komentárov je to 21 riadkov kódu, čo je celkom fajn. Skript som si uložil pod názvom hrad.sh a s právami na spúšťanie (chmod 755 hrad.sh) do adresára ~/bin, ktorý je v mojej $PATH. Tým pádom ho viem odkiaľkovek spustiť zadaním príkazu:

hrad.sh

No a takto to vyzerá hneď po spustení:

tmux session pre tento blog

Ukončenie session

Ukončenie je jednoduché, ak nemáte v žiadnom okne spustené niečo, čo sa netriviálne ukončuje. Stačí spustiť príkaz:

tmux kill-session -t nazov-session

Ak je v niektorom okne niečo netriviálne, tak sa to dá najskôr pomocou tmux send-keys ukončiť a upratať a potom môže nastúpiť tmux kill-session.

Znovupoužiteľná funkcia na otváranie session

Keďže vo veľa štartovacích skriptoch sa mi objavoval stále rovnaký kód pre vytvorenie session a toto je pre programátora signál, že niečo je možné abstrahovať a vytiahnuť do samostatnej funkcie, tak som urobil funkciu pre naštartovanie session.

Jej aktuálny kód je v repozitári tmux-functions na Bitbuckete, v čase písania tohto textu vyzerá nasledovne:

start_session_and_open_windows() {
    local SESSION_NAME=$1
    shift
    local WINDOW_NAMES=("$@")

    if [ -n "$TMUX" ]; then
        echo "Can not run inside another tmux session"
        exit
    fi

    if tmux has-session -t=$SESSION_NAME 2>/dev/null; then
        tmux attach -t $SESSION_NAME
        exit;
    fi

    tmux new-session -d -s $SESSION_NAME -n ${WINDOW_NAMES[0]}

    for i in ${!WINDOW_NAMES[@]}; do
        if [ $i -eq 0 ]; then
            continue
        fi

        local WINDOW_NAME=${WINDOW_NAMES[$i]}

        tmux new-window -n $WINDOW_NAME
    done
}

Stačí si ju naimportovať do skriptu, pri zavolaní jej odovzdať názov pre session a pre okná. V repozitári je aj ukážka použitia, ale keď s jej pomocou upravím svoj skript pre gatsby blog, tak sa dostanem k takémuto kódu:

#!/bin/bash

SESSION_NAME=hrad

source ~/.local/share/tmux-functions/tmux-functions.sh

cd ~/projects/perunhq.org/hrad.perunhq.org

start_session_and_open_windows $SESSION_NAME src gatsby deploy

tmux send-keys -t $SESSION_NAME:src "cd web" Enter "nvim" Enter
tmux send-keys -t $SESSION_NAME:gatsby "cd web" Enter "gatsby develop" Enter
tmux send-keys -t $SESSION_NAME:deploy "cd deploy" Enter

tmux attach -t $SESSION_NAME:src

Jeden skript na naštartovanie aj ukončenie

Najnovšia iterácia mojich štartovacích skriptov v sebe obsahuje aj variantu pre ukončenie celej session. Stačí, ak skript zavolám s argumentom stop.

# keď idem písat blogpost alebo meniť kód pre môj blog
hrad.sh

# keď som dopísal a chcem všetko zavrieť
hrad.sh stop

Docielil som to veľmi jednoducho. Proste som si prečítal prípadný argument pre skript a cez case v bash-i sa rozhodol, či sa session vytvorí alebo ukončí.

#!/bin/bash

COMMAND=$1
SESSION_NAME=hrad
WINDOW_NAMES=(src gatsby deploy)

source ~/.local/share/tmux-functions/tmux-functions.sh

start_project() {
    cd ~/projects/perunhq.org/hrad.perunhq.org

    start_session_and_open_windows $SESSION_NAME "${WINDOW_NAMES[@]}"

    tmux send-keys -t $SESSION_NAME:src "cd web" Enter "nvim" Enter
    tmux send-keys -t $SESSION_NAME:gatsby "cd web" Enter "gatsby develop" Enter
    tmux send-keys -t $SESSION_NAME:deploy "cd deploy" Enter

    tmux attach -t $SESSION_NAME:src
}

case "$COMMAND" in
    "stop")
        tmux kill-session -t $SESSION_NAME
        ;;

    *)
        start_project
        ;;
esac

A to je všetko. 🎉

🎁 Bonus: Skript pre zložitejší projekt

Jeden z mojich projektov je webová hra, ktorá má frontend v Reacte, backend v PHP, komunikuje aj cez websockety a na vývoj sa používa docker.

Keď na nej idem pracovať, tak musím najskôr spustiť docker kontajnery pre projekt a následne chcemvšetko toto:

  • jeden tab v tmux-e s editorom
  • jeden rezervný tab pre rôzne príkazy
  • jeden shell do webserver kontajneru na spúšťanie composer install a pod.
  • jeden shell do node kontajneru na spustenie dev serveru pre frontend
  • jeden shell do webserver kontajneru na spúšťanie testov v PHPUnit-e
  • jedno pripojenie do DB
  • jedno pripojenie do fixtúrovej DB (to je DB pre autoamtizované testy)
  • prihlásiť sa do webserver kontajneru a spustiť websocket server

Všetko mi to zabezpečuje skript nazvaný podľa projektu (wareena.sh), ktorý prijíma aj prípadný argument stop pre ukončenie session. Jeho kód je tu:

#!/bin/bash

SESSION_NAME=wareena

COMMAND=$1

TARGET_DIR="/home/perun/projects/wareena/src/web"
DOCKER_DIR="/home/perun/projects/wareena"

WINDOW_NAMES=(src misc node webserver test db fixture_db ws)

# https://bitbucket.org/perungrad/tmux-functions
source ~/.local/share/tmux-functions/tmux-functions.sh

start_project() {
    if tmux has-session -t=$SESSION_NAME 2>/dev/null; then
        tmux attach -t $SESSION_NAME
        exit;
    fi

    USER_ID=`id -u $USER`
    COLS=`tput cols`
    ROWS=`tput lines`

    WEB_LOGIN_CMD="cd $DOCKER_DIR; docker exec --user $USER_ID -it -e COLUMNS=$COLS -e LINES=$ROWS wareena_php_1 bash"
    DB_LOGIN_CMD="cd $DOCKER_DIR; docker exec -it -e COLUMNS=$COLS -e LINES=$ROWS wareena_db_1 bash"
    FIXTURE_DB_LOGIN_CMD="cd $DOCKER_DIR; docker exec -it -e COLUMNS=$COLS -e LINES=$ROWS wareena_fixture_db_1 bash"

    cd $DOCKER_DIR
    docker-compose up -d

    read -p "Continue with a new tmux session [Y/n]?" yn
    case $yn in
        [Nn]* ) exit;;
        * ) echo "OK";;
    esac

    start_session_and_open_windows $SESSION_NAME "${WINDOW_NAMES[@]}"

    tmux send-keys -t $SESSION_NAME:webserver "$WEB_LOGIN_CMD" Enter
    tmux send-keys -t $SESSION_NAME:node "$WEB_LOGIN_CMD" Enter
    tmux send-keys -t $SESSION_NAME:node "yarn dev" Enter
    tmux send-keys -t $SESSION_NAME:misc "cd $TARGET_DIR" Enter
    tmux send-keys -t $SESSION_NAME:test "$WEB_LOGIN_CMD" Enter
    tmux send-keys -t $SESSION_NAME:test "export SYMFONY_DEPRECATIONS_HELPER=disabled=1" Enter
    tmux send-keys -t $SESSION_NAME:test "bin/phpunit"
    tmux send-keys -t $SESSION_NAME:db "$DB_LOGIN_CMD" Enter
    tmux send-keys -t $SESSION_NAME:db "sleep 5; mysql -u root wareena" Enter
    tmux send-keys -t $SESSION_NAME:fixture_db "$FIXTURE_DB_LOGIN_CMD" Enter
    tmux send-keys -t $SESSION_NAME:fixture_db "sleep 5; mysql -u root wareena" Enter
    tmux send-keys -t $SESSION_NAME:ws "$WEB_LOGIN_CMD" Enter
    tmux send-keys -t $SESSION_NAME:ws "node bin/ws-server.js" Enter

    tmux send-keys -t $SESSION_NAME:src "cd $TARGET_DIR" Enter
    tmux send-keys -t $SESSION_NAME:src "nvim" Enter

    tmux attach -t $SESSION_NAME:src
}

stop_project() {
    cd $DOCKER_DIR
    docker-compose down

    tmux kill-session -t $SESSION_NAME
}

case "$COMMAND" in
    "stop")
        stop_project
        ;;

    *)
        start_project
        ;;
esac