Что ж... В нынешнее, немного придурковатое (хм... а когда было иным?) время "лихорадки ИИшницы" писать о чем-то приземленном, без верхнеуровневых CI/CD абстракций, про Solaris, да еще писать руками не используя это самое ИИ не выглядит чем-то рациональным и востребованным. Скорее чем-то вызывающим и маргинальным.
С другой стороны, Brain-Made скоро будет стоить как космический звездолет, так что почему бы и да? Пишем.
Задача:
Есть "коробка" с работающей 24/7 OmniOS. Со временем в "коробке" накопилось столько всего важного, что стоимость этой информации превысила стоимость самой "коробки". Наиболее вероятная угроза информации - это фатальный сбой файловой системы при сбоях электропитания. Нужно научить OmniOS "слушать" сообщения от ИБП и самостоятельно выключаться пока ИБП выдает напряжение.
Network UPS Tools (NUT)
Популярное и достаточно известное решение существующее с 2004 года. NUT - это своего рода FFMPEG в через-чур разнообразной вселенной ИБП, где каждый производитель считает своей святой обязанностью сделать свой, собственный протокол управления. То, что это порождает вселенский бардак производители не думают.
На Хабре много статей связанных с NUT. Поэтому не вижу смысла сильно углубляться в его описание. Проект живет тут: https://networkupstools.org. Думаю этого пока достаточно.
Команда проекта поддерживает Solaris, судя по всему с самого начала и по сей день. Стало быть NUT и будем устанавливать, попутно разбирая некоторые тонкости, решения для которых были либо вычислены, либо собранны из разных источников в сети.
Компиляция
NUT в виде подготовленного установочного пакета имеется практически в любой операционной системе. Однако, для OmniOS версии r151058 его не сделали. Придется собирать из исходников.
На старт!
Установка джентльменского набора разработчика (если не сделано):
pfexec pkg install developer/build-essential system/header
Далее, смотрим на интерфейс, которым ИБП мониторится/управляется. Если это COM-порт, либо сетевая плата Ethernet и управление по SNMP, то в принципе минимального джентльменского набора нам должно хватить. Если ИБП управляется по USB, Modbus или как-то иначе, то нужно чтобы в ОС были dev- и runtime- библиотеки них. В моем случае, ИБП - это APC Back-UPS XS 650CI, мониторится/управляется по USB. Нужно до-установить одну библиотеку:
pfexec pkg install libusb-1
Начинаем обратный отсчет! 9... 8...
Скачиваем исходный код NUT одним из способов со страницы https://networkupstools.org/download.html. На момент написания этого текста новейшая версия - nut-2.8.5. Загруженный архив распаковываем.
omnios:~$ mkdir nut_build && cd nut_build omnios:~/nut_build$ wget https://www.networkupstools.org/source/2.8/nut-2.8.5.tar.gz -- skipped -- omnios:~/nut_build$ tar -xzvf nut-2.8.5.tar.gz -- skipped -- omnios:~/nut_build$ cd nut-2.8.5 omnios:~/nut_build/nut-2.8.5$
Исходный код NUT распространяется как GNU Coding Standards проект. С уже привычной многим последовательностью действий configure, make, make install.
Для успешной компиляции на OmniOS нужно учесть некоторые нюансы. Один из них учтен в первом шаге - библиотека libusb-1. C ней же связан второй нюанс.
Скрипт configure для определения параметров компиляции использует утилиту pkg-config, которая входит в базовый набор developer/build-essential . Маинтейнером libusb-1 в OmniOS является сообщество OmniOS Community Edition (OOCE), и все пакеты софта из этого источника устанавливаются в каталог /opt/ooce . Для pkg-config это является проблемой, потому что он будет искать нужные ему файлы в одном из каталогов /usr/lib/pkgconfig, /usr/share/pkgconfig, /usr/local/lib/pkgconfig и /usr/local/share/pkgconfig.
Чтобы pkg-config выдал данные про libusb-1 в переменную окружения придется добавить еще один путь:PKG_CONFIG_PATH=/opt/ooce/lib/amd64/pkgconfig
Далее, забегая вперед, в архиве исходного кода версии nut-2.8.5, документация, т.е. man-страницы имеются только в формате .aadoc, и чтобы они стали именно файлами man-страниц нужны еще дополнительные утилиты, которые лично мне ставить очень неохота. Поэтому подготовку и установку man-страниц для NUT я просто отключил аргументом –-with-docs=no
Далее учитываем, что:
NUT должен обязательно работать с USB.
–-with-usbУстановиться в каталог
/opt/nutв соответствии с принципом KYSTY (Keep Your !{Shit|Stuff|Software} To Yourself) и по аналогии с софтом от OOCE.–-prefix=/opt/nutДолжны быть сформированы файлы для SMF, чтобы запускать процессы NUT в качестве сервисов в системе.
–-with-solaris-smf=yes
Все вышесказанное трансформируем в аргументы скрипта и запускаем configure :
PKG_CONFIG_PATH=/opt/ooce/lib/amd64/pkgconfig ./configure --with-usb --prefix=/opt/nut --with-solaris-smf=yes --with-docs=no
Работа скрипта закончится выводом информации о будущих настройках скомпилированного ПО, из которой мы узнаем, что наш NUT будет иметь драйвера только для Serial, USB и SNMP, что он умеет в SSL, что пользователь от имени которого будут запускаться процессы - это nobody , что найден интерпретатор Python и для него будет установлен модуль PyNUT и т.д.
NUT Configuration summary: ========================== * configured version: 2.8.5 release * Documentation website base URL: https://www.networkupstools.org/historic/v2.8.5 * Enable support for parallelization of some tools: yes * enable SSL support: yes (OpenSSL) * enable SSL client certificate validation: no * enable Avahi support: no * enable libwrap (tcp-wrappers) support: yes * enable libltdl (Libtool dlopen abstraction) support: no * use default Python interpreter: * use specific Python2 interpreter: * use specific Python3 interpreter: /usr/bin/python3.13 * requested to spell-check the documentation: no * requested to build and install documentation: 'no' => '' * would build specific documentation format(s): no * can install pre-built documentation (release/dist tarball): yes * would generate ChangeLog* file formats: text:no adoc:no html-single:no html-chunked:no pdf:no starting from 'v2.6.0' to 'HEAD' * build and install the development files: no * dynamically link to NUT common libraries instead of copying needed bits into each binary: no * consider basic SMF support: yes * consider basic systemd support: no * build with tighter systemd support: no * build C++11 codebase (client library, etc.): yes * build C++ tests with CPPUNIT: no * User to run as: nobody * Group of user to own state files: nobody * enable SSL support in C++ client library: no (OpenSSL) * build serial drivers: yes * build USB drivers: yes (libusb-1.0) * build neon based XML driver: no * build Powerman PDU client driver: no * build Modbus drivers: no * build IPMI driver: no * build GPIO driver (library version not detected): no * build Mac OS X meta-driver: no * build UPower driver and nut-scanner support: yes * build i2c based drivers: no * build SNMP drivers: yes * build SNMP drivers with statically linked lib(net)snmp: no * build nut-scanner: no * build CGI programs: no * install NUT-Monitor desktop application: no * install PyNUT binding module: yes * build and install the nutconf tool (experimental, may lack support for recent NUT options): yes * build and install the deliver the libnutconf library and headers (experimental): no NUT Paths: ---------- * Default installation prefix path: /opt/nut * State file path: /var/state/ups * Unprivileged PID file path: /var/state/ups * Privileged PID file path: /var/run * Driver program path: /opt/nut/bin * CGI program path: /opt/nut/cgi-bin * HTML file path: /opt/nut/html * Config file path: /opt/nut/etc/nut * Config file examples path: /opt/nut/etc/nut * Data file path: /opt/nut/share * Man page path: /opt/nut/share/man * Tool program path: /opt/nut/bin * System program path: /opt/nut/sbin * System library path: /opt/nut/lib * System exec-library path: /opt/nut/libexec
Марш!
Теперь нужно командой make запустить компиляцию. Смотрим на бегущие строки и убеждаемся, что процесс завершился успешно без сообщений Error
Если make завершился без ошибок, то это означает, что NUT собрался и готов к установке и использованию. В классическом варианте GNU Coding Standards, следующей командой должен быть make install , который скопирует файлы по рабочим каталогам. Однако при таком сценарии теряется информация об установленном софте. И через некоторое время начинается игра Что? Где? Когда? в сисадмин варианте Что, где лежит и когда я это устанавливал?
В любой современной системе есть свой software package manager. В OmniOS он тоже есть, даже не один, но мы сфокусируемся на Image Packaging System (IPS).
Image Packaging System (IPS)
Все "нормальные" пакетные менеджеры устроены примерно одинаково. Есть репозиторий, в нем лежит архив, содержащий файлы устанавливаемого софта и манифест, куда их положить. Скачиваем архив, читаем манифест, копируем файлы. Готово.
В IPS архивов нет. Чего-то подобного файлам .deb, .pkg, .rpm в IPS отсутствует. При этом репозитории и манифесты есть. Как это работает?
Репозиторий
В основу репозитория берем любой произвольный каталог. Назначаем на эту роль например: /TANK/packages. Всего две команды:
pkgrepo create /TANK/packages pkgrepo set -s /TANK/packages publisher/prefix=token.paul
... преобразуют обычный каталог в репозиторий. Аргумент publisher/prefix задает имя издателя. Издателей (Publisher) внутри одного репозитория может быть несколько. Нам пока достаточно одного - меня :)
Подключить и отключить репозитоий можно вот таким образом:
# # Подключаем $ pfexec pkg set-publisher -g /TANK/packages token.paul # Смотрим, что подключено # $ pkg publisher PUBLISHER TYPE STATUS P LOCATION extra.omnios origin online F https://pkg.omnios.org/r151058/extra/ omnios origin online F https://pkg.omnios.org/r151058/core/ token.paul origin online F file:///TANK/packages/ # # Отключаем $ pfexec pkg unset-publisher token.paul
После подключения OmniOS будет видеть, какие пакеты опубликованы, сможет их устанавливать, обновлять и удалять.
Публикация пакета в репозиторий
Вернемся в каталог с nut-2.8.5 и запустим make install. Но так, чтобы файлы легли не на боевую файловую систему, а во временный каталог-прототип. В GNU CS предусмотрена специальная команда make install DESTDIR=...
make install DESTDIR=/tmp/nut-2.8.5
Теперь в /tmp/nut-2.8.5 находятся все файлы NUT, размещенные относительно /tmp/nut-2.8.5 как относительно корня файловой системы, как это предусмотрено разработчиками. Главное не запускать make install DESTDIR=... из под root-a, меньше риска раскидать файлы и потом их потерять.
Формируем манифест
В манифесте IPS вручную определяем 4 переменные: pkg.fmri, pkg.summary, pkg.description и variant.arch, примерно вот-так:
set name=pkg.fmri value=pkg://token.paul/power/nut@2.8.5,1.0-0 set name=pkg.summary value="Network UPS Tools" set name=pkg.description value="Network UPS Tools (NUT) is a client/server monitoring system that allows computers to share uninterruptible power supply (UPS) and power distribution unit (PDU) hardware. Clients access the hardware through the server, and are notified whenever the power status changes." set name=variant.arch value=i386
Записываем это все в файл. Путь будет nut_manifest.p5m
На самом деле, если полностью соблюдать все правила оформления, то заданных переменных должно быть немного больше. При этом в минимальных настройках достаточно определения pkg.fmri, указывающей путь к пакету в репозитории. Более подробно расписано тут: https://man.omnios.org/man7/pkg и тут: https://man.omnios.org/man1/pkgsend
Немного подробностей про fmri все же не помешает. pkg.fmri (Fault Management Resource Identifier), уникальный идентификатор образа в репозитории IPS. В этом примере он вот такой: pkg://token.paul/power/nut@2.8.5,1.0-0
После указания схемы (pkg://) следует имя издателя (token.paul), за которым идет раздел в репозитории (power), название пакета ПО (nut), а так же версии самого ПО (2.8.5) и публикации (1.0-0)
Теперь, берем nut_manifest.p5m и дополняем его содержимое выводом команды pkgsend generate, которая прошерстит наш каталог-прототип и сформирует нужные строки манифеста для каждого файла, который находится в /tmp/nut-2.8.5
pkgsend generate /tmp/nut-2.8.5 >> nut_manifest.p5m
Не перепутайте, >> добавляет, > замещает!
Далее, на основе полученного манифеста и содержимого каталога /tmp/nut-2.8.5 определяем зависимости:
pkgdepend generate -m nut_manifest.p5m /tmp/nut-2.8.5 > nut_manifest.deps pkgdepend resolve -m nut_manifest.deps
Вывод pkgdepend generate сохраняем в другой файл, пусть будет nut_manifest.deps, а его в свою очередь скармливаем pkgdepend resolve
Результатом выполнения второй команды, pkgdepend resolve, будет файл с расширением .res, в нашем случае nut_manifest.deps.res и это финализированный манифест.
Нюанс манифестов IPS в OmniOS
Это задокументировано, в описании утилиты pkgsend написано:
In the output manifest,
fileanddiractions have owner set to root and group set to bin.
Владельцем всех файлов и директорий будет объявлен root и группа bin. Т.е. реальные права уже существующих на файловой системе файлов и каталогов pkgsend не учитывает. Во время установки пакета из репозитория это может привести к ошибке с выводом вот такого сообщения:
pkg install: The requested change to the system attempts to install multiple actions for dir 'opt' with conflicting attributes: 1 package delivers 'dir group=bin mode=0755 owner=root path=opt': pkg://token.paul/power/nut@2.8.0,1.0-0:20260803T051811Z 1 package delivers 'dir group=sys mode=0755 owner=root path=opt': pkg://omnios/SUNWcs@0.5.11,5.11-151058.0:20260708T190738Z These packages cannot be installed together. Any non-conflicting subset of the above packages can be installed.
В нашем файле nut_manifest.deps.res таких конфликтных определений два: /opt и /usr. Для них группа была задана ранее, пакетом pkg://omnios/SUNWcs . Это и вызовет конфликт и провал установки.
Чтобы все исправить, в файле nut_manifest.deps.res текстовым редактором меняем group=bin на group=sys в двух строках, идущих сразу после строк написанных нами вручную, должно быть вот так:
... dir group=sys mode=0755 owner=root path=opt dir group=sys mode=0755 owner=root path=usr ...
Остальные определения в манифесте не трогаем, поскольку атрибуты вновь создаваемых файлов ни с чем не конфликтуют.
Публикация пакета
Итак, у нас есть репозиторий, манифест и установленная в каталог-прототип софтина. Полный набор необходимых для публикации компонентов собран.
Публикуемся! pfexec pkgsend -s /TANK/packages publish -d /tmp/nut-2.8.5 nut_manifest.deps.res
-s Каталог репозитория
-d Каталог-прототип
nut_manifest.deps.res Файл минифеста
$ pfexec pkgsend -s /TANK/packages publish -d /tmp/nut-2.8.5 nut_manifest.deps.res pkg://token.paul/power/nut@2.8.5,1.0-0:20260807T015107Z PUBLISHED $
Командой pkgrepo можно управлять уже имеющимися публикациями: получать списки, удалять, сравнивать, исправлять...
$ pkgrepo list -s /TANK/packages/ PUBLISHER NAME O VERSION token.paul power/nut 2.8.5-0:20260807T015107Z $
Если публикация прошла успешно, то каталог-прототип и развернутый архив исходного кода можно удалить... Но по опыту скажу, что делать это лучше после того как пакет будет хотя бы раз установлен и результат этой операции удовлетворил. В общем, не откладывайте пока напильник, он еще может пригодиться.
Вы дочитали до середины. Поздравляю!
Инсталляция NUT
Устанавливаем результат наших трудов в систему штатными средствами:
(Репозиторий должен быть подключен)
pfexec pkg install power/nut
Задача установки выполнена. Мы скомпилировали NUT-2.8.5, собрали и опубликовали package, и установили его как штатный пакет программного обеспечения.
Можно зачистить /tmp/nut-2.8.5 и каталог с исходниками.
Настройка NUT
Путь к файлам конфигурации задается при компиляции. Возвращаясь к информации скрипта configure это:
Config file path: /opt/nut/etc/nut
Config file examples path: /opt/nut/etc/nut
Там они и будут лежать после инсталляции. Все же, этот текст не про то как настроить NUT, а про то, как настроить NUT на Solaris-е. Поэтому глубоких подробности по архитектуре NUT не будет. Будут солярошные страдания. И здесь есть о чем написать.
USB device vs Solaris
Начнем с того, что подключим ИБП к "коробке" с OmniOS и посмотрим как на это отреагирует система. Подключили. Никаких сообщений в лог-файлах мы не найдем, как будто ничего не произошло.
Посмотрим, что скажет cfgadm. Он покажет все известные системе порты ввода-вывода и их состояние. В одном из USB-портов что-то есть...
$ cfgadm Ap_Id Type Receptacle Occupant Condition ... usb2/7 usb-input connected configured ok ...
Протокол обмена в USB стандартизирован, и производители оборудования в основном его строго соблюдают. Для нас сейчас важно, что написано в колонке Occupant. Если там configured, то система уже определила какой драйвер ядра будет работать с этим устройством. Еслиunconfigured, то драйвер не привязан. А наша задача сделать так, чтобы подключенный к системе ИБП всегда воспринимался как устройство ugen (USB Generic)
И теперь нам нужен вывод команды prtconf -Dv. Это дли-и-и-инющая простыня в которой нужно найти информацию, которую система прочитала из подключенного по USB устройства.
Вот интересующая нас инфа:
$ prtconf -v ... name='usb-product-name' type=string items=1 value='Back-UPS XS 650CI FW:892.R3 .I USB FW:R3 ' name='usb-vendor-name' type=string items=1 value='American Power Conversion' name='usb-serialno' type=string items=1 value='3B1542X10144 ' name='usb-raw-cfg-descriptors' type=byte items=34 value=09.02.22.00.01.01.00.e0.0c.09.04.00.00.01.03.00.00.00.09.21.10.01.21.01.22.18.04.07.05.81.03.06.00.0a name='usb-dev-descriptor' type=byte items=18 value=12.01.10.01.00.00.00.20.1d.05.02.00.06.01.03.01.02.01 name='usb-release' type=int items=1 value=00000110 name='usb-num-configs' type=int items=1 value=00000001 name='usb-revision-id' type=int items=1 value=00000106 name='usb-product-id' type=int items=1 value=00000002 name='usb-vendor-id' type=int items=1 value=0000051d name='compatible' type=string items=9 value='usb51d,2.106' + 'usb51d,2' + 'usbif51d,class3.0.0' + 'usbif51d,class3.0' + 'usbif51d,class3' + 'usbif,class3.0.0' + 'usbif,class3.0' + 'usbif,class3' + 'usb,device' name='reg' type=int items=1 value=00000007 name='assigned-address' type=int items=1 value=00000003 ...
value из блока compatible - usb51d,2.106, идентификатор для APC Back-UPS XS 650CI и других устройств с идентичным контроллером, который система "собирает" из считанных данных: Vendor ID 051d, Product ID 02 и Revision ID 0106. По этому значению OmniOS будет осуществить привязку драйвера.
add_drv -i '"usb51d,2.106"' -m '* 0666 root sys' ugen
Эта команда добавит в файл /etc/driver_aliases строку, опираясь на которую ядро выберет нужный нам драйвер, заодно сделает эту привязку перманентной.
N.B. Мы указали, что права на доступ к устройству должны быть у всех пользователей системы, а именно '* 0666 root sys'. Однако, после перезагрузки OmniOS с этими настройками оказалось, что dev-файлы принадлежат пользователю root как заказано, но имеют права 0600, т.е. читать/писать в это у устройство может только владелец.
Я так и не нашел способа, при котором права на файлы устройств установлись бы так, как планировалось, что повлияло на настройку самого NUT в дальнейшем.
Ах, да... Источник.
Все вышесказанное взято отсюда: https://networkupstools.org/docs/solaris-usb.html
В результате этих манипуляций в /dev/usb должен появиться каталог соответствующий подключенному USB-устройству, в конкретном случае - 51d.2.
$ ls /dev/usb 51d.2 hub0 hub1
Файл nut.conf
MODE=netserver
NUT будет работать в режиме сетевого сервера.
Файл ups.conf
user = root [apc_ups] driver = usbhid-ups port = auto desc = "APC Back-UPS" vendorid = 051d ignorelb
ups.conf описывает настройки NUT-драйвера. NUT-драйвер - это обычная программа, которая с одной стороны подключается физическому порту, а с другой образует IPC-сокет. Драйвер осуществляет перевод сигналов полученных из ИБП на "внутренний" язык NUT и транслирует их в IPC-сокет. Своеобразный толмач, переводчик совбеза ООН.
Выход из ситуации с правами на dev-файлы заключается в первой строке: user = root. По документации NUT программа-драйвер запускается от имени пользователя указанного во время компиляции, т.е. от имени nobody. Коли так вышло, что права на USB-устройство имеет исключительно root, то данная настройка заставит работать NUT-драйвер от имени указанного пользователя, т.е. root-а.
vendorid = 051d - Добавлен ради того, чтобы драйвер из разнообразия подключенного по USB оборудования сразу выбирал то, у которого идентификатор производителя APC (051d). Так же можно указать идентификатор модели (productid) и даже серийный номер. Подробности тут: https://networkupstools.org/historic/v2.8.5/docs/man/usbhid-ups.html
ignorelb - Добавлен после того, как выяснилось, то данный ИБП выставляет сигнал LOW_BATTERY когда заряд батареи составляет 97%. Меня интересовал немного другой сценарий, поэтому сигнал LOW_BATTERY игнорируется.
Файлы upsd.conf и upsd.users
Следующий уровень архитектуры NUT - это программа-сервер, или upsd. Эта программа подключается к IPC-сокету, созданному программой-драйвером, и выдает эти данные тем, кто подключится к upsd по сети.
Поскольку один инстанс NUT может опрашивать множество ИБП, то upsd может осуществлять консолидацию этих данных, и система контроля множества ИБП начинает работать по принципу "одно окно", как в МФЦ.
upsd.conf
# Listen on all IPv4 interfaces at port 3493 (default) LISTEN 0.0.0.0 3493
upsd.users
[admin] password = 123qwe actions = SET instcmds = ALL [upsmon] password = 123qwe upsmon secondary
Содержание этих файлов не вижу смысла объяснять, они тривиальны.
Файлы upsmon.conf, upssched.conf, upssched-cmd
Уровень программы мониторинга, следующий отдельный уровень в архитектуре NUT. Здесь задается поведение системы при возникновении событий, о которых ИБП сочтет необходимым сообщить. Именно upsmon подаст команду shutdown когда увидит, что upsd транслировал от ИБП сигнал LOW_BATTERY.
Ковыряясь в сети я наткнулся вот на это:
https://community.ipfire.org/t/nut-ups-howto-shut-down-client-after-2-min-on-battery/9096/6
На этой странице пользователь 'R R raffe' задался вопросом: как сделать так, чтобы при отключении линейного питания система продолжала бы работать от ИБП некоторое заданное время, а по прошествии оного выполняла graceful-shutdown. Мне этот сценарий понравился и я его позаимствовал.
Чтобы воплотить этот сценарий нужно во-первых игнорировать сигнал LOW_BATTERY (это делается в настройках драйвера), во во-вторых запрограммировать под это upsmon.
Файл upsmon.conf
RUN_AS_USER root MONITOR apc_ups@localhost 1 upsmon 123qwe secondary MINSUPPLIES 1 SHUTDOWNCMD "/usr/sbin/init 5" NOTIFYFLAG ONLINE SYSLOG+EXEC NOTIFYFLAG ONBATT SYSLOG+EXEC NOTIFYFLAG LOWBATT SYSLOG+EXEC NOTIFYFLAG REPLBATT SYSLOG+EXEC NOTIFYCMD /opt/nut/sbin/upssched
Вся "магия" в том, что для каждого возможного сигнала ИБП предусматривается запуск шедулера, входящего в комплект NUT. upssched запускается как дочерний процесс программы-монитора, а значит будет работать от имени пользователя под которым работает процесс upsmon. И это... nobody, который ничего кроме как пожаловаться в Спортлото syslog не может. Собственно, изначально upssched предназначался для нотификации (NOTIFYCMD). А по сценарию R R raffe команду на shutdown требуется вызвать как раз из upssched, что делает обоснованным появление директивы RUN_AS_USER root.
Файл upssched.conf
CMDSCRIPT /opt/nut/bin/upssched-cmd.my PIPEFN /var/run/nut/upssched.pipe LOCKFN /var/run/nut/upssched.lock AT ONBATT * START-TIMER shutdown_onbatt 240 AT ONBATT * EXECUTE info_onbatt AT ONLINE * CANCEL-TIMER shutdown_onbatt AT ONLINE * EXECUTE ups-back-on-power AT LOWBATT * EXECUTE shutdown_lowbatt AT REPLBATT * EXECUTE replace_batt
upsmon передает вupssched NOTIFYFLAG. Тот либо сразу запускает программу объявленую в CMDSCRIPT, либо ставит таймер, по истечении которого опять же запускает то, что объявлено в CMDSCRIPT. В CMDSCRIPT передается аргумент, прописанный для пришедшего сигнала.
Т.е. если в upssched пришло REPLBATT, то он немедленно запустит:/opt/nut/bin/upssched-cmd.my replace_batt
Файл upssched-cmd.my
#! /bin/sh # # This script should be called by upssched via the CMDSCRIPT directive. # # Here is a quick example to show how to handle a bunch of possible # timer names with the help of the case structure. # # This script may be replaced with another program without harm. # # The first argument passed to your CMDSCRIPT is the name of the timer # from your AT lines. case $1 in shutdown_onbatt) logger -p daemon.notice -t upsmon[upssched] "shutdown_onbatt): Triggering shutdown after 4 minutes on battery" /sbin/init 5 ;; shutdown_lowbatt) logger -p daemon.notice -t upsmon[upssched] "shutdown_lowbatt): Triggering shutdown when battery.charge.low is under 50%" /sbin/init 5 ;; info_onbatt) logger -p daemon.notice -t upsmon[upssched] "info_onbatt): Now on battery" ;; ups-back-on-power) logger -p daemon.notice -t upsmon[upssched] "ups-back-on-power): UPS back on power" ;; replace_batt) message="Quick self-test indicates battery requires replacement" logger -p daemon.notice -t upsmon[upssched] "replace_batt): $message" ;; *) logger -p daemon.notice -t upsmon[upssched] "*) = Unrecognized command: $1" ;; esac
В этом файле финал логики работы NUT в рамках описываемого сценария. В зависимости от полученного аргумента выполняется последовательность команд.
К сугубо солярашным особенностям нужно отнести аргументы команды logger.
В Solaris-е, чтобы сообщение отправленное в syslog попало в файл messages нужно указывать правильную пару facility.level. В противном случае сообщение отправится в /dev/null. Поведение syslogd регулируется файлом /etc/syslog.conf, там перечисляется какой тип сообщений куда отправлять. Поэтому -p daemon.notice в данном случае must have.
Еще в скрипте не используется общепринятый shutdown. В Solaris-е (как и в других unix-ах) предусматривается несколько возможностей "погасить" систему. init 5 это gaceful-shutdown, но без сообщений wall и задержек по времени.
Взлетаем!
NUT установлен и настроен. Теперь его нужно запустить и попросить OmniOS, чтобы он автоматически запускался при старте.
NUT так устроен, что для его нормальной работы требуется строгая последовательность запуска его компонентов. Драйвер - Сервер - Монитор. И никак по-другому. Что в Linux, что в Solaris задача не из самых тривиальных.
Та работа, которую в Linux-системах выполняет systemd, в macOS launchd, в Solaris-е возложена на фреймвокр Service Management Facility (SMF), в котором какую-то основную программу выделить очень проблематично. SMF - это набор утилит для решения аналогичного спектра задач.
При компиляции NUT мы указывали ключик –-with-solaris-smf=yes. Следовательно где-то должны быть файлы, на подобии .service, но для SMF. После установки NUT они появятся в каталоге /opt/nut/share/solaris-smf/.
$ ls -l /opt/nut/share/solaris-smf/ total 2 drwxr-xr-x 2 root bin 8 авг. 5 13:36 manifest drwxr-xr-x 2 root bin 5 авг. 5 13:36 method
SMF оперирует двумя типами файлов manifest и method. Манифесты отвечают на вопрос "Что запускать?", а методы на вопрос "Как запускать?". Нам сейчас нужны манифесты, это файлы в формате .xml
$ ls -l /opt/nut/share/solaris-smf/manifest/ total 17 -rw-r--r-- 1 root bin 6428 авг. 5 13:36 nut-driver-enumerator.xml -rw-r--r-- 1 root bin 4716 авг. 5 13:36 nut-driver.xml -rw-r--r-- 1 root bin 3195 авг. 5 13:36 nut-logger.xml -rw-r--r-- 1 root bin 3420 авг. 5 13:36 nut-monitor.xml -rw-r--r-- 1 root bin 4879 авг. 5 13:36 nut-server.xml -rw-r--r-- 1 root bin 3668 авг. 5 13:36 nut.xml
Для того, чтобы вот эти файлы SMF начал воспринимать и обрабатывать, их можно поместить в каталоги /lib/svc/manifest/ и /lib/svc/method/, что не очень корректно с точки зрения "соблюдения чистоты системы", а для NUT перемещение этих файлов куда-то еще - вообще фатально.
Второй вариант - импортирование манифестов в SMF. В этом случае файлы остаются там, куда их изначально положили, но SMF будет о них знать, учитывать и работать с ними.
В общем случае, нужно вызывать утилиту svccfg c директивой import указывая следующим аргументом файл манифеста. Примерно так: svccfg import /opt/nut/share/solaris-smf/manifest/nut.xml
Однако, в отличии от версии nut-2.8.0, импортирование манифестов для версии 2.8.5 по одиночке, файл за файлом, у меня приводило к ошибкам импорта и неработоспособности сервисов NUT в конечном итоге.
Удачно сделать импорт получилось только сразу для всех манифестов, одной командой:svccfg import /opt/nut/share/solaris-smf/manifest/*.xml
При этом svccfg все равно выдал предупреждение, тем не менее все сервисы расставил как положено. В общем, сразу после импорта svcs должен показать вот такую картину:
$ svcs -a | grep nut disabled - svc:/system/power/nut-driver-enumerator:default disabled - svc:/system/power/nut-driver-enumerator:daemon disabled - svc:/system/power/nut-server:default disabled - svc:/system/power/nut-monitor:default disabled - svc:/system/power/nut-logger:default disabled - svc:/system/power/nut:default
Как отмечалось ранее, первым нужно запустить драйвер. Но сервиса с таким именем нет только файл nut-driver.xml, импорт которого к появлению сервиса не привел.
Это связано с тем, что прежде чем запустить программу-драйвер, NUT должен на основании файла ups.conf определить какой конкретно драйвер мы запросили (драйверов больше 50-ти). Для этой задачи у него имеется скрипт nut-driver-enumerator.sh. Именно с него все начинается.
nut-driver-enumerator:default и nut-driver-enumerator:daemon делают одно и то же действие. Разница в том, то nut-driver-enumerator:default отработает один раз, т.е. прочитает ups.conf, запустит обозначенную там программу и завершится, а nut-driver-enumerator:daemon останется в памяти и будет периодически читать ups.conf запуская и останавливая nut-драйвера. Два эти сервиса входят в exclusive-группу, т.е. не могут работать одновременно.
Стало быть, включаем nut-driver-enumerator:default, поскольку у меня один ИБП, а наполнение файла ups.conf не будет часто меняться, и в динамике поведения NUT, в моем случае, профита ровно ноль.
Все сервисы включаются сервис командой svcadm enable ...
svcadm enable nut-driver-enumerator:default
Если в конфигурационных файлах ups.conf и upsd.conf все написано верно, а права на каталоги и устройства соответствуют, то NUT должен запустить по очереди и драйвер и сервер upsd. Проверяем:
$ svcs -a | grep nut disabled 12:11:57 svc:/system/power/nut-logger:default disabled 12:11:58 svc:/system/power/nut:default disabled 12:14:51 svc:/system/power/nut-monitor:default disabled 12:12:49 svc:/system/power/nut-driver-enumerator:daemon online 12:13:30 svc:/system/power/nut-driver-enumerator:default online 12:14:07 svc:/system/power/nut-driver:apc_ups online 12:14:24 svc:/system/power/nut-server:default
svc:/system/power/nut-driver:apc_ups - вот сервис, соответствующий секции apc_ups в файле ups.conf.
Этот сервис был автоматически сконфигурирован, добавлен в SMF и активирован скриптом nut-driver-enumerator.sh Так же автоматически был активирован nut-server.
Впрочем такой фокус, с автоматическим запуском всей цепочки процессов, можно наблюдать только когда сервис NUT запускается в первый раз, т.е. когда сервиса nut-driver:<ups name> еще нет.
В Solaris-е это связано с тем, что сервис отключенный администратором с помощью svcadm disable никогда не будет поднят автоматически. Если отключили вручную, то и включить тоже извольте явочным порядком.
Если что-то пойдет не так, и вместо online мы видим maintenance, то командой
svcs -vx svc:/... определяем проблемы, чиним их и снова пытаемся запускать сервис пока он не начнет работать как положено.
Последний ингредиент - монитор. Включаем: svcadm enable nut-monitor
И вот, в конце концов мы должны увидеть целевую картину:
$ svcs -a | grep nut disabled 12:11:57 svc:/system/power/nut-logger:default disabled 12:11:58 svc:/system/power/nut:default disabled 12:12:49 svc:/system/power/nut-driver-enumerator:daemon online 12:13:30 svc:/system/power/nut-driver-enumerator:default online 12:14:07 svc:/system/power/nut-driver:apc_ups online 12:14:24 svc:/system/power/nut-server:default online 12:14:51 svc:/system/power/nut-monitor:default
Сервисам svc:/system/power/nut:default и svc:/system/power/nut-logger:default я применения не нашел.
nut:default по вроде как должен либо обновлять состояния всех сервисов NUT либо всех отключать. Для SMF достаточно, что сервис был в состоянии online до ребута, чтобы запуститься снова. svcadm enable nut просто переводит его в режим online ничего полезного не делая, а svcadm disable nut:default отключает только его самого, а не всю цепочку сервер-драйвер.
nut-logger пишет все сообщения от ИБП в файл. У меня в этом необходимости нет.
Финальная проверка
Отключаем ИБП из розетки и смотрим на реакцию. Согласно запрограммированному сценарию сразу после перехода на питание от батареи в syslog должно поступить сообщение Now on battery и запуститься таймер на 240 секунд, по истечении которых должен произойти graceful-shutdown. При возобновлении линейного питания, до истечения времени таймера, shutdown системы должен быть отменен.
Но прежде чем задействовать тяжелую артиллерию, убедимся, что ИБП хоть что-то сообщает. Для этого задействуем утилиту NUT - upsc.
$ /opt/nut/bin/upsc apc_ups battery.charge: 100 battery.charge.low: 50 battery.charge.warning: 50 battery.date: 2001/09/25 battery.mfr.date: 2015/10/16 battery.runtime: 17600 battery.runtime.low: 120 battery.type: PbAc battery.voltage: 13.6 battery.voltage.nominal: 12.0 device.mfr: American Power Conversion device.model: Back-UPS XS 650CI device.serial: 3B1542X10144 device.type: ups driver.debug: 0 driver.flag.allow_killpower: 0 driver.flag.ignorelb: enabled driver.name: usbhid-ups driver.parameter.interrupt_pipe_no_events_tolerance: -1 driver.parameter.override.battery.charge.low: 50 driver.parameter.pollfreq: 30 driver.parameter.pollinterval: 2 driver.parameter.port: auto driver.parameter.synchronous: auto driver.parameter.vendorid: 051d driver.state: quiet driver.version: 2.8.5 driver.version.data: APC HID 0.101 driver.version.internal: 0.71 driver.version.usb: libusb-1.0.29 (API: 0x0100010B) input.sensitivity: high input.transfer.high: 290 input.transfer.low: 150 input.transfer.reason: input voltage out of range input.voltage: 230.0 input.voltage.nominal: 230 ups.beeper.status: enabled ups.delay.shutdown: 20 ups.firmware: 892.R3 .I ups.firmware.aux: R3 ups.load: 0 ups.mfr: American Power Conversion ups.mfr.date: 2015/10/16 ups.model: Back-UPS XS 650CI ups.productid: 0002 ups.realpower.nominal: 390 ups.serial: 3B1542X10144 ups.status: OL ups.timer.reboot: 0 ups.timer.shutdown: -1 ups.vendorid: 051d
Что ж, уже хорошо... Но позвольте, battery.runtime: 17600 это же 293 минуты и 20 секунд, как так? Элементарно.
Эта "ошибка" возникла из-за другого значения: ups.load: 0, которое изменится только в двух случаях:
Когда ИБП запустит быстрый рутинный тест батареи
Когда ИБП переключится на питание нагрузки от батареи
Само по себе ups.load: 0 означает, что ИБП работая от линейного напряжения замеры потребляемой нагрузки не делает.
Тестирование батареи можно инициировать. Но как оказывается не для всех моделей ИБП. И как это ни печально, Back-UPS XS 650CI не поддерживает команду test.battery.
Если в сети поискать про периодический рутинный тест батареи, то можно найти информацию, что ИБП APC делают это раз в 7 или 14 дней. При чем все без исключений, вне зависимости от модели.
Ну что ж ты Шнайдер, убил легенду...
Ни через 7, ни через 14 дней UPS XS 650CI выпущенный в 2015-ом так и не запустил тестирование батареи. Может быть ему нужно 21 или 28 дней, может быть за более чем 10 лет жизни ему стряс мозги его прошлый владелец? Этого мне не ведомо. Есть только факт, что ups.load: 0 так и остается. Другого способа тестирования батареи, кроме как кратковременное переключение питания нагрузки с линии на нее ИБП не знают. Т.е. включись рутинный тест, то мощность потребления нагрузки получила бы ненулевое значение. В следствии отсутствия какой-либо индикации о состоянии этой батареи данный ИБП превращается в "тыкву". В общем, сообществу на заметку: шильдик - ничто!
Однако, статью надо закончить.
Отключаем линейное питание. Смотрим сообщения:
$ dmesg ... Aug 20 00:22:37 localhost upsmon[28473]: [ID 702911 daemon.notice] UPS apc_ups@localhost on battery Aug 20 00:22:37 localhost upsmon[upssched]: [ID 702911 daemon.notice] info_onbatt): Now on battery $ $ /opt/nut/sbin/upssched -l shutdown_onbatt 1787160397 210 ONBATT apc_ups@localhost UPS apc_ups@localhost on battery
Т.е. в syslog появилось сообщение и запустился таймер, который в момент запуска upssched -l, через 210 секунд запустит управляющий скрипт с аргументом shutdown_onbatt. Вот так:/opt/nut/bin/upssched-cmd.my shutdown_onbatt
После такой проверки ups.load: 0 волшебным образом преобразовался в ups.load: 10, что в пересчете дает 39Вт. (ups.realpower.nominal: 390)
На этом миссию можно считать завершенной.
Надеюсь, что эта статья кому-то поможет. Хотя бы отдохнуть от бесконечного потока AI-slop...