mindsellers
74 subscribers
25 photos
8 files
32 links
Практики из жизни Linux-админа. Asterisk, Bash, Python и черная магия.

Сайт http://mindsellers.ru
Группа VK https://vk.com/mindsellers
ТыТруба https://www.youtube.com/channel/UC7LUJgzIUL4VGiOWctoa0fw
По всяким вопросам обращаться @alex_dmit
Download Telegram
FOLDER_ID = "default" # Идентификатор каталога
API = "AQVN33ioCUgKDF-XXXXXXXXX-oWqt7zIrX0ZW-"

with open(file, "rb") as f:
data = f.read()

params = "&".join([
"topic=general",
"format=lpcm",
"sampleRateHertz=8000",
"lang=ru-RU"
])

url = urllib.request.Request("https://stt.api.cloud.yandex.net/speech/v1/stt:recognize?%s" % params, data=data)
url.add_header("Authorization", "Api-Key %s" % API)

responseData = urllib.request.urlopen(url).read().decode('UTF-8')
decodedData = json.loads(responseData)

os.remove(file)

if decodedData.get("error_code") is None:
resp=decodedData.get("result")
if resp.startswith('да') or resp.startswith('хорошо') or resp.startswith('норм') or resp.startswith('ладно') or 'оператор' in resp:
print('yes',end='')
elif resp.startswith('нет'):
print('no',end='')
elif 'абонент' in resp:
print('no',end='')
elif 'авто' in resp:
print('no',end='')
elif resp=='':
print('no',end='')
else:
print('dont',end='')



Здесь, в принципе, все понятно без комментариев - мы по-прежнему указываем в данных, что именно мы передаем, подсовываем файл с записью и получаем результат. В зависимости от того, что было распознано, мы получаем от скрипта в stdout yes, no или dont, соответственно, Да, Нет и Не понял.

В рассматриваемом примере, к сожалению, никакой интеграции asterisk с crm-системой нет, так что пойдем по пути наименьшего сопротивления, и будем принимать данные через get-запрос. Сделать это можно по-разному: от написания собственного мультипоточного socket-сервера до использования django, но я решил использовать супер-легковесный web-фреймворк под названием bottle. Так что начинаем с

pip install bottle
Сам фреймворк целиком состоит из единственного файла, так что можно его просто скачать и положить в каталог со своим приложением. Итак, перейдем к коду:


#!/usr/bin/env python
# -*- coding: utf-8 -*-
from bottle import run, request, get
import os
from syn import synthesize

def autodial(mobile,order):
with open('/tmp/'+str(mobile)+str(order), 'w') as callfile:
callfile.write('Channel: Local/'+mobile+'@from-internal'+'\n')
callfile.write('Callerid: Autodial <'+mobile+'>\n')
callfile.write('Context: from-internal\n')
callfile.write('Extension: 900\n')
callfile.write('Priority: 1\n')
callfile.write('WaitTime: 25000\n')
callfile.write('Set: __ORDER='+str(order)+'\n')
os.rename('/tmp/'+str(mobile)+str(order), '/var/spool/asterisk/outgoing/'+str(mobile)+str(order))

@get('/msg')
def index():
postdata = request.body.read()
name = request.query.get("name")
surname = request.query.get("surname")
number = request.query.get("number")
order = request.query.get("order")
address = request.query.get("address")
synthesize(output='/tmp/'+str(order)+'-name',text=str(name)+' '+str(surname))
synthesize(output='/tmp/'+str(order)+'-order',text=str(order))
synthesize(output='/tmp/'+str(order)+'-address',text=str(address))
autodial(mobile=number,order=order)


run(host='192.168.0.22', port=8000, debug=False)


Итак, после запуска сервера на указанном адресе и порту, мы отслеживаем исключительно get-запросы в /msg. Функция autodial в данном случае формирует callfile для asterisk, причем только после того, как мы сформировали необходимые файлы. Функция synthesize импортируется из первого скрипта. Запустим скрипт и в браузере откроем ссылку

http://192.168.0.22:8000/msg?name=Иван&surname=Петров&order=23232&address=Ленина 1&number=89278555666
Результатом работы данного кода будет:

три файла с названиями вида /tmp/23232-name.wav /tmp/23232-order.wav /tmp/23232-address.wav
callfile, который осуществит вызов клиента, соединит его со служебным номером 900, а также передаст в контекст переменную ORDER, соответствующую номеру заказа.
Теперь нам нужно сгенерировать те части фразы, которые будут неизменны. Сделаем это с помощью нашего скрипта, например,
./syn.py --output='/opt/autodial/syn/сonnect' --text='Ваш звонок переводится на оператора '
Повторим эту процедуру для всех необходимых фраз, и получим файлы

address.wav
goodbye.wav
hello.wav
operator.wav
order.wav
сonnect.wav
размещенные в указанном каталоге.

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

В секцию [from-internal-custom] файла extensions_custom.conf внесем следующее:

exten => 900,1,Answer()
exten => 900,n,Noop(${ORDER})
exten => 900,n,Playback(/opt/autodial/syn/hello) ;здравствуйте
exten => 900,n,Playback(/tmp/${ORDER}-name) ;Иван Петров
exten => 900,n,Playback(/opt/autodial/syn/order) ;ваш заказ с номером
exten => 900,n,Playback(/tmp/${ORDER}-order) ;23232
exten => 900,n,Playback(/opt/autodial/syn/address) ;готов к выдаче по адресу
exten => 900,n,Playback(/tmp/${ORDER}-address) ;Ленина 1
exten => 900,n,Playback(/opt/autodial/syn/operator) ;Если вы хотите соединиться
;с оператором, скажите ОПЕРАТОР
exten => 900,n(record),Monitor(wav,/tmp/${UNIQUEID},o) ;включаем запись
exten => 900,n,Wait(2) ;ждем 2 секунды
exten => 900,n,StopMonitor() ;останавливаем
exten => 900,n,Noop(/tmp/${UNIQUEID}-in.wav)
exten => 900,n,Set(RESULT=${SHELL(/usr/bin/python3 /opt/autodial/yandex.py /tmp/${UNIQUEID}-in.wav)})
;передаем запись скрипту и ждем результата
exten => 900,n,Noop(${RESULT})
exten => 900,n,GotoIf($["${RESULT}" = "yes"]?yes) ;если он yes, переходим на метку
exten => 900,n,Playback(/opt/autodial/syn/goodbye) ;иначе прощаемся
exten => 900,n,Hangup()
exten => 900,n(yes),Playback(/opt/autodial/syn/сonnect) ;Ваш звонок переводится на оператора
exten => 900,n,Goto(from-internal,600,1) ;Соединение с оператором или очередью
exten => 900,n,Hangup()


Как несложно заметить, в данном случае мы обрабатывали только один ответ скрипта, положительный. Однако сам скрипт отдает, как было сказано выше, 3 разных варианта(как обычно, скрипт остался от другой задачи)

На этом, собственно, все. Из возможных доработок видится логичным записать заранее все адреса пунктов выдачи, и не генерировать их, а в переменной address в get-запросе передавать просто порядковый номер пункта. Записи в таком случае имеет смысл назвать address-1, address-2 и так далее, при формировании callfile передать также переменную ADDRESS, ну и соответственно, проигрывать файл с именем address-${ADDRESS} из контекста.

Листинг скриптов удобнее посмотреть в wiki
http://pubwiki.mindsellers.ru/index.php/%D0%A1%D0%B8%D0%BD%D1%82%D0%B5%D0%B7_%D0%B8_%D1%80%D0%B0%D1%81%D0%BF%D0%BE%D0%B7%D0%BD%D0%B0%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D1%80%D0%B5%D1%87%D0%B8_%D1%81_%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%D1%8C%D1%8E_Yandex_Speech_API

#python #asterisk #yandex #speech
Перенос ubuntu на систему с EFI

Буквально сегодня возникла задача - срочно перенести Ubuntu 14.04 на новое железо со старого(накрылась материнка). Система ставилась довольно давно, мать тоже была старая. Казалось бы - все тривиально: выдернул винты, воткнул в новое железо, и получай удовольствие. Но не тут-то было! Старая мать знать не знала про UEFI, а новая, соответственно, только про UEFI и знает, никакого legacy.

В общем, не буду растягивать прелюдию. Для восстановления нам потребуется загрузочная флешка с чем-то убунту-образным. У меня нашлась с Linux Mint 18 x64, и на ней все прекрасно получилось.

Загружаемся с флешки и запускаем Gparted. Необходимо "отрезать" от диска раздел(я отрезал 512Мб) в самом начале, отформатировать его в FAT32, поставить метку тома EFI и флаг boot. Затем установить и запустить boot repair:

sudo add-apt-repository -y ppa:yannubuntu/boot-repair && sudo apt -y install boot-repair && boot-repair


Ничего в интерфейсе менять не надо, останавливаем свой выбор на Recommended repair и запускаем процесс. В процессе приложение попросит выполнить несколько действий из консоли, суть которых сводится к удалению старого grub из системы, установке нового вместе с поддержкой UEFI, ну и естественно, использованием свежесозданного раздела в качестве загрузочного. По завершении работы мастера выключаем компьютер и грузимся в восстановленную, работающую систему.

#linux #bios #uefi
Уведомление в telegram из zabbix

Задача мониторинга всегда актуальна. Одним из лучших продуктов для ее реализации я считаю Zabbix. Но к сожалению, по умолчанию он умеет слать уведомления по почте и jabber. При этом существует возможность использования внешних скриптов для обработки алярмов.

Телеграм предоставляет аж 2 разных API - для ботов и для клиентов. Про реализацию бота я уже писал, так что сейчас рассмотрим реализацию клиента. Для начала, нужно пойти на сайт телеграма, получить id и токен доступа к API client. После этого можно перейти к скрипту.

Для реализации доступа к API client существует прекрасная библиотека под названием Telethon, так что начнем с ее установки:

pip3 install telethon


Обратите внимание: либа реализована только под python3.x!

Работа с либой предельно проста, например, для посылки сообщения кому-то из своих контактов, требуется написать что-то вроде

client.send_message(alex_dmit, 'Привет!')


Однако слать сообщения в личку каждому из заинтересованных - не наш метод, нам нужно слать уведомления в чат. К сожалению, примера для отправки сообщений в группу я не нашел, но поковырявшись в самой библиотеке обнаружил метод: нужно по ссылке для приглашения в чат получить его идентификатор, а уже после слать сообщение. Рассмотрим весь скрипт:
#!/usr/bin/python3
# -*- coding: utf-8 -*-

from telethon import TelegramClient
import sys
api_id = 242424
api_hash = '8d79f63dfsfsfsdfsfsdfs'#параметры подключения
phone_number = '+79063448810' # номер телефона


client = TelegramClient('/usr/lib/zabbix/alertscripts/%sessionname%', api_id, api_hash)
client.connect()
if not client.is_user_authorized():
client.send_code_request(phone_number)
client.sign_in(phone_number, input('Enter the code: '))
print(sys.argv[1])
destination_group_invite_link='https://t.me/joinchat/Dk3ZYRWFRXXXXXXXXpI7ADaQ'
entity=client.get_entity(destination_group_invite_link)
client.send_message(entity=entity, message=sys.argv[1:])



Как несложно понять, destination_group_invite_link нужно скормить ссылку-приглашение в группу. При первом запуске скрипт запросит код подтверждения, который будет выслан на любое другое устройство, с которого в этот момент авторизован пользователь. После этого по указанному адресу(в нашем случае /usr/lib/zabbix/alertscripts/) будет создан файл сессии, и пока мы его не удалим, скрипт будет работать без запросов подтверждения.

Отправлять в чат мы будем все, что получили в качестве аргументов: sys.argv[1:]

Дадим права на запуск, и проверим работоспособность скрипта:

chmod +x ./send.py
./send.py 'Тестовое сообщение'


Переходим к настройке заббикса. Все, описанное ниже, имеет отношение к версии 3.4, но насколько мне известно, и в 2.х и в 4.х все делается примерно так же.

Для начала, нужно зайти в Настройка->Действия и включить дефолтное действие Report problems to Zabbix administrators. Затем, в Администрирование->Способы оповещения добавим новый способ под названием telegram. Тип оповещения "скрипт", параметры оповещения {ALERT.SUBJECT} и {ALERT.MESSAGE}.

Остается только перейти в настройки своей учетной записи и включить способ уведомлений telegram.

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

#zabbix #linux #python #telegram #telethon
Отправка уведомлений в телеграм с помощью простейшего бота

Итак, рассмотрим простого бота, предназначенного для отправки уведомлений в каналы telegram.

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

В первую очередь, нужно бота создать, делается это с помощью служебного бота @BotFather, который даст нам token нашего бота. Затем, создадим канал, добавим туда нашего бота по имени в качестве администратора. Внимание! Канал нужно создать публичным. Используя любую машину, где есть доступ к телеграму, получим id нашего канала:

curl https://api.telegram.org/bot<токен>/getChat?chat_id=@<название канала>

В выхлопе будет json, содержащий id. После этого канал можно сделать приватным, id у него уже не изменится.

Поставим все необходимое
apt install python3-pip
pip3 install setuptools
pip3 install pytelegrambotapi
pip3 install requests[socks]

requests[socks] нам будет необходим для того, чтобы наш бот умел работать через socks-прокси. Код поражает своей элементарностью:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import telebot
from telebot import apihelper
try:
id,message=sys.argv[1:]
except:
print('Необходимо 2 аргумента - id и сообщение')
exit(1)


apihelper.proxy={'https':'socks5h://proxyuser@proxypass@domain.ltd:port'}
token = '10300000:AAGRVSSSSSSSSSSSSS'
bot=telebot.TeleBot(token)
bot.send_message(id, message)

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


UDP для любителей однострочников на баше:



curl -x socks5h://proxyuser:proxypass@proxydomain.ltd:[proxyport] "https://api.telegram.org/bot103000000:XXXXXXXXXXXXX/sendMessage?chat_id=[id]&text=[sometext] "


#python #telegram #onestringscript #bash
Уведомления о новых задачах redmine в telegram


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

Поэтому, не будем использовать плагины, но воспользуемся питоном. Для начала, создадим в redmine пользователя с админскими правами(да, можно и без них, но с ними веселее), допустим, bot, а также в Настройках включим API. После этого приступаем к питоно-коду. Чуть ранее я писал о том, как легко отсылать уведомления в телегу через бота, читайте выше. Не буду повторяться, но приведу листинг чуть модернизированного скрипта
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import telebot
from telebot import apihelper

def messagesend(id, message):
apihelper.proxy={'https':'socks5h://proxy-user:proxy-pass@domain.ltd:[port]'}
token = '1037455712:XXXXXXXXXXXXXXX'
bot=telebot.TeleBot(token)
bot.send_message(id, message)


if __name__=='__main__':
try:
id,message=sys.argv[1:]
except:
print('Необходимо 2 аргумента - id и сообщение')
exit(1)
messagesend(id,message)
Всего-то я вынес отправку в функцию и написал классический
 __name__=='__main__'
: для тестирования.

Теперь поставим очень удобную и простую либу:
pip3 install python-redmine
и напишем элементарный скрипт. Отметим, что скрипт выше называется у меня
telegram_send.py
и лежит в одном каталоге с нижеследующим.

#!/usr/bin/python3

from redminelib import Redmine
from telegram_send import messagesend
redmine = Redmine('https://redmine.domain.ltd/', username='bot', password='BOTPASS')

CHATID='-100XXXXXXXXXXXX'

count=0
for issue in redmine.issue.filter(status_id=1): #status_id==1 Обычно соответствует новой задаче
#но вы можете воспользоваться отладочными строками ниже для определения своего id


# Строчки ниже могут пригодиться для отладки и проверки скрипта
# print(str(issue.id)+' '+str(issue))
# print(str(issue.status.id)+' '+str(issue.status))
# print(getattr(issue, 'assigned_to', None))
if getattr(issue, 'assigned_to', None)==None: #проверяем, что задача никому не назначена
count+=1 #считаем неназначенные
message=message+str(issue)+' в проекте '+str(issue.project)+': http://redmine.domain.ltd/issues/'+str(issue.id)+'\n'


if count>=1:
message='Новых неназначенных задач '+str(count)+'. Вы можете посмотреть их список ниже: \n'
print(message)
messagesend(CHATID, message)

Написанному скрипту даем права на исполнение и запихиваем в cron по вкусу.

В результате получаем следующее: как только скрипт запускается, он находит все задачи с
status_id==1
, что по дефолту соответствует новой задаче, и пробегая по всем задачам, обнаруживает те, у кого нет исполнителя:
getattr(issue, 'assigned_to', None)
. Если вам необходимо посмотреть вообще все задачи, то начало цикла примет вид
for issue in  redmine.issue.all():
и он переберет все задачи в принципе. Полезно попробовать так сделать, используя закомментированные выше print. Относительно полный список методов, функций и объектов либы можно найти на сайте ее разработчиков, который элементарно гуглится.

И как всегда, скрипты и текст в более удобном виде доступны у меня в вики - pubwiki.mindsellers.ru
#python #redmine #telegram
Здравствуйте, дорогие подписчики!

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

В компании, где я сейчас работаю, освободилась вакансия linux-админа. Работа полностью удаленная, график работы 5/2, но подразумевает периодические дежурства в режиме "не пропусти пиздец". Оформление полностью по ТК, предусмотрен испытательный срок. ЗП на испыталку - 60к. Основные задачи связаны с поддержкой текущей инфраструктуры(сервера арендованные, на них развернуто порядка 80 виртуальных машин, используется Proxmox), внедрением новых сервисов, обеспечением мониторинга(Zabbix)

Основные требования:
* опыт работы с Linux от 1 года
* понимание принципов работы основных сетевых сервисов(DNS, DHCP, VLAN, VPN и тд)
* опыт написания простых скриптов на bash и/или python
* ну там всякая исполнительность, ответственность и тд

Кому интересно - пишите в личку, лучше сразу присылать резюме

Резюме пока принимаются до вторника, уже во вторник буду списываться-созваниваться с желающими
Дорогие почему-то еще оставшиеся тут товарищи!

1. С наступившим на всех на нас 2022 годом
2. Адрес pubwiki.mindsellers.ru скоро будет деактивирован, останки вики переезжают на wiki.mindsellers.ru в связи со сменой сервака и хорошим настроением
3. За последние месяцы мне есть что добавить в вики, чем и займусь в ближайшее время
а вот вам и простенькое

Простая реализация любого API на Python и на коленке за 5 минут

В статье в основном код, не имеет смысла валить его сюда, так что милости прошу в вики

https://wiki.mindsellers.ru/index.php?title=%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%B0%D1%8F_%D1%80%D0%B5%D0%B0%D0%BB%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_JSON-API_%D0%BD%D0%B0_Bottle

#python
Народ, ищу работу в качестве админа (не девопса) linux и/или телефонии на asterisk. Обширный опыт работы, в том числе в федеральном операторе связи и всероссийских торговых организациях. Естественно, удаленка. Хочется фултайм, но готов и на проектную/парт-тайм работу, статус самозанятого в наличии(даже сбис с ЭЦП есть). Кому есть что предложить - прошу в личку
Тони_Гэддис_Начинаем_программировать_на_Python_5_е_издание_2022.pdf
18.4 MB
Хорошая книжка

Попалась тут хорошая книжка по python. Прям совсем для начинающих. Рекомендую, в том числе и себе, заиметь ее в бумажном виде, но ценник откровенно конский, так что вот вам pdf.

Да, воровать нехорошо. Будем называть это параллельным импортом интеллектуальной собственности
К книжке прилагается архив со всеми примерами, а также видео по каждой главе(видео на английском, но автор говорит очень разборчиво)
Товарищи, кто плотно работал с nextcloud - прошу написать мне в личку, вопросик есть.
Коллеги, кто ещё остался тут из Самары! Требуется админ-универсал: и коннектор обжать, и несколько микротов настроить, и скулевую базу сдампить. Из минусов: надо еблом торговать в офисе и иногда нажимать эникей, а также изредка гонять в краткие командировки.
Есть возрастной ценз - до 35-40 лет - заморочь хозяина
Из плюсов: начальник конторы - сам бывший админ, и всю инфру строил он, работу лишнюю никто не выдумывает: всё работает - сиди кури бамбук. Цена вопроса обсуждаема. Резюме в личку сразу с зарплатными ожиданиями.