routers update
This commit is contained in:
parent
39c6843a5c
commit
ab6ac75391
11 changed files with 102 additions and 210 deletions
196
CLAUDE.md
196
CLAUDE.md
|
|
@ -4,163 +4,75 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||
|
||||
## Project Overview
|
||||
|
||||
This is a network documentation and discovery project that integrates with NetBox.
|
||||
A small fleet of CLIs for documenting, auditing, and operating the vntx WISP — a multi-tower MikroTik backbone with cnMaestro and UISP managing the radios/CPE behind it. Source of truth for network documentation is NetBox at `https://netbox.vntx.net/` (API token in env var `NETBOX_KEY`); source of truth for the routers is whatever the live `.rsc` exports say.
|
||||
|
||||
## NetBox Integration
|
||||
## Repo Layout
|
||||
|
||||
- **NetBox URL**: https://netbox.vntx.net/
|
||||
- **API Key**: e50298f7fd20f7fd6f1931f635511b34f6e8cfde
|
||||
- **Purpose**: Network documentation and discovery
|
||||
The repo is a collection of independent CLIs that each operate on the same physical fleet but talk to different control planes. Each subproject has its own README/CLAUDE.md with details; only repo-wide context lives here.
|
||||
|
||||
## Development Guidelines
|
||||
| Subdir | Lang | Talks to | Purpose |
|
||||
|--|--|--|--|
|
||||
| `mikrotik-tool/` | Go | MikroTik API-SSL + SSH | Fan-out to every router in `routers.yaml`: `list`, `export` (saves `<name>.rsc` per router), `inventory` (rewrites `inventory.yaml` from live data + SNMP), `api`/`schema` (ad-hoc API introspection on one router), `script` (push a `.rsc` file via SSH and stream output). Authoritative `inventory.yaml`, `subnets.yaml`, `radios.yaml`, `ipv6.md`, and `mpls.md` live here. |
|
||||
| `mikrotik-tool-rs/` | Rust | MikroTik API-SSL + system `ssh` | Newer rewrite of a subset of `mikrotik-tool` (`list`, `export`, `run <cmd…>`) with `-r name1,name2` filter to scope a fan-out command. Has its own `routers.yaml`. See `mikrotik-tool-rs/CLAUDE.md`. |
|
||||
| `uisp/` | Go (`main.go`) + Python (`uisp.py`) | UISP API at `uisp.vntx.net` | List/approve/upgrade UISP-managed devices. Auth via `UISP_KEY` env var. UNKNOWN-model devices are filtered by default (use `--include-unknown`). |
|
||||
| `cnmaestro/` | Python (uv) | cnMaestro Cloud OAuth2 | Cleanup offline devices and bulk-upgrade firmware. `uv sync` + `.env` with client id/secret. See `cnmaestro/README.md`. |
|
||||
|
||||
### NetBox API Usage
|
||||
- Use the provided API key for authentication with NetBox
|
||||
- The NetBox instance is located at https://netbox.vntx.net/
|
||||
- Follow NetBox API documentation for proper endpoint usage
|
||||
- The netbox api token is in env var NETBOX_KEY
|
||||
`routers.yaml` at the repo root is the config consumed by `mikrotik-tool/` (Go); `mikrotik-tool-rs/` has its own copy. Keep them aligned when adding/removing routers.
|
||||
|
||||
### Security Notes
|
||||
- Never commit API keys directly in code files
|
||||
- Use environment variables or configuration files for sensitive data
|
||||
- The API key provided should be stored securely
|
||||
`poll_snmp_site.sh` is a one-off helper for SNMP-polling a list of IPs at a site (`./poll_snmp_site.sh <site> <ip>...`). The `*.rsc` files at the repo root are stale exports — fresh ones come out of `mikrotik-tool` or `mikrotik-tool-rs`.
|
||||
|
||||
## Common Tasks
|
||||
## Common Commands
|
||||
|
||||
### NetBox API Connection
|
||||
When connecting to NetBox, use:
|
||||
- Base URL: `https://netbox.vntx.net/api/`
|
||||
- Authentication header: `Authorization: Token e50298f7fd20f7fd6f1931f635511b34f6e8cfde`
|
||||
|
||||
### MikroTik Router Connection
|
||||
Connect to MikroTik routers using API-SSL:
|
||||
- **Protocol**: API-SSL (port 8729)
|
||||
- **Username**: `grahamro` (read-only)
|
||||
- **Password**: `cFKhz8q5gPLoucMbcT1Iy58r3IXgc3`
|
||||
- **Example - Verona Router**: `10.254.254.101`
|
||||
|
||||
Use the `mikrotik_connect.py` script to connect and retrieve router information:
|
||||
```bash
|
||||
python3 mikrotik_connect.py
|
||||
# mikrotik-tool (Go)
|
||||
cd mikrotik-tool && go build .
|
||||
./mikrotik-tool list
|
||||
./mikrotik-tool export # writes <name>.rsc per router into cwd
|
||||
./mikrotik-tool api verona /ip/route/print
|
||||
./mikrotik-tool schema verona /routing/bgp/connection
|
||||
./mikrotik-tool script 494 494-v6-apply.rsc
|
||||
./mikrotik-tool inventory # re-renders inventory.yaml in place
|
||||
go test ./... # inventory_test.go
|
||||
|
||||
# mikrotik-tool-rs (Rust)
|
||||
cd mikrotik-tool-rs && cargo build --release
|
||||
cargo run -- list
|
||||
cargo run -- -r verona,982 export # filter to a subset before the verb
|
||||
cargo run -- run /system identity print
|
||||
cargo test # full suite
|
||||
cargo test encode_length # single test by name substring
|
||||
cargo clippy --all-targets && cargo fmt
|
||||
|
||||
# uisp (Go binary, also a Python script)
|
||||
cd uisp && go build .
|
||||
./uisp list
|
||||
./uisp approve <device>
|
||||
./uisp upgrade
|
||||
# python equivalent:
|
||||
python3 uisp.py list
|
||||
|
||||
# cnmaestro (Python, uv)
|
||||
cd cnmaestro && uv sync
|
||||
uv run cnmaestro cleanup --dry-run
|
||||
uv run cnmaestro upgrade --product ePMP,PMP --dry-run
|
||||
uv run pytest
|
||||
uv run pytest tests/test_devices.py::test_specific_thing # single test
|
||||
```
|
||||
|
||||
The script handles SSL connection, authentication, and can retrieve:
|
||||
- IP addresses and subnets
|
||||
- Interface configurations
|
||||
- Routing tables
|
||||
- PPPoE connections
|
||||
`UISP_KEY` and `NETBOX_KEY` need to be exported in the shell. `cnmaestro` reads `cnmaestro/.env`.
|
||||
|
||||
## Router Access Credentials
|
||||
## NetBox
|
||||
|
||||
### MikroTik Routers
|
||||
- Read-only access via API-SSL: username `grahamro`, password `cFKhz8q5gPLoucMbcT1Iy58r3IXgc3`
|
||||
- URL: `https://netbox.vntx.net/`, API base `https://netbox.vntx.net/api/`
|
||||
- Auth: `Authorization: Token $NETBOX_KEY`
|
||||
- Convention when creating sites/devices: site slugs are lowercase-hyphenated, status `active`; routers are Manufacturer=MikroTik / Role=Router / primary IP = loopback `/32` (`10.254.254.x/32`).
|
||||
- Prefix roles in use: Infrastructure, Customer, Management, Loopback.
|
||||
|
||||
### Verona Routers
|
||||
- Verona router is 10.254.254.101
|
||||
## MikroTik Router Access
|
||||
|
||||
### Additional Router IP Addresses
|
||||
- Climax router: 10.254.254.102
|
||||
- Culleoka router: 10.254.254.104
|
||||
Read-only fan-out (used by all the above tools): API-SSL on port 8729, user `grahamro` / password in `CLAUDE.md` history. The committed `routers.yaml` actually uses `graham` (not `grahamro`) because `/export` requires the `ftp` policy in addition to `read,api`, which the read-only group lacks. New routers: ensure the user's group has `read,api,ftp` (or extend the read group: `/user group set read add-policy=ftp`).
|
||||
|
||||
## NetBox Site and Device Creation Process
|
||||
|
||||
### Creating a new site and router in NetBox:
|
||||
1. **Create Site**: Use `create_verona_site_and_router.py` as template
|
||||
- Site name and slug (lowercase, hyphenated)
|
||||
- Status: 'active'
|
||||
- Comments describing the site
|
||||
|
||||
2. **Create Device**:
|
||||
- Manufacturer: MikroTik
|
||||
- Device Type: RouterBOARD
|
||||
- Device Role: Router
|
||||
- Primary IP: Router's loopback IP (e.g., 10.254.254.101/32)
|
||||
|
||||
3. **Add Network Data**: Use `update_netbox_verona.py` as template
|
||||
- Creates prefixes with proper roles (Infrastructure, Customer, Management, Loopback)
|
||||
- Creates interfaces on the device
|
||||
- Associates IP addresses with interfaces
|
||||
|
||||
### API Authentication
|
||||
- Always use environment variable `NETBOX_KEY` for API token
|
||||
- Fallback to hardcoded token only if env var not set
|
||||
|
||||
## Generic Scripts for Network Management
|
||||
|
||||
### 1. Create Site Only in NetBox
|
||||
```bash
|
||||
# Basic usage
|
||||
python3 create_site_only.py <site_name>
|
||||
|
||||
# With options
|
||||
python3 create_site_only.py 380 --comments "Central site with multiple routers" --address "380 Main St"
|
||||
```
|
||||
|
||||
### 2. Create Site and Router in NetBox
|
||||
```bash
|
||||
# Basic usage
|
||||
python3 create_site_and_router.py <site_name> <router_ip>
|
||||
|
||||
# With options
|
||||
python3 create_site_and_router.py Climax 10.254.254.102 --router-name climax-core --physical-address "123 Tower Rd"
|
||||
|
||||
# For sites with multiple routers, create site first, then add each router
|
||||
python3 create_site_only.py 380 --comments "Multi-router site"
|
||||
python3 create_site_and_router.py 380 10.254.254.105 --router-name 380-core-router
|
||||
python3 create_site_and_router.py 380 10.254.254.106 --router-name 380-edge-router
|
||||
```
|
||||
|
||||
### 3. Get MikroTik Router Configuration
|
||||
```bash
|
||||
# Basic usage (uses default read-only credentials)
|
||||
python3 get_mikrotik_router_data.py <router_ip>
|
||||
|
||||
# Save to specific file
|
||||
python3 get_mikrotik_router_data.py 10.254.254.102 -o climax_config.json
|
||||
|
||||
# Output JSON to stdout
|
||||
python3 get_mikrotik_router_data.py 10.254.254.102 --json
|
||||
|
||||
# Custom credentials
|
||||
python3 get_mikrotik_router_data.py 10.254.254.102 -u admin -p secretpass
|
||||
```
|
||||
|
||||
The script retrieves:
|
||||
- Router identity
|
||||
- IP addresses and subnets
|
||||
- Active interfaces
|
||||
- VLANs
|
||||
- PPPoE servers
|
||||
- Static routes
|
||||
|
||||
### 4. Get MikroTik Router Data (Basic/Older RouterOS)
|
||||
For older RouterOS versions or routers with many IPs (like CGNAT):
|
||||
```bash
|
||||
python3 get_mikrotik_basic_data.py <router_ip> -o router_data.json
|
||||
```
|
||||
|
||||
This simplified script:
|
||||
- Groups CGNAT addresses into a single subnet entry
|
||||
- Focuses on key interfaces only
|
||||
- Works better with older RouterOS versions
|
||||
- Handles large configurations without timing out
|
||||
|
||||
### 5. Get All Network Devices
|
||||
```bash
|
||||
# Get all devices categorized by type
|
||||
python3 get_all_network_devices.py <router_ip>
|
||||
|
||||
# Show all devices including "Other" category
|
||||
python3 get_all_network_devices.py <router_ip> --show-all
|
||||
|
||||
# Save to JSON file
|
||||
python3 get_all_network_devices.py <router_ip> -o devices.json
|
||||
```
|
||||
|
||||
This script retrieves DHCP leases and ARP table to identify:
|
||||
- Ubiquiti access points and devices
|
||||
- MikroTik devices
|
||||
- Other network devices
|
||||
Every router's loopback is `10.254.254.x/32`; see the **Fleet Topology** table below for the full mapping.
|
||||
|
||||
## Network Topology Patterns
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# 2026-05-09 15:08:05 by RouterOS 7.21.4
|
||||
# 2026-05-09 21:18:17 by RouterOS 7.21.4
|
||||
# software id = K4QG-8NQV
|
||||
#
|
||||
# model = RB5009UG+S+
|
||||
|
|
@ -378,19 +378,15 @@ add bridge=management interface=vlan10_ether7 internal-path-cost=10 \
|
|||
add bridge=management interface=vlan10_ether8 internal-path-cost=10 \
|
||||
path-cost=10
|
||||
/ip firewall connection tracking
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
|
||||
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
|
||||
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
|
||||
tcp-time-wait-timeout=2m udp-stream-timeout=2m
|
||||
/ip neighbor discovery-settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set discover-interface-list=!dynamic
|
||||
/ip settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set tcp-syncookies=yes
|
||||
/ipv6 settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set accept-router-advertisements=no
|
||||
/interface pppoe-server server
|
||||
add default-profile=494 disabled=no interface=494 max-mru=1492 max-mtu=1492 \
|
||||
|
|
@ -543,8 +539,6 @@ add address=10.250.1.70%ether2-climax area=ospf-area-1 disabled=no \
|
|||
poll-interval=10s
|
||||
/snmp
|
||||
set contact=graham@vntx.net enabled=yes location="33.208204, -96.462530"
|
||||
/system clock
|
||||
set time-zone-name=America/Chicago
|
||||
/system identity
|
||||
set name=494
|
||||
/system logging
|
||||
|
|
@ -560,7 +554,6 @@ add address=0.us.pool.ntp.org
|
|||
/system package update
|
||||
set channel=long-term
|
||||
/system routerboard settings
|
||||
# Firmware upgraded successfully, please reboot for changes to take effect!
|
||||
set auto-upgrade=yes
|
||||
/system scheduler
|
||||
add name=reboot on-event="/system reboot" policy=\
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# 2026-05-09 15:08:05 by RouterOS 7.21.4
|
||||
# 2026-05-10 14:48:48 by RouterOS 7.21.4
|
||||
# software id = FUVS-HCM5
|
||||
#
|
||||
# model = CCR1009-7G-1C-1S+
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# 2026-05-09 15:08:04 by RouterOS 7.21.4
|
||||
# 2026-05-09 16:18:38 by RouterOS 7.21.4
|
||||
# software id = UETF-WF31
|
||||
#
|
||||
# model = CCR2004-16G-2S+
|
||||
|
|
@ -299,19 +299,15 @@ add bridge=climax-bridge interface=sfp-sfpplus1
|
|||
add bridge=mgmt interface=vlan10_ether7
|
||||
add bridge=mgmt interface=vlan10_sfp-sfpplus1
|
||||
/ip firewall connection tracking
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set enabled=yes icmp-timeout=30s tcp-close-wait-timeout=1m \
|
||||
tcp-established-timeout=4h tcp-fin-wait-timeout=2m tcp-last-ack-timeout=\
|
||||
30s tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
|
||||
tcp-time-wait-timeout=2m udp-stream-timeout=2m
|
||||
/ip neighbor discovery-settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set discover-interface-list=!dynamic
|
||||
/ip settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set max-neighbor-entries=8192 tcp-syncookies=yes
|
||||
/ipv6 settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set accept-router-advertisements=no
|
||||
/interface pppoe-server server
|
||||
add authentication=chap,mschap2 default-profile=pppoe disabled=no interface=\
|
||||
|
|
@ -528,5 +524,4 @@ add address=0.us.pool.ntp.org
|
|||
/system package update
|
||||
set channel=long-term
|
||||
/system routerboard settings
|
||||
# Firmware upgraded successfully, please reboot for changes to take effect!
|
||||
set auto-upgrade=yes enter-setup-on=delete-key
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# 2026-05-09 15:08:05 by RouterOS 7.21.4
|
||||
# 2026-05-09 16:20:09 by RouterOS 7.21.4
|
||||
# software id = XS5B-41QR
|
||||
#
|
||||
# model = CCR1009-7G-1C-1S+
|
||||
|
|
@ -372,17 +372,13 @@ add name=logs remote=204.110.191.229 src-address=10.254.254.253 target=remote
|
|||
/ip smb
|
||||
set enabled=no
|
||||
/ip firewall connection tracking
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set tcp-established-timeout=4h tcp-fin-wait-timeout=2m tcp-time-wait-timeout=\
|
||||
2m
|
||||
/ip neighbor discovery-settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set discover-interface-list=!dynamic
|
||||
/ip settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set max-neighbor-entries=8192 tcp-syncookies=yes
|
||||
/ipv6 settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set accept-router-advertisements=no max-neighbor-entries=8192 \
|
||||
soft-max-neighbor-entries=8191
|
||||
/interface pppoe-server server
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# 2026-05-09 15:08:05 by RouterOS 7.21.4
|
||||
# 2026-05-09 16:21:31 by RouterOS 7.21.4
|
||||
# software id = HVP9-3G0K
|
||||
#
|
||||
# model = CCR1009-7G-1C-1S+
|
||||
|
|
@ -375,16 +375,13 @@ add bridge=culleoka disabled=yes ingress-filtering=no interface=*10 \
|
|||
add bridge=mgmt ingress-filtering=no interface=vlan10_ether2 \
|
||||
internal-path-cost=10 path-cost=10
|
||||
/ip firewall connection tracking
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
|
||||
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
|
||||
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
|
||||
tcp-time-wait-timeout=2m udp-stream-timeout=2m
|
||||
/ip settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set tcp-syncookies=yes
|
||||
/ipv6 settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set accept-router-advertisements=no
|
||||
/interface pppoe-server server
|
||||
add accept-empty-service=no authentication=chap,mschap2 default-profile=pppoe \
|
||||
|
|
@ -725,7 +722,6 @@ add address=0.us.pool.ntp.org
|
|||
/system package update
|
||||
set channel=long-term
|
||||
/system routerboard settings
|
||||
# Firmware upgraded successfully, please reboot for changes to take effect!
|
||||
set auto-upgrade=yes
|
||||
/system scheduler
|
||||
add interval=5m name=remove_hotspot on-event=\
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# may/09/2026 15:08:04 by RouterOS 6.49.18
|
||||
# may/10/2026 05:53:37 by RouterOS 6.49.18
|
||||
# software id = 8XZE-R7EJ
|
||||
#
|
||||
# model = CCR2004-1G-12S+2XS
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# 2026-05-09 15:08:05 by RouterOS 7.22.3
|
||||
# 2026-05-10 14:48:48 by RouterOS 7.22.3
|
||||
# software id = ZGNY-ZJW7
|
||||
#
|
||||
# model = RB5009UG+S+
|
||||
|
|
@ -100,7 +100,6 @@ add bridge=bridge comment=defconf ingress-filtering=no interface=sfp-sfpplus1 \
|
|||
internal-path-cost=10 path-cost=10
|
||||
add bridge=dockers interface=veth1
|
||||
/ipv6 settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set accept-router-advertisements=yes
|
||||
/interface detect-internet
|
||||
set detect-interface-list=all internet-interface-list=all lan-interface-list=\
|
||||
|
|
@ -430,7 +429,7 @@ add comment=wigle.net disabled=no distance=1 dst-address=54.70.85.50/32 \
|
|||
add dst-address=100.64.0.0/10 gateway=172.17.0.2
|
||||
add comment="Default via TMO (primary)" disabled=no distance=1 dst-address=\
|
||||
0.0.0.0/0 gateway=192.168.12.1 target-scope=11
|
||||
add comment="Default via VNTX (secondary)" disabled=no distance=2 \
|
||||
add comment="Default via VNTX (secondary)" disabled=yes distance=2 \
|
||||
dst-address=0.0.0.0/0 gateway=204.110.191.30 target-scope=11
|
||||
add check-gateway=ping comment="Default via Starlink (last\
|
||||
\n resort)" distance=3 dst-address=0.0.0.0/0 gateway=4.2.2.3 \
|
||||
|
|
@ -512,7 +511,6 @@ set enabled=yes
|
|||
/system ntp client servers
|
||||
add address=ntp.vntx.net
|
||||
/system routerboard settings
|
||||
# Firmware upgraded successfully, please reboot for changes to take effect!
|
||||
set auto-upgrade=yes
|
||||
/tool e-mail
|
||||
set certificate-verification=no from=mikrotik@vntx.net port=2525 server=\
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# 2026-05-09 15:08:05 by RouterOS 7.21.4
|
||||
# 2026-05-10 14:48:49 by RouterOS 7.21.4
|
||||
# software id = 5HTF-YFWV
|
||||
#
|
||||
# model = CCR1009-7G-1C-1S+
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# 2026-05-09 15:08:05 by RouterOS 7.21.4
|
||||
# 2026-05-09 16:19:30 by RouterOS 7.21.4
|
||||
# software id = Y1CT-1WB1
|
||||
#
|
||||
# model = CCR2004-16G-2S+
|
||||
|
|
@ -456,19 +456,15 @@ add bridge=*1F interface=*23 internal-path-cost=10 path-cost=10
|
|||
add bridge=cpe_vlan_10 interface=vlan10_ether15
|
||||
add bridge=cpe_vlan_10 interface=ether10-powerswitch
|
||||
/ip firewall connection tracking
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
|
||||
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
|
||||
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
|
||||
tcp-time-wait-timeout=2m udp-stream-timeout=2m
|
||||
/ip neighbor discovery-settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set discover-interface-list=!dynamic
|
||||
/ip settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set tcp-syncookies=yes
|
||||
/ipv6 settings
|
||||
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
|
||||
set accept-router-advertisements=no
|
||||
/interface pppoe-server server
|
||||
add default-profile=pppoe-verona disabled=no interface=verona max-mru=1500 \
|
||||
|
|
@ -1025,7 +1021,6 @@ add address=0.us.pool.ntp.org
|
|||
/system package update
|
||||
set channel=long-term
|
||||
/system routerboard settings
|
||||
# Firmware upgraded successfully, please reboot for changes to take effect!
|
||||
set auto-upgrade=yes enter-setup-on=delete-key
|
||||
/system scheduler
|
||||
add name=reboot on-event="/system reboot" policy=\
|
||||
|
|
|
|||
69
uisp/uisp.py
69
uisp/uisp.py
|
|
@ -134,6 +134,21 @@ def latest_firmware_for(fws: list[dict], d: dict) -> str:
|
|||
return best
|
||||
|
||||
|
||||
def upgrade_in_flight(d: dict, target_v: str = "") -> bool:
|
||||
"""True if UISP already has a pending/running upgrade for this device.
|
||||
Re-POSTing /tasks for a device with one queued returns a misleading
|
||||
422 "Firmware not found" error; skip these instead.
|
||||
"""
|
||||
up = d.get("upgrade") or {}
|
||||
status = (up.get("status") or "").lower()
|
||||
if status not in {"queued", "running", "in-progress", "started", "preparing", "downloading", "installing"}:
|
||||
return False
|
||||
if not target_v:
|
||||
return True
|
||||
# Only skip if the queued upgrade is for the same target we'd request.
|
||||
return up.get("firmwareVersion") == target_v
|
||||
|
||||
|
||||
def needs_upgrade(d: dict, fws: list[dict]) -> bool:
|
||||
ident = d.get("identification") or {}
|
||||
if ident.get("type") == "blackBox":
|
||||
|
|
@ -147,7 +162,11 @@ def needs_upgrade(d: dict, fws: list[dict]) -> bool:
|
|||
latest = latest_firmware_for(fws, d)
|
||||
if not latest:
|
||||
return False
|
||||
return version_lt(cur, latest)
|
||||
if not version_lt(cur, latest):
|
||||
return False
|
||||
if upgrade_in_flight(d, latest):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_unauthorized(d: dict) -> bool:
|
||||
|
|
@ -595,7 +614,7 @@ def cmd_upgrade(args: argparse.Namespace) -> None:
|
|||
print(f"\ndry-run: pass --yes to actually trigger upgrades.")
|
||||
return
|
||||
|
||||
batch_size = max(1, args.batch_size)
|
||||
batch_size = args.batch_size if args.batch_size > 0 else len(targets)
|
||||
n_batches = (len(targets) + batch_size - 1) // batch_size
|
||||
failed_total = 0
|
||||
|
||||
|
|
@ -603,45 +622,35 @@ def cmd_upgrade(args: argparse.Namespace) -> None:
|
|||
batch = targets[batch_idx * batch_size:(batch_idx + 1) * batch_size]
|
||||
print(f"\n=== batch {batch_idx + 1}/{n_batches}: {len(batch)} device(s) ===")
|
||||
|
||||
# Build the upgrades payload and POST one task per device. UISP's
|
||||
# actual upgrade endpoint is POST /tasks with
|
||||
# {upgrades: [{deviceId, firmwareVersion}], upgradeInMaintenanceWindow}.
|
||||
# /devices/{id}/update only refreshes device data — it does NOT
|
||||
# trigger a firmware upgrade.
|
||||
# POST /tasks one device at a time. Bulk submission (all device
|
||||
# upgrades in a single /tasks payload) is atomic: any one device
|
||||
# whose target firmwareVersion doesn't exactly match a catalog
|
||||
# entry causes the whole call to 422 with "Firmware not found".
|
||||
# Per-device /tasks isolates failures.
|
||||
triggered: list[tuple[str, str, str]] = [] # (id, name, target_version)
|
||||
upgrades = []
|
||||
for d in batch:
|
||||
did = d["identification"]["id"]
|
||||
target_v = latest_firmware_for(fws, d)
|
||||
if not target_v:
|
||||
print(f" skip {did} {disp_name(d)}: no catalog firmware match", file=sys.stderr)
|
||||
print(f" skip {did} {disp_name(d):<30}: no catalog firmware match", file=sys.stderr)
|
||||
failed_total += 1
|
||||
continue
|
||||
upgrades.append({
|
||||
"deviceId": did,
|
||||
"firmwareVersion": target_v,
|
||||
})
|
||||
triggered.append((did, disp_name(d), target_v))
|
||||
|
||||
if upgrades:
|
||||
if upgrade_in_flight(d, target_v):
|
||||
up = d.get("upgrade") or {}
|
||||
print(f" skip {did} {disp_name(d):<30}: already {up.get('status')} → {up.get('firmwareVersion')}")
|
||||
continue
|
||||
try:
|
||||
api("POST", "/tasks", {
|
||||
"upgrades": upgrades,
|
||||
"upgrades": [{"deviceId": did, "firmwareVersion": target_v}],
|
||||
"upgradeInMaintenanceWindow": False,
|
||||
})
|
||||
for did, name, tv in triggered:
|
||||
print(f" trigger OK {did} {name:<30} → {tv}")
|
||||
print(f" trigger OK {did} {disp_name(d):<30} → {target_v}")
|
||||
triggered.append((did, disp_name(d), target_v))
|
||||
except SystemExit:
|
||||
print(f" batch trigger FAILED for {len(upgrades)} device(s)", file=sys.stderr)
|
||||
failed_total += len(upgrades)
|
||||
triggered = []
|
||||
print(f" trigger FAIL {did} {disp_name(d):<30} → {target_v}", file=sys.stderr)
|
||||
failed_total += 1
|
||||
|
||||
if not args.wait:
|
||||
# No completion wait — just space batches out so we don't blast
|
||||
# the whole fleet simultaneously.
|
||||
if batch_idx + 1 < n_batches:
|
||||
print(f" sleeping {args.batch_delay}s before next batch...")
|
||||
time.sleep(args.batch_delay)
|
||||
continue
|
||||
|
||||
# Wait for every device in this batch to land on its target version.
|
||||
|
|
@ -724,14 +733,12 @@ def main() -> None:
|
|||
pu.add_argument("--all", action="store_true", help="upgrade every device with a newer firmware available")
|
||||
pu.add_argument("--yes", action="store_true", help="actually trigger upgrades (default: dry-run)")
|
||||
pu.add_argument("--force", action="store_true", help="upgrade even if already on latest")
|
||||
pu.add_argument("--batch-size", type=int, default=5,
|
||||
help="how many devices to upgrade in parallel per batch (default: 5)")
|
||||
pu.add_argument("--batch-size", type=int, default=0,
|
||||
help="devices per batch; 0 means all in one batch (default). Only useful with --wait.")
|
||||
pu.add_argument("--wait", action="store_true",
|
||||
help="wait for each batch to land on the new firmware before starting the next")
|
||||
pu.add_argument("--wait-timeout", type=int, default=900,
|
||||
help="seconds to wait per batch when --wait is set (default: 900)")
|
||||
pu.add_argument("--batch-delay", type=int, default=30,
|
||||
help="seconds to sleep between batches when --wait is NOT set (default: 30)")
|
||||
pu.set_defaults(func=cmd_upgrade)
|
||||
|
||||
args = p.parse_args()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue