вторник, 15 сентября 2015 г.

Атомарное развертывание web приложения

Нужно развернуть новую версию web приложения написанного на php, в качестве веб сервера выступает nginx. Проблема возникает из-за realpath_cache описано http://habrahabr.ru/post/266909/ а решение хорошо описано тут http://sauna.dealdash.com/atomic-deploys-with-nginx-and-php-fpm/

суббота, 1 августа 2015 г.

Nginx в качестве прокси и редирект

На передовой стоит nginx который обслуживает основные запросы, но часть серверов обслуживает апач на порту 8080. И если апач делает редирект (например на страницу авторизации) то все ломается, т.к. 8080 порт с наружи не виден. Нужно заставить nginx переписывать ответ на редирект

server {
        listen 80;
        server_name ~^(.+)\.v1\.ac-test\.tk$;

        location / {
                resolver 8.8.8.8;
                proxy_set_header        X-Real-IP       $remote_addr;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://$host:8080;
                proxy_redirect     http://$host:8080/ http://$host/;
                proxy_set_header   Host $host;
        }
}

Надо учесть что для такой настройки требуется dns сервер, который будет резолвить наши  хосты. Если нужно резолвить на основе файла hosts, тогда придется установить dnsmasq

Виртуальный хостинг на apache с поддержкой CGI

Для одного проекта потребовалось настроить апач, чтобы каждый поддомен смотрел в свой каталог (т.е. в свою ветку git) Почему апач? Часть проекта написана на parser3 это такое говнище от лебедя. Можно было конечно прикрутить cgi к nginx но я и так потерял много времени, да и проекта переписывается на php.
Конфиг для Apache

<VirtualHost *:80>
    ServerAlias *.ac-test.tk
    UseCanonicalName Off
    VirtualDocumentRoot /opt/autocrm.v1/%1/shared/www
    VirtualScriptAlias  /opt/autocrm.v1/%1/shared/www/cgi-bin

<Directory /opt >
    Options Indexes MultiViews FollowSymlinks ExecCGI
    AllowOverride All
    Require all granted
</Directory>

</VirtualHost>

Главное в основном конфиге апача закоментировать строчку

#    ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"

Теперь при запросе http://dev.ac-test.tk апач будет лезть в папку /opt/autocrm.v1/dev/shared/www

Бесплатные домены

ЗонаРегистратор
TRhttp://www.dot.tk/ru/index.html?lang=ru
MLhttp://www.point.ml/en/index.html?lang=en
GAhttp://www.my.ga/en/index.html?lang=en
CFhttp://www.dot.cf/en/index.html?lang=en


Вообще это зоны каких-то "банановых республик", регистратор у них один freenom.com
Панель управления DNS у них "туповатая", поэтому я созданные домены делегировал яндексу. Там и почта есть и * можно использовать.

вторник, 21 июля 2015 г.

Самоподписной wildcard сертификат

Создаем конфигурационный файл openssl.conf примерного содержания


[req]
default_bits = 4096       # Длинна ключа в битах.
default_keyfile = cert.key    # Имя файла, в который будет записан закрытый ключ.
encrypt_key = no                    # Нам не нужно шифровать закрытый ключ паролем.
default_md = sha512              # Алгоритм хеша.
x509_extensions = v3_req      # Включаем расширение V3.
prompt = no                            # Не нужно запрашивать данные у пользователя, мы всё пропишем здесь.
distinguished_name = req_distinguished_name         # Имя секции с данными (может быть любым).

[req_distinguished_name]
C = RU     # Country - Двухбуквенный код страны.
L = Tula # Locality - Город.
CN = l.autocrm.ru      # Common Name - Имя домена.
emailAddress = support@autocrm.ru      # Адрес электронной почты.
# Можно ещё указать следующие поля:
# ST (State - штат, название провинции и т.п.)
# O (Organization - название организации)
# OU (Organizational Unit - название подразделения)

[v3_req]
# Список альтернативных имён. Можно указать прямо здесь, но это не
# удобно, особенно если их много, так что мы указываем название секции
# с именами.
subjectAltName = @alt_names

[alt_names]
# Имена. Можно указать хоть сколько, главное чтобы цифры после точки были разными.
DNS.0 = *.l.autocrm.ru
DNS.1 = *.d.autocrm.ru
Сертификат создаем командой
openssl req -new -x509 -days 9999 -nodes -newkey rsa:4096 -out cert.pem -keyout cert.key -config openssl.conf
Добавляем поддержку в nginx

server {
    listen          80;
        server_name ~^(?<branch>.+)\.l\.autocrm\.ru$;
    rewrite ^ https://$host$request_uri? permanent;
}

server {
        listen 443 ssl;
        server_name ~^(?<branch>.+)\.l\.autocrm\.ru$;
    ssl         on;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2; # SSLv3 исключить CVE-2014-3566
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/cert.key;
.................
}
 

среда, 8 июля 2015 г.

Настройка Zabbix в Centos

Все манулы по установке zabbix на CentOS рекомендуют выключить selinux. Однако доступность zabbix сервер из web интерфеса решается командой:
setsebool -P httpd_can_network_connect  on
Агент zabbix не сможет получать данные от mysql, придетсядать ему доступ командами:

grep zabbix_agent /var/log/audit/audit.log | audit2allow -M zabbix_agent
semodule -i zabbix_agent.pp
Zabbix сервер тоже будет ругаться, сделаем и для него правила
grep zabbix_server /var/log/audit/audit.log | audit2allow -M zabbix_server
semodule -i zabbix_server.pp

пятница, 3 июля 2015 г.

Установка php 5.6 в centos

В официальном репозитории CentOS находится довольно старая версия php, а если нужна посвяжее тогда будем использовать дополнительный репозиторий iuscommunity.org предварительно подключив epel

yum install http://mirror.yandex.ru/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
yum install https://dl.iuscommunity.org/pub/ius/stable/CentOS/7/x86_64/ius-release-1.0-14.ius.centos7.noarch.rpm

Пакет называется php56u-fpm 

среда, 3 июня 2015 г.

Развертывание приложения по хуку из github

Схема работы следующая. В github на репозиторий ставится хука - post на мой URL. Главное чтобы данные приходили не в json. На веб сервере запрос разбирается и добавляется задача в gearman какой скрипт выполнить и с какими параметрами (название ветки). В crone крутится скрипт который случает очередь gearman'а и при получении задания - выполняет его. Почему нужен gearman? Развертывание может происходить достаточно долго и если его выполнять сразу при получении запроса от github времени может не хватить. В схеме с gearman развертывание может длиться сколь угодно по времени и нет конкуренции, так как все запросы обрабатываются последовательно.
Скрипт для получения хук index.php
<?php

const QUEUE_NAME = 'deploy';

$map = [
    '/hook/super-secret' => [
        'name' => 'Project',
        'deploy' => '/opt/deploy/init.sh',
        'remove' => '/opt/deploy/remove.sh'
    ],
];

function log_message($message)
{
    $message = '[' . date('c') . '] ' . $message . PHP_EOL;
    file_put_contents(__DIR__ . '/../logs/deploy.log', $message, FILE_APPEND);
}

$uri = $_SERVER['REQUEST_URI'];

if (isset($map[$uri]) && isset($_POST['payload'])) {
    $data = json_decode($_POST['payload'], true);
    $branch = isset($data['ref']) ? $data['ref'] : null;
    $deleted = isset($data['deleted']) ? $data['deleted'] : false;

    if ($deleted) {
        log_message(sprintf('[%s] Delete branch %s', $map[$uri]['name'], $branch));
    } else {
        log_message(sprintf('[%s] Deploy branch %s', $map[$uri]['name'], $branch));
    }

    if (preg_match('/^refs\/heads\/(.+)$/', $branch, $match) && $match[1]) {
        $data = [
            'branch' => $match[1],
            'script' => $deleted ? $map[$uri]['remove'] : $map[$uri]['deploy'],
        ];
        $client = new GearmanClient();
        $client->addServer();
        $client->doHighBackground(QUEUE_NAME, json_encode($data));
    }
}
Скрипт для обработки заданий gearman deploy.php
<?php
/*
 *  flock -n /var/lock/php_deploy.lock -c "php /opt/deploy/deploy.php"
 */
const QUEUE_NAME = 'deploy';

function log_message($message)
{
    $message = '[' . date('c') . '] ' . $message . PHP_EOL;
    file_put_contents(__DIR__ . '/logs/deploy.log', $message, FILE_APPEND);
}

$worker = new GearmanWorker();
$worker->addServer();
$worker->setTimeout(60000);
$worker->addFunction(
    QUEUE_NAME,
    function ($job) {
        $data = @json_decode($job->workload(), true);
        if (isset($data['script'], $data['branch'])) {
            log_message($data['script'] . ' ' . $data['branch']);
            system($data['script'] . ' ' . $data['branch']);
        }

        return 0;
    }
);

do {
    $worker->work();
} while ($worker->returnCode() == GEARMAN_SUCCESS);
deploy.php добавляем в cron вот так flock -n /var/lock/php_deploy.lock -c "php /opt/deploy/deploy.php" чтобы не запускалось больше одной копии.

Настройть nginx для схемы свой субдомен для каждой ветки git

server {
        listen 80;
        server_name ~^(?<branch>.+)\.d\.domain\.ru$;
        root /opt/webroot/$branch/www;
        index index.php;
        # auth_basic "closed site";
        # auth_basic_user_file /etc/nginx/htpasswd;
        location / {
                try_files $uri $uri/ /index.php?$args;
        }
        location ~ \.php$ {
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                fastcgi_pass unix:/var/run/php-fpm.sock;
                fastcgi_read_timeout 300;
        }
}

Настройки gearman для Centos

Задача: заставить gearman использовать mysql в качестве хранилища. Файл /etc/sysconfig/germand принимает вид:
### Settings for gearmand
OPTIONS="--listen=localhost --queue-type=MySQL --mysql-host=localhost --mysql-port=3306 --mysql-user=root --mysql-password=qweasdzxc --mysql-db=gearman --mysql-table=queue"

вторник, 19 мая 2015 г.

Сборка parser из исходников

Есть такая хрень, называется parser. Создали его теже долбоебы что и дизайн yandex.ru Мне потребовалось развернуть проект написанный на этом чуде, но он все время сыпал в меня примерно такой ошибкой

'/var/.../cgi-bin/auto.p' parser is in safe mode: reading files of foreign group and user disabled [recompile parser with --disable-safe-mode configure option], actual filename '/var/.../cgi-bin/auto.p', fuid(500)!=euid(0) or fgid(502)!=egid(0)
Причина понятна, владельцом файлов являются я, а парсер запускается от имени apache. Может для продакшена это и оправдано (хотя нет НЕ ОПРАВДАНО), но для машины разработчика - жутко неудобно работать с файлами.
Решил пересобрать с опцией --disable-safe-mode. Но обычный UNIX-way подход
./configuration
make install 
не работает. У меня сработало только такой
rm Makefile
./buildAll --disable-safe-mode
 т.е. ВСЕГДА перед сборкой нужно удалять старый Makefile
Мне также не понятно зачем собирать дополнительные библиотеки из исходников (например prce) если можно использовать родные из ОС, тогда уж и gcc нужно было сначала собрать.

четверг, 19 марта 2015 г.

Ошибки в Ubuntu

После установки Ubuntu, в консоли постоянно будет ошибка
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
    LANGUAGE = (unset),
    LC_ALL = (unset),
    LANG = "ru_RU.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
исправить легко
locale-gen ru_RU.UTF-8
В лог файле /var/log/auth.log появляется сообщение
pam_env(sshd:session): Unable to open env file: /etc/default/locale: No such file or directory
исправляется командой
update-locale

понедельник, 16 марта 2015 г.

Полезные команды exim4

Заморозить все сообщения в очереди
exipick -i | xargs exim -Mf
Разморозить все сообщения в очереди
exipick -zi | xargs exim -Mt
Узнать чем занят exim
exiwhat

вторник, 3 марта 2015 г.

Оптимизация PNG

Для оптимизации (читай уменьшения размера) png файлов можно использовать optipng
optipng -o7 *.png
Но самый лучший результат показал pngquant
pngquant --force --ext .png  *.png

пятница, 27 февраля 2015 г.

Правила iptables

Начальные правила для iptables
# Generated by iptables-save v1.4.21 on Fri Feb 27 19:52:43 2015
*mangle
:PREROUTING ACCEPT [10:660]
:INPUT ACCEPT [10:660]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [7:840]
:POSTROUTING ACCEPT [7:840]
COMMIT
# Completed on Fri Feb 27 19:52:43 2015
# Generated by iptables-save v1.4.21 on Fri Feb 27 19:52:43 2015
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [7:840]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
# Completed on Fri Feb 27 19:52:43 2015

понедельник, 16 февраля 2015 г.

Кеширование в Yii2

После того как поиграл с профайлером захотелось мне немного ускорить генерацию страниц в одном глупом проекте за счет кеширования.
1. Кеширование страницы целиком, реализуется через поведение контроллера
    public function behaviors()
    {
        return [
            [
                'class' => 'yii\filters\PageCache',
                'only' => ['index'],
                'duration' => 3600,
            ],
        ];
    }

тут я кеширую страницу index на один час

2. Кеширование блоков внутри шаблона страницы
<?php if ($this->beginCache('shop' . $shop->id, ['duration' => 3600])) : ?>
    .......

    <?php $this->endCache() ?>
<?php endif ?>
кешируется все что попало между beginCache & endCache



Второй вариант интересен тем, что его можно использовать если у вас могут быть одинаковые блоки на разных страницах. Именно такой случай был у меня. Для этого мне пришлось даже немного переделать контроллер и часть логики перенести в модель.

До изменений контроллер выглядел примерно так
public function actionShop($id)
{
        $shop = Shop::findOne($id);
        $products= Product::find()->where(['shop_id'=>$shop->id]) ->orderBy(['price'=>SORT_DESC])->all();
        return $this->render('pricelist', ['shop'=>$shop, 'products'=>$products];
}

Однако после того как я кеширование фрагмента - список товаров, пришлось перенести
        $products= Product::find()->where(['shop_id'=>$shop->id]) ->orderBy(['price'=>SORT_DESC])->all();
в модель Shop. Тут многие могут сказать, что так и надо было сделать изначально, но я не считаю нужным делать такие вещи пока проект активно меняется. Если бы я убрал это раньше я мог пропустить очевидность этих изменений.

Вообще все закончилось хорошо. Скорость отдачи увеличилась, спасибо Redis, а я получил прекрасный опыт, что:
1. преждевременная оптимизация - хуже отсутствия её вообще
2. кеширование нужно использовать с умом

Подключение профайлера XHProf

Сегодня потребовалось заняться профилированием приложения, написанного на Yii2. Для этого будем использовать замечательный инструмент от Facebook - xhprof Большим преимуществом данного профайлера является возможность работы на боевом сервере. И так начнем.

Устанавливаем профайлер и инструмент для графа вызова функций следующей командой
sudo apt-get install php5-xhprof graphviz

Подключаем расширение xhprof к php-fpm.
Далее необходимо разместить папки xhprof_lib & xhprof_html и поместить их рядом с точкой входа в приложение index.php и подключить сборщик вызовов в index.php

<?php
// Начинаем сбор информации, дополнительно будем собирать информацию о  использовании оперативной памяти и процессоре
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);

// отключаем отладочный режим
define('YII_DEBUG', false);
define('YII_ENV', 'prod');

require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');

$config = require(__DIR__ . '/../config/web.php');

(new yii\web\Application($config))->run();

// останавливаем сбор информации о вызовах
$xhprof_data = xhprof_disable();

include_once __DIR__ . '/xhprof_lib/utils/xhprof_lib.php';
include_once __DIR__ . '/xhprof_lib/utils/xhprof_runs.php';

$xhprof_runs = new XHProfRuns_Default();

// save the run under a namespace "xhprof_foo"
$run_id = $xhprof_runs->save_run($xhprof_data, 'xhprof_foo');

// внизу страницы отобразим ссылку на просмотр
echo "<a href='/xhprof_html/index.php?run={$run_id}&source=xhprof_foo' target='_blank'>profile</a>";

пятница, 13 февраля 2015 г.

Teamcity

Хорошая статья по установке TeamCity на CentOS https://plone.lucidsolutions.co.nz/software-development/continuous-integration/teamcity/teamcity-v9.x-on-centos-v7.x-with-mariadb/view


Мне была поставлена задача - внедрить систему непрерывной интеграции, которая выполняла следующие задачи:
  1. забирала новую ветку из системы контроля версий git
  2. подгружала все зависимые библиотеки используя composer
  3. создавала/пересоздавала базу данных и применяла миграции
  4. выполняла все тесты
  5. в случае успешного выполнения всех пунктов, тестируемая ветка вливалась в ветку для разработчиков devel
Процедуру установки Teamcity я пока пропущу, начну сразу с настройки. Я использовал версию TeamCity Professional 8.1.5 (build 30240). 

1. Пользователи и уведомления

Для каждого пользователя, который будет работать с CI необходимо завести учетную запись и обязательно сделать сопоставление с именем пользователя из системы контроля версии. Только тогда пользователи будут получать уведомления.
Идем Administration > Users > Create user account, заполняем Username, Name, Email address (куда будут приходить уведомления), Password, Confirm password и нажимаем Create User. Находим пользователя в списке Administration > Users и открываем на редактирование. В разделе Version Control Username Settings нажимаем edit. В этом разделе настраивается сопоставление пользователя Teamcity и пользователя VCS. Можно указать одно правило для всех через запись Default for all of the VCS roots, а можно настроить для каждого типа или даже репозитория отдельно. В качестве имени я использовал email.
Если в качестве имени используется email, в настройках VCS Root нужно указать следующее:
Тогда Teamcity будет извлекать email из коммитов и сравнивать их с email'ами которые иказали в разделе Version Control Username Settings.

2. Настройка проекта

Идем в раздел Administration > Projects и нажимаем Create Project. Заполняем имя и описание, Project ID пускай присваивается автоматически и нажимаем Create.

2.1 Репозиторий исходного кода

Заходим в раздел VCS Roots и добавляем новый репозиторий.

Docker

Docker - интересная технология. Основное предназначение - стабилизация окружения выполнения программ, легкий перенос программ (вместе с окружением) на новый сервер, быстрое развертывание. Я довольно долго разбирался с Docker, все пытался сам понять как бы его можно было использовать. В своей практике я не сталкивался с deploy hell, когда имеются взаимоисключающие условия на окружение программ, но именно эту проблему с легкостью помогает решить docker. И все же я нашел для себя пример его использования - развертывание TeamCity и его агентов. Собственно мною было сделаны 2 контейнера:
  1. TeamCity сервер
  2. TeamCity build agent


воскресенье, 18 января 2015 г.

Создание модуля из командной строки

Для создания нового модуля в Yii2 через командную строку нужно выполнить
./yii gii/module --moduleID=admin --moduleClass=app\\modules\\admin\\Module

четверг, 15 января 2015 г.

Magallanes & Composer

Понадобилось мне прикрутить к проекту инструмент для деплоя. Поскольку я использую php, я остановился на Magallanes. Я работаю через Composer, поэтому делаем так
php composer.phar require "andres-montanez/magallanes:*"
Выносим mage в корень проекта, для этого создаем файл mage следующего содержания:
#!/usr/bin/env php
<?php

$baseDir = __DIR__ . '/vendor/andres-montanez/magallanes';

define('MAGALLANES_VERSION', '1.0.3');
define('MAGALLANES_DIRECTORY', $baseDir);

require_once __DIR__ . '/vendor/autoload.php';

// Clean arguments
array_shift($argv);

// Run Magallanes
$console = new Mage\Console;
$exitCode = $console->run($argv);

exit((integer) $exitCode);
И разрешаем его запускать
chmod +x ./mage
Дальше делаем все по инструкции. Настраиваем проект
mage init --name="My fantastic app" --email="notifications@my.app"
Добавляем конфигурацию для деплоя
mage add environment --name="production" --enableReleases 
Собственно сам деплой
mage deploy to:production
Основные настройки проекта хорошо расписаны на сайте magephp.com, но есть пара моментов, с которыми мне пришлось разбираться.

После деплоя устанавливаем особые разрешения на файлы/папки

Это бывает нужно, чтобы веб-сервер имел право писать скажем логи.
#production
deployment:
  user: vlad
  from: ./
  to: /opt/www/promokashka_com
  excludes:
    - .idea
    - composer.phar
    - mage
    - node_modules
    - web/assets
    - runtime
    - config/_local.php

releases:
  enabled: true
  max: 10
  symlink: current
  directory: releases

hosts:
  - promokashka.com:2222

tasks:
  pre-deploy:
    - composer/install
    - composer/generate-autoload
  on-deploy:
  post-release:
    - filesystem/apply-facls:
        recursive:true
        acl_param: "o:rwx"
        folders: [ runtime, web/assets ]
Для установки разрешений используем команду filesystem/apply-facls.

После деплоя нужно создать символьную ссылку на каталог или файл. 

Может применяться, для линковки конфига с настройками доступа к базе данных.
#production
deployment:
  user: vlad
  from: ./
  to: /opt/www/promokashka_com
  excludes:
    - .idea
    - composer.phar
    - mage
    - node_modules
    - web/assets
    - runtime
    - config/_local.php

releases:
  enabled: true
  max: 10
  symlink: current
  directory: releases

hosts:
  - promokashka.com:2222

tasks:
  pre-deploy:
    - composer/install
    - composer/generate-autoload
  on-deploy:
  post-release:
    - filesystem/apply-facls:
        recursive:true
        acl_param: "o:rwx"
        folders: [ runtime, web/assets ]

    - filesystem/link-shared-files:
        linked_folders:
            - web/assets
            - runtime
        linked_files:
            - config/_local.php
        shared: shared
        linking_strategy: absolute
Тут я прилинковываю каталоги runtime, web/assets и файл config/_local.php

Использованием символьных ссылок для папок assets и runtime и файлов _local.php имеет интересное применение. Фактически это те части приложения, которые будут постоянны для всех релизов (или будут меняться редко). В assets можно хранить объединенные и сжатые версии css & js файлов и ОЧЕНЬ ЖЕЛАТЕЛЬНО чтобы старые версии какое-то время там присутствовали тоже. Каталог runtime часто служит как хранилище для лог файлов и через символьные ссылки лог файлы не будут теряться при деплое нового релиза. Однако для полного счастья нам потребуется механизм работы с этими постоянными данными. В стандартной поставке magallanes его нет, пришлось написать.

<?php
namespace Task;

use Mage\Task\AbstractTask;

class SharedCopy extends AbstractTask
{
    public function getName()
    {
        return 'Deploy shared files';
    }

    public function run()
    {
        $deployToDirectory = $this->getConfig()->deployment('to');
        $files = $this->getParameter('files', []);
        $shared = $this->getParameter('shared', 'shared');
        foreach ($files as $file) {
            $command =
                'scp ' . $this->getConfig()->getHostIdentityFileOption() . '-P ' . $this->getConfig()->getHostPort()
                . ' ' . $file . ' ' . $this->getConfig()->deployment('user') . '@' . $this->getConfig()->getHostName()
                . ':' . $deployToDirectory . '/' . $shared . '/' . preg_replace('/\/([^\/]*)$/', '', $file);
            $this->runCommandLocal($command);
        }

        return true;
    }
}

Применение для сжатых файлов css& js  которые будут лежать в assets
#production
deployment:
  user: vlad
  from: ./
  to: /opt/www/promokashka_com
  excludes:
    - .idea
    - composer.phar
    - mage
    - node_modules
    - web/assets
    - runtime
    - config/_local.php

releases:
  enabled: true
  max: 10
  symlink: current
  directory: releases

hosts:
  - promokashka.com:2222

tasks:
  pre-deploy:
    - composer/install
    - composer/generate-autoload
  on-deploy:
  post-release:
    - filesystem/link-shared-files:
        linked_folders:
            - web/assets
            - runtime
        linked_files:
            - config/_local.php
        shared: shared
        linking_strategy: absolute

    - shared-copy:
        files:
            - web/assets/all*
Однако это не будет работать, если магелан подключен через composer, нужно подправить автолоадинг composer'а
добавляем в composer.json 
    "autoload": {
        "psr-4":{ "Task\\" : ".mage/tasks"}
    }
и обновляемся командов 
php composer.phar update