Annual Open Tech Conference - ISsoft Insights 2021. June 19. Learn more.
×Закрыть

Пишемо консольний двопанельний менеджер на Linux shell

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Привіт, мене звати Сергій, я працюю DevOps інженером в компанії Luxoft. Хочу розповісти, як лінь допомогла мені написати непоганий, як мені здається, «велосипед».

Багато хто з розробників недооцінює або некоректно ставиться до Linux Shell, порівнюючи його з іншими мовами програмування. Але не треба забувати, що найважливіше в Linux Shell — саме слово shell, тобто це не просто мова програмування для написання програм.

Це — оболонка операційної системи, що була написана системними інженерами для системних інженерів, і вона досить потужна та універсальна, щоб автоматизувати та спростити майже всі рутинні операції без застосування зайвих мов програмування.

Вона оперує не бібліотеками, а програмами (безліч GNU tools та все, що ви можете знайти та написати самостійно — легко інтегрується з оболонкою). Також вона слідує у першу чергу не за стандартами мови програмування, а за стандартами операційної системи. Тому навіть базові знання про те, як працює операційна система, покращать ваше володіння Linux Shell.

Про сертифікати

З того моменту, як HTTPS почав наступ на інтернет, майже у кожному проекті використовуються сертифікати. Зазвичай, їх є кілька видів:

  • ті, які кожен може згенерувати сам;
  • ті, які видав корпоративний CA;
  • безкоштовний публічний сертифікат від Let’s Encrypt;
  • або комерційний сертифікат від IdenTrust чи DigiCert та інших центрів сертифікації, що представляють додаткові функції, наприклад перевірку компанії, або державну акредитацію...

Також це може бути сертифікат для утворення tls/ssl каналу, або для авторизації на сторонньому сервісі, або сертифікат для створення двостороннього tls/ssl...

Якщо у вас багато сертифікатів, скоріше за все, ви користуєтесь сховищами — keystore, такими як JKS (Java keystore, що набув статус legacy з виходом дев’ятої версії Java) або PKCS12. Останній використовується майже 20 років, і не дивлячись на критику, є одним з найпопулярніших надійних відкритих форматів, що мають повністю витіснити JKS у недалекому майбутньому.

Я багато працював у проектах, де основною мовою програмування була Java, тому в мене основний інструмент для роботи з JKS — keytool, що йде разом із JDK.

Звісно, в мене є і невеличка шпаргалка з командами, такими як:

1. Вивести список сертифікатів

 keytool -list -v -keystore "файл.jks" -storepass "пароль"|grep -P '(Alias name:|Entry type:|Serial number:|Owner:|Valid from:)'

2. Скопіювати сертифікат із одного keystore в другий keystore

keytool -importkeystore -srckeystore "KEYSTORE_FROM.jks" -srcstorepass "SOURCE_PASSWORD" -destkeystore "KEYSTORE_TO.jks" -deststorepass "DESTINATION_PASSWORD" -srcalias "CERT_ALIAS" [ -destalias "NEW_CERT_ALIAS" ]

3. Завантажити сертифікат в keystore із PEM файла

keytool -import -file "FILE_FROM.pem" -keystore "KEYSTORE_TO.jks" -storepass "KEYSTORE_PASSWORD" -noprompt -alias "CERT_ALIAS"

4. Вивантажити сертифікат із keystore в файл

keytool -exportcert -v -alias "CERT_ALIAS" -keystore "KEYSTORE_FROM.jks" -storepass "KEYSTORE_PASSWORD" -rfc -file "FILE_TO.cer"

Одного разу я працював у проекті, де було кілька десятків мікросервісів, безліч віртуалок у тестових середовищах, на яких розробники руками правили сховища сертифікатів і не було ніякого централізованого контролю. Тільки щоб розібрати все це та привести у якийсь системний вид, треба було все прискіпливо перебрати, порівняти, домовитись із розробниками на перехід до якихось стандартів.

Звісно, немає такого, що було б неможливо зробити, але сертифікати оновлювались не дуже часто, тому ніхто не збирався ставити це завдання у беклог — при необхідності, сертифікати поновлювали вручну до наступного разу.

Мені, як інфраструктурному інженеру, це не дуже подобалось, але обсяг початкової рутини та низький пріоритет змушував мене ходити колами та займатися іншими, більш важливими завданнями.

Час від часу я намагався знайти якесь магічне рішення, навіть ознайомився із графічною утилитою, але потрібного функціоналу там не було, та й політика компанії не дозволяла використовувати на десктопі програми без аудиту безпеки, що міг розтягнутись на рік.

Можливо, я б зібрався та за тиждень зробив би все однострічніками + блокнот + excel, але зненацька в мене сталась відпустка через хворобу, а коли температура дозволила сидіти за монітором, але ще не виходити на роботу, я згадав, що я ж мислю себе спецом на bash.

Тому зустрічайте: консольний двопанельний менеджер на Linux shell.

~35 кілобайт POSIX-сумісного Linux Shell.

Для роботи треба:

  • не дуже старий шелл (ksh, bash, zsh)
  • sed, grep
  • keytool, що йде у поставці із JDK

Що вміє менеджер коротко можна наочно побачити на скриншотах, також працює справка по F1 або «H»

В одно-панельному режимі:

У двох-панельному режимі:

Для шанувальників панельних менеджерів (наприклад я жити не можу без FAR), таких як NC, VC, MC, FAR та ін., функціональні клавіші та навігація повинні бути інтуїтивно зрозумілими.

Що було найцікавіше під час розробки?

Спочатку я написав простенький скрипт на bash, вже із навігацією та першим функціоналом, але в якийсь момент в мене щось не запустилось на ksh. Я зрозумів свою помилку та переписав усе на користь POSIX-сумісності.

Код став виглядати більш громіздким, зате скрипт перевірено працює в bash/ksh/dash (навіть у git-bash, але із тормозами). Якщо щось пропустив — напишіть в коментарях.

Автоматичне налаштування панелей під висоту / ширину екрану

Так як екран у мене повністю оновлюється майже з кожним натисканням, то змінити розмір вікна (наприклад якщо сидіти через графічний ssh ​​клієнт) можна в будь який час — після першого ж натискання будь-якої клавіші все адаптується. Пару вечорів пішло на опції відображення/приховування стовпців — потрібно було порахувати й налагодити адаптацію для одно-панельного і дво-панельного режиму, плюс заголовки і текст обчислюються різними формулами. Ось шматочок для визначення наскільки треба обрізати Certificate Alias, якщо екран занадто вузький і потім приклад виведення заголовка:

WindowWidth="$(tput cols)"
if [ -n "$RFILE" ]; then # two-panel
    used=24
    [ -n "$SHOW_TYPE" ] && used=$(( $used+34 ))
    localWidth=$(( ( $WindowWidth - $used ) / 2 - 1 ))
    if [ $localWidth -ne $aliasWidth ]; then
        aliasWidth=$localWidth
        [ $aliasWidth -lt 1 ] && aliasWidth=1
        clear
    fi
.....................................................................
headerWidth=$(( $aliasWidth + 5 ))
[ -n "$SHOW_TYPE" ] && headerWidth=$(( $headerWidth + 17 ))
printf " store: ${blue}%-$(( $headerWidth ))s${rst}" "$LFILE"
printf "| store: ${blue}%-$(( $headerWidth -1 ))s${rst}\n" "$RFILE"

printf " %-10s" "Valid to"
[ -n "$SHOW_TYPE" ] && printf " %-16s" "Storetype"
printf " %-${aliasWidth}s |" "Alias"
printf " %-10s" "Valid to"
[ -n "$SHOW_TYPE" ] && printf " %-16s" "Storetype"
printf " %-${aliasWidth}s\n" "Alias"

Визначення натискання функціональних клавіш (updated)

Деякі спеціальні клавіші можуть мати довжину декiльца символів, тому просто через read було зробити непросто — він не дозволяє варіативно змінювати довжину строки, але після пошуку та експериментiв я визначив, що команда read розуміє що таке мілісекунда. І після експериментів, ця частина виглядає так:

# Special keypress could take variable amount of characters
keypress=""
read -rsN1 keytap
while [ -n "$keytap" ]; do
    keypress="${keypress}${keytap}"
    read -sN1 -t 0.01 keytap
done

Тепер можна порівнювати значення $keypress із комбінаціями типу:
F1_KEY=$’\e[11~’
F10_KEY=$’\e[21~’
UP_KEY=$’\e[A’
DOWN_KEY=$’\e[B’
TAB_KEY=$’\t’
DEL_KEY=$’\e[3~’
Я не зовсім впевнений, що мій варіант буде працювати скрізь ідеально — тому про всяк випадок більшість hot keys продубльована літерами.

У спрощеному вигляді подібну навігацію я вже використовую в інших скриптах — в деяких випадках це набагато зручніше ніж стандартний select завдяки можливості робити хоткеї та можливості зручно виводити списки.

Дуже важливим плюсом я вважаю те, що jks_mgr.sh — це просто скрипт одним текстовим файлом.

Ніяких обсфукацій, код максимально простий — перевірити на відсутність закладок може в принципі будь-який джуніор.

Тобто, якщо ви працюєте у проекті, де кожна програма потребує перегляду службою безпеки, перевірки на ліцензію, або на цільовій машині просто недостатньо прав — цей скрипт може бути стати вам в нагоді. Його не потрібно встановлювати, потрібен лише доступний шелл, keytool, sed та grep.

Та є й «печалька»: звичка роботи з Java змусила мене виконувати всю роботу зі сховищами саме через keytool, який повинен бути доступний в PATH (а можливо варто було б розібратися та зробити все через openssl? — напишіть у коментарях).

На поточний момент всі найважливіші для мене функції втілені і я взяв перепочинок, та власне й сам скрипт я почав писати, щоб отримати трохи практики з tput - і навігацією, але захопився — tput вже викiнув iз скрипта, користуюсь stty щоб дiзнатись розмiр термiналу, та напряму вивожу контрольнi коди.

Підсумок

Наприкінці потрібно написати щось важливе/мудре. Ну хай так: хочеш отримати від навчання/практики задоволення — придумай таке, щоб результат був корисний для тебе.

Хочеш отримати від навчання/практики подвійне задоволення — придумай таке, щоб результат був корисний для тебе і для когось ще.

Зараз моїм скриптом користується невелика кількість людей в компанії. А те, що я, не розробник, зміг написати щось корисне і потрібне, мене дуже радує.

P.S. Про всяк випадок (чули про nginx та Wargaming?), хочу заявити, що jks_mgr.sh був написаний ВИКЛЮЧНО в неробочий час, на приватному комп’ютері.

P.P.S. github.com/sfkulyk/jks-manager

👍НравитсяПонравилось14
В избранноеВ избранном7
Подписаться на автора
LinkedIn

Похожие статьи

Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Від читання коду отримав задоволення і здивований дивними токсичними коментами.

По коду. Не очікував, що двопанелька менше ніж в 1000 рядків вкладеться. Порадував printf, але трішки напряг eval. Що досі немає нормального read і треба якісь тайм-аути вигадувати — це сум, журба, бентега, туга, жаль, смуток, турба і побивання.

Дякую! Я гадаю, що проблема не в bash/read — воно лише читае stdin, i не може знати все про термiнал у якому працює i тим бiльш про рiзницю мiж keypress та keydown, це ж не десктоп.
eval — для скорочення коду

Під DOS було простіше писати кольорові консольні програми, це правда

Так не було ж UTF, ssh та multi-user

Так не було ж UTF

Эх, было ну почти UTF. IBM называла их PS/55 — Japanese PS/2.

www.ardent-tool.com/video/JP_SVGA.html

Для японского рынка выпускались специальные несовместимые VGA, SVGA и XGA адаптеры, в которых было 16 бит на знакоместо вместо 8 бит + 8 бит на аттрибуты.

Ну так это ж не UTF с переменной длиной символа, что собственно и есть основная проблема.
А так, кодировок была куча. У меня есть старый советский ноутбук, с собственной русской раскладкой и хардварной кнопкой для переключения языка. Когда-то сам под него конвертер писал под дос на асме.

старый советский ноутбук,

в КАМаЗ помещается?

Зря вы так, зрязрязрязрязря.
В то время СССР еще мог:

ru.wikipedia.org/wiki/Электроника_МС_1504

На нем я впервые прочитал «Сердца и моторы» Васильева (сейчас оно известно как «Горячий Старт»)

В то время СССР еще мог
Produced by the „Integral” Scientific Production Association in 1991, it was a clone of Toshiba T1100 Plus.[2]

Тогда все друг у друга слизывали.
НО
Это было слизано не как сейчас, переклеиванием этикеток, а как тогда — взяли схему и сами сделали на ее основе. Вся электроника — отечественная. Схема переделана под отечественную элементную базу. Из импортного — вроде бы дисководы. И по поводу экрана — было несколько вариантов модели, один из них даже с экраном отечественного производства. Я вот подозреваю, что у меня именно такой. Сейчас не могу проверить, ноут где-то на даче.
В общем это было серийное производство.

UTF-16 вполне себе фиксед сайз, если не использовать суррогаты.

Взагалі круто. Але є midnight commander, чому саме ця тема ? От кольорових du та df які одразу видають людський вивід замість скріптового — нема.

Подскажите, умеет ли mc работать с сертификатами?

наприклад я жити не можу без FAR

да собери себе фар 4 линукс и не мучайся

Подскажите плагин для far для работы с сертификатами и возможностью сравнивать?

не подскажу. наверное плагин написать проще, чем файл менеджер

Так у меня ж не файл менеджер. А написать плагин под FAR в принципе идея. Просто в баш я гораздо лучше, чем в С++

В Far2l конфлікти з шорткатами убунти.

Помучився пів години і забив.

да, есть такое. я в убунте их поотключал ибо не пользуюсь. но, думаю в исходниках можно поменять тоже

Можете привести пример как вы в mc работаете с jks?

Nope

mc рідко користуюсь
jks — взагалі гуглити довелось
xD

Господа из клуба «Составной Костыль Аццкого Сотоны» уполномочили меня предложить вам почетный пост председателя.

Господа из клуба «Составной Костыль Аццкого Сотоны» уполномочили меня предложить вам почетный пост председателя.

Пошел за попкорном

Если я соглашусь, значит ли это, что я буду вашим начальником?

Спасибо что пишете комментарии в техническом разделе, Ваше мнение очень важно...

Ну я мог бы написать о централизованном защищенном хранилище сертификатов и пайплайнах. Но зачем? Все и так это знают.

Вы про то, как это должно быть в сделанном виде, а не про то, как к этому прийти.

Вот представьте — вы приходите в проект, и видите несколько сотен виртуалок, на каждой парочка кейсторов и трастсторов по 20-30 сертификатов в каждом. Они неодинаковые, часто что-то дублируется, часто дублируется под разными алиасами.

Централизованное хранилище даже инвентаризацию сделать не позволит, чтобы дубликаты почистить и разобрать все по енвайрнментам и компонентам. Оно нужно как конечная цель.

p.s. Ощущение что статью читали наискосок.
Вы видимо путаете что я написал — это просто просто удобный враппер над keytool для работы с JKS/PKCS12, а не решение для автоматизации.

Вы про то, как это должно быть в сделанном виде, а не про то, как к этому прийти.

Могу написать как.

Почитал ваши последние 100 комментариев на доу.. Спасибо, но не надо.

Кроме того, обычно написать могут тогда, когда хотя бы немного знают проект изнутри и его «нюансы». В противном случае это будет что-то из «как рисовать сову» за 4 шага (рисуем кружок, кружок, треугольник нос, и остальную сову)

А читать надо было не комментарии а статьи.

Ну не знаю.. мне не нужно закрывать позицию, а радость и доверие вы у меня не вызываете :D

А в чому перевага над tmux?

Трохи дивне запитання. tmux зовсiм про друге — менеджер сессiй, нiяким боком не вiдноситься до сертификатiв

чув про nginx, але не чув про історію з Wargaming

можете пояснити?)

Деякі спеціальні клавіші можуть мати довжину до 4 символів

Абсолютно неверное утверждение. Может быть абсолютно любое количество символов.

Тепер можна порівнювати значення $keypress із комбінаціями типу ’[A’ (стрілка вгору), ’[B’ (стрілка вниз), ’[13 ~’ (F3) і так далі. Я не зовсім впевнений, що мій варіант буде працювати скрізь ідеально — тому про всяк випадок більшість hot keys продубльована літерами.

Все кода клавишь могут быть считаны из termcap/terminfo.

Эта строка:

WindowWidth="$(tput cols)"

Посылает терминалу запрос в виде ESC последовательностей и терминал возвращает ASCII число столбцов. Подобным образом можно из описания terminfo для текущего терминала вытащить комбинации клавиш.

Можно попробовать в консоли написать «tput kf1 | hd» — для F1 клавиши. kf2 — F2, etc.

kcub1 — left arrow
kcud1 — down arrow
kcuf1 — right arrow
kcuu1 — up arrow

На самом деле текст к времени публикации слегка устарел — я уже избавился от tput (как оказалось он не везде может быть), и в тексте скрипта сейчас его нет нигде, в пользу более универсального stty — для получения размеров я беру данные из «stty size» чтобы получить сразу ширину и высоту — и быстрее и проще, а управляющие коды для цветов вписал статикой.

Но насчет произвольного количества символов — я тогда пока не придумал, как в баш сделать правильное определение нажатие любой клавиши, если совсем неизвестна длина.
По тестам, самое длинное было 4, и если в каком-то терминале те кнопки, которые используются в менеджере будут занимать больше, они могут не сработать.
Насколько это актуально?

За комментарий спасибо!
Согласен, в принципе можно в начале запуска через tput получить актуальные для текущего терминала значения комбинаций для спецкнопок, взять от них max length и повысить совместимость, что важно. Попробую поискать, можно ли это сделать надежно без tput.

Обязательно займусь ( в нерабочее время ;)

Но насчет произвольного количества символов — я тогда пока не придумал, как в баш сделать правильное определение нажатие любой клавиши, если совсем неизвестна длина.

У ncurses есть управление задержкой (DELAY) для ввода. Не знаю, на сколько оно реализуемо на шеле, но правила такие, ставим задержку 0.2-0.3 секунды, например, и вычитываем не по одному байту, а, например, 16 сразу. Если это вводил пользователь разными кнопками, то они не будут склеены, а если это очень длинная ESC последовательность, то она в stdin будет записана вся сразу целиком. Поэтому читая по 16 байт, можно быть почти уверенным, что это одна длинная последовательность. Но, как это всегда происходит, shit happens и в stdin может быть целая серия последовательностей.

По тестам, самое длинное было 4, и если в каком-то терминале те кнопки, которые используются в менеджере будут занимать больше, они могут не сработать.
Насколько это актуально?

Ну если учесть, что xterm и его вариации на сегодняшний день покрывает, наверное, 95% всех терминалов, то, наверное, можно остальными пренебречь. В голой консоли сейчас мало кто работает. При использовании telnet/ssh — они первым делом пробуют xterm для negotiation. Но, есть Windows 10 и она использует vt100 терминал :D Если Windows не в списке, то можно и забить.

Попробую поискать, можно ли это сделать надежно без tput.

tput — это часть ncurses (libtermcap). Странно, что её может не быть.

Так как много работал в проектах где рут в продакшене отсутствует, основная цель — минимизировать любой дополнительный софт. ncurses — я точно помню, что сталкивался с проблемами. Возможно это было где-то в ембеддед... Могу ошибаться, сейчас подумал что это могло быть не отсутствие а какие-то compatible проблемы при обновлении.

Я обнаружил что read подерживает миллисекундные таймауты и в bash и в ksh — в статье собственно и есть пример в 4 read подряд. Можно наверное сделать «read -n1» первого нажатия и затем «read -n1 -t 0.01» в цикле, пока не получишь пустую строку, тогда будет универсально — попробую на днях поэксперементировать.

ncurses з якогось моменту став не обов’язковим пакетом для лінуксів :)
Наткнувся на його відсутність на одному із попередніх проектів, коли наші пекейжд скріпти на нього були зав’язані, але навіть не згадаю, чи це якийсь sles був, чи навіть кошерний centos.

ncurses з якогось моменту став не обов’язковим пакетом для лінуксів :)

libtinfo, тот, который работает с ncurses’овской terminfo database и есть ncurses. Почему они сделали так — я не знаю.

    # Special keypress could take variable amount of characters
    keypress=""
    read -rsN1 keytap
    while [ -n "$keytap" ]; do
        keypress="${keypress}${keytap}"
        read -sN1 -t 0.01 keytap
    done
работает отлично!

Как выяснилось, tput kcub1 и др, выдает немного другую информацию.
Во всяком случае KEY_DOWN=$(tput kcud1) записывает в переменную совсем не то, что я получаю через read

Пока что просто вынес все клавиши в переменные в начало скрипта и прописал их статикой. Можно поэксперементировать вот таким:

# Special keypress detection
while true; do
  printf "hexdump: [$(echo -n "$keypress"|hexdump -e '8/1 "%02X ""\t"" "' -e '8/1 "%c""\n"')], characters: $keycounter\n"
  declare -i keycounter=0
  keypress=""
  read -rsN1 keytap
  while [ -n "$keytap" ]; do
      keycounter+=1
      keypress="${keypress}${keytap}"
      read -sN1 -t 0.01 keytap
  done
done
Заодно выяснил, что для все F клавиш я пропускал последний символ, но оно как-то работало в bash, но вроде F1 не срабатывал в tectia ssh. Теперь работает =)
Как выяснилось, tput kcub1 и др, выдает немного другую информацию.
Во всяком случае KEY_DOWN=$(tput kcud1) записывает в переменную совсем не то, что я получаю через read

Я попробовал на ubuntu, так и есть. Неправильно, начал копать кто виноват, похоже, что это шел перепрограммировал ввод курсорных и F клавиш, либо это новый дефолт. Если интересно, вот стандарт:
invisible-island.net/...​xterm/ctlseqs/ctlseqs.pdf

Страница 34. Для многих клавиш, включая курсорные есть два режима работы — Normal и Application.

К сожалению terminfo тут не будет работать. Маразм, но по-ходу оно работает только для вывода, на ввод стандартные ANSI ESC[n коды.

Подписаться на комментарии