пятница, 30 октября 2015 г.

Организация SMS шлюза (smstools3)

Введение

Мне на работе понадобилось иметь возможность рассылать смс для нужд нашей системы мониторинга, а также для уведомлений от других систем для внутреннего использования.
Есть 2 пути: можно использовать внешний сервис рассылки смс, коих огромное количество, а можно поднять свой собственный автономный шлюз.
В первом случае обычно это какой-то оплачиваемый  сервис, часто веб, доступный через интернет и имеющий свой протокол или определенный формат обмена данными. В данном случае есть зависимость от работоспособности интернета, что само собой не подходит для нашей системы мониторинга.
Во втором случае нам нужны сим-карта, любой gsm-модем и софт для работы с модемом. Зависимость тут только от железа и прямоты рук. Такой вариант я и буду реализовывать.
Замечу, что мне нужно только отправлять и только смс. Прием смс или звонки как таковые мне не интересны. Всё буду делать на FreeBSD, но разница в Linux совсем непринципиальна. Вопрос покупки симки и выбора тарифа я тоже не рассматриваю.

Софт и железо

Существует некоторое количество софта, предоставляющего возможность работать с модемом. Например, gammu, gnokii и smstools3. Я попробовал каждый из них и больше всего мне понравился smstools3.
Смысл его работы сводится к тому, что есть постоянно запущенный демон smsd, работающий с подключенным физически модемом через файл устройства. Демон периодически проверяет некоторую директорию для отправки смс на предмет определённым образом офтоматированных текстовых файлов. Если файл обнаруживается, то он берется в обработку, парсится, результаты посылаются как команды модему и файл удаляется. Всё просто.
При этом доступны промежуточные шаги, в рамках которых демон может произвести какие-либо операции по преобразованию текстового файла, логированию или еще каких-нибудь действий, которые админ только захочет/напишет. В общем более подробно можно почитать в документации на сайте.
В качестве железа можно взять любой gsm-модем, обычный USB'шный 3G-модем или даже мобильный телефон (главное чтобы он системе рассказывал правильно). Конечно, перед покупкой 3G-модема нужно поискать в интернете по нему инфу. Часто они залочены на конкретного оператора и имеют несколько режимов работы (как модем, как флэшка или cdrom и т. д.). Я пока не встречал таких 3G-модемов, которые было бы невозможно разлочить и перевести в режим "только модем". Я не рассматриваю эти вопросы тут.

Подключение модема.

В моем распоряжении есть промышленный gsm-модем. Разница между промышленным gsm-модемом и 3G-свистком будет лишь в имени файла в каталоге /dev при подключении к машине. Предпогалается, что симка уже вставлена в модем.
После подключения к машине моего модема появился файл устройства /dev/cuaU0.
Через него я могу работать с модемом, посылать ему вручную команды с помощью штатной фряшной утилиты cu(8).
# cu -l /dev/cuaU0
Далее можно вводить at-команды и модем будет отвечать. Например, просто ввести AT и нажать Enter. Вводимые мной команды могут не выводиться на экран в некоторых случаях. Однако, ответы от модема должны выводиться всегда.
Выйти из cu(8) можно странной последовательностью комбинаций клавиш shift+~ и затем crtl+d или просто точка (.).
На Linux'е нет cu(8). Можно использовать любой софт для работы с последовательным портом. Народ на Linux'е обычно любит minicom, который, кстати, также доступен в портах FreeBSD.

Настройка smstools

Первым делом, конечно, надо smstools поставить. Во Фре этот софт известен как comms/smstools3.
Главная рабочая часть smstools - это демон smsd. В его конфиге указываются все необхолимые параметры, коих очень много. Но в моём случае хватит совсем чуть-чуть, я взял конфиг по умолчанию и немного его подправил:
$ cat /usr/local/etc/smsd.conf
# Example smsd.conf. Read the manual for a description
devices = GSM1
# let's use syslog
#logfile = /var/log/smsd/smsd.log
loglevel = 5
checkhandler = /usr/local/bin/smsd_convert.sh 
[GSM1]
#device = /dev/cuaU0.1
#pin = 1111
device = /dev/cuaU0
incoming = no
В глобальной части конфига указывается устройство, с которым мы будем работать. Их может быть несколько, они заданы далее в отдельных разделах. Затем я указал, что хочу для логов использовать syslog с severity 5 (notice). Потом идёт важный параметр checkhandler с указанием скрипта, который запускается для обработки каждого сообщения (файла). Далее описывается раздел устройства GSM1. Указываю где устройство находится и говорю, что мне не интересны входящие смс. Пин-код у меня на симке выключен.
Всё, можно запускать smsd.
По умолчанию все рабочие каталоги (для файлов входящих и исходящих сообщений) располагаются в /var/spool/sms/. Поскольку мне нужно только отсылать смс, интерес представляет только каталог /var/spool/sms/outgoing. Сюда по идее и надо складывать специальным образом отформатированные текстовые файлы для отправки.
Формат таких файлов прост, всё как везде: заголовки, пустая строка, тело. В простейшем случае такой текстовый файл будет выглядеть так:
To: +79XXXXXXXXX

Hello! How are you?
Файл можно назвать любыми именем и поместить в /var/spool/sms/outgoing. Smsd найдет его там, обработает, отошлёт смс и удалит.
Кстати, в базовой поставке smstools есть скрипт sendsms, которым можно воспользоваться для отсылки смс. Я потом напишу свой вариант подобной утилиты, но всё по порядку.

Non-ACSII текст в смс

Для того, чтобы отправлять текст в языке, отличном от английского нужно smsd сказать о том, что текст не английский. Для этого в отправляемый файл нужно добавить еще один заголовок
Alphabet: UCS
и закодировать само тело сообщения в кодировке UCS-2.
Для того, чтобы облегчить жизнь можно реализовать распознавание Non-ASCII текста и дальнейшую вставку заголовка и перекодировку тела на стороне smsd.
Для этого и нужна то самая опция checkhandler в конфиге smsd. Эта опция указывает на скрипт, который первым делом будет запускаться для каждого текстового файла найденного в каталоге outgoing. Т. е. мы будем просто создавать файлик с содержимым
To: +79XXXXXXXXX

Привет! Как дела?
а smsd уже сам выполнит кое-какие преобразования над этим файлом с помощью скрипта из checkhandler.
Мой скрипт довольно прост. За основу я взял скрипт sms2unicode, который входит в базовую поставку smstools.

$ cat /usr/local/bin/smsd_convert.sh
#!/bin/sh
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
if [ $# -lt 1 ]; then
        echo "Give me a file!"
        exit 10
fi
if [ ! -f "$1" ]; then
        echo "$1: No such file or it's not a regular file!"
        exit 20
fi
if ! which iconv > /dev/null 2>&1; then
        echo "There is no iconv(1) here! Please, install it."
        exit 40
fi
HEADER=`sed -e '/^$/ q' $1`
BODY=`sed -e '1,/^$/ d' $1`
ALPHABET=""
# Is body in english?
if ! echo -n "$BODY" | iconv -t ISO-8859-15 > /dev/null 2>&1; then
        ALPHABET="Alphabet: UCS"
fi
FILE=`mktemp /tmp/smsd_XXXXXX`
echo "$HEADER" >> $FILE
# If body isn't in english tell it to smsd
[ -n "$ALPHABET" ] && echo "$ALPHABET" >> $FILE
echo "" >> $FILE
# Convert body if it's needed.
if [ -n "$ALPHABET" ]; then
        echo -n "$BODY" | iconv -f UTF-8 -t UCS-2 >> $FILE
else
        echo -n "$BODY" >> $FILE
fi
mv $FILE $1
Тут я предполагаю, что изначально Non-ASCII текст записан в кодировке UTF-8. В принципе тут в комментариях всё написано.
Конечно, надо еще дать права на выполнение для этого скрипта:
# chmod 755 /usr/local/bin/smsd_convert.sh
Итак, у нас есть демон, который может отправлять смски на любом языке. Файлы с смс сообщениями мы можем генерировать вручную или с помощью sendsms. Однако, всё это работает локально на данной машине. Нужно предоставить возможность отправлять смски и с других машин в сети.

Отправка смс удаленно

Чтобы отправлять смс с удаленных машин нам по сути каким-то образом нужно положить текстовый файл в определенную директорию на машину, выполняющую функцию смс-шлюза. Вариантов как это сделать могут быть десятки. Например, можно смонтировать этот каталог по NFS всем машинам. Или написать простой CGI-скрипт, который бы парсил POST, например, и клал правильно сформированный файл в нужную директорию. Мне приглянулся другой вариант. Я решил предоставить к директории outgoing доступ по FTP.

Настройка смс-шлюза

Доступ у меня будет для анонимов, сеть-то внутреняя, хотя можно запилить и аутентификацию.
Для этого я добавил на машине-смс-шлюзе учетку для анонима. Взял её из портов, т. к. нынче эта учетка выпилена из базовой системы:
grep ftp /usr/ports/UIDs
ftp:*:14:14::0:0:Anonymous FTP:/var/ftp:/nonexistent
Единственное, я поменял домашний каталог с /var/ftp на /var/spool/sms/outgoing.
После добавления учетки для анонима я добавил учетку ftp в группу dialers, т. к. /var/spool/sms/outgoing по умолчанию принадлежит группе dialers. Затем добавил для этой директории права на запись для группы dialers.
Осталось только запустить ftp сервер. Для этого я добавил в /etc/rc.conf
ftpd_enable="YES"
ftpd_flags="-8 -A -M -O -l -l"
Ключи можно почитать в man. Если в краце, то доступ разрешен только анонимам и только на запись.

Отсылка смс удаленно

На удаленных машинах мне просто нужно написать скрипт, который бы подключался по ftp к смс-шлюзу и заливал туда правильно сформатированный текстовый файл.
Для работы этого скрипта я использовать утилиту ncftpput(1), которая входит в состав пакета ncftp. Во FreeBSD он известен как ftp/ncftp3. Штатный фряшный ftp(1) я не стал использовать, т. к. я не смог заставить его сделать put и отдать нормальный exit code.
$ cat /usr/local/bin/smsviagate.sh
#!/bin/sh
export PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
if ! which ncftpput > /dev/null 2>&1; then
        echo "Can't find ncftpput(1)! Please, install it."
        exit 1
fi
SMSGATE="smsgate.example.org"
DEST=$1
TEXT=$2
if [ -z "$DEST" ]; then
        printf "Destination(s): "
        read DEST
        if [ -z "$DEST" ]; then
                echo "No destination, stopping."
                exit 1
        fi
fi
if [ -z "$TEXT" ]; then
        printf "Text: "
        read TEXT
        if [ -z "$TEXT" ]; then
                echo "No text, stopping."
                exit 1
        fi
fi
if [ $# -gt 2 ]; then
        n=$#
        while [ $n -gt 1 ]; do
                destinations="$destinations $1"
                shift
                n=`expr $n - 1`
        done
        TEXT=$1
else
        destinations=$DEST
fi
for destination in $destinations; do
        TMPFILE=`mktemp /tmp/smsd_XXXXXX`
        echo "To: $destination" >> $TMPFILE
        echo "" >> $TMPFILE
        echo -n "$TEXT" >> $TMPFILE
        ncftpput -r 1 $SMSGATE / $TMPFILE > /dev/null 2>&1
        EXIT="$?"
        if [ $EXIT -eq 0 ]; then
                echo "SMS to $destination has been queued."
        else
                echo "SMS to $destination has not been queued, ncftpput exit code = $EXIT."
        fi
        rm $TMPFILE
done
За основу был взят sendsms из состава smstools. Нужно обратить внимание, что адрес смс-шлюза в скрипте захардкожен в переменной $SMSGATE.
Не забудем про права на выполнение:
# chmod 755 /usr/local/bin/smsviagate.sh
Запускаем скрипт
$ smsviagate.sh '+79xxxxxxxxxx' 'Превед!'
Или без параметров. Тогда скрипт спросит обо всем интерактивно.

Альтернативный вариант удаленной отсылки смс

Спустя какое-то время я сделал возмодность отсылки смс с удаленных машин через email. Это понадобилось для виндовых машин, которые могли только слать письма.
Я создал отдельную mx запись smsgate.example.org, которая указывала на сервер смс-шлюз.
В настройках sendmail в local-host-names добавил
$ cat /etc/mail/local-host-names
smsgate.example.org
Затем в файле aliases добавил перенаправление почты на скрипт:
$ grep sms /etc/mail/aliases
sms: "| /usr/local/bin/email2sms.sh"
Затем стандартные для перенастройки sendmail команды:
# cd /etc/mail && make && make install && make restart && newaliases
Конечно, sendmail должен принимать почту от удаленных машин:
$ grep sendmail /etc/rc.conf
sendmail_enable="YES"
Содержимое скрипта email2sms.sh выглядит так:

$ cat /usr/local/bin/email2sms.sh
#!/bin/sh
export PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
DEBUG="false"
SENDSMS="/usr/local/bin/smsviagate.sh"
EMAIL=`mktemp /tmp/email_XXXXXX`
LOG="/tmp/email2sms.debug"
cat >$EMAIL
FROM=`fgrep "From:" $EMAIL | cut -d : -f2- | sed -e 's/^ //g'`
DST=`fgrep "Subject:" $EMAIL | cut -d : -f2- | sed -e 's/,/ /g' -e 's/\./ /g' -e 's/;/ /g' -e 's/:/ /g' -e 's/"/ /g' -e "s/'/ /g"`
TEXT=`sed -e '1,/^$/ d' $EMAIL`
$SENDSMS $DST "$TEXT"
EXIT="$?"
$DEBUG && echo "`date`" >> $LOG
$DEBUG && echo "$SENDSMS exit code is $EXIT" >> $LOG
$DEBUG && echo "from is $FROM" >> $LOG
$DEBUG && echo "dst is $DST" >> $LOG
$DEBUG && echo "text is $TEXT" >> $LOG
$DEBUG && echo "---------------" >> $LOG
rm $EMAIL
Темой письма должен быть телефонный номер вроде +79XXXXXXXXXX.
Тело письма, соотвественно, должно быть в UTF-8.
Опять, нужно не забыть сделать скрипт выполняемым:
# chmod 755 /usr/local/bin/email2sms.sh

P. S.

Спустя какое-то время, я обнаружил, что при переустановке smstools права на /var/spool/sms/outgoing слетают на стандартные. Это не хорошо.
Чтобы предотвратить это я создал другой каталог, где повторил содержимое стандартного /var/spool/sms. Например:
# cd /var/spool && mkdir -p smsgate/outgoing smsgate/incoming smsgate/checked
# chown -R uucp:dialer /var/spool/smsgate
Затем задал нужные права (см. выше) для нового каталога outgoing:
# chmod g+w smsgate/outgoing
В заключении я поменял имена этих дректорий в 2-х местах:
- переопределил их его через параметры outgoing, checked и incoming в глобальной части конфига smsd.conf;
- изменил домашний каталог на смс-шлюзе для учетки ftp на /var/spool/smsgate/outgoing.

P. S. S. Как проверить счет:
У моего оператора счет проверяется по вызову *100#, поэтому:
# cu -l /dev/cuaU0
Connected
AT+CUSD=1,*100#,15
OK
Немного подождать и модем вернет что-то вроде такого:
+CUSD: 2,"003300360035002E003900300440002E00200416043C04380020002A003500320033002A003600230020043800200441043F0440043E044104380020042204100420041E002C002004470442043E0020044204350431044F002004360434043504420020002800330440002F04340029",72

Потом идем сюда http://smstools3.kekekasvi.com/topic.php?id=288
И копируем этот набор сиволов в USSD Entry/Display
Потом выбираем UCS2 и нажимаем convert.

четверг, 22 октября 2015 г.

Запуск IE от LocalSystem

Написали как-то для меня скрипт на PowerShell. Скрипт должен был просто вывести на экран содержимое веб-странички, адрес которой подавался ему как первый аргумент. Не помню всех обстоятельств, но такой подход не работал, т. к. в скрипте использовался метод, который был завязан на IE. При запуске оно писало что-то вроде "IE не был ни разу запущен до этого. Запустите и ответьте на все вопросы". Вопросы из серии какую службу поиска будете использовать и т. д. Как любой первый запуск IE.
Только этот скрипт запускался не от обычного пользователя, а от службы. Нафига эти вопросы нужны учетке службы? Полный бред, короче. Пришлось искать способы запуска IE от системного пользователя. Удалось решить с помощью пакет PsTools:
PsExec.exe -i -u "nt authority\system" "C:\Program Files\Internet Explorer\iexplore.exe"

вторник, 7 апреля 2015 г.

lvm resize

Шпаргалка о том, как увеличить/изменить обычный раздел или lvm том, например, на вирт. машине.
В инете нашел только инструкции как создать новый раздел и присоединить его к тому.
Тут по-другому. Я буду просто увеличивать корневой раздел в установленной Убунте с разделами установленными по умолчанию.

Ubuntu 14.04 с LVM.

Device       ID   System
/dev/sda1   83   Linux (boot)
/dev/sda2    5    Extended
/dev/sda5    8e  Linux LVM


0) Увеличить диск sda на гипервизоре (обычно требуется выключение машины).

1) Увеличить сами разделы Extended и Linux LVM (sda2 и sda5) в таблице разделов.
Можно разными способами. Например с помощью gparted или fdisk.
Я буду пользоваться fdisk. Прямо в работающей системе нужно удалить разделы /dev/sda5 и /dev/sda2. Затем создать такие же, но уже с нужным размером. После чего записать измененную таблицу на диск. После этого сообщить ядру об изменениях в помощью partprobe(8).

$ sudo fdisk /dev/sda

The device presents a logical sector size that is smaller than
the physical sector size. Aligning to a physical sector (or optimal
I/O) size boundary is recommended, or performance may be impacted.

Command (m for help): p

Disk /dev/sda: 11.8 GB, 11811160064 bytes
255 heads, 63 sectors/track, 1435 cylinders, total 23068672 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x000e5c3d

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux
/dev/sda2          501758    20969471    10233857    5  Extended
Partition 2 does not start on physical sector boundary.
/dev/sda5          501760    20969471    10233856   8e  Linux LVM

Command (m for help): d
Partition number (1-5): 5

Command (m for help): d
Partition number (1-5): 2

Command (m for help): p

Disk /dev/sda: 11.8 GB, 11811160064 bytes
255 heads, 63 sectors/track, 1435 cylinders, total 23068672 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x000e5c3d

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux

Command (m for help): n
Partition type:
   p   primary (1 primary, 0 extended, 3 free)
   e   extended
Select (default p): e
Partition number (1-4, default 2):
Using default value 2
First sector (499712-23068671, default 499712):
Using default value 499712
Last sector, +sectors or +size{K,M,G} (499712-23068671, default 23068671):
Using default value 23068671

Command (m for help): p

Disk /dev/sda: 11.8 GB, 11811160064 bytes
255 heads, 63 sectors/track, 1435 cylinders, total 23068672 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x000e5c3d

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux
/dev/sda2          499712    23068671    11284480    5  Extended


Command (m for help): n
Partition type:
   p   primary (1 primary, 1 extended, 2 free)
   l   logical (numbered from 5)
Select (default p): l
Adding logical partition 5
First sector (501760-23068671, default 501760):
Using default value 501760
Last sector, +sectors or +size{K,M,G} (501760-23068671, default 23068671):
Using default value 23068671

Command (m for help): p

Disk /dev/sda: 11.8 GB, 11811160064 bytes
255 heads, 63 sectors/track, 1435 cylinders, total 23068672 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x000e5c3d

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux
/dev/sda2          499712    23068671    11284480    5  Extended
/dev/sda5          501760    23068671    11283456   83  Linux


Command (m for help): t
Partition number (1-5): 5
Hex code (type L to list codes): 8e
Changed system type of partition 5 to 8e (Linux LVM)

Command (m for help): p

Disk /dev/sda: 11.8 GB, 11811160064 bytes
255 heads, 63 sectors/track, 1435 cylinders, total 23068672 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk identifier: 0x000e5c3d

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux
/dev/sda2          499712    23068671    11284480    5  Extended
/dev/sda5          501760    23068671    11283456   8e  Linux LVM

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

$ sudo partprobe /dev/sda

Всё, измененная таблица разделов на диске и ядро знает о ней.


2)  Теперь нужно сообщить lvm'у об изменениях

 2.1) Меняем размер physical volume:
          $ sudo pvresize /dev/sda5
           Physical volume "/dev/sda5" changed
           1 physical volume(s) resized / 0 physical volume(s) not resized

 2.2) Меняем размер logical volume:
         $ sudo lvextend -l +100%FREE <vg_name>/<lv_name>
          Extending logical volume <name> to <size>
          Logical volume <name> successfully resized

3) Расширяем файловую систему на нашем логическом разделе
  $ sudo resize2fs /dev/<vg_name>/<lv_name>
   resize2fs 1.42.9 (4-Feb-2014)
   Filesystem at /dev/<vg_name>/<lv_name> is mounted on /; on-line resizing required
   old_desc_blocks = 1, new_desc_blocks = 1
   The filesystem on /dev/<vg_name>/<lv_name> is now 2689024 blocks long.

Готово!



P. S. посмотреть vg_name:
$ sudo vgdisplay
посмотреть lv_name:
$ sudo lvdisplay
Из вывода последней команды можно получить и vg_name.

P. S. 2. Хорошая статья по LVM:
https://wiki.archlinux.org/index.php/LVM#Advanced_options
Видео от Яндекса на такую же тему:
http://www.youtube.com/watch?v=8bLpJ1IIF9A
http://www.youtube.com/watch?v=j5tGHiwO6gg

пятница, 13 марта 2015 г.

Утилиты для диагностики HDD

Понадобилось тут прогнать диск на бэд блоки. Помимо всяких mhdd, victoria, hdparm, backclocks и т. д. встретил в сети новые для меня программы такого плана:
- whdd;
- hdat2.

Первый - для линуха и работает на более низком уровне, чем hdparm или badblocks, входит в поставку современных версий SystemRescueCD.
Второй - для винды и даже включен в ubcd.
Вот так.

З. Ы. whdd на SystemRescueCD спасал меня не раз!

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

Создание своей репы с poudriere(8)

Небольшая шпаргалка по созданию своей репы с помощью ports-mgmt/poudriere.

Для полного понимания происходящего нужно уметь элементарно работать с портами: знать как выставлять опции, ставить/удалять порт, знать про /etc/make.conf и понимать в каком формате туда можно дописывать опции.

При написаниии использовал эти ссылки:
http://blather.michaelwlucas.com/archives/1941
http://www.bsdnow.tv/tutorials/poudriere
http://forums.freebsd.org/viewtopic.php?t=38859

Введение

Как известно, штатным источником софта во FreeBSD является дерево портов. Когда вы ставите софт из портов, система собирает на самом деле из порта пакет, и пакет уже устанавливается в систему. Для работы с пакетами используется утилита pkg(8).
Соответственно, вместо компилирования пакетов из портов на каждой подконтрольной машине, удобней иметь некий общий для всех машин репозиторий, с уже собранными пакетами.
pkg(8) как пакетный менеджер умеет работать с удаленными репозиториями, т. е. ставить софт из них, разрешать зависимости и т. д. Стандартной утилитой для создания своего репозитория сейчас считается poudriere(8), которая используется в том числе для сборки официальной фряшной репы.
В этой статье я создам свой репозиторий с помощью poudriere(8) с нуля, настрою http-сервер для отдачи пакетов, а затем настрою pkg(8) для того, чтобы он брал софт из этой репы.
При этом я в своей работе обычно использую только свою репу и добавляю туда софт или изменяю опции по надобности. Официальную репу FreeBSD я обычно использую только на самом раннем старте создания репы, а потом дизаблю.

Чуть подробнее

Poudriere(8) после сборки просто кладёт в указанную вами папку все пакеты и файлы метаданных. Т. е. он только собирает, готовит репу и ничего больше. Какой протокол вы будете использовать для работы с репозиторием это уже ваше дело. pkg(8) умеет работать с HTTP и FTP, например. Полный список протоколов в ман-странице. Вы берете любой http или ftp сервер и настраиваете его указывая, что вот тут лежат файлы, которые ты должен отдавать.
Я же в качестве транспорта буду использовать HTTP. Еще poudriere (8) умеет подписывать собранные пакеты , но я не буду использовать подпись, так проще.

Настройка poudriere(8)

У меня все данные, занимающие много места хранятся в каталоге /storage. В нём я создаю отдельный каталог poudriere, который будет являться рабочим каталогом для утилиты poudriere(8), т. е. там будет проходить сборка, храниться мир и дерево портов для сборки, логи сборки, сами пакеты с метаданными и т. д.
Создаю каталоги, сразу для всего
# mkdir -p /storage/poudriere/distfiles
По названию папки distfiles понятно зачем она нужна.

Затем устанавливаю минимальный набор софта, нужный для разворачивания и конфигурирования репы. Пока из официальной репы, т. к. других источником нет, да и разницы нет никакой.
Бутстрапим пакетный менеджер:
# pkg
Ставим poudriere(8):
# pkg install ports-mgmt/poudriere
Ставим утилиту для работы с фреймворком опций портов, т. е. чтобы можно было выбирать опции при сборке порта:
# pkg install ports-mgmt/dialog4ports

Далее редактирую конфиг для poudriere(8), скопировав sample. Тут сам конфиг хорошо откомментирован. В общем, всё можно найти в документации. У меня на сервере сборки используется только UFS.
# egrep -v '^\s*(#|$)' /usr/local/etc/poudriere.conf
NO_ZFS=yes
FREEBSD_HOST=ftp://ftp.freebsd.org
RESOLV_CONF=/etc/resolv.conf
BASEFS=/storage/poudriere
USE_PORTLINT=no
USE_TMPFS=yes
DISTFILES_CACHE=/storage/poudriere/distfiles
CHECK_CHANGED_OPTIONS=verbose #
CHECK_CHANGED_DEPS=yes #Ask when options on changing
NOLINUX=yes

Далее создаю каталог, где будут храниться вспомогательные конфиги, если он еще не создан:
# mkdir /usr/local/etc/poudriere.d

Вообще с помощью poudriere можно создавать любое число веток портов для любого количества релизов FreeBSD. Устроено это следующим образом.
С poudriere(8) вы создаёте окружение для сборки, иммитирующее нужный вам релиз. Такое окружение это "мир" нужного релиза, распакованный в определенную директорию. Вся эта штука в терминологии poudriere(8) называется jail. Тут есть некоторая путаница из-за такого названия. Это _не_ тот самый типичными фряшными джэйл, нет. Это что-то вроде контейнера. Хотя в процессе сборки poudriere(8) будет автоматом без вашего участия динамически создавать, а затем уничтожать пачку именно фряшных джэлов. Всё, чем нужно рулить вам, как пользователю - это poudriere jail'ы. Poudriere jail имеет такие параметры как имя, архитектура и релиз, для которого будет скачан и распакован "мир".
Таким образом достигается подход, когда на одной машине вы можете собирать пакеты для разных релизов и архитектур, просто создавая пачку таких poudriere jail'ов.

Создание окружения

Например, чтобы создать poudriere jail для 10.2-RELEASE архитектуры amd64 и с именем FreeBSD:10:amd64 нужно выполнить:
# poudriere jail -c -j FreeBSD:10:amd64 -v 10.2-RELEASE -a amd64
После этого poudriere(8) сам всё скачает и распакует в /storage/poudriere/jails.
В данном случае я выбрал имя "FreeBSD:10:amd64" для poudriere jail'а для удобства дальнейшего использования, т. к. такое имя полностью соответствует переменной ${ABI}, определяющей архитектуру релиза в некоторых утилитах. Так мне проще будет настраивать потом pkg(8).
Таком же образом можно создать остальные poudriere jail'ы, соответствующий другим релизам.
Я использую только последний релиз и собираю пакеты только для него. Стоит также помнить, что между релизами одной мажорной ветки обеспечена бинарная совместимость. Вывести список poudriere jail можно командой:
# poudriere jail -l
Поскольку poudriere jail это обычный "мир", то для него справедливы все те выходящие патчи от FreeBSD, закрывающие разные дыры. Подтянуть все фиксы, т. е. обновить poudriere jail, как бы это делалось для обычного сервера командой freebsd-update fetch/install, можно так:
# poudriere jail -u -j FreeBSD:10:amd64
Рекомендую это делать с такой же периодичностью, как для настроящего сервера с таким же релизом.

Дерево портов

Далее нам нужно дерево портов, из которого собственно будут собираться наши пакеты. Можно использовать уже существующее дерево портов, если оно у вас уже есть. Однако, я предпочитаю использовать отдельное дерево портов для нужд poudriere. Во-первых, я ставлю серверы без дерева портов, т. е. у меня нет его в привычном /usr/ports, т. к. это просто не нужно. Я ведь пакетами буду пользоваться. Во-вторых, мне кажется, что так больше порядка.
В poudriere(8) есть возможность создавать сколько угодно разных веток портов с привязкой к своему poudriere jail'у. Я с такими вещами не заморачиваюсь. Я использую только 1 ветку портов для всего. Мне это не нужно, да и так намного проще. Я делаю так:
# poudriere ports -c
Если было бы нужно с привязкой к конкретному джэйлу, то:
# poudriere ports -c -p JailName
После этого всё само скачается и распакуется в /storage/poudriere/ports/(default|JailName).
Если бы нужно было использовать уже существующее дерево портов в привычном /usr/ports, то я бы ввел:
# poudriere ports -c -F -f none -M /usr/ports -p default 
# poudriere ports -c -f none -m null -M /usr/ports -p default
Обновлять бы такое дерево можно было бы с помощью штатной утилиты portsnap(8) (или svn, что привычнее), как и раньше.
В ином, т. е. моем случае нужно просить об этом poudriere(8):
# poudriere ports -u
Если у нас не одно дерево портов, то посмотреть список можно так:
# poudriere ports -l
Теперь нужно определить список портов для сборки. Я задал свой список портов для сборки таким образом:
# cat /usr/local/etc/poudriere.d/pkglist.txt
...
www/apache24
lang/php56
net/freeradius2
net/openldap24-server
...

Опции портов

Обычно при работе с деревом портов можно использовать файл /etc/make.conf для задания предопределенных опций по умолчанию. Например, сразу отключить такие опции как иксы или дебаг. С poudriere(8) тоже можно использовать подход с опциями в make.conf, только это файл будет располагаться в другом месте. Поскольку у меня только одно дерево портов, мой make.conf будет располагаться тут:
# cat /usr/local/etc/poudriere.d/make.conf
OPTIONS_UNSET=X11 HAL DEBUG EGL
WANT_OPENLDAP_SASL="YES"
#SQUID_CONFIGURE_ARGS="--enable-useragent-log"
В файле у меня уже определено некоторое количество опций. Синтаксис меняется со временем, нужно сверяться с текущим фреймворком для задания опций.
Если бы у меня было несколько деревьев портов для каждого poudriere jail, то имя файла было бы иным:
# cat /usr/local/etc/poudriere.d/JailName-make.conf
Полностью опции для портов задаются опять же с помощью вызова poudriere(8). Т. е. никакие `make config` и т. д. тут не работают. Мало того, все определенные poudriere(8) опции хранятся в другом месте, отличном от /var/db/ports. А точнее в папке /usr/local/etc/poudriere.d/options. Поскольку я использую одно дерево портов для всего, я не могу сказать как будет в случае нескольких веток для разных poudriere jail.
Итак, задать опции для всех портов, перечисленных в /usr/local/etc/poudriere.d/pkglist.txt можно командой:
# poudriere options -f /usr/local/etc/poudriere.d/pkglist.txt
Т. е. тут poudriere(8) берет список портов из файла и последовательно выводит диалог с опциями. Чтобы задать опции для какого-то конкретного порта, вместо указания опции '-f' и имени файла нужно просто ввести неполный путь к порту, например:
# poudriere options shells/bash
Замечу, что в предыдущих двух случаях опции будут запрашиваться только если они никогда раньше не были заданы или с последнего момента задания опций появились новые. Чтобы всегда выводить диалог с опциями нужно использовать ключ '-c'. Например:
# poudriere options -cf /usr/local/etc/poudriere.d/pkglist.txt
# poudriere options -c shells/bash

Сборка портов

Всё готово. У нас есть окружение, дерево портов, для которых заданы опции. Можно собирать.
Запускаем сборку портов из списка pkglist.txt:
# poudriere bulk -j FreeBSD:10:amd64 -f /usr/local/etc/poudriere.d/pkglist.txt
Чтобы собрать вообще всё дерево портов нужно ввести ключ '-a', но я обычно так не делаю:
# poudriere bulk -j JailName -a
Далее будет долгий процесс сборки, в процессе которого на экран будет выводить куча информации. В процессе сборки можно нажать ctrl+t я посмотреть текущий отчет о том, сколько портов собрано, сколько времени затрачено на сбоку, сколько осталось собрать и т. д.
По окончанию сборки на экране появится отчет.
Мы получили готовый репозитарий. Осталось настроить транспорт и сам pkg(8).

Транспорт и настройка pkg(8)

Я буду раздавать пакеты по HTTP с помощью apache. Можно взять абсолютно любой другой веб-сервер или FTP сервер. Мой конфиг выглядит так:
# cat /usr/local/etc/apache24/Includes/pkg.example.org.conf
<VirtualHost *:80>
    ServerAdmin admin@example.org
    DocumentRoot /storage/poudriere/data/packages
    ServerName pkg.example.org
    <Directory />
        Options Indexes FollowSymLinks
        AllowOverride AuthConfig
        Require all granted
    </Directory>
</VirtualHost>
Мой репозиторий доступен по DNS имени pkg.example.org. Apache можно поставить из офф. репы или из своей же:
# pkg install /storage/poudriere/data/packages/FreeBSD:10:amd64-default/All/apache24-2.4.17.txz
Для pkg(8) всё просто. На всех клиентских машинах я дизаблю официальный репозиторий FreeBSD и использую свой. Т. е. в файле /etc/pkg/FreeBSD.conf меняю опцию 'enabled' с 'yes' на 'no', а затем создаю конфиг:
# mkdir -p /usr/local/etc/pkg/repos
# cat /usr/local/etc/pkg/repos/myrepo.conf
myrepo: {
 url: "http://pkg.example.org/${ABI}-default",
 mirror_type: "none",
 enabled: yes
}
Тут мне пригодилось имя моего poudriere jail, потому что оно соответствует переменной ${ABI}. Так по-моему удобней.
Далее все просто:
# pkg update
# pkg search squid
# pkg install www/squid
# pkg upgrade
и т. д.

Дальнейшее обслуживание

В дальнейшем я просто обновляю дерево портов и запускаю сборку. Будет собраны только те порты, новые версии которых появились в дереве и зависимые от них порты.
# poudriere ports -u
# poudriere bulk -j FreeBSD:10:amd64 -f /usr/local/etc/poudriere.d/pkglist.txt
Если у порта появились новые опции, то poudriere(8) в процессе сборки никаких диалогов не будет выводить. Так что я считаю полезным изредка запускать:
# poudriere options -f /usr/local/etc/poudriere.d/pkglist.txt
Если мы поменяли какие-то опции, то poudrire(8) в процессе сборки выведет список старых и новых опций и пересоберет порт, даже если он остался на той же версии.
Чтобы пересобрать какой-то порт принудительно, например, www/squid, нужно использовать ключ '-c':
# poudriere bulk -c -j FreeBSD:10:amd64 www/squid
Или '-f /usr/local/etc/poudriere.d/pkglist.txt' вместо имени порта, если нужно пересобрать вообще все из списка.
Для удобства я создал такие однобуквенные alias'ы для командного интерпретатора:
# alias | grep poudriere
B       (poudriere bulk -j FreeBSD:10:amd64 -f /usr/local/etc/poudriere.d/pkglist.txt)
P       (poudriere ports -u)
T       (poudriere testport -j FreeBSD:10:amd64 -n -o)
U       (poudriere jail -j FreeBSD:10:amd64 -u)
Вот так и живём.

понедельник, 17 ноября 2014 г.

Немного growfs

Маааленькая шпаргалка.
Понадобилось тут на виртуальной машине увеличить диск.
Увеличил диск на гипервизоре, при загрузке выскочило такое:
GEOM: da1: the secondary GPT header is not in the last LBA.

После этого сделал
# gpart recover da1
# gpart resize -i 1 da1
# growfs /dev/da1p1
Всё.

пятница, 20 июня 2014 г.

Squid и Domain Users

Достаточно много времени прошло с момента ввода в эксплуатацию прокси сервер со squid на борту, с доменном авторизацией. Все было хорошо, все работало.
Однако, в один момент админ одного из филиалов захотел, чтобы пользователей его поддомена наш squid не пускал в интернет. Не долго думая, в группу запрета интернета DenyInternet была добавлена группа Domain Users соответствующего поддомена. Быстро выяснилось, что такая конфигурация не работает. Пришлось разбираться почему.
Оказалось, что в MS AD группа Domain Users это primary группа пользователя, которая не числится в атрибуте memberOf учетки пользователя. Есть отдельный атрибут под названием primaryGroupID, в котором записан идентификатор группы. Именно идентификатор в виде цифры, а не текст или что-то еще. Как правило, если в атрибуте primaryGroupID указано 513, то это Domain Users, 512 - Domain Admins. Но это как правило, т.е. по идее не исключены ситуации, когда это не так.
Чтобы точно знать какой же группе принадлежит этот идентификатор, надо сделать поиск в AD. Что искать? Гугл подсказал, что группу надо искать по атрибуту objectSID. Чтобы его сформировать, надо взять objectSID запрашиваемого пользователя, например S-1-5-21-1232852338-251567878-923712399-72399, отрубить там последние циферки, идущие за последним дефисом. Так мы получить SID домена запрашиваемого пользователя. Вместо тех циферок подставить значение из атрибута primaryGroupID запрашиваемого пользователя. Так мы получим идентификатор группы S-1-5-21-1232852338-251567878-923712399-513, по которому и будет искать в AD. Т.е. теперь нужно выполнить поиск по ldap по атрибуту objectSID=S-1-5-21-1232852338-251567878-923712399-513 и получить имя группы. Поскольку эта группа тоже может иметь атрибут memberOf, в котором записано членство в других группах, то надо рекурсивно пройтись по ним так же, как это делалось в обычными группами.
По началу я попытался сам написать патч. Конечно, знаний у меня маловато, и я не сильно надеялся на успех. В добавок оказалось, что objectSID возвращается в бинарном виде. Тут я совсем сдался и решил написать разработчику хелпера, которого зовут Markus Moeller. В письме изложил ему проблему и идею ее решения. Markus ответил довольно быстро и легко согласился дописать хелпер. В общем диалог у нас вышел легкий. Через несколько дней он прислал патч, который я попробовал и отписался о результатах, потом еще несколько и в итоге все заработало как надо. Markus обещал, что этот патч он включит в транк. Так что в след. версии squid этот функционал будет доступен.
А пока новая версия еще не вышла, можно использовать этот патч для актуальной версии squid.
Писалось все это для squid34, так что к нему и надо применять. Для младших версии нужна правка.
Тут лежит новый файл support_ldap.cc, который надо заменять.
Вот такой хороший человек оказался Markus Moeller, легкий на подъем и в общении. Большое спасибо ему!

UPD. Squid 3.5 уже включает в себя данные изменения.