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 pracovať, 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í:
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