Статистика

Участников проекта 105
Опубликовано статей 78
Отчет по карме. Топ 20

Новости блога

1 29.11.2013  Сегодня самым активным участникам newblog'а был выплачен доход с sape.
7 02.11.2012  Ура! Свешилось, нашему сайту дали тИЦ 10. Спасибо всем кто принимает участие в развитии нашего блога.
8 21.08.2012  Интеграция с sape.ru. Теперь каждый автор статей на newblog автоматически зарабатывает на рекламе.
Все новости

Топ 5 категорий

Программирование 46
Операционные системы 9
Базы данных 4
Туризм 2
Заметки 2

Последние 5 заметок (90)

gullyar - Закладки gullyar
gullyar - Ваша первая закладка
osadchaya - Закладки osadchaya
Ira0231188 - Закладки Ira0231188
Ira0231188 - Закладки Ira0231188

Ссылки

www.freedev.asia

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

23.12.2011 22:47 | Просмотров: 1927 | Доход: 92.96 руб. | Комментариев: 8
[Программирование] 
Рейтинг: 5/1

Уже давно пользуюсь самодельной программой для снятия бэкапов с удаленных серверов к себе на съемный диск. Попытаюсь объяснить в чем изюминка этого способа. Разумеется бэкапы снимаются по крону и складываются, но удобно иметь пару последних версий у себя на винте. Весь процесс несложно автоматизировать и с помощью bash, но когда серверов много, они разные и на них крутится разный приклад - то самое время задуматься об универсальности способа. В кратце приемущества этого метода:

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

1. Основные принципы работы с программой:

Программа запускается как cgi:
php backup.php
И требует секретное слово для начала работы:

#############################
###  gmbackups v1.0
#############################
Введите пароль:[введенный пароль]
###  Выберите действие:
###  1. Расшифровать весь конфиг
###  2. Зашифровать весь конфиг
###  3. Снять бэкапы
#############################

1, 2 - шифрует и дешифрует пароли в конфигурационном файле к программе по секретному слову введенному ранее. За счет этого достигается крайне высокая степень зашиты информации. (Т.к. нет никаких сравнений с хешем в самом скрипте и всю программу целиком вместе с конфигом можно спокойно хранить где угодно).
3 - позволяет снять бэкапы в автоматическом режиме
Также программа позволяет принимать входные переменные arg[0] - секретное слово, arg[1] - действие.
php backup.php qwerty 1
Это сделано для удобства помещения конструкции в /etc/crontab, в случае если есть сервер который бэкапит у себя приклады и бд с других серверов по защищенному каналу ssh.

2. Конфигурационный файл

Ключи, которые допустимо использовать в конфиге:
ip - ip         -удаленного сервера с открытым sshd
crypt - статус     -зашифрован ли пароль в данный момент или нет
initX         - действия выполняемые (1-на сервере) (0-локально)


Константы, которые можно использовать в конфиге в initX:
+dbpassword+    - пароль к базе данных
+dir+         - временная директория софтины
+label+        - временная метка
+server+        - название конфигурации
+mount+        - точка монтирования

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

;основные настройки:
[main]
iv= - первоначально пустое значение
md5=  - первоначально пустое значение
;директория куда будут складываться бэкапы(например съемный винт или другой сервак)
mount=/home/gm/dumps
;список всех конфигруаций(для шифрования паролей)
allconfig=server1@server2@server3
;список конфигураций учавствующих в процессе бэкапа
backupconfig=server1@server3
;отображать все init события
showinit=1

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

[server1]
ip=95.95.95.95
login=root
password=пароль ssh
dbpassword=пароль на базу данных
crypt=1
init1=1***/usr/bin/mysqldump -uroot -p+dbpassword+ server1 > +dir++label+.sql
init2=1***/bin/gzip +dir++label+.sql
init3=1***cp +dir++label+.sql.gz /var/www/
init4=0***mkdir +mount+/+server+
init5=0***mkdir +mount+/+server+/+server+_+label+
init6=0***wget -P +mount+/+server+/+server+_+label+/ http://ip/+label+.sql.gz
init7=1***rm /var/www/+label+.sql.gz
init8=1***tar cf /var/www/+label+.tar -C /var/www podhome
init9=1***/bin/gzip /var/www/+label+.tar
init10=0***wget -P +mount+/+server+/+server+_+label+/ http://ip/+label+.tar.gz
init11=1***rm /var/www/+label+.tar.gz

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

3. Исходники:

Сам скрипт состоит всего из одного класса Backup, который содержит следующие методы:

3.1: Метод выполнения ssh команды на удаленном сервере:

    function __exec_ssh ($con,$command){
      if (!$stream = ssh2_exec($con, $command)){
        die ("error : ".$command);
      }else{
        stream_set_blocking($stream, true);
        $data = "";
        while($o = fgets($stream)){
          $data .= $o;
        }
        fclose($stream);
      }
      return $data;
  }

 

3.2: Шифрование паролей в конфиге:


 

function encrypt($ini,$all_sever=null,$key=null){
        $iv = mcrypt_create_iv (mcrypt_get_iv_size (MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND);
      foreach($all_sever as $server){
          $string = $ini->read($server,'password','');
          $dbpassword = $ini->read($server,'dbpassword','');
          $crypt = $ini->read($server,'crypt','');
          if($crypt==0){
                $encrypted_string = mcrypt_encrypt (MCRYPT_RIJNDAEL_256, $key, $string, MCRYPT_MODE_CBC, $iv);
                $encrypted_dbpassword = mcrypt_encrypt (MCRYPT_RIJNDAEL_256, $key, $dbpassword, MCRYPT_MODE_CBC, $iv);
                $res=bin2hex($encrypted_string);
                $res_db=bin2hex($encrypted_dbpassword);
                $ini->write($server,'password',$res);
                $ini->write($server,'dbpassword',$res_db);
                $ini->write($server,'crypt','1');
                `echo "$server...encrypt" >> log.txt`;
          }
      }
      $ini->write('main','md5',md5($key));
      $ini->write('main','iv',bin2hex($iv));
        $ini->updateFile();
        return true;
  }

3.3: Дешифрование паролей в конфиге:

 

  function decrypt($ini,$all_sever=null,$key=null,$one=false){
      $iv = $ini->read('main','iv','');
      $iv=pack('H*', $iv);
      if(!$one){
          foreach($all_sever as $server){
              $string = $ini->read($server,'password',''); $string=pack('H*', $string);
              $dbpassword = $ini->read($server,'dbpassword',''); $dbpassword=pack('H*', $dbpassword);
              $crypt = $ini->read($server,'crypt','');
              if($crypt==1){
                    $string = mcrypt_decrypt (MCRYPT_RIJNDAEL_256, $key, $string, MCRYPT_MODE_CBC, $iv);
                    $dbpassword = mcrypt_decrypt (MCRYPT_RIJNDAEL_256, $key, $dbpassword, MCRYPT_MODE_CBC, $iv);                
                    $ini->write($server,'password',trim($string));
                    $ini->write($server,'dbpassword',trim($dbpassword));
                    $ini->write($server,'crypt','0');
                    $ini->updateFile();
                    `echo "$server...decrypt" >> log.txt`;
              }
          }
      }else{
          $crypt = $ini->read($all_sever,'crypt','');
          $string = $ini->read($all_sever,'password','');
          $dbpassword = $ini->read($all_sever,'dbpassword','');
          if($crypt==1){
              $string=pack('H*', $string); $dbpassword=pack('H*', $dbpassword);
                $string = mcrypt_decrypt (MCRYPT_RIJNDAEL_256, $key, $string, MCRYPT_MODE_CBC, $iv);
                $dbpassword = mcrypt_decrypt (MCRYPT_RIJNDAEL_256, $key, $dbpassword, MCRYPT_MODE_CBC, $iv);                
          }
          return array('password'=>trim($string),'dbpassword'=>trim($dbpassword));
      }
      $ini->write('main','md5','');
      $ini->updateFile();
      return true;
  }

3.4 Основной метод для бэкапа:

  function start($all_server=null,$obj,$all=null,$settings=null,$argv=null){
      $label=date('d.m.Y_H.i.s',time());
      $ini = new TIniFileEx('config.ini');
     
      echo "#############################\n";
      echo "###  gmbackups v1.0\n";
      echo "#############################\n";
     
      if(empty($argv[1])){
          echo "Введите пароль:";
             $password=trim(fgets(STDIN));
      }else{
          $password=trim($argv[1]);
      }
        $md5=$ini->read('main','md5','');
        $mount=$ini->read('main','mount','');
        if(!empty($md5))if($md5!=md5($password)){ echo "Не верный пароль\n"; exit;}
        `echo "\r\n-------" >> log.txt`; `date >> log.txt`;
        if(empty($argv[2])){
          echo "###  Выберите действие:\n";
          echo "###  1. Расшифровать весь конфиг\n";
          echo "###  2. Засшифровать весь конфиг\n";
          echo "###  3. Снять бэкапы\n";
          echo "#############################\n";
            $action = trim(fgets(STDIN));
        }else{
            $action = trim($argv[2]);
        }
       
        switch ($action) {
            //расшифровать весь файл
            case 1: if($obj->decrypt($ini,$all_server,$password)) echo "конфиг успешно расшифрован\n"; break;
            case 2: if($obj->encrypt($ini,$all_server,$password)) echo "конфиг успешно зашифрован\n"; break;
            case 3:
                $start_time = microtime(true);
                foreach($all as $server){
                    echo "############# ".$server." ##############\n";
                    `echo "-------$server---------" >> log.txt`;
                    $ip=$ini->read($server,'ip','');
                        if($ip=='localhost'){
                            $con=true;
                        }else{
                            $con = ssh2_connect($ini->read($server,'ip',''),22);
                        }
                        if ($con) {
                            $tpass=$obj->decrypt($ini,$server,$password,true);
                            if($ip!='localhost'){
                                ssh2_auth_password($con,'root',$tpass['password']);
                                `echo "Подключение к $server.... ok" >> log.txt`;
                                $res=$this->__exec_ssh($con,'mkdir /home/gmbackup_'.$label);
                                `echo "Директория для сборки.... ok" >> log.txt`;
                            }else{
                                `echo "Определение локального сервера.... ok" >> log.txt`;
                            }
                            for($i=1;$i<100;$i++){
                                $init=$ini->read($server,'init'.$i,'');
                                if(!empty($init)){
                                    $mass=explode('***',$init);
                                    $init=$mass[1];
                                    $init=str_ireplace('+dbpassword+', trim($tpass['dbpassword']), $init);
                                    $init=str_ireplace('+dir+', '/home/gmbackup_'.$label.'/', $init);
                                    $init=str_ireplace('+label+', $label, $init);
                                    $init=str_ireplace('+server+', $server, $init);
                                    $init=str_ireplace('+mount+', $mount, $init);
                                    if($settings['showinit']==1) echo "init".$i.": ".$init."\n";
                                    //удаленно
                                    if($mass[0]==1){
                                        if($ip!='localhost'){
                                            $res=$this->__exec_ssh($con,$init);
                                            if(!empty($res)){`echo "res: $res" >> log.txt`; echo $res;}
                                        }else{
                                            `echo "нельзя выполнить удаленные команды локально :)" >> log.txt`;                                           
                                        }
                                    //локально
                                    }else{
                                        `$init`;
                                    }
                                }
                            }
                            `echo "init(all)....ok" >> log.txt`;
                            if($ip!='localhost'){
                                $res=$this->__exec_ssh($con,'rm -rf /home/gmbackup_'.$label);
                                `echo "Удаление директории для сборки.... ok" >> log.txt`;
                            }
                        }else{
                            `echo "Подключение к $server.... no" >> log.txt`;
                        }
              }
          echo "happy end :)\n";
          $exec_time = microtime(true) - $start_time;
            echo "Время снятия указанных бэкапов : ".$exec_time;
            break;
        }
  }

 

Создание экземпляра класса происходит примерно так:

include './scripts/ini.php';
$info = parse_ini_file('config.ini', true);
$all=explode('@', $info['main']['allconfig']);
$for_backup=explode('@', $info['main']['backupconfig']);
$settings['showinit']=$info['main']['showinit'];
$obj = new Backup;
$obj->start($all,$obj,$for_backup,$settings,$argv);

 

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

Закрытая часть

Чтобы просмотреть закрытую часть и получить консультацию от самого автора статьи необходимо зарегистрироваться!

© GM
| Комментировать статью |
  • WTP +711 (04.01.2012 16:12)
    Вот по этому куску кода у меня вопрос
    
       Создание экземпляра класса происходит примерно так:
       include './scripts/ini.php';
    
    Коли уж мы говорим про пхп - include получает адрес файла в скобках, а уж потом в кавычках. Если мне память ни с кем не изменяет).
    Кроме всего прочего - я попытался засунуть все это хозяйство в крон (дебиан 6). Оно не сработало). Запускаю руками из папки с файлом - работает. Ухожу на уровень выше - сругнулось именно на этот include. Решение - записать так: include('scripts/ini.php');
    
    Кстати о птичках. Не мешало бы добавить форматирование сообщений. Вдруг я захочу розовый текст? )))
    | Ответить |
    • WTP +711 (04.01.2012 16:13)
      Ну вот(( Все в кучу. Там энтеры на каждом углу...
      | Ответить |
      • GM +2587 (04.01.2012 16:25)
        include -  подключает и выполняет указанный файл, а экземпляр класса создается строкой ниже ($obj = new Backup;)
        На счет скобок сомневаюсь что именно они сыграли решающую роль. Скорее всего был указан абсолютный путь а не относительный.
        P.S.: Зачем в комментах энтеры? С ними будет не красиво :)
        
        | Ответить |
        • WTP +711 (04.01.2012 16:27)
          По поводу скобок - я просто удивился, что синтаксис команды отличается от привычного мне. Фиг его знает, мож и будет без скобок работать. А вот ./ перед именем файла убрать скорее всего придется.
          | Ответить |
        • WTP +711 (04.01.2012 16:28)
          Ну и про энтеры - вот надо мне чето выделить - а нету! Как быть? ХТМЛ порежет?
          | Ответить |
          • GM +2587 (04.01.2012 16:30)
            Cмотря что :)
            | Ответить |
  • WTP +711 (24.01.2012 11:30)
    Вернемся к нашим баранам.
    Пытался заставить запускаться скрипт из кронтаба в дебиане - нихатит.
    Опытным путем выяснил, что если запускать его ручками из консоли из родной папки - все прекрасно работает. А вот из другой папки - уже обломчеГ. Это натолкнуло на мысль проверить как у нас дела с путями. Расставил по коду эхи основных переменных и выяснил - некоторые пустые. Копнул глубже - оказалось таки в путях дело.

    Решил проблему так:
    $realpath = realpath(dirname(__FILE__))."/";
    В переменную складываю абсолютный путь до скрипта и присобачиваю ее ко всем подключениям файлов примерно таким макаром.
    include $realpath.\'scripts/ini.php\';
    $info = parse_ini_file($realpath.\'config.ini\', true);
    Это не все места, где я менял, в середине где-то еще есть одна строчка вроде.
    После такого допилинга скрипт наконец-то запустился автоматически из крона, что меня несказанно обрадовало!
    | Ответить |
    • WTP +711 (24.01.2012 11:36)
      Извиняюсь за лишние символы в коде - это не я, оно само пришло). Знающий разберется я думаю.

      Нащяльнике! Сделай таки уже обработку\отображение энтеров! Пипец неудобно после каждой строчки BR вставлять.
      | Ответить |