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

Пока всё...