Автоматическое обновление Mikrotik

By | 2017-09-07

Для роутеров Mikrotik, достаточно регулярно выходят обновления. Обновляться можно через меню System -> Packages -> Check_For_Updates, но когда устройств пара десятков и в эфир они выходят не регулярно — это не удобно. Ясно, что процесс нуждается в автоматизации.

Подготовка
Как подсказывает документация, если в корне файловой системы роутера лежит .npk файл с версией (зашита в заголовке файла, считывается автоматом) выше, чем установленная, то после перезагрузки по команде /system reboot (proper shutdown, как её называет лог) прошивка автоматически обновится.

Mikrotik
На роутере создаём скрипт следующего содержания:

: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 и пишем скрипт:

#!/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.

One thought on “Автоматическое обновление Mikrotik

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *