...

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

Бюджетная рассылка СМС

Приветствую всех хаброжителей!

Конечно, зализанная тема про рассылку смс сообщений, но как говориться: «много — не мало». Как-то так получилось, что именно она меня постоянно преследует: то одни, то другие добрые люди попросят принять участие (советом, например) в реализации бюджетной рассылки сообщений. И поэтому чтобы не пропадать накопленному добру, оставлю здесь, а вдруг кому-то пригодится…

Итак-с… Опускаем все варианты реализации на базе обычного компа и оси семейства NT. А перейдем сразу к «автономным» системам.

Чем может похвастаться arduino в этом направлении? Отвечу сразу, ОНО работает, но есть нюансы, о которых напишу ниже. Вообщем, имеем китайский вариант arduino 2560 (было перепробовано практически вся линейка) и два дополнительных модуля — сеть W5100 (наиболее стабильный вариант) и GSM SIM 900. Выглядит это все дело как-то так.

image

Задача была следующая:
— устройство должно уметь общаться по http
— отправлять сообщение
— выдавать результат в формате json

Гугл делится всей необходимой информацией, и на выходе получаем следующий код:

Скетч
#include <SPI.h>
#include <Ethernet.h>

#include <String.h>

#include "SIM900.h"
#include <SoftwareSerial.h>
#include "sms.h"

#include <LiquidCrystal_I2C.h>
#include <Wire.h>

byte mac[] = { 0x90, 0xA2, 0x00, 0x00, 0x00, 0x01 };    
IPAddress ip(192,168,34,139);                              

EthernetServer server(80);

char char_in = 0;    
String HTTP_req;    

SMSGSM sms;

boolean started=false;
bool power = false;

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void setup() {

  Serial.begin(9600);       
  
  lcd.begin(16,2);
  lcd.setCursor(0,0);
  lcd.print("INIT GSM...");
  lcd.setCursor(0,1);
  lcd.print("WAIT!!!");
  
  //powerUp();
  gsm.forceON();
  
  if (gsm.begin(4800)) {
    Serial.println("\nstatus=READY");
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("READY");    
    started=true;  
  }
  else {
    Serial.println("\nstatus=IDLE");
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("IDLE");
  }
  
  Ethernet.begin(mac, ip); 
  server.begin();         
  
}

void software_reset() {
  asm volatile ("  jmp 0");  
} 

void loop() {
  EthernetClient client = server.available();

  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char_in = client.read();  //
        HTTP_req += char_in;      

        if (char_in == '\n') {  
          
          Serial.println(HTTP_req);
          
          if(HTTP_req.indexOf("GET /res") >= 0) {
            reset_processing(&HTTP_req, &client);
            break;
          }

          if(HTTP_req.indexOf("GET /sms") >= 0) {
            sms_processing(&HTTP_req, &client);
            break;
          }    

          if(HTTP_req.indexOf("GET /test") >= 0) {
            test_processing(&HTTP_req, &client);
            break;
          }   
          else {
            client_header(&client);  
            break;
          }     
        }
      }
    }
    HTTP_req = "";    
    client.stop();
  } 
  
  if(power) {
    delay(1000);
    software_reset();
  }
}

char* string2char(String command) {
  if(command.length()!=0){
    char *p = const_cast<char*>(command.c_str());
    return p;
  }
}

void parse_data(String *data) {
  data->replace("GET /sms/","");
  data->replace("GET /test/", "");

  int lastPost = data->indexOf("\r");
  *data = data->substring(0, lastPost);
  data->replace(" HTTP/1.1", "");
  data->replace(" HTTP/1.0", "");
  data->trim();
}

// explode
 String request_value(String *data, char separator, int index) {
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = data->length()-1;

  for(int i=0; i<=maxIndex && found<=index; i++) {
    if(data->charAt(i)==separator || i==maxIndex) {
      found++;
      strIndex[0] = strIndex[1]+1;
      strIndex[1] = (i == maxIndex) ? i+1 : i;
    }
  }
  return found>index ? data->substring(strIndex[0], strIndex[1]) : "";
}

bool gsm_status() {
  bool result = false;
  switch(gsm.CheckRegistration()) {
    case 1:
      result = true;
      break;
    default:
      break;
  }
  return result;
}

bool gsm_send(char *number_str, char *message_str) {
  bool result = false;
  switch(sms.SendSMS(number_str, message_str)) {
    case 1:
      result = true;
      break;
    default:
      break;
  } 
  return result; 
}

void reset_processing(String *data, EthernetClient *cl) {
  client_header(cl);    
  cl->println("\{\"error\": 0, \"message\": \"restarting...\"\}");  

  power = true;   
}

void test_processing(String *data, EthernetClient *cl) {
  parse_data(data);
  
  if(started) {
    client_header(cl);
    cl->println("\{\"id\":" + request_value(data, '/',0) + ",\"error\":0" + ",\"message\":\"test success\"\}");   
  }
}

void sms_processing(String *data, EthernetClient *cl) {
  parse_data(data);

  if(started) {
    if (gsm_send(string2char(request_value(data, '/', 1)), string2char(request_value(data, '/', 2)))) {
      client_header(cl);
      cl->println("\{\"id\":" + request_value(data, '/',0) + ",\"error\":0" + ",\"message\":\"success\"\}");
    }
    else {
      if(!gsm_status()) {
        client_header(cl);
        cl->println("\{\"id\":" + request_value(data, '/',0) + ",\"error\":2" + ",\"message\":\"gsm not registered\"\}");   
        power = true;
      }
      else {
        client_header(cl);
        cl->println("\{\"id\":" + request_value(data, '/',0) + ",\"error\":1" + ",\"message\":\"fail\"\}");     
      }
    }
  }
}

void client_header(EthernetClient *cl) {
  cl->println("HTTP/1.1 200 OK");
  cl->println("Content-Type: text/plain");
  cl->println("Connection: close");  
  cl->println();
}


Заливаем, упаковываем в коробочку. Вроде бы выглядит красиво, отдаем добрым людям.

image

Что получилось в коде:
— подняли простенький http
— обрабатываем простые GET
— отправляем полученные данные через SERIAL на SIM 900
— отвечаем с помощью «JSON»

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

Наращиваем мощности… Задача полностью аналогичная: повторение — мать учения. Умные люди уже создали классный сервис для работы с предыдущим устройством: очередь, история и прочие полезности.

Итак, на руках имеем raspberry pi, такой же модуль SIM 900 (был взят только ради экспериментов, потому что линукс прекрасно работает с 3g-модемами через USB) и сам 3g-modem huawei e-линейки

image

Снова задаем гуглу нужные вопросы, читаем результаты, определяемся с языком реализации — python — быстро, просто, надежно…

скрипт
import serial, time
from flask import Flask
import RPi.GPIO as GPIO

app = Flask(__name__)


def sim900_on():
    gsm = serial.Serial('/dev/ttyAMA0', 115200, timeout=1)
    gsm.write('ATZ\r')
    time.sleep(0.05)

    abort_after = 5
    start = time.time()
    output = ""

    while True:
        output = output + gsm.readline()
        if 'OK' in output:
            gsm.close()
            return True
        delta = time.time() - start
        if delta >= abort_after:
            gsm.close()
            break


    #GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BOARD)                
    GPIO.setup(11, GPIO.OUT)                
    GPIO.output(11, True)                 
    time.sleep(1.2)
    GPIO.output(11, False) 
    return False


def gsm_send(id, port, phone, msg):

    delay = False
    if 'AMA' in port:
        delay = True


    msg = msg.replace('\\n', '\n')
    msg = msg.replace('\s', ' ')

    gsm = serial.Serial('/dev/tty%s' % port, 115200, timeout=1)
    gsm.write('ATZ\r')

    if delay:
        time.sleep(0.05)

    gsm.write('AT+CMGF=1\r\n')

    if delay:
        time.sleep(0.05)

    gsm.write('AT+CMGS="%s"\r\n' % phone)

    if delay:
        time.sleep(0.05)

    gsm.write(msg + '\r\n')

    if delay:
        time.sleep(0.05)

    gsm.write(chr(26))

    if delay:
        time.sleep(0.05)

    abort_after = 15
    start = time.time()
    output = ""

    while True:

        output = output + gsm.readline()
        #print output
        if '+CMGS:' in output:
            print output
            gsm.close()
            return '{"id":%s,"error":0,"message":"success", "raw":"%s"}' % (id, output)
        if 'ERROR' in output:
            print output
            gsm.close()
            return '{"id":%s,"error":0,"message":"fail", "raw":"%s"}' % (id, output)

        delta = time.time() - start
        if delta >= abort_after:
            gsm.close()
            return '{"id":%s,"error":1,"message":"timeout", "raw":"%s"}' % (id, output)

@app.route('/sms/<id>/<port>/<phone>/<msg>',methods=['GET'])
def get_data(id, port, phone, msg):
    return gsm_send(id, port, phone, msg)

@app.route('/',methods=['GET'])
def index():
    return "Hello World"

if __name__ == "__main__":
    sim900_on()
    app.run(host="0.0.0.0", port=8080, threaded=True)

Скармливаем питону, запускаем с помощью «start-stop-daemon», придаем дружелюбный вид, отдаем добрым людям…

image

Получилось практически один в один, только за счет шины USB систему можно расширять. В производстве претензий вообще не оказалось — все были ОЧЕНЬ довольны.

Устройство получилось настолько удачным, что появилось желание использовать это дело в «личных» интересах, а именно внедрить в систему мониторинга данный аппарат. Но надо было избавиться от главного нюанса — отсутствие очереди сообщений. Принцип реализации я взял у одного известного вендора (он предлагал программно-аппаратный комлекс, часть которого поднимала smtp-сервер для обработки уведомлений и отправки ее на gsm-устройство). Такая схема встраивается в любую систему мониторинга.

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

SMTP-демон
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

import smtpd
import asyncore
import email
import MySQLdb
import subprocess

def InsertNewMessage(phone, msg):
    conn = MySQLdb.connect(host="localhost", # your host, usually localhost
                     user="sms", # your username
                      passwd="sms", # your password
                      db="sms") # name of the data base
    c = conn.cursor()
    c.execute('insert into message_queue (phone, message) values ("%s", "%s")' % (phone, msg))
    conn.commit()
    conn.close()

class CustomSMTPServer(smtpd.SMTPServer):

    def process_message(self, peer, mailfrom, rcpttos, data):

        msg = email.message_from_string(data)
        phone = rcpttos[0].split('@',1)[0]
        addr = mailfrom

        for part in msg.walk():
            if part.get_content_type() == "text/plain": # ignore attachments/html
                body = part.get_payload(decode=True)


        InsertNewMessage(phone, str(body))

subprocess.Popen("/home/pi/daemons/sms/pygsmd.py", shell=True)

server = CustomSMTPServer(('0.0.0.0', 25), None)

asyncore.loop()

Демонизация происходит, как я уже писал выше, с помощью «start-stop-daemon», а сам smtp скрипт запускает подпроцесс для работы с базой сообщений.

gsm скрипт
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

import serial
import time
import MySQLdb
import commands


def gsm_send(port, phone, msg):

    print 'Sending message: %s to: %s' % (msg, phone)
    gsm = serial.Serial('/dev/tty%s' % port,
                        460800,
                        timeout=5,
                        xonxoff = False,
                        rtscts = False,
                        bytesize = serial.EIGHTBITS,
                        parity = serial.PARITY_NONE,
                        stopbits = serial.STOPBITS_ONE )
    gsm.write('ATZ\r\n')
    time.sleep(0.05)
    gsm.write('AT+CMGF=1\r\n')
    time.sleep(0.05)
    gsm.write('''AT+CMGS="''' + phone + '''"\r''')
    time.sleep(0.05)
    gsm.write(msg + '\r\n')
    time.sleep(0.05)
    gsm.write(chr(26))
    time.sleep(0.05)

    abort_after = 15
    start = time.time()
    output = ""


    while True:

        output = output + gsm.readline()
        #print output
        if '+CMGS:' in output:
            #print output
            gsm.close()
            return 0
        if 'ERROR' in output:
            #print output
            gsm.close()
            return 1

        delta = time.time() - start
        if delta >= abort_after:
            gsm.close()
            return 1

def msg_delete(list):
    conn = MySQLdb.connect(host="localhost",
                     user="sms",
                      passwd="sms",
                      db="sms")
    c = conn.cursor()
    c.execute("delete from  message_queue where id in %s;" % list)
    conn.commit()
    conn.close()

def msg_hadle():
    list = tuple()
    conn = MySQLdb.connect(host="localhost",
                     user="sms",
                      passwd="sms",
                      db="sms")
    c = conn.cursor()
    c.execute("select * from message_queue")

    numrows = int(c.rowcount)
    if numrows > 0:
        for row in c.fetchall():
            result = gsm_send('USB0', ('+' +  row[1]),  row[2])
            if result == 0:
                list +=(str(row[0]),)

    conn.close()

    if len(list) == 1:
        qlist = str(list).replace(',','')

    if len(list) > 1:
        qlist = str(list)

    if len(list) > 0:
        msg_delete(qlist)

    del list

while True:
    try:
        msg_hadle()
    except:
        print "mysql error"

    time.sleep(10)

В связке с моей системой мониторинга устройство ведет себя адекватно, хотя работает не так давно. Надеюсь, материал будет полезен кому-нибудь.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Заставляем сервис php-fpm 5.6, запущенный через systemd, читать глобальные переменные окружения

Это короткий how-to для реализации конфигурации php-сервиса, зависимого от окружения, в котором он запущен. Я буду рад, если кто-то подскажет более изящное решение или поправит в мелочах.

Основная идея


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

В этой статье слишком много раз повторяется «переменные окружения».
Из коробки php-fpm игнорирует глобальные переменные окружения (getenv function), в то время как php cli их может получать.
Предыстория

Этот раздел можно пропустить, если вы уже работали с .env


В данный момент я работаю над проектом, написанном на ZF2. Для конфигурации проекта использовались конфиг-файлы для разных окружений. Это порождает большое количество дубликатов конфигурации в репозитории проекта примерно такого вида:
  • session.global.php
  • session.local.php.dist
  • session.unittest.php.dist
  • db.global.php
  • db.local.php.dist
  • db.unittest.php.dist
  • ...

Эти дубликаты приходится постоянно синхронизировать друг с другом. Кроме того, они хранят определённую php-логику внутри себя, что порождает дублирование кода.

Я добавил к проекту библиотеку, которая умеет считывать окружение из .env файла и загружать его в $_ENV (упрощённо).

Подключение библиотеки vlucas/phpdotenv к ZF2
composer require vlucas/phpdotenv
Открыть public/index.php
После require 'init_autoloader.php' добавить:
$dotenv = new Dotenv\Dotenv(__DIR__ . '/../');
// $dotenv->required('SOME_IMPORTANT'); // можно сделать некоторые переменные обязательными
$dotenv->load(); // можно использовать overload(), тогда файл .env станет более важен, чем глобальные переменные окружения (каскадный принцип)



Кроме того (это совершенно необязательно), добавил helper-функцию env() из laravel, которая является обёрткой над php getenv().
Добавляем метод env($key, $default = null) в ZF2
Создать файл, например library/Common/Config/env.php, с содержимым:
if ( ! function_exists('value'))
{
    /**
     * Return the default value of the given value.
     *
     * @param  mixed  $value
     * @return mixed
     */
    function value($value)
    {
        return $value instanceof Closure ? $value() : $value;
    }
}
if ( ! function_exists('env'))
{
    /**
     * Gets the value of an environment variable. Supports boolean, empty and null.
     *
     * @param  string  $key
     * @param  mixed   $default
     * @return mixed
     */
    function env($key, $default = null)
    {
        $value = getenv($key);

        if ($value === false) return value($default);

        switch (strtolower($value))
        {
            case 'true':
            case '(true)':
                return true;

            case 'false':
            case '(false)':
                return false;

            case 'empty':
            case '(empty)':
                return '';

            case 'null':
            case '(null)':
                return;
        }

        return $value;
    }
}

В composer.json добавить в секцию «autoload»:

"autoload" : {
        ...,
        "files": ["library/Common/Config/env.php"]
    },


Затем выполнить composer dumpautoload.

Этот шаг позволил выбросить из репозитория все лишние дубликаты конфиг-файлов (local.php, unittest.php, *.php.dist). Вместо этого в корне проекта появился .env.global со списком всех доступных переменных, которые задействованы в конфигах.
Как теперь настраивать конфигурацию?
Не окружение знает о переменных сервиса, а сервис учитывает окружение, в котором он запущен.
1. Т.к. на рабочей машине в переменных окружения может ничего и нет, да и обмениваться проектом неудобно между разными машинами, библиотека phpdotenv перед запуском приложения считывает .env файл и загоняет его переменные в $_ENV[$name] = $value.
2. Конфигурационные файлы вызывают метод env(), который является обёрткой над php-функцией getenv(), и читает переменные окружения, подставляя значение по-умолчанию по необходимости.
// Примеры использования:
$config['emails']['from'] = env('APP_EMAIL', 'info@myemail.com');
$config['is_production'] = ( 'production' == env('APP_ENV') );
if (env('ZF_DEBUG_TOOLS', false)) {
    $config['modules'][] = 'ZendDeveloperTools';
}


3. Файл .env не обязательно заполнять. Можно использовать глобальные переменные окружения или значения по-умолчанию в конфигурации. При отсутствии .env файла бросается exception (особенность библиотеки, не самая правильная), на production сервере её можно вообще не подключать. Для избежания exception, файл необходимо просто создать в корне проекта (touch .env).
4. Файл .env не обязательно должен хранить все доступные переменные проекта. Если в конфигах устанавливаются значения по-умолчанию, достаточно записывать в .env только переменные, отличающиеся в данном окружении.
5. Файл .env не нужно коммитить в репозиторий. Его следует добавить в ignore для системы контроля версий.
6. Чтобы сделать переменную окружения обязательной, в index.php необходимо добавить такую конструкцию:
$dotenv->required('APP_ENV'); // Переменная APP_ENV после этого должна в обязательном порядке быть установленной через .env или через окружение

7. В репозитории проекта можно коммитить файлы вида .env.* (.env.phpunit, .env.develop). Это не что иное, как закладки с набором переменных для разного окружения. Оркестратор или CI-система просто копирует шаблон (или переменные из него) при разворачивании проекта там, где проект разворачивается в нескольких копиях в рамках одной системы или нет возможности оперировать глобальными переменными окружения. Закладки удобно сравнивать друг с другом. Эти закладки никак не участвуют в логике сервиса.

Важно: .env.production не должен храниться в репозитории проекта.
Удобно создать .env.default – файл, который содержит все переменные окружения, поддерживаемые в проекте на текущий момент (максимально-возможный template для .env).

Так что, теперь все конфиги нужно дублировать в .env? Когда добавлять новую переменную окружения?

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

$config['csv_separator] = ' | '; // это не меняется в разном окружении, не стоит это трогать.
$config['like_panel'] = true; // а это меняется, можно заменить на env('APP_LIKE_PANEL', false)
$config['facebook_app_id'] = 88888888881; // рекомендуется использовать переменные среды

А как быть с паролями и чувствительными данными?

Храните production-данные в отдельном репозитории, в хранилище паролей или, например, в защищенном хранилище оркестратора.

Итак, проект теперь учитывает окружение, но...


Пока разработка велась на рабочих машинках, проект читал .env файл и всё работало. Но когда я развернул тестовую среду, оказалось, что если задать взаправдашние системные переменные окружения, php-fpm их игнорирует. Различные рецепты из гугла и StackOverflow сводились к той или иной автоматизации использования двух известных способов:

1. Передача переменных через nginx параметром fastcgi_param SOMEENV test;
2. Установкой переменных в формате env[SOME_VAR] в конфигурации пула рабочих процессов php-fpm.

И первый, и второй вариант, удобны для каких-то особых ситуаций. Но если мыслить в парадигме «конфигурировать среду, а не приложение», то подобные способы оказываются куда труднее, чем например просто положить .env файл в папку с проектом. Но ведь оркестратор, CI-система или просто системный администратор не должен знать детали реализации проекта, это не изящно.

Предлагаемый способ решения

Скомбинировав различные рецепты из сети, я нащупал следующее рабочее решение.
Тестировалось под Centos 7, PHP 5.6.14.
1. Открыть /etc/php.ini

- Заменить
variables_order = "GPCS" 
на 
variables_order = "EGPCS"
# После этого PHP добавит в глобальное пространство переменные окружения
# http://ift.tt/1NxPg5U

2. Открыть /etc/php-fpm.d/www.conf, не путать с /etc/php-fpm.conf (в разных системах может быть в разном месте, это конфиг www-пула процессов для php-fpm.

- Добавить (или заменить, если вдруг есть):
clear_env = no # выключить очистку глобальных переменных для запускаемых воркеров

3. Установить необходимые переменные окружения в /etc/environment (стандартный синтаксис A=B)

4. ln -fs /etc/environment /etc/sysconfig/php-fpm # теперь конфиг переменных окружения сервиса php-fpm будет просто ссылкой на глобальный конфиг

5. systemctl daemon-reload && service php-fpm restart

Этот же подход с симлинком, в теории, применим и к другим сервисам.

Плюсы предложенного решения:
— Переменные, хранящиеся в /etc/environment, доступны разным приложениям. Можно вызвать echo $MYSQL_HOST в shell или getenv('MYSQL_HOST') в php.
— Переменные окружения, которые явно не заданы в /etc/environment, не попадут в php-fpm. Это позволяет с помощью оркестратора контролировать окружение извне изолированной системы, в которой запущен сервис.

Минусы:
— К сожалению, у php-fpm я не нашел работающей команды для reload по аналогии с nginx, так что в случае изменения /etc/environment, обязательно нужно делать systemctl daemon-reload && service php-fpm restart.

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

Ссылки:
Для тех, кто не читал статью
— Методология двенадцати факторов разработки SAAS: храните конфигурацию в окружении (англ.)
— Загрузка переменных окружения с помощью .env-файлов для development environment в php-проектах.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Система наблюдения в автомобиле за ним же на Raspberry Pi. Часть 2

В прошлой статье я описал:
создание на одном Raspberry Pi домашнего VPN-сервера;
установку и настройку на втором Raspberry Pi OpenVPN-клиента, Node.JS и 3G-модема.
В этот раз настроим и подключим GPS-приёмник и WEB-камеру (оба устройства – USB).

Подключение и настройка GPS-приёмника


Для этой цели я приобрёл

Проверим, определилось ли устройство:

Наше устройство — Prolific Technology.
Установим пакеты для нашего устройства, чтобы получать координаты и перезагрузимся:

sudo apt-get install gpsd gpsd-clients python-gps -y
sudo reboot


Теперь посмотрим и увидим, что у нас загружается сервис gpsd, но без указания устройства ввода (этот вариант не работает):

Отключим этот демон:
sudo dpkg-reconfigure gpsd


На первый вопрос отвечаем «No»:

На второй тоже «No»:

Можно поправить вручную файл /etc/default/gpsd, но там написано что лучше использовать реконфигуратор пакета, что я и сделал.
Запустим демон для работы с приёмником:
sudo gpsd /dev/ttyUSB0 -F /var/run/gpsd.sock


Теперь попробуем получить координаты:

Бывает что в помещении долго не может поймать координаты или при первом запуске — для ознакомления можно почитать про холодный и горячий старт.

Отлично! Теперь добавим верный запуск демона в автозагрузку:

crontab -e


Подключение и настройка WEB-камеры


Из-за скорости канала я предпочёл передачу изображения, а не видео.
После поиска и тестирования разных пакетов я остановился на fswebcam, с помощью которого я получил изображение за пару секунд.
Установим и сразу попробуем получить снимок:
sudo apt-get install fswebcam -y
fswebcam —save /home/pi/test.png



Заберём теперь и проверим:
scp pi@192.168.2.6:/home/pi/test.png ./


Где 192.168.2.6 — IP адрес, полученный от OpenVPN сервера.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Из песочницы] Как я победил в конкурсе BigData от Beeline

image

Все уже много раз слышали про конкурс по машинному обучению от Билайн и даже читали статьи (раз, два). Теперь конкурс закончился, и так вышло, что первое место досталось мне. И хотя от предыдущих участников меня и отделяли всего сотые доли процента, я все же хотел бы рассказать, что же такого особенного сделал. На самом деле — ничего невероятного.

Подготовка данных


Говорят, что 80% времени аналитики данных проводят за подготовкой данных и предварительными модификациями и только 20% за непосредственно анализом. И оно так не спроста, так как «garbage in — garbage out». Процесс подготовки исходных данных можно разбить на несколько этапов, по которым я предлагаю пройтись.
Исправление выбросов

После внимательного изучения гистограмм становится ясно, что в данные закралось довольно много выбросов (outliers). Например, если вы видите, что 99.9% наблюдений переменной Х сконцентрированы на отрезке [0;1], а 0.01% наблюдений выбрасывает за сотню, то довольно логично сделать две вещи: во-первых ввести новую колонку для индикации странных событий, а во-вторых заменить выбросы чем-то разумным.
data["x8_strange"] = (data["x8"] < -3.0)*1
data.loc[data["x8"] < -3.0 , "x8"] = -3.0
data["x31_strange"] = (data["x31"] < 0.0)*1.0
data.loc[data["x31"] < 0.0, "x31"] = 0.0
data["x40_zero"] = (data["x40"] == 0.0)*1.0


Нормализация распределений

Вообще, работать с нормальными распределениями крайне приятно, так как многие статистические тесты завязаны на гипотезу о нормальности. Современных методов машинного обучения это касается в меньшей степени, но все равно привести данные к разумному виду важно. Особенно важно для методов, которые работают с расстояниями между точками (почти все алгоритмы кластеризации, классификатор k-соседей). В этой части подготовки данных я обошелся стандартным подходом: логарифмирую все, что распределено плотнее около нуля. Таким образом для каждой переменной подобрал трансформацию, которая давала более приятный вид. Ну а после этого шкалировал все в отрезок [0;1]
Текстовые переменные

Вообще, текстовые переменные — кладезь для data mining’а, но в исходных данных были только хэши, да и названия переменных были анонимизированы. Поэтому только стандартная рутина: заменить все редкие хэши на слово Rare (редкие = встречаются реже 0.5%), заменить все пропущенные данные на слово Missing и развернуть как бинарную переменную (так как многие методы, в том числе xgboost, не умеют в категориальные переменные).
data = pd.get_dummies(data, columns=["x2", "x3", "x4", "x11", "x15"])
for col in data.columns[data.dtypes == "object"]:
    data.loc[data[col].isnull(), col] = 'Missing'
thr = 0.005
for col in data.columns[data.dtypes == "object"]:
    d = dict(data[col].value_counts(dropna=False)/len(data))
    data[col] = data[col].apply(lambda x: 'Rare' if d[x] <= thr else x)
d = dict(data['x0'].value_counts(dropna=False)/len(data))
data = pd.get_dummies(data, columns=data.columns[data.dtypes == "object"])


Feature engineering

Это то, за что мы все любим data science. Но все зашифровано, поэтому этот пункт придется опустить. Почти. После пристального изучения графиков, я заметил, что x55+x56+x57+x58+x59+x60 = 1, а значит это какие-то доли. Скажем, какой процент денег абонент тратит на СМС, звонки, интернет, etc. А значит особый интерес представляют те товарищи, у которых какая-либо из долей более 90% или менее 5%. Таким образом получаем 12 новых переменных.
thr_top = 0.9
thr_bottom = 0.05
for col in ["x55", "x56", "x57", "x58", "x59", "x60"]:
    data["mostly_"+col] = (data[col] >= thr_top)*1
    data["no_"+col] = (data[col] <= thr_bottom)*1


Убираем NA

Тут все предельно просто: после того, как все распределения приведены к разумному виду, можно смело заменять NA-шки средним или медианой (теперь они почти совпадают). Я пробовал вообще выкинуть из обучающей выборки те строки, где более 60% переменных имеют значение NA, но добром это не кончилось.
Регрессия в качестве регрессора

Следующий шаг уже не такой банальный. Из распределения классов я предположил, что возрастные группы упорядочены, то есть 0
from sklearn.linear_model import SGDRegressor
sgd = SGDRegressor(loss='huber', n_iter=100)
sgd.fit(train, target)
test  = np.hstack((test, sgd.predict(test)[None].T))
train = np.hstack((train, sgd.predict(train)[None].T))

Кластеризация

Вторая интересная мысль, которую я пробовал: кластеризация данных методом k-средних. Если в данных есть реальная структура (а в данных по абонентам она должна быть), то k-средних её почувствует. Сначала я взял k=7, потом добавил 3 и 15 (в два раза больше и в два раза меньше). Предсказания каждого из этих алгоритмов — номера кластеров для каждого образца. Так как эти номера не упорядочены, то оставить их числами нельзя, надо обязательно бинаризовать. Итого + 25 новых переменных.
from sklearn.cluster import KMeans
k15 = KMeans(n_clusters=15, precompute_distances = True, n_jobs=-1)
k15.fit(train)
k7 = KMeans(n_clusters=7, precompute_distances = True, n_jobs=-1)
k7.fit(train)
k3 = KMeans(n_clusters=3, precompute_distances = True, n_jobs=-1)
k3.fit(train)
test  = np.hstack((test,  k15.predict(test)[None].T,  k7.predict(test)[None].T,  k3.predict(test)[None].T))
train = np.hstack((train, k15.predict(train)[None].T, k7.predict(train)[None].T, k3.predict(train)[None].T))

Обучение


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

На самом деле, помимо xgboost я пробовал метод k-соседей. Несмотря на то, что в пространствах высокой размерности он считается неэффективным, мне удалось добиться 75% (маленький шаг для человека и большой шаг для k-neighbors), считая расстояние не в обычном евклидовом пространстве (где переменные равноценны), а делая поправку на важность переменных, как показано в презентации

Однако все это игрушки, и действительно хорошие результаты дала не нейросеть, не логистическая регрессия и не k-соседи, а то, что и ожидалось – xgboost. Позже, придя на защиту в Билайн, я узнал, что они также добились лучших результатов с помощью этой библиотеки. Для задач классификации она уже является чем-то типа «золотого стандарта».

«When in doubt – use xgboost»
Owen Zhang, top-2 on Kaggle.


Прежде чем начинать собственно бустить по-настоящему и получать отличные результаты, я решил посмотреть, насколько действительно важны все те колонки, которые были даны, и те, которые насоздавал я, развернув хэши и проведя кластеризацию методом К-средних. Для этого я провел легкий бустинг (не очень много деревьев), и построил столбцы, отсортированные по важности (по мнению xgboost).
gbm = xgb.XGBClassifier(silent=False, nthread=4, max_depth=10, n_estimators=800, subsample=0.5, learning_rate=0.03, seed=1337)
gbm.fit(train, target)
bst = gbm.booster()
imps = bst.get_fscore()


image

Мое мнение состоит в том, что те столбцы, важность которых оценена как “ничтожная” (а построена диаграмма только для самых важных 70 переменных из 335), содержат в себе больше шума, чем собственно полезных корреляций, и учиться по ним – себе вредить (i.e. переобучаться).

Интересно также заметить, что первая по важности переменная – x8, а вторая – результаты SGD-регрессии, добавленной мной. Те, кто пробовал участвовать в этом конкурсе, наверняка ломали голову, что за переменная x8, если она так хорошо разделяет классы. На защите в Билайне я не смог удержаться и не спросить, что же это было. Это был ВОЗРАСТ! Как мне объяснили, возраст, указанный в при покупке тарифа, и возраст, полученный в опросах, не всегда совпадают, так что да, участники действительно определяли возрастную группу в том числе и по возрасту.

image

Путем непродолжительных экспериментов я понял, что оставить 120 столбцов лучше, чем оставить 70 или оставить 170 (в первом случае, по всей видимости, все же выкидывается что-то полезное, во втором данные загрязнены чем-то бесполезным).
Теперь надо было бустить. Два параметра xgboost.XGBClassifier, заслуживающие наибольшего внимания – это eta (он же learning rate) и n_estimators (число деревьев). Остальные параметры не особо меняли результаты (поэтому я задал max_depth=8, subsample=0.5, а остальные параметры по умолчанию).

Между оптимальными значениями eta и n_estimators существует естественная зависимость – чем ниже eta (скорость обучения), тем больше деревьев необходимо, чтобы достичь максимальной точности. И мы действительно сталкиваемся здесь с оптимумом, после которого добавление дополнительных деревьев вызывает лишь переобучение, ухудшая точность на тестовой выборке. Например, для eta = 0.02 оптимальным будет примерно 800 деревьев:

image

Сначала я попробовал работать с средними eta (0.01-0.03) и увидел, что в зависимости от случайного состояния (seed) есть заметный разброс (например для 0.02 счет варьируется от 76.7 до 77.1), а также заметил, что этот разброс становится меньше при уменьшении eta. Стало понятно, что большие eta не подходят в принципе (как может быть хорошей модель, настолько зависимая от seed?).

Тогда я выбрал то eta, которое могу позволить себе на моем компьютере (не хотелось бы считать сутками). Это eta=0.006. Дальше необходимо было найти оптимальное число деревьев. Тем же способом, что показан выше, я установил, что для eta=0.006 подойдет 3400 деревьев. На всякий случай я попробовал два разных seed (важно было понять, велики ли флуктуации).

for seed in [202, 203]:  
    gbm = xgb.XGBClassifier(silent=False, nthread=10, max_depth=8, n_estimators=3400, subsample=0.5, learning_rate=0.006, seed=seed)
    gbm.fit(trainclean, target)
    p = gbm.predict(testclean) 
    filename = ("subs/sol3400x{1}x0006.csv".format(seed))
    pd.DataFrame({'ID' : test_id, 'y': p}).to_csv(filename, index=False)


Каждый ансамбль на обычном core i7 считался по полтора часа. Вполне приемлемое время, когда конкурс проходит полтора месяца. Флуктуации на public leaderboard были невелики (для seed=202 получил 77.23%, для seed=203 77.17%). Отправил лучший из них, хотя весьма вероятно, что на private leaderboard другой был бы не хуже. Впрочем, мы уже не узнаем.

Теперь немного о самом конкурсе. Первое, что бросается в глаза тому, кто знаком с Kaggle – немного необычные правила сабмита. На Kaggle число самбишнов ограничено (в зависимости от конкурса, но как правило, не более 5 в день), здесь же сабмишны безлимитные, что позволило некоторым участникам отправлять результаты аж 600 раз. Кроме того, финальный сабмишн надо было выбрать один, а на Kaggle обычно разрешается выбрать любые два, и счет на private leaderboard считается по лучшему из них.

Еще одна необычная вещь – анонимизированные столбцы. С одной стороны, это практически лишает какой-либо возможности для feature design. С другой стороны, отчасти это и понятно: столбцы с реальными названиями дали бы мощное преимущество людям, разбирающимся в мобильной связи, а цель конкурса была явно не в этом.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Перевод] Обзор ES6 в 350 пунктах. Часть первая

Моя серия заметок ES6 in Depth, состоящая из 24 записей, описывает большинство синтаксических изменений и нововведений в ES6. В этой публикации я подведу итог всего изложенного в предыдущих статьях, чтобы дать возможность посмотреть еще раз на всё вместе. Также я добавил ссылки на мой блог, чтобы в случае необходимости сразу же можно было посмотреть подробнее.

Я слышал, вы любите маркированные списки, так что вот вам статья со списком, который состоит из нескольких сотен элементов.
Для начала приведу оглавление, чтобы было понятно, о чем пойдет речь. Очевидно, что оглавление – это также ненумерованный список. Обратите внимание: если хотите глубже вникнуть в эту тему, прочтите весь цикл статей и самостоятельно «поковыряйте» код ES6.

Содержание

● Введение
● Инструментарий
● Assignment Destructing
● Spread Operator и Rest Parameters
● Стрелочные функции
● Шаблонные строки
● Литералы объектов
● Классы
● Let и Const
● Символы
● Итераторы
● Генераторы
● Промайсы
● Maps
● WeakMaps
● Sets
● WeakSets
● Прокси
● Reflection
● Number
● Math
● Array
● Object
● Строки и Unicode
● Модули

Извините за такое длинное оглавление. Начинаем.

Введение

● ES6 (также известен как Harmony, es-next, ES2015) – последняя и окончательная спецификация языка.
● Окончательная спецификация ES6 была утверждена в июне 2015 года (следовательно, ES2015).
● Будущие версии языка будут называться согласно шаблону ES[YYYY], например ES2016 для ES7.
o Ежегодный релиз-цикл, необратимые изменения начинают действовать со следующего релиза.
o Так как ES6 появился раньше, чем это решение, большинство из нас всё еще называет его ES6.
o Начиная с ES2016 (ES7), мы должны использовать шаблон ES[YYYY], чтобы ссылаться на новые версии.
o Главная причина использования такого шаблона именования – оказание давления на производителей браузеров с целью как можно более быстрого внедрения последних обновлений.

Инструментарий

● Чтобы использовать ES6 сегодня, вам потребуется JavaScript-to-JavaScript транспайлер.
● Транспайлеры пришли и останутся, потому что:
o они предоставляют возможность компилировать код новой версии языка в код старой версии;
o мы будем транспайлить ES2016 и ES2017 в ES6 и т. д., когда браузерная поддержка станет лучше;
o нам потребуется улучшенный функционал source mapping;
o на сегодняшний день это самый надежный способ запускать ES6 код в продакшн (несмотря на то, что браузеры поддерживают ES5).
● У babel (транспайлера) есть киллер-фича: человекочитаемый вывод (human-readable output).
● Используйте babel, чтобы транспайлить ES6 в ES5 для статических сборок.
● Используйте babelify, чтобы внедрить babel в свой grunt, gulp или npm run процесс сборки.
● Используйте nodejs весии 4.x.x или выше – там есть весьма приличная поддержка ES6 (спасибо v8).
● Используйте babel-node с любой версией node.js – он будет транспайлить модули в ES5.
● В babel есть разрастающаяся экосистема, которая уже поддерживает ES2016 и некоторые плагины.
● Прочитайте A Brief History of ES6 Tooling.

Assignment Destructuring

● var {foo} = pony то же, что var foo = pony.foo.
● var {foo: baz} = то же, что var baz = pony.foo.
● Можно задавать значения по умолчанию, var {foo='bar'} = baz вернет foo: 'bar', если baz.foo является undefined.
● Можно затащить сколько угодно свойств, под псевдонимами или без них:
o var {foo, bar: baz} = {foo: 0, bar: 1} даст foo: 0 и baz: 1.
● Можно пойти дальше: var {foo: {bar}} = { foo: { bar: 'baz' } } даст bar: 'baz'.
● Этому тоже можно назначить псевдоним: var {foo: {bar: deep}} = { foo: { bar: 'baz' } } даст вам deep: 'baz'.
● Свойства, которые не были найдены, по-прежнему возвращают undefined var {foo} = {}.
● Вложенные свойства, которые не были найдены, возвращают ошибку var {foo: {bar}} = {}.
● Это также работает для массивов, [a, b] = [0, 1] вернет a: 0 и b: 1.
● Можно пропускать элементы в массиве, [a,, b] = [0, 1, 2], получаем a: 0 и b: 2.
● Переменные можно менять местами, не прибегая к помощи третьей “aux” переменной, [a, b] = [b, a].
● Можно также использовать destructuring в параметрах функций:
o присваивание значений по умолчанию function foo (bar=2) {};
o эти значения могут также быть и объектами function foo (bar={ a: 1, b: 2 }) {};
o можно деструктурировать bar полностью: function foo ({ a=1, b=2 }) {};
o если ничего не было передано, по умолчанию получаем пустой массив: function foo ({ a=1, b=2 } = {}) {}.

Прочитайте ES6 JavaScript Destructuring in Depth.

Spread Operator и Rest Parameters

● Rest parameters – это как arguments, только лучше.
o сигнатура метода объявляется как function foo (...everything) {};
o everything – это массив со всеми параметрами, переданными в foo;
o можно присвоить имя нескольким параметрам перед ...everything, например: function foo (bar, ...rest) {};
o эти параметры будут исключены из ...rest;
o ...rest должен быть последним параметром в списке.
● Spread operator – это даже лучше, чем магия, он также обозначается при помощи … синтаксиса:
o отменяет необходимость apply при вызове методов, fn(...[1, 2, 3]) – то же, что fn(1, 2, 3);
o упрощенная конкатенация: [1, 2, ...[3, 4, 5], 6, 7];
o позволяет слепить массивоподобные элементы или коллекции в массивы [...document.querySelectorAll('img')];
o также полезен при destructing [a,, ...rest] = [1, 2, 3, 4, 5] возвращает a: 1 и rest: [3, 4, 5];
o делает new + .apply простым, new Date(...[2015, 31, 8]).
● Прочитайте ES6 Spread and Butter in Depth.

Стрелочные функции

● Лаконичный способ объявить функцию param => returnValue.
● Полезно при функциональном программировании, [1, 2].map(x => x * 2).
● Есть несколько разных способов использования (займет некоторое время, чтобы выработать привычку):
○ p1 => expr отлично подходит, если параметр один;
○ p1 => expr имеет неявный оператор return для выражения expr;
○ чтобы неявно вернуть объект, нужно обернуть его в круглые скобки () => ({ foo: 'bar' }), иначе получите ошибку;
○ круглые скобки необходимы, когда у вас 0, 2 или больше параметров () => expr or (p1, p2) => expr;
○ фигурные скобки в правой части представляют блок кода, в котором может содержаться несколько инструкций () => {};
○ при использовании такого синтаксиса неявного return нет, его нужно писать () => { return 'foo' }.
● Нельзя статически давать стрелочной функции имя, но тесты производительности намного лучше для большинства методов без имени.
● Стрелочные функции привязаны к лексическому окружению:
○ this это тот же контекст this, что и в родительском лексическом окружении;
○ this нельзя изменить при помощи .call, .apply, или похожих методов “reflection”-типа.
● Прочитайте ES6 Arrow Functions in Depth.

Шаблонные строки

● Строки можно объявлять при помощи обратных кавычек (`) в дополнение к одинарным и двойным.
● Строки с обратными кавычками являются шаблонными строками.
● Шаблонные строки могут быть многострочными.
● Шаблонные строки позволяют производить промежуточные вычисления `ponyfoo.com is ${rating}`, где rating – это переменная.
● Можно использовать любое валидное js-выражение для вычисления, например: `${2 * 3}` или `${foo()}`.
● Можно использовать помеченные шаблоны, чтобы изменить логику вычисления промежуточных значений:
○ добавить префикс fn к fn`foo, ${bar} and ${baz}`;
○ fn вызывается единожды с template, ...expressions;
○ template – это ['foo, ', ' and ', ''], а expressions – это [bar, baz];
○ результат fn становится значением шаблонной строки;
○ возможные примеры использования: очистка вводимых выражений от лишних данных, парсинг параметров и т. д.
● Шаблонные строки практически во всем лучше строк, обернутых в одинарные или двойные кавычки.
● Прочитайте ES6 Template Literals in Depth.

Литералы объектов

● Вместо { foo: foo }, можно писать просто { foo } – сокращенная форма записи пары свойство–значение.
● Вычисляемые имена свойств: { [prefix + 'Foo']: 'bar' }, где prefix: 'moz' возвращает { mozFoo: 'bar' }.
● Нельзя одновременно пытаться использовать две предыдущих особенности, запись {[foo]} – невалидна.
● Определения методов можно сделать более лаконичными при помощи следующего синтаксиса: { foo () {} }.
● Смотрите также секцию Object.
● Прочитайте ES6 Object Literal Features in Depth.

Классы

● Не «традиционные» классы, а синтаксический сахар поверх механизма наследования прототипов.
● Синтаксис похож на объявление объектов class Foo {}.
● Методы экземпляра new Foo().bar объявляются при помощи упрощенного синтаксиса литералов объекта class Foo { bar () {} }.
● Статические методы – Foo.isPonyFoo() – нуждаются в префиксе, ключевом слове static class Foo { staticisPonyFoo () {} }.
● Метод конструктора class Foo { constructor () { /* initialize instance */ }.
● Прототипное наследование с упрощенным синтаксисом class PonyFoo extends Foo {}.
● Прочитайте ES6 Classes in Depth.

Let и Const

● let и const – это альтернативы var при объявлении переменных.
● let имеет блочную область видимости, а не лексическую, по отношению к родительской функции.
● let поднимается в верхнюю часть блока, в то время как var поднимается в верхнюю часть функции.
● «Временная мертвая зона» или просто ВМЗ:
○ начинается в начале блока, в котором происходит объявление let foo;
○ заканчивается в том месте кода, где происходит объявление let foo (здесь «подъем» не имеет значения);
○ попытки доступа или определения foo внутри ВМЗ (до того, как будет выполнена инструкция let foo) приведут к ошибке;
○ помогает сократить число загадочных багов, в которых значение переменной претерпевает изменения раньше, чем происходит ее объявление.
● const также имеет блочную область видимости, «подъем» и следует семантике ВМЗ.
● Переменные const должны быть объявлены при помощи инициализатора const foo = 'bar'.
● Определение const после инициализации побуждает тихую ошибку (или не тихую – в строгом режиме).
● Переменные const не делают переменную неизменяемой:
○ const foo = { bar: 'baz' } значит, что foo всегда будет ссылаться на объект в правой части выражения;
○ const foo = { bar: 'baz' }; foo.bar = 'boo' не бросит исключение.
● Определение переменной с таким же именем – бросит.
● Предназначен для того, чтобы исправлять ошибки, когда вы переопределяете переменную и теряете ссылку на первоначальный объект.
● В ES6 функции имеют блочную область видимости:
○ предотвращают утечку данных через механизм «всплытия» { let _foo = 'secret', bar = () => _foo; };
○ не ломают пользовательский код в большинстве ситуаций и, как правило, являются тем, что вам нужно.
● Прочитайте ES6 Let, Const and the “Temporal Dead Zone” (TDZ) in Depth.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 6 ноября 2015 г.

[Перевод] Профессия Data Scientist: общие сведения и основные сложности, с которыми сталкиваются «ученые по данным»

Примечание переводчика:В нашем блоге мы рассказываем об облачных сервисах, хостинге и соответствующих технологиях. Почерпнуть что-то интересное можно не только, анализируя инфраструктурные проекты компаний, но и изучая работу современных ИТ-специалистов. Сегодня мы представляем вашему вниманию адаптированный перевод статьи доцента компьютерных наук университета Рочестера Филиппа Гуо (Philipp Guo) о том, кто такие «ученые по данным», как они работают и с какими сложностями сталкиваются.

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

В 2012 году, вскоре после окончания написания моей диссертации, понятие «Наука о данных» [англ. data science] стало постепенно распространяться. Некоторые специалисты в этой области называют профессию «ученого по данным» [англ. data scientists] «одной из наиболее привлекательных в 21 веке». Помимо прочего, руководство университетов вкладывает немалые средства в создание институтов обработки данных [англ. data science institutes].

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

Чем же занимаются ученые по данным, и с какими трудностями им приходится сталкиваться?
В этой статье рассмотрены основные этапы исследования в науке о данных, взятые из Главы 2 моей диссертации на соискание степени Ph.D. под названием «Программные средства для удобства проведения программных исследований».

Основные этапы исследования в науке о данных


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

Стадия подготовки


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

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

  • Файлы с данными можно скачивать из онлайн-хранилищ, таких как публичные сайты (например, данные переписи населения США).
  • Можно запрашивать потоки данных с онлайн-источников через API (например, поток финансовых данных Bloomberg).
  • Данные можно генерировать с помощью специальной аппаратуры, например, оборудования для научных исследований, подключенного к компьютерам.
  • Данные можно генерировать с помощью специального программного обеспечения, например, лог-файлы, полученные с веб-сервера, или данные, отсортированные при помощи алгоритма машинного обучения.
  • Данные можно записать вручную в таблицу или текстовый файл.

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

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

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

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

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

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

Реформатирование и очистка данных


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

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

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

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

Кроме того, на данном этапе возникает проблема интеграции данных. Например, опытный разработчик Кристиан Берд, у которого я брал интервью в Microsoft Research, собирает исходные данные из различных файлов формата .csv и XML, запросов к системе управления версиями и базы данных ошибок [англ. bug databases], а также находит ценную информацию в архиве электронных писем. Он объединяет все данные, полученные из этих источников, в единую реляционную базу данных MySQL, которая играет роль основного источника данных в его исследованиях.

В дополнение, предлагаю ознакомиться со следующим отрывком из введения к книге «Написание скриптов на Python для проведения вычислительных экспериментов», в котором отражена значимость рутинных операций в ходе подготовки данных:

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

Очень часто бывает, что полученный код должен решать задачи постоянного перемещения данных из одного места в другое, конвертирования из одного формата в другой, извлечения численных данных из текста и проведения численных экспериментов с использованием большого числа файлов и хранилищ. Эти задачи гораздо быстрее выполнить, если использовать Python вместо Fortran, C, C++, C# или Java.


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

Этап проведения анализа


Основным этапом исследования в науке о данных является фактическое проведение анализа, которое заключается в написании, исполнении и улучшении компьютерных программ, позволяющих анализировать и извлекать ценную информацию из данных. Такие программы я буду называть скриптами для анализа данных, так как специалисты по работе с данными зачастую предпочитают пользоваться интерпретируемыми «скриптовыми» языками вроде Python, Perl, R и MATLAB. Однако при необходимости они также используют компилируемые языки, такие как C, C++ и Fortran.

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

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

  • Абсолютное время исполнения программы. Исполнение скрипта может занимать много времени либо из-за большого количества обрабатываемых данных, либо из-за медленной работы алгоритмов, причиной которой может стать его асимптотическая сложность и/или медленное исполнение.
  • Относительное время исполнения программы. Исполнение скрипта может занимать много времени из-за того, что в часть кода в ходе анализа на каждой итерации могут вноситься незначительные коррективы. Из-за этого много времени уходит на повторные вычисления, которые выдают практически те же результаты.
  • Сбои из-за ошибок. Программа может вылетать из-за ошибок в коде или несоответствий в массивах данных. Разработчикам зачастую приходится по несколько раз проводить отладку и исправлять банальные ошибки вроде неточностей в синтаксисе, прежде чем завершится работа скрипта и будут получены нужные результаты.

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

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

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

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

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

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


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

Этап рефлексии


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

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

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

Цифровым вариантом обычно является запись в текстовый файл или на электронные стикеры, создание мультимедиа контента в PowerPoint или ведение заметок в специальном приложении, например, Evernote или Microsoft OneNote. У каждого варианта свои преимущества: рисунки и уравнения легче писать от руки на бумаге, а код и цифровые изображения лучше копировать и вставлять в электронные документы.

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

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

К встрече подготавливают копии визуального представления данных и отчеты о ходе выполнения работ, на основе которых строится обсуждение. После встречи составляется новый список задач для каждого из участников встречи. Например, во время летней стажировки в Microsoft Research я занимался исследованием на основе работы с данными [англ. data-driven study] – его целью было изучение факторов, влияющих на исправление багов – и каждый день я встречался со своим научным руководителем Томом Циммерманом.

Помимо изучения графиков и таблиц, являвшихся результатом проведенного мной анализа, он зачастую просил меня откорректировать скрипты и спланировать эксперименты так, чтобы можно было проверить сразу несколько альтернативных гипотез. Например, он говорил мне: «Изучите, пожалуйста, влияние местоположения сотрудника на количество исправленных багов путем проведения своего исследования отдельно для каждой страны».

Проведение сравнений и проверка альтернативных гипотез. Наиболее близким к фактическому анализу методом рефлексии является сопоставление выходных данных и проверка альтернативных гипотез путем регулирования параметров кода и его исполнения.

Ученые по данным любят открывать по несколько файлов с графическим представлением результатов на соседних мониторах, чтобы их можно было сравнить визуально и сопоставить их характеристики. Дайана МакЛин, анализируя деятельность ученых из Гарварда, пришла к следующему выводу:

Большая часть аналитической работы основана на методе проб и ошибок: ученый проводит эксперимент, выводить результаты на график, снова проводит эксперимент, снова выводит результаты на график и т.д. Ученые очень сильно зависят от графиков. Они пытаются все представить в графическом виде. Например, некоторые выводят график одной последовательности ДНК рядом с графиком другой.


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

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

Этап оформления результатов


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

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

Однако было бы намного лучше, если бы люди могли поработать с их программой и «прочувствовать» все методы, представленные в отчетах. В действительности же достаточно сложно распространить свой код так, чтобы остальные без труда могли запустить программу на своих компьютерах.

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

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

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

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

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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Из песочницы] MagOS в промышленном применении

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

1. Базовое описание основных принципов
1.1. Применение MagOS.
1.2. Технологии.
1.3. Выбор базового дистрибутива.
2. Структура сети.
2.1. Magos-server.
3. Настройка загрузчика.
3.1. Строки загрузчика.
3.2. Опции которые были использованы.
3.3. Опции, которые могут быть использованы.
3.4. Особенности сетевой загрузки.
4. Порядок инициализации системы.
4.1. Структура конфигурационного файла basecfg.ini по умолчанию.
4.2. Структура системной директории.
4.3. Реализация.
5. Сервер MagOS.
5.1. Общие сведения.
5.2. Настройки сети.
5.3. Настройка служб.
5.4. Репозиторий программ.
5.5. Дополнительные данные сервера
5.6. Мониторинг.
6. Пользовательские модули.
6.1. Общие принципы создания пользовательских модулей.
6.2. Сколько модулей делать.
6.3. Модули специального назначения.
6.4. Ограничения для модулей.
6.5. Инструкция по созданию модулей.
6.6. Модуль обновления системы.
6.7. Модуль установки офисных программ.
6.8. Модуль с утилитами и серверами.
6.9. Модуль системных настроек.
7. Скрипты.
7.1. Дополнения к magos-patches.
7.2. Скрипт установки ОС.
7.3. Скрипты включения в AD.
7.4. Управление системой (/root/bin).
7.5. Дополнительные скрипты, исправляющие работу программ magos и операционной системы.
8. Инструкция для техников.

Базовое описание основных принципов


Применение MagOS

MagOS — это специфичная сборка дистрибутива, выбираемого из обширного списка. В качестве основы для построения сборки могут выступать Live дистрибутивы Magea, Mandriva, Rosa, Ubuntu, Debian, Fedora, AltLinux и т.д. Специфичными элементами MagOS выступает модифицированное ядро (для поддержки AUFS), специальным образом созданный initrd (используется UIRD) и дополнительный набор скриптов, предназначенных для управления MagOS.

MagOS позволяет создать на основе Live сборок различных дистрибутивов (сборок, предназначенных для загрузки с CD, DVD диска) полноценную операционную систему. Специфика использования MagOS дает целый набор дополнительных преимуществ такого подхода:

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

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

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

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

К нереализованным возможностям MagOS следует отнести отсутствие возможности хранения пользовательских данных на защищенной распределенной файловой системе. Реализация такой распределенной файловой системы позволит обеспечить полную реентерабильность пользователей.

Некоторую вводную информацию о MagOS можно получить из статьи: MagOS Linux (сентябрьский выпуск).

Технологии

Модификация ядра Linux, входящего в MagOS заключается во включении патча с файловой системой AUFS. AUFS позволяет подключать в файловую систему при помощи интерфейса loopback внешние файловые системы, собирая результирующую файловую систему, как слоеный пирог. Промежуточные слои такой результирующей файловой системы подключаются, чаще всего, в режиме RO (read only), а самый верхний слой, как правило, подключается в режиме RW (read-write) и проецируется на дисковую файловую систему, которая может размещаться как в оперативной памяти, на физическом диске, в образе диска, так и сохраняться при операции отключения в специальном модуле, подключаемом при помощи файловой системы SquashFS.

Файловая система SquashFS позволяет выполнять сжатие при помощи блочного алгоритма, сохраняя все атрибуты файловой системы. В MagOS она используется для создания подгружаемых модулей с образами слоев файловой системы AUFS. Блочный алгоритм сжатия позволяет не распаковывать файлы модулей целиком при необходимости извлечения из них данных.

В MagOS образ диска initrd, используемый для загрузки ОС Linux в большинстве дистрибутивов, использован для организации работы системы. Там располагаются скрипты, создающие «слоеную» файловую систему дистрибутива, обрабатывающие конфигурационные файлы и т.п.

Необходимые для настройки системы данные передаются через параметры ядра операционной системы, прописываемые в загрузчике grub4dos/grub2/syslinux, и при помощи специального конфигурационного файла MagOS.ini. Где и какие параметры передаются описывается в документации. Параметры, связанные с общей настройкой операционной системы передаются при помощи параметров ядра. Они организованы в систему параметров Unified Init Ram Disk (uird).

Описание параметров приведено на сайте проекта: UIRD. Использован дистрибутив Magos multi.

В промышленном применении нельзя обойтись без сервера, содержащего дистрибутив и поддерживающего протоколы HTTP (для удаленной загрузки), TFTP (сервер удаленной загрузки по PXE), SSH (для управления файлами) и RSYNC (для установки и обновления ОС на компьютерах пользователей). Сервер может быть реализован на любом дистрибутиве, включая MagOS. В моем случае был использован виртуальный контейнер на основе дистрибутива CentOS 6.

Для управления системой в состав MagOS были добавлены необходимые опции и скрипты, которые их обрабатывают.

Выбор базового дистрибутива

Выбор дистрибутива, на основе которого строится сеть, всегда сложная и неоднозначная задача. Выбирая тот или иной дистрибутив приходится учитывать множество факторов, в том числе множество местных проблем. У нас такими факторами были следующие: весьма непроизводительные компьютеры пользователей, они, хоть и приобретены были совсем недавно, но на составе компьютеров сэкономили, поэтому в типовой машинке, за которой сидит пользователь всего 2 Гигабайта ОЗУ и двухъядерный Celeron с не очень большой тактовой частотой. Такая типовая конфигурация компьютера пользователя накладывает ограничения на выбор менеджера рабочего стола, в частности использовать KDE уже не представляется возможным без потери возможности комфортной работы пользователя.

Во вторых это проблема низкой квалификации персонала, основной функцией которого является, отнюдь, не работа с компьютером. Необходимо помнить о многолетней привычке к одной единственной операционной системе, с которой людям на протяжении многих лет приходилось работать — Windows XP, поддержка которой прекращена, из-за чего, собственно и встала проблема замены ОС на рабочих местах пользователей.

Исследуя возможности адаптации внешнего вида рабочего стола к внешнему виду старой доброй XP, мы решили остановиться на Cinnamon. Несмотря на то, что разработка еще не вышла на релиз, в стандартной конфигурации этот рабочий стол работает вполне устойчиво и достаточно легко адаптируется к внешнему виду XP установкой соответствующей темы. Дополнительным фактором, влияющим на выбор ОС, послужило «пожелание» Госструктур видеть на рабочих местах компьютеры с отечественной операционной системой. Стало быть весь выбор, в нашем случае, свелся к выбору между ОС Rosa и ОС Alt Linux.

Несмотря на значительный опыт работы с ОС Rosa, сравнение дистрибутивов AltLinux и Rosa оказалось не в пользу Rosa. В первую очередь из-за отсутствия Cinnamon в составе LiveDVD, во вторых из-за снижения качества дистрибутива в последнее время.

Таким образом, в качестве основы для создания сборки была выбрана разработка AltLinux — одна из сборок стартового набора P7, содержащая рабочий стол Cinnamon. Положительной стороной такого выбора является минимальный состав набора, что позволяет расширять его по своему усмотрению.

Структура сети


Magos-server

В сети предприятия имеется сервер виртуализации, на котором и был развернут Magos-server. Сервер выполняет несколько функций.

Во первых он служит сервером удаленной загрузки. Удаленная загрузка реализована при помощи TFTP и позволяет загружать MagOS в той же самой конфигурации, которая используется для работы на рабочих станциях. При помощи такой загрузки можно протестировать оборудование, выполнить установку операционной системы на рабочую станцию и выполнить множество других задач. Кроме того, при помощи сервера удаленной загрузки загружаются образы Clonezilla и Memtest.

Удаленная загрузка рабочей станции под управлением MagOS осуществляется по протоколу HTTP, для чего на сервер установлен Lighttpd, DocumentRoot которого указывает на репозиторий MagOS.

Установка дистрибутива на рабочую станцию и обновление рабочих станций выполняется по протоколу RSYNC. Поэтому на сервер установлен rsyncd.

Управление сервером осуществляется по протоколу SSH. По этому же протоколу на сервере обновляются изменения модулей программ, подготавливаемые на тестовом компьютере.

Этот компьютер укомплектован 4Gb памяти, поскольку возникли проблемы с созданием модулей при меньшем объеме памяти.

Интеграция в сеть

Развернута AD на основе Windows 2008 SP2 и все компьютеры сети включаются в AD. Не исключение и компьютеры под управлением ОС Linux.

Настройка загрузчика


Строки загрузчика
title AltLinux i586 cinamon save
#find --set-root --ignore-floppies --ignore-cd /MagOS/MagOS.sgn
kernel  /AltLinux/kernel/i586/vmlinuz uird.ro=*.xzm,*/live uird.from=/AltLinux/iso/altlinux-p7-cinnamon-latest-i586.iso;/AltLinux/modules/i586/ uird.load=* root=uird rw findswap vga=788 quiet plymouth.enable=0 uird.home=/dev/sda3/AltLinux-Data/homes/ uird.changes=/dev/sda3/AltLinux-Data/changes/ users
initrd /AltLinux/kernel/i586/uird.soft.cpio.xz /AltLinux/kernel/i586/uird.magos.cpio.xz


Опции которые были использованы

Ввиду множественности параметров ядра, для выделения параметров MagOS введен префикс параметров ‘uird’ (Unified Init Ram Disk).
uird.ro=*.xzm,*/live 


uird.ro — Параметр MagOS. Задает фильтр для модулей, которые монтируются в режиме RO. В качестве таковых выступают собственно модули MagOS и сам LiveDVD AltLinux.
uird.from=/AltLinux/iso/altlinux-p7-cinnamon-latest-i586.iso;/AltLinux/modules/i586/ 


uird.from — Параметр MagOS. Список источников, на которых лежат модули для системы. Это указание пути для загрузки модулей и самого дистрибутива.
uird.load=* 


uird.load — Параметр MagOS. Фильтр для модулей, которые необходимо подключить на этапе загрузки.
root=uird 


root – Параметр ядра. Указание корневой файловой системы.
rw


rw — включение режима read/write.
findswap


findswap — Параметр MagOS. Заставляет систему автоматически подключать Swap. Если в системе находится Linux раздел Swap, то подключается он. Иначе ищется файл подкачки Windows.
vga=788


vga — Параметр ядра. Включение режима работы графики.
quiet


quiet — Параметр ядра, указывает необходимость формирования журнала dmesg.
plymouth.enable=0


plymouth.enable — Параметр ядра. Управляет графическим экраном и выводом журнала во время загрузки операционной системы.
uird.home=/dev/sda3/AltLinux-Data/homes/ 


uird.home — Параметр MagOS. Задает источник, на котором хранятся домашние директории пользователей. Ввиду ошибки в существующей версии MagOS требуется задание полного пути, включая указание устройства.
uird.changes=/dev/sda3/AltLinux-Data/changes/ 


uird.changes — Параметр MagOS. Задает источник, на котором будут храниться персистентные изменения корневой файловой системы.
users


users — Параметр ядра.
Опции, которые могут быть использованы

Есть возможность использовать шифрование для сохраняемых на жесткий диск данных. В этом случае вместо сохранения данных на разделы необходимо использовать сохранение в образы диска. Образы должны принять следующий вид:
  *.RWM.ENC - RW слой криптованый
  *.ROM.ENC - RO слой криптованый


uird.copy2ram[+]= — фильтр для модулей, которые копируются в ОЗУ. Может быть применено для ускорения работы при наличии значительного объема оперативной памяти.
uird.copy2cache[+]= — фильтр для модулей, которые копируются в КЭШ.
uird.cache[+]= — источники, в которые стоит синхронизировать модули.

Имеется возможность использовать КЭШ вместо синхронизации файлов MagOS с сервером при выключении компьютера. К недостаткам метода следует отнести то, что обмен с сервером выполняется по протоколу HTTP, что само по себе значительно снижает быстродействие. Вторым недостатком является то, что возникает сложность при разделении объектов обновления — файла MagOS.ini, раздела boot и раздела собственно ОС. Нужно обратить внимание, что уровень кэша layer-cache и соответствующий параметр uird.cache, служащий для синхронизации удаленных репозиториев в локальные или частные (INTRANET) репозитории, а также для обновления системы, должны задаваться нижеприведенным образом:

uird.cache=/MagOS/cache;/MagOS-Data/cache;/MagOS-Data/netlive


Здесь для каждого источника задается собственный каталог.

uird.netfsopt[+]= — дополнительные опции монтирования сетевых ФС: sshfs, nfs, curlftpfs, cifs.

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

uird.noload[+]= — фильтр для модулей, которые необходимо пропустить во время загрузки
Можно выполнять выборочное отключение некоторых модулей для отдельных компьютеров или сетей.
uird.homes[+]= — источники, на которых хранятся домашние директории пользователей (объединяются AUFS).

По сути здесь вводится уровень домашних директорий пользователя layer-homes и соответствующий параметр: uird.homes:

uird.homes=/MagOS-Data/homes;/MagOS-Data/home.img;nfs://magos.sibsau.ru/homes/n/e/myuser


Все директории пользователя из различных источников каскадно-объединяются посредством AUFS и монтируются в /home. Более приоритетным является самый первый источник, затем, в порядке перечисления, уменьшается приоритет. В случае, если источник задан параметром uird.home=, то происходит монтирование источника в /home. Таким образом имеется возможность множественного подключения домашней папки с наложением разных файловых систем. Может использоваться при сетевом размещении домашних папок пользователей.

Типы источников:
/path/dir — директория на любом доступном носителе;
/dev/[..]/path/dir — директория на заданном носителе;
file-dvd.iso, file.img — образ диска (ISO, образ блочного устройства);
http://server/path/… — источник доступный по HTTP (используется httpfs);
ssh://server/path/… — источник доступный по SSH (исползуется sshfs);
ftp://server/path/… — источник доступный по FTP (используется curlftpfs);
nfs://server/path/… — источник доступный по NFS;
cifs://server/path/… — источник доступный по CIFS;
uird.machines= — источник, где хранятся машинно-зависимые персистентные изменения.

Имеется возможность использовать машинозависимые ресурсы для changes, что необходимо для обеспечения реентерабильности пользователей.

Особенности сетевой загрузки

Для сетевой загрузки используются нижеприведенные параметры:
kernel images/vmlinuz uird.ro=*.xzm,*/live uird.from=http://ift.tt/1WC17ph linux-p7-cinnamon-latest-i586.iso;http://ift.tt/1RByAcV uird.load=* root=uird rw findswap vga=788 quiet plymouth.enable=0 users


Это те же самые параметры, однако следует обратить внимание на указание параметра uird.from:
uird.from=http://ift.tt/1WC17pk


Здесь указывается полный http url сервера, с которого выполняется загрузка ОС. Базовый уровень layer-base и соответствующий параметр uird.from могут задаваться в следующем виде:
uird.from=/MagOS;/MagOS-Data;MagOS.iso;http://ift.tt/1RByAcY


Порядок инициализации системы


  • Осуществляется поиск конфигурационного файла по пути, указанному в параметре uird.basecfg.
  • Устанавливаются параметры из конфигурационного файла, которые еще не установлены в параметрах ядра.
  • Происходит монтирование источников base-уровня в порядке, указанном в параметре uird.from.
  • Происходит монтирование источников cache-уровня в порядке, указанном в параметре uird.cache.
  • Происходит монтирование источников homes-уровня в порядке, указанном в параметре uird.homes.
  • Происходит подключение в самый верхний уровень AUFS источника персистентных изменений, указанного в параметре uird.changes.
  • Осуществляется синхронизация base-уровня в cache-уровень с учетом параметра uird.copy2cache, а также соответствия подуровней. Если подуровней cache-уровня меньше, чем base-уровня, то оставшиеся подуровни синхронизируются в RAM.
        ├── layer-base       ==>      ├── layer-cache
        │   ├── 0            -->      │   ├── 0
        │   ├── 1            -->      │   ├── 1
        │   ├── ...          -->      │   └── ...
        │   └── ...          -->      │   RAM


  • Осуществляется синхронизация base,cache-уровней в RAM с учетом параметра uird.copy2ram.
  • Осуществляется поиск модулей в RAM, cache-уровне, base-уровне и подключение их на верхний уровень AUFS или копирование в корень (с учетом фильтров, указанных в параметрах uird.load,uird.noload,uird.ro,uird.rw,uird.cp).
  • Осуществляется каскадное объединение источников homes-уровня и подключение их в /home/.
  • Выполняются скрипты rc.preinit.

Структура конфигурационного файла basecfg.ini по умолчанию
uird.config=MagOS.ini
uird.ramsize=70%
uird.ro=*.xzm;*.rom;*.rom.enc;*.pfs;*.sfs
uird.rw=*.rwm;*.rwm.enc
uird.cp=*.xzm.cp,*/rootcopy
uird.load=/base/,/modules/,rootcopy
uird.noload=
uird.from=/MagOS;/MagOS-Data
uird.changes=/MagOS-Data/changes
uird.cache=/MagOS-Data/cache
uird.machines=/MagOS-Data/machines
uird.home=/MagOS-Data/homes


Если параметр uird.basecfg не задан, то используется /uird_configs/basecfg.ini внутри initrd.
Структура системной директории
 /memory/
  ├── bundles                   - точка монтирования модулей
  │   ├── 00-kernel.xzm
  │   ├── 01-firmware.xzm
  │   ├── 10-core.xzm
  │   ├── 80-eepm-1.5.2.xzm
  │   └── ...                   - и т.д.
  ├── changes                   - точка монтирования для хранения изменений 
  │   ├── etc
  │   ├── home
  │   ├── memory
  │   ├── run
  │   ├── var
  │   └── ...                   - и т.д.
  ├── data                      - точка монтирования источников
  │   ├── cache                     - кэш уровня
  │   ├── homes                     - homes уровня
  │   ├── machines                  - (зарезервировано)
  │   └── from                      - базового уровня
  ├── layer-base                - точка монтирования базового уровня
  │   ├── 0                         - ресурс первого источника
  │   ├── 1                         - ресурс второго источника (в порядке перечисления в uird.from=)
  │   └── ...                       - и т.д.
  ├── layer-cache               - точка монтирования кэш уровня
  │   ├── 0                         - ресурс первого источника
  │   ├── 1                         - ресурс второго источника (в порядке перечисления в uird.cache=)
  │   └── ...                       - и т.д.
  ├── layer-homes               - точка монтирования homes уровня
  │   ├── 0                         - ресурс первого источника
  │   ├── 1                         - ресурс второго источника (в порядке перечисления в uird.homes=)
  │   └── ...                       - и т.д.
  ├── cmdline                   - системный файл для хранения дополнительных параметров командной строки
  └── MagOS.ini.gz              - системный файл для хранения конфигурационного файла


Реализация

В основе реализации лежит набор скриптов инициализации dracut (модули base, busybox ) и скрипты uird (livekitlib+uird-init):
  • cmdline-hook: parse-root-uird.sh (проверяет параметр root=uird);
  • mount-hook: mount-uird.sh (выполняет скрипт uird-init);
  • livekitlib — содержит библиотеку функций системы инициализации;
  • uird-init — последовательно выполняет набор функций из livekitlib и осуществляет каскадно-блочное монтирование модулей системы в единый корень AUFS в директорию указанную в переменной dracut $NEWROOT.

Сервер MagOS


Общие сведения

В нашем случае magos-server развернут в качестве контейнера openvz. Реализация не имеет принципиального значения.
  • Он служит сервером удаленной загрузки. Удаленная загрузка реализована при помощи TFTP/PXE и позволяет загружать MagOS в той же самой конфигурации, которая используется для работы на рабочих станциях. При помощи такой загрузки можно протестировать оборудование, выполнить установку операционной системы на рабочую станцию и выполнить множество других задач.
  • Удаленная загрузка рабочей станции под управлением MagOS осуществляется по протоколу HTTP, для чего на сервер установлен Lighttpd, documentroot которого указывает на репозиторий MagOS.
  • Установка дистрибутива на рабочую станцию и обновление рабочих станций выполняется по протоколу rsync. Поэтому на сервер установлен rsyncd.
  • Управление сервером осуществляется по протоколу ssh. По этому же протоколу на сервере обновляются изменения модулей программ, подготавливаемые на тестовом компьютере.

Реализация сервера

Операционная система Centos v6. Для виртуального контейнера выделены следующие ресурсы: CPU — 2, RAM — 512Mb, swap — 1Gb, размер виртуального диска 40Gb.
Настройки сети

Настройка в ifcfg-eth0
DEVICE=eth0
IPADDR=192.168.1.xxx
NETMASK=255.255.255.0
NETWORK=192.168.1.0
GATEWAY=192.168.1.1
DNS1=192.168.1.xxx
BROADCAST=192.168.1.255
TYPE=Ethernet
ONBOOT=yes
NM_CONTROLLED=yes
BOOTPROTO=static



Сетевые службы (netstat -tunlp)
# netstat -tunlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address    Foreign Address State  PID/Program name  
tcp    0      0     0.0.0.0:873      0.0.0.0:*       LISTEN      494/xinetd        
tcp    0      0     192.168.1.xxx:80 0.0.0.0:*       LISTEN      551/lighttpd      
tcp    0      0     0.0.0.0:22       0.0.0.0:*       LISTEN      484/sshd          
udp    0      0     0.0.0.0:69       0.0.0.0:*                   494/xinetd 



Настройка служб

Lighthttpd

Запуск:
chkconfig --list lighttpd
lighttpd        0:off   1:off   2:on    3:on    4:off   5:on    6:off



Конфигурационный файл lighttpd.conf
var.log_root    = "/var/log/lighttpd"
var.server_root = "/var/www"
var.state_dir   = "/var/run"
var.home_dir    = "/var/lib/lighttpd"
var.conf_dir    = "/etc/lighttpd"
var.vhosts_dir  = server_root + "/vhosts"
var.cache_dir   = "/var/cache/lighttpd"
var.socket_dir  = home_dir + "/sockets"
include "modules.conf"
server.port = 80
server.use-ipv6 = "disable"
server.bind = "192.168.1.xxx"
server.username  = "lighttpd"
server.groupname = "lighttpd"
server.document-root = server_root + "/"
server.pid-file = state_dir + "/lighttpd.pid"
server.errorlog             = log_root + "/error.log"
include "conf.d/access_log.conf"
include "conf.d/debug.conf"
server.event-handler = "linux-sysepoll"
server.network-backend = "linux-sendfile"
server.stat-cache-engine = "simple"
server.max-connections = 1024
index-file.names += (
  "index.xhtml", "index.html", "index.htm", "default.htm", "index.php"
)
url.access-deny             = ( "~", ".inc" )
$HTTP["url"] =~ "\.pdf$" {
  server.range-requests = "disable"
}
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi", ".scgi" )
include "conf.d/mime.conf"
include "conf.d/dirlisting.conf"
server.follow-symlink = "enable"
server.upload-dirs = ( "/var/tmp" )



Конфигурационный файл vhosts.d/magos.conf
$HTTP["host"] == "magos-server.mydomain.local" {
  var.server_name = "magos-server.mydomain.local"
  server.name = server_name
  include "conf.d/trigger_b4_dl.conf"
  server.document-root = vhosts_dir + "/magos/"
  accesslog.filename          = log_root + "/" + server_name "/access.log"
}



Конфигурационный файл conf.d/dirlisting.conf
dir-listing.activate      = "enable"
dir-listing.hide-dotfiles = "disable"
dir-listing.exclude       = ( "~$" )
dir-listing.encoding = "UTF-8"
dir-listing.hide-header-file = "disable"
dir-listing.show-header = "disable"
dir-listing.hide-readme-file = "disable"
dir-listing.show-readme = "disable"



Tftpd

Запуск (/etc/xinetd.d/tftp:)
service tftp {
      socket_type             = dgram
      protocol                = udp
      wait                    = yes
      user                    = root
      server                  = /usr/sbin/in.tftpd
      server_args             = -s /var/lib/tftpboot
      disable                 = no
      per_source              = 11
      cps                     = 100 2
      flags                   = IPv4
} 


Конфигурационный файл /var/lib/tftpboot/pxelinux.cfg/default
default menu.c32
prompt 0
timeout 300
ONTIMEOUT local

MENU TITLE PXE Menu

# Первый пункт меню – загрузка с HD
LABEL Boot from hard disk
localboot 0x80

LABEL AltLinux-net
        MENU LABEL AltLinux-net
        kernel images/vmlinuz uird.ro=*.xzm,*/live uird.from=http://ift.tt/1WC15xP
x/iso/altlinux-p7-cinnamon-latest-i586.iso;http://ift.tt/1RByAcV ui
rd.load=* root=uird rw findswap vga=788 quiet plymouth.enable=0 users
        append initrd=images/uird.magos.cpio.xz
LABEL AltLinux-net testing
        MENU LABEL AltLinux-net testing
        kernel images/vmlinuz uird.ro=*.xzm,*/live uird.from=http://ift.tt/1RByAd2
/iso/altlinux-p7-cinnamon-latest-i586.iso;http://ift.tt/1WC17ps
/ uird.load=* root=uird rw findswap vga=788 quiet plymouth.enable=0 users
        append initrd=images/uird.magos.cpio.xz



Репозиторий состоит из двух частей: рабочего, имеющего название magos и тестового, имеющего название testing. Рабочий репозиторий предназначен для установки и обновления программного обеспечения рабочих станций пользователей. Предварительное тестирование устанавливаемого ПО выполняется на репозитории testing. Меню загрузки позволяет загружать операционную систему как из рабочего репозитория так и из тестового.
Rsync

Запуск (/etc/xinetd.d/rsync):
service rsync
{
        disable = no
        flags           = IPv4
        socket_type     = stream
        wait            = no
        user            = root
        server          = /usr/bin/rsync
        server_args     = --daemon
        log_on_failure  += USERID
}



Конфигурационный файл /etc/rsyncd.conf
use chroot = yes
max connections = 100
syslog facility = local5
pid file = /var/run/rsyncd.pid
[magos]
path = /var/www/magos
comment = whole MagOS boot
[testing]
path = /var/www/testing
comment = whole MagOS boot



sshd

Запуск:
# chkconfig –list sshd
sshd            0:off   1:off   2:on    3:on    4:on    5:on    6:off



Конфигурационный файл /etc/ssh/sshd_config
Protocol 2
SyslogFacility AUTHPRIV
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
UsePAM yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
X11Forwarding no
Subsystem       sftp    /usr/libexec/openssh/sftp-server



Репозиторий программ

В структуре репозитория программ показаны только существенные файлы и каталоги.

magos

├──AltLinux
│  ├──iso
│  │  └──altlinux-p7-cinnamon-latest-i586.iso
│  ├──kernel
│  │  └──i586
│  │     ├──uird.magos.cpio.xz
│  │     ├──uird.soft.cpio.xz
│  │     └──vmlinuz
│  └──modules
│     └──i586
│        ├──00-kernel.xzm
│        ├──01-firmware.xzm
│        ├──03-1-nvidia-current.xzm
│        ├──03-2-nvidia304.xzm
│        ├──03-9-fglrx.xzm
│        ├──80-eepm-1.5.2.xzm
│        ├──80-uird.soft.xzm
│        ├──90-magos-patches.xzm
│        ├──99-squashfs-tools.32.xzm
│        ├──99-u10-update.xzm
│        ├──99-u40-office4.xzm
│        ├──99-u50-utils.xzm
│        ├──99-u99-default.xzm
│        ├──MagOS.ini
│        └──update.txt
├──AltLinux-Data
│  ├──cache
│  ├──changes
│  ├──homes
│  ├──machines
│  ├──MagOS-Data.sgn
│  ├──modules
│  ├──optional
│  └──rootcopy
└──boot
   ├──grub4dos
   │  ├──install.lin
   │  ├──install.win
   │  └──local
   │     └──menu.lst
   ├──syslinux
   └──tools


testing
├──AltLinux
│  ├──iso
│  │  └──altlinux-p7-cinnamon-latest-i586.iso
│  ├──kernel
│  │  └──i586
│  │     ├──uird.magos.cpio.xz
│  │     ├──uird.soft.cpio.xz
│  │     └──vmlinuz
│  └──modules
│     └──i586
│        ├──00-kernel.xzm
│        ├──01-firmware.xzm
│        ├──03-1-nvidia-current.xzm
│        ├──03-2-nvidia304.xzm
│        ├──03-9-fglrx.xzm
│        ├──80-eepm-1.5.2.xzm
│        ├──80-uird.soft.xzm
│        ├──90-magos-patches.xzm
│        ├──99-squashfs-tools.32.xzm
│        ├──99-u10-update.xzm
│        ├──99-u40-office4.xzm
│        ├──99-u50-utils.xzm
│        ├──99-u99-default.xzm
│        ├──MagOS.ini
│        └──update.txt
├──AltLinux-Data
│  ├──cache
│  ├──changes
│  ├──homes
│  ├──machines
│  ├──MagOS-Data.sgn
│  ├──modules
│  ├──optional
│  └──rootcopy
├──boot
│  ├──grub4dos
│  │  ├──install.lin
│  │  ├──install.win
│  │  └──local
│  │     └──menu.lst
│  ├──syslinux
│  └──tools
└──update.tar.gz


Поскольку запись в репозиторий осуществляется с правами пользователя системы, необходимо создать группы, отвечающие за право записи в соответствующий репозиторий:
# groupadd magos
# groupadd testing


Установить права на все каталоги репозитория:
# cd /var/www
# find magos -type f -exec chmod 664 {} +
# find magos -type d -exec chmod 775 {} +
# find testing -type f -exec chmod 664 {} +
# find testing -type d -exec chmod 775 {} +


Установить группы владельцев репозиториев:
# chown -R :magos magos
# chown -R :testing testing


Установить SGID на каталоги репозиториев:
# chmod g+s magos
# chmod g+s testing


Дополнительные данные сервера
yum.repos.d
CentOS-Base.repo
CentOS-Debuginfo.repo
CentOS-fasttrack.repo
CentOS-Media.repo
CentOS-Vault.repo
epel.repo
epel-testing.repo
vz.repo


Скрипты управления

Скрипты создавались для текущего использования и не претендуют на «коробочность» использования, поэтому при их применении прошу быть внимательными!
Мониторинг

Скрипт update-txt.sh, выполняемый ежедневно по расписанию записывает в каталог modules файл update.txt, содержащий текущую дату. После синхронизации данных на пользовательский компьютер он позволяет легко проверить когда компьютер последний раз обновлялся. Это необходимый момент для отслеживания компьютеров, которые не перегружаются в течение длительного срока (пользователь не выключает компьютер).
Скрипт /etc/cron.daily/update-txt.sh
#!/bin/sh
echo "magos $(date)" > /var/www/magos/AltLinux/modules/i586/update.txt
echo "testing $(date)" > /var/www/testing/AltLinux/modules/i586/update.txt



Обновление

Скрипты testig2magos_kern.sh, testig2magos_mod.sh и testig2magos_all.sh предназначены для обновления рабочего репозитория из репозитория testing и выполняются только в ручном режиме.

testig2magos_kern.sh — обновляет только системные модули и каталог kernel.
testig2magos_mod.sh — выполняет обновление только пользовательских модулей.
testig2magos_all.sh — выполняет полное обновление включая папки kernel, iso и modules.

Ни один из скриптов не выполняет обновление файла MagOS.ini!

Изменения в этот файл вносятся только вручную.

Скрипт /root/bin/testig2magos_kern.sh
#!/bin/bash

# обновить ядро и системные модули MagOS в рупозитории magos
# из репозитория testing

MAGOS="/var/www/magos"
TESTING="/var/www/testing"
MAGOSGROUP="magos"

echo "!!! UPDATE KERNEL AND MAGOS MODULES TO magos REPOSITORY FROM testing REPOSITORY"
echo "                                       ====="
echo
echo "Pres Rnter to continue, or Ctrl+C to abort..."
read junk
clear

cp -ruv $TESTING/AltLinux/kernel/i586/*.xzm $MAGOS/AltLinux/kernel/i586/
cp -ruv $TESTING/AltLinux/modules/i586/[0-9]?-*.xzm $MAGOS/AltLinux/modules/i586/
find $MAGOS -type f -exec chmod 664 {} +
find $MAGOS -type d -exec chmod 775 {} +
chown -R :$MAGOSGROUP $MAGOS/*
echo "UPDATE KERNEL AND MAGOS MODULES FROM magos REPOSITORY IT IS EXECUTED"



Скрипт /root/bin/testig2magos_mod.sh
#!/bin/bash

# обновить пользовательские модули MagOS в рупозитории magos
# из репозитория testing

MAGOS="/var/www/magos"
TESTING="/var/www/testing"
MAGOSGROUP="magos"

echo "!!! UPDATE MODULES TO magos REPOSITORY FROM testing REPOSITORY"
echo "                      ====="
echo
echo "Pres Rnter to continue, or Ctrl+C to abort..."
read junk
clear
cp -ruv $TESTING/AltLinux/modules/i586/[0-9]??-*.xzm $MAGOS/AltLinux/modules/i586/
find $MAGOS -type f -exec chmod 664 {} +
find $MAGOS -type d -exec chmod 775 {} +
chown -R :$MAGOSGROUP $MAGOS/*
echo "UPDATE MODULES FROM magos REPOSITORY IT IS EXECUTED"



Скрипт /root/bin/testig2magos_all.sh
#!/bin/bash

# обновить целиком весь репозиторий magos
# из репозитория testing, включая каталог с исходным дистрибутивом iso

MAGOS="/var/www/magos"
TESTING="/var/www/testing"
MAGOSGROUP="magos"

echo "!!! UPDATE ALL MAGOS REPOSITORY magos FROM testing REPOSITORY"
echo "                                ====="
echo
echo "Pres Rnter to continue, or Ctrl+C to abort..."
read junk
clear

cp -ruv $TESTING/AltLinux/iso/ $MAGOS/AltLinux/iso/
cp -ruv $TESTING/AltLinux/kernel/i586/*.xzm $MAGOS/AltLinux/kernel/i586/
cp -ruv $TESTING/AltLinux/modules/i586/*.xzm $MAGOS/AltLinux/modules/i586/
find $MAGOS -type f -exec chmod 664 {} +
find $MAGOS -type d -exec chmod 775 {} +
chown -R :$MAGOSGROUP $MAGOS/*
echo "UPDATE magos REPOSITORY IT IS EXECUTED"



Пользовательские модули


Общие принципы создания пользовательских модулей

Что нужно знать

При создании модулей в MagOS приходится учитывать одну важную особенность, связанную с созданием пользователей и групп. При создании модуля в нем сохраняются измененные файлы passwd, group, shadow и т.д. Но, для того, чтобы следующий создаваемый модуль их «видел» скриптом epm2xzm, нужно, чтобы название модуля соответствовало шаблону «NN-». Если названия модулей не будут соответствовать этому шаблону, то каждый последующий создаваемый модуль так же, как и первый, будут создаваться на основе только базовых модулей MagOS. Больнее всего это ударит по файлам аутентификации: программы создающие системных пользователей и устанавливаемые в разные модули получают одинаковые UID и GID, а файлы типа passwd, созданные в разных модулях переписываются последующими слоями. В результате установленные в нижележащих модулях программы оказываются неработоспособными.

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

Скрипты создающие модули написаны так, что в качестве имени модуля используют собственное название. Скрипт, названный 99-u30-example.sh будет создавать модуль с названием 99-u30-example.xzm.

Сколько модулей делать

Порядок подключения базовых модулей не имеет значения, поскольку в них нет никаких перекрывающихся файлов и каталогов. Но имеет значение порядок подключения пользовательских модулей. Если в пользовательских модулях имеются исправления файлов базовых модулей, а в случае с адаптацией MagOS для дистрибутива AltLinux такая ситуация наблюдается, то они должны располагаться в верхних слоях aufs и подключаться после системных. Модули подключаются в систему, будучи отсортированными по названию, поэтому название модуля имеет важное значение. Поскольку название последнего системного модуля начинается с символов «99-», рекомендуется использовать названия пользовательских модулей, начинающееся с символов «99?-» таким образом, чтобы при сортировке каталога по названию они оказывались после системных модулей.
Модули специального назначения

Рекомендуется выделить два модуля имеющих особое назначение: модуль, в котором располагается текущее обновление операционной системы. Его рекомендуется устанавливать первым, поэтому его рекомендованное название «99-u10-update». И модуль, содержащий адаптированные файлы настроек операционной системы и ее программ. Его рекомендуется устанавливать последним. При создании модуля с настройками нужно соблюсти несколько правил:
  • в этот модуль нельзя ставить программы.
  • в этом модуле не должно быть файлов, ответственных за назначение прав (passwd, group и т.п.)

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

На количество модулей нет никаких ограничений (по умолчанию может быть до 127), но желательно соблюдать некоторый компромисс на разделение программ по модулям. Во первых, при пересборке нижележащего модуля нам требуется отключать все модули, которые должны грузиться позже. А после пересборки такого модуля рекомендуется выполнить пересборку и всех последующих модулей, даже если программы в них не обновлялись и не изменялись. Это связано с зависимостями программ по библиотекам. Таким образом нужно стремиться к тому, чтобы количество пользовательских модулей не было большим.

Вторым ограничением, но уже с другой стороны, выступают требования к ресурсам компьютера, на котором выполняется пересборка модулей. Во время установки программ в модуль файловая система, по-умолчанию, располагается в оперативной памяти, поэтому количество устанавливаемых программ напрямую связано с размером требуемой оперативной памяти. Так, для установки Libreoffice требуется, чтобы на компьютере было около 3 Гигабайт свободной оперативной памяти. Для того, чтобы устранить это ограничение требуется специальным образом подготовить директорию для размещения временных файлов для создания модулей.

Таким образом минимальное количество модулей, с которыми придется работать может быть 3, а оптимальное количество 4 модуля.

Инструкция по созданию модулей

Процедура сборки модуля выглядит следующим образом:
  1. Загрузить средства обновления /root/bin/loadupdate.sh.
  2. Отключить обновление системы при перегрузке ОС: в файлах /etc/sysconfig/MagOS и /mnt/livemedia/MagOS.ini установить переменную AUTOUPDATE=No.
  3. Переименовать обновляемый модуль и все последующие, добавив расширение .bak (другие расширения не допускаются).
  4. Выполнить сборку модуля. По запросу выполнить сохранение модуля на сервере magos-server.
  5. Перегрузить компьютер, для того, чтобы изменения корректно вступили в силу.
  6. Повторить операции 3-5 для всех последующих модулей.

Методика установки новых программ

Методика поиска, установки и настройки программ выглядит следующим образом:
  • Загрузить компьютер в режиме clean.
  • Найти нужную программу и установить ее.
  • Настроить программу имеющимися для настройки средствами, включая acc и веб интерфейс. Добиться результата.
  • Если программа на не подошла, перегрузить компьютер и начать сначала.
  • После получения результата сохранить измененные в процессе настройки конфигурационные файлы в отдельный каталог на жестком диске.
  • Внести программу в список для установки в составе подходящего модуля.
  • Создать модуль. Отредактировать скрипт создания модуля для получения нужных нам значений конфигурационных файлов. При необходимости повторять процесс многократно. Вместо редактирования скрипта создания модуля можно положить конфигурационные файлы и т.п. в каталог модуля установки значений по умолчанию. Все зависит от программы.
  • Выполнить последовательную пересборку всех вышележащих модулей.

Модуль обновления системы

Модуль с обновлениями операционной системы является наиболее простым. Количество обновлений зависит от времени, прошедшего с выпуска релиза дистрибутива. Если количество обновлений становится критическим, то стоит подумать над обновлением iso образа дистрибутива. Период обновлений на усмотрение администратора системы, поскольку обновления выполняются только в ручном режиме и требуют пересборки всех остальных модулей.
Скрипт 99-u10-update.sh
#!/bin/sh

. conf/devel.conf
NAME=`echo $0 | sed 's/\.\///'| sed 's/\..*//'`
. lib/mv.sh $NAME
epm2xzm $NAME upgrade $NAME.xzm

rm -rf $NAME
mkdir $NAME
xzm2dir $NAME.xzm $NAME
. lib/delhlam.sh $NAME

dir2xzm $NAME $NAME.xzm
. lib/update.sh $NAME



Модуль установки офисных программ

В состав модуля входят приложения для конечного пользователя, не требующие создания служебного аккаунта. Список приложений не очень большой, но ввиду большого количества зависимых пактов, этот модуль получается достаточно больших размеров.
Скрипт 99-u10-office4.sh
#!/bin/sh

. conf/devel.conf
NAME=`echo $0 | sed 's/\.\///'| sed 's/\..*//'`
. lib/mv.sh $NAME

epm2xzm $NAME -i 'java-1.7.0-openjdk LibreOffice4-langpack-ru LibreOffice4-integrated file-roller LibreOffice4 LibreOffice4-gnome
foomatic-db lsof foo2zjs foo2zjs-apps foo2zjs-fwdownloader mozilla-plugin-adobe-flash
mozilla-plugin-mozplugger mozilla-plugin-totem totem-plugins fonts-ttf-ms'

rm -rf $NAME
mkdir $NAME
xzm2dir $NAME.xzm $NAME
rm -rf $NAME/etc/urpmi $NAME/etc/.java

. lib/delhlam.sh $NAME

dir2xzm $NAME $NAME.xzm

. lib/update.sh $NAME



Модуль с утилитами и серверами

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

Кроме собственно установки программ выполняется настройка некоторых элементов операционной системы и установленных программ прямо в установочном скрипте.

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

Несколько слов об используемом наборе программного обеспечения. Программное обеспечение компьютеров не содержит никаких специфичных требований и задач. Корпоративные системы перенесены на уровень интранет приложений. Компьютеры представляют собой тонкие клиенты к ним и «печатные машинки».

Скрипт 99-u20-utils.sh
#!/bin/sh

. conf/devel.conf
NAME=`echo $0 | sed 's/\.\///'| sed 's/\..*//'`
. lib/mv.sh $NAME

epm2xzm $NAME -i 'samba samba-winbind alterator-auth cups-windows samba-client ntpdate ntp-utils
zabbix-agent zabbix-agent-sudo perl-FusionInventory-Agent perl-FusionInventory-Agent-scripts 
perl-Task-FusionInventory perl-Pod-Text-Ansi alterator-fbi alterator-net-iptables italc2-client 
installer-feature-init-italc rsync tcpdump nmap netcat telnet sane sane-server xsane xsane-gimp2 
sane-frontends yagf cuneiform cuneiform-data fonts-otf-gdouros-akkadian aspell-ru-lebedev 
aspell-ru-rk iperf whois rdesktop xfreerdp remmina-plugins sshpass pssh'

rm -rf $NAME
mkdir $NAME
xzm2dir $NAME.xzm $NAME
cp /usr/share/zoneinfo/Asia/Krasnoyarsk $NAME/etc/localtime
cp /etc/nsswitch.conf $NAME/etc/nsswitch.conf
sed -i s/'^hosts:      files mdns4_minimal \[NOTFOUND=return\]*'/'hosts:      files dns mdns4_minimal \[NOTFOUND=return\]  myhostname fallback'/ $NAME/etc/nsswitch.conf
sed -i s/'^# PidFile=\/var'/'PidFile=\/var'/ $NAME/etc/zabbix/zabbix_agentd.conf
sed -i s/'^# EnableRemoteCommands=0'/'EnableRemoteCommands=1'/ $NAME/etc/zabbix/zabbix_agentd.conf
sed -i s/'^LogFileSize='/'# LogFileSize='/ $NAME/etc/zabbix/zabbix_agentd.conf
sed -i s/'127.0.0.1'/'192.168.0.XXX'/ $NAME/etc/zabbix/zabbix_agentd.conf
sed -i s/'^# LogRemoteCommands=0'/'LogRemoteCommands=1'/ $NAME/etc/zabbix/zabbix_agentd.conf
sed -i s/'^Hostname='/'# Hostname='/ $NAME/etc/zabbix/zabbix_agentd.conf
sed -i s/'^# Timeout=3'/'Timeout=30'/ $NAME/etc/zabbix/zabbix_agentd.conf
mkdir $NAME/etc/fusioninventory
echo "server = http://ift.tt/1SvQZbV" > $NAME/etc/fusioninventory/agent.cfg
echo "delaytime = 3600" >> $NAME/etc/fusioninventory/agent.cfg
echo "timeout = 180" >> $NAME/etc/fusioninventory/agent.cfg
echo "logger = File" >> $NAME/etc/fusioninventory/agent.cfg
echo "logfile = /var/log/fusioninventory.log" >> $NAME/etc/fusioninventory/agent.cfg
echo "logfacility = LOG_USER" >> $NAME/etc/fusioninventory/agent.cfg
echo "debug = 3" >> $NAME/etc/fusioninventory/agent.cfg
mkdir $NAME/etc/cron.daily
echo "#!/bin/sh" > $NAME/etc/cron.daily/fusioninventory-agent
echo "" >> $NAME/etc/cron.daily/fusioninventory-agent
echo "/usr/bin/fusioninventory-agent --conf-file=/etc/fusioninventory/agent.cfg" >> $NAME/etc/cron.daily/fusioninventory-agent
chmod +x $NAME/etc/cron.daily/fusioninventory-agent

. lib/delhlam.sh $NAME

dir2xzm $NAME $NAME.xzm
. lib/update.sh $NAME



В нашем случае, в данном конфигурационном файле выполняется настройка timezone, поскольку ее значения отличаются от значений дистрибутива по умолчанию, а стандартная настройка указанных параметров MagOS, в нашем случае не срабатывает правильно (cp /usr/share/zoneinfo/Asia/Krasnoyarsk $NAME/etc/localtime).

Выполняется исправление какой-то странной ошибки в настройках дистрибутива AltLinux, при которой не резолвятся имена компьютеров локальной сети, если имя домена локальной сети оканчивается на local (cp /etc/nsswitch.conf $NAME/etc/nsswitch.conf sed -i s/'^hosts: files mdns4_minimal \[NOTFOUND=return\]*'/'hosts: files dns mdns4_minimal \[NOTFOUND=return\] myhostname fallback'/ $NAME/etc/nsswitch.conf).

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

Выполняется настройка конфигурационного файла fusioninventory-agent, утилиты, работающей совместно с системой автоматизации технической поддержки GLPI и позволяющей выполнять автоматическую инвентаризацию оборудования и программного обеспечения.

Модуль системных настроек

Модуль системных настроек не содержит никаких устанавливаемых программ и представляет собой всего лишь упаковщик для каталога модуля, который и представляет главный интерес.
Скрипт 99-u99-default.sh
#!/bin/sh

. conf/devel.conf
NAME=`echo $0 | sed 's/\.\///'| sed 's/\..*//'`
. lib/mv.sh $NAME

dir2xzm $NAME $NAME.xzm
. lib/update.sh $NAME


В каталоге etc/ модуля находятся следующие каталоги и конфигурационные файлы:
sudoers
-r--------  1 root root   730 Aug 20 15:42 ./sudoers



Внесена строка разрешения выполнения операций sudo администратору без пароля (необходимо для выполнения параллельных операций по протоколу ssh).
X11
./X11:
total 8
drwxr-xr-x 2 root root 4096 Aug 21 12:42 xinit
drwxr-xr-x 2 root root 4096 Aug 21 12:42 xorg.conf.d



Выполнена преднастройка переключателя локали клавиатуры:
etc/X11/xinit/Xkbmap
option grp:alt_shift_toggle -variant , -layout us,ru -model pc104



Выполнена настройка клавиатуры:
etc/X11/xorg.conf.d/00-keyboard.conf
# Read and parsed by systemd-localed. It's probably wise not to edit this file
# manually too freely.
Section "InputClass"
        Identifier "system-keyboard"
        MatchIsKeyboard "on"
        Option "XkbLayout" "us,ru"
EndSection



./apt
total 8
drwxr-xr-x 2 root root 4096 Jun  9 09:45 sources.list.d
drwxr-xr-x 2 root root 4096 Jun  9 09:42 vendors.list.d



Добавлен репозиторий autoimports для установки программы fusioninventory-agent.
etc/apt/sources.list.d/autoimports-p7.list
rpm [cronbuild] http://ift.tt/1RByylq noarch autoimports
/etc/apt/vendors.list.d/autoimports-p7.list 
simple-key "cronbuild" {
        Fingerprint "DE73F3444C163CCD751AC483B584C633278EB305";
        Name "Cronbuild Service <cronbuild@altlinux.org>";
}
simple-key "cronport" {
        Fingerprint "F3DBF34AB0CC0CE638DF7D509F61FBE7E2C322D8";
        Name "Cronport Service <cronport@altlinux.org>";
}



./italc
total 4
drwxr-xr-x 3 root root 4096 Jul 17 18:19 keys



Установлены ключи программы italc.
./lightdm
total 12
-rw-r--r-- 1 root root  909 Jun  8 13:03 lightdm-gtk-greeter.conf
-rw-r--r-- 1 root root 4536 Jun  8 13:18 lightdm.conf



Отключен режим autologin и выполнены дополнительные настройки.
etc/lightdm/lightdm-gtk-greeter.conf
[greeter]
logo=/usr/share/design/current/icons/large/altlinux.png
background=/usr/share/design/current/backgrounds/default.png
icon-theme-name=gnome
show-language-selector=false
show-indicators=a11y;power



etc/lightdm/lightdm.conf
[LightDM]
minimum-vt=7
user-authority-in-system-dir=true
log-directory=/var/log/lightdm
run-directory=/var/run/lightdm
cache-directory=/var/cache/lightdm
xsessions-directory=/etc/lightdm/sessions

[SeatDefaults]
xserver-command=/usr/bin/X
greeter-hide-users=true
session-wrapper=/etc/X11/Xsession

[XDMCPServer]

[VNCServer]


./net
total 8
drwxr-xr-x 3 root root 4096 Jul 16 12:35 ifaces
-rw-r--r-- 1 root root 1987 Jul 16 12:44 sysctl.conf



Выполнена предварительная настройка сетевого интерфейса и фаервола.
./pam.d
total 4
-rw-r----- 1 root root 237 Aug 24 11:28 reboot



etc/pam.d/reboot
auth     required       pam_nologin.so
auth     sufficient     pam_rootok.so
auth     sufficient     pam_console.so
#auth     required      pam_deny.so
auth     required       pam_permit.so
account  required       pam_permit.so
password required       pam_deny.so



./skel
total 16
drwxr-xr-x 8 root root 4096 Jun  8 16:17 Документы
drwxr-xr-x 2 root root 4096 Jun  8 16:17 Загрузки
drwxr-xr-x 2 root root 4096 Jun  8 16:17 Общедоступные
drwxr-xr-x 2 root root 4096 Jun  8 16:17 Рабочий стол



В каталоге skel располагаются файлы, выполняющие предварительную настройку среды и программ пользователя.
./sysconfig:
total 8 -rw-r–r– 1 root root 75 Jun 8 13:11 i18n



etc/sysconfig/i18n
SYSFONT=UniCyr_8x16
LANG=ru_RU.utf8



./systemd
total 8
drwxr-xr-x 5 root root 4096 Aug 20 18:52 system
drwxr-xr-x 2 root root 4096 Aug 20 18:51 user



Выполнены настройки служб, запускаемых по умолчанию или отсутствующих в дистрибутиве.
./xdg
total 4
drwxr-xr-x 2 root root 4096 Jul 17 17:59 iTALC Solutions



Программа Italc используется в конфигурации, отличной от первоначальной. Поскольку изменяются много параметров в конфигурационных файлах, конфигурационные файлы были просто размещены в модуле default.
/etc/xdg/iTALC Solutions/iTALC.conf
[Authentication]
KeyAuthenticationEnabled=1
LogonAuthenticationEnabled=0
LogonGroups="italc-admins,italc-supporters,italc-teachers,italc-students"
PermissionRequiredWithKeyAuthentication=0
PermissionRequiredWithLogonAuthentication=0
PrivateKeyBaseDir=$GLOBALAPPDATA/keys/private
PublicKeyBaseDir=$GLOBALAPPDATA/keys/public
SameUserConfirmationDisabled=0

[DemoServer]
Backend=0
Multithreaded=1

[Logging]
LimittedLogFileSize=0
LogFileDirectory=$TEMP
LogFileSizeLimit=-1
LogLevel=4
LogToStdErr=1
LogToWindowsEventLog=0

[Network]
CoreServerPort=11100
DemoServerPort=11400
FirewallExceptionEnabled=1
HttpServerEnabled=0
HttpServerPort=5800

[Paths]
GlobalConfiguration=$APPDATA/GlobalConfig.xml
PersonalConfiguration=$APPDATA/PersonalConfig.xml
SnapshotDirectory=$APPDATA/Snapshots

[Service]
Arguments=
Autostart=1
HideTrayIcon=0
LockWithDesktopSwitching=1

[VNC]
CaptureLayeredWindows=1
LowAccuracy=1
PollFullScreen=1



Скрипты


При проектировании системы особое внимание уделялось автоматизации администрирования системы, целью которого было снижение трудозатрат на ее последующее сопровождение. С этой целью был создан набор дополнительных скриптов, который можно условно разделить на две категории: скрипты автоматизации обслуживания и администрирования системы и скрипты для создания модулей. Основная часть скриптов обслуживания модулей была описана выше, однако там «за бортом» осталась их библиотечная часть, которая будет рассмотрена здесь.
Дополнения к magos-patches

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

Скрипт /usr/lib/magos/rc.halt/05-update.sh предназначен для автоматического обновления операционной системы при выключении или перегрузке компьютера. Для работы скрипта в конфигурационный файл MagOS.ini добавлено три параметра:
/usr/lib/magos/rc.halt/05-update.sh
# Выключатель автоматического обновления при выключении: Yes, No
AUTOUPDATE=Yes
# Обновление указанных каталогов при выключении: boot -
UPDATE=AltLinux,boot
# Адрес сервера с которого выполняется обновление по rsync
SRCUPDATE=192.168.1.XXX/magos



Здесь: параметр AUTOUPDATE указывает, нужно или нет проводить обновление при выключении питания. UPDATE — перечисление каталогов, для которых должно проводиться обновление.

SRCUPDATE — адрес сервера и репозиторий, с которого проводится обновление. Адрес может задаваться как IP-адрес, или как имя DNS сервера.
Эти же параметры участвуют при установке операционной системы на компьютер пользователя.

Исходный код скрипта:

/usr/lib/magos/rc.halt/05-update.sh
#!/bin/bash
# Initial script for MagOS-Linux Live operating system
# This script are launching before starting init from linux-live script.
# Current dir always must be set to root (/)
# All system path must be relative, except initrd dirs

export PATH=.:/:/usr/sbin:/usr/bin:/sbin:/bin

ENABLED=yes

. /mnt/live/liblinuxlive
[ -f /etc/sysconfig/MagOS ] && . /etc/sysconfig/MagOS
#. etc/sysconfig/MagOS

[ "$ENABLED" != "yes" ] && exit 0
[ "$AUTOUPDATE" != "Yes" or "$AUTOUPDATE" != "yes" ] && exit 0
[ -z "$UPDATE" -a -z "$SRCUPDATE" ] && exit 0
[ -z "$(grep changes /memory/cmdline)" ]  && exit 0
[ -n "$(grep 'from=http:' /memory/cmdline)" ]  && exit 0

if ! [ -z "$UPDATE" ] ;then
    for dirs in $(echo $UPDATE | tr ',;' ' ') ;do
        rsync -azr --delete --exclude=MagOS.ini http://rsync$SRCUPDATE/$dirs/ /mnt/livemedia/$dirs/
    done
fi



Выполнено исправление существующего скрипта /usr/lib/magos/rc.preinit.d/21-ntp, выполняющего настройку NTP сервера. По всей видимости эта часть является зависимой именно от дистрибутива AltLinux, поэтому возможно его исправление в дальнейшем.
/usr/lib/magos/rc.preinit.d/21-ntp
#!/bin/bash
# Initial script for MagOS-Linux Live operating system
# This script are launching before starting init from linux-live script.
# Current dir always must be set to root (/)
# All system path must be relative, except initrd dirs

export PATH=.:/:/usr/sbin:/usr/bin:/sbin:/bin

ENABLED=yes
[ "$ENABLED" != "yes" ] && exit 0

DEBUGMODE=no
. /liblinuxlive  2>/dev/null || . /mnt/live/liblinuxlive
. /livekitlib  2>/dev/null
debug_mode "$0" "$@"

. etc/sysconfig/MagOS

if  ! [ -z "$NTPSERVERS" ] ;then
    sed -i s/'^server'/'#server'/ etc/ntp.conf
    sed -i s/'^server'/'#server'/ etc/ntpd.conf
    for a in $(echo $NTPSERVERS | tr ',;' ' ') ;do
        sed -i '/^driftfile/ s/^/server '"$a"\\n/ etc/ntp.conf
        grep -q "restrict $a" etc/ntp.conf || echo "restrict $a noquerry notrap" >> etc/ntp.conf
        sed -i s/'^#listen on 127.0.0.1'/'listen on 127.0.0.1'/ etc/ntpd.conf
        echo "server $a" >> etc/ntpd.conf
    done
fi



Скрипт установки ОС

Установка ОС при помощи специального скрипта позволяет достичь единообразия при всех инсталляциях, устранить ошибки техников, выполняющих эту работу и многократно ускорить этот процесс.

Скрипт /usr/share/magos/install/magosinstall.sh является специализированным. Выполняет создание и форматирование разделов жесткого диска. Выполняет копирование разделов репозитория с сервера magos-server на локальные диски компьютера и установку загрузчика ОС.

/usr/share/magos/install/magosinstall.sh
#!/bin/bash
#
#Usage:
# $1 - source catalog: magos testing
# Default is magos

SRC="magos"
. /etc/sysconfig/MagOS

SRCINI=$(echo $SRCUPDATE | cut -d "/" -f 2)
if ! [ -z "$SRCINI" ];then
    SRC=$SRCINI
fi
if ! [ -z "$1" ] ;then
    SRC=$1
fi
echo "-------------------------------------------------------"
echo "INSTALL MagOS Altlinux FROM HARD DISK from $SRC !!!"
echo "                                           ========="
echo
echo "Press Enter to continue, or Ctrl+C to abort..."
read junk
clear
swapoff -a
echo "======================================================="
echo "Create parition table."
parted -s /dev/sda mklabel msdos
parted -s /dev/sda mkpart primary ext3 1 30000
parted -s /dev/sda mkpart primary linux-swap 30000 36000
parted -s /dev/sda mkpart primary ext3 36000 100%
parted -s /dev/sda toggle 1 boot
echo "-------------------------------------------------------"
echo "======================================================="
echo "Make file systems on /dev/sda1."
mkfs.ext3 -L system /dev/sda1
echo "Make file systems on /dev/sda2."
mkswap /dev/sda2
echo "Make file systems on /dev/sda3."
mkfs.ext3 -L data /dev/sda3
echo "-------------------------------------------------------"
echo "======================================================="
mkdir /media/system && mount /dev/sda1 /media/system
mkdir /media/data && mount /dev/sda3 /media/data
echo "Syncing instalation data."

for dirs in $(echo $UPDATE | tr ',;' ' ') ;do
    srv=$(echo $SRCUPDATE | cut -d '/' -f 1)
    if [ "$dirs" != "boot" ] ;then
            mkdir /media/system/$dirs
            rsync -azr --delete http://rsync$srv/$SRC/$dirs/ /media/system/$dirs/
            mkdir /media/data/$dirs-Data
            rsync -azr --delete http://rsync$srv/$SRC/$dirs-Data/ /media/data/$dirs-Data/
        else
            mkdir /media/system/$dirs
            rsync -azr --delete http://rsync$srv/$SRC/$dirs/ /media/system/$dirs/
        fi
    done

rm -rf /media/system/lost+found /media/data/lost+found
cd /media/system/boot/
bash ./Install_MagOS.bat
$(sync)
umount /dev/sda1 && rmdir /media/system
umount /dev/sda3 && rmdir /media/data
echo "-------------------------------------------------------"
echo "Instalation is OK."
echo "please reboot computer."



Из текста скрипта Вы видите, что на основной раздел, в который устанавливается ОС, выделено 30Gb, на swap – 6 Gb, а остальное отведено под раздел с данными, включая Changes и Home. Пока не используются переменные, позволяющие выносить эти параметры в конфигурационный файл или, хотя бы, в начало файла.

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

Скрипты включения в AD

Включение компьютера в ADS реализовано по разному в различных операционных системах. AltLinux, для выполнения этой задачи, создали свою программу. Однако ее функционал довольно большой и для упрощения выполнения этой операции были реализованы собственные скрипты. Скриптов два, один выполняет подключение в консольном режиме, второй использует язык TCL и работает в графическом режиме. Рекомендуется пользоваться консольным режимом, хотя графический тоже был протестирован.

После установки операционной системы компьютер имеет hostname=MagOS. При массовой установке компьютеров это критично, тем более, если компьютер включается в AD. Поэтому скрипты решают сразу две задачи: переименование компьютера и включение его в AD.

Небольшое замечание: имя компьютера не определяется в файле MagOS.ini, а заносится при помощи этих скриптов сразу в файл /etc/sysconfig/magos. Все прочие скрипты MagOS, запускаемые при старте операционной системы используют имя компьютера из файла /etc/sysconfig/magos.

/usr/share/magos/ad_join/ad_join.pl
#!/usr/bin/perl -w
# MagOS-linux.ru
# Author M.Fiskov

use strict;
#use Glib qw/TRUE FALSE/;
#use Gtk3 '-init';
my $hostname='';
my $username='';
my $password='';
my $domain='mydomain';
my $realm='mydomain.local';

for (my $i=0;$i<=$#ARGV;$i++){
    $_=$ARGV[$i];
    (/^--help$/) && do {&usage(); exit 0};
    (/^--hostname=/) && do { ($hostname=$ARGV[$i])=~s/^--hostname=//; };
    (/^-h$/) && do {$hostname=$ARGV[$i+1];$i++;};
    (/^--password=/) && do { ($password=$ARGV[$i]) =~s/^--password=//; };
    (/^-p$/) && do {$password=$ARGV[$i+1];$i++;};
    (/^--username=/) && do { ($username=$ARGV[$i]) =~s/^--username=//; };
    (/^-u$/) && do {$username=$ARGV[$i+1];$i++;};
}
if (open (F1,"</etc/altlinux-release") ) {
    close F1;
    &addname($hostname) && system("system-auth write ad $realm $hostname $domain $username $password")  && system("systemctl restart nm
b") && system("systemctl restart winbind") && system("systemctl restart smb");
    &wins();
    &winbind();
}else{
    &addname($hostname) && system("net join -U \"".$username.'%'.$password."\"") && system("systemctl restart nmb") && system("systemct
l restart winbind") && system("systemctl restart smb");
    &wins();
    &winbind();
};
#system("/sbin/reboot");
exit;

sub usage(){

print "Join this computer from MagOS to Active Directory Service (ADS)
-h or --hostname=       Computer name
-u or --username=       ADS administrator user name
-p or --password=       ADS administrator password
--help                  This usage
"
}

sub wins(){
    my @wins=split("\"",`/usr/bin/wbinfo -P`);
    system ("sed -i '/wins server = /d' /etc/samba/smb.conf");
    my $wsed=sprintf("sed -i \'/\\[global\\]/ s/\$/\\nwins server = ".$wins[1]."\'/ /etc/samba/smb.conf");
    system ($wsed);
    my $wgroup=sprintf("net groupmap add ntgroup=\"администраторы домена\" unixgroup=wheel rid=512 type=d");
    system ($wgroup);
    $wgroup=sprintf("net groupmap add ntgroup=\"пользователи домена\" unixgroup=wheel rid=513 type=d");
    system ($wgroup);
    $wgroup=sprintf("net groupmap add ntgroup=\"гости домена\" unixgroup=wheel rid=514 type=d");
    system ($wgroup);
}

sub addname (){
    my ($hostname)=@_;
    system ("sed -i '/netbios name =/d' /etc/samba/smb.conf");
    my $ssed=sprintf("sed -i \'/\\[global\\]/ s/\$/\\n   netbios name = ".$hostname."\'/ /etc/samba/smb.conf");
    system ($ssed);
    system ("sed -i '/HOSTNAME/d' /etc/sysconfig/MagOS");
    $ssed=sprintf("echo \"HOSTNAME=$hostname\" >>/etc/sysconfig/MagOS");
    system ($ssed);
    system("hostnamectl set-hostname $hostname");
    return 1;
}

sub winbind(){
    system ("sed -i s/'server string ='/';server string ='/ /etc/samba/smb.conf");
    system ("sed -i '/idmap backend = /d' /etc/samba/smb.conf");
    my $wsed=sprintf("sed -i \'/\\[global\\]/ s/\$/\\nidmap config $domain : backend = ad\'/ /etc/samba/smb.conf");
    system ($wsed);
    system ("sed -i '/winbind cache time /d' /etc/samba/smb.conf");
    $wsed=sprintf("sed -i \'/\\[global\\]/ s/\$/\\nwinbind cache time = 1440\'/ /etc/samba/smb.conf");
    system ($wsed);
}



Следует обратить внимание, что имя домена и realm указаны напрямую в скрипте в переменных $domain и $realm. Возможно, это не самое правильное решение…
/usr/share/magos/ad_join/ad_join_x.pl
#!/usr/bin/perl -w
# MagOS-linux.ru
# Author M.Zaripov
# No testing

use strict;
use Glib qw/TRUE FALSE/;
use Gtk3 '-init';

#standard window creation, placement, and signal connecting
my $window = Gtk3::Window->new('toplevel');
$window->signal_connect('delete_event' => sub { Gtk3->main_quit; });
$window->set_border_width(5);
$window->set_position('center_always');

#this vbox will geturn the bulk of the gui
my $vbox = &ret_vbox();

#add and show the vbox
$window->add($vbox);
$window->show();

#our main event-loop
Gtk3->main();

sub ret_vbox {
  my $vbox = Gtk3::VBox->new(FALSE,5);
  $vbox->pack_start ("Gtk3::Label"->new (" Please input password to join into domain "), 0, 0, 0);

  # create table with 2 entries
  my $table1 = Gtk3::Table->new (5, 2, FALSE);

  my $t1l0 = Gtk3::Label->new_with_mnemonic("Domain: ");
  $t1l0->set_alignment (0, 0);
  $table1->attach_defaults ($t1l0, 0, 1, 0, 1);
  my $t1e0 = Gtk3::Entry->new();
  $table1->attach_defaults ($t1e0, 1, 2, 0, 1);

  my $t1l0 = Gtk3::Label->new_with_mnemonic("workgroup: ");
  $t1l0->set_alignment (0, 0);
  $table1->attach_defaults ($t1l0, 0, 1, 1, 2);
  my $t1e1 = Gtk3::Entry->new();
  $table1->attach_defaults ($t1e0, 1, 2, 1, 2);

  my $t1l0 = Gtk3::Label->new_with_mnemonic("computer name: ");
  $t1l0->set_alignment (0, 0);
  $table1->attach_defaults ($t1l0, 0, 1, 2, 3);
  my $t1e2 = Gtk3::Entry->new();
  $table1->attach_defaults ($t1e0, 1, 2, 2, 3);

  my $t1l1 = Gtk3::Label->new_with_mnemonic("Domain Admin User Name: ");
  $t1l1->set_alignment (0, 0);
  $table1->attach_defaults ($t1l1, 0, 1, 3, 4);
  my $t1e3 = Gtk3::Entry->new();
  $table1->attach_defaults ($t1e1, 1, 2, 3, 4);
  my $t1l2 = Gtk3::Label->new_with_mnemonic("Domain Admin Password: ");
  $t1l2->set_alignment (0, 0);
  $table1->attach_defaults ($t1l2, 0, 1, 4, 5);
  my $t1e4 = Gtk3::Entry->new();
  $t1e2->set_visibility (FALSE);
  $table1->attach_defaults ($t1e2, 1, 2, 4, 5);
  $vbox->pack_start($table1, 0, 0 ,0);

  #$vbox->pack_end(Gtk3::HSeparator->new(),0, 0 ,0);
  # create table with 2 buttons
  my $table2 = Gtk3::Table->new (1, 2, FALSE);
  my $t2b1 = Gtk3::Button->new ('Join');
  $table2->attach_defaults ($t2b1, 0, 1, 0, 1);
  my $t2b2 = Gtk3::Button->new ('Cancel');
  $table2->attach_defaults ($t2b2, 1, 2, 0, 1);
  $t2b2->signal_connect (clicked => sub { Gtk3->main_quit; });
if (open (F1,"</etc/altlinux-release") ) {
close F1;
  $t2b1->signal_connect (clicked => sub { &addname($t1e2->get_text())
  || system("system-auth write ad ".$t1e0->get_text().'%'.$t1e0->get_text()." ".#domain
  $t1e2->get_text().'%'.$t1e2->get_text()." ".# hostname
  $t1e1->get_text().'%'.$t1e1->get_text()." ".# workgroup
  $t1e3->get_text().'%'.$t1e3->get_text()." ".# username
  $t1e4->get_text().'%'.$t1e4->get_text()."\"") # password
  || system("systemctl restart nmb")
  || system("systemctl restart winbind")
  || system("systemctl restart smb")
  || exit (1); });
}else{
  $t2b1->signal_connect (clicked => sub { &addname($t1e2->get_text()) #hostname
  || system("net join -U \"".
  $t1e3->get_text().'%'. # username
  $t1e4->get_text()."\"")  # password
  || system("systemctl restart nmb")
  || system("systemctl restart winbind")
  || system("systemctl restart smb")
  || exit (1); });
}
  $vbox->pack_start($table2, 0, 0 ,0);

  $vbox->show_all();

  return $vbox;
}

sub addname (){
my ($hostname)=@_;
system ("sed -i '/netbios name =/d' /etc/samba/smb.conf");
my $ssed=sprintf("sed -i \'/\\[global\\]/ s/\$/\\n   netbios name = ".$hostname."\'/ /etc/samba/smb.conf");
system ($ssed);
system ("sed -i '/HOSTNAME/d' /etc/sysconfig/MagOS");
$ssed=sprintf("echo \"HOSTNAME=$hostname\" >>/etc/sysconfig/MagOS");
system ($ssed);
system("hostnamectl set-hostname $hostname");
return 1;
}



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

У нас после подключения компьютера к ADS обнаружилась такая проблема: после загрузки компьютера winbind достаточно долго выполняет заполнение кэшей с информацией об AD, включая списки пользователей и группы. При этом он может зависнуть и по своему внутреннему расписанию перезапуститься, но на это может уходить много времени — до получаса. Проблема может быть связана с организацией внутренней структуры AD, а может и быть общей для всех реализаций. Известно только, что в достаточно пустой базе AD проблем не возникает. Однако у нас такая проблема была и для ее решения был изобретен приведенный ниже «костыль».

Опытным путем было выявлено, что решить проблему помогает перезапуск winbind после обнаружения им домена AD. Поэтому приведенная ниже программа запускается в качестве службы через systemd, мониторит обнаружение winbind домена, после чего выполняет рестарт winbind и завершает работу. Задержка составления списка пользователей, в этом случае, составляет 1 — 2 минуты. Если пользователь не очень быстро вводит свое имя и пароль, то он, практически, не замечает существования этой проблемы.

На этот же скрипт возложена еще одна функция — обновление системных файлов домашних папок пользователей AD, поскольку существующие в magos-patches скрипты обновляют домашнюю папку только одного пользователя, указанного в MagOS.ini — администратора системы, а обновление папок виртуальных пользователей не предусмотрено вообще. Скрипт выполняет копирование отсутствующих в домашних папках доменных пользователей скрытых системных файлов и каталогов из папки /etc/skel и назначение им прав хозяина папки. Таким образом, для обновления настроек в домашних папках пользователей нужно удалить соответствующие файлы и перегрузить службу скриптом winbind-restart. Необходимым условием корректности работы является отсутствие регистрации пользователей в системе в указанный момент.

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

/usr/sbin/winbind-restart
#!/bin/bash

export PATH=.:/:/usr/sbin:/usr/bin:/sbin:/bin

while [ "$(wbinfo --online-status | grep -i mydomain | cut -d ":" -f 2)" != " online" ]
do
    $(sleep 1)
done
$(systemctl restart winbind)

. etc/sysconfig/MagOS

# update home folders from domain users
if [ "$UPDATEHOME" = "yes" ] ;then
    DOMAIN=$(wbinfo --own-domain)
    if [ -d home/$DOMAIN ] ;then
        for LISTUSER in $(ls -1 home/$DOMAIN/); do
            $(cp -rHun etc/skel/.[a-zA-Z0-9]* home/$DOMAIN/$LISTUSER/)
            $(chown -R $LISTUSER:пользователи\ домена home/$DOMAIN/$LISTUSER/)
        done
    fi
fi



/etc/systemd/user/winbindrestart.service
[Unit]
Description=Samba Winbind Daemon restart from mydomain
After=winbind.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/winbind-restart

[Install]
WantedBy=multi-user.target



Управление системой (/root/bin)

Эти скрипты предназначены для управления системой MagOS на основе дистрибутива AltLinux при промышленном применении.
Если по каким-либо причинам требуется принудительное обновление ОС, то применяются два нижеследующих скрипта. Первый из них выполняет обновление операционной системы, второй обновление конфигурационного файла MagOS.ini.

Скрипт принудительного обновления операционной системы /root/bin/updatemagos.sh. После выполнения операции следует автоматическая перегрузка операционной системы.

/root/bin/updatemagos.sh
#!/bin/bash

echo "Update MagOS from Hard Disk to this computer!!!"
echo
echo "Press Enter to continue, or Ctrl+C to abort..."
read junk
clear

export PATH=.:/:/usr/sbin:/usr/bin:/sbin:/bin

SRC=192.168.1.XXX/magos
DEFAULT="AltLinux,boot"

. /mnt/live/liblinuxlive
[ -f /etc/sysconfig/MagOS ] && . /etc/sysconfig/MagOS

[ -z "$UPDATE" -a -z "$SRCUPDATE" ] && UPDATE=$(echo "$DEFAULT") && SRCUPDATE="$SRC"

if  ! [ -z "$UPDATE" ] ;then
    for dirs in $(echo $UPDATE | tr ',;' ' ') ;do
        rsync -azr --delete http://rsync$SRCUPDATE/$dirs/ /mnt/livemedia/$dirs/
    done
fi

reboot



/root/bin/updateini.sh
#!/bin/bash

# This script getting file MagOS.ini from magos server to this computer

SRCI="magos-server/magos"
SRC="magos"
srv="magos-server"
. /etc/sysconfig/MagOS
export PATH=.:/:/usr/sbin:/usr/bin:/sbin:/bin

echo "Update MagOS.ini from Hard Disk!!!"

[ -z "$SRCUPDATE" ] && SRCUPDATE="$SRCI"

SRCINI=$(echo $SRCUPDATE | cut -d "/" -f 2)

[ -n "$SRCUPDATE" ] && srv=$(echo $SRCUPDATE | cut -d '/' -f 1)
if ! [ -z "$SRCINI" ];then
    SRC=$SRCINI
fi
if ! [ -z "$1" ] ;then
    SRC=$1
fi

rsync -az  http://rsync$srv/$SRC/AltLinux/modules/i586/MagOS.ini  /mnt/livemedia/AltLinux/modules/i586/MagOS.ini



/root/bin/mnt.sh — достаточно простой вспомогательный скрипт подключения диска с данными пользователей и сохранениями операционной системы.
/root/bin/mnt.sh
#!/bin/sh

# mount data disk from /srv

mount /dev/sda3 /srv
#groupadd -g 501 magos
#groupadd -g 502 testing



Скрипты loadupdate и saveupdate предназначены для загрузки на тестовую машину скриптов генерации модулей и оригиналов модулей с сервера MagOS и последующей их выгрузки на сервер после внесения в них каких либо изменений.
/root/bin/saveupdate.sh
#!/bin/bash
#
# This script save update folder from this computer to magos server
# Fiskov M.M.

export PATH=.:/:/usr/sbin:/usr/bin:/sbin:/bin

ENABLED=yes
[ "$ENABLED" != "yes" ] && exit 0

. /usr/lib/magos/scripts/liblinuxlive
. /etc/sysconfig/MagOS
. /mnt/livemedia/update/conf/devel.conf

PWD=$(echo $(pwd))

cd /mnt/livemedia/update
mkdir /root/tmp/update
cp -r /mnt/livemedia/update/{*.sh,lib,conf} /root/tmp/update/
for files in $(echo $(ls 9*.sh| cut -d '.' -f 1)) ;do
    cp /mnt/livemedia/$DISTNAME/modules/$ARCH/$files.xzm /root/tmp/update/
done
cd /root/tmp
tar -czf /mnt/livemedia/update.tar.gz ./update
cd /mnt/livemedia/
scp /mnt/livemedia/update.tar.gz $USER@$SERVER:/var/www/$DISTTYPE/
rm -rf /root/tmp/update
cd $PWD



/root/bin/loadupdate.sh
#!/bin/bash
#
# This script load update folder from magos server to this computer
#

export PATH=.:/:/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/magos/scripts

ENABLED=yes
[ "$ENABLED" != "yes" ] && exit 0
PWD=$(echo $(pwd))
. /etc/sysconfig/MagOS

rsync -az http://rsync$SRCUPDATE/update.tar.gz /mnt/livemedia/update.tar.gz
cd /mnt/livemedia/
tar -xzf update.tar.gz
cd /mnt/livemedia/update
for files in $(echo $(ls 9*.sh| cut -d '.' -f 1)) ;do
    mkdir $files
    xzm2dir $files.xzm $files
done

cd $PWD



Скрипты обслуживания ОС

В процессе обслуживания операционной системы возникают задачи массового обслуживания компьютеров, например выполнения команд на всех компьютерах сразу. Один из вариантов решения такой задачи — параллельный доступ на компьютеры по протоколу ssh и запуск там соответствующей команды. Скрипт hostalt-create.sh не является универсальным, он предназначен для составления списка компьютеров, на которых установлен MagOS на основе сборки AltLinux. Для обнаружения компьютеров они должны иметь наименование, заканчивающееся на «-a». Список компьютеров является сохраняемым, что позволяет его постепенно расширять.

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

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

/root/bin/hostalt-create.sh
#!/bin/sh

# create list from hostalt file from programm pssh

$(nmap 192.168.1.0/24 -p T:8080 2>&1 | grep "mydomain" | grep '\-a' | cut -d " " -f 5 >> /tmp/hostalt1)
$(sort -u /tmp/hostalt1 > hostalt)



/root/bin/parallelssh.sh
#!/bin/sh

# parallelssh.sh sudo /root/bin/updateini.sh <password>

echo "parallelssh.sh <\"command\"> <password>"
echo "parallelssh.sh sudo \"/root/bin/updateini.sh\" <password>"
echo

[ -z "$2" ] && exit

sshpass -p $2 pssh -x "-o StrictHostKeyChecking=no" -h hostalt -l altlinux -A -i $1 2>&1 > /tmp/ssherr.txt
cat /tmp/ssherr.txt



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

Скрипты управления созданием модулей предназначены для автоматизации создания пользовательских модулей и их размещения на magos-server.
Скрипты представляют собой комплекс программ, содержащий конфигурационный файл и libexec библиотеки скриптов, автоматизирующие процесс и, собственно, самих скриптов, выполняющих сборку модулей, чистку полученных модулей от мусора и настойку программ модулей.

Скрипты и конфигурационный файл располагаются в корневом каталоге диска, но могут помещаться в любое удобное место. Обязательное условие — файловая система, на которой они располагаются должна поддерживать Unix ACL, т. е. должна быть Posix совместимой.

Конфигурационный файл системы сборки модулей /update/conf/devel.conf.

DISTTYPE="testing"
DISTNAME="AltLinux"
ARCH="i586"

UPDETESRV=yes
SERVER=192.168.0.3
USER=altlinux


Здесь:

DISTTYPE — указывает на каталог репозитория magos-server.
DISTNAME — Название каталога дистрибутива.
ARCH — Архитектура системы.
Все три указанных выше параметра определяют путь в котором должна находиться папка с модулями на сервере и в компьютере.
UPDETESRV — выполнять или нет автоматическое обновление репозитория сервера. SERVER — адрес или URL сервера magos-server.
USER — имя пользователя magos-server, имеющего права на запись в репозиторий. В домашней папке этого пользователя должны быть созданы симлинки на соответствующие папки репозиториев.

Пример скрипта создания модуля:

/update/99-u50-example.sh
#!/bin/sh

. conf/devel.conf
NAME=`echo $0 | sed 's/\.\///'| sed 's/\..*//'`
. lib/mv.sh $NAME

epm2xzm $NAME -i 'ntpdata samba'

rm -rf $NAME
mkdir $NAME
xzm2dir $NAME.xzm $NAME

#-------------------------------------------
# Конфигурирование установленных пакетов
cp /usr/share/zoneinfo/Asia/Krasnoyarsk $NAME/etc/localtime
cp /etc/nsswitch.conf $NAME/etc/nsswitch.conf
sed -i s/'^hosts:      files mdns4_minimal \[NOTFOUND=return\]*'/'hosts:      files dns mdns4_minimal \[NOTFOUND=return\]  myhostname f
#--------------------------------------------

. lib/delhlam.sh $NAME
dir2xzm $NAME $NAME.xzm
. lib/update.sh $NAME



Скрипт, вызываемый из собственно скрипта создания модуля, выполняющий очистку мусора, оставшегося после сборки модуля командой epm2xzm.
/update/lib/delhlam.sh
#!/bin/sh

NAME=$1

rm -rf $NAME/etc/urpmi $NAME/var/cach/ldconfig $NAME/var/cach/ldconfig/ $NAME/var/cache/ldconfig/ $NAME/var/lib/apt $NAME/var/log/rpmpk
gs
rm -f $NAME/etc/ld.so.cache $NAME/etc/resolv.conf
rm -f $NAME/etc/xinetd.conf $NAME/etc/group- $NAME/etc/gshadow- $NAME/etc/passwd-



Скрипт отключает модуль c настройками и делает резервную копию старого модуля.

Технология резервирования модулей следующая: мы руками переименовываем модули, которые собираемся менять, добавляя расширение .bak. В последующем мы можем несколько раз запускать скрипт создания модуля, исправляя возникшие ошибки. Каждый раз предыдущая версия модуля будет переименовываться и будет получать расширение .old. После завершения работы ненужные копии нужно будет удалить.

/update/lib/mv.sh
#!/bin/sh

NAME=$1
. conf/devel.conf

if [ $NAME != "99-u99-default" ] ;then
    $(sh /usr/lib/magos/scripts/deactivate $NAME.xzm)
fi
if [ -f /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm.bak ] && [ -f /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm ] ;then
    $(mv -nf /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm $NAME.xzm.old)
fi



Скрипт переносит модуль в каталог modules и копирует его в сетевой репозиторий. Устанавливает права доступа и выполняет переподключение модуля.
/update/lib/update.sh
#!/bin/bash

NAME=$1
. conf/devel.conf

if [ ! -f /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm.bak ] ;then
    $(mv /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm.bak)
fi

$(mv $NAME.xzm /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm)
$(chmod 664 /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm)
$(chown :root /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm)

if [ $NAME != "99-u99-default" ] ;then
    $(sh /usr/lib/magos/scripts/activate $NAME.xzm)
else
    $(sh /usr/lib/magos/scripts/deactivate $NAME.xzm)
    $(sh /usr/lib/magos/scripts/activate $NAME.xzm)
fi

[ "$UPDETESRV" != "yes" ] && exit 0
$(scp /mnt/livemedia/$DISTNAME/modules/$ARCH/$NAME.xzm $USER@$SERVER:~/$DISTTYPE/$DISTNAME/modules/$ARCH/)



Дополнительные скрипты, исправляющие работу программ magos и операционной системы

В MagOS не совсем верно реализован механизм подключения папок /tmp и /var/tmp в файловую систему tmpfs. Имеется Unit systemd для подключения /tmp в файловую систему tmpfs, однако он не включен. Для его включения выполнено размещение символической ссылки в каталоге /etc/systemd/system/local-fs.target.wants:
tmp.mount -> /lib/systemd/system/tmp.mount
var-tmp.mount -> ../var-tmp.mount


Unit var-tmp.mount просто не реализован. MagOS же при включении опции VARTMPFS делает символьную ссылку /var/tmp → /tmp. Для сервера печати CUPS это не допустимо, поэтому соответствующий unit приходится реализовывать самостоятельно.
/etc/systemd/system/var-tmp.mount
[Unit]
Description=Temporary Directory
Documentation=man:hier(7)
Documentation=http://ift.tt/1w0RChK
DefaultDependencies=no
Conflicts=umount.target
Before=local-fs.target umount.target

[Mount]
What=tmpfs
Where=/var/tmp
Type=tmpfs
Options=mode=1777,strictatime



Unit для подключения каталога /var/tmp в файловую систему tmpfs.
/etc/systemd/system/ntp-units.d/ntpd.service
[Unit]
Description=Network Time Service
After=syslog.target network.target

[Service]
EnvironmentFile=/etc/sysconfig/ntpd
ExecStart=/usr/sbin/ntpd -d $NTPD_ARGS

[Install]
WantedBy=multi-user.target



Инструкция для техников


Установка Alt Linux в режиме MagOS:
  • Выбрать в bios загрузку из сети.
  • Загрузить AltLinux.
  • Зарегистрироваться пользователем «altlinux» используя пароль по умолчанию.
  • Если требуется сохранение данных пользователей, то подключиться к сети и сохранить данные в сеть. Все данные на жестком диске будут уничтожены!
  • Открыть консоль.
  • Получить права администратора:
$ su -
Password:


При вводе пароля символы не отображаются. Это нормально. Используется пароль, принятый по умолчанию для сети mydomain:
  • Выполнить команду:
# /usr/share/magos/install/magosinstall.sh


  • Для установки системы придется трижды нажать клавишу «Enter». Имеется возможность отказаться от установки на первом этапе, нажав ^C.
  • Перегрузить компьютер. Если перегрузки не происходит, то выполнить аппаратный сброс.
  • Зайти в среду пользователя под пользователем altlinux.
  • Открыть консоль.
  • Получить права администратора, как было описано выше.
  • Ввести компьютер в домен при помощи команды:
# /usr/share/magos/ad_join/ad_join.pl -h <hostname> -u <username> -p <password>


Где:

hostname — Имя компьютера. Одновременно производится переименование компьютера.
username — Имя администратора домена mydomain.local.
password — Пароль администратора домена mydomain.local.

При правильном выполнении команды будет написано:

Joined 'Имя компьютера' to dns domain 'mydomain.local'


На прочие сообщения можно не обращать внимание.
Вынос ярлыков на рабочий стол

На нужной программе щелкнуть правой кнопкой мыши. Отправить ярлык на рабочий стол. При необходимости открыть свойства ярлыка и вписать необходимые параметры.

Авторы: Горошкин Антон Николаевич, Фисков Михаил Михайлович.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.