Package: pynetinstall
pyNetinstallFree and Open Source netInstall implementation for Flashing Mikrotik RouterBoardsIntroductionpyNetinstall is meant as a component of a zero-touch deployment system. Using it
one can configure RouterBoards en masse easily. The plug-in system allows
interfacing pyNetinstall with existing data center infrastructure management
systems, uploading individual firmware and configuration per device based on MAC
address, model type and serial number.It is possible to run pyNetinstall in a Container on a Routerboard itself,
providing a self-contained, (nearly) zero-touch deployment station.Unlike the official tooling, pyNetinstall does not include DHCP and TFTP
servers; these services should be handled by e.g.dnsmasqor the one included
with RouterOS.Usagepython -m pynetinstall [-c CONFIG] [-i INTERFACE] [-v]-c CONFIG: Path to the configuration file. Defaults to/etc/pynetinstall.ini.-i INTERFACE: Ethernet interface to listen on. Defaults toeth0.-l LOGGING:Python logging configuration. Defaults to stderr.-1: Enable one-shot mode (exit after flashing once).-v: Increase verbosity. Default is errors and warnings.-h: Display help and exit.Theory of OperationMikrotik provides a special boot image (embedded in the netinstall tools) that
will flash a RouterOS firmware and configuration file on the device. The
firmware and configuration is transmitted over a proprietary UDP based protocol,
which pyNetinstall implements.The netinstall protocol provides some device information, including model and
serial numbers. By implementing a simple python module, these can be used as
parameters to dynamically select firmware and configuration, or directly stream
them from HTTP.The boot image itself is loaded by the RouterBOOT bootloader using BOOTP/DHCP
and TFTP. Usually, RouterBoards can be set to boot from network once by pressing
reset for 15 seconds while powering on.Deploy on MikrotikBy building a smallcontainerand setting upDHCPandTFTP, pyNetinstall
can be deployed directly on another RouterBoard running RouterOS 7.4 or higher.This process is a relatively involved; seepyNetinstall on RouterOSfor an
example.Deploy on Linux (dnsmasq)Setupdnsmasqto provide DHCP and TFTP, so your RouterBoard can boot via
BOOTP. Boot images can be obtained either byextracting them fromnetinstall.exe, or alternatively, some can be downloaded from the
unaffiliatedmerlinthemagic/MTM-Mikrotikrepo.Below is a sample dnsmasq configuration. Depending on the CPU architecture of
your RouterBoard, a different boot file must be used. CPU architectures can be
differentiated through the vendor class identifier sent with the DHCP request.interface=eth0

dhcp-range=10.0.0.101,10.0.0.200,10m
dhcp-boot=vendor:Mips_boot,netinstall.mips
dhcp-boot=vendor:MMipsBoot,netinstall.mmips
dhcp-boot=vendor:ARM__boot,netinstall.arm32
dhcp-boot=vendor:ARM64__boot,netinstall.arm64

enable-tftp
tftp-root=/var/ftpd
tftp-no-blocksizeUsing the default pluginpyNetinstall includes a simple plugin that serves a single firmware and
optionally a single configuration file.The default plugin reads thefirmwareandconfigparameters frompynetinstall.ini. To disable uploading the config file, just remove the line
frompynetinstall.ini.[pynetinstall]
firmware=<PATH_TO_ROUTEROS_NPK>
config=<PATH_TO_CONFIG_RSC>Providing a custom pluginBy writing a small python module the served firmware and configuration file can
be varied at runtime, based on the device connected.To load a custom plugin, thepluginparameter should be defined inpynetinstall.ini. It expects the name of a Python module, a colon, and the
name of a class. The module will be searched for in Python's path ($PWD, $PATH
or $PYTHONPATH). The class is loaded once on startup and reused for each
flashing operation.[pynetinstall]
plugin=<PYTHON_MODULE>:<CLASS_NAME>
# additional keys or sections defined by the pluginSuch a plugin is simply a python class that implementsget_files(info),
returning a tuple (firmware, config). Firmware and config may be returned as a
path on disk (string), an HTTP  or HTTPS URL (string), or afile objectas
returned by e.g.open().Additionally, config may beNoneif no custom default configuration is
desired. If firmware isNone, an error is assumed and the current flashing
process is aborted and pyNetinstall resets for the next flashing cycle.get_files()is passed an InterfaceInfo object, which contains information on
the connected RouterBoard. The available attributes are described in the example
below.Implementing__init__(config)is optional and only required if access
topynetinstall.iniis needed through the passedConfigParserobject.
Exceptions raised during__init__()will result in pyNetinstall exiting.class Plugin:
    def __init__(self, config: ConfigParser):
        ...

    def get_files(self, info: InterfaceInfo):
        ...
        # info.mac.hex(':') = MAC address
        # info.model        = model name
        # info.arch         = CPU architecture
        # info.min_os       = oldest supported rOS version
        # info.lic_id       = installed license id
        # info.lic_key      = installed license key

        return firmware, configuration_or_NoneExtracting Boot ImagesYou will need to aquire the boot images that the official netinstall tool uses.
These are not included as they are not licensed for re-distribution. However, it
is fairly easy to extract them from the Mikrotik's Netinstall tool. This guide
describes the Linux CLI version, but the same techniques should work on the
Windows GUI version as well.Downloadnetinstall-<version>.tar.gzThe latest version that is linked in theDownloads pageGeneral section
should work fine for all RouterOS versions. TheDownload Archivehas links
to older versions.Startnetinstall-clisudo ./netinstall-cli -a 127.0.0.2 netinstall-cliInstalldhtestFor Fedora, openSuse and RHEL packages are in the default repositories. On
Debian or Ubuntu download thedhtest sourcesand compile them withmake.Rundhtestsudo dhtest -T 5 -o ARM64__boot -i loThis will set up extraction of the arm64/aarch64 image. Other valid options
areMips_boot,MMipsBoot,Powerboot,e500_boot,e500sboot,440__boot,tile_boot,ARM__bootandARM64__boot.Download the imagecurl tftp://127.0.0.1/linux.arm > netinstall.arm64The filename is alwayslinux.arm. Restartnetinstall-cliand rundhtestagagin before downloading another image.AcknowledgementsThis project is based on the wonderful reverse engineering work ofmerlinthemagic.LicenseCopyright 2022-2023 DVT - Daten-Verarbeitung Tirol GmbH. Made available under
the terms of theGNU General Public License, version 3.