Перейти к содержимому

tmux глубоко: сессии, панели и терминал без мыши

Представь типичный вечер инженера: ты по SSH дебажишь сервер, запустил долгий прогон миграций, и тут падает VPN. Переподключаешься — а шелл новый, процесс убит вместе с сессией, и непонятно, на каком шаге всё умерло. Или проще: закрыл окно терминала на локальной машине — и все шесть открытых в нём процессов испарились. tmux существует ровно для того, чтобы этих историй больше не было.

tmux — терминальный мультиплексор: программа, которая внутри одного терминала даёт множество виртуальных терминалов (окон и панелей) и, главное, отделяет жизнь процессов от жизни эмулятора терминала. Сессия tmux живёт на сервере tmux, а твой терминал — просто клиент, который к ней подключается. Закрыл терминал, отвалился SSH, перезагрузил иксы — сессия как работала, так и работает; подключаешься обратно и продолжаешь с того же места.

В краткой версии учебника tmux упоминался одной строкой в списке софта. Здесь разбираем его как следует: модель сервер/сессия/окно/панель, реальный боевой конфиг с префиксом Ctrl+Space и навигацией через Alt, copy-mode в vi-стиле — и под капотом: клиент-серверную архитектуру, зачем Neovim нужны escape-time 0 и focus-events, и как буфер обмена проезжает через SSH через OSC52.

Первая: выживаемость. Процессы внутри tmux — дети tmux-сервера, а не твоего терминала. SIGHUP при отвале соединения до них не доходит. Это не «удобство», это другой класс надёжности для всего долгоживущего: миграции, тейлы логов, локальные dev-серверы.

Вторая: окна и панели. Вместо десяти вкладок терминала с именами «Terminal 1..10» — именованные окна и разделённые панели внутри одного экрана: слева редактор, справа снизу тесты, отдельное окно под lazygit. Тайлинг, который мы видели в Hyprland на уровне окон ОС, tmux делает внутри терминала — и это работает одинаково локально и по SSH, где никакого Hyprland нет.

Третья: единый буфер обмена. Всё, что ты скопировал в copy-mode tmux, доступно во всех панелях и окнах сессии — и, при правильной настройке, в системном буфере. Скопировать стектрейс из логов в одной панели и вставить в редактор в другой — без мыши и без «выделил — переключился — вставил».

Четвёртая: скриптинг рабочих мест. tmux полностью управляется из командной строки: tmux new-session, split-window, send-keys. Это значит, что «рабочее место проекта» — редактор, тесты, dev-сервер, логи — описывается скриптом и поднимается одной командой. Вернулся к проекту через неделю — ./dev.sh, и весь layout на месте.

#!/usr/bin/env bash
# dev.sh — поднимает рабочее место pet-проекта одной командой
SESSION=dashboard
# Создаём сессию в фоне, первое окно — редактор
tmux new-session -d -s "$SESSION" -n code
tmux send-keys -t "$SESSION:code" 'nvim .' Enter
# Сплит снизу под тесты (30% высоты)
tmux split-window -v -p 30 -t "$SESSION:code"
tmux send-keys -t "$SESSION:code" 'npm run test:watch' Enter
# Отдельное окно под git
tmux new-window -t "$SESSION" -n git
tmux send-keys -t "$SESSION:git" 'lazygit' Enter
# Подключаемся к первому окну
tmux attach -t "$SESSION:code"

Иерархия tmux строгая, и её стоит выучить один раз, потому что все команды адресуются по ней:

tmux server (один на пользователя, слушает сокет в /tmp)
└── session: dashboard — «проект», отдельный контекст работы
├── window 1: code — «вкладка», полноэкранная
│ ├── pane 0: nvim — разделённая область внутри окна
│ └── pane 1: тесты
└── window 2: git (lazygit)
  • Сервер — фоновый процесс, владеющий всеми сессиями. Он стартует при первом tmux new и умирает только когда завершена последняя сессия.
  • Сессия — именованный контекст: обычно одна на проект или на задачу. tmux new -s имя, tmux attach -t имя, tmux kill-session -t имя.
  • Окно — полноэкранная «вкладка» внутри сессии, со своим именем.
  • Панель — разделённая часть окна. Окно без сплитов — одна панель.

Адресация в командах идёт через сессия:окно.панель — например, dashboard:code.1. Эту нотацию ты видел в скрипте выше.

Конфиг: префикс Ctrl+Space и навигация без префикса

Заголовок раздела «Конфиг: префикс Ctrl+Space и навигация без префикса»

Дефолтный префикс tmux — Ctrl+b: клавиша, которая требует скрючить пальцы и конфликтует с «назад» в readline. Первое, что делает каждый, — меняет префикс. Боевой конфиг использует Ctrl+Space: обе руки на месте, большой палец на пробеле, никаких конфликтов с readline и Neovim.

Вторая идея конфига: самое частое не должно требовать префикса вообще. Переключение окон и фокус панелей происходит сотни раз в день — им положены прямые клавиши на Alt:

Окно терминала
# ~/.tmux.conf — боевой конфиг с русскими комментариями
# Префикс: Ctrl+Space вместо Ctrl+b
unbind C-b
set -g prefix C-Space
bind C-Space send-prefix
# Vi-режим в copy-mode и статусной строке
set -g mode-keys vi
set -g status-keys vi
# Нумерация окон и панелей с единицы — 0 далеко на клавиатуре
set -g base-index 1
set -g pane-base-index 1
# Окна: Alt+цифра — прыжок, Alt+стрелки — листать, без префикса
bind -n M-1 select-window -t 1
bind -n M-2 select-window -t 2
bind -n M-3 select-window -t 3
bind -n M-4 select-window -t 4
bind -n M-5 select-window -t 5
bind -n M-6 select-window -t 6
bind -n M-7 select-window -t 7
bind -n M-8 select-window -t 8
bind -n M-9 select-window -t 9
bind -n M-Left previous-window
bind -n M-Right next-window
# Сплиты: Alt+Enter — вертикальный, Alt+Shift+Enter — горизонтальный
bind -n M-Enter split-window -h -c "#{pane_current_path}"
bind -n M-S-Enter split-window -v -c "#{pane_current_path}"
# Фокус панелей: Ctrl+Alt+стрелки
bind -n C-M-Left select-pane -L
bind -n C-M-Right select-pane -R
bind -n C-M-Up select-pane -U
bind -n C-M-Down select-pane -D
# Ресайз панелей: Ctrl+Alt+Shift+стрелки
bind -n C-M-S-Left resize-pane -L 5
bind -n C-M-S-Right resize-pane -R 5
bind -n C-M-S-Up resize-pane -U 3
bind -n C-M-S-Down resize-pane -D 3
# Окна и сессии через префикс: c — создать, r — переименовать, k — убить
bind c new-window -c "#{pane_current_path}"
bind r command-prompt -I "#W" "rename-window '%%'"
bind k kill-window
bind P switch-client -p # предыдущая сессия
bind N switch-client -n # следующая сессия
# Шпаргалка по всем биндам: префикс+?
bind ? list-keys
# Для Neovim: мгновенный Esc и события фокуса (разбор ниже)
set -sg escape-time 0
set -g focus-events on
# 256 цветов и truecolor для современных тем
set -g default-terminal "tmux-256color"
set -ag terminal-overrides ",xterm-256color:RGB"
# OSC52: копирование из tmux в системный буфер, в том числе через SSH
set -g set-clipboard on

Несколько деталей, которые здесь важны. bind -n — бинд без префикса (n = no prefix): именно он позволяет жать Alt+3 напрямую. #{pane_current_path} в сплитах — новая панель открывается в той же директории, где ты сейчас, а не в домашней: мелочь, без которой каждый сплит начинается с cd. Префикс+? выводит все биндинги — встроенную шпаргалку, которая всегда соответствует твоему конфигу.

Прокрутка и копирование в tmux происходят в copy-mode: префикс+[ (или просто крути колесо мыши, если mouse on). С mode-keys vi навигация там — привычные hjkl, / для поиска вверх по выводу, n/N по совпадениям. Копирование: v начинает выделение (как visual-режим), двигаешься до конца, y — скопировать и выйти. Вставка из буфера tmux — префикс+].

Здесь прячется суперсила: copy-mode — это поиск по выводу любой панели. Длинный лог тестов, стектрейс, вывод git log — всё это greppable прямо в терминале, без перенаправления в файл. Нашёл нужное, v…y, переключился в панель с Neovim, вставил.

Под капотом: клиент-серверная архитектура

Заголовок раздела «Под капотом: клиент-серверная архитектура»

Теперь главное — почему всё это вообще возможно. tmux состоит из двух процессов:

┌──────────────┐ unix-сокет ┌──────────────────────────┐
│ tmux client │ ◄────────────► │ tmux server │
│ (в твоём │ /tmp/tmux-… │ владеет сессиями, окнами│
│ терминале) │ │ и PTY всех процессов │
└──────────────┘ │ │
клиентов может │ ┌────────┐ ┌────────┐ │
быть несколько: │ │ сессия │ │ сессия │ │
локальный терминал │ │ dash │ │ infra │ │
+ SSH-подключение │ └───┬────┘ └────────┘ │
одновременно │ │ PTY: nvim, zsh… │
└───────┼──────────────────┘
▼
настоящие процессы живут здесь

Сервер создаёт для каждой панели псевдотерминал (PTY) и запускает в нём твой шелл. Клиент лишь пересылает нажатия клавиш серверу и отрисовывает то, что сервер присылает обратно. Отсюда следуют все свойства:

  • Клиент умер (закрыл терминал, порвался SSH) — серверу всё равно, PTY и процессы живы.
  • К одной сессии могут подключиться два клиента сразу: ты с десктопа и коллега по SSH видите и правите одно и то же. Это готовый парный дебаг без всякого screen sharing.
  • tmux attach после обрыва — это просто запуск нового клиента к живому серверу.

Две строки в конфиге выше выглядят загадочно, но без них Neovim внутри tmux работает «с заиканиями».

escape-time. Когда ты жмёшь Esc, терминал не может сразу отличить одиночный Esc от начала escape-последовательности (стрелки, Alt-комбинации — это всё последовательности, начинающиеся с байта ESC). Поэтому tmux по умолчанию ждёт ~500 мс: вдруг за Esc придёт продолжение. Для человека в Neovim это выглядит как «Esc срабатывает с лагом в полсекунды» — невыносимо, когда выход в normal-режим ты жмёшь сто раз в минуту. set -sg escape-time 0 говорит: не жди, одиночный Esc — это Esc. Плата — теоретическая возможность проблем с Alt-последовательностями на очень медленных соединениях; на практике с нулём живут все.

focus-events. Neovim умеет реагировать на получение и потерю фокуса — например, автоматически перечитывать файлы, изменённые снаружи (:checktime), или применять тему при смене системной темы (помнишь hot-reload из главы про Omarchy). Но когда Neovim работает внутри tmux, события фокуса терминала до него не доходят — tmux их проглатывает. set -g focus-events on велит tmux транслировать события фокуса внутрь панелей, и Neovim снова видит мир.

Осталась последняя магия. Ты по SSH на сервере, внутри tmux, внутри Neovim копируешь строку — и хочешь вставить её в браузер на своей локальной машине. Обычный системный буфер тут бессилен: сервер о нём ничего не знает. Спасает OSC52 — escape-последовательность терминала, придуманная именно для этого: программа печатает специальную последовательность с base64-содержимым, эмулятор терминала на твоей машине перехватывает её и кладёт данные в локальный буфер обмена.

Цепочка выглядит так: tmux с set-clipboard on при копировании (y в copy-mode) генерирует OSC52 → последовательность едет по SSH-каналу как обычный вывод → твой локальный терминал (Alacritty/Ghostty поддерживают OSC52) перехватывает её → строка в системном буфере. Neovim внутри tmux, кстати, тоже умеет OSC52 сам — регистр + в нём работает через ту же последовательность, если настроен clipboard-провайдер.

  • Одна вечная сессия на всё. Сорок безымянных окон — тот же хаос вкладок. Хорошо: сессия на проект, окна названы (r — переименовать), мусорные окна убиваются (k).
  • Сплиты открываются в домашней директории. Забыт -c "#{pane_current_path}" — и каждый новый сплит начинается с ручного cd. Это первая строка, которую стоит добавить в конфиг.
  • «Esc в vim лагает» — это дефолтный escape-time 500. Лечится одной строкой, но половина новичков списывает это на «vim тормозной».
  • Копирование мышью в обход tmux. Выделение мышью копирует текст одной панели с чужими границами и номерами строк. Хорошо: copy-mode (v/y) — tmux знает границы панелей и копирует чисто.
  • tmux kill-server вместо убийства сессии. Убивает ВСЕ сессии со всеми процессами. Нужна одна — tmux kill-session -t имя.
  • Вложенный tmux без понимания. Локальный tmux, внутри SSH, на сервере ещё tmux — и префикс ловит внешний. Приём: префикс, нажатый дважды (C-Space C-Space), отправляет префикс внутрь благодаря bind C-Space send-prefix — и бинды срабатывают на внутреннем tmux.
  • Конфиг без source-file. Отредактировал .tmux.conf, а бинды не применились — конфиг читается при старте сервера. Либо tmux source-file ~/.tmux.conf, либо бинд на перечитывание.
  1. Что происходит с процессами в tmux при обрыве SSH? Ничего: процессы — дети tmux-сервера, который держит их PTY. Клиентское соединение обрывается, SIGHUP до процессов не доходит. После переподключения tmux attach возвращает ровно то состояние, которое было.
  2. Объясни иерархию tmux. Сервер (один на пользователя, unix-сокет) → сессии (именованные контексты) → окна (полноэкранные вкладки) → панели (сплиты внутри окна, каждая со своим PTY). Адресация: сессия:окно.панель.
  3. Чем tmux отличается от вкладок эмулятора терминала? Вкладки терминала живут и умирают с окном терминала и не существуют по SSH. tmux — серверный процесс: сессии переживают закрытие терминала, доступны из любого клиента, скриптуются и работают на удалённых машинах.
  4. Зачем escape-time 0? Терминал отличает одиночный Esc от начала escape-последовательности только таймаутом (~500 мс по умолчанию). В модальном редакторе Esc нажимается постоянно, лаг неприемлем — ноль убирает ожидание.
  5. Как скопировать текст с удалённого сервера в локальный буфер обмена? Через OSC52: приложение печатает escape-последовательность с base64-данными, локальный эмулятор терминала перехватывает её и кладёт в системный буфер. Требуется поддержка терминалом и set-clipboard on в tmux.
  6. Что такое PTY и какую роль он играет в tmux? Псевдотерминал — пара «master/slave», эмулирующая физический терминал. tmux-сервер держит master-сторону каждой панели, шелл видит slave как настоящий терминал — поэтому программы ведут себя интерактивно, а tmux может перехватывать и перерисовывать вывод.
  7. Как два человека могут работать в одной tmux-сессии? Два клиента подключаются к одному серверу (tmux attach обоими): оба видят один экран и вводят в одни панели. Это простейший парный дебаг по SSH; для разных размеров окон есть tmux attach с группами сессий.
  1. Разверни конфиг из главы в ~/.tmux.conf (или ~/.config/tmux/tmux.conf), примени tmux source-file и проверь каждый бинд: Alt+цифры, сплиты по Alt+Enter, ресайз. Критерий: все клавиши из конфига работают и ты можешь объяснить каждую строку.
  2. Напиши dev.sh для pet-проекта: сессия, окно с nvim, панель с тестами, окно с lazygit. Критерий: одна команда поднимает полное рабочее место, tmux kill-session сносит его без осиротевших процессов.
  3. Сымитируй обрыв SSH. Подними VM или контейнер с SSH, подключись, запусти в tmux долгий процесс (sleep 1000 && echo done), убей SSH-клиент (kill процесса ssh локально). Переподключись, tmux attach — процесс жив. Это упражнение переключает tmux из категории «удобно» в категорию «доверяю».
  4. Проверь цепочку OSC52. На удалённой машине в tmux скопируй строку в copy-mode и вставь в локальное приложение. Если не работает — найди, какое звено режет последовательность.
  5. Проживи день в copy-mode. Весь поиск по выводу — только префикс+[, /, v, y. Никакого «перенаправлю в файл и открою». Критерий: к вечеру v/y жмутся бездумно.