Русский
Русский
English
Статистика
Реклама

Bash-скрипт

Как я мониторил РИП-12 от Bolid

28.04.2021 02:24:00 | Автор: admin

Резервированные источники питания используются повсеместно. Они обеспечивают бесперебойным электропитанием приборы охранной и пожарной сигнализации, оборудование систем контроля доступа и другие системы. На нашем предприятии в качестве таких источников, как правило, используются приборы от ЗАО НВП Болид. У некоторых из них, как, например, у РИП-12-6/80M3-R-RS, имеется интерфейс RS485, что позволяет включать их в систему мониторинга.

Средства мониторинга

Мы используем Zabbix 5.2. Получать данные от РИП будем по протоколу Modbus RTU over TCP. Поддержка этого протокола реализована в Zabbix с помощью загружаемого модуля libzbxmodbus. Также в процессе мониторинга принимают участие преобразователь протокола C2000-ПП (вер. 1,32) в режиме Master и преобразователь последовательных интерфейсов (RS485 в Ethernet).

Объекты мониторинга

Для начала определимся, что конкретно мы сможем контролировать. Из документации к РИП-12-6/80M3-R-RS и С2000-ПП выяснилось, что рассчитывать мы можем на получение состояния семи зон (ШС) и числовых значений тока и напряжения. В ходе экспериментов мне удалось воспроизвести следующие состояния ШС:

ШС 0 Состояние прибора

149

Взлом корпуса прибора

Корпус РИП открыт

152

Восстановление корпуса прибора

Корпус РИП закрыт

250

Потеряна связь с прибором

Потеряна связь с прибором

ШС 1 Выходное напряжение

193

Подключение выходного напряжения

РИП подключил выходное напряжение при появлении напряжения в сети

192

Отключение выходного напряжения

РИП отключил выходное напряжение при отсутствии напряжения в сети и разряде батареи

199

Восстановление питания

Напряжение питания прибора пришло в норму

250

Потеряна связь с прибором

Потеряна связь с прибором

ШС 2 Ток нагрузки

194

Перегрузка источника питания

Выходной ток РИП более 7,5 А

195

Перегрузка источника питания устранена

Выходной ток РИП менее 7,5 А

250

Потеряна связь с прибором

Потеряна связь с прибором

ШС 3 и ШС 4 Напряжение на батарее 1 и 2 соответственно

200

Восстановление батареи

Напряжение батареи выше 10 В, заряд батареи возможен

202

Неисправность батареи

Напряжение на батарее ниже 7 В или не подключена

211

Батарея разряжена

Напряжение на батарее ниже 11 В при отсутствии сетевого напряжения

250

Потеряна связь с прибором

Потеряна связь с прибором

ШС 5 Степень заряда батарей

196

Неисправность зарядного устройства

ЗУ не обеспечивает напряжение и ток для заряда батареи в заданных пределах

197

Восстановление зарядного устройства

ЗУ обеспечивает напряжение и ток для заряда батареи в заданных пределах

250

Потеряна связь с прибором

Потеряна связь с прибором

ШС 6 Напряжение сети

1

Восстановление сети 220

Сетевое напряжение питания < 150 В или > 250 В

2

Авария сети 220 В

Сетевое напряжение питания в пределах 150250 В

250

Потеряна связь с прибором

Потеряна связь с прибором

Крайне вероятно, что мной была получена только часть из всех возможных состояний. Например, имеются догадки, что ШС 3 и 4 должны также иметь состояние [204] Необходимо обслуживание, а ШС 0 - состояние [203] Сброс прибора и другие. К сожалению, чтение документации ситуацию не прояснило. В связи с этим нам необходимо следить и реагировать на появление событий, которые мы не предусмотрели.

Конфигурирование устройств

Не будем долго останавливаться на процессе конфигурирования приборов, только коротко рассмотрим перечень необходимых действий. Настройка устройств Болид осуществляется при помощи утилиты UProg и имеет следующий порядок:

  1. Назначение сетевых адресов всем устройствам (РИП и С2000-ПП),

  2. Конфигурирование интерфейса интеграции С2000-ПП (Modbus RTU),

  3. Добавление ШС, описанных выше, в таблицу зон С2000-ПП. Крайне важно, чтобы, во-первых, были добавлены все ШС, а во-вторых, ШС должны следовать друг за другом в порядке возрастания.

UProg. Конфигурация С2000-ППUProg. Конфигурация С2000-ПП

При заполнении таблицы зон следует помнить следующее:

  • адрес прибора - сетевой адрес РИП, в нашем случае 126,

  • номер ШС - номер ШС от 0 до 6,

  • тип зоны - тип ШС, для ШС 0 назначаем тип зоны "3 - состояние прибора", для всех остальных - "8-РИП напряжение / ток".

Создаем шаблоны Zabbix

Напомню, что Zabbix с модулем libzbxmodbus выступает в роли Modbus-мастера. Из-за особенностей получения данных от C2000-ПП, о которых речь пойдет в процессе создания шаблонов, мы будем рассматривать два подхода к мониторингу.

  • мониторинг состояния ШС.

  • мониторинг как состояния, так и числовых параметров РИП.

Мониторинг состояния ШС

Итак, создадим шаблон RIP 12 mod 56 RIP 12 6 80 M3 R RS. Шаблон имеет один элемент данных с именем Request и типом "Простая проверка". Ключом элемента является функция: modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},{$STATUS_REG},3,7*uint16] . В параметрах функции используются значения макросов, которые позволяют составить корректный modbus запрос к C2000-ПП.

  • {MODBUS_PORT} - тип используемого протокола (enc - Modbus RTU over TCP), адрес и порт преобразователя последовательных интерфейсов.

  • {MODBUS_SLAVE} - Modbus UID С2000-ПП (настраивается в UProg на вкладке прибор).

  • {STATUS_REG} - адрес регистра в котором расположен ШС 0 интересующего нас РИПа. Получить данный адрес можно следующим образом: "Номер зоны в таблице зон С2000-ПП" + 40000 - 1. В нашем примере это: 450+40000-1 = 40449.

Основная задача элемента Request - запросить у С2000-ПП значение всех семи ШС контролируемого РИП и предоставить их в формате JSON. Результирующий JSON содержит объекты, ключами которых являются адресы регистров С2000-ПП, а значениями - содержимое этих регистров:

{  "40449":39115,  "40450":51195,  "40451":50171,  "40452":51963,  "40453":51451,  "40454":50683,  "40455":763}

Зависимые элементы данных

Элемент данных Request имеет 7 зависимых элементов. Основная задача этих элементов - распарсить JSON и получить состояние каждого ШС индивидуально. Вот эти элементы:

  • Status - состояние прибора (ШС 0),

  • Uout - выходное напряжение (ШС 1),

  • Iout - ток нагрузки (ШС 2),

  • Ubat1 - напряжение АКБ1 (ШС 3),

  • Ubat2 - напряжение АКБ2 (ШС 4),

  • Capacity - степень заряда АКБ (ШС 5),

  • Uin - напряжение сети (ШС 6).

Предобработка зависимых элементов данных

Чтобы получить состояние ШС 0 (Status), нам достаточно два шага предобработки. На первом шаге мы воспользуемся стандартным функционалом JSONPath, а затем разделим полученное значение на 256, тем самым получим код состояния.

К сожалению, мне не удалось использовать математические операции в параметрах JSONPath. Поэтому для оставшихся элементов данных пришлось использовать javascritpt-предобработку. Например, для элемента данных Iout (ШС 2) javascript-предобработка выглядит так:

function (value){    var reg = parseInt({$STATUS_REG})+2;    var data = JSON.parse(value);    return data[reg];}

Триггеры

После добавления триггеров создание шаблона можно считать завершенным. Перечень созданных триггеров:

  1. Взлом корпуса прибора,

  2. Перегрузка источника питания,

  3. Отключение выходного напряжения,

  4. Неисправность батареи АКБ1,

  5. Неисправность батареи АКБ2,

  6. АКБ1 разряжен,

  7. АКБ2 разряжен,

  8. Авария сети 220 В,

  9. Потеряна связь с прибором,

  10. Неизвестное состояние Status,

  11. Неизвестное состояние Iout,

  12. Неизвестное состояние Uout,

  13. Неизвестное состояние АКБ1,

  14. Неизвестное состояние АКБ2,

  15. Неизвестное состояние Capacity,

  16. Неизвестное состояние Uin,

  17. Превышено время отсутствия по MODBUS.

Демонстрация и импорт RIP 12 mod 56 RIP 12 6 80 M3 R RS

Шаблон RIP 12 mod 56 RIP 12 6 80 M3 R RS в картинкахШаблон RIP 12 mod 56 RIP 12 6 80 M3 R RS в картинкахПример создания узла сетиПример создания узла сети

Ссылки для импорта: Шаблон RIP 12 mod 56 RIP 12 6 80 M3 R RS, Преобразование значений RIP 12 mod 56 RIP 12 6 80 M3 R RS.

Мониторинг состояния и числовых параметров

Мониторинг числовых параметров имеет свои особенности. Все дело в том, что для получения числового значения нам необходимо сделать два modbus-запроса к С2000-ПП. Первый запрос устанавливает зону для запроса тока или напряжения, второй - непосредственное получение значения. В таком случае мы не имеем возможности использовать функционал libzbxmodbus, т.к. попросту не cможем гарантировать правильную очередность запросов.

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

В связи с вышесказанным, для этих целей было решено отказаться от использования libzbxmodbus и написать скрипт, который сможет предоставлять и числовые параметры РИП и состояния его ШС.

Пишем shell скрипт для внешней проверки

Для того, чтобы синхронизировать доступ к преобразователю последовательных интерфейсов, воспользуемся утилитой flock. Работу с Modbus будем осуществлять при помощи modpoll. В /usr/lib/zabbix/externalscripts создадим скрипт rip_12_mod_56.sh

#!/bin/bash# rip_12_mod_56.sh# $1 - protocol://host:port# $2 - Modbus UID# $3 - Status register# $4 - Offset (0 - 6)# Example of requesting statuses:       ./rip_12_mod_56.sh enc://127.0.0.1:4001 1 40000# Example value request:                ./rip_12_mod_56.sh enc://127.0.0.1:4001 1 40000 3(($# < 3)) && { printf '%s\n' "You have given little data. Command exited with non-zero"; exit 1; }lockfile=$(echo "$1" | awk -F "://" '{print $2}')setzone(){        modpoll -m $1 -a $4 -r 46181 -0 -1 -c 1 -p $3 $2 $5> /dev/null 2>&1    (($? != 0)) && { printf '%s\n' "Command exited with non-zero"; exit 1; }    sleep 0.15}getvalue (){        value=$(modpoll -m $1 -a $4 -r 46328 -0 -1 -c 1 -t 4:hex -p $3 $2 |grep ]: |awk '{print $2}')        printf "%d" $value}getstatus (){        status=$(modpoll -m $1 -a $4 -r $5 -1 -c 7 -t 4:hex -p $3 $2 | grep ]: | awk -F "0x" 'BEGIN { printf"["} NR!=7{printf "\""$2"\","} NR==7 {printf "\""$2"\""} END { printf "]"}')    echo "{ \"status\": $status }"}(        flock -e 200        protocol=$(echo $1 | awk -F "://" '{print $1}');        host=$(echo $1 | awk -F "://" '{print $2}' | awk -F ":" '{print $1}')        port=$(echo $1 | awk -F "://" '{print $2}' | awk -F ":" '{print $2}')        register=$(($3+1))        if (($# >= 4)); then                zone=$(($register+$4-40000))                setzone $protocol $host $port $2 $zone                echo $(getvalue $protocol $host $port $2)                sleep 0.15                exit 0        fi        echo $(getstatus $protocol $host $port $2 $register)        sleep 0.15;) 200> /tmp/$lockfile

Подробности настройки внешних проверок в Zabbix уточняйте в документации.

Создаем RIP 12 mod 56 RIP 12 6 80 M3 R RS EXTENDED

Для получения информации о состоянии ШС шаблон содержит элемент данных Request с типом "Внешняя проверка". Ключом элемента является скрипт: rip_12_mod_56.sh[{$MODBUS_PORT}, {$MODBUS_SLAVE}, {$STATUS_REG}]. Как и в шаблоне RIP 12 mod 56 RIP 12 6 80 M3 R RS, задача элемента Request - сформировать JSON с состояниями всех ШС.

Возвращаемый JSON оптимизирован для использования функционала JSONPath. Для упрощения скрипта значения возвращаются в шестнадцатеричной форме:

{  "status": ["98CB","C7FB","C3FB","CAFB","C8FB","C5FB","02FB"]}

Состояния ШС. Снова зависимые элементы данных.

Как и в предыдущем шаблоне, элемент данных Request имеет 7 зависимых элементов. Задача этих элементов тоже неизменна - распарсить JSON и получить состояние каждого ШС.

Получаем числовые значения

Для получения числовых значений создадим 5 элементов данных с типом "Внешняя проверка".

  • Uout_value - значение выходного напряжения, В.

  • Iout_value - значение выходного тока, А.

  • Ubat1_value - значение напряжения на батарее 1, В.

  • Ubat2_value - значение напряжения на батарее 2, В.

  • Uin_value -значение напряжения сети, В.

Ключом этих элементов является скрипт: rip_12_mod_56.sh[{$MODBUS_PORT}, {$MODBUS_SLAVE}, {$STATUS_REG}, <НОМЕР ШС>].

Триггеры

Перечень триггеров не отличается от триггеров, созданных в шаблоне RIP 12 mod 56 RIP 12 6 80 M3 R RS.

Демонстрация и импорт RIP 12 mod 56 RIP 12 6 80 M3 R RS EXTENDED

Шаблон RIP 12 mod 56 RIP 12 6 80 M3 R RS EXTENDED в картинкахШаблон RIP 12 mod 56 RIP 12 6 80 M3 R RS EXTENDED в картинкахПоследние значения RIP 12 mod 56 RIP 12 6 80 M3 R RS EXTENDEDПоследние значения RIP 12 mod 56 RIP 12 6 80 M3 R RS EXTENDED

Ссылки для импорта: Шаблон RIP 12 mod 56 RIP 12 6 80 M3 R RS EXTENDED, rip_12_mod_56.sh.

Вместо заключения

В своем мониторинге мы используем шаблон RIP 12 mod 56 RIP 12 6 80 M3 R RS. По-большому счету причина такого решения одна - расширяемость системы. Использование загружаемого модуля позволяет включать в одну линию приборы разных типов и модификаций, организовать их мониторинг стандартными средствами. Кроме этого, большой потребности в получении числовых значений у нас пока не возникало.

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

Спасибо за внимание!

Подробнее..

Перевод Оптимизация рабочего процесса при помощи fzf

05.04.2021 12:20:40 | Автор: admin

Работа в оболочке включает в себя выполнение одних и тех же команд снова и снова; что меняется, так это порядок выполнения команд и их параметры. Один из способов упростить рабочий процесс найти паттерны выполнения команд с аргументами и обернуть их в небольшие скрипты. Такой подход часто оказывается полезным, сильно упрощая работу; другой способ оптимизации рабочего процесса понять, как добавляются параметры, и попробовать упростить сам ввод. В этом посте я расскажу о втором подходе.


Обычные параметры моего рабочего процесса имена файлов и ветвей git: если посмотреть на мою историю команд, окажется, что git я ввожу чаще всего; ручной ввод команд git сопряжен с трудностями и часто приводит к ошибкам, поэтому я не ввожу команды руками везде, где это возможно. В зависимости от команды может подойти автозамена по табуляции, и она может оказаться очень полезной, но удобна она не всегда. В этом посте я покажу, как в качестве альтернативы использовать fzf.

Базовая функциональность fzf очень проста: он читает строки из стандартного потока ввода и даёт пользователю интерфейс, чтобы можно было выбрать одну или несколько строк и вписать их в стандартный поток вывода. Вики-страницы этого инструмента содержат массу примеров эффективного применения fzf. Это прекрасный ресурс, я взял несколько функций оттуда в свой репертуар и пользуюсь этими функциями почти каждый день, но в своей работе вы можете обнаружить очень специфические процессы, которых нет в вики, тогда как автоматизировать их было бы полезно.

Чтобы показать, как я подхожу к процессам подобного рода, я расскажу о 4 задачах, с которыми обычно сталкиваюсь. Затем напишу функцию оболочки с fzf, которая сделает работу удобнее. Кроме того, я расположу эти функции в порядке возрастания сложности:

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

Последние версии функций, включая варианты для fish, вы найдёте на Github.

Активация виртуальных сред python

Переменные моих виртуальных сред python содержится в файле ~/.venv. Вот, что я обычно делаю, чтобы активировать одну из сред:

  • начинаю ввод source ~/.venv/;

  • чтобы запустить автозавершение, нажимаю <tab>;

  • выбираю среду по желанию;

  • добавляю bin/activate и нажимаю <enter>.

Процесс можно улучшить чем-то вроде virtualenvwrapper, но есть и хороший пример с fzf: это простейшее решение, которое может занять всего одну строку.

function activate-venv() {  source "$HOME/.venv/$(ls ~/.venv/ | fzf)/bin/activate"}

activate-venv-simple.bash(download)

Активировать эту функцию можно с помощью команды:

source activate-venv-simple.bash

(добавьте этот код в свой .bashrc, чтобы он выполнялся постоянно), а затем используйте его, как показано ниже.

В окне выбора fzf показывает несколько виртуальных сред; среда активируется, когда строка выбрана.

Меньшая проблема то, что, если выйти из fzf нажатием ctrl-d, скрипт упадет с такой ошибкой:

bash: /home/crepels/.venv//bin/activate: No such file or directory

Её можно проигнорировать, так как вы получите желаемый эффект никаких средств активирован не будет; но решить эту проблему можно ещё проще, а именно сохранить вывод в переменную и попробовать активировать виртуальную среду, только если переменная не пуста.

function activate-venv() {  local selected_env  selected_env=$(ls ~/.venv/ | fzf)  if [ -n "$selected_env" ]; then    source "$HOME/.venv/$selected_env/bin/activate"  fi}

Удаление веток git

Другим обнаруженным паттернам самоповторения оказалось удаление веток git. Как только фича смержена в основную ветку или эксперимент больше не нужен, ветка может быть удалена, но обычно я не удаляю её сразу: вместо этого ветки накапливаются, пока их количество не достигает критической точки, в которой навигация по веткам затрудняется. Когда дело в самом деле доходит до удаления веток, картина выглядит так:

  • я начинаю ввод git branch -D;

  • нажимаю табуляцию, чтобы вызвать автозавершение;

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

Иногда я не уверен в том, какую ветку действительно можно удалить. Сначала мне нужно запустить команду git log в этой ветке, чтобы увидеть, что она содержит, и затем удалить её, как показано выше. Затем процесс повторяется до тех пор, пока я не удалю все залежавшиеся ветки.

Этот рабочий процесс хороший пример того, как сильно fzf упрощает работу. В этот раз мы воспользуемся опцией fzf --multi, которая позволяет по нажатию табуляции выбрать несколько записей.

function delete-branches() {  local branches_to_delete  branches_to_delete=$(git branch | fzf --multi)  if [ -n "$branches_to_delete" ]; then     git branch --delete --force $branches_to_delete  fi}

После выполнения source delete-branches-simple.bash мы можем использовать этот код следующим образом.

 Удаление веток при помощи fzf Удаление веток при помощи fzf

Код в основном работает, но реализовать эту функциональность можно по-разному. Первый вариант git branch показывает все ветки, включая ту, в которой мы находимся, она отмечена звёздочкой (*). Поскольку нельзя удалить ветку, в которой мы находимся, то и показывать её смысла нет, так что мы можем опустить эту ветку, предоставив вывод git branch команде grep --invert-match

Ещё один способ: мы можем пропустить переменным $branches_to_delete без кавычек в git branch -D. Сделать это нужно потому, что git каждая ветка нужна как отдельный аргумент. Если вы пользуетесь линтером вроде shallcheck, эта строка ему не понравится, поскольку переменные без кавычек могут вызвать глоббинг и разделение слов. В нашем случае срабатывание будет ложным: ветка не может содержать символов глоббинга; тем не менее я думаю, что избегать переменных без кавычек, где это возможно, хорошая практика, и один из способов сделать это пропустить вывод fzf через xargs прямо в git branch -D, а не хранить этот вывод в переменной. Если в xargs добавить опцию --no-run-if-empty, git будет вызываться только в том случае, если была выбрана хотя бы одна ветка.

Наконец, я упоминал, что, чтобы увидеть выбранную ветку, полезно посмотреть на вывод git log. Сделать это можно при помощи опции --preview: значением этой опции может быть какая-нибудь команда, которая будет выполняться всякий раз, когда в fzf будет выбрана новая строка, и вывод будет показан в окне предварительного просмотра. Фигурные скобки в этой команде работают как плейсхолдер, то есть заменяются на текущую выбранную строку.

function delete-branches() {  git branch |    grep --invert-match '\*' |    cut -c 3- |    fzf --multi --preview="git log {} --" |    xargs --no-run-if-empty git branch --delete --force}

Также обратите внимание на то, что вывод git branch пропускается через cut -с -3, которая из каждой строки удаляет 2 пробела. Если посмотреть на вывод git branch, видно, что каждая ветка, за исключением текущей, имеет префикс в 2 пробела. Если их не удалить, команда в --preview будет такой: git log ' branch-name', что приведёт к жалобам git на лишние начальные пробелы. В качестве альтернативы используйте команду git log {..}, которая тоже удалит пробелы из выбранной строки.

Вот пример: мы удаляем те же три ветки, что и выше, но при этом получаем больше информации.

Поток fzf для удаления ветвей в окне предварительного просмотра. Показаны ветки и вывод git log. Ударение ветвей с помощью fzf улучшенная версия.

Локально заходим в пул-реквест

Когда делается код-ревью, полезно бывает переключиться в ветку кода, который вы просматриваете . Интерфейс командной строки от гитхаба упрощает эту задачу: можно просто выполнить в репозитории команду пр pr-checkout. Так вы окажетесь в ветке соответствующего пул-реквеста и уже локально. Но как узнать номер пул-реквеста? Вот что я обычно делал:

  • открывал пул-реквест в браузере;

  • читал номер в URL;

  • переключался на окно терминала и вводил gh pr checkout, а затем номер.

Этот подход работает, когда мы имеем дело с пул-реквестом в 1 или 2 цифры, но, даже когда цифры всего 3, иногда я переключаюсь на браузер, чтобы убедиться, что запомнил номер правильно.

В моём прошлом посте я уже рассказывал, как при помощи gh автоматически опрашивал api Github, чтобы узнать номер пул-реквеста. Вы можете воспользоваться запросом к api, который я показываю ниже:

gh api 'repos/:owner/:repo/pulls'

Этот запрос возвращает массив JSON-объектов по одному объекту на каждый пул-реквест. Нам нужно конвертировать этот массив в подходящий fzf формат по строке на пул-реквест. Если говорить о данных, которые нам нужны, первое это номер пул-реквеста, который мы хотим пропустить через gh checkout. Также нам нужен способ идентифицировать интересный нам пул-реквест, в этом смысле лучший кандидат его заголовок. Чтобы извлечь эту информацию из JSON, мы можем воспользоваться интерполяцией строки в jq.

gh api 'repos/:owner/:repo/pulls' |    jq --raw-output '.[] | "#\(.number) - \(.title)"'

Вот опция сырого вывода --raw-output, которая определяет строку JSON; без неё каждая строка данных будет окружена кавычками. К примеру, если я выполню команду pr checkout https://github.com/junegunn/fzf, она выведет эти строки:

#2368 - ansi: speed up parsing by roughly 7.5x#2349 - Vim plugin fix for Cygwin 3.1.7 and above#2348 - [completion] Default behaviour to use fd if present else use find.#2302 - Leading double-quote for exact match + case sensitive search#2197 - Action accept-1 to accept a single match#2183 - Fix quality issues#2172 - Draft: Introduce --print-selected-count#2131 - #2130 allow sudo -E env fzf completion#2112 - Add arglist support to fzf.vim#2107 - Add instructions on command for installing fzf with Guix and/or Guix System#2077 - Use fzf-redraw-prompt in history widget#2004 - Milis Linux support#1964 - Use tmux shell-command#1900 - Prompt generally signals that the shell is ready#1867 - add {r}aw flag to disable quoting in templates#1802 - [zsh completion] Expand aliases recursively#1705 - Option to select line index of input feed and to output cursor line index#1667 - $(...) calls should be quoted: \"$(...)\"#1664 - Add information about installing using Vundle#1616 - Use the vim-specific shell instead of the environment variable#1581 - add pre / post completion 'hooks'#1439 - Suppress the zsh autocomplete line number output#1299 - zsh completion: Add support for per-command completion triggers.#1245 - Respect switchbuf option#1177 - [zsh] let key bindings be customized through zstyle#1154 - Improve kill completion.#1115 - _fzf_complete_ssh: support Include in ssh configs#559 - [vim] use a window-local variable to find the previous window#489 - Bash: Key bindings fixes

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

function pr-checkout() {  local pr_number  pr_number=$(    gh api 'repos/:owner/:repo/pulls' |    jq --raw-output '.[] | "#\(.number) \(.title)"' |    fzf |    sed 's/^#\([0-9]\+\).*/\1/'  )  if [ -n "$pr_number" ]; then    gh pr checkout "$pr_number"  fi}

Попробуем его на репозитории fzf.

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

В большинстве случаев этого, вероятно, достаточно. В блоге на гитхабе скрипт даже проще: он работает с выводом gh напрямую, но, чтобы определить нужный нам пул-реквест, заголовка может быть недостаточно. Нам поможет дополнительная информация. Например, в окне предпросмотра мы можем показывать описание пул-реквеста и иную информацию, которую мы можем получить с помощью запроса к api.

В этой функции мы удаляем ветки выше и заполняем окно предварительного просмотра с помощью вызова git log в выбранной ветви. Первой идеей может быть попытка попробовать то, что я уже показывал, то есть сделать запрос к api, чтобы получить информацию о выбранном реквесте. Но если мы выбираем разные ветви, то задержка запроса к api может начать нас раздражать, затрудняя работу. К счастью, запросы api нам больше не понадобятся: все нужные данные у нас уже есть: мы получили их, когда сделали первый запрос. Что нам нужно это дописать шаблон строки jq, чтобы извлечь всю нужную информацию и затем воспользоваться функцией fzf, которая позволяет спрятать информацию входящих строк в окне выбора и показать её в окне предпросмотра.

fzf рассматривает каждую строку как массив полей. По умолчанию поля разделяются последовательностями пробелов (табуляциями и пробелами), но мы можем управлять разделителем с помощью опции --delimiter. Например, если мы зададим --delimiter=',' и передадим строку first,second,third в fzf, то поля будут first,, second и third. Само по себе это бесполезно. Но с помощью опции --with-nth мы можем управлять полями в окне выбора. Например, fzf --with-nth=1,2 будет отображать только первое и второе поля каждой строки. Кроме того, мы видели выше, что можно написать {} в качестве плейсхолдера в команде предварительного просмотра и fzf заменит его текущей выбранной строкой. Но {} это простейшая форма плейсхолдера. Можно указать индексы полей в фигурных скобках, и fzf заменит плейсхолдер этими полями.

Вот пример, где мы используем как --with-nth, так и --preview, а <tab> играет роль разделителя.

echo -e 'first line\tfirst preview\nsecond line\tsecond preview' |    fzf --delimiter='\t' --with-nth=1 --preview='echo {2}'

fzf разбивает каждую строку по символу табуляции; опция --with-nth=1 указывает fzf показать первую часть в окне выбора; {2} в команде предварительного просмотра будет заменена второй частью, и так как она передаётся в echo, то просто отобразится.

Пример работы с полями в fzfПример работы с полями в fzf

Этот скрипт мы используем, чтобы показать полезную информацию в окне предпросмотра. Давайте сначала просто посмотрим на него, а затем я расскажу, как он выполняется, шаг за шагом.

function pr-checkout() {  local jq_template pr_number  jq_template='"'\'#\(.number) - \(.title)'\'\t'\'Author: \(.user.login)\n'\'Created: \(.created_at)\n'\'Updated: \(.updated_at)\n\n'\'\(.body)'\'"'  pr_number=$(    gh api 'repos/:owner/:repo/pulls' |    jq ".[] | $jq_template" |    sed -e 's/"\(.*\)"/\1/' -e 's/\\t/\t/' |    fzf \      --with-nth=1 \      --delimiter='\t' \      --preview='echo -e {2}' \      --preview-window=top:wrap |    sed 's/^#\([0-9]\+\).*/\1/'  )  if [ -n "$pr_number" ]; then    gh pr checkout "$pr_number"  fi}

Мы немного изменили эту простую функцию. Извлекли шаблон строки jq в переменную, а затем дополнили её информацией об авторе, времени создания пул-реквеста, времени его последнего обновления, а также его описанием . Всю эту информацию мы получили в объекте JSON. Ответьте на этот запрос к api гитхаба: gh api 'repos/:owner/:repo/pulls'.

Обратите внимание, что мы отделили новую информацию номер и заголовок символом табуляции \t. Символ табуляции используется также в качестве разделителя в fzf, затем мы показываем номер пул-реквеста и заголовок в окне выбора (при помощи --with-nth=1), а оставшуюся информацию показываем в окне предварительного просмотра (при помощи --preview='echo -e {2}').

Обратите внимание также, что на этот раз в jq мы не используем опцию --raw-output. Причина немного неочевидна. Строки, которые мы создаём с помощью jq, содержат экранированные символы новой строки. Если мы передадим опцию --raw-output в jq, она будет интерпретировать все экранированные символы, и, в частности, вместо \n отобразится именно новая строка. Вот пример, сравните выходные данные этой команды:

echo '{}' | jq --raw-output '"first\nsecond"'

и команды

echo '{}' | jq '"first\nsecond"'

первая выведет

firstsecond

А вторая вот такую строку:

"first\nsecond"

Первая версия проблематична. Помните, что fzf работает со строками, делает список строк, позволяя пользователю выбрать одну или несколько строк и вывести их. Это означает, что без опции сырого вывода каждый пул-реквест в fzf будет показан как множество строк. И это определённо не то, чего мы хотим. Поэтому позволим jq вывести escape-версию, чтобы гарантировать, что каждый пул-реквест это одна строка.

Однако такой подход вводит новые проблемы, первая мы по-прежнему хотим настоящие символы новой строки, а не символы \n. Эта проблема решается командой echo -e, которая включает интерпретацию escape-символов. Вторая проблема в том, что без опции сырого вывода jq в начале и в конце строки показывает символы кавычек и распечатывает наш разделитель, то есть табуляцию, как символ в escape. Эту проблему мы решим удалением кавычек в ручном режиме и заменой первого escape-символа \t на настоящую табуляцию. Именно это делается в sed после jq.

Наконец, обратите внимание, что мы определили опцию --preview-window=top:wrap, чтобы fzf оборачивал строки в окне предпросмотра и отображал их верхней части экрана, а не справа.

И вот как это выглядит в действии:

Создание веток для фич из проблем (issues) в JIRA

Мы видели выше, как использовать fzf для удаления ветвей git. Теперь давайте посмотрим на противоположную задачу создание новых ветвей. На работе для отслеживания проблем мы используем JIRA. Каждая ветвь функции обычно соответствует какой-то проблеме JIRA. Чтобы поддерживать эту взаимосвязь, я использую схему именования ветвей git, о которой расскажу ниже. Предположим, что проект JIRA называется BLOG, и сейчас я работаю над проблемой BLOG-1232 с названием Добавить в сценарий запуска флаг вывода подробностей. Я называю свою ветку BLOG-1232/add-a-verbose-flag-to-the-startup-script; описание обычно даёт достаточно информации, чтобы определить функцию, которой соответствует ветвь, а часть BLOG-1232 позволяет мне перейти к тикету JIRA, когда я ищу подробности о проблеме.

Вполне понятно, как выглядит рабочий процесс создания этих веток:

  • вы открываете issue из JIRA в браузере;

  • копируете номер проблемы или запоминаете его;

  • переключаетесь на терминал, начинаете вводить git checkout -b BLOG-1232/;

  • переключаетесь на браузер и смотрите на название;

  • переключаетесь на терминал и добавляете похожее на название в JIRA описание в kebab-cased.

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

И это ещё один рабочий процесс, который можно полностью автоматизировать. С проблемами в Jira можно работать так же, как мы работали с пул-реквестами, через API JIRA. Функция, которую мы напишем, подобна pr-checkout, но будет иметь несколько заметных отличий от неё.

Во-первых, от жира нет удобного инструмента, подобного gh, чтобы общаться с её api. Во-вторых, сервер (по крайней мере сервер, с которым работаю я) не разрешает создавать токены доступа, что заставляет меня при доступе к api использовать простые имя пользователя и пароль. Мне не хочется сохранить мой пароль в скрипте оболочки, а точнее, не хочется делать это в незашифрованном файле, поэтому, чтобы пароль хранился безопаснее, воспользуемся secret-tool. Наконец, создание имени ветки требует большего, чем простое извлечение текста; воспользуемся комбинаций cut, sed, и awk.

Давайте сначала посмотрим на скрипт, а потом попробуем понять, как он работает.

function create-branch() {  # The function expectes that username and password are stored using secret-tool.  # To store these, use  # secret-tool store --label="JIRA username" jira username  # secret-tool store --label="JIRA password" jira password  local jq_template query username password branch_name  jq_template='"'\'\(.key). \(.fields.summary)'\'\t'\'Reporter: \(.fields.reporter.displayName)\n'\'Created: \(.fields.created)\n'\'Updated: \(.fields.updated)\n\n'\'\(.fields.description)'\'"'  query='project=BLOG AND status="In Progress" AND assignee=currentUser()'  username=$(secret-tool lookup jira username)  password=$(secret-tool lookup jira password)  branch_name=$(    curl \      --data-urlencode "jql=$query" \      --get \      --user "$username:$password" \      --silent \      --compressed \      'https://jira.example.com/rest/api/2/search' |    jq ".issues[] | $jq_template" |    sed -e 's/"\(.*\)"/\1/' -e 's/\\t/\t/' |    fzf \      --with-nth=1 \      --delimiter='\t' \      --preview='echo -e {2}' \      --preview-window=top:wrap |    cut -f1 |    sed -e 's/\. /\t/' -e 's/[^a-zA-Z0-9\t]/-/g' |    awk '{printf "%s/%s", $1, tolower($2)}'  )  if [ -n "$branch_name" ]; then    git checkout -b "$branch_name"  fi}

В скрипте мы видим три части. Первая часть это команда curl, её переменные. Через них скрипт общается с API JIRA. Затем вывод api конвертируется строки формата, удобного для fzf; это часть скрипта такая же, как у pr-checkout. Наконец, вывод fzf конвертируется формат имени ветки.

Самые существенные изменения в сравнении с pr-checkout эта команда curl. Мы воспользовались конечной точкой поиска JIRA, которая в качестве параметра URL ожидает запрос на языке JQL. В моём случае меня интересуют все проблемы проекта BLOG, которые закреплены за мной, и те, что отмечены строкой In Progress. Строка запроса JQL содержит пробелы, знаки и скобки. Все они недопустимы в url, поэтому их нужно закодировать. Опция curl --data-urlencode автоматически закодирует эти символы. Поскольку в этой опции по умолчанию применяется запрос POST, чтобы переключиться на get, мы должны добавить опцию --get. Также воспользуемся опцией --user, чтобы сообщить curl, что нужно добавить заголовок базовой аутентификации. И последнее: добавим опцию --silent, чтобы опустить информацию о прогрессе выполнения и --compressed, чтобы сэкономить на пропускную способность.

Затем, чтобы конвертировать записи массива в JSON ответе в одну строку, воспользуемся той же техничкой, что и выше, разделив строку поиска в окне предпросмотра по символу табуляции и пропустив вывод через fzf, чтобы позволить пользователю выбрать запись. Вывод fzf будет строкой вроде BLOG-1232. Add a verbose flag to the startup script{...preview part}, чтобы удалить часть предварительного просмотра строки, воспользуемся командой cut. По умолчанию cut в качестве разделителя использует символ табуляции, а опция -f1 сообщает cut, что нужно вывести первое поле. Результат выполнения команды будет таким: BLOG-1232. Add-a-verbose-flag-to-the-startup-scrip. Затем команда sed заменит первую точку на символ табуляции, а все нечисловые и неалфавитные символы на -, сохранив при этом наши табуляции. И вот результат: BLOG-1232<tab>Add-a-verbose-flag-to-the-startup-script. Наконец, awk возьмёт строку, разделит её по табуляции, преобразует её вторую часть в нижний регистр и вернёт обе части символом косой черты в качестве разделителя.

 Создание новой ветки из проблем в JIRA Создание новой ветки из проблем в JIRA

Заключение

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

Представленные мной рабочие процессы могут никак не касаться вас. Но, надеюсь, вам поможет техника в целом: попробовать понаблюдать, как вы добавляете параметр к командам и как этот процесс можно автоматизировать. Параметры могут быть файлами или каким-то местом в системе, расположение которого не меняется, например, виртуальными средами. Или это могут быть параметры, которые пропускаются через другую команду (пример: ветви гита) или через API (номер пул-реквеста или заголовок из JIRA).

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Русификация баша

08.01.2021 06:09:33 | Автор: admin

Чтож... Начну с откашливаний - я не писака ( в хорошем смысле этого слова, писать " на поток" это надо уметь), хоть и немного писатель. И даже не очень уверен в том, что я хочу вам сегодня поведать.
Но моей Прокрастинации нет границ, так что пристёгивайте ремни, это будет ухабистая поездка!)

Вступление

Началось всё с комментария моего хорошего знакомого @Oxyd, который меня заинтересовал.
Я уже до этого успел с ним подискутировать на тему русского в програмировании ( безответно) ), и не мог пропустить такой шанс. Да и интересно стало) Поэтому появился первый сферический прототип в вакууме:

Из $1 выбрать  Демон) запустить $скрипт/демон.об;;  Аминь) запустить $скрипт/демон.об очистка;;ГотовоЕсли [ $2 ]; тогда  вывести "| $ном_проц | $путь_окр |"илсе# Местная подсветка синтаксиса совершенно не понимает что твориться :D

Это было забавно и интересно, но как-то... синтетически, что ли.
Поэтому я остался недоволен и, вспомнив что в моём любимом баше есть alias-ы (синонимы для команд), решил переписать целый скрипт на русский bash.

Русифицированный bash

Мой выбор пал на другой шуточный проект, window slicer. Он был вдохновлён не менее шуточным проектом с reddit, предназначенным для другого оконного менеджера.
Вкратце, этот скрипт разделяет окно надвое на основе координат курсора и заданного направления. Если привязать его к любому демону "мышиных" жестов, то можно сыграть в забавный аналог известной игры Fruct Ninja, только с окнами вместо фруктов)

Несколько тестов в оболочке спустя я был готов к переписыванию, только уже на zsh - аналог bash. Почему, спрашивается, ведь в названии статьи указан баш?
Ответ прост - bash не понимает переменных на кириллице. Увы)
Но разница между ними незаметна для нашей темы, поэтому будем считать что мы просто работаем с оболочкой.
Всё равно слово "баш" время от времени используют для её обозначения.

И вот, собственно, что получилось:

#! /bin/zsh# Магия, не подглядывай!ТЕРМИНАЛ=$TERMalias если="if" \      тогда="then" \      илсе="fi" \      из="case" \      выбрать="in" \      готово="done" \      вывод="echo" \      оценить="eval" \      пока="until" \      делаем="do" \      спим="sleep" \      иначе="else" \      иначе_если="elif"# Собственно программа# Объявляем переменныеX=0Y=0x_окна=0y_окна=0ширину_окна=2высоту_окна=2приложение=$ТЕРМИНАЛ# Количество окон до старта. Костыль \_()_/кол_окон=$(i3-msg -t get_tree | jq -r '.. | .nodes?[]? | select(.window_type == "normal") | .name' | wc -l)# Получаем координаты курсораоценить $(xdotool getmouselocation --shell)# Получаем параметры окна от i3wmоценить $( i3-msg -t get_tree | jq -r '..|try select(.focused == true)| "x_окна=\(.rect.x)\ny_окна=\(.rect.y)\nширину_окна=\(.rect.width)\nвысоту_окна=\(.rect.height)\nприложение=\(.window_properties.instance)"' )ждём(){    пока [ $кол_окон -lt $нов_кол_окон ];    делаем        нов_кол_окон=$(i3-msg -t get_tree | jq -r '.. | .nodes?[]? | select(.window_type == "normal") | .name' | wc -l)        спим 0.1    готово}рубим(){    разница=$(( $1 / 2 - ($2 - $3) )) # количество пикселей от от центра окна до места "рубки"    i3-msg split "$5" && $приложение & disown && ждём    # Подтягиваем новое окно до нужного размера    если [ $разница -gt 0 ]; тогда        i3-msg resize grow "$4" "$разница"    иначе        разница=$(( разница * - 1 ))        i3-msg resize shrink "$4" "$разница"    илсе}если [ "$1" = "гор" ]; тогда рубим $высоту_окна $Y $y_окна height vиначе_если [ "$1" = "вер" ]; тогда рубим $ширину_окна $X $x_окна width hилсе

Для удобства и домашнего насилия изучения есть ссылка на gist

Анализ

Честно говоря, я отделался лёгким испугом.Оказывается 13 алиасов и переменных на русском с головой достаточно для полной трансформации кода. Я был готов расписать весь скрипт чуть ли не побуквенно, но после очередного прочтения и добавления пары коментариев это оказалось просто не нужно.
Небольшой хитростью было оставить все обращения к "внешним" программам нетронутыми, но это можно назвать вынужденной мерой - реализация полного перевода явно выходит за рамки проекта just for fun. Так же пришлось притянуть за уши слово "оценить", потому как в русском нету красивого слова со значением "одарить/придать/задать/дать значение/цену" - аналога evaluate.
Тем не менее, результат уже на лицо и любой не знающий английского IT-шник (есть такие, интересно?) мгновенно поймёт функцию скрипта и даже его структуру, с поправкой на неведомый механизм добычи переменных (коментарии помогут).
Искать ошибки проще, делиться тоже, казалось бы, всё прекрасно, но не тут то было!

Писать этот код было ужасно неудобно. Вот совсем. Отчасти в этом виноват мой editor-of-choise, vim, который не дружит с русской раскладкой для хоткеев. Но даже если убрать его из уравнения, в русской раскладке просто нету нужных символов!
Все необычные скобки, доллар, амперсант и всё что я не вспомнил приходилось писать на английской расскладке. А уж ошибок из-за привычек привязанных к раскладками ( как примеры написание точек, запятых и кавычек) просто не счесть.

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

Моё мнение

  1. Это действительно удобно для понимания кода. Можно советовать новичкам, а то и вовсе написать транслятор, который будет выводить привычный код в "читабельном" виде. Наверняка пригодится на обсуждениях логики работы.

  2. Писать это неудобно. Может разработкой языка заточеного под русскую раскладку и решаться некоторые проблемы, но таких маловато. ( и я не пробовал )
    Так же возможно поможет IDE, но это надо проверять.

  3. Лично я этим пользоваться не буду. Я свободно владею английским и потому в моей голове происходит ровно то же проговаривание, при чём без перевода. А вот людям не знающим английского свободно посоветую хотя бы попробовать.

И в общем-то на этом всё. Не так страшен чёрт как его рисуют)

Концовка получилась скомканной, у меня в планах были ещё абзаца 4 растекания мыслею по древу, но оно оказалось просто не нужным. Оставлю только сухую выжимку: на чужое заглядевшись про своё не забудь. И хоть русский язык в IT затерялся, он ни разу не запрещён и даже полезен, поэтому попробуйте сами, прежде чем ввязываться в спор.

Подробнее..

Скрипт для создания OpenVPN сервера, или как один админ удаленку облегчал

01.04.2021 16:06:37 | Автор: admin
image
Доброго дня товарищи! Во время пандемии у всех был выбор, что использовать для организации удаленного доступа. Я выбрал OpenVPN. Чтобы помочь другим, (в первую очередь себе, конечно), был написан скрипт для простой установки сервера Ovpn на centos 8 с нуля, без заморочек.
Интересующимся под кат, там скрипт и небольшие пояснения.

Весь скрипт комментирован, а также настроен на общение с пользователем. Это очень облегчает понимание.
Вкратце. Создается пользователь openvpn, нужен в основном для обмена клиентскими конфигами, чтобы не давать лишнего доступа по ssh\ftp тем, кому нужно их скачать.
Проверки на число добавлены для уменьшения ошибок на дурака. Тем, кто захочет сломать точно сломает, но так хоть что-то. Дополнительных утилит самый минимум.
В этом скрипте selinux настраивается, а не отключается. Из настроек пользователя остаются несколько строк файла vars, а также порт, протокол, ip-адрес, и количество пользователей. Ко всем вопросам пользователю, есть пояснение. Остальная установка проходит по стандартной процедуре настройки OpenVPN сервера. Хотел сделать silent режим, но думаю это уже лишнее. Готовые файлы конфигурации складываются в папку /home/openvpn/ ready_conf. Они уже готовы к использованию.
Скрипт здесь, а также есть на github
На centos 7 заменой dnf на yum не проверял. Если есть желание можете попробовать, потом расскажете.
Скрипт установки
#!/bin/bashecho "Этот скрипт создаст OpenVPN сервер с нуля, от вас потребуется указать количество клиентов и минимальные настройки"echo "К каждому пункту будет пояснение"echo "Для начала создадим пользователя openvpn"      #Создадим нового пользователя openvpn с правами администратора      #Проверка на наличие пользователя в системе, для отсутствия ошибок при повторном запускеusername=openvpn #переменная с именем пользователяclient_name=client #имя клиентаanswer=y #ответ пользователяgrep "^$username:" /etc/passwd >/dev/nullif [[ $? -ne 0 ]]; then   adduser openvpn; usermod -aG wheel openvpn; passwd openvpn   echo "Пользователь создан"else   echo "Пользователь уже создан в системе"fi      #Создание клиентов по умолчаниюecho "Укажите количество клиентов по умолчанию. Потом можно добавить еще по необходимости"read quantity_client      #Проверка-значение число, иначе сначалаif [[ $quantity_client =~ ^[0-9]+$ ]]; then   #количество клиентов   echo "Будут создано "$quantity_client" клиентских конфигураций с именами "$client_name"[X].ovpn"else   echo "введённый символ не является числом, попробуйте снова"   echo "Попробовать снова? (y/n/e)"   read answer   case $answer in           "y")              $0              ;;           "n")              echo "bye"              exit              ;;           "e")              exit              ;;            *)              echo "error"              ;;   esacfiecho 'Установим утилиты необходимые для дальнейшей работы'dnf install wget -y; dnf install tar -y; dnf install zip -y      #Начинаем установку. Подключим репозиторий и скачаем сам дистрибутивdnf install epel-release -y; sudo dnf install openvpn -y      #Проверка наличия директории openvpn если есть то удаляем и создаем заново, иначе создаемif [[ -e /etc/openvpn ]]; then   rm -rf /etc/openvpn   mkdir /etc/openvpn; mkdir /etc/openvpn/keys; chown -R openvpn:openvpn /etc/openvpn   echo "Удалена старая директория openvpn, создана новая"else   mkdir /etc/openvpn; mkdir /etc/openvpn/keys; chown -R openvpn:openvpn /etc/openvpn   echo "создана новая дирктория openvpn"fi      #Скачиваем easy-rsawget -P /etc/openvpn https://github.com/OpenVPN/easy-rsa/releases/download/v3.0.8/EasyRSA-3.0.8.tgztar -xvzf /etc/openvpn/EasyRSA-3.0.8.tgz -C /etc/openvpnrm -rf /etc/openvpn/EasyRSA-3.0.8.tgz      #Создадим файл vars, с настройками пользователяtouch /etc/openvpn/EasyRSA-3.0.8/vars      #Значения переменных для varsecho "Укажите основные настройки создания сертификатов"echo "Для каждого пункта есть настройки по умолчанию, их можно оставить"echo "Страна(по умолчанию RH):"; read countryif [[ -z $country ]]; then   country="RH"fiecho "Размер ключа(по умолчанию 2048):"; read key_sizeif [[ $key_size =~ ^[0-9]+$ ]]; then #проверка на число   echo "Установлен размер ключа:" $key_sizeelse   key_size=2048; echo "Значение ключа установлено по умолчанию"fiecho "Укажите область\край(по умолчанию Tegucigalpa"; read provinceif [[ -z $province ]]; then   province="Tegucigalpa"fiecho "Город(по умолчанию Tegucigalpa)"; read cityif [[ -z $city ]]; then   city="Tegucigalpa"fiecho "email(по умолчанию temp@mass.hn)"; read mailif [[ -z $mail ]]; then   mail="temp@mass.hn"fiecho "срок действия сертификата, дней(по умолчанию 3650/10 лет): "; read expireif [[ $expire =~ ^[0-9]+$ ]]; then   echo "Срок действия сертификата" $expire "дней"else   expire=3650fi      #Набиваем varscat <<EOF > /etc/openvpn/EasyRSA-3.0.8/varsset_var EASYRSA_REQ_COUNTRY $countryset_var EASYRSA_KEY_SIZE $key_sizeset_var EASYRSA_REQ_PROVINCE $provinceset_var EASYRSA_REQ_CITY $cityset_var EASYRSA_REQ_ORG $domain_nameset_var EASYRSA_REQ_EMAIL $mailset_var EASYRSA_REQ_OU $domain_nameset_var EASYRSA_REQ_CN changemeset_var EASYRSA_CERT_EXPIRE $expireset_var EASYRSA_DH_KEY_SIZE $key_sizeEOF      #Теперь инициализируем инфраструктуру публичных ключейcd /etc/openvpn/; /etc/openvpn/EasyRSA-3.0.8/easyrsa init-pki      #Создаем свой ключ/etc/openvpn/EasyRSA-3.0.8/easyrsa build-ca nopass      #Создаем сертификат сервера/etc/openvpn/EasyRSA-3.0.8/easyrsa build-server-full server_cert nopass      #Создаем Диффи Хелмана/etc/openvpn/EasyRSA-3.0.8/easyrsa gen-dh      #crl для информации об активных/отозванных сертификатах/etc/openvpn/EasyRSA-3.0.8/easyrsa gen-crl      #Теперь копируем все что создали в папку keyscp /etc/openvpn/pki/ca.crt /etc/openvpn/pki/crl.pem /etc/openvpn/pki/dh.pem /etc/openvpn/keys/cp /etc/openvpn/pki/issued/server_cert.crt /etc/openvpn/keys/cp /etc/openvpn/pki/private/server_cert.key /etc/openvpn/keys/      #Получим настройки для файла server.confecho "Сейчас соберем информацию для файла конфигурации сервера."echo "Порт(по умолчанию 1194):"; read port_numif [[ $port_num =~ ^[0-9]+$ ]]; then #проверка на число   echo "Установлен порт:" $port_numelse   port_num=1194; echo "Номер порта установлен по умолчанию"echo "Протокол(по умолчанию udp)для установки tcp введите 1"; read protocolfiif [[ $protocol -eq 1 ]]; then   protocol="tcp"   echo "Выбран протокол tcp"else   protocol="udp"   echo "Выбран протокол udp"fi      #Теперь создадим директорию и файлы для логовmkdir /var/log/openvpntouch /var/log/openvpn/{openvpn-status,openvpn}.log; chown -R openvpn:openvpn /var/log/openvpn      #Включаем движение трафикаecho net.ipv4.ip_forward=1 >>/etc/sysctl.confsysctl -p /etc/sysctl.conf      #Настроим selinuxdnf install policycoreutils-python-utils -ydnf install setroubleshoot -ysemanage port -a -t openvpn_port_t -p $protocol $port_num/sbin/restorecon -v /var/log/openvpn/openvpn.log/sbin/restorecon -v /var/log/openvpn/openvpn-status.log      #Настроим firewalldfirewall-cmd --add-port="$port_num"/"$protocol"firewall-cmd --zone=trusted --add-source=172.31.1.0/24firewall-cmd --permanent --add-port="$port_num"/"$protocol"firewall-cmd --permanent --zone=trusted --add-source=172.31.1.0/24firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -s 172.31.1.0/24 -j MASQUERADEfirewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 172.31.1.0/24 -j MASQUERADEsystemctl restart firewalld     #Создадим server.confmkdir /etc/openvpn/servertouch /etc/openvpn/server/server.conf#chmod -R a+r /etc/openvpncat <<EOF > /etc/openvpn/server/server.confport $port_numproto $protocoldev tunca /etc/openvpn/keys/ca.crtcert /etc/openvpn/keys/server_cert.crtkey /etc/openvpn/keys/server_cert.keydh /etc/openvpn/keys/dh.pemcrl-verify /etc/openvpn/keys/crl.pemtopology subnetserver 172.31.1.0 255.255.255.0route 172.31.1.0 255.255.255.0push "route 172.31.1.0 255.255.255.0"push "dhcp-option DNS 8.8.8.8"push "dhcp-option DNS 8.8.4.4"keepalive 10 120persist-keypersist-tunstatus /var/log/openvpn/openvpn-status.loglog-append /var/log/openvpn/openvpn.logverb 2mute 20daemonmode serveruser nobodygroup nobodyEOFecho "Добавим сервер в автозагрузку и запустим"sudo systemctl enable openvpn-server@serversudo systemctl start openvpn-server@serversudo systemctl status openvpn-server@server      #Начнем создавать клиентов      #Директория для готовых конфиговmkdir /home/openvpn/ready_confecho "IP к которому необходимо подключаться клиентам в формате 111.111.111.111"; read ip_adress      #Создадим темповый файл конфигурации клиента с настройкамиtouch /home/openvpn/temp_conf_client.txtcat <<EOF > /home/openvpn/temp_conf_client.txtclientdev tunproto $protocolremote $ip_adress $port_numpersist-keypersist-tunverb 3route-method exeroute-delay 2EOF      #теперь функция создания клиентовcreate_client () {   cd /etc/openvpn/   /etc/openvpn/EasyRSA-3.0.8/easyrsa build-client-full "$client_name$quantity_client" nopass   cp /home/openvpn/temp_conf_client.txt /home/openvpn/ready_conf/"$client_name$quantity_client"'.ovpn'{   echo "<ca>"; cat "/etc/openvpn/pki/ca.crt"; echo "</ca>"   echo "<cert>"; awk '/BEGIN/,/END/' "/etc/openvpn/pki/issued/$client_name$quantity_client.crt"; echo "</cert>"   echo "<key>"; cat "/etc/openvpn/pki/private/$client_name$quantity_client.key"; echo "</key>"   echo "<dh>"; cat "/etc/openvpn/pki/dh.pem"; echo "</dh>"} >> "/home/openvpn/ready_conf/"$client_name$quantity_client".ovpn"}       #Запускать функцию создания клиентов, по счетчикуwhile [[ $quantity_client -ne 0 ]]; do   create_client   let "quantity_client=$quantity_client-1"done/etc/openvpn/EasyRSA-3.0.8/easyrsa gen-crl #генерируем crl для информации об активных сертификатахcp /etc/openvpn/pki/crl.pem /etc/openvpn/keys/ #Копируем в директорию с активными сертификатамиsudo systemctl restart openvpn-server@server #перезапускаем сервер, для применения crlcd /home/openvpn/ready_conf/; ls -alh ./echo "сейчас вы в директории с готовыми файлами конфигураций, их уже можно использовать"echo "скрипт завершен успешно"exec bash

Подробнее..

Конвертация скрипта Bash в код С для отправки СМС через usb модем HUAWEI E3372

17.06.2020 16:18:48 | Автор: admin
Несколько моих проектов отправляют СМС и в последнее время после обновлений сервера, а может и ряда других причин, отправка СМС стала почти невозможной.

Старенький USB модем HUAWEI (марку не буду разглашать) перестал стабильно висеть на одном COM порту и временами переподключался на другие порты, совсем отключался и терял антенну.

Да и ситуация с библиотекой GSMComm была непонятной и болезненной.

GSMComm это пакет для телефонов GSM, в основном для выполнения задач, связанных с SMS.
www.nuget.org/packages/GSMComm последня версия 1.21.1 от 10.10.2015 года.


Поиск по интерент показал, что есть возможность использовать встроенный функционал WEB API новых модемов HUAWEI, более эффективно, чем старый подход с AT командами реализованный в GSMComm.

Выяснилось, что есть прекрасный usb модем HUAWEI E3372, который почти хакерским способом способен отправлять СМС как из скрипта (Curl + Bash), так и из кода (Python, Perl), и, как я предположил, из C#.

Самое печальное, что компания HUAWEI не предоставляет никакой документации как это сделать и все найденные методы имели экспериментальный харктер и зависели от семейства устройств.

В общем, опираясь на найденный материал, не гарантирующий работу кода с момедом, был приобретен HUAWEI E3372.

Не углублясь в эксперименты с Python или Perl я решил попробовать разобраться с вариантами Bash + Curl.

В общем, после нескольких экспериментов был найден работающий код под MS Windows 10 + Git Bash for MS Windows.

Скрипт

curl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1#TOKEN=$(curl -s -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)TOKEN=$(echo $TOKEN | cut -d'"' -f 10)echo $TOKEN > token.txtNUMBER=$1MESSAGE=$2LENGTH=${#MESSAGE}TIME=$(date +"%Y-%m-%d %T")TOKEN=$(<token.txt)SMS="<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>$NUMBER</Phone></Phones><Sca/><Content>$MESSAGE</Content><Length>$LENGTH</Length><Reserved>1</Reserved><Date>$TIME</Date></request>"echo $SMScurl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS" http://192.168.8.1/api/sms/send-sms --header "__RequestVerificationToken: $TOKEN" --header "Content-Type:text/xml" --header "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" --header "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3"


Скрипт удивительным образом работал. И это было уже счастье, так как деньги были потрачены на модем не зря!

Осталось только понять КАК же он работает. Почитав документацию по Curl и Bash (ну по bash я не читал так догадался) прояснилась работа скрипта. Привожу этот же скрипт с моими комментариями.

Скрипт с комментариями

# https://stackoverflow.com/questions/28070500/grab-current-sessions-cookie-with-curl/28070870# Содержимое файла session.txt определяется опцией -b# Сделать GET запрос и получить куки в первый раз и записать их в файлcurl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1# Сделать GET запрос и получить куки во второй раз и записать в файл и сохранить содержимое страницы HTML в переменную TOKEN как строкуTOKEN=$(curl -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)# Извлчеь из переменной значение метатега из <meta name="csrf_token" content="b/XNeODpHCthQXEOEjBNkICn2n7e9v4e"/> и перезаписать в ту же переменную TOKENTOKEN=$(echo $TOKEN | cut -d'"' -f 10)# Отобразить на экранеecho "$TOKEN"# сохранить подстроку в файлеecho $TOKEN > token.txt# Получить два параметра командной строки: (1) номер телефона и (2) текст СМСNUMBER=$1MESSAGE=$2# Получить количество символов в текстеLENGTH=${#MESSAGE}# Получить текущее время и отформатировать егоTIME=$(date +"%Y-%m-%d %T")# Загрузить содержимое файла в переменнуюTOKEN=$(<token.txt)# Сфоромировать текст для отправки СМС как XML SMS="<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>$NUMBER</Phone></Phones><Sca/><Content>$MESSAGE</Content><Length>$LENGTH</Length><Reserved>1</Reserved><Date>$TIME</Date></request>"# Отобразить переменную с текстом СМС на экране echo $SMS# Сделать POST для отправки СМСcurl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS" http://192.168.8.1/api/sms/send-sms --header "__RequestVerificationToken: $TOKEN" --header "Content-Type:text/xml"

Скажу честно, что СМС на русском я не смог добиться. Приходит абракадабра. Так что этот вопрос остался открытым и если
у кого-то есть желание закрыть тему отправки СМС на русском милости просим, дерзайте. )))

Понимание работы скрипта принесло свою пользу и приблизило к написанию кода на C#.

Было понятно, что в нем должно быть также 3 запроса и должны они делать то же самое что и благословенный Curl. Поэтому в коде приведен Curl, а ниже, аналогичный ему C# код.

Код С# для WinForms

private void button1_Click(object sender, EventArgs e){   var ip = "192.168.8.1"; // IP адрес который выдает модем в браузере после установки   var phone = "+70000000000"; // Номер телефона   var msg = "Привет!!! СМС работает!!!";   var result = SendSMS(ip, phone, msg);   if (result)   {       //TODO  Сохранить в БД, например   }   else   {       //TODO  Сохранить в БД, например   }}private bool SendSMS(string ip, string phone, string msg){    try    {        /* curl -b session.txt -c session.txt http://192.168.8.1/html/index.html > /dev/null 2>&1 */        Cookie firstCookie = null;        Cookie secondCookie = null;        string token = string.Empty;        //В первый раз получить куки        var cookieContainer = new CookieContainer();        var uri = new Uri($"http://{ip}/html/index.html");        using (var httpClientHandler = new HttpClientHandler { CookieContainer = cookieContainer })        {            using (var httpClient = new HttpClient(httpClientHandler))            {                httpClient.GetAsync(uri).Wait();                var all = cookieContainer.GetCookies(uri);                firstCookie = all[0];            }        }        /*        TOKEN=$(curl -s -b session.txt -c session.txt http://192.168.8.1/html/smsinbox.html)        TOKEN=$(echo $TOKEN | cut -d'"' -f 10)        echo $TOKEN > token.txt         */    // И спользуя куки из первого запроса получить страницу и извлечь из нее токен        if (firstCookie != null)        {            var cookieContainer2 = new CookieContainer();            cookieContainer2.Add(firstCookie); // Поместить в конейнер куки из первого запроса к сайту            var uri2 = new Uri($"http://{ip}/html/smsinbox.html");            using (var httpClientHandler = new HttpClientHandler            {                CookieContainer = cookieContainer2            })            {                using (var httpClient = new HttpClient(httpClientHandler))                {                    var html = httpClient.GetStringAsync(uri2).Result; // Получить страницу HTML                    var all = cookieContainer2.GetCookies(uri2);                    secondCookie = all[0];                    var doc = new HtmlAgilityPack.HtmlDocument(); // Используем HtmlAgilityPack чтобы преобразовать текст HTML в структурный вид                     doc.LoadHtml(html);                    var items = doc.DocumentNode.SelectNodes("//meta");                    if (items.Count >= 2) // Получить второй по счету meta тег.                    {                        token = items[1].GetAttributeValue("content", ""); // Получить значение метатега. Не спрашивайтепочему второй метатаг с токеном рабочий - не знаю )))                    }                }            }            // Когда в наличии есть куки и токен делаем отправку СМС через запрос POST            if (!string.IsNullOrEmpty(token))            {                var msgLength = msg.Length;                var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //    TIME=$(date +"%Y-%m-%d %T")                var sms = $"<?xml version='1.0' encoding='UTF-8'?><request><Index>-1</Index><Phones><Phone>{phone}</Phone></Phones><Sca/><Content>{msg}</Content><Length>{msgLength}</Length><Reserved>1</Reserved><Date>{time}</Date></request>";                /*# Сделать POST для отправки СМС                  curl -v -b session.txt -c session.txt -H "X-Requested-With: XMLHttpRequest" --data "$SMS"                     http://192.168.8.1/api/sms/send-sms  --header "__RequestVerificationToken: $TOKEN"  --header "Content-Type:text/xml"                 */                var uri3 = new Uri($"http://{ip}/api/sms/send-sms");                var client = new RestSharp.RestClient { BaseUrl = uri3 }; // Используем RestSharp для запроса (дело вкуса)                var request = new RestSharp.RestRequest(RestSharp.Method.POST);                // Формируем свой заголовой запроса - ничего лишненго все по примеру из Curl    request.AddHeader("__RequestVerificationToken", token);                var ses = secondCookie.ToString();                request.AddCookie("cookie", ses);                request.AddHeader("Content-Type", "text/xml");                request.AddHeader("X-Requested-With", "XMLHttpRequest");                request.AddParameter("text/html", sms, RestSharp.ParameterType.RequestBody);                RestSharp.IRestResponse response = client.Execute(request);                if (response.IsSuccessful)                {                    var xmlDoc = new XmlDocument();                    xmlDoc.LoadXml(response.Content); // <?xml version="1.0" encoding="UTF-8"?><response> OK </response>                    var responseElemenets = xmlDoc.GetElementsByTagName("response");                    var resultOK = responseElemenets[0].InnerXml.ToLower();                    return resultOK == "ok"; // Ну вот и признак того, что СМС отправлено, но без отчета о доставке.                 }            }        }    }    catch (Exception)    {        //TODO в лог ошибку;    }    return false;}


Как только у вас в руках рабочий C# код вы всегда можете его улучшить.

В моем случае он работает как часы и для количества СМС в минуту вполне годится. )))

Надеюсь эта статься принелса пользу и в профессиональном и экономическом смыслах.

Душевно благодарю!
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru