Pull to refresh

Использование 7zip для бэкапа данных. Продолжение

Reading time 8 min
Views 13K
Статью публикую по просьбе знакомого, не имеющего пока аккаунта на Хабре, если у кого есть лишний инвайт, — просьба поделиться evgeny.sementsov [песик] gmail.com

Идея написать свои «напильниковые труды» родилась после прочтения статьи об использовании 7Zip для бэкапа данных.
Нужно сказать, что идея «изобрести велосипед» пришла не просто так. На начальном этапе все-таки планировалось использование внешнего средства.
Были следующие требования, рожденные опытом:
  • бесплатность
  • копирование в фоновом режиме, незаметно для пользователя
  • возможность полноценно работать на компьютере во время выполнения бэкапа (т.е. выполнение бэкапа не должно критически сказаться на производительности)
  • возможность гибкой настройки целей копирования (от файлов, директорий до реестра), пользователей, которым нужно делать бэкап
  • возможность для пользователя управлять выполнением бэкапа (т.е. прервать, — это в крайнем случае), возможность для администратора узнать — целый ли бэкап.
  • «умное бэкапирование», т.е. возможность на ходу (при указании четких критериев) решать что копировать, а что нет. Например, не копировать профиль The Bat!, если есть профиль Thunderbird.
  • возможность управлять частотой бэкапов для различных пользователей
  • распределение времени бэкапа по рабочему времени, дабы не забивать канал на сервере бэкапов
  • возможность управлять количеством бэкапов, дабы не заполнить сервер бэкапов полностью
  • легкость нахождения нужного бэкапа и извлечения нужных файлов


Самые близкие из более-менее подходящих решений — Cobian Backup, Amanda.
Я думаю они подойдут в большинстве случаев. Но не в нашем. Я забыл сказать, что у нас нет возможности развертывания MSI-пакетов при помощи средств групповых политик Active Directory. Мы уже давно используем SAMBA. Т.к. создавать альтернативные тихие установщики с прошитыми внутрь нужными настройками и шаманить затем с psexec (для тихой установки под правами администратора), мягко говоря, не хотелось, то на ум пришла очевидная мысль — почему бы не попробовать использовать те мощные средства, что и так в кармане? В целом неплохой язык Javascript, зашитый в недра WSH, а также мощный бесплатный консольный архиватор 7Zip.

От рассуждений перейдем к делу.
Начнем поэтапно.

1) Как определить когда какому пользователю делать бэкап.

Т.е. сделать так чтобы канал остался не забитым в течение дня (у многих одни только почтовые папки занимают по несколько гигабайт).
С некоторой некритичной погрешностью можно считать, что лучше всего распределить все время поровну между всеми пользователями.
Будем считать, что бэкапы должны производиться с 10 до 16. Итак, на samba-сервере считаем количество всех пользователей и распределяем временные интервалы. Для этого нужно запустить сервис winbindd.
Итак, set_intervals_for_backup.sh.

#!/bin/sh

all=`wbinfo -u|wc -l`
wbinfo -u | awk -v all=$all 'BEGIN {i=0;}; {i++; j=int(360*i/all); h=10+int(j/60); m=j%60; print $1" "h" "m}' > /usr/samba/netlogon/intervals.txt

На выходе получим что-то вроде:
andrey 10 2
boris 10 4
boriska 10 6
cidor 10 8
...


Нужно правильным образом прописать запуск этого скрипта в cron.

2) Как создавать бэкапы.

В smb.conf нужно прописать запуск logon.bat для всех пользователей.
А в нем в свою очередь:

start wscript \\sambaserver\netlogon\backups_v2.js

backups_v2.js:

//---------------------------------------------- НАСТРОЙКИ ->
// smb-путь хранилища бэкапов
var path_for_saving = "\\\\backupserver\\backup\\profiles\\";

// задаем массив тех пользователей, у которых огромный профиль, но нет нужды его бэкапить каждый день
users = new Array();
users.push('fed');
users.push('sol');
users.push('lanis');
users.push('solod');
//---------------------------------------------- <- НАСТРОЙКИ

// функция для определения - нужно ли выполнять бэкап в данный день для пользователя (реализация задания частоты бэкапов)
function findU_andExit(thisUser)
{
d = new Date;
if (d.getDate() % 5 != 0)
return;
for (i = 0; i < users.length; i++) // 0,1,2,3,4 possible but it will be only in case "0"
if (users[i] == thisUser)
WScript.Quit();
}

// функция для создания случайного числа из диапозона
function rand(vmin, vmax)
{
if (vmax) {
return Math.floor(Math.random() * (vmax - vmin + 1)) + vmin;
} else {
return Math.floor(Math.random() * (vmin + 1));
}
}

// функция для создания "красивого" номера, т.е. вместо 9 имеем "09".
function d_2(p)
{
var tmp = 0;
if (p)
tmp = (p < 10) ? "0"+p : p;
else
tmp = '00';
return tmp;
}

// функция получения даты в формате ДД.ММ.ГГГГ
function get2FDate()
{
d = new Date();
s = d_2(d.getDate())+'.'+d_2(d.getMonth()+1)+'.'+d.getYear();
return s;
}

// подключаем средства WMI
WMI = GetObject("winmgmts:");
// ищем процесс с заданным именем и задаем ему низкий приоритет
function lowPrior(procName)
{

result = WMI.ExecQuery("SELECT * FROM Win32_Process Where Name = '"+procName+"'");
var IDLE = 64;
var colCS = new Enumerator(result);
for (; !colCS.atEnd(); colCS.moveNext())
colCS.item().SetPriority(IDLE);
}
// функция, которая смотрит сколько выполняется процессов с заданным именем
function countProc(procName)
{
result = WMI.ExecQuery("SELECT * FROM Win32_Process Where Name = '"+procName+"'");
var colCS = new Enumerator(result);
var j = 0;
for (; !colCS.atEnd(); colCS.moveNext())
j++;
return j;
}

// инициализация
var WshShell = WScript.CreateObject("WScript.Shell")
var FSO = new ActiveXObject("Scripting.FileSystemObject");
var WshProcEnv = WshShell.Environment("PROCESS");
// узнаем имя компьютера
var cn = WshProcEnv("COMPUTERNAME");
// имя пользователя
var un = WshProcEnv("USERNAME");
// путь для сохранения - в папку пользователя в директории для бэкапов на сервере бэкапов
// предполагается, что папки пользователей созданы с нужными разрешениями
// это можно сделать при помощи все того же winbindd - вот вам и HOMEWORK
// не забудьте прописать в nsswitch.conf winbind
// а если база пользователей еще и в LDAP - то ldapsearch вам в руки (именно так мы и делали)
path_for_saving += un;

// проверяем - нужно ли делать бэкап в этот день
findU_andExit(un);

// путь к профилю
var up = WshProcEnv("USERPROFILE");

// -------------------- ОПРЕДЕЛЕНИЕ ВРЕМЕНИЯ НАЧАЛА БЭКАПА ->
// переменные для хранения времени - часы и минуты
var h1=0;
var m1=0;

ForReading = 1;
f_intervals = FSO.OpenTextFile('\\\\sambaserver\\netlogon\\intervals.txt', ForReading, false);

// регулярное выражение для поиска
var re = new RegExp("("+un+")\\s(\\d+?)\\s(\\d+?)","ig");
while (!f_intervals.AtEndOfStream)
{
s = f_intervals.ReadLine();
if ((re.exec(s)) != null)
{
h1 = RegExp.$2 * 1;
m1 = RegExp.$3 * 1;
}
}
f_intervals.Close();

// если не нашли в файле, то задаем случайно, используем диапазон от 10 до 16 часов
// эта ситуация вполне реально, если мы только-только завели пользователя в базе
if (h1==0 && m1==0)
{
h1 = rand(10,15);
m1 = rand(0,59);
}

// проверяем нужно ли начать делать бэкап прямо сейчас
function chkDate()
{
d = new Date();
if (h1==d.getHours() && m1==d.getMinutes())
return true;
else
return false;
}

// -------------------- <- ОПРЕДЕЛЕНИЕ ВРЕМЕНИЯ НАЧАЛА БЭКАПА

// путь до папки "Рабочий стол"
strDesktop = WshShell.SpecialFolders("Desktop");
// путь до папки "Мои документы"
strMyDocuments = WshShell.SpecialFolders("MyDocuments");
// путь до папки, где хранятся данные программ, в частности The Bat!, Thunderbird.
strAppData = up + "\\Application Data";
strTheBat = strAppData + "\\The Bat!";
strThunderBird = strAppData + "\\Thunderbird";
// QIP у нас находится в корне профиля.
strQIP = up + "\\QIP";

// в этой переменной укажем все папки для создания бэкапа
var saved_dirs = '';
// определяем имя завершившегося бэкапа
var saveFN = path_for_saving+"\\"+get2FDate()+'_from_'+cn+'.zip';
// определяем имя для бэкапа (временное)
var tmp_saveFN = saveFN+'.1';

while (1)
{
// с промежутком в 5 сек проверяем - пора ли начинать бэкап
WScript.Sleep(5000);
if (chkDate()) // если наступило время для бэкапа
{
// указываем, что будем бэкапить
saved_dirs += "\""+strDesktop+"\" \""+strMyDocuments+"\"";

// если по какой-то причине файл бэкапа существует - удаляем
try
{
if (FSO.FileExists(tmp_saveFN))
FSO.DeleteFile(tmp_saveFN, true);
}
catch(e)
{
}

// дополнительно бэкапим QIP, если такая папка есть; также бэкапим почту, если не Thunderbird, то The Bat!
if (FSO.FolderExists(strQIP))
saved_dirs += " \""+strQIP+"\"";
if (FSO.FolderExists(strThunderBird))
saved_dirs += " \""+strThunderBird+"\"";
else
{
if (FSO.FolderExists(strTheBat))
saved_dirs += " \""+strTheBat+"\"";
}

// бэкапим реестр пользователя
WshShell.Run("regedit /ea "+path_for_saving+"\\"+get2FDate()+"_HKCU.reg HKEY_CURRENT_USER", 0, false);

// запускаем бэкап - с небольшим сжатием (чтобы не напрягать ЦП), не дожидаясь выполнения и в скрытом режиме
// подробно останавливаться на всех параметрах не будем - они описаны в той самой статье про использование 7Zip.
WshShell.Run("\\\\sambaserver\\netlogon\\7za.exe a -tzip "+tmp_saveFN+' '+saved_dirs+' -ssw -mx1 -mmt -mm=Deflate', 0, false);

// процесс запустился - подождем и поменяем приоритет
WScript.Sleep(7500);
lowPrior('7za.exe');

while (1)
{
// через секунду проверяем - есть ли процесс 7za
// важная ремарка - вы можете еще запускать процессы 7za,
// просто в этом случае скрипт, возможно, ошибочно будет думать, что бэкап выполняется, что в целом, нестрашно,
// т.к. в конечном итоге процесс ведь все равно завершится
WScript.Sleep(1000);
if (countProc('7za.exe') == 0)
{
try
{
// бэкап завершился, можно переименовать в нормальное имя
if (FSO.FileExists(saveFN))
FSO.DeleteFile(saveFN, true);
FSO.MoveFile(tmp_saveFN, saveFN);

}
catch(e)
{
}

// бэкап окончен - пора на выход
WScript.Quit();
}
}
}
}


3) Как управлять выполнением бэкапов.

Я написал простой скрипт для пользователей.
backups_STOP_NOW.js:

var WMI = GetObject("winmgmts:");
var WshShell = WScript.CreateObject("WScript.Shell");

var IMname = "7za.exe";
var result = WMI.ExecQuery("SELECT * FROM Win32_Process Where Name = '"+ IMname + "'");
var j = 0;
var colCS = new Enumerator(result);
for (; !colCS.atEnd(); colCS.moveNext())
j++;

if (j>0)
{
var BtnCode = WshShell.Popup("Вы действительно хотите остановить выполнение рез. копии?", 0, "Резервное копирование профиля", 4 + 32);
switch(BtnCode)
{
case 6: //yes
WshShell.Run("taskkill /IM " + IMname);
break;
}
}

4) Как управлять хранилищем с бэкапами.

Количество места на нашем сервере позволило хранить 3 последних бэкапа.
Сейчас я приведу скрипт, который запускается с админской машины, который очищает «лишние» бэкапы.
Его нужно элементарно засунуть в «Планировщик задач» и запускать под пользователем, который имеет права записи в папке с хранилищем бэкапов.
clear_old_backups.js:

fso = new ActiveXObject("Scripting.FileSystemObject");

// функция для сравнения дат
function diffDates(a1, a2)
{
//31.08.2009 => 20090831
a1 = fso.GetBaseName(a1);
a2 = fso.GetBaseName(a2);

var t1, t2;
t1 = a1.substr(6,4)+a1.substr(3,2)+a1.substr(0,2);
t2 = a2.substr(6,4)+a2.substr(3,2)+a2.substr(0,2);
t1 = t1 - 0; t2 = t2 - 0;
if (t2 > t1)
return 1;
else
return 0;
}
// функция для обхода папок с бэкапами
function ShowFolderList(folderspec)
{
var f, fc;
var p;

var ar;
var foldername = fso.GetFileName(folderspec);

// смотрим бэкапы .zip и файлы реестра .reg
// в нашем случае, сюда попадут и "битые" бэкапы
var r = new Array(/\.zip/i, /\.reg/i);

f = fso.GetFolder(folderspec);

// получаем файлы с нужным разрешением и запихиваем их в массив
// массивов, стало быть, будет два
for (k in r) // for .zip, .reg
{
ar = new Array();
fc = new Enumerator(f.Files);

for (; !fc.atEnd(); fc.moveNext())
{
p = fc.item();
if (r[k].test(p))
ar.push(p);
}
// необходимость этой строки обусловлена тем,
// что элементами массива, вообще говоря, являются не строки
ar = (ar.toString()).split(",");

// сортируем по дате создания массив имен файлов
var tmp;
for (i=0; i < ar.length-1; i++)
for (j=i+1; j < ar.length; j++)
if (diffDates(ar[i],ar[j]))
{
tmp = ar[i];
ar[i] = ar[j];
ar[j] = tmp;
}

// оставляем только 3 бэкапа
for (i=3; i < ar.length; i++) //0, 1, 2 will be
{
try
{
fso.DeleteFile(ar[i], true);
}
catch(e)
{
}
}
}
}

// задаем имя папки с бэкапами и начинаем "шпарить"
var d = fso.GetFolder("\\\\backupserver\\backup\\profiles");

fc = new Enumerator(d.SubFolders);
for (; !fc.atEnd(); fc.moveNext())
{
ShowFolderList(fc.item());
}


5) Подводим итоги
Итак, в итоге мы достигли именно того, чего хотели. Бэкапы делаются в формате .zip, который откроется любой программой-архиватором.
Надеюсь проблем с поиском 7za.exe на сайте 7-zip.org у вас не возникнет.
В нашем случае пользователь сам может достать нужные файлы из бэкапа, что (в случае отсутствия админов) тоже является плюсом.

Жду ваших комментариев! Это мой первый пост на хабре и надеюсь не последний. К сожалению, у меня нет аккаунта и написать комментариев (на текущий момент) я не могу.
Tags:
Hubs:
+30
Comments 20
Comments Comments 20

Articles