07 апреля, 2012

LAMP одной командой

Одна команда способна превратить Debian в систему, отвечающий потребностям среднестатистического php-быдлокодера:

apt-get install apache2 libapache2-mod-php5 php5-cli php5-curl php5-gd \
 php5-imap php5-mysql php5-sqlite php5-memcached php5-geoip memcached \
 mysql-server mysql-client mytop iftop iotop htop ntp

Всё это ещё и настраивать надо, впрочем кого это волнует ;)

27 марта, 2012

Часовые пояса в Java

В виду отмены перехода на зимнее время и обратно весьма актуальна проблема обновления временных зон. В Debian/Ubuntu проблема решается легко - обновлением пакета tzdata. К сожалению, часть приложений стоят особняком и данные для них приходится обновлять отдельно. В список «проблемных» программ попадают mysql со специальной утилитой и героиня поста - Java.

Для OpenJDK зоны вынесены в отдельный пакет tzdata-java и легко обновляются. Для Oracle (Sun) Java, которую требуют некоторые упёртые товарищи, придётся скачивать специальную утилиту - TZUpdater. Скачиваем, распаковываем, запускаем java -jar tzupdater.jar --update. После каждого изменения в часовых поясов утилита обновляется и её нужно снова скачивать.

Проверить корректность временных зон можно при помощи простой программы:

$ cat TestMSKtz.java 
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class TestMSKtz {
  public static void main(String[] args) {

    Calendar cal = new GregorianCalendar();
    System.out.printf("Local time: %04d-%02d-%02d %02d:%02d:%02d\n", cal.get(Calendar.YEAR),cal.get(Calendar.MONTH),cal.get(Calendar.DAY_OF_MONTH),cal.get(Calendar.HOUR_OF_DAY),cal.get(Calendar.MINUTE),cal.get(Calendar.SECOND));

    cal = new GregorianCalendar(TimeZone.getTimeZone("Europe/Moscow"));
    System.out.printf("Moscow time: %04d-%02d-%02d %02d:%02d:%02d\n", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND));

    cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
    System.out.printf("UTC time: %04d-%02d-%02d %02d:%02d:%02d\n", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND));    
  }
}
$ javac TestMSKtz.java && java TestMSKtz
Local time: 2012-02-27 16:40:50
Moscow time: 2012-02-27 16:40:50
UTC time: 2012-02-27 12:40:50

После обновления зон нужно будет перезапустить java-приложения.

26 марта, 2012

yum: command not found

Не знаю, зачем продавать VPS с урезанной CentOS, наверное чтобы проще было впарить какую-нибудь панель. Маркетологи хуже политиков :(

Так или иначе сервер уже куплен, я обещал его настроит, а yum на сервере отсутствует. Немного погуглив составил рецепт для пятой версии:

rpm -Uvh http://mirror.centos.org/centos/5/os/x86_64/CentOS/python-elementtree-1.2.6-5.x86_64.rpm \
http://mirror.centos.org/centos/5/os/x86_64/CentOS/python-iniparse-0.2.3-4.el5.noarch.rpm \
http://mirror.centos.org/centos/5/os/x86_64/CentOS/python-sqlite-1.1.7-1.2.1.x86_64.rpm \
http://mirror.centos.org/centos/5/os/x86_64/CentOS/python-urlgrabber-3.1.0-6.el5.noarch.rpm \
http://mirror.centos.org/centos/5/os/x86_64/CentOS/yum-fastestmirror-1.1.16-21.el5.centos.noarch.rpm \
http://mirror.centos.org/centos/5/os/x86_64/CentOS/yum-metadata-parser-1.1.2-3.el5.centos.x86_64.rpm \
http://mirror.centos.org/centos/5/os/x86_64/CentOS/m2crypto-0.16-8.el5.x86_64.rpm \
http://mirror.centos.org/centos/5/os/x86_64/CentOS/yum-3.2.22-39.el5.centos.noarch.rpm

18 марта, 2012

GLPI: неправильная кодировка при обработке почты.

После начала использования GLPI в качестве Helpdesk всплыл весьма неприятный баг: часть обращений (тикетов) импортировались в неправильной кодировке. С первого взгляда «плохие» письма ничем не выделялись.

Тут нужно отметить, что мы используем Gmail-аккаунт. Только после отладки, стало понятно, что в «плохом» случае Gmail отдаёт тему письма в KOI8-R, а тело в UTF-8. В этой ситуации в коде GLPI происходит вызов iconv с параметрами ('KOI8-R' -> 'UTF-8').

Что же, воспроизведённый баг - половина патча. Приведённый ниже патч проработал у меня полтора месяца, проблем не выявлено.

--- inc/mailcollector.class.php.orig      2012-01-30 00:08:25.000000000 +0400
+++ inc/mailcollector.class.php   2012-01-30 01:29:35.000000000 +0400
@@ -955,6 +955,11 @@
                      $text = mb_convert_encoding($text, 'utf-8',$param->value);
                      $this->body_converted=true;
                   }
+                  if ((strtoupper($param->attribute)=='CHARSET')
+                      && function_exists('mb_convert_encoding')
+                      && strtoupper($param->value) == 'UTF-8') {
+                     $this->body_converted=true;
+                  }
                }
             }
             return $text;

P.S. Разработчикам отписал

15 марта, 2012

Генерация самоподписанного сертификата с openSSL

Переработка этой статьи, чтобы не искать её каждый раз, когда нужно сгенерировать сертификат.

Для начала сгенерируем ключ. OpenSSL не позволяет сгенерировать ключь, не защищенный паролем. Nginx не запускается с ключем, защищенным паролем. Чтобы устранить противоречия, сначала создаётся защищенный ключ, потом с него снимается защита.

# openssl genrsa -des3 -out server.key.encrypted 2048
 Generating RSA private key, 2048 bit long modulus
 ........+++
 ..................+++
 e is 65537 (0x10001)
 Enter pass phrase for server.key.encrypted:
 Verifying - Enter pass phrase for server.key.encrypted:
# openssl rsa -in server.key.encrypted -out server.key
 Enter pass phrase for server.key.encrypted:
 writing RSA key
# rm server.key.encrypted

Теперь имея ключ, можно создавать сертификат. Никаких особых хитростей, главное чтобы в CN был целевой адрес.

# openssl req -new -key server.key -out server.csr
 You are about to be asked to enter information that will be incorporated
 into your certificate request.
 What you are about to enter is what is called a Distinguished Name or a DN.
 There are quite a few fields but you can leave some blank
 For some fields there will be a default value,
 If you enter '.', the field will be left blank.
 -----
 Country Name (2 letter code) [AU]:RU
 State or Province Name (full name) [Some-State]:Moscow
 Locality Name (eg, city) []:Moscow
 Organization Name (eg, company) [Internet Widgits Pty Ltd]:Mind
 Organizational Unit Name (eg, section) []:
 Common Name (eg, YOUR name) []:example.ru
 Email Address []:

 Please enter the following 'extra' attributes
 to be sent with your certificate request
 A challenge password []:
 An optional company name []:
# openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
 Signature ok
 subject=/C=RU/ST=Moscow/L=Moscow/O=Mind/CN=example.ru
 Getting Private key

27 февраля, 2012

Базовая настройка MySQL

MySQL весьма популярная СУБД, на удивление, приемлемые настройки «из коробки» мне ни разу не попадались. Приведу наиболее важные параметры, которые стоит поменять сразу после установки.

Начнём с кодировки и метода сравнения строк, потому как latin1 плохой вариант, latin1_swedish_ci просто не вариант для кириллицы. Кодировка без вариантов utf8. Метод сравнения - utf8_general_ci, хороший вариант для русского и английского языков, для других случаев, возможно, стоит использовать utf8_unicode_ci.

character_set_client = utf8
character_set_server = utf8
collation_server = utf8_general_ci

Многие разработчики не указывать «движок» при создании таблицы, следующая опция позволит сделать выбор за них, естественно в сторону InnoDB.

default_storage_engine = InnoDB

По умолчанию все InnoDB таблицы хранятся в одном файле - ibdata1. Файл этот увеличивается, при росте объёма данных, но никогда не уменьшается. При значительном объёме БД это создаёт определённые сложности, например с помощью Xtrabackup нельзя будет скопировать отдельную БД, на InnoDB-таблицы не будут распространяться файловые квоты (привет разработчикам ISPmanager, надеюсь они уже поправили эту багу). Следующая опция заставит MySQL хранить каждую таблицу в отдельном файле:

innodb_file_per_table = 1
При при хранении объёмных blob'ов или текстов, max_allowed_packet должен быть выставлен на клиенте и сервере в значение превосходящее максимальный объём записи.
max_allowed_packet = 16M

На опции ниже так же нужно обратить внимание, если скорость выполнения запросов вызывает нарекания.

innodb_flush_log_at_trx_commit = 2

innodb_open_files = 300

innodb_buffer_pool_size = 128MB

query_cache_size = 10M
query_cache_limit = 1M

16 февраля, 2012

Exim frozen message

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

Беглый анализ показал, что сообщения сервер не покидали. Вывод mailq пестрел сообщениями вида «3h 5.1K 1RxZQT-0005q8-O8 <> *** frozen ***». В ответ на sendmail -q в лог exim сыпалось примерно то же самое: «2012-02-15 07:35:13 1RxEYG-0001lU-JE Message is frozen». При попытке отправить e-mail в лог падает сообщение «Mailing to remote domains not supported».

Тут всё становится ясно, exim заботливо настроен на доставку только локальной почты. Ситуацию исправляется можно с помощью команды dpkg-reconfigure exim4-config. После правильной настройки, свежая почта начинает доходить до адресата, но замёрзшая так и висит в очереди. Протолкнуть её можно не хитрой командой, tail выполняет роль дозатора:

mailq  | grep "*** frozen ***" | awk '{print $3}' | tail -10 | xargs exim -d -M

12 февраля, 2012

Проблемы с кодировкой в Zabbix

Мониторинг дело полезное, настало время заняться вплотную IIS, до этого дальше проверки наличия w3wp.exe в списке процессов дело не шло. Полный список счетчиков, доступных в системе, можно посмотреть с помощью утилиты typeperf с ключем -qx. Ключем -o лучше не пользоваться, перенаправление вывода - лучший способ записать список в файл.

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

# echo 'perf_counter[\W3SVC_W3WP(_Total)\Всего потоков]' | nc alpha.example.com 10050
ZBXD    20.000000

Однако, при попытке использовать ключи с латиницей в web-интерфейсе, русские буквы заменялись вопросами.

Опущу скучный процесс копания в коде. Во-первых кодировка БД очередной раз оказалась latin1. Если БД большая, как оказалось вв моём случае, то для решения проблемы достаточно конвертировать таблицу items. Вторая проблема - баг ZBX-4044, исправленный в версии 1.8.7. Если нет желания ставить новую версию - можно убрать строчку «DBexecute('SET CHARACTER SET utf8');» в файле include/db.inc.php

Дело в том, что SET NAMES utf8 устанавливает значение переменных character_set_client, character_set_connection, character_set_results в «utf8», а collation_connection в «utf8_general_ci». SET CHARACTER SET utf8 делает почти то же самое, но character_set_connection и collation_connection сбрасываются к значению по умолчанию, а это, для не настроенного MySQL, в «latin1» и «utf8_general_ci», соответственно.

11 февраля, 2012

Nginx open file limit

Думаю любой администратор, работавший с Nginx в Debian/Ubuntu под большой нагрузкой, сталкивался с сообщением «failed (24: Too many open files)» в логах. По дефолту в Debian лимит на количество открытых файлов равен 1024, по странному стечению обстоятельств лимит конектов у Nginx так же равен 1024. Очевидно, что сервер будет использовать файловые дескрипторы не только для соединения с клиентом, но и для загрузки библиотек, работы с логами, чтения статики или соединения с бэкендом. Естественно Nginx упирается в лимит файловых дескрипторов раньше, чем в лимит соединений.

К сожалению, не все знают как правильно бороться с этой проблемой. Наихудший путь - правка /etc/init.d/nginx, а бесполезный - правка /etc/security/limits.conf. Думаю, причины по которым не стоит править скрипты инициализации очевидны. Правка limits.conf не поможет, потому что это конфигурационный файл PAM, а он в запуске Nginx не участвует.

Универсальный метод - правка /etc/default/nginx, он будет работать с другими демонами, например OpenFire (естественно, править надо будет /etc/default/openfire). Добавим в файл строку:

ulimit -n 4048

проверим:

root@server:/etc# ps axu | grep nginx
root     14572  0.0  0.0  30188   924 ?        Ss   22:45   0:00 nginx: master process /usr/sbin/nginx
www-data 14573  0.0  0.1  30576  1496 ?        S    22:45   0:00 nginx: worker process
root     14575  0.0  0.0   7568   868 pts/6    S+   22:45   0:00 grep nginx
root@lab:/etc# cat /proc/14573/limits 
***
Max open files            4048                 4048                 files     
***     

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

Не забываем, что worker_rlimit_nofile должен быть больше, чем worker_connections. Формула worker_rlimit_nofile = worker_connections * 2 + 32 должна дать рабочее значение.

10 февраля, 2012

IIS 7.0 redirect

В связи с переездом внутренней инфраструктуры на новый домен, решено было настроить редиректы со старого домена на новый. Так как процесс настройки IIS интуитивно понятным не назовёшь, решил сделать памятку. На скринах IIS в английской локализации, надо будет так же на остальных серверах сделать :)

Для начала нужно скачать и установить модуль URL Rewrite. После установки в диспетчере служб IIS появится иконка "URL Rewrite" (в русской локализации "Переопределение URL-адресов")

pic01.png

После двойного клика по иконке, попадаем в панель настроек модуля, пока тут пусто. Жмём "Add Rule(s)" ("Добавить правила")

pic02.png

Название произвольное. Под "Match URL" ("Соответствует URL-адрес") подразумивается не URL, как можно было подумать, а строка запроса (query string). Чтобы не перенаправлять всё подряд задаём условия ("Conditions"), в нашей задаче это {HTTP_HOST} равный старому имени.

pic03.png

"Action" ("Действие") указывает что именно нужно сделать, в нашем случае постоянный редирект (301) с сохранением строки запроса.

pic04.png

Собственно всё, чтобы правило вступило в силу достаточно нажать ("Apply") "Применить"

08 февраля, 2012

tmpfs vs ramfs

Такая картина ждала меня, на неожиданно пропавшем из эфира сервере:

Kernel panic - not syncing: Out of memory and no killable processes...

После перезагрузки в истори команд нашлась строчка: «mount -t ramfs -o size=256m ramfs /media/ramdisk/». На разделе в памяти размещались данные web-приложения. Интересно, согласно man mount у ramfs нет опции size, точнее у неё вообще нет опций. Действительно:

root@server:~# mount -t ramfs -o size=1m ramfs /media/ramdisk/
root@server:~# dd if=/dev/urandom of=/media/ramdisk/test bs=1024K count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 1.58535 s, 6.6 MB/s
root@baku:~# ls -lh /media/ramdisk/
total 10M
-rw-r--r-- 1 root root 10M Feb  8 18:35 123

ramfs проглатывает любые опции, что может ввести в заблуждение невнимательного администратора. На самом деле ramfs никак не ограничивает размер данных, который вы пытаетесь в неё поместить. Если размер данных достигнет размера оперативной памяти, то на экране вас будет ждать сообщение «Kernel panic - not syncing: Out of memory and no killable processes...» Даже если на компьютере достаточно swap'а, ramfs не будет его использовать.

С tmpfs таких проблем не возникнет. Если опция size, то она считается равной половине объёма оперативной памяти.

root@server:~# mount -t tmpfs -o size=1m ramfs /media/ramdisk/
root@server:~# dd if=/dev/urandom of=/media/ramdisk/123 bs=1024K count=10
dd: writing `/media/ramdisk/123': No space left on device
1+0 records in
0+0 records out
1040384 bytes (1.0 MB) copied, 0.160041 s, 6.5 MB/s

В общем, ramfs стоит использовать только если критично расположение данных именно в памяти (tmpfs может уходить в swap) и размер файлов находится под жестким контролем. В остальных случаях в качестве «быстрого раздела» лучше использовать tmpfs.

В Debian из коробки есть 2 раздела с tmpfs: /lib/init/rw и /dev/shm. Первый используется init-скриптами, второй доступен всем пользователям, является аналогом /tmp. По дефолту размер этих разделов равняется половине оперативной памяти, он может быть изменён с помощью переменных TMPFS_SIZE и SHM_SIZE в файле /etc/default/tmpfs. Первая переменная регулирует размер /lib/init/rw. Размер /dev/shm определяется переменной SHM_SIZE, если она не задана будет использована TMPFS_SIZE, если не задана и она - размер будет равным половине оперативной памяти.

05 февраля, 2012

RFC 7xx

Забавный проект(eng, перевод), предлагающий расширить набор кодов состояния HTTP, попал мне на глаза. В принципе ничего особенного, но некоторые коды задели за живое, чего стоят «721 Known Unknowns», «722 Unknown Unknowns», «742 A kitten dies». Решил оставить здесь ссылки.

К стати, оно работает! Приведённый ниже HTTP-ответ обрабатывается браузером, контент отображается.

HTTP/1.1 721 Known Unknowns
Date: Wed, 05 Feb 2012 11:20:59 GMT
Server: Apache
X-Powered-By: nc
Last-Modified: Wed, 05 Feb 2012 11:20:59 GMT
Content-Language: ru
Content-Type: text/html; charset=utf-8
Content-Length: 15
Connection: close

Known Unknowns

03 февраля, 2012

Meta, PageUP и PageDown в терминале MacOS

Когда пересел за Mac меня сильно раздражали не работающие (как мне надо) клавиши meta (она же левый Alt), PageUp и PageDown. Коллеги, тоже испытывающие некоторые неудобства, не подсказали решения, пришлось копаться самому.

Галочка «Use option as meta key» ставится в настройках (Command-, или пункт Preferences в меню Terminal), вкладка Settings, вкладка Keyboard.

С PageUp и PageDown чуть сложнее, в той же вкладки нужно найти соответствующие клавиши, в меню «Action» выбрать «send string to shell», вбить для PageUp - «\033[5~», PageDown - «\033[6~». «\033» вводится нажатием ctrl-[

PROFIT :)

31 января, 2012

OCS Inventory NG и 500-я ошибка

Сегодня коллеги попросили помочь разобраться с проблемой: при отправке данных в OCS агент получал в ответ ошибку с кодом 500. После включения логирования ошибок (нужно в конфиге выставить OCS_OPT_DBI_PRINT_ERROR в 1) в логах появились следующие ошибки:

DBD::mysql::db do failed: Illegal mix of collations (latin1_swedish_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '=' at /usr/local/share/perl/5.10.1/Apache/Ocsinventory/Server/Inventory/Cache.pm line 34.

Вот на этом месте OCS спотыкается:

# sed '260,265p;d' /usr/local/share/perl/5.10.1/Apache/Ocsinventory.pm
  # Retrieve Device if exists
  $request = $CURRENT_CONTEXT{'DBI_HANDLE'}->prepare('
    SELECT DEVICEID,ID,UNIX_TIMESTAMP(LASTCOME) AS LCOME,UNIX_TIMESTAMP(LASTDATE) AS LDATE,QUALITY,FIDELITY 
    FROM hardware WHERE DEVICEID=?'
  );
  unless($request->execute($CURRENT_CONTEXT{'DEVICEID'})){

Ну тут всё понятно - имя в кириллице, меняем на латиницу - всё работает. Проблема локализована. А чтобы OCS могла импортировать имена в кириллице нужно немного поправить БД:

ALTER TABLE `ocs`.`hardware` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `ocs`.`hardware` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

Ну а лучше не полениться и конвертировать всю БД простым скриптом на PHP:

<?php
$connection = mysql_connect('localhost', 'debian-sys-maint', '**************') or die ('Could not connect to server');
mysql_select_db('ocs', $connection) or die ('Could not select DB');
mysql_query("alter database `ocs` default character set 'utf8' collate 'utf8_general_ci'", $connection);
$result = mysql_query("show tables", $connection);
while($row = mysql_fetch_row($result)) {
   mysql_query("alter table `$row[0]` convert to character set 'utf8' collate 'utf8_general_ci'", $connection);
   mysql_query("alter table `$row[0]` default character set 'utf8' collate 'utf8_general_ci'", $connection);
}

Те, кто заранее позаботился о настройке сервера с этой проблемой не столкнутся, достаточно двух строчек в /etc/my.cnf:

default-character-set   = utf8  
default-collation       = utf8_general_ci

Естественно настраивать надо было до установки OCS

25 января, 2012

Сборка deb-пакета на примере swftools

Иногда нужной программы (или нужной версии программы) не оказывается в репозитории и приходится ставить её самостоятельно. Ставить через 'make install' вариант плохой, даже если ставить в домашнюю директорию, или /usr/local - через некоторое время понимаешь, управлять софтом в этой «помойке» не представляется возможным. Ранее мне приходилось пользоваться утилитами alien и checkinstall, но в этот раз потребовалось собрать свежие SWFTools в пакет с корректно указанными зависимостями.

Процесс оказался несколько дольше, чем я предпологал, в основном из-за необходимости патча на сборочные скрипты. Во-первых кто-то забыл дописать к цели clear удаление бинарника swfrender, что крайне «не нравилось» dpkg-buildpackage. Во-вторых система сборки «клала с прибором» на переменную DESTDIR, из-за чего install пытался писать файлы туда, куда пускаю только суперпользователя. Обе проблемы решил патч, приведённый ниже.

diff -rupN swftools-0.9.1.orig/avi2swf/Makefile.in swftools-0.9.1/avi2swf/Makefile.in
--- swftools-0.9.1.orig/avi2swf/Makefile.in     2009-08-05 14:21:22.000000000 +0400
+++ swftools-0.9.1/avi2swf/Makefile.in  2012-01-11 15:56:16.000000000 +0400
@@ -25,8 +25,8 @@ avi2swf$(E): avi2swf.$(O) v2swf.$(O) vid
        $(STRIP) avi2swf$(E)
 
 install:
-       $(mkinstalldirs) $(bindir)
-       $(mkinstalldirs) $(man1dir)
+       $(mkinstalldirs) $(DESTDIR)$(bindir)
+       $(mkinstalldirs) $(DESTDIR)$(man1dir)
        @file=avi2swf;$(INSTALL_BIN);$(INSTALL_MAN1)
        
 uninstall:
diff -rupN swftools-0.9.1.orig/lib/pdf/Makefile.in swftools-0.9.1/lib/pdf/Makefile.in
--- swftools-0.9.1.orig/lib/pdf/Makefile.in     2010-06-06 06:38:42.000000000 +0400
+++ swftools-0.9.1/lib/pdf/Makefile.in  2012-01-11 15:56:16.000000000 +0400
@@ -195,7 +195,7 @@ gfx2gfx$(E): $(XPDFOK) ../../src/gfx2gfx
        $(LL) $(CPPFLAGS) -g ../../src/gfx2gfx.c $(libgfxpdf_objects) $(xpdf_in_source) $(splash_in_source) $(gfx_objects2) -o gfx2gfx$(E) $(LIBS)
 
 install:
-       $(mkinstalldirs) $(bindir)
+       $(mkinstalldirs) $(DESTDIR)$(bindir)
        @for file in pdfinfo pdftoppm pdftotext; do if test -f $$file;then $(INSTALL_BIN);fi;done
 
 uninstall:
diff -rupN swftools-0.9.1.orig/Makefile.common.in swftools-0.9.1/Makefile.common.in
--- swftools-0.9.1.orig/Makefile.common.in      2010-04-03 23:34:42.000000000 +0400
+++ swftools-0.9.1/Makefile.common.in   2012-01-11 15:56:16.000000000 +0400
@@ -59,7 +59,7 @@ datadir = @datadir@
 libdir = @libdir@
 includedir = @includedir@
 sysconfdir = @sysconfdir@
-pkgdatadir = $(datadir)/@PACKAGE@
+pkgdatadir = $(DESTDIR)$(datadir)/@PACKAGE@
 
 # man pages
 mandir = @mandir@
@@ -67,10 +67,10 @@ man1dir = $(mandir)/man1
 
 # ------------------- defines -------------------------
 
-INSTALL_BIN = echo installing $$file to $(bindir);$(INSTALL_PROGRAM) $$file $(bindir)/`echo $$file|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`
-UNINSTALL_BIN = ff=`echo $$file|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`;echo rm -f $(bindir)/$$ff;rm -f $(bindir)/$$ff
-INSTALL_MAN1 = ff=$(srcdir)/$$file.1; inst=`echo $$file | sed '$(transform)'`.1; echo "$(INSTALL_DATA) $$ff $(man1dir)/$$inst"; $(INSTALL_DATA) $$ff $(man1dir)/$$inst
-UNINSTALL_MAN1 = ff=$(srcdir)/$$file.1; inst=`echo $$file | sed '$(transform)'`.1; echo "rm -f $(man1dir)/$$inst"; rm -f $(man1dir)/$$inst
+INSTALL_BIN = echo installing $$file to $(DESTDIR)$(bindir);$(INSTALL_PROGRAM) $$file $(DESTDIR)$(bindir)/`echo $$file|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`
+UNINSTALL_BIN = ff=`echo $$file|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`;echo rm -f $(DESTDIR)$(bindir)/$$ff;rm -f $(DESTDIR)$(bindir)/$$ff
+INSTALL_MAN1 = ff=$(srcdir)/$$file.1; inst=`echo $$file | sed '$(transform)'`.1; echo "$(INSTALL_DATA) $$ff $(DESTDIR)$(man1dir)/$$inst"; $(INSTALL_DATA) $$ff $(DESTDIR)$(man1dir)/$$inst
+UNINSTALL_MAN1 = ff=$(srcdir)/$$file.1; inst=`echo $$file | sed '$(transform)'`.1; echo "rm -f $(DESTDIR)$(man1dir)/$$inst"; rm -f $(DESTDIR)$(man1dir)/$$inst
 
 #%.o: %.c
 #      $(C) $< -o $@
diff -rupN swftools-0.9.1.orig/src/Makefile.in swftools-0.9.1/src/Makefile.in
--- swftools-0.9.1.orig/src/Makefile.in 2010-04-03 23:34:42.000000000 +0400
+++ swftools-0.9.1/src/Makefile.in      2012-01-11 15:56:16.000000000 +0400
@@ -129,8 +129,8 @@ swfc$(E): parser.$(O) swfc.$(O) swfc-fee
        $(STRIP) $@
 
 install:
-       $(mkinstalldirs) $(bindir)
-       $(mkinstalldirs) $(man1dir)
+       $(mkinstalldirs) $(DESTDIR)$(bindir)
+       $(mkinstalldirs) $(DESTDIR)$(man1dir)
        @for file in $(programs) $(opt_programs); do if test -f $$file;then $(INSTALL_BIN);$(INSTALL_MAN1);fi;done
        
 uninstall:
@@ -138,9 +138,9 @@ uninstall:
 
 clean:  
        rm -f *.o *.obj *.lo *.la *~ gmon.out
-       rm -f as3compile gif2swf swfbbox swfbytes swfbytes swfdump pdf2swf wav2swf png2swf swfcombine swfextract swfstrings png2swf jpeg2swf swfc font2swf pdf2pdf gfx2gfx
-       @rm -f gif2swf.exe swfbytes.exe swfbytes.exe pdf2swf.exe swfbbox.exe swfdump.exe wav2swf.exe png2swf.exe swfcombine.exe swfextract.exe swfstrings.exe png2swf.exe jpeg2swf.exe swfc.exe font2swf.exe pdf2pdf.exe gfx2gfx.exe
-       @rm -f gif2swf$(E) pdf2swf$(E) swfbytes$(E) swfbytes$(E) swfbbox$(E) swfdump$(E) wav2swf$(E) png2swf$(E) swfcombine$(E) swfextract$(E) swfstrings$(E) png2swf$(E) jpeg2swf$(E) swfc$(E) font2swf$(E) pdf2pdf$(E) gfx2gfx$(E)
+       rm -f as3compile gif2swf swfbbox swfbytes swfbytes swfdump pdf2swf wav2swf png2swf swfcombine swfextract swfstrings png2swf jpeg2swf swfc font2swf pdf2pdf gfx2gfx swfrender
+       @rm -f gif2swf.exe swfbytes.exe swfbytes.exe pdf2swf.exe swfbbox.exe swfdump.exe wav2swf.exe png2swf.exe swfcombine.exe swfextract.exe swfstrings.exe png2swf.exe jpeg2swf.exe swfc.exe font2swf.exe pdf2pdf.exe gfx2gfx.exe swfrender.exe
+       @rm -f gif2swf$(E) pdf2swf$(E) swfbytes$(E) swfbytes$(E) swfbbox$(E) swfdump$(E) wav2swf$(E) png2swf$(E) swfcombine$(E) swfextract$(E) swfstrings$(E) png2swf$(E) jpeg2swf$(E) swfc$(E) font2swf$(E) pdf2pdf$(E) gfx2gfx$(E) swfrender$(E)
 
 doc:
        perl ../parsedoc.pl wav2swf.doc

ИМХО, собирать лучше в свежеустановленной системе. При установке кроме базовой системы и ssh-сервера можно ничего не выбирать. После того, как система для сборки готова, ставим нужные для сборки пакеты:

sudo apt-get install autoconf automake libtool autotools-dev fakeroot dh-make

На начальном этапе можно задать e-mail майнтейнера, то есть наш:

export DEBEMAIL=anton@geekhere.com

Хотя особого смысла в этом не вижу, всё равно придётся править debian/control. Создадим каталог для сборки:

mkdir build/swftools/0.9.1/
cd build/swftools/0.9.1/

Путь особого сакрального смысла не имеет, заметил её в одном из HOWTO, которые просматривал перед сборкой, понравилась. Скачиваем исходные коды, сразу присваиваем им имя, которое ожидает dpkg-buildpackage

wget http://www.swftools.org/swftools-0.9.1.tar.gz -O swftools_0.9.1.orig.tar.gz

Распакуем исходники:

tar zxvf swftools_0.9.1.orig.tar.gz

Накатываем патч, описанный выше:

patch -p0 < 0.9.1_deb.patch
Создаём конфигурационные файлы будущего пакета:
cd swftools-0.9.1/
dh_make

Тут нам нужно навести лоск, поправив файлы в директории debian. Наш пакет ну будет содержать pre-install, post-install и т.д. скриптов, поэтому достаточно отредактировать файл debian/control. Заполним пункты Homepage и Description. Можно собирать пакет!

dpkg-buildpackage -rfakeroot

Всё, пакет готов и находится каталогом выше (../swftools_0.9.1-1_amd64.deb )

Потратив время на создание патча я задумался, нельзя ли было пойти более простым путём? Действительно, собрать пакет с checkinstall несколько проще:

sudo apt-get install checkinstall
cd swftools-0.9.1/
./configure --prefix=/usr
make
sudo checkinstall -D -y --pkgname=swftools --pkgversion=0.9.1

Патчить исходники не надо - это плюс, зависимости у пакета не прописаны - это минус. Ну что же, посмотри что внутри пакета:

dpkg --extract swftools_0.9.1-1_amd64.deb swftools_0.9.1-1_amd64
dpkg --control swftools_0.9.1-1_amd64.deb swftools_0.9.1-1_amd64/DEBIAN

Список библиотек, от которых зависит swftool получить довольно просто:

$ ldd swftools_0.9.1-1_amd64/usr/bin/* | sed 's,.*[[:space:]]\(/[^ ]*\).*,\1,g ; /^[^/]/d' | \
    xargs -L1 dpkg -S | sed 's/:.*//g' | sort -u | xargs dpkg -l | egrep '^ii'
dpkg: файл /lib64/ld-linux-x86-64.so.2 не найден.
                    -//-
ii  libc6                               2.11.2-10                    Embedded GNU C Library: Shared libraries
ii  libfreetype6                        2.4.2-2.1+squeeze3           FreeType 2 font engine, shared library files
ii  libgcc1                             1:4.4.5-8                    GCC support library
ii  libgif4                             4.1.6-9                      library for GIF images (library)
ii  libjpeg8                            8b-1                         The Independent JPEG Group's JPEG runtime library
ii  libstdc++6                          4.4.5-8                      The GNU Standard C++ Library v3
ii  zlib1g                              1:1.2.3.4.dfsg-3             compression library - runtime

На ld-linux-x86-64.so.2 можно не обращать внимания. Для сравнения зависимости, которые сгенерил dpkg-buildpackage: libc6 (>= 2.7), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libgif4 (>= 4.1.4), libjpeg8, libstdc++6 (>= 4.1.1), zlib1g (>= 1:1.1.4) Можно отредактировать swftools_0.9.1-1_amd64/DEBIAN/control и собрать пакет обратно:

dpkg-deb --build swftools_0.9.1-1_amd64 swftools_0.9.1-1_amd64.deb

Полезные ссылки:
Подробнее о файле control (en)
Внутреннее устройство пакетов Debian (ru)

23 января, 2012

Простой скрипт для бэкапа MySQL-баз

Сразу оговорюсь, для нагруженных серверов с тяжелыми БД нужно использовать репликацию и/или XtraBackup. Но если требуется организовать бэкап на машине, где работает полтора разработчика, или дохлой VPS'ке, то решение вполне подходящее.

#!/bin/sh
# Run script from this user
RUNAS="mysql"
# Credentials to run mysqldump
MYSQL_USER="root" 
MYSQL_PASS="SomePassword"
# database for backup
DATABASES="db1 db2"
# dir for backup
BDIR="/var/backup/mysql"

# If need be, switch user
if [ -n "$RUNAS" -a "$RUNAS" != "$(id -nu)" ] ; then
    su -c "$0" - $RUNAS 
    exit $?
fi

DATE=`date +"%Y-%m-%d_%H.%M"`

# create backup dir if not exist, exit on fail 
[ -d "$BDIR" ] || mkdir -p "$BDIR" || exit 1

# check write permission, exit if not granted
[ -w "$BDIR" ] || exit 1

for db in $DATABASES ; do
    # do backup
    /usr/bin/mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASS" "$db" | \
        gzip -9c > "${BDIR}/${db}_${DATE}.sql.gz"
    # clear old files
    /usr/bin/find "$BDIR" -mtime +30 -name "${db}_*.sql.gz" -type f -delete
done

Для Ubuntu/Debina можно не задавать логин и пароль для mysqldump, воспользовавшись системной учетной записью (работает только под root):

/usr/bin/mysqldump --defaults-file=/etc/mysql/debian.cnf "$db" | \
    gzip -9c > "${BDIR}/${db}_${DATE}.sql.gz"

KVM, Windows Server 2008 и ограничения на количество сокетов

Microsoft Windows Server 2008 R2 Standard Edition имеет ограничение на количество процессоров - не более 4-х, но при этом в количестве ядер не ограничено. По дефолту KVM каждое выделенное ядро представляет отдельным CPU, из-за чего из 12 выделенных ядер Windows использовала только 4. Лечится настройкой топологии, через virt-manager (если не лень мышкой тыкать), или через редактирование XML-ки (что на мой взгляд быстрее). Во втором случае нужно просто добавить описание топологии, пример ниже:

  <vcpu>12</vcpu>
  <cpu>
    <topology sockets='2' cores='6' threads='1'/>
  </cpu>

Ссылка по теме:
http://libvirt.org/formatdomain.html

21 января, 2012

Об ограничениях CNAME

Вчера ко мне обратился коллега, у него никак не получалось отредактировать файл зоны, точнее изменения никак не отображались в браузере. Записи не редактировались и не удалялись. Предположение о том, что офисный name-сервер что-то закэшировал было сразу откинуто, проблема была на сервере отвечающем за зону. В логах bind при вызове rndc reload появлялись подобные строки:

Jan 20 13:21:35 black named[8289]: dns_master_load: master/example.com:92: sub.example.com: CNAME and other data
Jan 20 13:21:35 black named[8289]: zone example.com/IN/default: loading master file master/example.com: CNAME and other data

После недолгих разбирательств выяснилось, что проблема была в CNAME-записи, для sub.example.com. После замены её на A-запись всё заработало. Отличался sub.example.com от других поддоменов наличием MX и TXT записей.

Почему же CNAME "конфликтует" с MX и TXT-записями? Ответ даёт RFC 1034, пункт 3.6.2:

If a CNAME RR is present at a node, no other data should be present; this ensures that the data for a canonical name and its aliases cannot be different.

Т.е., при использовании CNAME, будут использованы ресурсные записи домена, на который ссылается запись. В примере ниже foo.example.com ссылается на ya.ru

$ dig ya.ru MX

;; QUESTION SECTION:
;ya.ru.                         IN      MX

;; ANSWER SECTION:
ya.ru.                  1844    IN      MX      10 mx.yandex.ru.

$ dig foo.example.com. MX

;; QUESTION SECTION:
;foo.example.com.               IN      MX

;; ANSWER SECTION:
foo.example.com.        86400   IN      CNAME   ya.ru.
ya.ru.                  7199    IN      MX      10 mx.yandex.ru.

С появлением DNSSEC (список RFC тут), ситуация немного изменилась, теперь вместе с CNAME могут использоваться RRSIG, NSEC и KEY

Век живи - век учись...

17 января, 2012

Памятка по XtraBackup

Как быстро склонировать базы данных с одного сервера на другой и поднять реплику:

Чистим старый инстенс mysql на ведомом сервере:

slave # cd /var/lib/mysql/
slave # rm -rf *
slave # mysql_install_db

Копируем БД:

master # innobackupex-1.5.1  --defaults-file=/etc/mysql/my.cnf --slave-info --stream=tar ./ | pv | ssh root@slave "tar ixf - -C /var/lib/mysql/ "

Это вариант для ленивых, если не жалко времени - можно через nc перекинуть. pv можно и не использовать, но медитировать на процесс с ним приятнее. Для распаковки tar обязательно нужно запускать с опцией -i, иначе извлечь из архива получится только один файл (см. man tar). Во FreeBSD извлечь файлы можно с помощью GNU tar.

Восстанавливаем БД, запускаем сервер:

slave # innobackupex-1.5.1 --apply-log  --defaults=/tmp/my.cnf --use-memory=10G ./
slave # /etc/rc.d/mysql-server start

Настраиваем реплику, метки лежат в xtrabackup_binlog_info.

Баг с авторизацией по ключу в RedHat

Долго не мог понять, почему не работает авторизация по ключу в свежеустановленном RedHat. Пока не в выводе strace не увидел строчку:

[pid 21812] open("/root/.ssh/authorized_keys", O_RDONLY|O_NONBLOCK) = -1 EACCES (Permission denied)

Оказывается не даёт читать .ssh/authorized_keys SELinux, лечится выключением последнего. Разбираться как перенастроить SELinux не стал, на данных машинах оно не нужно. Временно (до перезагрузки) выключается через proc, перманентно перманентно через /etc/selinux/config

echo 0 >/selinux/enforce

15 января, 2012

Как линуксоиду выжить в Windows

Человеку, хорошо знающему Bash или другой UNIX-shell, при работе с Windows не хватает привычных утилит. К счастью есть PowerShell. Это не Bash или csh, он не привычен и не столь удобен, но позволяет выполнить некоторые полезные операции. Этот пост будет моей памяткой по PowerShell, надеюсь он окажется полезен ещё кому-нибудь.

grep, что может быть лучше для поиска нужной строки в логах? А как же Windows? А в Windows есть Select-String:

select-string "TO: " C:\Windows\System32\LogFiles\SMTPSVC1\in120113.log

А теперь вариант похуже, скорее для расширения кругозора. Используем Where-Object (он же ? и where, об алиасах немного позже)

cat C:\Windows\System32\LogFiles\SMTPSVC1\in120113.log | ? {$_ -match "TO: "}

Переменная $_ тут является аналогом одноимённый переменной в Perl. cat это алиас для Get-Content (алиасы: cat, gc, type)

Замечательно, а как же grep -R? Тут всё немного сложнее, нам понадобится Get-ChildItem (он же ls, dir и gci)

ls -Recurse -Include *.config C:\inetpub\wwwroot | select-string SMTP

В PowerShell мы не найдём ключа -h и команд man и info. Найти выход поможет Get-Help (он же help)

help ls

Выше уже упоминались алиасы, получить их список в рамках текущей сессии поможет Get-Alias, можно выполнять и обратный поиск:

Get-Alias -Definition Get-Alias

это команда подскажет, что у Get-Alias есть более короткое написание - gal. Создать свои алиасы поможет Set-Alias.

sed, как же его не хватает. Как написать тетрис не знаю, приведу лишь вариант с заменой с помощью ForEach-object (он же % и foreach)

cat somefile.txt | % {$_ -replace "foo","bar"}

head, tail - нам поможет Select-Object (он же select):

cat somefile.txt | select -First 10
cat somefile.txt | select -Last 10

Далее сводная таблица:

UnixPowerShellпримечание
grep -i pattern [file ...]Select-String pattern [file,...] Несколько файлов нужно указывать через запятую, а не через пробел, как в UNIX
grep pattern [file]Select-String -CaseSensitive pattern [file]
grep -iR --include=match pattern ls -Recurse -Include match | Select-String patten
cat [file ...]cat [file,...]
wcmeasure -Line -Word -Character
aliasGet-Alias
passwd username$user = [adsi]"WinNT://hostname/username,user"
$user.SetPassword("password")
envGet-Variable

Пока всё...