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-подобный, то эта конструкция избавляет нас от лишнего копирования кода.
Секция User
Здесь описываются общие парамметры, применимые к любому типу юнита.
[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
Ограничиваем права
Если необходимо наложить какие-либо ограничения на права, то сделать это, конечно же, нужно.
Создаём копии нашего сервиса и скрипта к нему:
$ 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-а — набора программ для решения совершенно разных задач. Успехов!
Автор: kalterfive