MikroTik ZTP
A router joins the network by itself. Nine hundred and fifty sites to go.
Problem
Every surveillance site is a router, a recorder and cameras, tunnelled back to head office. At that scale configuring them one by one is not possible, and a mistake means sending an engineer out.
Solution
A server that takes a new router's request, validates it, generates its configuration and creates the peer at head office over the API. One CSV is the single source of truth. Addressing runs from the end of the pool downwards, so widening the range is one command rather than touching nine hundred and fifty devices.
What it does
- The router announces itself; the server validates and generates its config
- Peer and route created at head office over the API, no logging into hardware
- One source of truth: a CSV of clients
- Addressing runs from the end of the pool down; widening it is one command
- Client isolation as a single interface rule instead of address lists
- Token authorisation and public-key pinning
- A panel with a site map, syslog server, alarms, snapshots and backups: the state of the whole network without a terminal on every router
How it's built
- A Python ZTP server on Debian 13 under systemd, HTTPS with its own certificate
- Router configuration generated as .rsc files from a single CSV
- Peer and route created at head office through the RouterOS 7 REST API
- WireGuard as the transport, addressing running from the end of the pool down
- A web panel with a Leaflet site map, syslog server, alarms and backups
- Proxmox on the head-office side, in its own module
- Tests: 44 assertions that need no hardware, plus a runbook of eleven acceptance tests
What it changed
- Configuring routers one at a time goes away: the router announces itself and the server generates its file from a single CSV
- Widening the address pool is one command instead of touching 950 devices, and every manual touch risks an engineer driving out, the most expensive line in the whole process
- A bad row does not surface in the field: validation runs before any config is generated
- First run on physical hardware: nine tests, tunnel 3/3, client isolation 0/3 in both directions
- Throughput 20.7 Mbit/s through the tunnel against 22.1 raw, 5.5% overhead; hub CPU at 6% under 42 Mbit/s aggregate
- 44 unit assertions that run without any hardware
- These come from the first run on hardware, not from production across 950 sites, that has not happened yet
Stack
- Python
- RouterOS 7 REST API
- WireGuard
- Debian 13
- systemd
- Leaflet
Story
BGP was rejected on purpose: it fights WireGuard's allowed-address list by design. A static route generator from one file replaced it. Client isolation, which had not worked at seven hundred sites, came down to a single interface rule instead of address lists. The project's first rule: never cut access to a device, because a router you cannot reach means an engineer driving out.