Дело было вечером, делать было уже пора что-то с этим со всем…
Зайду из далека
Жил-был у меня CRS125-24G-1S-RM от одного неудачного проекта. На тот момент, я уже много чего слышал о сабже и его родителях, но как то юзать не решался, ибо forwarding на моём какинтоше и точка тупо-Link меня тогда вполне себе грели. Но пришло время собрать силы в кулак и…. распаковать зверушку. Юзаж ограничился добавлением masquerade в NAT, что превратило сабж в маршрутизатор, плюс пару штрихов и всё взлетело буквально в два клика, после чего последовали долгие месяцы любви и взаимопонимания.
Русскоязычный онлайн-курс по MikroTik от нашего коллеги Дмитрия Скромнова. Здесь можно изучить MikroTik и RouterOS самостоятельно по курсу «Настройка оборудования MikroTik». Курс основан на официальной программе MTCNA, но содержит больше информации. Это 162 видеоурока и большая практическая задача, разбитая на 45 лабораторных работ. Время на изучение неограниченно – все материалы передаются бессрочно и их можно пересматривать сколько нужно. Первые 25 уроков можно посмотреть бесплатно, оставив заявку на странице курса.
Спустя неопределённое время, мой пытливый ум и руки полезли в скрипты RouterOS. Уже не припомню с какого перепуга мне понадобилось бэкапить конфиг, но тем не менее именно эта задача познакомила меня со скриптами на MikroTik’е, что в корне поменяло моё отношение к железу этой мамки.
Задача
Задача стояла достаточно просто, срезать бэкапы с системы в виде файла скрипта и файла бэкапа, плюс скрипты отдельных секций, таких как firewall, route пр. Все файлы нужно бережно складывать на плешку в заданное время с почтовым голубем извещением и аттачем основного бэкапа. По достижению заданного срока давности, старые бэкапы нужно было сносить.
Первые грабли
Как долго я удалял старые бэкапы руками… Это пожалуй бесило даже больше, чем отсутствие в скриптах RouterOS привычных мне функций join()
и split()
. Однако и это решаемо, в отличие от бага с полем from=" "
в /tool email
и отсутствием возможности множественных аттачментов к письму. Для справки: Письма не ходят с произвольно заполненным или отсутствующим полем from=" "
(v6.37.4). Поле нужно указать именно вот так from=" "
с пробелом в кавычках или from="<используемая@учётная.запись>"
либо указать ящик на который отправляется почта.
Рецепт
Для лучшего восприятия моих потуг, весь скрипт будет распилен на части с пояснениями как и чего работает. Для удобства я сохраняю множество секций отдельными скриптами. Мне так было нужно и удобно, кому не пригодится, можно выпилить.
Для начала объявляем переменные:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | :local history "7d"; # Как долго храним бэкапы. Указываем в секундах минутах часах или днях (s/m/h/d) :local email "mailn@example.com" # Куда спамить результатами :local pathForBackups "disk1/backup/"; # Куда писать основной бэкап :local pathForConfigs "disk1/etc/config/"; # Куда сохранять конфиги секций :local pathForScripts "disk1/etc/script/"; # Куда сохранять бэкап скриптов :local configsArray {"ip route";"ip firewall";"ip pool";"ip dhcp-server";"interface"}; # Перечисляем секции, # которые будут бэкапиться по отдельности в виде скриптов. Пишем их полностью так, как они звучат до # команды "export". К примеру ip route export - в массив вписываем только ip route :local nme [/system identity get name]; # Берём имя машинки :local scriptName "Backup"; # Имя скрипта :local mailSubject ([/system identity get name]."::".$scriptName); # Формируем тему для письма :local cdte ([/system clock get date]." ".[/system clock get time]); # Дата и время log info "$scriptName: Starting..."; # Логируем старт /tool e-mail send from=" " to=$email subject=$mailSubject body="$mailSubject:: $cdte:: Started."; # Спамим о старте beep frequency=1975 length=100ms; # Бип о начале delay 150ms; |
Функция конвертирует дату и время формата Feb/25/2017 14:18:12
в два варианта, первый — это количество секунд со дня творения каледраля от рождества Христова (это нам нужно для вычисления возраста файла) и второй — вот такого вида 25-02-2017-18-43
для формирования удобоваримых имён файлов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | :local datr do={ :local dfy 0; :local yar [pick $fdte 7 11]; :local mth [pick $fdte 0 3]; :local day [pick $fdte 4 6]; :local hur [pick $fdte 12 14]; :local min [pick $fdte 15 17]; :local sec [pick $fdte 18 20]; :local mts ("jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"); :local mtd {0;31;28;31;30;31;30;31;31;30;31;30;31}; :local mtn ([find $mts $mth -1]+1); :local yrd (($yar*365)+($yar / 4)); :local cmd ($mtd->$mtn); :local conA $mtn; do { set $conA ($conA-1); set $dfy ($dfy+($mtd->$conA)); if ($conA=0) do={set $dfy ($dfy+$day);} } while ($conA>0); if ($mtn<=9) do={set mtn ("0".$mtn);} return {((($yrd+$dfy)*86400)+($hur*3600)+($min*60)+$sec);($day."-".$mtn."-".$yar."-".$hur."-".$min)}; } |
Функция преобразовывает заданный период хранения бэкапов в секунды.
1 2 3 4 5 6 7 8 9 10 | :local hgen do={ :local rlt; :local dgt [pick $hist 0 ([len $hist]-1)]; :local ltr [pick $hist ([len $hist]-1) [len $hist]]; if ($ltr="s") do={set $rlt $dgt;} if ($ltr="m") do={set $rlt ($dgt*60);} if ($ltr="h") do={set $rlt ($dgt*3600);} if ($ltr="d") do={set $rlt ($dgt*86400);} return $rlt; } |
Функция удаляет старые бэкапы путём выборки по файлам и выполнения простого действия: если сегодняшняя дата, минус дата создания файла, больше чем заданный период, то файл удаляется.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | :local delo do={ :local far [/file find name~"@".$nme]; :local conB ([len $far]); do { set $conB ($conB-1); :local ftm ([$datr fdte=[/file get number=($far->$conB) value-name=creation-time]]->0); :local bdte ([$datr fdte=[/file get number=($far->$conB) value-name=creation-time]]->1); if (($cdte-$ftm)>($hgen-10)) do={ # Отнимаем 10 секунд от значения, иначе самый старый бекап не будет удаляться. log info "$scriptName: Deleting old backups: $bdte"; /file remove number=($far->$conB); beep frequency=1568 length=10ms; delay 100ms; } else={ beep frequency=1975 length=10ms; delay 100ms; } } while ($conB>0); } |
Функция создаёт скрипты для каждой из перечисленных в массиве секций {"ip route";"ip firewall";"ip pool";"ip dhcp-server";"interface"}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | :local cspt do={ :local cbn; :local conC ([len $cba]); do { set $conC ($conC-1); beep frequency=1975 length=1000ms; set $cbn [($cba->$conC)]; log info "$scriptName: Create $cbn script"; execute "/$cbn export file=\"$pfc$cbn.rsc\""; beep frequency=1975 length=10ms; log info "$scriptName: Done"; delay 100ms; } while ($conC>0); } |
После успешного объявления переменных и функций, весело звучим, создаём шаблон имени для файлов бэкапа, бэкапимся и удаляем старые файлы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | beep frequency=1975 length=100ms; delay 1s; :local bfn ($pathForBackups.([$datr fdte=$cdte]->1)."@".$nme); # Шаблон имени файлов log info "$scriptName: Create system backup"; # Основной бэкап /system backup save name=($bfn.".backup"); beep frequency=1975 length=10ms; log info "$scriptName: Done"; delay 100ms; log info "$scriptName: Create system script"; # Основной бэкап в скрипт /export compact file=($bfn.".rsc"); beep frequency=1975 length=10ms; log info "$scriptName: Done"; delay 100ms; log info "$scriptName: Create Scripts script"; # Бэкап скриптов /system script export file=($pathForScripts."scripts.rsc"); beep frequency=1975 length=10ms; log info "$scriptName: Done"; delay 100ms; [$cspt pfc=$pathForConfigs cba=$configsArray scriptName=$scriptName] # Бэкапим заданные секции [$delo nme=$nme cdte=([$datr fdte=$cdte]->0) datr=$datr hgen=[$hgen hist=$history] scriptName=$scriptName] # Удаляем старые файлы |
В финале мероприятия объявляем глобально дату последнего бэкапа, сколько раз отработал скрипт, весело пищим и спамим результатом на заданный почтовый ящик.
1 2 3 4 5 6 7 8 | :global BackupLastTime $cdte; :global BackupTimes ($BackupTimes+1); beep frequency=1760 length=20ms; delay 150ms; beep frequency=1760 length=20ms; delay 100ms; /tool e-mail send from=" " to=$email subject=$mailSubject body="$mailSubject:: $[/system clock get date] $[/system clock get time]:: Success!\n" file=($bfn.".backup"); log info "$scriptName: Exit"; |
Теперь осталось сохранить код с нужным именем и добавить в Scheduler строку запуска скрипта /system script run Backup
, предварительно настроив время запуска.
Что стоит бекапить в первую очередь и как потом использовать резервные копии, расскажет Дмитрий Скромнов в русскоязычном онлайн-курсе по MikroTik для самостоятельного изучения. Серия курсов по MikroTik и RouterOS основана на официальной программе MTCNA, однако содержит намного больше полезной информации. 162 видеоурока и большая практическая задача, разбитая на 45 лабораторных работ. Время на изучение неограниченно – все материалы передаются бессрочно и их можно пересматривать сколько нужно. Первые 25 уроков можно посмотреть бесплатно, оставив заявку на странице курса.
Скрипт полностью:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | :local history "7d"; :local email "mailn@example.com"; :local pathForBackups "disk1/backup/"; :local pathForConfigs "disk1/etc/config/"; :local pathForScripts "disk1/etc/script/"; :local configsArray {"ip route";"ip firewall";"ip pool";"ip dhcp-server";"interface"}; :local nme [/system identity get name]; :local scriptName "Backup"; :local mailSubject ([/system identity get name]."::".$scriptName); :local cdte ([/system clock get date]." ".[/system clock get time]); log info "$scriptName: Starting..."; /tool e-mail send from=" " to=$email subject=$mailSubject body="$mailSubject:: $cdte:: Started."; beep frequency=1975 length=100ms; delay 150ms; :local datr do={ :local dfy 0; :local yar [pick $fdte 7 11]; :local mth [pick $fdte 0 3]; :local day [pick $fdte 4 6]; :local hur [pick $fdte 12 14]; :local min [pick $fdte 15 17]; :local sec [pick $fdte 18 20]; :local mts ("jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"); :local mtd {0;31;28;31;30;31;30;31;31;30;31;30;31}; :local mtn ([find $mts $mth -1]+1); :local yrd (($yar*365)+($yar / 4)); :local cmd ($mtd->$mtn); :local conA $mtn; do { set $conA ($conA-1); set $dfy ($dfy+($mtd->$conA)); if ($conA=0) do={set $dfy ($dfy+$day);} } while ($conA>0); if ($mtn<=9) do={set mtn ("0".$mtn);} return {((($yrd+$dfy)*86400)+($hur*3600)+($min*60)+$sec);($day."-".$mtn."-".$yar."-".$hur."-".$min)}; } :local hgen do={ :local rlt; :local dgt [pick $hist 0 ([len $hist]-1)]; :local ltr [pick $hist ([len $hist]-1) [len $hist]]; if ($ltr="s") do={set $rlt $dgt;} if ($ltr="m") do={set $rlt ($dgt*60);} if ($ltr="h") do={set $rlt ($dgt*3600);} if ($ltr="d") do={set $rlt ($dgt*86400);} return $rlt; } :local delo do={ :local far [/file find name~"@".$nme]; :local conB ([len $far]); do { set $conB ($conB-1); :local ftm ([$datr fdte=[/file get number=($far->$conB) value-name=creation-time]]->0); :local bdte ([$datr fdte=[/file get number=($far->$conB) value-name=creation-time]]->1); if (($cdte-$ftm)>($hgen-10)) do={ log info "$scriptName: Deleting old backups: $bdte"; /file remove number=($far->$conB); beep frequency=1568 length=10ms; delay 100ms; } else={ beep frequency=1975 length=10ms; delay 100ms; } } while ($conB>0); } :local cspt do={ :local cbn; :local conC ([len $cba]); do { set $conC ($conC-1); beep frequency=1975 length=1000ms; set $cbn [($cba->$conC)]; log info "$scriptName: Create $cbn script"; execute "/$cbn export file=\"$pfc$cbn.rsc\""; beep frequency=1975 length=10ms; log info "$scriptName: Done"; delay 100ms; } while ($conC>0); } beep frequency=1975 length=100ms; delay 1s; :local bfn ($pathForBackups.([$datr fdte=$cdte]->1)."@".$nme); log info "$scriptName: Create system backup"; /system backup save name=($bfn.".backup"); beep frequency=1975 length=10ms; log info "$scriptName: Done"; delay 100ms; log info "$scriptName: Create system script"; /export compact file=($bfn.".rsc"); beep frequency=1975 length=10ms; log info "$scriptName: Done"; delay 100ms; log info "$scriptName: Create Scripts script"; /system script export file=($pathForScripts."scripts.rsc"); beep frequency=1975 length=10ms; log info "$scriptName: Done"; delay 100ms; [$cspt pfc=$pathForConfigs cba=$configsArray scriptName=$scriptName] [$delo nme=$nme cdte=([$datr fdte=$cdte]->0) datr=$datr hgen=[$hgen hist=$history] scriptName=$scriptName] :global BackupLastTime $cdte; :global BackupTimes ($BackupTimes+1); beep frequency=1760 length=20ms; delay 150ms; beep frequency=1760 length=20ms; delay 100ms; /tool e-mail send from=" " to=$email subject=$mailSubject body="$mailSubject:: $[/system clock get date] $[/system clock get time]:: Success!\n" file=($bfn.".backup"); log info "$scriptName: Exit"; |
Уведомление:Отказоустойчивый кластер из MikroTik’ов — Aleksov's Blog
Уведомление:Бекап MikroTik’а с Эдельвейсом — Aleksov's Blog