5 ноября 2009 г.

Как сделать deb-пакет со своими скриптами

Рано или поздно у продвинутого пользователя линукса накапливается набор собственных скриптов, хранящихся где-то в «~/bin». Но каталог с программами не так красиво смотрится в домашнем каталоге, да и потом если делиться с друзьями, то приходится отправлять в архиве, говорить куда что копировать, убеждать их, что загромождение домашнего каталога — неизбежное зло. Можно, конечно и куда-то в «/usr/local/bin» их поместить, но можно потратить десять минут и собрать пакет, который потом легко устанавливается (и удаляется) штатными средствами. Причём туда, куда надо.

Посмотрим, как это делается.

В качестве примера скриптов возьмем, например следующие два.

Первый загружает цитаты с lorquotes.ru.
#!/bin/sh
[ -x ~/.lor ] || mkdir ~/.lor
wget http://lorquotes.ru/fortraw.php -O -| iconv -f koi8-r -o ~/.lor/lor
strfile ~/.lor/lor ~/.lor/lor.dat
А второй — выводит и при помощи утилиты fortune.
#!/bin/sh
fortune ~/.lor
Назовем их соответственно «lor-update» и «lor».

Сохраним их в отдельный каталог, который при сборке пакета должен иметь имя вида «пакет-версия». В нашем случае это будет «lorquotes-0.1».

Теперь создадим заготовку пакета при помощи команды dh_make (есть в репозитории под именем — сюрприз! — «dh-make»). Для этого в нашем каталоге выполним следующую команду:
dh_make --createorig -c gpl3 -e user@example.com -i -p lorquotes_0.1
Довольно длинно, не так ли? Разберём по порядку:
--createorig — создать отдельную папку с исходниками, это будет нужно при создании пакета;
-c gpl3 — указать в качестве лицензии GPL3 (какие ещё есть лицензии ,можно посмотреть в man-страничке утилиты dh_make);
-e user@example.com — электронная почта собравшего пакет (то есть ваш);
-i — говорим, что пакет не зависит от платформы (bash-скрипты везде одинаковы ведь);
-p lorquotes_0.1 — указываем имя пакета (вообще, необязательно).

Утилита спросит, всё ли она правильно поняла, на что мы честно соглашаемся и заходим в папку.

Как видим, старое содержимое скопировалось в каталог «lorquotes-0.1.orig» (спасибо опции --createorig), а в «lorquotes-0.1» появился каталог «debian» с кучей файликов непонятного назначения. Собственно, содержимое этого каталога и определяет как будет вести себя пакет при установке и что он будет содержать.

Но сперва нужно создать мейкфайл. Это специальный файл, имеющий имя Makefile и говорящий сборщику пакета, как скомпилировать исходнуе тексты и куда устанавливать его содержимое. Структура нашего мейкфайла очень проста:
DESTDIR =
BIN = $(DESTDIR)/usr/bin

build:
true

install:
install -d $(BIN)
install -m755 lor $(BIN)
install -m755 lor-update $(BIN)
Заметьте, что некоторые строки набраны с отступом при помощи табуляции. Именно так и нужно набирать. Это важно!

Мейкфайл — это просто предписание команде «make» выполнить определенный набор действий.

В строке 1 определены специальные переменные: DESTDIR — каталог, в который будет устанавливаться наш пакет. Эта переменная обязательно должна быть в мейкфайле, так как черех неё потом сборщик пакета указывает каталог назначения.

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

Строка с «build» — это одна из целей, которых должен достичь make. После двоеточия можно было указать список целей, от которых цель «build» зависит, но у нас и компилировать-то нечего, потому ничего и не указано. После цели идут команды (внимание, с отступом!), которые необходимо выполнить для достижения этой цели. Так как ничего выполнять не надо, то там стоит команда true, которая ничего не делает (такая у неё работа).

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

Итак, создаем в папке со скриптами (не в debian) мейкфайл под именем Makefile. Теперь мы можем при помощи команд «make» (ничего не делает, т.к выполнится первая цель в файле, т.е. «build») и «sudo make install» (копирует скрипты куда надо) собрать и установить вручную наши скрипты. Но вручную нам не нужно, мы ведь пакет хотим собрать. Поэтому переходим к следующему этапу.

Настроим свойства нашего пакета. Перейдём в каталог «debian» и откроем файл «control». Он содержит информацию о пакете, его зависимостях и так далее. Заполним его как анкету.

Source — название того, что мы сейчас собираем,
Section — в какую секцию дерева репозитория поместить наш пакет,
Priority — важность пакета,
Maintainer — сборщик,
Build-Depends — какие программы нужны для сборки пакета,
Standards-Version — версия стандарта, описывающего сборку пакетов,
Homepage — домашняя страничка (если есть),
Package — имя собственно deb-пакета,
Architecture — архитектура, под которую он собирается (i386, amd64 и иже с ними),
Depends — от каких пакетом наш зависит,
Description — описание, короткое в той же строке и длинное ниже (обратите внимание на пробел в начале строки с длинным описанием).

Я для примера сделал такой файл:
Source: lorquotes
Section: misc
Priority: optional
Maintainer: Vasiliy Pupkin
Build-Depends: debhelper (>= 7)
Standards-Version: 3.8.3
Homepage: http://pupkin.example.com/lorquotes/

Package: lorquotes
Architecture: all
Depends: wget,fortunes
Description: Small LOR quotes utility
Shows you random quote from lorquotes.ru.
Думаю, указывать в зависимостях шелл и libc6 смысла нет. Они точно есть в любой системе (или нет?).

Теперь подправим файл «changelog». Он содержит историю версия нашего пакета. Во время правки не забывайте, что он тоже имеет строгий формат и не добавляйте лишние отступы и строки.

Вот мой вариант:
lorquotes (0.1-1) unstable; urgency=low

* Initial release

-- Vasiliy Pupkin Thu, 05 Nov 2009 19:53:58 +0200
unstable — это имя ветки дистрибутива, для которой готовится пакет, а urgency — это приоритет.

В том же каталоге есть файл «rules». Это обычный мекфайл, который и отвечает за конфигурирование и сборку пакета. Но так как мы ничего экстраординарного не делали, оставим всё как есть.

В файле «copyright» находится лицензия. Мы лицензию сразу указали, так что и этот файл трогать не будем.

Итак, остались ещё следующие важные для нас файлы.

«cron.d.ex». Переименовываем его в «cron.d». Он содержит информацию о регулярных действиях, которые будет выполнять установленная программа. В нашем случае — ежедневное обновление базы цитат в полдень:
#
# Regular cron jobs for the lorquotes package
#
0 12 * * * root [ -x /usr/bin/lor-update ] && /usr/bin/lor-update


«postrm.ez». Переименовываем в «postrm». Это скрипт, запускаемый после удаления пакета. Заставим его очищать нашу базу цитат, хранящуюся в «~/.lor». Хотя хранить базу в домашнем каталоге не совсем верно идеологически. Но, думаю, после этого поста читатель и сам исправит эту неточность. Итак, мой «postrm»:
#!/bin/sh
# postrm script for lorquotes
#
# see: dh_installdeb(1)

set -e

case "$1" in
purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
rm -rf ~/.lor
;;

*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac

# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.

#DEBHELPER#

exit 0
Остальные файлы нам не нужны и смело их удаляем. Это разные заготовки. Например, для man-страниц.

Переходим теперь в каталог со скриптами (т.е. на уровень выше) и даём команду на сбору пакета:
debuild
Она собирает пакет, проверяет на ошибки, а потом пробует его подписать вашим GPG-ключом. Но даже если ей подписать не удастся, пакет будет собран. Найти его можно будет в каталоге уровнем выше. Там же будет архив с исходниками и вспомогательные файлы, нужные, если вы хотите создать свой репозиторий или загрузить пакет в уже существующий.

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

Вот и всё! Осталось только установить пакет. :)

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

А вот архив с исходниками и пакетом.

Читать дальше…

2 ноября 2009 г.

Отправка email из командной строки

О том как отправлять сообщения через jabber, я уже написал. А вот сегодня мне захотелось написать утилитку, которая бы делала то же самое и с теми же (примерно) параметрами командной строки, но для электронной почты. Конечно, в линуксе полно консольных почтовых программ (я в курсе, что есть команда mail), но мне не нужны все их возможности, а кроме того, хотелось бы ещё и кроссплатформенности (я в курсе, что в Windows есть утилита blat) и возможности быстро самому что-то дописать. Вот, что получилось.

Не буду тянуть, сразу приведу код скрипта.
#!/usr/bin/env python
import smtplib, ConfigParser, email.mime.text, getopt, os, sys, locale

name = os.path.basename(sys.argv[0])

optlist, args = getopt.getopt(sys.argv[1:], 's:f:')
if len(args) < 2:
print "Send a message via email"
print
print "Syntax:", name, "[-f from] [-s subject] email text"
print "email\trecipient's e-mail address"
print "from\tsender's email"
print "subject\tmessage subject"
print "text\tmessage, use '-' for stdin"
sys.exit(0)

configFile = os.path.join(os.environ['HOME'], '.' + name)
config = ConfigParser.RawConfigParser()
try:
config.read(configFile)
fromEmail = config.get('From', 'E-mail')
smtpServer = config.get('SMTP', 'Server')
smtpLogin = config.get('SMTP', 'Login')
smtpPassword = config.get('SMTP', 'Password')
except:
print "Default configuration was written to", configFile
config.add_section('From')
config.set('From', 'E-mail', 'user@example.com')
config.add_section('SMTP')
config.set('SMTP', 'Server', 'smtp.example.com')
config.set('SMTP', 'Login', 'user')
config.set('SMTP', 'Password', '********')
f = file(configFile, 'w')
config.write(f)
f.close()
sys.exit(0)

toEmail = args[0]
subject = ''
for option, value in optlist:
if option == '-s':
subject = value
elif option == '-f':
fromEmail = value

encoding = locale.getpreferredencoding()
if args[1] == '-':
message = email.mime.text.MIMEText(sys.stdin.read(), 'plain', encoding)
else:
message = email.mime.text.MIMEText(' '.join(args[1:]), 'plain', encoding)

message['Subject'] = subject
message['From'] = fromEmail
message['To'] = toEmail

server = smtplib.SMTP(smtpServer)
server.login(smtpLogin, smtpPassword)
server.sendmail(message['From'], message['To'], message.as_string())
server.quit()
Всё это сохраняем в файл с именем, например, «mailto» и даём права на выполнение:
chmod +x mailto
После первого создаётся файл с настройками подключения. По умолчанию это файл «.mailto» (если скрипт называется «mailto», конечно) в домашнем каталоге. В нём нужно указать адрес отправителя и настройки smtp-сервера.

Параметров у скрипта минимум, только то, что нужно. Есть два опциональных для указания темы сообщения и адреса отправителя (по умолчанию берётся из файла с настройками). Для них используются ключи «-s» и «-f» соответственно (от слов «From» и «Subject»).

Обязательных параметров всего два и они без ключей. Это адрес получателя собственно текст сообщения. Если вместо текста поставить «-», то текст будет браться из стандартного входа, что удобно для отправки результатов вывода какой-то программы.

Использовать программу можно для чего угодно. Например, чтобы отправить список пользователей себе на ящик можно воспользоваться командой:
who | mailto user@example.com -
Я, например, использую этот скрипт, чтобы автоматически в определённое время слать себе СМС с напоминанием о чём-либо через гейт «email—SMS» моего мобильного оператора. Получилось удобно.

Читать дальше…

1 ноября 2009 г.

Отправка jabber-сообщений из командной строки

О том, как отправить jabber-сообщение из консоли при помощи клиента Gajim, я уже писал ранее. Тем не менее, использование gajim-remote имеет ряд недостатков: нужно установить сам Gajim и запустить его. Это может вызвать некоторые затруднения, например, на сервере, где вовсе может не быть графического интерфейса. Однако, проблема имеет и более простое решение.

Сделаем скрипт, которому бы в качестве параметров передавались данные учётной записи jabber, JID отправителя и текст сообщения. В качестве основы возьмём питоновскую библиотеку xmpppy. В частности, рассмотрим повнимательнее один из примеров, входящих в её комплект, который (сюрприз!) как раз и предназначен для отправки сообщений из консоли. Остаётся только его чуть-чуть «допилить» по вкусу.

Итак, нам потребуется библиотека. Они есть в репозиториях Debian (да и других популярных дистрибутивов), поэтому устанавливаем как обычно:
sudo aptitude install python-xmpp python-dnspython
Без python-dnspython работать тоже будет, кстати. Но лучше поставить, так как в этом случае придётся вводить точное имя узла, на котором располагается сервер.

А вот и сам скрипт:
#!/usr/bin/env python
import sys,os,xmpp,time,ConfigParser

name = os.path.basename(sys.argv[0])

if len(sys.argv) < 2:
print "Send a message via Jabber (XMPP)"
print "Syntax:", name, "JID text"
print "JID\trecipient's Jabber ID"
print "text\tmessage, use '-' for stdin"
sys.exit(0)

toJID = sys.argv[1]
if sys.argv[2] == '-':
text = sys.stdin.read()
else:
text = ' '.join(sys.argv[2:])

configFile = os.path.join(os.environ['HOME'], '.' + name)
config = ConfigParser.RawConfigParser()

try:
config.read(configFile)
fromJID = config.get('Connection', 'JID')
password = config.get('Connection', 'Password')
except:
print "Default configuration was written to", configFile
config.add_section('Connection')
config.set('Connection', 'JID', 'user@example.com/home')
config.set('Connection', 'Password', '********')
f = file(configFile, 'w')
config.write(f)
f.close()
sys.exit(0)

fromJID = xmpp.protocol.JID(fromJID)
client = xmpp.Client(fromJID.getDomain(),debug=[])

conn = client.connect()
if not conn:
print 'Could not connect!'
sys.exit(1)
print 'Connected with', conn

auth = client.auth(fromJID.getNode(), password, resource=fromJID.getResource())
if not auth:
print 'Could not authenticate!'
sys.exit(1)
print 'Authenticated using', auth

messageID = client.send(xmpp.protocol.Message(toJID, text))
print 'Message was sent'

time.sleep(1)
client.disconnect()
Скрипт довольно простой, так что разбирать его работу не буду.

Всё это сохраняем в каталог «~/bin» под именем, например, «jsend». Имя и каталог значения не имеют, просто «~/bin» удобен для хранения пользовательских скриптов, так как он входит в переменную PATH и для запуска скритов из него достаточно указать их имена. В любом случае нужно разрешить выполнение нашей программки:
chmod +x jsend
Рассмотрим её работу. После первого запуска утилита создаёт в домашнем каталоге файл с JID и паролем отправителя. Это удобнее, чем вводить пароль в качестве параметра командной строки, так как в этом случае никто не сможет его подсмотреть. Да и доступ к файлу можно будет закрыть. Файл поличит имя, совпадающее с именем скрипта, но с точкой впереди, чтобы не отображаться лишний раз в списке файлов. В моём случае имя будет «~/.jsend». Естественно, файл после первого запуска нужно будет открыть и впесать свои JID и пароль.

Формат запуска скрипта:
jsend JID text
JID — это идентификатор получателя сообщения, а text — это собственно текст, который нужно отправить.

Если текста много или нужно отправить вывод какой-либо команды, то вместо text можно поставить «-». Например, команда
df | jsend user@example.com -
отправит информацию о заполненности разделов на дисках пользователю user@example.com.

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

Читать дальше…