<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Tmux on DevOps Way - Практические гайды</title>
    <link>https://devopsway.ru/tags/tmux/</link>
    <description>Recent content in Tmux on DevOps Way - Практические гайды</description>
    <image>
      <title>DevOps Way - Практические гайды</title>
      <url>https://devopsway.ru/images/devopsway-og.png</url>
      <link>https://devopsway.ru/images/devopsway-og.png</link>
    </image>
    <generator>Hugo -- 0.161.1</generator>
    <language>ru</language>
    <lastBuildDate>Fri, 30 Oct 2026 11:26:11 -0400</lastBuildDate>
    <atom:link href="https://devopsway.ru/tags/tmux/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>zellij пошагово: рабочий терминал на сервере, который переживает обрыв ssh</title>
      <link>https://devopsway.ru/posts/zellij-step-by-step/</link>
      <pubDate>Thu, 29 Oct 2026 17:51:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/zellij-step-by-step/</guid>
      <description>Пошаговая сборка рабочего места на zellij 0.45.1: установка с проверкой контрольной суммы, панели и вкладки, раскладка файлом, detach/attach, воскрешение сессии после падения сервера, автоматическое переподключение при обрыве ssh и меню сессий на fzf. Каждая команда прогнана на чистой Ubuntu 22.04.</description>
      <content:encoded><![CDATA[<p>В посте <a href="https://t.me/DevITWay/232">&quot;Из браузера в терминал&quot;</a> было обещание: наберутся реакции – будет пошаговая статья. Реакции набрались, вот статья.</p>
<p>К концу на сервере будет свой zellij: панели и вкладки, рабочее место из файла раскладки, отключение без потери работы, воскрешение сессии после падения сервера, переподключение при обрыве ssh и меню сессий по <code>Alt+s</code>. Нужен только опыт работы с ssh, с tmux и screen можно не быть знакомым.</p>
<p>Всё ниже прогнано на чистой Ubuntu 22.04 с zellij 0.45.1, последним релизом на конец октября 2026. Клавиши даны для пресета Default. Поведение, взятое из исходников, помечено файлом из тега <code>v0.45.1</code>. Скриншоты – снимки окна терминала: zellij работал в такой же Ubuntu 22.04 в контейнере, рядом с учебным кластером k3s.</p>
<h2 id="что-понадобится">Что понадобится</h2>
<ul>
<li>сервер с Linux на x86_64 или ARM (здесь Ubuntu 22.04), доступ по ssh и <code>curl</code>;</li>
<li>на ноутбуке ssh-клиент и bash или zsh, для функции переподключения в шаге 8;</li>
<li>fzf для меню сессий в шаге 9.</li>
</ul>
<h2 id="шаг-1-установка">Шаг 1. Установка</h2>
<p>В репозиториях Ubuntu 22.04 zellij нет. Проще всего взять готовый статический бинарь из релиза на GitHub:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">V</span><span class="o">=</span>0.45.1
</span></span><span class="line"><span class="cl"><span class="nv">A</span><span class="o">=</span>zellij-x86_64-unknown-linux-musl      <span class="c1"># на ARM: zellij-aarch64-unknown-linux-musl</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> /tmp
</span></span><span class="line"><span class="cl">curl -fsSLO https://github.com/zellij-org/zellij/releases/download/v<span class="nv">$V</span>/<span class="nv">$A</span>.tar.gz
</span></span><span class="line"><span class="cl">curl -fsSLO https://github.com/zellij-org/zellij/releases/download/v<span class="nv">$V</span>/<span class="nv">$A</span>.sha256sum
</span></span><span class="line"><span class="cl">tar -xzf <span class="nv">$A</span>.tar.gz
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;</span><span class="k">$(</span>cut -d<span class="s1">&#39; &#39;</span> -f1 <span class="nv">$A</span>.sha256sum<span class="k">)</span><span class="s2">  zellij&#34;</span> <span class="p">|</span> sha256sum -c -
</span></span><span class="line"><span class="cl">install -D -m <span class="m">755</span> zellij ~/.local/bin/zellij
</span></span></code></pre></div><p>Обычный <code>sha256sum -c $A.sha256sum</code> здесь упадёт с <code>FAILED open or read</code>. В файле лежит хэш самого бинаря, записанный под путём сборки <code>target/x86_64-unknown-linux-musl/release/zellij</code>. Поэтому сначала распаковываем, потом сверяем хэш с распакованным <code>zellij</code> и ждём ответа <code>zellij: OK</code>.</p>
<p>В релизе есть ещё сборка <code>zellij-no-web-…</code> без встроенного веб-сервера, если он не нужен.</p>
<p>Ubuntu добавляет <code>~/.local/bin</code> в PATH из <code>~/.profile</code>, и только если каталог существовал на момент входа. Каталог только что создан, поэтому перед проверкой версии перелогиньтесь или выполните <code>source ~/.profile</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij --version
</span></span><span class="line"><span class="cl"><span class="c1"># zellij 0.45.1</span>
</span></span></code></pre></div><p>Команда, выполненная по ssh без входа в шелл (<code>ssh srv 'zellij ls'</code>), <code>~/.profile</code> не читает и ответит <code>command not found</code>. Поэтому в скриптах ниже PATH выставлен явно.</p>
<h2 id="шаг-2-первый-запуск-и-режимы">Шаг 2. Первый запуск и режимы</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij
</span></span></code></pre></div><p>Первым откроется окно First Run Setup Wizard с выбором из двух пресетов:</p>
<table>
  <thead>
      <tr>
          <th>Пресет</th>
          <th>Как входить в режимы</th>
          <th>Кому подходит</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Default</td>
          <td>сразу Ctrl-клавишей: <code>Ctrl+p</code> – панели, <code>Ctrl+t</code> – вкладки</td>
          <td>тем, кто не пользуется этими сочетаниями в шелле и редакторе</td>
      </tr>
      <tr>
          <td>Unlock First</td>
          <td>сначала <code>Ctrl+g</code>, потом буква: <code>Ctrl+g</code> → <code>p</code>, <code>Ctrl+g</code> → <code>t</code></td>
          <td>тем, кому нужны <code>Ctrl+p</code>, <code>Ctrl+n</code>, <code>Ctrl+s</code> в bash, vim или emacs</td>
      </tr>
  </tbody>
</table>
<p>В статье используется Default, он выбирается по Enter. Выбор сохранится в <code>~/.config/zellij/config.kdl</code>. Этот файл zellij создаёт уже при первом запуске, в нём полный конфиг со всеми клавишами. После выбора пресета zellij переписывает его, а прежнюю версию кладёт рядом как <code>config.kdl.bak</code>.</p>
<img alt="Мастер первого запуска zellij: пресеты Default и Unlock First" loading="lazy" src="/images/zellij-step-by-step/01-wizard.png"><p>При каждом старте сессии поверх экрана выходит окно About Zellij с подсказкой дня. Esc его закрывает, <code>Ctrl+c</code> отключает подсказки при старте. Закрыть его стоит сразу: пока оно на экране, у вкладки включён слой плавающих панелей, и <code>Alt+n</code> откроет новую панель плавающей поверх экрана.</p>
<p>Если в строке вкладок и строке статуса вместо стрелок квадраты, в шрифте терминала нет символа U+E0B0 из набора Powerline. Поставьте шрифт из Nerd Fonts или включите в <code>config.kdl</code> строку <code>simplified_ui true</code>: zellij нарисует те же строки без стрелок (<code>zellij-utils/assets/config/default.kdl:284</code>).</p>
<p>Строка статуса внизу показывает режимы и их клавиши, поэтому учить таблицу наизусть не нужно:</p>
<table>
  <thead>
      <tr>
          <th>Клавиша</th>
          <th>Режим</th>
          <th>Для чего</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>Ctrl+p</code></td>
          <td>PANE</td>
          <td>панели</td>
      </tr>
      <tr>
          <td><code>Ctrl+t</code></td>
          <td>TAB</td>
          <td>вкладки</td>
      </tr>
      <tr>
          <td><code>Ctrl+n</code></td>
          <td>RESIZE</td>
          <td>размер панелей</td>
      </tr>
      <tr>
          <td><code>Ctrl+h</code></td>
          <td>MOVE</td>
          <td>перестановка панелей</td>
      </tr>
      <tr>
          <td><code>Ctrl+s</code></td>
          <td>SCROLL</td>
          <td>прокрутка и поиск по выводу</td>
      </tr>
      <tr>
          <td><code>Ctrl+o</code></td>
          <td>SESSION</td>
          <td>отключиться, менеджер сессий</td>
      </tr>
      <tr>
          <td><code>Ctrl+b</code></td>
          <td>TMUX</td>
          <td>клавиши в духе tmux: <code>&quot;</code>, <code>%</code>, <code>c</code>, <code>n</code>, <code>p</code></td>
      </tr>
      <tr>
          <td><code>Ctrl+g</code></td>
          <td>LOCKED</td>
          <td>все клавиши, кроме <code>Ctrl+g</code>, уходят программе в панели</td>
      </tr>
      <tr>
          <td><code>Ctrl+q</code></td>
          <td>–</td>
          <td>выйти из zellij и убить сессию, об этом ниже</td>
      </tr>
  </tbody>
</table>
<p>Из любого режима в обычный возвращают Esc или Enter.</p>
<p>У Default есть цена: <code>Ctrl+p</code> в bash – предыдущая команда, а zellij перехватывает его раньше. Когда сочетание нужно программе внутри панели, нажмите <code>Ctrl+g</code>. Строка статуса покажет <code>-- INTERFACE LOCKED --</code>, <code>Ctrl+p</code> уйдёт в bash, а повторный <code>Ctrl+g</code> снимет блокировку.</p>
<p>Если вы на macOS, Alt-сочетания доходят до сервера, только когда Option в терминале работает как Meta (в iTerm2 это настройка Esc+ для клавиши Option).</p>
<h2 id="шаг-3-панели-и-вкладки">Шаг 3. Панели и вкладки</h2>
<table>
  <thead>
      <tr>
          <th>Действие</th>
          <th>Клавиши</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Новая панель, zellij сам выберет место</td>
          <td><code>Alt+n</code> или <code>Ctrl+p</code> → <code>n</code></td>
      </tr>
      <tr>
          <td>Новая панель справа / снизу</td>
          <td><code>Ctrl+p</code> → <code>r</code> / <code>Ctrl+p</code> → <code>d</code></td>
      </tr>
      <tr>
          <td>Перейти в соседнюю панель</td>
          <td><code>Alt+стрелка</code> или <code>Alt+h/j/k/l</code></td>
      </tr>
      <tr>
          <td>Закрыть панель</td>
          <td><code>Ctrl+p</code> → <code>x</code></td>
      </tr>
      <tr>
          <td>Развернуть панель на всю вкладку и обратно</td>
          <td><code>Ctrl+p</code> → <code>f</code></td>
      </tr>
      <tr>
          <td>Показать или скрыть плавающие панели</td>
          <td><code>Alt+f</code> или <code>Ctrl+p</code> → <code>w</code></td>
      </tr>
      <tr>
          <td>Сделать текущую панель плавающей и вернуть обратно</td>
          <td><code>Ctrl+p</code> → <code>e</code></td>
      </tr>
      <tr>
          <td>Новая панель в стопку</td>
          <td><code>Ctrl+p</code> → <code>s</code></td>
      </tr>
      <tr>
          <td>Переименовать панель</td>
          <td><code>Ctrl+p</code> → <code>c</code>, имя, Enter</td>
      </tr>
      <tr>
          <td>Изменить размер</td>
          <td><code>Ctrl+n</code> и стрелки, или <code>Alt+=</code> и <code>Alt+-</code></td>
      </tr>
      <tr>
          <td>Новая вкладка</td>
          <td><code>Ctrl+t</code> → <code>n</code></td>
      </tr>
      <tr>
          <td>Переименовать вкладку</td>
          <td><code>Ctrl+t</code> → <code>r</code>, имя, Enter</td>
      </tr>
      <tr>
          <td>Вкладка по номеру</td>
          <td><code>Ctrl+t</code> → <code>1…9</code></td>
      </tr>
      <tr>
          <td>Предыдущая открытая вкладка</td>
          <td><code>Ctrl+t</code> → <code>Tab</code></td>
      </tr>
      <tr>
          <td>Закрыть вкладку</td>
          <td><code>Ctrl+t</code> → <code>x</code></td>
      </tr>
      <tr>
          <td>Поиск по выводу панели</td>
          <td><code>Ctrl+s</code> → <code>s</code>, текст, Enter; <code>n</code> и <code>p</code> – следующее и предыдущее совпадение; <code>Ctrl+c</code> – вернуться</td>
      </tr>
  </tbody>
</table>
<p>Несколько деталей, которые видно только в работе:</p>
<ul>
<li><code>Alt+l</code> в самой правой панели переходит на следующую вкладку, с последней вкладки – на первую. <code>Alt+h</code> работает так же влево.</li>
<li>Если плавающих панелей во вкладке ещё нет, первое <code>Ctrl+p</code> → <code>w</code> или <code>Alt+f</code> создаёт новую плавающую.</li>
<li>В стопке видна целиком одна панель, от остальных остаются строки заголовков. Это удобно, когда панелей много, а экран маленький.</li>
<li><code>Alt+=</code> растит текущую панель: соседние сворачиваются в стопку, после нескольких нажатий панель занимает всю вкладку. <code>Alt+-</code> уменьшает её обратно.</li>
</ul>
<img alt="Три панели и плавающая поверх них: kubectl get deploy, htop, git log и kubectl get pods" loading="lazy" src="/images/zellij-step-by-step/02-panes.png"><h2 id="шаг-4-сессии-отключиться-и-вернуться">Шаг 4. Сессии: отключиться и вернуться</h2>
<p>Сессия – это сервер zellij со всеми вкладками и процессами внутри. Без имени zellij придумает случайное (<code>loyal-yak</code>, <code>splendid-peach</code>), своё задаётся ключом <code>-s</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij -s work
</span></span></code></pre></div><p>В сессии запустите что-нибудь долгое, например <code>sleep 100000</code>, и отключитесь: <code>Ctrl+o</code> → <code>d</code>. Терминал вернётся в шелл с сообщением <code>Bye from Zellij!</code>, а процесс продолжит работать:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij ls -n                 <span class="c1"># список сессий; -n – без цветов, удобно для скриптов</span>
</span></span><span class="line"><span class="cl"><span class="c1"># work [Created 25s ago]</span>
</span></span><span class="line"><span class="cl">pgrep -a -x sleep
</span></span><span class="line"><span class="cl"><span class="c1"># 2508457 sleep 100000</span>
</span></span><span class="line"><span class="cl">zellij attach work           <span class="c1"># вернуться: вкладки, панели и sleep на месте</span>
</span></span></code></pre></div><p>Остальные команды для сессий:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij attach -c work        <span class="c1"># подключиться, а если сессии нет – создать</span>
</span></span><span class="line"><span class="cl">zellij kill-session work     <span class="c1"># остановить сервер: процессы умрут, раскладка останется</span>
</span></span><span class="line"><span class="cl">zellij delete-session work   <span class="c1"># удалить остановленную сессию совсем</span>
</span></span></code></pre></div><p>Изнутри сессии список всех сессий с числом вкладок и подключённых клиентов открывает <code>Ctrl+o</code> → <code>w</code> (Session Manager).</p>
<h2 id="шаг-5-ctrlq-и-правка-конфига">Шаг 5. Ctrl+q и правка конфига</h2>
<p>По умолчанию <code>Ctrl+q</code> в zellij – <code>Quit</code> (<code>zellij-utils/assets/config/default.kdl:198</code>): все процессы сессии завершаются. После этого <code>zellij ls</code> покажет <code>work [...] (EXITED - attach to resurrect)</code>. Вкладки вернуть можно, но всё запущенное придётся поднимать заново. <code>Ctrl+q</code> стоит переназначить на отключение раньше, чем он сработает случайно:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/bind &#34;Ctrl q&#34; { Quit; }/bind &#34;Ctrl q&#34; { Detach; }/&#39;</span> ~/.config/zellij/config.kdl
</span></span><span class="line"><span class="cl">grep -n <span class="s1">&#39;Ctrl q&#39;</span> ~/.config/zellij/config.kdl
</span></span><span class="line"><span class="cl"><span class="c1"># 174:        bind &#34;Ctrl q&#34; { Detach; }</span>
</span></span></code></pre></div><p>Новые клавиши гарантированно подхватываются при следующем подключении: <code>Ctrl+o</code> → <code>d</code>, затем <code>zellij attach work</code>. После этого <code>Ctrl+q</code> отключает от сессии, а <code>zellij ls</code> показывает её живой, без <code>EXITED</code>.</p>
<p>Остальное в <code>config.kdl</code> можно не трогать. В рабочем конфиге на главном сервере руками изменены ровно две строки, всё остальное записал мастер первого запуска:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">-        bind &#34;Ctrl q&#34; { Quit; }
</span></span></span><span class="line"><span class="cl"><span class="gi">+        bind &#34;Ctrl q&#34; { Detach; }
</span></span></span><span class="line"><span class="cl"><span class="gi">+        bind &#34;Alt s&#34; { Run &#34;/home/ubuntu/.local/bin/lc-pick&#34; { floating true; close_on_exit true; name &#34;сессии&#34;; }; }
</span></span></span></code></pre></div><p>Про <code>Alt+s</code> – в шаге 9.</p>
<h2 id="шаг-6-раскладка-рабочее-место-одной-командой">Шаг 6. Раскладка: рабочее место одной командой</h2>
<p>Раскладка – это файл KDL с описанием вкладок, панелей и команд. Файлы из <code>~/.config/zellij/layouts/</code> можно вызывать по имени, например <code>~/.config/zellij/layouts/work.kdl</code> ниже вызывается как <code>work</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kdl" data-lang="kdl"><span class="line"><span class="cl"><span class="nl">layout</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">    default_tab_template</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">size=</span><span class="m">1</span><span class="w"> </span><span class="na">borderless=</span><span class="s">true</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            plugin</span><span class="w"> </span><span class="na">location=</span><span class="s">&#34;zellij:tab-bar&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">        children</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">size=</span><span class="m">2</span><span class="w"> </span><span class="na">borderless=</span><span class="s">true</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            plugin</span><span class="w"> </span><span class="na">location=</span><span class="s">&#34;zellij:status-bar&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">    tab</span><span class="w"> </span><span class="na">name=</span><span class="s">&#34;код&#34;</span><span class="w"> </span><span class="na">focus=</span><span class="s">true</span><span class="w"> </span><span class="na">cwd=</span><span class="s">&#34;~/project&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">split_direction=</span><span class="s">&#34;vertical&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            pane</span><span class="w"> </span><span class="na">size=</span><span class="s">&#34;65%&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">            pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;git&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">                args</span><span class="w"> </span><span class="s">&#34;log&#34;</span><span class="w"> </span><span class="s">&#34;--oneline&#34;</span><span class="w"> </span><span class="s">&#34;-20&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">    tab</span><span class="w"> </span><span class="na">name=</span><span class="s">&#34;сервер&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;htop&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;df&#34;</span><span class="w"> </span><span class="na">size=</span><span class="s">&#34;30%&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            args</span><span class="w"> </span><span class="s">&#34;-h&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">}</span><span class="w">
</span></span></span></code></pre></div><p><code>default_tab_template</code> – шаблон для каждой вкладки: сверху строка вкладок, снизу строка статуса, на месте <code>children</code> – панели самой вкладки. Без шаблона zellij откроет вкладки голыми, без этих двух строк. <code>pane command=</code> запускает программу вместо шелла. Когда программа завершается, панель остаётся на экране с кодом выхода в заголовке, например <code>git log --oneline -20 [ EXIT CODE: 0 ]</code>.</p>
<p><code>~/project</code> замените на свой каталог. Если каталога нет, zellij ошибку не покажет и откроет панели в том каталоге, откуда запущен.</p>
<p>Запуск:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij -s dev -n work        <span class="c1"># -n – раскладка по имени или по пути к файлу</span>
</span></span></code></pre></div><p>Если сессия <code>dev</code> уже есть, zellij откажется: <code>Session with name &quot;dev&quot; already exists. Use attach command to connect to it or specify a different name.</code> Обёртка, которая сама выбирает между созданием и подключением, – в шаге 8.</p>
<p>Пример ближе к повседневной работе с Kubernetes – раскладка <code>~/.config/zellij/layouts/ops.kdl</code>. Во вкладке &quot;кластер&quot; k9s, во вкладке &quot;логи&quot; поток логов воркеров и <code>kubectl get pods --watch</code>, во вкладке &quot;код&quot; репозиторий. На сервере нужны <code>kubectl</code>, <code>k9s</code> и доступ к кластеру, <code>shop</code> – пространство имён учебного кластера:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kdl" data-lang="kdl"><span class="line"><span class="cl"><span class="nl">layout</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">    default_tab_template</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">size=</span><span class="m">1</span><span class="w"> </span><span class="na">borderless=</span><span class="s">true</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            plugin</span><span class="w"> </span><span class="na">location=</span><span class="s">&#34;zellij:tab-bar&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">        children</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">size=</span><span class="m">2</span><span class="w"> </span><span class="na">borderless=</span><span class="s">true</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            plugin</span><span class="w"> </span><span class="na">location=</span><span class="s">&#34;zellij:status-bar&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">    tab</span><span class="w"> </span><span class="na">name=</span><span class="s">&#34;кластер&#34;</span><span class="w"> </span><span class="na">focus=</span><span class="s">true</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;k9s&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            args</span><span class="w"> </span><span class="s">&#34;-n&#34;</span><span class="w"> </span><span class="s">&#34;shop&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">    tab</span><span class="w"> </span><span class="na">name=</span><span class="s">&#34;логи&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;kubectl&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            args</span><span class="w"> </span><span class="s">&#34;-n&#34;</span><span class="w"> </span><span class="s">&#34;shop&#34;</span><span class="w"> </span><span class="s">&#34;logs&#34;</span><span class="w"> </span><span class="s">&#34;-f&#34;</span><span class="w"> </span><span class="s">&#34;-l&#34;</span><span class="w"> </span><span class="s">&#34;app=worker&#34;</span><span class="w"> </span><span class="s">&#34;--prefix&#34;</span><span class="w"> </span><span class="s">&#34;--tail&#34;</span><span class="w"> </span><span class="s">&#34;8&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;kubectl&#34;</span><span class="w"> </span><span class="na">size=</span><span class="s">&#34;40%&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            args</span><span class="w"> </span><span class="s">&#34;-n&#34;</span><span class="w"> </span><span class="s">&#34;shop&#34;</span><span class="w"> </span><span class="s">&#34;get&#34;</span><span class="w"> </span><span class="s">&#34;pods&#34;</span><span class="w"> </span><span class="s">&#34;--watch&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">    tab</span><span class="w"> </span><span class="na">name=</span><span class="s">&#34;код&#34;</span><span class="w"> </span><span class="na">cwd=</span><span class="s">&#34;~/project&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">split_direction=</span><span class="s">&#34;vertical&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            pane</span><span class="w"> </span><span class="na">size=</span><span class="s">&#34;60%&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">            pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;git&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">                args</span><span class="w"> </span><span class="s">&#34;log&#34;</span><span class="w"> </span><span class="s">&#34;--oneline&#34;</span><span class="w"> </span><span class="s">&#34;-15&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">}</span><span class="w">
</span></span></span></code></pre></div><p>Запуск – <code>zellij -s ops -n ops</code>. Так выглядят вкладки &quot;кластер&quot; и &quot;логи&quot;, во время снимка в нижней панели шёл <code>kubectl rollout restart</code> деплоймента <code>web</code>:</p>
<img alt="Вкладка &quot;кластер&quot;: k9s со списком подов" loading="lazy" src="/images/zellij-step-by-step/03-ops-k9s.png"><img alt="Вкладка &quot;логи&quot;: логи воркеров и kubectl get pods &ndash;watch во время перезапуска web" loading="lazy" src="/images/zellij-step-by-step/04-ops-logs.png"><p>Рабочая раскладка на главном сервере короче. В ней одна вкладка, меню агентов, а остальные вкладки открываются из меню:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kdl" data-lang="kdl"><span class="line"><span class="cl"><span class="nl">layout</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">    default_tab_template</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">size=</span><span class="m">1</span><span class="w"> </span><span class="na">borderless=</span><span class="s">true</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            plugin</span><span class="w"> </span><span class="na">location=</span><span class="s">&#34;zellij:tab-bar&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">        children</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">size=</span><span class="m">2</span><span class="w"> </span><span class="na">borderless=</span><span class="s">true</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            plugin</span><span class="w"> </span><span class="na">location=</span><span class="s">&#34;zellij:status-bar&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">    tab</span><span class="w"> </span><span class="na">name=</span><span class="s">&#34;☰ меню (Alt-s)&#34;</span><span class="w"> </span><span class="na">focus=</span><span class="s">true</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">        pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;/home/ubuntu/.local/bin/lc-pick&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">            args</span><span class="w"> </span><span class="s">&#34;--loop&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="o">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">}</span><span class="w">
</span></span></span></code></pre></div><h2 id="шаг-7-воскрешение-после-падения-сервера">Шаг 7. Воскрешение после падения сервера</h2>
<p>Сохранение сессий включено по умолчанию (<code>zellij-server/src/screen.rs:8074</code>). Раз в 60 секунд (<code>zellij-server/src/background_jobs.rs:124</code>) zellij записывает раскладку сессии в <code>~/.cache/zellij/contract_version_1/session_info/&lt;имя&gt;/session-layout.kdl</code>: вкладки, панели, их каталоги и команды, запущенные в панелях. <code>sleep 100000</code>, набранный руками в шелле, попадает туда как команда панели:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kdl" data-lang="kdl"><span class="line"><span class="cl"><span class="nl">pane</span><span class="w"> </span><span class="na">command=</span><span class="s">&#34;sleep&#34;</span><span class="w"> </span><span class="na">cwd=</span><span class="s">&#34;home/ubuntu/zellij-statya&#34;</span><span class="w"> </span><span class="na">size=</span><span class="s">&#34;50%&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl">
</span></span></span><span class="line"><span class="cl"><span class="nl">    args</span><span class="w"> </span><span class="s">&#34;100000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nl">    start_suspended</span><span class="w"> </span><span class="s">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">}</span><span class="w">
</span></span></span></code></pre></div><p>Сохранить сразу, не дожидаясь минуты:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij action save-session
</span></span></code></pre></div><p>Теперь уроним сервер. <code>zellij kill-session work</code> и <code>kill -9</code> процесса <code>zellij --server</code> ведут себя одинаково: процессы в панелях умирают вместе с сервером, а сессия остаётся в списке:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij ls -n
</span></span><span class="line"><span class="cl"><span class="c1"># work [Created 10s ago] (EXITED - attach to resurrect)</span>
</span></span><span class="line"><span class="cl">pgrep -a -x sleep <span class="p">|</span> grep <span class="m">100000</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;sleep умер вместе с сервером&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># sleep умер вместе с сервером</span>
</span></span></code></pre></div><p><code>zellij attach work</code> вернёт вкладки, панели и каталоги, но команды сразу не запустит. Из-за <code>start_suspended true</code> на месте каждой будет:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Waiting to run: sleep 100000
</span></span><span class="line"><span class="cl">&lt;ENTER&gt; run, &lt;ESC&gt; drop to shell, &lt;Ctrl-c&gt; exit
</span></span></code></pre></div><p>Так выглядит вкладка &quot;логи&quot; из <code>ops.kdl</code> после падения сервера, обе команды <code>kubectl</code> ждут Enter:</p>
<img alt="Воскрешённая вкладка: команды kubectl ждут запуска" loading="lazy" src="/images/zellij-step-by-step/05-resurrect.png"><p>Чтобы запустить все команды сразу:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zellij attach -f work        <span class="c1"># --force-run-commands</span>
</span></span><span class="line"><span class="cl">pgrep -a -x sleep <span class="p">|</span> grep <span class="m">100000</span>
</span></span><span class="line"><span class="cl"><span class="c1"># 2513575 sleep 100000</span>
</span></span></code></pre></div><p>PID новый: воскрешение запускает команды заново, прежние процессы погибли вместе с сервером. Состояние программ и вывод на экране пропадают, раскладка и каталоги остаются. Если сервер упал раньше первого сохранения, воскрешать нечего: сессия, созданная и убитая в первые секунды, из <code>zellij ls</code> пропадает целиком. Файл раскладки лежит в <code>~/.cache</code>, а сокеты сервера – в <code>/tmp/zellij-&lt;uid&gt;/</code>.</p>
<h2 id="шаг-8-переподключение-при-обрыве-ssh">Шаг 8. Переподключение при обрыве ssh</h2>
<p>Сама сессия на сервере обрыв переживает, ssh-клиент – нет. Его нужно запустить заново и снова подключиться. Это делают две маленькие обёртки: скрипт на сервере и функция на ноутбуке.</p>
<p>Скрипт на сервере, <code>~/.local/bin/work-attach</code>, выбирает одно из трёх действий:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="c1"># work-attach – войти в zellij-сессию: создать, воскресить или подключиться</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">S</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="nv">work</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">st</span><span class="o">=</span><span class="k">$(</span>zellij ls -n 2&gt;/dev/null <span class="p">|</span> grep -E <span class="s2">&#34;^</span><span class="nv">$S</span><span class="s2"> &#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$st</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exec</span> zellij -s <span class="s2">&#34;</span><span class="nv">$S</span><span class="s2">&#34;</span> -n work            <span class="c1"># сессии нет – поднять из раскладки</span>
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$st</span><span class="s2">&#34;</span> <span class="p">|</span> grep -q EXITED<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exec</span> zellij attach -f <span class="s2">&#34;</span><span class="nv">$S</span><span class="s2">&#34;</span>             <span class="c1"># сервер умер – воскресить и сразу запустить команды</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exec</span> zellij attach <span class="s2">&#34;</span><span class="nv">$S</span><span class="s2">&#34;</span>                <span class="c1"># живая – подключиться</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">chmod +x ~/.local/bin/work-attach
</span></span></code></pre></div><p>Ветки нужны потому, что <code>zellij attach -c</code> создаёт сессию без раскладки, а <code>zellij -s</code> с раскладкой падает на существующем имени. <code>export PATH</code> стоит из-за того, что по ssh без входа в шелл <code>~/.local/bin</code> в PATH не попадает.</p>
<p>Функция на ноутбуке, в <code>~/.bashrc</code> или <code>~/.zshrc</code>. <code>srv</code> – хост из <code>~/.ssh/config</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">w<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">while</span> :<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    ssh -t -o <span class="nv">ServerAliveInterval</span><span class="o">=</span><span class="m">10</span> -o <span class="nv">ServerAliveCountMax</span><span class="o">=</span><span class="m">3</span> srv <span class="s2">&#34;~/.local/bin/work-attach&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">break</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;связь потеряна, переподключаюсь через 3 с (Ctrl-C – выход)&#34;</span><span class="p">;</span> sleep <span class="m">3</span>
</span></span><span class="line"><span class="cl">  <span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>При отключении по <code>Ctrl+q</code> zellij выходит с кодом 0, ssh возвращает 0, и цикл заканчивается. При обрыве ssh завершается с кодом 255, и цикл подключается снова.</p>
<p>Без <code>ServerAlive*</code> функция бесполезна при самом частом обрыве, когда сеть пропала, а TCP-соединение формально не закрыто. ssh тогда просто висит. Это проверялось так: ssh пускался через <code>socat</code>, который потом замораживался <code>kill -STOP</code>. С параметрами из функции (10 секунд × 3 проверки) новое соединение поднялось через 42 секунды после заморозки. С <code>ServerAliveInterval=0</code> ssh провисел почти три минуты без признаков жизни, после чего проверку остановили. Те же параметры можно прописать и для хоста в <code>~/.ssh/config</code>.</p>
<p>На главном сервере эта пара называется <code>lc</code> и <code>lc-attach</code> и устроена так же. Есть одно отличие: при подключении к живой сессии фокус сразу переходит на первую вкладку с меню, даже если отключались с другой:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="c1"># lc-attach – вход в рабочее место: zellij-сессия &#34;lc&#34; (создать / подключиться / воскресить)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">S</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">LC_SESSION</span><span class="k">:-</span><span class="nv">lc</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">st</span><span class="o">=</span><span class="k">$(</span>zellij ls -n 2&gt;/dev/null <span class="p">|</span> grep -E <span class="s2">&#34;^</span><span class="nv">$S</span><span class="s2"> &#34;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -z <span class="s2">&#34;</span><span class="nv">$st</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exec</span> zellij -s <span class="s2">&#34;</span><span class="nv">$S</span><span class="s2">&#34;</span> -n <span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.config/zellij/layouts/lc.kdl&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$st</span><span class="s2">&#34;</span> <span class="p">|</span> grep -q EXITED<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exec</span> zellij attach -f <span class="s2">&#34;</span><span class="nv">$S</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="o">(</span> sleep 1.5<span class="p">;</span> zellij --session <span class="s2">&#34;</span><span class="nv">$S</span><span class="s2">&#34;</span> action go-to-tab <span class="m">1</span> <span class="o">)</span> &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exec</span> zellij attach <span class="s2">&#34;</span><span class="nv">$S</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><h2 id="шаг-9-меню-сессий-на-fzf">Шаг 9. Меню сессий на fzf</h2>
<p>Когда сессий больше двух, удобно выбирать их из списка с поиском и сразу видеть, что в каждой открыто. Нужны fzf и jq: <code>sudo apt install fzf jq</code> (на Ubuntu 22.04 fzf версии 0.29) или fzf бинарём из релиза на GitHub. Скрипт ниже проверен на fzf 0.29 и 0.74.5.</p>
<p><code>~/.local/bin/zpick</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="c1"># zpick – меню zellij-сессий на fzf: слева сессии, справа что в них открыто.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Enter – перейти, новое имя + Enter – создать, Esc – выход.</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.local/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">card<span class="o">()</span> <span class="o">{</span>   <span class="c1"># карточка сессии для превью: состояние, вкладки и что в них запущено</span>
</span></span><span class="line"><span class="cl">  <span class="nb">local</span> <span class="nv">s</span><span class="o">=</span><span class="nv">$1</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$s</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> zellij ls -n 2&gt;/dev/null <span class="p">|</span> grep -E <span class="s2">&#34;^</span><span class="nv">$s</span><span class="s2"> &#34;</span> <span class="p">|</span> grep -q EXITED<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;○ остановлена, Enter воскресит&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span>
</span></span><span class="line"><span class="cl">    awk -F<span class="s1">&#39;&#34;&#39;</span> <span class="s1">&#39;/^ *tab name=/ {print $2} / pane command=/ {print &#34;  &#34; $2}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">      ~/.cache/zellij/*/session_info/<span class="s2">&#34;</span><span class="nv">$s</span><span class="s2">&#34;</span>/session-layout.kdl 2&gt;/dev/null
</span></span><span class="line"><span class="cl">  <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;● работает · подключено клиентов: </span><span class="k">$(</span>zellij -s <span class="s2">&#34;</span><span class="nv">$s</span><span class="s2">&#34;</span> action list-clients 2&gt;/dev/null <span class="p">|</span> tail -n +2 <span class="p">|</span> wc -l<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span>
</span></span><span class="line"><span class="cl">    jq -rn --argjson tabs <span class="s2">&#34;</span><span class="k">$(</span>zellij -s <span class="s2">&#34;</span><span class="nv">$s</span><span class="s2">&#34;</span> action list-tabs -j 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> <span class="s1">&#39;[]&#39;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">           --argjson panes <span class="s2">&#34;</span><span class="k">$(</span>zellij -s <span class="s2">&#34;</span><span class="nv">$s</span><span class="s2">&#34;</span> action list-panes -a -j 2&gt;/dev/null <span class="o">||</span> <span class="nb">echo</span> <span class="s1">&#39;[]&#39;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="s1">&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">      $tabs[] as $t | $t.name,
</span></span></span><span class="line"><span class="cl"><span class="s1">      ($panes[] | select(.tab_position == $t.position and (.is_plugin | not))
</span></span></span><span class="line"><span class="cl"><span class="s1">                | &#34;  &#34; + (.terminal_command // &#34;шелл&#34;) + (if .is_floating then &#34; (плавающая)&#34; else &#34;&#34; end))&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> <span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span> <span class="o">=</span> --card <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="o">{</span> card <span class="s2">&#34;</span><span class="nv">$2</span><span class="s2">&#34;</span><span class="p">;</span> <span class="nb">exit</span> 0<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">list<span class="o">()</span> <span class="o">{</span>   <span class="c1"># ● имя  возраст  – из zellij ls</span>
</span></span><span class="line"><span class="cl">  zellij ls -n 2&gt;/dev/null <span class="p">|</span> sed -E <span class="s1">&#39;s/^([^ ]+) \[Created ([^]]*) ago\](.*)$/\1\t\2\t\3/&#39;</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">    awk -F<span class="s1">&#39;\t&#39;</span> <span class="s1">&#39;{ printf &#34;%s %-22s %s%s\n&#34;, ($3 ~ /EXITED/ ? &#34;○&#34; : &#34;●&#34;), $1, $2, ($3 ~ /current/ ? &#34; · здесь&#34; : &#34;&#34;) }&#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">out</span><span class="o">=</span><span class="k">$(</span>list <span class="p">|</span> fzf --print-query --reverse --no-sort --info<span class="o">=</span>inline --prompt<span class="o">=</span><span class="s1">&#39;сессия&gt; &#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">        --header<span class="o">=</span><span class="s1">&#39;Enter – перейти · новое имя – создать · Esc – выход&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">        --preview<span class="o">=</span><span class="s2">&#34;</span><span class="nv">$0</span><span class="s2"> --card {2}&#34;</span> --preview-window<span class="o">=</span><span class="s1">&#39;down,50%,wrap&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> <span class="nv">$?</span> -eq <span class="m">130</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="nv">name</span><span class="o">=</span><span class="k">$(</span>sed -n 2p <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span> <span class="p">|</span> awk <span class="s1">&#39;{print $2}&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">name</span><span class="o">=</span><span class="si">${</span><span class="nv">name</span><span class="k">:-$(</span>sed -n 1p <span class="o">&lt;&lt;&lt;</span><span class="s2">&#34;</span><span class="nv">$out</span><span class="s2">&#34;</span><span class="k">)</span><span class="si">}</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span> <span class="o">]</span> <span class="o">||</span> <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$ZELLIJ</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  zellij action switch-session <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span>   <span class="c1"># уже внутри zellij – переключиться</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exec</span> work-attach <span class="s2">&#34;</span><span class="nv">$name</span><span class="s2">&#34;</span>               <span class="c1"># из шелла – войти</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">chmod +x ~/.local/bin/zpick
</span></span></code></pre></div><p>В списке у каждой сессии значок: ● – работает, ○ – сервер остановлен. Внизу карточка выбранной сессии: её вкладки и что в каждой запущено. У живой сессии это берётся из <code>zellij action list-tabs</code> и <code>list-panes</code>, у остановленной – из сохранённой раскладки в <code>~/.cache/zellij</code>, так что видно, что поднимется после воскрешения. <code>--print-query</code> нужен для новых сессий: если набрать имя, которого нет в списке, скрипт получит набранный текст и создаст сессию с этим именем. Внутри zellij переменная <code>ZELLIJ</code> задана, и скрипт переключает текущий терминал на другую сессию через <code>zellij action switch-session</code>. Той же командой создаётся сессия с новым именем (раскладка у неё стандартная, <code>work</code> применяется только при входе из шелла) и воскрешается остановленная.</p>
<p>Чтобы меню открывалось плавающим окном поверх любой вкладки, в <code>config.kdl</code> в блок <code>shared_except &quot;locked&quot; {</code> добавляется одна строка:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kdl" data-lang="kdl"><span class="line"><span class="cl"><span class="nl">        bind</span><span class="w"> </span><span class="s">&#34;Alt s&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl"> Run</span><span class="w"> </span><span class="s">&#34;zpick&#34;</span><span class="w"> </span><span class="o">{</span><span class="nl"> floating</span><span class="w"> </span><span class="s">true</span><span class="o">;</span><span class="nl"> close_on_exit</span><span class="w"> </span><span class="s">true</span><span class="o">;</span><span class="nl"> name</span><span class="w"> </span><span class="s">&#34;сессии&#34;</span><span class="o">;</span><span class="w"> </span><span class="o">};</span><span class="w"> </span><span class="o">}</span><span class="w">
</span></span></span></code></pre></div><p>В пресете Default сочетание <code>Alt+s</code> свободно. После переподключения к сессии <code>Alt+s</code> открывает окно &quot;сессии&quot; со списком, выбор переключает сессию, окно закрывается само (<code>close_on_exit</code>).</p>
<img alt="Меню сессий по Alt+s поверх k9s: список сессий и карточка выбранной" loading="lazy" src="/images/zellij-step-by-step/06-menu.png"><p>На главном сервере то же меню (<code>lc-pick</code>) работает поверх реестра сессий агентов со всех четырёх серверов. Enter открывает сессию агента во вкладке или переходит в уже открытую. Есть клавиши завершить, скрыть и обновить, в превью – карточка сессии. Реестр – отдельный скрипт под эту задачу, поэтому здесь показан общий вариант над <code>zellij ls</code>. Принцип тот же: fzf над списком, <code>Alt+s</code> через <code>Run</code>, вкладка меню в раскладке.</p>
<h2 id="веб-клиент">Веб-клиент</h2>
<p>В zellij встроен веб-сервер: сессию можно открыть из браузера. По умолчанию он выключен. Запускается командой <code>zellij web</code>, вход по токену из <code>zellij web --create-token</code>, слушает <code>127.0.0.1:8082</code> (<code>zellij-client/src/web_client/mod.rs:96-99</code>). Сессии, запущенные из терминала, в браузер по умолчанию не отдаются (<code>web_sharing</code>, по умолчанию <code>&quot;off&quot;</code>). Общий доступ для такой сессии включается в окне Share (<code>Ctrl+o</code> → <code>s</code>). В этой статье веб-клиент не прогонялся, подробности – в документации zellij.</p>
<h2 id="оформление">Оформление</h2>
<p>Цвета, рамки и стрелки меняются в том же <code>~/.config/zellij/config.kdl</code>, по одной строке на настройку. Новая сессия подхватывает их при запуске.</p>
<table>
  <thead>
      <tr>
          <th>Что</th>
          <th>Строка в config.kdl</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Тема</td>
          <td><code>theme &quot;dracula&quot;</code></td>
      </tr>
      <tr>
          <td>Без рамок и заголовков панелей</td>
          <td><code>pane_frames false</code></td>
      </tr>
      <tr>
          <td>Полная рамка вокруг каждой панели</td>
          <td><code>pane_frame_style &quot;full&quot;</code></td>
      </tr>
      <tr>
          <td>Строки вкладок и статуса без стрелок</td>
          <td><code>simplified_ui true</code></td>
      </tr>
  </tbody>
</table>
<p>В zellij 0.45.1 встроена 41 тема (<code>zellij-utils/src/consts.rs:24</code>), среди них catppuccin, dracula, gruvbox, nord, solarized и tokyo-night. Полный список – каталог <code>zellij-utils/assets/themes</code> в репозитории zellij. Если терминал сам переключает светлую и тёмную схему, zellij может следовать за ним, для этого нужны обе строки: <code>theme_dark &quot;nightfox&quot;</code> и <code>theme_light &quot;dayfox&quot;</code> (<code>zellij-utils/assets/config/default.kdl:370-376</code>, на стенде не проверялось).</p>
<h2 id="итого">Итого</h2>
<p>На сервере теперь работает сессия, которая переживает отключение, падение сервера zellij и обрыв ssh. Вход – функцией <code>w</code>, выбор сессии – по <code>Alt+s</code>. Команды и клавиши из статьи одной таблицей:</p>
<table>
  <thead>
      <tr>
          <th>Что</th>
          <th>Как</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Новая сессия с именем</td>
          <td><code>zellij -s work</code></td>
      </tr>
      <tr>
          <td>Сессия из раскладки</td>
          <td><code>zellij -s dev -n work</code></td>
      </tr>
      <tr>
          <td>Список сессий</td>
          <td><code>zellij ls -n</code></td>
      </tr>
      <tr>
          <td>Отключиться</td>
          <td><code>Ctrl+o</code> → <code>d</code> (после правки конфига – <code>Ctrl+q</code>)</td>
      </tr>
      <tr>
          <td>Подключиться</td>
          <td><code>zellij attach work</code></td>
      </tr>
      <tr>
          <td>Подключиться или создать</td>
          <td><code>zellij attach -c work</code></td>
      </tr>
      <tr>
          <td>Воскресить и сразу запустить команды</td>
          <td><code>zellij attach -f work</code></td>
      </tr>
      <tr>
          <td>Сохранить раскладку сессии сейчас</td>
          <td><code>zellij action save-session</code></td>
      </tr>
      <tr>
          <td>Остановить / удалить</td>
          <td><code>zellij kill-session work</code> / <code>zellij delete-session work</code></td>
      </tr>
      <tr>
          <td>Менеджер сессий</td>
          <td><code>Ctrl+o</code> → <code>w</code></td>
      </tr>
      <tr>
          <td>Заблокировать клавиши zellij</td>
          <td><code>Ctrl+g</code></td>
      </tr>
      <tr>
          <td>Новая панель / перейти</td>
          <td><code>Alt+n</code> / <code>Alt+стрелки</code></td>
      </tr>
      <tr>
          <td>Плавающие панели</td>
          <td><code>Alt+f</code></td>
      </tr>
      <tr>
          <td>Вкладки</td>
          <td><code>Ctrl+t</code>, затем <code>n</code>, <code>r</code>, <code>x</code>, <code>1…9</code></td>
      </tr>
      <tr>
          <td>Поиск по выводу</td>
          <td><code>Ctrl+s</code> → <code>s</code></td>
      </tr>
  </tbody>
</table>
]]></content:encoded>
    </item>
  </channel>
</rss>
