суббота, 7 сентября 2013 г.

Установка расширения SHH2 для PHP на CentOS

Установить расширение SHH2 для PHP на CentOS очень просто. Его нельзя поставить через yum install, но все-равно установка сводится к выполнению нескольких простых шагов.

Перед установкой необходимо проверить, что у вас установлен репозиторий EPEL. Если у вас отсутствует данный репозиторий, то перейдите на http://fedoraproject.org/wiki/About_EPEL и следуйте инструкциям.

После того как репозиторий EPEL установлен, можно приступать к установке завимостей.
yum install gcc php-devel php-pear libssh2 libssh2-devel

Это позволит нам установить расширение SSH2 для PHP с помощью pecl.
pecl install -f ssh2

Экстеншен установлен. Дайте опишем конфиг чтобы PHP мог использовать данное расширение.
touch /etc/php.d/ssh2.ini
echo extension=ssh2.so > /etc/php.d/ssh2.ini

Если вы всё сделали правильно, то при выполнении следующей команды вы получите строку с "ssh2".
php -m | grep ssh2

Расширение SSH2 готово к использованию. Не забудьте перезагрузить веб-сервер ;)
Читать далее

пятница, 6 сентября 2013 г.

Установка TeamCity на CentOS

TeamCity — серверное программное обеспечение от компании JetBrains, написанное на языке Java, билд-сервер для обеспечения непрерывной интеграции.

Я узнал о TeamCity два года назад, когда ребята поднимали CI на нашем проекте. В то время я не совсем понимал что из себя представляет CI и как это работает. Со временем мне пришлось создавать новые билды и описывать их конфигурацию в Ant-скриптах. Недавно мне пришлось самому устанавливать TeamCity на "чистую" CentOS. Об этом и расскажу. Будете удивлены, но делается это очень просто.

Для примера я взял CentOS 6.4 и TeamCity 8.3.

1. Устанавливаем Oracle 1.6 JDK.
yum install java-openjdk

2. Устанавливам tomcat 6.*-7.*. Я выбрал шестую версию. Разработчики TeamCity рекомендуют Tomcat 6.0.27+.
yum install tomcat6 tomcat6-webapps tomcat6-admin-webapps

4. Открываем http://www.jetbrains.com/teamcity/download/index.html и качаем версию для Linux.



mkdir /opt/jetbrains
cd /opt/jetbrains
wget http://download.jetbrains.com/teamcity/TeamCity-8.0.3.tar.gz
tar -xzf TeamCity-8.0.3.tar.gz

5. TeamCity скачан, распакован, и находится в папке /opt/jetbrains/TeamCity. Пришло время запустить TeamCity сервер и дефолтный билд-агент.
cd /opt/jetbrains/TeamCity/bin
sh runAll.sh start

Всё готово. Теперь вы можете открыть http://your_host:8111/ и начинать работать с TeamCity. Если всё установилось правильно, то вы должны увидеть страницу с таким содержанием:



Я описал как просто и быстро "завести" TeamCity. О детальной настройке можно прочитать в разделе документации TeamCity.

Читать далее

суббота, 16 марта 2013 г.

SVN: Как мерджить из одной ветки в другую

Subversion была моей основной CVS более трёх лет. С Git я познакомился всего год назад. Не скажу что мой мир сильно изменился, но он мне показался более удобным. По-этому свои личные проекты я храню только в Git, а две недели назад даже купил Micro Plan на GitHub.

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

Нам понадобится Svnmerge.py и две ветки (например, http://svn.local/dev и http://svn.local/qa). Вторая ветка это копия первой, она была создана с помощью 'svn cp' и закомичена ревизией 12830.

Теперь предположим что у нас есть задача, реализацию которой мы запилили в dev-ветку. Список ревизий: 12834, 12839, 12845. Теперь нами стоит задача мерджа этих ревизий в qa-ветку.

1. Создаем папку и выполняем туда чекаут qa-ветки:
mkdir qa 
svn co http://svn.local/qa qa

2. После окончания чекаута нужно положить svnmerge.py в папку qa и сделать его исполняемым.
3. Заходим в папку qa и инициализируем dev-ветку. Эту единоразовая операция, ее не придется повторять в будущем:
svnmerge.py init -r1-12830 http://svn.local/dev
svn ci -F svnmerge-commit-message.txt

12830 это ревизия, в которой была создана qa-ветка. Мы записали в свойства бранча информацию о том, что в нем находится код dev-ветки с 1й по 12830й ревизии. Теперь у нас не получится повторно вмерджить эти ревизии.

4. Собираем необходимые ревизии и проверяем их наличие в dev/qa ветках:
svnmerge.py avail -r12834,12839,12845 -Shttp://svn.local/dev

Если это ревизии dev-верки и они не были ранее вмерджены в qa-ветку, то мы получим список доступных ревизий:
12834,12839,12845

Если какая-то из ревизий отсутствует в списке, на это может быть 2 причины:
  • Это не ревизия dev-ветки;
  • Это ревизия dev-ветки, но она уже была вмерджена в qa-ветку.

5. Мы предполагаем что у нас все хорошо и выполняем команду для мерджа:
svnmerge.py merge -r12834,12839,12845 -Shttp://svn.local/dev # мердж
svn st # проверяем изменения
svn ci -F svnmerge-commit-message.txt # комитим результат

Как видите все не так просто как в Git, но и сложного ничего нет :)

Читать далее

воскресенье, 3 марта 2013 г.

DLE: rewrite правила для nginx

Исторически так сложилось, что DataLife Engine (DLE) это первая CMS, которую я изучил. По-этому некоторые проекты еще крутятся на ней. С каждый годом этот геморрой становится все больнее, но пока что нет времени все переписывать.

2 недели назад мне пришло уведомление из ITL, что на серваке, на котором лежит мой VPS, посыпался винт. Извинились, дали 99% скидку на 2 месяца и сказали что надо бы переезжать на новый VPS. Заказал, предоставили чистую FreeBSD и начал Игорь сисадминить.

Сразу решил полностью отказаться от Apache, использовать только nginx. Думаю, все те кто юзал DLE помнят .htaccess с большим количеством реврайтов. Это и была основная проблема при миграции с Apache+nginx на standalone nginx. Авторы DLE позаботились об этом и предоставили rewrite правила для nginx (ссылка для тех, у кого спизженная CMS отсутствует лицензия - ТЫЦ). Но лично мне они не понравились, по-этому я продолжил поиски и собрал из нескольких источников следующий конфиг:

server {
    listen 80;
    server_name YOUR_HOST;

    root /path/to/document_root;

    rewrite ^/page/(.*)$ /index.php?cstart=$1 last;

    location / {
        rewrite "^/([0-9]{4})/([0-9]{2})/([0-9]{2})(/?)+$" /index.php?year=$1&month=$2&day=$3 last;
        rewrite "^/([0-9]{4})/([0-9]{2})/([0-9]{2})/page/([0-9]+)(/?)+$" /index.php?year=$1&month=$2&day=$3&cstart=$4 last;
        rewrite "^/([0-9]{4})/([0-9]{2})(/?)+$" /index.php?year=$1&month=$2 last;
        rewrite "^/([0-9]{4})/([0-9]{2})/page/([0-9]+)(/?)+$" /index.php?year=$1&month=$2&cstart=$3 last;
        rewrite "^/([0-9]{4})(/?)+$" /index.php?year=$1 last;
        rewrite "^/([0-9]{4})/page/([0-9]+)(/?)+$" /index.php?year=$1&cstart=$2 last;
        rewrite "^/([^.]+)/page/([0-9]+)(/?)+$" /index.php?do=cat&category=$1&cstart=$2 last; 
        rewrite "^/([^.]+)(/?)+$" /index.php?do=cat&category=$1 last;
        index  index.php index.html index.htm;
    }

    location /tags/ {
        rewrite ^/tags/([^/]*)(/?)+$ /index.php?do=tags&tag=$1 last;
        rewrite ^/tags/([^/]*)/page/([0-9]+)(/?)+$ /index.php?do=tags&tag=$1&cstart=$2 last;
    }

    location /user/ {
        rewrite ^/user/([^/]*)/rss.xml$ /engine/rss.php?subaction=allnews&user=$1 last;
        rewrite ^/user/([^/]*)(/?)+$ /index.php?subaction=userinfo&user=$1 last;
        rewrite ^/user/([^/]*)/page/([0-9]+)(/?)+$ /index.php?subaction=userinfo&user=$1&cstart=$2 last;
        rewrite ^/user/([^/]*)/news(/?)+$ /index.php?subaction=allnews&user=$1 last;
        rewrite ^/user/([^/]*)/news/page/([0-9]+)(/?)+$ /index.php?subaction=allnews&user=$1&cstart=$2 last;
        rewrite ^/user/([^/]*)/news/rss.xml(/?)+$ /engine/rss.php?subaction=allnews&user=$1 last;
    }

    location /lastnews/ {
        rewrite ^/lastnews/(/?)+$ index.php?do=lastnews last;
        rewrite ^/lastnews/page/([0-9]+)(/?)+$ /index.php?do=lastnews&cstart=$1 last;
    }

    location /catalog/ {
        rewrite ^/catalog/([^/]*)/rss.xml$ /engine/rss.php?catalog=$1 last;
        rewrite ^/catalog/([^/]*)(/?)+$ /index.php?catalog=$1 last;
        rewrite ^/catalog/([^/]*)/page/([0-9]+)(/?)+$ /index.php?catalog=$1&cstart=$2 last;
    }

    location /newposts {
        rewrite ^/newposts(/?)+$ /index.php?subaction=newposts last;
        rewrite ^/newposts/page/([0-9]+)(/?)+$ /index.php?subaction=newposts&cstart=$1 last;
    }

    location /favorites {
        rewrite ^/favorites(/?)+$ /index.php?do=favorites last;
        rewrite ^/favorites/page/([0-9]+)(/?)+$ /index.php?do=favorites&cstart=$1 last;
    }

    location ~ \.(html|xml) {
        rewrite "^/([0-9]{4})/([0-9]{2})/([0-9]{2})/page,([0-9]+),([0-9]+),(.*).html(/?)+$" /index.php?subaction=showfull&year=$1&month=$2&day=$3&news_page=$4&cstart=$5&news_name=$6 last;
        rewrite "^/([0-9]{4})/([0-9]{2})/([0-9]{2})/page,([0-9]+),(.*).html(/?)+$" /index.php?subaction=showfull&year=$1&month=$2&day=$3&news_page=$4&news_name=$5 last;
        rewrite "^/([0-9]{4})/([0-9]{2})/([0-9]{2})/print:page,([0-9]+),(.*).html(/?)+$" /engine/print.php?subaction=showfull&year=$1&month=$2&day=$3&news_page=$4&news_name=$5 last;
        rewrite "^/([0-9]{4})/([0-9]{2})/([0-9]{2})/(.*).html(/?)+$" /index.php?subaction=showfull&year=$1&month=$2&day=$3&news_name=$4 last;
        rewrite "^/([^.]+)/page,([0-9]+),([0-9]+),([0-9]+)-(.*).html(/?)+$" /index.php?newsid=$4&news_page=$2&cstart=$3 last;
        rewrite "^/([^.]+)/page,([0-9]+),([0-9]+)-(.*).html(/?)+$" /index.php?newsid=$3&news_page=$2 last;
        rewrite "^/([^.]+)/print:page,([0-9]+),([0-9]+)-(.*).html(/?)+$" /engine/print.php?news_page=$2&newsid=$3 last;
        rewrite "^/([^.]+)/([0-9]+)-(.*).html(/?)+$" /index.php?newsid=$2 last;
        rewrite "^/page,([0-9]+),([0-9]+),([0-9]+)-(.*).html(/?)+$" /index.php?newsid=$3&news_page=$1&cstart=$2 last;
        rewrite "^/page,([0-9]+),([0-9]+)-(.*).html(/?)+$" /index.php?newsid=$2&news_page=$1 last;
        rewrite "^/print:page,([0-9]+),([0-9]+)-(.*).html(/?)+$" /engine/print.php?news_page=$1&newsid=$2 last;
        rewrite "^/([0-9]+)-(.*).html(/?)+$" /index.php?newsid=$1 last;
        rewrite "^/static/(.*).html(/?)+$" /index.php?do=static&page=$1 last;
        rewrite ^/rules.html$ /index.php?do=rules last;
        rewrite ^/statistics.html$ /index.php?do=stats last;
        rewrite ^/addnews.html$ /index.php?do=addnews last;
        rewrite ^/([^.]+)/rss.xml$ /engine/rss.php?do=cat&category=$1 last;
        rewrite ^/page,([0-9]+),([^/]+).html$ /index.php?do=static&page=$2&news_page=$1 last;
        rewrite ^/print:([^/]+).html$ /engine/print.php?do=static&page=$1 last;
        rewrite ^/rss.xml$ /engine/rss.php last;
        rewrite ^/sitemap.xml$ /uploads/sitemap.xml last;
        rewrite ^/([^/]+).html$ /index.php?do=static&page=$1 last;
    }

    location ~* (uploads|uploads/fotos|templates|language)/.+\.php {
        deny all;
    }

    location ~* /templates/.+\.tpl {
        deny all;
    }

    location ~* (engine/cache) {
        deny all;
    }

    location ~ /\.ht {
        deny  all;
    }

    location ~* \.(jpg|jpeg|gif|png|ico|swf|css|js)$ {
        expires             30d;
        add_header          Cache-Control public;
    }

    # extra login to admin panel
    location =/admin.php {
        auth_basic            "closed section";
        auth_basic_user_file  htpasswd;
        fastcgi_pass   unix:/tmp/fastcgi.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }

    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000; # or socket
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

Конфиг проверен и успешно работает на моем проекте, но не стоит вслепую его копировать. Лучше разобраться: возможно, что-то вам покажется лишним или, наоборот, недостаточным.

Читать далее

четверг, 28 февраля 2013 г.

SVN: делаем файл исполняемым

Последние 4 дня занимаюсь созданием CI билдов для TeamCity. Одна из задач - запускать php скрипты через Apache Ant. Для того чтобы его запускать нужно сделать его исполняемым. Вариант добавления 'chmod +x file' в Ant скрипт не подходит, так как это костыль.

Начал гуглить и нашел.

Чтобы сделать файл исполняемым необходимо выполнить:
svn propset svn:executable ON filename

Обратная операция:
svn propdel svn:executable ON filename

Читать далее

среда, 6 февраля 2013 г.

JavaScript: сортировка массивов и json-объектов

Сортировка массива:
[1, 3, 9, 2].sort();

С сортировкой json-объектов дела состоят немного сложнее, так как сортировать приходится по какому-то значению: цена, возраст и т.д. Для этого в функцию sort можно передавать функцию сортировки.

Пример сортировки по-возрастанию:
[
  { name: "Item 1", price: 2000 },
  { name: "Item 2", price: 1000 },
  { name: "Item 4", price: 1200 },
  { name: "Item 3", price: 3000  }
].sort(function(obj1, obj2) {
  return obj1.price-obj2.price;
});

Пример сортировки по-убыванию:
[
  { name: "Item 1", price: 2000 },
  { name: "Item 2", price: 1000 },
  { name: "Item 4", price: 1200 },
  { name: "Item 3", price: 3000  }
].sort(function(obj1, obj2) {
  return obj2.price-obj1.price;
});

Читать далее

вторник, 5 февраля 2013 г.

PHP: особенности функции ip2long

Думаю, большинство программистов встречались с задачей хранения ip в БД. Хранить их как plain text (например, 127.0.0.1) неудобно и непрактично. Потому принято использовать функцию ip2long, которая преобразовывает ip адрес в целове число.

Но есть некоторые особенности ее использования в x32/x64. Допустим, для его хранения мы выделяем INT ячейку в БД.

Результат функции на x32:
ip2long(127.127.127.127) = 2147483648
ip2long(255.255.255.255) = -2147483648

Результат функции на x64:
ip2long(127.127.127.127) = 2147483648
ip2long(255.255.255.255) = 4294967296

Получается что 4294967296 не запишется в БД, так как ячейка в нашей БД типа INT и максимальное число, которое можно в нее записать: 2147483648.

Потому добавляем для атрибут UNSIGNED, который позволяет записывать в БД диапазон чисел от 0 до 4294967296.

Но как же быть с x32? Ведь в ней функция ip2long может вернуть отрицательное значение. Для решения этой проблемы нужно обрабатывать результат перед записью БД:
$ip = sprintf('%u', ip2long($ip)); // возвращает строку с положительным значением

Читать далее

суббота, 2 февраля 2013 г.

четверг, 31 января 2013 г.

Magento: выключение всех модулей в local code pool

Magento позволяет быстро и безболезненно выключить все модули, котолые лежат в local code pool (app/code/local). Для этого нужно открыть файл app/etc/local.xml и установить true для ноды disable_local_modules:

    
        ...
        true
        ...
    
    ...


Если у вас включено кеширование, то после сохранения изменений нужно очистить кеш.

Это действие предотвратит использование любого кода из вашего local code pool и поможет разобраться с проблемами переопределения кода.
Читать далее

воскресенье, 20 января 2013 г.

PHP: кавычки в str_replace()

Недавно передо мной стала задача удаления некоторых ASCII Device Control Characters из текста. Поскольку удалять нужно было определенные символы, я решил использовать str_replace(). В процессе имплементации было обнаружено разное поведение функции, в зависимости от типа кавычек, в которых мы передаем первый параметр.

Одинарные кавычки
str_replace('\x07', '', $text);
В этом случае функция ищет вхождения плейнтекста '\x07'.

Двойные кавычки
str_replace("\x07", '', $text);
В этом случае функция ищет вхождения ASCII символа '\x07'.
Читать далее