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

Меню

Ruby On Rails

Linux


Android


AngularJS

Android. Get absolute path of image

Во многих Android приложениях требуется работа с изображениями, будь то выбор фото из галереи или съемка с помощью камеры. Зачастую, после выполнения этих действий необходимо манипулировать файлом изображения, записанным в память телефона.

В каждом из этих случаев путь к изображению можно получить из Intent, переданный в метод onActivityResult:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    Uri imagePath = data.getData();
}

Однако если вы захотите использовать этот путь для доступа к файлу, у вас ничего не получится.

Решение

Чтобы получить абсолютный путь к файлу в памяти телефона, воспользуйтесь этим методом:

private String getImagePath(Intent data) {
    Uri path = data.getData();

    String[] filePathColumn = { MediaStore.Images.Media.DATA };

    Cursor cursor = getActivity().getContentResolver().query(path,
            filePathColumn, null, null, null);
    cursor.moveToFirst();

    int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
    String picturePath = cursor.getString(columnIndex);
    cursor.close();

    return picturePath;
}



Android. Upload file to server.

Многие мобильные приложения оперируют изображениями и другими файлами, которые нужно отправлять на сервер. В системе Android это не является тривиальной задачей.

Общая теория и проблемы


Обычно для отправки файлов на сервер используется POST запрос протокола HTTP с типом содержимого multipart/form-data.

Сообщение такого типа состоит из нескольких частей, каждая из которых представляет собой содержимое некоторого элемента формы. Каждый элемент имеет заголовок, в котором указано имя элемента и тип контента. Передаваемый файл является одним их таких элементов.

Данный тип запроса используется и для передачи файлов с Android устройств. Однако Android не умеет создавать данные для передачи такого запроса в отличие от веб браузеров. Это приходится делать вручную.

В сети существуют библиотеки для формирования multipart запросов, однако они очень сложны в установке, что не стоит потраченного времени.


Реализация


Приведу сразу весь класс для загрузки изображений с комментариями. Класс представляет из себя AsyncTask, в котором выполняется POST запрос с типом содержимого multipart. Передаваемый файл записывается как один элемент multipart запроса.

/**
 * Загружает файл на сервер
 */
public class FilesUploadingTask extends AsyncTask<Void, Void, String> {

    // Конец строки
    private String lineEnd = "\r\n";
    // Два тире
    private String twoHyphens = "--";
    // Разделитель
    private String boundary =  "----WebKitFormBoundary9xFB2hiUhzqbBQ4M";

    // Переменные для считывания файла в оперативную память
    private int bytesRead, bytesAvailable, bufferSize;
    private byte[] buffer;
    private int maxBufferSize = 1*1024*1024;

    // Путь к файлу в памяти устройства
    private String filePath;

    // Адрес метода api для загрузки файла на сервер
    public static final String API_FILES_UPLOADING_PATH = "127.0.0.1/upload";

    // Ключ, под которым файл передается на сервер
    public static final String FORM_FILE_NAME = "file1";

    public FilesUploadingTask(String filePath) {
        this.filePath = filePath;
    }

    @Override
    protected String doInBackground(Void... params) {
        // Результат выполнения запроса, полученный от сервера
        String result = null;

        try {
            // Создание ссылки для отправки файла
            URL uploadUrl = new URL(API_FILES_UPLOADING_PATH);

            // Создание соединения для отправки файла
            HttpURLConnection connection = (HttpURLConnection) uploadUrl.openConnection();

            // Разрешение ввода соединению
            connection.setDoInput(true);
            // Разрешение вывода соединению
            connection.setDoOutput(true);
            // Отключение кеширования
            connection.setUseCaches(false);

            // Задание запросу типа POST
            connection.setRequestMethod("POST");

            // Задание необходимых свойств запросу
            connection.setRequestProperty("Connection", "Keep-Alive");
            connection.setRequestProperty("Content-Type", "multipart/form-data;boundary="+boundary);

            // Создание потока для записи в соединение
            DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());

            // Формирование multipart контента

            // Начало контента
            outputStream.writeBytes(twoHyphens + boundary + lineEnd);
            // Заголовок элемента формы
            outputStream.writeBytes("Content-Disposition: form-data; name=\"" +
                    FORM_FILE_NAME + "\"; filename=\"" + filePath + "\"" + lineEnd);
            // Тип данных элемента формы
            outputStream.writeBytes("Content-Type: image/jpeg" + lineEnd);
            // Конец заголовка
            outputStream.writeBytes(lineEnd);

            // Поток для считывания файла в оперативную память
            FileInputStream fileInputStream = new FileInputStream(new File(filePath));

            bytesAvailable = fileInputStream.available();
            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            buffer = new byte[bufferSize];

            // Считывание файла в оперативную память и запись его в соединение
            bytesRead = fileInputStream.read(buffer, 0, bufferSize);

            while (bytesRead > 0) {
                outputStream.write(buffer, 0, bufferSize);
                bytesAvailable = fileInputStream.available();
                bufferSize = Math.min(bytesAvailable, maxBufferSize);
                bytesRead = fileInputStream.read(buffer, 0, bufferSize);
            }

            // Конец элемента формы
            outputStream.writeBytes(lineEnd);
            outputStream.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

            // Получение ответа от сервера
            int serverResponseCode = connection.getResponseCode();

            // Закрытие соединений и потоков
            fileInputStream.close();
            outputStream.flush();
            outputStream.close();

            // Считка ответа от сервера в зависимости от успеха
            if(serverResponseCode == 200) {
                result = readStream(connection.getInputStream());
            } else {
                result = readStream(connection.getErrorStream());
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }

    // Считка потока в строку
    public static String readStream(InputStream inputStream) throws IOException {
        StringBuffer buffer = new StringBuffer();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

        String line;
        while ((line = reader.readLine()) != null) {
            buffer.append(line);
        }

        return buffer.toString();
    }
}

Использование


Чтобы загрузить файл на сервер, запустите выполнение AsyncTask:

new FilesUploadingTask(filePath).execute();






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

Android GCM. Часть 1. Регистрация устройства.

Ни одно современное мобильное приложение сейчас не обходится без пуш уведомлений - сообщений, рассылаемых сервером на устройства клиентов. Google предоставляет сервис Cloud Messaging (GCM) для рассылки уведомлений на устройства Android.

Здесь будет рассказано, как интегрировать поддержку GCM в свое приложение.
(Реализация на серверной стороне будет рассмотрена отдельно)

Архитектура


Реализация GCM включает в себя 3 составляющих:
1. Сервер приложения, с которого исходят уведомления;
2. Мобильное устройство, которое принимает уведомления;
3. GCM серверы, которые отправляют сообщения с сервера приложения на мобильные устройства.


Перед началом работы


Для разработки понадобиться:
1. Установить Google Play services SDK, если он еще не установлен у вас. Инструкция по установке здесь.
2. Подключить Google Play Services для GCM в приложении, добавив строчку compile 'com.google.android.gms:play-services-gcm:7.5.0' в Gradle. Так же убедитесь, что все ссылки на Google Play Services в Gradle имеют одинаковую версию, иначе вы не сможете запустить отладку. Инструкция здесь.
3. Создать приложение в Google Developer Console и активировать в нем GCM API, как описано здесь. Это забота серверной стороны. Клиентскому приложению нужен только номер созданного приложения из Google Developer Console. Если вы создаете только клиентское приложение, уточните этот номер у разработчика серверной части.

Редактирование файла Manifest


Добавьте следующие строчки в manifest приложения:

1. <uses-permission android:name="android.permission.INTERNET" />
Для того, чтобы приложение могло отправить идентификатор устройства на сервер.

2. <uses-permission android:name="android.permission.WAKE_LOCK" />
Предотвращает засыпание процессора во время приема сообщений.

3. <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
Для разрешения приложению регистрировать и принимать сообщения

4. <permission android:name="com.example.gcm.permission.C2D_MESSAGE"
       android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
Нужен для запрета другим приложениям принимать уведомления создаваемого приложения.
Здесь нужно заменить com.example.gcm на имя пакета вашего приложения.

5. <uses-permission android:name="android.permission.GET_ACCOUNTS" />
Было в описании, но не сказано, для чего нужно.

Полная инструкция здесь.

Регистрация устройства


Чтобы удостовериться, что клиент и сервер приложения могут обмениваться сообщениями, они должны выполнить процедуру "рукопожатия". Для этого каждое устройство приобретает уникальный идентификационный токен, который нужно отправить на сервер приложения. Сервер сохраняет его и в дальнейшем использует для отправки уведомлений на соответствующие устройства.

Токен будем хранить в Shared Preferences. Так же нужно хранить информацию о том, отправлен токен на сервер или нет. Сначала напишем методы для этого:

// Имя Shared Preferences
private static final String SHARED_PREF_NAME = "Preferences";
// Ключ токена
private static final String PREF_DEVICE_TOKEN = "DeviceToken";
// Флаг, отвечающий за то, отправлен токен на сервер или нет
private static final String PREF_DEVICE_TOKEN_SENT = "DeviceTokenSent";

// Возвращает Shared Preferences

public static SharedPreferences getSharedPreferences(Context context) {
    return context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
}

// Сохраняет токен в Shared Preferences

public static void setDeviceToken(Context context, String deviceToken) {
    SharedPreferences sp = getSharedPreferences(context);
    SharedPreferences.Editor editor = sp.edit();
    editor.putString(PREF_DEVICE_TOKEN, deviceToken);
    editor.commit();
}

// Достает токен из Shared Preferences

public static String getDeviceToken(Context context) {
    SharedPreferences sp = getSharedPreferences(context);
    return sp.getString(PREF_DEVICE_TOKEN, null);
}

// Возвращает true, если токен отправлен на сервер

public static boolean isDeviceTokenSent(Context context) {
    SharedPreferences sp = getSharedPreferences(context);
    return sp.getBoolean(PREF_DEVICE_TOKEN_SENT, false);
}

// Задает флаг, отправлен токен на сервер или нет

public static void setDeviceTokenSend(Context context, boolean sent) {
    SharedPreferences sp = getSharedPreferences(context);
    SharedPreferences.Editor editor = sp.edit();
    editor.putBoolean(PREF_DEVICE_TOKEN_SENT, sent);
    editor.commit();
}

Теперь пишем метод для создания и сохранения токена устройства. Для получения токена на мобильном устройстве используется Instance ID API:

// Номер проекта в Google Developer Console
public final static String PROJECT_NUMBER = "111233456789";
// Пространство для получение токена. Равно строке "GCM"
public final static String SCOPE = GoogleCloudMessaging.INSTANCE_ID_SCOPE;

// Возвращает токен устройства.

// Если его еще не существует, то создает его и сохраняет в Shared Preferences
public static String createDeviceToken(Context context) {
    String token = getDeviceToken(context);
    if(token == null) {
        token = InstanceID.getInstance(context).getToken(PROJECT_NUMBER, SCOPE, null);
        setDeviceToken(context, token);
    }
    return token;
}

Номер проекта можно найти во вкладке Overview:



Метод getToken в Instance ID API является долгой операцией, следовательно createDeviceToken должен быть выполнен в отдельном потоке.

// Отправляет токен на сервер.
// Если токена не существует, создает его.
public static void createAndSendDeviceToken(final Context context) {
    // Если токен уже отправлен на сервер, то ничего не делаем
    if(isDeviceTokenSent(context)) {
        return;
    }

    new AsyncTask<Void, Void, String>() {

        @Override
        protected String doInBackground(Void... params) {
            try {
                return createDeviceToken(context)
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        @Override

        protected void onPostExecute(String s) {
            super.onPostExecute(s);

            if (s != null) {

                sendTokenToServer(s);
            } else {
                // Действие при ошибке
            }
        }
    }.execute();
}

Осталось реализовать отправку токена на сервер в методе sendTokenToServer. Если токен успешно отправлен, запомните это вызовом метода setDeviceTokenSend(context, true);

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

Convert mkv to mp4 on Linux



Ставший популярным видео формат mvk поддерживается не всеми плеерами и редакторами. Однако, для операционных систем Linux доступен инструмент под названием avconv, поставляемый с пакетом libav-tools, с помощью которого можно преобразовывать видео файлы из одного формата в другой.

Установите его следующей командой:

sudo apt-get install libav-tools

Для преобразования файла из формата mkv в mp4 воспользуйтесь командой:

avconv -i input.mkv -codec copy output.mp4

Это автоматически преобразует файл в формат mp4 путем копирования потоков без их перекодирования (-code copy).

среда, 25 марта 2015 г.

Android. Add custom button to SearchView

Некоторые Android приложения оперируют большим числом данных, поиск которых обычно осуществляется через действие поиска:


Добавления этой функциональности в приложение хорошо описано в этих статьях:

Однако, добавление собственных кнопок в SearchView не такая уж и простая задача.

Для этого необходимо:
1. Создать экземпляр кнопки;
2. Найти в разметке SearchView элемент, в который можно поместить кнопку;
3. Поместить кнопку в найденный View.

Разметка кнопки


В папке layout создайте xml файл для кнопки, которую нужно поместить в SearchView. Я назвал его search_view_custom_button.xml:

<?xml version="1.0" encoding="utf-8"?>
<ImageButton
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/custom_button"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:src="@drawable/custom_button_icon"
    android:background="?android:attr/selectableItemBackground"
    android:focusable="true"/>

Задайте кнопке id и изображение (src).
Атрибуту android:background задано стандартное значение "?android:attr/selectableItemBackground", которое обеспечивает анимацию нажатия на кнопку в строке поиска.

Создание экземпляра кнопки


"Надуйте" экземпляр кнопки из только что созданной разметки:

final ImageButton customButton = (ImageButton) getLayoutInflater().inflate(R.layout.search_view_custom_button, null);

Добавление кнопки в SearchView


Разметка SearchView имеет следующую структуру:

<LinearLayout android:id="@+id/search_bar">
  <TextView android:id="@+id/search_badge/> <ImageView android:id="@+id/search_button"/>
  <LinearLayout android:id="@+id/search_edit_frame"
    <ImageView android:id="@+id/search_mag_icon"/>
    <LinearLayout android:id="@+id/search_plate">
      <view class="android.widget.SearchView$SearchAutoComplete"
          android:id="@+id/search_src_text"/>
      <ImageView android:id="@+id/search_close_btn"/>
    </LinearLayout>
    <LinearLayout android:id="@+id/submit_area">
      <ImageView android:id="@+id/search_go_btn"/>
      <ImageView android:id="@+id/search_voice_btn"
    </LinearLayout>
  </LinearLayout>
</LinearLayout>


search_plate - Элемент со строкой поиска и кнопкой очистки.
submit_area - Элемент с кнопкой голосового поиска.

Добавить кнопку можно в любую часть разметки. В рамках примера добавим кнопку рядом с кнопкой голосового поиска:

SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
LinearLayout searchViewSubmitArea = (LinearLayout) searchView.findViewById(R.id.submit_area);
searchViewSubmitArea.addView(searchViewCustomButton);


Для корректного отображения кнопки настройте параметры разметки:

searchViewCustomButton.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));

Готово! Кнопка добавлена прямо в строку поиска:









среда, 4 февраля 2015 г.

Poltergeist - Can not allocate memory

Poltergeist - замечательный Ruby Gem, который является оберткой для headless браузера PhantomJS. Он позволяет пользоваться этим браузером через Capybara - средство эмуляции пользователя в сети интернет.

Я использую poltergeist для парсинга динамических интернет сайтов. Этот браузер построен движке webkit и позволяет исполнять javascript, в отличие от стандартного браузера капибары.

При длительной работе с ним может возникнуть ошибка: Can not allocate memory - phantomjs. Это происходит из-за того, что он не может автоматически освободить память от сессий, которые уже не используются. Чтобы это предотвратить, используйте метод quit у драйвере сессии, чтобы освободить память:

session.driver.quit

Если по каким-либо причинам это не заработало, создайте файл config/initializers/poltergeist.rb и добавьте в него следующее содержимое:
https://gist.github.com/jonstokes/04b10617a497ec2cc8a8

Это позволит насильно убивать процесс браузера в системе, что поможет освободить неиспользуемую память.

вторник, 3 февраля 2015 г.

Run 32-bit program on a 64-bit system

Бывают случаи, когда нужно запустить 32-битную программу на 64-битной системе. Я столкнулся с этим, когда пытался установить web интерфейс базы данных postgresql.

Решение простое - установить библиотеки совместимости:
sudo apt-get install ia32-libs
Если не помогло, установите так же это:
sudo apt-get install lib32z1 lib32ncurses5 lib32bz2-1.0

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

Android. How to change action bar title color programmatically

Если Вам необходимо программно поменять цвет заголовка у ActionBar, но мир взбунтовался против Вас и рекомендует делать это разметкой, то добро пожаловать сюда.

Сделать это проще простого.

Заголовок у ActionBar может быть задан строкой из ресурсов, строкой Java или экземпляром класса Spannable.

В Android есть класс SpannableString, наследник Spannable, который позволяет описать разметку и оформление строки.

Воспользуемся им и создадим заголовок необходимого цвета:

String titleText = "Заголовок";
Spannable title = new SpannableString(titleText);
title.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.black)), 0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
actionBar.setTitle(title);

Если смена цвета заголовка связана с меню, добавьте приведенный выше код в метод onCreateOptionsMenu у Activity.

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

Android. Save fragment after screen rotation

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

О пользователях нужно заботиться и сохранять введенные значения в формах после поворота экрана.

Причина


Activity во время своей жизни может множество раз менять свое состояние, вплоть до уничтожения и пересоздания. Это как раз и происходит во время поворота экрана. Activity уничтожается и создается снова.

Решение в лоб


Эта проблема довольно легко решается с помощью методов onSaveInstanceState и onRestoreInstanceState у Activity. В первом методе можно сохранить данные в виде пары Ключ/Значение, а во втором восстановить их. Эти данные будут сохранены после уничтожения Activity.

По данной теме можно найти множество материалов.

Однако, этот метод неуместен, если необходимо сохранять большое число данных.

Решение при использовании Fragments


Если форма с полями ввода находится во фрагменте, можно просто напросто сохранить фрагмент в памяти, и снова задействовать его после поворота экрана. Однако, нужно иметь ввиду, что фрагмент в любом случае пересоздаст свое представление, что необходимо обработать.

Первым делом нужно сообщить фрагменту, чтобы его экземпляр сохранился после пересоздания Activity. В конструкторе фрагмента добавьте строку:

this.setRetainInstance(true);

После этого скорректируйте код метода onCreate у Activity, чтобы восстановить сохраненный после пересоздания фрагмент:

private static String FRAGMENT_INSTANCE_NAME = "fragment";
MyFragment fragment = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.my_activity);

    // Восстанавливаем уже созданный фрагмент
    FragmentManager fm = getSupportFragmentManager();
    fragment = (MyFragment) fm.findFragmentByTag(FRAGMENT_INSTANCE_NAME);
    // Если фрагмент не сохранен, создаем новый экземпляр
    if(fragment == null){
        fragment = new MyFragment();
        fm.beginTransaction().add(R.id.container, fragment, FRAGMENT_INSTANCE_NAME).commit();
    }
}

Теперь фрагмент не пересоздается после поворота экрана, однако его представление все равно создается заново, так как вызывается метод onCreateView. Стандартного способа предотвратить вызов этого метода я не нашел, так что, нужно включить несколько дополнительных проверок в этот метод:

View view = null;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // Проверяем, создано ли представление фрагмента
    if(view == null) {
        // Если представления нет, создаем его
        view = inflater.inflate(R.layout.my_fragment, container, false);
    } else {
        // Если представление есть, удаляем его из разметки,
        // иначе возникнет ошибка при его добавлении
        ((ViewGroup) view.getParent()).removeView(view);
    }
    return view;
}

Все готово. При повороте экрана Activity использует старый экземпляр фрагмента, представление которого так же не пересоздается. Это позволяет сохранить все введенные в форме данные.

Внимание


Если вы используете адаптеры, то при повороте экрана они автоматически уничтожат и создадут заново каждый элемент, за который они отвечают. Это произойдет с вызовом метода getView.



среда, 14 января 2015 г.

Linux. Resources (Memory, CPU, swap) monitoring with notifications

Нехватка ресурсов на сервере (RAM, место на жестком диске, загрузка CPU), может привести к плохим последствиям. Чтобы быстро узнать об этом и принять меры, можно настроить мониторинг ресурсов и оповещение в случае опасности.

Для этого существует множество сервисов, которые можно найти в сети.
В этой статье описано, как настроить мониторинг системы через сервис monit (http://mmonit.com/monit/), а именно:
1. Мониторинг загруженности CPU, swap и оперативной памяти.
2. Оповещение на email в случае достижения загрузки предельного значения.

Краткое описание




Monit - легковесная система мониторинга серверов. Monit устанавливается на сервер и обеспечивает возможность отправки уведомлений в случае обнаружения проблем. Кроме того, Monit может автономно выполнять определенное действие в качестве реакции на заданные события.

Программа имеет следующую функциональность:
  • Отслеживание состояния серверов (доступность, потребление ресурсов).
  • Мониторинг демонов (состояние, потребляемые ресурсы, количество child-process и многое другое).
  • Мониторинг сетевых сервисов (возможность подключения и корректность ответа).
  • Выполнение встроенных или собственных (с помощью скриптов) действий при достижении определенных событий.
  • Отправка уведомлений на Email или в централизованный web-интерфейс M\Monit.

Поддерживаются ОС GNU\LinuxFreeBSDOpenBSDSolarisMac OS XAIX.

Установка


Здесь все проще простого. Выполняем:
sudo apt-get install monit

Доступ к настройкам


Настройка поведения monit осуществляется через консоль или файл настроек. Опции, заданные через консоль, перегружают настройки из файла.

Настройки содержатся в файле monitrc, который, по умолчанию, должен находиться в ~/.monitrc.

Однако, после установки, он находился в /etc/monit/monitrc.

Чтобы проверить файл на синтаксические ошибки, выполните команду:
monit -t

Структура файла настроек


Файл настроек включает в себя следующие категории:
1. Глобальные значения (Global Section)
2. Сервисы (Services)
3. Подключаемые модули (Includes)

Глобальные значения, это опции поведения программы (Период проверки, путь к лог файлу, список адресов для рассылки и т.д.).

Они задаются следующим образом:
set option_name parameters

Некоторые значения заданы в файле по умолчанию, например:
set daemon 120 

Значение 120 означает, что демон будет запускать проверку системы каждые 120 секунд.

Сервисы, это то, что monit будет делать во время проверки (Проверить состояние процессора, памяти, какого-либо процесса и т.д.)

Они задаются следующим образом:
check category parameters

Подключаемые модули позволяют разделить файл настроек на несколько файлов. По умолчанию в файле настроек есть строка:
include /etc/monit/conf.d/*

Настройка


И так, откройте файл настроек для редактирования:
vim /etc/monit/monitrc

Ниже приведен список опций для решения поставленной задачи.
Добавьте недостающие опции в файл настроек.
# Периодичность проверки в секундах
set daemon 120
# Путь к лог файлу. Оставил без изменений
set logfile /var/log/monit.log
# Путь к уникальному идентификатору. Оставил без изменений
set idfile /var/lib/monit/id
# Путь к состоянию. Оставил без изменений
set statefile /var/lib/monit/state
# Email адрес, с которого производить рассылку оповещений
set mailserver smtp.gmail.com port 587
    username "EmailAccount" password "email_password"
    using tlsv1
# Очередь событий. Оставил без изменений
set eventqueue
      basedir /var/lib/monit/events
      slots 100
# Адреса, куда присылать уведомления
set alert 4ybakut2004@gmail.com
set alert ExtraEmail@gmail.com

Так же есть возможность настроить web интерфейс, но в рамках решаемой задачи это не нужно.

Далее приведен сервис для проверки состояния CPU, swap и RAM.
Пропишите его в файле настроек.
check system ServiceName
      if memory usage > 10% then alert
      if swap usage > 25% then alert
      if cpu usage (user) > 70% then alert
      if cpu usage (system) > 30% then alert
      if cpu usage (wait) > 20% then alert

Здесь ServiceName - любое имя. Оно будет использоваться в email уведомлениях.
system - Категория проверки. Каждая категория имеет собственный набор настроек, которые можно посмотреть на официальной странице. В данном случае мониторится система.

Синтаксис довольно понятный и не нуждается в объяснении. 
Всякий раз, когда возникает alert, monit записывает данные об этом в лог, а так же отправляет email на указанные адреса.

Запуск


Чтобы запустить monit, выполните команду:
sudo monit

При этом запустится демон, выполняющий все описанные сервисы.
Если напечатать команду еще раз, сервисы запустятся немедленно вне очереди.

Чтобы прекратить мониторинг, выполните:
sudo monit quit

Полный список опций программы доступен здесь:

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

Backups in Ruby On Rails. Gem Backup

Каждый Ruby On Rails программист, опасающийся за сохранность своих зубов, должен задуматься о настройке резервного копирования приложения и базы данных (Backup copy, далее - бэкап).

Здесь описан процесс настройки резервного копирования Ruby On Rails приложений с помощью гема Backup (https://github.com/meskyanichi/backup).

В результате будет настроено следующее:
1. Сохранение в архив всех файлов приложения и инструкций для восстановления БД один раз в сутки;
2. Отправка архива на FTP сервер;
3. Оповещение по email о результате бэкапа.

Установка


Добавьте в gemfile строку:
gem 'backup'

Выполните
bundle install

Создание модели бэкапа


Модель бэкапа описывает, каким образом делать резервное копирование (Какие файлы и базы данных сохранять, где их сохранять, каким образом сжимать файлы, как оповещать о проделанной работе и т.д.)

Создайте простейшую модель бэкапа:
backup generate:model --trigger my_backup --archives --storages='local' --compressors='gzip' --notifiers='mail' --databases='postgresql'

--trigger - Имя триггера для модели бэкапа
--archives - Модель будет использовать tar архиватор
--storages - Места хранения бэкапов (локальное хранилище, облачное хранилище, ftp)
--compressors - (bzip2, custom, gzip)
--notifiers - способы оповещения, например, через email
--databases - используемые базы данных (в данном случае - postgresql)

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

После выполнения команды должны появиться строки:
Generated model file: '/home/username/Backup/models/my_backup.rb'.
Generated configuration file: '/home/username/Backup/config.rb'.

Конфигурационный файл можно оставить без изменений. Приступим к доработке модели.

Настройка модели бэкапа


Стандартно созданный файл модели имел следующий вид (комментарии упущены):
Backup::Model.new(:my_backup, 'Description for my_backup') do
  split_into_chunks_of 250
  
  archive :my_archive do |archive|
    archive.add "/path/to/a/file.rb"
    archive.add "/path/to/a/folder/"
    archive.exclude "/path/to/a/excluded_file.rb"
    archive.exclude "/path/to/a/excluded_folder"
  end

  database PostgreSQL do |db|
    db.name               = "my_database_name"
    db.username           = "my_username"
    db.password           = "my_password"
    db.host               = "localhost"
    db.port               = 5432
    db.socket             = "/tmp/pg.sock"
    db.skip_tables        = ["skip", "these", "tables"]
    db.only_tables        = ["only", "these", "tables"]
    db.additional_options = ["-xc", "-E=utf8"]
  end

  store_with Local do |local|
    local.path       = "~/backups/"
    local.keep       = 5
  end

  compress_with Gzip

  notify_by Mail do |mail|
    mail.on_success           = true
    mail.on_warning           = true
    mail.on_failure           = true

    mail.from                 = "sender@email.com"
    mail.to                   = "receiver@email.com"
    mail.address              = "smtp.gmail.com"
    mail.port                 = 587
    mail.domain               = "your.host.name"
    mail.user_name            = "sender@email.com"
    mail.password             = "my_password"
    mail.authentication       = "plain"
    mail.encryption           = :starttls
  end

end

Файл нужно доработать, а именно:

1. Указать, какие каталоги нужно добавить в бэкап, а какие исключить:
archive :mayak_archive do |archive|
    archive.add "/home/username/Workspace/RubyOnRails/Appname"
end

Все предельно просто. Указываем папки и файлы, которые нужно добавить в архив с помощью archive.add.
Для исключения каталога из архива используйте archive.exclude.

2. Настроить доступ к БД:
database PostgreSQL do |db|
    db.name               = "my_database_name"
    db.username           = "my_username"
    db.password           = "my_password"
    db.host               = "localhost"
    db.port               = 5432
    db.socket             = "/var/run/postgresql"
    db.additional_options = []
end

Указываем имя базы данных, пользователя БД с паролем, адрес и порт для доступа к БД и сокет БД.

Путь к сокету БД у меня отличался от стандартного. Я указал свой: /var/run/postgresql.
Чтобы выполнить бэкап БД на удаленной машине, вместо localhost нужно указать ip адрес сервера.

Если вы хотите сохранять лишь некоторые таблицы, используйте db.skip_tables и db.only_tables.

3. Настроить оповещение по email:
Я использовал почтовый ящик gmail. Конфигурация приведена именно для него.
notify_by Mail do |mail|
    mail.on_success           = true
    mail.on_warning           = true
    mail.on_failure           = true

    mail.from                 = "my_email@gmail.com"
    mail.to                   = "4ybakut2004@gmail.com"
    mail.address              = "smtp.gmail.com"
    mail.port                 = 587
    mail.domain               = "gmail.com"
    mail.user_name            = "my_email@gmail.com"
    mail.password             = "my_password"
    mail.authentication       = "plain"
    mail.encryption           = :starttls
end

Вместо my_email@gmail.com укажите адрес, с которого будет происходить рассылка.
Вместо my_password укажите пароль к этому почтовому ящику.

4. Настроить отправление архива на FTP:
store_with FTP do |server|
    server.username     = 'ftp_username'
    server.password     = 'ftp_password'
    server.ip           = 'ftp_ip_address'
    server.port         = 21
    server.path         = '/путь/сохранения/на/ftp/сервере'
    server.keep         = 4
    server.passive_mode = false
end

Указываем данные для доступа к FTP (логин, пароль, ip адрес), путь для сохранения бэкапов и количество хранимых бэкапов.

Настройка завершена. Полный вид файла модели:
Backup::Model.new(:my_backup, 'Description for my_backup') do
  split_into_chunks_of 250

  archive :mayak_archive do |archive|
    archive.add "/home/username/Workspace/RubyOnRails/Appname"
  end

  database PostgreSQL do |db|
    db.name               = "my_database_name"
    db.username           = "my_username"
    db.password           = "my_password"
    db.host               = "localhost"
    db.port               = 5432
    db.socket             = "/var/run/postgresql"
    db.additional_options = []
  end

  store_with Local do |local|
    local.path       = "~/backups/"
    local.keep       = 5
  end

  store_with FTP do |server|
    server.username     = 'ftp_username'
    server.password     = 'ftp_password'
    server.ip           = 'ftp_ip_address'
    server.port         = 21
    server.path         = '/путь/сохранения/на/ftp/сервере'
    server.keep         = 4
    server.passive_mode = false
  end

  compress_with Gzip

  notify_by Mail do |mail|
    mail.on_success           = true
    mail.on_warning           = true
    mail.on_failure           = true

    mail.from                 = "my_email@gmail.com"
    mail.to                   = "4ybakut2004@gmail.com"
    mail.address              = "smtp.gmail.com"
    mail.port                 = 587
    mail.domain               = "gmail.com"
    mail.user_name            = "my_email@gmail.com"
    mail.password             = "my_password"
    mail.authentication       = "plain"
    mail.encryption           = :starttls
  end

end

Запуск бэкапа


Чтобы запустить бэкап выполните команду:
bundle exec backup perform --trigger my_backup

Имя триггера укажите то, которое вы задали во время создания модели.

После этого будет создан архив приложения и БД, который поместится по указанному адресу на FTP сервере. Письмо с результатом будет отправлено на указанный почтовый ящик.

Периодическое выполнения бэкапа


Для выполнения бэкапа по расписанию можно создать cron job. Для этого я использовал гем whenever (https://github.com/javan/whenever), позволяющий легко организовывать периодические операции в rails приложениях через cron jobs.

Однако, для выполнения бэкапа на сервере, команду пришлось доработать и указать путь к gemfile:
BUNDLE_GEMFILE=/path/to/gemfile/Gemfile  bundle exec backup perform --trigger my_backup


Восстановление БД


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

Чтобы восстановить БД из бэкапа, найдите файла дампа в архиве (он будет иметь примерно такое имя - PostgreSQL.sql) и выполните команду:

psql имя_БД < файл_дампа

Подробное описание здесь
И на русском, здесь: