вторник, 30 декабря 2008 г.

Lightweight wiki engine

У всех популярных wiki-движков есть один существенный недостаток - они предлагают редактировать контент с помощью браузера. Этот недостаток давно и успешно лечится, однако осадок остается. Действительно, зачем все эти извращения в тех случаях, когда список авторов ограничен и все они имеют shell-доступ к серверу, на котором и живет wiki engine? Пусть редактируют вволю с помощью своего любимого текстового редактора (заодно и какую-нибудь систему управления версиями можно прикрутить), а wiki engine пусть всего лишь отдает текст, отформатированный в html.

Концепт такого движка в виде автономного http-сервера можно можно посмотреть здесь.

вторник, 25 ноября 2008 г.

ALTSP inside OpenVZ VE

Требуется обеспечить всем необходимым (доступ в интернет, почта, офисные приложения и учетное ПО) небольшой офис, в котором имеется один приличный сервер и десяток бездисковых рабочих станций. Задача прекрасно решается средствами ALTSP, однако есть одна проблема - очень уж неудобно держать на одном хосте офис, почтовый сервер, прокси-сервер и т.д. Решение - OpenVZ, возможно, с размазыванием функциональности ALTSP по разным VE.

Первый вопрос, на который следует ответить: будем ли мы использовать venet, veth или вообще проброс физических интерфейсов в VE. Первый вариант, конечно, bit faster and more efficient, но не умеет броадкастов, а потому всю инфраструктуру, необходимую для загрузки бездисковых клиентов придется разместить в HN. В дальнейшем предполагаем, что используется OpenVZ в ALT Linux Lite 4.0 c виртуальной сетью 192.168.0.0/24 и физической сетью 192.168.1.0/24 - в последней и будут размещены бездисковые клиенты.

Настраиваем загрузку клиентов по TFTP:
# apt-get install dnsmasq

# chkconfig dnsmasq on

# cat /dev/null > /etc/sysconfig/dnsmasq

# cat > /etc/dnsmasq.conf <<END
bind-interfaces
interface=lo
interface=lan
resolv-file=/etc/resolv.conf.dnsmasq
strict-order
expand-hosts
domain=local
dhcp-range=192.168.1.10,192.168.1.50
enable-tftp
tftp-root=/diskless/altsp/boot/i586
dhcp-boot=pxelinux.0
END

# cat > /etc/resolv.conf.dnsmasq <<END
nameserver ${NAMESERVER}
END

# cat > /etc/resolv.conf <<END
nameserver 127.0.0.1
search local
END

# mkdir -p /diskless/altsp/boot/i586/pxelinux.cfg

# cat > /diskless/altsp/boot/i586/pxelinux.cfg/default <<END
DEFAULT vmlinuz ro initrd=initrd.img root=/dev/nfs nfsroot=/diskless/altsp/root/i586,udp ip=dhcp
END

# service dnsmasq start
В /diskless/altsp/boot/i586 нужно положить vmlinuz, initrd.img и pxelinux.0 из уже существующей инсталляции ALTSP (см. каталог /var/lib/tftpboot/ltsp/i586/) - увы, автономного инструмента для сборки пока нет :(

Уже на этом этапе загрузка бездисковых клиентов начнется, но остановится на монтировании корневой файловой системы. Настроим раздачу корня по NFS:
# apt-get install unfs3

# chkconfig nfs on
# chkconfig portmap on

# cat > /etc/exports <<END
/diskless/altsp/root/i586 (ro,no_root_squash)
END

# service portmap start
# service nfs start
В /diskless/altsp/root/i586 тоже нужно положить каталог /var/lib/ltsp/i586 из существующей инсталляции ALTSP.

Также включим поддержку сетевого свопа на случай непредвиденного расхода памяти на клиентах:
# apt-get install ltspswapd
# chkconfig ltspswapd on
# service ltspswapd start
Теперь бездисковые клиенты смогут полностью загрузиться и обратиться по XDMCP к тому же серверу, с которого загрузились, но безуспешно: никакого XDMCP там нет и не предполагается. Сначала укажем адрес сервера XDMCP:
# cat >> /diskless/altsp/root/i586/etc/lts.conf <<END
XDM_SERVER=192.168.0.101
END
Затем создадим соответствующий виртуальный сервер с помощью скрипта-обертки, описанного ранее:
# create-ve.sh 101 desktop 4.1 i586
Установим в него все необходимое:
# vzctl start desktop
# vzctl enter desktop

# cat > /etc/sysconfig/i18n <<END
LANG=ru_RU.UTF-8
SUPPORTED=ru_RU
END

# cat > /etc/X11/xinit/Xkbmap <<END
-layout us,ru
-option grp:ctrl_shift_toggle
-variant ,winkeys
END

# apt-get install gdm gdm-theme-altlinux xfce4-minimal xfce-mcs-plugins xfce-settings-simple icon-theme-hicolor xterm Thunar leafpad firefox-ru fonts-ttf-ms

# subst 's/happygnome-list/altlinux/' /etc/X11/gdm/custom.conf
# subst 's/Greeter/RemoteGreeter/' /etc/X11/gdm/custom.conf
# subst '/\[greeter\]/aGraphicalThemedColor=#90a8ca' /etc/X11/gdm/custom.conf
# subst '/\[xdmcp\]/aEnable=true' /etc/X11/gdm/custom.conf
# subst 's/id:3:initdefault:/id:5:initdefault:/' /etc/inittab

# exit

# vzctl restart desktop
И вот теперь бездисковые рабочие станции должны загрузиться и показать приглашение GDM.

понедельник, 24 ноября 2008 г.

OpenVZ в ALT Linux Lite 4.0

Так сложилось, что даже для инсталляций с OpenVZ мне удобнее использовать ALT Linux 4.0 Server Lite (как раз сегодня пересобрал все iso на свежей пакетной базе branch/4.0) вместо официального ALT Linux 4.0 Server.

Для запуска OpenVZ необходимо и достаточно сказать:
# apt-get install kernel-image-ovz-smp vzctl
После перезагрузки с новым ядром можно создавать виртуальные машины (VE) следующим простым скриптом-оберткой:
#!/bin/sh

# проверяем параметры
if [ -z "$4" ]; then
  echo "Usage : $0 \$VEID \$NAME \$BRANCH \$ARCH"
  exit 1
fi

# определяем переменные
VE_NAMESERVER="192.168.1.1"
VE_DOMAIN="local"
VE_NET="192.168.0."
VE_REPO="/repo"
HN_REPO="/lvm/distrib/free/linux/alt"

# создаем VE
vzctl create $1 --ostemplate altlinux-$3-$4

# задаем дефолтные значения для VE
vzctl set $1 --name $2 --ipadd $VE_NET$1 --hostname $2.$VE_DOMAIN \
  --nameserver $VE_NAMESERVER --searchdomain $VE_DOMAIN --onboot yes --save

# создаем каталог с репозитарием внутри VE
mkdir /var/lib/vz/private/$1/repo

# создаем скрипт, который будет монтировать репозитарий из HN
cat > /etc/vz/conf/$1.mount <<END
#!/bin/sh

. /etc/vz/vz.conf

mount -n -o bind $HN_REPO/$3 \$VE_ROOT/repo
END

# делаем скрипт исполняемым
chmod 700 /etc/vz/conf/$1.mount

# настроиваем apt
cat > /var/lib/vz/private/$1/etc/apt/sources.list <<END
rpm file:///repo/branch/ $4 classic
rpm file:///repo/branch/ noarch classic
END
Многое здесь прибито гвоздями: например, предполагается, что в /lvm/distrib/free/linux/alt находятся копии branch/4.0 и branch/4.1, а в /var/lib/vz/template/cache/ - собранные из бранчей с помощью Hasher шаблоны VE:

altlinux-4.0-i586.tar.gz
altlinux-4.0-x86_64.tar.gz
altlinux-4.1-i586.tar.gz
altlinux-4.1-x86_64.tar.gz

В шаблонах находятся basesystem, apt, sysklogd, etcnet, glibc-nss, glibc-locales, netlist, passwd, su, openssh-server, vim-console, mc, less, man и все, что было вытянуто по зависимостям.

Предполагается, что свежесозданные VE будут запущены с помощью vzctl start и первоначально настроены с помощью vzctl enter (т.е. будут созданы необходимые пользователи и пароли/ключи) - далее с ними можно будет работать обычным образом по ssh, сверяясь по мере надобности с полезными советами.

Впрочем, все это локальные хаки. Планов по опакечиванию нет, т.к. Anton Protopopov потихоньку делает mkve - гораздо более продвинутый инструмент аналогичного назначения, которым также будет пользоваться Alterator.

среда, 17 сентября 2008 г.

Userspace routing policy

Задача: построить маршрутизатор, который пропускал бы хосты в некоторую сеть (например, в интернет) на основании т.н. "билетов". В билете указывается максимальный объем входящего трафика, по исчерпании которого билет изымается и доступ запрещается, однако на тот же хост через некоторое время можно выписать новый билет.

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

Устанавливаем все необходимое:
# apt-get install python-module-cxnet python-module-MySQLdb MySQL-server
# service mysqld start
Создаем базу данных и таблицу, в которой будем хранить билеты, выписываем первый билет:
$ mysql -u root
...
mysql> create database cx;
Query OK, 1 row affected (0.00 sec)
mysql> use cx;
Database changed
mysql> create table tickets (
->         id        serial,
->         host      varchar(50),
->         bytes     bigint,
->         max_bytes bigint,
->         enabled   boolean
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> insert into tickets(host, bytes, max_bytes, enabled) values ('192.168.100.103', 0, 100, true);
Query OK, 1 row affected (0.00 sec)
mysql> select * from tickets;
+----+-----------------+-------+-----------+---------+
| id | host            | bytes | max_bytes | enabled |
+----+-----------------+-------+-----------+---------+
|  1 | 192.168.100.103 |      0|       100 |       1 |
+----+-----------------+-------+-----------+---------+
1 row in set (0.00 sec)
Входящие пакеты для хоста 192.168.100.103 завернем в userspace следующим образом:
# modprobe ip_queue
# iptables -A FORWARD -p icmp -d 192.168.100.103 -j QUEUE
А обрабатывать эти пакеты и решать, пропустить или нет, мы будем следующей программой, использующей один из компонентов connexion - библиотеку cxnet:
#!/usr/bin/python

from cxnet.netlink.ipq import *
from cxnet.ip4 import *
from cxnet.utils import *
import MySQLdb

db = MySQLdb.connect(host="localhost", user="root", passwd="", db="cx")
cursor = db.cursor()

ipqs = ipq_socket()

while True:
  (l,msg) = ipqs.recv()
  header = iphdr.from_address(addressof(msg.data.payload))
  if header.daddr>0:
    host = int_to_dqn(header.daddr)
    size = header.tot_len
    print "packet: %s:%s" % (host,size)
    cursor.execute("update tickets set bytes = bytes+%s where enabled = true and host = %s", (size, host))
    cursor.execute("update tickets set enabled = false where enabled = true and bytes >= max_bytes")
    cursor.execute("select count(*) from tickets where enabled = true and host = %s", host)
    for record in cursor.fetchall():
      print "count: %s" % record[0]
      if record[0] > 0:
        ipqs.verdict(msg.data.packet_id, NF_ACCEPT)
        continue
    ipqs.verdict(msg.data.packet_id, NF_DROP)
Теперь отправляем первый пинг с хоста 192.168.100.103 наружу:
$ ping -c 1 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=1.12 ms

--- 192.168.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.121/1.121/1.121/0.000 ms
При этом билет изменится следующим образом:
mysql> select * from tickets;
+----+-----------------+-------+-----------+---------+
| id | host            | bytes | max_bytes | enabled |
+----+-----------------+-------+-----------+---------+
|  1 | 192.168.100.103 |    84 |       100 |       1 |
+----+-----------------+-------+-----------+---------+
1 row in set (0.00 sec)
Отправим следующий пинг:
$ ping -c 1 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.

--- 192.168.1.1 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
При этом билет будет заблокирован, поскольку число принятых байт превысило лимит:
mysql> select * from tickets;
+----+-----------------+-------+-----------+---------+
| id | host            | bytes | max_bytes | enabled |
+----+-----------------+-------+-----------+---------+
|  1 | 192.168.100.103 |   168 |       100 |       0 |
+----+-----------------+-------+-----------+---------+

1 row in set (0.00 sec)
Работает! Правда, тормозить должно жутко, ибо задавать кучу вопросов СУБД (пусть даже и MySQL с дефолтным хранилищем MyISAM, которое и транзакций-то не умеет) на каждый входящий пакет - удовольствие не дешевое. Но о бенчмарках в следующий раз ...

Новые сборки ALT Linux 4.0 Server Lite

Обновлены сборки ALT Linux 4.0 Server Lite. В них добавлен connexion - пока (и, возможно, навсегда) все же не по дефолту, а в дополнительный репозитарий. Обратите внимание, что опакеченная документация в данный момент актуальнее той, что предлагается на официальном сайте.

Также сделана специальная сборка без дополнительного репозитария для тех, кому нужен маленький iso. Он получился размером в 184M, можно и еще урезать, выкинув rescue, но мне жалко его выкидывать.

Итого:

для i586
для x86_64
для i586 без дополнительного репозитария
для x86_64 без дополнительного репозитария

среда, 27 августа 2008 г.

Lightweight JEE

Расскажу о том, каким образом я разрабатываю на Java серверные приложения, у которых, как правило, несложный web-интерфейс и довольно замороченная внутренняя логика, в основном из-за необходимости общаться с внешними, иногда довольно "странными" системами (отрасль - телеком, если это кому-то что-то скажет ;) ). Число активных разработчиков от 1 до 2 :) - таким образом, с одной стороны, можно представить себе масштабы проектов, а с другой стороны - разрушить миф о том, что "для разработки на Java нужно много индусов".

Разумеется, я не использую тяжелые сервера приложений вроде JBoss и тем более WebSphere или WebLogic. Более того, я не использую Tomcat. В моем случае каждое приложение живет в отдельной JVM - примерно так же как OpenFire или XWiki.

Исходный код минимального приложения, которое ничего не делает, а просто пишет в лог сообщения о запуске и о завершении работы, можно взять здесь. Все необходимые для работы приложения библиотеки включены в проект и находятся в каталоге lib, рядом в каталоге lib-build также находятся библиотеки для сборки проекта - у вас должен быть установлен только JDK. В результате сборки получается архив, в который все включено - для запуска приложения вам потребуется только JRE.

Для тех, кому грустно тащить лишние мегабайты jar-ов, на http://enp.itx.ru/java/examples/ рядом с каждым *App.zip лежит *App.NoLib.zip.

IDE для работы в принципе не обязательна, однако без нее навигация по коду будет сильно затрудена. Можно использовать любую IDE с поддержкой Java, XML и JavaScript - я использую Eclipse IDE for Java EE Developers

Для сборки приложения используется Ant, который читает инструкции из файла build.xml. На UNIX сборка запускается с помощью build.sh, на Windows - с помощью build.bat - эти файлы можно переносить из одного проекта в другой без изменений. По умолчанию при сборке выполняется цель all, которая последовательно выполняет цели clean,prepare,compile,jar,dist. Цель можно указать явно как параметр в командной строке. Можно также увидеть список целей с помощью ключа -p.

При сборке генерируются стартовые скрипты statup.sh для запуска на UNIX и statup.bat для запуска на Windows, а также инит-скрипт в стиле SysV для работы приложения в качестве юниксового демона. Для генерации написан специальный ant task - его исходный код здесь. Исходные данные для генерации (краткое и полное имя приложения, имя главного класса) берутся из build.xml.

Главный и единственный класс приложения - startup.ServiceApp. Он имеет стандартную точку входа для запуска в качестве обычного java-приложения - метод main, а кроме этого реализует интерфейс org.apache.commons.daemon.Daemon из библиотеки Jakarta Commons Daemon - и таким образом может быть запущен в качестве сервиса UNIX System V с помощью jsvc и в качестве сервиса Windows c помощью procrun. Для реализации первой возможности и используется автоматически генерируемый при сборке инит-скрипт, который предполагает наличие установленного jsvc (в репозитариях ALT Linux эта утилита находится в одноменном пакете).

Для протоколирования используются библиотеки Commons Logging в качестве фасада и Log4j в качестве реализации.

Пока что все описанное сильно напоминает подъем солнца вручную. Т.е. можно, конечно, на этом остановиться и писать прикладную логику в виде java-классов без задействования всех преимуществ enterprise-технологий. Более того, новичкам я бы даже рекомендовал какое-то время воздержаться от их использования. Например, мы хотим, чтобы наше приложение научилось отвечать на http-запросы. Для этого мы просто задействуем встроенный в Java 1.6 http-сервер - исходный код нового приложения можно взять здесь.

А вот теперь, после некоторой передышки, давайте задействуем Spring Framework. Если честно, я затрудняюсь объяснить в двух словах, зачем он нужен. Он умеет слишком много, но все use cases можно условно свести к двум случаям:
  • упрощение взаимодействия модулей системы друг с другом с помощью таких техник, как IoC и AOP и перенос взаимодействия в рантайм для повышения гибкости
  • упрощение работы с JDBC, ORM, JMX, JMS, Web Services и прочими полезными технологиями
Исходный код простого приложения, использующего Spring, можно взять здесь. Главный класс startup.SpringApp инициализирует контекст на основании файла context.xml или других файлов, указанных в командной строке при запуске приложения, и закрывает его при завершении работы приложения. Контекст - это и есть описание модулей (бинов) в формате xml. В нашем случае будут инициализированы бины fakeBookLoader и xmlInputStreamBookLoader, реализующие интерфейс beans.BookLoader (т.е. умеющие каким-либо способом загружать экземпляры класса beans.Book), и главный бин bookManager. Последний при инициализации получит ссылки на экземпляры beans.BookLoader, а после инициализации Spring вызовет его метод displayBooks. Подробности в документации Spring.

Теперь усложним приложение по двум направлениям:
  • реализуем извлечение экземпляров класса beans.Book из реляционной БД - исходный код приложения берем здесь
  • реализуем web-интерфейс к фунциональности бина bookManager - а это берем здесь
Первый пример использует 3 способа работы работы с СУБД (и, соответственно, 3 реализации beans.BookLoader, используемые бином bookManager):
  • JDBC без управления транзакциями (каждая операция выполняется в отдельной транзакции) - для этого в контексте определены бины dataSource (параметры подключения вынесены в отдельный файл db.properties, т.к. они же используются еще и в build.xml для создания таблиц в БД) и jdbcBookLoader
  • JPA (стандарт ORM для Java) с ручным управлением транзакциями - для этого определены бины entityManagerFactory (фабрика реализаций интерфейса javax.persistence.EntityManager, управляющих сохранением/извлечением объектов в/из БД) и jpaBookLoader
  • JPA с автоматическим управлением транзакциями - для этого используются бин transactionManager, специальные тэги <tx:advice>, <aop:config> и <context:annotation-config>, а также бин jpaTransactionalBookLoader (для него при вызове каждого метода Spring автоматически создает экземпляр EntityManager, стартует транзакцию и завершает ее после выполненения метода или откатывает в том случае, если при выполнении метода возникло исключение).
Класс beans.Book аннотирован для того, чтобы EntityManager знал, каким образом его экземпляры нужно сохранять/извлекать в/из БД. Кроме того, в файле META-INF/persistence.xml описано, какой провайдер JPA и с какими параметрами будет использоваться. В данном случае используется Hibernate, который помимо стандартного JPA API имеет также собственный API, пример использования можно взять здесь, рядом лежит более простой пример использования Hibernate без Spring, а тут - пример совсем простого приложения для работы с БД без всяких фреймворков с использованием аналога спрингового уровня абстракции над JDBC - Jakarta Commons DbUtils.

Теперь о приложении с web-интерфейсом для бина bookManager. Как правило, такого рода приложения пакуются в WAR и размещаются в одном из сервлет-контейнеров. В случае использования Spring контекст инициализируется и закрывается сервлетом. Такая схема представляется вполне логичной для web-ориентированных приложений, особенно в том случае, когда в одном контейнере предполагается размещать множество web-приложений.

В моем случае это не так: web-часть приложения сравнительно небольшая по сравнению с основной логикой, приложение является единственным на хосте, и мне хочется конфигурировать http-сервер в том же контексте, что и все приложение. Чем, в конце концов, мои бины, бины Spring, бины дополнительных библиотек отличаются от бинов http-сервера?

Если бы мне требовалась загрузка и выгрузка модулей без остановки всего приложения, я, возможно, обратил бы внимание на совсем недавно появившуюся SpringSource Application Platform, которая использует стандарт OSGi, однако пока это не нужно, проще описать бины http-сервера прямо в контексте.

Я не пробовал встраивать Tomcat в контекст Spring, Jetty встраивается без проблем, т.к. его основные классы удовлетворяют спецификации JavaBeans (если бы это и было не так, можно было бы написать небольшую обертку), а для встроенного в Java 1.6 http-сервера такая обертка уже есть.

Приложение интенсивно использует технологию AJAX, которая позволяет отделить клиентскую логику от серверной - взаимодействуют они по протоколу JSON-RPC. Существует единственная полная реализация JSON-RPC для Java - jabsorb, но она использует в качестве транспорта сервлеты, не реализованные во встроенном в Java 1.6 http-сервере. Существует также JSON-lib для сериализации JavaBeans в JSON, транспорта там просто нет, но его можно реализовать по аналогии с описанным здесь способом.

Итак, в контексте приложения определен бин httpServer с двумя http-контекстами:
  • staticHttpHandler - выдает статические страницы по имени файла
  • jsonRpcHttpHandler - отвечает на запросы JSON-RPC к бинам, перечисленным в свойстве services
Клиентов целых два: один использует для отрисовки данных стандартные теги html, другой использует библиотеку виджетов ExtJS. Для формирования запросов JSON-RPC оба используют реализацию от jabsorb.

Ну и наконец итоговое приложение BookManager - в нем объединены возможности двух предыдущих: книги сохраняются в БД и извлекаются оттуда, класс tools.JsonRpcHttpHandler переписан и поддерживает базовую http-авторизацию, которая может быть использована двумя способами:
  • для ограничения доступа определенных групп пользователей к сервисам
  • для получения методом сервиса информации о пользователе, который его вызвал
На этом все. Некоторые релевантные обсуждения, долго сподвигавшие и таки сподвигнувшие меня написать этот текст, можно найти здесь (там же жалкие попытки оправдать включение библиотек в состав проекта, более настойчивые попытки объяснить, зачем нужен Spring, а также некоторые уже неактуальные вещи вроде Jetty, вместо которого я сейчас использую встроенный в Java 1.6 http-сервер):

Fax/SMTP gateway для CallWeaver

Задача: отправить факс путем отправки письма с вложенным ps/pdf/tif, принять факс в виде почтового сообщения с вложением. Инструменты - CallWeaver и какой-то враппер для ОGI и Manager API на Python, который я вовремя не опакетил, таская с собой, а теперь уж и забыл, откуда взял (да, я понимаю, что я неправ, но вот только сейчас руки дошли хотя бы до публикации решения). Архив со скриптами можно взять здесь.

Работает оно следующим образом. От локального отправителя письмо с вложением получает MTA и передает его на stdin скрипту fax-send.py. Скрипт делает из файла tiff и с помощью Originate коммутирует вызываемого внешнего абонента (контекст [call-fax-send], если не дозвонились, вызывается fail-fax.py) и факс(контекст [fax-send], о результате прохождения факса отправителю сообщает check-fax.py). Параметры передаются через Variable.

Упомянутые контексты описаны так:
[call-fax-send]
exten => 0,1,NoOp(Calling ${RECIPIENT} for sending fax)
exten => 0,2,Dial(SIP/${RECIPIENT}@${PEER},25)
exten => 0,3,NoOp(${DIALSTATUS})
exten => 0,4,DeadOGI(fail-fax.py)

[fax-send]
exten => 0,1,NoOp(Sending file ${FILE} as fax from ${SENDER} to ${RECIPIENT}@${PEER} with NOTIFY=${NOTIFY})
exten => 0,2,GotoIf($[${NOTIFY}=Yes]?3:6)
exten => 0,3,Playback(fax)
exten => 0,4,Playback(beep)
exten => 0,5,Wait(2)
exten => 0,6,Set(LOCALSTATIONID=CallWeaver)
exten => 0,7,Set(LOCALHEADERINFO=CallWeaver Fax)
exten => 0,8,TxFAX(${FILE})
exten => h,1,NoOp(RX: REMOTESTATIONID is ${REMOTESTATIONID})
exten => h,2,NoOp(RX: PHASEESTATUS is ${PHASEESTATUS})
exten => h,3,NoOp(RX: PHASEESTRING is ${PHASEESTRING})
exten => h,4,DeadOGI(check-fax.py)
Локальный же получатель факсов переадресует внешнего отправителя в контекст [fax-receive], там получившийся файл подбирает read-fax.py, он же сообщает о неудаче.

Контекст для приема факсов:
[fax-receive]
exten => _0.,1,Set(SENDER=${CALLERID(num)})
exten => _0.,2,Set(RECIPIENT=${EXTEN:1})
exten => _0.,3,Set(LOCALSTATIONID=Vertol EXPO)
exten => _0.,4,Set(LOCALHEADERINFO=Vertol EXPO Fax)
exten => _0.,5,Set(FILE=/data/callweaver/fax-receive/${UNIQUEID}.tif)
exten => _0.,6,RxFAX(${FILE})
exten => h,1,NoOp(RX: REMOTESTATIONID is ${REMOTESTATIONID})
exten => h,2,NoOp(RX: PHASEESTATUS is ${PHASEESTATUS})
exten => h,3,NoOp(RX: PHASEESTRING is ${PHASEESTRING})
exten => h,4,DeadOGI(read-fax.py)
Отлов исключений сделан только там, где без него совсем грустно (например, MTA лучше не знать, что внутри fax-send.py приключилось что-то нехорошее). В OGI невозможность удалить файл, например, ни к чему плохому не приводит.

Да, о поддержке факсов в CallWeaver читать здесь.

CallWeaver из коробки

В дополнение к предыдущему сообщению хочу добавить, что ALT Linux 4.0 Server Lite - один из немногих дистрибутивов, в которых CallWeaver работает практически из коробки и не слишком устарел (ну а если вдруг, то я его майнтейнер :) ). После установки достаточно, не вынимая установочного диска, сказать:
# apt-get install callweaver callweaver-sounds freemusic-signate
и прочесть файл /usr/share/doc/callweaver-1.2/QUICKSTART.ru_RU.UTF-8. Процитирую, пожалуй, его содержимое:

Введение
========

CallWeaver - это IP PBX, форк проекта Asterisk, причинами создания которого послужили 
организационные (зависимость от компании Digium, двойное лицензирование) и технические 
(зависимость от zaptel, отсутствие поддержки T.38 и т.д.) проблемы последнего. Подробнее - 
http://www.callweaver.org/wiki/CallWeaver


Описание конфигурации по умолчанию
==================================

CallWeaver реализован в виде загрузчика с минимальной функциональностью и набора модулей 
расположенных в каталоге /usr/lib/callweaver/modules, которые необходимо описать в файле 
modules.conf. В этом файле отключена автозагрузка модулей, а вместо этого явно указаны 
минимально необходимые модули.

Загрузка модуля chan_sip для поддержки протокола SIP по умолчанию закомментирована. 
При загрузке модуль chan_sip читает файл sip.conf, в котором описаны:

* общие настройки в секции [general]
* собственные абоненты в секциях [101] и [102]
* выход во внешний мир - параметры подключения к оператору sipnet.ru в секции [sipnet] 
и параметр register в секции [global] - в качестве {account} и {password} должны быть 
указаны реальные параметры, выданные оператором

Правила коммутации собственных абонентов друг с другом и с внешним миром описаны в файле 
extensions.conf. Правила оформляются в виде контестов, возможно использующих друг друга 
с помощью include. В контексте [local] описан вызов процедуры Hello с проигрыванием 
звукового файла (файлы находятся в пакете callweaver-sounds) при наборе 100 и вызов 
соответствующих внутренних абонентов при наборе 1ХХ (X - любая цифра от 0 до 9). 
Абоненты были предварительно описаны в файле sip.conf, и для них был указан контекст 
[office] - это значит, что им разрешено выполнять действия, описанные в этом контексте, 
т.е. во вложенном в него [local], а также набирать XXX. (. - любое количество любых 
цифр) - при этом вызов будет выполняться c использованием [sipnet] из sip.conf. 
В контекст [incoming] на номер 100 (как указано в параметре register в sip.conf) 
поступают входящие вызова с sipnet.ru

Более сложные примеры настройки доступны в пакете callweaver-docs в каталоге samples.


Как подключиться и начать использовать CallWeaver
=================================================

В ALT Linux есть несколько софтфонов, поддерживающих протокол SIP, с помощью которых 
можно подключиться к CallWeaver - ekiga, twinkle, sflphone. Для подключения необходимо 
сначала раскомментировать загрузку модуля chan_sip в modules.conf и запустить CallWeaver 
с помощью service callweaver start. В софтфонах необходимо создать учетную запись, указав 
в качестве SIP Proxy адрес сервера с запущенным CallWeaver, а в качестве имени и пароля - 
параметры из секций [101] и [102] файла sip.conf. После этого с каждого софтфона можно будет 
набрать 100 и услышать звуковой файл или набрать 101 или 102 и услышать друг друга. Если 
настроено подключение к sipnet.ru, можно позвонить во внешний мир или принять вызов снаружи 
и проиграть для него звуковой файл.

Для наблюдения за работой CallWeaver можно подключится к его консоли с помощью callweaver_cli. 
То, что будет видно на консоли, нельзя протоколировать стандартным образом, но, поскольку 
для подключения к серверу CallWeaver используется UNIX-сокет, можно использовать конструкцию, 
подобную socat -u UNIX-CONNECT:/var/run/callweaver/callweaver.ctl STDOUT

вторник, 26 августа 2008 г.

ALT Linux 4.0 Server Lite - неофициальная сборка

Долгое время, отвечая на вопрос "а что мне лучше поставить на сервер?", я пребывал в некотором замешательстве. С одной стороны, было бы странно мне, как члену ALT Linux Team, советовать что-либо отличное от дистрибутивов ALT Linux. С другой стороны, ни один из официальных и неофициальных дистрибутивов не отвечает (точнее, до сих пор не отвечал) моим собственным представлениям о том, как должен выглядеть серверный дистрибутив общего назначения.

А представления мои таковы: на сервере не должно быть ничего лишнего (т.е. сервисов, которые не будут использоваться). Я не использую Alterator, предпочитая делать все руками и скриптами, и не далеко не на всех серверах использую OpenVZ, поэтому ALT Linux 4.0 Server и тем более ALT Linux 4.0 Office Server практически для всех моих задач требуют основательной чистки после установки.

Проблема, конечно, решается изготовлением эталонного образа минимальной системы, однако для новичка, задающего вопрос "а что мне лучше поставить на сервер?", это решение несколько неудобно. Более того, мне оно тоже неудобно. Инсталлятор все же значительно лучше.

И тут можно вспомнить, что вообще-то ALT Linux - это не дистрибутив, а сундук с инструментами для построения дистрибутивов. Штатным инструментом для сборки специализированных дистрибутивов в настоящее время является mkimage. С его помощью, а также с помощью участников рассылки devel-distro (которые совсем недавно перебрались сюда из devel и devel-conf) и был изготовлен так называемый ALT Linux 4.0 Server Lite.

Собран он на пакетной базе branch/4.0. В свежеустановленной системе отсутствуют alterator и ovz, потушен portmap (т.к. nfs нужен не всем), поднят acpid и загружен модуль button для корректного выключения системы кнопкой power (в официальном ALT Linux 4.0 Server эта фича уже не помню почему отсутствовала).

Доступны iso для архитертур i586 и x86_64.

Шаг выбора дополнительного ПО из инсталлера исключен, дополнительное ПО предлагается устанавливать с помощью apt уже после установки, перечень дополнительного ПО здесь.

Замечания и пожелания по составу ПО принимаются (уже поступила просьба добавить все, что имеет отношение к pppoe).

В дальнейших планах - всерьез посмотреть на connexion/ncsh и, возможно, заменить им дефолтный etcnet.