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



Для удобства это программу можно держать всегда запущенной: в отдельном окне терминала, в его вкладках или на каком-нибудь рабочем столе. Но есть ещё одно решение: запускать его на фиксированном VT, на который можно в любой момент переключиться. Преимущество такого подхода заключается в чистом окружении и независимости от иксов/терминала.


Это возможно (и правильно) сделать с помощью системы инициализации, потому что мы фактичесски хотим получить специальный getty-подобный сервис для htop.


Как запускаются VT1, ..., VT6?


agetty — это такая программа, которая открывает порт tty, выдаёт prompt для аутентификации и передаёт последующее управление другой программе — login.


$ which agetty login | xargs ls -l
-rwxr-xr-x 1 root root 44104 Sep 29 05:21 /usr/bin/agetty
-rwxr-xr-x 1 root root 35968 Sep 29 05:21 /usr/bin/login

Традиционные системы инициализации Linux конфигурируются на запуск фиксированного количества agetty при загрузке. В большинстве случаев рождаются шесть инстансов для шести VT: от tty1 до tty6 соответственно. В systemd используется другой подход.


  • Первый — динамический. Инстанс сервиса getty@.service запускается по требованию. То есть только в том случае, если нам нужен какой-то конкретный VT. За это отвечает logind, который при переключении на ttyN запускает сервис autovt@ttyN.service, который является симлинком на getty@.service. Такая логика работает для tty2-tty6.
  • Второй — статический. Конкретный инстанс сервиса getty@.service втягивается автоматически через getty.target, что даёт нам всегда запущенный getty на tty1.

systemctl cat getty@.service покажет содержимое этого сервиса. Рассматривать подробно мы его не собираемся, поскольку это для нас не столь важно.


Соответственно, если предположить, что у нас есть некий htop@.service, то и добавить его в автозагрузку можно двумя путями: либо сделать симлинк под именем autovt@ttyN.service — тогда при переключении на выбранный VT htop будет запускаться вместо getty, либо отключить getty@ttyN.service и вместо него включить htop@ttyN.service — это даёт нам всегда запущенный htop на фиксированном VT.


Пишем собственный getty-подобный юнит


Теперь переходим в /etc/systemd/system — одна из директорий, где располагаются юниты, — и создаём собственный сервис:


$ "$EDITOR" htop@.service

Наличие суффикса (@) означает, что стартует не сам по себе сервис, а один из его инстансов. А суффикс передаётся в него парамметром (%i и %I).


Выше уже было отмечено, что содержимое getty@.service для нас не столь важно. Всё так, потому что его можно заинклюдить в наш сервис:


.include /usr/lib/systemd/system/getty@.service

Если учесть, что наш сервис getty-подобный, то эта конструкция избавляет нас от лишнего копирования кода.


Секция Unit


Здесь описываются общие парамметры, применимые к любому типу юнита.


[Unit]
Description=htop on %I
Documentation=man:htop(1)

Здесь всё прозрачно: директива Description задаёт краткое описание юнита, а Documentation путь к документации. %I — имя инстанса. Важно заметить, что обе переменные, задающие имя инстанса, различны по значению: %I — тоже самое, что и %i, но она не экранирует escape-последовательности.


Секция Service


Эта секция задаёт конфигурацию конкретно сервиса. Иными словами, описывает способ запуска процесса.


[Service]
Environment=
Environment=TERM=linux HOME=/root
ExecStart=
ExecStart=/usr/bin/htop
StandardInput=tty-fail
StandardOutput=tty

Необходимые унаследованные значения директив мы оставим в покое, а некоторые нам необходимо сбросить (задаём для них пустое значение) и определить самостоятельно. К таковым относятся Environment — задание переменных, и ExecStart, — собственно, запуск процесса.


StandardInput=tty-fail
StandardOutput=tty

— это указание systemd запускать htop подсоединённым напрямую к терминалу.


Можно, кстати, добавить не мгновенный запуск, а с ожиданием ввода. Для этого создаём простой скрипт на баше:


#!/bin/bash

echo "Press a key to launch $(basename "$1")"
read
exec "$@"

Всё что он делает — ожидает ввода и запускает какую-то программу (которой будет являться htop в нашем случае). Помещаем куда угодно, называем как угодно, делаем его исполняемым (chmod +x) и правим ExecStart в нашем сервисе:


ExecStart=/etc/systemd/scripts/run_wait /usr/bin/htop

Ограничиваем права


Если необходимо наложить какие-либо ограничения на права, то сделать это, конечно же, нужно. Для этого мы создадим ещё один сервис и ещё один скрипт, теперь — htop_secure@.service и run_wait_su. Их мы переконфигурируем так, чтобы htop запускался с правами конкретного пользователя и конкретной группы, а также требовал пароль администратора.


Итак, создаём новый сервис и новый скрипт на основе двух предыдущих:


$ cd /etc/systemd
$ cp system/htop@.service system/htop_secure@.service
$ cp scripts/run_wait scripts/run_wait_su

И редактируем каждый из них. Для сервиса в секции Service изменяем значение Environment и задаём имя пользователя с его группой:


User=kalterfive
Group=users
Environment=TERM=linux

А в скрипте обращаемся к su(1):


#!/bin/bash
echo "Press a key to launch $(basename "$1")"
read
exec su -c "$@"

Установка сервиса


Теперь наш сервис готов, осталось только добавить его в автозагрузку:


$ systemctl daemon-reload
$ systemctl disable getty@tty2.service
$ systemctl enable htop@tty2.service

Первая команда обновляет менеджер конфигурации systemd, а вторая создаёт симлинк на наш сервис в getty.target.wants.


Заключение


Теперь перезагружаемся (либо вручную убиваем getty@ и включаем htop@ для инстанса tty2), переключаемся на второй VT и наблюдаем успешно запущенный htop. Продемонстрированный трюк задевает лишь малую часть systemd, как системы инициализации, от всего простора его возможностей, как универсального plumbing layer-а — набора программ для решения совершенно разных задач. Успехов!


Поделиться с друзьями
-->

Комментарии (27)


  1. imwode
    02.07.2016 20:25
    +1

    Класс, работает. Я даже немного понял, что тут происходит
    Ничего только не понял про безопасность — зачем делать копию скрипта htop_secure и какой смысл в прописывании юзера? И должен ли это быть один и тот же юзер в обоих сервисах?
    tty2 теперь не требует логина, т.е. там сразу висит htop. Это очень удобно, но при желании может быть использовано, чтобы основательно подгадить хозяину машины


    1. kalterfive
      02.07.2016 21:40

      <...> при желании может быть использовано, чтобы основательно подгадить хозяину машины

      Да, в том-то и дело. Для этого мы создаём ещё один сервис и ещё один скрипт, теперь — htop_secure@.service и run_wait_su. Их мы конфигурируем так, чтобы htop запускался с правами конкретного пользователя (в сервисе в секции Service) и требовал пароль (использование в скрипте exec su -c "$@" вместо exec "$@").


      Вообще этот момент был написан на скорую руку, и, действительно, он не сопровождался никакими пояснениями действий. Теперь слегка дополнил статью, так что спасибо =)


      1. icoz
        03.07.2016 17:44

        А после переключения с этой консоли как её погасить? Ну чтобы при повторном переключении на неё заново спросило пароль?


        1. kalterfive
          03.07.2016 18:47

          Выйти из htop (F10 или q).


    1. kalterfive
      02.07.2016 21:48

      И должен ли это быть один и тот же юзер в обоих сервисах?

      Нет, мы создаём ещё один независимый сервис и ещё один независимый скрипт. htop@ никак не связан с htop_secure@, а run_wait никак не связан с run_wait_su. Вторая связка запускает софтину с указанными правами и только после корректного ввода рутового пароля, а первая просто запускает её же, но от рута и без лишних действий.


  1. ploop
    03.07.2016 01:50

    Вообще сам принцип запуска процессов подобным образом очень интересен, особенно в плане логина. А вот конкретно htop держать всегда запущенным, думаю, смысла нет. Переключиться на свободный tty и вбить команду в 4 символа — дело нескольких секунд.


    1. Halt
      03.07.2016 08:17
      +1

      Есть сценарий, при котором какая-то рыжая морда процесс внезапно отожрал тонну памяти, начался лютый swap out всего что только есть, но OOM killer еще не прибил негодяя. В таком случае, вы тупо не сможете снять задачу из нового терминала/процесса, ибо вы его не дождетесь. Переключиться же на физический терминал с уже запущенным htop будет быстрее.

      Есть конечно еще magic sysrq key, но он не всегда включен и часто oom killer выбирает не тот процесс (у меня по крайней мере).


      1. ploop
        04.07.2016 00:16

        Частенько сталкивался с такими «негодяями», особенно при нехватке памяти (запущена виртуалка, всё впритык, и вдруг что-то тяжёлое запустил(ось)). Даже при полном зависоне интерфейса на терминал переключается без проблем. Ну, с лагами конечно, но так, чтоб критично, ни разу не было.


  1. grossws
    03.07.2016 04:18
    +1

    Каждый, кто работает за машиной с линуксом на борту, хоть раз использовал её: будь то поиск процесса (и его последующее убийство)

    Не стоит говорить за всех. Это даже не считая того, что использование для этого error-prone даже при подходе с выделением процесса (пробелом) и отправкой сигнала после. Если процесс умер, можно послать kill не тому. Подходы с ps+kill и pgrep/pkill куда более безопасны.


  1. Andrey_Perelygin
    03.07.2016 12:42

    А в чем проблема для подобной цели просто использовать screen без лишних плясок с бубном?


    1. kalterfive
      03.07.2016 14:03
      +1

      Преимущество такого подхода заключается в чистом окружении и независимости от иксов/терминала.

      Ну или от терминала-мультиплексора в случае с screen. Вообще каждый решает для себя насколько этот вариант лучше (или наоборот), цель статьи — раскрыть метод.


  1. ASMelnikov
    03.07.2016 17:29

    что за шрифт на скриншоте?


    1. kalterfive
      03.07.2016 17:29

      Terminus Re33.


  1. iqiaqqivik
    04.07.2016 12:24
    -1

    `$ "$EDITOR" htop@.service` ? sudo бы не помешал, а то намучаетесь сохранять изменения.


    1. kalterfive
      04.07.2016 12:27

      Зачем уточнять о повышении прав для работы с директорией, с которой никак иначе поработать не получится? =)


      1. iqiaqqivik
        05.07.2016 09:44

        Так можно далеко зайти: «зачем писать `$EDITOR`, если и так понятно, что `cowsay` здесь не подходит» и «зачем писать `htop@.service`, если и так ясно, что за файл мы собираемся редактировать.

        Доллар впереди намекает на то, что текущий шелл — не рутовый. Хорошим тоном в howto считается указывать команды, которые можно скопипастить и выполнить, но вы, конечно, можете продолжать писать половину и считать, что умные разберутся.


        1. kalterfive
          05.07.2016 10:23

          На самом деле всё нормально. Традиционно принято писать # "$EDITOR" htop@.service, но я не стал из-за подсветки синтаксиса — выглядит как комментарий. sudo же писать не стану, потому что, действительно, 1) "умные разберуться", и 2) лишняя сущность мешает восприятию команды "как есть"; тут, кстати, везде sudo нужен.


          В общем, это будет лишним. Как и, например, лишним будет объяснение механизма контейнеризации, а потом вызов machinectl shell.


          1. iqiaqqivik
            08.07.2016 08:39
            -1

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

            Впрочем, заметка ваша, чего я тут со своими нормами, которые сообщество выработало за десятилетия. Вам-то виднее, конечно.


    1. vessd
      05.07.2016 13:04
      +1

      У меня есть предложение получше: `$ sudoedit htop@.service`
      И переменная "$EDITOR" пригодится, и доллар на решётку меня не надо.


  1. Nexen2
    04.07.2016 12:24
    +1

    Странны подход. Всегда делал так — заводил отдельного пользователя, в качестве шелла прописывал ему нужную программу, в данном случае htop.
    При логине на данного пользователя сразу видишь htop, нажимаешь q — вываливаешься. Можно этому пользователю прописать все нужные права, сделать еще чего-то кроме в рамках указанного ему шелла сложно.


    1. kalterfive
      04.07.2016 12:42

      Да, интересное решение. Но оно имеет место быть ровно до того момента, пока не придётся писать надстройку в виде скрипта для имитирования опций вроде RootDirectory= или EnvironmentFile=.


      // да и что-то отличное от шеллов в /etc/shells выглядит как-то противоречиво по отношению к семантике этого файла.


      1. mayorovp
        05.07.2016 09:38

        Зачем /etc/shells? Оболочка для конкретного пользователя разве не в /etc/passwd хранится?


        1. kalterfive
          05.07.2016 10:28

          В нём. Но /sbin/login не будет работать без наличия шелла в /etc/shells.


          1. mayorovp
            05.07.2016 12:36

            У меня почему-то работает. Старая версия?..


            1. kalterfive
              05.07.2016 14:57

              Другая настройка PAM. В арче по дефолту login требует pam_shells.so, и правильно делает.


      1. astraleuro
        11.11.2016 15:52

        Мне нравится Ваша идея (хотя хотелось бы конечно чтобы путешествия во времени были возможными)
        Для себя представляю пространство-время в виде лифта, где кабина — это все пространство во вселенной, а шахта — это временная ось, всё пространство движется во времени и существует только в данный момент, теоретически, конечно, можно отправиться в прошлое (выбраться из кабины и поползти по шахте вниз), но пространства там (тогда?) уже не существует.


        1. kalterfive
          05.07.2016 10:29

          А /sbin/login, соответственно, не заработает.