4 октября 2015 г.

Исправляем проблему сборки модулей VirtualBox под Ubuntu

Который раз уже сталкиваюсь с проблемой, что срочно нужна виртуальная машина с WinXP, но быстро выясняется, что после обновления ядра DKMS не собрал необходимые модули автоматически. Поэтому приходилось вручную собирать модули следующей командой:
sudo /etc/init.d/vboxdrv setup
По сообщениям в консоли, стало понятно, что регистрация модулей через DKMS не срабатывает, поэтому после установки обновлений ядра автоматическая установка не происходит, что подтвердил вывод команды:
dkms status | grep vboxhost
Немного погуглив по английским сайтам, нашел рабочее решение, чтобы восстановить сборку через dkms нужно сделать следующее:
sudo rm -rf /var/lib/dkms/vboxhost/
sudo dpkg-reconfigure virtualbox-5.0
Первой командой мы удаляем настройки от старых версий virtualbox (а у меня их была целая куча), затем перенастраиваем пакет с virtualbox (здесь нужно будет указать свою версию virtualbox, если вы пользуетесь отличной от моей). После чего вас могут попросить закрыть виртуалбокс, а так же спросят, нужно ли установить драйвер прямо сейчас - отвечаем ДА. После чего через dkms соберется драйвер для текущей версии ядра. Убедиться в том, что все прошло успешно, можно командой:
dkms status | grep vboxhost
При успехе получим следующее:
vboxhost, 5.0.4, 3.16.0-50-generic, x86_64: installed
Обратите внимание, что версия ядра должна совпадать с текущим. На этом все, надеюсь кому-то эта информация пригодится.

Arduino + Raspberry Pi = термометр + гигрометр с отправкой данных на сервер (часть 2)

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

Для сбора данных с Arduino используется программа на языке Python, который читает данные в формате CSV с серийного порта ардуины, затем обрабатывает их и отправляет на веб-сервер, FTP и OpenWeatherMap. Так же я предусмотрел возможность записи в журнал сообщений о действии программы или об возникающих ошибках, поэтому можно легко понять, из-за чего данные могли не дойти до серверов.

Теперь рассмотрим поподробнее работу нашего сборщика данных. Удобнее всего будет давать объяснения прямо по тексту программы, поэтому начнем:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# тут мы импортируем все необходимые библиотеки и модули для дальнейшей работы
import serial
from serial import SerialException
import datetime
import re
import ftplib
import socket
import os
import sys
import time
import signal
import logging
from logging.handlers import WatchedFileHandler
import threading
import requests
from requests.exceptions import *

# тут у нас указан путь к файлу журнала, куда будут записываться все сообщения
LOG_PATH = '/var/log/mstation.log'
# формат сообщения, в конце статьи приведу ссылку на описание, что тут что означает
LOG_FORMAT = '[%(asctime)s] %(levelname)s: %(funcName)s: %(message)s'
# путь к серийному порту (наша ардуина)
SERIAL_DEV = '/dev/ttyACM0'
# здесь указываем имя пользователя, логин и пароль для ftp сервера
FTP_SERV = 'ftp_serv'
FTP_NAME = 'ftp_name'
FTP_PASS = 'ftp_password'
# путь к директории, в которой будут храниться полученные данные (на Raspberry Pi)
DATA_PATH = '/usr/local/MStation/data/'
# количество попыток установить соединение с ардуиной
CONNECT_TRYOUT = 10
# путь к php файлу на сервере, который обрабатывает отправленные данные
UPLOAD_URL = 'upload_handler'
# OpenWeatherMap
# данные для проекта OpenWeatherMap (логин, пароль, название станции и ее координаты)
URL_OPENW = 'http://openweathermap.org/data/post'
LATITUDE = 56.3294
LONGITUDE = 37.7532
ALTITUDE = 215
USER_NAME = 'user_name'
PASSW = 'password'
STATION_NAME = 'station_name'
# OpenWeatherMap

# блокировки для корректной работы потоков, подробности будут ниже
write_lock = threading.Lock()
upload_lock = threading.Lock()

# очередь с пакетами, которые не удалось отправить
failed_upload = []

# тут мы настраиваем работу логгера
log = logging.getLogger('main_log')
log.setLevel(logging.INFO)
log_handler = WatchedFileHandler(LOG_PATH)
log_fmt = logging.Formatter(fmt=LOG_FORMAT)
log_handler.setFormatter(log_fmt)
log.addHandler(log_handler)


# основная функция, отсюда начинается выполнение
def main():
    signal.signal(signal.SIGINT, ctrl_c_handler)
    # генерируем имя файла, в который будут записаны полученные от Arduino данные
    f_name = datetime.datetime.now().strftime('%Y-%m-%d.cvs')
    # получаем полный путь к файлу
    f_path = DATA_PATH + f_name
    # открываем файл в режиме присоединения, т.е. если файл уже есть на диске, то данные будут в него дописываться
    f_table = open(f_path, 'a', 0)

    # открываем соединение с Arduino
    ser = serial_connect(SERIAL_DEV, 115200)

    # паттерн для отсеивания строки инициализации
    init_patt = re.compile(r'MStation')
    # запоминаем текущую дату
    curr_date = datetime.date.today()

    # считывание данных у нас происходит в бесконечном цикле
    while True:
        try:
            # считываем строку из порта пока не встретится символ окончания строки
            curr = ser.readline()
        except SerialException as e:
            # если возникла ошибка, то открываем соединение заново
            log.error('Serial error: ' + str(e))
            time.sleep(120)
            ser = serial_connect(SERIAL_DEV, 115200)
            continue

        # проверяем, считанная строка - строка инициализации ?
        init_match = re.search(init_patt, curr)

        if init_match:
            # если да, то отправляем ее в журнал
            log.info(curr)
            continue
        # если у нас изменилась дата, то меняем имя файла и открываем новый файл
        if curr_date.day != datetime.date.today().day:
            f_table.close()
            f_name = datetime.datetime.now().strftime('%Y-%m-%d.cvs')
            f_path = DATA_PATH + f_name
            f_table = open(f_path, 'a', 0)
            curr_date = datetime.date.today()

        # приписываем к строки с данными текущие дату и время
        date_str = datetime.datetime.now().strftime('%Y/%m/%d %H:%M')
        final_str = date_str + ',' + curr

        # создаем поток, который отсылает данные на ftp сервер
        ftp_thread = threading.Thread(
            target=upload_to_ftp,
            args=(FTP_SERV, FTP_NAME, FTP_PASS, f_name)
        )
        # создаем поток, который отсылает данные на веб сервер
        upl_thread = threading.Thread(
            target=upload_to_site,
            args=(UPLOAD_URL, final_str)
        )
        # создаем поток, который отсылает данные на проект OpenWeatherMap сервер
        openw_thread = threading.Thread(
            target=upload_to_openweathermap,
            args=[final_str]
        )

        # записываем данные в файл
        write_lock.acquire()
        f_table.write(final_str)
        write_lock.release()
        # запускаем работу потоков
        ftp_thread.start()
        upl_thread.start()
        openw_thread.start()

    sys.exit(0)


# функция для создания соединения с Arduino через серийный порт
def serial_connect(dev, speed):
    try_count = 1

    while try_count <= CONNECT_TRYOUT:
        try:
            ser = serial.Serial(dev, speed)
        except SerialException as e:
            log.error('Serial Connect Error: ' + str(e))
            print 'Connection Error'
            time.sleep(120)
        else:
            log.info('Connected after ' + str(try_count) + ' tryouts')
            return ser
        finally:
            try_count += 1

    log.critical('FAIL to connect. Exit.')
    sys.exit(-1)


# функция для отправки данных на ftp сервер
def upload_to_ftp(host, name, passw, f_name):
    # генерируем полный путь к файлу
    path = DATA_PATH + f_name
    # открываем файл
    upl_file = open(path, 'rb')
    # к файлу на сервере при загрузке дописывается .new для того, чтобы в случае проблем с соединением, на сервере не оказался испорченный файл, поэтому файл заливается с приписанным к нему .new, а при успехе файл переименовывается
    name_new = f_name + '.new'
    try:
        # устанавливаем соединение и загружаем файл в папку public_html
        ftp_serv = ftplib.FTP(host, name, passw, '', 30)
        ftp_serv.cwd('public_html')
        ftp_serv.voidcmd('TYPE I')
        ftp_serv.voidcmd('PASV')
        write_lock.acquire()
        ftp_serv.storbinary('STOR '+name_new, upl_file)
        # смотрим размер залитого файла
        upl_size = ftp_serv.size(name_new)
        if upl_size:
            # сравниваем с файлом на диске, при совпадении переименовываем на сервере
            f_size = os.path.getsize(path)
            if upl_size == f_size:
                ftp_serv.rename(name_new, f_name)
            else:
                raise uplFail('sizes not match')
        else:
            raise uplFail('unknown error')
        ftp_serv.close()
    # обработка потенциальных ошибок
    except (socket.error, socket.gaierror) as e:
        log.error('FTP Socket Error: ' + str(e))
    except ftplib.error_temp as e:
        log.error('FTP Temporary Error: ' + str(e))
    except ftplib.error_perm as e:
        log.error('FTP Permanent Error: ' + str(e))
    except ftplib.error_reply as e:
        log.error('FTP Unexpected Error: ' + str(e))
    except uplFail as e:
        log.error('FTP Upload Failed: ' + str(e))
    else:
        log.info('FTP Upload Success')
    finally:
        if write_lock.locked():
            write_lock.release()

    upl_file.close()


# необходим для работы загрузки по FTP
class uplFail(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


# отправка данных на веб-сервер
# при запуске отправляет один пакет на сервер, при успехе смотрит, не остались ли неотправленные данные, если остались, то пытается их отослать, если отправка одного пакета оказалась неудачной - добавляет пакет в список неотправленных пакетов
def upload_to_site(url, data_str):
    if not upload_to_url(url, data_str):
        failed_upload.append(data_str)
    else:
        if upload_lock.acquire(False):
            while len(failed_upload) != 0:
                data = failed_upload[0]
                if upload_to_url(url, data):
                    failed_upload.pop(0)
            upload_lock.release()


# отправляет один пакет на сервер
def upload_to_url(url, data_str):
    # подготавливаем данные к отправке
    data_send = data_str.split(',')
    data_req = {
        'date': data_send[0],
        'temp1': data_send[1],
        'temp2': data_send[2],
        'humid': data_send[3]
    }

    try:
        # пытаемся отправить данные на веб-сервер
        req = requests.post(url, data=data_req, timeout=60)
    # обрабатываем ошибки, если они были
    except (ConnectionError, HTTPError, URLRequired, Timeout) as e:
        log.error(str(e))
        return False
    else:
        # проверяем, правильно ли обработан наш POST запрос
        if (req.status_code == requests.codes['ok']):
            log.info('Upload success.')
            return True
        else:
            log.error('Upload failed with code ' + str(req.status_code))
            return False


# отправка данных на проект OpenWeatherMap, работает аналогично предыдущей функции
def upload_to_openweathermap(data_str):
    data_send = data_str.split(',')
    data_post = {
        'temp': data_send[1],
        'humidity': data_send[3],
        'lat': LATITUDE,
        'long': LONGITUDE,
        'alt': ALTITUDE,
        'name': STATION_NAME
    }
    try:
        req = requests.post(
            URL_OPENW,
            auth=(USER_NAME, PASSW),
            data=data_post,
            timeout=60
        )
    except (ConnectionError, HTTPError, URLRequired, Timeout) as e:
        log.error(str(e))
    else:
        if (req.status_code == requests.codes['ok']):
            log.info('Upload to OpenWeather success.')
        else:
            log.error(
                'Upload to OpenWeather failed with code '
                +
                str(req.status_code)
            )


# обработчик сигнала об завершении работы программы
def ctrl_c_handler(signum, frame):
    print 'Exit.'
    log.info('Exit.')
    logging.shutdown()
    sys.exit(0)

if __name__ == '__main__':
    main()

Теперь необходимо рассказать о некоторых аспектах работы всей этой штуковины. Как уже говорилось в предыдущей части статьи, данные с ардуины приходят в формате CSV, здесь же к этим данным дописываются текущие дата и время, естественно с условием соблюдения этого формата. Все эти данные хранятся на диске (в случае с Raspberry Pi на sd карточке), и при каждом изменении файла отправляются на FTP-сервер. Для чего ? Все довольно просто, изначально данные на сервере брались из файла, но впоследствии я реализовал хранение в бд MySQL, а реализованную загрузку на ftp решил не удалять. Если вам она не нужна, то просто удалите код, связанный с FTP. И да, один файл содержит данные, собранные за сутки, поэтому называется, к примеру, 2015-10-01.cvs, а его содержимое выглядит примерно вот так:
2014/09/05 14:35,20.57,21.90,50.80
2014/09/05 14:40,20.66,21.90,50.00
2014/09/05 14:45,20.51,22.00,49.90
2014/09/05 14:50,20.98,22.00,50.00
2014/09/05 14:55,20.98,22.00,50.00
Данные на сервер отправляются POST запросом (для собственного веб-сервера и для OpenWeatherMap). О том, что этот запрос из себя представляет, почитайте вот тут: http://www.myfirstsite.ru/articles/get-and-post 
Так как я пользуюсь потоками для отправки данных на сервера, то возникла необходимость ввести примитивы синхронизации потоков, такие как блокировки:
write_lock = threading.Lock()
upload_lock = threading.Lock()
Первая блокировка нужна для того, чтобы у нас не возникло ситуации, что мы записываем данные в файл, а в этот момент у нас происходит его отправка на сервер, без блокировки мы бы получили файл, в конце которого был бы кусок данных, что не очень то хорошо. Вторая блокировка нужна, чтобы у нас не возникло ситуации, что у нас одновременно работают два потока и отправляют на сервер одни и те же данные. Подробнее об этих механизмах потоков и блокировках почитайте вот тут: https://www.ibm.com/developerworks/ru/library/l-python_part_9/

В конце скажу, что автозапуск программы осуществляется через upstart, подробно об этом я писал у себя вот в этой статье: Raspberry Pi + Raspbian = автозапуск программы при помощи upstart после синхронизации с NTP

На этом, пожалуй, и остановимся, в следующей части статьи расскажу о той части, которая работает на стороне сервера и обрабатывает запросу на прием и выдачу данных. Если возникнут какие-то вопросы, пишите мне в G+ или в комментарии.

Всего вам доброго.

Ссылка на исходный код: https://github.com/edgar-ch/mstation/blob/stable/mstation_serv.py
Предыдущая часть статьи: http://blog.edtex.ru/2014/09/arduino-raspberry-pi-1.html

12 сентября 2015 г.

Установка Redmine под Ubuntu 12.04

Недавно понадобилось установить Redmine для разработки всяких плагинов, и этот рпоцесс прошел с довольно-таки неплохими корчами. Кроме самого редмайна понадобится установить еще базу данных и сервер. Поехали.


Для начала нужно установить пакеты с бд, сервером и прочим стафом, который нам понадобится:       
sudo aptitude install mysql-server mysql-client libmysqlclient-dev gcc build-essential 
                      zlib1g zlib1g-dev zlibc ruby-zip libssl-dev libyaml-dev 
                      libcurl4-openssl-dev ruby gem libapache2-mod-passenger 
                      apache2-mpm-prefork apache2-dev libapr1-dev libxslt1-dev 
                      checkinstall libxml2-dev ruby-dev vim
 
Теперь скачаем и подготовим сам редмайн:
cd /opt/
sudo mkdir redmine
sudo chown -R $your_user redmine
cd redmine
wget $redmine.tar.gz
tar xzf $redmine.tar.gz
cd redmine-X.X.X
Если что-то работает не так, всегда можно распоковать менеджером архивов ;)

Теперь подготовим MySQL:
mysql --user=root --password=$password
CREATE DATABASE redmine CHARACTER SET utf8;
CREATE USER 'redmine'@'localhost' IDENTIFIED BY 'my_password';
GRANT ALL PRIVILEGES ON redmine.* TO 'redmine'@'localhost';
exit
Вместо my_password здесь и далее не забудьте указать ваш пароль.
Сделаем конфиг для рейдмайн бд и попарвим его:
cp config/database.yml.example config/database.yml
В самом файле config/database.yml запишем: 
production:
  adapter: mysql2
  database: redmine
  host: localhost
  username: redmine
  password: my_password 
Пора заняться бандлером. Установим его:
sudo gem install bundler
bundle install --without development test rmagick
Сгенерируем secret token:
bundle exec rake generate_secret_token
Подготовим бд и проинсталируем все таблицы:
RAILS_ENV=production bundle exec rake db:migrate
RAILS_ENV=production bundle exec rake redmine:load_default_data
Пора протестить редмайн. Вместо $IP введите ваш внешний IP:
bundle exec ruby bin/rails server -b $IP webrick -e production
и посетите адрес http://$IP:3000.
Время для сервера. В нашем случае - Apach.

Apache работает с данными через www-data, так что нам нужно будет дать ей доступ к файлам:
sudo chown -R www-data files log tmp public/plugin_assets
sudo chmod -R 755 files log tmp public/plugin_assets
Создадим ссылку между публичной папкой редмайна и рутом апача:
sudo ln -s /opt/redmine/redmine-X.X.X/public/ /var/www/html/redmine
Создадим новый конфиг VirtalHost:
sudo vim /etc/apache2/sites-available/master.conf
и запишем в этот файл следующее для редмайна:
<VirtualHost *:80>

ServerAdmin admin@example.com
Servername hostname
DocumentRoot /var/www/html/

        <Location /redmine>
                RailsEnv production
                RackBaseURI /redmine
                Options -MultiViews
        </Location>

</VirtualHost>
Сохраните и закройте вим. Чтобы это сделать в командной строке вима нужно прописать :q. Я недавно узнала, что некоторые от отчаяния перезагружают комп, чтобы закрыть вим. Не надо так)

Отключаем дефолтный виртуал-хост:
sudo a2dissite 000-default.conf
И подрубаем новый мастер виртуал-хост:
sudo a2ensite master.conf
Чтобы избежать ошибки доступа, пассенджер мод тоже нужно запускать как www-data. В файле /etc/apache2/mods-available/passenger.conf добавьте эту строку:
PassengerUser www-data
И перезапустите апач:
sudo service apache2 restart
Посетите http://$IP/redmine - счастье откроется вам.

7 сентября 2015 г.

Сканирование локальной сети (IP и MAC адреса)

Недавно возникла необходимость узнать IP и MAC адрес raspberry pi, которая была подключена к роутеру по ethernet с ноутбука, который подключен к тому же роутеру, но по wifi. Наиболее удобной оказалась утилита arp-scan, которая рассылает по сети ARP пакеты и отображает данные о хостах, от которых был получен ответ. Запускаем ее из консоли со следующими опциями:

sudo arp-scan --interface wlan0 --localnet

Для ее работы необходимы привилегии root, поэтому используем sudo. В результате работы получим список хостов в локальной сети с их IP и MAC адресами:
результаты работы arp-scan
Таким образом, можно быстро узнать адрес raspberry pi в сети (как и любого другой платы или компьютера). В опции --interface указываем интерфейс, на котором нужно произвести сканирование.

5 сентября 2015 г.

Клонирование SD/microSD карты для Raspberry PI

Возникла необходимость сделать полную копию microSD карты с дистрибутивом Raspbian, не потеряв настройки и установленные программы. Мне копия понадобилась, чтобы переехать на большую шуструю карточку, да и резервная копия тоже не помешает.
Под Ubuntu есть удобная утилитка dd, которая позволяет делать копию устройства побайтно (копируются не файлы в отдельности, а целиком вся файловая система, включая так же и таблицу разделов, по сути получается клон sd карточки). Ей мы и воспользуемся. Работа с ней выглядит следующим образом (естественно в консоли):

dd if=/dev/sdX of=/path/to/image bs=1M

If у нас указывает на путь к устройству, с которого мы будем делать копию, а of указывает на путь образа, куда будет сохранена копия, bs указывает на размер блока, который будет считываться за один раз, в данном случае 1 мегабайт.
Чтобы узнать путь к нужному устройству, введем команду lsblk в консоли:

NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0 298,1G  0 disk
├─sda1   8:1    0   100G  0 part
├─sda2   8:2    0    15G  0 part
├─sda3   8:3    0     1K  0 part
├─sda4   8:4    0    16M  0 part
├─sda5   8:5    0    17G  0 part /
├─sda6   8:6    0 161,1G  0 part /home
└─sda7   8:7    0     5G  0 part [SWAP]
sdb      8:16   1   3,8G  0 disk
├─sdb1   8:17   1    56M  0 part /media/edgar/boot
└─sdb2   8:18   1   3,7G  0 part /media/edgar/13d368bf-6dbf-4751-8ba1-88bed06bef


Чтобы понять, какое устройство нужное, смотрим на размер и путь, по которому примонтирована файловая система (если не работает автомонтирование, ориентируемся на размер). Тут у нас размер 3.8G, это как раз наша microSD карточка на 4 гигабайта. Так же тут видно, что raspbian использует отдельный раздел для boot, где лежит все необходимое для успешной загрузки. В итоге команда будет выглядеть вот так:

sudo dd if=/dev/sdb of=/home/edgar/raspbian_2015-09-04.img bs=1M

Для работы dd нужно привилегии root-пользователя, поэтому используем sudo. В конце работы утилитка выдала следующее:

3840+0 записей получено
3840+0 записей отправлено
скопировано 4026531840 байт (4,0 GB), 392,461 c, 10,3 MB/c


Тут у нас видно сколько было скопировано и на какой скорости. Теперь зальем образ на другую карточку. Отключим уже скопированную карточку и вставим новую, после чего еще раз запустим lsblk:

NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0 298,1G  0 disk
├─sda1   8:1    0   100G  0 part
├─sda2   8:2    0    15G  0 part
├─sda3   8:3    0     1K  0 part
├─sda4   8:4    0    16M  0 part
├─sda5   8:5    0    17G  0 part /
├─sda6   8:6    0 161,1G  0 part /home
└─sda7   8:7    0     5G  0 part [SWAP]
sdb      8:16   1   7,4G  0 disk
└─sdb1   8:17   1   7,4G  0 part /media/edgar/6632-3961

Новая карточка у нас имеет размер 8 гигабайт, и находится она по пути /dev/sdb. Теперь запустим копирование образа на эту карточку:

sudo dd if=/home/edgar/raspbian_2015-09-04.img of=/dev/sdb bs=1M

В результате получим:

3840+0 записей получено
3840+0 записей отправлено
скопировано 4026531840 байт (4,0 GB), 429,094 c, 9,4 MB/c

Таким образом, наш образ скопирован на новую карточку. Теперь отключим и заново вставим карточку и убедимся, что образ успешно скопирован (через lsblk):

sdb      8:16   1   7,4G  0 disk
├─sdb1   8:17   1    56M  0 part /media/edgar/boot
└─sdb2   8:18   1   3,7G  0 part /media/edgar/13d368bf-6dbf-4751-8ba1-88bed06bef


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

14 мая 2015 г.

Как проверить zip архив на целостность в Ubuntu

Недавно качал свежую прошивку для своего смартфона через 3g сеть на даче, которая была в виде zip архива, а контрольных сумм на сайте производителя небыло. А проверить, не битый ли архив, очень хотелось.

Итак, чтобы проверить архив, открываем консоль, переходим в директорию, где лежит наш архив, и запускаем:
zip -T your_firmware.zip
Если архив не битый и с ним все в порядке, то увидим следующее сообщение:
test of your_firmware.zip OK
В этот раз нам повезло и архив в полном порядке, можно обновлять прошивку на смартфоне.

13 мая 2015 г.

Решаем проблему установки Steam на Ubuntu 14.04

При установке клиента Steam на версию Ubuntu 14.04.2 64-битной редакции (amd64) может возникнуть ошибка зависимостей пакетов, на подобии такой:

Пакеты, имеющие неудовлетворённые зависимости:
libgl1-mesa-glx:i386 : Зависит: libglapi-mesa:i386 (= 10.1.3-0ubuntu0.4)
                        Рекомендует: libgl1-mesa-dri:i386 (>= 7.2)
E: Невозможно исправить ошибки, у вас отложены (held) битые пакеты.

Это связано с тем, что при установке убунту версии равной 14.04.2 или выше, устанавливается графический стек от более поздней редакции (на данный момент от версии 14.10 15.10).

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

sudo apt-get install libc6:i386 libgl1-mesa-dri-lts-utopic:i386 libgl1-mesa-glx-lts-utopic:i386

После установки пакетов, steam должен без проблем запуститься и начать скачивать файлы или запуститься, если такие уже имеются в домашней директории.

Update:
В выпусках 14.04.3 и 14.04.4 так же обновился графический стек (от версий 15.04 и 15.10 соответственно), поэтому актуальные команды будут выглядеть следующим образом.
Для стека от версии 15.04 (14.04.3):

sudo apt-get install libc6:i386 libgl1-mesa-dri-lts-vivid:i386 libgl1-mesa-glx-lts-vivid:i386

Для стека от версии 15.10 (14.04.4):

sudo apt-get install libc6:i386 libgl1-mesa-dri-lts-wily:i386 libgl1-mesa-glx-lts-wily:i386

Если будут какие-то другие проблемы, пишите в комментарии.

6 мая 2015 г.

Отслеживание действий с файлами или папками в Ubuntu с помощью iwatch

Отслеживаем изменения файлов

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

iwatch

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

iwatch - консольный режим

В самом простом случае для запуска достаточно указать команду в таком формате:
iwatch "путь к директории или файлу"
Если указали директорию и нужно рекурсивно просмотреть все поддиректории, то добавим опцию -r:
iwatch -r "путь к директории или файлу"
Но в таком формате iwatch будет отслеживать события по-умолчанию, в которые не входит события доступа и изменения (события по-умолчанию: close_write, create, delete, move, delete_self and move_self).
Чтобы нам указать, какие события нужно отслеживать, добавим опцию -e:
iwatch -r -e default,modify "путь к директории или файлу"
Попробуем отследить изменения файлов в директории командой:
iwatch -r -e default,modify petrenko/
Поменяем один символ в файле и сохраним его. В результате получим:
[ 5/мая/2015 23:36:50] IN_CLOSE_WRITE petrenko/flex/lab2/flex2_1.l
[ 5/мая/2015 23:36:50] * petrenko/flex/lab2/flex2_1.l is closed
[ 5/мая/2015 23:36:56] IN_MODIFY petrenko/flex/lab2/flex2_1.l
[ 5/мая/2015 23:36:56] IN_CLOSE_WRITE petrenko/flex/lab2/flex2_1.l
[ 5/мая/2015 23:36:56] * petrenko/flex/lab2/flex2_1.l is closed
Хорошо видно, что происходило с файлом во время наших манипуляций. 

iwatch поддерживает большой набор событий, опишем самые основные из них:
access - был доступ к файлу
modify - файл был изменен
attrib - изменились атрибуты файла
close_write - файл закрыт после открытия в режиме записи
close_nowrite - файл закрыт после открытия в режиме только для чтения
close - файл закрыт, независимо, после чтения или записи
open - файл был открыт
moved_from - файл был перемещен в другое место
moved_to - файл был перемещен в отслеживаемую директорию
move - файл или директория были перемещены
create - создан файл в отслеживаемых директориях
delete - удален файл в отслеживаемых директориях
delete_self - отслеживаемый файл удален
unmount - файловая система, на которой находится отслеживаемый файл, размонтирована
isdir - событие с директорией
default - стандартные события

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

iwatch - фильтруем отслеживаемые файлы

Для фильтрации у нас есть три опции:
-t <строка фильтра> - указываем строку (в формате регулярных выражений), по которой будут выбраны файлы или директории
-X <строка фильтра> - указываем фильтр, по которому будут исключены файлы или директории
-x <имя файла или директории> - указываем файл или директорию, которые будут исключены из отслеживаемых

Чтобы отслеживать доступ к файлам в формате flac:
iwatch -r -e access -t '.flac' Музыка/
Чтобы исключить из отслеживаемых все скрытые файлы, введем:
iwatch -r -e access -X '/\.' petrenko/

iwatch - выполняем команду при событии

Чтобы выполнить команду при возникновении события, используем опцию , например, чтобы вывести уведомление в системе при изменении файла:
iwatch -r -c "notify-send iwatch 'Событие %e в %f'" -e modify petrenko/
У iwatch есть возможность подставлять информацию о событии на места спецификаторов, в примере выше использованы %e и %f, которые означают имя события и путь к файлу или директории, с котором это событие произошло. 

Остальные спецификаторы (как и дополнительную информацию об использовании утилиты) можно увидеть, если запустить iwatch без всяких опций.

На этом закончим, о работе в режиме демона читайте в следующей части статьи.

19 апреля 2015 г.

ImageMagick = добавляем поля к изображениям или фото под Ubuntu

Сегодня расскажу, как можно быстро добавить поля (или границы, кому как больше нравится) к нескольким изображениям или фото. Для этого воспользуемся утилитой convert из набора ImageMagick. Это довольно распространенный пакет, найти его можно практически в любом дистрибутиве Linux.

Для установки под Ubuntu выполним команду:

sudo apt-get install imagemagick

Теперь можем приступать. Для примера возьмем вот эту картинку из википедии:
фото для манипуляций

Чтобы добавить границы к изображению, наберем в терминале:
convert -bordercolor '#00FFFF' -border 70x50 lemon.jpg lemon1.jpg
В результате получим:
добавляем границы со всех сторон

Теперь по порядку. Опцией bordercolor мы задаем цвет границ, можно указывать в формате HTML, название цвета или через директиву rgb, выглядит это вот так (результат будет идентичен):

convert -bordercolor aqua -border 70x50 lemon.jpg lemon1.jpg
convert -bordercolor '#0FF' -border 70x50 lemon.jpg lemon1.jpg
convert -bordercolor 'rgb(0, 255, 255)' -border 70x50 lemon.jpg lemon1.jpg
convert -bordercolor 'rgb(0, 100%, 100%)' -border 70x50 lemon.jpg lemon1.jpg

Опцией border указываем размеры границы. Есть несколько вариантов передачи параметров. Рассмотрим их на примерах. Первый вариант имеет формат XxY, где X - размер границы слева и справа от изображения, Y - сверху и снизу:

convert -bordercolor yellow -border 70x50 lemon.jpg lemon1.jpg
convert -bordercolor yellow -border 70x0 lemon.jpg lemon2.jpg
convert -bordercolor yellow -border 0x50 lemon.jpg lemon3.jpg

Соответственно получим:
поля со всех сторонполя слева и справаполя сверху и снизу

Второй вариант - это когда мы указываем размер не в пикселях, а в процентах от размера (ширина и высота) нашего изображения. например:

convert -bordercolor '#808000' -border 20%x10% lemon.jpg lemon4.jpg
convert -bordercolor '#808000' -border 20%x0% lemon.jpg lemon5.jpg
convert -bordercolor '#808000' -border 0%x10% lemon.jpg lemon6.jpg

Получим:
поля со всех сторонполя слева и справаполя сверху и снизу

Третий вариант - просто указываем размеры границы в пикселях или в процентах от ширины/высоты (этот вариант очень удобен, так как размер высчитывается отдельно от ширины и высоты):

convert -bordercolor 'rgb(173, 255, 47)' -border 50 lemon.jpg lemon7.jpg
convert -bordercolor 'rgb(173, 255, 47)' -border 15% lemon.jpg lemon8.jpg

В результате получаем:

Чтобы сделать прозрачную границу (обратим внимание, что не все форматы поддерживают альфа-канал), добавим опцию matte и в качестве цвета укажем none:

convert -matte -bordercolor none -border 10% lemon.jpg lemon9.png

Результат:
прозрачные поля
С опциями разобрались. Теперь расскажу как обработать сразу несколько изображений. Для этого есть 2 способа - воспользоваться командой mogrify или использовать bash-скрипт.

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

mogrify -bordercolor white -border 30 *.jpg

В результате ко всем изображениям в формате jpg будет добавлена белая рамка. (ВАЖНО! в таком формате все изображения будут перезаписаны! Будьте внимательны и храните копии изображений). Чтобы избежать этого, укажем формат выходного файла:

mogrify -bordercolor white -border 30 -format png  *.jpg

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

#!/bin/bash

img=("$@")
count=${#img[@]}

for (( i=0; i<$count; i++ )); do
 name=${img[${i}]%.*}
 convert -bordercolor white -border 100x30 "${img[${i}]}" "$name bord.jpg"
done

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

Для запуска скрипта скопируйте его в текстовый файл, который назовите, например, add_border.sh и добавьте возможность запуска командой chmod u+x add_border.sh 

Скрипт запускам командой:
./add_border.sh files*.jpg

Файлы можно указывать и как:
./add_border.sh file1.jpg file2.jpg и так далее.

На этом все.
Всего вам доброго.

17 апреля 2015 г.

Устанавливаем Telegram на Ubuntu 14.04

В свете того, что вконтакте уже поднадоел тем, что постоянно глючит, сообщения отправляются медленно, приложение на Android постоянно вылетает когда ему захочется (это больше всего огорчает), поэтому расскажу кратко что это за зверь Telegram и как установить его desktop версию на Ubuntu.

Разработал его основатель вконтакте Павел Дуров, но Telegram не предназначался для российского рынка, поэтому оффициальные приложения на английском. И так, в чем плюсы:
  • использует свой собственный протокол под название MTProto, который предполагает, что для аутентификации используется шифрование RSA и DH, ключ хранится у нас и на сервере, так же используется хеш-суммы SHA1 и MD5
  • можно организовать секретный чат, при ктором сообщения шифруются алгоритмом AES, причем самое крутое, что ключ хранится только у участников чата, то есть на сервере телеграма ключа нет и история переписки доступна только нам (да да, можно устраивать шифрованные виртуальные сходки)
  • в целом удобное приложение, интерфейс похож на мобильное приложение вк, что не удивительно, так же мной небыли замечены серьезные глюки или недоработки, проще говоря пользоваться им удобно
А теперь о минусах:
  • увы, но сложно будет победить консерватизм ваших друзей и пересадить их на новый сервис, sad but true
  • закрытый исходный код сервера, это скорее минус, потому что черт знает что на нем происходит, хотите секретности - используйте секретные чаты или глухую поляну в темном лесу
  • лично для меня минус - нельзя кидать музыку
Под убунту есть две версии приложения, расскажу как установить обе версии.
Первый вариант - официальное приложение с сайта самого телеграма - https://desktop.telegram.org/
Скачивать не нужно, мы будем устанавливать через PPA. Для этого открываем терминал и вбиваем туда:
sudo add-apt-repository ppa:atareao/telegram
Соглашаемя с добавлением нового PPA (жмем Enter)
Дальше вводим:
sudo apt-get update
sudo apt-get install telegram
После открываем Unity, вбиваем telegram, выбираем там наше приложение и приступаем к настройке:
открываем telegram в unity

Далее жмем Start Messaging:
настройка tekegram
И вводим свой номер телефона. Да, при этом Telegram уже должен быть установлен на телефоне, нам туда придет код подтверждения:
вводим номер телофна
Вводим код, и сразу можем приступать к использованию. Для этой версии все.

Теперь о втором варианте, есть еще вариант приложения, которое за основу берет веб-версию телеграма. По сути у нас будет веб-приложение в отдельном окне.
Для установки открываем терминал и вводим туда:
sudo add-apt-repository ppa:costales/unity-webapps-telegram
sudo apt-get update
sudo apt-get install unity-webapps-telegram
После успешной установки аналогично открываем unity и запускаем приложение:
запускам telegram
Вводим номер телефона:

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

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

UPD: тут можно почитать, как добавить первую версию (официальный клиент) в автозапуск: http://blog.edtex.ru/2015/04/telegram-autostart-ubuntu-1404.html

На этом закончим. Если возникнут вопросы или проблемы, пишите в комментарии.