Для роутеров Mikrotik, достаточно регулярно выходят обновления. Обновляться можно через меню System -> Packages -> Check_For_Updates, но когда устройств пара десятков и в эфир они выходят не регулярно — это не удобно. Ясно, что процесс нуждается в автоматизации.
Подготовка
Как подсказывает документация, если в корне файловой системы роутера лежит .npk файл с версией (зашита в заголовке файла, считывается автоматом) выше, чем установленная, то после перезагрузки по команде /system reboot (proper shutdown, как её называет лог) прошивка автоматически обновится.
Mikrotik
На роутере создаём скрипт следующего содержания:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
:local url "176.28.64.20:16002/mikrotik/update" :local user "cgb-mikrot" :local pass "Pr0v1s10" :local serial [/system routerboard get value-name="serial-number"] :local osver [:pick [/system resource get value-name=version] 0 [:find [/system resource get value-name=version] " "]] if ( [/system routerboard get value-name=upgrade-firmware] != [/system routerboard get value-name=current-firmware] ) do={ /system routerboard upgrade :put "y" /system reboot :put "y" } if ( [:len [/file find name=routeros-mipsbe.npk]]=1 ) do={ /file remove routeros-mipsbe.npk } :do { /tool fetch dst-path=routeros-mipsbe.npk mode=https user=$user password=$pass [:put ("https://$url\?serial=$serial&osver=$osver)] /system reboot :put "y" } on-error={ /file remove routeros-mipsbe.npk } |
Здесь, получаем серийник, текущую версию прошивки, проверяем соответствие установленной версии загрузчика и доступной и если они не равны делаем обновление и перезагружаем устройство.
Далее, серверу передаётся набор следующих параметров: osver — текущая версия прошивки, serial — серийный номер роутера.
Если на сервере есть версия свежее, чем osver, то сервер отдаёт файл для скачивания, после успешного завершения которой, Mikrotik уходит в перезагрузку, иначе — ошибку 404.
Сервер
На стороне сервера поднимаем Web-сервер с поддержкой CGI и пишем скрипт:
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 |
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import sys import cgi import re import requests from bs4 import BeautifulSoup def not_found(): print('Status: 404 Not Found', end='\r\n\r\n') exit() def version2int(version, multfactor=100): if not version: return 0 version_int = 0 mult = 1 for i in reversed(re.findall(r'\d+', str(version))): version_int = version_int + (int(i) * mult) mult = mult * multfactor return version_int fw_dir = '/usr/local/www/mikrotik/firmware/' params = cgi.FieldStorage() osver = params.getfirst('osver') if len(sys.argv) > 1 and sys.argv[1] == 'fetch': r = requests.get('https://mikrotik.com/download', verify=False) soup = BeautifulSoup(r.text.encode("utf-8")) table = soup.find('table', {'class': 'table downloadTable'}) thead = table.find('thead').find_all('th') bf_row = 0 for col in thead: if 'Bugfix only' in col.text: break bf_row += 1 else: not_found() tbody = table.find('tbody') mips = 0 for i in tbody.find_all('tr'): if 'MIPS' in i.text: mips = 1 continue if mips: link = 'https:' + i.find_all('td')[bf_row].select('a')[0]['href'] filename = link.split('/')[-1] for f in os.listdir(path=fw_dir): if f == filename: not_found() filereq = requests.get(link, stream = True) with open(fw_dir + filename, 'wb') as f: for chunk in filereq.iter_content(chunk_size=1024): if chunk: f.write(chunk) f.close() not_found() not_found() if not osver: not_found() osver_int = version2int(osver) avl_dict = {i: version2int(i) for i in os.listdir(path=fw_dir)} avl_newest = [(k, avl_dict[k]) for k in sorted(avl_dict, key=avl_dict.get, reverse=True)][0] if avl_newest[1] <= osver_int: not_found() print('Content-type: application/octet-stream; name=' + avl_newest[0], end='\r\n') print('Content-Length: ' + str(os.path.getsize(fw_dir + avl_newest[0])), end='\r\n') print('Content-Disposition: attachment; filename=' + avl_newest[0], end='\r\n') print('Content-Transfer-Encoding: binary', end='\r\n') print('Accept-Ranges: bytes', end='\r\n\n') fd = open(fw_dir + avl_newest[0], 'rb') content = fd.read() sys.stdout.flush() sys.stdout.buffer.write(content) fd.close() |
Тут смотрим, если скрипт запустили из консоли с параметром «fetch», то парсим страницу загрузок сайта Mikrotik’а, в таблице ищем самую верхнюю прошивку с архитектурой MIPS, из ветки Bugfix only (как-то были проблемы после ручного обновления на Current, а тут можно автоматически положить все устройства, так что — онли баг фикс), смотрим нет ли у нас уже такого файла и если нет — качаем, выходим.
Если скрипт запущен через CGI: ищем новейшую версию на сервере, сравниваем с версией на устройстве и, в случае различия в пользу версии на сервере, отдаём файл.
P.S: Для сравнения версий используется преобразование в число функцией version2int. Например версия 6.38.7 преобразуется к 63807, как показал беглый просмотр сайта mikrotik.com ветка Bugfix only придерживается такого формата уже лет 7.
Еще вариант скрипта автоматического обновления + резервные копии
https://github.com/beeyev/Mikrotik-RouterOS-automatic-backup-and-update