Статью публикую по просьбе знакомого, не имеющего пока аккаунта на Хабре, если у кого есть лишний инвайт, — просьба поделиться evgeny.sementsov [песик] gmail.com
Идея написать свои «напильниковые труды» родилась после прочтения статьи об использовании 7Zip для бэкапа данных.
Нужно сказать, что идея «изобрести велосипед» пришла не просто так. На начальном этапе все-таки планировалось использование внешнего средства.
Были следующие требования, рожденные опытом:
Самые близкие из более-менее подходящих решений — Cobian Backup, Amanda.
Я думаю они подойдут в большинстве случаев. Но не в нашем. Я забыл сказать, что у нас нет возможности развертывания MSI-пакетов при помощи средств групповых политик Active Directory. Мы уже давно используем SAMBA. Т.к. создавать альтернативные тихие установщики с прошитыми внутрь нужными настройками и шаманить затем с psexec (для тихой установки под правами администратора), мягко говоря, не хотелось, то на ум пришла очевидная мысль — почему бы не попробовать использовать те мощные средства, что и так в кармане? В целом неплохой язык Javascript, зашитый в недра WSH, а также мощный бесплатный консольный архиватор 7Zip.
От рассуждений перейдем к делу.
Начнем поэтапно.
1) Как определить когда какому пользователю делать бэкап.
Т.е. сделать так чтобы канал остался не забитым в течение дня (у многих одни только почтовые папки занимают по несколько гигабайт).
С некоторой некритичной погрешностью можно считать, что лучше всего распределить все время поровну между всеми пользователями.
Будем считать, что бэкапы должны производиться с 10 до 16. Итак, на samba-сервере считаем количество всех пользователей и распределяем временные интервалы. Для этого нужно запустить сервис winbindd.
Итак, set_intervals_for_backup.sh.
Нужно правильным образом прописать запуск этого скрипта в cron.
2) Как создавать бэкапы.
В smb.conf нужно прописать запуск logon.bat для всех пользователей.
А в нем в свою очередь:
backups_v2.js:
3) Как управлять выполнением бэкапов.
Я написал простой скрипт для пользователей.
backups_STOP_NOW.js:
4) Как управлять хранилищем с бэкапами.
Количество места на нашем сервере позволило хранить 3 последних бэкапа.
Сейчас я приведу скрипт, который запускается с админской машины, который очищает «лишние» бэкапы.
Его нужно элементарно засунуть в «Планировщик задач» и запускать под пользователем, который имеет права записи в папке с хранилищем бэкапов.
clear_old_backups.js:
5) Подводим итоги
Итак, в итоге мы достигли именно того, чего хотели. Бэкапы делаются в формате .zip, который откроется любой программой-архиватором.
Надеюсь проблем с поиском 7za.exe на сайте 7-zip.org у вас не возникнет.
В нашем случае пользователь сам может достать нужные файлы из бэкапа, что (в случае отсутствия админов) тоже является плюсом.
Жду ваших комментариев! Это мой первый пост на хабре и надеюсь не последний. К сожалению, у меня нет аккаунта и написать комментариев (на текущий момент) я не могу.
Идея написать свои «напильниковые труды» родилась после прочтения статьи об использовании 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 у вас не возникнет.
В нашем случае пользователь сам может достать нужные файлы из бэкапа, что (в случае отсутствия админов) тоже является плюсом.
Жду ваших комментариев! Это мой первый пост на хабре и надеюсь не последний. К сожалению, у меня нет аккаунта и написать комментариев (на текущий момент) я не могу.